From 3734a826675b9368436749fecf1641ce0ba34dd0 Mon Sep 17 00:00:00 2001 From: DragonDidlo <111651007+DragonDidlo@users.noreply.github.com> Date: Sat, 8 Nov 2025 22:15:19 +0300 Subject: [PATCH 1/3] blin --- Content.Client/_RMC14/Ladder/LadderSystem.cs | 5 + .../_RMC14/Teleporter/RMCTeleporterSystem.cs | 20 ++ .../Teleporter/RMCTeleporterViewerOverlay.cs | 103 ++++++ Content.Server/_RMC14/Ladder/LadderSystem.cs | 87 +++++ .../_RMC14/Teleporter/RMCTeleporterSystem.cs | 22 ++ .../_RMC14/Ladder/LadderComponent.cs | 33 ++ .../_RMC14/Ladder/LadderDoAfterEvent.cs | 7 + .../_RMC14/Ladder/LadderWatchingComponent.cs | 11 + .../_RMC14/Ladder/SharedLadderSystem.cs | 334 ++++++++++++++++++ .../Teleporter/RMCTeleporterComponent.cs | 12 + .../RMCTeleporterViewerComponent.cs | 11 + .../Teleporter/SharedRMCTeleporterSystem.cs | 127 +++++++ .../Prototypes/_RMC14/Entities/rmc_ladder.yml | 37 ++ .../_RMC14/Structures/ladder.rsi/ladder00.png | Bin 0 -> 539 bytes .../_RMC14/Structures/ladder.rsi/ladder01.png | Bin 0 -> 588 bytes .../_RMC14/Structures/ladder.rsi/ladder10.png | Bin 0 -> 371 bytes .../_RMC14/Structures/ladder.rsi/ladder11.png | Bin 0 -> 539 bytes .../Structures/ladder.rsi/ladderdown.png | Bin 0 -> 588 bytes .../_RMC14/Structures/ladder.rsi/ladderup.png | Bin 0 -> 371 bytes .../_RMC14/Structures/ladder.rsi/meta.json | 29 ++ 20 files changed, 838 insertions(+) create mode 100644 Content.Client/_RMC14/Ladder/LadderSystem.cs create mode 100644 Content.Client/_RMC14/Teleporter/RMCTeleporterSystem.cs create mode 100644 Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs create mode 100644 Content.Server/_RMC14/Ladder/LadderSystem.cs create mode 100644 Content.Server/_RMC14/Teleporter/RMCTeleporterSystem.cs create mode 100644 Content.Shared/_RMC14/Ladder/LadderComponent.cs create mode 100644 Content.Shared/_RMC14/Ladder/LadderDoAfterEvent.cs create mode 100644 Content.Shared/_RMC14/Ladder/LadderWatchingComponent.cs create mode 100644 Content.Shared/_RMC14/Ladder/SharedLadderSystem.cs create mode 100644 Content.Shared/_RMC14/Teleporter/RMCTeleporterComponent.cs create mode 100644 Content.Shared/_RMC14/Teleporter/RMCTeleporterViewerComponent.cs create mode 100644 Content.Shared/_RMC14/Teleporter/SharedRMCTeleporterSystem.cs create mode 100644 Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml create mode 100644 Resources/Textures/_RMC14/Structures/ladder.rsi/ladder00.png create mode 100644 Resources/Textures/_RMC14/Structures/ladder.rsi/ladder01.png create mode 100644 Resources/Textures/_RMC14/Structures/ladder.rsi/ladder10.png create mode 100644 Resources/Textures/_RMC14/Structures/ladder.rsi/ladder11.png create mode 100644 Resources/Textures/_RMC14/Structures/ladder.rsi/ladderdown.png create mode 100644 Resources/Textures/_RMC14/Structures/ladder.rsi/ladderup.png create mode 100644 Resources/Textures/_RMC14/Structures/ladder.rsi/meta.json 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..1ece09cee43 --- /dev/null +++ b/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs @@ -0,0 +1,103 @@ +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.Physics; +using Robust.Client.Player; +using Robust.Shared.Containers; +using Robust.Shared.Enums; +using static Robust.Shared.GameObjects.LookupFlags; + +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 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, 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..13b90ef3bc1 --- /dev/null +++ b/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml @@ -0,0 +1,37 @@ +- 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 + +- 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 0000000000000000000000000000000000000000..f4eb1f8f5221c9dca85bac80f733fdcc2688d29d GIT binary patch literal 539 zcmV+$0_6RPP)YO(+Yz|m;v0hIz64hAq^EVzC3`aN@ZMXvMt z0`hqcX0wlY0IbP`B%sfJbGIO!N`ZQL9UXw(|A64o>AWO(V=)P(0HTTprw#}@H}(S9 zwI7wt!6XcDTdl(Rg+ki9nH^-qSO9`7h@v*67W`b=Bxz#-z)8pBcUY}fzRxro58Kv{ z1xw{}s0D=p@O9&X666Bl*f5z)LN=R|2MDtus@Lm!`lt&Eg&TPQE$6O6r~uL6STn*b zC;OFg6!Lp2EgE) zM08ip!cYrh^^66ezgazMcb`qO&xJuLib5)(G=OMwu`sk=!~zgSTj;gukJp2g<9%#- d9Dhs>sV^Ox-tp~AgfRdB002ovPDHLkV1mi}?^6H( literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..229348a0f7fe2de5b469101bf94d4a9a2f10b95c GIT binary patch literal 588 zcmV-S0<-;zP) z15?%vU^em=fC^=^ zME;7*6$2QJMhJ&5hyYO6Ce{IDUa34UkpQqc#X#U-G626RS%667N_BB1xv~JLjp_6~ zdIUt~XxloSo3ao*0C#9eX=`f-g(3+6%Hf7ksy25x2|94%E#r9eFpnesZsPBSLPsS2 z_#EK0viOSQ-bM$<^SFh-Cjcl^+c+|em-HSu_LJDdWJeCe&&l&}g)Y zhtcic2CY^D4ATUfG2m--3Si$QJR1Z6iKpH>qgs8z(QLNjm`p~|GXpY3!Ry@>)#E%Q z0j41y0JX}q*2QfaR6KsGy0}!XXSK^d=B60nC)w@mP%e|UpM8t!7QkYmAd^XqTqijL z!!j7G?CY2e0Od$Y^$fUNej)&xepIwh06@R>whK4?NL&kZIb{W7mzylzW z1c`??K%!mY~|+tpr4W(}`aHFW2tRl|i9;U8K0iu`B^YCiO}HRKM4Q z-5ydQ1j$@V`L5k7_h|tt)fxoB41B*y0tmx1BuS#(rv*UaL19SpH!?Q{@H`Jzt0f5l zyJoEeXnwt38Wez6O*9;i>;QaIT7d0#!#>=kRttb_JRT2t1tfF)ZQJdR7Q*NXy4?XT z5d{EitUW^1bULBz7vtOhF*P z4wK1K$Y!sh*?idm3V6NiH959ke_Ar~n@Zp>U`ha;U{;XCni4>5ES2uj2&js)EDPcA zo+$~U5hN0a$kb>EpqO^MZR$t71L^b`O?}|`Dv=)%0dx|cr>akMdSU_$!$2l;MMWT$ zI-lPNP;puv7{y>6m2W%a%b->mEg#fz$897j~{9B z$u)Ed{09^Qs8zA;A#T$oIPPxdI|QT`d741cN)!XwbGFz~x#2pO1xdIX~4; zcL#pM?e;?`L`ML{=wap^h(z|O2nfO>k)Miy@IHQB z15?%vU^em=fC^=^ zME;7*6$2QJMhJ&5hyYO6Ce{IDUa34UkpQqc#X#U-G626RS%667N_BB1xv~JLjp_6~ zdIUt~XxloSo3ao*0C#9eX=`f-g(3+6%Hf7ksy25x2|94%E#r9eFpnesZsPBSLPsS2 z_#EK0viOSQ-bM$<^SFh-Cjcl^+c+|em-HSu_LJDdWJeCe&&l&}g)Y zhtcic2CY^D4ATUfG2m--3Si$QJR1Z6iKpH>qgs8z(QLNjm`p~|GXpY3!Ry@>)#E%Q z0j41y0JX}q*2QfaR6KsGy0}!XXSK^d=B60nC)w@mP%e|UpM8t!7QkYmAd^XqTqijL z!!j7G?CY2e0Od$Y^$fUNej)&xepIwh06@R>whK4?NL&kZIb{W7mzylzW z1c`??K%!mY~|+tpr4W(}`aHFW2tRl|i9;U8K0iu`B^YCiO}HRKM4Q z-5ydQ1j$@V`L5k7_h|tt)fxoB41B*y0tmx1BuS#(rv*UaL19SpH!?Q{@H`Jzt0f5l zyJoEeXnwt38Wez6O*9;i>;QaIT7d0#!#>=kRttb_JRT2t1tfF)ZQJdR7Q*NXy4?XT z5d{EitUW^1bULB Date: Mon, 10 Nov 2025 07:40:18 +0300 Subject: [PATCH 2/3] fix: building --- .../_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs b/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs index 1ece09cee43..983e24ef3ad 100644 --- a/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs +++ b/Content.Client/_RMC14/Teleporter/RMCTeleporterViewerOverlay.cs @@ -4,11 +4,8 @@ using Content.Shared.Physics; using Robust.Client.GameObjects; using Robust.Client.Graphics; -using Robust.Client.Physics; using Robust.Client.Player; using Robust.Shared.Containers; -using Robust.Shared.Enums; -using static Robust.Shared.GameObjects.LookupFlags; namespace Content.Client._RMC14.Teleporter; @@ -20,7 +17,7 @@ public sealed class RMCTeleporterViewerOverlay : Overlay private readonly SharedContainerSystem _container; private readonly EntityLookupSystem _entityLookup; - private readonly PhysicsSystem _physics; + private readonly Robust.Client.Physics.PhysicsSystem _physics; private readonly SharedRMCTeleporterSystem _teleporter; private readonly SharedTransformSystem _transform; @@ -36,7 +33,7 @@ public RMCTeleporterViewerOverlay() _container = _entity.System(); _entityLookup = _entity.System(); - _physics = _entity.System(); + _physics = _entity.System(); _teleporter = _entity.System(); _transform = _entity.System(); @@ -66,7 +63,7 @@ protected override void Draw(in OverlayDrawArgs args) var otherViewerAABB = _physics.GetWorldAABB(otherViewer); _toDraw.Clear(); - foreach (var viewerContact in _entityLookup.GetEntitiesIntersecting(otherViewerPosition.MapId, otherViewerAABB, Uncontained)) + foreach (var viewerContact in _entityLookup.GetEntitiesIntersecting(otherViewerPosition.MapId, otherViewerAABB, LookupFlags.Uncontained)) { if (!_spriteQuery.TryComp(viewerContact, out var viewerContactSprite) || !viewerContactSprite.Visible || From 3fc6c9793dc87796e0ad969f5712ba4975f88aa2 Mon Sep 17 00:00:00 2001 From: DragonDidlo <111651007+DragonDidlo@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:46:07 +0300 Subject: [PATCH 3/3] Update rmc_ladder.yml --- Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml b/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml index 13b90ef3bc1..8e8f74f1bc2 100644 --- a/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml +++ b/Resources/Prototypes/_RMC14/Entities/rmc_ladder.yml @@ -19,6 +19,7 @@ - type: PlacementReplacement key: ladders - type: Ladder + id: PutNewId - type: entity parent: RMCLadder