]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Prevent stacking pipes (#28308)
authorNemanja <98561806+EmoGarbage404@users.noreply.github.com>
Mon, 27 May 2024 22:37:27 +0000 (18:37 -0400)
committerGitHub <noreply@github.com>
Mon, 27 May 2024 22:37:27 +0000 (16:37 -0600)
* prevent stacking pipes

* access

* notafet review

* notafet review pt. 2

* not the actual fix

Content.Server/Atmos/Components/PipeRestrictOverlapComponent.cs [new file with mode: 0644]
Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs [new file with mode: 0644]
Content.Server/Construction/ConstructionSystem.Initial.cs
Content.Server/NodeContainer/Nodes/PipeNode.cs
Resources/Locale/en-US/construction/conditions/no-unstackable-in-tile.ftl
Resources/Prototypes/Entities/Structures/Piping/Atmospherics/binary.yml
Resources/Prototypes/Entities/Structures/Piping/Atmospherics/pipes.yml
Resources/Prototypes/Entities/Structures/Piping/Atmospherics/unary.yml
Resources/Prototypes/Entities/Structures/Power/Generation/teg.yml

diff --git a/Content.Server/Atmos/Components/PipeRestrictOverlapComponent.cs b/Content.Server/Atmos/Components/PipeRestrictOverlapComponent.cs
new file mode 100644 (file)
index 0000000..49e1a8c
--- /dev/null
@@ -0,0 +1,9 @@
+using Content.Server.Atmos.EntitySystems;
+
+namespace Content.Server.Atmos.Components;
+
+/// <summary>
+/// This is used for restricting anchoring pipes so that they do not overlap.
+/// </summary>
+[RegisterComponent, Access(typeof(PipeRestrictOverlapSystem))]
+public sealed partial class PipeRestrictOverlapComponent : Component;
diff --git a/Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs b/Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs
new file mode 100644 (file)
index 0000000..c2ff87c
--- /dev/null
@@ -0,0 +1,123 @@
+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.Construction.Components;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Shared.Map.Components;
+
+namespace Content.Server.Atmos.EntitySystems;
+
+/// <summary>
+/// This handles restricting pipe-based entities from overlapping outlets/inlets with other entities.
+/// </summary>
+public sealed class PipeRestrictOverlapSystem : EntitySystem
+{
+    [Dependency] private readonly MapSystem _map = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly TransformSystem _xform = default!;
+
+    private readonly List<EntityUid> _anchoredEntities = new();
+    private EntityQuery<NodeContainerComponent> _nodeContainerQuery;
+
+    /// <inheritdoc/>
+    public override void Initialize()
+    {
+        SubscribeLocalEvent<PipeRestrictOverlapComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
+        SubscribeLocalEvent<PipeRestrictOverlapComponent, AnchorAttemptEvent>(OnAnchorAttempt);
+
+        _nodeContainerQuery = GetEntityQuery<NodeContainerComponent>();
+    }
+
+    private void OnAnchorStateChanged(Entity<PipeRestrictOverlapComponent> ent, ref AnchorStateChangedEvent args)
+    {
+        if (!args.Anchored)
+            return;
+
+        if (HasComp<AnchorableComponent>(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<PipeRestrictOverlapComponent> 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<NodeContainerComponent, TransformComponent> ent)
+    {
+        if (ent.Comp2.GridUid is not { } grid || !TryComp<MapGridComponent>(grid, out var gridComp))
+            return false;
+
+        var indices = _map.TileIndicesFor(grid, gridComp, ent.Comp2.Coordinates);
+        _anchoredEntities.Clear();
+        _map.GetAnchoredEntities((grid, gridComp), indices, _anchoredEntities);
+
+        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;
+
+            if (PipeNodesOverlap(ent, (otherEnt, otherComp, Transform(otherEnt))))
+                return true;
+        }
+
+        return false;
+    }
+
+    public bool PipeNodesOverlap(Entity<NodeContainerComponent, TransformComponent> ent, Entity<NodeContainerComponent, TransformComponent> other)
+    {
+        var entDirs = GetAllDirections(ent).ToList();
+        var otherDirs = GetAllDirections(other).ToList();
+
+        foreach (var dir in entDirs)
+        {
+            foreach (var otherDir in otherDirs)
+            {
+                if ((dir & otherDir) != 0)
+                    return true;
+            }
+        }
+
+        return false;
+
+        IEnumerable<PipeDirection> GetAllDirections(Entity<NodeContainerComponent, TransformComponent> 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);
+            }
+        }
+    }
+}
index 17ed5c90f4d42d113b0a8b2b0e7661ddab9c97ba..04d3722c66cb76f6c4311b5d07282074fc7e896c 100644 (file)
@@ -15,6 +15,7 @@ using Content.Shared.Interaction;
 using Content.Shared.Inventory;
 using Content.Shared.Storage;
 using Robust.Shared.Containers;
