Big project cleanup with overhaul of file responsibilities (KISS) and code (DRY, YAGNI)
This commit is contained in:
@@ -225,6 +225,7 @@ layout_mode = 2
|
|||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
|
|
||||||
[node name="CodingWindow" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=1576652491 node_paths=PackedStringArray("codeBlocks", "editorWindow", "availableScripts", "scriptName", "nameInput")]
|
[node name="CodingWindow" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=1576652491 node_paths=PackedStringArray("codeBlocks", "editorWindow", "availableScripts", "scriptName", "nameInput")]
|
||||||
|
visible = false
|
||||||
layout_mode = 1
|
layout_mode = 1
|
||||||
anchors_preset = 11
|
anchors_preset = 11
|
||||||
anchor_left = 1.0
|
anchor_left = 1.0
|
||||||
@@ -293,6 +294,14 @@ size_flags_horizontal = 3
|
|||||||
size_flags_vertical = 3
|
size_flags_vertical = 3
|
||||||
theme_override_constants/separation = 10
|
theme_override_constants/separation = 10
|
||||||
|
|
||||||
|
[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/CodeBlocks/VBoxContainer" unique_id=1914788051]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "Click to add"
|
||||||
|
fit_content = true
|
||||||
|
autowrap_mode = 0
|
||||||
|
horizontal_alignment = 1
|
||||||
|
vertical_alignment = 1
|
||||||
|
|
||||||
[node name="EditorWindow" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting" unique_id=919757187]
|
[node name="EditorWindow" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting" unique_id=919757187]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
|||||||
+71
-67
@@ -1,88 +1,92 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
public class FileHandler
|
public static class FileHandler
|
||||||
{
|
{
|
||||||
private const string ScriptDirectory = "user://scripts";
|
private const string ScriptDirectory = "user://scripts";
|
||||||
private const string ScriptExtension = ".json";
|
private const string ScriptExtension = ".json";
|
||||||
|
|
||||||
public static void CreateScriptDirectory()
|
public static void CreateScriptDirectory()
|
||||||
{
|
{
|
||||||
DirAccess.MakeDirRecursiveAbsolute(ScriptDirectory);
|
DirAccess.MakeDirRecursiveAbsolute(ScriptDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SaveProgram(string filename, string content)
|
public static void SaveProgram(string filename, string content)
|
||||||
{
|
{
|
||||||
CreateScriptDirectory();
|
CreateScriptDirectory();
|
||||||
string path = GetProgramPath(filename);
|
string path = GetProgramPath(filename);
|
||||||
|
|
||||||
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
|
||||||
file.StoreString(content);
|
if (file == null) return;
|
||||||
}
|
|
||||||
|
|
||||||
public static List<string> LoadProgramNames()
|
file.StoreString(content);
|
||||||
{
|
file.Flush();
|
||||||
CreateScriptDirectory();
|
}
|
||||||
List<string> programs = new List<string>();
|
|
||||||
|
|
||||||
DirAccess dir = DirAccess.Open(ScriptDirectory);
|
public static List<string> LoadProgramNames()
|
||||||
if (dir == null)
|
{
|
||||||
{
|
CreateScriptDirectory();
|
||||||
return programs;
|
List<string> programs = new List<string>();
|
||||||
}
|
|
||||||
|
|
||||||
dir.ListDirBegin();
|
DirAccess dir = DirAccess.Open(ScriptDirectory);
|
||||||
while (true)
|
if (dir == null)
|
||||||
{
|
{
|
||||||
string fileName = dir.GetNext();
|
return programs;
|
||||||
if (fileName == "")
|
}
|
||||||
break;
|
|
||||||
|
|
||||||
if (!dir.CurrentIsDir() && fileName.EndsWith(ScriptExtension))
|
dir.ListDirBegin();
|
||||||
{
|
while (true)
|
||||||
programs.Add(fileName.Replace(ScriptExtension, ""));
|
{
|
||||||
}
|
string fileName = dir.GetNext();
|
||||||
}
|
if (fileName == "") break;
|
||||||
dir.ListDirEnd();
|
|
||||||
|
|
||||||
return programs;
|
if (dir.CurrentIsDir()) continue;
|
||||||
}
|
if (!fileName.EndsWith(ScriptExtension)) continue;
|
||||||
|
|
||||||
public static string LoadProgram(string name)
|
programs.Add(fileName.Replace(ScriptExtension, ""));
|
||||||
{
|
}
|
||||||
CreateScriptDirectory();
|
dir.ListDirEnd();
|
||||||
string path = GetProgramPath(name);
|
|
||||||
|
|
||||||
if (!FileAccess.FileExists(path))
|
return programs;
|
||||||
{
|
}
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
public static string LoadProgram(string name)
|
||||||
return file.GetAsText();
|
{
|
||||||
}
|
CreateScriptDirectory();
|
||||||
|
string path = GetProgramPath(name);
|
||||||
|
|
||||||
public static bool DeleteProgram(string name)
|
if (!FileAccess.FileExists(path))
|
||||||
{
|
{
|
||||||
CreateScriptDirectory();
|
return "";
|
||||||
string path = GetProgramPath(name);
|
}
|
||||||
|
|
||||||
if (!FileAccess.FileExists(path))
|
FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
|
||||||
{
|
if (file == null) return "";
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
DirAccess dir = DirAccess.Open(ScriptDirectory);
|
return file.GetAsText();
|
||||||
if (dir == null)
|
}
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dir.Remove($"{name}{ScriptExtension}") == Error.Ok;
|
public static bool DeleteProgram(string name)
|
||||||
}
|
{
|
||||||
|
CreateScriptDirectory();
|
||||||
|
string path = GetProgramPath(name);
|
||||||
|
|
||||||
private static string GetProgramPath(string filename)
|
if (!FileAccess.FileExists(path))
|
||||||
{
|
{
|
||||||
return $"{ScriptDirectory}/{filename}{ScriptExtension}";
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DirAccess dir = DirAccess.Open(ScriptDirectory);
|
||||||
|
if (dir == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir.Remove($"{name}{ScriptExtension}") == Error.Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetProgramPath(string filename)
|
||||||
|
{
|
||||||
|
return $"{ScriptDirectory}/{filename}{ScriptExtension}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>();
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://jv7k2npga7u0
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b87q17gdv4pfh
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public class CraftNode : ProgramNode
|
|||||||
|
|
||||||
if (amount <= 0)
|
if (amount <= 0)
|
||||||
{
|
{
|
||||||
lastExecutionMessage = "Amount has to be atleast 1";
|
lastExecutionMessage = "Amount has to be at least 1";
|
||||||
return NodeResult.FAILURE;
|
return NodeResult.FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,90 +4,127 @@ using Godot;
|
|||||||
|
|
||||||
public class ExploreNode : ProgramNode
|
public class ExploreNode : ProgramNode
|
||||||
{
|
{
|
||||||
public Vector3 startPosition;
|
public Vector3 startPosition;
|
||||||
public Vector3I targetPosition;
|
public Vector3I targetPosition;
|
||||||
public List<Vector3> pathPoints;
|
public List<Vector3> pathPoints;
|
||||||
public ExploreNode()
|
|
||||||
{
|
|
||||||
DisplayText = "Explore";
|
|
||||||
}
|
|
||||||
public override NodeResult Execute(Robot robot, double delta)
|
|
||||||
{
|
|
||||||
if (pathPoints == null)
|
|
||||||
{
|
|
||||||
int safetyCounter = 0;
|
|
||||||
int layerRange = Math.Max(GameData.lowestLayer, 1);
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
targetPosition = new Vector3I(GameData.rand.Next(GameData.layerSize), GameData.rand.Next(layerRange), GameData.rand.Next(GameData.layerSize));
|
|
||||||
if (!GameData.map[targetPosition.Y].tiles[targetPosition.X, targetPosition.Z].wasVisited) break;
|
|
||||||
safetyCounter++;
|
|
||||||
if (safetyCounter > Math.Pow(GameData.layerSize, 2) * 2)
|
|
||||||
{
|
|
||||||
lastExecutionMessage = "No tiles left to explore";
|
|
||||||
return NodeResult.SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pathPoints ??= new List<Vector3>(Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition));
|
public ExploreNode()
|
||||||
|
{
|
||||||
|
DisplayText = "Explore";
|
||||||
|
}
|
||||||
|
|
||||||
if (pathPoints.Count <= 0)
|
public override NodeResult Execute(Robot robot, double delta)
|
||||||
{
|
{
|
||||||
lastExecutionMessage = $"No path available {targetPosition}";
|
if (pathPoints == null && !TrySelectTarget())
|
||||||
return NodeResult.FAILURE;
|
{
|
||||||
}
|
lastExecutionMessage = "No tiles left to explore";
|
||||||
|
return NodeResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
startPosition = robot.Position;
|
if (pathPoints == null)
|
||||||
Vector3 target = pathPoints[0] - startPosition;
|
{
|
||||||
float distance = target.Length();
|
pathPoints = new List<Vector3>(
|
||||||
|
Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
float movementSpeed = robot.GetMovementSpeed();
|
if (pathPoints.Count <= 0)
|
||||||
|
{
|
||||||
|
lastExecutionMessage = $"No path available {targetPosition}";
|
||||||
|
return NodeResult.FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
|
return MoveAlongPath(robot, delta);
|
||||||
{
|
}
|
||||||
robot.Position = pathPoints[0];
|
|
||||||
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
|
||||||
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
|
|
||||||
if (!tile.wasVisited)
|
|
||||||
{
|
|
||||||
tile.VisitTile();
|
|
||||||
}
|
|
||||||
|
|
||||||
pathPoints.Remove(pathPoints[0]);
|
private bool TrySelectTarget()
|
||||||
if (pathPoints.Count <= 0)
|
{
|
||||||
{
|
int safetyCounter = 0;
|
||||||
lastExecutionMessage = "Current exploration finished";
|
int layerRange = Math.Max(GameData.lowestLayer, 1);
|
||||||
pathPoints = null;
|
int maximumAttempts = (int)Math.Pow(GameData.layerSize, 2) * 2;
|
||||||
return NodeResult.RUNNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastExecutionMessage = "";
|
while (safetyCounter <= maximumAttempts)
|
||||||
return NodeResult.RUNNING;
|
{
|
||||||
}
|
targetPosition = new Vector3I(
|
||||||
|
GameData.rand.Next(GameData.layerSize),
|
||||||
|
GameData.rand.Next(layerRange),
|
||||||
|
GameData.rand.Next(GameData.layerSize)
|
||||||
|
);
|
||||||
|
if (!GameData.map[targetPosition.Y].tiles[targetPosition.X, targetPosition.Z].wasVisited)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Vector3 direction = target / distance;
|
safetyCounter++;
|
||||||
Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
|
}
|
||||||
if (lookDirection.Length() > 0.1f)
|
|
||||||
{
|
|
||||||
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
|
|
||||||
}
|
|
||||||
robot.GlobalPosition += direction * (float)delta * movementSpeed;
|
|
||||||
|
|
||||||
return NodeResult.RUNNING;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ProgramNode Duplicate()
|
private NodeResult MoveAlongPath(Robot robot, double delta)
|
||||||
{
|
{
|
||||||
ExploreNode duplicate = new ExploreNode
|
startPosition = robot.Position;
|
||||||
{
|
Vector3 target = pathPoints[0] - startPosition;
|
||||||
targetPosition = targetPosition
|
float distance = target.Length();
|
||||||
};
|
float movementSpeed = robot.GetMovementSpeed();
|
||||||
return duplicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Save()
|
if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
|
||||||
{
|
{
|
||||||
return $"Name: {DisplayText}";
|
return FinishCurrentStep(robot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Vector3 direction = target / distance;
|
||||||
|
RotateRobot(robot, direction);
|
||||||
|
robot.GlobalPosition += direction * (float)delta * movementSpeed;
|
||||||
|
|
||||||
|
return NodeResult.RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeResult FinishCurrentStep(Robot robot)
|
||||||
|
{
|
||||||
|
robot.Position = pathPoints[0];
|
||||||
|
VisitCurrentTile(robot);
|
||||||
|
pathPoints.RemoveAt(0);
|
||||||
|
|
||||||
|
if (pathPoints.Count > 0)
|
||||||
|
{
|
||||||
|
lastExecutionMessage = "";
|
||||||
|
return NodeResult.RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastExecutionMessage = "Current exploration finished";
|
||||||
|
pathPoints = null;
|
||||||
|
return NodeResult.RUNNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VisitCurrentTile(Robot robot)
|
||||||
|
{
|
||||||
|
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
||||||
|
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
|
||||||
|
if (!tile.wasVisited)
|
||||||
|
{
|
||||||
|
tile.VisitTile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateRobot(Robot robot, Vector3 direction)
|
||||||
|
{
|
||||||
|
Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
|
||||||
|
if (lookDirection.Length() <= 0.1f) return;
|
||||||
|
|
||||||
|
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ProgramNode Duplicate()
|
||||||
|
{
|
||||||
|
return new ExploreNode
|
||||||
|
{
|
||||||
|
targetPosition = targetPosition
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Save()
|
||||||
|
{
|
||||||
|
return $"Name: {DisplayText}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,12 @@ public class ForNode : ProgramNode
|
|||||||
{
|
{
|
||||||
DisplayText = "For";
|
DisplayText = "For";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override NodeResult Execute(Robot robot, double delta)
|
public override NodeResult Execute(Robot robot, double delta)
|
||||||
{
|
{
|
||||||
bool isConditionFulfilled = DetermineCondition();
|
bool isConditionFulfilled = DetermineCondition();
|
||||||
amountExecuted++;
|
amountExecuted++;
|
||||||
return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
|
return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DetermineCondition()
|
private bool DetermineCondition()
|
||||||
@@ -41,21 +42,6 @@ public class ForNode : ProgramNode
|
|||||||
Dictionary<StringName, ProgramNode> availableNodes
|
Dictionary<StringName, ProgramNode> availableNodes
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
nextNode = null;
|
SetBranchNodes(connections, availableNodes);
|
||||||
NegativeNode = null;
|
|
||||||
|
|
||||||
foreach (Godot.Collections.Dictionary connection in connections)
|
|
||||||
{
|
|
||||||
int port = (int)connection["from_port"];
|
|
||||||
ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
|
|
||||||
if (port == 0)
|
|
||||||
{
|
|
||||||
nextNode = connectedNode;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NegativeNode = connectedNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,51 +2,48 @@ using Godot;
|
|||||||
|
|
||||||
public class HarvestNode : ProgramNode
|
public class HarvestNode : ProgramNode
|
||||||
{
|
{
|
||||||
public HarvestNode()
|
public HarvestNode()
|
||||||
{
|
{
|
||||||
DisplayText = "Harvest";
|
DisplayText = "Harvest";
|
||||||
}
|
}
|
||||||
public override NodeResult Execute(Robot robot, double delta)
|
|
||||||
{
|
|
||||||
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
|
||||||
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
|
|
||||||
|
|
||||||
if (!tile.containsResource)
|
public override NodeResult Execute(Robot robot, double delta)
|
||||||
{
|
{
|
||||||
lastExecutionMessage = "No resource on this tile";
|
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
||||||
return NodeResult.FAILURE;
|
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
|
||||||
}
|
|
||||||
|
|
||||||
if (!tile.resource.CanExtract())
|
if (!tile.containsResource || tile.resource == null)
|
||||||
{
|
{
|
||||||
lastExecutionMessage = "Resource is depleted and not endless or you haven't unlocked it yet";
|
lastExecutionMessage = "No resource on this tile";
|
||||||
return NodeResult.SUCCESS;
|
return NodeResult.FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile.resource.Extract(delta))
|
if (!tile.resource.CanExtract())
|
||||||
{
|
{
|
||||||
SoundManager.PlayMining();
|
lastExecutionMessage = "Resource is depleted and not endless or you haven't unlocked it yet";
|
||||||
if (!GameData.inventory.AddItem(new Item {data = tile.resource.item}, 1))
|
return NodeResult.SUCCESS;
|
||||||
{
|
}
|
||||||
lastExecutionMessage = "Not enough space";
|
|
||||||
return NodeResult.FAILURE;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return NodeResult.SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NodeResult.RUNNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override ProgramNode Duplicate()
|
if (!tile.resource.Extract(delta)) return NodeResult.RUNNING;
|
||||||
{
|
|
||||||
HarvestNode duplicate = new HarvestNode();
|
|
||||||
return duplicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Save()
|
SoundManager.PlayMining();
|
||||||
{
|
if (!GameData.inventory.AddItem(new Item { data = tile.resource.item }, 1))
|
||||||
return $"Name: {DisplayText}";
|
{
|
||||||
}
|
lastExecutionMessage = "Not enough space";
|
||||||
|
return NodeResult.FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastExecutionMessage = "";
|
||||||
|
return NodeResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ProgramNode Duplicate()
|
||||||
|
{
|
||||||
|
return new HarvestNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Save()
|
||||||
|
{
|
||||||
|
return $"Name: {DisplayText}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-31
@@ -10,6 +10,7 @@ public class IfNode : ProgramNode
|
|||||||
{
|
{
|
||||||
DisplayText = "If";
|
DisplayText = "If";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override NodeResult Execute(Robot robot, double delta)
|
public override NodeResult Execute(Robot robot, double delta)
|
||||||
{
|
{
|
||||||
if (selectedItem == null)
|
if (selectedItem == null)
|
||||||
@@ -19,27 +20,21 @@ public class IfNode : ProgramNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isConditionFulfilled = DetermineCondition();
|
bool isConditionFulfilled = DetermineCondition();
|
||||||
return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
|
return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DetermineCondition()
|
private bool DetermineCondition()
|
||||||
{
|
{
|
||||||
int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
|
int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
|
||||||
switch (comparator)
|
return comparator switch
|
||||||
{
|
{
|
||||||
case "is bigger than":
|
"is bigger than" => inventoryAmount > amount,
|
||||||
return inventoryAmount > amount;
|
"is less than" => inventoryAmount < amount,
|
||||||
case "is less than":
|
"is not" => inventoryAmount != amount,
|
||||||
return inventoryAmount < amount;
|
"is less than or equal to" => inventoryAmount <= amount,
|
||||||
case "is not":
|
"is bigger than or equal to" => inventoryAmount >= amount,
|
||||||
return inventoryAmount != amount;
|
_ => inventoryAmount == amount
|
||||||
case "is less than or equal to":
|
};
|
||||||
return inventoryAmount <= amount;
|
|
||||||
case "is bigger than or equal to":
|
|
||||||
return inventoryAmount >= amount;
|
|
||||||
default:
|
|
||||||
return inventoryAmount == amount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ProgramNode Duplicate()
|
public override ProgramNode Duplicate()
|
||||||
@@ -63,21 +58,6 @@ public class IfNode : ProgramNode
|
|||||||
Dictionary<StringName, ProgramNode> availableNodes
|
Dictionary<StringName, ProgramNode> availableNodes
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
nextNode = null;
|
SetBranchNodes(connections, availableNodes);
|
||||||
NegativeNode = null;
|
|
||||||
|
|
||||||
foreach (Godot.Collections.Dictionary connection in connections)
|
|
||||||
{
|
|
||||||
int port = (int)connection["from_port"];
|
|
||||||
ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
|
|
||||||
if (port == 0)
|
|
||||||
{
|
|
||||||
nextNode = connectedNode;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NegativeNode = connectedNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,79 +3,98 @@ using Godot;
|
|||||||
|
|
||||||
public class MoveNode : ProgramNode
|
public class MoveNode : ProgramNode
|
||||||
{
|
{
|
||||||
public Vector3 startPosition;
|
public Vector3 startPosition;
|
||||||
public Vector3I targetPosition;
|
public Vector3I targetPosition;
|
||||||
public List<Vector3> pathPoints;
|
public List<Vector3> pathPoints;
|
||||||
public MoveNode()
|
|
||||||
{
|
|
||||||
DisplayText = "Move";
|
|
||||||
}
|
|
||||||
public override NodeResult Execute(Robot robot, double delta)
|
|
||||||
{
|
|
||||||
Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position);
|
|
||||||
pathPoints ??= new List<Vector3>(Pathfinding.GetPath(closestPosition, targetPosition));
|
|
||||||
|
|
||||||
if (pathPoints.Count <= 0)
|
public MoveNode()
|
||||||
{
|
{
|
||||||
if ((closestPosition - targetPosition).Length() == 0)
|
DisplayText = "Move";
|
||||||
{
|
}
|
||||||
lastExecutionMessage = "";
|
|
||||||
return NodeResult.SUCCESS;
|
|
||||||
}
|
|
||||||
lastExecutionMessage = "No path available";
|
|
||||||
return NodeResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
startPosition = robot.Position;
|
public override NodeResult Execute(Robot robot, double delta)
|
||||||
Vector3 target = pathPoints[0] - startPosition;
|
{
|
||||||
float distance = target.Length();
|
Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position);
|
||||||
|
if (pathPoints == null)
|
||||||
|
{
|
||||||
|
pathPoints = new List<Vector3>(Pathfinding.GetPath(closestPosition, targetPosition));
|
||||||
|
}
|
||||||
|
|
||||||
float movementSpeed = robot.GetMovementSpeed();
|
if (pathPoints.Count <= 0)
|
||||||
|
{
|
||||||
|
if ((closestPosition - targetPosition).Length() == 0)
|
||||||
|
{
|
||||||
|
lastExecutionMessage = "";
|
||||||
|
return NodeResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
|
lastExecutionMessage = "No path available";
|
||||||
{
|
return NodeResult.FAILURE;
|
||||||
robot.Position = pathPoints[0];
|
}
|
||||||
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
|
||||||
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
|
|
||||||
if (!tile.wasVisited)
|
|
||||||
{
|
|
||||||
tile.VisitTile();
|
|
||||||
}
|
|
||||||
|
|
||||||
pathPoints.Remove(pathPoints[0]);
|
return MoveAlongPath(robot, delta);
|
||||||
if (pathPoints.Count <= 0)
|
}
|
||||||
{
|
|
||||||
pathPoints = null;
|
|
||||||
lastExecutionMessage = "";
|
|
||||||
return NodeResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastExecutionMessage = "";
|
private NodeResult MoveAlongPath(Robot robot, double delta)
|
||||||
return NodeResult.RUNNING;
|
{
|
||||||
}
|
startPosition = robot.Position;
|
||||||
|
Vector3 target = pathPoints[0] - startPosition;
|
||||||
|
float distance = target.Length();
|
||||||
|
float movementSpeed = robot.GetMovementSpeed();
|
||||||
|
|
||||||
Vector3 direction = target / distance;
|
if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
|
||||||
Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
|
{
|
||||||
if (lookDirection.Length() > 0.1f)
|
return FinishCurrentStep(robot);
|
||||||
{
|
}
|
||||||
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
|
|
||||||
}
|
|
||||||
robot.GlobalPosition += direction * (float)delta * movementSpeed;
|
|
||||||
|
|
||||||
return NodeResult.RUNNING;
|
Vector3 direction = target / distance;
|
||||||
}
|
RotateRobot(robot, direction);
|
||||||
|
robot.GlobalPosition += direction * (float)delta * movementSpeed;
|
||||||
|
|
||||||
public override ProgramNode Duplicate()
|
return NodeResult.RUNNING;
|
||||||
{
|
}
|
||||||
MoveNode duplicate = new MoveNode
|
|
||||||
{
|
|
||||||
targetPosition = targetPosition
|
|
||||||
};
|
|
||||||
return duplicate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Save()
|
private NodeResult FinishCurrentStep(Robot robot)
|
||||||
{
|
{
|
||||||
return $"Name: {DisplayText}, Position: ({targetPosition.X}|{targetPosition.Y}|{targetPosition.Z})";
|
robot.Position = pathPoints[0];
|
||||||
}
|
VisitCurrentTile(robot);
|
||||||
|
pathPoints.RemoveAt(0);
|
||||||
|
lastExecutionMessage = "";
|
||||||
|
|
||||||
|
if (pathPoints.Count > 0) return NodeResult.RUNNING;
|
||||||
|
|
||||||
|
pathPoints = null;
|
||||||
|
return NodeResult.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VisitCurrentTile(Robot robot)
|
||||||
|
{
|
||||||
|
Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
|
||||||
|
Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
|
||||||
|
if (!tile.wasVisited)
|
||||||
|
{
|
||||||
|
tile.VisitTile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RotateRobot(Robot robot, Vector3 direction)
|
||||||
|
{
|
||||||
|
Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
|
||||||
|
if (lookDirection.Length() <= 0.1f) return;
|
||||||
|
|
||||||
|
robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override ProgramNode Duplicate()
|
||||||
|
{
|
||||||
|
return new MoveNode
|
||||||
|
{
|
||||||
|
targetPosition = targetPosition
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Save()
|
||||||
|
{
|
||||||
|
return $"Name: {DisplayText}, Position: ({targetPosition.X}|{targetPosition.Y}|{targetPosition.Z})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,35 +3,57 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
public abstract class ProgramNode
|
public abstract class ProgramNode
|
||||||
{
|
{
|
||||||
public ProgramNode nextNode;
|
public ProgramNode nextNode;
|
||||||
public ProgramNode NegativeNode;
|
public ProgramNode NegativeNode;
|
||||||
public string DisplayText;
|
public string DisplayText;
|
||||||
public string lastExecutionMessage;
|
public string lastExecutionMessage;
|
||||||
|
|
||||||
public abstract NodeResult Execute(Robot robot, double delta);
|
public abstract NodeResult Execute(Robot robot, double delta);
|
||||||
public abstract ProgramNode Duplicate();
|
public abstract ProgramNode Duplicate();
|
||||||
public abstract string Save();
|
public abstract string Save();
|
||||||
|
|
||||||
public virtual void SetNextNode(
|
public virtual void SetNextNode(
|
||||||
List<Godot.Collections.Dictionary> connections,
|
List<Godot.Collections.Dictionary> connections,
|
||||||
Dictionary<StringName, ProgramNode> availableNodes
|
Dictionary<StringName, ProgramNode> availableNodes
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
nextNode = null;
|
nextNode = null;
|
||||||
|
|
||||||
if (connections.Count <= 0) return;
|
if (connections.Count <= 0) return;
|
||||||
|
|
||||||
nextNode = GetConnectedNode(connections[0], availableNodes);
|
nextNode = GetConnectedNode(connections[0], availableNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ProgramNode GetConnectedNode(
|
protected void SetBranchNodes(
|
||||||
Godot.Collections.Dictionary connection,
|
List<Godot.Collections.Dictionary> connections,
|
||||||
Dictionary<StringName, ProgramNode> availableNodes
|
Dictionary<StringName, ProgramNode> availableNodes
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
StringName nodeName = connection["to_node"].AsStringName();
|
nextNode = null;
|
||||||
if (!availableNodes.ContainsKey(nodeName)) return null;
|
NegativeNode = null;
|
||||||
|
|
||||||
return availableNodes[nodeName];
|
foreach (Godot.Collections.Dictionary connection in connections)
|
||||||
}
|
{
|
||||||
|
ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
|
||||||
|
if ((int)connection["from_port"] == 0)
|
||||||
|
{
|
||||||
|
nextNode = connectedNode;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NegativeNode = connectedNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ProgramNode GetConnectedNode(
|
||||||
|
Godot.Collections.Dictionary connection,
|
||||||
|
Dictionary<StringName, ProgramNode> availableNodes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
StringName nodeName = connection["to_node"].AsStringName();
|
||||||
|
if (!availableNodes.ContainsKey(nodeName)) return null;
|
||||||
|
|
||||||
|
return availableNodes[nodeName];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ public class WhileNode : ProgramNode
|
|||||||
{
|
{
|
||||||
DisplayText = "While";
|
DisplayText = "While";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override NodeResult Execute(Robot robot, double delta)
|
public override NodeResult Execute(Robot robot, double delta)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (selectedItem == null)
|
if (selectedItem == null)
|
||||||
{
|
{
|
||||||
lastExecutionMessage = "No Item selected";
|
lastExecutionMessage = "No Item selected";
|
||||||
@@ -20,27 +20,21 @@ public class WhileNode : ProgramNode
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isConditionFulfilled = DetermineCondition();
|
bool isConditionFulfilled = DetermineCondition();
|
||||||
return isConditionFulfilled? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
|
return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool DetermineCondition()
|
private bool DetermineCondition()
|
||||||
{
|
{
|
||||||
int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
|
int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
|
||||||
switch (comparator)
|
return comparator switch
|
||||||
{
|
{
|
||||||
case "is bigger than":
|
"is bigger than" => inventoryAmount > amount,
|
||||||
return inventoryAmount > amount;
|
"is less than" => inventoryAmount < amount,
|
||||||
case "is less than":
|
"is not" => inventoryAmount != amount,
|
||||||
return inventoryAmount < amount;
|
"is less than or equal to" => inventoryAmount <= amount,
|
||||||
case "is not":
|
"is bigger than or equal to" => inventoryAmount >= amount,
|
||||||
return inventoryAmount != amount;
|
_ => inventoryAmount == amount
|
||||||
case "is less than or equal to":
|
};
|
||||||
return inventoryAmount <= amount;
|
|
||||||
case "is bigger than or equal to":
|
|
||||||
return inventoryAmount >= amount;
|
|
||||||
default:
|
|
||||||
return inventoryAmount == amount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override ProgramNode Duplicate()
|
public override ProgramNode Duplicate()
|
||||||
@@ -64,21 +58,6 @@ public class WhileNode : ProgramNode
|
|||||||
Dictionary<StringName, ProgramNode> availableNodes
|
Dictionary<StringName, ProgramNode> availableNodes
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
nextNode = null;
|
SetBranchNodes(connections, availableNodes);
|
||||||
NegativeNode = null;
|
|
||||||
|
|
||||||
foreach (Godot.Collections.Dictionary connection in connections)
|
|
||||||
{
|
|
||||||
int port = (int)connection["from_port"];
|
|
||||||
ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
|
|
||||||
if (port == 0)
|
|
||||||
{
|
|
||||||
nextNode = connectedNode;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NegativeNode = connectedNode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
public class Building
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://cl2yvllo35qbb
|
|
||||||
@@ -1,92 +1,95 @@
|
|||||||
public class GameResource
|
public class GameResource
|
||||||
{
|
{
|
||||||
private const float NormalExtractionSpeed = 1f;
|
private const float NormalExtractionSpeed = 1f;
|
||||||
private const float EndlessExtractionSpeed = 4f;
|
private const float EndlessExtractionSpeed = 4f;
|
||||||
|
|
||||||
public string name;
|
public string name;
|
||||||
public ItemData item;
|
public ItemData item;
|
||||||
|
|
||||||
private int currentAmount;
|
private int currentAmount;
|
||||||
private int maxAmount;
|
private int maxAmount;
|
||||||
private bool isEndless;
|
private bool isEndless;
|
||||||
private float extractionSpeed;
|
private float extractionSpeed;
|
||||||
private double timeSinceLastExtraction;
|
private double timeSinceLastExtraction;
|
||||||
|
|
||||||
public GameResource(string name)
|
public GameResource(string name)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
maxAmount = GameData.rand.Next(1000, 10000);
|
maxAmount = GameData.rand.Next(1000, 10000);
|
||||||
currentAmount = maxAmount;
|
currentAmount = maxAmount;
|
||||||
isEndless = false;
|
isEndless = false;
|
||||||
extractionSpeed = NormalExtractionSpeed;
|
extractionSpeed = NormalExtractionSpeed;
|
||||||
item = GameData.availableItems[name];
|
item = GameData.availableItems[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GameResource FromSaveData(ResourceSaveData saveData)
|
public static GameResource FromSaveData(ResourceSaveData saveData)
|
||||||
{
|
{
|
||||||
GameResource resource = new GameResource(saveData.Name)
|
GameResource resource = new GameResource(saveData.Name)
|
||||||
{
|
{
|
||||||
currentAmount = saveData.CurrentAmount,
|
currentAmount = saveData.CurrentAmount,
|
||||||
maxAmount = saveData.MaxAmount,
|
maxAmount = saveData.MaxAmount,
|
||||||
isEndless = saveData.IsEndless,
|
isEndless = saveData.IsEndless,
|
||||||
extractionSpeed = saveData.ExtractionSpeed
|
extractionSpeed = saveData.ExtractionSpeed,
|
||||||
};
|
timeSinceLastExtraction = saveData.TimeSinceLastExtraction
|
||||||
if (resource.isEndless && resource.extractionSpeed <= NormalExtractionSpeed)
|
};
|
||||||
{
|
resource.NormalizeExtractionSpeed();
|
||||||
resource.extractionSpeed = EndlessExtractionSpeed;
|
return resource;
|
||||||
}
|
}
|
||||||
resource.timeSinceLastExtraction = saveData.TimeSinceLastExtraction;
|
|
||||||
return resource;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ResourceSaveData CreateSaveData()
|
public ResourceSaveData CreateSaveData()
|
||||||
{
|
{
|
||||||
return new ResourceSaveData
|
return new ResourceSaveData
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
CurrentAmount = currentAmount,
|
CurrentAmount = currentAmount,
|
||||||
MaxAmount = maxAmount,
|
MaxAmount = maxAmount,
|
||||||
IsEndless = isEndless,
|
IsEndless = isEndless,
|
||||||
ExtractionSpeed = extractionSpeed,
|
ExtractionSpeed = extractionSpeed,
|
||||||
TimeSinceLastExtraction = timeSinceLastExtraction
|
TimeSinceLastExtraction = timeSinceLastExtraction
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Extract(double delta)
|
public bool Extract(double delta)
|
||||||
{
|
{
|
||||||
timeSinceLastExtraction += delta;
|
timeSinceLastExtraction += delta;
|
||||||
if (timeSinceLastExtraction < extractionSpeed) return false;
|
if (timeSinceLastExtraction < extractionSpeed) return false;
|
||||||
|
|
||||||
timeSinceLastExtraction = 0;
|
timeSinceLastExtraction = 0;
|
||||||
if (isEndless) return true;
|
if (isEndless) return true;
|
||||||
|
|
||||||
if (currentAmount > 0)
|
if (currentAmount <= 0) return false;
|
||||||
{
|
|
||||||
currentAmount--;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
currentAmount--;
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool CanExtract()
|
public bool CanExtract()
|
||||||
{
|
{
|
||||||
return (isEndless || currentAmount > 0) && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED;
|
return (isEndless || currentAmount > 0)
|
||||||
}
|
&& GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED;
|
||||||
|
}
|
||||||
|
|
||||||
public void MakeEndless()
|
public void MakeEndless()
|
||||||
{
|
{
|
||||||
isEndless = true;
|
isEndless = true;
|
||||||
extractionSpeed = EndlessExtractionSpeed;
|
extractionSpeed = EndlessExtractionSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsEndless()
|
public bool IsEndless()
|
||||||
{
|
{
|
||||||
return isEndless;
|
return isEndless;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetExtractionSpeed()
|
public float GetExtractionSpeed()
|
||||||
{
|
{
|
||||||
return extractionSpeed;
|
return extractionSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NormalizeExtractionSpeed()
|
||||||
|
{
|
||||||
|
if (!isEndless) return;
|
||||||
|
if (extractionSpeed > NormalExtractionSpeed) return;
|
||||||
|
|
||||||
|
extractionSpeed = EndlessExtractionSpeed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -33,58 +33,70 @@ public partial class Robot : Node3D
|
|||||||
|
|
||||||
if (isExecuting)
|
if (isExecuting)
|
||||||
{
|
{
|
||||||
if (CanExecute(delta))
|
UpdateExecution(delta);
|
||||||
{
|
|
||||||
switch (currentNode.Execute(this, delta))
|
|
||||||
{
|
|
||||||
case NodeResult.SUCCESS:
|
|
||||||
currentNode = currentNode.nextNode;
|
|
||||||
if (currentNode == null)
|
|
||||||
{
|
|
||||||
isExecuting = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NodeResult.FAILURE:
|
|
||||||
isExecuting = false;
|
|
||||||
currentMessage = "(FAILED)" + currentNode.lastExecutionMessage;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case NodeResult.RUNNING:
|
|
||||||
currentMessage = "";
|
|
||||||
break;
|
|
||||||
case NodeResult.CONDITIONFALSE:
|
|
||||||
currentNode = currentNode.NegativeNode;
|
|
||||||
if (currentNode == null)
|
|
||||||
{
|
|
||||||
isExecuting = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (currentMessage.Length <= 0)
|
|
||||||
{
|
|
||||||
CoolDown(
|
|
||||||
delta,
|
|
||||||
GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond)
|
|
||||||
* TypeStats.CoolingMultiplier
|
|
||||||
);
|
|
||||||
|
|
||||||
currentMessage = "No script executing";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CoolDown(
|
UpdateIdle(delta);
|
||||||
delta,
|
|
||||||
GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond)
|
|
||||||
* TypeStats.CoolingMultiplier
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer;
|
Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateExecution(double delta)
|
||||||
|
{
|
||||||
|
if (!CanExecute(delta)) return;
|
||||||
|
|
||||||
|
NodeResult result = currentNode.Execute(this, delta);
|
||||||
|
ApplyNodeResult(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyNodeResult(NodeResult result)
|
||||||
|
{
|
||||||
|
switch (result)
|
||||||
|
{
|
||||||
|
case NodeResult.SUCCESS:
|
||||||
|
MoveToNextNode(currentNode.nextNode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NodeResult.CONDITIONFALSE:
|
||||||
|
MoveToNextNode(currentNode.NegativeNode);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NodeResult.FAILURE:
|
||||||
|
isExecuting = false;
|
||||||
|
currentMessage = "(FAILED)" + currentNode.lastExecutionMessage;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NodeResult.RUNNING:
|
||||||
|
currentMessage = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveToNextNode(ProgramNode nextNode)
|
||||||
|
{
|
||||||
|
currentNode = nextNode;
|
||||||
|
if (currentNode == null)
|
||||||
|
{
|
||||||
|
isExecuting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateIdle(double delta)
|
||||||
|
{
|
||||||
|
CoolDown(
|
||||||
|
delta,
|
||||||
|
GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond)
|
||||||
|
* TypeStats.CoolingMultiplier
|
||||||
|
);
|
||||||
|
|
||||||
|
if (currentMessage.Length <= 0)
|
||||||
|
{
|
||||||
|
currentMessage = "No script executing";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetupExecution(List<ProgramNode> nodes)
|
public void SetupExecution(List<ProgramNode> nodes)
|
||||||
{
|
{
|
||||||
if (nodes.Count <= 0) return;
|
if (nodes.Count <= 0) return;
|
||||||
@@ -200,32 +212,13 @@ public partial class Robot : Node3D
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
float energyUse =
|
if (!TryConsumeEnergy(delta))
|
||||||
GameData.robotStats.GetEnergyUse(EnergyUsePerSecond)
|
|
||||||
* TypeStats.EnergyUseMultiplier
|
|
||||||
* (float)delta;
|
|
||||||
|
|
||||||
if (!GameData.survival.TryConsumeEnergy(energyUse))
|
|
||||||
{
|
{
|
||||||
currentMessage = "Not enough energy";
|
currentMessage = "Not enough energy";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
heat = Math.Clamp(
|
ApplyWear(delta);
|
||||||
heat + GameData.robotStats.GetHeatGain(HeatGainPerSecond)
|
|
||||||
* TypeStats.HeatGainMultiplier
|
|
||||||
* (float)delta,
|
|
||||||
0f,
|
|
||||||
100f
|
|
||||||
);
|
|
||||||
|
|
||||||
maintenance = Math.Clamp(
|
|
||||||
maintenance - GameData.robotStats.GetMaintenanceLoss(MaintenanceLossPerSecond)
|
|
||||||
* TypeStats.MaintenanceLossMultiplier
|
|
||||||
* (float)delta,
|
|
||||||
0f,
|
|
||||||
100f
|
|
||||||
);
|
|
||||||
|
|
||||||
if (heat >= 100f)
|
if (heat >= 100f)
|
||||||
{
|
{
|
||||||
@@ -244,6 +237,35 @@ public partial class Robot : Node3D
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool TryConsumeEnergy(double delta)
|
||||||
|
{
|
||||||
|
float energyUse =
|
||||||
|
GameData.robotStats.GetEnergyUse(EnergyUsePerSecond)
|
||||||
|
* TypeStats.EnergyUseMultiplier
|
||||||
|
* (float)delta;
|
||||||
|
|
||||||
|
return GameData.survival.TryConsumeEnergy(energyUse);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyWear(double delta)
|
||||||
|
{
|
||||||
|
heat = Math.Clamp(
|
||||||
|
heat + GameData.robotStats.GetHeatGain(HeatGainPerSecond)
|
||||||
|
* TypeStats.HeatGainMultiplier
|
||||||
|
* (float)delta,
|
||||||
|
0f,
|
||||||
|
100f
|
||||||
|
);
|
||||||
|
|
||||||
|
maintenance = Math.Clamp(
|
||||||
|
maintenance - GameData.robotStats.GetMaintenanceLoss(MaintenanceLossPerSecond)
|
||||||
|
* TypeStats.MaintenanceLossMultiplier
|
||||||
|
* (float)delta,
|
||||||
|
0f,
|
||||||
|
100f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private void CoolDown(double delta, float heatLossPerSecond)
|
private void CoolDown(double delta, float heatLossPerSecond)
|
||||||
{
|
{
|
||||||
heat = Math.Clamp(
|
heat = Math.Clamp(
|
||||||
|
|||||||
@@ -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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dn38ysswhpy04
|
||||||
@@ -17,8 +17,7 @@ public partial class Camera3d : Camera3D
|
|||||||
{
|
{
|
||||||
Control focused = GetViewport().GuiGetFocusOwner();
|
Control focused = GetViewport().GuiGetFocusOwner();
|
||||||
|
|
||||||
if (focused is LineEdit || focused is TextEdit)
|
if (focused is LineEdit || focused is TextEdit) return;
|
||||||
return;
|
|
||||||
if (canMove) MoveCamera(delta);
|
if (canMove) MoveCamera(delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,17 +37,13 @@ public partial class Camera3d : Camera3D
|
|||||||
|
|
||||||
if (direction != Vector3.Zero)
|
if (direction != Vector3.Zero)
|
||||||
{
|
{
|
||||||
if(robot != null) robot = null;
|
robot = null;
|
||||||
direction = direction.Normalized() * Speed * (Input.IsActionPressed("sprint") ? 2.5f : 1) * d;
|
direction = direction.Normalized() * Speed * (Input.IsActionPressed("sprint") ? 2.5f : 1) * d;
|
||||||
Translate(direction);
|
Translate(direction);
|
||||||
}
|
}
|
||||||
else
|
else if (robot != null)
|
||||||
{
|
{
|
||||||
if(robot != null)
|
Position = new Vector3(robot.Position.X, 10 - visibleLayer * 4, robot.Position.Z + 4f);
|
||||||
{
|
|
||||||
Position = new Vector3(robot.Position.X, 10 - visibleLayer * 4, robot.Position.Z + 4f);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Position.Y != 10 - visibleLayer * 4)
|
if (Position.Y != 10 - visibleLayer * 4)
|
||||||
|
|||||||
+20
-103
@@ -1,5 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
public partial class UIHandler : Control
|
public partial class UIHandler : Control
|
||||||
@@ -46,10 +44,7 @@ public partial class UIHandler : Control
|
|||||||
DisplayStats();
|
DisplayStats();
|
||||||
DisplayRobotAlarm();
|
DisplayRobotAlarm();
|
||||||
|
|
||||||
Control focused = GetViewport().GuiGetFocusOwner();
|
if (IsTextInputFocused()) return;
|
||||||
|
|
||||||
if (focused is LineEdit || focused is TextEdit)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (Input.IsActionJustPressed("map")) HandleMapButton();
|
if (Input.IsActionJustPressed("map")) HandleMapButton();
|
||||||
if (Input.IsActionJustPressed("menu")) HandleMenuButton();
|
if (Input.IsActionJustPressed("menu")) HandleMenuButton();
|
||||||
@@ -58,22 +53,29 @@ public partial class UIHandler : Control
|
|||||||
if (Input.IsActionJustPressed("research")) HandleResearchButton();
|
if (Input.IsActionJustPressed("research")) HandleResearchButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsTextInputFocused()
|
||||||
|
{
|
||||||
|
Control focused = GetViewport().GuiGetFocusOwner();
|
||||||
|
return focused is LineEdit || focused is TextEdit;
|
||||||
|
}
|
||||||
|
|
||||||
public void HandleMenuButton()
|
public void HandleMenuButton()
|
||||||
{
|
{
|
||||||
if(GameData.survival.isDead) return;
|
if (GameData.survival.isDead) return;
|
||||||
|
|
||||||
OpenUIElement(menu);
|
OpenUIElement(menu);
|
||||||
GameData.isPaused = menu.Visible || options.Visible;
|
GameData.isPaused = menu.Visible || options.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleMenu()
|
public void HandleMenu()
|
||||||
{
|
{
|
||||||
if(GameData.survival.isDead) return;
|
|
||||||
HandleMenuButton();
|
HandleMenuButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowOptions()
|
public void ShowOptions()
|
||||||
{
|
{
|
||||||
if(GameData.survival.isDead) return;
|
if (GameData.survival.isDead) return;
|
||||||
|
|
||||||
menu.Hide();
|
menu.Hide();
|
||||||
OpenUIElement(options);
|
OpenUIElement(options);
|
||||||
GameData.isPaused = options.Visible;
|
GameData.isPaused = options.Visible;
|
||||||
@@ -81,14 +83,16 @@ public partial class UIHandler : Control
|
|||||||
|
|
||||||
public void HandleMapButton()
|
public void HandleMapButton()
|
||||||
{
|
{
|
||||||
if(GameData.survival.isDead) return;
|
if (GameData.survival.isDead) return;
|
||||||
|
|
||||||
OpenUIElement(map);
|
OpenUIElement(map);
|
||||||
if (map.Visible) map.ShowMap();
|
if (map.Visible) map.ShowMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleRobotListButton()
|
public void HandleRobotListButton()
|
||||||
{
|
{
|
||||||
if(GameData.survival.isDead) return;
|
if (GameData.survival.isDead) return;
|
||||||
|
|
||||||
receivedRobotFollowSignal = false;
|
receivedRobotFollowSignal = false;
|
||||||
receivedRobotJumpSignal = false;
|
receivedRobotJumpSignal = false;
|
||||||
OpenUIElement(robotList);
|
OpenUIElement(robotList);
|
||||||
@@ -96,28 +100,19 @@ public partial class UIHandler : Control
|
|||||||
|
|
||||||
public void HandleInventoryButton()
|
public void HandleInventoryButton()
|
||||||
{
|
{
|
||||||
if(GameData.survival.isDead) return;
|
if (GameData.survival.isDead) return;
|
||||||
|
|
||||||
OpenUIElement(inventory);
|
OpenUIElement(inventory);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HandleResearchButton()
|
public void HandleResearchButton()
|
||||||
{
|
{
|
||||||
if(GameData.survival.isDead) return;
|
if (GameData.survival.isDead) return;
|
||||||
|
|
||||||
OpenUIElement(researchList);
|
OpenUIElement(researchList);
|
||||||
if (researchList.Visible) researchList.SetupGraph();
|
if (researchList.Visible) researchList.SetupGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisplayStats()
|
|
||||||
{
|
|
||||||
FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS";
|
|
||||||
double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024);
|
|
||||||
string memoryDisplay = memory > 1024 ? Math.Round(memory / 1024, 2).ToString() + " GB" : memory.ToString() + " MB";
|
|
||||||
RAM.Text = memoryDisplay;
|
|
||||||
DisplaySurvivalStats();
|
|
||||||
DisplayWorldStats();
|
|
||||||
DisplayLoseCondition();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExitGame()
|
public void ExitGame()
|
||||||
{
|
{
|
||||||
GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn");
|
GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn");
|
||||||
@@ -139,14 +134,7 @@ public partial class UIHandler : Control
|
|||||||
public void OpenUIElement(Control element)
|
public void OpenUIElement(Control element)
|
||||||
{
|
{
|
||||||
SoundManager.PlayButton();
|
SoundManager.PlayButton();
|
||||||
if (element.Visible)
|
element.Visible = !element.Visible;
|
||||||
{
|
|
||||||
element.Hide();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
element.Show();
|
|
||||||
}
|
|
||||||
HideUIElements(element);
|
HideUIElements(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,25 +152,6 @@ public partial class UIHandler : Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisplayRobotAlarm()
|
|
||||||
{
|
|
||||||
string messages = "";
|
|
||||||
if (GameData.survival.isDead)
|
|
||||||
{
|
|
||||||
messages += GameData.survival.currentStatus + "\r";
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Robot robot in GameData.robots)
|
|
||||||
{
|
|
||||||
if (robot.currentMessage.Length > 0)
|
|
||||||
{
|
|
||||||
messages += $"{robot.Name}: {robot.currentMessage}\r";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
robotAlarm.Visible = messages.Length > 0;
|
|
||||||
robotAlarm.TooltipText = messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRobotJumpTo(Robot robot)
|
private void OnRobotJumpTo(Robot robot)
|
||||||
{
|
{
|
||||||
if (receivedRobotJumpSignal) return;
|
if (receivedRobotJumpSignal) return;
|
||||||
@@ -201,42 +170,6 @@ public partial class UIHandler : Control
|
|||||||
mainCam.Follow(robot);
|
mainCam.Follow(robot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisplaySurvivalStats()
|
|
||||||
{
|
|
||||||
energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}";
|
|
||||||
waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}";
|
|
||||||
hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}";
|
|
||||||
survivalStatus.Text = GameData.survival.currentStatus;
|
|
||||||
survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical")
|
|
||||||
? UIStyle.GetWarningColor()
|
|
||||||
: Colors.White;
|
|
||||||
|
|
||||||
if (GameData.survival.isDead)
|
|
||||||
{
|
|
||||||
ShowGameOver();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisplayWorldStats()
|
|
||||||
{
|
|
||||||
currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}";
|
|
||||||
deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}";
|
|
||||||
if (GameData.lowestLayer == GameData.ruinSize)
|
|
||||||
{
|
|
||||||
unlockLayer.Visible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients();
|
|
||||||
unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DisplayLoseCondition()
|
|
||||||
{
|
|
||||||
if (!GameData.HasNoRobotRecovery()) return;
|
|
||||||
|
|
||||||
ShowGameOver("No robots remain and no robot can be spawned from inventory.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UnlockLayer()
|
public void UnlockLayer()
|
||||||
{
|
{
|
||||||
if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1))
|
if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1))
|
||||||
@@ -259,20 +192,4 @@ public partial class UIHandler : Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowGameOver()
|
|
||||||
{
|
|
||||||
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowGameOver(string message)
|
|
||||||
{
|
|
||||||
if (gameOver.Visible) return;
|
|
||||||
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
|
|
||||||
gameOver.Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void HideGameOver()
|
|
||||||
{
|
|
||||||
gameOver.Hide();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
public partial class UIHandler
|
||||||
|
{
|
||||||
|
public void DisplayStats()
|
||||||
|
{
|
||||||
|
FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS";
|
||||||
|
RAM.Text = GetMemoryDisplay();
|
||||||
|
DisplaySurvivalStats();
|
||||||
|
DisplayWorldStats();
|
||||||
|
DisplayLoseCondition();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetMemoryDisplay()
|
||||||
|
{
|
||||||
|
double memory = Process.GetCurrentProcess().WorkingSet64 / (1024 * 1024);
|
||||||
|
if (memory > 1024)
|
||||||
|
{
|
||||||
|
return Math.Round(memory / 1024, 2).ToString() + " GB";
|
||||||
|
}
|
||||||
|
|
||||||
|
return memory.ToString() + " MB";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayRobotAlarm()
|
||||||
|
{
|
||||||
|
string messages = "";
|
||||||
|
if (GameData.survival.isDead)
|
||||||
|
{
|
||||||
|
messages += GameData.survival.currentStatus + "\r";
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Robot robot in GameData.robots)
|
||||||
|
{
|
||||||
|
if (robot.currentMessage.Length > 0)
|
||||||
|
{
|
||||||
|
messages += $"{robot.Name}: {robot.currentMessage}\r";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
robotAlarm.Visible = messages.Length > 0;
|
||||||
|
robotAlarm.TooltipText = messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplaySurvivalStats()
|
||||||
|
{
|
||||||
|
energyLabel.Text = $"Energy: {GameData.survival.energy:0}/{GameData.survival.maxEnergy:0}";
|
||||||
|
waterLabel.Text = $"Water: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}";
|
||||||
|
hungerLabel.Text = $"Food: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}";
|
||||||
|
survivalStatus.Text = GameData.survival.currentStatus;
|
||||||
|
survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical")
|
||||||
|
? UIStyle.GetWarningColor()
|
||||||
|
: Colors.White;
|
||||||
|
|
||||||
|
if (GameData.survival.isDead)
|
||||||
|
{
|
||||||
|
ShowGameOver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayWorldStats()
|
||||||
|
{
|
||||||
|
currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}";
|
||||||
|
deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}";
|
||||||
|
if (GameData.lowestLayer == GameData.ruinSize)
|
||||||
|
{
|
||||||
|
unlockLayer.Visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients();
|
||||||
|
unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayLoseCondition()
|
||||||
|
{
|
||||||
|
if (!GameData.HasNoRobotRecovery()) return;
|
||||||
|
|
||||||
|
ShowGameOver("No robots remain and no robot can be spawned from inventory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowGameOver()
|
||||||
|
{
|
||||||
|
ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowGameOver(string message)
|
||||||
|
{
|
||||||
|
if (gameOver.Visible) return;
|
||||||
|
gameOver.GetNode<RichTextLabel>("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
|
||||||
|
gameOver.Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HideGameOver()
|
||||||
|
{
|
||||||
|
gameOver.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://b2q7e88lokg3l
|
||||||
+70
-327
@@ -13,15 +13,6 @@ public partial class CodingWindow : PanelContainer
|
|||||||
[Export] LineEdit nameInput;
|
[Export] LineEdit nameInput;
|
||||||
|
|
||||||
public System.Collections.Generic.Dictionary<ProgramNode, PackedScene> DSLNodes;
|
public System.Collections.Generic.Dictionary<ProgramNode, PackedScene> DSLNodes;
|
||||||
private System.Collections.Generic.Dictionary<StringName, ProgramNode> availableNodes;
|
|
||||||
|
|
||||||
private class ScriptConnection
|
|
||||||
{
|
|
||||||
public string FromNodeId;
|
|
||||||
public int FromPort;
|
|
||||||
public string ToNodeId;
|
|
||||||
public int ToPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
@@ -51,6 +42,7 @@ public partial class CodingWindow : PanelContainer
|
|||||||
{
|
{
|
||||||
availableScripts.Clear();
|
availableScripts.Clear();
|
||||||
availableScripts.AddItem("Select script to load...");
|
availableScripts.AddItem("Select script to load...");
|
||||||
|
|
||||||
List<string> scripts = FileHandler.LoadProgramNames();
|
List<string> scripts = FileHandler.LoadProgramNames();
|
||||||
scripts.Sort((a, b) => a.CompareTo(b));
|
scripts.Sort((a, b) => a.CompareTo(b));
|
||||||
foreach (string script in scripts)
|
foreach (string script in scripts)
|
||||||
@@ -73,10 +65,9 @@ public partial class CodingWindow : PanelContainer
|
|||||||
|
|
||||||
public void GenerateCodingBlocks()
|
public void GenerateCodingBlocks()
|
||||||
{
|
{
|
||||||
Button nodeListButton;
|
|
||||||
foreach (ProgramNode nodeTemplate in DSLNodes.Keys)
|
foreach (ProgramNode nodeTemplate in DSLNodes.Keys)
|
||||||
{
|
{
|
||||||
nodeListButton = new Button
|
Button nodeListButton = new Button
|
||||||
{
|
{
|
||||||
Name = nodeTemplate.DisplayText,
|
Name = nodeTemplate.DisplayText,
|
||||||
Text = nodeTemplate.DisplayText
|
Text = nodeTemplate.DisplayText
|
||||||
@@ -92,17 +83,14 @@ public partial class CodingWindow : PanelContainer
|
|||||||
private void AddEditorNode(ProgramNode node)
|
private void AddEditorNode(ProgramNode node)
|
||||||
{
|
{
|
||||||
NodeDisplay editorDisplay = DSLNodes[node].Instantiate<NodeDisplay>();
|
NodeDisplay editorDisplay = DSLNodes[node].Instantiate<NodeDisplay>();
|
||||||
editorDisplay.PositionOffset = (editorWindow.ScrollOffset + editorWindow.Size / 2) / editorWindow.Zoom - editorDisplay.Size / 2;
|
editorDisplay.PositionOffset = GetVisibleGraphCenter() - editorDisplay.Size / 2f;
|
||||||
editorWindow.AddChild(editorDisplay);
|
editorWindow.AddChild(editorDisplay);
|
||||||
RegisterEditorNode(editorDisplay);
|
RegisterEditorNode(editorDisplay);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MoveNodeToVisibleGraphCenter(NodeDisplay nodeDisplay)
|
private Vector2 GetVisibleGraphCenter()
|
||||||
{
|
{
|
||||||
Vector2 visibleCenter = editorWindow.ScrollOffset
|
return (editorWindow.ScrollOffset + editorWindow.Size / 2f) / editorWindow.Zoom;
|
||||||
+ editorWindow.Size / (2f * editorWindow.Zoom);
|
|
||||||
|
|
||||||
nodeDisplay.PositionOffset = visibleCenter - nodeDisplay.Size / (2f * editorWindow.Zoom);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterEditorNode(NodeDisplay editorDisplay)
|
private void RegisterEditorNode(NodeDisplay editorDisplay)
|
||||||
@@ -115,6 +103,13 @@ public partial class CodingWindow : PanelContainer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ClearWindow()
|
public void ClearWindow()
|
||||||
|
{
|
||||||
|
DisconnectAllNodes();
|
||||||
|
RemoveEditorNodes();
|
||||||
|
scriptName.Text = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisconnectAllNodes()
|
||||||
{
|
{
|
||||||
foreach (Dictionary connection in editorWindow.GetConnectionList())
|
foreach (Dictionary connection in editorWindow.GetConnectionList())
|
||||||
{
|
{
|
||||||
@@ -125,109 +120,41 @@ public partial class CodingWindow : PanelContainer
|
|||||||
(int)connection["to_port"]
|
(int)connection["to_port"]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveEditorNodes()
|
||||||
|
{
|
||||||
foreach (Node child in editorWindow.GetChildren())
|
foreach (Node child in editorWindow.GetChildren())
|
||||||
{
|
{
|
||||||
if (child is GraphNode)
|
if (child is not GraphNode) continue;
|
||||||
{
|
|
||||||
editorWindow.RemoveChild(child);
|
editorWindow.RemoveChild(child);
|
||||||
child.QueueFree();
|
child.QueueFree();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
scriptName.Text = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CompileProgram()
|
public void CompileProgram()
|
||||||
{
|
{
|
||||||
if (robot == null) return;
|
if (robot == null) return;
|
||||||
|
|
||||||
NodeDisplay startNode = FindStartNode();
|
ScriptGraphCompiler compiler = new ScriptGraphCompiler(editorWindow);
|
||||||
if (startNode == null)
|
string errorMessage;
|
||||||
|
List<ProgramNode> nodes = compiler.BuildProgram(out errorMessage);
|
||||||
|
if (errorMessage.Length > 0)
|
||||||
{
|
{
|
||||||
robot.StopExecution("(FAILED) Script needs exactly one Start node");
|
robot.StopExecution(errorMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildAvailableNodeLookup();
|
|
||||||
List<ProgramNode> nodes = BuildScriptOrder(
|
|
||||||
startNode,
|
|
||||||
new List<ProgramNode>(),
|
|
||||||
new HashSet<StringName>()
|
|
||||||
);
|
|
||||||
if (nodes.Count > 0) robot.SetupExecution(nodes);
|
if (nodes.Count > 0) robot.SetupExecution(nodes);
|
||||||
robot.currentProgram = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text;
|
robot.currentProgram = GetCurrentScriptName();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildAvailableNodeLookup()
|
private string GetCurrentScriptName()
|
||||||
{
|
{
|
||||||
availableNodes = new System.Collections.Generic.Dictionary<StringName, ProgramNode>();
|
if (scriptName.Text.Length > 0) return scriptName.Text;
|
||||||
|
|
||||||
for (int i = 0; i < editorWindow.GetChildCount(); i++)
|
return $"Script{availableScripts.ItemCount}";
|
||||||
{
|
|
||||||
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
|
|
||||||
if (nodeDisplay == null) continue;
|
|
||||||
|
|
||||||
nodeDisplay.ReadParameters();
|
|
||||||
availableNodes.Add(nodeDisplay.Name, nodeDisplay.node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ProgramNode> BuildScriptOrder(
|
|
||||||
NodeDisplay node,
|
|
||||||
List<ProgramNode> program,
|
|
||||||
HashSet<StringName> visitedNodes
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (node == null) return program;
|
|
||||||
if (visitedNodes.Contains(node.Name)) return program;
|
|
||||||
|
|
||||||
visitedNodes.Add(node.Name);
|
|
||||||
program.Add(node.node);
|
|
||||||
if (editorWindow.GetConnectionListFromNode(node.Name).Count <= 0) return program;
|
|
||||||
List<Dictionary> nextConnections = CheckNodeConnections(node);
|
|
||||||
if (nextConnections.Count <= 0) return program;
|
|
||||||
node.node.SetNextNode(nextConnections, availableNodes);
|
|
||||||
foreach (Dictionary connection in nextConnections)
|
|
||||||
{
|
|
||||||
NodeDisplay nextNode = editorWindow.GetNodeOrNull<NodeDisplay>(
|
|
||||||
new NodePath(connection["to_node"].AsStringName())
|
|
||||||
);
|
|
||||||
program = BuildScriptOrder(
|
|
||||||
nextNode,
|
|
||||||
program,
|
|
||||||
visitedNodes
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return program;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<Dictionary> CheckNodeConnections(NodeDisplay node)
|
|
||||||
{
|
|
||||||
List<Dictionary> result = new List<Dictionary>();
|
|
||||||
Array<Dictionary> connections = editorWindow.GetConnectionListFromNode(node.Name);
|
|
||||||
for (int i = 0; i < connections.Count; i++)
|
|
||||||
{
|
|
||||||
if (connections[i]["from_node"].AsStringName() == node.Name)
|
|
||||||
{
|
|
||||||
result.Add(connections[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private NodeDisplay FindStartNode()
|
|
||||||
{
|
|
||||||
NodeDisplay startNode = null;
|
|
||||||
for (int i = 0; i < editorWindow.GetChildCount(); i++)
|
|
||||||
{
|
|
||||||
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
|
|
||||||
if (nodeDisplay == null) continue;
|
|
||||||
if (nodeDisplay.node is not StartNode) continue;
|
|
||||||
if (startNode != null) return null;
|
|
||||||
|
|
||||||
startNode = nodeDisplay;
|
|
||||||
}
|
|
||||||
return startNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRobot(Robot robot)
|
public void SetRobot(Robot robot)
|
||||||
@@ -241,131 +168,15 @@ public partial class CodingWindow : PanelContainer
|
|||||||
|
|
||||||
ClearWindow();
|
ClearWindow();
|
||||||
string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index));
|
string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index));
|
||||||
LoadStructuredProgram(scriptContent);
|
CreateSerializer().Load(scriptContent);
|
||||||
scriptName.Text = availableScripts.GetItemText(index);
|
scriptName.Text = availableScripts.GetItemText(index);
|
||||||
availableScripts.Select(0);
|
availableScripts.Select(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadStructuredProgram(string scriptContent)
|
private void AddLoadedNode(NodeDisplay nodeDisplay)
|
||||||
{
|
{
|
||||||
Variant parsedScript = Json.ParseString(scriptContent);
|
editorWindow.AddChild(nodeDisplay);
|
||||||
if (parsedScript.VariantType != Variant.Type.Dictionary) return;
|
RegisterEditorNode(nodeDisplay);
|
||||||
|
|
||||||
Dictionary scriptData = parsedScript.AsGodotDictionary();
|
|
||||||
if (!scriptData.ContainsKey("Nodes")) return;
|
|
||||||
|
|
||||||
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes =
|
|
||||||
new System.Collections.Generic.Dictionary<string, NodeDisplay>();
|
|
||||||
Array nodes = scriptData["Nodes"].AsGodotArray();
|
|
||||||
for (int i = 0; i < nodes.Count; i++)
|
|
||||||
{
|
|
||||||
Dictionary nodeData = nodes[i].AsGodotDictionary();
|
|
||||||
NodeDisplay nodeDisplay = LoadStructuredNode(nodeData);
|
|
||||||
if (nodeDisplay == null) continue;
|
|
||||||
|
|
||||||
editorWindow.AddChild(nodeDisplay);
|
|
||||||
RegisterEditorNode(nodeDisplay);
|
|
||||||
RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadStructuredConnections(scriptData, loadedNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterLoadedNode(
|
|
||||||
Dictionary nodeData,
|
|
||||||
NodeDisplay nodeDisplay,
|
|
||||||
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
|
|
||||||
)
|
|
||||||
{
|
|
||||||
string nodeId = nodeDisplay.Name.ToString();
|
|
||||||
if (nodeData.ContainsKey("Id"))
|
|
||||||
{
|
|
||||||
nodeId = nodeData["Id"].AsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!loadedNodes.ContainsKey(nodeId))
|
|
||||||
{
|
|
||||||
loadedNodes.Add(nodeId, nodeDisplay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private NodeDisplay LoadStructuredNode(Dictionary nodeData)
|
|
||||||
{
|
|
||||||
if (!nodeData.ContainsKey("Type")) return null;
|
|
||||||
if (!nodeData.ContainsKey("Content")) return null;
|
|
||||||
|
|
||||||
string type = nodeData["Type"].AsString();
|
|
||||||
string content = nodeData["Content"].AsString();
|
|
||||||
NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, DSLNodes);
|
|
||||||
if (nodeDisplay == null) return null;
|
|
||||||
|
|
||||||
if (nodeData.ContainsKey("Id"))
|
|
||||||
{
|
|
||||||
nodeDisplay.Name = nodeData["Id"].AsString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY"))
|
|
||||||
{
|
|
||||||
float positionX = (float)nodeData["PositionX"].AsDouble();
|
|
||||||
float positionY = (float)nodeData["PositionY"].AsDouble();
|
|
||||||
nodeDisplay.PositionOffset = new Vector2(positionX, positionY);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodeDisplay;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadStructuredConnections(
|
|
||||||
Dictionary scriptData,
|
|
||||||
System.Collections.Generic.Dictionary<string, NodeDisplay> loadedNodes
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (!scriptData.ContainsKey("Connections")) return;
|
|
||||||
|
|
||||||
Array connectionData = scriptData["Connections"].AsGodotArray();
|
|
||||||
for (int i = 0; i < connectionData.Count; i++)
|
|
||||||
{
|
|
||||||
Dictionary savedConnection = connectionData[i].AsGodotDictionary();
|
|
||||||
if (!savedConnection.ContainsKey("From")) continue;
|
|
||||||
if (!savedConnection.ContainsKey("To")) continue;
|
|
||||||
if (!savedConnection.ContainsKey("FromPort")) continue;
|
|
||||||
if (!savedConnection.ContainsKey("ToPort")) continue;
|
|
||||||
|
|
||||||
ScriptConnection connection = new ScriptConnection
|
|
||||||
{
|
|
||||||
FromNodeId = savedConnection["From"].AsString(),
|
|
||||||
FromPort = savedConnection["FromPort"].AsInt32(),
|
|
||||||
ToNodeId = savedConnection["To"].AsString(),
|
|
||||||
ToPort = savedConnection["ToPort"].AsInt32()
|
|
||||||
};
|
|
||||||
if (!loadedNodes.ContainsKey(connection.FromNodeId)) continue;
|
|
||||||
if (!loadedNodes.ContainsKey(connection.ToNodeId)) continue;
|
|
||||||
|
|
||||||
NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId];
|
|
||||||
NodeDisplay toDisplay = loadedNodes[connection.ToNodeId];
|
|
||||||
if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) continue;
|
|
||||||
|
|
||||||
editorWindow.ConnectNode(
|
|
||||||
fromDisplay.Name,
|
|
||||||
connection.FromPort,
|
|
||||||
toDisplay.Name,
|
|
||||||
connection.ToPort
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ConnectionExists(StringName fromNode, int fromPort, StringName toNode, int toPort)
|
|
||||||
{
|
|
||||||
foreach (Dictionary connection in editorWindow.GetConnectionList())
|
|
||||||
{
|
|
||||||
if (connection["from_node"].AsStringName() != fromNode) continue;
|
|
||||||
if ((int)connection["from_port"] != fromPort) continue;
|
|
||||||
if (connection["to_node"].AsStringName() != toNode) continue;
|
|
||||||
if ((int)connection["to_port"] != toPort) continue;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadTemporaryProgram()
|
public void LoadTemporaryProgram()
|
||||||
@@ -373,65 +184,16 @@ public partial class CodingWindow : PanelContainer
|
|||||||
if (robot == null) return;
|
if (robot == null) return;
|
||||||
if (robot.currentNode == null) return;
|
if (robot.currentNode == null) return;
|
||||||
|
|
||||||
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes =
|
RunningProgramGraphBuilder builder = new RunningProgramGraphBuilder(
|
||||||
new System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay>();
|
DSLNodes,
|
||||||
LoadTemporaryNode(robot.currentNode, loadedNodes);
|
AddLoadedNode,
|
||||||
|
ConnectNodes
|
||||||
|
);
|
||||||
|
builder.Load(robot.currentNode);
|
||||||
|
|
||||||
scriptName.Text = robot.currentProgram ?? "";
|
scriptName.Text = robot.currentProgram ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
private NodeDisplay LoadTemporaryNode(
|
|
||||||
ProgramNode programNode,
|
|
||||||
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes
|
|
||||||
)
|
|
||||||
{
|
|
||||||
if (programNode == null) return null;
|
|
||||||
if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode];
|
|
||||||
|
|
||||||
NodeDisplay nodeDisplay = NodeDisplay.Load(
|
|
||||||
programNode.DisplayText,
|
|
||||||
programNode.Save(),
|
|
||||||
DSLNodes
|
|
||||||
);
|
|
||||||
if (nodeDisplay == null) return null;
|
|
||||||
|
|
||||||
editorWindow.AddChild(nodeDisplay);
|
|
||||||
RegisterEditorNode(nodeDisplay);
|
|
||||||
loadedNodes.Add(programNode, nodeDisplay);
|
|
||||||
|
|
||||||
ConnectTemporaryNode(nodeDisplay, 0, programNode.nextNode, loadedNodes);
|
|
||||||
ConnectTemporaryNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes);
|
|
||||||
|
|
||||||
return nodeDisplay;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ConnectTemporaryNode(
|
|
||||||
NodeDisplay fromDisplay,
|
|
||||||
int fromPort,
|
|
||||||
ProgramNode targetNode,
|
|
||||||
System.Collections.Generic.Dictionary<ProgramNode, NodeDisplay> loadedNodes
|
|
||||||
)
|
|
||||||
{
|
|
||||||
NodeDisplay toDisplay = LoadTemporaryNode(targetNode, loadedNodes);
|
|
||||||
if (toDisplay == null) return;
|
|
||||||
|
|
||||||
ScriptConnection connection = new ScriptConnection
|
|
||||||
{
|
|
||||||
FromNodeId = fromDisplay.Name.ToString(),
|
|
||||||
FromPort = fromPort,
|
|
||||||
ToNodeId = toDisplay.Name.ToString(),
|
|
||||||
ToPort = 0
|
|
||||||
};
|
|
||||||
if (ConnectionExists(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort)) return;
|
|
||||||
|
|
||||||
editorWindow.ConnectNode(
|
|
||||||
fromDisplay.Name,
|
|
||||||
connection.FromPort,
|
|
||||||
toDisplay.Name,
|
|
||||||
connection.ToPort
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DeleteProgram()
|
public void DeleteProgram()
|
||||||
{
|
{
|
||||||
string filename = scriptName.Text;
|
string filename = scriptName.Text;
|
||||||
@@ -450,69 +212,50 @@ public partial class CodingWindow : PanelContainer
|
|||||||
|
|
||||||
public void SaveProgram()
|
public void SaveProgram()
|
||||||
{
|
{
|
||||||
Array<Dictionary> savedNodes = BuildSavedNodes();
|
string result = CreateSerializer().Save();
|
||||||
if (savedNodes.Count <= 0) return;
|
|
||||||
|
|
||||||
Dictionary scriptData = new Dictionary();
|
|
||||||
scriptData["Nodes"] = savedNodes;
|
|
||||||
scriptData["Connections"] = BuildSavedConnections();
|
|
||||||
|
|
||||||
string result = Json.Stringify(scriptData);
|
|
||||||
if (result.Length <= 0) return;
|
if (result.Length <= 0) return;
|
||||||
string filename = scriptName.Text.Length <= 0 ? $"Script{availableScripts.ItemCount}" : scriptName.Text;
|
|
||||||
FileHandler.SaveProgram(filename, result);
|
FileHandler.SaveProgram(GetCurrentScriptName(), result);
|
||||||
SetupScriptOptions();
|
SetupScriptOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Array<Dictionary> BuildSavedNodes()
|
|
||||||
{
|
|
||||||
Array<Dictionary> savedNodes = new Array<Dictionary>();
|
|
||||||
for (int i = 0; i < editorWindow.GetChildCount(); i++)
|
|
||||||
{
|
|
||||||
NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
|
|
||||||
if (nodeDisplay == null) continue;
|
|
||||||
|
|
||||||
nodeDisplay.ReadParameters();
|
|
||||||
Dictionary savedNode = new Dictionary();
|
|
||||||
savedNode["Id"] = nodeDisplay.Name.ToString();
|
|
||||||
savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower();
|
|
||||||
savedNode["Content"] = nodeDisplay.node.Save();
|
|
||||||
savedNode["PositionX"] = nodeDisplay.PositionOffset.X;
|
|
||||||
savedNode["PositionY"] = nodeDisplay.PositionOffset.Y;
|
|
||||||
savedNodes.Add(savedNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return savedNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Array<Dictionary> BuildSavedConnections()
|
|
||||||
{
|
|
||||||
Array<Dictionary> savedConnections = new Array<Dictionary>();
|
|
||||||
foreach (Dictionary connection in editorWindow.GetConnectionList())
|
|
||||||
{
|
|
||||||
Dictionary savedConnection = new Dictionary();
|
|
||||||
savedConnection["From"] = connection["from_node"].AsStringName().ToString();
|
|
||||||
savedConnection["FromPort"] = (int)connection["from_port"];
|
|
||||||
savedConnection["To"] = connection["to_node"].AsStringName().ToString();
|
|
||||||
savedConnection["ToPort"] = (int)connection["to_port"];
|
|
||||||
savedConnections.Add(savedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
return savedConnections;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort)
|
public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort)
|
||||||
{
|
{
|
||||||
if (to == from) return;
|
if (to == from) return;
|
||||||
|
|
||||||
foreach (Dictionary connection in editorWindow.GetConnectionList())
|
ConnectNodes(from, fromPort, to, toPort);
|
||||||
{
|
}
|
||||||
if (connection["from_node"].AsStringName() == from && (int)connection["from_port"] == fromPort) return;
|
|
||||||
}
|
private void ConnectNodes(StringName from, int fromPort, StringName to, int toPort)
|
||||||
|
{
|
||||||
|
if (HasOutputConnection(from, fromPort)) return;
|
||||||
|
|
||||||
editorWindow.ConnectNode(from, fromPort, to, toPort);
|
editorWindow.ConnectNode(from, fromPort, to, toPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool HasOutputConnection(StringName from, int fromPort)
|
||||||
|
{
|
||||||
|
foreach (Dictionary connection in editorWindow.GetConnectionList())
|
||||||
|
{
|
||||||
|
if (connection["from_node"].AsStringName() != from) continue;
|
||||||
|
if ((int)connection["from_port"] != fromPort) continue;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScriptGraphSerializer CreateSerializer()
|
||||||
|
{
|
||||||
|
return new ScriptGraphSerializer(
|
||||||
|
editorWindow,
|
||||||
|
DSLNodes,
|
||||||
|
AddLoadedNode,
|
||||||
|
ConnectNodes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public void OnNodeDisconnect(StringName from, int fromPort, StringName to, int toPort)
|
public void OnNodeDisconnect(StringName from, int fromPort, StringName to, int toPort)
|
||||||
{
|
{
|
||||||
editorWindow.DisconnectNode(from, fromPort, to, toPort);
|
editorWindow.DisconnectNode(from, fromPort, to, toPort);
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
using Godot;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
public class RunningProgramGraphBuilder
|
||||||
|
{
|
||||||
|
private readonly Dictionary<ProgramNode, PackedScene> dslNodes;
|
||||||
|
private readonly Action<NodeDisplay> addNode;
|
||||||
|
private readonly Action<StringName, int, StringName, int> connectNodes;
|
||||||
|
|
||||||
|
public RunningProgramGraphBuilder(
|
||||||
|
Dictionary<ProgramNode, PackedScene> dslNodes,
|
||||||
|
Action<NodeDisplay> addNode,
|
||||||
|
Action<StringName, int, StringName, int> connectNodes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.dslNodes = dslNodes;
|
||||||
|
this.addNode = addNode;
|
||||||
|
this.connectNodes = connectNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load(ProgramNode startNode)
|
||||||
|
{
|
||||||
|
Dictionary<ProgramNode, NodeDisplay> loadedNodes =
|
||||||
|
new Dictionary<ProgramNode, NodeDisplay>();
|
||||||
|
LoadNode(startNode, loadedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeDisplay LoadNode(
|
||||||
|
ProgramNode programNode,
|
||||||
|
Dictionary<ProgramNode, NodeDisplay> loadedNodes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
if (programNode == null) return null;
|
||||||
|
if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode];
|
||||||
|
|
||||||
|
NodeDisplay nodeDisplay = NodeDisplay.Load(
|
||||||
|
programNode.DisplayText,
|
||||||
|
programNode.Save(),
|
||||||
|
dslNodes
|
||||||
|
);
|
||||||
|
if (nodeDisplay == null) return null;
|
||||||
|
|
||||||
|
addNode(nodeDisplay);
|
||||||
|
loadedNodes.Add(programNode, nodeDisplay);
|
||||||
|
|
||||||
|
ConnectNode(nodeDisplay, 0, programNode.nextNode, loadedNodes);
|
||||||
|
ConnectNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes);
|
||||||
|
|
||||||
|
return nodeDisplay;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConnectNode(
|
||||||
|
NodeDisplay fromDisplay,
|
||||||
|
int fromPort,
|
||||||
|
ProgramNode targetNode,
|
||||||
|
Dictionary<ProgramNode, NodeDisplay> loadedNodes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
NodeDisplay toDisplay = LoadNode(targetNode, loadedNodes);
|
||||||
|
if (toDisplay == null) return;
|
||||||
|
|
||||||
|
connectNodes(fromDisplay.Name, fromPort, toDisplay.Name, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://deaptod52fe2s
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
public class ScriptConnection
|
||||||
|
{
|
||||||
|
public string FromNodeId;
|
||||||
|
public int FromPort;
|
||||||
|
public string ToNodeId;
|
||||||
|
public int ToPort;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cvhobhufyp2ni
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -3,7 +3,8 @@ using Godot;
|
|||||||
|
|
||||||
public partial class InventoryDisplay : PanelContainer
|
public partial class InventoryDisplay : PanelContainer
|
||||||
{
|
{
|
||||||
private PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay();
|
private readonly PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay();
|
||||||
|
|
||||||
[Export] VBoxContainer itemList;
|
[Export] VBoxContainer itemList;
|
||||||
[Export] RichTextLabel inventorySpace;
|
[Export] RichTextLabel inventorySpace;
|
||||||
|
|
||||||
@@ -34,24 +35,32 @@ public partial class InventoryDisplay : PanelContainer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadItems()
|
public void ReloadItems()
|
||||||
|
{
|
||||||
|
ClearItems();
|
||||||
|
|
||||||
|
foreach (Item item in GameData.inventory.items)
|
||||||
|
{
|
||||||
|
itemList.AddChild(CreateItemDisplay(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearItems()
|
||||||
{
|
{
|
||||||
foreach (Node node in itemList.GetChildren())
|
foreach (Node node in itemList.GetChildren())
|
||||||
{
|
{
|
||||||
itemList.RemoveChild(node);
|
itemList.RemoveChild(node);
|
||||||
node.QueueFree();
|
node.QueueFree();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ItemDisplay display;
|
private ItemDisplay CreateItemDisplay(Item item)
|
||||||
|
{
|
||||||
foreach (Item item in GameData.inventory.items)
|
ItemDisplay display = itemDisplayPrefab.Instantiate<ItemDisplay>();
|
||||||
{
|
display.item = item;
|
||||||
display = itemDisplayPrefab.Instantiate<ItemDisplay>();
|
display.text.Text = item.data.GetReadableName();
|
||||||
display.item = item;
|
display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}";
|
||||||
display.text.Text = item.data.GetReadableName();
|
display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
|
||||||
display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}";
|
return display;
|
||||||
display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
|
|
||||||
itemList.AddChild(display);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnInventoryUpdate(object sender, EventArgs args)
|
public void OnInventoryUpdate(object sender, EventArgs args)
|
||||||
|
|||||||
@@ -2,82 +2,86 @@ using Godot;
|
|||||||
|
|
||||||
public partial class OptionsMenu : PanelContainer
|
public partial class OptionsMenu : PanelContainer
|
||||||
{
|
{
|
||||||
private readonly Vector2 panelSize = new Vector2(420, 260);
|
private readonly Vector2 panelSize = new Vector2(420, 260);
|
||||||
|
|
||||||
[Export] private OptionButton screenMode;
|
[Export] private OptionButton screenMode;
|
||||||
[Export] private HSlider soundVolume;
|
[Export] private HSlider soundVolume;
|
||||||
[Export] private ColorPickerButton lightColor;
|
[Export] private ColorPickerButton lightColor;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
CenterPanel();
|
CenterPanel();
|
||||||
SetupScreenModes();
|
SetupScreenModes();
|
||||||
screenMode.Select(GameData.screenMode);
|
screenMode.Select(GameData.screenMode);
|
||||||
soundVolume.Value = GameData.soundVolume * 100f;
|
soundVolume.Value = GameData.soundVolume * 100f;
|
||||||
lightColor.Color = GameData.lightColor;
|
lightColor.Color = GameData.lightColor;
|
||||||
ApplyScreenMode(GameData.screenMode);
|
ApplyScreenMode(GameData.screenMode);
|
||||||
SoundManager.SetMasterVolume(GameData.soundVolume);
|
SoundManager.SetMasterVolume(GameData.soundVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupScreenModes()
|
private void SetupScreenModes()
|
||||||
{
|
{
|
||||||
screenMode.Clear();
|
screenMode.Clear();
|
||||||
screenMode.AddItem("Fullscreen");
|
screenMode.AddItem("Fullscreen");
|
||||||
screenMode.AddItem("Windowed");
|
screenMode.AddItem("Windowed");
|
||||||
screenMode.AddItem("Windowed Fullscreen");
|
screenMode.AddItem("Windowed Fullscreen");
|
||||||
screenMode.Select(2);
|
screenMode.Select(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnScreenModeSelected(int index)
|
public void OnScreenModeSelected(int index)
|
||||||
{
|
{
|
||||||
GameData.screenMode = index;
|
GameData.screenMode = index;
|
||||||
ApplyScreenMode(index);
|
ApplyScreenMode(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyScreenMode(int index)
|
private void ApplyScreenMode(int index)
|
||||||
{
|
{
|
||||||
switch (index)
|
switch (index)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
|
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
|
||||||
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
|
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
|
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
|
||||||
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
|
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
|
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
|
||||||
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
|
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
|
||||||
break;
|
break;
|
||||||
}
|
default:
|
||||||
}
|
DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
|
||||||
|
DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void OnSoundVolumeChanged(double value)
|
public void OnSoundVolumeChanged(double value)
|
||||||
{
|
{
|
||||||
GameData.soundVolume = (float)value / 100f;
|
GameData.soundVolume = (float)value / 100f;
|
||||||
SoundManager.SetMasterVolume(GameData.soundVolume);
|
SoundManager.SetMasterVolume(GameData.soundVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnLightColorChanged(Color color)
|
public void OnLightColorChanged(Color color)
|
||||||
{
|
{
|
||||||
GameData.lightColor = color;
|
GameData.lightColor = color;
|
||||||
LightHandler.RedrawLights(color);
|
LightHandler.RedrawLights(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseOptions()
|
public void CloseOptions()
|
||||||
{
|
{
|
||||||
Hide();
|
Hide();
|
||||||
GameData.isPaused = false;
|
GameData.isPaused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CenterPanel()
|
private void CenterPanel()
|
||||||
{
|
{
|
||||||
CustomMinimumSize = panelSize;
|
CustomMinimumSize = panelSize;
|
||||||
SetAnchorsPreset(LayoutPreset.Center);
|
SetAnchorsPreset(LayoutPreset.Center);
|
||||||
OffsetLeft = -panelSize.X / 2f;
|
OffsetLeft = -panelSize.X / 2f;
|
||||||
OffsetTop = -panelSize.Y / 2f;
|
OffsetTop = -panelSize.Y / 2f;
|
||||||
OffsetRight = panelSize.X / 2f;
|
OffsetRight = panelSize.X / 2f;
|
||||||
OffsetBottom = panelSize.Y / 2f;
|
OffsetBottom = panelSize.Y / 2f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ public partial class ResearchList : PanelContainer
|
|||||||
private bool hasArrangedNodes = false;
|
private bool hasArrangedNodes = false;
|
||||||
|
|
||||||
private List<Research> currentResearch = new List<Research>();
|
private List<Research> currentResearch = new List<Research>();
|
||||||
private List<Research> toDelete = new List<Research>();
|
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
@@ -18,30 +17,37 @@ public partial class ResearchList : PanelContainer
|
|||||||
if (Visible) SetupGraph();
|
if (Visible) SetupGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void _Process(double delta)
|
public override void _Process(double delta)
|
||||||
{
|
{
|
||||||
if(currentResearch.Count > 0) toDelete = new List<Research>();
|
if (currentResearch.Count <= 0) return;
|
||||||
|
|
||||||
|
List<Research> finishedResearch = new List<Research>();
|
||||||
foreach (Research research in currentResearch)
|
foreach (Research research in currentResearch)
|
||||||
{
|
{
|
||||||
ResearchResult result = research.Execute(delta);
|
ResearchResult result = research.Execute(delta);
|
||||||
if (result == ResearchResult.FINISHED)
|
if (!IsResearchFinished(research, result)) continue;
|
||||||
{
|
|
||||||
toDelete.Add(research);
|
finishedResearch.Add(research);
|
||||||
RecalculateResearchStates();
|
|
||||||
SetupGraph();
|
|
||||||
}
|
|
||||||
else if (result == ResearchResult.FAILED)
|
|
||||||
{
|
|
||||||
research.state = ResearchState.AVAILABLE;
|
|
||||||
toDelete.Add(research);
|
|
||||||
RecalculateResearchStates();
|
|
||||||
SetupGraph();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
foreach (Research delete in toDelete)
|
|
||||||
|
if (finishedResearch.Count <= 0) return;
|
||||||
|
|
||||||
|
foreach (Research research in finishedResearch)
|
||||||
{
|
{
|
||||||
currentResearch.Remove(delete);
|
currentResearch.Remove(research);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecalculateResearchStates();
|
||||||
|
SetupGraph();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsResearchFinished(Research research, ResearchResult result)
|
||||||
|
{
|
||||||
|
if (result == ResearchResult.FINISHED) return true;
|
||||||
|
if (result != ResearchResult.FAILED) return false;
|
||||||
|
|
||||||
|
research.state = ResearchState.AVAILABLE;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetupGraph()
|
public void SetupGraph()
|
||||||
@@ -83,11 +89,10 @@ public partial class ResearchList : PanelContainer
|
|||||||
|
|
||||||
foreach (Node child in researchGraph.GetChildren())
|
foreach (Node child in researchGraph.GetChildren())
|
||||||
{
|
{
|
||||||
if (child is GraphNode)
|
if (child is not GraphNode) continue;
|
||||||
{
|
|
||||||
researchGraph.RemoveChild(child);
|
researchGraph.RemoveChild(child);
|
||||||
child.QueueFree();
|
child.QueueFree();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,9 +132,10 @@ public partial class ResearchList : PanelContainer
|
|||||||
|
|
||||||
private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state)
|
private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state)
|
||||||
{
|
{
|
||||||
|
Research research = GameData.availableResearch[id];
|
||||||
Texture2D texture = GD.Load<Texture2D>(texturePath);
|
Texture2D texture = GD.Load<Texture2D>(texturePath);
|
||||||
Color stateColor = GetColorByState(state);
|
Color stateColor = GetColorByState(state);
|
||||||
string tooltipText = GetResearchTooltip(GameData.availableResearch[id]);
|
string tooltipText = GetResearchTooltip(research);
|
||||||
|
|
||||||
TextureRect icon = new TextureRect
|
TextureRect icon = new TextureRect
|
||||||
{
|
{
|
||||||
@@ -141,8 +147,8 @@ public partial class ResearchList : PanelContainer
|
|||||||
|
|
||||||
Button button = new Button
|
Button button = new Button
|
||||||
{
|
{
|
||||||
Text = GetResearchButtonText(GameData.availableResearch[id], state),
|
Text = GetResearchButtonText(research, state),
|
||||||
Disabled = state != ResearchState.AVAILABLE || !GameData.availableResearch[id].CanStart(),
|
Disabled = state != ResearchState.AVAILABLE || !research.CanStart(),
|
||||||
TooltipText = tooltipText
|
TooltipText = tooltipText
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -30,37 +30,50 @@ public partial class RobotList : PanelContainer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadRobots()
|
public void ReloadRobots()
|
||||||
|
{
|
||||||
|
ClearRobotList();
|
||||||
|
|
||||||
|
foreach (Robot robotObject in GameData.robots)
|
||||||
|
{
|
||||||
|
robotList.AddChild(CreateRobotDisplay(robotObject));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearRobotList()
|
||||||
{
|
{
|
||||||
foreach (Node node in robotList.GetChildren())
|
foreach (Node node in robotList.GetChildren())
|
||||||
{
|
{
|
||||||
robotList.RemoveChild(node);
|
robotList.RemoveChild(node);
|
||||||
node.QueueFree();
|
node.QueueFree();
|
||||||
}
|
}
|
||||||
RobotDisplay display;
|
}
|
||||||
|
|
||||||
foreach (Robot robotObject in GameData.robots)
|
private RobotDisplay CreateRobotDisplay(Robot robotObject)
|
||||||
{
|
{
|
||||||
display = robotDisplayPrefab.Instantiate<RobotDisplay>();
|
RobotDisplay display = robotDisplayPrefab.Instantiate<RobotDisplay>();
|
||||||
display.robot = robotObject;
|
display.robot = robotObject;
|
||||||
display.listItem.Text = robotObject.Name;
|
display.listItem.Text = robotObject.Name;
|
||||||
display.OnRobotJumpTo += (robot) =>
|
display.OnRobotJumpTo += HandleRobotJumpTo;
|
||||||
{
|
display.OnRobotFollow += HandleRobotFollow;
|
||||||
EmitSignal(SignalName.OnRobotJumpTo, robot);
|
return display;
|
||||||
Visible = false;
|
}
|
||||||
};
|
|
||||||
display.OnRobotFollow += (robot) =>
|
private void HandleRobotJumpTo(Robot robot)
|
||||||
{
|
{
|
||||||
EmitSignal(SignalName.OnRobotFollow, robot);
|
EmitSignal(SignalName.OnRobotJumpTo, robot);
|
||||||
Visible = false;
|
Visible = false;
|
||||||
};
|
}
|
||||||
robotList.AddChild(display);
|
|
||||||
}
|
private void HandleRobotFollow(Robot robot)
|
||||||
|
{
|
||||||
|
EmitSignal(SignalName.OnRobotFollow, robot);
|
||||||
|
Visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadSelectableRobots()
|
public void ReloadSelectableRobots()
|
||||||
{
|
{
|
||||||
selectableRobots.Clear();
|
selectableRobots.Clear();
|
||||||
if(GameData.robots.Count >= GameData.maxRobotCount)
|
if (GameData.robots.Count >= GameData.maxRobotCount)
|
||||||
{
|
{
|
||||||
selectableRobots.AddItem("You can't have more robots currently!");
|
selectableRobots.AddItem("You can't have more robots currently!");
|
||||||
selectableRobots.Disabled = true;
|
selectableRobots.Disabled = true;
|
||||||
@@ -81,7 +94,8 @@ public partial class RobotList : PanelContainer
|
|||||||
|
|
||||||
public void SpawnRobot()
|
public void SpawnRobot()
|
||||||
{
|
{
|
||||||
if(spawnId.Length <= 0) return;
|
if (spawnId.Length <= 0) return;
|
||||||
|
|
||||||
GameData.inventory.RemoveItem(spawnId, 1);
|
GameData.inventory.RemoveItem(spawnId, 1);
|
||||||
Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
|
Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate<Robot>();
|
||||||
robot.Name = $"Robot #{GameData.robots.Count}";
|
robot.Name = $"Robot #{GameData.robots.Count}";
|
||||||
@@ -97,8 +111,8 @@ public partial class RobotList : PanelContainer
|
|||||||
|
|
||||||
public void OnRobotSelect(int index)
|
public void OnRobotSelect(int index)
|
||||||
{
|
{
|
||||||
//Selected option is the "please select..." option
|
if (index == 0) return;
|
||||||
if(index == 0) return;
|
|
||||||
spawnId = ItemData.GetIndex(selectableRobots.GetItemText(index));
|
spawnId = ItemData.GetIndex(selectableRobots.GetItemText(index));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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."
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
using Godot;
|
using Godot;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
public class LightHandler
|
public static class LightHandler
|
||||||
{
|
{
|
||||||
public static List<OmniLight3D> lights = new List<OmniLight3D>();
|
public static List<OmniLight3D> lights = new List<OmniLight3D>();
|
||||||
|
|
||||||
public static void RedrawLights(Color color)
|
public static void RedrawLights(Color color)
|
||||||
{
|
{
|
||||||
List<OmniLight3D> availableLights = new List<OmniLight3D>();
|
List<OmniLight3D> availableLights = new List<OmniLight3D>();
|
||||||
|
|
||||||
foreach (OmniLight3D light in lights)
|
foreach (OmniLight3D light in lights)
|
||||||
{
|
{
|
||||||
if (GodotObject.IsInstanceValid(light))
|
if (!GodotObject.IsInstanceValid(light)) continue;
|
||||||
{
|
|
||||||
light.LightColor = color;
|
|
||||||
availableLights.Add(light);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lights = availableLights;
|
light.LightColor = color;
|
||||||
}
|
availableLights.Add(light);
|
||||||
|
}
|
||||||
|
|
||||||
|
lights = availableLights;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+108
-103
@@ -1,131 +1,136 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
public class Pathfinding
|
public static class Pathfinding
|
||||||
{
|
{
|
||||||
private static AStar3D aStar = new AStar3D();
|
private static AStar3D aStar = new AStar3D();
|
||||||
private static Dictionary<Vector3I, long> coordToId = new Dictionary<Vector3I, long>();
|
private static Dictionary<Vector3I, long> coordToId = new Dictionary<Vector3I, long>();
|
||||||
private static Dictionary<long, Vector3I> idToCoord = new Dictionary<long, Vector3I>();
|
private static Dictionary<long, Vector3I> idToCoord = new Dictionary<long, Vector3I>();
|
||||||
private static long nextId = 1;
|
private static long nextId = 1;
|
||||||
private static Dictionary<int, (long fromId, long toId)> verticalConnections = new Dictionary<int, (long fromId, long toId)>();
|
private static Dictionary<int, (long fromId, long toId)> verticalConnections = new Dictionary<int, (long fromId, long toId)>();
|
||||||
|
|
||||||
private static long GetOrCreateId(Vector3I coord)
|
private static long GetOrCreateId(Vector3I coord)
|
||||||
{
|
{
|
||||||
if (coordToId.TryGetValue(coord, out long id))
|
if (coordToId.TryGetValue(coord, out long id)) return id;
|
||||||
return id;
|
|
||||||
|
|
||||||
id = nextId++;
|
id = nextId++;
|
||||||
coordToId[coord] = id;
|
coordToId[coord] = id;
|
||||||
idToCoord[id] = coord;
|
idToCoord[id] = coord;
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void BuildAStarGraph()
|
public static void BuildAStarGraph()
|
||||||
{
|
{
|
||||||
aStar.Clear();
|
aStar.Clear();
|
||||||
coordToId.Clear();
|
coordToId.Clear();
|
||||||
idToCoord.Clear();
|
idToCoord.Clear();
|
||||||
verticalConnections.Clear();
|
verticalConnections.Clear();
|
||||||
nextId = 1;
|
nextId = 1;
|
||||||
|
|
||||||
for (int y = 0; y < GameData.ruinSize; y++)
|
for (int y = 0; y < GameData.ruinSize; y++)
|
||||||
{
|
{
|
||||||
for (int x = 0; x < GameData.layerSize; x++)
|
for (int x = 0; x < GameData.layerSize; x++)
|
||||||
{
|
{
|
||||||
for (int z = 0; z < GameData.layerSize; z++)
|
for (int z = 0; z < GameData.layerSize; z++)
|
||||||
{
|
{
|
||||||
Vector3I coord = new Vector3I(x, y, z);
|
AddPointIfValid(x, y, z);
|
||||||
Tile tile = GameData.map[y].tiles[x, z];
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tile == null || tile.collapsedMesh == null) continue;
|
foreach (KeyValuePair<Vector3I, long> kvp in coordToId)
|
||||||
|
{
|
||||||
|
ConnectPoint(kvp.Key, kvp.Value);
|
||||||
|
}
|
||||||
|
|
||||||
long id = GetOrCreateId(coord);
|
for (int y = 0; y < GameData.ruinSize; y++)
|
||||||
|
{
|
||||||
|
UpdateGatePoint(y, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
aStar.AddPoint(id, tile.Position);
|
private static void AddPointIfValid(int x, int y, int z)
|
||||||
}
|
{
|
||||||
}
|
Vector3I coord = new Vector3I(x, y, z);
|
||||||
}
|
Tile tile = GameData.map[y].tiles[x, z];
|
||||||
|
if (tile == null || tile.collapsedMesh == null) return;
|
||||||
|
|
||||||
foreach (KeyValuePair<Vector3I, long> kvp in coordToId)
|
long id = GetOrCreateId(coord);
|
||||||
{
|
aStar.AddPoint(id, tile.Position);
|
||||||
Vector3I from = kvp.Key;
|
}
|
||||||
long fromId = kvp.Value;
|
|
||||||
|
|
||||||
foreach (Vector3I offset in WFC.offsets3D)
|
private static void ConnectPoint(Vector3I from, long fromId)
|
||||||
{
|
{
|
||||||
Vector3I to = new Vector3I(
|
foreach (Vector3I offset in WFC.offsets3D)
|
||||||
from.X + offset.X,
|
{
|
||||||
from.Y + offset.Y,
|
Vector3I to = new Vector3I(
|
||||||
from.Z + offset.Z
|
from.X + offset.X,
|
||||||
);
|
from.Y + offset.Y,
|
||||||
|
from.Z + offset.Z
|
||||||
|
);
|
||||||
|
|
||||||
if (!coordToId.ContainsKey(to)) continue;
|
if (!coordToId.ContainsKey(to)) continue;
|
||||||
|
if (!WFC.CanWalk3D(from, to)) continue;
|
||||||
|
|
||||||
if (!WFC.CanWalk3D(from, to)) continue;
|
long toId = coordToId[to];
|
||||||
|
if (TryRegisterGateConnection(from, to, fromId, toId)) continue;
|
||||||
|
|
||||||
long toId = coordToId[to];
|
ConnectPointsIfNeeded(fromId, toId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (from.Y != to.Y && GameData.map[from.Y].tiles[from.X, from.Z].collapsedMesh == "gate")
|
private static bool TryRegisterGateConnection(Vector3I from, Vector3I to, long fromId, long toId)
|
||||||
{
|
{
|
||||||
verticalConnections[from.Y] = (fromId, toId);
|
if (from.Y == to.Y) return false;
|
||||||
if (GameData.map[from.Y].isGateOpen)
|
if (GameData.map[from.Y].tiles[from.X, from.Z].collapsedMesh != "gate") return false;
|
||||||
{
|
|
||||||
if (!aStar.ArePointsConnected(fromId, toId))
|
|
||||||
{
|
|
||||||
aStar.ConnectPoints(fromId, toId, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!aStar.ArePointsConnected(fromId, toId))
|
verticalConnections[from.Y] = (fromId, toId);
|
||||||
{
|
if (GameData.map[from.Y].isGateOpen)
|
||||||
aStar.ConnectPoints(fromId, toId, true);
|
{
|
||||||
}
|
ConnectPointsIfNeeded(fromId, toId);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (int y = 0; y < GameData.ruinSize; y++)
|
return true;
|
||||||
{
|
}
|
||||||
UpdateGatePoint(y, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void UpdateGatePoint(int layer, bool isOpen)
|
private static void ConnectPointsIfNeeded(long fromId, long toId)
|
||||||
{
|
{
|
||||||
if (!verticalConnections.ContainsKey(layer)) return;
|
if (aStar.ArePointsConnected(fromId, toId)) return;
|
||||||
|
|
||||||
(long fromId, long toId) = verticalConnections[layer];
|
aStar.ConnectPoints(fromId, toId, true);
|
||||||
|
}
|
||||||
|
|
||||||
if (isOpen)
|
public static void UpdateGatePoint(int layer, bool isOpen)
|
||||||
{
|
{
|
||||||
if (!aStar.ArePointsConnected(fromId, toId))
|
if (!verticalConnections.ContainsKey(layer)) return;
|
||||||
{
|
|
||||||
aStar.ConnectPoints(fromId, toId, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (aStar.ArePointsConnected(fromId, toId))
|
|
||||||
{
|
|
||||||
aStar.DisconnectPoints(fromId, toId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<Vector3> GetPath(Vector3I start, Vector3I end)
|
(long fromId, long toId) = verticalConnections[layer];
|
||||||
{
|
|
||||||
if (!coordToId.ContainsKey(start) || !coordToId.ContainsKey(end)) return new List<Vector3>();
|
|
||||||
|
|
||||||
long startId = coordToId[start];
|
if (isOpen)
|
||||||
long endId = coordToId[end];
|
{
|
||||||
|
ConnectPointsIfNeeded(fromId, toId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return new List<Vector3>(aStar.GetPointPath(startId, endId));
|
if (aStar.ArePointsConnected(fromId, toId))
|
||||||
}
|
{
|
||||||
|
aStar.DisconnectPoints(fromId, toId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Vector3I GetClosestStartPoint(Vector3 robotPosition)
|
public static List<Vector3> GetPath(Vector3I start, Vector3I end)
|
||||||
{
|
{
|
||||||
return idToCoord[aStar.GetClosestPoint(robotPosition)];
|
if (!coordToId.ContainsKey(start) || !coordToId.ContainsKey(end)) return new List<Vector3>();
|
||||||
}
|
|
||||||
|
long startId = coordToId[start];
|
||||||
|
long endId = coordToId[end];
|
||||||
|
|
||||||
|
return new List<Vector3>(aStar.GetPointPath(startId, endId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Vector3I GetClosestStartPoint(Vector3 robotPosition)
|
||||||
|
{
|
||||||
|
return idToCoord[aStar.GetClosestPoint(robotPosition)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
public class ResourceDistributor
|
public static class ResourceDistributor
|
||||||
{
|
{
|
||||||
public static Dictionary<string, Texture2D> resources = ResourceLoader.LoadResourceSymbols();
|
public static Dictionary<string, Texture2D> resources = ResourceLoader.LoadResourceSymbols();
|
||||||
public static Dictionary<string, float[]> weights = ResourceLoader.LoadResourceWeights();
|
public static Dictionary<string, float[]> weights = ResourceLoader.LoadResourceWeights();
|
||||||
|
|
||||||
public static string GetResource(int layer)
|
public static string GetResource(int layer)
|
||||||
{
|
{
|
||||||
return ChooseWeighted(layer);
|
return ChooseWeighted(layer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string ChooseWeighted(int layer)
|
private static string ChooseWeighted(int layer)
|
||||||
{
|
{
|
||||||
float totalWeight = 0f;
|
float totalWeight = 0f;
|
||||||
float weightLerp;
|
|
||||||
|
|
||||||
foreach (string resource in resources.Keys)
|
foreach (string resource in resources.Keys)
|
||||||
{
|
{
|
||||||
weightLerp = Mathf.Lerp(weights[resource][0], weights[resource][1], layer);
|
totalWeight += GetWeight(resource, layer);
|
||||||
totalWeight += weightLerp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float r = (float)(GameData.rand.NextDouble() * totalWeight);
|
float randomWeight = (float)(GameData.rand.NextDouble() * totalWeight);
|
||||||
|
|
||||||
float cumulative = 0f;
|
float cumulative = 0f;
|
||||||
|
|
||||||
foreach (string resource in resources.Keys)
|
foreach (string resource in resources.Keys)
|
||||||
{
|
{
|
||||||
weightLerp = Mathf.Lerp(weights[resource][0], weights[resource][1], layer);
|
cumulative += GetWeight(resource, layer);
|
||||||
cumulative += weightLerp;
|
|
||||||
|
|
||||||
if (r <= cumulative)
|
if (randomWeight <= cumulative) return resource;
|
||||||
return resource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "stone";
|
return "stone";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static float GetWeight(string resource, int layer)
|
||||||
|
{
|
||||||
|
return Mathf.Lerp(weights[resource][0], weights[resource][1], layer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-41
@@ -3,7 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
public class WFC
|
public static class WFC
|
||||||
{
|
{
|
||||||
public static Dictionary<string, Dictionary<Direction, List<string>>> adjacency = new Dictionary<string, Dictionary<Direction, List<string>>>();
|
public static Dictionary<string, Dictionary<Direction, List<string>>> adjacency = new Dictionary<string, Dictionary<Direction, List<string>>>();
|
||||||
public enum Direction
|
public enum Direction
|
||||||
@@ -122,8 +122,6 @@ public class WFC
|
|||||||
return aOpen == bOpen;
|
return aOpen == bOpen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static void FillAdjacencies()
|
public static void FillAdjacencies()
|
||||||
{
|
{
|
||||||
foreach (string tile in tileConnections.Keys)
|
foreach (string tile in tileConnections.Keys)
|
||||||
@@ -136,8 +134,7 @@ public class WFC
|
|||||||
|
|
||||||
foreach (string other in tileConnections.Keys)
|
foreach (string other in tileConnections.Keys)
|
||||||
{
|
{
|
||||||
if (CanConnect(tile, other, dir, false))
|
if (CanConnect(tile, other, dir, false)) valid.Add(other);
|
||||||
valid.Add(other);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
adjacency[tile][dir] = valid;
|
adjacency[tile][dir] = valid;
|
||||||
@@ -156,8 +153,7 @@ public class WFC
|
|||||||
Tile fromTile = layer[from.X, from.Y];
|
Tile fromTile = layer[from.X, from.Y];
|
||||||
Tile toTile = layer[to.X, to.Y];
|
Tile toTile = layer[to.X, to.Y];
|
||||||
|
|
||||||
if (!IsWalkable(toTile))
|
if (!IsWalkable(toTile)) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true);
|
return CanConnect(fromTile.collapsedMesh, toTile.collapsedMesh, dir, true);
|
||||||
}
|
}
|
||||||
@@ -167,29 +163,21 @@ public class WFC
|
|||||||
Tile fromTile = GameData.map[from.Y].tiles[from.X, from.Z];
|
Tile fromTile = GameData.map[from.Y].tiles[from.X, from.Z];
|
||||||
Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z];
|
Tile toTile = GameData.map[to.Y].tiles[to.X, to.Z];
|
||||||
|
|
||||||
if (fromTile == null || toTile == null)
|
if (fromTile == null || toTile == null) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (from.Y != to.Y)
|
if (from.Y != to.Y)
|
||||||
{
|
{
|
||||||
if (Math.Abs(from.Y - to.Y) != 1)
|
if (Math.Abs(from.Y - to.Y) != 1) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (from.Y > to.Y)
|
return from.Y > to.Y
|
||||||
{
|
? toTile.collapsedMesh == "gate"
|
||||||
return toTile.collapsedMesh == "gate";
|
: fromTile.collapsedMesh == "gate";
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return fromTile.collapsedMesh == "gate";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int dx = to.X - from.X;
|
int dx = to.X - from.X;
|
||||||
int dz = to.Z - from.Z;
|
int dz = to.Z - from.Z;
|
||||||
|
|
||||||
if (Math.Abs(dx) + Math.Abs(dz) != 1)
|
if (Math.Abs(dx) + Math.Abs(dz) != 1) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
Direction dir;
|
Direction dir;
|
||||||
|
|
||||||
@@ -207,29 +195,23 @@ public class WFC
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshhold)
|
public static bool IsMapConnected(Tile[,] layer, float accessibilityThreshold)
|
||||||
{
|
{
|
||||||
bool result = false;
|
|
||||||
HashSet<Vector2I> visited = new HashSet<Vector2I>();
|
HashSet<Vector2I> visited = new HashSet<Vector2I>();
|
||||||
List<Vector2I> toCheck = new List<Vector2I>();
|
List<Vector2I> toCheck = new List<Vector2I>();
|
||||||
Vector2I position;
|
|
||||||
toCheck.Add(new Vector2I(1, 1));
|
toCheck.Add(new Vector2I(1, 1));
|
||||||
|
|
||||||
int safetyCounter = 0;
|
int safetyCounter = 0;
|
||||||
while (true)
|
while (toCheck.Count > 0)
|
||||||
{
|
{
|
||||||
if (toCheck.Count <= 0) break;
|
Vector2I position = TakeRandomPosition(toCheck);
|
||||||
int index = GameData.rand.Next(toCheck.Count);
|
|
||||||
position = toCheck[index];
|
|
||||||
toCheck[index] = toCheck[^1];
|
|
||||||
toCheck.RemoveAt(toCheck.Count - 1);
|
|
||||||
if (!visited.Add(position)) continue;
|
if (!visited.Add(position)) continue;
|
||||||
|
|
||||||
for (int i = 0; i < offsets2D.Length; i++)
|
for (int i = 0; i < offsets2D.Length; i++)
|
||||||
{
|
{
|
||||||
Vector2I next = position + offsets2D[i];
|
Vector2I next = position + offsets2D[i];
|
||||||
|
|
||||||
if (!InBounds(next, layer.GetLength(0)))
|
if (!InBounds(next, layer.GetLength(0))) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (CanWalk(layer, position, next, dirs[i]))
|
if (CanWalk(layer, position, next, dirs[i]))
|
||||||
{
|
{
|
||||||
@@ -238,22 +220,30 @@ public class WFC
|
|||||||
}
|
}
|
||||||
safetyCounter++;
|
safetyCounter++;
|
||||||
if (safetyCounter > layer.Length * 2) break;
|
if (safetyCounter > layer.Length * 2) break;
|
||||||
if (visited.Count >= Math.Pow(layer.GetLength(0) - 1, 2) * accessibilityThreshhold)
|
if (visited.Count >= Math.Pow(layer.GetLength(0) - 1, 2) * accessibilityThreshold)
|
||||||
{
|
{
|
||||||
result = true;
|
return true;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector2I TakeRandomPosition(List<Vector2I> positions)
|
||||||
|
{
|
||||||
|
int index = GameData.rand.Next(positions.Count);
|
||||||
|
Vector2I position = positions[index];
|
||||||
|
positions[index] = positions[positions.Count - 1];
|
||||||
|
positions.RemoveAt(positions.Count - 1);
|
||||||
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool InBounds(Vector2I pos, int layerSize)
|
public static bool InBounds(Vector2I pos, int layerSize)
|
||||||
{
|
{
|
||||||
return pos.X > 0 &&
|
return pos.X > 0
|
||||||
pos.Y > 0 &&
|
&& pos.Y > 0
|
||||||
pos.X < layerSize &&
|
&& pos.X < layerSize
|
||||||
pos.Y < layerSize;
|
&& pos.Y < layerSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<string> GetBorderPossibilities(int x, int z)
|
public static List<string> GetBorderPossibilities(int x, int z)
|
||||||
|
|||||||
+1
-81
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user