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
+10 -6
View File
@@ -1,7 +1,7 @@
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";
@@ -17,7 +17,10 @@ public class FileHandler
string path = GetProgramPath(filename); string path = GetProgramPath(filename);
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write); FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
if (file == null) return;
file.StoreString(content); file.StoreString(content);
file.Flush();
} }
public static List<string> LoadProgramNames() public static List<string> LoadProgramNames()
@@ -35,14 +38,13 @@ public class FileHandler
while (true) while (true)
{ {
string fileName = dir.GetNext(); string fileName = dir.GetNext();
if (fileName == "") if (fileName == "") break;
break;
if (dir.CurrentIsDir()) continue;
if (!fileName.EndsWith(ScriptExtension)) continue;
if (!dir.CurrentIsDir() && fileName.EndsWith(ScriptExtension))
{
programs.Add(fileName.Replace(ScriptExtension, "")); programs.Add(fileName.Replace(ScriptExtension, ""));
} }
}
dir.ListDirEnd(); dir.ListDirEnd();
return programs; return programs;
@@ -59,6 +61,8 @@ public class FileHandler
} }
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read); FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
if (file == null) return "";
return file.GetAsText(); return file.GetAsText();
} }
+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;
} }
+71 -34
View File
@@ -7,30 +7,26 @@ 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() public ExploreNode()
{ {
DisplayText = "Explore"; DisplayText = "Explore";
} }
public override NodeResult Execute(Robot robot, double delta) public override NodeResult Execute(Robot robot, double delta)
{ {
if (pathPoints == null) if (pathPoints == null && !TrySelectTarget())
{
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"; lastExecutionMessage = "No tiles left to explore";
return NodeResult.SUCCESS; return NodeResult.SUCCESS;
} }
}
}
pathPoints ??= new List<Vector3>(Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)); if (pathPoints == null)
{
pathPoints = new List<Vector3>(
Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)
);
}
if (pathPoints.Count <= 0) if (pathPoints.Count <= 0)
{ {
@@ -38,52 +34,93 @@ public class ExploreNode : ProgramNode
return NodeResult.FAILURE; return NodeResult.FAILURE;
} }
return MoveAlongPath(robot, delta);
}
private bool TrySelectTarget()
{
int safetyCounter = 0;
int layerRange = Math.Max(GameData.lowestLayer, 1);
int maximumAttempts = (int)Math.Pow(GameData.layerSize, 2) * 2;
while (safetyCounter <= maximumAttempts)
{
targetPosition = new Vector3I(
GameData.rand.Next(GameData.layerSize),
GameData.rand.Next(layerRange),
GameData.rand.Next(GameData.layerSize)
);
if (!GameData.map[targetPosition.Y].tiles[targetPosition.X, targetPosition.Z].wasVisited)
{
return true;
}
safetyCounter++;
}
return false;
}
private NodeResult MoveAlongPath(Robot robot, double delta)
{
startPosition = robot.Position; startPosition = robot.Position;
Vector3 target = pathPoints[0] - startPosition; Vector3 target = pathPoints[0] - startPosition;
float distance = target.Length(); float distance = target.Length();
float movementSpeed = robot.GetMovementSpeed(); float movementSpeed = robot.GetMovementSpeed();
if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
{
return FinishCurrentStep(robot);
}
Vector3 direction = target / distance;
RotateRobot(robot, direction);
robot.GlobalPosition += direction * (float)delta * movementSpeed;
return NodeResult.RUNNING;
}
private NodeResult FinishCurrentStep(Robot robot)
{ {
robot.Position = pathPoints[0]; 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); Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z]; Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
if (!tile.wasVisited) if (!tile.wasVisited)
{ {
tile.VisitTile(); tile.VisitTile();
} }
}
pathPoints.Remove(pathPoints[0]); private void RotateRobot(Robot robot, Vector3 direction)
if (pathPoints.Count <= 0)
{ {
lastExecutionMessage = "Current exploration finished";
pathPoints = null;
return NodeResult.RUNNING;
}
lastExecutionMessage = "";
return NodeResult.RUNNING;
}
Vector3 direction = target / distance;
Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
if (lookDirection.Length() > 0.1f) if (lookDirection.Length() <= 0.1f) return;
{
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
}
robot.GlobalPosition += direction * (float)delta * movementSpeed;
return NodeResult.RUNNING; robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
} }
public override ProgramNode Duplicate() public override ProgramNode Duplicate()
{ {
ExploreNode duplicate = new ExploreNode return new ExploreNode
{ {
targetPosition = targetPosition targetPosition = targetPosition
}; };
return duplicate;
} }
public override string Save() public override string Save()
+2 -16
View File
@@ -9,6 +9,7 @@ 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();
@@ -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;
}
}
} }
} }
+7 -10
View File
@@ -6,12 +6,13 @@ public class HarvestNode : ProgramNode
{ {
DisplayText = "Harvest"; DisplayText = "Harvest";
} }
public override NodeResult Execute(Robot robot, double delta) public override NodeResult Execute(Robot robot, double delta)
{ {
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position); Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z]; Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
if (!tile.containsResource) if (!tile.containsResource || tile.resource == null)
{ {
lastExecutionMessage = "No resource on this tile"; lastExecutionMessage = "No resource on this tile";
return NodeResult.FAILURE; return NodeResult.FAILURE;
@@ -23,26 +24,22 @@ public class HarvestNode : ProgramNode
return NodeResult.SUCCESS; return NodeResult.SUCCESS;
} }
if (tile.resource.Extract(delta)) if (!tile.resource.Extract(delta)) return NodeResult.RUNNING;
{
SoundManager.PlayMining(); SoundManager.PlayMining();
if (!GameData.inventory.AddItem(new Item { data = tile.resource.item }, 1)) if (!GameData.inventory.AddItem(new Item { data = tile.resource.item }, 1))
{ {
lastExecutionMessage = "Not enough space"; lastExecutionMessage = "Not enough space";
return NodeResult.FAILURE; return NodeResult.FAILURE;
} }
else
{ lastExecutionMessage = "";
return NodeResult.SUCCESS; return NodeResult.SUCCESS;
} }
}
return NodeResult.RUNNING;
}
public override ProgramNode Duplicate() public override ProgramNode Duplicate()
{ {
HarvestNode duplicate = new HarvestNode(); return new HarvestNode();
return duplicate;
} }
public override string Save() public override string Save()
+10 -30
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)
@@ -25,21 +26,15 @@ public class IfNode : ProgramNode
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;
}
}
} }
} }
+41 -22
View File
@@ -6,14 +6,19 @@ 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() public MoveNode()
{ {
DisplayText = "Move"; DisplayText = "Move";
} }
public override NodeResult Execute(Robot robot, double delta) public override NodeResult Execute(Robot robot, double delta)
{ {
Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position); Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position);
pathPoints ??= new List<Vector3>(Pathfinding.GetPath(closestPosition, targetPosition)); if (pathPoints == null)
{
pathPoints = new List<Vector3>(Pathfinding.GetPath(closestPosition, targetPosition));
}
if (pathPoints.Count <= 0) if (pathPoints.Count <= 0)
{ {
@@ -22,56 +27,70 @@ public class MoveNode : ProgramNode
lastExecutionMessage = ""; lastExecutionMessage = "";
return NodeResult.SUCCESS; return NodeResult.SUCCESS;
} }
lastExecutionMessage = "No path available"; lastExecutionMessage = "No path available";
return NodeResult.FAILURE; return NodeResult.FAILURE;
} }
return MoveAlongPath(robot, delta);
}
private NodeResult MoveAlongPath(Robot robot, double delta)
{
startPosition = robot.Position; startPosition = robot.Position;
Vector3 target = pathPoints[0] - startPosition; Vector3 target = pathPoints[0] - startPosition;
float distance = target.Length(); float distance = target.Length();
float movementSpeed = robot.GetMovementSpeed(); float movementSpeed = robot.GetMovementSpeed();
if (distance < 0.1f * Mathf.Sqrt(movementSpeed)) if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
{
return FinishCurrentStep(robot);
}
Vector3 direction = target / distance;
RotateRobot(robot, direction);
robot.GlobalPosition += direction * (float)delta * movementSpeed;
return NodeResult.RUNNING;
}
private NodeResult FinishCurrentStep(Robot robot)
{ {
robot.Position = pathPoints[0]; 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); Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z]; Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
if (!tile.wasVisited) if (!tile.wasVisited)
{ {
tile.VisitTile(); tile.VisitTile();
} }
}
pathPoints.Remove(pathPoints[0]); private void RotateRobot(Robot robot, Vector3 direction)
if (pathPoints.Count <= 0)
{ {
pathPoints = null;
lastExecutionMessage = "";
return NodeResult.SUCCESS;
}
lastExecutionMessage = "";
return NodeResult.RUNNING;
}
Vector3 direction = target / distance;
Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z); Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
if (lookDirection.Length() > 0.1f) if (lookDirection.Length() <= 0.1f) return;
{
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
}
robot.GlobalPosition += direction * (float)delta * movementSpeed;
return NodeResult.RUNNING; robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
} }
public override ProgramNode Duplicate() public override ProgramNode Duplicate()
{ {
MoveNode duplicate = new MoveNode return new MoveNode
{ {
targetPosition = targetPosition targetPosition = targetPosition
}; };
return duplicate;
} }
public override string Save() public override string Save()
+22
View File
@@ -24,6 +24,28 @@ public abstract class ProgramNode
nextNode = GetConnectedNode(connections[0], availableNodes); nextNode = GetConnectedNode(connections[0], availableNodes);
} }
protected void SetBranchNodes(
List<Godot.Collections.Dictionary> connections,
Dictionary<StringName, ProgramNode> availableNodes
)
{
nextNode = null;
NegativeNode = null;
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( protected ProgramNode GetConnectedNode(
Godot.Collections.Dictionary connection, Godot.Collections.Dictionary connection,
Dictionary<StringName, ProgramNode> availableNodes Dictionary<StringName, ProgramNode> availableNodes
+10 -31
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";
@@ -26,21 +26,15 @@ public class WhileNode : ProgramNode
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
+15 -12
View File
@@ -29,13 +29,10 @@ public class GameResource
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;
}
resource.timeSinceLastExtraction = saveData.TimeSinceLastExtraction;
return resource; return resource;
} }
@@ -60,18 +57,16 @@ public class GameResource
timeSinceLastExtraction = 0; timeSinceLastExtraction = 0;
if (isEndless) return true; if (isEndless) return true;
if (currentAmount > 0) if (currentAmount <= 0) return false;
{
currentAmount--; currentAmount--;
return true; return true;
} }
return false;
}
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()
@@ -89,4 +84,12 @@ public class GameResource
{ {
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)
+66 -44
View File
@@ -33,16 +33,34 @@ public partial class Robot : Node3D
if (isExecuting) if (isExecuting)
{ {
if (CanExecute(delta)) UpdateExecution(delta);
}
else
{ {
switch (currentNode.Execute(this, delta)) UpdateIdle(delta);
}
Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer;
}
private void UpdateExecution(double delta)
{
if (!CanExecute(delta)) return;
NodeResult result = currentNode.Execute(this, delta);
ApplyNodeResult(result);
}
private void ApplyNodeResult(NodeResult result)
{
switch (result)
{ {
case NodeResult.SUCCESS: case NodeResult.SUCCESS:
currentNode = currentNode.nextNode; MoveToNextNode(currentNode.nextNode);
if (currentNode == null) break;
{
isExecuting = false; case NodeResult.CONDITIONFALSE:
} MoveToNextNode(currentNode.NegativeNode);
break; break;
case NodeResult.FAILURE: case NodeResult.FAILURE:
@@ -53,17 +71,19 @@ public partial class Robot : Node3D
case NodeResult.RUNNING: case NodeResult.RUNNING:
currentMessage = ""; currentMessage = "";
break; break;
case NodeResult.CONDITIONFALSE: }
currentNode = currentNode.NegativeNode; }
private void MoveToNextNode(ProgramNode nextNode)
{
currentNode = nextNode;
if (currentNode == null) if (currentNode == null)
{ {
isExecuting = false; isExecuting = false;
} }
break;
} }
}
} private void UpdateIdle(double delta)
else if (currentMessage.Length <= 0)
{ {
CoolDown( CoolDown(
delta, delta,
@@ -71,18 +91,10 @@ public partial class Robot : Node3D
* TypeStats.CoolingMultiplier * TypeStats.CoolingMultiplier
); );
if (currentMessage.Length <= 0)
{
currentMessage = "No script executing"; currentMessage = "No script executing";
} }
else
{
CoolDown(
delta,
GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond)
* TypeStats.CoolingMultiplier
);
}
Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer;
} }
public void SetupExecution(List<ProgramNode> nodes) public void SetupExecution(List<ProgramNode> nodes)
@@ -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
+3 -8
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,19 +37,15 @@ 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)
{ {
Position = new Vector3(Position.X, 10 - visibleLayer * 4, Position.Z); Position = new Vector3(Position.X, 10 - visibleLayer * 4, Position.Z);
+14 -97
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;
@@ -82,6 +84,7 @@ 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();
} }
@@ -89,6 +92,7 @@ public partial class UIHandler : Control
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);
@@ -97,27 +101,18 @@ 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
+65 -322
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);
if (parsedScript.VariantType != Variant.Type.Dictionary) return;
Dictionary scriptData = parsedScript.AsGodotDictionary();
if (!scriptData.ContainsKey("Nodes")) return;
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes =
new System.Collections.Generic.Dictionary<string, NodeDisplay>();
Array nodes = scriptData["Nodes"].AsGodotArray();
for (int i = 0; i < nodes.Count; i++)
{
Dictionary nodeData = nodes[i].AsGodotDictionary();
NodeDisplay nodeDisplay = LoadStructuredNode(nodeData);
if (nodeDisplay == null) continue;
editorWindow.AddChild(nodeDisplay); editorWindow.AddChild(nodeDisplay);
RegisterEditorNode(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
+16 -7
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)
{ {
display = itemDisplayPrefab.Instantiate<ItemDisplay>(); ItemDisplay display = itemDisplayPrefab.Instantiate<ItemDisplay>();
display.item = item; display.item = item;
display.text.Text = item.data.GetReadableName(); display.text.Text = item.data.GetReadableName();
display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}"; display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}";
display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture); display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
itemList.AddChild(display); return display;
}
} }
public void OnInventoryUpdate(object sender, EventArgs args) public void OnInventoryUpdate(object sender, EventArgs args)
+4
View File
@@ -50,6 +50,10 @@ public partial class OptionsMenu : PanelContainer
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;
} }
} }
+26 -20
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()
{ {
@@ -20,28 +19,35 @@ public partial class ResearchList : PanelContainer
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;
finishedResearch.Add(research);
}
if (finishedResearch.Count <= 0) return;
foreach (Research research in finishedResearch)
{ {
toDelete.Add(research); currentResearch.Remove(research);
}
RecalculateResearchStates(); RecalculateResearchStates();
SetupGraph(); SetupGraph();
} }
else if (result == ResearchResult.FAILED)
private bool IsResearchFinished(Research research, ResearchResult result)
{ {
if (result == ResearchResult.FINISHED) return true;
if (result != ResearchResult.FAILED) return false;
research.state = ResearchState.AVAILABLE; research.state = ResearchState.AVAILABLE;
toDelete.Add(research); return true;
RecalculateResearchStates();
SetupGraph();
}
}
foreach (Research delete in toDelete)
{
currentResearch.Remove(delete);
}
} }
public void SetupGraph() public void SetupGraph()
@@ -83,13 +89,12 @@ 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();
} }
} }
}
private void CreateResearchNodes() private void CreateResearchNodes()
{ {
@@ -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);
+24 -10
View File
@@ -30,31 +30,44 @@ 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;
return display;
}
private void HandleRobotJumpTo(Robot robot)
{ {
EmitSignal(SignalName.OnRobotJumpTo, robot); EmitSignal(SignalName.OnRobotJumpTo, robot);
Visible = false; Visible = false;
}; }
display.OnRobotFollow += (robot) =>
private void HandleRobotFollow(Robot robot)
{ {
EmitSignal(SignalName.OnRobotFollow, robot); EmitSignal(SignalName.OnRobotFollow, robot);
Visible = false; Visible = false;
};
robotList.AddChild(display);
}
} }
public void ReloadSelectableRobots() public void ReloadSelectableRobots()
@@ -82,6 +95,7 @@ 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)
+3 -4
View File
@@ -1,7 +1,7 @@
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>();
@@ -11,12 +11,11 @@ public class LightHandler
foreach (OmniLight3D light in lights) foreach (OmniLight3D light in lights)
{ {
if (GodotObject.IsInstanceValid(light)) if (!GodotObject.IsInstanceValid(light)) continue;
{
light.LightColor = color; light.LightColor = color;
availableLights.Add(light); availableLights.Add(light);
} }
}
lights = availableLights; lights = availableLights;
} }
+41 -36
View File
@@ -1,7 +1,7 @@
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>();
@@ -11,8 +11,7 @@ public class Pathfinding
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;
@@ -35,23 +34,34 @@ public class Pathfinding
{ {
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;
long id = GetOrCreateId(coord);
aStar.AddPoint(id, tile.Position);
} }
} }
} }
foreach (KeyValuePair<Vector3I, long> kvp in coordToId) foreach (KeyValuePair<Vector3I, long> kvp in coordToId)
{ {
Vector3I from = kvp.Key; ConnectPoint(kvp.Key, kvp.Value);
long fromId = kvp.Value; }
for (int y = 0; y < GameData.ruinSize; y++)
{
UpdateGatePoint(y, false);
}
}
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;
long id = GetOrCreateId(coord);
aStar.AddPoint(id, tile.Position);
}
private static void ConnectPoint(Vector3I from, long fromId)
{
foreach (Vector3I offset in WFC.offsets3D) foreach (Vector3I offset in WFC.offsets3D)
{ {
Vector3I to = new Vector3I( Vector3I to = new Vector3I(
@@ -61,35 +71,34 @@ public class Pathfinding
); );
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]; long toId = coordToId[to];
if (TryRegisterGateConnection(from, to, fromId, toId)) continue;
if (from.Y != to.Y && GameData.map[from.Y].tiles[from.X, from.Z].collapsedMesh == "gate") ConnectPointsIfNeeded(fromId, toId);
}
}
private static bool TryRegisterGateConnection(Vector3I from, Vector3I to, long fromId, long toId)
{ {
if (from.Y == to.Y) return false;
if (GameData.map[from.Y].tiles[from.X, from.Z].collapsedMesh != "gate") return false;
verticalConnections[from.Y] = (fromId, toId); verticalConnections[from.Y] = (fromId, toId);
if (GameData.map[from.Y].isGateOpen) if (GameData.map[from.Y].isGateOpen)
{ {
if (!aStar.ArePointsConnected(fromId, toId)) ConnectPointsIfNeeded(fromId, toId);
{
aStar.ConnectPoints(fromId, toId, true);
}
}
continue;
} }
if (!aStar.ArePointsConnected(fromId, toId)) return true;
{
aStar.ConnectPoints(fromId, toId, true);
}
}
} }
for (int y = 0; y < GameData.ruinSize; y++) private static void ConnectPointsIfNeeded(long fromId, long toId)
{ {
UpdateGatePoint(y, false); if (aStar.ArePointsConnected(fromId, toId)) return;
}
aStar.ConnectPoints(fromId, toId, true);
} }
public static void UpdateGatePoint(int layer, bool isOpen) public static void UpdateGatePoint(int layer, bool isOpen)
@@ -100,19 +109,15 @@ public class Pathfinding
if (isOpen) if (isOpen)
{ {
if (!aStar.ArePointsConnected(fromId, toId)) ConnectPointsIfNeeded(fromId, toId);
{ return;
aStar.ConnectPoints(fromId, toId, true);
} }
}
else
{
if (aStar.ArePointsConnected(fromId, toId)) if (aStar.ArePointsConnected(fromId, toId))
{ {
aStar.DisconnectPoints(fromId, toId); aStar.DisconnectPoints(fromId, toId);
} }
} }
}
public static List<Vector3> GetPath(Vector3I start, Vector3I end) public static List<Vector3> GetPath(Vector3I start, Vector3I end)
{ {
+10 -10
View File
@@ -1,7 +1,7 @@
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();
@@ -14,27 +14,27 @@ public class ResourceDistributor
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);
}
} }
+30 -40
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 false;
} }
return result;
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;
}
}
}
} }