diff --git a/Scenes/Game.tscn b/Scenes/Game.tscn index a63f018..f60a7d9 100644 --- a/Scenes/Game.tscn +++ b/Scenes/Game.tscn @@ -225,6 +225,7 @@ layout_mode = 2 size_flags_vertical = 3 [node name="CodingWindow" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=1576652491 node_paths=PackedStringArray("codeBlocks", "editorWindow", "availableScripts", "scriptName", "nameInput")] +visible = false layout_mode = 1 anchors_preset = 11 anchor_left = 1.0 @@ -293,6 +294,14 @@ size_flags_horizontal = 3 size_flags_vertical = 3 theme_override_constants/separation = 10 +[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/CodeBlocks/VBoxContainer" unique_id=1914788051] +layout_mode = 2 +text = "Click to add" +fit_content = true +autowrap_mode = 0 +horizontal_alignment = 1 +vertical_alignment = 1 + [node name="EditorWindow" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting" unique_id=919757187] layout_mode = 2 size_flags_horizontal = 3 diff --git a/Scripts/Core/FileHandler.cs b/Scripts/Core/FileHandler.cs index acb0b62..84151ec 100644 --- a/Scripts/Core/FileHandler.cs +++ b/Scripts/Core/FileHandler.cs @@ -1,88 +1,92 @@ using System.Collections.Generic; using Godot; -public class FileHandler +public static class FileHandler { - private const string ScriptDirectory = "user://scripts"; - private const string ScriptExtension = ".json"; + private const string ScriptDirectory = "user://scripts"; + private const string ScriptExtension = ".json"; - public static void CreateScriptDirectory() - { - DirAccess.MakeDirRecursiveAbsolute(ScriptDirectory); - } + public static void CreateScriptDirectory() + { + DirAccess.MakeDirRecursiveAbsolute(ScriptDirectory); + } - public static void SaveProgram(string filename, string content) - { - CreateScriptDirectory(); - string path = GetProgramPath(filename); + public static void SaveProgram(string filename, string content) + { + CreateScriptDirectory(); + string path = GetProgramPath(filename); - FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write); - file.StoreString(content); - } + FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write); + if (file == null) return; - public static List LoadProgramNames() - { - CreateScriptDirectory(); - List programs = new List(); + file.StoreString(content); + file.Flush(); + } - DirAccess dir = DirAccess.Open(ScriptDirectory); - if (dir == null) - { - return programs; - } + public static List LoadProgramNames() + { + CreateScriptDirectory(); + List programs = new List(); - dir.ListDirBegin(); - while (true) - { - string fileName = dir.GetNext(); - if (fileName == "") - break; + DirAccess dir = DirAccess.Open(ScriptDirectory); + if (dir == null) + { + return programs; + } - if (!dir.CurrentIsDir() && fileName.EndsWith(ScriptExtension)) - { - programs.Add(fileName.Replace(ScriptExtension, "")); - } - } - dir.ListDirEnd(); + dir.ListDirBegin(); + while (true) + { + string fileName = dir.GetNext(); + if (fileName == "") break; - return programs; - } + if (dir.CurrentIsDir()) continue; + if (!fileName.EndsWith(ScriptExtension)) continue; - public static string LoadProgram(string name) - { - CreateScriptDirectory(); - string path = GetProgramPath(name); + programs.Add(fileName.Replace(ScriptExtension, "")); + } + dir.ListDirEnd(); - if (!FileAccess.FileExists(path)) - { - return ""; - } + return programs; + } - FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read); - return file.GetAsText(); - } + public static string LoadProgram(string name) + { + CreateScriptDirectory(); + string path = GetProgramPath(name); - public static bool DeleteProgram(string name) - { - CreateScriptDirectory(); - string path = GetProgramPath(name); + if (!FileAccess.FileExists(path)) + { + return ""; + } - if (!FileAccess.FileExists(path)) - { - return false; - } + FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read); + if (file == null) return ""; - DirAccess dir = DirAccess.Open(ScriptDirectory); - if (dir == null) - { - return false; - } + return file.GetAsText(); + } - return dir.Remove($"{name}{ScriptExtension}") == Error.Ok; - } + public static bool DeleteProgram(string name) + { + CreateScriptDirectory(); + string path = GetProgramPath(name); - private static string GetProgramPath(string filename) - { - return $"{ScriptDirectory}/{filename}{ScriptExtension}"; - } + 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/ResourceLoader.cs b/Scripts/Core/ResourceLoader.cs index 0b5734d..b842666 100644 --- a/Scripts/Core/ResourceLoader.cs +++ b/Scripts/Core/ResourceLoader.cs @@ -2,7 +2,7 @@ using Godot; using System.Collections.Generic; using System.Text.Json; -public partial class ResourceLoader +public static class ResourceLoader { private const string LayerPrefabPath = "res://Prefabs/Layer.tscn"; private const string RobotPrefabPath = "res://Prefabs/Robot/Robot.tscn"; @@ -39,7 +39,7 @@ public partial class ResourceLoader public static Dictionary LoadTiles() { Dictionary tileMeshes = new Dictionary(); - PackedScene tileCollection = GD.Load($"res://Assets/Objects/Tiles.glb"); + PackedScene tileCollection = GD.Load("res://Assets/Objects/Tiles.glb"); Node root = tileCollection.Instantiate(); foreach (MeshInstance3D child in root.GetChildren()) { @@ -52,7 +52,7 @@ public partial class ResourceLoader public static Dictionary LoadDecorations() { Dictionary decorationMeshes = new Dictionary(); - PackedScene decorationCollection = GD.Load($"res://Assets/Objects/Decorations.glb"); + PackedScene decorationCollection = GD.Load("res://Assets/Objects/Decorations.glb"); Node root = decorationCollection.Instantiate(); foreach (MeshInstance3D child in root.GetChildren()) { @@ -114,8 +114,9 @@ public partial class ResourceLoader public static SortedDictionary LoadItems() { - FileAccess file = FileAccess.Open(RecipesPath, FileAccess.ModeFlags.Read); + if (file == null) return new SortedDictionary(); + string json = file.GetAsText(); SortedDictionary result = new SortedDictionary(); @@ -133,8 +134,9 @@ public partial class ResourceLoader public static Dictionary LoadResearch() { - FileAccess file = FileAccess.Open(ResearchPath, FileAccess.ModeFlags.Read); + if (file == null) return new Dictionary(); + string json = file.GetAsText(); Dictionary result = new Dictionary(); diff --git a/Scripts/Core/SaveGameDataApplier.cs b/Scripts/Core/SaveGameDataApplier.cs new file mode 100644 index 0000000..97ffa09 --- /dev/null +++ b/Scripts/Core/SaveGameDataApplier.cs @@ -0,0 +1,148 @@ +using Godot; +using System.Collections.Generic; + +public static class SaveGameDataApplier +{ + public static void ApplyWorldData(SaveGameData saveGame) + { + if (saveGame == null) return; + + GameData.seed = saveGame.Seed; + GameData.currentLayer = saveGame.CurrentLayer; + GameData.visibleLayer = saveGame.VisibleLayer; + GameData.lowestLayer = saveGame.LowestLayer; + GameData.maxRobotCount = saveGame.MaxRobotCount; + GameData.canMove = saveGame.CanMove; + + ApplySettingsData(saveGame.Settings); + ApplySurvivalData(saveGame.Survival); + ApplyInventoryData(saveGame.Inventory); + ApplyResearchData(saveGame.Research); + ApplyLayerData(saveGame.Layers); + } + + private static void ApplySurvivalData(SurvivalSaveData survival) + { + if (survival == null) return; + + GameData.survival.hunger = survival.Hunger; + GameData.survival.thirst = survival.Thirst; + GameData.survival.energy = survival.Energy; + GameData.survival.isDead = survival.IsDead; + GameData.survival.deathReason = survival.DeathReason ?? ""; + GameData.survival.currentStatus = survival.CurrentStatus ?? ""; + 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; + default: + DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed); + DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); + break; + } + } + + private static void ApplyInventoryData(List savedItems) + { + GameData.inventory = new Inventory(); + if (savedItems == null) return; + + foreach (ItemSaveData savedItem in savedItems) + { + if (!GameData.availableItems.ContainsKey(savedItem.Id)) continue; + + GameData.inventory.AddItem( + new Item { data = GameData.availableItems[savedItem.Id] }, + savedItem.Amount + ); + } + } + + private static void ApplyResearchData(List savedResearch) + { + if (savedResearch == null) return; + + foreach (ResearchSaveData savedState in savedResearch) + { + if (!GameData.availableResearch.ContainsKey(savedState.Id)) continue; + + Research research = GameData.availableResearch[savedState.Id]; + research.state = savedState.State; + research.elapsedResearchTime = savedState.ElapsedResearchTime; + research.paidResources = savedState.PaidResources; + } + + GameData.RebuildRobotStatsFromResearch(); + } + + private static void ApplyLayerData(List savedLayers) + { + if (savedLayers == null || GameData.map == null) return; + + foreach (LayerSaveData savedLayer in savedLayers) + { + if (savedLayer.Level < 0 || savedLayer.Level >= GameData.map.Length) continue; + + Layer layer = GameData.map[savedLayer.Level]; + layer.isGateOpen = savedLayer.IsGateOpen; + layer.hasContentGenerated = savedLayer.HasContentGenerated; + layer.gateIngredients = savedLayer.GateIngredients ?? new List(); + layer.currentResources = savedLayer.CurrentResources ?? new List(); + + ApplyTileData(layer, savedLayer.Tiles); + } + } + + private static void ApplyTileData(Layer layer, List savedTiles) + { + if (savedTiles == null) return; + + foreach (TileSaveData savedTile in savedTiles) + { + if (savedTile.X < 0 || savedTile.X >= GameData.layerSize) continue; + if (savedTile.Y < 0 || savedTile.Y >= GameData.layerSize) continue; + + Tile tile = layer.tiles[savedTile.X, savedTile.Y]; + tile.collapsedMesh = savedTile.CollapsedMesh; + tile.containsLight = savedTile.ContainsLight; + tile.containsDecoration = savedTile.ContainsDecoration; + 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" && !layer.isGateOpen); + } + } +} diff --git a/Scripts/Core/SaveGameDataApplier.cs.uid b/Scripts/Core/SaveGameDataApplier.cs.uid new file mode 100644 index 0000000..3c849d4 --- /dev/null +++ b/Scripts/Core/SaveGameDataApplier.cs.uid @@ -0,0 +1 @@ +uid://jv7k2npga7u0 diff --git a/Scripts/Core/SaveGameDataFactory.cs b/Scripts/Core/SaveGameDataFactory.cs new file mode 100644 index 0000000..5948659 --- /dev/null +++ b/Scripts/Core/SaveGameDataFactory.cs @@ -0,0 +1,160 @@ +using System.Collections.Generic; + +public static class SaveGameDataFactory +{ + public static SaveGameData CreateSaveData() + { + return new SaveGameData + { + Seed = GameData.seed, + CurrentLayer = GameData.currentLayer, + VisibleLayer = GameData.visibleLayer, + LowestLayer = GameData.lowestLayer, + MaxRobotCount = GameData.maxRobotCount, + CanMove = GameData.canMove, + Settings = CreateSettingsSaveData(), + Survival = CreateSurvivalSaveData(), + Inventory = CreateInventorySaveData(), + Research = CreateResearchSaveData(), + Layers = CreateLayerSaveData(), + Robots = CreateRobotSaveData() + }; + } + + public static SaveGameData CreateCoreSaveData(SaveGameData saveGame) + { + return new SaveGameData + { + Seed = saveGame.Seed, + CurrentLayer = saveGame.CurrentLayer, + VisibleLayer = saveGame.VisibleLayer, + LowestLayer = saveGame.LowestLayer, + MaxRobotCount = saveGame.MaxRobotCount, + CanMove = saveGame.CanMove, + Settings = saveGame.Settings, + Survival = saveGame.Survival, + Inventory = saveGame.Inventory, + Research = new List(), + Layers = new List(), + Robots = new List() + }; + } + + private static SurvivalSaveData CreateSurvivalSaveData() + { + return new SurvivalSaveData + { + Hunger = GameData.survival.hunger, + Thirst = GameData.survival.thirst, + Energy = GameData.survival.energy, + IsDead = GameData.survival.isDead, + DeathReason = GameData.survival.deathReason, + CurrentStatus = GameData.survival.currentStatus, + ElapsedSeconds = GameData.survival.elapsedSeconds + }; + } + + 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(); + + foreach (Item item in GameData.inventory.items) + { + result.Add(new ItemSaveData + { + Id = item.data.Id, + Amount = item.currentAmount + }); + } + + return result; + } + + private static List CreateResearchSaveData() + { + List result = new List(); + + foreach (Research research in GameData.availableResearch.Values) + { + result.Add(new ResearchSaveData + { + Id = research.data.Id, + State = research.state, + ElapsedResearchTime = research.elapsedResearchTime, + PaidResources = research.paidResources + }); + } + + return result; + } + + private static List CreateLayerSaveData() + { + List result = new List(); + if (GameData.map == null) return result; + + foreach (Layer layer in GameData.map) + { + if (layer == null) continue; + + result.Add(new LayerSaveData + { + Level = layer.level, + IsGateOpen = layer.isGateOpen, + HasContentGenerated = layer.hasContentGenerated, + GateIngredients = new List(layer.gateIngredients), + CurrentResources = new List(layer.currentResources), + Tiles = CreateTileSaveData(layer) + }); + } + + return result; + } + + private static List CreateTileSaveData(Layer layer) + { + List result = new List(); + + foreach (Tile tile in layer.tiles) + { + result.Add(new TileSaveData + { + X = tile.GridPosition.X, + Y = tile.GridPosition.Y, + CollapsedMesh = tile.collapsedMesh, + ContainsLight = tile.containsLight, + ContainsDecoration = tile.containsDecoration, + ContainsResource = tile.containsResource, + WasVisited = tile.wasVisited, + Resource = tile.resource == null ? null : tile.resource.CreateSaveData() + }); + } + + return result; + } + + private static List CreateRobotSaveData() + { + List result = new List(); + + foreach (Robot robot in GameData.robots) + { + result.Add(robot.CreateSaveData()); + } + + return result; + } +} diff --git a/Scripts/Core/SaveGameDataFactory.cs.uid b/Scripts/Core/SaveGameDataFactory.cs.uid new file mode 100644 index 0000000..2fb1ccf --- /dev/null +++ b/Scripts/Core/SaveGameDataFactory.cs.uid @@ -0,0 +1 @@ +uid://b87q17gdv4pfh diff --git a/Scripts/Core/SaveGameManager.cs b/Scripts/Core/SaveGameManager.cs index 915eaee..f16fdf3 100644 --- a/Scripts/Core/SaveGameManager.cs +++ b/Scripts/Core/SaveGameManager.cs @@ -27,7 +27,7 @@ public static class SaveGameManager CreateSaveDirectory(); ClearOldLayerFiles(); - SaveJson(GameDataPath, CreateCoreSaveData(saveGame)); + SaveJson(GameDataPath, SaveGameDataFactory.CreateCoreSaveData(saveGame)); SaveJson(RobotsPath, saveGame.Robots); SaveJson(ResearchPath, saveGame.Research); @@ -53,176 +53,12 @@ public static class SaveGameManager public static SaveGameData CreateSaveData() { - return new SaveGameData - { - Seed = GameData.seed, - CurrentLayer = GameData.currentLayer, - VisibleLayer = GameData.visibleLayer, - LowestLayer = GameData.lowestLayer, - MaxRobotCount = GameData.maxRobotCount, - CanMove = GameData.canMove, - Settings = CreateSettingsSaveData(), - Survival = CreateSurvivalSaveData(), - Inventory = CreateInventorySaveData(), - Research = CreateResearchSaveData(), - Layers = CreateLayerSaveData(), - Robots = CreateRobotSaveData() - }; + return SaveGameDataFactory.CreateSaveData(); } public static void ApplyWorldData(SaveGameData saveGame) { - if (saveGame == null) return; - - GameData.seed = saveGame.Seed; - GameData.currentLayer = saveGame.CurrentLayer; - GameData.visibleLayer = saveGame.VisibleLayer; - GameData.lowestLayer = saveGame.LowestLayer; - GameData.maxRobotCount = saveGame.MaxRobotCount; - GameData.canMove = saveGame.CanMove; - - ApplySettingsData(saveGame.Settings); - ApplySurvivalData(saveGame.Survival); - ApplyInventoryData(saveGame.Inventory); - ApplyResearchData(saveGame.Research); - ApplyLayerData(saveGame.Layers); - } - - private static SurvivalSaveData CreateSurvivalSaveData() - { - return new SurvivalSaveData - { - Hunger = GameData.survival.hunger, - Thirst = GameData.survival.thirst, - Energy = GameData.survival.energy, - IsDead = GameData.survival.isDead, - DeathReason = GameData.survival.deathReason, - CurrentStatus = GameData.survival.currentStatus, - ElapsedSeconds = GameData.survival.elapsedSeconds - }; - } - - 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(); - - foreach (Item item in GameData.inventory.items) - { - result.Add(new ItemSaveData - { - Id = item.data.Id, - Amount = item.currentAmount - }); - } - - return result; - } - - private static List CreateResearchSaveData() - { - List result = new List(); - - foreach (Research research in GameData.availableResearch.Values) - { - result.Add(new ResearchSaveData - { - Id = research.data.Id, - State = research.state, - ElapsedResearchTime = research.elapsedResearchTime, - PaidResources = research.paidResources - }); - } - - return result; - } - - private static List CreateLayerSaveData() - { - List result = new List(); - if (GameData.map == null) return result; - - foreach (Layer layer in GameData.map) - { - if (layer == null) continue; - - result.Add(new LayerSaveData - { - Level = layer.level, - IsGateOpen = layer.isGateOpen, - HasContentGenerated = layer.hasContentGenerated, - GateIngredients = new List(layer.gateIngredients), - CurrentResources = new List(layer.currentResources), - Tiles = CreateTileSaveData(layer) - }); - } - - return result; - } - - private static List CreateTileSaveData(Layer layer) - { - List result = new List(); - - foreach (Tile tile in layer.tiles) - { - result.Add(new TileSaveData - { - X = tile.GridPosition.X, - Y = tile.GridPosition.Y, - CollapsedMesh = tile.collapsedMesh, - ContainsLight = tile.containsLight, - ContainsDecoration = tile.containsDecoration, - ContainsResource = tile.containsResource, - WasVisited = tile.wasVisited, - Resource = tile.resource == null ? null : tile.resource.CreateSaveData() - }); - } - - return result; - } - - private static List CreateRobotSaveData() - { - List result = new List(); - - foreach (Robot robot in GameData.robots) - { - result.Add(robot.CreateSaveData()); - } - - return result; - } - - private static SaveGameData CreateCoreSaveData(SaveGameData saveGame) - { - return new SaveGameData - { - Seed = saveGame.Seed, - CurrentLayer = saveGame.CurrentLayer, - VisibleLayer = saveGame.VisibleLayer, - LowestLayer = saveGame.LowestLayer, - MaxRobotCount = saveGame.MaxRobotCount, - CanMove = saveGame.CanMove, - Settings = saveGame.Settings, - Survival = saveGame.Survival, - Inventory = saveGame.Inventory, - Research = new List(), - Layers = new List(), - Robots = new List() - }; + SaveGameDataApplier.ApplyWorldData(saveGame); } private static List LoadLayerSaveData() @@ -276,6 +112,8 @@ public static class SaveGameManager { string json = JsonSerializer.Serialize(data, JsonOptions); FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write); + if (file == null) return; + file.StoreString(json); file.Flush(); } @@ -285,128 +123,9 @@ public static class SaveGameManager if (!FileAccess.FileExists(path)) return default; FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read); + if (file == null) return default; + string json = file.GetAsText(); return JsonSerializer.Deserialize(json); } - - private static void ApplySurvivalData(SurvivalSaveData survival) - { - if (survival == null) return; - - GameData.survival.hunger = survival.Hunger; - GameData.survival.thirst = survival.Thirst; - GameData.survival.energy = survival.Energy; - GameData.survival.isDead = survival.IsDead; - GameData.survival.deathReason = survival.DeathReason ?? ""; - GameData.survival.currentStatus = survival.CurrentStatus ?? ""; - 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(); - if (savedItems == null) return; - - foreach (ItemSaveData savedItem in savedItems) - { - if (!GameData.availableItems.ContainsKey(savedItem.Id)) continue; - - GameData.inventory.AddItem( - new Item { data = GameData.availableItems[savedItem.Id] }, - savedItem.Amount - ); - } - } - - private static void ApplyResearchData(List savedResearch) - { - if (savedResearch == null) return; - - foreach (ResearchSaveData savedState in savedResearch) - { - if (!GameData.availableResearch.ContainsKey(savedState.Id)) continue; - - Research research = GameData.availableResearch[savedState.Id]; - research.state = savedState.State; - research.elapsedResearchTime = savedState.ElapsedResearchTime; - research.paidResources = savedState.PaidResources; - } - - GameData.RebuildRobotStatsFromResearch(); - } - - private static void ApplyLayerData(List savedLayers) - { - if (savedLayers == null || GameData.map == null) return; - - foreach (LayerSaveData savedLayer in savedLayers) - { - if (savedLayer.Level < 0 || savedLayer.Level >= GameData.map.Length) continue; - - Layer layer = GameData.map[savedLayer.Level]; - layer.isGateOpen = savedLayer.IsGateOpen; - layer.hasContentGenerated = savedLayer.HasContentGenerated; - layer.gateIngredients = savedLayer.GateIngredients ?? new List(); - layer.currentResources = savedLayer.CurrentResources ?? new List(); - - ApplyTileData(layer, savedLayer.Tiles); - } - } - - private static void ApplyTileData(Layer layer, List savedTiles) - { - if (savedTiles == null) return; - - foreach (TileSaveData savedTile in savedTiles) - { - if (savedTile.X < 0 || savedTile.X >= GameData.layerSize) continue; - if (savedTile.Y < 0 || savedTile.Y >= GameData.layerSize) continue; - - Tile tile = layer.tiles[savedTile.X, savedTile.Y]; - tile.collapsedMesh = savedTile.CollapsedMesh; - tile.containsLight = savedTile.ContainsLight; - tile.containsDecoration = savedTile.ContainsDecoration; - 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" && !layer.isGateOpen); - } - } } diff --git a/Scripts/Core/SteamworksHandler.cs b/Scripts/Core/SteamworksHandler.cs index 788de29..bad294b 100644 --- a/Scripts/Core/SteamworksHandler.cs +++ b/Scripts/Core/SteamworksHandler.cs @@ -13,12 +13,9 @@ public partial class SteamworksHandler : Node SteamInitExStatus status = Steam.SteamInitEx(false).Status; if (status != 0) { - GD.Print("Steam not initialized!"); return; } - GD.Print("Steam initialized!"); - GD.Print("User: " + Steam.GetPersonaName()); isSteamInitialized = true; } diff --git a/Scripts/DSL/Nodes/CraftNode.cs b/Scripts/DSL/Nodes/CraftNode.cs index aa53d45..1f2164b 100644 --- a/Scripts/DSL/Nodes/CraftNode.cs +++ b/Scripts/DSL/Nodes/CraftNode.cs @@ -18,7 +18,7 @@ public class CraftNode : ProgramNode if (amount <= 0) { - lastExecutionMessage = "Amount has to be atleast 1"; + lastExecutionMessage = "Amount has to be at least 1"; return NodeResult.FAILURE; } diff --git a/Scripts/DSL/Nodes/ExploreNode.cs b/Scripts/DSL/Nodes/ExploreNode.cs index d50428e..7ee42db 100644 --- a/Scripts/DSL/Nodes/ExploreNode.cs +++ b/Scripts/DSL/Nodes/ExploreNode.cs @@ -4,90 +4,127 @@ using Godot; public class ExploreNode : ProgramNode { - public Vector3 startPosition; - public Vector3I targetPosition; - public List pathPoints; - public ExploreNode() - { - DisplayText = "Explore"; - } - public override NodeResult Execute(Robot robot, double delta) - { - if (pathPoints == null) - { - int safetyCounter = 0; - int layerRange = Math.Max(GameData.lowestLayer, 1); - while (true) - { - targetPosition = new Vector3I(GameData.rand.Next(GameData.layerSize), GameData.rand.Next(layerRange), GameData.rand.Next(GameData.layerSize)); - if (!GameData.map[targetPosition.Y].tiles[targetPosition.X, targetPosition.Z].wasVisited) break; - safetyCounter++; - if (safetyCounter > Math.Pow(GameData.layerSize, 2) * 2) - { - lastExecutionMessage = "No tiles left to explore"; - return NodeResult.SUCCESS; - } - } - } + public Vector3 startPosition; + public Vector3I targetPosition; + public List pathPoints; - pathPoints ??= new List(Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)); - - if (pathPoints.Count <= 0) - { - lastExecutionMessage = $"No path available {targetPosition}"; - return NodeResult.FAILURE; - } + public ExploreNode() + { + DisplayText = "Explore"; + } - startPosition = robot.Position; - Vector3 target = pathPoints[0] - startPosition; - float distance = target.Length(); + public override NodeResult Execute(Robot robot, double delta) + { + if (pathPoints == null && !TrySelectTarget()) + { + lastExecutionMessage = "No tiles left to explore"; + return NodeResult.SUCCESS; + } - float movementSpeed = robot.GetMovementSpeed(); + if (pathPoints == null) + { + pathPoints = new List( + Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition) + ); + } - if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) - { - robot.Position = pathPoints[0]; - Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position); - Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z]; - if (!tile.wasVisited) - { - tile.VisitTile(); - } + if (pathPoints.Count <= 0) + { + lastExecutionMessage = $"No path available {targetPosition}"; + return NodeResult.FAILURE; + } - pathPoints.Remove(pathPoints[0]); - if (pathPoints.Count <= 0) - { - lastExecutionMessage = "Current exploration finished"; - pathPoints = null; - return NodeResult.RUNNING; - } + return MoveAlongPath(robot, delta); + } - lastExecutionMessage = ""; - return NodeResult.RUNNING; - } + private bool TrySelectTarget() + { + int safetyCounter = 0; + int layerRange = Math.Max(GameData.lowestLayer, 1); + int maximumAttempts = (int)Math.Pow(GameData.layerSize, 2) * 2; - Vector3 direction = target / distance; - Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); - if (lookDirection.Length() > 0.1f) - { - robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up); - } - robot.GlobalPosition += direction * (float)delta * movementSpeed; + while (safetyCounter <= maximumAttempts) + { + targetPosition = new Vector3I( + GameData.rand.Next(GameData.layerSize), + GameData.rand.Next(layerRange), + GameData.rand.Next(GameData.layerSize) + ); + if (!GameData.map[targetPosition.Y].tiles[targetPosition.X, targetPosition.Z].wasVisited) + { + return true; + } - return NodeResult.RUNNING; - } + safetyCounter++; + } - public override ProgramNode Duplicate() - { - ExploreNode duplicate = new ExploreNode - { - targetPosition = targetPosition - }; - return duplicate; - } + return false; + } - public override string Save() - { - return $"Name: {DisplayText}"; - } + private NodeResult MoveAlongPath(Robot robot, double delta) + { + startPosition = robot.Position; + Vector3 target = pathPoints[0] - startPosition; + float distance = target.Length(); + float movementSpeed = robot.GetMovementSpeed(); + + if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) + { + return FinishCurrentStep(robot); + } + + Vector3 direction = target / distance; + RotateRobot(robot, direction); + robot.GlobalPosition += direction * (float)delta * movementSpeed; + + return NodeResult.RUNNING; + } + + private NodeResult FinishCurrentStep(Robot robot) + { + robot.Position = pathPoints[0]; + VisitCurrentTile(robot); + pathPoints.RemoveAt(0); + + if (pathPoints.Count > 0) + { + lastExecutionMessage = ""; + return NodeResult.RUNNING; + } + + lastExecutionMessage = "Current exploration finished"; + pathPoints = null; + return NodeResult.RUNNING; + } + + private void VisitCurrentTile(Robot robot) + { + Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position); + Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z]; + if (!tile.wasVisited) + { + tile.VisitTile(); + } + } + + private void RotateRobot(Robot robot, Vector3 direction) + { + Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); + if (lookDirection.Length() <= 0.1f) return; + + robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up); + } + + public override ProgramNode Duplicate() + { + return new ExploreNode + { + targetPosition = targetPosition + }; + } + + public override string Save() + { + return $"Name: {DisplayText}"; + } } diff --git a/Scripts/DSL/Nodes/ForNode.cs b/Scripts/DSL/Nodes/ForNode.cs index 10ffccb..8a623aa 100644 --- a/Scripts/DSL/Nodes/ForNode.cs +++ b/Scripts/DSL/Nodes/ForNode.cs @@ -9,11 +9,12 @@ public class ForNode : ProgramNode { DisplayText = "For"; } + public override NodeResult Execute(Robot robot, double delta) { bool isConditionFulfilled = DetermineCondition(); amountExecuted++; - return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; + return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; } private bool DetermineCondition() @@ -41,21 +42,6 @@ public class ForNode : ProgramNode Dictionary availableNodes ) { - nextNode = null; - NegativeNode = null; - - foreach (Godot.Collections.Dictionary connection in connections) - { - int port = (int)connection["from_port"]; - ProgramNode connectedNode = GetConnectedNode(connection, availableNodes); - if (port == 0) - { - nextNode = connectedNode; - } - else - { - NegativeNode = connectedNode; - } - } + SetBranchNodes(connections, availableNodes); } } diff --git a/Scripts/DSL/Nodes/HarvestNode.cs b/Scripts/DSL/Nodes/HarvestNode.cs index 954e3f4..fd1d9c1 100644 --- a/Scripts/DSL/Nodes/HarvestNode.cs +++ b/Scripts/DSL/Nodes/HarvestNode.cs @@ -2,51 +2,48 @@ using Godot; public class HarvestNode : ProgramNode { - public HarvestNode() - { - DisplayText = "Harvest"; - } - 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]; + public HarvestNode() + { + DisplayText = "Harvest"; + } - if (!tile.containsResource) - { - lastExecutionMessage = "No resource on this tile"; - return NodeResult.FAILURE; - } + 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.resource.CanExtract()) - { - lastExecutionMessage = "Resource is depleted and not endless or you haven't unlocked it yet"; - return NodeResult.SUCCESS; - } + if (!tile.containsResource || tile.resource == null) + { + lastExecutionMessage = "No resource on this tile"; + return NodeResult.FAILURE; + } - if (tile.resource.Extract(delta)) - { - SoundManager.PlayMining(); - if (!GameData.inventory.AddItem(new Item {data = tile.resource.item}, 1)) - { - lastExecutionMessage = "Not enough space"; - return NodeResult.FAILURE; - } - else - { - return NodeResult.SUCCESS; - } - } - return NodeResult.RUNNING; - } + if (!tile.resource.CanExtract()) + { + lastExecutionMessage = "Resource is depleted and not endless or you haven't unlocked it yet"; + return NodeResult.SUCCESS; + } - public override ProgramNode Duplicate() - { - HarvestNode duplicate = new HarvestNode(); - return duplicate; - } + if (!tile.resource.Extract(delta)) return NodeResult.RUNNING; - public override string Save() - { - return $"Name: {DisplayText}"; - } + SoundManager.PlayMining(); + if (!GameData.inventory.AddItem(new Item { data = tile.resource.item }, 1)) + { + lastExecutionMessage = "Not enough space"; + return NodeResult.FAILURE; + } + + lastExecutionMessage = ""; + return NodeResult.SUCCESS; + } + + public override ProgramNode Duplicate() + { + return new HarvestNode(); + } + + public override string Save() + { + return $"Name: {DisplayText}"; + } } diff --git a/Scripts/DSL/Nodes/IfNode.cs b/Scripts/DSL/Nodes/IfNode.cs index 017edb2..2fd65a4 100644 --- a/Scripts/DSL/Nodes/IfNode.cs +++ b/Scripts/DSL/Nodes/IfNode.cs @@ -10,6 +10,7 @@ public class IfNode : ProgramNode { DisplayText = "If"; } + public override NodeResult Execute(Robot robot, double delta) { if (selectedItem == null) @@ -19,27 +20,21 @@ public class IfNode : ProgramNode } bool isConditionFulfilled = DetermineCondition(); - return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; + return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; } private bool DetermineCondition() { int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id); - switch (comparator) + return comparator switch { - case "is bigger than": - return inventoryAmount > amount; - case "is less than": - return inventoryAmount < amount; - case "is not": - return inventoryAmount != amount; - case "is less than or equal to": - return inventoryAmount <= amount; - case "is bigger than or equal to": - return inventoryAmount >= amount; - default: - return inventoryAmount == amount; - } + "is bigger than" => inventoryAmount > amount, + "is less than" => inventoryAmount < amount, + "is not" => inventoryAmount != amount, + "is less than or equal to" => inventoryAmount <= amount, + "is bigger than or equal to" => inventoryAmount >= amount, + _ => inventoryAmount == amount + }; } public override ProgramNode Duplicate() @@ -63,21 +58,6 @@ public class IfNode : ProgramNode Dictionary availableNodes ) { - nextNode = null; - NegativeNode = null; - - foreach (Godot.Collections.Dictionary connection in connections) - { - int port = (int)connection["from_port"]; - ProgramNode connectedNode = GetConnectedNode(connection, availableNodes); - if (port == 0) - { - nextNode = connectedNode; - } - else - { - NegativeNode = connectedNode; - } - } + SetBranchNodes(connections, availableNodes); } } diff --git a/Scripts/DSL/Nodes/MoveNode.cs b/Scripts/DSL/Nodes/MoveNode.cs index 5f0815e..0bb808d 100644 --- a/Scripts/DSL/Nodes/MoveNode.cs +++ b/Scripts/DSL/Nodes/MoveNode.cs @@ -3,79 +3,98 @@ using Godot; public class MoveNode : ProgramNode { - public Vector3 startPosition; - public Vector3I targetPosition; - public List pathPoints; - public MoveNode() - { - DisplayText = "Move"; - } - public override NodeResult Execute(Robot robot, double delta) - { - Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position); - pathPoints ??= new List(Pathfinding.GetPath(closestPosition, targetPosition)); + public Vector3 startPosition; + public Vector3I targetPosition; + public List pathPoints; - if (pathPoints.Count <= 0) - { - if ((closestPosition - targetPosition).Length() == 0) - { - lastExecutionMessage = ""; - return NodeResult.SUCCESS; - } - lastExecutionMessage = "No path available"; - return NodeResult.FAILURE; - } + public MoveNode() + { + DisplayText = "Move"; + } - startPosition = robot.Position; - Vector3 target = pathPoints[0] - startPosition; - float distance = target.Length(); - - float movementSpeed = robot.GetMovementSpeed(); + public override NodeResult Execute(Robot robot, double delta) + { + Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position); + if (pathPoints == null) + { + pathPoints = new List(Pathfinding.GetPath(closestPosition, targetPosition)); + } - if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) - { - robot.Position = pathPoints[0]; - Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position); - Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z]; - if (!tile.wasVisited) - { - tile.VisitTile(); - } + if (pathPoints.Count <= 0) + { + if ((closestPosition - targetPosition).Length() == 0) + { + lastExecutionMessage = ""; + return NodeResult.SUCCESS; + } - pathPoints.Remove(pathPoints[0]); - if (pathPoints.Count <= 0) - { - pathPoints = null; - lastExecutionMessage = ""; - return NodeResult.SUCCESS; - } + lastExecutionMessage = "No path available"; + return NodeResult.FAILURE; + } - lastExecutionMessage = ""; - return NodeResult.RUNNING; - } + return MoveAlongPath(robot, delta); + } - Vector3 direction = target / distance; - Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); - if (lookDirection.Length() > 0.1f) - { - robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up); - } - robot.GlobalPosition += direction * (float)delta * movementSpeed; + private NodeResult MoveAlongPath(Robot robot, double delta) + { + startPosition = robot.Position; + Vector3 target = pathPoints[0] - startPosition; + float distance = target.Length(); + float movementSpeed = robot.GetMovementSpeed(); - return NodeResult.RUNNING; - } + if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) + { + return FinishCurrentStep(robot); + } - public override ProgramNode Duplicate() - { - MoveNode duplicate = new MoveNode - { - targetPosition = targetPosition - }; - return duplicate; - } + Vector3 direction = target / distance; + RotateRobot(robot, direction); + robot.GlobalPosition += direction * (float)delta * movementSpeed; - public override string Save() - { - return $"Name: {DisplayText}, Position: ({targetPosition.X}|{targetPosition.Y}|{targetPosition.Z})"; - } + return NodeResult.RUNNING; + } + + private NodeResult FinishCurrentStep(Robot robot) + { + robot.Position = pathPoints[0]; + VisitCurrentTile(robot); + pathPoints.RemoveAt(0); + lastExecutionMessage = ""; + + if (pathPoints.Count > 0) return NodeResult.RUNNING; + + pathPoints = null; + return NodeResult.SUCCESS; + } + + private void VisitCurrentTile(Robot robot) + { + Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position); + Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z]; + if (!tile.wasVisited) + { + tile.VisitTile(); + } + } + + private void RotateRobot(Robot robot, Vector3 direction) + { + Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); + if (lookDirection.Length() <= 0.1f) return; + + robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up); + } + + public override ProgramNode Duplicate() + { + return new MoveNode + { + targetPosition = targetPosition + }; + } + + public override string Save() + { + return $"Name: {DisplayText}, Position: ({targetPosition.X}|{targetPosition.Y}|{targetPosition.Z})"; + } } diff --git a/Scripts/DSL/Nodes/ProgramNode.cs b/Scripts/DSL/Nodes/ProgramNode.cs index f3bdbe2..e150dae 100644 --- a/Scripts/DSL/Nodes/ProgramNode.cs +++ b/Scripts/DSL/Nodes/ProgramNode.cs @@ -3,35 +3,57 @@ using System.Collections.Generic; public abstract class ProgramNode { - public ProgramNode nextNode; - public ProgramNode NegativeNode; - public string DisplayText; - public string lastExecutionMessage; + public ProgramNode nextNode; + public ProgramNode NegativeNode; + public string DisplayText; + public string lastExecutionMessage; - public abstract NodeResult Execute(Robot robot, double delta); - public abstract ProgramNode Duplicate(); - public abstract string Save(); + public abstract NodeResult Execute(Robot robot, double delta); + public abstract ProgramNode Duplicate(); + public abstract string Save(); - public virtual void SetNextNode( - List connections, - Dictionary availableNodes - ) - { - nextNode = null; + public virtual void SetNextNode( + List connections, + Dictionary availableNodes + ) + { + nextNode = null; - if (connections.Count <= 0) return; + if (connections.Count <= 0) return; - nextNode = GetConnectedNode(connections[0], availableNodes); - } + nextNode = GetConnectedNode(connections[0], availableNodes); + } - protected ProgramNode GetConnectedNode( - Godot.Collections.Dictionary connection, - Dictionary availableNodes - ) - { - StringName nodeName = connection["to_node"].AsStringName(); - if (!availableNodes.ContainsKey(nodeName)) return null; + protected void SetBranchNodes( + List connections, + Dictionary availableNodes + ) + { + nextNode = null; + NegativeNode = null; - return availableNodes[nodeName]; - } + foreach (Godot.Collections.Dictionary connection in connections) + { + ProgramNode connectedNode = GetConnectedNode(connection, availableNodes); + if ((int)connection["from_port"] == 0) + { + nextNode = connectedNode; + } + else + { + NegativeNode = connectedNode; + } + } + } + + protected ProgramNode GetConnectedNode( + Godot.Collections.Dictionary connection, + Dictionary availableNodes + ) + { + StringName nodeName = connection["to_node"].AsStringName(); + if (!availableNodes.ContainsKey(nodeName)) return null; + + return availableNodes[nodeName]; + } } diff --git a/Scripts/DSL/Nodes/WhileNode.cs b/Scripts/DSL/Nodes/WhileNode.cs index 1c2b8f8..9503663 100644 --- a/Scripts/DSL/Nodes/WhileNode.cs +++ b/Scripts/DSL/Nodes/WhileNode.cs @@ -10,9 +10,9 @@ public class WhileNode : ProgramNode { DisplayText = "While"; } + public override NodeResult Execute(Robot robot, double delta) { - if (selectedItem == null) { lastExecutionMessage = "No Item selected"; @@ -20,27 +20,21 @@ public class WhileNode : ProgramNode } bool isConditionFulfilled = DetermineCondition(); - return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; + return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; } private bool DetermineCondition() { int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id); - switch (comparator) + return comparator switch { - case "is bigger than": - return inventoryAmount > amount; - case "is less than": - return inventoryAmount < amount; - case "is not": - return inventoryAmount != amount; - case "is less than or equal to": - return inventoryAmount <= amount; - case "is bigger than or equal to": - return inventoryAmount >= amount; - default: - return inventoryAmount == amount; - } + "is bigger than" => inventoryAmount > amount, + "is less than" => inventoryAmount < amount, + "is not" => inventoryAmount != amount, + "is less than or equal to" => inventoryAmount <= amount, + "is bigger than or equal to" => inventoryAmount >= amount, + _ => inventoryAmount == amount + }; } public override ProgramNode Duplicate() @@ -64,21 +58,6 @@ public class WhileNode : ProgramNode Dictionary availableNodes ) { - nextNode = null; - NegativeNode = null; - - foreach (Godot.Collections.Dictionary connection in connections) - { - int port = (int)connection["from_port"]; - ProgramNode connectedNode = GetConnectedNode(connection, availableNodes); - if (port == 0) - { - nextNode = connectedNode; - } - else - { - NegativeNode = connectedNode; - } - } + SetBranchNodes(connections, availableNodes); } } diff --git a/Scripts/Gameplay/Crafting/Building.cs b/Scripts/Gameplay/Crafting/Building.cs deleted file mode 100644 index 93505a1..0000000 --- a/Scripts/Gameplay/Crafting/Building.cs +++ /dev/null @@ -1,3 +0,0 @@ -public class Building -{ -} diff --git a/Scripts/Gameplay/Crafting/Building.cs.uid b/Scripts/Gameplay/Crafting/Building.cs.uid deleted file mode 100644 index bac56b0..0000000 --- a/Scripts/Gameplay/Crafting/Building.cs.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cl2yvllo35qbb diff --git a/Scripts/Gameplay/Crafting/GameResource.cs b/Scripts/Gameplay/Crafting/GameResource.cs index d2aaacb..b515022 100644 --- a/Scripts/Gameplay/Crafting/GameResource.cs +++ b/Scripts/Gameplay/Crafting/GameResource.cs @@ -1,92 +1,95 @@ public class GameResource { - private const float NormalExtractionSpeed = 1f; - private const float EndlessExtractionSpeed = 4f; + private const float NormalExtractionSpeed = 1f; + private const float EndlessExtractionSpeed = 4f; - public string name; - public ItemData item; + public string name; + public ItemData item; - private int currentAmount; - private int maxAmount; - private bool isEndless; - private float extractionSpeed; - private double timeSinceLastExtraction; + private int currentAmount; + private int maxAmount; + private bool isEndless; + private float extractionSpeed; + private double timeSinceLastExtraction; - public GameResource(string name) - { - this.name = name; - maxAmount = GameData.rand.Next(1000, 10000); - currentAmount = maxAmount; - isEndless = false; - extractionSpeed = NormalExtractionSpeed; - item = GameData.availableItems[name]; - } + public GameResource(string name) + { + this.name = name; + maxAmount = GameData.rand.Next(1000, 10000); + currentAmount = maxAmount; + isEndless = false; + extractionSpeed = NormalExtractionSpeed; + item = GameData.availableItems[name]; + } - public static GameResource FromSaveData(ResourceSaveData saveData) - { - 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; - } + public static GameResource FromSaveData(ResourceSaveData saveData) + { + GameResource resource = new GameResource(saveData.Name) + { + currentAmount = saveData.CurrentAmount, + maxAmount = saveData.MaxAmount, + isEndless = saveData.IsEndless, + extractionSpeed = saveData.ExtractionSpeed, + timeSinceLastExtraction = saveData.TimeSinceLastExtraction + }; + resource.NormalizeExtractionSpeed(); + return resource; + } - public ResourceSaveData CreateSaveData() - { - return new ResourceSaveData - { - Name = name, - CurrentAmount = currentAmount, - MaxAmount = maxAmount, - IsEndless = isEndless, - ExtractionSpeed = extractionSpeed, - TimeSinceLastExtraction = timeSinceLastExtraction - }; - } + public ResourceSaveData CreateSaveData() + { + return new ResourceSaveData + { + Name = name, + CurrentAmount = currentAmount, + MaxAmount = maxAmount, + IsEndless = isEndless, + ExtractionSpeed = extractionSpeed, + TimeSinceLastExtraction = timeSinceLastExtraction + }; + } - public bool Extract(double delta) - { - timeSinceLastExtraction += delta; - if (timeSinceLastExtraction < extractionSpeed) return false; + public bool Extract(double delta) + { + timeSinceLastExtraction += delta; + if (timeSinceLastExtraction < extractionSpeed) return false; - timeSinceLastExtraction = 0; - if (isEndless) return true; + timeSinceLastExtraction = 0; + if (isEndless) return true; - if (currentAmount > 0) - { - currentAmount--; - return true; - } + if (currentAmount <= 0) return false; - return false; - } + currentAmount--; + return true; + } - public bool CanExtract() - { - return (isEndless || currentAmount > 0) && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED; - } + public bool CanExtract() + { + return (isEndless || currentAmount > 0) + && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED; + } - public void MakeEndless() - { - isEndless = true; - extractionSpeed = EndlessExtractionSpeed; - } + public void MakeEndless() + { + isEndless = true; + extractionSpeed = EndlessExtractionSpeed; + } - public bool IsEndless() - { - return isEndless; - } + public bool IsEndless() + { + return isEndless; + } - public float GetExtractionSpeed() - { - return extractionSpeed; - } + public float GetExtractionSpeed() + { + return extractionSpeed; + } + + private void NormalizeExtractionSpeed() + { + if (!isEndless) return; + if (extractionSpeed > NormalExtractionSpeed) return; + + extractionSpeed = EndlessExtractionSpeed; + } } diff --git a/Scripts/Gameplay/Crafting/ItemData.cs b/Scripts/Gameplay/Crafting/ItemData.cs index d334421..895d19b 100644 --- a/Scripts/Gameplay/Crafting/ItemData.cs +++ b/Scripts/Gameplay/Crafting/ItemData.cs @@ -29,12 +29,13 @@ public class ItemData public string GetReadableName() { - string noUnderscore = Id.Replace("_", " ").ToLower(); - return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1); + return GetReadableName(Id); } public static string GetReadableName(string input) { + if (string.IsNullOrEmpty(input)) return ""; + string noUnderscore = input.Replace("_", " ").ToLower(); return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1); } @@ -46,6 +47,8 @@ public class ItemData public string GetCraftingDisplay() { + if (Inputs.Count <= 0) return GetReadableName() + ": \r"; + string result = GetReadableName() + ": \r"; foreach (Ingredient ingredient in Inputs) @@ -53,8 +56,6 @@ public class ItemData result += $"{GetReadableName(ingredient.Item)} ({ingredient.Amount}),\r"; } - if (Inputs.Count <= 0) return result; - result = result.Remove(result.Length - 2); return result; } diff --git a/Scripts/Gameplay/Research/Research.cs b/Scripts/Gameplay/Research/Research.cs index 1e3580c..28f21ef 100644 --- a/Scripts/Gameplay/Research/Research.cs +++ b/Scripts/Gameplay/Research/Research.cs @@ -53,7 +53,6 @@ public class Research public static string GetReadableName(string input) { - string noUnderscore = input.Replace("_", " ").ToLower(); - return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1); + return ItemData.GetReadableName(input); } } diff --git a/Scripts/Gameplay/Research/ResearchData.cs b/Scripts/Gameplay/Research/ResearchData.cs index aca3542..1887273 100644 --- a/Scripts/Gameplay/Research/ResearchData.cs +++ b/Scripts/Gameplay/Research/ResearchData.cs @@ -23,8 +23,7 @@ public class ResearchData public string GetReadableName() { - string noUnderscore = Id.Replace("_", " ").ToLower(); - return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1); + return ItemData.GetReadableName(Id); } public static string GetIndex(string readable) diff --git a/Scripts/Gameplay/Robots/Robot.cs b/Scripts/Gameplay/Robots/Robot.cs index 824bf0f..0d67a83 100644 --- a/Scripts/Gameplay/Robots/Robot.cs +++ b/Scripts/Gameplay/Robots/Robot.cs @@ -33,58 +33,70 @@ public partial class Robot : Node3D if (isExecuting) { - if (CanExecute(delta)) - { - switch (currentNode.Execute(this, delta)) - { - case NodeResult.SUCCESS: - currentNode = currentNode.nextNode; - if (currentNode == null) - { - isExecuting = false; - } - break; - - case NodeResult.FAILURE: - isExecuting = false; - currentMessage = "(FAILED)" + currentNode.lastExecutionMessage; - break; - - case NodeResult.RUNNING: - currentMessage = ""; - break; - case NodeResult.CONDITIONFALSE: - currentNode = currentNode.NegativeNode; - if (currentNode == null) - { - isExecuting = false; - } - break; - } - } - } - else if (currentMessage.Length <= 0) - { - CoolDown( - delta, - GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond) - * TypeStats.CoolingMultiplier - ); - - currentMessage = "No script executing"; + UpdateExecution(delta); } else { - CoolDown( - delta, - GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond) - * TypeStats.CoolingMultiplier - ); + UpdateIdle(delta); } Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer; } + private void UpdateExecution(double delta) + { + if (!CanExecute(delta)) return; + + NodeResult result = currentNode.Execute(this, delta); + ApplyNodeResult(result); + } + + private void ApplyNodeResult(NodeResult result) + { + switch (result) + { + case NodeResult.SUCCESS: + MoveToNextNode(currentNode.nextNode); + break; + + case NodeResult.CONDITIONFALSE: + MoveToNextNode(currentNode.NegativeNode); + break; + + case NodeResult.FAILURE: + isExecuting = false; + currentMessage = "(FAILED)" + currentNode.lastExecutionMessage; + break; + + case NodeResult.RUNNING: + currentMessage = ""; + break; + } + } + + private void MoveToNextNode(ProgramNode nextNode) + { + currentNode = nextNode; + if (currentNode == null) + { + isExecuting = false; + } + } + + private void UpdateIdle(double delta) + { + CoolDown( + delta, + GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond) + * TypeStats.CoolingMultiplier + ); + + if (currentMessage.Length <= 0) + { + currentMessage = "No script executing"; + } + } + public void SetupExecution(List nodes) { if (nodes.Count <= 0) return; @@ -200,32 +212,13 @@ public partial class Robot : Node3D return false; } - float energyUse = - GameData.robotStats.GetEnergyUse(EnergyUsePerSecond) - * TypeStats.EnergyUseMultiplier - * (float)delta; - - if (!GameData.survival.TryConsumeEnergy(energyUse)) + if (!TryConsumeEnergy(delta)) { currentMessage = "Not enough energy"; return false; } - heat = Math.Clamp( - heat + GameData.robotStats.GetHeatGain(HeatGainPerSecond) - * TypeStats.HeatGainMultiplier - * (float)delta, - 0f, - 100f - ); - - maintenance = Math.Clamp( - maintenance - GameData.robotStats.GetMaintenanceLoss(MaintenanceLossPerSecond) - * TypeStats.MaintenanceLossMultiplier - * (float)delta, - 0f, - 100f - ); + ApplyWear(delta); if (heat >= 100f) { @@ -244,6 +237,35 @@ public partial class Robot : Node3D return true; } + private bool TryConsumeEnergy(double delta) + { + float energyUse = + GameData.robotStats.GetEnergyUse(EnergyUsePerSecond) + * TypeStats.EnergyUseMultiplier + * (float)delta; + + return GameData.survival.TryConsumeEnergy(energyUse); + } + + private void ApplyWear(double delta) + { + heat = Math.Clamp( + heat + GameData.robotStats.GetHeatGain(HeatGainPerSecond) + * TypeStats.HeatGainMultiplier + * (float)delta, + 0f, + 100f + ); + + maintenance = Math.Clamp( + maintenance - GameData.robotStats.GetMaintenanceLoss(MaintenanceLossPerSecond) + * TypeStats.MaintenanceLossMultiplier + * (float)delta, + 0f, + 100f + ); + } + private void CoolDown(double delta, float heatLossPerSecond) { heat = Math.Clamp( diff --git a/Scripts/Tests/TestRunner.cs b/Scripts/Tests/TestRunner.cs index c38c684..6c8d28d 100644 --- a/Scripts/Tests/TestRunner.cs +++ b/Scripts/Tests/TestRunner.cs @@ -629,98 +629,4 @@ public partial class TestRunner : Node AssertTrue(hasStartNode, "start node prefab loaded"); } - private Layer CreateTestLayer(int level, string collapsedMesh) - { - Layer layer = new Layer - { - level = level, - currentResources = new List { "stone" }, - gateIngredients = new List(), - tiles = new Tile[1, 1] - }; - - Tile tile = new Tile - { - GridPosition = new Vector2I(0, 0), - Position = Vector3.Zero, - collapsedMesh = collapsedMesh, - containsResource = true, - resource = new GameResource("stone") - }; - - layer.tiles[0, 0] = tile; - return layer; - } - - private bool IsGeneratedLayerValid(Layer layer) - { - bool hasGate = false; - - foreach (Tile tile in layer.tiles) - { - if (tile.collapsedMesh == null) - { - return false; - } - - if (tile.collapsedMesh == "gate") - { - hasGate = true; - } - } - - if (!hasGate) - { - return false; - } - - return WFC.IsMapConnected(layer.tiles, 1f); - } - - private Godot.Collections.Dictionary CreateConnection( - string fromNode, - int fromPort, - string toNode, - int toPort - ) - { - Godot.Collections.Dictionary connection = new Godot.Collections.Dictionary(); - connection["from_node"] = new StringName(fromNode); - connection["from_port"] = fromPort; - connection["to_node"] = new StringName(toNode); - connection["to_port"] = toPort; - return connection; - } - - private void AssertTrue(bool value, string message) - { - if (!value) - { - throw new Exception(message); - } - } - - private void AssertFalse(bool value, string message) - { - if (value) - { - throw new Exception(message); - } - } - - private void AssertEqual(T expected, T actual, string message) - { - if (!EqualityComparer.Default.Equals(expected, actual)) - { - throw new Exception($"{message}: expected {expected}, got {actual}"); - } - } - - private void AssertClose(float expected, float actual, float tolerance, string message) - { - if (Math.Abs(expected - actual) > tolerance) - { - throw new Exception($"{message}: expected {expected}, got {actual}"); - } - } } diff --git a/Scripts/Tests/TestRunnerHelpers.cs b/Scripts/Tests/TestRunnerHelpers.cs new file mode 100644 index 0000000..89035b4 --- /dev/null +++ b/Scripts/Tests/TestRunnerHelpers.cs @@ -0,0 +1,101 @@ +using Godot; +using System; +using System.Collections.Generic; + +public partial class TestRunner +{ + private Layer CreateTestLayer(int level, string collapsedMesh) + { + Layer layer = new Layer + { + level = level, + currentResources = new List { "stone" }, + gateIngredients = new List(), + tiles = new Tile[1, 1] + }; + + Tile tile = new Tile + { + GridPosition = new Vector2I(0, 0), + Position = Vector3.Zero, + collapsedMesh = collapsedMesh, + containsResource = true, + resource = new GameResource("stone") + }; + + layer.tiles[0, 0] = tile; + return layer; + } + + private bool IsGeneratedLayerValid(Layer layer) + { + bool hasGate = false; + + foreach (Tile tile in layer.tiles) + { + if (tile.collapsedMesh == null) + { + return false; + } + + if (tile.collapsedMesh == "gate") + { + hasGate = true; + } + } + + if (!hasGate) + { + return false; + } + + return WFC.IsMapConnected(layer.tiles, 1f); + } + + private Godot.Collections.Dictionary CreateConnection( + string fromNode, + int fromPort, + string toNode, + int toPort + ) + { + Godot.Collections.Dictionary connection = new Godot.Collections.Dictionary(); + connection["from_node"] = new StringName(fromNode); + connection["from_port"] = fromPort; + connection["to_node"] = new StringName(toNode); + connection["to_port"] = toPort; + return connection; + } + + private void AssertTrue(bool value, string message) + { + if (!value) + { + throw new Exception(message); + } + } + + private void AssertFalse(bool value, string message) + { + if (value) + { + throw new Exception(message); + } + } + + private void AssertEqual(T expected, T actual, string message) + { + if (!EqualityComparer.Default.Equals(expected, actual)) + { + throw new Exception($"{message}: expected {expected}, got {actual}"); + } + } + + private void AssertClose(float expected, float actual, float tolerance, string message) + { + if (Math.Abs(expected - actual) > tolerance) + { + throw new Exception($"{message}: expected {expected}, got {actual}"); + } + } +} diff --git a/Scripts/Tests/TestRunnerHelpers.cs.uid b/Scripts/Tests/TestRunnerHelpers.cs.uid new file mode 100644 index 0000000..eddbcee --- /dev/null +++ b/Scripts/Tests/TestRunnerHelpers.cs.uid @@ -0,0 +1 @@ +uid://dn38ysswhpy04 diff --git a/Scripts/UI/Common/Camera3d.cs b/Scripts/UI/Common/Camera3d.cs index 7c90868..11045f9 100644 --- a/Scripts/UI/Common/Camera3d.cs +++ b/Scripts/UI/Common/Camera3d.cs @@ -17,8 +17,7 @@ public partial class Camera3d : Camera3D { Control focused = GetViewport().GuiGetFocusOwner(); - if (focused is LineEdit || focused is TextEdit) - return; + if (focused is LineEdit || focused is TextEdit) return; if (canMove) MoveCamera(delta); } @@ -38,17 +37,13 @@ public partial class Camera3d : Camera3D if (direction != Vector3.Zero) { - if(robot != null) robot = null; + robot = null; direction = direction.Normalized() * Speed * (Input.IsActionPressed("sprint") ? 2.5f : 1) * d; Translate(direction); } - else + else if (robot != null) { - if(robot != null) - { - Position = new Vector3(robot.Position.X, 10 - visibleLayer * 4, robot.Position.Z + 4f); - } - + Position = new Vector3(robot.Position.X, 10 - visibleLayer * 4, robot.Position.Z + 4f); } if (Position.Y != 10 - visibleLayer * 4) diff --git a/Scripts/UI/Common/UIHandler.cs b/Scripts/UI/Common/UIHandler.cs index ae588a8..5a4ab11 100644 --- a/Scripts/UI/Common/UIHandler.cs +++ b/Scripts/UI/Common/UIHandler.cs @@ -1,5 +1,3 @@ -using System; -using System.Diagnostics; using Godot; public partial class UIHandler : Control @@ -46,10 +44,7 @@ public partial class UIHandler : Control DisplayStats(); DisplayRobotAlarm(); - Control focused = GetViewport().GuiGetFocusOwner(); - - if (focused is LineEdit || focused is TextEdit) - return; + if (IsTextInputFocused()) return; if (Input.IsActionJustPressed("map")) HandleMapButton(); if (Input.IsActionJustPressed("menu")) HandleMenuButton(); @@ -58,22 +53,29 @@ public partial class UIHandler : Control if (Input.IsActionJustPressed("research")) HandleResearchButton(); } + private bool IsTextInputFocused() + { + Control focused = GetViewport().GuiGetFocusOwner(); + return focused is LineEdit || focused is TextEdit; + } + public void HandleMenuButton() { - if(GameData.survival.isDead) return; + if (GameData.survival.isDead) return; + OpenUIElement(menu); GameData.isPaused = menu.Visible || options.Visible; } public void HandleMenu() { - if(GameData.survival.isDead) return; HandleMenuButton(); } public void ShowOptions() { - if(GameData.survival.isDead) return; + if (GameData.survival.isDead) return; + menu.Hide(); OpenUIElement(options); GameData.isPaused = options.Visible; @@ -81,14 +83,16 @@ public partial class UIHandler : Control public void HandleMapButton() { - if(GameData.survival.isDead) return; + if (GameData.survival.isDead) return; + OpenUIElement(map); if (map.Visible) map.ShowMap(); } public void HandleRobotListButton() { - if(GameData.survival.isDead) return; + if (GameData.survival.isDead) return; + receivedRobotFollowSignal = false; receivedRobotJumpSignal = false; OpenUIElement(robotList); @@ -96,28 +100,19 @@ public partial class UIHandler : Control public void HandleInventoryButton() { - if(GameData.survival.isDead) return; + if (GameData.survival.isDead) return; + OpenUIElement(inventory); } public void HandleResearchButton() { - if(GameData.survival.isDead) return; + if (GameData.survival.isDead) return; + OpenUIElement(researchList); if (researchList.Visible) researchList.SetupGraph(); } - public void DisplayStats() - { - FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS"; - double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024); - string memoryDisplay = memory > 1024 ? Math.Round(memory / 1024, 2).ToString() + " GB" : memory.ToString() + " MB"; - RAM.Text = memoryDisplay; - DisplaySurvivalStats(); - DisplayWorldStats(); - DisplayLoseCondition(); - } - public void ExitGame() { GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn"); @@ -139,14 +134,7 @@ public partial class UIHandler : Control public void OpenUIElement(Control element) { SoundManager.PlayButton(); - if (element.Visible) - { - element.Hide(); - } - else - { - element.Show(); - } + element.Visible = !element.Visible; HideUIElements(element); } @@ -164,25 +152,6 @@ public partial class UIHandler : Control } } - private void DisplayRobotAlarm() - { - string messages = ""; - if (GameData.survival.isDead) - { - messages += GameData.survival.currentStatus + "\r"; - } - - foreach (Robot robot in GameData.robots) - { - if (robot.currentMessage.Length > 0) - { - messages += $"{robot.Name}: {robot.currentMessage}\r"; - } - } - robotAlarm.Visible = messages.Length > 0; - robotAlarm.TooltipText = messages; - } - private void OnRobotJumpTo(Robot robot) { if (receivedRobotJumpSignal) return; @@ -201,42 +170,6 @@ public partial class UIHandler : Control mainCam.Follow(robot); } - private void DisplaySurvivalStats() - { - energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}"; - waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}"; - hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}"; - survivalStatus.Text = GameData.survival.currentStatus; - survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical") - ? UIStyle.GetWarningColor() - : Colors.White; - - if (GameData.survival.isDead) - { - ShowGameOver(); - } - } - - private void DisplayWorldStats() - { - currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}"; - deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}"; - if (GameData.lowestLayer == GameData.ruinSize) - { - unlockLayer.Visible = false; - return; - } - unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients(); - unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1); - } - - private void DisplayLoseCondition() - { - if (!GameData.HasNoRobotRecovery()) return; - - ShowGameOver("No robots remain and no robot can be spawned from inventory."); - } - public void UnlockLayer() { if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1)) @@ -259,20 +192,4 @@ public partial class UIHandler : Control } } - public void ShowGameOver() - { - ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time."); - } - - public void ShowGameOver(string message) - { - if (gameOver.Visible) return; - gameOver.GetNode("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r"; - gameOver.Show(); - } - - public void HideGameOver() - { - gameOver.Hide(); - } } diff --git a/Scripts/UI/Common/UIHandlerDisplay.cs b/Scripts/UI/Common/UIHandlerDisplay.cs new file mode 100644 index 0000000..0446492 --- /dev/null +++ b/Scripts/UI/Common/UIHandlerDisplay.cs @@ -0,0 +1,98 @@ +using System; +using System.Diagnostics; +using Godot; + +public partial class UIHandler +{ + public void DisplayStats() + { + FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS"; + RAM.Text = GetMemoryDisplay(); + DisplaySurvivalStats(); + DisplayWorldStats(); + DisplayLoseCondition(); + } + + private string GetMemoryDisplay() + { + double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024); + if (memory > 1024) + { + return Math.Round(memory / 1024, 2).ToString() + " GB"; + } + + return memory.ToString() + " MB"; + } + + private void DisplayRobotAlarm() + { + string messages = ""; + if (GameData.survival.isDead) + { + messages += GameData.survival.currentStatus + "\r"; + } + + foreach (Robot robot in GameData.robots) + { + if (robot.currentMessage.Length > 0) + { + messages += $"{robot.Name}: {robot.currentMessage}\r"; + } + } + robotAlarm.Visible = messages.Length > 0; + robotAlarm.TooltipText = messages; + } + + private void DisplaySurvivalStats() + { + energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}"; + waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}"; + hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}"; + survivalStatus.Text = GameData.survival.currentStatus; + survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical") + ? UIStyle.GetWarningColor() + : Colors.White; + + if (GameData.survival.isDead) + { + ShowGameOver(); + } + } + + private void DisplayWorldStats() + { + currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}"; + deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}"; + if (GameData.lowestLayer == GameData.ruinSize) + { + unlockLayer.Visible = false; + return; + } + unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients(); + unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1); + } + + private void DisplayLoseCondition() + { + if (!GameData.HasNoRobotRecovery()) return; + + ShowGameOver("No robots remain and no robot can be spawned from inventory."); + } + + public void ShowGameOver() + { + ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time."); + } + + public void ShowGameOver(string message) + { + if (gameOver.Visible) return; + gameOver.GetNode("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r"; + gameOver.Show(); + } + + public void HideGameOver() + { + gameOver.Hide(); + } +} diff --git a/Scripts/UI/Common/UIHandlerDisplay.cs.uid b/Scripts/UI/Common/UIHandlerDisplay.cs.uid new file mode 100644 index 0000000..35ce7e0 --- /dev/null +++ b/Scripts/UI/Common/UIHandlerDisplay.cs.uid @@ -0,0 +1 @@ +uid://b2q7e88lokg3l diff --git a/Scripts/UI/DSL/CodingWindow.cs b/Scripts/UI/DSL/CodingWindow.cs index 4e6f981..94db4d7 100644 --- a/Scripts/UI/DSL/CodingWindow.cs +++ b/Scripts/UI/DSL/CodingWindow.cs @@ -13,15 +13,6 @@ public partial class CodingWindow : PanelContainer [Export] LineEdit nameInput; public System.Collections.Generic.Dictionary DSLNodes; - private System.Collections.Generic.Dictionary availableNodes; - - private class ScriptConnection - { - public string FromNodeId; - public int FromPort; - public string ToNodeId; - public int ToPort; - } public override void _Ready() { @@ -51,6 +42,7 @@ public partial class CodingWindow : PanelContainer { availableScripts.Clear(); availableScripts.AddItem("Select script to load..."); + List scripts = FileHandler.LoadProgramNames(); scripts.Sort((a, b) => a.CompareTo(b)); foreach (string script in scripts) @@ -73,10 +65,9 @@ public partial class CodingWindow : PanelContainer public void GenerateCodingBlocks() { - Button nodeListButton; foreach (ProgramNode nodeTemplate in DSLNodes.Keys) { - nodeListButton = new Button + Button nodeListButton = new Button { Name = nodeTemplate.DisplayText, Text = nodeTemplate.DisplayText @@ -92,17 +83,14 @@ public partial class CodingWindow : PanelContainer private void AddEditorNode(ProgramNode node) { NodeDisplay editorDisplay = DSLNodes[node].Instantiate(); - editorDisplay.PositionOffset = (editorWindow.ScrollOffset + editorWindow.Size / 2) / editorWindow.Zoom - editorDisplay.Size / 2; + editorDisplay.PositionOffset = GetVisibleGraphCenter() - editorDisplay.Size / 2f; editorWindow.AddChild(editorDisplay); RegisterEditorNode(editorDisplay); } - private void MoveNodeToVisibleGraphCenter(NodeDisplay nodeDisplay) + private Vector2 GetVisibleGraphCenter() { - Vector2 visibleCenter = editorWindow.ScrollOffset - + editorWindow.Size / (2f * editorWindow.Zoom); - - nodeDisplay.PositionOffset = visibleCenter - nodeDisplay.Size / (2f * editorWindow.Zoom); + return (editorWindow.ScrollOffset + editorWindow.Size / 2f) / editorWindow.Zoom; } private void RegisterEditorNode(NodeDisplay editorDisplay) @@ -115,6 +103,13 @@ public partial class CodingWindow : PanelContainer } public void ClearWindow() + { + DisconnectAllNodes(); + RemoveEditorNodes(); + scriptName.Text = ""; + } + + private void DisconnectAllNodes() { foreach (Dictionary connection in editorWindow.GetConnectionList()) { @@ -125,109 +120,41 @@ public partial class CodingWindow : PanelContainer (int)connection["to_port"] ); } + } + private void RemoveEditorNodes() + { foreach (Node child in editorWindow.GetChildren()) { - if (child is GraphNode) - { - editorWindow.RemoveChild(child); - child.QueueFree(); - } + if (child is not GraphNode) continue; + + editorWindow.RemoveChild(child); + child.QueueFree(); } - scriptName.Text = ""; } public void CompileProgram() { if (robot == null) return; - NodeDisplay startNode = FindStartNode(); - if (startNode == null) + ScriptGraphCompiler compiler = new ScriptGraphCompiler(editorWindow); + string errorMessage; + List nodes = compiler.BuildProgram(out errorMessage); + if (errorMessage.Length > 0) { - robot.StopExecution("(FAILED) Script needs exactly one Start node"); + robot.StopExecution(errorMessage); return; } - BuildAvailableNodeLookup(); - List nodes = BuildScriptOrder( - startNode, - new List(), - new HashSet() - ); if (nodes.Count > 0) robot.SetupExecution(nodes); - robot.currentProgram = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text; + robot.currentProgram = GetCurrentScriptName(); } - private void BuildAvailableNodeLookup() + private string GetCurrentScriptName() { - availableNodes = new System.Collections.Generic.Dictionary(); + if (scriptName.Text.Length > 0) return scriptName.Text; - for (int i = 0; i < editorWindow.GetChildCount(); i++) - { - NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; - if (nodeDisplay == null) continue; - - nodeDisplay.ReadParameters(); - availableNodes.Add(nodeDisplay.Name, nodeDisplay.node); - } - } - - private List BuildScriptOrder( - NodeDisplay node, - List program, - HashSet visitedNodes - ) - { - if (node == null) return program; - if (visitedNodes.Contains(node.Name)) return program; - - visitedNodes.Add(node.Name); - program.Add(node.node); - if (editorWindow.GetConnectionListFromNode(node.Name).Count <= 0) return program; - List nextConnections = CheckNodeConnections(node); - if (nextConnections.Count <= 0) return program; - node.node.SetNextNode(nextConnections, availableNodes); - foreach (Dictionary connection in nextConnections) - { - NodeDisplay nextNode = editorWindow.GetNodeOrNull( - new NodePath(connection["to_node"].AsStringName()) - ); - program = BuildScriptOrder( - nextNode, - program, - visitedNodes - ); - } - return program; - } - - private List CheckNodeConnections(NodeDisplay node) - { - List result = new List(); - Array connections = editorWindow.GetConnectionListFromNode(node.Name); - for (int i = 0; i < connections.Count; i++) - { - if (connections[i]["from_node"].AsStringName() == node.Name) - { - result.Add(connections[i]); - } - } - return result; - } - - private NodeDisplay FindStartNode() - { - NodeDisplay startNode = null; - for (int i = 0; i < editorWindow.GetChildCount(); i++) - { - NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; - if (nodeDisplay == null) continue; - if (nodeDisplay.node is not StartNode) continue; - if (startNode != null) return null; - - startNode = nodeDisplay; - } - return startNode; + return $"Script{availableScripts.ItemCount}"; } public void SetRobot(Robot robot) @@ -241,131 +168,15 @@ public partial class CodingWindow : PanelContainer ClearWindow(); string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index)); - LoadStructuredProgram(scriptContent); + CreateSerializer().Load(scriptContent); scriptName.Text = availableScripts.GetItemText(index); availableScripts.Select(0); } - private void LoadStructuredProgram(string scriptContent) + private void AddLoadedNode(NodeDisplay nodeDisplay) { - Variant parsedScript = Json.ParseString(scriptContent); - if (parsedScript.VariantType != Variant.Type.Dictionary) return; - - Dictionary scriptData = parsedScript.AsGodotDictionary(); - if (!scriptData.ContainsKey("Nodes")) return; - - System.Collections.Generic.Dictionary loadedNodes = - new System.Collections.Generic.Dictionary(); - Array nodes = scriptData["Nodes"].AsGodotArray(); - for (int i = 0; i < nodes.Count; i++) - { - Dictionary nodeData = nodes[i].AsGodotDictionary(); - NodeDisplay nodeDisplay = LoadStructuredNode(nodeData); - if (nodeDisplay == null) continue; - - editorWindow.AddChild(nodeDisplay); - RegisterEditorNode(nodeDisplay); - RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes); - } - - LoadStructuredConnections(scriptData, loadedNodes); - } - - private void RegisterLoadedNode( - Dictionary nodeData, - NodeDisplay nodeDisplay, - System.Collections.Generic.Dictionary loadedNodes - ) - { - string nodeId = nodeDisplay.Name.ToString(); - if (nodeData.ContainsKey("Id")) - { - nodeId = nodeData["Id"].AsString(); - } - - if (!loadedNodes.ContainsKey(nodeId)) - { - loadedNodes.Add(nodeId, nodeDisplay); - } - } - - private NodeDisplay LoadStructuredNode(Dictionary nodeData) - { - if (!nodeData.ContainsKey("Type")) return null; - if (!nodeData.ContainsKey("Content")) return null; - - string type = nodeData["Type"].AsString(); - string content = nodeData["Content"].AsString(); - NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, DSLNodes); - if (nodeDisplay == null) return null; - - if (nodeData.ContainsKey("Id")) - { - nodeDisplay.Name = nodeData["Id"].AsString(); - } - - if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY")) - { - float positionX = (float)nodeData["PositionX"].AsDouble(); - float positionY = (float)nodeData["PositionY"].AsDouble(); - nodeDisplay.PositionOffset = new Vector2(positionX, positionY); - } - - return nodeDisplay; - } - - private void LoadStructuredConnections( - Dictionary scriptData, - System.Collections.Generic.Dictionary loadedNodes - ) - { - if (!scriptData.ContainsKey("Connections")) return; - - Array connectionData = scriptData["Connections"].AsGodotArray(); - for (int i = 0; i < connectionData.Count; i++) - { - Dictionary savedConnection = connectionData[i].AsGodotDictionary(); - if (!savedConnection.ContainsKey("From")) continue; - if (!savedConnection.ContainsKey("To")) continue; - if (!savedConnection.ContainsKey("FromPort")) continue; - if (!savedConnection.ContainsKey("ToPort")) continue; - - ScriptConnection connection = new ScriptConnection - { - FromNodeId = savedConnection["From"].AsString(), - FromPort = savedConnection["FromPort"].AsInt32(), - ToNodeId = savedConnection["To"].AsString(), - ToPort = savedConnection["ToPort"].AsInt32() - }; - if (!loadedNodes.ContainsKey(connection.FromNodeId)) continue; - if (!loadedNodes.ContainsKey(connection.ToNodeId)) continue; - - NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId]; - NodeDisplay toDisplay = loadedNodes[connection.ToNodeId]; - if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) continue; - - editorWindow.ConnectNode( - fromDisplay.Name, - connection.FromPort, - toDisplay.Name, - connection.ToPort - ); - } - } - - private bool ConnectionExists(StringName fromNode, int fromPort, StringName toNode, int toPort) - { - foreach (Dictionary connection in editorWindow.GetConnectionList()) - { - if (connection["from_node"].AsStringName() != fromNode) continue; - if ((int)connection["from_port"] != fromPort) continue; - if (connection["to_node"].AsStringName() != toNode) continue; - if ((int)connection["to_port"] != toPort) continue; - - return true; - } - - return false; + editorWindow.AddChild(nodeDisplay); + RegisterEditorNode(nodeDisplay); } public void LoadTemporaryProgram() @@ -373,65 +184,16 @@ public partial class CodingWindow : PanelContainer if (robot == null) return; if (robot.currentNode == null) return; - System.Collections.Generic.Dictionary loadedNodes = - new System.Collections.Generic.Dictionary(); - LoadTemporaryNode(robot.currentNode, loadedNodes); + RunningProgramGraphBuilder builder = new RunningProgramGraphBuilder( + DSLNodes, + AddLoadedNode, + ConnectNodes + ); + builder.Load(robot.currentNode); scriptName.Text = robot.currentProgram ?? ""; } - private NodeDisplay LoadTemporaryNode( - ProgramNode programNode, - System.Collections.Generic.Dictionary loadedNodes - ) - { - if (programNode == null) return null; - if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode]; - - NodeDisplay nodeDisplay = NodeDisplay.Load( - programNode.DisplayText, - programNode.Save(), - DSLNodes - ); - if (nodeDisplay == null) return null; - - editorWindow.AddChild(nodeDisplay); - RegisterEditorNode(nodeDisplay); - loadedNodes.Add(programNode, nodeDisplay); - - ConnectTemporaryNode(nodeDisplay, 0, programNode.nextNode, loadedNodes); - ConnectTemporaryNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes); - - return nodeDisplay; - } - - private void ConnectTemporaryNode( - NodeDisplay fromDisplay, - int fromPort, - ProgramNode targetNode, - System.Collections.Generic.Dictionary loadedNodes - ) - { - NodeDisplay toDisplay = LoadTemporaryNode(targetNode, loadedNodes); - if (toDisplay == null) return; - - ScriptConnection connection = new ScriptConnection - { - FromNodeId = fromDisplay.Name.ToString(), - FromPort = fromPort, - ToNodeId = toDisplay.Name.ToString(), - ToPort = 0 - }; - if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) return; - - editorWindow.ConnectNode( - fromDisplay.Name, - connection.FromPort, - toDisplay.Name, - connection.ToPort - ); - } - public void DeleteProgram() { string filename = scriptName.Text; @@ -450,69 +212,50 @@ public partial class CodingWindow : PanelContainer public void SaveProgram() { - Array savedNodes = BuildSavedNodes(); - if (savedNodes.Count <= 0) return; - - Dictionary scriptData = new Dictionary(); - scriptData["Nodes"] = savedNodes; - scriptData["Connections"] = BuildSavedConnections(); - - string result = Json.Stringify(scriptData); + string result = CreateSerializer().Save(); if (result.Length <= 0) return; - string filename = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text; - FileHandler.SaveProgram(filename, result); + + FileHandler.SaveProgram(GetCurrentScriptName(), result); SetupScriptOptions(); } - private Array BuildSavedNodes() - { - Array savedNodes = new Array(); - for (int i = 0; i < editorWindow.GetChildCount(); i++) - { - NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; - if (nodeDisplay == null) continue; - - nodeDisplay.ReadParameters(); - Dictionary savedNode = new Dictionary(); - savedNode["Id"] = nodeDisplay.Name.ToString(); - savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower(); - savedNode["Content"] = nodeDisplay.node.Save(); - savedNode["PositionX"] = nodeDisplay.PositionOffset.X; - savedNode["PositionY"] = nodeDisplay.PositionOffset.Y; - savedNodes.Add(savedNode); - } - - return savedNodes; - } - - private Array BuildSavedConnections() - { - Array savedConnections = new Array(); - foreach (Dictionary connection in editorWindow.GetConnectionList()) - { - Dictionary savedConnection = new Dictionary(); - savedConnection["From"] = connection["from_node"].AsStringName().ToString(); - savedConnection["FromPort"] = (int)connection["from_port"]; - savedConnection["To"] = connection["to_node"].AsStringName().ToString(); - savedConnection["ToPort"] = (int)connection["to_port"]; - savedConnections.Add(savedConnection); - } - - return savedConnections; - } - public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort) { if (to == from) return; - foreach (Dictionary connection in editorWindow.GetConnectionList()) - { - if (connection["from_node"].AsStringName() == from && (int)connection["from_port"] == fromPort) return; - } + ConnectNodes(from, fromPort, to, toPort); + } + + private void ConnectNodes(StringName from, int fromPort, StringName to, int toPort) + { + if (HasOutputConnection(from, fromPort)) return; editorWindow.ConnectNode(from, fromPort, to, toPort); } + private bool HasOutputConnection(StringName from, int fromPort) + { + foreach (Dictionary connection in editorWindow.GetConnectionList()) + { + if (connection["from_node"].AsStringName() != from) continue; + if ((int)connection["from_port"] != fromPort) continue; + + return true; + } + + return false; + } + + private ScriptGraphSerializer CreateSerializer() + { + return new ScriptGraphSerializer( + editorWindow, + DSLNodes, + AddLoadedNode, + ConnectNodes + ); + } + public void OnNodeDisconnect(StringName from, int fromPort, StringName to, int toPort) { editorWindow.DisconnectNode(from, fromPort, to, toPort); diff --git a/Scripts/UI/DSL/RunningProgramGraphBuilder.cs b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs new file mode 100644 index 0000000..8551a6a --- /dev/null +++ b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs @@ -0,0 +1,65 @@ +using Godot; +using System; +using System.Collections.Generic; + +public class RunningProgramGraphBuilder +{ + private readonly Dictionary dslNodes; + private readonly Action addNode; + private readonly Action connectNodes; + + public RunningProgramGraphBuilder( + Dictionary dslNodes, + Action addNode, + Action connectNodes + ) + { + this.dslNodes = dslNodes; + this.addNode = addNode; + this.connectNodes = connectNodes; + } + + public void Load(ProgramNode startNode) + { + Dictionary loadedNodes = + new Dictionary(); + LoadNode(startNode, loadedNodes); + } + + private NodeDisplay LoadNode( + ProgramNode programNode, + Dictionary loadedNodes + ) + { + if (programNode == null) return null; + if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode]; + + NodeDisplay nodeDisplay = NodeDisplay.Load( + programNode.DisplayText, + programNode.Save(), + dslNodes + ); + if (nodeDisplay == null) return null; + + addNode(nodeDisplay); + loadedNodes.Add(programNode, nodeDisplay); + + ConnectNode(nodeDisplay, 0, programNode.nextNode, loadedNodes); + ConnectNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes); + + return nodeDisplay; + } + + private void ConnectNode( + NodeDisplay fromDisplay, + int fromPort, + ProgramNode targetNode, + Dictionary loadedNodes + ) + { + NodeDisplay toDisplay = LoadNode(targetNode, loadedNodes); + if (toDisplay == null) return; + + connectNodes(fromDisplay.Name, fromPort, toDisplay.Name, 0); + } +} diff --git a/Scripts/UI/DSL/RunningProgramGraphBuilder.cs.uid b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs.uid new file mode 100644 index 0000000..f3ce0b9 --- /dev/null +++ b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs.uid @@ -0,0 +1 @@ +uid://deaptod52fe2s diff --git a/Scripts/UI/DSL/ScriptConnection.cs b/Scripts/UI/DSL/ScriptConnection.cs new file mode 100644 index 0000000..abd93a5 --- /dev/null +++ b/Scripts/UI/DSL/ScriptConnection.cs @@ -0,0 +1,7 @@ +public class ScriptConnection +{ + public string FromNodeId; + public int FromPort; + public string ToNodeId; + public int ToPort; +} diff --git a/Scripts/UI/DSL/ScriptConnection.cs.uid b/Scripts/UI/DSL/ScriptConnection.cs.uid new file mode 100644 index 0000000..b505b35 --- /dev/null +++ b/Scripts/UI/DSL/ScriptConnection.cs.uid @@ -0,0 +1 @@ +uid://cvhobhufyp2ni diff --git a/Scripts/UI/DSL/ScriptGraphCompiler.cs b/Scripts/UI/DSL/ScriptGraphCompiler.cs new file mode 100644 index 0000000..d8873cb --- /dev/null +++ b/Scripts/UI/DSL/ScriptGraphCompiler.cs @@ -0,0 +1,108 @@ +using Godot; +using Godot.Collections; +using System.Collections.Generic; + +public class ScriptGraphCompiler +{ + private readonly GraphEdit editorWindow; + private System.Collections.Generic.Dictionary availableNodes; + + public ScriptGraphCompiler(GraphEdit editorWindow) + { + this.editorWindow = editorWindow; + } + + public List BuildProgram(out string errorMessage) + { + errorMessage = ""; + NodeDisplay startNode = FindStartNode(); + if (startNode == null) + { + errorMessage = "(FAILED) Script needs exactly one Start node"; + return new List(); + } + + BuildAvailableNodeLookup(); + return BuildScriptOrder( + startNode, + new List(), + new HashSet() + ); + } + + private void BuildAvailableNodeLookup() + { + availableNodes = new System.Collections.Generic.Dictionary(); + + for (int i = 0; i < editorWindow.GetChildCount(); i++) + { + NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; + if (nodeDisplay == null) continue; + + nodeDisplay.ReadParameters(); + availableNodes.Add(nodeDisplay.Name, nodeDisplay.node); + } + } + + private List BuildScriptOrder( + NodeDisplay node, + List program, + HashSet visitedNodes + ) + { + if (node == null) return program; + if (visitedNodes.Contains(node.Name)) return program; + + visitedNodes.Add(node.Name); + program.Add(node.node); + + List nextConnections = GetOutgoingConnections(node); + if (nextConnections.Count <= 0) return program; + + node.node.SetNextNode(nextConnections, availableNodes); + foreach (Dictionary connection in nextConnections) + { + NodeDisplay nextNode = editorWindow.GetNodeOrNull( + new NodePath(connection["to_node"].AsStringName()) + ); + program = BuildScriptOrder( + nextNode, + program, + visitedNodes + ); + } + + return program; + } + + private List GetOutgoingConnections(NodeDisplay node) + { + List result = new List(); + Array connections = editorWindow.GetConnectionListFromNode(node.Name); + for (int i = 0; i < connections.Count; i++) + { + if (connections[i]["from_node"].AsStringName() == node.Name) + { + result.Add(connections[i]); + } + } + + return result; + } + + private NodeDisplay FindStartNode() + { + NodeDisplay startNode = null; + for (int i = 0; i < editorWindow.GetChildCount(); i++) + { + NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; + if (nodeDisplay == null) continue; + if (nodeDisplay.node is not StartNode) continue; + if (startNode != null) return null; + + startNode = nodeDisplay; + } + + return startNode; + } +} diff --git a/Scripts/UI/DSL/ScriptGraphCompiler.cs.uid b/Scripts/UI/DSL/ScriptGraphCompiler.cs.uid new file mode 100644 index 0000000..7ed291e --- /dev/null +++ b/Scripts/UI/DSL/ScriptGraphCompiler.cs.uid @@ -0,0 +1 @@ +uid://d122nx50nj3wc diff --git a/Scripts/UI/DSL/ScriptGraphSerializer.cs b/Scripts/UI/DSL/ScriptGraphSerializer.cs new file mode 100644 index 0000000..6af51e3 --- /dev/null +++ b/Scripts/UI/DSL/ScriptGraphSerializer.cs @@ -0,0 +1,189 @@ +using Godot; +using Godot.Collections; +using System; +public class ScriptGraphSerializer +{ + private readonly GraphEdit editorWindow; + private readonly System.Collections.Generic.Dictionary dslNodes; + private readonly Action addNode; + private readonly Action connectNodes; + + public ScriptGraphSerializer( + GraphEdit editorWindow, + System.Collections.Generic.Dictionary dslNodes, + Action addNode, + Action connectNodes + ) + { + this.editorWindow = editorWindow; + this.dslNodes = dslNodes; + this.addNode = addNode; + this.connectNodes = connectNodes; + } + + public void Load(string scriptContent) + { + Variant parsedScript = Json.ParseString(scriptContent); + if (parsedScript.VariantType != Variant.Type.Dictionary) return; + + Dictionary scriptData = parsedScript.AsGodotDictionary(); + if (!scriptData.ContainsKey("Nodes")) return; + + System.Collections.Generic.Dictionary loadedNodes = + new System.Collections.Generic.Dictionary(); + Godot.Collections.Array nodes = scriptData["Nodes"].AsGodotArray(); + for (int i = 0; i < nodes.Count; i++) + { + Dictionary nodeData = nodes[i].AsGodotDictionary(); + NodeDisplay nodeDisplay = LoadNode(nodeData); + if (nodeDisplay == null) continue; + + addNode(nodeDisplay); + RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes); + } + + LoadConnections(scriptData, loadedNodes); + } + + public string Save() + { + Array savedNodes = BuildSavedNodes(); + if (savedNodes.Count <= 0) return ""; + + Dictionary scriptData = new Dictionary(); + scriptData["Nodes"] = savedNodes; + scriptData["Connections"] = BuildSavedConnections(); + + return Json.Stringify(scriptData); + } + + private NodeDisplay LoadNode(Dictionary nodeData) + { + if (!nodeData.ContainsKey("Type")) return null; + if (!nodeData.ContainsKey("Content")) return null; + + string type = nodeData["Type"].AsString(); + string content = nodeData["Content"].AsString(); + NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, dslNodes); + if (nodeDisplay == null) return null; + + if (nodeData.ContainsKey("Id")) + { + nodeDisplay.Name = nodeData["Id"].AsString(); + } + + if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY")) + { + float positionX = (float)nodeData["PositionX"].AsDouble(); + float positionY = (float)nodeData["PositionY"].AsDouble(); + nodeDisplay.PositionOffset = new Vector2(positionX, positionY); + } + + return nodeDisplay; + } + + private void RegisterLoadedNode( + Dictionary nodeData, + NodeDisplay nodeDisplay, + System.Collections.Generic.Dictionary loadedNodes + ) + { + string nodeId = nodeDisplay.Name.ToString(); + if (nodeData.ContainsKey("Id")) + { + nodeId = nodeData["Id"].AsString(); + } + + if (!loadedNodes.ContainsKey(nodeId)) + { + loadedNodes.Add(nodeId, nodeDisplay); + } + } + + private void LoadConnections( + Dictionary scriptData, + System.Collections.Generic.Dictionary loadedNodes + ) + { + if (!scriptData.ContainsKey("Connections")) return; + + Godot.Collections.Array connectionData = scriptData["Connections"].AsGodotArray(); + for (int i = 0; i < connectionData.Count; i++) + { + Dictionary savedConnection = connectionData[i].AsGodotDictionary(); + if (!IsConnectionDataValid(savedConnection)) continue; + + ScriptConnection connection = CreateConnection(savedConnection); + ConnectLoadedNodes(connection, loadedNodes); + } + } + + private bool IsConnectionDataValid(Dictionary savedConnection) + { + return savedConnection.ContainsKey("From") + && savedConnection.ContainsKey("To") + && savedConnection.ContainsKey("FromPort") + && savedConnection.ContainsKey("ToPort"); + } + + private ScriptConnection CreateConnection(Dictionary savedConnection) + { + return new ScriptConnection + { + FromNodeId = savedConnection["From"].AsString(), + FromPort = savedConnection["FromPort"].AsInt32(), + ToNodeId = savedConnection["To"].AsString(), + ToPort = savedConnection["ToPort"].AsInt32() + }; + } + + private void ConnectLoadedNodes( + ScriptConnection connection, + System.Collections.Generic.Dictionary loadedNodes + ) + { + if (!loadedNodes.ContainsKey(connection.FromNodeId)) return; + if (!loadedNodes.ContainsKey(connection.ToNodeId)) return; + + NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId]; + NodeDisplay toDisplay = loadedNodes[connection.ToNodeId]; + connectNodes(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort); + } + + private Array BuildSavedNodes() + { + Array savedNodes = new Array(); + for (int i = 0; i < editorWindow.GetChildCount(); i++) + { + NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay; + if (nodeDisplay == null) continue; + + nodeDisplay.ReadParameters(); + Dictionary savedNode = new Dictionary(); + savedNode["Id"] = nodeDisplay.Name.ToString(); + savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower(); + savedNode["Content"] = nodeDisplay.node.Save(); + savedNode["PositionX"] = nodeDisplay.PositionOffset.X; + savedNode["PositionY"] = nodeDisplay.PositionOffset.Y; + savedNodes.Add(savedNode); + } + + return savedNodes; + } + + private Array BuildSavedConnections() + { + Array savedConnections = new Array(); + foreach (Dictionary connection in editorWindow.GetConnectionList()) + { + Dictionary savedConnection = new Dictionary(); + savedConnection["From"] = connection["from_node"].AsStringName().ToString(); + savedConnection["FromPort"] = (int)connection["from_port"]; + savedConnection["To"] = connection["to_node"].AsStringName().ToString(); + savedConnection["ToPort"] = (int)connection["to_port"]; + savedConnections.Add(savedConnection); + } + + return savedConnections; + } +} diff --git a/Scripts/UI/DSL/ScriptGraphSerializer.cs.uid b/Scripts/UI/DSL/ScriptGraphSerializer.cs.uid new file mode 100644 index 0000000..ce873a0 --- /dev/null +++ b/Scripts/UI/DSL/ScriptGraphSerializer.cs.uid @@ -0,0 +1 @@ +uid://dsrkrw6524c diff --git a/Scripts/UI/Inventory/InventoryDisplay.cs b/Scripts/UI/Inventory/InventoryDisplay.cs index b87db54..7e448e9 100644 --- a/Scripts/UI/Inventory/InventoryDisplay.cs +++ b/Scripts/UI/Inventory/InventoryDisplay.cs @@ -3,7 +3,8 @@ using Godot; public partial class InventoryDisplay : PanelContainer { - private PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay(); + private readonly PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay(); + [Export] VBoxContainer itemList; [Export] RichTextLabel inventorySpace; @@ -34,24 +35,32 @@ public partial class InventoryDisplay : PanelContainer } public void ReloadItems() + { + ClearItems(); + + foreach (Item item in GameData.inventory.items) + { + itemList.AddChild(CreateItemDisplay(item)); + } + } + + private void ClearItems() { foreach (Node node in itemList.GetChildren()) { itemList.RemoveChild(node); node.QueueFree(); } + } - ItemDisplay display; - - foreach (Item item in GameData.inventory.items) - { - display = itemDisplayPrefab.Instantiate(); - display.item = item; - display.text.Text = item.data.GetReadableName(); - display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}"; - display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture); - itemList.AddChild(display); - } + private ItemDisplay CreateItemDisplay(Item item) + { + ItemDisplay display = itemDisplayPrefab.Instantiate(); + display.item = item; + display.text.Text = item.data.GetReadableName(); + display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}"; + display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture); + return display; } public void OnInventoryUpdate(object sender, EventArgs args) diff --git a/Scripts/UI/Menus/OptionsMenu.cs b/Scripts/UI/Menus/OptionsMenu.cs index dc20d72..8d36b86 100644 --- a/Scripts/UI/Menus/OptionsMenu.cs +++ b/Scripts/UI/Menus/OptionsMenu.cs @@ -2,82 +2,86 @@ using Godot; public partial class OptionsMenu : PanelContainer { - private readonly Vector2 panelSize = new Vector2(420, 260); + private readonly Vector2 panelSize = new Vector2(420, 260); - [Export] private OptionButton screenMode; - [Export] private HSlider soundVolume; - [Export] private ColorPickerButton lightColor; + [Export] private OptionButton screenMode; + [Export] private HSlider soundVolume; + [Export] private ColorPickerButton lightColor; - public override void _Ready() - { - CenterPanel(); - SetupScreenModes(); - screenMode.Select(GameData.screenMode); - soundVolume.Value = GameData.soundVolume * 100f; - lightColor.Color = GameData.lightColor; - ApplyScreenMode(GameData.screenMode); - SoundManager.SetMasterVolume(GameData.soundVolume); - } + public override void _Ready() + { + CenterPanel(); + SetupScreenModes(); + screenMode.Select(GameData.screenMode); + soundVolume.Value = GameData.soundVolume * 100f; + lightColor.Color = GameData.lightColor; + ApplyScreenMode(GameData.screenMode); + SoundManager.SetMasterVolume(GameData.soundVolume); + } - private void SetupScreenModes() - { - screenMode.Clear(); - screenMode.AddItem("Fullscreen"); - screenMode.AddItem("Windowed"); - screenMode.AddItem("Windowed Fullscreen"); - screenMode.Select(2); - } + private void SetupScreenModes() + { + screenMode.Clear(); + screenMode.AddItem("Fullscreen"); + screenMode.AddItem("Windowed"); + screenMode.AddItem("Windowed Fullscreen"); + screenMode.Select(2); + } - public void OnScreenModeSelected(int index) - { - GameData.screenMode = index; - ApplyScreenMode(index); - } + public void OnScreenModeSelected(int index) + { + GameData.screenMode = index; + ApplyScreenMode(index); + } - private void ApplyScreenMode(int index) - { - switch (index) - { - case 0: - DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen); - DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); - break; - case 1: - DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed); - DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); - break; - case 2: - DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen); - DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true); - break; - } - } + private void ApplyScreenMode(int index) + { + switch (index) + { + case 0: + DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen); + DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); + break; + case 1: + DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed); + DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); + break; + case 2: + DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen); + DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true); + break; + default: + DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed); + DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); + break; + } + } - public void OnSoundVolumeChanged(double value) - { - GameData.soundVolume = (float)value / 100f; - SoundManager.SetMasterVolume(GameData.soundVolume); - } + public void OnSoundVolumeChanged(double value) + { + GameData.soundVolume = (float)value / 100f; + SoundManager.SetMasterVolume(GameData.soundVolume); + } - public void OnLightColorChanged(Color color) - { - GameData.lightColor = color; - LightHandler.RedrawLights(color); - } + public void OnLightColorChanged(Color color) + { + GameData.lightColor = color; + LightHandler.RedrawLights(color); + } - public void CloseOptions() - { - Hide(); - GameData.isPaused = false; - } + public void CloseOptions() + { + Hide(); + GameData.isPaused = false; + } - private void CenterPanel() - { - CustomMinimumSize = panelSize; - SetAnchorsPreset(LayoutPreset.Center); - OffsetLeft = -panelSize.X / 2f; - OffsetTop = -panelSize.Y / 2f; - OffsetRight = panelSize.X / 2f; - OffsetBottom = panelSize.Y / 2f; - } + private void CenterPanel() + { + CustomMinimumSize = panelSize; + SetAnchorsPreset(LayoutPreset.Center); + OffsetLeft = -panelSize.X / 2f; + OffsetTop = -panelSize.Y / 2f; + OffsetRight = panelSize.X / 2f; + OffsetBottom = panelSize.Y / 2f; + } } diff --git a/Scripts/UI/Research/ResearchList.cs b/Scripts/UI/Research/ResearchList.cs index adf286d..01d5d0f 100644 --- a/Scripts/UI/Research/ResearchList.cs +++ b/Scripts/UI/Research/ResearchList.cs @@ -10,7 +10,6 @@ public partial class ResearchList : PanelContainer private bool hasArrangedNodes = false; private List currentResearch = new List(); - private List toDelete = new List(); public override void _Ready() { @@ -18,30 +17,37 @@ public partial class ResearchList : PanelContainer if (Visible) SetupGraph(); } - public override void _Process(double delta) + public override void _Process(double delta) { - if(currentResearch.Count > 0) toDelete = new List(); + if (currentResearch.Count <= 0) return; + + List finishedResearch = new List(); foreach (Research research in currentResearch) { ResearchResult result = research.Execute(delta); - if (result == ResearchResult.FINISHED) - { - toDelete.Add(research); - RecalculateResearchStates(); - SetupGraph(); - } - else if (result == ResearchResult.FAILED) - { - research.state = ResearchState.AVAILABLE; - toDelete.Add(research); - RecalculateResearchStates(); - SetupGraph(); - } + if (!IsResearchFinished(research, result)) continue; + + finishedResearch.Add(research); } - foreach (Research delete in toDelete) + + if (finishedResearch.Count <= 0) return; + + foreach (Research research in finishedResearch) { - currentResearch.Remove(delete); + currentResearch.Remove(research); } + + RecalculateResearchStates(); + SetupGraph(); + } + + private bool IsResearchFinished(Research research, ResearchResult result) + { + if (result == ResearchResult.FINISHED) return true; + if (result != ResearchResult.FAILED) return false; + + research.state = ResearchState.AVAILABLE; + return true; } public void SetupGraph() @@ -83,11 +89,10 @@ public partial class ResearchList : PanelContainer foreach (Node child in researchGraph.GetChildren()) { - if (child is GraphNode) - { - researchGraph.RemoveChild(child); - child.QueueFree(); - } + if (child is not GraphNode) continue; + + researchGraph.RemoveChild(child); + child.QueueFree(); } } @@ -127,9 +132,10 @@ public partial class ResearchList : PanelContainer private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state) { + Research research = GameData.availableResearch[id]; Texture2D texture = GD.Load(texturePath); Color stateColor = GetColorByState(state); - string tooltipText = GetResearchTooltip(GameData.availableResearch[id]); + string tooltipText = GetResearchTooltip(research); TextureRect icon = new TextureRect { @@ -141,8 +147,8 @@ public partial class ResearchList : PanelContainer Button button = new Button { - Text = GetResearchButtonText(GameData.availableResearch[id], state), - Disabled = state != ResearchState.AVAILABLE || !GameData.availableResearch[id].CanStart(), + Text = GetResearchButtonText(research, state), + Disabled = state != ResearchState.AVAILABLE || !research.CanStart(), TooltipText = tooltipText }; diff --git a/Scripts/UI/Robots/RobotDisplay.cs b/Scripts/UI/Robots/RobotDisplay.cs index 1154f92..af36980 100644 --- a/Scripts/UI/Robots/RobotDisplay.cs +++ b/Scripts/UI/Robots/RobotDisplay.cs @@ -12,14 +12,19 @@ public partial class RobotDisplay : PanelContainer public override void _Process(double delta) { - string programName = robot.currentProgram ?? ""; - string status = $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%"; + string status = GetStatusText(); if (status != currentScript.Text) { currentScript.Text = status; } } + private string GetStatusText() + { + string programName = robot.currentProgram ?? ""; + return $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%"; + } + public void OnJumpToClicked() { EmitSignal(SignalName.OnRobotJumpTo, robot); diff --git a/Scripts/UI/Robots/RobotList.cs b/Scripts/UI/Robots/RobotList.cs index 4e2ccfb..be7a9f1 100644 --- a/Scripts/UI/Robots/RobotList.cs +++ b/Scripts/UI/Robots/RobotList.cs @@ -30,37 +30,50 @@ public partial class RobotList : PanelContainer } public void ReloadRobots() + { + ClearRobotList(); + + foreach (Robot robotObject in GameData.robots) + { + robotList.AddChild(CreateRobotDisplay(robotObject)); + } + } + + private void ClearRobotList() { foreach (Node node in robotList.GetChildren()) { robotList.RemoveChild(node); node.QueueFree(); } - RobotDisplay display; + } - foreach (Robot robotObject in GameData.robots) - { - display = robotDisplayPrefab.Instantiate(); - display.robot = robotObject; - display.listItem.Text = robotObject.Name; - display.OnRobotJumpTo += (robot) => - { - EmitSignal(SignalName.OnRobotJumpTo, robot); - Visible = false; - }; - display.OnRobotFollow += (robot) => - { - EmitSignal(SignalName.OnRobotFollow, robot); - Visible = false; - }; - robotList.AddChild(display); - } + private RobotDisplay CreateRobotDisplay(Robot robotObject) + { + RobotDisplay display = robotDisplayPrefab.Instantiate(); + display.robot = robotObject; + display.listItem.Text = robotObject.Name; + display.OnRobotJumpTo += HandleRobotJumpTo; + display.OnRobotFollow += HandleRobotFollow; + return display; + } + + private void HandleRobotJumpTo(Robot robot) + { + EmitSignal(SignalName.OnRobotJumpTo, robot); + Visible = false; + } + + private void HandleRobotFollow(Robot robot) + { + EmitSignal(SignalName.OnRobotFollow, robot); + Visible = false; } public void ReloadSelectableRobots() { selectableRobots.Clear(); - if(GameData.robots.Count >= GameData.maxRobotCount) + if (GameData.robots.Count >= GameData.maxRobotCount) { selectableRobots.AddItem("You can't have more robots currently!"); selectableRobots.Disabled = true; @@ -81,7 +94,8 @@ public partial class RobotList : PanelContainer public void SpawnRobot() { - if(spawnId.Length <= 0) return; + if (spawnId.Length <= 0) return; + GameData.inventory.RemoveItem(spawnId, 1); Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate(); robot.Name = $"Robot #{GameData.robots.Count}"; @@ -97,8 +111,8 @@ public partial class RobotList : PanelContainer public void OnRobotSelect(int index) { - //Selected option is the "please select..." option - if(index == 0) return; + if (index == 0) return; + spawnId = ItemData.GetIndex(selectableRobots.GetItemText(index)); } } diff --git a/Scripts/UI/Tutorial/TutorialBubble.cs b/Scripts/UI/Tutorial/TutorialBubble.cs index f2fc40b..4783535 100644 --- a/Scripts/UI/Tutorial/TutorialBubble.cs +++ b/Scripts/UI/Tutorial/TutorialBubble.cs @@ -35,24 +35,24 @@ public partial class TutorialBubble : PanelContainer { "Welcome to the ruin. I am B.O.B.", "B.O.B. means Building Operation Buddy. I will keep the lamps on while you teach the robots what to do.", - "The last remaining unit in this ruin saved you from your fall and brought you here. If you want to leave, you have to explore the ruin", + "The last remaining unit in this ruin saved you from your fall and brought you here. If you want to leave, you have to explore the ruin.", "You do not walk through the ruin yourself. Your robots explore, harvest, craft and carry progress for you.", "The top bar shows survival pressure: energy, water and food. If those run out, the expedition ends.", "The necessary resources will be auto-consumed during your time here.", - "For Energy: Steam, Battery v1 and Battery v2; For Thirst: Water; For Food: Mushrooms", - "So try to keep a certain stock of those items to avoid dying in this ruin", + "For energy: steam, battery v1 and battery v2. For thirst: water. For food: mushrooms.", + "Try to keep a stock of those items to avoid dying in this ruin.", "Open the robot panel (Default: [R]) to inspect your robots. A robot can overheat, lose maintenance and slow down if ignored.", - "A robot that overheated has to cool down for a certain amount of time and cannot execute scripts", + "An overheated robot has to cool down for a while and cannot execute scripts.", "Use the script editor (Clicking 'Jump to' from the robot panel) to give robots commands. Start simple: move, explore, harvest, then craft.", "Research unlocks better tools, buildings, robot upgrades and deeper progression. The graph is your technology map (Default: [T]).", - "The necessary ingredients can be found when hovering over the respective technologies in your technology map", + "The required ingredients can be found by hovering over technologies in your technology map.", "The inventory (Default: [I]) stores everything your robots collect. Gates and research both consume items from it.", "Each gate blocks the next layer. When you have the required items, use Open Gate in the top bar.", - "The necessary ingredients can be found when hovering over the 'Open Gate' button", + "The required ingredients can be found by hovering over the Open Gate button.", "The map (Default: [M]) shows what your robots have discovered. Exploration matters because resources are hidden in the ruin.", - "Depper layers contain more advanced resources and unlocking the gate at the lowest point allows you to leave the ruin", - "Be careful: The resources are not endless from the beginning. Converting a robot to a drill-unit (Using the node 'Sacrifice') makes them endless", - "Two things to keep in mind: 1)Endless resources have a lower rate of extraction 2)Sacrificing your last robot without backups in your inventory makes you unable to do anything", + "Deeper layers contain more advanced resources. Unlocking the gate at the lowest point allows you to leave the ruin.", + "Be careful: resources are not endless at the beginning. Converting a robot to a drill unit with Sacrifice makes them endless.", + "Two things to keep in mind: endless resources extract slower, and sacrificing your last robot without a backup in your inventory leaves you stranded.", "That is enough briefing. Build a loop, keep the robots alive, and open the lower gates. B.O.B. believes in organized chaos." }; } diff --git a/Scripts/World/GateRequirementGenerator.cs b/Scripts/World/GateRequirementGenerator.cs new file mode 100644 index 0000000..a817204 --- /dev/null +++ b/Scripts/World/GateRequirementGenerator.cs @@ -0,0 +1,121 @@ +using Godot; +using System.Collections.Generic; +using System.Linq; + +public static class GateRequirementGenerator +{ + public static void ApplyGateRequirements(Layer[] layers) + { + List availableResources = new List(); + + foreach (Layer layer in layers) + { + GateRequirementOptions options = BuildRequirementOptions(layer, availableResources); + ApplyLayerRequirements(layer, options); + } + } + + private static GateRequirementOptions BuildRequirementOptions(Layer layer, List availableResources) + { + GateRequirementOptions options = new GateRequirementOptions(); + + foreach (string resource in layer.currentResources) + { + if (availableResources.Contains(resource)) continue; + + availableResources.Add(resource); + } + + bool addedNewItem; + do + { + addedNewItem = false; + + foreach (ItemData item in GameData.availableItems.Values) + { + if (options.PossibleIngredients.Any(existing => existing.Id == item.Id)) continue; + if (!CanCraftItem(item, availableResources)) continue; + + options.PossibleIngredients.Add(item); + availableResources.Add(item.Id); + options.LowestCraftTime = Mathf.Min(options.LowestCraftTime, item.CraftTime); + options.HighestCraftTime = Mathf.Max(options.HighestCraftTime, item.CraftTime); + + addedNewItem = true; + } + } while (addedNewItem); + + return options; + } + + private static bool CanCraftItem(ItemData item, List availableResources) + { + return item.Inputs.All(input => availableResources.Contains(input.Item)); + } + + private static void ApplyLayerRequirements(Layer layer, GateRequirementOptions options) + { + double goalCraftTime = GetGoalCraftTime(layer, options); + int ingredientAmount = Mathf.Clamp(1 + layer.level / 3, 1, 4); + float craftTimeModifier = 0f; + + for (int i = 0; i < ingredientAmount; i++) + { + List validIngredients = GetValidIngredients(layer, options, goalCraftTime, craftTimeModifier); + if (validIngredients.Count == 0) + { + i--; + craftTimeModifier += 0.05f; + continue; + } + + AddGateIngredient(layer, validIngredients); + craftTimeModifier = 0f; + } + } + + private static double GetGoalCraftTime(Layer layer, GateRequirementOptions options) + { + return Mathf.Lerp( + options.LowestCraftTime, + options.HighestCraftTime, + Mathf.Clamp(layer.level / (float)GameData.ruinSize, 0, 1) + ); + } + + private static List GetValidIngredients( + Layer layer, + GateRequirementOptions options, + double goalCraftTime, + float craftTimeModifier + ) + { + double craftTimeLower = goalCraftTime - goalCraftTime * craftTimeModifier; + double craftTimeUpper = goalCraftTime + goalCraftTime * craftTimeModifier; + + return options.PossibleIngredients + .Where(item => + item.CraftTime >= craftTimeLower && + item.CraftTime <= craftTimeUpper && + !layer.gateIngredients.Any(ingredient => ingredient.Item == item.Id)) + .ToList(); + } + + private static void AddGateIngredient(Layer layer, List validIngredients) + { + ItemData item = validIngredients[GameData.rand.Next(validIngredients.Count)]; + + layer.gateIngredients.Add(new Ingredient + { + Item = item.Id, + Amount = GameData.rand.Next(3 + layer.level * 2, 9 + layer.level * 4) + }); + } + + private class GateRequirementOptions + { + public List PossibleIngredients = new List(); + public double HighestCraftTime = 0; + public double LowestCraftTime = double.MaxValue; + } +} diff --git a/Scripts/World/GateRequirementGenerator.cs.uid b/Scripts/World/GateRequirementGenerator.cs.uid new file mode 100644 index 0000000..ce10300 --- /dev/null +++ b/Scripts/World/GateRequirementGenerator.cs.uid @@ -0,0 +1 @@ +uid://bct2we4yxah6v diff --git a/Scripts/World/Layer.cs b/Scripts/World/Layer.cs index 768d713..6e27ff2 100644 --- a/Scripts/World/Layer.cs +++ b/Scripts/World/Layer.cs @@ -16,7 +16,7 @@ public partial class Layer : Node3D public Vector2I gateCoordinate; public List currentResources; public bool isGateOpen = false; - public List gateIngredients = new(); + public List gateIngredients = new List(); public override void _Ready() { @@ -125,14 +125,12 @@ public partial class Layer : Node3D for (int z = 0; z < layerSize; z++) { if (x == 0 && z == 0 && level == 0) continue; - if (!IsBorder(x, z)) - continue; + if (!IsBorder(x, z)) continue; Tile tile = tiles[x, z]; List possibilities = GetBorderPossibilities(x, z); - if (possibilities.Count == 0) - continue; + if (possibilities.Count == 0) continue; tile.Collapse(possibilities[GameData.rand.Next(possibilities.Count)]); Propagate(new Vector2I(x, z)); @@ -147,7 +145,6 @@ public partial class Layer : Node3D private void GenerateNecessaryTiles() { - //Generate spawn only in the first layer if (level == 0) { tiles[0, 0].Collapse("spawn"); @@ -197,9 +194,7 @@ public partial class Layer : Node3D safetyCounter++; if (safetyCounter == layerSize * layerSize) return false; } - if (updateFailed) return false; - if (!WFC.IsMapConnected(tiles, 1f)) return false; - return true; + return !updateFailed && WFC.IsMapConnected(tiles, 1f); } private void Propagate(Vector2I startPos) diff --git a/Scripts/World/LightHandler.cs b/Scripts/World/LightHandler.cs index 543e278..b75983a 100644 --- a/Scripts/World/LightHandler.cs +++ b/Scripts/World/LightHandler.cs @@ -1,23 +1,22 @@ using Godot; using System.Collections.Generic; -public class LightHandler +public static class LightHandler { - public static List lights = new List(); + public static List lights = new List(); - public static void RedrawLights(Color color) - { - List availableLights = new List(); + public static void RedrawLights(Color color) + { + List availableLights = new List(); - foreach (OmniLight3D light in lights) - { - if (GodotObject.IsInstanceValid(light)) - { - light.LightColor = color; - availableLights.Add(light); - } - } + foreach (OmniLight3D light in lights) + { + if (!GodotObject.IsInstanceValid(light)) continue; - lights = availableLights; - } + light.LightColor = color; + availableLights.Add(light); + } + + lights = availableLights; + } } diff --git a/Scripts/World/Pathfinding.cs b/Scripts/World/Pathfinding.cs index a6b09bb..ae2b963 100644 --- a/Scripts/World/Pathfinding.cs +++ b/Scripts/World/Pathfinding.cs @@ -1,131 +1,136 @@ using System.Collections.Generic; using Godot; -public class Pathfinding +public static class Pathfinding { - private static AStar3D aStar = new AStar3D(); - private static Dictionary coordToId = new Dictionary(); - private static Dictionary idToCoord = new Dictionary(); - private static long nextId = 1; - private static Dictionary verticalConnections = new Dictionary(); + private static AStar3D aStar = new AStar3D(); + private static Dictionary coordToId = new Dictionary(); + private static Dictionary idToCoord = new Dictionary(); + private static long nextId = 1; + private static Dictionary verticalConnections = new Dictionary(); - private static long GetOrCreateId(Vector3I coord) - { - if (coordToId.TryGetValue(coord, out long id)) - return id; + private static long GetOrCreateId(Vector3I coord) + { + if (coordToId.TryGetValue(coord, out long id)) return id; - id = nextId++; - coordToId[coord] = id; - idToCoord[id] = coord; + id = nextId++; + coordToId[coord] = id; + idToCoord[id] = coord; - return id; - } + return id; + } - public static void BuildAStarGraph() - { - aStar.Clear(); - coordToId.Clear(); - idToCoord.Clear(); - verticalConnections.Clear(); - nextId = 1; + public static void BuildAStarGraph() + { + aStar.Clear(); + coordToId.Clear(); + idToCoord.Clear(); + verticalConnections.Clear(); + nextId = 1; - for (int y = 0; y < GameData.ruinSize; y++) - { - for (int x = 0; x < GameData.layerSize; x++) - { - for (int z = 0; z < GameData.layerSize; z++) - { - Vector3I coord = new Vector3I(x, y, z); - Tile tile = GameData.map[y].tiles[x, z]; + for (int y = 0; y < GameData.ruinSize; y++) + { + for (int x = 0; x < GameData.layerSize; x++) + { + for (int z = 0; z < GameData.layerSize; z++) + { + AddPointIfValid(x, y, z); + } + } + } - if (tile == null || tile.collapsedMesh == null) continue; + foreach (KeyValuePair kvp in coordToId) + { + ConnectPoint(kvp.Key, kvp.Value); + } - long id = GetOrCreateId(coord); + for (int y = 0; y < GameData.ruinSize; y++) + { + UpdateGatePoint(y, false); + } + } - aStar.AddPoint(id, tile.Position); - } - } - } + private static void AddPointIfValid(int x, int y, int z) + { + Vector3I coord = new Vector3I(x, y, z); + Tile tile = GameData.map[y].tiles[x, z]; + if (tile == null || tile.collapsedMesh == null) return; - foreach (KeyValuePair kvp in coordToId) - { - Vector3I from = kvp.Key; - long fromId = kvp.Value; + long id = GetOrCreateId(coord); + aStar.AddPoint(id, tile.Position); + } - foreach (Vector3I offset in WFC.offsets3D) - { - Vector3I to = new Vector3I( - from.X + offset.X, - from.Y + offset.Y, - from.Z + offset.Z - ); + private static void ConnectPoint(Vector3I from, long fromId) + { + foreach (Vector3I offset in WFC.offsets3D) + { + Vector3I to = new Vector3I( + from.X + offset.X, + from.Y + offset.Y, + from.Z + offset.Z + ); - if (!coordToId.ContainsKey(to)) continue; + if (!coordToId.ContainsKey(to)) continue; + if (!WFC.CanWalk3D(from, to)) continue; - if (!WFC.CanWalk3D(from, to)) continue; + long toId = coordToId[to]; + if (TryRegisterGateConnection(from, to, fromId, toId)) continue; - long toId = coordToId[to]; + ConnectPointsIfNeeded(fromId, toId); + } + } - if (from.Y != to.Y && GameData.map[from.Y].tiles[from.X, from.Z].collapsedMesh == "gate") - { - verticalConnections[from.Y] = (fromId, toId); - if (GameData.map[from.Y].isGateOpen) - { - if (!aStar.ArePointsConnected(fromId, toId)) - { - aStar.ConnectPoints(fromId, toId, true); - } - } - continue; - } + private static bool TryRegisterGateConnection(Vector3I from, Vector3I to, long fromId, long toId) + { + if (from.Y == to.Y) return false; + if (GameData.map[from.Y].tiles[from.X, from.Z].collapsedMesh != "gate") return false; - if (!aStar.ArePointsConnected(fromId, toId)) - { - aStar.ConnectPoints(fromId, toId, true); - } - } - } + verticalConnections[from.Y] = (fromId, toId); + if (GameData.map[from.Y].isGateOpen) + { + ConnectPointsIfNeeded(fromId, toId); + } - for (int y = 0; y < GameData.ruinSize; y++) - { - UpdateGatePoint(y, false); - } - } + return true; + } - public static void UpdateGatePoint(int layer, bool isOpen) - { - if (!verticalConnections.ContainsKey(layer)) return; + private static void ConnectPointsIfNeeded(long fromId, long toId) + { + if (aStar.ArePointsConnected(fromId, toId)) return; - (long fromId, long toId) = verticalConnections[layer]; + aStar.ConnectPoints(fromId, toId, true); + } - if (isOpen) - { - if (!aStar.ArePointsConnected(fromId, toId)) - { - aStar.ConnectPoints(fromId, toId, true); - } - } - else - { - if (aStar.ArePointsConnected(fromId, toId)) - { - aStar.DisconnectPoints(fromId, toId); - } - } - } + public static void UpdateGatePoint(int layer, bool isOpen) + { + if (!verticalConnections.ContainsKey(layer)) return; - public static List GetPath(Vector3I start, Vector3I end) - { - if (!coordToId.ContainsKey(start) || !coordToId.ContainsKey(end)) return new List(); + (long fromId, long toId) = verticalConnections[layer]; - long startId = coordToId[start]; - long endId = coordToId[end]; + if (isOpen) + { + ConnectPointsIfNeeded(fromId, toId); + return; + } - return new List(aStar.GetPointPath(startId, endId)); - } + if (aStar.ArePointsConnected(fromId, toId)) + { + aStar.DisconnectPoints(fromId, toId); + } + } - public static Vector3I GetClosestStartPoint(Vector3 robotPosition) - { - return idToCoord[aStar.GetClosestPoint(robotPosition)]; - } + public static List GetPath(Vector3I start, Vector3I end) + { + if (!coordToId.ContainsKey(start) || !coordToId.ContainsKey(end)) return new List(); + + long startId = coordToId[start]; + long endId = coordToId[end]; + + return new List(aStar.GetPointPath(startId, endId)); + } + + public static Vector3I GetClosestStartPoint(Vector3 robotPosition) + { + return idToCoord[aStar.GetClosestPoint(robotPosition)]; + } } diff --git a/Scripts/World/ResourceDistributor.cs b/Scripts/World/ResourceDistributor.cs index f259117..e25f881 100644 --- a/Scripts/World/ResourceDistributor.cs +++ b/Scripts/World/ResourceDistributor.cs @@ -1,40 +1,40 @@ using System.Collections.Generic; using Godot; -public class ResourceDistributor +public static class ResourceDistributor { - public static Dictionary resources = ResourceLoader.LoadResourceSymbols(); - public static Dictionary weights = ResourceLoader.LoadResourceWeights(); + public static Dictionary resources = ResourceLoader.LoadResourceSymbols(); + public static Dictionary weights = ResourceLoader.LoadResourceWeights(); - public static string GetResource(int layer) - { - return ChooseWeighted(layer); - } + public static string GetResource(int layer) + { + return ChooseWeighted(layer); + } - private static string ChooseWeighted(int layer) + private static string ChooseWeighted(int layer) { float totalWeight = 0f; - float weightLerp; foreach (string resource in resources.Keys) { - weightLerp = Mathf.Lerp(weights[resource][0], weights[resource][1], layer); - totalWeight += weightLerp; + totalWeight += GetWeight(resource, layer); } - float r = (float)(GameData.rand.NextDouble() * totalWeight); - + float randomWeight = (float)(GameData.rand.NextDouble() * totalWeight); float cumulative = 0f; foreach (string resource in resources.Keys) { - weightLerp = Mathf.Lerp(weights[resource][0], weights[resource][1], layer); - cumulative += weightLerp; + cumulative += GetWeight(resource, layer); - if (r <= cumulative) - return resource; + if (randomWeight <= cumulative) return resource; } return "stone"; } + + private static float GetWeight(string resource, int layer) + { + return Mathf.Lerp(weights[resource][0], weights[resource][1], layer); + } } diff --git a/Scripts/World/WFC.cs b/Scripts/World/WFC.cs index a4c0f75..d74b311 100644 --- a/Scripts/World/WFC.cs +++ b/Scripts/World/WFC.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using Godot; -public class WFC +public static class WFC { public static Dictionary>> adjacency = new Dictionary>>(); public enum Direction @@ -122,8 +122,6 @@ public class WFC return aOpen == bOpen; } - - public static void FillAdjacencies() { foreach (string tile in tileConnections.Keys) @@ -136,8 +134,7 @@ public class WFC foreach (string other in tileConnections.Keys) { - if (CanConnect(tile, other, dir, false)) - valid.Add(other); + if (CanConnect(tile, other, dir, false)) valid.Add(other); } adjacency[tile][dir] = valid; @@ -156,8 +153,7 @@ public class WFC Tile fromTile = layer[from.X, from.Y]; Tile toTile = layer[to.X, to.Y]; - if (!IsWalkable(toTile)) - return false; + if (!IsWalkable(toTile)) return false; return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true); } @@ -167,29 +163,21 @@ public class WFC Tile fromTile = GameData.map[from.Y].tiles[from.X, from.Z]; Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z]; - if (fromTile == null || toTile == null) - return false; + if (fromTile == null || toTile == null) return false; if (from.Y != to.Y) { - if (Math.Abs(from.Y - to.Y) != 1) - return false; + if (Math.Abs(from.Y - to.Y) != 1) return false; - if (from.Y > to.Y) - { - return toTile.collapsedMesh == "gate"; - } - else - { - return fromTile.collapsedMesh == "gate"; - } + return from.Y > to.Y + ? toTile.collapsedMesh == "gate" + : fromTile.collapsedMesh == "gate"; } int dx = to.X - from.X; int dz = to.Z - from.Z; - if (Math.Abs(dx) + Math.Abs(dz) != 1) - return false; + if (Math.Abs(dx) + Math.Abs(dz) != 1) return false; Direction dir; @@ -207,29 +195,23 @@ public class WFC ); } - public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshhold) + public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshold) { - bool result = false; HashSet visited = new HashSet(); List toCheck = new List(); - Vector2I position; toCheck.Add(new Vector2I(1, 1)); int safetyCounter = 0; - while (true) + while (toCheck.Count > 0) { - if (toCheck.Count <= 0) break; - int index = GameData.rand.Next(toCheck.Count); - position = toCheck[index]; - toCheck[index] = toCheck[^1]; - toCheck.RemoveAt(toCheck.Count - 1); + Vector2I position = TakeRandomPosition(toCheck); if (!visited.Add(position)) continue; + for (int i = 0; i < offsets2D.Length; i++) { Vector2I next = position + offsets2D[i]; - if (!InBounds(next, layer.GetLength(0))) - continue; + if (!InBounds(next, layer.GetLength(0))) continue; if (CanWalk(layer, position, next, dirs[i])) { @@ -238,22 +220,30 @@ public class WFC } safetyCounter++; if (safetyCounter > layer.Length * 2) break; - if (visited.Count >= Math.Pow(layer.GetLength(0) - 1, 2) * accessibilityThreshhold) + if (visited.Count >= Math.Pow(layer.GetLength(0) - 1, 2) * accessibilityThreshold) { - result = true; - break; + return true; } - } - return result; + + return false; + } + + private static Vector2I TakeRandomPosition(List positions) + { + int index = GameData.rand.Next(positions.Count); + Vector2I position = positions[index]; + positions[index] = positions[positions.Count - 1]; + positions.RemoveAt(positions.Count - 1); + return position; } public static bool InBounds(Vector2I pos, int layerSize) { - return pos.X > 0 && - pos.Y > 0 && - pos.X < layerSize && - pos.Y < layerSize; + return pos.X > 0 + && pos.Y > 0 + && pos.X < layerSize + && pos.Y < layerSize; } public static List GetBorderPossibilities(int x, int z) diff --git a/Scripts/World/World.cs b/Scripts/World/World.cs index d84a74e..5b0335b 100644 --- a/Scripts/World/World.cs +++ b/Scripts/World/World.cs @@ -1,6 +1,5 @@ using Godot; using System.Collections.Generic; -using System.Linq; using static GameData; public partial class World : Node3D @@ -48,7 +47,7 @@ public partial class World : Node3D map = new Layer[ruinSize]; GenerateWorld(); - SetGateRequirements(); + GateRequirementGenerator.ApplyGateRequirements(map); if (shouldLoadSave && saveGame != null) { @@ -288,83 +287,4 @@ public partial class World : Node3D } } - private void SetGateRequirements() - { - List availableResources = new List(); - List possibleIngredients; - bool canCraft; - double highestCraftTime; - double lowestCraftTime; - foreach (Layer layer in map) - { - highestCraftTime = 0; - lowestCraftTime = double.MaxValue; - possibleIngredients = new List(); - //Step 1: Determine all possible resources for this and all previous layers combined - foreach (string resource in layer.currentResources) - { - if (availableResources.Contains(resource)) continue; - availableResources.Add(resource); - } - //Step 2: Check which items can be crafted with those items, repeat until no further items are added to the list - bool addedNewItem; - do - { - addedNewItem = false; - - foreach (ItemData item in availableItems.Values) - { - if (possibleIngredients.Any(existing => existing.Id == item.Id)) - continue; - - canCraft = item.Inputs.All(input => availableResources.Contains(input.Item)); - - if (!canCraft) - continue; - - possibleIngredients.Add(item); - availableResources.Add(item.Id); - - lowestCraftTime = Mathf.Min(lowestCraftTime, item.CraftTime); - highestCraftTime = Mathf.Max(highestCraftTime, item.CraftTime); - - addedNewItem = true; - } - - } while (addedNewItem); - //Step 3: Choose gate items needed based on crafting time and layer it is for (Lower layers -> More advanced items -> More crafting time) - double goalCraftTime = Mathf.Lerp(lowestCraftTime, highestCraftTime, Mathf.Clamp(layer.level/(float)ruinSize, 0, 1)); - int ingredientAmount = Mathf.Clamp(1 + layer.level / 3, 1, 4); - float craftTimeModifier = 0f; - double craftTimeLower, craftTimeUpper; - for (int i = 0; i < ingredientAmount; i++) - { - craftTimeLower = goalCraftTime - goalCraftTime * craftTimeModifier; - craftTimeUpper = goalCraftTime + goalCraftTime * craftTimeModifier; - - List validIngredients = possibleIngredients - .Where(item => - item.CraftTime >= craftTimeLower && - item.CraftTime <= craftTimeUpper && - !layer.gateIngredients.Any(ingredient => ingredient.Item == item.Id)) - .ToList(); - - if (validIngredients.Count == 0) - { - i--; - craftTimeModifier += 0.05f; - continue; - } - - ItemData item = validIngredients[rand.Next(validIngredients.Count)]; - - layer.gateIngredients.Add(new Ingredient - { - Item = item.Id, - Amount = rand.Next(3 + layer.level * 2, 9 + layer.level * 4) - }); - craftTimeModifier = 0f; - } - } - } }