diff --git a/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs b/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs index 875c0aed95c..45b8b4ef87c 100644 --- a/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs +++ b/Content.Server/Shipyard/Systems/ShipyardSystem.Consoles.cs @@ -44,6 +44,7 @@ using Content.Shared.Tiles; using Content.Server._NF.Smuggling.Components; using Content.Shared._NF.ShuttleRecords; +using Content.Server.StationEvents.Components; namespace Content.Server.Shipyard.Systems; @@ -290,6 +291,9 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp prot.PreventArtifactTriggers = true; } + // Ensure cleanup on ship sale + EnsureComp(shuttleUid); + var sellValue = 0; if (!voucherUsed) { diff --git a/Content.Server/Worldgen/Systems/LocalityLoaderSystem.cs b/Content.Server/Worldgen/Systems/LocalityLoaderSystem.cs index 152a54dac91..742b9687bfc 100644 --- a/Content.Server/Worldgen/Systems/LocalityLoaderSystem.cs +++ b/Content.Server/Worldgen/Systems/LocalityLoaderSystem.cs @@ -7,7 +7,8 @@ using Robust.Shared.Map; // Frontier using Content.Server._NF.Salvage; // Frontier -using EntityPosition = (Robust.Shared.GameObjects.EntityUid Entity, Robust.Shared.Map.EntityCoordinates Coordinates); // Frontier +using EntityPosition = (Robust.Shared.GameObjects.EntityUid Entity, Robust.Shared.Map.EntityCoordinates Coordinates); +using Content.Server.StationEvents.Events; // Frontier namespace Content.Server.Worldgen.Systems; @@ -17,15 +18,10 @@ namespace Content.Server.Worldgen.Systems; public sealed class LocalityLoaderSystem : BaseWorldSystem { [Dependency] private readonly TransformSystem _xformSys = default!; - - // Frontier - private List<(Entity Entity, EntityUid MapUid, Vector2 LocalPosition)> _detachEnts = new(); // Frontier - private EntityQuery _debrisQuery; - private readonly List<(EntityUid Debris, List Entity)> _terminatingDebris = []; + [Dependency] private readonly LinkedLifecycleGridSystem _linkedLifecycleGrid = default!; public override void Initialize() { - _debrisQuery = GetEntityQuery(); SubscribeLocalEvent(OnDebrisDespawn); } // Frontier @@ -91,23 +87,8 @@ private void OnDebrisDespawn(EntityUid entity, SpaceDebrisComponent component, E } } - var mobQuery = AllEntityQuery(); - _detachEnts.Clear(); - - while (mobQuery.MoveNext(out var mobUid, out _, out _, out var xform)) - { - if (xform.GridUid == null || entity != xform.GridUid.Value || xform.MapUid == null) - continue; - - // Can't parent directly to map as it runs grid traversal. - _detachEnts.Add(((mobUid, xform), xform.MapUid.Value, _xformSys.GetWorldPosition(xform))); - _xformSys.DetachParentToNull(mobUid, xform); - } - - foreach (var detachEnt in _detachEnts) - { - _xformSys.SetCoordinates(detachEnt.Entity.Owner, new EntityCoordinates(detachEnt.MapUid, detachEnt.LocalPosition)); - } + // Do not delete the grid, it is being deleted. + _linkedLifecycleGrid.UnparentPlayersFromGrid(entity, false); } } // Frontier diff --git a/Content.Server/_NF/Smuggling/DeadDropSystem.cs b/Content.Server/_NF/Smuggling/DeadDropSystem.cs index bafbdaf098d..5ec919c1b18 100644 --- a/Content.Server/_NF/Smuggling/DeadDropSystem.cs +++ b/Content.Server/_NF/Smuggling/DeadDropSystem.cs @@ -10,6 +10,7 @@ using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; using Content.Server.Station.Systems; +using Content.Server.StationEvents.Events; using Content.Shared._NF.CCVar; using Content.Shared._NF.Smuggling.Prototypes; using Content.Shared.Database; @@ -47,6 +48,7 @@ public sealed class DeadDropSystem : EntitySystem [Dependency] private readonly SectorServiceSystem _sectorService = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly SharedGameTicker _ticker = default!; + [Dependency] private readonly LinkedLifecycleGridSystem _linkedLifecycleGrid = default!; private ISawmill _sawmill = default!; private readonly Queue _drops = []; @@ -490,7 +492,7 @@ private void SendDeadDrop(EntityUid uid, DeadDropComponent component, EntityUid //removes the first element of the queue var entityToRemove = _drops.Dequeue(); _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{entityToRemove} queued for deletion"); - EntityManager.QueueDeleteEntity(entityToRemove); + _linkedLifecycleGrid.UnparentPlayersFromGrid(entityToRemove, true); } } @@ -611,7 +613,7 @@ private void SendDeadDrop(EntityUid uid, DeadDropComponent component, EntityUid } } - // Generates a random hint from a given set of entities (grabs the first N, N randomly generated between min/max), + // Generates a random hint from a given set of entities (grabs the first N, N randomly generated between min/max), public string GenerateRandomHint(List<(EntityUid station, EntityUid ent)>? entityList = null) { if (entityList == null) diff --git a/Content.Server/_NF/StationEvents/EventSystems/LinkedLifecycleGridSystem.cs b/Content.Server/_NF/StationEvents/EventSystems/LinkedLifecycleGridSystem.cs index 4b3c5b1c5b1..d663831a4f1 100644 --- a/Content.Server/_NF/StationEvents/EventSystems/LinkedLifecycleGridSystem.cs +++ b/Content.Server/_NF/StationEvents/EventSystems/LinkedLifecycleGridSystem.cs @@ -1,9 +1,21 @@ +using System.Numerics; using Content.Server.StationEvents.Components; +using Content.Shared.Buckle.Components; +using Content.Shared.Humanoid; +using Content.Shared.Mech.Components; +using Content.Shared.Mind; +using Content.Shared.Mind.Components; +using Content.Shared.Mobs.Components; +using Content.Shared.Vehicle.Components; +using Robust.Shared.Map; namespace Content.Server.StationEvents.Events; public sealed class LinkedLifecycleGridSystem : EntitySystem { + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; + public override void Initialize() { base.Initialize(); @@ -48,6 +60,96 @@ private void OnMasterRemoved(EntityUid uid, LinkedLifecycleGridParentComponent c // Destroy child entities foreach (var entity in component.LinkedEntities) - QueueDel(entity); + UnparentPlayersFromGrid(entity, true); + } + + // Try to get parent of entity where appropriate. + private (EntityUid, TransformComponent) GetParentToReparent(EntityUid uid, TransformComponent xform) + { + if (TryComp(uid, out var rider) && rider.Vehicle != null) + { + var vehicleXform = Transform(rider.Vehicle.Value); + if (vehicleXform.MapUid != null) + { + return (rider.Vehicle.Value, vehicleXform); + } + } + if (TryComp(uid, out var mechPilot)) + { + var mechXform = Transform(mechPilot.Mech); + if (mechXform.MapUid != null) + { + return (mechPilot.Mech, mechXform); + } + } + return (uid, xform); + } + + // Returns a list of entities to reparent on a grid. + // Useful if you need to do your own bookkeeping. + public List<(Entity Entity, EntityUid MapUid, Vector2 LocalPosition)> GetEntitiesToReparent(EntityUid grid) + { + List<(Entity Entity, EntityUid MapUid, Vector2 LocalPosition)> reparentEntities = new(); + HashSet handledEntities = new(); + + // Get humanoids + var mobQuery = AllEntityQuery(); + while (mobQuery.MoveNext(out var mobUid, out _, out _, out var xform)) + { + handledEntities.Add(mobUid); + + if (xform.GridUid == null || xform.MapUid == null || xform.GridUid != grid) + continue; + + var (targetUid, targetXform) = GetParentToReparent(mobUid, xform); + + reparentEntities.Add(((targetUid, targetXform), targetXform.MapUid!.Value, _transform.GetWorldPosition(targetXform))); + } + + // Get occupied MindContainers + var mindQuery = AllEntityQuery(); + while (mindQuery.MoveNext(out var mobUid, out var mindContainer, out var xform)) + { + if (xform.GridUid == null || xform.MapUid == null || xform.GridUid != grid) + continue; + + // Not player-controlled, nothing to lose + if (_mind.GetMind(mobUid, mindContainer) == null) + continue; + + // Already handled + if (handledEntities.Contains(mobUid)) + continue; + + var (targetUid, targetXform) = GetParentToReparent(mobUid, xform); + + reparentEntities.Add(((targetUid, targetXform), targetXform.MapUid!.Value, _transform.GetWorldPosition(targetXform))); + } + + return reparentEntities; + } + + // Deletes a grid, reparenting every humanoid and player character that's on it. + public void UnparentPlayersFromGrid(EntityUid grid, bool deleteGrid) + { + if (MetaData(grid).EntityLifeStage >= EntityLifeStage.Terminating) + return; + + var reparentEntities = GetEntitiesToReparent(grid); + + foreach (var target in reparentEntities) + { + // Move the target and all of its children (for bikes, mechs, etc.) + _transform.DetachEntity(target.Entity.Owner, target.Entity.Comp); + } + + // Deletion has to happen before grid traversal re-parents players. + if (deleteGrid) + Del(grid); + + foreach (var target in reparentEntities) + { + _transform.SetCoordinates(target.Entity.Owner, new EntityCoordinates(target.MapUid, target.LocalPosition)); + } } } diff --git a/Content.Server/_NF/StationEvents/Events/BluespaceErrorRule.cs b/Content.Server/_NF/StationEvents/Events/BluespaceErrorRule.cs index 15f3ef12497..3451a0951f7 100644 --- a/Content.Server/_NF/StationEvents/Events/BluespaceErrorRule.cs +++ b/Content.Server/_NF/StationEvents/Events/BluespaceErrorRule.cs @@ -31,8 +31,7 @@ public sealed class BluespaceErrorRule : StationEventSystem Entity, EntityUid MapUid, Vector2 LocalPosition)> _playerMobs = new(); + [Dependency] private readonly LinkedLifecycleGridSystem _linkedLifecycleGrid = default!; public override void Initialize() { @@ -216,17 +215,10 @@ protected override void Ended(EntityUid uid, BluespaceErrorRuleComponent compone } } - var mobQuery = AllEntityQuery(); - _playerMobs.Clear(); - - while (mobQuery.MoveNext(out var mobUid, out _, out _, out var xform)) + var playerMobs = _linkedLifecycleGrid.GetEntitiesToReparent(gridUid); + foreach (var mob in playerMobs) { - if (xform.GridUid == null || xform.MapUid == null || xform.GridUid != gridUid) - continue; - - // Can't parent directly to map as it runs grid traversal. - _playerMobs.Add(((mobUid, xform), xform.MapUid.Value, _transform.GetWorldPosition(xform))); - _transform.DetachEntity(mobUid, xform); + _transform.DetachEntity(mob.Entity.Owner, mob.Entity.Comp); } var gridValue = _pricing.AppraiseGrid(gridUid, null); @@ -234,7 +226,7 @@ protected override void Ended(EntityUid uid, BluespaceErrorRuleComponent compone // Deletion has to happen before grid traversal re-parents players. Del(gridUid); - foreach (var mob in _playerMobs) + foreach (var mob in playerMobs) { _transform.SetCoordinates(mob.Entity.Owner, new EntityCoordinates(mob.MapUid, mob.LocalPosition)); }