From 03a40855bea67b931623bf11b11ebc773141dff9 Mon Sep 17 00:00:00 2001 From: Samuka <47865393+Samuka-C@users.noreply.github.com> Date: Sun, 26 Apr 2026 17:08:34 -0300 Subject: [PATCH] Anchor only on station component (#43716) * shared stuff * server stuff * locale stuff * use the comp for the method call * pass transform comp * use query instead of GetStations * return bool inside the while loop * empty commit * Update Content.Shared/Station/SharedStationSystem.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> --- .../Construction/AnchorOnlyOnStationSystem.cs | 39 +++++++++++++++++++ .../AnchorOnlyOnStationComponent.cs | 23 +++++++++++ .../Components/AnchorableComponent.cs | 8 +++- .../EntitySystems/AnchorableSystem.cs | 30 +++++++++++++- Content.Shared/Station/SharedStationSystem.cs | 23 +++++++++++ .../en-US/anchorable/anchorable-component.ftl | 2 + 6 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 Content.Server/Construction/AnchorOnlyOnStationSystem.cs create mode 100644 Content.Shared/Construction/Components/AnchorOnlyOnStationComponent.cs diff --git a/Content.Server/Construction/AnchorOnlyOnStationSystem.cs b/Content.Server/Construction/AnchorOnlyOnStationSystem.cs new file mode 100644 index 0000000000..53a2245835 --- /dev/null +++ b/Content.Server/Construction/AnchorOnlyOnStationSystem.cs @@ -0,0 +1,39 @@ +using System.Linq; +using Content.Server.Station.Systems; +using Content.Shared.Construction.Components; +using Robust.Server.GameObjects; + +namespace Content.Server.Construction; + +public sealed class AnchorOnlyOnStationSystem : EntitySystem +{ + [Dependency] private readonly StationSystem _stationSystem = null!; + [Dependency] private readonly TransformSystem _transform = null!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGridSplit); + } + + private void OnGridSplit(ref GridSplitEvent args) + { + var allGrids = args.NewGrids.ToList(); + + var query = AllEntityQuery(); + while (query.MoveNext(out var ent, out var anchorOnlyOnStationComp, out var entXform)) + { + if (entXform.GridUid == null) + continue; + + if (!allGrids.Contains(entXform.GridUid.Value)) + continue; + + if (_stationSystem.IsOnStation(ent, anchorOnlyOnStationComp.OnlyCountLargestGrid)) + continue; + + _transform.Unanchor(ent, entXform); + } + } +} diff --git a/Content.Shared/Construction/Components/AnchorOnlyOnStationComponent.cs b/Content.Shared/Construction/Components/AnchorOnlyOnStationComponent.cs new file mode 100644 index 0000000000..38fcaa636e --- /dev/null +++ b/Content.Shared/Construction/Components/AnchorOnlyOnStationComponent.cs @@ -0,0 +1,23 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Construction.Components; + +/// +/// If a entity has this component it can only be anchored to the station +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class AnchorOnlyOnStationComponent : Component +{ + /// + /// Pop up message when you try to anchor the entity on any grid that isn't the station grid + /// + [DataField] + public LocId PopupMessageAnchorFail = "anchorable-fail-not-on-station"; + + /// + /// If true, it will only be able to be anchored on the largest grid of a station. + /// If false, it will only be able to anchored on any grid of a station. + /// + [DataField] + public bool OnlyCountLargestGrid = true; +} diff --git a/Content.Shared/Construction/Components/AnchorableComponent.cs b/Content.Shared/Construction/Components/AnchorableComponent.cs index e17a49937a..eccd17289c 100644 --- a/Content.Shared/Construction/Components/AnchorableComponent.cs +++ b/Content.Shared/Construction/Components/AnchorableComponent.cs @@ -39,6 +39,11 @@ namespace Content.Shared.Construction.Components public EntityUid User { get; } public EntityUid Tool { get; } + /// + /// This is shown to the player after the entity fails to anchor or unanchor as a popup + /// + public string? FailMessage; + /// /// Extra delay to add to the do_after. /// Add to this, don't replace it. @@ -46,10 +51,11 @@ namespace Content.Shared.Construction.Components /// public float Delay { get; set; } = 0f; - protected BaseAnchoredAttemptEvent(EntityUid user, EntityUid tool) + protected BaseAnchoredAttemptEvent(EntityUid user, EntityUid tool, string? failMessage = null) { User = user; Tool = tool; + FailMessage = failMessage; } } diff --git a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs index 495dddfe69..8892c0f6eb 100644 --- a/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs +++ b/Content.Shared/Construction/EntitySystems/AnchorableSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Interaction; using Content.Shared.Movement.Pulling.Components; using Content.Shared.Movement.Pulling.Systems; using Content.Shared.Popups; +using Content.Shared.Station; using Content.Shared.Tools.Components; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -29,6 +30,7 @@ public sealed partial class AnchorableSystem : EntitySystem [Dependency] private readonly SharedPopupSystem _popup = default!; [Dependency] private readonly PullingSystem _pulling = default!; [Dependency] private readonly SharedMapSystem _map = default!; + [Dependency] private readonly SharedStationSystem _stationSystem = null!; [Dependency] private readonly SharedToolSystem _tool = default!; [Dependency] private readonly SharedTransformSystem _transformSystem = default!; [Dependency] private readonly TagSystem _tagSystem = default!; @@ -49,6 +51,8 @@ public sealed partial class AnchorableSystem : EntitySystem SubscribeLocalEvent(OnAnchoredExamine); SubscribeLocalEvent(OnAnchorStartup); SubscribeLocalEvent(OnAnchorStateChange); + + SubscribeLocalEvent(OnAnchorOnStation); } private void OnAnchorStartup(EntityUid uid, AnchorableComponent comp, ComponentStartup args) @@ -56,6 +60,15 @@ public sealed partial class AnchorableSystem : EntitySystem _appearance.SetData(uid, AnchorVisuals.Anchored, Transform(uid).Anchored); } + private void OnAnchorOnStation(Entity ent, ref AnchorAttemptEvent args) + { + if (_stationSystem.IsOnStation(ent, ent.Comp.OnlyCountLargestGrid)) + return; + + args.FailMessage = Loc.GetString(ent.Comp.PopupMessageAnchorFail); + args.Cancel(); + } + private void OnAnchorStateChange(EntityUid uid, AnchorableComponent comp, AnchorStateChangedEvent args) { _appearance.SetData(uid, AnchorVisuals.Anchored, args.Anchored); @@ -76,8 +89,12 @@ public sealed partial class AnchorableSystem : EntitySystem if (!Resolve(usingUid, ref usingTool)) return; - if (!Valid(uid, userUid, usingUid, false)) + if (!Valid(uid, userUid, usingUid, false, out var failMessage)) + { + if (failMessage != null) + _popup.PopupClient(failMessage, uid, userUid); return; + } // Log unanchor attempt (server only) _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to unanchor {ToPrettyString(uid):entity} from {transform.Coordinates:targetlocation}"); @@ -227,8 +244,12 @@ public sealed partial class AnchorableSystem : EntitySystem if (!Resolve(usingUid, ref usingTool)) return; - if (!Valid(uid, userUid, usingUid, true, anchorable, usingTool)) + if (!Valid(uid, userUid, usingUid, true, out var failMessage, anchorable, usingTool)) + { + if (failMessage != null) + _popup.PopupClient(Loc.GetString(failMessage), uid, userUid); return; + } // Log anchor attempt (server only) _adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to anchor {ToPrettyString(uid):entity} to {transform.Coordinates:targetlocation}"); @@ -250,9 +271,12 @@ public sealed partial class AnchorableSystem : EntitySystem EntityUid userUid, EntityUid usingUid, bool anchoring, + out string? failMessage, AnchorableComponent? anchorable = null, ToolComponent? usingTool = null) { + failMessage = null; + if (!Resolve(uid, ref anchorable)) return false; @@ -276,6 +300,8 @@ public sealed partial class AnchorableSystem : EntitySystem anchorable.Delay += attempt.Delay; + failMessage = attempt.FailMessage; + return !attempt.Cancelled; } diff --git a/Content.Shared/Station/SharedStationSystem.cs b/Content.Shared/Station/SharedStationSystem.cs index 13726ac2e1..4fee0f7ad5 100644 --- a/Content.Shared/Station/SharedStationSystem.cs +++ b/Content.Shared/Station/SharedStationSystem.cs @@ -175,4 +175,27 @@ public abstract partial class SharedStationSystem : EntitySystem return null; } + + /// + /// Returns true if a entity's parent is the station, and false if the entity is not on the station + /// + /// The entity to check if is on a grid + /// If true, only the largest grid of each station will count, so if the entity is on + /// the ATS or a escape pod, it will return false + public bool IsOnStation(EntityUid entity, bool onlyCountLargestGrid = true) + { + var entityParent = Transform(entity).ParentUid; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var stationDataComp)) + { + if (onlyCountLargestGrid && GetLargestGrid(uid) == entityParent) + return true; + + if (!onlyCountLargestGrid && stationDataComp.Grids.Contains(entityParent)) + return true; + } + + return false; + } } diff --git a/Resources/Locale/en-US/anchorable/anchorable-component.ftl b/Resources/Locale/en-US/anchorable/anchorable-component.ftl index fdac0aa741..602d88f595 100644 --- a/Resources/Locale/en-US/anchorable/anchorable-component.ftl +++ b/Resources/Locale/en-US/anchorable/anchorable-component.ftl @@ -1,3 +1,5 @@ anchorable-anchored = Anchored anchorable-unanchored = Unanchored anchorable-occupied = Tile occupied + +anchorable-fail-not-on-station = This can only be anchored on a station!