diff --git a/Content.Client/_RMC14/Ladder/LadderSystem.cs b/Content.Client/_RMC14/Ladder/LadderSystem.cs new file mode 100644 index 00000000000..9cdd0517fb9 --- /dev/null +++ b/Content.Client/_RMC14/Ladder/LadderSystem.cs @@ -0,0 +1,5 @@ +using Content.Shared._RMC14.Ladder; + +namespace Content.Client._RMC14.Ladder; + +public sealed class LadderSystem : SharedLadderSystem; diff --git a/Content.Client/_RMC14/Teleporter/RMCTeleporterSystem.cs b/Content.Client/_RMC14/Teleporter/RMCTeleporterSystem.cs new file mode 100644 index 00000000000..95965422774 --- /dev/null +++ b/Content.Client/_RMC14/Teleporter/RMCTeleporterSystem.cs @@ -0,0 +1,20 @@ +using Content.Shared._RMC14.Teleporter; +using Robust.Client.Graphics; + +namespace Content.Client._RMC14.Teleporter; + +public sealed class RMCTeleporterSystem : SharedRMCTeleporterSystem +{ + [Dependency] private readonly IOverlayManager _overlay = default!; + + public override void Initialize() + { + base.Initialize(); + _overlay.AddOverlay(new RMCTeleporterViewerOverlay()); + } + + public override void Shutdown() + { + _overlay.RemoveOverlay(); + } +} diff --git a/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs b/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs new file mode 100644 index 00000000000..983e24ef3ad --- /dev/null +++ b/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs @@ -0,0 +1,100 @@ +using System.Numerics; +using System.Runtime.InteropServices; +using Content.Shared._RMC14.Teleporter; +using Content.Shared.Physics; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Containers; + +namespace Content.Client._RMC14.Teleporter; + +public sealed class RMCTeleporterViewerOverlay : Overlay +{ + [Dependency] private readonly IEntityManager _entity = default!; + [Dependency] private readonly IPlayerManager _player = default!; + [Dependency] private readonly IOverlayManager _overlay = default!; + + private readonly SharedContainerSystem _container; + private readonly EntityLookupSystem _entityLookup; + private readonly Robust.Client.Physics.PhysicsSystem _physics; + private readonly SharedRMCTeleporterSystem _teleporter; + private readonly SharedTransformSystem _transform; + + private readonly EntityQuery _spriteQuery; + private readonly EntityQuery _teleporterViewerQuery; + private readonly EntityQuery _transformQuery; + + private readonly List<(Entity Ent, Vector2 Position, Angle Rotation)> _toDraw = new(); + + public RMCTeleporterViewerOverlay() + { + IoCManager.InjectDependencies(this); + + _container = _entity.System(); + _entityLookup = _entity.System(); + _physics = _entity.System(); + _teleporter = _entity.System(); + _transform = _entity.System(); + + _spriteQuery = _entity.GetEntityQuery(); + _teleporterViewerQuery = _entity.GetEntityQuery(); + _transformQuery = _entity.GetEntityQuery(); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (_player.LocalEntity is not { } player) + return; + + var handle = args.WorldHandle; + var eye = args.Viewport.Eye; + var eyeRot = eye?.Rotation ?? default; + foreach (var selfContact in _physics.GetEntitiesIntersectingBody(player, (int) CollisionGroup.MobLayer)) + { + if (!_teleporterViewerQuery.TryComp(selfContact, out var viewer)) + continue; + + var viewerPosition = _transform.GetWorldPosition(selfContact); + foreach (var otherViewer in _teleporter.GetMatchingTeleporterViewers((selfContact, viewer))) + { + var otherViewerPosition = _transform.GetMapCoordinates(otherViewer); + var viewerPositionDiff = otherViewerPosition.Position - viewerPosition; + var otherViewerAABB = _physics.GetWorldAABB(otherViewer); + + _toDraw.Clear(); + foreach (var viewerContact in _entityLookup.GetEntitiesIntersecting(otherViewerPosition.MapId, otherViewerAABB, LookupFlags.Uncontained)) + { + if (!_spriteQuery.TryComp(viewerContact, out var viewerContactSprite) || + !viewerContactSprite.Visible || + !_transformQuery.TryComp(viewerContact, out var viewerContactTransform)) + { + continue; + } + + if (_container.IsEntityInContainer(viewerContact)) + continue; + + if (viewerContactTransform.Anchored) + continue; + + // technically you could use try delta here for the vast majority of cases + // since they share a parent entity uid but i don't got time to do that + // good luck future coder + var (position, rotation) = _transform.GetWorldPositionRotation(viewerContactTransform); + _toDraw.Add(((viewerContact, viewerContactSprite), position, rotation)); + } + + _toDraw.Sort((a, b) => a.Ent.Comp.DrawDepth.CompareTo(b.Ent.Comp.DrawDepth)); + + foreach (ref var draw in CollectionsMarshal.AsSpan(_toDraw)) + { + draw.Position -= viewerPositionDiff; + draw.Ent.Comp.Render(handle, eyeRot, draw.Rotation, position: draw.Position); + } + } + } + + handle.SetTransform(Matrix3x2.Identity); + } +} diff --git a/Content.Server/_RMC14/Ladder/LadderSystem.cs b/Content.Server/_RMC14/Ladder/LadderSystem.cs new file mode 100644 index 00000000000..8eabdb18549 --- /dev/null +++ b/Content.Server/_RMC14/Ladder/LadderSystem.cs @@ -0,0 +1,87 @@ +using Content.Shared._RMC14.Ladder; +using Robust.Server.GameObjects; +using Robust.Shared.Player; + +namespace Content.Server._RMC14.Ladder; + +public sealed class LadderSystem : SharedLadderSystem +{ + [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly ViewSubscriberSystem _viewSubscriber = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnWatchingRemove); + SubscribeLocalEvent(OnWatchingRemove); + } + + private void OnWatchingRemove(Entity ent, ref T args) + { + RemoveWatcher(ent); + } + + protected override void Watch(Entity watcher, Entity toWatch) + { + base.Watch(watcher, toWatch); + + if (!Resolve(toWatch, ref toWatch.Comp, false)) + return; + + if (watcher.Owner == toWatch.Owner) + return; + + if (!Resolve(watcher, ref watcher.Comp1, ref watcher.Comp2) || + !Resolve(toWatch, ref toWatch.Comp)) + { + return; + } + + _eye.SetTarget(watcher, toWatch, watcher); + _viewSubscriber.AddViewSubscriber(toWatch, watcher.Comp1.PlayerSession); + + RemoveWatcher(watcher); + EnsureComp(watcher).Watching = toWatch; + toWatch.Comp.Watching.Add(watcher); + } + + protected override void Unwatch(Entity watcher, ICommonSession player) + { + if (!Resolve(watcher, ref watcher.Comp)) + return; + + var oldTarget = watcher.Comp.Target; + + base.Unwatch(watcher, player); + + if (oldTarget != null && oldTarget != watcher.Owner) + _viewSubscriber.RemoveViewSubscriber(oldTarget.Value, player); + + RemoveWatcher(watcher); + } + + private void RemoveWatcher(EntityUid toRemove) + { + if (!TryComp(toRemove, out LadderWatchingComponent? watching)) + return; + + if (TryComp(watching.Watching, out LadderComponent? watched)) + watched.Watching.Remove(toRemove); + + watching.Watching = null; + RemCompDeferred(toRemove); + } + + protected override void AddViewer(Entity ent, ICommonSession player) + { + base.AddViewer(ent, player); + _viewSubscriber.AddViewSubscriber(ent, player); + } + + protected override void RemoveViewer(Entity ent, ICommonSession player) + { + base.RemoveViewer(ent, player); + _viewSubscriber.RemoveViewSubscriber(ent, player); + } +} diff --git a/Content.Server/_RMC14/Teleporter/RMCTeleporterSystem.cs b/Content.Server/_RMC14/Teleporter/RMCTeleporterSystem.cs new file mode 100644 index 00000000000..98de335e3b0 --- /dev/null +++ b/Content.Server/_RMC14/Teleporter/RMCTeleporterSystem.cs @@ -0,0 +1,22 @@ +using Content.Shared._RMC14.Teleporter; +using Robust.Server.GameObjects; +using Robust.Shared.Player; + +namespace Content.Server._RMC14.Teleporter; + +public sealed class RMCTeleporterSystem : SharedRMCTeleporterSystem +{ + [Dependency] private readonly ViewSubscriberSystem _viewSubscriber = default!; + + protected override void AddViewer(Entity viewer, ICommonSession player) + { + base.AddViewer(viewer, player); + _viewSubscriber.AddViewSubscriber(viewer, player); + } + + protected override void RemoveViewer(Entity viewer, ICommonSession player) + { + base.RemoveViewer(viewer, player); + _viewSubscriber.RemoveViewSubscriber(viewer, player); + } +} diff --git a/Content.Shared/_RMC14/Ladder/LadderComponent.cs b/Content.Shared/_RMC14/Ladder/LadderComponent.cs new file mode 100644 index 00000000000..5b106d8a65c --- /dev/null +++ b/Content.Shared/_RMC14/Ladder/LadderComponent.cs @@ -0,0 +1,33 @@ +using Content.Shared.Interaction; +using Robust.Shared.GameStates; + +namespace Content.Shared._RMC14.Ladder; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause] +[Access(typeof(SharedLadderSystem))] +public sealed partial class LadderComponent : Component +{ + [DataField, AutoNetworkedField] + public string? Id; + + [DataField, AutoNetworkedField] + public EntityUid? Other; + + [DataField, AutoNetworkedField] + public TimeSpan Delay = TimeSpan.FromSeconds(2); + + [DataField, AutoNetworkedField] + public float Range = SharedInteractionSystem.InteractionRange + 0.1f; + + [DataField, AutoNetworkedField] + public EntityUid? LastDoAfterEnt; + + [DataField, AutoNetworkedField] + public ushort? LastDoAfterId; + + [DataField, AutoNetworkedField, AutoPausedField] + public TimeSpan LastDoAfterTime; + + [DataField, AutoNetworkedField] + public HashSet Watching = new(); +} diff --git a/Content.Shared/_RMC14/Ladder/LadderDoAfterEvent.cs b/Content.Shared/_RMC14/Ladder/LadderDoAfterEvent.cs new file mode 100644 index 00000000000..844aa32f932 --- /dev/null +++ b/Content.Shared/_RMC14/Ladder/LadderDoAfterEvent.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared._RMC14.Ladder; + +[Serializable, NetSerializable] +public sealed partial class LadderDoAfterEvent : SimpleDoAfterEvent; diff --git a/Content.Shared/_RMC14/Ladder/LadderWatchingComponent.cs b/Content.Shared/_RMC14/Ladder/LadderWatchingComponent.cs new file mode 100644 index 00000000000..02c22509f4d --- /dev/null +++ b/Content.Shared/_RMC14/Ladder/LadderWatchingComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._RMC14.Ladder; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedLadderSystem))] +public sealed partial class LadderWatchingComponent : Component +{ + [DataField, AutoNetworkedField] + public EntityUid? Watching; +} diff --git a/Content.Shared/_RMC14/Ladder/SharedLadderSystem.cs b/Content.Shared/_RMC14/Ladder/SharedLadderSystem.cs new file mode 100644 index 00000000000..a9d54152d82 --- /dev/null +++ b/Content.Shared/_RMC14/Ladder/SharedLadderSystem.cs @@ -0,0 +1,334 @@ +using System.Linq; +using Content.Shared._RMC14.Teleporter; +using Content.Shared.Coordinates; +using Content.Shared.DoAfter; +using Content.Shared.DragDrop; +using Content.Shared.GameTicking; +using Content.Shared.Interaction; +using Content.Shared.Movement.Events; +using Content.Shared.Popups; +using Content.Shared.Verbs; +using Robust.Shared.Map; +using Robust.Shared.Network; +using Robust.Shared.Player; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared._RMC14.Ladder; + +public abstract class SharedLadderSystem : EntitySystem +{ + [Dependency] private readonly SharedDoAfterSystem _doAfter = default!; + [Dependency] private readonly SharedEyeSystem _eye = default!; + [Dependency] private readonly SharedInteractionSystem _interaction = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly ISharedPlayerManager _player = default!; + [Dependency] private readonly SharedPopupSystem _popup = default!; + [Dependency] private readonly SharedRMCTeleporterSystem _rmcTeleporter = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private readonly HashSet> _toUpdate = new(); + private readonly Dictionary>> _toUpdateIds = new(); + + private EntityQuery _actorQuery; + private EntityQuery _ladderQuery; + + public override void Initialize() + { + _actorQuery = GetEntityQuery(); + _ladderQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnRoundRestartCleanup); + + SubscribeLocalEvent(OnLadderMapInit); + SubscribeLocalEvent(OnLadderRemove); + SubscribeLocalEvent(OnLadderRemove); + SubscribeLocalEvent(OnLadderActivateInWorld); + SubscribeLocalEvent>(OnLadderDoAfterAttempt); + SubscribeLocalEvent(OnLadderDoAfter); + SubscribeLocalEvent>(OnLadderGetAltVerbs); + SubscribeLocalEvent(OnLadderCanDropDragged); + SubscribeLocalEvent(OnLadderCanDrag); + SubscribeLocalEvent(OnLadderDragDropDragged); + + SubscribeLocalEvent(OnWatchingMoveInput); + } + + private void OnRoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _toUpdate.Clear(); + _toUpdateIds.Clear(); + } + + private void OnLadderMapInit(Entity ent, ref MapInitEvent args) + { + _toUpdate.Add(ent); + } + + private void OnLadderRemove(Entity ent, ref T args) + { + foreach (var watching in ent.Comp.Watching) + { + if (TerminatingOrDeleted(watching)) + continue; + + RemCompDeferred(watching); + } + + if (!TerminatingOrDeleted(ent.Comp.Other) && + _ladderQuery.TryComp(ent.Comp.Other, out var otherLadder)) + { + otherLadder.Other = null; + Dirty(ent.Comp.Other.Value, otherLadder); + } + + ent.Comp.Other = null; + } + + private void OnLadderActivateInWorld(Entity ent, ref ActivateInWorldEvent args) + { + var user = args.User; + if (ent.Comp.Other == null) + { + var msg = Loc.GetString("rmc-ladder-leads-nowhere"); + _popup.PopupClient(msg, ent, user, PopupType.SmallCaution); + return; + } + + var time = _timing.CurTime; + if (ent.Comp.LastDoAfterEnt is { } lastEnt && + ent.Comp.LastDoAfterId is { } lastId && + time - ent.Comp.LastDoAfterTime < ent.Comp.Delay * 5 && + _doAfter.GetStatus(new DoAfterId(lastEnt, lastId)) == DoAfterStatus.Running) + { + if (ent.Comp.LastDoAfterEnt != user) + { + var msg = Loc.GetString("rmc-ladder-someone-else-climbing"); + _popup.PopupClient(msg, ent, user, PopupType.SmallCaution); + } + + return; + } + + var ev = new LadderDoAfterEvent(); + var doAfter = new DoAfterArgs(EntityManager, user, ent.Comp.Delay, ev, ent, ent, ent) + { + AttemptFrequency = AttemptFrequency.EveryTick, + }; + + if (!_doAfter.TryStartDoAfter(doAfter, out var doAfterId)) + return; + + ent.Comp.LastDoAfterEnt = doAfterId.Value.Uid; + ent.Comp.LastDoAfterId = doAfterId.Value.Index; + ent.Comp.LastDoAfterTime = time; + Dirty(ent); + + if (ent.Comp.Delay > TimeSpan.Zero) + { + var selfMessage = Loc.GetString("rmc-ladder-start-climbing-self"); + var othersMessage = Loc.GetString("rmc-ladder-start-climbing-others", ("user", user)); + _popup.PopupPredicted(selfMessage, othersMessage, user, user); + } + + if (_actorQuery.TryComp(user, out var actor)) + AddViewer(ent, actor.PlayerSession); + } + + private void OnLadderDoAfterAttempt(Entity ent, ref DoAfterAttemptEvent args) + { + if (args.Cancelled) + return; + + var user = args.DoAfter.Args.User; + var target = ent.Owner.ToCoordinates(); + if (user.ToCoordinates().TryDistance(EntityManager, _transform, target, out var distance) && + distance > ent.Comp.Range) + { + args.Cancel(); + } + } + + private void OnLadderDoAfter(Entity ent, ref LadderDoAfterEvent args) + { + var user = args.User; + if (_net.IsClient && user != _player.LocalEntity) + return; + + if (_actorQuery.TryComp(user, out var actor)) + RemoveViewer(ent, actor.PlayerSession); + + if (args.Cancelled || args.Handled) + return; + + args.Handled = true; + + if (ent.Comp.Other is not { } other || TerminatingOrDeleted(ent.Comp.Other)) + return; + + var coordinates = _transform.GetMapCoordinates(other); + if (coordinates.MapId == MapId.Nullspace) + return; + + _transform.SetMapCoordinates(user, coordinates); + + var selfMessage = Loc.GetString("rmc-ladder-finish-climbing-self"); + var othersMessage = Loc.GetString("rmc-ladder-finish-climbing-others", ("user", user)); + _popup.PopupPredicted(selfMessage, othersMessage, user, user); + + ent.Comp.LastDoAfterEnt = null; + ent.Comp.LastDoAfterId = null; + Dirty(ent); + + _rmcTeleporter.HandlePulling(user, coordinates); + } + + private void OnLadderGetAltVerbs(Entity ent, ref GetVerbsEvent args) + { + if (ent.Comp.Other is not { } other) + return; + + var user = args.User; + if (!CanWatchPopup(ent, user)) + return; + + args.Verbs.Add(new AlternativeVerb + { + Priority = 100, + Act = () => + { + if (!CanWatchPopup(ent, user)) + return; + + Watch(user, other); + }, + Text = Loc.GetString("rmc-ladder-look-through"), + }); + } + + private void OnLadderCanDropDragged(Entity ent, ref CanDropDraggedEvent args) + { + if (args.User != args.Target) + return; + + args.Handled = true; + args.CanDrop = true; + } + + private void OnLadderCanDrag(Entity ent, ref CanDragEvent args) + { + args.Handled = true; + } + + private void OnLadderDragDropDragged(Entity ent, ref DragDropDraggedEvent args) + { + var user = args.User; + if (ent.Comp.Other is not { } other || + user != args.Target) + { + return; + } + + if (!CanWatchPopup(ent, user)) + return; + + args.Handled = true; + Watch(user, other); + } + + private void OnWatchingMoveInput(Entity ent, ref MoveInputEvent args) + { + if (!args.HasDirectionalMovement) + return; + + if (_net.IsClient && _player.LocalEntity == ent.Owner && _player.LocalSession != null) + Unwatch(ent.Owner, _player.LocalSession); + else if (TryComp(ent, out ActorComponent? actor)) + Unwatch(ent.Owner, actor.PlayerSession); + } + + protected virtual void AddViewer(Entity ent, ICommonSession player) + { + } + + protected virtual void RemoveViewer(Entity ent, ICommonSession player) + { + } + + protected virtual void Watch(Entity watcher, Entity toWatch) + { + } + + protected virtual void Unwatch(Entity watcher, ICommonSession player) + { + if (!Resolve(watcher, ref watcher.Comp)) + return; + + _eye.SetTarget(watcher, null); + } + + protected bool CanWatchPopup(Entity ladder, EntityUid user) + { + if (!_interaction.InRangeUnobstructed(user, ladder.Owner, popup: true)) + return false; + + return true; + } + + public override void Update(float frameTime) + { + if (_toUpdate.Count == 0) + return; + + if (_net.IsClient) + { + _toUpdateIds.Clear(); + _toUpdate.Clear(); + return; + } + + _toUpdateIds.Clear(); + foreach (var entity in _toUpdate) + { + if (entity.Comp.Id is not { } id) + continue; + + var ids = _toUpdateIds.GetOrNew(id); + if (ids.Count > 2) + { + var idsString = string.Join(",", ids.Select(e => ToPrettyString(e))); + Log.Error($"Found more than 2 ladders with id {id}, current ladder: {ToPrettyString(entity)}, previous ladders: {idsString}"); + } + + ids.Add(entity); + } + + _toUpdate.Clear(); + + var ladders = EntityQueryEnumerator(); + while (ladders.MoveNext(out var uid, out var ladder)) + { + if (ladder.Id == null) + continue; + + if (!_toUpdateIds.TryGetValue(ladder.Id, out var ids)) + continue; + + if (ids.FirstOrNull(e => e.Owner != uid) is not { } toUpdate) + continue; + + if (toUpdate.Owner == uid) + continue; + + if (ladder.Other is { } old && old != toUpdate.Owner) + Log.Error($"Found {ToPrettyString(toUpdate)} with duplicate ID {toUpdate.Comp.Id}, previous ladder: {ToPrettyString(old)}"); + + ladder.Other = toUpdate; + Dirty(uid, ladder); + + toUpdate.Comp.Other = uid; + Dirty(toUpdate); + } + } +} diff --git a/Content.Shared/_RMC14/Teleporter/RMCTeleporterComponent.cs b/Content.Shared/_RMC14/Teleporter/RMCTeleporterComponent.cs new file mode 100644 index 00000000000..cbc6674d549 --- /dev/null +++ b/Content.Shared/_RMC14/Teleporter/RMCTeleporterComponent.cs @@ -0,0 +1,12 @@ +using System.Numerics; +using Robust.Shared.GameStates; + +namespace Content.Shared._RMC14.Teleporter; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedRMCTeleporterSystem))] +public sealed partial class RMCTeleporterComponent : Component +{ + [DataField, AutoNetworkedField] + public Vector2 Adjust; +} diff --git a/Content.Shared/_RMC14/Teleporter/RMCTeleporterViewerComponent.cs b/Content.Shared/_RMC14/Teleporter/RMCTeleporterViewerComponent.cs new file mode 100644 index 00000000000..57db342e1c2 --- /dev/null +++ b/Content.Shared/_RMC14/Teleporter/RMCTeleporterViewerComponent.cs @@ -0,0 +1,11 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._RMC14.Teleporter; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +[Access(typeof(SharedRMCTeleporterSystem))] +public sealed partial class RMCTeleporterViewerComponent : Component +{ + [DataField, AutoNetworkedField] + public string Id = string.Empty; +} diff --git a/Content.Shared/_RMC14/Teleporter/SharedRMCTeleporterSystem.cs b/Content.Shared/_RMC14/Teleporter/SharedRMCTeleporterSystem.cs new file mode 100644 index 00000000000..c62e95813b6 --- /dev/null +++ b/Content.Shared/_RMC14/Teleporter/SharedRMCTeleporterSystem.cs @@ -0,0 +1,127 @@ +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Movement.Pulling.Systems; +using Robust.Shared.Map; +using Robust.Shared.Map.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Player; + +namespace Content.Shared._RMC14.Teleporter; + +public abstract class SharedRMCTeleporterSystem : EntitySystem +{ + [Dependency] private readonly PullingSystem _pulling = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + + private EntityQuery _actorQuery; + private EntityQuery _mapGridQuery; + + public override void Initialize() + { + _actorQuery = GetEntityQuery(); + _mapGridQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnTeleportStartCollide); + SubscribeLocalEvent(OnViewerStartCollide); + SubscribeLocalEvent(OnViewerEndCollide); + } + + private void OnTeleportStartCollide(Entity ent, ref StartCollideEvent args) + { + var other = args.OtherEntity; + if (_mapGridQuery.HasComp(other)) + { + return; + } + + var otherCoords = _transform.GetMapCoordinates(other); + var teleporter = _transform.GetMapCoordinates(ent); + if (otherCoords.MapId != teleporter.MapId) + return; + + var diff = otherCoords.Position - teleporter.Position; + if (diff.Length() > 10) + return; + + teleporter = teleporter.Offset(diff); + teleporter = teleporter.Offset(ent.Comp.Adjust); + + HandlePulling(other, teleporter); + } + + private void OnViewerStartCollide(Entity ent, ref StartCollideEvent args) + { + if (!_actorQuery.TryComp(args.OtherEntity, out var actor)) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var viewer)) + { + if (uid == ent.Owner || viewer.Id != ent.Comp.Id) + continue; + + AddViewer((uid, viewer), actor.PlayerSession); + } + } + + private void OnViewerEndCollide(Entity ent, ref EndCollideEvent args) + { + if (!_actorQuery.TryComp(args.OtherEntity, out var actor)) + return; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var viewer)) + { + if (uid == ent.Owner || viewer.Id != ent.Comp.Id) + continue; + + RemoveViewer((uid, viewer), actor.PlayerSession); + } + } + + protected virtual void AddViewer(Entity viewer, ICommonSession player) + { + } + + protected virtual void RemoveViewer(Entity viewer, ICommonSession player) + { + } + + public IEnumerable> GetMatchingTeleporterViewers(Entity viewer) + { + var viewers = EntityQueryEnumerator(); + while (viewers.MoveNext(out var otherUid, out var otherViewer)) + { + if (viewer.Owner != otherUid && viewer.Comp.Id == otherViewer.Id) + yield return (otherUid, otherViewer); + } + } + + public void HandlePulling(EntityUid user, MapCoordinates teleport) + { + if (TryComp(user, out PullableComponent? otherPullable) && + otherPullable.Puller != null) + { + _pulling.TryStopPull(user, otherPullable, otherPullable.Puller.Value); + } + + if (TryComp(user, out PullerComponent? puller) && + TryComp(puller.Pulling, out PullableComponent? pullable)) + { + if (TryComp(puller.Pulling, out PullerComponent? otherPullingPuller) && + TryComp(otherPullingPuller.Pulling, out PullableComponent? otherPullingPullable)) + { + _pulling.TryStopPull(otherPullingPuller.Pulling.Value, otherPullingPullable, puller.Pulling); + } + + var pulling = puller.Pulling.Value; + _pulling.TryStopPull(pulling, pullable, user); + _transform.SetMapCoordinates(user, teleport); + _transform.SetMapCoordinates(pulling, teleport); + _pulling.TryStartPull(user, pulling); + } + else + { + _transform.SetMapCoordinates(user, teleport); + } + } +} diff --git a/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml b/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml new file mode 100644 index 00000000000..8e8f74f1bc2 --- /dev/null +++ b/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml @@ -0,0 +1,38 @@ +- type: entity + id: RMCLadder + name: ladder + description: A sturdy metal ladder. + placement: + mode: SnapgridCenter + components: + - type: Transform + anchored: true + - type: Physics + bodyType: Static + canCollide: false + - type: Clickable + - type: InteractionOutline + - type: Sprite + sprite: _RMC14/Structures/ladder.rsi + drawdepth: WallTops + state: ladderdown + - type: PlacementReplacement + key: ladders + - type: Ladder + id: PutNewId + +- type: entity + parent: RMCLadder + id: RMCLadderUp + suffix: Up + components: + - type: Sprite + state: ladderup + +- type: entity + parent: RMCLadder + id: RMCLadderThrough + suffix: Through + components: + - type: Sprite + state: ladder11 diff --git a/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder00.png b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder00.png new file mode 100644 index 00000000000..f4eb1f8f522 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder00.png differ diff --git a/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder01.png b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder01.png new file mode 100644 index 00000000000..229348a0f7f Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder01.png differ diff --git a/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder10.png b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder10.png new file mode 100644 index 00000000000..3479c0e8911 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder10.png differ diff --git a/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder11.png b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder11.png new file mode 100644 index 00000000000..88fcc644ab7 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladder11.png differ diff --git a/Resources/Textures/_RMC14/Structures/ladder.rsi/ladderdown.png b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladderdown.png new file mode 100644 index 00000000000..229348a0f7f Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladderdown.png differ diff --git a/Resources/Textures/_RMC14/Structures/ladder.rsi/ladderup.png b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladderup.png new file mode 100644 index 00000000000..3479c0e8911 Binary files /dev/null and b/Resources/Textures/_RMC14/Structures/ladder.rsi/ladderup.png differ diff --git a/Resources/Textures/_RMC14/Structures/ladder.rsi/meta.json b/Resources/Textures/_RMC14/Structures/ladder.rsi/meta.json new file mode 100644 index 00000000000..355a6462591 --- /dev/null +++ b/Resources/Textures/_RMC14/Structures/ladder.rsi/meta.json @@ -0,0 +1,29 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/7cb618c69b75873f3ce893022fe08d1233b3152d/icons/obj/structures/structures.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "ladder00" + }, + { + "name": "ladder10" + }, + { + "name": "ladder01" + }, + { + "name": "ladder11" + }, + { + "name": "ladderdown" + }, + { + "name": "ladderup" + } + ] +} \ No newline at end of file