Prevent stacking pipes (#28308)
* prevent stacking pipes * access * notafet review * notafet review pt. 2 * not the actual fix
This commit is contained in:
@@ -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;
|
||||
123
Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs
Normal file
123
Content.Server/Atmos/EntitySystems/PipeRestrictOverlapSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -358,6 +358,7 @@
|
||||
- type: PipeColorVisuals
|
||||
- type: Rotatable
|
||||
- type: GasRecycler
|
||||
- type: PipeRestrictOverlap
|
||||
- type: NodeContainer
|
||||
nodes:
|
||||
inlet:
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
- type: Appearance
|
||||
- type: PipeColorVisuals
|
||||
- type: NodeContainer
|
||||
- type: PipeRestrictOverlap
|
||||
- type: AtmosUnsafeUnanchor
|
||||
- type: AtmosPipeColor
|
||||
- type: Tag
|
||||
|
||||
@@ -246,6 +246,7 @@
|
||||
key: enum.ThermomachineUiKey.Key
|
||||
- type: WiresPanel
|
||||
- type: WiresVisuals
|
||||
- type: PipeRestrictOverlap
|
||||
- type: NodeContainer
|
||||
nodes:
|
||||
pipe:
|
||||
@@ -420,6 +421,7 @@
|
||||
- type: GasCondenser
|
||||
- type: AtmosPipeColor
|
||||
- type: AtmosDevice
|
||||
- type: PipeRestrictOverlap
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 10000
|
||||
- type: Machine
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
nodeGroupID: Teg
|
||||
|
||||
- type: AtmosUnsafeUnanchor
|
||||
- type: PipeRestrictOverlap
|
||||
- type: TegCirculator
|
||||
- type: StealTarget
|
||||
stealGroup: Teg
|
||||
|
||||
Reference in New Issue
Block a user