using System.Linq; using Content.Server.Atmos.Components; using Content.Server.NodeContainer; using Content.Server.NodeContainer.Nodes; using Content.Server.Popups; using Content.Shared.Atmos; using Content.Shared.CCVar; using Content.Shared.Construction.Components; using JetBrains.Annotations; using Robust.Server.GameObjects; using Robust.Shared.Configuration; using Robust.Shared.Map.Components; namespace Content.Server.Atmos.EntitySystems; /// /// This handles restricting pipe-based entities from overlapping outlets/inlets with other entities. /// public sealed class PipeRestrictOverlapSystem : EntitySystem { [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly MapSystem _map = default!; [Dependency] private readonly PopupSystem _popup = default!; [Dependency] private readonly TransformSystem _xform = default!; private readonly List _anchoredEntities = new(); private EntityQuery _nodeContainerQuery; // Goobstation - Allow device-on-pipe stacking private EntityQuery _restrictOverlapQuery; public bool StrictPipeStacking = false; /// public override void Initialize() { SubscribeLocalEvent(OnAnchorStateChanged); SubscribeLocalEvent(OnAnchorAttempt); Subs.CVar(_cfg, CCVars.StrictPipeStacking, (bool val) => {StrictPipeStacking = val;}, false); _nodeContainerQuery = GetEntityQuery(); // Goobstation - Allow device-on-pipe stacking _restrictOverlapQuery = GetEntityQuery(); } private void OnAnchorStateChanged(Entity ent, ref AnchorStateChangedEvent args) { if (!args.Anchored) return; if (HasComp(ent) && CheckOverlap(ent)) { _popup.PopupEntity(Loc.GetString("pipe-restrict-overlap-popup-blocked", ("pipe", ent.Owner)), ent); _xform.Unanchor(ent, Transform(ent)); } } private void OnAnchorAttempt(Entity ent, ref AnchorAttemptEvent args) { if (args.Cancelled) return; if (!_nodeContainerQuery.TryComp(ent, out var node)) return; var xform = Transform(ent); if (CheckOverlap((ent, node, xform))) { _popup.PopupEntity(Loc.GetString("pipe-restrict-overlap-popup-blocked", ("pipe", ent.Owner)), ent, args.User); args.Cancel(); } } [PublicAPI] public bool CheckOverlap(EntityUid uid) { if (!_nodeContainerQuery.TryComp(uid, out var node)) return false; return CheckOverlap((uid, node, Transform(uid))); } public bool CheckOverlap(Entity ent) { if (ent.Comp2.GridUid is not { } grid || !TryComp(grid, out var gridComp)) return false; var indices = _map.TileIndicesFor(grid, gridComp, ent.Comp2.Coordinates); _anchoredEntities.Clear(); _map.GetAnchoredEntities((grid, gridComp), indices, _anchoredEntities); // ATMOS: change to long if you add more pipe layers than 5 + z levels var takenDirs = PipeDirection.None; foreach (var otherEnt in _anchoredEntities) { // this should never actually happen but just for safety if (otherEnt == ent.Owner) continue; if (!_nodeContainerQuery.TryComp(otherEnt, out var otherComp)) continue; // Goobstation - Allow device-on-pipe stacking if (!_restrictOverlapQuery.HasComp(otherEnt)) continue; var (overlapping, which) = PipeNodesOverlap(ent, (otherEnt, otherComp, Transform(otherEnt)), takenDirs); takenDirs |= which; if (overlapping) return true; } return false; } public (bool, PipeDirection) PipeNodesOverlap(Entity ent, Entity other, PipeDirection takenDirs) { var entDirs = GetAllDirections(ent).ToList(); var otherDirs = GetAllDirections(other).ToList(); var entDirsCollapsed = PipeDirection.None; foreach (var dir in entDirs) { entDirsCollapsed |= dir; foreach (var otherDir in otherDirs) { takenDirs |= otherDir; if (StrictPipeStacking) if ((dir & otherDir) != 0) return (true, takenDirs); else if ((dir ^ otherDir) != 0) break; } } // If no strict pipe stacking, then output ("are all entDirs occupied", takenDirs) return (StrictPipeStacking ? false : ((takenDirs & entDirsCollapsed) == entDirsCollapsed), takenDirs); IEnumerable GetAllDirections(Entity pipe) { foreach (var node in pipe.Comp1.Nodes.Values) { // we need to rotate the pipe manually like this because the rotation doesn't update for pipes that are unanchored. if (node is PipeNode pipeNode) yield return pipeNode.OriginalPipeDirection.RotatePipeDirection(pipe.Comp2.LocalRotation); } } } }