+using Robust.Shared.Map;
 using Robust.Shared.Player;
 using Robust.Shared.Timing;
 
@@ -91,7 +92,14 @@ namespace Content.Server.Construction
         }
 
         // LEGACY CODE. See warning at the top of the file!
-        private async Task<EntityUid?> Construct(EntityUid user, string materialContainer, ConstructionGraphPrototype graph, ConstructionGraphEdge edge, ConstructionGraphNode targetNode)
+        private async Task<EntityUid?> Construct(
+            EntityUid user,
+            string materialContainer,
+            ConstructionGraphPrototype graph,
+            ConstructionGraphEdge edge,
+            ConstructionGraphNode targetNode,
+            EntityCoordinates coords,
+            Angle angle = default)
         {
             // We need a place to hold our construction items!
             var container = _container.EnsureContainer<Container>(user, materialContainer, out var existed);
@@ -261,7 +269,7 @@ namespace Content.Server.Construction
             }
 
             var newEntityProto = graph.Nodes[edge.Target].Entity.GetId(null, user, new(EntityManager));
-            var newEntity = EntityManager.SpawnEntity(newEntityProto, EntityManager.GetComponent<TransformComponent>(user).Coordinates);
+            var newEntity = Spawn(newEntityProto, _transformSystem.ToMapCoordinates(coords), rotation: angle);
 
             if (!TryComp(newEntity, out ConstructionComponent? construction))
             {
@@ -376,7 +384,13 @@ namespace Content.Server.Construction
                 }
             }
 
-            if (await Construct(user, "item_construction", constructionGraph, edge, targetNode) is not { Valid: true } item)
+            if (await Construct(
+                    user,
+                    "item_construction",
+                    constructionGraph,
+                    edge,
+                    targetNode,
+                    Transform(user).Coordinates) is not { Valid: true } item)
                 return false;
 
             // Just in case this is a stack, attempt to merge it. If it isn't a stack, this will just normally pick up
@@ -511,23 +525,18 @@ namespace Content.Server.Construction
                 return;
             }
 
-            if (await Construct(user, (ev.Ack + constructionPrototype.GetHashCode()).ToString(), constructionGraph,
-                    edge, targetNode) is not {Valid: true} structure)
+            if (await Construct(user,
+                    (ev.Ack + constructionPrototype.GetHashCode()).ToString(),
+                    constructionGraph,
+                    edge,
+                    targetNode,
+                    GetCoordinates(ev.Location),
+                    constructionPrototype.CanRotate ? ev.Angle : Angle.Zero) is not {Valid: true} structure)
             {
                 Cleanup();
                 return;
             }
 
-            // We do this to be able to move the construction to its proper position in case it's anchored...
-            // Oh wow transform anchoring is amazing wow I love it!!!!
-            // ikr
-            var xform = Transform(structure);
-            var wasAnchored = xform.Anchored;
-            xform.Anchored = false;
-            xform.Coordinates = GetCoordinates(ev.Location);
-            xform.LocalRotation = constructionPrototype.CanRotate ? ev.Angle : Angle.Zero;
-            xform.Anchored = wasAnchored;
-
             RaiseNetworkEvent(new AckStructureConstructionMessage(ev.Ack, GetNetEntity(structure)));
             _adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(user):player} has turned a {ev.PrototypeName} construction ghost into {ToPrettyString(structure)} at {Transform(structure).Coordinates}");
             Cleanup();
