#nullable enable using System.Collections.Generic; using Content.Server.Atmos; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Content.Server.Interfaces; using Content.Shared.GameObjects.Components.Atmos; using Robust.Server.GameObjects; using Robust.Shared.GameObjects; using Robust.Shared.Maths; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.NodeContainer.Nodes { /// /// Connects with other s whose /// correctly correspond. /// [DataDefinition] public class PipeNode : Node, IGasMixtureHolder, IRotatableNode { /// /// Modifies the of this pipe, and ensures the sprite is correctly rotated. /// This is a property for the sake of calling the method via ViewVariables. /// [ViewVariables(VVAccess.ReadWrite)] public PipeDirection SetPipeDirectionAndSprite { get => PipeDirection; set => AdjustPipeDirectionAndSprite(value); } /// /// The directions in which this pipe can connect to other pipes around it. /// Used to check if this pipe can connect to another pipe in a given direction. /// [ViewVariables] [DataField("pipeDirection")] public PipeDirection PipeDirection { get; private set; } /// /// The directions in which this node is connected to other nodes. /// Used by . /// [ViewVariables(VVAccess.ReadWrite)] private PipeDirection ConnectedDirections { get => _connectedDirections; set { _connectedDirections = value; UpdateAppearance(); } } private PipeDirection _connectedDirections; /// /// The this pipe is a part of. Set to when not in an . /// [ViewVariables] private IPipeNet _pipeNet = PipeNet.NullNet; /// /// If is set to . /// When true, this pipe may be storing gas in . /// [ViewVariables] private bool _needsPipeNet = true; /// /// Prevents rotation events from re-calculating the . /// Used while rotating the sprite to the correct orientation while not affecting the pipe. /// private bool IgnoreRotation { get; set; } /// /// The gases in this pipe. /// [ViewVariables] public GasMixture Air { get => _needsPipeNet ? LocalAir : _pipeNet.Air; set { if (_needsPipeNet) LocalAir = value; else _pipeNet.Air = value; } } /// /// Stores gas in this pipe when disconnected from a . /// Only for usage by s. /// [ViewVariables] [DataField("gasMixture")] public GasMixture LocalAir { get; set; } = new(DefaultVolume); [ViewVariables] public float Volume => LocalAir.Volume; private AppearanceComponent? _appearance; private const float DefaultVolume = 1; public override void Initialize(IEntity owner) { base.Initialize(owner); Owner.TryGetComponent(out _appearance); } public override void OnContainerStartup() { base.OnContainerStartup(); OnConnectedDirectionsNeedsUpdating(); UpdateAppearance(); } public override void OnContainerShutdown() { base.OnContainerShutdown(); UpdateAdjacentConnectedDirections(); } public void JoinPipeNet(IPipeNet pipeNet) { _pipeNet = pipeNet; _needsPipeNet = false; } public void ClearPipeNet() { _pipeNet = PipeNet.NullNet; _needsPipeNet = true; } /// /// Rotates the when the entity is rotated, and re-calculates the . /// void IRotatableNode.RotateEvent(RotateEvent ev) { if (IgnoreRotation) return; var diff = ev.NewRotation - ev.OldRotation; PipeDirection = PipeDirection.RotatePipeDirection(diff); RefreshNodeGroup(); OnConnectedDirectionsNeedsUpdating(); UpdateAppearance(); } protected override IEnumerable GetReachableNodes() { for (var i = 0; i < PipeDirectionHelpers.PipeDirections; i++) { var pipeDir = (PipeDirection) (1 << i); if (!PipeDirection.HasDirection(pipeDir)) continue; foreach (var pipe in LinkableNodesInDirection(pipeDir)) yield return pipe; } } /// /// Gets the pipes that can connect to us from entities on the tile adjacent in a direction. /// private IEnumerable LinkableNodesInDirection(PipeDirection pipeDir) { foreach (var pipe in PipesInDirection(pipeDir)) { if (pipe.PipeDirection.HasDirection(pipeDir.GetOpposite())) yield return pipe; } } /// /// Gets the pipes from entities on the tile adjacent in a direction. /// private IEnumerable PipesInDirection(PipeDirection pipeDir) { if (!Owner.TryGetComponent(out SnapGridComponent? grid)) yield break; var entities = grid.GetInDir(pipeDir.ToDirection()); foreach (var entity in entities) { if (!entity.TryGetComponent(out var container)) continue; foreach (var node in container.Nodes) { if (node is PipeNode pipe) yield return pipe; } } } /// /// Updates the of this and all sorrounding pipes. /// private void OnConnectedDirectionsNeedsUpdating() { UpdateConnectedDirections(); UpdateAdjacentConnectedDirections(); } /// /// Checks what directions there are connectable pipes in, to update . /// private void UpdateConnectedDirections() { ConnectedDirections = PipeDirection.None; for (var i = 0; i < PipeDirectionHelpers.PipeDirections; i++) { var pipeDir = (PipeDirection) (1 << i); if (!PipeDirection.HasDirection(pipeDir)) continue; foreach (var pipe in LinkableNodesInDirection(pipeDir)) { if (pipe.Connectable && pipe.NodeGroupID == NodeGroupID) { ConnectedDirections |= pipeDir; break; } } } } /// /// Calls on all adjacent pipes, /// to update their when this pipe is changed. /// private void UpdateAdjacentConnectedDirections() { for (var i = 0; i < PipeDirectionHelpers.PipeDirections; i++) { var pipeDir = (PipeDirection) (1 << i); foreach (var pipe in LinkableNodesInDirection(pipeDir)) pipe.UpdateConnectedDirections(); } } /// /// 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() { var netConnectedDirections = PipeDirection.None; if (Owner.TryGetComponent(out var container)) { foreach (var node in container.Nodes) { if (node is PipeNode pipe) { netConnectedDirections |= pipe.ConnectedDirections; } } } _appearance?.SetData(PipeVisuals.VisualState, new PipeVisualState(PipeDirection.PipeDirectionToPipeShape(), netConnectedDirections)); } /// /// Changes the directions of this pipe while ensuring the sprite is correctly rotated. /// public void AdjustPipeDirectionAndSprite(PipeDirection newDir) { IgnoreRotation = true; var baseDir = newDir.PipeDirectionToPipeShape().ToBaseDirection(); var newAngle = Angle.FromDegrees(0); for (var i = 0; i < PipeDirectionHelpers.PipeDirections; i++) { var pipeDir = (PipeDirection) (1 << i); var angle = pipeDir.ToAngle(); if (baseDir.RotatePipeDirection(angle) == newDir) //finds what angle the entity needs to be rotated from the base to be set to the correct direction { newAngle = angle; break; } } Owner.Transform.LocalRotation = newAngle; //rotate the entity so the sprite's new state will be of the correct direction PipeDirection = newDir; RefreshNodeGroup(); OnConnectedDirectionsNeedsUpdating(); UpdateAppearance(); IgnoreRotation = false; } } }