committed by
GitHub
parent
ea60a81fdf
commit
103bc19508
@@ -11,16 +11,51 @@ namespace Content.Server.NodeContainer.EntitySystems
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<NodeContainerComponent, ComponentInit>(OnInitEvent);
|
||||
SubscribeLocalEvent<NodeContainerComponent, ComponentStartup>(OnStartupEvent);
|
||||
SubscribeLocalEvent<NodeContainerComponent, ComponentShutdown>(OnShutdownEvent);
|
||||
SubscribeLocalEvent<NodeContainerComponent, AnchorStateChangedEvent>(OnAnchorStateChanged);
|
||||
SubscribeLocalEvent<NodeContainerComponent, RotateEvent>(OnRotateEvent);
|
||||
}
|
||||
|
||||
private void OnAnchorStateChanged(EntityUid uid, NodeContainerComponent component, AnchorStateChangedEvent args)
|
||||
private static void OnInitEvent(EntityUid uid, NodeContainerComponent component, ComponentInit args)
|
||||
{
|
||||
component.AnchorUpdate();
|
||||
foreach (var (key, node) in component.Nodes)
|
||||
{
|
||||
node.Name = key;
|
||||
node.Initialize(component.Owner);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnRotateEvent(EntityUid uid, NodeContainerComponent container, RotateEvent ev)
|
||||
private static void OnStartupEvent(EntityUid uid, NodeContainerComponent component, ComponentStartup args)
|
||||
{
|
||||
foreach (var node in component.Nodes.Values)
|
||||
{
|
||||
node.OnContainerStartup();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnShutdownEvent(EntityUid uid, NodeContainerComponent component, ComponentShutdown args)
|
||||
{
|
||||
foreach (var node in component.Nodes.Values)
|
||||
{
|
||||
node.OnContainerShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnAnchorStateChanged(
|
||||
EntityUid uid,
|
||||
NodeContainerComponent component,
|
||||
AnchorStateChangedEvent args)
|
||||
{
|
||||
foreach (var node in component.Nodes.Values)
|
||||
{
|
||||
node.AnchorUpdate();
|
||||
node.AnchorStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnRotateEvent(EntityUid uid, NodeContainerComponent container, RotateEvent ev)
|
||||
{
|
||||
if (ev.NewRotation == ev.OldRotation)
|
||||
{
|
||||
|
||||
@@ -1,30 +1,385 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Content.Server.Administration.Managers;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.NodeContainer;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.NodeContainer.EntitySystems
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class NodeGroupSystem : EntitySystem
|
||||
{
|
||||
private readonly HashSet<INodeGroup> _dirtyNodeGroups = new();
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public void AddDirtyNodeGroup(INodeGroup nodeGroup)
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IAdminManager _adminManager = default!;
|
||||
[Dependency] private readonly INodeGroupFactory _nodeGroupFactory = default!;
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
|
||||
private readonly List<int> _visDeletes = new();
|
||||
private readonly List<BaseNodeGroup> _visSends = new();
|
||||
|
||||
private readonly HashSet<IPlayerSession> _visPlayers = new();
|
||||
private readonly HashSet<BaseNodeGroup> _toRemake = new();
|
||||
private readonly HashSet<Node> _toRemove = new();
|
||||
private readonly List<Node> _toReflood = new();
|
||||
|
||||
private ISawmill _sawmill = default!;
|
||||
|
||||
public bool VisEnabled => _visPlayers.Count != 0;
|
||||
|
||||
private int _gen = 1;
|
||||
private int _groupNetIdCounter = 1;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
_dirtyNodeGroups.Add(nodeGroup);
|
||||
base.Initialize();
|
||||
|
||||
_sawmill = _logManager.GetSawmill("nodegroup");
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
|
||||
SubscribeNetworkEvent<NodeVis.MsgEnable>(HandleEnableMsg);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void HandleEnableMsg(NodeVis.MsgEnable msg, EntitySessionEventArgs args)
|
||||
{
|
||||
var session = (IPlayerSession) args.SenderSession;
|
||||
if (!_adminManager.HasAdminFlag(session, AdminFlags.Debug))
|
||||
return;
|
||||
|
||||
if (msg.Enabled)
|
||||
{
|
||||
_visPlayers.Add(session);
|
||||
VisSendFullStateImmediate(session);
|
||||
}
|
||||
else
|
||||
{
|
||||
_visPlayers.Remove(session);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Disconnected)
|
||||
_visPlayers.Remove(e.Session);
|
||||
}
|
||||
|
||||
public void QueueRemakeGroup(BaseNodeGroup group)
|
||||
{
|
||||
if (group.Remaking)
|
||||
return;
|
||||
|
||||
_toRemake.Add(group);
|
||||
group.Remaking = true;
|
||||
|
||||
foreach (var node in group.Nodes)
|
||||
{
|
||||
QueueReflood(node);
|
||||
}
|
||||
}
|
||||
|
||||
public void QueueReflood(Node node)
|
||||
{
|
||||
if (node.FlaggedForFlood)
|
||||
return;
|
||||
|
||||
_toReflood.Add(node);
|
||||
node.FlaggedForFlood = true;
|
||||
}
|
||||
|
||||
public void QueueNodeRemove(Node node)
|
||||
{
|
||||
_toRemove.Add(node);
|
||||
}
|
||||
|
||||
public void CreateSingleNetImmediate(Node node)
|
||||
{
|
||||
if (node.NodeGroup != null)
|
||||
return;
|
||||
|
||||
QueueReflood(node);
|
||||
|
||||
InitGroup(node, new List<Node> {node});
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
foreach (var group in _dirtyNodeGroups)
|
||||
DoGroupUpdates();
|
||||
VisDoUpdate();
|
||||
}
|
||||
|
||||
private void DoGroupUpdates()
|
||||
{
|
||||
// "Why is there a separate queue for group remakes and node refloods when they both cause eachother"
|
||||
// Future planning for the potential ability to do more intelligent group updating.
|
||||
|
||||
if (_toRemake.Count == 0 && _toReflood.Count == 0 && _toRemove.Count == 0)
|
||||
return;
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
foreach (var toRemove in _toRemove)
|
||||
{
|
||||
group.RemakeGroup();
|
||||
if (toRemove.NodeGroup == null)
|
||||
continue;
|
||||
|
||||
var group = (BaseNodeGroup) toRemove.NodeGroup;
|
||||
|
||||
group.RemoveNode(toRemove);
|
||||
toRemove.NodeGroup = null;
|
||||
|
||||
QueueRemakeGroup(group);
|
||||
}
|
||||
|
||||
_dirtyNodeGroups.Clear();
|
||||
// Break up all remaking groups.
|
||||
// Don't clear the list yet, we'll come back to these later.
|
||||
foreach (var toRemake in _toRemake)
|
||||
{
|
||||
QueueRemakeGroup(toRemake);
|
||||
}
|
||||
|
||||
_gen += 1;
|
||||
|
||||
// Go over all nodes to calculate reachable nodes and make an undirected graph out of them.
|
||||
// Node.GetReachableNodes() may return results asymmetrically,
|
||||
// i.e. node A may return B, but B may not return A.
|
||||
//
|
||||
// Must be for loop to allow concurrent modification from RemakeGroupImmediate.
|
||||
for (var i = 0; i < _toReflood.Count; i++)
|
||||
{
|
||||
var node = _toReflood[i];
|
||||
|
||||
if (node.Deleting)
|
||||
continue;
|
||||
|
||||
ClearReachableIfNecessary(node);
|
||||
|
||||
if (node.NodeGroup?.Remaking == false)
|
||||
{
|
||||
QueueRemakeGroup((BaseNodeGroup) node.NodeGroup);
|
||||
}
|
||||
|
||||
foreach (var compatible in GetCompatibleNodes(node))
|
||||
{
|
||||
ClearReachableIfNecessary(compatible);
|
||||
|
||||
if (compatible.NodeGroup?.Remaking == false)
|
||||
{
|
||||
// We are expanding into an existing group,
|
||||
// remake it so that we can treat it uniformly.
|
||||
var group = (BaseNodeGroup) compatible.NodeGroup;
|
||||
QueueRemakeGroup(group);
|
||||
}
|
||||
|
||||
node.ReachableNodes.Add(compatible);
|
||||
compatible.ReachableNodes.Add(node);
|
||||
}
|
||||
}
|
||||
|
||||
var newGroups = new List<BaseNodeGroup>();
|
||||
|
||||
// Flood fill over nodes. Every node will only be flood filled once.
|
||||
foreach (var node in _toReflood)
|
||||
{
|
||||
node.FlaggedForFlood = false;
|
||||
|
||||
// Check if already flood filled.
|
||||
if (node.FloodGen == _gen || node.Deleting)
|
||||
continue;
|
||||
|
||||
// Flood fill
|
||||
var groupNodes = FloodFillNode(node);
|
||||
|
||||
var newGroup = InitGroup(node, groupNodes);
|
||||
newGroups.Add(newGroup);
|
||||
}
|
||||
|
||||
// Go over dead groups that need to be cleaned up.
|
||||
// Tell them to push their data to new groups too.
|
||||
foreach (var oldGroup in _toRemake)
|
||||
{
|
||||
// Group by the NEW group.
|
||||
var newGrouped = oldGroup.Nodes.GroupBy(n => n.NodeGroup);
|
||||
|
||||
oldGroup.Removed = true;
|
||||
oldGroup.AfterRemake(newGrouped);
|
||||
if (VisEnabled)
|
||||
_visDeletes.Add(oldGroup.NetId);
|
||||
}
|
||||
|
||||
var refloodCount = _toReflood.Count;
|
||||
|
||||
_toReflood.Clear();
|
||||
_toRemake.Clear();
|
||||
_toRemove.Clear();
|
||||
|
||||
foreach (var group in newGroups)
|
||||
{
|
||||
foreach (var node in group.Nodes)
|
||||
{
|
||||
node.OnPostRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
_sawmill.Debug($"Updated node groups in {sw.Elapsed.TotalMilliseconds}ms. {newGroups.Count} new groups, {refloodCount} nodes processed.");
|
||||
}
|
||||
|
||||
private void ClearReachableIfNecessary(Node node)
|
||||
{
|
||||
if (node.UndirectGen != _gen)
|
||||
{
|
||||
node.ReachableNodes.Clear();
|
||||
node.UndirectGen = _gen;
|
||||
}
|
||||
}
|
||||
|
||||
private BaseNodeGroup InitGroup(Node node, List<Node> groupNodes)
|
||||
{
|
||||
var newGroup = (BaseNodeGroup) _nodeGroupFactory.MakeNodeGroup(node.NodeGroupID);
|
||||
newGroup.Initialize(node);
|
||||
newGroup.NetId = _groupNetIdCounter++;
|
||||
|
||||
var netIdCounter = 0;
|
||||
foreach (var groupNode in groupNodes)
|
||||
{
|
||||
groupNode.NodeGroup = newGroup;
|
||||
groupNode.NetId = ++netIdCounter;
|
||||
}
|
||||
|
||||
newGroup.LoadNodes(groupNodes);
|
||||
|
||||
if (VisEnabled)
|
||||
_visSends.Add(newGroup);
|
||||
|
||||
return newGroup;
|
||||
}
|
||||
|
||||
private List<Node> FloodFillNode(Node rootNode)
|
||||
{
|
||||
// All nodes we're filling into that currently have NO network.
|
||||
var allNodes = new List<Node>();
|
||||
|
||||
var stack = new Stack<Node>();
|
||||
stack.Push(rootNode);
|
||||
rootNode.FloodGen = _gen;
|
||||
|
||||
while (stack.TryPop(out var node))
|
||||
{
|
||||
allNodes.Add(node);
|
||||
|
||||
foreach (var reachable in node.ReachableNodes)
|
||||
{
|
||||
if (reachable.FloodGen == _gen)
|
||||
continue;
|
||||
|
||||
reachable.FloodGen = _gen;
|
||||
stack.Push(reachable);
|
||||
}
|
||||
}
|
||||
|
||||
return allNodes;
|
||||
}
|
||||
|
||||
private static IEnumerable<Node> GetCompatibleNodes(Node node)
|
||||
{
|
||||
foreach (var reachable in node.GetReachableNodes())
|
||||
{
|
||||
DebugTools.Assert(reachable != node, "GetReachableNodes() should not include self.");
|
||||
|
||||
if (reachable.Connectable && reachable.NodeGroupID == node.NodeGroupID)
|
||||
yield return reachable;
|
||||
}
|
||||
}
|
||||
|
||||
private void VisDoUpdate()
|
||||
{
|
||||
if (_visSends.Count == 0 && _visDeletes.Count == 0)
|
||||
return;
|
||||
|
||||
var msg = new NodeVis.MsgData();
|
||||
|
||||
msg.GroupDeletions.AddRange(_visDeletes);
|
||||
msg.Groups.AddRange(_visSends.Select(VisMakeGroupState));
|
||||
|
||||
_visSends.Clear();
|
||||
_visDeletes.Clear();
|
||||
|
||||
foreach (var player in _visPlayers)
|
||||
{
|
||||
RaiseNetworkEvent(msg, player.ConnectedClient);
|
||||
}
|
||||
}
|
||||
|
||||
private void VisSendFullStateImmediate(IPlayerSession player)
|
||||
{
|
||||
var msg = new NodeVis.MsgData();
|
||||
|
||||
var allNetworks = ComponentManager
|
||||
.EntityQuery<NodeContainerComponent>()
|
||||
.SelectMany(nc => nc.Nodes.Values)
|
||||
.Select(n => (BaseNodeGroup?) n.NodeGroup)
|
||||
.Where(n => n != null)
|
||||
.Distinct();
|
||||
|
||||
foreach (var network in allNetworks)
|
||||
{
|
||||
msg.Groups.Add(VisMakeGroupState(network!));
|
||||
}
|
||||
|
||||
RaiseNetworkEvent(msg, player.ConnectedClient);
|
||||
}
|
||||
|
||||
private static NodeVis.GroupData VisMakeGroupState(BaseNodeGroup group)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
NetId = group.NetId,
|
||||
GroupId = group.GroupId.ToString(),
|
||||
Color = CalcNodeGroupColor(group),
|
||||
Nodes = group.Nodes.Select(n => new NodeVis.NodeDatum
|
||||
{
|
||||
Name = n.Name,
|
||||
NetId = n.NetId,
|
||||
Reachable = n.ReachableNodes.Select(r => r.NetId).ToArray(),
|
||||
Entity = n.Owner.Uid,
|
||||
Type = n.GetType().Name
|
||||
}).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private static Color CalcNodeGroupColor(BaseNodeGroup group)
|
||||
{
|
||||
return group.GroupId switch
|
||||
{
|
||||
NodeGroupID.HVPower => Color.Orange,
|
||||
NodeGroupID.MVPower => Color.Yellow,
|
||||
NodeGroupID.Apc => Color.LimeGreen,
|
||||
NodeGroupID.AMEngine => Color.Purple,
|
||||
NodeGroupID.Pipe => Color.Blue,
|
||||
NodeGroupID.WireNet => Color.DarkMagenta,
|
||||
_ => Color.White
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,60 +20,18 @@ namespace Content.Server.NodeContainer
|
||||
{
|
||||
public override string Name => "NodeContainer";
|
||||
|
||||
[ViewVariables]
|
||||
public IReadOnlyDictionary<string, Node> Nodes => _nodes;
|
||||
[DataField("nodes")] [ViewVariables] public Dictionary<string, Node> Nodes { get; } = new();
|
||||
|
||||
[DataField("nodes")]
|
||||
private readonly Dictionary<string, Node> _nodes = new();
|
||||
|
||||
[DataField("examinable")]
|
||||
private bool _examinable = false;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
foreach (var node in _nodes.Values)
|
||||
{
|
||||
node.Initialize(Owner);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
foreach (var node in _nodes.Values)
|
||||
{
|
||||
node.OnContainerStartup();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
|
||||
foreach (var node in _nodes.Values)
|
||||
{
|
||||
node.OnContainerShutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public void AnchorUpdate()
|
||||
{
|
||||
foreach (var node in Nodes.Values)
|
||||
{
|
||||
node.AnchorUpdate();
|
||||
node.AnchorStateChanged();
|
||||
}
|
||||
}
|
||||
[DataField("examinable")] private bool _examinable = false;
|
||||
|
||||
public T GetNode<T>(string identifier) where T : Node
|
||||
{
|
||||
return (T)_nodes[identifier];
|
||||
return (T) Nodes[identifier];
|
||||
}
|
||||
|
||||
public bool TryGetNode<T>(string identifier, [NotNullWhen(true)] out T? node) where T : Node
|
||||
{
|
||||
if (_nodes.TryGetValue(identifier, out var n) && n is T t)
|
||||
if (Nodes.TryGetValue(identifier, out var n) && n is T t)
|
||||
{
|
||||
node = t;
|
||||
return true;
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Server.Power.Components;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
{
|
||||
public abstract class BaseNetConnectorNodeGroup<TNetConnector, TNetType> : BaseNodeGroup where TNetConnector : BaseNetConnectorComponent<TNetType>
|
||||
{
|
||||
private readonly Dictionary<Node, List<TNetConnector>> _netConnectorComponents = new();
|
||||
|
||||
protected override void OnAddNode(Node node)
|
||||
{
|
||||
var newNetConnectorComponents = node.Owner
|
||||
.GetAllComponents<TNetConnector>()
|
||||
.Where(powerComp => (NodeGroupID) powerComp.Voltage == node.NodeGroupID)
|
||||
.ToList();
|
||||
_netConnectorComponents[node] = newNetConnectorComponents;
|
||||
foreach (var netConnectorComponent in newNetConnectorComponents)
|
||||
{
|
||||
SetNetConnectorNet(netConnectorComponent);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void SetNetConnectorNet(TNetConnector netConnectorComponent);
|
||||
|
||||
protected override void OnRemoveNode(Node node)
|
||||
{
|
||||
foreach (var netConnectorComponent in _netConnectorComponents[node])
|
||||
{
|
||||
netConnectorComponent.ClearNet();
|
||||
}
|
||||
_netConnectorComponents.Remove(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Content.Server/NodeContainer/NodeGroups/BaseNodeGroup.cs
Normal file
91
Content.Server/NodeContainer/NodeGroups/BaseNodeGroup.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Maintains a collection of <see cref="Node"/>s, and performs operations requiring a list of
|
||||
/// all connected <see cref="Node"/>s.
|
||||
/// </summary>
|
||||
public interface INodeGroup
|
||||
{
|
||||
bool Remaking { get; }
|
||||
|
||||
IReadOnlyList<Node> Nodes { get; }
|
||||
|
||||
void Create(NodeGroupID groupId);
|
||||
|
||||
void Initialize(Node sourceNode);
|
||||
|
||||
void RemoveNode(Node node);
|
||||
|
||||
void LoadNodes(List<Node> groupNodes);
|
||||
|
||||
// In theory, the SS13 curse ensures this method will never be called.
|
||||
void AfterRemake(IEnumerable<IGrouping<INodeGroup?, Node>> newGroups);
|
||||
|
||||
// TODO: Why is this method needed?
|
||||
void QueueRemake();
|
||||
}
|
||||
|
||||
[NodeGroup(NodeGroupID.Default, NodeGroupID.WireNet)]
|
||||
public class BaseNodeGroup : INodeGroup
|
||||
{
|
||||
public bool Remaking { get; set; }
|
||||
|
||||
IReadOnlyList<Node> INodeGroup.Nodes => Nodes;
|
||||
|
||||
[ViewVariables] public readonly List<Node> Nodes = new();
|
||||
|
||||
[ViewVariables] public int NodeCount => Nodes.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Debug variable to indicate that this NodeGroup should not be being used by anything.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Removed { get; set; } = false;
|
||||
|
||||
[ViewVariables]
|
||||
protected GridId GridId { get; private set; }
|
||||
|
||||
[ViewVariables]
|
||||
public int NetId;
|
||||
|
||||
[ViewVariables]
|
||||
public NodeGroupID GroupId { get; private set; }
|
||||
|
||||
public void Create(NodeGroupID groupId)
|
||||
{
|
||||
GroupId = groupId;
|
||||
}
|
||||
|
||||
public virtual void Initialize(Node sourceNode)
|
||||
{
|
||||
// TODO: Can we get rid of this GridId?
|
||||
GridId = sourceNode.Owner.Transform.GridID;
|
||||
}
|
||||
|
||||
public virtual void RemoveNode(Node node)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void LoadNodes(
|
||||
List<Node> groupNodes)
|
||||
{
|
||||
Nodes.AddRange(groupNodes);
|
||||
}
|
||||
|
||||
public virtual void AfterRemake(IEnumerable<IGrouping<INodeGroup?, Node>> newGroups) { }
|
||||
|
||||
public void QueueRemake()
|
||||
{
|
||||
EntitySystem.Get<NodeGroupSystem>().QueueRemakeGroup(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,132 +0,0 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
{
|
||||
/// <summary>
|
||||
/// Maintains a collection of <see cref="Node"/>s, and performs operations requiring a list of
|
||||
/// all connected <see cref="Node"/>s.
|
||||
/// </summary>
|
||||
public interface INodeGroup
|
||||
{
|
||||
IReadOnlyList<Node> Nodes { get; }
|
||||
|
||||
void Initialize(Node sourceNode);
|
||||
|
||||
void AddNode(Node node);
|
||||
|
||||
void RemoveNode(Node node);
|
||||
|
||||
void CombineGroup(INodeGroup newGroup);
|
||||
|
||||
void RemakeGroup();
|
||||
}
|
||||
|
||||
[NodeGroup(NodeGroupID.Default, NodeGroupID.WireNet)]
|
||||
public class BaseNodeGroup : INodeGroup
|
||||
{
|
||||
[ViewVariables]
|
||||
public IReadOnlyList<Node> Nodes => _nodes;
|
||||
private readonly List<Node> _nodes = new();
|
||||
|
||||
[ViewVariables]
|
||||
public int NodeCount => Nodes.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Debug variable to indicate that this NodeGroup should not be being used by anything.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Removed { get; private set; } = false;
|
||||
|
||||
public static readonly INodeGroup NullGroup = new NullNodeGroup();
|
||||
|
||||
protected GridId GridId { get; private set;}
|
||||
|
||||
public virtual void Initialize(Node sourceNode)
|
||||
{
|
||||
GridId = sourceNode.Owner.Transform.GridID;
|
||||
}
|
||||
|
||||
public void AddNode(Node node)
|
||||
{
|
||||
_nodes.Add(node);
|
||||
OnAddNode(node);
|
||||
}
|
||||
|
||||
public void RemoveNode(Node node)
|
||||
{
|
||||
_nodes.Remove(node);
|
||||
OnRemoveNode(node);
|
||||
EntitySystem.Get<NodeGroupSystem>().AddDirtyNodeGroup(this);
|
||||
}
|
||||
|
||||
public void CombineGroup(INodeGroup newGroup)
|
||||
{
|
||||
if (newGroup.Nodes.Count < Nodes.Count)
|
||||
{
|
||||
newGroup.CombineGroup(this);
|
||||
return;
|
||||
}
|
||||
|
||||
OnGivingNodesForCombine(newGroup);
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
node.NodeGroup = newGroup;
|
||||
}
|
||||
|
||||
Removed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Causes all <see cref="Node"/>s to remake their groups. Called when a <see cref="Node"/> is removed
|
||||
/// and may have split a group in two, so multiple new groups may need to be formed.
|
||||
/// </summary>
|
||||
public void RemakeGroup()
|
||||
{
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
node.ClearNodeGroup();
|
||||
}
|
||||
|
||||
var newGroups = new HashSet<INodeGroup>();
|
||||
|
||||
foreach (var node in Nodes)
|
||||
{
|
||||
if (node.TryAssignGroupIfNeeded())
|
||||
{
|
||||
node.SpreadGroup();
|
||||
newGroups.Add(node.NodeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
AfterRemake(newGroups);
|
||||
|
||||
Removed = true;
|
||||
}
|
||||
|
||||
protected virtual void OnAddNode(Node node) { }
|
||||
|
||||
protected virtual void OnRemoveNode(Node node) { }
|
||||
|
||||
protected virtual void OnGivingNodesForCombine(INodeGroup newGroup) { }
|
||||
|
||||
protected virtual void AfterRemake(IEnumerable<INodeGroup> newGroups) { }
|
||||
|
||||
protected class NullNodeGroup : INodeGroup
|
||||
{
|
||||
public IReadOnlyList<Node> Nodes => _nodes;
|
||||
private readonly List<Node> _nodes = new();
|
||||
public void Initialize(Node sourceNode) { }
|
||||
public void AddNode(Node node) { }
|
||||
public void CombineGroup(INodeGroup newGroup) { }
|
||||
public void RemoveNode(Node node) { }
|
||||
public void RemakeGroup() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
{
|
||||
@@ -9,6 +10,7 @@ namespace Content.Server.NodeContainer.NodeGroups
|
||||
/// have the same type of <see cref="INodeGroup"/>. Used by <see cref="INodeGroupFactory"/>.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
[MeansImplicitUse]
|
||||
public class NodeGroupAttribute : Attribute
|
||||
{
|
||||
public NodeGroupID[] NodeGroupIDs { get; }
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace Content.Server.NodeContainer.NodeGroups
|
||||
/// <summary>
|
||||
/// Returns a new <see cref="INodeGroup"/> instance.
|
||||
/// </summary>
|
||||
INodeGroup MakeNodeGroup(Node sourceNode);
|
||||
INodeGroup MakeNodeGroup(NodeGroupID id);
|
||||
}
|
||||
|
||||
public class NodeGroupFactory : INodeGroupFactory
|
||||
@@ -45,15 +45,14 @@ namespace Content.Server.NodeContainer.NodeGroups
|
||||
}
|
||||
}
|
||||
|
||||
public INodeGroup MakeNodeGroup(Node sourceNode)
|
||||
public INodeGroup MakeNodeGroup(NodeGroupID id)
|
||||
{
|
||||
if (_groupTypes.TryGetValue(sourceNode.NodeGroupID, out var type))
|
||||
{
|
||||
var nodeGroup = _typeFactory.CreateInstance<INodeGroup>(type);
|
||||
nodeGroup.Initialize(sourceNode);
|
||||
return nodeGroup;
|
||||
}
|
||||
throw new ArgumentException($"{sourceNode.NodeGroupID} did not have an associated {nameof(INodeGroup)}.");
|
||||
if (!_groupTypes.TryGetValue(id, out var type))
|
||||
throw new ArgumentException($"{id} did not have an associated {nameof(INodeGroup)} implementation.");
|
||||
|
||||
var instance = _typeFactory.CreateInstance<INodeGroup>(type);
|
||||
instance.Create(id);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.Components;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.GameObjects.Components.NodeContainer.Nodes;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
@@ -24,17 +25,15 @@ namespace Content.Server.NodeContainer.NodeGroups
|
||||
[NodeGroup(NodeGroupID.Pipe)]
|
||||
public class PipeNet : BaseNodeGroup, IPipeNet
|
||||
{
|
||||
[ViewVariables]
|
||||
public GasMixture Air { get; set; } = new() {Temperature = Atmospherics.T20C};
|
||||
[ViewVariables] public GasMixture Air { get; set; } = new() {Temperature = Atmospherics.T20C};
|
||||
|
||||
public static readonly IPipeNet NullNet = new NullPipeNet();
|
||||
|
||||
[ViewVariables]
|
||||
private readonly List<PipeNode> _pipes = new();
|
||||
[ViewVariables] private readonly List<PipeNode> _pipes = new();
|
||||
|
||||
[ViewVariables] private AtmosphereSystem? _atmosphereSystem;
|
||||
|
||||
[ViewVariables] private IGridAtmosphereComponent? GridAtmos => _atmosphereSystem?.GetGridAtmosphere(GridId);
|
||||
[ViewVariables]
|
||||
private IGridAtmosphereComponent? GridAtmos =>
|
||||
_atmosphereSystem?.GetGridAtmosphere(GridId);
|
||||
|
||||
public override void Initialize(Node sourceNode)
|
||||
{
|
||||
@@ -49,35 +48,30 @@ namespace Content.Server.NodeContainer.NodeGroups
|
||||
_atmosphereSystem?.React(Air, this);
|
||||
}
|
||||
|
||||
protected override void OnAddNode(Node node)
|
||||
public override void LoadNodes(List<Node> groupNodes)
|
||||
{
|
||||
if (node is not PipeNode pipeNode)
|
||||
return;
|
||||
base.LoadNodes(groupNodes);
|
||||
|
||||
_pipes.Add(pipeNode);
|
||||
pipeNode.JoinPipeNet(this);
|
||||
Air.Volume += pipeNode.Volume;
|
||||
foreach (var node in groupNodes)
|
||||
{
|
||||
var pipeNode = (PipeNode) node;
|
||||
_pipes.Add(pipeNode);
|
||||
pipeNode.JoinPipeNet(this);
|
||||
Air.Volume += pipeNode.Volume;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRemoveNode(Node node)
|
||||
public override void RemoveNode(Node node)
|
||||
{
|
||||
RemoveFromGridAtmos();
|
||||
if (node is not PipeNode pipeNode)
|
||||
return;
|
||||
base.RemoveNode(node);
|
||||
|
||||
pipeNode.ClearPipeNet();
|
||||
var pipeNode = (PipeNode) node;
|
||||
Air.Volume -= pipeNode.Volume;
|
||||
// TODO: Bad O(n^2)
|
||||
_pipes.Remove(pipeNode);
|
||||
}
|
||||
|
||||
protected override void OnGivingNodesForCombine(INodeGroup newGroup)
|
||||
{
|
||||
if (newGroup is not IPipeNet newPipeNet)
|
||||
return;
|
||||
|
||||
EntitySystem.Get<AtmosphereSystem>().Merge(newPipeNet.Air, Air);
|
||||
}
|
||||
|
||||
protected override void AfterRemake(IEnumerable<INodeGroup> newGroups)
|
||||
public override void AfterRemake(IEnumerable<IGrouping<INodeGroup?, Node>> newGroups)
|
||||
{
|
||||
RemoveFromGridAtmos();
|
||||
|
||||
@@ -86,14 +80,15 @@ namespace Content.Server.NodeContainer.NodeGroups
|
||||
|
||||
foreach (var newGroup in newGroups)
|
||||
{
|
||||
if (newGroup is not IPipeNet newPipeNet)
|
||||
if (newGroup.Key is not IPipeNet newPipeNet)
|
||||
continue;
|
||||
|
||||
var newAir = newPipeNet.Air;
|
||||
var newVolume = newGroup.Cast<PipeNode>().Sum(n => n.Volume);
|
||||
|
||||
buffer.Clear();
|
||||
atmosphereSystem.Merge(buffer, Air);
|
||||
buffer.Multiply(MathF.Min(newAir.Volume / Air.Volume, 1f));
|
||||
buffer.Multiply(MathF.Min(newVolume / Air.Volume, 1f));
|
||||
atmosphereSystem.Merge(newAir, buffer);
|
||||
}
|
||||
}
|
||||
@@ -102,20 +97,5 @@ namespace Content.Server.NodeContainer.NodeGroups
|
||||
{
|
||||
GridAtmos?.RemovePipeNet(this);
|
||||
}
|
||||
|
||||
private class NullPipeNet : NullNodeGroup, IPipeNet
|
||||
{
|
||||
private readonly GasMixture _air;
|
||||
|
||||
GasMixture IGasMixtureHolder.Air { get => _air; set { } }
|
||||
|
||||
public NullPipeNet()
|
||||
{
|
||||
_air = new GasMixture(1f) {Temperature = Atmospherics.T20C};
|
||||
_air.MarkImmutable();
|
||||
}
|
||||
|
||||
public void Update() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Content.Server.Power.Components;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.NodeContainer.NodeGroups
|
||||
{
|
||||
public interface IPowerNet
|
||||
{
|
||||
void AddSupplier(PowerSupplierComponent supplier);
|
||||
|
||||
void RemoveSupplier(PowerSupplierComponent supplier);
|
||||
|
||||
void UpdateSupplierSupply(PowerSupplierComponent supplier, int oldSupplyRate, int newSupplyRate);
|
||||
|
||||
void AddConsumer(PowerConsumerComponent consumer);
|
||||
|
||||
void RemoveConsumer(PowerConsumerComponent consumer);
|
||||
|
||||
void UpdateConsumerDraw(PowerConsumerComponent consumer, int oldDrawRate, int newDrawRate);
|
||||
|
||||
void UpdateConsumerPriority(PowerConsumerComponent consumer, Priority oldPriority, Priority newPriority);
|
||||
|
||||
void UpdateConsumerReceivedPower();
|
||||
}
|
||||
|
||||
[NodeGroup(NodeGroupID.HVPower, NodeGroupID.MVPower)]
|
||||
public class PowerNetNodeGroup : BaseNetConnectorNodeGroup<BasePowerNetComponent, IPowerNet>, IPowerNet
|
||||
{
|
||||
private static readonly Priority[] CachedPriorities = (Priority[]) Enum.GetValues(typeof(Priority));
|
||||
|
||||
[Dependency] private readonly IPowerNetManager _powerNetManager = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly List<PowerSupplierComponent> _suppliers = new();
|
||||
|
||||
[ViewVariables]
|
||||
private int _totalSupply = 0;
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Priority, List<PowerConsumerComponent>> _consumersByPriority = new();
|
||||
|
||||
[ViewVariables]
|
||||
private readonly Dictionary<Priority, int> _drawByPriority = new();
|
||||
|
||||
public static readonly IPowerNet NullNet = new NullPowerNet();
|
||||
|
||||
public PowerNetNodeGroup()
|
||||
{
|
||||
foreach (Priority priority in Enum.GetValues(typeof(Priority)))
|
||||
{
|
||||
_consumersByPriority.Add(priority, new List<PowerConsumerComponent>());
|
||||
_drawByPriority.Add(priority, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void SetNetConnectorNet(BasePowerNetComponent netConnectorComponent)
|
||||
{
|
||||
netConnectorComponent.Net = this;
|
||||
}
|
||||
|
||||
#region IPowerNet Methods
|
||||
|
||||
public void AddSupplier(PowerSupplierComponent supplier)
|
||||
{
|
||||
_suppliers.Add(supplier);
|
||||
_totalSupply += supplier.SupplyRate;
|
||||
_powerNetManager.AddDirtyPowerNet(this);
|
||||
}
|
||||
|
||||
public void RemoveSupplier(PowerSupplierComponent supplier)
|
||||
{
|
||||
Debug.Assert(_suppliers.Contains(supplier));
|
||||
_suppliers.Remove(supplier);
|
||||
_totalSupply -= supplier.SupplyRate;
|
||||
_powerNetManager.AddDirtyPowerNet(this);
|
||||
}
|
||||
|
||||
public void UpdateSupplierSupply(PowerSupplierComponent supplier, int oldSupplyRate, int newSupplyRate)
|
||||
{
|
||||
Debug.Assert(_suppliers.Contains(supplier));
|
||||
_totalSupply -= oldSupplyRate;
|
||||
_totalSupply += newSupplyRate;
|
||||
_powerNetManager.AddDirtyPowerNet(this);
|
||||
}
|
||||
|
||||
public void AddConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
_consumersByPriority[consumer.Priority].Add(consumer);
|
||||
_drawByPriority[consumer.Priority] += consumer.DrawRate;
|
||||
_powerNetManager.AddDirtyPowerNet(this);
|
||||
}
|
||||
|
||||
public void RemoveConsumer(PowerConsumerComponent consumer)
|
||||
{
|
||||
Debug.Assert(_consumersByPriority[consumer.Priority].Contains(consumer));
|
||||
consumer.ReceivedPower = 0;
|
||||
_consumersByPriority[consumer.Priority].Remove(consumer);
|
||||
_drawByPriority[consumer.Priority] -= consumer.DrawRate;
|
||||
_powerNetManager.AddDirtyPowerNet(this);
|
||||
}
|
||||
|
||||
public void UpdateConsumerDraw(PowerConsumerComponent consumer, int oldDrawRate, int newDrawRate)
|
||||
{
|
||||
Debug.Assert(_consumersByPriority[consumer.Priority].Contains(consumer));
|
||||
_drawByPriority[consumer.Priority] -= oldDrawRate;
|
||||
_drawByPriority[consumer.Priority] += newDrawRate;
|
||||
_powerNetManager.AddDirtyPowerNet(this);
|
||||
}
|
||||
|
||||
public void UpdateConsumerPriority(PowerConsumerComponent consumer, Priority oldPriority, Priority newPriority)
|
||||
{
|
||||
Debug.Assert(_consumersByPriority[oldPriority].Contains(consumer));
|
||||
_consumersByPriority[oldPriority].Remove(consumer);
|
||||
_drawByPriority[oldPriority] -= consumer.DrawRate;
|
||||
_consumersByPriority[newPriority].Add(consumer);
|
||||
_drawByPriority[newPriority] += consumer.DrawRate;
|
||||
_powerNetManager.AddDirtyPowerNet(this);
|
||||
}
|
||||
|
||||
public void UpdateConsumerReceivedPower()
|
||||
{
|
||||
var remainingSupply = _totalSupply;
|
||||
foreach (var priority in CachedPriorities)
|
||||
{
|
||||
var categoryPowerDemand = _drawByPriority[priority];
|
||||
if (remainingSupply >= categoryPowerDemand) //can fully power all in category
|
||||
{
|
||||
remainingSupply -= categoryPowerDemand;
|
||||
foreach (var consumer in _consumersByPriority[priority])
|
||||
{
|
||||
consumer.ReceivedPower = consumer.DrawRate;
|
||||
}
|
||||
}
|
||||
else //cannot fully power all, split power
|
||||
{
|
||||
var availiablePowerFraction = (float) remainingSupply / categoryPowerDemand;
|
||||
remainingSupply = 0;
|
||||
foreach (var consumer in _consumersByPriority[priority])
|
||||
{
|
||||
consumer.ReceivedPower = (int) (consumer.DrawRate * availiablePowerFraction); //give each consumer a fraction of what they requested (rounded down to nearest int)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private class NullPowerNet : IPowerNet
|
||||
{
|
||||
public void AddConsumer(PowerConsumerComponent consumer) { }
|
||||
public void AddSupplier(PowerSupplierComponent supplier) { }
|
||||
public void UpdateSupplierSupply(PowerSupplierComponent supplier, int oldSupplyRate, int newSupplyRate) { }
|
||||
public void RemoveConsumer(PowerConsumerComponent consumer) { }
|
||||
public void RemoveSupplier(PowerSupplierComponent supplier) { }
|
||||
public void UpdateConsumerDraw(PowerConsumerComponent consumer, int oldDrawRate, int newDrawRate) { }
|
||||
public void UpdateConsumerPriority(PowerConsumerComponent consumer, Priority oldPriority, Priority newPriority) { }
|
||||
public void UpdateConsumerReceivedPower() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
@@ -12,29 +13,19 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
[DataDefinition]
|
||||
public class AdjacentNode : Node
|
||||
{
|
||||
protected override IEnumerable<Node> GetReachableNodes()
|
||||
public override IEnumerable<Node> GetReachableNodes()
|
||||
{
|
||||
if (!Owner.Transform.Anchored)
|
||||
yield break;
|
||||
|
||||
var compMgr = IoCManager.Resolve<IComponentManager>();
|
||||
var grid = IoCManager.Resolve<IMapManager>().GetGrid(Owner.Transform.GridID);
|
||||
var coords = Owner.Transform.Coordinates;
|
||||
foreach (var cell in grid.GetCardinalNeighborCells(coords))
|
||||
var gridIndex = grid.TileIndicesFor(Owner.Transform.Coordinates);
|
||||
|
||||
foreach (var (_, node) in NodeHelpers.GetCardinalNeighborNodes(compMgr, grid, gridIndex))
|
||||
{
|
||||
foreach (var entity in grid.GetLocal(Owner.EntityManager.GetEntity(cell).Transform.Coordinates))
|
||||
{
|
||||
if (!Owner.EntityManager.GetEntity(entity).TryGetComponent<NodeContainerComponent>(out var container))
|
||||
continue;
|
||||
|
||||
foreach (var node in container.Nodes.Values)
|
||||
{
|
||||
if (node != null && node != this)
|
||||
{
|
||||
yield return node;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (node != this)
|
||||
yield return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -26,20 +23,14 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
[DataField("nodeGroupID")]
|
||||
public NodeGroupID NodeGroupID { get; private set; } = NodeGroupID.Default;
|
||||
|
||||
[ViewVariables]
|
||||
public INodeGroup NodeGroup { get => _nodeGroup; set => SetNodeGroup(value); }
|
||||
private INodeGroup _nodeGroup = BaseNodeGroup.NullGroup;
|
||||
[ViewVariables] public INodeGroup? NodeGroup;
|
||||
|
||||
[ViewVariables]
|
||||
public IEntity Owner { get; private set; } = default!;
|
||||
|
||||
[ViewVariables]
|
||||
private bool _needsGroup = true;
|
||||
[ViewVariables] public IEntity Owner { get; private set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// If this node should be considered for connection by other nodes.
|
||||
/// </summary>
|
||||
public bool Connectable => !_deleting && Anchored;
|
||||
public bool Connectable => !Deleting && Anchored;
|
||||
|
||||
protected bool Anchored => !NeedAnchored || Owner.Transform.Anchored;
|
||||
|
||||
@@ -50,7 +41,15 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
/// <summary>
|
||||
/// Prevents a node from being used by other nodes while midway through removal.
|
||||
/// </summary>
|
||||
private bool _deleting;
|
||||
public bool Deleting;
|
||||
|
||||
public readonly HashSet<Node> ReachableNodes = new();
|
||||
|
||||
internal int FloodGen;
|
||||
internal int UndirectGen;
|
||||
internal bool FlaggedForFlood;
|
||||
internal int NetId;
|
||||
public string Name = default!;
|
||||
|
||||
public virtual void Initialize(IEntity owner)
|
||||
{
|
||||
@@ -59,20 +58,23 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
|
||||
public virtual void OnContainerStartup()
|
||||
{
|
||||
TryAssignGroupIfNeeded();
|
||||
CombineGroupWithReachable();
|
||||
EntitySystem.Get<NodeGroupSystem>().QueueReflood(this);
|
||||
}
|
||||
|
||||
public void CreateSingleNetImmediate()
|
||||
{
|
||||
EntitySystem.Get<NodeGroupSystem>().CreateSingleNetImmediate(this);
|
||||
}
|
||||
|
||||
public void AnchorUpdate()
|
||||
{
|
||||
if (Anchored)
|
||||
{
|
||||
TryAssignGroupIfNeeded();
|
||||
CombineGroupWithReachable();
|
||||
EntitySystem.Get<NodeGroupSystem>().QueueReflood(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
RemoveSelfFromGroup();
|
||||
EntitySystem.Get<NodeGroupSystem>().QueueNodeRemove(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,107 +82,21 @@ namespace Content.Server.NodeContainer.Nodes
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void OnPostRebuild()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void OnContainerShutdown()
|
||||
{
|
||||
_deleting = true;
|
||||
NodeGroup.RemoveNode(this);
|
||||
}
|
||||
|
||||
public bool TryAssignGroupIfNeeded()
|
||||
{
|
||||
if (!_needsGroup || !Connectable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
NodeGroup = GetReachableCompatibleGroups().FirstOrDefault() ?? MakeNewGroup();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SpreadGroup()
|
||||
{
|
||||
Debug.Assert(!_needsGroup);
|
||||
foreach (var node in GetReachableCompatibleNodes())
|
||||
{
|
||||
if (node._needsGroup)
|
||||
{
|
||||
node.NodeGroup = NodeGroup;
|
||||
node.SpreadGroup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearNodeGroup()
|
||||
{
|
||||
_nodeGroup = BaseNodeGroup.NullGroup;
|
||||
_needsGroup = true;
|
||||
}
|
||||
|
||||
protected void RefreshNodeGroup()
|
||||
{
|
||||
RemoveSelfFromGroup();
|
||||
TryAssignGroupIfNeeded();
|
||||
CombineGroupWithReachable();
|
||||
Deleting = true;
|
||||
EntitySystem.Get<NodeGroupSystem>().QueueNodeRemove(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How this node will attempt to find other reachable <see cref="Node"/>s to group with.
|
||||
/// Returns a set of <see cref="Node"/>s to consider grouping with. Should not return this current <see cref="Node"/>.
|
||||
/// </summary>
|
||||
protected abstract IEnumerable<Node> GetReachableNodes();
|
||||
|
||||
private IEnumerable<Node> GetReachableCompatibleNodes()
|
||||
{
|
||||
foreach (var node in GetReachableNodes())
|
||||
{
|
||||
if (node.NodeGroupID == NodeGroupID && node.Connectable)
|
||||
{
|
||||
yield return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<INodeGroup> GetReachableCompatibleGroups()
|
||||
{
|
||||
foreach (var node in GetReachableCompatibleNodes())
|
||||
{
|
||||
if (!node._needsGroup)
|
||||
{
|
||||
var group = node.NodeGroup;
|
||||
if (group != NodeGroup)
|
||||
{
|
||||
yield return group;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CombineGroupWithReachable()
|
||||
{
|
||||
if (_needsGroup || !Connectable)
|
||||
return;
|
||||
|
||||
foreach (var group in GetReachableCompatibleGroups())
|
||||
{
|
||||
NodeGroup.CombineGroup(group);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetNodeGroup(INodeGroup newGroup)
|
||||
{
|
||||
_nodeGroup = newGroup;
|
||||
NodeGroup.AddNode(this);
|
||||
_needsGroup = false;
|
||||
}
|
||||
|
||||
private INodeGroup MakeNewGroup()
|
||||
{
|
||||
return IoCManager.Resolve<INodeGroupFactory>().MakeNodeGroup(this);
|
||||
}
|
||||
|
||||
private void RemoveSelfFromGroup()
|
||||
{
|
||||
NodeGroup.RemoveNode(this);
|
||||
ClearNodeGroup();
|
||||
}
|
||||
public abstract IEnumerable<Node> GetReachableNodes();
|
||||
}
|
||||
}
|
||||
|
||||
89
Content.Server/NodeContainer/Nodes/NodeHelpers.cs
Normal file
89
Content.Server/NodeContainer/Nodes/NodeHelpers.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Content.Server.NodeContainer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper utilities for implementing <see cref="Node"/>.
|
||||
/// </summary>
|
||||
public static class NodeHelpers
|
||||
{
|
||||
public static IEnumerable<Node> GetNodesInTile(IComponentManager compMgr, IMapGrid grid, Vector2i coords)
|
||||
{
|
||||
foreach (var entityUid in grid.GetAnchoredEntities(coords))
|
||||
{
|
||||
if (!compMgr.TryGetComponent(entityUid, out NodeContainerComponent? container))
|
||||
continue;
|
||||
|
||||
foreach (var node in container.Nodes.Values)
|
||||
{
|
||||
yield return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<(Direction dir, Node node)> GetCardinalNeighborNodes(
|
||||
IComponentManager compMgr,
|
||||
IMapGrid grid,
|
||||
Vector2i coords,
|
||||
bool includeSameTile = true)
|
||||
{
|
||||
foreach (var (dir, entityUid) in GetCardinalNeighborCells(grid, coords, includeSameTile))
|
||||
{
|
||||
if (!compMgr.TryGetComponent(entityUid, out NodeContainerComponent? container))
|
||||
continue;
|
||||
|
||||
foreach (var node in container.Nodes.Values)
|
||||
{
|
||||
yield return (dir, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "EnforceForeachStatementBraces")]
|
||||
public static IEnumerable<(Direction dir, EntityUid entity)> GetCardinalNeighborCells(
|
||||
IMapGrid grid,
|
||||
Vector2i coords,
|
||||
bool includeSameTile = true)
|
||||
{
|
||||
if (includeSameTile)
|
||||
{
|
||||
foreach (var uid in grid.GetAnchoredEntities(coords))
|
||||
yield return (Direction.Invalid, uid);
|
||||
}
|
||||
|
||||
foreach (var uid in grid.GetAnchoredEntities(coords + (0, 1)))
|
||||
yield return (Direction.North, uid);
|
||||
|
||||
foreach (var uid in grid.GetAnchoredEntities(coords + (0, -1)))
|
||||
yield return (Direction.South, uid);
|
||||
|
||||
foreach (var uid in grid.GetAnchoredEntities(coords + (1, 0)))
|
||||
yield return (Direction.East, uid);
|
||||
|
||||
foreach (var uid in grid.GetAnchoredEntities(coords + (-1, 0)))
|
||||
yield return (Direction.West, uid);
|
||||
}
|
||||
|
||||
public static Vector2i TileOffsetForDir(Direction dir)
|
||||
{
|
||||
return dir switch
|
||||
{
|
||||
Direction.Invalid => (0, 0),
|
||||
Direction.South => (0, -1),
|
||||
Direction.SouthEast => (1, -1),
|
||||
Direction.East => (1, 0),
|
||||
Direction.NorthEast => (1, 1),
|
||||
Direction.North => (0, 1),
|
||||
Direction.NorthWest => (-1, 1),
|
||||
Direction.West => (-1, 0),
|
||||
Direction.SouthWest => (-1, -1),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(dir), dir, null)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,8 @@ using System.Collections.Generic;
|
||||
using Content.Server.Atmos;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Interfaces;
|
||||
using Content.Server.NodeContainer;
|
||||
using Content.Server.NodeContainer.EntitySystems;
|
||||
using Content.Server.NodeContainer.NodeGroups;
|
||||
using Content.Server.NodeContainer.Nodes;
|
||||
using Content.Shared.Atmos;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Shared.Containers;
|
||||
@@ -13,9 +12,10 @@ using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
|
||||
namespace Content.Server.NodeContainer.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects with other <see cref="PipeNode"/>s whose <see cref="PipeDirection"/>
|
||||
@@ -63,21 +63,23 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
|
||||
set
|
||||
{
|
||||
_connectionsEnabled = value;
|
||||
RefreshNodeGroup();
|
||||
|
||||
if (NodeGroup != null)
|
||||
EntitySystem.Get<NodeGroupSystem>().QueueRemakeGroup((BaseNodeGroup) NodeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
[DataField("connectionsEnabled")]
|
||||
private bool _connectionsEnabled = true;
|
||||
|
||||
[DataField("rotationsEnabled")]
|
||||
public bool RotationsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPipeNet"/> this pipe is a part of. Set to <see cref="PipeNet.NullNet"/> when not in an <see cref="IPipeNet"/>.
|
||||
/// The <see cref="IPipeNet"/> this pipe is a part of.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
private IPipeNet _pipeNet = PipeNet.NullNet;
|
||||
|
||||
[DataField("connectionsEnabled")]
|
||||
private bool _connectionsEnabled = true;
|
||||
private IPipeNet? PipeNet => (IPipeNet?) NodeGroup;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to ignore the pipenet and return the environment's air.
|
||||
@@ -92,8 +94,12 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
|
||||
[ViewVariables]
|
||||
public GasMixture Air
|
||||
{
|
||||
get => !EnvironmentalAir ? _pipeNet.Air : Owner.Transform.Coordinates.GetTileAir() ?? GasMixture.SpaceGas;
|
||||
set => _pipeNet.Air = value;
|
||||
get => (!EnvironmentalAir ? PipeNet?.Air : Owner.Transform.Coordinates.GetTileAir()) ?? GasMixture.SpaceGas;
|
||||
set
|
||||
{
|
||||
DebugTools.Assert(PipeNet != null);
|
||||
PipeNet!.Air = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void AssumeAir(GasMixture giver)
|
||||
@@ -105,7 +111,7 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
|
||||
return;
|
||||
}
|
||||
|
||||
EntitySystem.Get<AtmosphereSystem>().Merge(_pipeNet.Air, giver);
|
||||
EntitySystem.Get<AtmosphereSystem>().Merge(PipeNet!.Air, giver);
|
||||
}
|
||||
|
||||
[ViewVariables]
|
||||
@@ -128,13 +134,6 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
|
||||
|
||||
public void JoinPipeNet(IPipeNet pipeNet)
|
||||
{
|
||||
_pipeNet = pipeNet;
|
||||
OnConnectedDirectionsNeedsUpdating();
|
||||
}
|
||||
|
||||
public void ClearPipeNet()
|
||||
{
|
||||
_pipeNet = PipeNet.NullNet;
|
||||
OnConnectedDirectionsNeedsUpdating();
|
||||
}
|
||||
|
||||
@@ -146,12 +145,11 @@ namespace Content.Server.GameObjects.Components.NodeContainer.Nodes
|
||||
if (!RotationsEnabled) return;
|
||||
var diff = ev.NewRotation - ev.OldRotation;
|
||||
PipeDirection = PipeDirection.RotatePipeDirection(diff);
|
||||
RefreshNodeGroup();
|
||||
OnConnectedDirectionsNeedsUpdating();
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
protected override IEnumerable<Node> GetReachableNodes()
|
||||
public override IEnumerable<Node> GetReachableNodes()
|
||||
{
|
||||
for (var i = 0; i < PipeDirectionHelpers.AllPipeDirections; i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user