diff --git a/Prefabs/DSL/CraftNode.tscn b/Prefabs/DSL/CraftNode.tscn index 6ee81bc..3772b07 100644 --- a/Prefabs/DSL/CraftNode.tscn +++ b/Prefabs/DSL/CraftNode.tscn @@ -1,6 +1,6 @@ [gd_scene format=3 uid="uid://cinn18bl736rk"] -[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs" id="1_qemp1"] +[ext_resource type="Script" uid="uid://bfosue8mejnr5" path="res://Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs" id="1_qemp1"] [node name="Craft" type="GraphNode" unique_id=908155742] offset_right = 158.0 diff --git a/Scripts/DSL/Nodes/ForNode.cs b/Scripts/DSL/Nodes/ForNode.cs index 1aedbab..76d432f 100644 --- a/Scripts/DSL/Nodes/ForNode.cs +++ b/Scripts/DSL/Nodes/ForNode.cs @@ -19,12 +19,12 @@ public class ForNode : ProgramNode { amountExecuted = 0; } - return isConditionFulfilled ? NodeResult.CONDITIONFALSE : NodeResult.SUCCESS; + return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; } private bool DetermineCondition() { - return amountExecuted < amount; + return amountExecuted >= amount; } public override ProgramNode Duplicate() diff --git a/Scripts/DSL/Nodes/ProgramNode.cs b/Scripts/DSL/Nodes/ProgramNode.cs index c346c78..42da444 100644 --- a/Scripts/DSL/Nodes/ProgramNode.cs +++ b/Scripts/DSL/Nodes/ProgramNode.cs @@ -5,6 +5,7 @@ public abstract class ProgramNode { public ProgramNode nextNode; public ProgramNode NegativeNode; + public string EditorNodeId; public string DisplayText; public string TooltipText; public string lastExecutionMessage; @@ -13,6 +14,13 @@ public abstract class ProgramNode public abstract ProgramNode Duplicate(); public abstract string Save(); + public ProgramNode DuplicateForRuntime(string editorNodeId) + { + ProgramNode duplicate = Duplicate(); + duplicate.EditorNodeId = editorNodeId; + return duplicate; + } + public virtual void SetNextNode( List connections, Dictionary availableNodes diff --git a/Scripts/Gameplay/Robots/Robot.cs b/Scripts/Gameplay/Robots/Robot.cs index d6e8fce..54813e7 100644 --- a/Scripts/Gameplay/Robots/Robot.cs +++ b/Scripts/Gameplay/Robots/Robot.cs @@ -11,8 +11,9 @@ public partial class Robot : Node3D private const float CooldownTarget = 35f; private const float MaintenanceLossPerSecond = 0.025f; - private bool isExecuting = false; + public bool isExecuting = false; + public ProgramNode programStartNode; public ProgramNode currentNode; public string currentProgram; public string currentMessage = ""; @@ -103,6 +104,7 @@ public partial class Robot : Node3D if (nodes.Count <= 0) return; isExecuting = true; + programStartNode = nodes[0]; currentNode = nodes[0]; currentMessage = ""; } @@ -110,6 +112,7 @@ public partial class Robot : Node3D public void StopExecution(string message) { isExecuting = false; + programStartNode = null; currentNode = null; currentMessage = message; } diff --git a/Scripts/Tests/TestRunner.cs b/Scripts/Tests/TestRunner.cs index d76916d..70f8ef9 100644 --- a/Scripts/Tests/TestRunner.cs +++ b/Scripts/Tests/TestRunner.cs @@ -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("Runtime node keeps editor id without sharing state", TestRuntimeNodeKeepsEditorIdWithoutSharingState); Run("Start node succeeds immediately", TestStartNodeSucceedsImmediately); Run("Split node connections restore both branches", TestSplitNodeConnectionsRestoreBothBranches); Run("Linear node connection restores next node", TestLinearNodeConnectionRestoresNextNode); @@ -479,6 +480,21 @@ public partial class TestRunner : Node AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "loop finished"); } + private void TestRuntimeNodeKeepsEditorIdWithoutSharingState() + { + MoveNode editorNode = new MoveNode + { + targetPosition = new Vector3I(1, 0, 1) + }; + + ProgramNode runtimeNode = editorNode.DuplicateForRuntime("MoveNode42"); + MoveNode runtimeMoveNode = runtimeNode as MoveNode; + + AssertFalse(ReferenceEquals(editorNode, runtimeNode), "runtime node should be a copy"); + AssertEqual("MoveNode42", runtimeNode.EditorNodeId, "runtime node editor id"); + AssertEqual(editorNode.targetPosition, runtimeMoveNode.targetPosition, "runtime node parameters"); + } + private void TestStartNodeSucceedsImmediately() { StartNode node = new StartNode(); diff --git a/Scripts/UI/DSL/CodingWindow.cs b/Scripts/UI/DSL/CodingWindow.cs index 4ccc78e..c05f933 100644 --- a/Scripts/UI/DSL/CodingWindow.cs +++ b/Scripts/UI/DSL/CodingWindow.cs @@ -15,6 +15,7 @@ public partial class CodingWindow : PanelContainer public System.Collections.Generic.Dictionary DSLNodes; public NodeDisplay selectedNode; + public NodeDisplay highlightedNode; public override void _Ready() { @@ -30,7 +31,7 @@ public partial class CodingWindow : PanelContainer } } - public override void _Process(double delta) + public override void _Process(double delta) { if (Input.IsActionJustPressed("delete_node")) { @@ -41,11 +42,63 @@ public partial class CodingWindow : PanelContainer editorWindow.RemoveChild(selectedNode); selectedNode.QueueFree(); } + + if (robot != null && Visible && robot.isExecuting) + { + UpdateCurrentNode(); + } + } + + private void UpdateCurrentNode() + { + if (robot.currentNode == null) + { + ClearHighlightedNode(); + return; + } + + if (highlightedNode != null) + { + if (IsCurrentRuntimeNode(highlightedNode)) + { + return; + } + + ClearHighlightedNode(); + } + + foreach (Node child in editorWindow.GetChildren()) + { + NodeDisplay nodeDisplay = child as NodeDisplay; + if (nodeDisplay == null) continue; + + if (IsCurrentRuntimeNode(nodeDisplay)) + { + nodeDisplay.SetHighlighted(true); + highlightedNode = nodeDisplay; + break; + } + } + } + + private bool IsCurrentRuntimeNode(NodeDisplay nodeDisplay) + { + return robot.currentNode.EditorNodeId != null + && robot.currentNode.EditorNodeId.Length > 0 + && nodeDisplay.Name.ToString() == robot.currentNode.EditorNodeId; + } + + private void ClearHighlightedNode() + { + if (highlightedNode == null) return; + + highlightedNode.SetHighlighted(false); + highlightedNode = null; } public void OnMapToggled(bool toggledOn) { - if(robot == null) return; + if (robot == null) return; robot.showOnMap = toggledOn; } @@ -133,6 +186,7 @@ public partial class CodingWindow : PanelContainer public void ClearWindow() { + ClearHighlightedNode(); DisconnectAllNodes(); RemoveEditorNodes(); scriptName.Text = ""; @@ -210,16 +264,18 @@ public partial class CodingWindow : PanelContainer public void LoadTemporaryProgram() { if (robot == null) return; - if (robot.currentNode == null) return; + ProgramNode rootNode = robot.programStartNode ?? robot.currentNode; + if (rootNode == null) return; RunningProgramGraphBuilder builder = new RunningProgramGraphBuilder( DSLNodes, AddLoadedNode, ConnectNodes ); - builder.Load(robot.currentNode); + builder.Load(rootNode); scriptName.Text = robot.currentProgram ?? ""; + UpdateCurrentNode(); } public void DeleteProgram() diff --git a/Scripts/UI/DSL/NodeDisplay.cs b/Scripts/UI/DSL/NodeDisplay.cs index 6da245d..7988fd7 100644 --- a/Scripts/UI/DSL/NodeDisplay.cs +++ b/Scripts/UI/DSL/NodeDisplay.cs @@ -4,6 +4,7 @@ using Godot; public partial class NodeDisplay : GraphNode { public ProgramNode node; + private bool isHighlighted = false; public override void _Ready() { @@ -48,6 +49,14 @@ public partial class NodeDisplay : GraphNode public virtual void ReadParameters() { } + public void SetHighlighted(bool highlighted) + { + if (isHighlighted == highlighted) return; + + isHighlighted = highlighted; + SelfModulate = highlighted ? UIStyle.GetWarningColor() : Colors.White; + } + public HBoxContainer GetValueContainer() { return GetNode("./Values"); diff --git a/Scripts/UI/DSL/RunningProgramGraphBuilder.cs b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs index 8551a6a..03137f7 100644 --- a/Scripts/UI/DSL/RunningProgramGraphBuilder.cs +++ b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs @@ -40,6 +40,10 @@ public class RunningProgramGraphBuilder dslNodes ); if (nodeDisplay == null) return null; + if (programNode.EditorNodeId != null && programNode.EditorNodeId.Length > 0) + { + nodeDisplay.Name = programNode.EditorNodeId; + } addNode(nodeDisplay); loadedNodes.Add(programNode, nodeDisplay); diff --git a/Scripts/UI/DSL/ScriptGraphCompiler.cs b/Scripts/UI/DSL/ScriptGraphCompiler.cs index d8873cb..1b4d525 100644 --- a/Scripts/UI/DSL/ScriptGraphCompiler.cs +++ b/Scripts/UI/DSL/ScriptGraphCompiler.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; public class ScriptGraphCompiler { private readonly GraphEdit editorWindow; - private System.Collections.Generic.Dictionary availableNodes; + private System.Collections.Generic.Dictionary runtimeNodes; public ScriptGraphCompiler(GraphEdit editorWindow) { @@ -22,7 +22,7 @@ public class ScriptGraphCompiler return new List(); } - BuildAvailableNodeLookup(); + BuildRuntimeNodeLookup(); return BuildScriptOrder( startNode, new List(), @@ -30,9 +30,9 @@ public class ScriptGraphCompiler ); } - private void BuildAvailableNodeLookup() + private void BuildRuntimeNodeLookup() { - availableNodes = new System.Collections.Generic.Dictionary(); + runtimeNodes = new System.Collections.Generic.Dictionary(); for (int i = 0; i < editorWindow.GetChildCount(); i++) { @@ -40,7 +40,10 @@ public class ScriptGraphCompiler if (nodeDisplay == null) continue; nodeDisplay.ReadParameters(); - availableNodes.Add(nodeDisplay.Name, nodeDisplay.node); + runtimeNodes.Add( + nodeDisplay.Name, + nodeDisplay.node.DuplicateForRuntime(nodeDisplay.Name.ToString()) + ); } } @@ -52,14 +55,16 @@ public class ScriptGraphCompiler { if (node == null) return program; if (visitedNodes.Contains(node.Name)) return program; + if (!runtimeNodes.ContainsKey(node.Name)) return program; visitedNodes.Add(node.Name); - program.Add(node.node); + ProgramNode runtimeNode = runtimeNodes[node.Name]; + program.Add(runtimeNode); List nextConnections = GetOutgoingConnections(node); if (nextConnections.Count <= 0) return program; - node.node.SetNextNode(nextConnections, availableNodes); + runtimeNode.SetNextNode(nextConnections, runtimeNodes); foreach (Dictionary connection in nextConnections) { NodeDisplay nextNode = editorWindow.GetNodeOrNull(