Fix station deletion bug (#10348)

This commit is contained in:
Leon Friedrich
2022-08-07 15:53:07 +12:00
committed by GitHub
parent 31141ad0fb
commit 099d38b562
5 changed files with 44 additions and 21 deletions

View File

@@ -64,6 +64,10 @@ namespace Content.Client.LateJoin
_jobCategories.Clear(); _jobCategories.Clear();
var gameTicker = EntitySystem.Get<ClientGameTicker>(); var gameTicker = EntitySystem.Get<ClientGameTicker>();
if (!gameTicker.DisallowedLateJoin && gameTicker.StationNames.Count == 0)
Logger.Warning("No stations exist, nothing to display in late-join GUI");
foreach (var (id, name) in gameTicker.StationNames) foreach (var (id, name) in gameTicker.StationNames)
{ {
var jobList = new BoxContainer var jobList = new BoxContainer

View File

@@ -101,6 +101,7 @@ namespace Content.IntegrationTests.Tests
"DebugExceptionStartup", "DebugExceptionStartup",
"Map", // We aren't testing a map entity in this test "Map", // We aren't testing a map entity in this test
"MapGrid", "MapGrid",
"StationData", // errors when removed mid-round
"Actor", // We aren't testing actor components, those need their player session set. "Actor", // We aren't testing actor components, those need their player session set.
}; };
@@ -195,6 +196,7 @@ namespace Content.IntegrationTests.Tests
"DebugExceptionStartup", "DebugExceptionStartup",
"Map", // We aren't testing a map entity in this test "Map", // We aren't testing a map entity in this test
"MapGrid", "MapGrid",
"StationData", // errors when deleted mid-round
"Actor", // We aren't testing actor components, those need their player session set. "Actor", // We aren't testing actor components, those need their player session set.
}; };

View File

@@ -43,22 +43,7 @@ public sealed class FollowerSystemTest
followerSystem.StartFollowingEntity(follower, followed); followerSystem.StartFollowingEntity(follower, followed);
foreach (var ent in entMan.GetEntities().ToArray()) entMan.DeleteEntity(mapMan.GetMapEntityId(map));
{
// Let's skip entities that have been deleted, as we want to get their TransformComp for extra info.
if (entMan.Deleted(ent))
{
logger.Info($"Skipping {entMan.ToPrettyString(ent)}...");
continue;
}
// Log some information about the entity before we delete it.
var transform = entMan.GetComponent<TransformComponent>(ent);
logger.Info($"Deleting entity {entMan.ToPrettyString(ent)}... Parent: {entMan.ToPrettyString(transform.ParentUid)} | Children: {string.Join(", ", transform.Children.Select(c => entMan.ToPrettyString(c.Owner)))}");
// Actually delete the entity now.
entMan.DeleteEntity(ent);
}
}); });
await pairTracker.CleanReturnAsync(); await pairTracker.CleanReturnAsync();
} }

View File

@@ -1,5 +1,6 @@
using Content.Server.Ghost.Components; using Content.Server.Ghost.Components;
using Content.Server.Singularity.Components; using Content.Server.Singularity.Components;
using Content.Server.Station.Components;
using Content.Shared.Singularity; using Content.Shared.Singularity;
using Content.Shared.Singularity.Components; using Content.Shared.Singularity.Components;
using JetBrains.Annotations; using JetBrains.Annotations;
@@ -130,6 +131,7 @@ namespace Content.Server.Singularity.EntitySystems
return entity != component.Owner && return entity != component.Owner &&
!EntityManager.HasComponent<IMapGridComponent>(entity) && !EntityManager.HasComponent<IMapGridComponent>(entity) &&
!EntityManager.HasComponent<GhostComponent>(entity) && !EntityManager.HasComponent<GhostComponent>(entity) &&
!EntityManager.HasComponent<StationDataComponent>(entity) && // these SHOULD be in null-space... but just in case. Also, maybe someone moves a singularity there..
(component.Level > 4 || (component.Level > 4 ||
!EntityManager.HasComponent<ContainmentFieldComponent>(entity) && !EntityManager.HasComponent<ContainmentFieldComponent>(entity) &&
!EntityManager.HasComponent<ContainmentFieldGeneratorComponent>(entity)); !EntityManager.HasComponent<ContainmentFieldGeneratorComponent>(entity));