index 861f3eea98d215a2c1507d30ae2839a5a76a5d9d..31ee5712493d698427b79ba6f8670714af04ec70 100644 (file)
@@ -20,7 +20,7 @@ namespace Content.Server.NodeContainer.Nodes
         ///     The directions in which this pipe can connect to other pipes around it.
         /// </summary>
         [DataField("pipeDirection")]
-        private PipeDirection _originalPipeDirection;
+        public PipeDirection OriginalPipeDirection;
 
         /// <summary>
         ///     The *current* pipe directions (accounting for rotation)
@@ -110,26 +110,26 @@ namespace Content.Server.NodeContainer.Nodes
                 return;
 
             var xform = entMan.GetComponent<TransformComponent>(owner);
-            CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation);
+            CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation);
         }
 
         bool IRotatableNode.RotateNode(in MoveEvent ev)
         {
-            if (_originalPipeDirection == PipeDirection.Fourway)
+            if (OriginalPipeDirection == PipeDirection.Fourway)
                 return false;
 
             // update valid pipe direction
             if (!RotationsEnabled)
             {
-                if (CurrentPipeDirection == _originalPipeDirection)
+                if (CurrentPipeDirection == OriginalPipeDirection)
                     return false;
 
-                CurrentPipeDirection = _originalPipeDirection;
+                CurrentPipeDirection = OriginalPipeDirection;
                 return true;
             }
 
             var oldDirection = CurrentPipeDirection;
-            CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(ev.NewRotation);
+            CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(ev.NewRotation);
             return oldDirection != CurrentPipeDirection;
         }
 
@@ -142,12 +142,12 @@ namespace Content.Server.NodeContainer.Nodes
 
             if (!RotationsEnabled)
             {
-                CurrentPipeDirection = _originalPipeDirection;
+                CurrentPipeDirection = OriginalPipeDirection;
                 return;
             }
 
             var xform = entityManager.GetComponent<TransformComponent>(Owner);
-            CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation);
+            CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation);
         }
 
         public override IEnumerable<Node> GetReachableNodes(TransformComponent xform,
index 37ce0de9e8e75b811d3c44937af60df188249fed..715825e801c80cd4f0883a08856c0d5bb5f77121 100644 (file)
@@ -1 +1,2 @@
 construction-step-condition-no-unstackable-in-tile = You cannot make a stack of similar devices.
+pipe-restrict-overlap-popup-blocked = { CAPITALIZE(THE($pipe))} doesn't fit over the other pipes!
index fa5804c64525ef4930bf7b319795b87358e7cd5f..213b0b893d94ea4aebf642b543a8f883edb002c3 100644 (file)
   - type: PipeColorVisuals
   - type: Rotatable
   - type: GasRecycler
+  - type: PipeRestrictOverlap
   - type: NodeContainer
     nodes:
       inlet:
index 0025fc5ae1b3045e30efaa7cb49fbf99f2cefe40..c2fc4e0565b0d48be46dad1f3f09d58c17c9417e 100644 (file)
@@ -51,6 +51,7 @@
   - type: Appearance
   - type: PipeColorVisuals
   - type: NodeContainer
+  - type: PipeRestrictOverlap
   - type: AtmosUnsafeUnanchor
   - type: AtmosPipeColor
   - type: Tag
index c0664602b498b6cf111c5b38cfed1c30ba9d4864..d301f43c78872e3373678553af24974bb4a40b2a 100644 (file)
       key: enum.ThermomachineUiKey.Key
     - type: WiresPanel
     - type: WiresVisuals
+    - type: PipeRestrictOverlap
     - type: NodeContainer
       nodes:
         pipe:
   - type: GasCondenser
   - type: AtmosPipeColor
   - type: AtmosDevice
+  - type: PipeRestrictOverlap
   - type: ApcPowerReceiver
     powerLoad: 10000
   - type: Machine
index 9a378c26a449bd2460fc8797e6299029b4822789..78d979ab8ebaa1bd83d7f0e8f6340c2398c0d78f 100644 (file)
           nodeGroupID: Teg
 
     - type: AtmosUnsafeUnanchor
+    - type: PipeRestrictOverlap
     - type: TegCirculator
     - type: StealTarget
       stealGroup: Teg