Finished first EA Version #1

Merged
Nicola merged 110 commits from dev into main 2026-05-19 20:01:13 +02:00
54 changed files with 2030 additions and 1745 deletions
Showing only changes of commit 300c8f5a42 - Show all commits
+9
View File
@@ -225,6 +225,7 @@ layout_mode = 2
size_flags_vertical = 3 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")] [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 layout_mode = 1
anchors_preset = 11 anchors_preset = 11
anchor_left = 1.0 anchor_left = 1.0
@@ -293,6 +294,14 @@ size_flags_horizontal = 3
size_flags_vertical = 3 size_flags_vertical = 3
theme_override_constants/separation = 10 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] [node name="EditorWindow" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting" unique_id=919757187]
layout_mode = 2 layout_mode = 2
size_flags_horizontal = 3 size_flags_horizontal = 3
+71 -67
View File
@@ -1,88 +1,92 @@
using System.Collections.Generic; using System.Collections.Generic;
using Godot; using Godot;
public class FileHandler public static class FileHandler
{ {
private const string ScriptDirectory = "user://scripts"; private const string ScriptDirectory = "user://scripts";
private const string ScriptExtension = ".json"; private const string ScriptExtension = ".json";
public static void CreateScriptDirectory() public static void CreateScriptDirectory()
{ {
DirAccess.MakeDirRecursiveAbsolute(ScriptDirectory); DirAccess.MakeDirRecursiveAbsolute(ScriptDirectory);
} }
public static void SaveProgram(string filename, string content) public static void SaveProgram(string filename, string content)
{ {
CreateScriptDirectory(); CreateScriptDirectory();
string path = GetProgramPath(filename); string path = GetProgramPath(filename);
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write); FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
file.StoreString(content); if (file == null) return;
}
public static List<string> LoadProgramNames() file.StoreString(content);
{ file.Flush();
CreateScriptDirectory(); }
List<string> programs = new List<string>();
DirAccess dir = DirAccess.Open(ScriptDirectory); public static List<string> LoadProgramNames()
if (dir == null) {
{ CreateScriptDirectory();
return programs; List<string> programs = new List<string>();
}
dir.ListDirBegin(); DirAccess dir = DirAccess.Open(ScriptDirectory);
while (true) if (dir == null)
{ {
string fileName = dir.GetNext(); return programs;
if (fileName == "") }
break;
if (!dir.CurrentIsDir() && fileName.EndsWith(ScriptExtension)) dir.ListDirBegin();
{ while (true)
programs.Add(fileName.Replace(ScriptExtension, "")); {
} string fileName = dir.GetNext();
} if (fileName == "") break;
dir.ListDirEnd();
return programs; if (dir.CurrentIsDir()) continue;
} if (!fileName.EndsWith(ScriptExtension)) continue;
public static string LoadProgram(string name) programs.Add(fileName.Replace(ScriptExtension, ""));
{ }
CreateScriptDirectory(); dir.ListDirEnd();
string path = GetProgramPath(name);
if (!FileAccess.FileExists(path)) return programs;
{ }
return "";
}
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read); public static string LoadProgram(string name)
return file.GetAsText(); {
} CreateScriptDirectory();
string path = GetProgramPath(name);
public static bool DeleteProgram(string name) if (!FileAccess.FileExists(path))
{ {
CreateScriptDirectory(); return "";
string path = GetProgramPath(name); }
if (!FileAccess.FileExists(path)) FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
{ if (file == null) return "";
return false;
}
DirAccess dir = DirAccess.Open(ScriptDirectory); return file.GetAsText();
if (dir == null) }
{
return false;
}
return dir.Remove($"{name}{ScriptExtension}") == Error.Ok; public static bool DeleteProgram(string name)
} {
CreateScriptDirectory();
string path = GetProgramPath(name);
private static string GetProgramPath(string filename) if (!FileAccess.FileExists(path))
{ {
return $"{ScriptDirectory}/{filename}{ScriptExtension}"; 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}";
}
} }
+7 -5
View File
@@ -2,7 +2,7 @@ using Godot;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.Json; using System.Text.Json;
public partial class ResourceLoader public static class ResourceLoader
{ {
private const string LayerPrefabPath = "res://Prefabs/Layer.tscn"; private const string LayerPrefabPath = "res://Prefabs/Layer.tscn";
private const string RobotPrefabPath = "res://Prefabs/Robot/Robot.tscn"; private const string RobotPrefabPath = "res://Prefabs/Robot/Robot.tscn";
@@ -39,7 +39,7 @@ public partial class ResourceLoader
public static Dictionary<string, MeshInstance3D> LoadTiles() public static Dictionary<string, MeshInstance3D> LoadTiles()
{ {
Dictionary<string, MeshInstance3D> tileMeshes = new Dictionary<string, MeshInstance3D>(); Dictionary<string, MeshInstance3D> tileMeshes = new Dictionary<string, MeshInstance3D>();
PackedScene tileCollection = GD.Load<PackedScene>($"res://Assets/Objects/Tiles.glb"); PackedScene tileCollection = GD.Load<PackedScene>("res://Assets/Objects/Tiles.glb");
Node root = tileCollection.Instantiate(); Node root = tileCollection.Instantiate();
foreach (MeshInstance3D child in root.GetChildren()) foreach (MeshInstance3D child in root.GetChildren())
{ {
@@ -52,7 +52,7 @@ public partial class ResourceLoader
public static Dictionary<string, MeshInstance3D> LoadDecorations() public static Dictionary<string, MeshInstance3D> LoadDecorations()
{ {
Dictionary<string, MeshInstance3D> decorationMeshes = new Dictionary<string, MeshInstance3D>(); Dictionary<string, MeshInstance3D> decorationMeshes = new Dictionary<string, MeshInstance3D>();
PackedScene decorationCollection = GD.Load<PackedScene>($"res://Assets/Objects/Decorations.glb"); PackedScene decorationCollection = GD.Load<PackedScene>("res://Assets/Objects/Decorations.glb");
Node root = decorationCollection.Instantiate(); Node root = decorationCollection.Instantiate();
foreach (MeshInstance3D child in root.GetChildren()) foreach (MeshInstance3D child in root.GetChildren())
{ {
@@ -114,8 +114,9 @@ public partial class ResourceLoader
public static SortedDictionary<string, ItemData> LoadItems() public static SortedDictionary<string, ItemData> LoadItems()
{ {
FileAccess file = FileAccess.Open(RecipesPath, FileAccess.ModeFlags.Read); FileAccess file = FileAccess.Open(RecipesPath, FileAccess.ModeFlags.Read);
if (file == null) return new SortedDictionary<string, ItemData>();
string json = file.GetAsText(); string json = file.GetAsText();
SortedDictionary<string, ItemData> result = new SortedDictionary<string, ItemData>(); SortedDictionary<string, ItemData> result = new SortedDictionary<string, ItemData>();
@@ -133,8 +134,9 @@ public partial class ResourceLoader
public static Dictionary<string, Research> LoadResearch() public static Dictionary<string, Research> LoadResearch()
{ {
FileAccess file = FileAccess.Open(ResearchPath, FileAccess.ModeFlags.Read); FileAccess file = FileAccess.Open(ResearchPath, FileAccess.ModeFlags.Read);
if (file == null) return new Dictionary<string, Research>();
string json = file.GetAsText(); string json = file.GetAsText();
Dictionary<string, Research> result = new Dictionary<string, Research>(); Dictionary<string, Research> result = new Dictionary<string, Research>();
+148
View File
@@ -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<ItemSaveData> 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<ResearchSaveData> 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<LayerSaveData> 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<Ingredient>();
layer.currentResources = savedLayer.CurrentResources ?? new List<string>();
ApplyTileData(layer, savedLayer.Tiles);
}
}
private static void ApplyTileData(Layer layer, List<TileSaveData> 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);
}
}
}
+1
View File
@@ -0,0 +1 @@
uid://jv7k2npga7u0
+160
View File
@@ -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<ResearchSaveData>(),
Layers = new List<LayerSaveData>(),
Robots = new List<RobotSaveData>()
};
}
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<ItemSaveData> CreateInventorySaveData()
{
List<ItemSaveData> result = new List<ItemSaveData>();
foreach (Item item in GameData.inventory.items)
{
result.Add(new ItemSaveData
{
Id = item.data.Id,
Amount = item.currentAmount
});
}
return result;
}
private static List<ResearchSaveData> CreateResearchSaveData()
{
List<ResearchSaveData> result = new List<ResearchSaveData>();
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<LayerSaveData> CreateLayerSaveData()
{
List<LayerSaveData> result = new List<LayerSaveData>();
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<Ingredient>(layer.gateIngredients),
CurrentResources = new List<string>(layer.currentResources),
Tiles = CreateTileSaveData(layer)
});
}
return result;
}
private static List<TileSaveData> CreateTileSaveData(Layer layer)
{
List<TileSaveData> result = new List<TileSaveData>();
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<RobotSaveData> CreateRobotSaveData()
{
List<RobotSaveData> result = new List<RobotSaveData>();
foreach (Robot robot in GameData.robots)
{
result.Add(robot.CreateSaveData());
}
return result;
}
}
+1
View File
@@ -0,0 +1 @@
uid://b87q17gdv4pfh
+7 -288
View File
@@ -27,7 +27,7 @@ public static class SaveGameManager
CreateSaveDirectory(); CreateSaveDirectory();
ClearOldLayerFiles(); ClearOldLayerFiles();
SaveJson(GameDataPath, CreateCoreSaveData(saveGame)); SaveJson(GameDataPath, SaveGameDataFactory.CreateCoreSaveData(saveGame));
SaveJson(RobotsPath, saveGame.Robots); SaveJson(RobotsPath, saveGame.Robots);
SaveJson(ResearchPath, saveGame.Research); SaveJson(ResearchPath, saveGame.Research);
@@ -53,176 +53,12 @@ public static class SaveGameManager
public static SaveGameData CreateSaveData() public static SaveGameData CreateSaveData()
{ {
return new SaveGameData return SaveGameDataFactory.CreateSaveData();
{
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 void ApplyWorldData(SaveGameData saveGame) public static void ApplyWorldData(SaveGameData saveGame)
{ {
if (saveGame == null) return; SaveGameDataApplier.ApplyWorldData(saveGame);
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<ItemSaveData> CreateInventorySaveData()
{
List<ItemSaveData> result = new List<ItemSaveData>();
foreach (Item item in GameData.inventory.items)
{
result.Add(new ItemSaveData
{
Id = item.data.Id,
Amount = item.currentAmount
});
}
return result;
}
private static List<ResearchSaveData> CreateResearchSaveData()
{
List<ResearchSaveData> result = new List<ResearchSaveData>();
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<LayerSaveData> CreateLayerSaveData()
{
List<LayerSaveData> result = new List<LayerSaveData>();
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<Ingredient>(layer.gateIngredients),
CurrentResources = new List<string>(layer.currentResources),
Tiles = CreateTileSaveData(layer)
});
}
return result;
}
private static List<TileSaveData> CreateTileSaveData(Layer layer)
{
List<TileSaveData> result = new List<TileSaveData>();
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<RobotSaveData> CreateRobotSaveData()
{
List<RobotSaveData> result = new List<RobotSaveData>();
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<ResearchSaveData>(),
Layers = new List<LayerSaveData>(),
Robots = new List<RobotSaveData>()
};
} }
private static List<LayerSaveData> LoadLayerSaveData() private static List<LayerSaveData> LoadLayerSaveData()
@@ -276,6 +112,8 @@ public static class SaveGameManager
{ {
string json = JsonSerializer.Serialize(data, JsonOptions); string json = JsonSerializer.Serialize(data, JsonOptions);
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write); FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
if (file == null) return;
file.StoreString(json); file.StoreString(json);
file.Flush(); file.Flush();
} }
@@ -285,128 +123,9 @@ public static class SaveGameManager
if (!FileAccess.FileExists(path)) return default; if (!FileAccess.FileExists(path)) return default;
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read); FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
if (file == null) return default;
string json = file.GetAsText(); string json = file.GetAsText();
return JsonSerializer.Deserialize<T>(json); return JsonSerializer.Deserialize<T>(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<ItemSaveData> 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<ResearchSaveData> 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<LayerSaveData> 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<Ingredient>();
layer.currentResources = savedLayer.CurrentResources ?? new List<string>();
ApplyTileData(layer, savedLayer.Tiles);
}
}
private static void ApplyTileData(Layer layer, List<TileSaveData> 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);
}
}
} }
-3
View File
@@ -13,12 +13,9 @@ public partial class SteamworksHandler : Node
SteamInitExStatus status = Steam.SteamInitEx(false).Status; SteamInitExStatus status = Steam.SteamInitEx(false).Status;
if (status != 0) if (status != 0)
{ {
GD.Print("Steam not initialized!");
return; return;
} }
GD.Print("Steam initialized!");
GD.Print("User: " + Steam.GetPersonaName());
isSteamInitialized = true; isSteamInitialized = true;
} }
+1 -1
View File
@@ -18,7 +18,7 @@ public class CraftNode : ProgramNode
if (amount <= 0) if (amount <= 0)
{ {
lastExecutionMessage = "Amount has to be atleast 1"; lastExecutionMessage = "Amount has to be at least 1";
return NodeResult.FAILURE; return NodeResult.FAILURE;
} }
+112 -75
View File
@@ -4,90 +4,127 @@ using Godot;
public class ExploreNode : ProgramNode public class ExploreNode : ProgramNode
{ {
public Vector3 startPosition; public Vector3 startPosition;
public Vector3I targetPosition; public Vector3I targetPosition;
public List<Vector3> pathPoints; public List<Vector3> 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;
}
}
}
pathPoints ??= new List<Vector3>(Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)); public ExploreNode()
{
DisplayText = "Explore";
}
if (pathPoints.Count <= 0) public override NodeResult Execute(Robot robot, double delta)
{ {
lastExecutionMessage = $"No path available {targetPosition}"; if (pathPoints == null && !TrySelectTarget())
return NodeResult.FAILURE; {
} lastExecutionMessage = "No tiles left to explore";
return NodeResult.SUCCESS;
}
startPosition = robot.Position; if (pathPoints == null)
Vector3 target = pathPoints[0] - startPosition; {
float distance = target.Length(); pathPoints = new List<Vector3>(
Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)
);
}
float movementSpeed = robot.GetMovementSpeed(); if (pathPoints.Count <= 0)
{
lastExecutionMessage = $"No path available {targetPosition}";
return NodeResult.FAILURE;
}
if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) return MoveAlongPath(robot, delta);
{ }
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();
}
pathPoints.Remove(pathPoints[0]); private bool TrySelectTarget()
if (pathPoints.Count <= 0) {
{ int safetyCounter = 0;
lastExecutionMessage = "Current exploration finished"; int layerRange = Math.Max(GameData.lowestLayer, 1);
pathPoints = null; int maximumAttempts = (int)Math.Pow(GameData.layerSize, 2) * 2;
return NodeResult.RUNNING;
}
lastExecutionMessage = ""; while (safetyCounter <= maximumAttempts)
return NodeResult.RUNNING; {
} 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;
}
Vector3 direction = target / distance; safetyCounter++;
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;
return NodeResult.RUNNING; return false;
} }
public override ProgramNode Duplicate() private NodeResult MoveAlongPath(Robot robot, double delta)
{ {
ExploreNode duplicate = new ExploreNode startPosition = robot.Position;
{ Vector3 target = pathPoints[0] - startPosition;
targetPosition = targetPosition float distance = target.Length();
}; float movementSpeed = robot.GetMovementSpeed();
return duplicate;
}
public override string Save() if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
{ {
return $"Name: {DisplayText}"; 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}";
}
} }
+3 -17
View File
@@ -9,11 +9,12 @@ public class ForNode : ProgramNode
{ {
DisplayText = "For"; DisplayText = "For";
} }
public override NodeResult Execute(Robot robot, double delta) public override NodeResult Execute(Robot robot, double delta)
{ {
bool isConditionFulfilled = DetermineCondition(); bool isConditionFulfilled = DetermineCondition();
amountExecuted++; amountExecuted++;
return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
} }
private bool DetermineCondition() private bool DetermineCondition()
@@ -41,21 +42,6 @@ public class ForNode : ProgramNode
Dictionary<StringName, ProgramNode> availableNodes Dictionary<StringName, ProgramNode> availableNodes
) )
{ {
nextNode = null; SetBranchNodes(connections, availableNodes);
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;
}
}
} }
} }
+39 -42
View File
@@ -2,51 +2,48 @@ using Godot;
public class HarvestNode : ProgramNode public class HarvestNode : ProgramNode
{ {
public HarvestNode() public HarvestNode()
{ {
DisplayText = "Harvest"; 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];
if (!tile.containsResource) public override NodeResult Execute(Robot robot, double delta)
{ {
lastExecutionMessage = "No resource on this tile"; Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
return NodeResult.FAILURE; Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
}
if (!tile.resource.CanExtract()) if (!tile.containsResource || tile.resource == null)
{ {
lastExecutionMessage = "Resource is depleted and not endless or you haven't unlocked it yet"; lastExecutionMessage = "No resource on this tile";
return NodeResult.SUCCESS; return NodeResult.FAILURE;
} }
if (tile.resource.Extract(delta)) if (!tile.resource.CanExtract())
{ {
SoundManager.PlayMining(); lastExecutionMessage = "Resource is depleted and not endless or you haven't unlocked it yet";
if (!GameData.inventory.AddItem(new Item {data = tile.resource.item}, 1)) return NodeResult.SUCCESS;
{ }
lastExecutionMessage = "Not enough space";
return NodeResult.FAILURE;
}
else
{
return NodeResult.SUCCESS;
}
}
return NodeResult.RUNNING;
}
public override ProgramNode Duplicate() if (!tile.resource.Extract(delta)) return NodeResult.RUNNING;
{
HarvestNode duplicate = new HarvestNode();
return duplicate;
}
public override string Save() SoundManager.PlayMining();
{ if (!GameData.inventory.AddItem(new Item { data = tile.resource.item }, 1))
return $"Name: {DisplayText}"; {
} 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}";
}
} }
+11 -31
View File
@@ -10,6 +10,7 @@ public class IfNode : ProgramNode
{ {
DisplayText = "If"; DisplayText = "If";
} }
public override NodeResult Execute(Robot robot, double delta) public override NodeResult Execute(Robot robot, double delta)
{ {
if (selectedItem == null) if (selectedItem == null)
@@ -19,27 +20,21 @@ public class IfNode : ProgramNode
} }
bool isConditionFulfilled = DetermineCondition(); bool isConditionFulfilled = DetermineCondition();
return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
} }
private bool DetermineCondition() private bool DetermineCondition()
{ {
int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id); int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
switch (comparator) return comparator switch
{ {
case "is bigger than": "is bigger than" => inventoryAmount > amount,
return inventoryAmount > amount; "is less than" => inventoryAmount < amount,
case "is less than": "is not" => inventoryAmount != amount,
return inventoryAmount < amount; "is less than or equal to" => inventoryAmount <= amount,
case "is not": "is bigger than or equal to" => inventoryAmount >= amount,
return inventoryAmount != amount; _ => 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;
}
} }
public override ProgramNode Duplicate() public override ProgramNode Duplicate()
@@ -63,21 +58,6 @@ public class IfNode : ProgramNode
Dictionary<StringName, ProgramNode> availableNodes Dictionary<StringName, ProgramNode> availableNodes
) )
{ {
nextNode = null; SetBranchNodes(connections, availableNodes);
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;
}
}
} }
} }
+84 -65
View File
@@ -3,79 +3,98 @@ using Godot;
public class MoveNode : ProgramNode public class MoveNode : ProgramNode
{ {
public Vector3 startPosition; public Vector3 startPosition;
public Vector3I targetPosition; public Vector3I targetPosition;
public List<Vector3> pathPoints; public List<Vector3> pathPoints;
public MoveNode()
{
DisplayText = "Move";
}
public override NodeResult Execute(Robot robot, double delta)
{
Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position);
pathPoints ??= new List<Vector3>(Pathfinding.GetPath(closestPosition, targetPosition));
if (pathPoints.Count <= 0) public MoveNode()
{ {
if ((closestPosition - targetPosition).Length() == 0) DisplayText = "Move";
{ }
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
lastExecutionMessage = "No path available";
return NodeResult.FAILURE;
}
startPosition = robot.Position; public override NodeResult Execute(Robot robot, double delta)
Vector3 target = pathPoints[0] - startPosition; {
float distance = target.Length(); Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position);
if (pathPoints == null)
{
pathPoints = new List<Vector3>(Pathfinding.GetPath(closestPosition, targetPosition));
}
float movementSpeed = robot.GetMovementSpeed(); if (pathPoints.Count <= 0)
{
if ((closestPosition - targetPosition).Length() == 0)
{
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) lastExecutionMessage = "No path available";
{ return NodeResult.FAILURE;
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();
}
pathPoints.Remove(pathPoints[0]); return MoveAlongPath(robot, delta);
if (pathPoints.Count <= 0) }
{
pathPoints = null;
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
lastExecutionMessage = ""; private NodeResult MoveAlongPath(Robot robot, double delta)
return NodeResult.RUNNING; {
} startPosition = robot.Position;
Vector3 target = pathPoints[0] - startPosition;
float distance = target.Length();
float movementSpeed = robot.GetMovementSpeed();
Vector3 direction = target / distance; if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); {
if (lookDirection.Length() > 0.1f) return FinishCurrentStep(robot);
{ }
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
}
robot.GlobalPosition += direction * (float)delta * movementSpeed;
return NodeResult.RUNNING; Vector3 direction = target / distance;
} RotateRobot(robot, direction);
robot.GlobalPosition += direction * (float)delta * movementSpeed;
public override ProgramNode Duplicate() return NodeResult.RUNNING;
{ }
MoveNode duplicate = new MoveNode
{
targetPosition = targetPosition
};
return duplicate;
}
public override string Save() private NodeResult FinishCurrentStep(Robot robot)
{ {
return $"Name: {DisplayText}, Position: ({targetPosition.X}|{targetPosition.Y}|{targetPosition.Z})"; 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})";
}
} }
+47 -25
View File
@@ -3,35 +3,57 @@ using System.Collections.Generic;
public abstract class ProgramNode public abstract class ProgramNode
{ {
public ProgramNode nextNode; public ProgramNode nextNode;
public ProgramNode NegativeNode; public ProgramNode NegativeNode;
public string DisplayText; public string DisplayText;
public string lastExecutionMessage; public string lastExecutionMessage;
public abstract NodeResult Execute(Robot robot, double delta); public abstract NodeResult Execute(Robot robot, double delta);
public abstract ProgramNode Duplicate(); public abstract ProgramNode Duplicate();
public abstract string Save(); public abstract string Save();
public virtual void SetNextNode( public virtual void SetNextNode(
List<Godot.Collections.Dictionary> connections, List<Godot.Collections.Dictionary> connections,
Dictionary<StringName, ProgramNode> availableNodes Dictionary<StringName, ProgramNode> availableNodes
) )
{ {
nextNode = null; 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( protected void SetBranchNodes(
Godot.Collections.Dictionary connection, List<Godot.Collections.Dictionary> connections,
Dictionary<StringName, ProgramNode> availableNodes Dictionary<StringName, ProgramNode> availableNodes
) )
{ {
StringName nodeName = connection["to_node"].AsStringName(); nextNode = null;
if (!availableNodes.ContainsKey(nodeName)) return 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<StringName, ProgramNode> availableNodes
)
{
StringName nodeName = connection["to_node"].AsStringName();
if (!availableNodes.ContainsKey(nodeName)) return null;
return availableNodes[nodeName];
}
} }
+11 -32
View File
@@ -10,9 +10,9 @@ public class WhileNode : ProgramNode
{ {
DisplayText = "While"; DisplayText = "While";
} }
public override NodeResult Execute(Robot robot, double delta) public override NodeResult Execute(Robot robot, double delta)
{ {
if (selectedItem == null) if (selectedItem == null)
{ {
lastExecutionMessage = "No Item selected"; lastExecutionMessage = "No Item selected";
@@ -20,27 +20,21 @@ public class WhileNode : ProgramNode
} }
bool isConditionFulfilled = DetermineCondition(); bool isConditionFulfilled = DetermineCondition();
return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE; return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
} }
private bool DetermineCondition() private bool DetermineCondition()
{ {
int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id); int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
switch (comparator) return comparator switch
{ {
case "is bigger than": "is bigger than" => inventoryAmount > amount,
return inventoryAmount > amount; "is less than" => inventoryAmount < amount,
case "is less than": "is not" => inventoryAmount != amount,
return inventoryAmount < amount; "is less than or equal to" => inventoryAmount <= amount,
case "is not": "is bigger than or equal to" => inventoryAmount >= amount,
return inventoryAmount != amount; _ => 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;
}
} }
public override ProgramNode Duplicate() public override ProgramNode Duplicate()
@@ -64,21 +58,6 @@ public class WhileNode : ProgramNode
Dictionary<StringName, ProgramNode> availableNodes Dictionary<StringName, ProgramNode> availableNodes
) )
{ {
nextNode = null; SetBranchNodes(connections, availableNodes);
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;
}
}
} }
} }
-3
View File
@@ -1,3 +0,0 @@
public class Building
{
}
@@ -1 +0,0 @@
uid://cl2yvllo35qbb
+79 -76
View File
@@ -1,92 +1,95 @@
public class GameResource public class GameResource
{ {
private const float NormalExtractionSpeed = 1f; private const float NormalExtractionSpeed = 1f;
private const float EndlessExtractionSpeed = 4f; private const float EndlessExtractionSpeed = 4f;
public string name; public string name;
public ItemData item; public ItemData item;
private int currentAmount; private int currentAmount;
private int maxAmount; private int maxAmount;
private bool isEndless; private bool isEndless;
private float extractionSpeed; private float extractionSpeed;
private double timeSinceLastExtraction; private double timeSinceLastExtraction;
public GameResource(string name) public GameResource(string name)
{ {
this.name = name; this.name = name;
maxAmount = GameData.rand.Next(1000, 10000); maxAmount = GameData.rand.Next(1000, 10000);
currentAmount = maxAmount; currentAmount = maxAmount;
isEndless = false; isEndless = false;
extractionSpeed = NormalExtractionSpeed; extractionSpeed = NormalExtractionSpeed;
item = GameData.availableItems[name]; item = GameData.availableItems[name];
} }
public static GameResource FromSaveData(ResourceSaveData saveData) public static GameResource FromSaveData(ResourceSaveData saveData)
{ {
GameResource resource = new GameResource(saveData.Name) GameResource resource = new GameResource(saveData.Name)
{ {
currentAmount = saveData.CurrentAmount, currentAmount = saveData.CurrentAmount,
maxAmount = saveData.MaxAmount, maxAmount = saveData.MaxAmount,
isEndless = saveData.IsEndless, isEndless = saveData.IsEndless,
extractionSpeed = saveData.ExtractionSpeed extractionSpeed = saveData.ExtractionSpeed,
}; timeSinceLastExtraction = saveData.TimeSinceLastExtraction
if (resource.isEndless && resource.extractionSpeed <= NormalExtractionSpeed) };
{ resource.NormalizeExtractionSpeed();
resource.extractionSpeed = EndlessExtractionSpeed; return resource;
} }
resource.timeSinceLastExtraction = saveData.TimeSinceLastExtraction;
return resource;
}
public ResourceSaveData CreateSaveData() public ResourceSaveData CreateSaveData()
{ {
return new ResourceSaveData return new ResourceSaveData
{ {
Name = name, Name = name,
CurrentAmount = currentAmount, CurrentAmount = currentAmount,
MaxAmount = maxAmount, MaxAmount = maxAmount,
IsEndless = isEndless, IsEndless = isEndless,
ExtractionSpeed = extractionSpeed, ExtractionSpeed = extractionSpeed,
TimeSinceLastExtraction = timeSinceLastExtraction TimeSinceLastExtraction = timeSinceLastExtraction
}; };
} }
public bool Extract(double delta) public bool Extract(double delta)
{ {
timeSinceLastExtraction += delta; timeSinceLastExtraction += delta;
if (timeSinceLastExtraction < extractionSpeed) return false; if (timeSinceLastExtraction < extractionSpeed) return false;
timeSinceLastExtraction = 0; timeSinceLastExtraction = 0;
if (isEndless) return true; if (isEndless) return true;
if (currentAmount > 0) if (currentAmount <= 0) return false;
{
currentAmount--;
return true;
}
return false; currentAmount--;
} return true;
}
public bool CanExtract() public bool CanExtract()
{ {
return (isEndless || currentAmount > 0) && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED; return (isEndless || currentAmount > 0)
} && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED;
}
public void MakeEndless() public void MakeEndless()
{ {
isEndless = true; isEndless = true;
extractionSpeed = EndlessExtractionSpeed; extractionSpeed = EndlessExtractionSpeed;
} }
public bool IsEndless() public bool IsEndless()
{ {
return isEndless; return isEndless;
} }
public float GetExtractionSpeed() public float GetExtractionSpeed()
{ {
return extractionSpeed; return extractionSpeed;
} }
private void NormalizeExtractionSpeed()
{
if (!isEndless) return;
if (extractionSpeed > NormalExtractionSpeed) return;
extractionSpeed = EndlessExtractionSpeed;
}
} }
+5 -4
View File
@@ -29,12 +29,13 @@ public class ItemData
public string GetReadableName() public string GetReadableName()
{ {
string noUnderscore = Id.Replace("_", " ").ToLower(); return GetReadableName(Id);
return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1);
} }
public static string GetReadableName(string input) public static string GetReadableName(string input)
{ {
if (string.IsNullOrEmpty(input)) return "";
string noUnderscore = input.Replace("_", " ").ToLower(); string noUnderscore = input.Replace("_", " ").ToLower();
return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1); return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1);
} }
@@ -46,6 +47,8 @@ public class ItemData
public string GetCraftingDisplay() public string GetCraftingDisplay()
{ {
if (Inputs.Count <= 0) return GetReadableName() + ": \r";
string result = GetReadableName() + ": \r"; string result = GetReadableName() + ": \r";
foreach (Ingredient ingredient in Inputs) foreach (Ingredient ingredient in Inputs)
@@ -53,8 +56,6 @@ public class ItemData
result += $"{GetReadableName(ingredient.Item)} ({ingredient.Amount}),\r"; result += $"{GetReadableName(ingredient.Item)} ({ingredient.Amount}),\r";
} }
if (Inputs.Count <= 0) return result;
result = result.Remove(result.Length - 2); result = result.Remove(result.Length - 2);
return result; return result;
} }
+1 -2
View File
@@ -53,7 +53,6 @@ public class Research
public static string GetReadableName(string input) public static string GetReadableName(string input)
{ {
string noUnderscore = input.Replace("_", " ").ToLower(); return ItemData.GetReadableName(input);
return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1);
} }
} }
+1 -2
View File
@@ -23,8 +23,7 @@ public class ResearchData
public string GetReadableName() public string GetReadableName()
{ {
string noUnderscore = Id.Replace("_", " ").ToLower(); return ItemData.GetReadableName(Id);
return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1);
} }
public static string GetIndex(string readable) public static string GetIndex(string readable)
+87 -65
View File
@@ -33,58 +33,70 @@ public partial class Robot : Node3D
if (isExecuting) if (isExecuting)
{ {
if (CanExecute(delta)) UpdateExecution(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";
} }
else else
{ {
CoolDown( UpdateIdle(delta);
delta,
GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond)
* TypeStats.CoolingMultiplier
);
} }
Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer; 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<ProgramNode> nodes) public void SetupExecution(List<ProgramNode> nodes)
{ {
if (nodes.Count <= 0) return; if (nodes.Count <= 0) return;
@@ -200,32 +212,13 @@ public partial class Robot : Node3D
return false; return false;
} }
float energyUse = if (!TryConsumeEnergy(delta))
GameData.robotStats.GetEnergyUse(EnergyUsePerSecond)
* TypeStats.EnergyUseMultiplier
* (float)delta;
if (!GameData.survival.TryConsumeEnergy(energyUse))
{ {
currentMessage = "Not enough energy"; currentMessage = "Not enough energy";
return false; return false;
} }
heat = Math.Clamp( ApplyWear(delta);
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
);
if (heat >= 100f) if (heat >= 100f)
{ {
@@ -244,6 +237,35 @@ public partial class Robot : Node3D
return true; 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) private void CoolDown(double delta, float heatLossPerSecond)
{ {
heat = Math.Clamp( heat = Math.Clamp(
-94
View File
@@ -629,98 +629,4 @@ public partial class TestRunner : Node
AssertTrue(hasStartNode, "start node prefab loaded"); AssertTrue(hasStartNode, "start node prefab loaded");
} }
private Layer CreateTestLayer(int level, string collapsedMesh)
{
Layer layer = new Layer
{
level = level,
currentResources = new List<string> { "stone" },
gateIngredients = new List<Ingredient>(),
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>(T expected, T actual, string message)
{
if (!EqualityComparer<T>.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}");
}
}
} }
+101
View File
@@ -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<string> { "stone" },
gateIngredients = new List<Ingredient>(),
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>(T expected, T actual, string message)
{
if (!EqualityComparer<T>.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}");
}
}
}
+1
View File
@@ -0,0 +1 @@
uid://dn38ysswhpy04
+4 -9
View File
@@ -17,8 +17,7 @@ public partial class Camera3d : Camera3D
{ {
Control focused = GetViewport().GuiGetFocusOwner(); Control focused = GetViewport().GuiGetFocusOwner();
if (focused is LineEdit || focused is TextEdit) if (focused is LineEdit || focused is TextEdit) return;
return;
if (canMove) MoveCamera(delta); if (canMove) MoveCamera(delta);
} }
@@ -38,17 +37,13 @@ public partial class Camera3d : Camera3D
if (direction != Vector3.Zero) if (direction != Vector3.Zero)
{ {
if(robot != null) robot = null; robot = null;
direction = direction.Normalized() * Speed * (Input.IsActionPressed("sprint") ? 2.5f : 1) * d; direction = direction.Normalized() * Speed * (Input.IsActionPressed("sprint") ? 2.5f : 1) * d;
Translate(direction); 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) if (Position.Y != 10 - visibleLayer * 4)
+20 -103
View File
@@ -1,5 +1,3 @@
using System;
using System.Diagnostics;
using Godot; using Godot;
public partial class UIHandler : Control public partial class UIHandler : Control
@@ -46,10 +44,7 @@ public partial class UIHandler : Control
DisplayStats(); DisplayStats();
DisplayRobotAlarm(); DisplayRobotAlarm();
Control focused = GetViewport().GuiGetFocusOwner(); if (IsTextInputFocused()) return;
if (focused is LineEdit || focused is TextEdit)
return;
if (Input.IsActionJustPressed("map")) HandleMapButton(); if (Input.IsActionJustPressed("map")) HandleMapButton();
if (Input.IsActionJustPressed("menu")) HandleMenuButton(); if (Input.IsActionJustPressed("menu")) HandleMenuButton();
@@ -58,22 +53,29 @@ public partial class UIHandler : Control
if (Input.IsActionJustPressed("research")) HandleResearchButton(); if (Input.IsActionJustPressed("research")) HandleResearchButton();
} }
private bool IsTextInputFocused()
{
Control focused = GetViewport().GuiGetFocusOwner();
return focused is LineEdit || focused is TextEdit;
}
public void HandleMenuButton() public void HandleMenuButton()
{ {
if(GameData.survival.isDead) return; if (GameData.survival.isDead) return;
OpenUIElement(menu); OpenUIElement(menu);
GameData.isPaused = menu.Visible || options.Visible; GameData.isPaused = menu.Visible || options.Visible;
} }
public void HandleMenu() public void HandleMenu()
{ {
if(GameData.survival.isDead) return;
HandleMenuButton(); HandleMenuButton();
} }
public void ShowOptions() public void ShowOptions()
{ {
if(GameData.survival.isDead) return; if (GameData.survival.isDead) return;
menu.Hide(); menu.Hide();
OpenUIElement(options); OpenUIElement(options);
GameData.isPaused = options.Visible; GameData.isPaused = options.Visible;
@@ -81,14 +83,16 @@ public partial class UIHandler : Control
public void HandleMapButton() public void HandleMapButton()
{ {
if(GameData.survival.isDead) return; if (GameData.survival.isDead) return;
OpenUIElement(map); OpenUIElement(map);
if (map.Visible) map.ShowMap(); if (map.Visible) map.ShowMap();
} }
public void HandleRobotListButton() public void HandleRobotListButton()
{ {
if(GameData.survival.isDead) return; if (GameData.survival.isDead) return;
receivedRobotFollowSignal = false; receivedRobotFollowSignal = false;
receivedRobotJumpSignal = false; receivedRobotJumpSignal = false;
OpenUIElement(robotList); OpenUIElement(robotList);
@@ -96,28 +100,19 @@ public partial class UIHandler : Control
public void HandleInventoryButton() public void HandleInventoryButton()
{ {
if(GameData.survival.isDead) return; if (GameData.survival.isDead) return;
OpenUIElement(inventory); OpenUIElement(inventory);
} }
public void HandleResearchButton() public void HandleResearchButton()
{ {
if(GameData.survival.isDead) return; if (GameData.survival.isDead) return;
OpenUIElement(researchList); OpenUIElement(researchList);
if (researchList.Visible) researchList.SetupGraph(); 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() public void ExitGame()
{ {
GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn"); GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn");
@@ -139,14 +134,7 @@ public partial class UIHandler : Control
public void OpenUIElement(Control element) public void OpenUIElement(Control element)
{ {
SoundManager.PlayButton(); SoundManager.PlayButton();
if (element.Visible) element.Visible = !element.Visible;
{
element.Hide();
}
else
{
element.Show();
}
HideUIElements(element); 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) private void OnRobotJumpTo(Robot robot)
{ {
if (receivedRobotJumpSignal) return; if (receivedRobotJumpSignal) return;
@@ -201,42 +170,6 @@ public partial class UIHandler : Control
mainCam.Follow(robot); 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() public void UnlockLayer()
{ {
if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1)) if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1))
@@ -259,20 +192,4 @@ public partial class UIHandler : Control
} }
} }
public void ShowGameOver()
{
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
}
public void ShowGameOver(string message)
{
if (gameOver.Visible) return;
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
gameOver.Show();
}
public void HideGameOver()
{
gameOver.Hide();
}
} }
+98
View File
@@ -0,0 +1,98 @@
using System;
using System.Diagnostics;
using Godot;
public partial class UIHandler
{
public void DisplayStats()
{
FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS";
RAM.Text = GetMemoryDisplay();
DisplaySurvivalStats();
DisplayWorldStats();
DisplayLoseCondition();
}
private string GetMemoryDisplay()
{
double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024);
if (memory > 1024)
{
return Math.Round(memory / 1024, 2).ToString() + " GB";
}
return memory.ToString() + " MB";
}
private void DisplayRobotAlarm()
{
string messages = "";
if (GameData.survival.isDead)
{
messages += GameData.survival.currentStatus + "\r";
}
foreach (Robot robot in GameData.robots)
{
if (robot.currentMessage.Length > 0)
{
messages += $"{robot.Name}: {robot.currentMessage}\r";
}
}
robotAlarm.Visible = messages.Length > 0;
robotAlarm.TooltipText = messages;
}
private void DisplaySurvivalStats()
{
energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}";
waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}";
hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}";
survivalStatus.Text = GameData.survival.currentStatus;
survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical")
? UIStyle.GetWarningColor()
: Colors.White;
if (GameData.survival.isDead)
{
ShowGameOver();
}
}
private void DisplayWorldStats()
{
currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}";
deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}";
if (GameData.lowestLayer == GameData.ruinSize)
{
unlockLayer.Visible = false;
return;
}
unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients();
unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1);
}
private void DisplayLoseCondition()
{
if (!GameData.HasNoRobotRecovery()) return;
ShowGameOver("No robots remain and no robot can be spawned from inventory.");
}
public void ShowGameOver()
{
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
}
public void ShowGameOver(string message)
{
if (gameOver.Visible) return;
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
gameOver.Show();
}
public void HideGameOver()
{
gameOver.Hide();
}
}
@@ -0,0 +1 @@
uid://b2q7e88lokg3l
+70 -327
View File
@@ -13,15 +13,6 @@ public partial class CodingWindow : PanelContainer
[Export] LineEdit nameInput; [Export] LineEdit nameInput;
public System.Collections.Generic.Dictionary<ProgramNode, PackedScene> DSLNodes; public System.Collections.Generic.Dictionary<ProgramNode, PackedScene> DSLNodes;
private System.Collections.Generic.Dictionary<StringName, ProgramNode> availableNodes;
private class ScriptConnection
{
public string FromNodeId;
public int FromPort;
public string ToNodeId;
public int ToPort;
}
public override void _Ready() public override void _Ready()
{ {
@@ -51,6 +42,7 @@ public partial class CodingWindow : PanelContainer
{ {
availableScripts.Clear(); availableScripts.Clear();
availableScripts.AddItem("Select script to load..."); availableScripts.AddItem("Select script to load...");
List<string> scripts = FileHandler.LoadProgramNames(); List<string> scripts = FileHandler.LoadProgramNames();
scripts.Sort((a, b) => a.CompareTo(b)); scripts.Sort((a, b) => a.CompareTo(b));
foreach (string script in scripts) foreach (string script in scripts)
@@ -73,10 +65,9 @@ public partial class CodingWindow : PanelContainer
public void GenerateCodingBlocks() public void GenerateCodingBlocks()
{ {
Button nodeListButton;
foreach (ProgramNode nodeTemplate in DSLNodes.Keys) foreach (ProgramNode nodeTemplate in DSLNodes.Keys)
{ {
nodeListButton = new Button Button nodeListButton = new Button
{ {
Name = nodeTemplate.DisplayText, Name = nodeTemplate.DisplayText,
Text = nodeTemplate.DisplayText Text = nodeTemplate.DisplayText
@@ -92,17 +83,14 @@ public partial class CodingWindow : PanelContainer
private void AddEditorNode(ProgramNode node) private void AddEditorNode(ProgramNode node)
{ {
NodeDisplay editorDisplay = DSLNodes[node].Instantiate<NodeDisplay>(); NodeDisplay editorDisplay = DSLNodes[node].Instantiate<NodeDisplay>();
editorDisplay.PositionOffset = (editorWindow.ScrollOffset + editorWindow.Size / 2) / editorWindow.Zoom - editorDisplay.Size / 2; editorDisplay.PositionOffset = GetVisibleGraphCenter() - editorDisplay.Size / 2f;
editorWindow.AddChild(editorDisplay); editorWindow.AddChild(editorDisplay);
RegisterEditorNode(editorDisplay); RegisterEditorNode(editorDisplay);
} }
private void MoveNodeToVisibleGraphCenter(NodeDisplay nodeDisplay) private Vector2 GetVisibleGraphCenter()
{ {
Vector2 visibleCenter = editorWindow.ScrollOffset return (editorWindow.ScrollOffset + editorWindow.Size / 2f) / editorWindow.Zoom;
+ editorWindow.Size / (2f * editorWindow.Zoom);
nodeDisplay.PositionOffset = visibleCenter - nodeDisplay.Size / (2f * editorWindow.Zoom);
} }
private void RegisterEditorNode(NodeDisplay editorDisplay) private void RegisterEditorNode(NodeDisplay editorDisplay)
@@ -115,6 +103,13 @@ public partial class CodingWindow : PanelContainer
} }
public void ClearWindow() public void ClearWindow()
{
DisconnectAllNodes();
RemoveEditorNodes();
scriptName.Text = "";
}
private void DisconnectAllNodes()
{ {
foreach (Dictionary connection in editorWindow.GetConnectionList()) foreach (Dictionary connection in editorWindow.GetConnectionList())
{ {
@@ -125,109 +120,41 @@ public partial class CodingWindow : PanelContainer
(int)connection["to_port"] (int)connection["to_port"]
); );
} }
}
private void RemoveEditorNodes()
{
foreach (Node child in editorWindow.GetChildren()) foreach (Node child in editorWindow.GetChildren())
{ {
if (child is GraphNode) if (child is not GraphNode) continue;
{
editorWindow.RemoveChild(child); editorWindow.RemoveChild(child);
child.QueueFree(); child.QueueFree();
}
} }
scriptName.Text = "";
} }
public void CompileProgram() public void CompileProgram()
{ {
if (robot == null) return; if (robot == null) return;
NodeDisplay startNode = FindStartNode(); ScriptGraphCompiler compiler = new ScriptGraphCompiler(editorWindow);
if (startNode == null) string errorMessage;
List<ProgramNode> nodes = compiler.BuildProgram(out errorMessage);
if (errorMessage.Length > 0)
{ {
robot.StopExecution("(FAILED) Script needs exactly one Start node"); robot.StopExecution(errorMessage);
return; return;
} }
BuildAvailableNodeLookup();
List<ProgramNode> nodes = BuildScriptOrder(
startNode,
new List<ProgramNode>(),
new HashSet<StringName>()
);
if (nodes.Count > 0) robot.SetupExecution(nodes); if (nodes.Count > 0) robot.SetupExecution(nodes);
robot.currentProgram = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text; robot.currentProgram = GetCurrentScriptName();
} }
private void BuildAvailableNodeLookup() private string GetCurrentScriptName()
{ {
availableNodes = new System.Collections.Generic.Dictionary<StringName, ProgramNode>(); if (scriptName.Text.Length > 0) return scriptName.Text;
for (int i = 0; i < editorWindow.GetChildCount(); i++) return $"Script{availableScripts.ItemCount}";
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
availableNodes.Add(nodeDisplay.Name, nodeDisplay.node);
}
}
private List<ProgramNode> BuildScriptOrder(
NodeDisplay node,
List<ProgramNode> program,
HashSet<StringName> visitedNodes
)
{
if (node == null) return program;
if (visitedNodes.Contains(node.Name)) return program;
visitedNodes.Add(node.Name);
program.Add(node.node);
if (editorWindow.GetConnectionListFromNode(node.Name).Count <= 0) return program;
List<Dictionary> nextConnections = CheckNodeConnections(node);
if (nextConnections.Count <= 0) return program;
node.node.SetNextNode(nextConnections, availableNodes);
foreach (Dictionary connection in nextConnections)
{
NodeDisplay nextNode = editorWindow.GetNodeOrNull<NodeDisplay>(
new NodePath(connection["to_node"].AsStringName())
);
program = BuildScriptOrder(
nextNode,
program,
visitedNodes
);
}
return program;
}
private List<Dictionary> CheckNodeConnections(NodeDisplay node)
{
List<Dictionary> result = new List<Dictionary>();
Array<Dictionary> connections = editorWindow.GetConnectionListFromNode(node.Name);
for (int i = 0; i < connections.Count; i++)
{
if (connections[i]["from_node"].AsStringName() == node.Name)
{
result.Add(connections[i]);
}
}
return result;
}
private NodeDisplay FindStartNode()
{
NodeDisplay startNode = null;
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
if (nodeDisplay.node is not StartNode) continue;
if (startNode != null) return null;
startNode = nodeDisplay;
}
return startNode;
} }
public void SetRobot(Robot robot) public void SetRobot(Robot robot)
@@ -241,131 +168,15 @@ public partial class CodingWindow : PanelContainer
ClearWindow(); ClearWindow();
string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index)); string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index));
LoadStructuredProgram(scriptContent); CreateSerializer().Load(scriptContent);
scriptName.Text = availableScripts.GetItemText(index); scriptName.Text = availableScripts.GetItemText(index);
availableScripts.Select(0); availableScripts.Select(0);
} }
private void LoadStructuredProgram(string scriptContent) private void AddLoadedNode(NodeDisplay nodeDisplay)
{ {
Variant parsedScript = Json.ParseString(scriptContent); editorWindow.AddChild(nodeDisplay);
if (parsedScript.VariantType != Variant.Type.Dictionary) return; RegisterEditorNode(nodeDisplay);
Dictionary scriptData = parsedScript.AsGodotDictionary();
if (!scriptData.ContainsKey("Nodes")) return;
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes =
new System.Collections.Generic.Dictionary<string, NodeDisplay>();
Array nodes = scriptData["Nodes"].AsGodotArray();
for (int i = 0; i < nodes.Count; i++)
{
Dictionary nodeData = nodes[i].AsGodotDictionary();
NodeDisplay nodeDisplay = LoadStructuredNode(nodeData);
if (nodeDisplay == null) continue;
editorWindow.AddChild(nodeDisplay);
RegisterEditorNode(nodeDisplay);
RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes);
}
LoadStructuredConnections(scriptData, loadedNodes);
}
private void RegisterLoadedNode(
Dictionary nodeData,
NodeDisplay nodeDisplay,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
string nodeId = nodeDisplay.Name.ToString();
if (nodeData.ContainsKey("Id"))
{
nodeId = nodeData["Id"].AsString();
}
if (!loadedNodes.ContainsKey(nodeId))
{
loadedNodes.Add(nodeId, nodeDisplay);
}
}
private NodeDisplay LoadStructuredNode(Dictionary nodeData)
{
if (!nodeData.ContainsKey("Type")) return null;
if (!nodeData.ContainsKey("Content")) return null;
string type = nodeData["Type"].AsString();
string content = nodeData["Content"].AsString();
NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, DSLNodes);
if (nodeDisplay == null) return null;
if (nodeData.ContainsKey("Id"))
{
nodeDisplay.Name = nodeData["Id"].AsString();
}
if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY"))
{
float positionX = (float)nodeData["PositionX"].AsDouble();
float positionY = (float)nodeData["PositionY"].AsDouble();
nodeDisplay.PositionOffset = new Vector2(positionX, positionY);
}
return nodeDisplay;
}
private void LoadStructuredConnections(
Dictionary scriptData,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
if (!scriptData.ContainsKey("Connections")) return;
Array connectionData = scriptData["Connections"].AsGodotArray();
for (int i = 0; i < connectionData.Count; i++)
{
Dictionary savedConnection = connectionData[i].AsGodotDictionary();
if (!savedConnection.ContainsKey("From")) continue;
if (!savedConnection.ContainsKey("To")) continue;
if (!savedConnection.ContainsKey("FromPort")) continue;
if (!savedConnection.ContainsKey("ToPort")) continue;
ScriptConnection connection = new ScriptConnection
{
FromNodeId = savedConnection["From"].AsString(),
FromPort = savedConnection["FromPort"].AsInt32(),
ToNodeId = savedConnection["To"].AsString(),
ToPort = savedConnection["ToPort"].AsInt32()
};
if (!loadedNodes.ContainsKey(connection.FromNodeId)) continue;
if (!loadedNodes.ContainsKey(connection.ToNodeId)) continue;
NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId];
NodeDisplay toDisplay = loadedNodes[connection.ToNodeId];
if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) continue;
editorWindow.ConnectNode(
fromDisplay.Name,
connection.FromPort,
toDisplay.Name,
connection.ToPort
);
}
}
private bool ConnectionExists(StringName fromNode, int fromPort, StringName toNode, int toPort)
{
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
if (connection["from_node"].AsStringName() != fromNode) continue;
if ((int)connection["from_port"] != fromPort) continue;
if (connection["to_node"].AsStringName() != toNode) continue;
if ((int)connection["to_port"] != toPort) continue;
return true;
}
return false;
} }
public void LoadTemporaryProgram() public void LoadTemporaryProgram()
@@ -373,65 +184,16 @@ public partial class CodingWindow : PanelContainer
if (robot == null) return; if (robot == null) return;
if (robot.currentNode == null) return; if (robot.currentNode == null) return;
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes = RunningProgramGraphBuilder builder = new RunningProgramGraphBuilder(
new System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay>(); DSLNodes,
LoadTemporaryNode(robot.currentNode, loadedNodes); AddLoadedNode,
ConnectNodes
);
builder.Load(robot.currentNode);
scriptName.Text = robot.currentProgram ?? ""; scriptName.Text = robot.currentProgram ?? "";
} }
private NodeDisplay LoadTemporaryNode(
ProgramNode programNode,
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
if (programNode == null) return null;
if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode];
NodeDisplay nodeDisplay = NodeDisplay.Load(
programNode.DisplayText,
programNode.Save(),
DSLNodes
);
if (nodeDisplay == null) return null;
editorWindow.AddChild(nodeDisplay);
RegisterEditorNode(nodeDisplay);
loadedNodes.Add(programNode, nodeDisplay);
ConnectTemporaryNode(nodeDisplay, 0, programNode.nextNode, loadedNodes);
ConnectTemporaryNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes);
return nodeDisplay;
}
private void ConnectTemporaryNode(
NodeDisplay fromDisplay,
int fromPort,
ProgramNode targetNode,
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
NodeDisplay toDisplay = LoadTemporaryNode(targetNode, loadedNodes);
if (toDisplay == null) return;
ScriptConnection connection = new ScriptConnection
{
FromNodeId = fromDisplay.Name.ToString(),
FromPort = fromPort,
ToNodeId = toDisplay.Name.ToString(),
ToPort = 0
};
if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) return;
editorWindow.ConnectNode(
fromDisplay.Name,
connection.FromPort,
toDisplay.Name,
connection.ToPort
);
}
public void DeleteProgram() public void DeleteProgram()
{ {
string filename = scriptName.Text; string filename = scriptName.Text;
@@ -450,69 +212,50 @@ public partial class CodingWindow : PanelContainer
public void SaveProgram() public void SaveProgram()
{ {
Array<Dictionary> savedNodes = BuildSavedNodes(); string result = CreateSerializer().Save();
if (savedNodes.Count <= 0) return;
Dictionary scriptData = new Dictionary();
scriptData["Nodes"] = savedNodes;
scriptData["Connections"] = BuildSavedConnections();
string result = Json.Stringify(scriptData);
if (result.Length <= 0) return; 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(); SetupScriptOptions();
} }
private Array<Dictionary> BuildSavedNodes()
{
Array<Dictionary> savedNodes = new Array<Dictionary>();
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
Dictionary savedNode = new Dictionary();
savedNode["Id"] = nodeDisplay.Name.ToString();
savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower();
savedNode["Content"] = nodeDisplay.node.Save();
savedNode["PositionX"] = nodeDisplay.PositionOffset.X;
savedNode["PositionY"] = nodeDisplay.PositionOffset.Y;
savedNodes.Add(savedNode);
}
return savedNodes;
}
private Array<Dictionary> BuildSavedConnections()
{
Array<Dictionary> savedConnections = new Array<Dictionary>();
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
Dictionary savedConnection = new Dictionary();
savedConnection["From"] = connection["from_node"].AsStringName().ToString();
savedConnection["FromPort"] = (int)connection["from_port"];
savedConnection["To"] = connection["to_node"].AsStringName().ToString();
savedConnection["ToPort"] = (int)connection["to_port"];
savedConnections.Add(savedConnection);
}
return savedConnections;
}
public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort) public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort)
{ {
if (to == from) return; if (to == from) return;
foreach (Dictionary connection in editorWindow.GetConnectionList()) ConnectNodes(from, fromPort, to, toPort);
{ }
if (connection["from_node"].AsStringName() == from && (int)connection["from_port"] == fromPort) return;
} private void ConnectNodes(StringName from, int fromPort, StringName to, int toPort)
{
if (HasOutputConnection(from, fromPort)) return;
editorWindow.ConnectNode(from, fromPort, to, toPort); 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) public void OnNodeDisconnect(StringName from, int fromPort, StringName to, int toPort)
{ {
editorWindow.DisconnectNode(from, fromPort, to, toPort); editorWindow.DisconnectNode(from, fromPort, to, toPort);
@@ -0,0 +1,65 @@
using Godot;
using System;
using System.Collections.Generic;
public class RunningProgramGraphBuilder
{
private readonly Dictionary<ProgramNode, PackedScene> dslNodes;
private readonly Action<NodeDisplay> addNode;
private readonly Action<StringName, int, StringName, int> connectNodes;
public RunningProgramGraphBuilder(
Dictionary<ProgramNode, PackedScene> dslNodes,
Action<NodeDisplay> addNode,
Action<StringName, int, StringName, int> connectNodes
)
{
this.dslNodes = dslNodes;
this.addNode = addNode;
this.connectNodes = connectNodes;
}
public void Load(ProgramNode startNode)
{
Dictionary<ProgramNode, NodeDisplay> loadedNodes =
new Dictionary<ProgramNode, NodeDisplay>();
LoadNode(startNode, loadedNodes);
}
private NodeDisplay LoadNode(
ProgramNode programNode,
Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
if (programNode == null) return null;
if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode];
NodeDisplay nodeDisplay = NodeDisplay.Load(
programNode.DisplayText,
programNode.Save(),
dslNodes
);
if (nodeDisplay == null) return null;
addNode(nodeDisplay);
loadedNodes.Add(programNode, nodeDisplay);
ConnectNode(nodeDisplay, 0, programNode.nextNode, loadedNodes);
ConnectNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes);
return nodeDisplay;
}
private void ConnectNode(
NodeDisplay fromDisplay,
int fromPort,
ProgramNode targetNode,
Dictionary<ProgramNode, NodeDisplay> loadedNodes
)
{
NodeDisplay toDisplay = LoadNode(targetNode, loadedNodes);
if (toDisplay == null) return;
connectNodes(fromDisplay.Name, fromPort, toDisplay.Name, 0);
}
}
@@ -0,0 +1 @@
uid://deaptod52fe2s
+7
View File
@@ -0,0 +1,7 @@
public class ScriptConnection
{
public string FromNodeId;
public int FromPort;
public string ToNodeId;
public int ToPort;
}
+1
View File
@@ -0,0 +1 @@
uid://cvhobhufyp2ni
+108
View File
@@ -0,0 +1,108 @@
using Godot;
using Godot.Collections;
using System.Collections.Generic;
public class ScriptGraphCompiler
{
private readonly GraphEdit editorWindow;
private System.Collections.Generic.Dictionary<StringName, ProgramNode> availableNodes;
public ScriptGraphCompiler(GraphEdit editorWindow)
{
this.editorWindow = editorWindow;
}
public List<ProgramNode> BuildProgram(out string errorMessage)
{
errorMessage = "";
NodeDisplay startNode = FindStartNode();
if (startNode == null)
{
errorMessage = "(FAILED) Script needs exactly one Start node";
return new List<ProgramNode>();
}
BuildAvailableNodeLookup();
return BuildScriptOrder(
startNode,
new List<ProgramNode>(),
new HashSet<StringName>()
);
}
private void BuildAvailableNodeLookup()
{
availableNodes = new System.Collections.Generic.Dictionary<StringName, ProgramNode>();
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
availableNodes.Add(nodeDisplay.Name, nodeDisplay.node);
}
}
private List<ProgramNode> BuildScriptOrder(
NodeDisplay node,
List<ProgramNode> program,
HashSet<StringName> visitedNodes
)
{
if (node == null) return program;
if (visitedNodes.Contains(node.Name)) return program;
visitedNodes.Add(node.Name);
program.Add(node.node);
List<Dictionary> nextConnections = GetOutgoingConnections(node);
if (nextConnections.Count <= 0) return program;
node.node.SetNextNode(nextConnections, availableNodes);
foreach (Dictionary connection in nextConnections)
{
NodeDisplay nextNode = editorWindow.GetNodeOrNull<NodeDisplay>(
new NodePath(connection["to_node"].AsStringName())
);
program = BuildScriptOrder(
nextNode,
program,
visitedNodes
);
}
return program;
}
private List<Dictionary> GetOutgoingConnections(NodeDisplay node)
{
List<Dictionary> result = new List<Dictionary>();
Array<Dictionary> connections = editorWindow.GetConnectionListFromNode(node.Name);
for (int i = 0; i < connections.Count; i++)
{
if (connections[i]["from_node"].AsStringName() == node.Name)
{
result.Add(connections[i]);
}
}
return result;
}
private NodeDisplay FindStartNode()
{
NodeDisplay startNode = null;
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
if (nodeDisplay.node is not StartNode) continue;
if (startNode != null) return null;
startNode = nodeDisplay;
}
return startNode;
}
}
@@ -0,0 +1 @@
uid://d122nx50nj3wc
+189
View File
@@ -0,0 +1,189 @@
using Godot;
using Godot.Collections;
using System;
public class ScriptGraphSerializer
{
private readonly GraphEdit editorWindow;
private readonly System.Collections.Generic.Dictionary<ProgramNode, PackedScene> dslNodes;
private readonly Action<NodeDisplay> addNode;
private readonly Action<StringName, int, StringName, int> connectNodes;
public ScriptGraphSerializer(
GraphEdit editorWindow,
System.Collections.Generic.Dictionary<ProgramNode, PackedScene> dslNodes,
Action<NodeDisplay> addNode,
Action<StringName, int, StringName, int> connectNodes
)
{
this.editorWindow = editorWindow;
this.dslNodes = dslNodes;
this.addNode = addNode;
this.connectNodes = connectNodes;
}
public void Load(string scriptContent)
{
Variant parsedScript = Json.ParseString(scriptContent);
if (parsedScript.VariantType != Variant.Type.Dictionary) return;
Dictionary scriptData = parsedScript.AsGodotDictionary();
if (!scriptData.ContainsKey("Nodes")) return;
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes =
new System.Collections.Generic.Dictionary<string, NodeDisplay>();
Godot.Collections.Array nodes = scriptData["Nodes"].AsGodotArray();
for (int i = 0; i < nodes.Count; i++)
{
Dictionary nodeData = nodes[i].AsGodotDictionary();
NodeDisplay nodeDisplay = LoadNode(nodeData);
if (nodeDisplay == null) continue;
addNode(nodeDisplay);
RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes);
}
LoadConnections(scriptData, loadedNodes);
}
public string Save()
{
Array<Dictionary> savedNodes = BuildSavedNodes();
if (savedNodes.Count <= 0) return "";
Dictionary scriptData = new Dictionary();
scriptData["Nodes"] = savedNodes;
scriptData["Connections"] = BuildSavedConnections();
return Json.Stringify(scriptData);
}
private NodeDisplay LoadNode(Dictionary nodeData)
{
if (!nodeData.ContainsKey("Type")) return null;
if (!nodeData.ContainsKey("Content")) return null;
string type = nodeData["Type"].AsString();
string content = nodeData["Content"].AsString();
NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, dslNodes);
if (nodeDisplay == null) return null;
if (nodeData.ContainsKey("Id"))
{
nodeDisplay.Name = nodeData["Id"].AsString();
}
if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY"))
{
float positionX = (float)nodeData["PositionX"].AsDouble();
float positionY = (float)nodeData["PositionY"].AsDouble();
nodeDisplay.PositionOffset = new Vector2(positionX, positionY);
}
return nodeDisplay;
}
private void RegisterLoadedNode(
Dictionary nodeData,
NodeDisplay nodeDisplay,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
string nodeId = nodeDisplay.Name.ToString();
if (nodeData.ContainsKey("Id"))
{
nodeId = nodeData["Id"].AsString();
}
if (!loadedNodes.ContainsKey(nodeId))
{
loadedNodes.Add(nodeId, nodeDisplay);
}
}
private void LoadConnections(
Dictionary scriptData,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
if (!scriptData.ContainsKey("Connections")) return;
Godot.Collections.Array connectionData = scriptData["Connections"].AsGodotArray();
for (int i = 0; i < connectionData.Count; i++)
{
Dictionary savedConnection = connectionData[i].AsGodotDictionary();
if (!IsConnectionDataValid(savedConnection)) continue;
ScriptConnection connection = CreateConnection(savedConnection);
ConnectLoadedNodes(connection, loadedNodes);
}
}
private bool IsConnectionDataValid(Dictionary savedConnection)
{
return savedConnection.ContainsKey("From")
&& savedConnection.ContainsKey("To")
&& savedConnection.ContainsKey("FromPort")
&& savedConnection.ContainsKey("ToPort");
}
private ScriptConnection CreateConnection(Dictionary savedConnection)
{
return new ScriptConnection
{
FromNodeId = savedConnection["From"].AsString(),
FromPort = savedConnection["FromPort"].AsInt32(),
ToNodeId = savedConnection["To"].AsString(),
ToPort = savedConnection["ToPort"].AsInt32()
};
}
private void ConnectLoadedNodes(
ScriptConnection connection,
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
)
{
if (!loadedNodes.ContainsKey(connection.FromNodeId)) return;
if (!loadedNodes.ContainsKey(connection.ToNodeId)) return;
NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId];
NodeDisplay toDisplay = loadedNodes[connection.ToNodeId];
connectNodes(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort);
}
private Array<Dictionary> BuildSavedNodes()
{
Array<Dictionary> savedNodes = new Array<Dictionary>();
for (int i = 0; i < editorWindow.GetChildCount(); i++)
{
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
if (nodeDisplay == null) continue;
nodeDisplay.ReadParameters();
Dictionary savedNode = new Dictionary();
savedNode["Id"] = nodeDisplay.Name.ToString();
savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower();
savedNode["Content"] = nodeDisplay.node.Save();
savedNode["PositionX"] = nodeDisplay.PositionOffset.X;
savedNode["PositionY"] = nodeDisplay.PositionOffset.Y;
savedNodes.Add(savedNode);
}
return savedNodes;
}
private Array<Dictionary> BuildSavedConnections()
{
Array<Dictionary> savedConnections = new Array<Dictionary>();
foreach (Dictionary connection in editorWindow.GetConnectionList())
{
Dictionary savedConnection = new Dictionary();
savedConnection["From"] = connection["from_node"].AsStringName().ToString();
savedConnection["FromPort"] = (int)connection["from_port"];
savedConnection["To"] = connection["to_node"].AsStringName().ToString();
savedConnection["ToPort"] = (int)connection["to_port"];
savedConnections.Add(savedConnection);
}
return savedConnections;
}
}
@@ -0,0 +1 @@
uid://dsrkrw6524c
+21 -12
View File
@@ -3,7 +3,8 @@ using Godot;
public partial class InventoryDisplay : PanelContainer public partial class InventoryDisplay : PanelContainer
{ {
private PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay(); private readonly PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay();
[Export] VBoxContainer itemList; [Export] VBoxContainer itemList;
[Export] RichTextLabel inventorySpace; [Export] RichTextLabel inventorySpace;
@@ -34,24 +35,32 @@ public partial class InventoryDisplay : PanelContainer
} }
public void ReloadItems() public void ReloadItems()
{
ClearItems();
foreach (Item item in GameData.inventory.items)
{
itemList.AddChild(CreateItemDisplay(item));
}
}
private void ClearItems()
{ {
foreach (Node node in itemList.GetChildren()) foreach (Node node in itemList.GetChildren())
{ {
itemList.RemoveChild(node); itemList.RemoveChild(node);
node.QueueFree(); node.QueueFree();
} }
}
ItemDisplay display; private ItemDisplay CreateItemDisplay(Item item)
{
foreach (Item item in GameData.inventory.items) ItemDisplay display = itemDisplayPrefab.Instantiate<ItemDisplay>();
{ display.item = item;
display = itemDisplayPrefab.Instantiate<ItemDisplay>(); display.text.Text = item.data.GetReadableName();
display.item = item; display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}";
display.text.Text = item.data.GetReadableName(); display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}"; return display;
display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
itemList.AddChild(display);
}
} }
public void OnInventoryUpdate(object sender, EventArgs args) public void OnInventoryUpdate(object sender, EventArgs args)
+73 -69
View File
@@ -2,82 +2,86 @@ using Godot;
public partial class OptionsMenu : PanelContainer 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 OptionButton screenMode;
[Export] private HSlider soundVolume; [Export] private HSlider soundVolume;
[Export] private ColorPickerButton lightColor; [Export] private ColorPickerButton lightColor;
public override void _Ready() public override void _Ready()
{ {
CenterPanel(); CenterPanel();
SetupScreenModes(); SetupScreenModes();
screenMode.Select(GameData.screenMode); screenMode.Select(GameData.screenMode);
soundVolume.Value = GameData.soundVolume * 100f; soundVolume.Value = GameData.soundVolume * 100f;
lightColor.Color = GameData.lightColor; lightColor.Color = GameData.lightColor;
ApplyScreenMode(GameData.screenMode); ApplyScreenMode(GameData.screenMode);
SoundManager.SetMasterVolume(GameData.soundVolume); SoundManager.SetMasterVolume(GameData.soundVolume);
} }
private void SetupScreenModes() private void SetupScreenModes()
{ {
screenMode.Clear(); screenMode.Clear();
screenMode.AddItem("Fullscreen"); screenMode.AddItem("Fullscreen");
screenMode.AddItem("Windowed"); screenMode.AddItem("Windowed");
screenMode.AddItem("Windowed Fullscreen"); screenMode.AddItem("Windowed Fullscreen");
screenMode.Select(2); screenMode.Select(2);
} }
public void OnScreenModeSelected(int index) public void OnScreenModeSelected(int index)
{ {
GameData.screenMode = index; GameData.screenMode = index;
ApplyScreenMode(index); ApplyScreenMode(index);
} }
private void ApplyScreenMode(int index) private void ApplyScreenMode(int index)
{ {
switch (index) switch (index)
{ {
case 0: case 0:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen); DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break; break;
case 1: case 1:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed); DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false); DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break; break;
case 2: case 2:
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen); DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true); DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
break; break;
} default:
} DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
break;
}
}
public void OnSoundVolumeChanged(double value) public void OnSoundVolumeChanged(double value)
{ {
GameData.soundVolume = (float)value / 100f; GameData.soundVolume = (float)value / 100f;
SoundManager.SetMasterVolume(GameData.soundVolume); SoundManager.SetMasterVolume(GameData.soundVolume);
} }
public void OnLightColorChanged(Color color) public void OnLightColorChanged(Color color)
{ {
GameData.lightColor = color; GameData.lightColor = color;
LightHandler.RedrawLights(color); LightHandler.RedrawLights(color);
} }
public void CloseOptions() public void CloseOptions()
{ {
Hide(); Hide();
GameData.isPaused = false; GameData.isPaused = false;
} }
private void CenterPanel() private void CenterPanel()
{ {
CustomMinimumSize = panelSize; CustomMinimumSize = panelSize;
SetAnchorsPreset(LayoutPreset.Center); SetAnchorsPreset(LayoutPreset.Center);
OffsetLeft = -panelSize.X / 2f; OffsetLeft = -panelSize.X / 2f;
OffsetTop = -panelSize.Y / 2f; OffsetTop = -panelSize.Y / 2f;
OffsetRight = panelSize.X / 2f; OffsetRight = panelSize.X / 2f;
OffsetBottom = panelSize.Y / 2f; OffsetBottom = panelSize.Y / 2f;
} }
} }
+32 -26
View File
@@ -10,7 +10,6 @@ public partial class ResearchList : PanelContainer
private bool hasArrangedNodes = false; private bool hasArrangedNodes = false;
private List<Research> currentResearch = new List<Research>(); private List<Research> currentResearch = new List<Research>();
private List<Research> toDelete = new List<Research>();
public override void _Ready() public override void _Ready()
{ {
@@ -18,30 +17,37 @@ public partial class ResearchList : PanelContainer
if (Visible) SetupGraph(); if (Visible) SetupGraph();
} }
public override void _Process(double delta) public override void _Process(double delta)
{ {
if(currentResearch.Count > 0) toDelete = new List<Research>(); if (currentResearch.Count <= 0) return;
List<Research> finishedResearch = new List<Research>();
foreach (Research research in currentResearch) foreach (Research research in currentResearch)
{ {
ResearchResult result = research.Execute(delta); ResearchResult result = research.Execute(delta);
if (result == ResearchResult.FINISHED) if (!IsResearchFinished(research, result)) continue;
{
toDelete.Add(research); finishedResearch.Add(research);
RecalculateResearchStates();
SetupGraph();
}
else if (result == ResearchResult.FAILED)
{
research.state = ResearchState.AVAILABLE;
toDelete.Add(research);
RecalculateResearchStates();
SetupGraph();
}
} }
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() public void SetupGraph()
@@ -83,11 +89,10 @@ public partial class ResearchList : PanelContainer
foreach (Node child in researchGraph.GetChildren()) foreach (Node child in researchGraph.GetChildren())
{ {
if (child is GraphNode) if (child is not GraphNode) continue;
{
researchGraph.RemoveChild(child); researchGraph.RemoveChild(child);
child.QueueFree(); child.QueueFree();
}
} }
} }
@@ -127,9 +132,10 @@ public partial class ResearchList : PanelContainer
private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state) private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state)
{ {
Research research = GameData.availableResearch[id];
Texture2D texture = GD.Load<Texture2D>(texturePath); Texture2D texture = GD.Load<Texture2D>(texturePath);
Color stateColor = GetColorByState(state); Color stateColor = GetColorByState(state);
string tooltipText = GetResearchTooltip(GameData.availableResearch[id]); string tooltipText = GetResearchTooltip(research);
TextureRect icon = new TextureRect TextureRect icon = new TextureRect
{ {
@@ -141,8 +147,8 @@ public partial class ResearchList : PanelContainer
Button button = new Button Button button = new Button
{ {
Text = GetResearchButtonText(GameData.availableResearch[id], state), Text = GetResearchButtonText(research, state),
Disabled = state != ResearchState.AVAILABLE || !GameData.availableResearch[id].CanStart(), Disabled = state != ResearchState.AVAILABLE || !research.CanStart(),
TooltipText = tooltipText TooltipText = tooltipText
}; };
+7 -2
View File
@@ -12,14 +12,19 @@ public partial class RobotDisplay : PanelContainer
public override void _Process(double delta) public override void _Process(double delta)
{ {
string programName = robot.currentProgram ?? ""; string status = GetStatusText();
string status = $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%";
if (status != currentScript.Text) if (status != currentScript.Text)
{ {
currentScript.Text = status; currentScript.Text = status;
} }
} }
private string GetStatusText()
{
string programName = robot.currentProgram ?? "";
return $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%";
}
public void OnJumpToClicked() public void OnJumpToClicked()
{ {
EmitSignal(SignalName.OnRobotJumpTo, robot); EmitSignal(SignalName.OnRobotJumpTo, robot);
+36 -22
View File
@@ -30,37 +30,50 @@ public partial class RobotList : PanelContainer
} }
public void ReloadRobots() public void ReloadRobots()
{
ClearRobotList();
foreach (Robot robotObject in GameData.robots)
{
robotList.AddChild(CreateRobotDisplay(robotObject));
}
}
private void ClearRobotList()
{ {
foreach (Node node in robotList.GetChildren()) foreach (Node node in robotList.GetChildren())
{ {
robotList.RemoveChild(node); robotList.RemoveChild(node);
node.QueueFree(); node.QueueFree();
} }
RobotDisplay display; }
foreach (Robot robotObject in GameData.robots) private RobotDisplay CreateRobotDisplay(Robot robotObject)
{ {
display = robotDisplayPrefab.Instantiate<RobotDisplay>(); RobotDisplay display = robotDisplayPrefab.Instantiate<RobotDisplay>();
display.robot = robotObject; display.robot = robotObject;
display.listItem.Text = robotObject.Name; display.listItem.Text = robotObject.Name;
display.OnRobotJumpTo += (robot) => display.OnRobotJumpTo += HandleRobotJumpTo;
{ display.OnRobotFollow += HandleRobotFollow;
EmitSignal(SignalName.OnRobotJumpTo, robot); return display;
Visible = false; }
};
display.OnRobotFollow += (robot) => private void HandleRobotJumpTo(Robot robot)
{ {
EmitSignal(SignalName.OnRobotFollow, robot); EmitSignal(SignalName.OnRobotJumpTo, robot);
Visible = false; Visible = false;
}; }
robotList.AddChild(display);
} private void HandleRobotFollow(Robot robot)
{
EmitSignal(SignalName.OnRobotFollow, robot);
Visible = false;
} }
public void ReloadSelectableRobots() public void ReloadSelectableRobots()
{ {
selectableRobots.Clear(); selectableRobots.Clear();
if(GameData.robots.Count >= GameData.maxRobotCount) if (GameData.robots.Count >= GameData.maxRobotCount)
{ {
selectableRobots.AddItem("You can't have more robots currently!"); selectableRobots.AddItem("You can't have more robots currently!");
selectableRobots.Disabled = true; selectableRobots.Disabled = true;
@@ -81,7 +94,8 @@ public partial class RobotList : PanelContainer
public void SpawnRobot() public void SpawnRobot()
{ {
if(spawnId.Length <= 0) return; if (spawnId.Length <= 0) return;
GameData.inventory.RemoveItem(spawnId, 1); GameData.inventory.RemoveItem(spawnId, 1);
Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>(); Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
robot.Name = $"Robot #{GameData.robots.Count}"; robot.Name = $"Robot #{GameData.robots.Count}";
@@ -97,8 +111,8 @@ public partial class RobotList : PanelContainer
public void OnRobotSelect(int index) 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)); spawnId = ItemData.GetIndex(selectableRobots.GetItemText(index));
} }
} }
+9 -9
View File
@@ -35,24 +35,24 @@ public partial class TutorialBubble : PanelContainer
{ {
"Welcome to the ruin. I am B.O.B.", "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.", "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.", "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 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.", "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", "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", "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.", "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.", "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]).", "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.", "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.", "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.", "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", "Deeper layers contain more advanced resources. 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", "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: 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", "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." "That is enough briefing. Build a loop, keep the robots alive, and open the lower gates. B.O.B. believes in organized chaos."
}; };
} }
+121
View File
@@ -0,0 +1,121 @@
using Godot;
using System.Collections.Generic;
using System.Linq;
public static class GateRequirementGenerator
{
public static void ApplyGateRequirements(Layer[] layers)
{
List<string> availableResources = new List<string>();
foreach (Layer layer in layers)
{
GateRequirementOptions options = BuildRequirementOptions(layer, availableResources);
ApplyLayerRequirements(layer, options);
}
}
private static GateRequirementOptions BuildRequirementOptions(Layer layer, List<string> 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<string> 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<ItemData> 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<ItemData> 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<ItemData> 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<ItemData> PossibleIngredients = new List<ItemData>();
public double HighestCraftTime = 0;
public double LowestCraftTime = double.MaxValue;
}
}
@@ -0,0 +1 @@
uid://bct2we4yxah6v
+4 -9
View File
@@ -16,7 +16,7 @@ public partial class Layer : Node3D
public Vector2I gateCoordinate; public Vector2I gateCoordinate;
public List<string> currentResources; public List<string> currentResources;
public bool isGateOpen = false; public bool isGateOpen = false;
public List<Ingredient> gateIngredients = new(); public List<Ingredient> gateIngredients = new List<Ingredient>();
public override void _Ready() public override void _Ready()
{ {
@@ -125,14 +125,12 @@ public partial class Layer : Node3D
for (int z = 0; z < layerSize; z++) for (int z = 0; z < layerSize; z++)
{ {
if (x == 0 && z == 0 && level == 0) continue; if (x == 0 && z == 0 && level == 0) continue;
if (!IsBorder(x, z)) if (!IsBorder(x, z)) continue;
continue;
Tile tile = tiles[x, z]; Tile tile = tiles[x, z];
List<string> possibilities = GetBorderPossibilities(x, z); List<string> possibilities = GetBorderPossibilities(x, z);
if (possibilities.Count == 0) if (possibilities.Count == 0) continue;
continue;
tile.Collapse(possibilities[GameData.rand.Next(possibilities.Count)]); tile.Collapse(possibilities[GameData.rand.Next(possibilities.Count)]);
Propagate(new Vector2I(x, z)); Propagate(new Vector2I(x, z));
@@ -147,7 +145,6 @@ public partial class Layer : Node3D
private void GenerateNecessaryTiles() private void GenerateNecessaryTiles()
{ {
//Generate spawn only in the first layer
if (level == 0) if (level == 0)
{ {
tiles[0, 0].Collapse("spawn"); tiles[0, 0].Collapse("spawn");
@@ -197,9 +194,7 @@ public partial class Layer : Node3D
safetyCounter++; safetyCounter++;
if (safetyCounter == layerSize * layerSize) return false; if (safetyCounter == layerSize * layerSize) return false;
} }
if (updateFailed) return false; return !updateFailed && WFC.IsMapConnected(tiles, 1f);
if (!WFC.IsMapConnected(tiles, 1f)) return false;
return true;
} }
private void Propagate(Vector2I startPos) private void Propagate(Vector2I startPos)
+14 -15
View File
@@ -1,23 +1,22 @@
using Godot; using Godot;
using System.Collections.Generic; using System.Collections.Generic;
public class LightHandler public static class LightHandler
{ {
public static List<OmniLight3D> lights = new List<OmniLight3D>(); public static List<OmniLight3D> lights = new List<OmniLight3D>();
public static void RedrawLights(Color color) public static void RedrawLights(Color color)
{ {
List<OmniLight3D> availableLights = new List<OmniLight3D>(); List<OmniLight3D> availableLights = new List<OmniLight3D>();
foreach (OmniLight3D light in lights) foreach (OmniLight3D light in lights)
{ {
if (GodotObject.IsInstanceValid(light)) if (!GodotObject.IsInstanceValid(light)) continue;
{
light.LightColor = color;
availableLights.Add(light);
}
}
lights = availableLights; light.LightColor = color;
} availableLights.Add(light);
}
lights = availableLights;
}
} }
+108 -103
View File
@@ -1,131 +1,136 @@
using System.Collections.Generic; using System.Collections.Generic;
using Godot; using Godot;
public class Pathfinding public static class Pathfinding
{ {
private static AStar3D aStar = new AStar3D(); private static AStar3D aStar = new AStar3D();
private static Dictionary<Vector3I, long> coordToId = new Dictionary<Vector3I, long>(); private static Dictionary<Vector3I, long> coordToId = new Dictionary<Vector3I, long>();
private static Dictionary<long, Vector3I> idToCoord = new Dictionary<long, Vector3I>(); private static Dictionary<long, Vector3I> idToCoord = new Dictionary<long, Vector3I>();
private static long nextId = 1; private static long nextId = 1;
private static Dictionary<int, (long fromId, long toId)> verticalConnections = new Dictionary<int, (long fromId, long toId)>(); private static Dictionary<int, (long fromId, long toId)> verticalConnections = new Dictionary<int, (long fromId, long toId)>();
private static long GetOrCreateId(Vector3I coord) private static long GetOrCreateId(Vector3I coord)
{ {
if (coordToId.TryGetValue(coord, out long id)) if (coordToId.TryGetValue(coord, out long id)) return id;
return id;
id = nextId++; id = nextId++;
coordToId[coord] = id; coordToId[coord] = id;
idToCoord[id] = coord; idToCoord[id] = coord;
return id; return id;
} }
public static void BuildAStarGraph() public static void BuildAStarGraph()
{ {
aStar.Clear(); aStar.Clear();
coordToId.Clear(); coordToId.Clear();
idToCoord.Clear(); idToCoord.Clear();
verticalConnections.Clear(); verticalConnections.Clear();
nextId = 1; nextId = 1;
for (int y = 0; y < GameData.ruinSize; y++) for (int y = 0; y < GameData.ruinSize; y++)
{ {
for (int x = 0; x < GameData.layerSize; x++) for (int x = 0; x < GameData.layerSize; x++)
{ {
for (int z = 0; z < GameData.layerSize; z++) for (int z = 0; z < GameData.layerSize; z++)
{ {
Vector3I coord = new Vector3I(x, y, z); AddPointIfValid(x, y, z);
Tile tile = GameData.map[y].tiles[x, z]; }
}
}
if (tile == null || tile.collapsedMesh == null) continue; foreach (KeyValuePair<Vector3I, long> 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<Vector3I, long> kvp in coordToId) long id = GetOrCreateId(coord);
{ aStar.AddPoint(id, tile.Position);
Vector3I from = kvp.Key; }
long fromId = kvp.Value;
foreach (Vector3I offset in WFC.offsets3D) private static void ConnectPoint(Vector3I from, long fromId)
{ {
Vector3I to = new Vector3I( foreach (Vector3I offset in WFC.offsets3D)
from.X + offset.X, {
from.Y + offset.Y, Vector3I to = new Vector3I(
from.Z + offset.Z 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") private static bool TryRegisterGateConnection(Vector3I from, Vector3I to, long fromId, long toId)
{ {
verticalConnections[from.Y] = (fromId, toId); if (from.Y == to.Y) return false;
if (GameData.map[from.Y].isGateOpen) if (GameData.map[from.Y].tiles[from.X, from.Z].collapsedMesh != "gate") return false;
{
if (!aStar.ArePointsConnected(fromId, toId))
{
aStar.ConnectPoints(fromId, toId, true);
}
}
continue;
}
if (!aStar.ArePointsConnected(fromId, toId)) verticalConnections[from.Y] = (fromId, toId);
{ if (GameData.map[from.Y].isGateOpen)
aStar.ConnectPoints(fromId, toId, true); {
} ConnectPointsIfNeeded(fromId, toId);
} }
}
for (int y = 0; y < GameData.ruinSize; y++) return true;
{ }
UpdateGatePoint(y, false);
}
}
public static void UpdateGatePoint(int layer, bool isOpen) private static void ConnectPointsIfNeeded(long fromId, long toId)
{ {
if (!verticalConnections.ContainsKey(layer)) return; if (aStar.ArePointsConnected(fromId, toId)) return;
(long fromId, long toId) = verticalConnections[layer]; aStar.ConnectPoints(fromId, toId, true);
}
if (isOpen) public static void UpdateGatePoint(int layer, bool isOpen)
{ {
if (!aStar.ArePointsConnected(fromId, toId)) if (!verticalConnections.ContainsKey(layer)) return;
{
aStar.ConnectPoints(fromId, toId, true);
}
}
else
{
if (aStar.ArePointsConnected(fromId, toId))
{
aStar.DisconnectPoints(fromId, toId);
}
}
}
public static List<Vector3> GetPath(Vector3I start, Vector3I end) (long fromId, long toId) = verticalConnections[layer];
{
if (!coordToId.ContainsKey(start) || !coordToId.ContainsKey(end)) return new List<Vector3>();
long startId = coordToId[start]; if (isOpen)
long endId = coordToId[end]; {
ConnectPointsIfNeeded(fromId, toId);
return;
}
return new List<Vector3>(aStar.GetPointPath(startId, endId)); if (aStar.ArePointsConnected(fromId, toId))
} {
aStar.DisconnectPoints(fromId, toId);
}
}
public static Vector3I GetClosestStartPoint(Vector3 robotPosition) public static List<Vector3> GetPath(Vector3I start, Vector3I end)
{ {
return idToCoord[aStar.GetClosestPoint(robotPosition)]; if (!coordToId.ContainsKey(start) || !coordToId.ContainsKey(end)) return new List<Vector3>();
}
long startId = coordToId[start];
long endId = coordToId[end];
return new List<Vector3>(aStar.GetPointPath(startId, endId));
}
public static Vector3I GetClosestStartPoint(Vector3 robotPosition)
{
return idToCoord[aStar.GetClosestPoint(robotPosition)];
}
} }
+17 -17
View File
@@ -1,40 +1,40 @@
using System.Collections.Generic; using System.Collections.Generic;
using Godot; using Godot;
public class ResourceDistributor public static class ResourceDistributor
{ {
public static Dictionary<string, Texture2D> resources = ResourceLoader.LoadResourceSymbols(); public static Dictionary<string, Texture2D> resources = ResourceLoader.LoadResourceSymbols();
public static Dictionary<string, float[]> weights = ResourceLoader.LoadResourceWeights(); public static Dictionary<string, float[]> weights = ResourceLoader.LoadResourceWeights();
public static string GetResource(int layer) public static string GetResource(int layer)
{ {
return ChooseWeighted(layer); return ChooseWeighted(layer);
} }
private static string ChooseWeighted(int layer) private static string ChooseWeighted(int layer)
{ {
float totalWeight = 0f; float totalWeight = 0f;
float weightLerp;
foreach (string resource in resources.Keys) foreach (string resource in resources.Keys)
{ {
weightLerp = Mathf.Lerp(weights[resource][0], weights[resource][1], layer); totalWeight += GetWeight(resource, layer);
totalWeight += weightLerp;
} }
float r = (float)(GameData.rand.NextDouble() * totalWeight); float randomWeight = (float)(GameData.rand.NextDouble() * totalWeight);
float cumulative = 0f; float cumulative = 0f;
foreach (string resource in resources.Keys) foreach (string resource in resources.Keys)
{ {
weightLerp = Mathf.Lerp(weights[resource][0], weights[resource][1], layer); cumulative += GetWeight(resource, layer);
cumulative += weightLerp;
if (r <= cumulative) if (randomWeight <= cumulative) return resource;
return resource;
} }
return "stone"; return "stone";
} }
private static float GetWeight(string resource, int layer)
{
return Mathf.Lerp(weights[resource][0], weights[resource][1], layer);
}
} }
+31 -41
View File
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Godot; using Godot;
public class WFC public static class WFC
{ {
public static Dictionary<string, Dictionary<Direction, List<string>>> adjacency = new Dictionary<string, Dictionary<Direction, List<string>>>(); public static Dictionary<string, Dictionary<Direction, List<string>>> adjacency = new Dictionary<string, Dictionary<Direction, List<string>>>();
public enum Direction public enum Direction
@@ -122,8 +122,6 @@ public class WFC
return aOpen == bOpen; return aOpen == bOpen;
} }
public static void FillAdjacencies() public static void FillAdjacencies()
{ {
foreach (string tile in tileConnections.Keys) foreach (string tile in tileConnections.Keys)
@@ -136,8 +134,7 @@ public class WFC
foreach (string other in tileConnections.Keys) foreach (string other in tileConnections.Keys)
{ {
if (CanConnect(tile, other, dir, false)) if (CanConnect(tile, other, dir, false)) valid.Add(other);
valid.Add(other);
} }
adjacency[tile][dir] = valid; adjacency[tile][dir] = valid;
@@ -156,8 +153,7 @@ public class WFC
Tile fromTile = layer[from.X, from.Y]; Tile fromTile = layer[from.X, from.Y];
Tile toTile = layer[to.X, to.Y]; Tile toTile = layer[to.X, to.Y];
if (!IsWalkable(toTile)) if (!IsWalkable(toTile)) return false;
return false;
return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true); 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 fromTile = GameData.map[from.Y].tiles[from.X, from.Z];
Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z]; Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z];
if (fromTile == null || toTile == null) if (fromTile == null || toTile == null) return false;
return false;
if (from.Y != to.Y) if (from.Y != to.Y)
{ {
if (Math.Abs(from.Y - to.Y) != 1) if (Math.Abs(from.Y - to.Y) != 1) return false;
return false;
if (from.Y > to.Y) return from.Y > to.Y
{ ? toTile.collapsedMesh == "gate"
return toTile.collapsedMesh == "gate"; : fromTile.collapsedMesh == "gate";
}
else
{
return fromTile.collapsedMesh == "gate";
}
} }
int dx = to.X - from.X; int dx = to.X - from.X;
int dz = to.Z - from.Z; int dz = to.Z - from.Z;
if (Math.Abs(dx) + Math.Abs(dz) != 1) if (Math.Abs(dx) + Math.Abs(dz) != 1) return false;
return false;
Direction dir; 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<Vector2I> visited = new HashSet<Vector2I>(); HashSet<Vector2I> visited = new HashSet<Vector2I>();
List<Vector2I> toCheck = new List<Vector2I>(); List<Vector2I> toCheck = new List<Vector2I>();
Vector2I position;
toCheck.Add(new Vector2I(1, 1)); toCheck.Add(new Vector2I(1, 1));
int safetyCounter = 0; int safetyCounter = 0;
while (true) while (toCheck.Count > 0)
{ {
if (toCheck.Count <= 0) break; Vector2I position = TakeRandomPosition(toCheck);
int index = GameData.rand.Next(toCheck.Count);
position = toCheck[index];
toCheck[index] = toCheck[^1];
toCheck.RemoveAt(toCheck.Count - 1);
if (!visited.Add(position)) continue; if (!visited.Add(position)) continue;
for (int i = 0; i < offsets2D.Length; i++) for (int i = 0; i < offsets2D.Length; i++)
{ {
Vector2I next = position + offsets2D[i]; Vector2I next = position + offsets2D[i];
if (!InBounds(next, layer.GetLength(0))) if (!InBounds(next, layer.GetLength(0))) continue;
continue;
if (CanWalk(layer, position, next, dirs[i])) if (CanWalk(layer, position, next, dirs[i]))
{ {
@@ -238,22 +220,30 @@ public class WFC
} }
safetyCounter++; safetyCounter++;
if (safetyCounter > layer.Length * 2) break; 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; return true;
break;
} }
} }
return result;
return false;
}
private static Vector2I TakeRandomPosition(List<Vector2I> 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) public static bool InBounds(Vector2I pos, int layerSize)
{ {
return pos.X > 0 && return pos.X > 0
pos.Y > 0 && && pos.Y > 0
pos.X < layerSize && && pos.X < layerSize
pos.Y < layerSize; && pos.Y < layerSize;
} }
public static List<string> GetBorderPossibilities(int x, int z) public static List<string> GetBorderPossibilities(int x, int z)
+1 -81
View File
@@ -1,6 +1,5 @@
using Godot; using Godot;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using static GameData; using static GameData;
public partial class World : Node3D public partial class World : Node3D
@@ -48,7 +47,7 @@ public partial class World : Node3D
map = new Layer[ruinSize]; map = new Layer[ruinSize];
GenerateWorld(); GenerateWorld();
SetGateRequirements(); GateRequirementGenerator.ApplyGateRequirements(map);
if (shouldLoadSave && saveGame != null) if (shouldLoadSave && saveGame != null)
{ {
@@ -288,83 +287,4 @@ public partial class World : Node3D
} }
} }
private void SetGateRequirements()
{
List<string> availableResources = new List<string>();
List<ItemData> possibleIngredients;
bool canCraft;
double highestCraftTime;
double lowestCraftTime;
foreach (Layer layer in map)
{
highestCraftTime = 0;
lowestCraftTime = double.MaxValue;
possibleIngredients = new List<ItemData>();
//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<ItemData> 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;
}
}
}
} }