using System.Numerics; using Content.Server.StationEvents.Components; using Content.Shared._NF.Bank.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.Silicons.Borgs.Components; using Content.Shared.Movement.Pulling.Components; using Robust.Shared.Map; using Robust.Shared.Player; using Content.Shared._Goobstation.Vehicles; 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(); SubscribeLocalEvent(OnParentSplit); SubscribeLocalEvent(OnChildSplit); SubscribeLocalEvent(OnMasterRemoved); } private void OnParentSplit(EntityUid uid, LinkedLifecycleGridParentComponent component, ref GridSplitEvent args) { LinkSplitGrids(uid, ref args); } private void OnChildSplit(EntityUid uid, LinkedLifecycleGridChildComponent component, ref GridSplitEvent args) { LinkSplitGrids(component.LinkedUid, ref args); } private void LinkSplitGrids(EntityUid target, ref GridSplitEvent args) { if (!TryComp(target, out LinkedLifecycleGridParentComponent? master)) return; foreach (var grid in args.NewGrids) { if (grid == target) continue; var comp = EnsureComp(grid); comp.LinkedUid = target; master.LinkedEntities.Add(grid); } } private void OnMasterRemoved(EntityUid uid, LinkedLifecycleGridParentComponent component, ref ComponentRemove args) { // Somebody destroyed our component, but the entity lives on, do not destroy the grids. if (MetaData(uid).EntityLifeStage < EntityLifeStage.Terminating) return; // Destroy child entities foreach (var entity in component.LinkedEntities) UnparentPlayersFromGrid(entity, true); } // Try to get parent of entity where appropriate. private (EntityUid, TransformComponent) GetParentToReparent(EntityUid uid, TransformComponent xform) { if (TryComp(xform.ParentUid, out var vehicle) && vehicle.Driver == uid) { var vehicleXform = Transform(xform.ParentUid); if (vehicleXform.MapUid != null) { return (xform.ParentUid, 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 MapPosition)> GetEntitiesToReparent(EntityUid grid) { List<(Entity Entity, EntityUid MapUid, Vector2 MapPosition)> reparentEntities = new(); HashSet handledMindContainers = new(); // Get player characters var mobQuery = AllEntityQuery(); while (mobQuery.MoveNext(out var mobUid, out _, out _, out var xform)) { handledMindContainers.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))); HandlePulledEntity(targetUid, ref reparentEntities); } // Get silicon var borgQuery = AllEntityQuery(); while (borgQuery.MoveNext(out var borgUid, out _, out _, out var xform)) { handledMindContainers.Add(borgUid); if (xform.GridUid == null || xform.MapUid == null || xform.GridUid != grid) continue; var (targetUid, targetXform) = GetParentToReparent(borgUid, xform); reparentEntities.Add(((targetUid, targetXform), targetXform.MapUid!.Value, _transform.GetWorldPosition(targetXform))); HandlePulledEntity(targetUid, ref reparentEntities); } // Get occupied MindContainers (non-humanoids, pets, etc.) 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, little to lose if (_mind.GetMind(mobUid, mindContainer) == null) continue; // All humans and borgs should have mind containers - if we've handled them already, no need. if (handledMindContainers.Contains(mobUid)) continue; var (targetUid, targetXform) = GetParentToReparent(mobUid, xform); reparentEntities.Add(((targetUid, targetXform), targetXform.MapUid!.Value, _transform.GetWorldPosition(targetXform))); HandlePulledEntity(targetUid, ref reparentEntities); } return reparentEntities; } /// /// Tries to get what the passed entity is pulling, if anything, and adds it to the passed list. /// private void HandlePulledEntity(Entity entity, ref List<(Entity Entity, EntityUid MapUid, Vector2 MapPosition)> listToReparent) { if (!Resolve(entity, ref entity.Comp)) return; if (entity.Comp.Pulling is not EntityUid pulled) return; var pulledXform = Transform(pulled); if (pulledXform.MapUid is not EntityUid pulledMapUid) return; // Note: this entry may be duplicated. listToReparent.Add(((pulled, pulledXform), pulledMapUid, _transform.GetWorldPosition(pulledXform))); } // Deletes a grid, reparenting every humanoid and player character that's on it. public void UnparentPlayersFromGrid(EntityUid grid, bool deleteGrid, bool ignoreLifeStage = false) { if (!ignoreLifeStage && TerminatingOrDeleted(grid)) return; var reparentEntities = GetEntitiesToReparent(grid); foreach (var target in reparentEntities) { // If the item has already been moved to nullspace, skip it. if (Transform(target.Entity).MapID == MapId.Nullspace) continue; // 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) { // If the item has already been moved out of nullspace, skip it. if (Transform(target.Entity).MapID != MapId.Nullspace) continue; _transform.SetCoordinates(target.Entity.Owner, new EntityCoordinates(target.MapUid, target.MapPosition)); } } }