diff --git a/Content.Server/_CP14/MagicRituals/Components/CP14MagicRitualPhaseComponent.cs b/Content.Server/_CP14/MagicRituals/Components/CP14MagicRitualPhaseComponent.cs index 4319787b68..d0b4a90209 100644 --- a/Content.Server/_CP14/MagicRituals/Components/CP14MagicRitualPhaseComponent.cs +++ b/Content.Server/_CP14/MagicRituals/Components/CP14MagicRitualPhaseComponent.cs @@ -1,3 +1,6 @@ +using Content.Shared._CP14.MagicRituals.Requirements; +using Robust.Shared.Prototypes; + namespace Content.Server._CP14.MagicRituals.Components; /// @@ -11,4 +14,7 @@ public sealed partial class CP14MagicRitualPhaseComponent : Component /// [DataField] public Entity? Ritual; + + [DataField(required: true)] + public Dictionary> Requirements = new(); } diff --git a/Content.Shared/_CP14/MagicRitual/CP14MagicRitualEvents.cs b/Content.Shared/_CP14/MagicRituals/CP14MagicRitualEvents.cs similarity index 92% rename from Content.Shared/_CP14/MagicRitual/CP14MagicRitualEvents.cs rename to Content.Shared/_CP14/MagicRituals/CP14MagicRitualEvents.cs index f67722f99b..59308df2b4 100644 --- a/Content.Shared/_CP14/MagicRitual/CP14MagicRitualEvents.cs +++ b/Content.Shared/_CP14/MagicRituals/CP14MagicRitualEvents.cs @@ -1,3 +1,4 @@ +using JetBrains.Annotations; using Robust.Shared.Prototypes; namespace Content.Shared._CP14.MagicRitual; diff --git a/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphEdge.cs b/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphEdge.cs new file mode 100644 index 0000000000..7b946ea68c --- /dev/null +++ b/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphEdge.cs @@ -0,0 +1,14 @@ +using Content.Shared._CP14.MagicRituals.Requirements; + +namespace Content.Shared._CP14.MagicRituals.Graph; + +[Serializable] +[DataDefinition] +public sealed partial class CP14RitualGraphEdge +{ + [DataField("to", required:true)] + public string Target { get; private set; } = string.Empty; + + [DataField] + public CP14RitualRequirement[] Requirements = Array.Empty(); +} diff --git a/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphNode.cs b/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphNode.cs new file mode 100644 index 0000000000..70a1c94606 --- /dev/null +++ b/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphNode.cs @@ -0,0 +1,18 @@ + +using Robust.Shared.Prototypes; + +namespace Content.Shared._CP14.MagicRituals.Graph; + +[Serializable] +[DataDefinition] +public sealed partial class CP14RitualGraphNode +{ + [DataField("node", required: true)] + public string Name { get; private set; } = default!; + + [DataField(serverOnly: true)] + public EntProtoId Entity { get; private set; } + + [DataField] + public CP14RitualGraphEdge[] Edges = Array.Empty(); +} diff --git a/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphPrototype.cs b/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphPrototype.cs new file mode 100644 index 0000000000..56b02d351b --- /dev/null +++ b/Content.Shared/_CP14/MagicRituals/Graph/CP14RitualGraphPrototype.cs @@ -0,0 +1,117 @@ +using System.Diagnostics.CodeAnalysis; +using Robust.Shared.Prototypes; + +namespace Content.Shared._CP14.MagicRituals.Graph; + +[Prototype("ritualGraph")] +public sealed partial class CP14RitualGraphPrototype : IPrototype +{ + private readonly Dictionary _nodes = new(); + private readonly Dictionary<(string, string), CP14RitualGraphNode[]?> _paths = new(); + private readonly Dictionary> _pathfinding = new(); + + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + [DataField] + public string? Start { get; private set; } + + [DataField] + public List Graph = new(); + + public bool TryPath(string startNode, string finishNode, [NotNullWhen(true)] out CP14RitualGraphNode[]? path) + { + return (path = Path(startNode, finishNode)) != null; + } + + public string[]? PathId(string startNode, string finishNode) + { + if (Path(startNode, finishNode) is not {} path) + return null; + + var nodes = new string[path.Length]; + + for (var i = 0; i < path.Length; i++) + { + nodes[i] = path[i].Name; + } + + return nodes; + } + + public CP14RitualGraphNode[]? Path(string startNode, string finishNode) + { + var tuple = (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; + } +} diff --git a/Content.Shared/_CP14/MagicRituals/Requirements/CP14Ritualequirement.cs b/Content.Shared/_CP14/MagicRituals/Requirements/CP14Ritualequirement.cs new file mode 100644 index 0000000000..175cf4be7b --- /dev/null +++ b/Content.Shared/_CP14/MagicRituals/Requirements/CP14Ritualequirement.cs @@ -0,0 +1,10 @@ +using JetBrains.Annotations; + +namespace Content.Shared._CP14.MagicRituals.Requirements; + +[ImplicitDataDefinitionForInheritors] +[MeansImplicitUse] +public abstract partial class CP14RitualRequirement +{ + public abstract void Check(EntityManager entManager); +} diff --git a/Content.Server/_CP14/MagicRituals/Components/Requirements/CP14RitualRequirementEntitiesComponent.cs b/Content.Shared/_CP14/MagicRituals/Requirements/EntitiesRequirement.cs similarity index 67% rename from Content.Server/_CP14/MagicRituals/Components/Requirements/CP14RitualRequirementEntitiesComponent.cs rename to Content.Shared/_CP14/MagicRituals/Requirements/EntitiesRequirement.cs index 142cedb111..4561ffefbb 100644 --- a/Content.Server/_CP14/MagicRituals/Components/Requirements/CP14RitualRequirementEntitiesComponent.cs +++ b/Content.Shared/_CP14/MagicRituals/Requirements/EntitiesRequirement.cs @@ -1,13 +1,9 @@ using Content.Shared.Stacks; using Robust.Shared.Prototypes; -namespace Content.Server._CP14.MagicRituals.Components.Requirements; +namespace Content.Shared._CP14.MagicRituals.Requirements; -/// -/// -/// -[RegisterComponent, Access(typeof(CP14RitualSystem))] -public sealed partial class CP14RitualRequirementEntitiesComponent : Component +public sealed partial class EntitiesRequirement : CP14RitualRequirement { [DataField] public float CheckRange = 1f; @@ -26,4 +22,9 @@ public sealed partial class CP14RitualRequirementEntitiesComponent : Component /// [DataField] public EntProtoId? VisualEffect = "CP14DustEffect"; + + public override void Check(EntityManager entManager) + { + throw new NotImplementedException(); + } }