diff --git a/Content.Client/Station/StationSystem.cs b/Content.Client/Station/StationSystem.cs index 2a3e4850cf..5a4a1853d2 100644 --- a/Content.Client/Station/StationSystem.cs +++ b/Content.Client/Station/StationSystem.cs @@ -5,7 +5,7 @@ namespace Content.Client.Station; /// /// This handles letting the client know stations are a thing. Only really used by an admin menu. /// -public sealed class StationSystem : EntitySystem +public sealed partial class StationSystem : SharedStationSystem { private readonly List<(string Name, NetEntity Entity)> _stations = new(); @@ -15,11 +15,14 @@ public sealed class StationSystem : EntitySystem /// /// I'd have this just invoke an entity query, but we're on the client and the client barely knows about stations. /// + // TODO: Stations have a global PVS override now, this can probably be changed into a query. public IReadOnlyList<(string Name, NetEntity Entity)> Stations => _stations; /// public override void Initialize() { + base.Initialize(); + SubscribeNetworkEvent(StationsUpdated); } diff --git a/Content.Server/Station/Systems/StationSystem.cs b/Content.Server/Station/Systems/StationSystem.cs index d80e2d08d1..456d3c6b80 100644 --- a/Content.Server/Station/Systems/StationSystem.cs +++ b/Content.Server/Station/Systems/StationSystem.cs @@ -27,7 +27,7 @@ namespace Content.Server.Station.Systems; /// For jobs, look at StationJobSystem. For spawning, look at StationSpawningSystem. /// [PublicAPI] -public sealed class StationSystem : EntitySystem +public sealed partial class StationSystem : SharedStationSystem { [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly IPlayerManager _player = default!; @@ -48,6 +48,8 @@ public sealed class StationSystem : EntitySystem /// public override void Initialize() { + base.Initialize(); + _sawmill = _logManager.GetSawmill("station"); _gridQuery = GetEntityQuery(); @@ -60,6 +62,9 @@ public sealed class StationSystem : EntitySystem SubscribeLocalEvent(OnStationGridDeleted); SubscribeLocalEvent(OnStationSplitEvent); + SubscribeLocalEvent(OnStationGridAdded); + SubscribeLocalEvent(OnStationGridRemoved); + _player.PlayerStatusChanged += OnPlayerStatusChanged; } @@ -90,6 +95,18 @@ public sealed class StationSystem : EntitySystem } } + private void UpdateTrackersOnGrid(EntityUid gridId, EntityUid? station) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var tracker, out var xform)) + { + if (xform.GridUid == gridId) + { + SetStation((uid, tracker), station); + } + } + } + #region Event handlers private void OnStationAdd(EntityUid uid, StationDataComponent component, ComponentStartup args) @@ -107,6 +124,9 @@ public sealed class StationSystem : EntitySystem foreach (var grid in component.Grids) { RemComp(grid); + + // If the station gets deleted, we raise the event for every grid that was a part of it + RaiseLocalEvent(new StationGridRemovedEvent(grid, uid)); } RaiseNetworkEvent(new StationsUpdatedEvent(GetStationNames()), Filter.Broadcast()); @@ -159,6 +179,18 @@ public sealed class StationSystem : EntitySystem } } + private void OnStationGridAdded(StationGridAddedEvent ev) + { + // When a grid is added to a station, update all trackers on that grid + UpdateTrackersOnGrid(ev.GridId, ev.Station); + } + + private void OnStationGridRemoved(StationGridRemovedEvent ev) + { + // When a grid is removed from a station, update all trackers on that grid to null + UpdateTrackersOnGrid(ev.GridId, null); + } + #endregion Event handlers /// @@ -353,7 +385,7 @@ public sealed class StationSystem : EntitySystem stationMember.Station = station; stationData.Grids.Add(mapGrid); - RaiseLocalEvent(station, new StationGridAddedEvent(mapGrid, false), true); + RaiseLocalEvent(station, new StationGridAddedEvent(mapGrid, station, false), true); _sawmill.Info($"Adding grid {mapGrid} to station {Name(station)} ({station})"); } @@ -376,7 +408,7 @@ public sealed class StationSystem : EntitySystem RemComp(mapGrid); stationData.Grids.Remove(mapGrid); - RaiseLocalEvent(station, new StationGridRemovedEvent(mapGrid), true); + RaiseLocalEvent(station, new StationGridRemovedEvent(mapGrid, station), true); _sawmill.Info($"Removing grid {mapGrid} from station {Name(station)} ({station})"); } @@ -553,15 +585,21 @@ public sealed class StationGridAddedEvent : EntityEventArgs /// public EntityUid GridId; + /// + /// EntityUid of the station this grid was added to. + /// + public EntityUid Station; + /// /// Indicates that the event was fired during station setup, /// so that it can be ignored if StationInitializedEvent was already handled. /// public bool IsSetup; - public StationGridAddedEvent(EntityUid gridId, bool isSetup) + public StationGridAddedEvent(EntityUid gridId, EntityUid station, bool isSetup) { GridId = gridId; + Station = station; IsSetup = isSetup; } } @@ -577,9 +615,15 @@ public sealed class StationGridRemovedEvent : EntityEventArgs /// public EntityUid GridId; - public StationGridRemovedEvent(EntityUid gridId) + /// + /// EntityUid of the station this grid was added to. + /// + public EntityUid Station; + + public StationGridRemovedEvent(EntityUid gridId, EntityUid station) { GridId = gridId; + Station = station; } } diff --git a/Content.Shared/Station/Components/StationTrackerComponent.cs b/Content.Shared/Station/Components/StationTrackerComponent.cs new file mode 100644 index 0000000000..6272b28de4 --- /dev/null +++ b/Content.Shared/Station/Components/StationTrackerComponent.cs @@ -0,0 +1,18 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Station.Components; + +/// +/// Component that tracks which station an entity is currently on. +/// Mainly used for UI purposes on the client to easily get station-specific data like alert levels. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, Access(typeof(SharedStationSystem))] +public sealed partial class StationTrackerComponent : Component +{ + /// + /// The station this entity is currently on, if any. + /// Null when in space or not on any grid. + /// + [DataField, AutoNetworkedField] + public EntityUid? Station; +} diff --git a/Content.Shared/Station/SharedStationSystem.cs b/Content.Shared/Station/SharedStationSystem.cs new file mode 100644 index 0000000000..c067af610d --- /dev/null +++ b/Content.Shared/Station/SharedStationSystem.cs @@ -0,0 +1,111 @@ +using Content.Shared.Station.Components; +using JetBrains.Annotations; +using Robust.Shared.Map; + +namespace Content.Shared.Station; + +public abstract partial class SharedStationSystem : EntitySystem +{ + [Dependency] private readonly MetaDataSystem _meta = default!; + + private EntityQuery _xformQuery; + private EntityQuery _stationMemberQuery; + + /// + public override void Initialize() + { + base.Initialize(); + + _xformQuery = GetEntityQuery(); + _stationMemberQuery = GetEntityQuery(); + + SubscribeLocalEvent(OnTrackerMapInit); + SubscribeLocalEvent(OnTrackerRemove); + SubscribeLocalEvent(OnTrackerGridChanged); + SubscribeLocalEvent(OnMetaFlagRemoveAttempt); + } + + private void OnTrackerMapInit(Entity ent, ref MapInitEvent args) + { + _meta.AddFlag(ent, MetaDataFlags.ExtraTransformEvents); + UpdateStationTracker(ent.AsNullable()); + } + + private void OnTrackerRemove(Entity ent, ref ComponentRemove args) + { + _meta.RemoveFlag(ent, MetaDataFlags.ExtraTransformEvents); + } + + private void OnTrackerGridChanged(Entity ent, ref GridUidChangedEvent args) + { + UpdateStationTracker((ent, ent.Comp, args.Transform)); + } + + private void OnMetaFlagRemoveAttempt(Entity ent, ref MetaFlagRemoveAttemptEvent args) + { + if ((args.ToRemove & MetaDataFlags.ExtraTransformEvents) != 0 && + ent.Comp.LifeStage <= ComponentLifeStage.Running) + { + args.ToRemove &= ~MetaDataFlags.ExtraTransformEvents; + } + } + + /// + /// Updates the station tracker component based on entity's current location. + /// + [PublicAPI] + public void UpdateStationTracker(Entity ent) + { + if (!Resolve(ent, ref ent.Comp1)) + return; + + var xform = ent.Comp2; + + if (!_xformQuery.Resolve(ent, ref xform)) + return; + + // Entity is in nullspace or not on a grid + if (xform.MapID == MapId.Nullspace || xform.GridUid == null) + { + SetStation(ent, null); + return; + } + + // Check if the grid is part of a station + if (!_stationMemberQuery.TryGetComponent(xform.GridUid.Value, out var stationMember)) + { + SetStation(ent, null); + return; + } + + SetStation(ent, stationMember.Station); + } + + /// + /// Sets the station for a StationTrackerComponent. + /// + [PublicAPI] + public void SetStation(Entity ent, EntityUid? station) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + if (ent.Comp.Station == station) + return; + + ent.Comp.Station = station; + Dirty(ent); + } + + /// + /// Gets the station an entity is currently on, if any. + /// + [PublicAPI] + public EntityUid? GetCurrentStation(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, logMissing: false)) + return null; + + return ent.Comp.Station; + } +}