diff --git a/Content.Server/Atmos/Piping/EntitySystems/AtmosPipeNodeAppearanceSystem.cs b/Content.Server/Atmos/Piping/EntitySystems/AtmosPipeNodeAppearanceSystem.cs new file mode 100644 index 0000000000..fc1f27218e --- /dev/null +++ b/Content.Server/Atmos/Piping/EntitySystems/AtmosPipeNodeAppearanceSystem.cs @@ -0,0 +1,81 @@ +using Content.Server.NodeContainer; +using Content.Server.NodeContainer.EntitySystems; +using Content.Server.NodeContainer.Nodes; +using Content.Shared.Atmos; +using Robust.Shared.GameObjects; +using Robust.Shared.IoC; +using Robust.Shared.Map; +using System.Collections.Generic; + +namespace Content.Server.Atmos.Piping.EntitySystems; + +public sealed class AtmosPipeNodeAppearanceSystem : EntitySystem +{ + [Dependency] private readonly IMapManager _mapManager = default!; + + public override void Initialize() + { + base.Initialize(); + + // This should probably just be a directed event, but that would require a weird component that exists only to + // receive directed events (*cough* *cough* CableVisComponent *cough*). + // + // Really I want to target any entity with a PipeConnectorVisualizer or a NodeContainerComponent that contains a + // pipe-node. But I don't know of nice way of doing that. + SubscribeLocalEvent(OnNodeUpdate); + } + + private void OnNodeUpdate(ref NodeGroupsRebuilt ev) + { + UpdateAppearance(ev.NodeOwner); + } + + private void UpdateAppearance(EntityUid uid, AppearanceComponent? appearance = null, NodeContainerComponent? container = null, + TransformComponent? xform = null) + { + if (!Resolve(uid, ref appearance, ref container, ref xform, false)) + return; + + if (!_mapManager.TryGetGrid(xform.GridID, out var grid)) + return; + + // get connected entities + var anyPipeNodes = false; + HashSet connected = new(); + foreach (var node in container.Nodes.Values) + { + if (node is not PipeNode) + continue; + + anyPipeNodes = true; + + foreach (var connectedNode in node.ReachableNodes) + { + if (connectedNode is PipeNode) + connected.Add(connectedNode.Owner); + } + } + + if (!anyPipeNodes) + return; + + // find the cardinal directions of any connected entities + var netConnectedDirections = PipeDirection.None; + var tile = grid.TileIndicesFor(xform.Coordinates); + foreach (var neighbour in connected) + { + var otherTile = grid.TileIndicesFor(Transform(neighbour).Coordinates); + + netConnectedDirections |= (otherTile - tile) switch + { + (0, 1) => PipeDirection.North, + (0, -1) => PipeDirection.South, + (1, 0) => PipeDirection.East, + (-1, 0) => PipeDirection.West, + _ => PipeDirection.None + }; + } + + appearance.SetData(PipeVisuals.VisualState, netConnectedDirections); + } +} diff --git a/Content.Server/NodeContainer/EntitySystems/NodeContainerSystem.cs b/Content.Server/NodeContainer/EntitySystems/NodeContainerSystem.cs index 9800f3fc78..8e53fe9c02 100644 --- a/Content.Server/NodeContainer/EntitySystems/NodeContainerSystem.cs +++ b/Content.Server/NodeContainer/EntitySystems/NodeContainerSystem.cs @@ -1,6 +1,7 @@ -using Content.Server.NodeContainer.Nodes; +using Content.Server.NodeContainer.Nodes; using JetBrains.Annotations; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; namespace Content.Server.NodeContainer.EntitySystems { @@ -9,8 +10,10 @@ namespace Content.Server.NodeContainer.EntitySystems /// /// [UsedImplicitly] - public class NodeContainerSystem : EntitySystem + public sealed class NodeContainerSystem : EntitySystem { + [Dependency] private readonly NodeGroupSystem _nodeGroupSystem = default!; + public override void Initialize() { base.Initialize(); @@ -22,44 +25,50 @@ namespace Content.Server.NodeContainer.EntitySystems SubscribeLocalEvent(OnRotateEvent); } - private static void OnInitEvent(EntityUid uid, NodeContainerComponent component, ComponentInit args) + private void OnInitEvent(EntityUid uid, NodeContainerComponent component, ComponentInit args) { foreach (var (key, node) in component.Nodes) { node.Name = key; - node.Initialize(component.Owner); + node.Initialize(component.Owner, EntityManager); } } - private static void OnStartupEvent(EntityUid uid, NodeContainerComponent component, ComponentStartup args) + private void OnStartupEvent(EntityUid uid, NodeContainerComponent component, ComponentStartup args) { foreach (var node in component.Nodes.Values) { - node.OnContainerStartup(); + _nodeGroupSystem.QueueReflood(node); } } - private static void OnShutdownEvent(EntityUid uid, NodeContainerComponent component, ComponentShutdown args) + private void OnShutdownEvent(EntityUid uid, NodeContainerComponent component, ComponentShutdown args) { foreach (var node in component.Nodes.Values) { - node.OnContainerShutdown(); + _nodeGroupSystem.QueueNodeRemove(node); + node.Deleting = true; } } - private static void OnAnchorStateChanged( + private void OnAnchorStateChanged( EntityUid uid, NodeContainerComponent component, ref AnchorStateChangedEvent args) { foreach (var node in component.Nodes.Values) { - node.AnchorUpdate(); - node.AnchorStateChanged(); + if (!node.NeedAnchored) + continue; + + if (args.Anchored) + _nodeGroupSystem.QueueReflood(node); + else + _nodeGroupSystem.QueueNodeRemove(node); } } - private static void OnRotateEvent(EntityUid uid, NodeContainerComponent container, ref RotateEvent ev) + private void OnRotateEvent(EntityUid uid, NodeContainerComponent container, ref RotateEvent ev) { if (ev.NewRotation == ev.OldRotation) { @@ -68,8 +77,11 @@ namespace Content.Server.NodeContainer.EntitySystems foreach (var node in container.Nodes.Values) { - if (node is not IRotatableNode rotatableNode) continue; - rotatableNode.RotateEvent(ref ev); + if (node is not IRotatableNode rotatableNode) + continue; + + if (rotatableNode.RotateEvent(ref ev)) + _nodeGroupSystem.QueueReflood(node); } } } diff --git a/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs b/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs index c205c6e4b3..fe3ed92af0 100644 --- a/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs +++ b/Content.Server/NodeContainer/EntitySystems/NodeGroupSystem.cs @@ -245,14 +245,22 @@ namespace Content.Server.NodeContainer.EntitySystems _toRemake.Clear(); _toRemove.Clear(); + // notify entities that node groups have been updated, so they can do things like update their visuals. + HashSet entities = new(); foreach (var group in newGroups) { foreach (var node in group.Nodes) { - node.OnPostRebuild(); + entities.Add(node.Owner); } } + foreach (var uid in entities) + { + var ev = new NodeGroupsRebuilt(uid); + RaiseLocalEvent(uid, ref ev, true); + } + _sawmill.Debug($"Updated node groups in {sw.Elapsed.TotalMilliseconds}ms. {newGroups.Count} new groups, {refloodCount} nodes processed."); } @@ -402,4 +410,19 @@ namespace Content.Server.NodeContainer.EntitySystems }; } } + + /// + /// Event raised after node groups have been updated. Directed at any entity with a that had a relevant node. + /// + [ByRefEvent] + public readonly struct NodeGroupsRebuilt + { + public readonly EntityUid NodeOwner; + + public NodeGroupsRebuilt(EntityUid nodeOwner) + { + NodeOwner = nodeOwner; + } + } } diff --git a/Content.Server/NodeContainer/NodeGroups/PipeNet.cs b/Content.Server/NodeContainer/NodeGroups/PipeNet.cs index a8ffb13814..180b21ccbb 100644 --- a/Content.Server/NodeContainer/NodeGroups/PipeNet.cs +++ b/Content.Server/NodeContainer/NodeGroups/PipeNet.cs @@ -54,7 +54,6 @@ namespace Content.Server.NodeContainer.NodeGroups { var pipeNode = (PipeNode) node; _pipes.Add(pipeNode); - pipeNode.JoinPipeNet(this); Air.Volume += pipeNode.Volume; } } diff --git a/Content.Server/NodeContainer/Nodes/IRotatableNode.cs b/Content.Server/NodeContainer/Nodes/IRotatableNode.cs index 6ca1728e1f..8f5da7c6eb 100644 --- a/Content.Server/NodeContainer/Nodes/IRotatableNode.cs +++ b/Content.Server/NodeContainer/Nodes/IRotatableNode.cs @@ -9,8 +9,8 @@ namespace Content.Server.NodeContainer.Nodes public interface IRotatableNode { /// - /// Rotates this . + /// Rotates this . Returns true if the node's connections need to be updated. /// - void RotateEvent(ref RotateEvent ev); + bool RotateEvent(ref RotateEvent ev); } } diff --git a/Content.Server/NodeContainer/Nodes/Node.cs b/Content.Server/NodeContainer/Nodes/Node.cs index 06391f693c..4e4aee4f80 100644 --- a/Content.Server/NodeContainer/Nodes/Node.cs +++ b/Content.Server/NodeContainer/Nodes/Node.cs @@ -56,7 +56,7 @@ namespace Content.Server.NodeContainer.Nodes [ViewVariables(VVAccess.ReadWrite)] [DataField("needAnchored")] - private bool NeedAnchored { get; } = true; + public bool NeedAnchored { get; } = true; /// /// Prevents a node from being used by other nodes while midway through removal. @@ -83,70 +83,11 @@ namespace Content.Server.NodeContainer.Nodes /// Invoked when the owning is initialized. /// /// The owning entity. - public virtual void Initialize(EntityUid owner) + public virtual void Initialize(EntityUid owner, IEntityManager entMan) { Owner = owner; } - /// - /// Invoked when the owning is started. - /// - public virtual void OnContainerStartup() - { - EntitySystem.Get().QueueReflood(this); - } - - /// - /// Immediately create a single-node node group for this node if it does not have one yet. - /// - /// - /// This can be useful for nodes like pipes - /// that need immediate access to their node group to set parameters like node volume. - /// The node group created by this function (if necessary) will still update and form new, - /// merged groups later if necessary. - /// Set parameters like pipe net volume should then be transferred/merged there. - /// - public void CreateSingleNetImmediate() - { - EntitySystem.Get().CreateSingleNetImmediate(this); - } - - public void AnchorUpdate() - { - if (Anchored) - { - EntitySystem.Get().QueueReflood(this); - } - else - { - EntitySystem.Get().QueueNodeRemove(this); - } - } - - /// - /// Called when the anchored state of the owning entity changes. - /// - public virtual void AnchorStateChanged() - { - } - - /// - /// Called after the parent node group has been rebuilt. - /// - public virtual void OnPostRebuild() - { - - } - - /// - /// Called when the owning is shut down. - /// - public virtual void OnContainerShutdown() - { - Deleting = true; - EntitySystem.Get().QueueNodeRemove(this); - } - /// /// How this node will attempt to find other reachable s to group with. /// Returns a set of s to consider grouping with. Should not return this current . diff --git a/Content.Server/NodeContainer/Nodes/PipeNode.cs b/Content.Server/NodeContainer/Nodes/PipeNode.cs index fd3d033e47..780c2974e1 100644 --- a/Content.Server/NodeContainer/Nodes/PipeNode.cs +++ b/Content.Server/NodeContainer/Nodes/PipeNode.cs @@ -21,8 +21,6 @@ namespace Content.Server.NodeContainer.Nodes [DataDefinition] public class PipeNode : Node, IGasMixtureHolder, IRotatableNode { - private PipeDirection _connectedDirections; - /// /// The directions in which this pipe can connect to other pipes around it. /// @@ -56,21 +54,6 @@ namespace Content.Server.NodeContainer.Nodes EntitySystem.Get().QueueRemakeGroup((BaseNodeGroup) NodeGroup); } - /// - /// The directions in which this node is connected to other nodes. - /// Used by . - /// - [ViewVariables(VVAccess.ReadWrite)] - public PipeDirection ConnectedDirections - { - get => _connectedDirections; - private set - { - _connectedDirections = value; - UpdateAppearance(); - } - } - /// /// Whether this node can connect to others or not. /// @@ -124,30 +107,37 @@ namespace Content.Server.NodeContainer.Nodes private const float DefaultVolume = 200f; - public override void OnContainerStartup() + public override void Initialize(EntityUid owner, IEntityManager entMan) { - base.OnContainerStartup(); - OnConnectedDirectionsNeedsUpdating(); + base.Initialize(owner, entMan); + + if (!RotationsEnabled) + return; + + var xform = entMan.GetComponent(owner); + CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(xform.LocalRotation); } - public override void OnContainerShutdown() + bool IRotatableNode.RotateEvent(ref RotateEvent ev) { - base.OnContainerShutdown(); - UpdateAdjacentConnectedDirections(); - } + if (_originalPipeDirection == PipeDirection.Fourway) + return false; - public void JoinPipeNet(IPipeNet pipeNet) - { - OnConnectedDirectionsNeedsUpdating(); - } + // update valid pipe direction + if (!RotationsEnabled) + { + if (CurrentPipeDirection == _originalPipeDirection) + return false; - /// - /// Rotates the when the entity is rotated, and re-calculates the . - /// - void IRotatableNode.RotateEvent(ref RotateEvent ev) - { - OnConnectedDirectionsNeedsUpdating(); - UpdateAppearance(); + CurrentPipeDirection = _originalPipeDirection; + } + else + { + CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(ev.NewRotation); + } + + // node connections need to be updated + return true; } public override IEnumerable GetReachableNodes(TransformComponent xform, @@ -229,104 +219,5 @@ namespace Content.Server.NodeContainer.Nodes } } } - - /// - /// Updates the of this and all sorrounding pipes. - /// Also updates CurrentPipeDirection. - /// - private void OnConnectedDirectionsNeedsUpdating() - { - if (RotationsEnabled) - { - CurrentPipeDirection = _originalPipeDirection.RotatePipeDirection(IoCManager.Resolve().GetComponent(Owner).LocalRotation); - } - else - { - CurrentPipeDirection = _originalPipeDirection; - } - UpdateConnectedDirections(); - UpdateAdjacentConnectedDirections(); - UpdateAppearance(); - } - - /// - /// Checks what directions there are connectable pipes in, to update . - /// - private void UpdateConnectedDirections() - { - ConnectedDirections = PipeDirection.None; - - var entMan = IoCManager.Resolve(); - var xform = entMan.GetComponent(Owner); - if (!IoCManager.Resolve().TryGetGrid(xform.GridID, out var grid)) - return; - var pos = grid.WorldToTile(xform.WorldPosition); - var query = entMan.GetEntityQuery(); - - for (var i = 0; i < PipeDirectionHelpers.AllPipeDirections; i++) - { - var pipeDir = (PipeDirection) (1 << i); - - if (!CurrentPipeDirection.HasDirection(pipeDir)) - continue; - - foreach (var pipe in LinkableNodesInDirection(pos, pipeDir, grid, query)) - { - if (pipe.Connectable(entMan) && pipe.NodeGroupID == NodeGroupID) - { - ConnectedDirections |= pipeDir; - break; - } - } - } - } - - /// - /// Calls on all adjacent pipes, - /// to update their when this pipe is changed. - /// - private void UpdateAdjacentConnectedDirections() - { - var entMan = IoCManager.Resolve(); - var xform = entMan.GetComponent(Owner); - if (!IoCManager.Resolve().TryGetGrid(xform.GridID, out var grid)) - return; - var pos = grid.WorldToTile(xform.WorldPosition); - var query = entMan.GetEntityQuery(); - - for (var i = 0; i < PipeDirectionHelpers.PipeDirections; i++) - { - var pipeDir = (PipeDirection) (1 << i); - - foreach (var pipe in LinkableNodesInDirection(pos, pipeDir, grid, query)) - { - pipe.UpdateConnectedDirections(); - pipe.UpdateAppearance(); - } - } - } - - /// - /// Updates the . - /// Gets the combined of every pipe on this entity, so the visualizer on this entity can draw the pipe connections. - /// - private void UpdateAppearance() - { - if (!IoCManager.Resolve().TryGetComponent(Owner, out AppearanceComponent? appearance) - || !IoCManager.Resolve().TryGetComponent(Owner, out NodeContainerComponent? container)) - return; - - var netConnectedDirections = PipeDirection.None; - - foreach (var node in container.Nodes.Values) - { - if (node is PipeNode pipe) - { - netConnectedDirections |= pipe.ConnectedDirections; - } - } - - appearance.SetData(PipeVisuals.VisualState, netConnectedDirections); - } } } diff --git a/Content.Server/Power/EntitySystems/CableVisSystem.cs b/Content.Server/Power/EntitySystems/CableVisSystem.cs index 5ed4866f76..c4dac39e35 100644 --- a/Content.Server/Power/EntitySystems/CableVisSystem.cs +++ b/Content.Server/Power/EntitySystems/CableVisSystem.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Content.Server.NodeContainer; using Content.Server.NodeContainer.EntitySystems; using Content.Server.Power.Components; @@ -16,74 +17,49 @@ namespace Content.Server.Power.EntitySystems { [Dependency] private readonly IMapManager _mapManager = default!; - private readonly HashSet _toUpdate = new(); - - public void QueueUpdate(EntityUid uid) - { - _toUpdate.Add(uid); - } - public override void Initialize() { base.Initialize(); - UpdatesAfter.Add(typeof(NodeGroupSystem)); + SubscribeLocalEvent(UpdateAppearance); } - public override void Update(float frameTime) + private void UpdateAppearance(EntityUid uid, CableVisComponent cableVis, ref NodeGroupsRebuilt args) { - base.Update(frameTime); + if (cableVis.Node == null) + return; - foreach (var uid in _toUpdate) + if (!TryComp(uid, out NodeContainerComponent? nodeContainer) || !TryComp(uid, out AppearanceComponent? appearance)) + return; + + var transform = Transform(uid); + if (!_mapManager.TryGetGrid(transform.GridID, out var grid)) + return; + + var mask = WireVisDirFlags.None; + var tile = grid.TileIndicesFor(transform.Coordinates); + var node = nodeContainer.GetNode(cableVis.Node); + + foreach (var reachable in node.ReachableNodes) { - if (!EntityManager.TryGetComponent(uid, out NodeContainerComponent? nodeContainer) - || !EntityManager.TryGetComponent(uid, out CableVisComponent? cableVis) - || !EntityManager.TryGetComponent(uid, out AppearanceComponent? appearance)) + if (reachable is not CableNode) + continue; + + var otherTransform = Transform(reachable.Owner); + var otherTile = grid.TileIndicesFor(otherTransform.Coordinates); + var diff = otherTile - tile; + + mask |= diff switch { - continue; - } - - if (cableVis.Node == null) - continue; - - var mask = WireVisDirFlags.None; - - var transform = EntityManager.GetComponent(uid); - - // Only valid grids allowed. - if(!transform.GridID.IsValid()) - continue; - - var grid = _mapManager.GetGrid(transform.GridID); - var tile = grid.TileIndicesFor(transform.Coordinates); - var node = nodeContainer.GetNode(cableVis.Node); - - foreach (var reachable in node.ReachableNodes) - { - if (reachable is not CableNode) - continue; - - var otherTransform = EntityManager.GetComponent(reachable.Owner); - if (otherTransform.GridID != grid.Index) - continue; - - var otherTile = grid.TileIndicesFor(otherTransform.Coordinates); - var diff = otherTile - tile; - - mask |= diff switch - { - (0, 1) => WireVisDirFlags.North, - (0, -1) => WireVisDirFlags.South, - (1, 0) => WireVisDirFlags.East, - (-1, 0) => WireVisDirFlags.West, - _ => WireVisDirFlags.None - }; - } - - appearance.SetData(WireVisVisuals.ConnectedMask, mask); + (0, 1) => WireVisDirFlags.North, + (0, -1) => WireVisDirFlags.South, + (1, 0) => WireVisDirFlags.East, + (-1, 0) => WireVisDirFlags.West, + _ => WireVisDirFlags.None + }; } - _toUpdate.Clear(); + appearance.SetData(WireVisVisuals.ConnectedMask, mask); } } } diff --git a/Content.Server/Power/Nodes/CableNode.cs b/Content.Server/Power/Nodes/CableNode.cs index d315f7331c..1d681b0dee 100644 --- a/Content.Server/Power/Nodes/CableNode.cs +++ b/Content.Server/Power/Nodes/CableNode.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Content.Server.NodeContainer; using Content.Server.NodeContainer.Nodes; -using Content.Server.Power.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -70,12 +69,5 @@ namespace Content.Server.Power.Nodes yield return node; } } - - public override void OnPostRebuild() - { - base.OnPostRebuild(); - - EntitySystem.Get().QueueUpdate(Owner); - } } }