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)