Added final features for this release. Now only polishing (if needed) remains.

Features: Sacrifice-Node, Maintain-Node, Options for screen type, lightcolor and soundvolume, tied in sound effects, game pause when menu is open, visibly open up gate when opening it.
This commit is contained in:
2026-05-10 14:09:14 +02:00
parent 228e81ab4e
commit 8170b700b2
28 changed files with 797 additions and 14 deletions
+46
View File
@@ -0,0 +1,46 @@
[gd_scene format=3 uid="uid://fe7so4543q4x"]
[ext_resource type="Script" uid="uid://b6kxwmuhmruul" path="res://Scripts/UI/DSL/NodeDisplay.cs" id="1_maintain"]
[ext_resource type="Texture2D" uid="uid://wq8yc0u0ee33" path="res://Assets/Images/TrashSymbol.png" id="2_trash"]
[node name="Maintain" type="PanelContainer" unique_id=346305609 node_paths=PackedStringArray("editorDisplay", "listDisplay")]
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_bottom = 31.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
script = ExtResource("1_maintain")
editorDisplay = NodePath("EditorDisplay")
listDisplay = NodePath("ListDisplay")
[node name="EditorDisplay" type="PanelContainer" parent="." unique_id=237286109]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="EditorDisplay" unique_id=222639319]
layout_mode = 2
alignment = 1
[node name="Flavour" type="RichTextLabel" parent="EditorDisplay/HBoxContainer" unique_id=50532719]
layout_mode = 2
size_flags_horizontal = 3
text = "Maintain"
fit_content = true
autowrap_mode = 0
horizontal_alignment = 1
vertical_alignment = 1
[node name="TextureButton" type="TextureButton" parent="EditorDisplay/HBoxContainer" unique_id=1753994239]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.2
texture_normal = ExtResource("2_trash")
[node name="ListDisplay" type="Button" parent="." unique_id=991375625]
layout_mode = 2
tooltip_text = "Repairs the robot by 10% and consumes one matching gear."
text = "Maintain"
[connection signal="pressed" from="EditorDisplay/HBoxContainer/TextureButton" to="." method="DeleteNodePressed"]
+46
View File
@@ -0,0 +1,46 @@
[gd_scene format=3 uid="uid://bxph44i8mad1i"]
[ext_resource type="Script" uid="uid://b6kxwmuhmruul" path="res://Scripts/UI/DSL/NodeDisplay.cs" id="1_sacrifice"]
[ext_resource type="Texture2D" uid="uid://wq8yc0u0ee33" path="res://Assets/Images/TrashSymbol.png" id="2_trash"]
[node name="Sacrifice" type="PanelContainer" unique_id=442348941 node_paths=PackedStringArray("editorDisplay", "listDisplay")]
anchors_preset = 14
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
offset_bottom = 31.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
script = ExtResource("1_sacrifice")
editorDisplay = NodePath("EditorDisplay")
listDisplay = NodePath("ListDisplay")
[node name="EditorDisplay" type="PanelContainer" parent="." unique_id=1064798043]
layout_mode = 2
[node name="HBoxContainer" type="HBoxContainer" parent="EditorDisplay" unique_id=1195324631]
layout_mode = 2
alignment = 1
[node name="Flavour" type="RichTextLabel" parent="EditorDisplay/HBoxContainer" unique_id=1628437503]
layout_mode = 2
size_flags_horizontal = 3
text = "Sacrifice"
fit_content = true
autowrap_mode = 0
horizontal_alignment = 1
vertical_alignment = 1
[node name="TextureButton" type="TextureButton" parent="EditorDisplay/HBoxContainer" unique_id=1813377435]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.2
texture_normal = ExtResource("2_trash")
[node name="ListDisplay" type="Button" parent="." unique_id=2140824916]
layout_mode = 2
tooltip_text = "Sacrifices the robot and makes the resource on this tile endless."
text = "Sacrifice"
[connection signal="pressed" from="EditorDisplay/HBoxContainer/TextureButton" to="." method="DeleteNodePressed"]
+22
View File
@@ -3,7 +3,10 @@
[ext_resource type="Script" uid="uid://br2udyi6t8yvf" path="res://Scripts/World/World.cs" id="1_7lihs"] [ext_resource type="Script" uid="uid://br2udyi6t8yvf" path="res://Scripts/World/World.cs" id="1_7lihs"]
[ext_resource type="AudioStream" uid="uid://dcb746ldlm6ta" path="res://Assets/Sound/Background.mp3" id="1_rajel"] [ext_resource type="AudioStream" uid="uid://dcb746ldlm6ta" path="res://Assets/Sound/Background.mp3" id="1_rajel"]
[ext_resource type="Script" uid="uid://dqrdb3bvws6b6" path="res://Scripts/Core/SteamworksHandler.cs" id="2_b2bpf"] [ext_resource type="Script" uid="uid://dqrdb3bvws6b6" path="res://Scripts/Core/SteamworksHandler.cs" id="2_b2bpf"]
[ext_resource type="AudioStream" uid="uid://d001edcgov542" path="res://Assets/Sound/button.wav" id="2_button"]
[ext_resource type="AudioStream" uid="uid://bo0jlfldvl3fc" path="res://Assets/Sound/mining.wav" id="2_mining"]
[ext_resource type="Script" uid="uid://c7khr6oist3ku" path="res://Scripts/UI/Common/Camera3d.cs" id="3_7lihs"] [ext_resource type="Script" uid="uid://c7khr6oist3ku" path="res://Scripts/UI/Common/Camera3d.cs" id="3_7lihs"]
[ext_resource type="Script" uid="uid://gvy12mefkwax" path="res://Scripts/Core/SoundManager.cs" id="3_sound"]
[ext_resource type="Script" uid="uid://bm7knir4552j5" path="res://Scripts/UI/Common/UIHandler.cs" id="4_fgofq"] [ext_resource type="Script" uid="uid://bm7knir4552j5" path="res://Scripts/UI/Common/UIHandler.cs" id="4_fgofq"]
[ext_resource type="Script" uid="uid://bsd6n6b06a4pe" path="res://Scripts/UI/DSL/CodingWindow.cs" id="6_7lihs"] [ext_resource type="Script" uid="uid://bsd6n6b06a4pe" path="res://Scripts/UI/DSL/CodingWindow.cs" id="6_7lihs"]
[ext_resource type="Script" uid="uid://k6vlo7ulvtep" path="res://Scripts/UI/Robots/RobotList.cs" id="7_2irst"] [ext_resource type="Script" uid="uid://k6vlo7ulvtep" path="res://Scripts/UI/Robots/RobotList.cs" id="7_2irst"]
@@ -59,6 +62,19 @@ volume_db = -25.0
autoplay = true autoplay = true
parameters/looping = true parameters/looping = true
[node name="ButtonSound" type="AudioStreamPlayer" parent="." unique_id=696063778]
stream = ExtResource("2_button")
volume_db = -8.0
[node name="MiningSound" type="AudioStreamPlayer" parent="." unique_id=1851503354]
stream = ExtResource("2_mining")
volume_db = -12.0
[node name="SoundManager" type="Node" parent="." unique_id=1220134706 node_paths=PackedStringArray("buttonSound", "miningSound")]
script = ExtResource("3_sound")
buttonSound = NodePath("../ButtonSound")
miningSound = NodePath("../MiningSound")
[node name="World" type="Node3D" parent="." unique_id=770208789] [node name="World" type="Node3D" parent="." unique_id=770208789]
script = ExtResource("1_7lihs") script = ExtResource("1_7lihs")
@@ -329,6 +345,11 @@ layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
text = "Save" text = "Save"
[node name="Delete" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving" unique_id=1891250978]
layout_mode = 2
size_flags_horizontal = 3
text = "Delete"
[node name="Load" type="OptionButton" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow" unique_id=970393437] [node name="Load" type="OptionButton" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow" unique_id=970393437]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
@@ -679,6 +700,7 @@ text = "Next"
[connection signal="button_up" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons/Clear" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="ClearWindow"] [connection signal="button_up" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons/Clear" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="ClearWindow"]
[connection signal="button_up" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons/Compile" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="CompileProgram"] [connection signal="button_up" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons/Compile" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="CompileProgram"]
[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving/Save" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="SaveProgram"] [connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving/Save" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="SaveProgram"]
[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving/Delete" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="DeleteProgram"]
[connection signal="item_selected" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Load" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="LoadProgram"] [connection signal="item_selected" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Load" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="LoadProgram"]
[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Close" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="CloseWindow"] [connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Close" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="CloseWindow"]
[connection signal="item_selected" from="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer/Spawning/OptionButton" to="CanvasLayer/UIHandler/MainUI/Content/RobotList" method="OnRobotSelect"] [connection signal="item_selected" from="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer/Spawning/OptionButton" to="CanvasLayer/UIHandler/MainUI/Content/RobotList" method="OnRobotSelect"]
+8 -1
View File
@@ -6,6 +6,7 @@
[ext_resource type="Texture2D" uid="uid://dt84awx33mulb" path="res://Assets/Images/ResearchSymbol.png" id="3_df05h"] [ext_resource type="Texture2D" uid="uid://dt84awx33mulb" path="res://Assets/Images/ResearchSymbol.png" id="3_df05h"]
[ext_resource type="Texture2D" uid="uid://dm0w2gtcsa5l4" path="res://Assets/Images/Items/Batteryv1Symbol.png" id="4_8um5k"] [ext_resource type="Texture2D" uid="uid://dm0w2gtcsa5l4" path="res://Assets/Images/Items/Batteryv1Symbol.png" id="4_8um5k"]
[ext_resource type="Texture2D" uid="uid://qqprre8xl8gv" path="res://Assets/Images/Items/Dynamov2Symbol.png" id="5_xim88"] [ext_resource type="Texture2D" uid="uid://qqprre8xl8gv" path="res://Assets/Images/Items/Dynamov2Symbol.png" id="5_xim88"]
[ext_resource type="PackedScene" uid="uid://cpq7ppe8bw2bq" path="res://Scenes/Options.tscn" id="6_options"]
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bnhvo"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bnhvo"]
bg_color = Color(0.24115604, 0.24115607, 0.24115595, 1) bg_color = Color(0.24115604, 0.24115607, 0.24115595, 1)
@@ -27,7 +28,7 @@ corner_radius_top_right = 10
corner_radius_bottom_right = 10 corner_radius_bottom_right = 10
corner_radius_bottom_left = 10 corner_radius_bottom_left = 10
[node name="MainMenu" type="Control" unique_id=622011874] [node name="MainMenu" type="Control" unique_id=622011874 node_paths=PackedStringArray("options")]
layout_mode = 3 layout_mode = 3
anchors_preset = 15 anchors_preset = 15
anchor_right = 1.0 anchor_right = 1.0
@@ -35,6 +36,7 @@ anchor_bottom = 1.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("1_tt5f1") script = ExtResource("1_tt5f1")
options = NodePath("Options")
[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1984828000] [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1984828000]
stream = ExtResource("2_8um5k") stream = ExtResource("2_8um5k")
@@ -152,6 +154,11 @@ theme_override_styles/normal = SubResource("StyleBoxFlat_bnhvo")
theme_override_styles/hover = SubResource("StyleBoxFlat_tt5f1") theme_override_styles/hover = SubResource("StyleBoxFlat_tt5f1")
text = "Exit Game" text = "Exit Game"
[node name="Options" parent="." instance=ExtResource("6_options")]
visible = false
layout_mode = 1
[connection signal="button_up" from="CenterContainer/VBoxContainer/btnPlay" to="." method="OnPlayPressed"] [connection signal="button_up" from="CenterContainer/VBoxContainer/btnPlay" to="." method="OnPlayPressed"]
[connection signal="button_up" from="CenterContainer/VBoxContainer/btnLoad" to="." method="OnLoadPressed"] [connection signal="button_up" from="CenterContainer/VBoxContainer/btnLoad" to="." method="OnLoadPressed"]
[connection signal="button_up" from="CenterContainer/VBoxContainer/btnOptions" to="." method="OnOptionsPressed"]
[connection signal="button_up" from="CenterContainer/VBoxContainer/btnExit" to="." method="OnQuitPressed"] [connection signal="button_up" from="CenterContainer/VBoxContainer/btnExit" to="." method="OnQuitPressed"]
+44 -4
View File
@@ -1,16 +1,28 @@
[gd_scene format=3 uid="uid://cpq7ppe8bw2bq"] [gd_scene format=3 uid="uid://cpq7ppe8bw2bq"]
[node name="Options" type="PanelContainer" unique_id=230632848] [ext_resource type="Script" path="res://Scripts/UI/Menus/OptionsMenu.cs" id="1_options"]
[node name="Options" type="PanelContainer" unique_id=230632848 node_paths=PackedStringArray("screenMode", "soundVolume", "lightColor")]
custom_minimum_size = Vector2(420, 260)
anchors_preset = 8 anchors_preset = 8
anchor_left = 0.5 anchor_left = 0.5
anchor_top = 0.5 anchor_top = 0.5
anchor_right = 0.5 anchor_right = 0.5
anchor_bottom = 0.5 anchor_bottom = 0.5
offset_left = -210.0
offset_top = -130.0
offset_right = 210.0
offset_bottom = 130.0
grow_horizontal = 2 grow_horizontal = 2
grow_vertical = 2 grow_vertical = 2
script = ExtResource("1_options")
screenMode = NodePath("VBoxContainer/GridContainer/ScreenMode")
soundVolume = NodePath("VBoxContainer/GridContainer/SoundVolume")
lightColor = NodePath("VBoxContainer/GridContainer/LightColor")
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1149639381] [node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1149639381]
layout_mode = 2 layout_mode = 2
theme_override_constants/separation = 10
[node name="Title" type="RichTextLabel" parent="VBoxContainer" unique_id=1694731141] [node name="Title" type="RichTextLabel" parent="VBoxContainer" unique_id=1694731141]
layout_mode = 2 layout_mode = 2
@@ -24,16 +36,44 @@ horizontal_alignment = 1
layout_mode = 2 layout_mode = 2
columns = 2 columns = 2
[node name="RichTextLabel" type="RichTextLabel" parent="VBoxContainer/GridContainer" unique_id=189106367] [node name="ScreenModeLabel" type="RichTextLabel" parent="VBoxContainer/GridContainer"]
layout_mode = 2 layout_mode = 2
text = "Light color: " text = "Screen"
fit_content = true fit_content = true
autowrap_mode = 0 autowrap_mode = 0
[node name="ColorPickerButton" type="ColorPickerButton" parent="VBoxContainer/GridContainer" unique_id=1830638845] [node name="ScreenMode" type="OptionButton" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
[node name="SoundVolumeLabel" type="RichTextLabel" parent="VBoxContainer/GridContainer"]
layout_mode = 2
text = "Sound"
fit_content = true
autowrap_mode = 0
[node name="SoundVolume" type="HSlider" parent="VBoxContainer/GridContainer"]
layout_mode = 2
size_flags_horizontal = 3
max_value = 100.0
step = 1.0
value = 80.0
[node name="LightColorLabel" type="RichTextLabel" parent="VBoxContainer/GridContainer"]
layout_mode = 2
text = "Light color"
fit_content = true
autowrap_mode = 0
[node name="LightColor" type="ColorPickerButton" parent="VBoxContainer/GridContainer" unique_id=1830638845]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
[node name="Button" type="Button" parent="VBoxContainer" unique_id=1183264327] [node name="Button" type="Button" parent="VBoxContainer" unique_id=1183264327]
layout_mode = 2 layout_mode = 2
text = "Close" text = "Close"
[connection signal="item_selected" from="VBoxContainer/GridContainer/ScreenMode" to="." method="OnScreenModeSelected"]
[connection signal="value_changed" from="VBoxContainer/GridContainer/SoundVolume" to="." method="OnSoundVolumeChanged"]
[connection signal="color_changed" from="VBoxContainer/GridContainer/LightColor" to="." method="OnLightColorChanged"]
[connection signal="pressed" from="VBoxContainer/Button" to="." method="CloseOptions"]
+19
View File
@@ -62,6 +62,25 @@ public class FileHandler
return file.GetAsText(); return file.GetAsText();
} }
public static bool DeleteProgram(string name)
{
CreateScriptDirectory();
string path = GetProgramPath(name);
if (!FileAccess.FileExists(path))
{
return false;
}
DirAccess dir = DirAccess.Open(ScriptDirectory);
if (dir == null)
{
return false;
}
return dir.Remove($"{name}{ScriptExtension}") == Error.Ok;
}
private static string GetProgramPath(string filename) private static string GetProgramPath(string filename)
{ {
return $"{ScriptDirectory}/{filename}{ScriptExtension}"; return $"{ScriptDirectory}/{filename}{ScriptExtension}";
+23
View File
@@ -25,9 +25,12 @@ public partial class GameData
public static Dictionary<int, List<Ingredient>> gateUnlocks; public static Dictionary<int, List<Ingredient>> gateUnlocks;
public static bool loadSaveOnStart = false; public static bool loadSaveOnStart = false;
public static bool showTutorial = true; public static bool showTutorial = true;
public static bool isPaused = false;
public static Color primaryColor = new Color("#276ac2"); public static Color primaryColor = new Color("#276ac2");
public static Color lightColor = new Color("#7efff5"); public static Color lightColor = new Color("#7efff5");
public static int screenMode = 2;
public static float soundVolume = 0.8f;
public static int ruinSize = 10; public static int ruinSize = 10;
public static int layerSize = 20; public static int layerSize = 20;
@@ -44,12 +47,14 @@ public partial class GameData
robotStats = new RobotStats(); robotStats = new RobotStats();
inventory = new Inventory(); inventory = new Inventory();
availableResearch = ResourceLoader.LoadResearch(); availableResearch = ResourceLoader.LoadResearch();
map = null;
robots.Clear(); robots.Clear();
currentLayer = 0; currentLayer = 0;
visibleLayer = 0; visibleLayer = 0;
lowestLayer = 0; lowestLayer = 0;
maxRobotCount = 10; maxRobotCount = 10;
canMove = true; canMove = true;
isPaused = false;
} }
public static void RebuildRobotStatsFromResearch() public static void RebuildRobotStatsFromResearch()
@@ -65,4 +70,22 @@ public partial class GameData
} }
} }
} }
public static bool HasSpawnableRobotInInventory()
{
foreach (Item item in inventory.items)
{
if (robotStats.RobotTypes.ContainsKey(item.data.Id) && item.currentAmount > 0)
{
return true;
}
}
return false;
}
public static bool HasNoRobotRecovery()
{
return robots.Count <= 0 && !HasSpawnableRobotInInventory();
}
} }
+3 -1
View File
@@ -74,7 +74,9 @@ public partial class ResourceLoader
{ new ElseNode(), GD.Load<PackedScene>("res://Prefabs/DSL/ElseNode.tscn") }, { new ElseNode(), GD.Load<PackedScene>("res://Prefabs/DSL/ElseNode.tscn") },
{ new UntilNode(), GD.Load<PackedScene>("res://Prefabs/DSL/UntilNode.tscn") }, { new UntilNode(), GD.Load<PackedScene>("res://Prefabs/DSL/UntilNode.tscn") },
{ new ForNode(), GD.Load<PackedScene>("res://Prefabs/DSL/ForNode.tscn") }, { new ForNode(), GD.Load<PackedScene>("res://Prefabs/DSL/ForNode.tscn") },
{ new WhileNode(), GD.Load<PackedScene>("res://Prefabs/DSL/WhileNode.tscn") } { new WhileNode(), GD.Load<PackedScene>("res://Prefabs/DSL/WhileNode.tscn") },
{ new MaintainNode(), GD.Load<PackedScene>("res://Prefabs/DSL/MaintainNode.tscn") },
{ new SacrificeNode(), GD.Load<PackedScene>("res://Prefabs/DSL/SacrificeNode.tscn") }
}; };
return nodes; return nodes;
} }
+11
View File
@@ -8,6 +8,7 @@ public class SaveGameData
public int LowestLayer { get; set; } public int LowestLayer { get; set; }
public int MaxRobotCount { get; set; } public int MaxRobotCount { get; set; }
public bool CanMove { get; set; } public bool CanMove { get; set; }
public SettingsSaveData Settings { get; set; }
public SurvivalSaveData Survival { get; set; } public SurvivalSaveData Survival { get; set; }
public List<ItemSaveData> Inventory { get; set; } public List<ItemSaveData> Inventory { get; set; }
public List<ResearchSaveData> Research { get; set; } public List<ResearchSaveData> Research { get; set; }
@@ -15,6 +16,16 @@ public class SaveGameData
public List<RobotSaveData> Robots { get; set; } public List<RobotSaveData> Robots { get; set; }
} }
public class SettingsSaveData
{
public int ScreenMode { get; set; }
public float SoundVolume { get; set; }
public float LightColorR { get; set; }
public float LightColorG { get; set; }
public float LightColorB { get; set; }
public float LightColorA { get; set; }
}
public class SurvivalSaveData public class SurvivalSaveData
{ {
public float Hunger { get; set; } public float Hunger { get; set; }
+54 -1
View File
@@ -61,6 +61,7 @@ public static class SaveGameManager
LowestLayer = GameData.lowestLayer, LowestLayer = GameData.lowestLayer,
MaxRobotCount = GameData.maxRobotCount, MaxRobotCount = GameData.maxRobotCount,
CanMove = GameData.canMove, CanMove = GameData.canMove,
Settings = CreateSettingsSaveData(),
Survival = CreateSurvivalSaveData(), Survival = CreateSurvivalSaveData(),
Inventory = CreateInventorySaveData(), Inventory = CreateInventorySaveData(),
Research = CreateResearchSaveData(), Research = CreateResearchSaveData(),
@@ -80,6 +81,7 @@ public static class SaveGameManager
GameData.maxRobotCount = saveGame.MaxRobotCount; GameData.maxRobotCount = saveGame.MaxRobotCount;
GameData.canMove = saveGame.CanMove; GameData.canMove = saveGame.CanMove;
ApplySettingsData(saveGame.Settings);
ApplySurvivalData(saveGame.Survival); ApplySurvivalData(saveGame.Survival);
ApplyInventoryData(saveGame.Inventory); ApplyInventoryData(saveGame.Inventory);
ApplyResearchData(saveGame.Research); ApplyResearchData(saveGame.Research);
@@ -100,6 +102,19 @@ public static class SaveGameManager
}; };
} }
private static SettingsSaveData CreateSettingsSaveData()
{
return new SettingsSaveData
{
ScreenMode = GameData.screenMode,
SoundVolume = GameData.soundVolume,
LightColorR = GameData.lightColor.R,
LightColorG = GameData.lightColor.G,
LightColorB = GameData.lightColor.B,
LightColorA = GameData.lightColor.A
};
}
private static List<ItemSaveData> CreateInventorySaveData() private static List<ItemSaveData> CreateInventorySaveData()
{ {
List<ItemSaveData> result = new List<ItemSaveData>(); List<ItemSaveData> result = new List<ItemSaveData>();
@@ -201,6 +216,7 @@ public static class SaveGameManager
LowestLayer = saveGame.LowestLayer, LowestLayer = saveGame.LowestLayer,
MaxRobotCount = saveGame.MaxRobotCount, MaxRobotCount = saveGame.MaxRobotCount,
CanMove = saveGame.CanMove, CanMove = saveGame.CanMove,
Settings = saveGame.Settings,
Survival = saveGame.Survival, Survival = saveGame.Survival,
Inventory = saveGame.Inventory, Inventory = saveGame.Inventory,
Research = new List<ResearchSaveData>(), Research = new List<ResearchSaveData>(),
@@ -286,6 +302,43 @@ public static class SaveGameManager
GameData.survival.elapsedSeconds = survival.ElapsedSeconds; GameData.survival.elapsedSeconds = survival.ElapsedSeconds;
} }
private static void ApplySettingsData(SettingsSaveData settings)
{
if (settings == null) return;
GameData.screenMode = settings.ScreenMode;
GameData.soundVolume = settings.SoundVolume;
GameData.lightColor = new Color(
settings.LightColorR,
settings.LightColorG,
settings.LightColorB,
settings.LightColorA
);
ApplyScreenMode(settings.ScreenMode);
SoundManager.SetMasterVolume(settings.SoundVolume);
LightHandler.RedrawLights(GameData.lightColor);
}
private static void ApplyScreenMode(int screenMode)
{
switch (screenMode)
{
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 static void ApplyInventoryData(List<ItemSaveData> savedItems) private static void ApplyInventoryData(List<ItemSaveData> savedItems)
{ {
GameData.inventory = new Inventory(); GameData.inventory = new Inventory();
@@ -353,7 +406,7 @@ public static class SaveGameManager
tile.containsResource = savedTile.ContainsResource; tile.containsResource = savedTile.ContainsResource;
tile.wasVisited = savedTile.WasVisited; tile.wasVisited = savedTile.WasVisited;
tile.resource = savedTile.Resource == null ? null : GameResource.FromSaveData(savedTile.Resource); tile.resource = savedTile.Resource == null ? null : GameResource.FromSaveData(savedTile.Resource);
tile.ContentNode.Visible = savedTile.WasVisited || tile.collapsedMesh == "gate"; tile.ContentNode.Visible = savedTile.WasVisited || (tile.collapsedMesh == "gate" && !layer.isGateOpen);
} }
} }
} }
+44
View File
@@ -0,0 +1,44 @@
using Godot;
public partial class SoundManager : Node
{
private static SoundManager instance;
[Export] private AudioStreamPlayer buttonSound;
[Export] private AudioStreamPlayer miningSound;
public override void _Ready()
{
instance = this;
}
public override void _ExitTree()
{
if (instance == this)
{
instance = null;
}
}
public static void PlayButton()
{
if (instance == null || instance.buttonSound == null) return;
instance.buttonSound.Play();
}
public static void PlayMining()
{
if (instance == null || instance.miningSound == null) return;
instance.miningSound.Play();
}
public static void SetMasterVolume(float percent)
{
percent = Mathf.Clamp(percent, 0f, 1f);
int busIndex = AudioServer.GetBusIndex("Master");
AudioServer.SetBusVolumeDb(busIndex, Mathf.LinearToDb(percent));
AudioServer.SetBusMute(busIndex, percent <= 0f);
}
}
+1
View File
@@ -0,0 +1 @@
uid://gvy12mefkwax
+1
View File
@@ -25,6 +25,7 @@ public class HarvestNode : ProgramNode
if (tile.resource.Extract(delta)) if (tile.resource.Extract(delta))
{ {
SoundManager.PlayMining();
if (!GameData.inventory.AddItem(new Item {data = tile.resource.item}, 1)) if (!GameData.inventory.AddItem(new Item {data = tile.resource.item}, 1))
{ {
lastExecutionMessage = "Not enough space"; lastExecutionMessage = "Not enough space";
+51
View File
@@ -0,0 +1,51 @@
using Godot;
public class MaintainNode : ProgramNode
{
private const float MaintenancePerGear = 10f;
public MaintainNode()
{
DisplayText = "Maintain";
}
public override NodeResult Execute(Robot robot, double delta)
{
string gearId = GetGearId(robot);
if (!GameData.inventory.TryRemoveItem(gearId, 1))
{
lastExecutionMessage = $"Missing {ItemData.GetReadableName(gearId)}";
return NodeResult.FAILURE;
}
robot.Maintain(MaintenancePerGear);
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
public override void ReadParameters(NodeDisplay display)
{
}
public override ProgramNode Duplicate()
{
MaintainNode duplicate = new MaintainNode();
return duplicate;
}
public override void Setup(NodeDisplay display)
{
}
public override string Save()
{
return $"Name: {DisplayText}";
}
public static string GetGearId(Robot robot)
{
string robotType = robot == null ? "stone_robot" : robot.robotType;
return robotType.Replace("_robot", "_gear");
}
}
+1
View File
@@ -0,0 +1 @@
uid://bmibtsh1iq2x
+52
View File
@@ -0,0 +1,52 @@
using Godot;
public class SacrificeNode : ProgramNode
{
public SacrificeNode()
{
DisplayText = "Sacrifice";
}
public override NodeResult Execute(Robot robot, double delta)
{
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
if (!tile.containsResource || tile.resource == null)
{
lastExecutionMessage = "No resource on this tile";
return NodeResult.FAILURE;
}
if (tile.resource.IsEndless())
{
lastExecutionMessage = "Resource is already endless";
return NodeResult.FAILURE;
}
tile.resource.MakeEndless();
GameData.robots.Remove(robot);
robot.QueueFree();
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
public override void ReadParameters(NodeDisplay display)
{
}
public override ProgramNode Duplicate()
{
SacrificeNode duplicate = new SacrificeNode();
return duplicate;
}
public override void Setup(NodeDisplay display)
{
}
public override string Save()
{
return $"Name: {DisplayText}";
}
}
+1
View File
@@ -0,0 +1 @@
uid://dq8e7txyldpew
+31 -6
View File
@@ -1,5 +1,8 @@
public class GameResource public class GameResource
{ {
private const float NormalExtractionSpeed = 1f;
private const float EndlessExtractionSpeed = 4f;
public string name; public string name;
public ItemData item; public ItemData item;
@@ -15,17 +18,23 @@ public class GameResource
maxAmount = GameData.rand.Next(1000, 10000); maxAmount = GameData.rand.Next(1000, 10000);
currentAmount = maxAmount; currentAmount = maxAmount;
isEndless = false; isEndless = false;
extractionSpeed = 1f; extractionSpeed = NormalExtractionSpeed;
item = GameData.availableItems[name]; item = GameData.availableItems[name];
} }
public static GameResource FromSaveData(ResourceSaveData saveData) public static GameResource FromSaveData(ResourceSaveData saveData)
{ {
GameResource resource = new GameResource(saveData.Name); GameResource resource = new GameResource(saveData.Name)
resource.currentAmount = saveData.CurrentAmount; {
resource.maxAmount = saveData.MaxAmount; currentAmount = saveData.CurrentAmount,
resource.isEndless = saveData.IsEndless; maxAmount = saveData.MaxAmount,
resource.extractionSpeed = saveData.ExtractionSpeed; isEndless = saveData.IsEndless,
extractionSpeed = saveData.ExtractionSpeed
};
if (resource.isEndless && resource.extractionSpeed <= NormalExtractionSpeed)
{
resource.extractionSpeed = EndlessExtractionSpeed;
}
resource.timeSinceLastExtraction = saveData.TimeSinceLastExtraction; resource.timeSinceLastExtraction = saveData.TimeSinceLastExtraction;
return resource; return resource;
} }
@@ -64,4 +73,20 @@ public class GameResource
{ {
return (isEndless || currentAmount > 0) && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED; return (isEndless || currentAmount > 0) && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED;
} }
public void MakeEndless()
{
isEndless = true;
extractionSpeed = EndlessExtractionSpeed;
}
public bool IsEndless()
{
return isEndless;
}
public float GetExtractionSpeed()
{
return extractionSpeed;
}
} }
+12
View File
@@ -29,6 +29,8 @@ public partial class Robot : Node3D
public override void _Process(double delta) public override void _Process(double delta)
{ {
if (GameData.isPaused) return;
if (isExecuting) if (isExecuting)
{ {
if (CanExecute(delta)) if (CanExecute(delta))
@@ -155,6 +157,16 @@ public partial class Robot : Node3D
currentMessage = ""; currentMessage = "";
} }
public void Maintain(float amount)
{
maintenance = Math.Clamp(maintenance + amount, 0f, 100f);
if (maintenance > 0f)
{
isBroken = false;
}
currentMessage = "";
}
public RobotSaveData CreateSaveData() public RobotSaveData CreateSaveData()
{ {
return new RobotSaveData return new RobotSaveData
+135
View File
@@ -19,8 +19,15 @@ public partial class TestRunner : Node
Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes); Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes);
Run("Research cannot start without resources", TestResearchCannotStartWithoutResources); Run("Research cannot start without resources", TestResearchCannotStartWithoutResources);
Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged); Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged);
Run("Saved scripts can be deleted", TestSavedScriptsCanBeDeleted);
Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip); Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip);
Run("Endless resources extract slower", TestEndlessResourcesExtractSlower);
Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip); Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip);
Run("Maintain node consumes matching gear", TestMaintainNodeConsumesMatchingGear);
Run("Sacrifice node makes resource endless", TestSacrificeNodeMakesResourceEndless);
Run("No robot recovery detects loss", TestNoRobotRecoveryDetectsLoss);
Run("Robot item prevents no robot loss", TestRobotItemPreventsNoRobotLoss);
Run("Save data captures and restores options", TestSaveDataRestoresOptions);
Run("Save data captures and restores global state", TestSaveDataRestoresGlobalState); Run("Save data captures and restores global state", TestSaveDataRestoresGlobalState);
Run("Save data restores survival timer", TestSaveDataRestoresSurvivalTimer); Run("Save data restores survival timer", TestSaveDataRestoresSurvivalTimer);
Run("Split save files store and load data", TestSplitSaveFilesRoundtrip); Run("Split save files store and load data", TestSplitSaveFilesRoundtrip);
@@ -28,6 +35,8 @@ public partial class TestRunner : Node
Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons); Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
Run("While node reports false conditions", TestWhileNodeReportsFalseConditions); Run("While node reports false conditions", TestWhileNodeReportsFalseConditions);
Run("For node stops after configured amount", TestForNodeStopsAfterConfiguredAmount); Run("For node stops after configured amount", TestForNodeStopsAfterConfiguredAmount);
Run("Paused world does not drain survival", TestPausedWorldDoesNotDrainSurvival);
Run("Open gate hides gate content", TestOpenGateHidesGateContent);
Run("Item data readable names are stable", TestItemDataReadableNames); Run("Item data readable names are stable", TestItemDataReadableNames);
Run("Resource files load core game data", TestResourceFilesLoadCoreData); Run("Resource files load core game data", TestResourceFilesLoadCoreData);
@@ -173,6 +182,20 @@ public partial class TestRunner : Node
AssertEqual(saveData.CurrentAmount, loadedResource.CreateSaveData().CurrentAmount, "resource amount"); AssertEqual(saveData.CurrentAmount, loadedResource.CreateSaveData().CurrentAmount, "resource amount");
} }
private void TestEndlessResourcesExtractSlower()
{
GameResource resource = new GameResource("stone");
AssertTrue(resource.Extract(1.0), "normal resource should extract after one second");
resource.MakeEndless();
AssertFalse(resource.Extract(1.0), "endless resource should not extract after one second");
AssertTrue(resource.Extract(3.0), "endless resource should extract after four total seconds");
AssertTrue(resource.IsEndless(), "resource should be endless");
AssertTrue(resource.GetExtractionSpeed() > 1f, "endless extraction speed should be slower");
}
private void TestRobotSaveRoundtrip() private void TestRobotSaveRoundtrip()
{ {
Robot robot = new Robot Robot robot = new Robot
@@ -201,6 +224,59 @@ public partial class TestRunner : Node
AssertTrue(loadedRobot.isCoolingDown, "robot cooling state"); AssertTrue(loadedRobot.isCoolingDown, "robot cooling state");
} }
private void TestMaintainNodeConsumesMatchingGear()
{
Robot robot = new Robot
{
robotType = "copper_robot",
maintenance = 50f,
isBroken = true
};
GameData.inventory.AddItem(new Item { data = GameData.availableItems["copper_gear"] }, 1);
MaintainNode node = new MaintainNode();
AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "maintain should succeed");
AssertClose(60f, robot.maintenance, 0.001f, "maintenance repaired by 10");
AssertFalse(robot.isBroken, "robot should be usable again");
AssertEqual(0, GameData.inventory.GetItemAmount("copper_gear"), "gear should be consumed");
}
private void TestSacrificeNodeMakesResourceEndless()
{
GameData.ruinSize = 1;
GameData.layerSize = 1;
GameData.map = new Layer[1];
GameData.map[0] = CreateTestLayer(0, "spawn");
GameData.map[0].tiles[0, 0].resource = new GameResource("stone");
GameData.map[0].tiles[0, 0].containsResource = true;
Pathfinding.BuildAStarGraph();
Robot robot = new Robot
{
Position = Vector3.Zero
};
GameData.robots.Add(robot);
SacrificeNode node = new SacrificeNode();
AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "sacrifice should succeed");
AssertTrue(GameData.map[0].tiles[0, 0].resource.IsEndless(), "resource should become endless");
AssertEqual(0, GameData.robots.Count, "robot should be removed");
}
private void TestNoRobotRecoveryDetectsLoss()
{
AssertTrue(GameData.HasNoRobotRecovery(), "no robots and no robot items should be a loss");
}
private void TestRobotItemPreventsNoRobotLoss()
{
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone_robot"] }, 1);
AssertFalse(GameData.HasNoRobotRecovery(), "robot item should prevent loss");
}
private void TestSaveDataRestoresGlobalState() private void TestSaveDataRestoresGlobalState()
{ {
GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 12); GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 12);
@@ -221,6 +297,27 @@ public partial class TestRunner : Node
AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research"); AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research");
} }
private void TestSaveDataRestoresOptions()
{
GameData.screenMode = 1;
GameData.soundVolume = 0.35f;
GameData.lightColor = new Color(0.2f, 0.4f, 0.6f, 1f);
SaveGameData saveData = SaveGameManager.CreateSaveData();
GameData.screenMode = 2;
GameData.soundVolume = 0.8f;
GameData.lightColor = Colors.White;
SaveGameManager.ApplyWorldData(saveData);
AssertEqual(1, GameData.screenMode, "saved screen mode");
AssertClose(0.35f, GameData.soundVolume, 0.001f, "saved sound volume");
AssertClose(0.2f, GameData.lightColor.R, 0.001f, "saved light red");
AssertClose(0.4f, GameData.lightColor.G, 0.001f, "saved light green");
AssertClose(0.6f, GameData.lightColor.B, 0.001f, "saved light blue");
}
private void TestSaveDataRestoresSurvivalTimer() private void TestSaveDataRestoresSurvivalTimer()
{ {
GameData.survival.elapsedSeconds = 321.5; GameData.survival.elapsedSeconds = 321.5;
@@ -270,6 +367,17 @@ public partial class TestRunner : Node
AssertEqual(0, GameData.inventory.items.Count, "failed add should not create stacks"); AssertEqual(0, GameData.inventory.items.Count, "failed add should not create stacks");
} }
private void TestSavedScriptsCanBeDeleted()
{
string scriptName = "delete_test_script";
FileHandler.SaveProgram(scriptName, "Name: Explore;");
AssertTrue(FileHandler.LoadProgramNames().Contains(scriptName), "script should be listed");
AssertTrue(FileHandler.DeleteProgram(scriptName), "delete should succeed");
AssertEqual("", FileHandler.LoadProgram(scriptName), "deleted script should be empty");
AssertFalse(FileHandler.DeleteProgram(scriptName), "second delete should fail");
}
private void TestResearchCannotStartWithoutResources() private void TestResearchCannotStartWithoutResources()
{ {
ResearchData researchData = new ResearchData ResearchData researchData = new ResearchData
@@ -367,6 +475,33 @@ public partial class TestRunner : Node
AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "loop finished"); AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "loop finished");
} }
private void TestPausedWorldDoesNotDrainSurvival()
{
GameData.isPaused = true;
GameData.canMove = false;
GameData.survival.energy = 100f;
World world = new World();
world.UpdateGameLoop(100.0);
AssertClose(100f, GameData.survival.energy, 0.001f, "energy should not drain while paused");
}
private void TestOpenGateHidesGateContent()
{
Layer layer = CreateTestLayer(0, "gate");
layer.gateCoordinate = new Vector2I(0, 0);
layer.tiles[0, 0].ContentNode = new Node3D
{
Visible = true
};
layer.OpenGate();
AssertTrue(layer.isGateOpen, "gate should be open");
AssertFalse(layer.tiles[0, 0].ContentNode.Visible, "gate content should be hidden");
}
private void TestItemDataReadableNames() private void TestItemDataReadableNames()
{ {
AssertEqual("Iron gear", ItemData.GetReadableName("iron_gear"), "readable name"); AssertEqual("Iron gear", ItemData.GetReadableName("iron_gear"), "readable name");
+29 -1
View File
@@ -58,6 +58,7 @@ public partial class UIHandler : Control
public void HandleMenuButton() public void HandleMenuButton()
{ {
OpenUIElement(menu); OpenUIElement(menu);
GameData.isPaused = menu.Visible || options.Visible;
} }
public void HandleMenu() public void HandleMenu()
@@ -67,7 +68,9 @@ public partial class UIHandler : Control
public void ShowOptions() public void ShowOptions()
{ {
menu.Hide();
OpenUIElement(options); OpenUIElement(options);
GameData.isPaused = options.Visible;
} }
public void HandleMapButton() public void HandleMapButton()
@@ -101,6 +104,7 @@ public partial class UIHandler : Control
RAM.Text = memoryDisplay; RAM.Text = memoryDisplay;
DisplaySurvivalStats(); DisplaySurvivalStats();
DisplayWorldStats(); DisplayWorldStats();
DisplayLoseCondition();
} }
public void ExitGame() public void ExitGame()
@@ -123,6 +127,7 @@ public partial class UIHandler : Control
public void OpenUIElement(Control element) public void OpenUIElement(Control element)
{ {
SoundManager.PlayButton();
if (element.Visible) if (element.Visible)
{ {
element.Hide(); element.Hide();
@@ -141,6 +146,11 @@ public partial class UIHandler : Control
if (child == element) continue; if (child == element) continue;
child.Visible = false; child.Visible = false;
} }
if (element != menu && element != options)
{
GameData.isPaused = false;
}
} }
private void DisplayRobotAlarm() private void DisplayRobotAlarm()
@@ -201,15 +211,28 @@ public partial class UIHandler : Control
unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1); 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() public void UnlockLayer()
{ {
if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1)) if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1))
{ {
int openedLayer = GameData.lowestLayer;
foreach (Ingredient ingredient in GameData.map[GameData.lowestLayer].gateIngredients) foreach (Ingredient ingredient in GameData.map[GameData.lowestLayer].gateIngredients)
{ {
GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount); GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount);
} }
GameData.lowestLayer++; GameData.lowestLayer++;
World world = GetNodeOrNull<World>("/root/Main/World");
if (world != null)
{
world.OpenGate(openedLayer);
}
if (GameData.lowestLayer == GameData.ruinSize) if (GameData.lowestLayer == GameData.ruinSize)
{ {
gameOver.Show(); gameOver.Show();
@@ -218,9 +241,14 @@ public partial class UIHandler : Control
} }
public void ShowGameOver() public void ShowGameOver()
{
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
}
public void ShowGameOver(string message)
{ {
if (gameOver.Visible) return; if (gameOver.Visible) return;
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.\r"; gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
gameOver.Show(); gameOver.Show();
} }
+18
View File
@@ -130,6 +130,8 @@ public partial class CodingWindow : PanelContainer
public void LoadProgram(int index) public void LoadProgram(int index)
{ {
if (index <= 0) return;
ClearWindow(); ClearWindow();
string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index)); string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index));
string[] nodes = scriptContent.Split(";"); string[] nodes = scriptContent.Split(";");
@@ -151,6 +153,22 @@ public partial class CodingWindow : PanelContainer
availableScripts.Select(0); availableScripts.Select(0);
} }
public void DeleteProgram()
{
string filename = scriptName.Text;
int selectedIndex = availableScripts.GetSelectedId();
if (selectedIndex > 0)
{
filename = availableScripts.GetItemText(selectedIndex);
}
if (filename.Length <= 0) return;
if (!FileHandler.DeleteProgram(filename)) return;
ClearWindow();
SetupScriptOptions();
}
public void SaveProgram() public void SaveProgram()
{ {
string result = ""; string result = "";
+12
View File
@@ -89,6 +89,14 @@ public partial class NodeDisplay : PanelContainer
result.node = new ElseNode(); result.node = new ElseNode();
result.LoadElse(nodeSanitized); result.LoadElse(nodeSanitized);
break; break;
case "maintain":
result.node = new MaintainNode();
result.LoadMaintain(nodeSanitized);
break;
case "sacrifice":
result.node = new SacrificeNode();
result.LoadSacrifice(nodeSanitized);
break;
default: default:
result.QueueFree(); result.QueueFree();
return null; return null;
@@ -99,6 +107,10 @@ public partial class NodeDisplay : PanelContainer
private void LoadElse(string content) { } private void LoadElse(string content) { }
private void LoadMaintain(string content) { }
private void LoadSacrifice(string content) { }
private void LoadIf(string content) private void LoadIf(string content)
{ {
HBoxContainer valueContainer = GetNode<HBoxContainer>("./EditorDisplay/VBoxContainer/Values"); HBoxContainer valueContainer = GetNode<HBoxContainer>("./EditorDisplay/VBoxContainer/Values");
+9
View File
@@ -2,6 +2,8 @@ using Godot;
public partial class MainMenu : Control public partial class MainMenu : Control
{ {
[Export] private PanelContainer options;
public override void _Ready() public override void _Ready()
{ {
UIStyle.Apply(this); UIStyle.Apply(this);
@@ -26,4 +28,11 @@ public partial class MainMenu : Control
{ {
GetTree().Quit(); GetTree().Quit();
} }
public void OnOptionsPressed()
{
if (options == null) return;
options.Show();
}
} }
+83
View File
@@ -0,0 +1,83 @@
using Godot;
public partial class OptionsMenu : PanelContainer
{
private readonly Vector2 panelSize = new Vector2(420, 260);
[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);
}
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);
}
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;
}
}
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 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;
}
}
+1
View File
@@ -0,0 +1 @@
uid://ca1rfge0y3y4i
+10
View File
@@ -39,6 +39,16 @@ public partial class Layer : Node3D
} }
} }
public void OpenGate()
{
isGateOpen = true;
Tile gateTile = tiles[gateCoordinate.X, gateCoordinate.Y];
if (gateTile.ContentNode != null)
{
gateTile.ContentNode.Visible = false;
}
}
public void SetupLayer(int layerSize, int level, Dictionary<string, MeshInstance3D> tileMeshes, Vector2I collapseOrigin) public void SetupLayer(int layerSize, int level, Dictionary<string, MeshInstance3D> tileMeshes, Vector2I collapseOrigin)
{ {
this.layerSize = layerSize; this.layerSize = layerSize;
+30
View File
@@ -98,6 +98,13 @@ public partial class World : Node3D
public override void _Process(double delta) public override void _Process(double delta)
{ {
UpdateGameLoop(delta);
}
public void UpdateGameLoop(double delta)
{
if (isPaused) return;
survival.Update(delta); survival.Update(delta);
if (!canMove) return; if (!canMove) return;
@@ -110,6 +117,25 @@ public partial class World : Node3D
} }
} }
public void RefreshVisibleLayer()
{
ShowLayer(visibleLayer);
}
public void OpenGate(int layerIndex)
{
if (layerIndex < 0 || layerIndex >= map.Length) return;
Layer layer = map[layerIndex];
layer.OpenGate();
Pathfinding.UpdateGatePoint(layerIndex, true);
if (layerIndex == visibleLayer)
{
RefreshVisibleLayer();
}
}
private void SpawnDefaultRobot() private void SpawnDefaultRobot()
{ {
Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>(); Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
@@ -184,6 +210,10 @@ public partial class World : Node3D
for (int y = 0; y < layerSize; y++) for (int y = 0; y < layerSize; y++)
{ {
Tile tile = layer.tiles[x, y]; Tile tile = layer.tiles[x, y];
if (layer.isGateOpen && tile.collapsedMesh == "gate")
{
continue;
}
result.Add(new TileRenderData result.Add(new TileRenderData
{ {
Tile = tile, Tile = tile,