View File

@@ -25,7 +25,6 @@ namespace Content.Server.Station.Systems;
[PublicAPI] [PublicAPI]
public sealed class StationSystem : EntitySystem public sealed class StationSystem : EntitySystem
{ {
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IConfigurationManager _configurationManager = default!;
[Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly ILogManager _logManager = default!;
[Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IMapManager _mapManager = default!;
@@ -59,8 +58,9 @@ public sealed class StationSystem : EntitySystem
SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRoundEnd); SubscribeLocalEvent<GameRunLevelChangedEvent>(OnRoundEnd);
SubscribeLocalEvent<PreGameMapLoad>(OnPreGameMapLoad); SubscribeLocalEvent<PreGameMapLoad>(OnPreGameMapLoad);
SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad); SubscribeLocalEvent<PostGameMapLoad>(OnPostGameMapLoad);
SubscribeLocalEvent<StationDataComponent, ComponentAdd>(OnStationStartup); SubscribeLocalEvent<StationDataComponent, ComponentAdd>(OnStationAdd);
SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted); SubscribeLocalEvent<StationDataComponent, ComponentShutdown>(OnStationDeleted);
SubscribeLocalEvent<StationDataComponent, EntParentChangedMessage>(OnParentChanged);
_configurationManager.OnValueChanged(CCVars.StationOffset, x => _randomStationOffset = x, true); _configurationManager.OnValueChanged(CCVars.StationOffset, x => _randomStationOffset = x, true);
_configurationManager.OnValueChanged(CCVars.MaxStationOffset, x => _maxRandomStationOffset = x, true); _configurationManager.OnValueChanged(CCVars.MaxStationOffset, x => _maxRandomStationOffset = x, true);
@@ -85,7 +85,7 @@ public sealed class StationSystem : EntitySystem
#region Event handlers #region Event handlers
private void OnStationStartup(EntityUid uid, StationDataComponent component, ComponentAdd args) private void OnStationAdd(EntityUid uid, StationDataComponent component, ComponentAdd args)
{ {
_stations.Add(uid); _stations.Add(uid);
@@ -94,11 +94,36 @@ public sealed class StationSystem : EntitySystem
private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args) private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args)
{ {
_stations.Remove(uid); if (_stations.Contains(uid) && // Was not deleted via DeleteStation()
_gameTicker.RunLevel == GameRunLevel.InRound) // And not due to a round restart
{
throw new InvalidOperationException($"Station entity {ToPrettyString(uid)} is getting deleted mid-round.");
}
_stations.Remove(uid);
RaiseNetworkEvent(new StationsUpdatedEvent(_stations), Filter.Broadcast()); 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)}");
}
private void OnPreGameMapLoad(PreGameMapLoad ev) private void OnPreGameMapLoad(PreGameMapLoad ev)
{ {
// this is only for maps loaded during round setup! // this is only for maps loaded during round setup!
@@ -261,8 +286,12 @@ public sealed class StationSystem : EntitySystem
/// <returns>The initialized station.</returns> /// <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)
{ {
//HACK: This needs to go in null-space but that crashes currently.
var station = Spawn(null, new MapCoordinates(0, 0, _gameTicker.DefaultMap)); var station = Spawn(null, new MapCoordinates(0, 0, _gameTicker.DefaultMap));
// 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?
_transform.DetachParentToNull(Transform(station));
var data = AddComp<StationDataComponent>(station); var data = AddComp<StationDataComponent>(station);
var metaData = MetaData(station); var metaData = MetaData(station);
data.StationConfig = stationConfig; data.StationConfig = stationConfig;
@@ -374,6 +403,7 @@ public sealed class StationSystem : EntitySystem
if (!Resolve(station, ref stationData)) if (!Resolve(station, ref stationData))
throw new ArgumentException("Tried to use a non-station entity as a station!", nameof(station)); 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); _stations.Remove(station);
Del(station); Del(station);
} }