#nullable enable using System.Collections.Generic; using System.Diagnostics; using System.Linq; using Content.Server.GameObjects.Components.NodeContainer.NodeGroups; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace Content.Server.GameObjects.Components.NodeContainer.Nodes { /// /// Organizes themselves into distinct s with other s /// that they can "reach" and have the same . /// public abstract class Node : IExposeData { /// /// An ID used as a criteria for combining into groups. Determines which /// implementation is used as a group, detailed in . /// [ViewVariables] public NodeGroupID NodeGroupID { get; private set; } [ViewVariables] public INodeGroup NodeGroup { get => _nodeGroup; set => SetNodeGroup(value); } private INodeGroup _nodeGroup = BaseNodeGroup.NullGroup; [ViewVariables] public IEntity Owner { get; private set; } = default!; [ViewVariables] private bool _needsGroup = true; /// /// If this node should be considered for connection by other nodes. /// public bool Connectable => !_deleting && Anchored; private bool Anchored => !Owner.TryGetComponent(out var physics) || physics.Anchored; /// /// Prevents a node from being used by other nodes while midway through removal. /// private bool _deleting = false; public virtual void ExposeData(ObjectSerializer serializer) { serializer.DataField(this, x => x.NodeGroupID, "nodeGroupID", NodeGroupID.Default); } public virtual void Initialize(IEntity owner) { Owner = owner; } public virtual void OnContainerStartup() { TryAssignGroupIfNeeded(); CombineGroupWithReachable(); } public void AnchorUpdate() { if (Anchored) { TryAssignGroupIfNeeded(); CombineGroupWithReachable(); } else { RemoveSelfFromGroup(); } } public virtual void OnContainerRemove() { _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(); } /// /// 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 . /// protected abstract IEnumerable GetReachableNodes(); private IEnumerable GetReachableCompatibleNodes() { foreach (var node in GetReachableNodes()) { if (node.NodeGroupID == NodeGroupID && node.Connectable) { yield return node; } } } private IEnumerable 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().MakeNodeGroup(this); } private void RemoveSelfFromGroup() { NodeGroup.RemoveNode(this); ClearNodeGroup(); } } }