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();
+ }
}