Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace the teleportation logic on the SCRAM implant! #26429

Merged
merged 6 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions Content.Server/Implants/Components/ScramImplantComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,6 @@ public sealed partial class ScramImplantComponent : Component
[DataField, ViewVariables(VVAccess.ReadWrite)]
public float TeleportRadius = 100f;

/// <summary>
/// How many times to check for a valid tile to teleport to
/// </summary>
[DataField, ViewVariables(VVAccess.ReadOnly)]
public int TeleportAttempts = 20;

[DataField, ViewVariables(VVAccess.ReadWrite)]
public SoundSpecifier TeleportSound = new SoundPathSpecifier("/Audio/Effects/teleport_arrival.ogg");
}
110 changes: 82 additions & 28 deletions Content.Server/Implants/SubdermalImplantSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@
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;
using System.Numerics;
using Content.Shared.Movement.Pulling.Components;
using Content.Shared.Movement.Pulling.Systems;
using Robust.Shared.Collections;
using Robust.Shared.Map.Components;

namespace Content.Server.Implants;

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!;
Expand All @@ -37,8 +37,11 @@ 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<PhysicsComponent> _physicsQuery;
private HashSet<Entity<MapGridComponent>> _targetGrids = [];

public override void Initialize()
{
Expand Down Expand Up @@ -107,41 +110,92 @@ private void OnScramImplant(EntityUid uid, SubdermalImplantComponent component,
_pullingSystem.TryStopPull(ent, pull);

var xform = Transform(ent);
var entityCoords = xform.Coordinates.ToMap(EntityManager, _xform);
var targetCoords = SelectRandomTileInRange(xform, implant.TeleportRadius);

// 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++)
if (targetCoords != null)
{
var distance = implant.TeleportRadius * MathF.Sqrt(_random.NextFloat()); // to get an uniform distribution
targetCoords = entityCoords.Offset(_random.NextAngle().ToVec() * distance);

// prefer teleporting to grids
if (!_mapManager.TryFindGridAt(targetCoords, out var gridUid, out var grid))
continue;
_xform.SetCoordinates(ent, targetCoords.Value);
_audio.PlayPvs(implant.TeleportSound, ent);
args.Handled = true;
}
}

// the implant user probably does not want to be in your walls
var valid = true;
foreach (var entity in grid.GetAnchoredEntities(targetCoords))
private EntityCoordinates? SelectRandomTileInRange(TransformComponent userXform, float radius)
{
var userCoords = userXform.Coordinates.ToMap(EntityManager, _xform);
_targetGrids.Clear();
_lookupSystem.GetEntitiesInRange(userCoords, radius, _targetGrids);
Entity<MapGridComponent>? 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<MapGridComponent>(userXform.GridUid, out var gridComp))
{
var userGrid = new Entity<MapGridComponent>(userXform.GridUid.Value, gridComp);
if (_random.Prob(0.5f))
{
if (!_physicsQuery.TryGetComponent(entity, out var body))
continue;
_targetGrids.Remove(userGrid);
targetGrid = userGrid;
}
}

if (body.BodyType != BodyType.Static ||
!body.Hard ||
(body.CollisionLayer & (int) CollisionGroup.Impassable) == 0)
continue;
if (targetGrid == null)
targetGrid = _random.GetRandom().PickAndTake(_targetGrids);

valid = false;
break;
EntityCoordinates? targetCoords = null;

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<Vector2i>();

while (tilesInRange.MoveNext(out var tile))
{
tileList.Add(tile.GridIndices);
}

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 (valid)

if (valid || _targetGrids.Count == 0) // if we don't do the check here then PickAndTake will blow up on an empty set.
break;
}
_xform.SetWorldPosition(ent, targetCoords.Position);
_audio.PlayPvs(implant.TeleportSound, ent);

args.Handled = true;
targetGrid = _random.GetRandom().PickAndTake(_targetGrids);
} while (true);
Comment on lines +189 to +196
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just do while _targetGrids.Count > 0, or just do a regular while loop instead?

Copy link
Contributor Author

@nikthechampiongr nikthechampiongr Apr 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe if I rearranged some stuff in the function. At the time I made it I probably thought about it a bit weird. Do you want me to make a new PR with that?


return targetCoords;
}

private void OnDnaScramblerImplant(EntityUid uid, SubdermalImplantComponent component, UseDnaScramblerImplantEvent args)
Expand Down
Loading