(WIP) Switch from previous DSL System (UI order based) to a new DSL System (node connection based) still in Progress.

This commit is contained in:
2026-05-13 22:21:16 +02:00
parent 5893f9f7b9
commit 33a618b0b9
19 changed files with 273 additions and 78 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
[gd_scene format=3 uid="uid://co7op7et6is8p"]
[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs" id="1_xshi5"]
[ext_resource type="Script" uid="uid://gptqyjv5swwc" path="res://Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs" id="1_xshi5"]
[node name="For" type="GraphNode" unique_id=1235671673]
offset_right = 229.0
@@ -16,7 +16,7 @@ slot/0/right_type = 0
slot/0/right_color = Color(1, 1, 1, 1)
slot/0/right_icon = null
slot/0/draw_stylebox = true
slot/1/left_enabled = true
slot/1/left_enabled = false
slot/1/left_type = 0
slot/1/left_color = Color(1, 1, 1, 1)
slot/1/left_icon = null
@@ -61,7 +61,7 @@ vertical_alignment = 1
[node name="RichTextLabel" type="RichTextLabel" parent="." unique_id=1677882951]
layout_mode = 2
text = "Until"
text = "Afterwards"
fit_content = true
autowrap_mode = 0
horizontal_alignment = 1
+2 -2
View File
@@ -1,6 +1,6 @@
[gd_scene format=3 uid="uid://ctmad6foidkvp"]
[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs" id="1_ygi5c"]
[ext_resource type="Script" uid="uid://cngxwfcrim746" path="res://Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs" id="1_ygi5c"]
[node name="If" type="GraphNode" unique_id=821127877]
offset_right = 468.0
@@ -16,7 +16,7 @@ slot/0/right_type = 0
slot/0/right_color = Color(1, 1, 1, 1)
slot/0/right_icon = null
slot/0/draw_stylebox = true
slot/1/left_enabled = true
slot/1/left_enabled = false
slot/1/left_type = 0
slot/1/left_color = Color(1, 1, 1, 1)
slot/1/left_icon = null
+21
View File
@@ -0,0 +1,21 @@
[gd_scene format=3 uid="uid://b1y4r3s7ukmfw"]
[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs" id="1_start"]
[node name="Start" type="GraphNode" unique_id=2100000001]
offset_right = 65.0
offset_bottom = 55.0
title = "Start"
slot/0/left_enabled = false
slot/0/left_type = 0
slot/0/left_color = Color(1, 1, 1, 1)
slot/0/left_icon = null
slot/0/right_enabled = true
slot/0/right_type = 0
slot/0/right_color = Color(1, 1, 1, 1)
slot/0/right_icon = null
slot/0/draw_stylebox = true
script = ExtResource("1_start")
[node name="Control" type="Control" parent="." unique_id=2100000002]
layout_mode = 2
+3 -3
View File
@@ -1,6 +1,6 @@
[gd_scene format=3 uid="uid://bwiaqvl0d4x8v"]
[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs" id="1_q0rc7"]
[ext_resource type="Script" uid="uid://d3npiur46icru" path="res://Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs" id="1_q0rc7"]
[node name="While" type="GraphNode" unique_id=2040226700]
offset_right = 499.0
@@ -16,7 +16,7 @@ slot/0/right_type = 0
slot/0/right_color = Color(1, 1, 1, 1)
slot/0/right_icon = null
slot/0/draw_stylebox = true
slot/1/left_enabled = true
slot/1/left_enabled = false
slot/1/left_type = 0
slot/1/left_color = Color(1, 1, 1, 1)
slot/1/left_icon = null
@@ -82,7 +82,7 @@ vertical_alignment = 1
[node name="RichTextLabel" type="RichTextLabel" parent="." unique_id=1571031910]
layout_mode = 2
text = "Until"
text = "Afterwards"
fit_content = true
autowrap_mode = 0
horizontal_alignment = 1
+1
View File
@@ -66,6 +66,7 @@ public partial class ResourceLoader
{
Dictionary<ProgramNode, PackedScene> nodes = new Dictionary<ProgramNode, PackedScene>()
{
{ new StartNode(), GD.Load<PackedScene>("res://Prefabs/DSL/StartNode.tscn") },
{ new MoveNode(), GD.Load<PackedScene>("res://Prefabs/DSL/MoveNode.tscn") },
{ new HarvestNode(), GD.Load<PackedScene>("res://Prefabs/DSL/HarvestNode.tscn") },
{ new CraftNode(), GD.Load<PackedScene>("res://Prefabs/DSL/CraftNode.tscn") },
+24
View File
@@ -1,4 +1,5 @@
using Godot;
using System.Collections.Generic;
public class ForNode : ProgramNode
{
@@ -34,4 +35,27 @@ public class ForNode : ProgramNode
{
return $"Name: {DisplayText}, AmountExecuted: {amountExecuted}, Amount: {amount}";
}
public override void SetNextNode(
List<Godot.Collections.Dictionary> connections,
Dictionary<StringName, ProgramNode> availableNodes
)
{
nextNode = null;
NegativeNode = null;
foreach (Godot.Collections.Dictionary connection in connections)
{
int port = (int)connection["from_port"];
ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
if (port == 0)
{
nextNode = connectedNode;
}
else
{
NegativeNode = connectedNode;
}
}
}
}
+24
View File
@@ -1,4 +1,5 @@
using Godot;
using System.Collections.Generic;
public class IfNode : ProgramNode
{
@@ -56,4 +57,27 @@ public class IfNode : ProgramNode
{
return $"Name: {DisplayText}, Item: {(selectedItem == null ? "Empty" : selectedItem.data.Id)}, Comparator: {comparator}, Amount: {amount}";
}
public override void SetNextNode(
List<Godot.Collections.Dictionary> connections,
Dictionary<StringName, ProgramNode> availableNodes
)
{
nextNode = null;
NegativeNode = null;
foreach (Godot.Collections.Dictionary connection in connections)
{
int port = (int)connection["from_port"];
ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
if (port == 0)
{
nextNode = connectedNode;
}
else
{
NegativeNode = connectedNode;
}
}
}
}
+1
View File
@@ -45,6 +45,7 @@ public class MoveNode : ProgramNode
pathPoints.Remove(pathPoints[0]);
if (pathPoints.Count <= 0)
{
pathPoints = null;
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
+25 -1
View File
@@ -1,13 +1,37 @@
using Godot;
using System.Collections.Generic;
public abstract class ProgramNode
{
public ProgramNode nextNode;
public ProgramNode previousNode;
public ProgramNode NegativeNode;
public string DisplayText;
public string lastExecutionMessage;
public abstract NodeResult Execute(Robot robot, double delta);
public abstract ProgramNode Duplicate();
public abstract string Save();
public virtual void SetNextNode(
List<Godot.Collections.Dictionary> connections,
Dictionary<StringName, ProgramNode> availableNodes
)
{
nextNode = null;
if (connections.Count <= 0) return;
nextNode = GetConnectedNode(connections[0], availableNodes);
}
protected ProgramNode GetConnectedNode(
Godot.Collections.Dictionary connection,
Dictionary<StringName, ProgramNode> availableNodes
)
{
StringName nodeName = connection["to_node"].AsStringName();
if (!availableNodes.ContainsKey(nodeName)) return null;
return availableNodes[nodeName];
}
}
+23
View File
@@ -0,0 +1,23 @@
public class StartNode : ProgramNode
{
public StartNode()
{
DisplayText = "Start";
}
public override NodeResult Execute(Robot robot, double delta)
{
return NodeResult.SUCCESS;
}
public override ProgramNode Duplicate()
{
StartNode duplicate = new StartNode();
return duplicate;
}
public override string Save()
{
return $"Name: {DisplayText}";
}
}
+1
View File
@@ -0,0 +1 @@
uid://d3kyu2j3nxjsh
+24
View File
@@ -1,4 +1,5 @@
using Godot;
using System.Collections.Generic;
public class WhileNode : ProgramNode
{
@@ -57,4 +58,27 @@ public class WhileNode : ProgramNode
{
return $"Name: {DisplayText}, Item: {(selectedItem == null ? "Empty" : selectedItem.data.Id)}, Comparator: {comparator}, Amount: {amount}";
}
public override void SetNextNode(
List<Godot.Collections.Dictionary> connections,
Dictionary<StringName, ProgramNode> availableNodes
)
{
nextNode = null;
NegativeNode = null;
foreach (Godot.Collections.Dictionary connection in connections)
{
int port = (int)connection["from_port"];
ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
if (port == 0)
{
nextNode = connectedNode;
}
else
{
NegativeNode = connectedNode;
}
}
}
}
+9 -40
View File
@@ -35,26 +35,11 @@ public partial class Robot : Node3D
{
if (CanExecute(delta))
{
GD.Print(currentNode.DisplayText);
switch (currentNode.Execute(this, delta))
{
case NodeResult.SUCCESS:
if (currentNode.DisplayText == "Until")
{
while (true)
{
currentNode = currentNode.previousNode;
if (currentNode == null)
{
isExecuting = false;
break;
}
if (currentNode.DisplayText == "For" || currentNode.DisplayText == "While") break;
}
}
else
{
currentNode = currentNode.nextNode;
}
if (currentNode == null)
{
isExecuting = false;
@@ -70,33 +55,10 @@ public partial class Robot : Node3D
currentMessage = "";
break;
case NodeResult.CONDITIONFALSE:
string sourceNode = currentNode.DisplayText;
while (true)
{
currentNode = currentNode.nextNode;
currentNode = currentNode.NegativeNode;
if (currentNode == null)
{
isExecuting = false;
break;
}
if (sourceNode == "If")
{
if (currentNode.DisplayText == "If" || currentNode.DisplayText == "Else") break;
}
else
{
if (currentNode.DisplayText == "While" || currentNode.DisplayText == "For" || currentNode.DisplayText == "Until")
{
if (currentNode.nextNode == null)
{
isExecuting = false;
break;
}
currentNode = currentNode.nextNode;
break;
}
}
}
break;
}
@@ -133,6 +95,13 @@ public partial class Robot : Node3D
currentMessage = "";
}
public void StopExecution(string message)
{
isExecuting = false;
currentNode = null;
currentMessage = message;
}
public float GetMovementSpeed()
{
return GameData.robotStats.GetMovementSpeed(GameData.robotSpeed)
+21
View File
@@ -35,6 +35,7 @@ public partial class TestRunner : Node
Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
Run("While node reports false conditions", TestWhileNodeReportsFalseConditions);
Run("For node stops after configured amount", TestForNodeStopsAfterConfiguredAmount);
Run("Start node succeeds immediately", TestStartNodeSucceedsImmediately);
Run("Paused world does not drain survival", TestPausedWorldDoesNotDrainSurvival);
Run("Open gate hides gate content", TestOpenGateHidesGateContent);
Run("Layer generation succeeds above threshold", TestLayerGenerationSuccessRate);
@@ -476,6 +477,14 @@ public partial class TestRunner : Node
AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "loop finished");
}
private void TestStartNodeSucceedsImmediately()
{
StartNode node = new StartNode();
AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "start should succeed");
AssertEqual("Name: Start", node.Save(), "start save");
}
private void TestPausedWorldDoesNotDrainSurvival()
{
GameData.isPaused = true;
@@ -549,6 +558,18 @@ public partial class TestRunner : Node
AssertTrue(GameData.availableItems.ContainsKey("water"), "water item loaded");
AssertTrue(GameData.availableResearch.ContainsKey("basics"), "basics research loaded");
AssertTrue(GameData.availableResearch.ContainsKey("iron_robotics"), "iron robotics research loaded");
Dictionary<ProgramNode, PackedScene> dslNodes = ResourceLoader.LoadDSLNodes();
bool hasStartNode = false;
foreach (ProgramNode node in dslNodes.Keys)
{
if (node is StartNode)
{
hasStartNode = true;
break;
}
}
AssertTrue(hasStartNode, "start node prefab loaded");
}
private Layer CreateTestLayer(int level, string collapsedMesh)
+78 -24
View File
@@ -13,6 +13,7 @@ public partial class CodingWindow : PanelContainer
[Export] LineEdit nameInput;
public System.Collections.Generic.Dictionary<ProgramNode, PackedScene> DSLNodes;
private System.Collections.Generic.Dictionary<StringName, ProgramNode> availableNodes;
public override void _Ready()
{
@@ -83,10 +84,19 @@ public partial class CodingWindow : PanelContainer
private void AddEditorNode(ProgramNode node)
{
NodeDisplay editorDisplay = DSLNodes[node].Instantiate<NodeDisplay>();
editorDisplay.PositionOffset = (editorWindow.ScrollOffset + editorWindow.Size / 2) / editorWindow.Zoom - editorDisplay.Size / 2;
editorWindow.AddChild(editorDisplay);
RegisterEditorNode(editorDisplay);
}
private void MoveNodeToVisibleGraphCenter(NodeDisplay nodeDisplay)
{
Vector2 visibleCenter = editorWindow.ScrollOffset
+ editorWindow.Size / (2f * editorWindow.Zoom);
nodeDisplay.PositionOffset = visibleCenter - nodeDisplay.Size / (2f * editorWindow.Zoom);
}
private void RegisterEditorNode(NodeDisplay editorDisplay)
{
editorDisplay.OnDeleteNode += () =>
@@ -121,7 +131,25 @@ public partial class CodingWindow : PanelContainer
public void CompileProgram()
{
List<ProgramNode> nodes = new List<ProgramNode>();
if (robot == null) return;
NodeDisplay startNode = FindStartNode();
if (startNode == null)
{
robot.StopExecution("(FAILED) Script needs exactly one Start node");
return;
}
availableNodes = BuildAvailableNodeLookup();
List<ProgramNode> nodes = BuildScriptOrder(startNode, new List<ProgramNode>());
if (nodes.Count > 0) robot.SetupExecution(nodes);
robot.currentProgram = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text;
}
private System.Collections.Generic.Dictionary<StringName, ProgramNode> BuildAvailableNodeLookup()
{
System.Collections.Generic.Dictionary<StringName, ProgramNode> availableNodes =
new System.Collections.Generic.Dictionary<StringName, ProgramNode>();
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
@@ -129,22 +157,56 @@ public partial class CodingWindow : PanelContainer
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
ProgramNode executableNode = nodeDisplay.node.Duplicate();
nodes.Add(executableNode);
if (nodes.Count > 1)
{
nodes[nodes.Count - 2].nextNode = executableNode;
}
if (nodes.Count > 1)
{
executableNode.previousNode = nodes[nodes.Count - 2];
}
availableNodes.Add(nodeDisplay.Name, nodeDisplay.node);
}
if (robot == null) return;
if (nodes.Count > 0) robot.SetupExecution(nodes);
robot.currentProgram = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text;
return availableNodes;
}
private List<ProgramNode> BuildScriptOrder(NodeDisplay node, List<ProgramNode> program)
{
program.Add(node.node);
if (editorWindow.GetConnectionListFromNode(node.Name).Count <= 0) return program;
List<Dictionary> nextConnections = CheckNodeConnections(node);
if (nextConnections.Count <= 0) return program;
node.node.SetNextNode(nextConnections, availableNodes);
foreach (Dictionary connection in nextConnections)
{
program = BuildScriptOrder(
editorWindow.GetNode<NodeDisplay>(new NodePath(connection["to_node"].AsStringName())),
program
);
}
return program;
}
private List<Dictionary> CheckNodeConnections(NodeDisplay node)
{
List<Dictionary> result = new List<Dictionary>();
Array<Dictionary> connections = editorWindow.GetConnectionListFromNode(node.Name);
for (int i = 0; i < connections.Count; i++)
{
if (connections[i]["from_node"].AsStringName() == node.Name)
{
result.Add(connections[i]);
}
}
return result;
}
private NodeDisplay FindStartNode()
{
NodeDisplay startNode = null;
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
if (nodeDisplay.node is not StartNode) continue;
if (startNode != null) return null;
startNode = nodeDisplay;
}
return startNode;
}
public void SetRobot(Robot robot)
@@ -177,14 +239,8 @@ public partial class CodingWindow : PanelContainer
if (robot == null) return;
if (robot.currentNode == null) return;
ProgramNode firstNode = robot.currentNode;
while (firstNode.previousNode != null)
{
firstNode = firstNode.previousNode;
}
HashSet<ProgramNode> loadedNodes = new HashSet<ProgramNode>();
ProgramNode nodeToLoad = firstNode;
ProgramNode nodeToLoad = robot.currentNode;
while (nodeToLoad != null && !loadedNodes.Contains(nodeToLoad))
{
loadedNodes.Add(nodeToLoad);
@@ -238,12 +294,10 @@ public partial class CodingWindow : PanelContainer
public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort)
{
GD.Print($"From {fromPort} to {toPort}");
if (to == from) return;
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
if (connection["to_node"].AsStringName() == to && (int)connection["to_port"] == toPort) return;
if (connection["from_node"].AsStringName() == from && (int)connection["from_port"] == fromPort) return;
}
@@ -0,0 +1,7 @@
public partial class StartNodeDisplay : NodeDisplay
{
protected override ProgramNode CreateProgramNode()
{
return new StartNode();
}
}
@@ -0,0 +1 @@
uid://c7w5380k530wb
+1 -1
View File
@@ -81,7 +81,7 @@ public class Pathfinding
if (!aStar.ArePointsConnected(fromId, toId))
{
aStar.ConnectPoints(fromId, toId);
aStar.ConnectPoints(fromId, toId, true);
}
}
}
+1 -1
View File
@@ -15,7 +15,7 @@ compatibility/default_parent_skeleton_in_mesh_instance_3d=true
[application]
config/name="RuinAdventurer"
run/main_scene="uid://dlommaelbbw2b"
run/main_scene="res://Scenes/MainMenu.tscn"
config/features=PackedStringArray("4.6", "C#", "Forward Plus")
config/icon="res://icon.svg"