using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; using Robust.Shared.Prototypes; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.ViewVariables; namespace Content.Shared.Construction { [Prototype("constructionGraph")] public class ConstructionGraphPrototype : IPrototype, ISerializationHooks { private readonly Dictionary _nodes = new(); private readonly Dictionary, ConstructionGraphNode[]?> _paths = new(); private readonly Dictionary> _pathfinding = new(); [ViewVariables] [DataField("id", required: true)] public string ID { get; } = default!; [ViewVariables] [DataField("start")] public string? Start { get; } [DataField("graph", priority: 0)] private List _graph = new(); [ViewVariables] public IReadOnlyDictionary Nodes => _nodes; void ISerializationHooks.AfterDeserialization() { _nodes.Clear(); foreach (var graphNode in _graph) { if (string.IsNullOrEmpty(graphNode.Name)) { throw new InvalidDataException($"Name of graph node is null in construction graph {ID}!"); } _nodes[graphNode.Name] = graphNode; } if (string.IsNullOrEmpty(Start) || !_nodes.ContainsKey(Start)) throw new InvalidDataException($"Starting node for construction graph {ID} is null, empty or invalid!"); } public ConstructionGraphEdge? Edge(string startNode, string nextNode) { var start = _nodes[startNode]; return start.GetEdge(nextNode); } public bool TryPath(string startNode, string finishNode, [NotNullWhen(true)] out ConstructionGraphNode[]? path) { return (path = Path(startNode, finishNode)) != null; } public ConstructionGraphNode[]? Path(string startNode, string finishNode) { var tuple = new ValueTuple(startNode, finishNode); if (_paths.ContainsKey(tuple)) return _paths[tuple]; // Get graph given the current start. Dictionary pathfindingForStart; if (_pathfinding.ContainsKey(startNode)) { pathfindingForStart = _pathfinding[startNode]; } else { pathfindingForStart = _pathfinding[startNode] = PathsForStart(startNode); } // Follow the chain backwards. var start = _nodes[startNode]; var finish = _nodes[finishNode]; var current = finish; var path = new List(); while (current != start) { // No path. if (current == null || !pathfindingForStart.ContainsKey(current)) { // We remember this for next time. _paths[tuple] = null; return null; } path.Add(current); current = pathfindingForStart[current]; } path.Reverse(); return _paths[tuple] = path.ToArray(); } /// /// Uses breadth first search for pathfinding. /// /// private Dictionary PathsForStart(string start) { // TODO: Make this use A* or something, although it's not that important. var startNode = _nodes[start]; var frontier = new Queue(); var cameFrom = new Dictionary(); frontier.Enqueue(startNode); cameFrom[startNode] = null; while (frontier.Count != 0) { var current = frontier.Dequeue(); foreach (var edge in current.Edges) { var edgeNode = _nodes[edge.Target]; if(cameFrom.ContainsKey(edgeNode)) continue; frontier.Enqueue(edgeNode); cameFrom[edgeNode] = current; } } return cameFrom; } } }