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;
+ }
+}