Prevent stacking pipes (#28308)

* prevent stacking pipes

* access

* notafet review

* notafet review pt. 2

* not the actual fix
This commit is contained in:
Nemanja
2024-05-27 18:37:27 -04:00
committed by GitHub
parent 984af3584f
commit 44b93e68ee
9 changed files with 170 additions and 23 deletions

View File

@@ -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;

View File

@@ -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);
}
}
}
}

View File

@@ -15,6 +15,7 @@ using Content.Shared.Interaction;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Storage; using Content.Shared.Storage;
using Robust.Shared.Containers; using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Timing; using Robust.Shared.Timing;
@@ -91,7 +92,14 @@ namespace Content.Server.Construction
} }
// LEGACY CODE. See warning at the top of the file! // 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! // We need a place to hold our construction items!
var container = _container.EnsureContainer<Container>(user, materialContainer, out var existed); 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 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 (!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; 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 // 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; return;
} }
if (await Construct(user, (ev.Ack + constructionPrototype.GetHashCode()).ToString(), constructionGraph, if (await Construct(user,
edge, targetNode) is not {Valid: true} structure) (ev.Ack + constructionPrototype.GetHashCode()).ToString(),
constructionGraph,
edge,
targetNode,
GetCoordinates(ev.Location),
constructionPrototype.CanRotate ? ev.Angle : Angle.Zero) is not {Valid: true} structure)
{ {
Cleanup(); Cleanup();
return; 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))); 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}"); _adminLogger.Add(LogType.Construction, LogImpact.Low, $"{ToPrettyString(user):player} has turned a {ev.PrototypeName} construction ghost into {ToPrettyString(structure)} at {Transform(structure).Coordinates}");
Cleanup(); Cleanup();

View File

@@ -20,7 +20,7 @@ namespace Content.Server.NodeContainer.Nodes
/// The directions in which this pipe can connect to other pipes around it. /// The directions in which this pipe can connect to other pipes around it.
/// </summary> /// </summary>
[DataField("pipeDirection")] [DataField("pipeDirection")]
private PipeDirection _originalPipeDirection; public PipeDirection OriginalPipeDirection;
/// <summary> /// <summary>
/// The *current* pipe directions (accounting for rotation) /// The *current* pipe directions (accounting for rotation)
@@ -110,26 +110,26 @@ namespace Content.Server.NodeContainer.Nodes
return; return;
var xform = entMan.GetComponent<TransformComponent>(owner); var xform = entMan.GetComponent<TransformComponent>(owner);
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation); CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation);
} }
bool IRotatableNode.RotateNode(in MoveEvent ev) bool IRotatableNode.RotateNode(in MoveEvent ev)
{ {
if (_originalPipeDirection == PipeDirection.Fourway) if (OriginalPipeDirection == PipeDirection.Fourway)
return false; return false;
// update valid pipe direction // update valid pipe direction
if (!RotationsEnabled) if (!RotationsEnabled)
{ {
if (CurrentPipeDirection == _originalPipeDirection) if (CurrentPipeDirection == OriginalPipeDirection)
return false; return false;
CurrentPipeDirection = _originalPipeDirection; CurrentPipeDirection = OriginalPipeDirection;
return true; return true;
} }
var oldDirection = CurrentPipeDirection; var oldDirection = CurrentPipeDirection;
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(ev.NewRotation); CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(ev.NewRotation);
return oldDirection != CurrentPipeDirection; return oldDirection != CurrentPipeDirection;
} }
@@ -142,12 +142,12 @@ namespace Content.Server.NodeContainer.Nodes
if (!RotationsEnabled) if (!RotationsEnabled)
{ {
CurrentPipeDirection = _originalPipeDirection; CurrentPipeDirection = OriginalPipeDirection;
return; return;
} }
var xform = entityManager.GetComponent<TransformComponent>(Owner); var xform = entityManager.GetComponent<TransformComponent>(Owner);
CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation); CurrentPipeDirection = OriginalPipeDirection.RotatePipeDirection(xform.LocalRotation);
} }
public override IEnumerable<Node> GetReachableNodes(TransformComponent xform, public override IEnumerable<Node> GetReachableNodes(TransformComponent xform,

View File

@@ -1 +1,2 @@
construction-step-condition-no-unstackable-in-tile = You cannot make a stack of similar devices. 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!

View File

@@ -358,6 +358,7 @@
- type: PipeColorVisuals - type: PipeColorVisuals
- type: Rotatable - type: Rotatable
- type: GasRecycler - type: GasRecycler
- type: PipeRestrictOverlap
- type: NodeContainer - type: NodeContainer
nodes: nodes:
inlet: inlet:

View File

@@ -51,6 +51,7 @@
- type: Appearance - type: Appearance
- type: PipeColorVisuals - type: PipeColorVisuals
- type: NodeContainer - type: NodeContainer
- type: PipeRestrictOverlap
- type: AtmosUnsafeUnanchor - type: AtmosUnsafeUnanchor
- type: AtmosPipeColor - type: AtmosPipeColor
- type: Tag - type: Tag

View File

@@ -246,6 +246,7 @@
key: enum.ThermomachineUiKey.Key key: enum.ThermomachineUiKey.Key
- type: WiresPanel - type: WiresPanel
- type: WiresVisuals - type: WiresVisuals
- type: PipeRestrictOverlap
- type: NodeContainer - type: NodeContainer
nodes: nodes:
pipe: pipe:
@@ -420,6 +421,7 @@
- type: GasCondenser - type: GasCondenser
- type: AtmosPipeColor - type: AtmosPipeColor
- type: AtmosDevice - type: AtmosDevice
- type: PipeRestrictOverlap
- type: ApcPowerReceiver - type: ApcPowerReceiver
powerLoad: 10000 powerLoad: 10000
- type: Machine - type: Machine

View File

@@ -176,6 +176,7 @@
nodeGroupID: Teg nodeGroupID: Teg
- type: AtmosUnsafeUnanchor - type: AtmosUnsafeUnanchor
- type: PipeRestrictOverlap
- type: TegCirculator - type: TegCirculator
- type: StealTarget - type: StealTarget
stealGroup: Teg stealGroup: Teg