From 8170b700b2ad87640747aca05f466e985fc855bc Mon Sep 17 00:00:00 2001 From: Nicola Date: Sun, 10 May 2026 14:09:14 +0200 Subject: [PATCH] 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. --- Prefabs/DSL/MaintainNode.tscn | 46 ++++++++ Prefabs/DSL/SacrificeNode.tscn | 46 ++++++++ Scenes/Game.tscn | 22 ++++ Scenes/MainMenu.tscn | 9 +- Scenes/Options.tscn | 48 +++++++- Scripts/Core/FileHandler.cs | 19 +++ Scripts/Core/GameData.cs | 23 ++++ Scripts/Core/ResourceLoader.cs | 4 +- Scripts/Core/SaveGameData.cs | 11 ++ Scripts/Core/SaveGameManager.cs | 55 ++++++++- Scripts/Core/SoundManager.cs | 44 +++++++ Scripts/Core/SoundManager.cs.uid | 1 + Scripts/DSL/Nodes/HarvestNode.cs | 1 + Scripts/DSL/Nodes/MaintainNode.cs | 51 ++++++++ Scripts/DSL/Nodes/MaintainNode.cs.uid | 1 + Scripts/DSL/Nodes/SacrificeNode.cs | 52 +++++++++ Scripts/DSL/Nodes/SacrificeNode.cs.uid | 1 + Scripts/Gameplay/Crafting/GameResource.cs | 37 +++++- Scripts/Gameplay/Robots/Robot.cs | 12 ++ Scripts/Tests/TestRunner.cs | 135 ++++++++++++++++++++++ Scripts/UI/Common/UIHandler.cs | 30 ++++- Scripts/UI/DSL/CodingWindow.cs | 18 +++ Scripts/UI/DSL/NodeDisplay.cs | 12 ++ Scripts/UI/Menus/MainMenu.cs | 9 ++ Scripts/UI/Menus/OptionsMenu.cs | 83 +++++++++++++ Scripts/UI/Menus/OptionsMenu.cs.uid | 1 + Scripts/World/Layer.cs | 10 ++ Scripts/World/World.cs | 30 +++++ 28 files changed, 797 insertions(+), 14 deletions(-) create mode 100644 Prefabs/DSL/MaintainNode.tscn create mode 100644 Prefabs/DSL/SacrificeNode.tscn create mode 100644 Scripts/Core/SoundManager.cs create mode 100644 Scripts/Core/SoundManager.cs.uid create mode 100644 Scripts/DSL/Nodes/MaintainNode.cs create mode 100644 Scripts/DSL/Nodes/MaintainNode.cs.uid create mode 100644 Scripts/DSL/Nodes/SacrificeNode.cs create mode 100644 Scripts/DSL/Nodes/SacrificeNode.cs.uid create mode 100644 Scripts/UI/Menus/OptionsMenu.cs create mode 100644 Scripts/UI/Menus/OptionsMenu.cs.uid diff --git a/Prefabs/DSL/MaintainNode.tscn b/Prefabs/DSL/MaintainNode.tscn new file mode 100644 index 0000000..816f140 --- /dev/null +++ b/Prefabs/DSL/MaintainNode.tscn @@ -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"] diff --git a/Prefabs/DSL/SacrificeNode.tscn b/Prefabs/DSL/SacrificeNode.tscn new file mode 100644 index 0000000..c933494 --- /dev/null +++ b/Prefabs/DSL/SacrificeNode.tscn @@ -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"] diff --git a/Scenes/Game.tscn b/Scenes/Game.tscn index d44149e..bd8c1ff 100644 --- a/Scenes/Game.tscn +++ b/Scenes/Game.tscn @@ -3,7 +3,10 @@ [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="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://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://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"] @@ -59,6 +62,19 @@ volume_db = -25.0 autoplay = 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] script = ExtResource("1_7lihs") @@ -329,6 +345,11 @@ layout_mode = 2 size_flags_horizontal = 3 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] layout_mode = 2 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/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/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="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"] diff --git a/Scenes/MainMenu.tscn b/Scenes/MainMenu.tscn index ad094d3..2aa8e0c 100644 --- a/Scenes/MainMenu.tscn +++ b/Scenes/MainMenu.tscn @@ -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://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="PackedScene" uid="uid://cpq7ppe8bw2bq" path="res://Scenes/Options.tscn" id="6_options"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bnhvo"] 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_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 anchors_preset = 15 anchor_right = 1.0 @@ -35,6 +36,7 @@ anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 script = ExtResource("1_tt5f1") +options = NodePath("Options") [node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1984828000] stream = ExtResource("2_8um5k") @@ -152,6 +154,11 @@ theme_override_styles/normal = SubResource("StyleBoxFlat_bnhvo") theme_override_styles/hover = SubResource("StyleBoxFlat_tt5f1") 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/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"] diff --git a/Scenes/Options.tscn b/Scenes/Options.tscn index f14416e..2b053d6 100644 --- a/Scenes/Options.tscn +++ b/Scenes/Options.tscn @@ -1,16 +1,28 @@ [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 anchor_left = 0.5 anchor_top = 0.5 anchor_right = 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_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] layout_mode = 2 +theme_override_constants/separation = 10 [node name="Title" type="RichTextLabel" parent="VBoxContainer" unique_id=1694731141] layout_mode = 2 @@ -24,16 +36,44 @@ horizontal_alignment = 1 layout_mode = 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 -text = "Light color: " +text = "Screen" fit_content = true 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 size_flags_horizontal = 3 [node name="Button" type="Button" parent="VBoxContainer" unique_id=1183264327] layout_mode = 2 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"] diff --git a/Scripts/Core/FileHandler.cs b/Scripts/Core/FileHandler.cs index f2aaf69..1e4d54a 100644 --- a/Scripts/Core/FileHandler.cs +++ b/Scripts/Core/FileHandler.cs @@ -62,6 +62,25 @@ public class FileHandler 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) { return $"{ScriptDirectory}/{filename}{ScriptExtension}"; diff --git a/Scripts/Core/GameData.cs b/Scripts/Core/GameData.cs index 84eec0f..5223b65 100644 --- a/Scripts/Core/GameData.cs +++ b/Scripts/Core/GameData.cs @@ -25,9 +25,12 @@ public partial class GameData public static Dictionary> gateUnlocks; public static bool loadSaveOnStart = false; public static bool showTutorial = true; + public static bool isPaused = false; public static Color primaryColor = new Color("#276ac2"); 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 layerSize = 20; @@ -44,12 +47,14 @@ public partial class GameData robotStats = new RobotStats(); inventory = new Inventory(); availableResearch = ResourceLoader.LoadResearch(); + map = null; robots.Clear(); currentLayer = 0; visibleLayer = 0; lowestLayer = 0; maxRobotCount = 10; canMove = true; + isPaused = false; } 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(); + } } diff --git a/Scripts/Core/ResourceLoader.cs b/Scripts/Core/ResourceLoader.cs index 32b8799..b8f344d 100644 --- a/Scripts/Core/ResourceLoader.cs +++ b/Scripts/Core/ResourceLoader.cs @@ -74,7 +74,9 @@ public partial class ResourceLoader { new ElseNode(), GD.Load("res://Prefabs/DSL/ElseNode.tscn") }, { new UntilNode(), GD.Load("res://Prefabs/DSL/UntilNode.tscn") }, { new ForNode(), GD.Load("res://Prefabs/DSL/ForNode.tscn") }, - { new WhileNode(), GD.Load("res://Prefabs/DSL/WhileNode.tscn") } + { new WhileNode(), GD.Load("res://Prefabs/DSL/WhileNode.tscn") }, + { new MaintainNode(), GD.Load("res://Prefabs/DSL/MaintainNode.tscn") }, + { new SacrificeNode(), GD.Load("res://Prefabs/DSL/SacrificeNode.tscn") } }; return nodes; } diff --git a/Scripts/Core/SaveGameData.cs b/Scripts/Core/SaveGameData.cs index 281c07f..ccf2106 100644 --- a/Scripts/Core/SaveGameData.cs +++ b/Scripts/Core/SaveGameData.cs @@ -8,6 +8,7 @@ public class SaveGameData public int LowestLayer { get; set; } public int MaxRobotCount { get; set; } public bool CanMove { get; set; } + public SettingsSaveData Settings { get; set; } public SurvivalSaveData Survival { get; set; } public List Inventory { get; set; } public List Research { get; set; } @@ -15,6 +16,16 @@ public class SaveGameData public List 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 float Hunger { get; set; } diff --git a/Scripts/Core/SaveGameManager.cs b/Scripts/Core/SaveGameManager.cs index 0757918..915eaee 100644 --- a/Scripts/Core/SaveGameManager.cs +++ b/Scripts/Core/SaveGameManager.cs @@ -61,6 +61,7 @@ public static class SaveGameManager LowestLayer = GameData.lowestLayer, MaxRobotCount = GameData.maxRobotCount, CanMove = GameData.canMove, + Settings = CreateSettingsSaveData(), Survival = CreateSurvivalSaveData(), Inventory = CreateInventorySaveData(), Research = CreateResearchSaveData(), @@ -80,6 +81,7 @@ public static class SaveGameManager GameData.maxRobotCount = saveGame.MaxRobotCount; GameData.canMove = saveGame.CanMove; + ApplySettingsData(saveGame.Settings); ApplySurvivalData(saveGame.Survival); ApplyInventoryData(saveGame.Inventory); 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 CreateInventorySaveData() { List result = new List(); @@ -201,6 +216,7 @@ public static class SaveGameManager LowestLayer = saveGame.LowestLayer, MaxRobotCount = saveGame.MaxRobotCount, CanMove = saveGame.CanMove, + Settings = saveGame.Settings, Survival = saveGame.Survival, Inventory = saveGame.Inventory, Research = new List(), @@ -286,6 +302,43 @@ public static class SaveGameManager 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 savedItems) { GameData.inventory = new Inventory(); @@ -353,7 +406,7 @@ public static class SaveGameManager tile.containsResource = savedTile.ContainsResource; tile.wasVisited = savedTile.WasVisited; 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); } } } diff --git a/Scripts/Core/SoundManager.cs b/Scripts/Core/SoundManager.cs new file mode 100644 index 0000000..80b25eb --- /dev/null +++ b/Scripts/Core/SoundManager.cs @@ -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); + } +} diff --git a/Scripts/Core/SoundManager.cs.uid b/Scripts/Core/SoundManager.cs.uid new file mode 100644 index 0000000..9feb1a5 --- /dev/null +++ b/Scripts/Core/SoundManager.cs.uid @@ -0,0 +1 @@ +uid://gvy12mefkwax diff --git a/Scripts/DSL/Nodes/HarvestNode.cs b/Scripts/DSL/Nodes/HarvestNode.cs index 96d791c..f45a96b 100644 --- a/Scripts/DSL/Nodes/HarvestNode.cs +++ b/Scripts/DSL/Nodes/HarvestNode.cs @@ -25,6 +25,7 @@ public class HarvestNode : ProgramNode if (tile.resource.Extract(delta)) { + SoundManager.PlayMining(); if (!GameData.inventory.AddItem(new Item {data = tile.resource.item}, 1)) { lastExecutionMessage = "Not enough space"; diff --git a/Scripts/DSL/Nodes/MaintainNode.cs b/Scripts/DSL/Nodes/MaintainNode.cs new file mode 100644 index 0000000..b50a076 --- /dev/null +++ b/Scripts/DSL/Nodes/MaintainNode.cs @@ -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"); + } +} diff --git a/Scripts/DSL/Nodes/MaintainNode.cs.uid b/Scripts/DSL/Nodes/MaintainNode.cs.uid new file mode 100644 index 0000000..155e28f --- /dev/null +++ b/Scripts/DSL/Nodes/MaintainNode.cs.uid @@ -0,0 +1 @@ +uid://bmibtsh1iq2x diff --git a/Scripts/DSL/Nodes/SacrificeNode.cs b/Scripts/DSL/Nodes/SacrificeNode.cs new file mode 100644 index 0000000..70d1ad0 --- /dev/null +++ b/Scripts/DSL/Nodes/SacrificeNode.cs @@ -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}"; + } +} diff --git a/Scripts/DSL/Nodes/SacrificeNode.cs.uid b/Scripts/DSL/Nodes/SacrificeNode.cs.uid new file mode 100644 index 0000000..0d73163 --- /dev/null +++ b/Scripts/DSL/Nodes/SacrificeNode.cs.uid @@ -0,0 +1 @@ +uid://dq8e7txyldpew diff --git a/Scripts/Gameplay/Crafting/GameResource.cs b/Scripts/Gameplay/Crafting/GameResource.cs index a2f6618..d2aaacb 100644 --- a/Scripts/Gameplay/Crafting/GameResource.cs +++ b/Scripts/Gameplay/Crafting/GameResource.cs @@ -1,5 +1,8 @@ public class GameResource { + private const float NormalExtractionSpeed = 1f; + private const float EndlessExtractionSpeed = 4f; + public string name; public ItemData item; @@ -15,17 +18,23 @@ public class GameResource maxAmount = GameData.rand.Next(1000, 10000); currentAmount = maxAmount; isEndless = false; - extractionSpeed = 1f; + extractionSpeed = NormalExtractionSpeed; item = GameData.availableItems[name]; } public static GameResource FromSaveData(ResourceSaveData saveData) { - GameResource resource = new GameResource(saveData.Name); - resource.currentAmount = saveData.CurrentAmount; - resource.maxAmount = saveData.MaxAmount; - resource.isEndless = saveData.IsEndless; - resource.extractionSpeed = saveData.ExtractionSpeed; + GameResource resource = new GameResource(saveData.Name) + { + currentAmount = saveData.CurrentAmount, + maxAmount = saveData.MaxAmount, + isEndless = saveData.IsEndless, + extractionSpeed = saveData.ExtractionSpeed + }; + if (resource.isEndless && resource.extractionSpeed <= NormalExtractionSpeed) + { + resource.extractionSpeed = EndlessExtractionSpeed; + } resource.timeSinceLastExtraction = saveData.TimeSinceLastExtraction; return resource; } @@ -64,4 +73,20 @@ public class GameResource { 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; + } } diff --git a/Scripts/Gameplay/Robots/Robot.cs b/Scripts/Gameplay/Robots/Robot.cs index 7cf3f01..9f7c8c1 100644 --- a/Scripts/Gameplay/Robots/Robot.cs +++ b/Scripts/Gameplay/Robots/Robot.cs @@ -29,6 +29,8 @@ public partial class Robot : Node3D public override void _Process(double delta) { + if (GameData.isPaused) return; + if (isExecuting) { if (CanExecute(delta)) @@ -155,6 +157,16 @@ public partial class Robot : Node3D currentMessage = ""; } + public void Maintain(float amount) + { + maintenance = Math.Clamp(maintenance + amount, 0f, 100f); + if (maintenance > 0f) + { + isBroken = false; + } + currentMessage = ""; + } + public RobotSaveData CreateSaveData() { return new RobotSaveData diff --git a/Scripts/Tests/TestRunner.cs b/Scripts/Tests/TestRunner.cs index 253bedf..c878239 100644 --- a/Scripts/Tests/TestRunner.cs +++ b/Scripts/Tests/TestRunner.cs @@ -19,8 +19,15 @@ public partial class TestRunner : Node Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes); Run("Research cannot start without resources", TestResearchCannotStartWithoutResources); Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged); + Run("Saved scripts can be deleted", TestSavedScriptsCanBeDeleted); Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip); + Run("Endless resources extract slower", TestEndlessResourcesExtractSlower); 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 restores survival timer", TestSaveDataRestoresSurvivalTimer); 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("While node reports false conditions", TestWhileNodeReportsFalseConditions); 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("Resource files load core game data", TestResourceFilesLoadCoreData); @@ -173,6 +182,20 @@ public partial class TestRunner : Node 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() { Robot robot = new Robot @@ -201,6 +224,59 @@ public partial class TestRunner : Node 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() { 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"); } + 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() { 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"); } + 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() { ResearchData researchData = new ResearchData @@ -367,6 +475,33 @@ public partial class TestRunner : Node 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() { AssertEqual("Iron gear", ItemData.GetReadableName("iron_gear"), "readable name"); diff --git a/Scripts/UI/Common/UIHandler.cs b/Scripts/UI/Common/UIHandler.cs index 6595c4a..7fb8893 100644 --- a/Scripts/UI/Common/UIHandler.cs +++ b/Scripts/UI/Common/UIHandler.cs @@ -58,6 +58,7 @@ public partial class UIHandler : Control public void HandleMenuButton() { OpenUIElement(menu); + GameData.isPaused = menu.Visible || options.Visible; } public void HandleMenu() @@ -67,7 +68,9 @@ public partial class UIHandler : Control public void ShowOptions() { + menu.Hide(); OpenUIElement(options); + GameData.isPaused = options.Visible; } public void HandleMapButton() @@ -101,6 +104,7 @@ public partial class UIHandler : Control RAM.Text = memoryDisplay; DisplaySurvivalStats(); DisplayWorldStats(); + DisplayLoseCondition(); } public void ExitGame() @@ -123,6 +127,7 @@ public partial class UIHandler : Control public void OpenUIElement(Control element) { + SoundManager.PlayButton(); if (element.Visible) { element.Hide(); @@ -141,6 +146,11 @@ public partial class UIHandler : Control if (child == element) continue; child.Visible = false; } + + if (element != menu && element != options) + { + GameData.isPaused = false; + } } private void DisplayRobotAlarm() @@ -201,15 +211,28 @@ public partial class UIHandler : Control 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)) { + int openedLayer = GameData.lowestLayer; foreach (Ingredient ingredient in GameData.map[GameData.lowestLayer].gateIngredients) { GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount); } GameData.lowestLayer++; + World world = GetNodeOrNull("/root/Main/World"); + if (world != null) + { + world.OpenGate(openedLayer); + } if (GameData.lowestLayer == GameData.ruinSize) { gameOver.Show(); @@ -218,9 +241,14 @@ 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("./VBoxContainer/Content").Text = $"[font_size=32]You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.\r"; + gameOver.GetNode("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r"; gameOver.Show(); } diff --git a/Scripts/UI/DSL/CodingWindow.cs b/Scripts/UI/DSL/CodingWindow.cs index f0f7ff4..2e32eaa 100644 --- a/Scripts/UI/DSL/CodingWindow.cs +++ b/Scripts/UI/DSL/CodingWindow.cs @@ -130,6 +130,8 @@ public partial class CodingWindow : PanelContainer public void LoadProgram(int index) { + if (index <= 0) return; + ClearWindow(); string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index)); string[] nodes = scriptContent.Split(";"); @@ -151,6 +153,22 @@ public partial class CodingWindow : PanelContainer 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() { string result = ""; diff --git a/Scripts/UI/DSL/NodeDisplay.cs b/Scripts/UI/DSL/NodeDisplay.cs index 4accf01..20b9fcc 100644 --- a/Scripts/UI/DSL/NodeDisplay.cs +++ b/Scripts/UI/DSL/NodeDisplay.cs @@ -89,6 +89,14 @@ public partial class NodeDisplay : PanelContainer result.node = new ElseNode(); result.LoadElse(nodeSanitized); break; + case "maintain": + result.node = new MaintainNode(); + result.LoadMaintain(nodeSanitized); + break; + case "sacrifice": + result.node = new SacrificeNode(); + result.LoadSacrifice(nodeSanitized); + break; default: result.QueueFree(); return null; @@ -99,6 +107,10 @@ public partial class NodeDisplay : PanelContainer private void LoadElse(string content) { } + private void LoadMaintain(string content) { } + + private void LoadSacrifice(string content) { } + private void LoadIf(string content) { HBoxContainer valueContainer = GetNode("./EditorDisplay/VBoxContainer/Values"); diff --git a/Scripts/UI/Menus/MainMenu.cs b/Scripts/UI/Menus/MainMenu.cs index 599df98..985b0f0 100644 --- a/Scripts/UI/Menus/MainMenu.cs +++ b/Scripts/UI/Menus/MainMenu.cs @@ -2,6 +2,8 @@ using Godot; public partial class MainMenu : Control { + [Export] private PanelContainer options; + public override void _Ready() { UIStyle.Apply(this); @@ -26,4 +28,11 @@ public partial class MainMenu : Control { GetTree().Quit(); } + + public void OnOptionsPressed() + { + if (options == null) return; + + options.Show(); + } } diff --git a/Scripts/UI/Menus/OptionsMenu.cs b/Scripts/UI/Menus/OptionsMenu.cs new file mode 100644 index 0000000..dc20d72 --- /dev/null +++ b/Scripts/UI/Menus/OptionsMenu.cs @@ -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; + } +} diff --git a/Scripts/UI/Menus/OptionsMenu.cs.uid b/Scripts/UI/Menus/OptionsMenu.cs.uid new file mode 100644 index 0000000..8314e7b --- /dev/null +++ b/Scripts/UI/Menus/OptionsMenu.cs.uid @@ -0,0 +1 @@ +uid://ca1rfge0y3y4i diff --git a/Scripts/World/Layer.cs b/Scripts/World/Layer.cs index 95ee1c5..edb0730 100644 --- a/Scripts/World/Layer.cs +++ b/Scripts/World/Layer.cs @@ -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 tileMeshes, Vector2I collapseOrigin) { this.layerSize = layerSize; diff --git a/Scripts/World/World.cs b/Scripts/World/World.cs index a24ad4b..f7d188c 100644 --- a/Scripts/World/World.cs +++ b/Scripts/World/World.cs @@ -98,6 +98,13 @@ public partial class World : Node3D public override void _Process(double delta) { + UpdateGameLoop(delta); + } + + public void UpdateGameLoop(double delta) + { + if (isPaused) return; + survival.Update(delta); 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() { Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate(); @@ -184,6 +210,10 @@ public partial class World : Node3D for (int y = 0; y < layerSize; y++) { Tile tile = layer.tiles[x, y]; + if (layer.isGateOpen && tile.collapsedMesh == "gate") + { + continue; + } result.Add(new TileRenderData { Tile = tile,