using Content.Shared.Examine; using Content.Shared.Interaction; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Popups; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; namespace Content.Shared.Teleportation.Systems; /// /// Handles by checking line of sight and whether the performer /// would collide with anything at the destination before moving them. /// public sealed partial class TeleportActionSystem : EntitySystem { [Dependency] private EntityLookupSystem _lookup = default!; [Dependency] private SharedPhysicsSystem _physics = default!; [Dependency] private ExamineSystemShared _examine = default!; [Dependency] private SharedMapSystem _map = default!; [Dependency] private SharedPopupSystem _popup = default!; [Dependency] private PullingSystem _pulling = default!; [Dependency] private SharedTransformSystem _transform = default!; private readonly HashSet> _intersecting = new(); [SubscribeLocalEvent] private void OnTeleportAction(TeleportActionEvent args) { if (args.Handled) return; args.Handled = TryTeleport( args.Performer, args.Target, args.StopBeingPulled, args.StopPulling); } /// /// Attempts to teleport user entity to target coordinates. Stops pulling or being pulled if requested. /// /// Entity to teleport. /// Target coordinates. /// Should stop being used in pulling interaction? /// Should stop pulling interaction on any entity he is currently pulling? /// public bool TryTeleport( EntityUid user, EntityCoordinates target, bool stopBeingPulled = false, bool stopPulling = false) { if (!target.IsValid(EntityManager)) return false; var xform = Transform(user); var targetXform = Transform(target.EntityId); if (!_examine.InRangeUnOccluded(user, target, SharedInteractionSystem.MaxRaycastRange)) { _popup.PopupEntity(Loc.GetString("teleport-action-popup-cant-see"), user, user); return false; } var targetEntPosRot = _transform.GetWorldPositionRotation(targetXform); var targetRotated = targetEntPosRot.WorldRotation.RotateVec(target.Position); var targetMapCoordinates = new MapCoordinates(targetEntPosRot.WorldPosition + targetRotated, targetXform.MapID); if (IsDestinationBlocked(user, targetMapCoordinates, targetEntPosRot.WorldRotation)) { _popup.PopupEntity(Loc.GetString("teleport-action-popup-blocked"), user, user); return false; } if (stopBeingPulled && TryComp(user, out var pullable) && _pulling.IsPulled(user, pullable)) { _pulling.TryStopPull(user, pullable); } if (stopPulling && TryComp(user, out var puller) && TryComp(puller.Pulling, out var pulled)) { _pulling.TryStopPull(puller.Pulling.Value, pulled); } var destination = _map.TryFindGridAt(targetMapCoordinates, out var grid, out _) ? _map.MapToGrid(grid, targetMapCoordinates) : _transform.ToCoordinates(targetMapCoordinates); _transform.SetCoordinates(user, xform, destination); return true; } /// /// Checks whether the entity would collide with another hard, collidable entity at the target coordinates. /// /// The entity being checked. /// The map coordinates to check. /// The rotation of the entity at the target coordinates. /// The entity's fixtures component. /// The entity's physics component. /// Whether the destination is blocked. public bool IsDestinationBlocked( EntityUid user, MapCoordinates target, Angle rotation, FixturesComponent? fixtures = null, PhysicsComponent? physics = null ) { if (!Resolve(user, ref fixtures, ref physics, false) || !physics.CanCollide || !physics.Hard) { return false; } var destinationTransform = new Transform(target.Position, rotation); foreach (var fixture in fixtures.Fixtures.Values) { if (!fixture.Hard) continue; _intersecting.Clear(); _lookup.GetEntitiesIntersecting( target.MapId, fixture.Shape, destinationTransform, _intersecting, LookupFlags.Dynamic | LookupFlags.Static ); foreach (var other in _intersecting) { if (other.Owner == user) continue; if (_physics.IsCurrentlyHardCollidable((other.Owner, null, other.Comp), (user, fixtures, physics))) { return true; } } } return false; } }