Refactor stations to properly use entity prototypes. (stationsv3) (#16570)
* Update StationSpawningSystem.cs Web-edit to allow feeding in an existing entity. * Update StationSpawningSystem.cs value type moment * Update StationSpawningSystem.cs * Oh goddamnit this is a refactor now. * awawawa * aaaaaaaaaaa * ee * forgot records. * no records? no records. * What's in a name? * Sloth forcing me to do the refactor properly smh. * e * optional evac in test. * tests pls work * awa --------- Co-authored-by: moonheart08 <moonheart08@users.noreply.github.com>
This commit is contained in:
@@ -13,6 +13,7 @@ using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Station.Systems;
|
||||
|
||||
@@ -35,16 +36,6 @@ public sealed class StationSystem : EntitySystem
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
private readonly HashSet<EntityUid> _stations = new();
|
||||
|
||||
/// <summary>
|
||||
/// All stations that currently exist.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// I'd have this just invoke an entity query, but I want this to be a hashset for convenience and it allocating on use would be lame.
|
||||
/// </remarks>
|
||||
public IReadOnlySet<EntityUid> Stations => _stations;
|
||||
|
||||
private bool _randomStationOffset;
|
||||
private bool _randomStationRotation;
|
||||
private float _maxRandomStationOffset;
|
||||
@@ -57,9 +48,8 @@ public sealed class StationSystem : EntitySystem
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRoundEnd);
|
||||
SubscribeLocalEvent<PreGameMapLoad>(OnPreGameMapLoad);
|
||||
SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad);
|
||||
SubscribeLocalEvent<StationDataComponent, ComponentAdd>(OnStationAdd);
|
||||
SubscribeLocalEvent<StationDataComponent, ComponentStartup>(OnStationAdd);
|
||||
SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted);
|
||||
SubscribeLocalEvent<StationDataComponent, EntParentChangedMessage>(OnParentChanged);
|
||||
SubscribeLocalEvent<StationMemberComponent, ComponentShutdown>(OnStationGridDeleted);
|
||||
SubscribeLocalEvent<StationMemberComponent, PostGridSplitEvent>(OnStationSplitEvent);
|
||||
|
||||
@@ -89,68 +79,34 @@ public sealed class StationSystem : EntitySystem
|
||||
_player.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the server shuts down or restarts to avoid uneccesarily logging mid-round station deletion errors.
|
||||
/// </summary>
|
||||
public void OnServerDispose()
|
||||
{
|
||||
_stations.Clear();
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Connected)
|
||||
{
|
||||
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), e.Session);
|
||||
RaiseNetworkEvent(new StationsUpdatedEvent(GetStationsSet()), e.Session);
|
||||
}
|
||||
}
|
||||
|
||||
#region Event handlers
|
||||
|
||||
private void OnStationAdd(EntityUid uid, StationDataComponent component, ComponentAdd args)
|
||||
private void OnStationAdd(EntityUid uid, StationDataComponent component, ComponentStartup args)
|
||||
{
|
||||
_stations.Add(uid);
|
||||
RaiseNetworkEvent(new StationsUpdatedEvent(GetStationsSet()), Filter.Broadcast());
|
||||
|
||||
var metaData = MetaData(uid);
|
||||
RaiseLocalEvent(new StationInitializedEvent(uid));
|
||||
_sawmill.Info($"Set up station {metaData.EntityName} ({uid}).");
|
||||
|
||||
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast());
|
||||
}
|
||||
|
||||
private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args)
|
||||
{
|
||||
if (_stations.Contains(uid) && // Was not deleted via DeleteStation()
|
||||
_gameTicker.RunLevel == GameRunLevel.InRound && // And not due to a round restart
|
||||
_gameTicker.LobbyEnabled) // If there isn't a lobby, this is probably sandbox, single player, or a test
|
||||
{
|
||||
// printing a stack trace, rather than throwing an exception so that entity deletion continues as normal.
|
||||
Logger.Error($"Station entity {ToPrettyString(uid)} is getting deleted mid-round. Trace: {Environment.StackTrace}");
|
||||
}
|
||||
|
||||
foreach (var grid in component.Grids)
|
||||
{
|
||||
RemComp<StationMemberComponent>(grid);
|
||||
}
|
||||
|
||||
_stations.Remove(uid);
|
||||
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If a station data entity is getting re-parented mid-round, this will log an error.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This doesn't really achieve anything, it just for debugging any future station data bugs.
|
||||
/// </remarks>
|
||||
private void OnParentChanged(EntityUid uid, StationDataComponent component, ref EntParentChangedMessage args)
|
||||
{
|
||||
if (_gameTicker.RunLevel != GameRunLevel.InRound ||
|
||||
MetaData(uid).EntityLifeStage >= EntityLifeStage.MapInitialized ||
|
||||
component.LifeStage <= ComponentLifeStage.Initializing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Yeah this doesn't actually stop the parent change..... it just ineffectually yells about it.
|
||||
// STOP RIGHT THERE CRIMINAL SCUM
|
||||
_sawmill.Error($"Station entity {ToPrettyString(uid)} is getting reparented from {ToPrettyString(args.OldParent ?? EntityUid.Invalid)} to {ToPrettyString(args.Transform.ParentUid)}");
|
||||
RaiseNetworkEvent(new StationsUpdatedEvent(GetStationsSet()), Filter.Broadcast());
|
||||
}
|
||||
|
||||
private void OnPreGameMapLoad(PreGameMapLoad ev)
|
||||
@@ -199,23 +155,18 @@ public sealed class StationSystem : EntitySystem
|
||||
_sawmill.Error($"There were no station grids for {ev.GameMap.ID}!");
|
||||
}
|
||||
|
||||
// Iterate over all PartOfStation
|
||||
// TODO: Remove this whenever pillar finally gets replaced. It's the sole user.
|
||||
foreach (var grid in ev.Grids)
|
||||
{
|
||||
if (!TryComp<PartOfStationComponent>(grid, out var partOfStation))
|
||||
continue;
|
||||
|
||||
AddGrid(partOfStation.Id, grid);
|
||||
}
|
||||
|
||||
foreach (var (id, gridIds) in dict)
|
||||
{
|
||||
StationConfig? stationConfig = null;
|
||||
StationConfig stationConfig;
|
||||
|
||||
if (ev.GameMap.Stations.ContainsKey(id))
|
||||
stationConfig = ev.GameMap.Stations[id];
|
||||
else
|
||||
{
|
||||
_sawmill.Error($"The station {id} in map {ev.GameMap.ID} does not have an associated station config!");
|
||||
continue;
|
||||
}
|
||||
|
||||
InitializeNewStation(stationConfig, gridIds, ev.StationName);
|
||||
}
|
||||
}
|
||||
@@ -225,9 +176,10 @@ public sealed class StationSystem : EntitySystem
|
||||
if (eventArgs.New != GameRunLevel.PreRoundLobby)
|
||||
return;
|
||||
|
||||
foreach (var entity in _stations)
|
||||
var query = EntityQueryEnumerator<StationDataComponent>();
|
||||
while (query.MoveNext(out var station, out _))
|
||||
{
|
||||
DeleteStation(entity);
|
||||
QueueDel(station);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,47 +278,26 @@ public sealed class StationSystem : EntitySystem
|
||||
return filter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a station name from the given config.
|
||||
/// </summary>
|
||||
public static string GenerateStationName(StationConfig config)
|
||||
{
|
||||
return config.NameGenerator is not null
|
||||
? config.NameGenerator.FormatName(config.StationNameTemplate)
|
||||
: config.StationNameTemplate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new station with the given information.
|
||||
/// </summary>
|
||||
/// <param name="stationConfig">The game map prototype used, if any.</param>
|
||||
/// <param name="gridIds">All grids that should be added to the station.</param>
|
||||
/// <param name="name">Optional override for the station name.</param>
|
||||
/// <remarks>This is for ease of use, manually spawning the entity works just fine.</remarks>
|
||||
/// <returns>The initialized station.</returns>
|
||||
public EntityUid InitializeNewStation(StationConfig? stationConfig, IEnumerable<EntityUid>? gridIds, string? name = null)
|
||||
public EntityUid InitializeNewStation(StationConfig stationConfig, IEnumerable<EntityUid>? gridIds, string? name = null)
|
||||
{
|
||||
var station = Spawn(null, MapCoordinates.Nullspace);
|
||||
// Use overrides for setup.
|
||||
var station = EntityManager.SpawnEntity(stationConfig.StationPrototype, MapCoordinates.Nullspace, stationConfig.StationComponentOverrides);
|
||||
|
||||
// TODO SERIALIZATION The station data needs to be saveable somehow, but when a map gets saved, this entity
|
||||
// won't be included because its in null-space. Also, what happens to shuttles on other maps?
|
||||
if (name is not null)
|
||||
RenameStation(station, name, false);
|
||||
|
||||
var data = AddComp<StationDataComponent>(station);
|
||||
var metaData = MetaData(station);
|
||||
data.StationConfig = stationConfig;
|
||||
DebugTools.Assert(HasComp<StationDataComponent>(station), "Stations should have StationData in their prototype.");
|
||||
|
||||
if (stationConfig is not null && name is null)
|
||||
{
|
||||
name = GenerateStationName(stationConfig);
|
||||
}
|
||||
else if (name is null)
|
||||
{
|
||||
_sawmill.Error($"When setting up station {station}, was unable to find a valid name in the config and no name was provided.");
|
||||
name = "unnamed station";
|
||||
}
|
||||
|
||||
metaData.EntityName = name;
|
||||
RaiseLocalEvent(new StationInitializedEvent(station));
|
||||
_sawmill.Info($"Set up station {metaData.EntityName} ({station}).");
|
||||
var data = Comp<StationDataComponent>(station);
|
||||
name ??= MetaData(station).EntityName;
|
||||
|
||||
foreach (var grid in gridIds ?? Array.Empty<EntityUid>())
|
||||
{
|
||||
@@ -397,11 +328,11 @@ public sealed class StationSystem : EntitySystem
|
||||
|
||||
var stationMember = AddComp<StationMemberComponent>(mapGrid);
|
||||
stationMember.Station = station;
|
||||
stationData.Grids.Add(gridComponent.Owner);
|
||||
stationData.Grids.Add(mapGrid);
|
||||
|
||||
RaiseLocalEvent(station, new StationGridAddedEvent(gridComponent.Owner, false), true);
|
||||
RaiseLocalEvent(station, new StationGridAddedEvent(mapGrid, false), true);
|
||||
|
||||
_sawmill.Info($"Adding grid {mapGrid}:{gridComponent.Owner} to station {Name(station)} ({station})");
|
||||
_sawmill.Info($"Adding grid {mapGrid} to station {Name(station)} ({station})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -420,10 +351,10 @@ public sealed class StationSystem : EntitySystem
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
RemComp<StationMemberComponent>(mapGrid);
|
||||
stationData.Grids.Remove(gridComponent.Owner);
|
||||
stationData.Grids.Remove(mapGrid);
|
||||
|
||||
RaiseLocalEvent(station, new StationGridRemovedEvent(gridComponent.Owner), true);
|
||||
_sawmill.Info($"Removing grid {mapGrid}:{gridComponent.Owner} from station {Name(station)} ({station})");
|
||||
RaiseLocalEvent(station, new StationGridRemovedEvent(mapGrid), true);
|
||||
_sawmill.Info($"Removing grid {mapGrid} from station {Name(station)} ({station})");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -462,9 +393,7 @@ public sealed class StationSystem : EntitySystem
|
||||
if (!Resolve(station, ref stationData))
|
||||
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station));
|
||||
|
||||
// component shutdown will error if the station was not removed from _stations prior to deletion.
|
||||
_stations.Remove(station);
|
||||
Del(station);
|
||||
QueueDel(station);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -501,6 +430,16 @@ public sealed class StationSystem : EntitySystem
|
||||
|
||||
return CompOrNull<StationMemberComponent>(xform.GridUid)?.Station;
|
||||
}
|
||||
|
||||
public List<EntityUid> GetStations()
|
||||
{
|
||||
return EntityQuery<StationDataComponent>().Select(x => x.Owner).ToList();
|
||||
}
|
||||
|
||||
public HashSet<EntityUid> GetStationsSet()
|
||||
{
|
||||
return EntityQuery<StationDataComponent>().Select(x => x.Owner).ToHashSet();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user