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:
Moony
2023-05-19 15:45:09 -05:00
committed by GitHub
parent 0d9b9e113e
commit e92a8fedab
77 changed files with 1176 additions and 987 deletions

View File

@@ -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>