diff --git a/Content.Server/Implants/Components/ScramImplantComponent.cs b/Content.Server/Implants/Components/ScramImplantComponent.cs index f3bbc9e584274e..88c433abfbe9b8 100644 --- a/Content.Server/Implants/Components/ScramImplantComponent.cs +++ b/Content.Server/Implants/Components/ScramImplantComponent.cs @@ -15,6 +15,12 @@ public sealed partial class ScramImplantComponent : Component [DataField, ViewVariables(VVAccess.ReadWrite)] public float TeleportRadius = 100f; + /// + /// How many times to check for a valid tile to teleport to + /// + [DataField, ViewVariables(VVAccess.ReadOnly)] + public int TeleportAttempts = 20; + [DataField, ViewVariables(VVAccess.ReadWrite)] public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg"); } diff --git a/Content.Server/Implants/SubdermalImplantSystem.cs b/Content.Server/Implants/SubdermalImplantSystem.cs index 7c69ec8ea5448d..99efd14e632848 100644 --- a/Content.Server/Implants/SubdermalImplantSystem.cs +++ b/Content.Server/Implants/SubdermalImplantSystem.cs @@ -15,6 +15,7 @@ using Content.Shared.Preferences; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; +using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Random; @@ -31,6 +32,7 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem { [Dependency] private readonly CuffableSystem _cuffable = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!; + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly StoreSystem _store = default!; @@ -39,11 +41,8 @@ public sealed class SubdermalImplantSystem : SharedSubdermalImplantSystem [Dependency] private readonly SharedTransformSystem _xform = default!; [Dependency] private readonly ForensicsSystem _forensicsSystem = default!; [Dependency] private readonly PullingSystem _pullingSystem = default!; - [Dependency] private readonly EntityLookupSystem _lookupSystem = default!; - [Dependency] private readonly SharedMapSystem _mapSystem = default!; private EntityQuery _physicsQuery; - private HashSet> _targetGrids = []; public override void Initialize() { @@ -112,92 +111,41 @@ private void OnScramImplant(EntityUid uid, SubdermalImplantComponent component, _pullingSystem.TryStopPull(ent, pull); var xform = Transform(ent); - var targetCoords = SelectRandomTileInRange(xform, implant.TeleportRadius); + var entityCoords = xform.Coordinates.ToMap(EntityManager, _xform); - if (targetCoords != null) + // try to find a valid position to teleport to, teleport to whatever works if we can't + var targetCoords = new MapCoordinates(); + for (var i = 0; i < implant.TeleportAttempts; i++) { - _xform.SetCoordinates(ent, targetCoords.Value); - _audio.PlayPvs(implant.TeleportSound, ent); - args.Handled = true; - } - } - - private EntityCoordinates? SelectRandomTileInRange(TransformComponent userXform, float radius) - { - var userCoords = userXform.Coordinates.ToMap(EntityManager, _xform); - _targetGrids.Clear(); - _lookupSystem.GetEntitiesInRange(userCoords, radius, _targetGrids); - Entity? targetGrid = null; - - if (_targetGrids.Count == 0) - return null; - - // Give preference to the grid the entity is currently on. - // This does not guarantee that if the probability fails that the owner's grid won't be picked. - // In reality the probability is higher and depends on the number of grids. - if (userXform.GridUid != null && TryComp(userXform.GridUid, out var gridComp)) - { - var userGrid = new Entity(userXform.GridUid.Value, gridComp); - if (_random.Prob(0.5f)) - { - _targetGrids.Remove(userGrid); - targetGrid = userGrid; - } - } - - if (targetGrid == null) - targetGrid = _random.GetRandom().PickAndTake(_targetGrids); - - EntityCoordinates? targetCoords = null; + var distance = implant.TeleportRadius * MathF.Sqrt(_random.NextFloat()); // to get an uniform distribution + targetCoords = entityCoords.Offset(_random.NextAngle().ToVec() * distance); - do - { - var valid = false; - - var range = (float) Math.Sqrt(radius); - var box = Box2.CenteredAround(userCoords.Position, new Vector2(range, range)); - var tilesInRange = _mapSystem.GetTilesEnumerator(targetGrid.Value.Owner, targetGrid.Value.Comp, box, false); - var tileList = new ValueList(); + // prefer teleporting to grids + if (!_mapManager.TryFindGridAt(targetCoords, out var gridUid, out var grid)) + continue; - while (tilesInRange.MoveNext(out var tile)) + // the implant user probably does not want to be in your walls + var valid = true; + foreach (var entity in grid.GetAnchoredEntities(targetCoords)) { - tileList.Add(tile.GridIndices); - } + if (!_physicsQuery.TryGetComponent(entity, out var body)) + continue; - while (tileList.Count != 0) - { - var tile = tileList.RemoveSwap(_random.Next(tileList.Count)); - valid = true; - foreach (var entity in _mapSystem.GetAnchoredEntities(targetGrid.Value.Owner, targetGrid.Value.Comp, - tile)) - { - if (!_physicsQuery.TryGetComponent(entity, out var body)) - continue; - - if (body.BodyType != BodyType.Static || - !body.Hard || - (body.CollisionLayer & (int) CollisionGroup.MobMask) == 0) - continue; - - valid = false; - break; - } - - if (valid) - { - targetCoords = new EntityCoordinates(targetGrid.Value.Owner, - _mapSystem.TileCenterToVector(targetGrid.Value, tile)); - break; - } - } + if (body.BodyType != BodyType.Static || + !body.Hard || + (body.CollisionLayer & (int) CollisionGroup.Impassable) == 0) + continue; - if (valid || _targetGrids.Count == 0) // if we don't do the check here then PickAndTake will blow up on an empty set. + valid = false; break; + } + if (valid) + break; + } + _xform.SetWorldPosition(ent, targetCoords.Position); + _audio.PlayPvs(implant.TeleportSound, ent); - targetGrid = _random.GetRandom().PickAndTake(_targetGrids); - } while (true); - - return targetCoords; + args.Handled = true; } private void OnDnaScramblerImplant(EntityUid uid, SubdermalImplantComponent component, UseDnaScramblerImplantEvent args)