Big project cleanup with overhaul of file responsibilities (KISS) and code (DRY, YAGNI)

This commit is contained in:
2026-05-14 11:17:02 +02:00
parent bd6cdeb97b
commit 300c8f5a42
54 changed files with 2030 additions and 1745 deletions
+4 -9
View File
@@ -17,8 +17,7 @@ public partial class Camera3d : Camera3D
{
Control focused = GetViewport().GuiGetFocusOwner();
if (focused is LineEdit || focused is TextEdit)
return;
if (focused is LineEdit || focused is TextEdit) return;
if (canMove) MoveCamera(delta);
}
@@ -38,17 +37,13 @@ public partial class Camera3d : Camera3D
if (direction != Vector3.Zero)
{
if(robot != null) robot = null;
robot = null;
direction = direction.Normalized() * Speed * (Input.IsActionPressed("sprint") ? 2.5f : 1) * d;
Translate(direction);
}
else
else if (robot != null)
{
if(robot != null)
{
Position = new Vector3(robot.Position.X, 10 - visibleLayer * 4, robot.Position.Z + 4f);
}
Position = new Vector3(robot.Position.X, 10 - visibleLayer * 4, robot.Position.Z + 4f);
}
if (Position.Y != 10 - visibleLayer * 4)
+20 -103
View File
@@ -1,5 +1,3 @@
using System;
using System.Diagnostics;
using Godot;
public partial class UIHandler : Control
@@ -46,10 +44,7 @@ public partial class UIHandler : Control
DisplayStats();
DisplayRobotAlarm();
Control focused = GetViewport().GuiGetFocusOwner();
if (focused is LineEdit || focused is TextEdit)
return;
if (IsTextInputFocused()) return;
if (Input.IsActionJustPressed("map")) HandleMapButton();
if (Input.IsActionJustPressed("menu")) HandleMenuButton();
@@ -58,22 +53,29 @@ public partial class UIHandler : Control
if (Input.IsActionJustPressed("research")) HandleResearchButton();
}
private bool IsTextInputFocused()
{
Control focused = GetViewport().GuiGetFocusOwner();
return focused is LineEdit || focused is TextEdit;
}
public void HandleMenuButton()
{
if(GameData.survival.isDead) return;
if (GameData.survival.isDead) return;
OpenUIElement(menu);
GameData.isPaused = menu.Visible || options.Visible;
}
public void HandleMenu()
{
if(GameData.survival.isDead) return;
HandleMenuButton();
}
public void ShowOptions()
{
if(GameData.survival.isDead) return;
if (GameData.survival.isDead) return;
menu.Hide();
OpenUIElement(options);
GameData.isPaused = options.Visible;
@@ -81,14 +83,16 @@ public partial class UIHandler : Control
public void HandleMapButton()
{
if(GameData.survival.isDead) return;
if (GameData.survival.isDead) return;
OpenUIElement(map);
if (map.Visible) map.ShowMap();
}
public void HandleRobotListButton()
{
if(GameData.survival.isDead) return;
if (GameData.survival.isDead) return;
receivedRobotFollowSignal = false;
receivedRobotJumpSignal = false;
OpenUIElement(robotList);
@@ -96,28 +100,19 @@ public partial class UIHandler : Control
public void HandleInventoryButton()
{
if(GameData.survival.isDead) return;
if (GameData.survival.isDead) return;
OpenUIElement(inventory);
}
public void HandleResearchButton()
{
if(GameData.survival.isDead) return;
if (GameData.survival.isDead) return;
OpenUIElement(researchList);
if (researchList.Visible) researchList.SetupGraph();
}
public void DisplayStats()
{
FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS";
double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024);
string memoryDisplay = memory > 1024 ? Math.Round(memory / 1024, 2).ToString() + " GB" : memory.ToString() + " MB";
RAM.Text = memoryDisplay;
DisplaySurvivalStats();
DisplayWorldStats();
DisplayLoseCondition();
}
public void ExitGame()
{
GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn");
@@ -139,14 +134,7 @@ public partial class UIHandler : Control
public void OpenUIElement(Control element)
{
SoundManager.PlayButton();
if (element.Visible)
{
element.Hide();
}
else
{
element.Show();
}
element.Visible = !element.Visible;
HideUIElements(element);
}
@@ -164,25 +152,6 @@ public partial class UIHandler : Control
}
}
private void DisplayRobotAlarm()
{
string messages = "";
if (GameData.survival.isDead)
{
messages += GameData.survival.currentStatus + "\r";
}
foreach (Robot robot in GameData.robots)
{
if (robot.currentMessage.Length > 0)
{
messages += $"{robot.Name}: {robot.currentMessage}\r";
}
}
robotAlarm.Visible = messages.Length > 0;
robotAlarm.TooltipText = messages;
}
private void OnRobotJumpTo(Robot robot)
{
if (receivedRobotJumpSignal) return;
@@ -201,42 +170,6 @@ public partial class UIHandler : Control
mainCam.Follow(robot);
}
private void DisplaySurvivalStats()
{
energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}";
waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}";
hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}";
survivalStatus.Text = GameData.survival.currentStatus;
survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical")
? UIStyle.GetWarningColor()
: Colors.White;
if (GameData.survival.isDead)
{
ShowGameOver();
}
}
private void DisplayWorldStats()
{
currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}";
deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}";
if (GameData.lowestLayer == GameData.ruinSize)
{
unlockLayer.Visible = false;
return;
}
unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients();
unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1);
}
private void DisplayLoseCondition()
{
if (!GameData.HasNoRobotRecovery()) return;
ShowGameOver("No robots remain and no robot can be spawned from inventory.");
}
public void UnlockLayer()
{
if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1))
@@ -259,20 +192,4 @@ public partial class UIHandler : Control
}
}
public void ShowGameOver()
{
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
}
public void ShowGameOver(string message)
{
if (gameOver.Visible) return;
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
gameOver.Show();
}
public void HideGameOver()
{
gameOver.Hide();
}
}
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Diagnostics;
using Godot;
public partial class UIHandler
{
public void DisplayStats()
{
FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS";
RAM.Text = GetMemoryDisplay();
DisplaySurvivalStats();
DisplayWorldStats();
DisplayLoseCondition();
}
private string GetMemoryDisplay()
{
double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024);
if (memory > 1024)
{
return Math.Round(memory / 1024, 2).ToString() + " GB";
}
return memory.ToString() + " MB";
}
private void DisplayRobotAlarm()
{
string messages = "";
if (GameData.survival.isDead)
{
messages += GameData.survival.currentStatus + "\r";
}
foreach (Robot robot in GameData.robots)
{
if (robot.currentMessage.Length > 0)
{
messages += $"{robot.Name}: {robot.currentMessage}\r";
}
}
robotAlarm.Visible = messages.Length > 0;
robotAlarm.TooltipText = messages;
}
private void DisplaySurvivalStats()
{
energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}";
waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}";
hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}";
survivalStatus.Text = GameData.survival.currentStatus;
survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical")
? UIStyle.GetWarningColor()
: Colors.White;
if (GameData.survival.isDead)
{
ShowGameOver();
}
}
private void DisplayWorldStats()
{
currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}";
deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}";
if (GameData.lowestLayer == GameData.ruinSize)
{
unlockLayer.Visible = false;
return;
}
unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients();
unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1);
}
private void DisplayLoseCondition()
{
if (!GameData.HasNoRobotRecovery()) return;
ShowGameOver("No robots remain and no robot can be spawned from inventory.");
}
public void ShowGameOver()
{
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
}
public void ShowGameOver(string message)
{
if (gameOver.Visible) return;
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
gameOver.Show();
}
public void HideGameOver()
{
gameOver.Hide();
}
}
@@ -0,0 +1 @@
uid://b2q7e88lokg3l
+70 -327
View File
@@ -13,15 +13,6 @@ public partial class CodingWindow : PanelContainer
[Export] LineEdit nameInput;
public System.Collections.Generic.Dictionary<ProgramNode, PackedScene> DSLNodes;
private System.Collections.Generic.Dictionary<StringName, ProgramNode> availableNodes;
private class ScriptConnection
{
public string FromNodeId;
public int FromPort;
public string ToNodeId;
public int ToPort;
}
public override void _Ready()
{
@@ -51,6 +42,7 @@ public partial class CodingWindow : PanelContainer
{
availableScripts.Clear();
availableScripts.AddItem("Select script to load...");
List<string> scripts = FileHandler.LoadProgramNames();
scripts.Sort((a, b) => a.CompareTo(b));
foreach (string script in scripts)
@@ -73,10 +65,9 @@ public partial class CodingWindow : PanelContainer
public void GenerateCodingBlocks()
{
Button nodeListButton;
foreach (ProgramNode nodeTemplate in DSLNodes.Keys)
{
nodeListButton = new Button
Button nodeListButton = new Button
{
Name = nodeTemplate.DisplayText,
Text = nodeTemplate.DisplayText
@@ -92,17 +83,14 @@ 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;
editorDisplay.PositionOffset = GetVisibleGraphCenter() - editorDisplay.Size / 2f;
editorWindow.AddChild(editorDisplay);
RegisterEditorNode(editorDisplay);
}
private void MoveNodeToVisibleGraphCenter(NodeDisplay nodeDisplay)
private Vector2 GetVisibleGraphCenter()
{
Vector2 visibleCenter = editorWindow.ScrollOffset
+ editorWindow.Size / (2f * editorWindow.Zoom);
nodeDisplay.PositionOffset = visibleCenter - nodeDisplay.Size / (2f * editorWindow.Zoom);
return (editorWindow.ScrollOffset + editorWindow.Size / 2f) / editorWindow.Zoom;
}
private void RegisterEditorNode(NodeDisplay editorDisplay)
@@ -115,6 +103,13 @@ public partial class CodingWindow : PanelContainer
}
public void ClearWindow()
{
DisconnectAllNodes();
RemoveEditorNodes();
scriptName.Text = "";
}
private void DisconnectAllNodes()
{
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
@@ -125,109 +120,41 @@ public partial class CodingWindow : PanelContainer
(int)connection["to_port"]
);
}
}
private void RemoveEditorNodes()
{
foreach (Node child in editorWindow.GetChildren())
{
if (child is GraphNode)
{
editorWindow.RemoveChild(child);
child.QueueFree();
}
if (child is not GraphNode) continue;
editorWindow.RemoveChild(child);
child.QueueFree();
}
scriptName.Text = "";
}
public void CompileProgram()
{
if (robot == null) return;
NodeDisplay startNode = FindStartNode();
if (startNode == null)
ScriptGraphCompiler compiler = new ScriptGraphCompiler(editorWindow);
string errorMessage;
List<ProgramNode> nodes = compiler.BuildProgram(out errorMessage);
if (errorMessage.Length > 0)
{
robot.StopExecution("(FAILED) Script needs exactly one Start node");
robot.StopExecution(errorMessage);
return;
}
BuildAvailableNodeLookup();
List<ProgramNode> nodes = BuildScriptOrder(
startNode,
new List<ProgramNode>(),
new HashSet<StringName>()
);
if (nodes.Count > 0) robot.SetupExecution(nodes);
robot.currentProgram = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text;
robot.currentProgram = GetCurrentScriptName();
}
private void BuildAvailableNodeLookup()
private string GetCurrentScriptName()
{
availableNodes = new System.Collections.Generic.Dictionary<StringName, ProgramNode>();
if (scriptName.Text.Length > 0) return scriptName.Text;
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
availableNodes.Add(nodeDisplay.Name, nodeDisplay.node);
}
}
private List<ProgramNode> BuildScriptOrder(
NodeDisplay node,
List<ProgramNode> program,
HashSet<StringName> visitedNodes
)
{
if (node == null) return program;
if (visitedNodes.Contains(node.Name)) return program;
visitedNodes.Add(node.Name);
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)
{
NodeDisplay nextNode = editorWindow.GetNodeOrNull<NodeDisplay>(
new NodePath(connection["to_node"].AsStringName())
);
program = BuildScriptOrder(
nextNode,
program,
visitedNodes
);
}
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;
return $"Script{availableScripts.ItemCount}";
}
public void SetRobot(Robot robot)
@@ -241,131 +168,15 @@ public partial class CodingWindow : PanelContainer
ClearWindow();
string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index));
LoadStructuredProgram(scriptContent);
CreateSerializer().Load(scriptContent);
scriptName.Text = availableScripts.GetItemText(index);
availableScripts.Select(0);
}
private void LoadStructuredProgram(string scriptContent)
private void AddLoadedNode(NodeDisplay nodeDisplay)
{
Variant parsedScript = Json.ParseString(scriptContent);
if (parsedScript.VariantType != Variant.Type.Dictionary) return;
Dictionary scriptData = parsedScript.AsGodotDictionary();
if (!scriptData.ContainsKey("Nodes")) return;
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes =
new System.Collections.Generic.Dictionary<string, NodeDisplay>();
Array nodes = scriptData["Nodes"].AsGodotArray();
for (int i = 0; i < nodes.Count; i++)
{
Dictionary nodeData = nodes[i].AsGodotDictionary();
NodeDisplay nodeDisplay = LoadStructuredNode(nodeData);
if (nodeDisplay == null) continue;
editorWindow.AddChild(nodeDisplay);
RegisterEditorNode(nodeDisplay);
RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes);
}
LoadStructuredConnections(scriptData, loadedNodes);
}
private void RegisterLoadedNode(
Dictionary nodeData,
NodeDisplay nodeDisplay,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
string nodeId = nodeDisplay.Name.ToString();
if (nodeData.ContainsKey("Id"))
{
nodeId = nodeData["Id"].AsString();
}
if (!loadedNodes.ContainsKey(nodeId))
{
loadedNodes.Add(nodeId, nodeDisplay);
}
}
private NodeDisplay LoadStructuredNode(Dictionary nodeData)
{
if (!nodeData.ContainsKey("Type")) return null;
if (!nodeData.ContainsKey("Content")) return null;
string type = nodeData["Type"].AsString();
string content = nodeData["Content"].AsString();
NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, DSLNodes);
if (nodeDisplay == null) return null;
if (nodeData.ContainsKey("Id"))
{
nodeDisplay.Name = nodeData["Id"].AsString();
}
if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY"))
{
float positionX = (float)nodeData["PositionX"].AsDouble();
float positionY = (float)nodeData["PositionY"].AsDouble();
nodeDisplay.PositionOffset = new Vector2(positionX, positionY);
}
return nodeDisplay;
}
private void LoadStructuredConnections(
Dictionary scriptData,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
if (!scriptData.ContainsKey("Connections")) return;
Array connectionData = scriptData["Connections"].AsGodotArray();
for (int i = 0; i < connectionData.Count; i++)
{
Dictionary savedConnection = connectionData[i].AsGodotDictionary();
if (!savedConnection.ContainsKey("From")) continue;
if (!savedConnection.ContainsKey("To")) continue;
if (!savedConnection.ContainsKey("FromPort")) continue;
if (!savedConnection.ContainsKey("ToPort")) continue;
ScriptConnection connection = new ScriptConnection
{
FromNodeId = savedConnection["From"].AsString(),
FromPort = savedConnection["FromPort"].AsInt32(),
ToNodeId = savedConnection["To"].AsString(),
ToPort = savedConnection["ToPort"].AsInt32()
};
if (!loadedNodes.ContainsKey(connection.FromNodeId)) continue;
if (!loadedNodes.ContainsKey(connection.ToNodeId)) continue;
NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId];
NodeDisplay toDisplay = loadedNodes[connection.ToNodeId];
if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) continue;
editorWindow.ConnectNode(
fromDisplay.Name,
connection.FromPort,
toDisplay.Name,
connection.ToPort
);
}
}
private bool ConnectionExists(StringName fromNode, int fromPort, StringName toNode, int toPort)
{
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
if (connection["from_node"].AsStringName() != fromNode) continue;
if ((int)connection["from_port"] != fromPort) continue;
if (connection["to_node"].AsStringName() != toNode) continue;
if ((int)connection["to_port"] != toPort) continue;
return true;
}
return false;
editorWindow.AddChild(nodeDisplay);
RegisterEditorNode(nodeDisplay);
}
public void LoadTemporaryProgram()
@@ -373,65 +184,16 @@ public partial class CodingWindow : PanelContainer
if (robot == null) return;
if (robot.currentNode == null) return;
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes =
new System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay>();
LoadTemporaryNode(robot.currentNode, loadedNodes);
RunningProgramGraphBuilder builder = new RunningProgramGraphBuilder(
DSLNodes,
AddLoadedNode,
ConnectNodes
);
builder.Load(robot.currentNode);
scriptName.Text = robot.currentProgram ?? "";
}
private NodeDisplay LoadTemporaryNode(
ProgramNode programNode,
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
if (programNode == null) return null;
if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode];
NodeDisplay nodeDisplay = NodeDisplay.Load(
programNode.DisplayText,
programNode.Save(),
DSLNodes
);
if (nodeDisplay == null) return null;
editorWindow.AddChild(nodeDisplay);
RegisterEditorNode(nodeDisplay);
loadedNodes.Add(programNode, nodeDisplay);
ConnectTemporaryNode(nodeDisplay, 0, programNode.nextNode, loadedNodes);
ConnectTemporaryNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes);
return nodeDisplay;
}
private void ConnectTemporaryNode(
NodeDisplay fromDisplay,
int fromPort,
ProgramNode targetNode,
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
NodeDisplay toDisplay = LoadTemporaryNode(targetNode, loadedNodes);
if (toDisplay == null) return;
ScriptConnection connection = new ScriptConnection
{
FromNodeId = fromDisplay.Name.ToString(),
FromPort = fromPort,
ToNodeId = toDisplay.Name.ToString(),
ToPort = 0
};
if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) return;
editorWindow.ConnectNode(
fromDisplay.Name,
connection.FromPort,
toDisplay.Name,
connection.ToPort
);
}
public void DeleteProgram()
{
string filename = scriptName.Text;
@@ -450,69 +212,50 @@ public partial class CodingWindow : PanelContainer
public void SaveProgram()
{
Array<Dictionary> savedNodes = BuildSavedNodes();
if (savedNodes.Count <= 0) return;
Dictionary scriptData = new Dictionary();
scriptData["Nodes"] = savedNodes;
scriptData["Connections"] = BuildSavedConnections();
string result = Json.Stringify(scriptData);
string result = CreateSerializer().Save();
if (result.Length <= 0) return;
string filename = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text;
FileHandler.SaveProgram(filename, result);
FileHandler.SaveProgram(GetCurrentScriptName(), result);
SetupScriptOptions();
}
private Array<Dictionary> BuildSavedNodes()
{
Array<Dictionary> savedNodes = new Array<Dictionary>();
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
Dictionary savedNode = new Dictionary();
savedNode["Id"] = nodeDisplay.Name.ToString();
savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower();
savedNode["Content"] = nodeDisplay.node.Save();
savedNode["PositionX"] = nodeDisplay.PositionOffset.X;
savedNode["PositionY"] = nodeDisplay.PositionOffset.Y;
savedNodes.Add(savedNode);
}
return savedNodes;
}
private Array<Dictionary> BuildSavedConnections()
{
Array<Dictionary> savedConnections = new Array<Dictionary>();
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
Dictionary savedConnection = new Dictionary();
savedConnection["From"] = connection["from_node"].AsStringName().ToString();
savedConnection["FromPort"] = (int)connection["from_port"];
savedConnection["To"] = connection["to_node"].AsStringName().ToString();
savedConnection["ToPort"] = (int)connection["to_port"];
savedConnections.Add(savedConnection);
}
return savedConnections;
}
public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort)
{
if (to == from) return;
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
if (connection["from_node"].AsStringName() == from && (int)connection["from_port"] == fromPort) return;
}
ConnectNodes(from, fromPort, to, toPort);
}
private void ConnectNodes(StringName from, int fromPort, StringName to, int toPort)
{
if (HasOutputConnection(from, fromPort)) return;
editorWindow.ConnectNode(from, fromPort, to, toPort);
}
private bool HasOutputConnection(StringName from, int fromPort)
{
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
if (connection["from_node"].AsStringName() != from) continue;
if ((int)connection["from_port"] != fromPort) continue;
return true;
}
return false;
}
private ScriptGraphSerializer CreateSerializer()
{
return new ScriptGraphSerializer(
editorWindow,
DSLNodes,
AddLoadedNode,
ConnectNodes
);
}
public void OnNodeDisconnect(StringName from, int fromPort, StringName to, int toPort)
{
editorWindow.DisconnectNode(from, fromPort, to, toPort);
@@ -0,0 +1,65 @@
using Godot;
using System;
using System.Collections.Generic;
public class RunningProgramGraphBuilder
{
private readonly Dictionary<ProgramNode, PackedScene> dslNodes;
private readonly Action<NodeDisplay> addNode;
private readonly Action<StringName, int, StringName, int> connectNodes;
public RunningProgramGraphBuilder(
Dictionary<ProgramNode, PackedScene> dslNodes,
Action<NodeDisplay> addNode,
Action<StringName, int, StringName, int> connectNodes
)
{
this.dslNodes = dslNodes;
this.addNode = addNode;
this.connectNodes = connectNodes;
}
public void Load(ProgramNode startNode)
{
Dictionary<ProgramNode, NodeDisplay> loadedNodes =
new Dictionary<ProgramNode, NodeDisplay>();
LoadNode(startNode, loadedNodes);
}
private NodeDisplay LoadNode(
ProgramNode programNode,
Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
if (programNode == null) return null;
if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode];
NodeDisplay nodeDisplay = NodeDisplay.Load(
programNode.DisplayText,
programNode.Save(),
dslNodes
);
if (nodeDisplay == null) return null;
addNode(nodeDisplay);
loadedNodes.Add(programNode, nodeDisplay);
ConnectNode(nodeDisplay, 0, programNode.nextNode, loadedNodes);
ConnectNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes);
return nodeDisplay;
}
private void ConnectNode(
NodeDisplay fromDisplay,
int fromPort,
ProgramNode targetNode,
Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
NodeDisplay toDisplay = LoadNode(targetNode, loadedNodes);
if (toDisplay == null) return;
connectNodes(fromDisplay.Name, fromPort, toDisplay.Name, 0);
}
}
@@ -0,0 +1 @@
uid://deaptod52fe2s
+7
View File
@@ -0,0 +1,7 @@
public class ScriptConnection
{
public string FromNodeId;
public int FromPort;
public string ToNodeId;
public int ToPort;
}
+1
View File
@@ -0,0 +1 @@
uid://cvhobhufyp2ni
+108
View File
@@ -0,0 +1,108 @@
using Godot;
using Godot.Collections;
using System.Collections.Generic;
public class ScriptGraphCompiler
{
private readonly GraphEdit editorWindow;
private System.Collections.Generic.Dictionary<StringName, ProgramNode> availableNodes;
public ScriptGraphCompiler(GraphEdit editorWindow)
{
this.editorWindow = editorWindow;
}
public List<ProgramNode> BuildProgram(out string errorMessage)
{
errorMessage = "";
NodeDisplay startNode = FindStartNode();
if (startNode == null)
{
errorMessage = "(FAILED) Script needs exactly one Start node";
return new List<ProgramNode>();
}
BuildAvailableNodeLookup();
return BuildScriptOrder(
startNode,
new List<ProgramNode>(),
new HashSet<StringName>()
);
}
private void BuildAvailableNodeLookup()
{
availableNodes = new System.Collections.Generic.Dictionary<StringName, ProgramNode>();
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
availableNodes.Add(nodeDisplay.Name, nodeDisplay.node);
}
}
private List<ProgramNode> BuildScriptOrder(
NodeDisplay node,
List<ProgramNode> program,
HashSet<StringName> visitedNodes
)
{
if (node == null) return program;
if (visitedNodes.Contains(node.Name)) return program;
visitedNodes.Add(node.Name);
program.Add(node.node);
List<Dictionary> nextConnections = GetOutgoingConnections(node);
if (nextConnections.Count <= 0) return program;
node.node.SetNextNode(nextConnections, availableNodes);
foreach (Dictionary connection in nextConnections)
{
NodeDisplay nextNode = editorWindow.GetNodeOrNull<NodeDisplay>(
new NodePath(connection["to_node"].AsStringName())
);
program = BuildScriptOrder(
nextNode,
program,
visitedNodes
);
}
return program;
}
private List<Dictionary> GetOutgoingConnections(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;
}
}
@@ -0,0 +1 @@
uid://d122nx50nj3wc
+189
View File
@@ -0,0 +1,189 @@
using Godot;
using Godot.Collections;
using System;
public class ScriptGraphSerializer
{
private readonly GraphEdit editorWindow;
private readonly System.Collections.Generic.Dictionary<ProgramNode, PackedScene> dslNodes;
private readonly Action<NodeDisplay> addNode;
private readonly Action<StringName, int, StringName, int> connectNodes;
public ScriptGraphSerializer(
GraphEdit editorWindow,
System.Collections.Generic.Dictionary<ProgramNode, PackedScene> dslNodes,
Action<NodeDisplay> addNode,
Action<StringName, int, StringName, int> connectNodes
)
{
this.editorWindow = editorWindow;
this.dslNodes = dslNodes;
this.addNode = addNode;
this.connectNodes = connectNodes;
}
public void Load(string scriptContent)
{
Variant parsedScript = Json.ParseString(scriptContent);
if (parsedScript.VariantType != Variant.Type.Dictionary) return;
Dictionary scriptData = parsedScript.AsGodotDictionary();
if (!scriptData.ContainsKey("Nodes")) return;
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes =
new System.Collections.Generic.Dictionary<string, NodeDisplay>();
Godot.Collections.Array nodes = scriptData["Nodes"].AsGodotArray();
for (int i = 0; i < nodes.Count; i++)
{
Dictionary nodeData = nodes[i].AsGodotDictionary();
NodeDisplay nodeDisplay = LoadNode(nodeData);
if (nodeDisplay == null) continue;
addNode(nodeDisplay);
RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes);
}
LoadConnections(scriptData, loadedNodes);
}
public string Save()
{
Array<Dictionary> savedNodes = BuildSavedNodes();
if (savedNodes.Count <= 0) return "";
Dictionary scriptData = new Dictionary();
scriptData["Nodes"] = savedNodes;
scriptData["Connections"] = BuildSavedConnections();
return Json.Stringify(scriptData);
}
private NodeDisplay LoadNode(Dictionary nodeData)
{
if (!nodeData.ContainsKey("Type")) return null;
if (!nodeData.ContainsKey("Content")) return null;
string type = nodeData["Type"].AsString();
string content = nodeData["Content"].AsString();
NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, dslNodes);
if (nodeDisplay == null) return null;
if (nodeData.ContainsKey("Id"))
{
nodeDisplay.Name = nodeData["Id"].AsString();
}
if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY"))
{
float positionX = (float)nodeData["PositionX"].AsDouble();
float positionY = (float)nodeData["PositionY"].AsDouble();
nodeDisplay.PositionOffset = new Vector2(positionX, positionY);
}
return nodeDisplay;
}
private void RegisterLoadedNode(
Dictionary nodeData,
NodeDisplay nodeDisplay,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
string nodeId = nodeDisplay.Name.ToString();
if (nodeData.ContainsKey("Id"))
{
nodeId = nodeData["Id"].AsString();
}
if (!loadedNodes.ContainsKey(nodeId))
{
loadedNodes.Add(nodeId, nodeDisplay);
}
}
private void LoadConnections(
Dictionary scriptData,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
if (!scriptData.ContainsKey("Connections")) return;
Godot.Collections.Array connectionData = scriptData["Connections"].AsGodotArray();
for (int i = 0; i < connectionData.Count; i++)
{
Dictionary savedConnection = connectionData[i].AsGodotDictionary();
if (!IsConnectionDataValid(savedConnection)) continue;
ScriptConnection connection = CreateConnection(savedConnection);
ConnectLoadedNodes(connection, loadedNodes);
}
}
private bool IsConnectionDataValid(Dictionary savedConnection)
{
return savedConnection.ContainsKey("From")
&& savedConnection.ContainsKey("To")
&& savedConnection.ContainsKey("FromPort")
&& savedConnection.ContainsKey("ToPort");
}
private ScriptConnection CreateConnection(Dictionary savedConnection)
{
return new ScriptConnection
{
FromNodeId = savedConnection["From"].AsString(),
FromPort = savedConnection["FromPort"].AsInt32(),
ToNodeId = savedConnection["To"].AsString(),
ToPort = savedConnection["ToPort"].AsInt32()
};
}
private void ConnectLoadedNodes(
ScriptConnection connection,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
if (!loadedNodes.ContainsKey(connection.FromNodeId)) return;
if (!loadedNodes.ContainsKey(connection.ToNodeId)) return;
NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId];
NodeDisplay toDisplay = loadedNodes[connection.ToNodeId];
connectNodes(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort);
}
private Array<Dictionary> BuildSavedNodes()
{
Array<Dictionary> savedNodes = new Array<Dictionary>();
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
Dictionary savedNode = new Dictionary();
savedNode["Id"] = nodeDisplay.Name.ToString();
savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower();
savedNode["Content"] = nodeDisplay.node.Save();
savedNode["PositionX"] = nodeDisplay.PositionOffset.X;
savedNode["PositionY"] = nodeDisplay.PositionOffset.Y;
savedNodes.Add(savedNode);
}
return savedNodes;
}
private Array<Dictionary> BuildSavedConnections()
{
Array<Dictionary> savedConnections = new Array<Dictionary>();
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
Dictionary savedConnection = new Dictionary();
savedConnection["From"] = connection["from_node"].AsStringName().ToString();
savedConnection["FromPort"] = (int)connection["from_port"];
savedConnection["To"] = connection["to_node"].AsStringName().ToString();
savedConnection["ToPort"] = (int)connection["to_port"];
savedConnections.Add(savedConnection);
}
return savedConnections;
}
}
@@ -0,0 +1 @@
uid://dsrkrw6524c
+21 -12
View File
@@ -3,7 +3,8 @@ using Godot;
public partial class InventoryDisplay : PanelContainer
{
private PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay();
private readonly PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay();
[Export] VBoxContainer itemList;
[Export] RichTextLabel inventorySpace;
@@ -34,24 +35,32 @@ public partial class InventoryDisplay : PanelContainer
}
public void ReloadItems()
{
ClearItems();
foreach (Item item in GameData.inventory.items)
{
itemList.AddChild(CreateItemDisplay(item));
}
}
private void ClearItems()
{
foreach (Node node in itemList.GetChildren())
{
itemList.RemoveChild(node);
node.QueueFree();
}
}
ItemDisplay display;
foreach (Item item in GameData.inventory.items)
{
display = itemDisplayPrefab.Instantiate<ItemDisplay>();
display.item = item;
display.text.Text = item.data.GetReadableName();
display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}";
display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
itemList.AddChild(display);
}
private ItemDisplay CreateItemDisplay(Item item)
{
ItemDisplay display = itemDisplayPrefab.Instantiate<ItemDisplay>();
display.item = item;
display.text.Text = item.data.GetReadableName();
display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}";
display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
return display;
}
public void OnInventoryUpdate(object sender, EventArgs args)
+73 -69
View File
@@ -2,82 +2,86 @@ using Godot;
public partial class OptionsMenu : PanelContainer
{
private readonly Vector2 panelSize = new Vector2(420, 260);
private readonly Vector2 panelSize = new Vector2(420, 260);
[Export] private OptionButton screenMode;
[Export] private HSlider soundVolume;
[Export] private ColorPickerButton lightColor;
[Export] private OptionButton screenMode;
[Export] private HSlider soundVolume;
[Export] private ColorPickerButton lightColor;
public override void _Ready()
{
CenterPanel();
SetupScreenModes();
screenMode.Select(GameData.screenMode);
soundVolume.Value = GameData.soundVolume * 100f;
lightColor.Color = GameData.lightColor;
ApplyScreenMode(GameData.screenMode);
SoundManager.SetMasterVolume(GameData.soundVolume);
}
public override void _Ready()
{
CenterPanel();
SetupScreenModes();
screenMode.Select(GameData.screenMode);
soundVolume.Value = GameData.soundVolume * 100f;
lightColor.Color = GameData.lightColor;
ApplyScreenMode(GameData.screenMode);
SoundManager.SetMasterVolume(GameData.soundVolume);
}
private void SetupScreenModes()
{
screenMode.Clear();
screenMode.AddItem("Fullscreen");
screenMode.AddItem("Windowed");
screenMode.AddItem("Windowed Fullscreen");
screenMode.Select(2);
}
private void SetupScreenModes()
{
screenMode.Clear();
screenMode.AddItem("Fullscreen");
screenMode.AddItem("Windowed");
screenMode.AddItem("Windowed Fullscreen");
screenMode.Select(2);
}
public void OnScreenModeSelected(int index)
{
GameData.screenMode = index;
ApplyScreenMode(index);
}
public void OnScreenModeSelected(int index)
{
GameData.screenMode = index;
ApplyScreenMode(index);
}
private void ApplyScreenMode(int index)
{
switch (index)
{
case 0:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 1:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 2:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
break;
}
}
private void ApplyScreenMode(int index)
{
switch (index)
{
case 0:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 1:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
case 2:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
break;
default:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
}
}
public void OnSoundVolumeChanged(double value)
{
GameData.soundVolume = (float)value / 100f;
SoundManager.SetMasterVolume(GameData.soundVolume);
}
public void OnSoundVolumeChanged(double value)
{
GameData.soundVolume = (float)value / 100f;
SoundManager.SetMasterVolume(GameData.soundVolume);
}
public void OnLightColorChanged(Color color)
{
GameData.lightColor = color;
LightHandler.RedrawLights(color);
}
public void OnLightColorChanged(Color color)
{
GameData.lightColor = color;
LightHandler.RedrawLights(color);
}
public void CloseOptions()
{
Hide();
GameData.isPaused = false;
}
public void CloseOptions()
{
Hide();
GameData.isPaused = false;
}
private void CenterPanel()
{
CustomMinimumSize = panelSize;
SetAnchorsPreset(LayoutPreset.Center);
OffsetLeft = -panelSize.X / 2f;
OffsetTop = -panelSize.Y / 2f;
OffsetRight = panelSize.X / 2f;
OffsetBottom = panelSize.Y / 2f;
}
private void CenterPanel()
{
CustomMinimumSize = panelSize;
SetAnchorsPreset(LayoutPreset.Center);
OffsetLeft = -panelSize.X / 2f;
OffsetTop = -panelSize.Y / 2f;
OffsetRight = panelSize.X / 2f;
OffsetBottom = panelSize.Y / 2f;
}
}
+32 -26
View File
@@ -10,7 +10,6 @@ public partial class ResearchList : PanelContainer
private bool hasArrangedNodes = false;
private List<Research> currentResearch = new List<Research>();
private List<Research> toDelete = new List<Research>();
public override void _Ready()
{
@@ -18,30 +17,37 @@ public partial class ResearchList : PanelContainer
if (Visible) SetupGraph();
}
public override void _Process(double delta)
public override void _Process(double delta)
{
if(currentResearch.Count > 0) toDelete = new List<Research>();
if (currentResearch.Count <= 0) return;
List<Research> finishedResearch = new List<Research>();
foreach (Research research in currentResearch)
{
ResearchResult result = research.Execute(delta);
if (result == ResearchResult.FINISHED)
{
toDelete.Add(research);
RecalculateResearchStates();
SetupGraph();
}
else if (result == ResearchResult.FAILED)
{
research.state = ResearchState.AVAILABLE;
toDelete.Add(research);
RecalculateResearchStates();
SetupGraph();
}
if (!IsResearchFinished(research, result)) continue;
finishedResearch.Add(research);
}
foreach (Research delete in toDelete)
if (finishedResearch.Count <= 0) return;
foreach (Research research in finishedResearch)
{
currentResearch.Remove(delete);
currentResearch.Remove(research);
}
RecalculateResearchStates();
SetupGraph();
}
private bool IsResearchFinished(Research research, ResearchResult result)
{
if (result == ResearchResult.FINISHED) return true;
if (result != ResearchResult.FAILED) return false;
research.state = ResearchState.AVAILABLE;
return true;
}
public void SetupGraph()
@@ -83,11 +89,10 @@ public partial class ResearchList : PanelContainer
foreach (Node child in researchGraph.GetChildren())
{
if (child is GraphNode)
{
researchGraph.RemoveChild(child);
child.QueueFree();
}
if (child is not GraphNode) continue;
researchGraph.RemoveChild(child);
child.QueueFree();
}
}
@@ -127,9 +132,10 @@ public partial class ResearchList : PanelContainer
private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state)
{
Research research = GameData.availableResearch[id];
Texture2D texture = GD.Load<Texture2D>(texturePath);
Color stateColor = GetColorByState(state);
string tooltipText = GetResearchTooltip(GameData.availableResearch[id]);
string tooltipText = GetResearchTooltip(research);
TextureRect icon = new TextureRect
{
@@ -141,8 +147,8 @@ public partial class ResearchList : PanelContainer
Button button = new Button
{
Text = GetResearchButtonText(GameData.availableResearch[id], state),
Disabled = state != ResearchState.AVAILABLE || !GameData.availableResearch[id].CanStart(),
Text = GetResearchButtonText(research, state),
Disabled = state != ResearchState.AVAILABLE || !research.CanStart(),
TooltipText = tooltipText
};
+7 -2
View File
@@ -12,14 +12,19 @@ public partial class RobotDisplay : PanelContainer
public override void _Process(double delta)
{
string programName = robot.currentProgram ?? "";
string status = $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%";
string status = GetStatusText();
if (status != currentScript.Text)
{
currentScript.Text = status;
}
}
private string GetStatusText()
{
string programName = robot.currentProgram ?? "";
return $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%";
}
public void OnJumpToClicked()
{
EmitSignal(SignalName.OnRobotJumpTo, robot);
+36 -22
View File
@@ -30,37 +30,50 @@ public partial class RobotList : PanelContainer
}
public void ReloadRobots()
{
ClearRobotList();
foreach (Robot robotObject in GameData.robots)
{
robotList.AddChild(CreateRobotDisplay(robotObject));
}
}
private void ClearRobotList()
{
foreach (Node node in robotList.GetChildren())
{
robotList.RemoveChild(node);
node.QueueFree();
}
RobotDisplay display;
}
foreach (Robot robotObject in GameData.robots)
{
display = robotDisplayPrefab.Instantiate<RobotDisplay>();
display.robot = robotObject;
display.listItem.Text = robotObject.Name;
display.OnRobotJumpTo += (robot) =>
{
EmitSignal(SignalName.OnRobotJumpTo, robot);
Visible = false;
};
display.OnRobotFollow += (robot) =>
{
EmitSignal(SignalName.OnRobotFollow, robot);
Visible = false;
};
robotList.AddChild(display);
}
private RobotDisplay CreateRobotDisplay(Robot robotObject)
{
RobotDisplay display = robotDisplayPrefab.Instantiate<RobotDisplay>();
display.robot = robotObject;
display.listItem.Text = robotObject.Name;
display.OnRobotJumpTo += HandleRobotJumpTo;
display.OnRobotFollow += HandleRobotFollow;
return display;
}
private void HandleRobotJumpTo(Robot robot)
{
EmitSignal(SignalName.OnRobotJumpTo, robot);
Visible = false;
}
private void HandleRobotFollow(Robot robot)
{
EmitSignal(SignalName.OnRobotFollow, robot);
Visible = false;
}
public void ReloadSelectableRobots()
{
selectableRobots.Clear();
if(GameData.robots.Count >= GameData.maxRobotCount)
if (GameData.robots.Count >= GameData.maxRobotCount)
{
selectableRobots.AddItem("You can't have more robots currently!");
selectableRobots.Disabled = true;
@@ -81,7 +94,8 @@ public partial class RobotList : PanelContainer
public void SpawnRobot()
{
if(spawnId.Length <= 0) return;
if (spawnId.Length <= 0) return;
GameData.inventory.RemoveItem(spawnId, 1);
Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
robot.Name = $"Robot #{GameData.robots.Count}";
@@ -97,8 +111,8 @@ public partial class RobotList : PanelContainer
public void OnRobotSelect(int index)
{
//Selected option is the "please select..." option
if(index == 0) return;
if (index == 0) return;
spawnId = ItemData.GetIndex(selectableRobots.GetItemText(index));
}
}
+9 -9
View File
@@ -35,24 +35,24 @@ public partial class TutorialBubble : PanelContainer
{
"Welcome to the ruin. I am B.O.B.",
"B.O.B. means Building Operation Buddy. I will keep the lamps on while you teach the robots what to do.",
"The last remaining unit in this ruin saved you from your fall and brought you here. If you want to leave, you have to explore the ruin",
"The last remaining unit in this ruin saved you from your fall and brought you here. If you want to leave, you have to explore the ruin.",
"You do not walk through the ruin yourself. Your robots explore, harvest, craft and carry progress for you.",
"The top bar shows survival pressure: energy, water and food. If those run out, the expedition ends.",
"The necessary resources will be auto-consumed during your time here.",
"For Energy: Steam, Battery v1 and Battery v2; For Thirst: Water; For Food: Mushrooms",
"So try to keep a certain stock of those items to avoid dying in this ruin",
"For energy: steam, battery v1 and battery v2. For thirst: water. For food: mushrooms.",
"Try to keep a stock of those items to avoid dying in this ruin.",
"Open the robot panel (Default: [R]) to inspect your robots. A robot can overheat, lose maintenance and slow down if ignored.",
"A robot that overheated has to cool down for a certain amount of time and cannot execute scripts",
"An overheated robot has to cool down for a while and cannot execute scripts.",
"Use the script editor (Clicking 'Jump to' from the robot panel) to give robots commands. Start simple: move, explore, harvest, then craft.",
"Research unlocks better tools, buildings, robot upgrades and deeper progression. The graph is your technology map (Default: [T]).",
"The necessary ingredients can be found when hovering over the respective technologies in your technology map",
"The required ingredients can be found by hovering over technologies in your technology map.",
"The inventory (Default: [I]) stores everything your robots collect. Gates and research both consume items from it.",
"Each gate blocks the next layer. When you have the required items, use Open Gate in the top bar.",
"The necessary ingredients can be found when hovering over the 'Open Gate' button",
"The required ingredients can be found by hovering over the Open Gate button.",
"The map (Default: [M]) shows what your robots have discovered. Exploration matters because resources are hidden in the ruin.",
"Depper layers contain more advanced resources and unlocking the gate at the lowest point allows you to leave the ruin",
"Be careful: The resources are not endless from the beginning. Converting a robot to a drill-unit (Using the node 'Sacrifice') makes them endless",
"Two things to keep in mind: 1)Endless resources have a lower rate of extraction 2)Sacrificing your last robot without backups in your inventory makes you unable to do anything",
"Deeper layers contain more advanced resources. Unlocking the gate at the lowest point allows you to leave the ruin.",
"Be careful: resources are not endless at the beginning. Converting a robot to a drill unit with Sacrifice makes them endless.",
"Two things to keep in mind: endless resources extract slower, and sacrificing your last robot without a backup in your inventory leaves you stranded.",
"That is enough briefing. Build a loop, keep the robots alive, and open the lower gates. B.O.B. believes in organized chaos."
};
}