--- /dev/null
+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;
--- /dev/null
+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);
+ }
+ }
+ }
+}
using Content.Shared.Inventory;
using Content.Shared.Storage;
using Robust.Shared.Containers;
+using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Timing;
}
// 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);
}
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))
{
}
}
- 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
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();
/// 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)
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;
}
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,
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!
- type: PipeColorVisuals
- type: Rotatable
- type: GasRecycler
+ - type: PipeRestrictOverlap
- type: NodeContainer
nodes:
inlet:
- type: Appearance
- type: PipeColorVisuals
- type: NodeContainer
+ - type: PipeRestrictOverlap
- type: AtmosUnsafeUnanchor
- type: AtmosPipeColor
- type: Tag
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
nodeGroupID: Teg
- type: AtmosUnsafeUnanchor
+ - type: PipeRestrictOverlap
- type: TegCirculator
- type: StealTarget
stealGroup: Teg