using System.Diagnostics.CodeAnalysis;
using Content.Shared.Xenoarchaeology.Artifact.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Shared.Xenoarchaeology.Artifact;
///
/// User-friendly API for viewing and modifying the complex graph relationship in XenoArtifacts
///
public abstract partial class SharedXenoArtifactSystem
{
///
/// Gets the index, corresponding to a given node, throwing if the node is not present.
///
public int GetIndex(Entity ent, EntityUid node)
{
if (TryGetIndex((ent, ent), node, out var index))
{
return index.Value;
}
throw new ArgumentException($"node {ToPrettyString(node)} is not present in {ToPrettyString(ent)}");
}
///
/// Tries to get index inside nodes collection, corresponding to a given node EntityUid.
///
public bool TryGetIndex(Entity ent, EntityUid node, [NotNullWhen(true)] out int? index)
{
index = null;
if (!Resolve(ent, ref ent.Comp))
return false;
for (var i = 0; i < ent.Comp.NodeVertices.Length; i++)
{
if (!TryGetNode(ent, i, out var iNode))
continue;
if (node != iNode.Value.Owner)
continue;
index = i;
return true;
}
return false;
}
///
/// Gets node entity with node component from artifact by index of node inside artifact nodes collection.
///
/// Throws if requested index doesn't exist on artifact.
public Entity GetNode(Entity ent, int index)
{
if (ent.Comp.NodeVertices[index] is { } netUid && GetEntity(netUid) is var uid && _nodeQuery.TryComp(uid, out var comp))
return (uid, comp);
throw new ArgumentException($"index {index} does not correspond to an existing node in {ToPrettyString(ent)}");
}
///
/// Tries to get node entity with node component from artifact by index of node inside artifact nodes collection.
///
public bool TryGetNode(Entity ent, int index, [NotNullWhen(true)] out Entity? node)
{
node = null;
if (!Resolve(ent, ref ent.Comp))
return false;
if (index < 0 || index >= ent.Comp.NodeVertices.Length)
return false;
if (ent.Comp.NodeVertices[index] is { } netUid && GetEntity(netUid) is var uid && _nodeQuery.TryComp(uid, out var comp))
node = (uid, comp);
return node != null;
}
///
/// Gets the index of the first empty spot in the NodeVertices array.
/// If there is none, resizes both arrays and returns the new index.
///
public int GetFreeNodeIndex(Entity ent)
{
var length = ent.Comp.NodeVertices.Length;
for (var i = 0; i < length; i++)
{
if (ent.Comp.NodeVertices[i] == null)
return i;
}
ResizeNodeGraph(ent, length + 1);
return length;
}
///
/// Extracts node entities from artifact container
/// (uses pre-cached and mapping from NetEntity).
///
public IEnumerable> GetAllNodes(Entity ent)
{
foreach (var netNode in ent.Comp.NodeVertices)
{
if (TryGetEntity(netNode, out var node) && _nodeQuery.TryComp(node, out var comp))
yield return (node.Value, comp);
}
}
///
/// Extracts enumeration of all indices that artifact node container have.
///
public IEnumerable GetAllNodeIndices(Entity ent)
{
for (var i = 0; i < ent.Comp.NodeVertices.Length; i++)
{
if (ent.Comp.NodeVertices[i] is not null)
yield return i;
}
}
///
/// Adds edge between artifact nodes - and
///
/// Artifact entity that contains 'from' and 'to' node entities.
/// Node from which we need to draw edge.
/// Node to which we need to draw edge.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
/// True if adding edge was successful, false otherwise.
public bool AddEdge(Entity ent, EntityUid from, EntityUid to, bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp))
return false;
if (!TryGetIndex(ent, from, out var fromIdx) ||
!TryGetIndex(ent, to, out var toIdx))
return false;
return AddEdge(ent, fromIdx.Value, toIdx.Value, dirty: dirty);
}
///
/// Adds edge between artifact nodes by indices inside node container - and
///
/// Artifact entity that contains 'from' and 'to' node entities.
/// Node index inside artifact node container, from which we need to draw edge.
/// Node index inside artifact node container, to which we need to draw edge.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
/// True if adding edge was successful, false otherwise.
public bool AddEdge(Entity ent, int fromIdx, int toIdx, bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp))
return false;
DebugTools.Assert(fromIdx >= 0 && fromIdx < ent.Comp.NodeVertices.Length, $"fromIdx is out of bounds for fromIdx {fromIdx}");
DebugTools.Assert(toIdx >= 0 && toIdx < ent.Comp.NodeVertices.Length, $"toIdx is out of bounds for toIdx {toIdx}");
if (ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx])
return false; //Edge already exists
// TODO: add a safety check to prohibit cyclic paths.
ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx] = true;
if (dirty)
{
RebuildXenoArtifactMetaData(ent);
}
return true;
}
///
/// Removes edge between artifact nodes.
///
/// Artifact entity that contains 'from' and 'to' node entities.
/// Entity of node from which edge to remove is connected.
/// Entity of node to which edge to remove is connected.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
/// True if removed edge was successfully, false otherwise.
public bool RemoveEdge(Entity ent, EntityUid from, EntityUid to, bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp))
return false;
if (!TryGetIndex(ent, from, out var fromIdx) ||
!TryGetIndex(ent, to, out var toIdx))
return false;
return RemoveEdge(ent, fromIdx.Value, toIdx.Value, dirty);
}
///
/// Removes edge between artifact nodes.
///
/// Artifact entity that contains 'from' and 'to' node entities.
/// First node index inside artifact node container, from which we need to remove connecting edge.
/// Other node index inside artifact node container, from which we need to remove connecting edge.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
/// True if removed edge was successfully, false otherwise.
public bool RemoveEdge(Entity ent, int fromIdx, int toIdx, bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp))
return false;
DebugTools.Assert(fromIdx >= 0 && fromIdx < ent.Comp.NodeVertices.Length, $"fromIdx is out of bounds for fromIdx {fromIdx}");
DebugTools.Assert(toIdx >= 0 && toIdx < ent.Comp.NodeVertices.Length, $"toIdx is out of bounds for toIdx {toIdx}");
if (!ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx])
return false; //Edge doesn't exist
ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx] = false;
if (dirty)
{
RebuildXenoArtifactMetaData(ent);
}
return true;
}
///
/// Creates node entity (spawns) and adds node into artifact node container.
///
/// Artifact entity, to container of which node should be added.
/// EntProtoId of node to be added.
/// Created node or null.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
/// True if node creation and adding was successful, false otherwise.
public bool AddNode(
Entity ent,
EntProtoId entProtoId,
[NotNullWhen(true)] out Entity? node,
bool dirty = true
)
{
node = null;
if (!Resolve(ent, ref ent.Comp))
return false;
var uid = Spawn(entProtoId);
var comp = EnsureComp(uid);
node = (uid, comp);
return AddNode(ent, (node.Value, node.Value.Comp), dirty: dirty);
}
///
/// Adds node entity to artifact node container.
///
/// Artifact entity, to container of which node should be added.
/// Node entity to add.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
/// True if node adding was successful, false otherwise.
public bool AddNode(Entity ent, Entity node, bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp) || !Resolve(node, ref node.Comp, false))
return false;
node.Comp.Attached = ent.Owner;
var nodeIdx = GetFreeNodeIndex((ent, ent.Comp));
_container.Insert(node.Owner, ent.Comp.NodeContainer);
ent.Comp.NodeVertices[nodeIdx] = GetNetEntity(node);
Dirty(node);
if (dirty)
{
RebuildXenoArtifactMetaData(ent);
}
return true;
}
///
/// Removes artifact node from artifact node container.
///
/// Artifact from container of which node should be removed
/// Node entity to be removed.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
/// True if node was removed successfully, false otherwise.
public bool RemoveNode(Entity ent, Entity node, bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp) || !Resolve(node, ref node.Comp, false))
return false;
if (!TryGetIndex(ent, node, out var idx))
return false; // node isn't attached to this entity.
RemoveAllNodeEdges(ent, idx.Value, dirty: false);
_container.Remove(node.Owner, ent.Comp.NodeContainer);
node.Comp.Attached = null;
ent.Comp.NodeVertices[idx.Value] = null;
if (dirty)
{
RebuildXenoArtifactMetaData(ent);
}
Dirty(node);
return true;
}
///
/// Remove edges, connected to passed artifact node.
///
/// Entity of artifact, in node container of which node resides.
/// Index of node (inside node container), for which all edges should be removed.
///
/// Marker, if we need to recalculate caches and mark related components dirty to update on client side.
/// Should be disabled for initial graph creation to not recalculate cache on each node/edge.
///
public void RemoveAllNodeEdges(Entity ent, int nodeIdx, bool dirty = true)
{
if (!Resolve(ent, ref ent.Comp))
return;
var predecessors = GetDirectPredecessorNodes(ent, nodeIdx);
foreach (var p in predecessors)
{
RemoveEdge(ent, p, nodeIdx, dirty: false);
}
var successors = GetDirectSuccessorNodes(ent, nodeIdx);
foreach (var s in successors)
{
RemoveEdge(ent, nodeIdx, s, dirty: false);
}
if (dirty)
{
RebuildXenoArtifactMetaData(ent);
}
}
///
/// Gets set of node entities, that are direct predecessors to passed node entity.
///
///
/// Direct predecessors are nodes, which are connected by edges directly to target node,
/// and are on outgoing ('FROM') side of edge connection.
///
public HashSet> GetDirectPredecessorNodes(Entity ent, EntityUid node)
{
if (!Resolve(ent, ref ent.Comp))
return new();
if (!TryGetIndex(ent, node, out var index))
return new();
var indices = GetDirectPredecessorNodes(ent, index.Value);
var output = new HashSet>();
foreach (var i in indices)
{
if (TryGetNode(ent, i, out var predecessor))
output.Add(predecessor.Value);
}
return output;
}
///
/// Gets set of node indices (in artifact node container) which are direct predecessors to node with passed node index.
///
///
/// Direct predecessors are nodes, which are connected by edges directly to target node,
/// and are on outgoing ('FROM') side of edge connection.
///
public HashSet GetDirectPredecessorNodes(Entity ent, int nodeIdx)
{
if (!Resolve(ent, ref ent.Comp))
return new();
DebugTools.Assert(nodeIdx >= 0 && nodeIdx < ent.Comp.NodeVertices.Length, $"node index {nodeIdx} is out of bounds!");
var indices = new HashSet();
for (var i = 0; i < ent.Comp.NodeAdjacencyMatrixRows; i++)
{
if (ent.Comp.NodeAdjacencyMatrix[i][nodeIdx])
indices.Add(i);
}
return indices;
}
///
/// Gets set of node entities, that are direct successors to passed node entity.
///
///
/// Direct successors are nodes, which are connected by edges
/// directly to target node, and are on incoming ('TO') side of edge connection.
///
public HashSet> GetDirectSuccessorNodes(Entity ent, EntityUid node)
{
if (!Resolve(ent, ref ent.Comp))
return new();
if (!TryGetIndex(ent, node, out var index))
return new();
var indices = GetDirectSuccessorNodes(ent, index.Value);
var output = new HashSet>();
foreach (var i in indices)
{
if (TryGetNode(ent, i, out var successor))
output.Add(successor.Value);
}
return output;
}
///
/// Gets set of node indices (in artifact node container) which are direct successors to node with passed node index.
///
///
/// Direct successors are nodes, which are connected by edges
/// directly to target node, and are on incoming ('TO') side of edge connection.
///
public HashSet GetDirectSuccessorNodes(Entity ent, int nodeIdx)
{
if (!Resolve(ent, ref ent.Comp))
return new();
DebugTools.Assert(nodeIdx >= 0 && nodeIdx < ent.Comp.NodeVertices.Length, "node index is out of bounds!");
var indices = new HashSet();
for (var i = 0; i < ent.Comp.NodeAdjacencyMatrixColumns; i++)
{
if (ent.Comp.NodeAdjacencyMatrix[nodeIdx][i])
indices.Add(i);
}
return indices;
}
///
/// Gets set of node entities, that are predecessors to passed node entity.
///
///
/// Predecessors are nodes, which are connected by edges directly to target node on 'FROM' side of edge,
/// or connected to such node on 'FROM' side of edge, etc recursively.
///
public HashSet> GetPredecessorNodes(Entity ent, Entity node)
{
if (!Resolve(ent, ref ent.Comp))
return new();
var predecessors = GetPredecessorNodes(ent, GetIndex((ent, ent.Comp), node));
var output = new HashSet>();
foreach (var p in predecessors)
{
output.Add(GetNode((ent, ent.Comp), p));
}
return output;
}
///
/// Gets set of node indices inside artifact node container, that are predecessors to entity with passed node index.
///
///
/// Predecessors are nodes, which are connected by edges directly to target node on 'FROM' side of edge,
/// or connected to such node on 'FROM' side of edge, etc recursively.
///
public HashSet GetPredecessorNodes(Entity ent, int nodeIdx)
{
if (!Resolve(ent, ref ent.Comp))
return new();
var predecessors = GetDirectPredecessorNodes(ent, nodeIdx);
if (predecessors.Count == 0)
return new();
var output = new HashSet();
foreach (var p in predecessors)
{
output.Add(p);
var recursivePredecessors = GetPredecessorNodes(ent, p);
foreach (var rp in recursivePredecessors)
{
output.Add(rp);
}
}
return output;
}
///
/// Gets set of node entities, that are successors to passed node entity.
///
///
/// Successors are nodes, which are connected by edges directly to target node on 'TO' side of edge,
/// or connected to such node on 'TO' side of edge, etc recursively.
///
public HashSet> GetSuccessorNodes(Entity ent, Entity node)
{
if (!Resolve(ent, ref ent.Comp))
return new();
var successors = GetSuccessorNodes(ent, GetIndex((ent, ent.Comp), node));
var output = new HashSet>();
foreach (var s in successors)
{
output.Add(GetNode((ent, ent.Comp), s));
}
return output;
}
///
/// Gets set of node indices inside artifact node container, that are successors to entity with passed node index.
///
///
/// Successors are nodes, which are connected by edges directly to target node on 'TO' side of edge,
/// or connected to such node on 'TO' side of edge, etc recursively.
///
public HashSet GetSuccessorNodes(Entity ent, int nodeIdx)
{
if (!Resolve(ent, ref ent.Comp))
return new();
var successors = GetDirectSuccessorNodes(ent, nodeIdx);
if (successors.Count == 0)
return new();
var output = new HashSet();
foreach (var s in successors)
{
output.Add(s);
var recursiveSuccessors = GetSuccessorNodes(ent, s);
foreach (var rs in recursiveSuccessors)
{
output.Add(rs);
}
}
return output;
}
///
/// Determines, if there is an edge (directed link) FROM one node TO other in passed artifact.
///
/// Artifact, inside which node container nodes are.
/// Node FROM which existence of edge should be checked.
/// Node TO which existance of edge should be checked.
public bool NodeHasEdge(
Entity ent,
Entity from,
Entity to
)
{
if (!Resolve(ent, ref ent.Comp))
return new();
var fromIdx = GetIndex((ent, ent.Comp), from);
var toIdx = GetIndex((ent, ent.Comp), to);
return ent.Comp.NodeAdjacencyMatrix[fromIdx][toIdx];
}
///
/// Resizes the adjacency matrix and vertices array to ,
/// or at least what it WOULD do if i wasn't forced to use shitty lists.
///
protected void ResizeNodeGraph(Entity ent, int newSize)
{
Array.Resize(ref ent.Comp.NodeVertices, newSize);
while (ent.Comp.NodeAdjacencyMatrix.Count < newSize)
{
ent.Comp.NodeAdjacencyMatrix.Add(new());
}
foreach (var row in ent.Comp.NodeAdjacencyMatrix)
{
while (row.Count < newSize)
{
row.Add(false);
}
}
Dirty(ent);
}
/// Removes unlocking state from artifact.
private void CancelUnlockingOnGraphStructureChange(Entity ent)
{
if (!TryComp(ent, out var unlockingComponent))
return;
Entity artifactEnt = (ent, unlockingComponent, ent.Comp);
CancelUnlockingState(artifactEnt);
}
}