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

@@ -292,7 +292,7 @@ public sealed partial class StationJobsSystem
assignedJobs.Add(player, (null, EntityUid.Invalid));
continue;
}
_random.Shuffle(givenStations);
foreach (var station in givenStations)
@@ -318,9 +318,8 @@ public sealed partial class StationJobsSystem
foreach (var (station, count) in jobsCount)
{
var jobs = Comp<StationJobsComponent>(station);
var data = Comp<StationDataComponent>(station);
var thresh = data.StationConfig?.ExtendedAccessThreshold ?? -1;
var thresh = jobs.ExtendedAccessThreshold;
jobs.ExtendedAccess = count <= thresh;

View File

@@ -53,30 +53,32 @@ public sealed partial class StationJobsSystem : EntitySystem
private void OnStationInitialized(StationInitializedEvent msg)
{
var stationJobs = AddComp<StationJobsComponent>(msg.Station);
var stationData = Comp<StationDataComponent>(msg.Station);
if (stationData.StationConfig == null)
if (!TryComp<StationJobsComponent>(msg.Station, out var stationJobs))
return;
var mapJobList = stationData.StationConfig.AvailableJobs;
var mapJobList = stationJobs.SetupAvailableJobs;
stationJobs.RoundStartTotalJobs = mapJobList.Values.Where(x => x[0] is not null && x[0] > 0).Sum(x => x[0]!.Value);
stationJobs.MidRoundTotalJobs = mapJobList.Values.Where(x => x[1] is not null && x[1] > 0).Sum(x => x[1]!.Value);
stationJobs.TotalJobs = stationJobs.MidRoundTotalJobs;
stationJobs.JobList = mapJobList.ToDictionary(x => x.Key, x =>
{
if (x.Value[1] <= -1)
return null;
return (uint?) x.Value[1];
});
stationJobs.RoundStartJobList = mapJobList.ToDictionary(x => x.Key, x =>
{
if (x.Value[0] <= -1)
return null;
return (uint?) x.Value[0];
});
stationJobs.OverflowJobs = stationData.StationConfig.OverflowJobs.ToHashSet();
stationJobs.OverflowJobs = stationJobs.OverflowJobs.ToHashSet();
UpdateJobsAvailable();
}
@@ -464,9 +466,11 @@ public sealed partial class StationJobsSystem : EntitySystem
var jobs = new Dictionary<EntityUid, Dictionary<string, uint?>>();
var stationNames = new Dictionary<EntityUid, string>();
foreach (var station in _stationSystem.Stations)
var query = EntityQueryEnumerator<StationJobsComponent>();
while (query.MoveNext(out var station, out var comp))
{
var list = Comp<StationJobsComponent>(station).JobList.ToDictionary(x => x.Key, x => x.Value);
var list = comp.JobList.ToDictionary(x => x.Key, x => x.Value);
jobs.Add(station, list);
stationNames.Add(station, Name(station));
}

View File

@@ -0,0 +1,35 @@
using Content.Server.Station.Components;
namespace Content.Server.Station.Systems;
/// <summary>
/// This handles naming stations.
/// </summary>
public sealed class StationNameSystem : EntitySystem
{
[Dependency] private readonly StationSystem _station = default!;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<StationNameSetupComponent, ComponentInit>(OnStationNameSetupInit);
}
private void OnStationNameSetupInit(EntityUid uid, StationNameSetupComponent component, ComponentInit args)
{
if (!HasComp<StationDataComponent>(uid))
return;
_station.RenameStation(uid, GenerateStationName(component), false);
}
/// <summary>
/// Generates a station name from the given config.
/// </summary>
private static string GenerateStationName(StationNameSetupComponent config)
{
return config.NameGenerator is not null
? config.NameGenerator.FormatName(config.StationNameTemplate)
: config.StationNameTemplate;
}
}

View File

@@ -50,15 +50,9 @@ public sealed class StationSpawningSystem : EntitySystem
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<StationInitializedEvent>(OnStationInitialized);
_configurationManager.OnValueChanged(CCVars.ICRandomCharacters, e => _randomizeCharacters = e, true);
}
private void OnStationInitialized(StationInitializedEvent ev)
{
AddComp<StationSpawningComponent>(ev.Station);
}
/// <summary>
/// Attempts to spawn a player character onto the given station.
/// </summary>
@@ -95,16 +89,19 @@ public sealed class StationSpawningSystem : EntitySystem
/// <param name="job">Job to assign to the character, if any.</param>
/// <param name="profile">Appearance profile to use for the character.</param>
/// <param name="station">The station this player is being spawned on.</param>
/// <param name="entity">The entity to use, if one already exists.</param>
/// <returns>The spawned entity</returns>
public EntityUid SpawnPlayerMob(
EntityCoordinates coordinates,
Job? job,
HumanoidCharacterProfile? profile,
EntityUid? station)
EntityUid? station,
EntityUid? entity = null)
{
// If we're not spawning a humanoid, we're gonna exit early without doing all the humanoid stuff.
if (job?.JobEntity != null)
{
DebugTools.Assert(entity is null);
var jobEntity = EntityManager.SpawnEntity(job.JobEntity, coordinates);
MakeSentientCommand.MakeSentient(jobEntity, EntityManager);
DoJobSpecials(job, jobEntity);
@@ -131,7 +128,7 @@ public sealed class StationSpawningSystem : EntitySystem
if (!_prototypeManager.TryIndex<SpeciesPrototype>(speciesId, out var species))
throw new ArgumentException($"Invalid species prototype was used: {speciesId}");
var entity = Spawn(species.Prototype, coordinates);
entity ??= Spawn(species.Prototype, coordinates);
if (_randomizeCharacters)
{
@@ -141,24 +138,24 @@ public sealed class StationSpawningSystem : EntitySystem
if (job?.StartingGear != null)
{
var startingGear = _prototypeManager.Index<StartingGearPrototype>(job.StartingGear);
EquipStartingGear(entity, startingGear, profile);
EquipStartingGear(entity.Value, startingGear, profile);
if (profile != null)
EquipIdCard(entity, profile.Name, job.Prototype, station);
EquipIdCard(entity.Value, profile.Name, job.Prototype, station);
}
if (profile != null)
{
_humanoidSystem.LoadProfile(entity, profile);
MetaData(entity).EntityName = profile.Name;
_humanoidSystem.LoadProfile(entity.Value, profile);
MetaData(entity.Value).EntityName = profile.Name;
if (profile.FlavorText != "" && _configurationManager.GetCVar(CCVars.FlavorText))
{
AddComp<DetailExaminableComponent>(entity).Content = profile.FlavorText;
AddComp<DetailExaminableComponent>(entity.Value).Content = profile.FlavorText;
}
}
DoJobSpecials(job, entity);
_identity.QueueIdentityUpdate(entity);
return entity;
DoJobSpecials(job, entity.Value);
_identity.QueueIdentityUpdate(entity.Value);
return entity.Value;
}
private void DoJobSpecials(Job? job, EntityUid entity)

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>