diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..f28239b
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,4 @@
+root = true
+
+[*]
+charset = utf-8
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8ad74f7
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf
diff --git a/.gitignore b/.gitignore
index bf83296..ae18240 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# ---> Godot
# Godot 4+ specific ignores
.godot/
+addons/
# Godot-specific ignores
.import/
diff --git a/Assets/Buildings.json b/Assets/Buildings.json
new file mode 100644
index 0000000..cfed5e7
--- /dev/null
+++ b/Assets/Buildings.json
@@ -0,0 +1,252 @@
+[
+ {
+ "id": "workbench",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 4
+ }
+ ],
+ "research": "basics",
+ "texture": "res://Assets/Images/Buildings/Workbench.png",
+ "crafttime": 3.0,
+ "stacksize": 1
+ },
+ {
+ "id": "furnace",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 12
+ },
+ {
+ "item": "heating_element_v1",
+ "amount": 1
+ }
+ ],
+ "research": "heat_control",
+ "texture": "res://Assets/Images/Buildings/Furnace.png",
+ "crafttime": 10.0,
+ "stacksize": 1
+ },
+ {
+ "id": "crusher",
+ "inputs": [
+ {
+ "item": "stone_gear",
+ "amount": 2
+ },
+ {
+ "item": "rope",
+ "amount": 2
+ },
+ {
+ "item": "stone",
+ "amount": 8
+ }
+ ],
+ "research": "basic_machines",
+ "texture": "res://Assets/Images/Buildings/Crusher.png",
+ "crafttime": 8.0,
+ "stacksize": 1
+ },
+ {
+ "id": "press",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 8
+ },
+ {
+ "item": "rope",
+ "amount": 2
+ }
+ ],
+ "research": "basic_machines",
+ "texture": "res://Assets/Images/Buildings/Press.png",
+ "crafttime": 6.5,
+ "stacksize": 1
+ },
+ {
+ "id": "anvil",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 10
+ },
+ {
+ "item": "rope",
+ "amount": 3
+ }
+ ],
+ "research": "basic_machines",
+ "texture": "res://Assets/Images/Buildings/Anvil.png",
+ "crafttime": 7.5,
+ "stacksize": 1
+ },
+ {
+ "id": "loom",
+ "inputs": [
+ {
+ "item": "copper_gear",
+ "amount": 2
+ },
+ {
+ "item": "rope",
+ "amount": 4
+ }
+ ],
+ "research": "textiles",
+ "texture": "res://Assets/Images/Buildings/Loom.png",
+ "crafttime": 12.0,
+ "stacksize": 1
+ },
+ {
+ "id": "steam_generator_v1",
+ "inputs": [
+ {
+ "item": "dynamo_v1",
+ "amount": 1
+ },
+ {
+ "item": "battery_v1",
+ "amount": 2
+ },
+ {
+ "item": "heating_element_v1",
+ "amount": 2
+ }
+ ],
+ "research": "basic_electricity",
+ "texture": "res://Assets/Images/Buildings/SteamGeneratorv1.png",
+ "crafttime": 18.0,
+ "stacksize": 1
+ },
+ {
+ "id": "stone_chest",
+ "inputs": [
+ {
+ "item": "stone_plate",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 2
+ }
+ ],
+ "research": "stone_storage",
+ "texture": "res://Assets/Images/Buildings/StoneChest.png",
+ "crafttime": 5.5,
+ "stacksize": 1
+ },
+ {
+ "id": "copper_chest",
+ "inputs": [
+ {
+ "item": "copper_plate",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 3
+ }
+ ],
+ "research": "metalworking",
+ "texture": "res://Assets/Images/Buildings/CopperChest.png",
+ "crafttime": 9.5,
+ "stacksize": 1
+ },
+ {
+ "id": "bronze_chest",
+ "inputs": [
+ {
+ "item": "bronze_plate",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 4
+ }
+ ],
+ "research": "bronze_storage",
+ "texture": "res://Assets/Images/Buildings/BronzeChest.png",
+ "crafttime": 14.0,
+ "stacksize": 1
+ },
+ {
+ "id": "iron_chest",
+ "inputs": [
+ {
+ "item": "iron_plate",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 5
+ }
+ ],
+ "research": "iron_machining",
+ "texture": "res://Assets/Images/Buildings/IronChest.png",
+ "crafttime": 17.0,
+ "stacksize": 1
+ },
+ {
+ "id": "steam_generator_v2",
+ "inputs": [
+ {
+ "item": "dynamo_v2",
+ "amount": 1
+ },
+ {
+ "item": "battery_v2",
+ "amount": 2
+ },
+ {
+ "item": "heating_element_v1",
+ "amount": 3
+ }
+ ],
+ "research": "advanced_power",
+ "texture": "res://Assets/Images/Buildings/SteamGeneratorv2.png",
+ "crafttime": 28.0,
+ "stacksize": 1
+ },
+ {
+ "id": "water_purifier",
+ "inputs": [
+ {
+ "item": "heating_element_v1",
+ "amount": 2
+ },
+ {
+ "item": "glass_bottles",
+ "amount": 3
+ }
+ ],
+ "research": "glass_work",
+ "texture": "res://Assets/Images/Buildings/WaterPurifier.png",
+ "crafttime": 12.5,
+ "stacksize": 1
+ },
+ {
+ "id": "glassblower",
+ "inputs": [
+ {
+ "item": "heating_element_v1",
+ "amount": 2
+ },
+ {
+ "item": "stone",
+ "amount": 8
+ },
+ {
+ "item": "copper_rod",
+ "amount": 2
+ }
+ ],
+ "research": "glass_work",
+ "texture": "res://Assets/Images/Buildings/Glassblower.png",
+ "crafttime": 13.5,
+ "stacksize": 1
+ }
+]
\ No newline at end of file
diff --git a/Assets/Images/AlarmSign.png b/Assets/Images/AlarmSign.png
new file mode 100644
index 0000000..548f4d8
Binary files /dev/null and b/Assets/Images/AlarmSign.png differ
diff --git a/Assets/Images/AlarmSign.png.import b/Assets/Images/AlarmSign.png.import
new file mode 100644
index 0000000..a7c0172
--- /dev/null
+++ b/Assets/Images/AlarmSign.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bmcpkt6mae2qi"
+path="res://.godot/imported/AlarmSign.png-c3f9e97a0a8bd2aaf756ab4fda47ea32.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/AlarmSign.png"
+dest_files=["res://.godot/imported/AlarmSign.png-c3f9e97a0a8bd2aaf756ab4fda47ea32.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/ArrowDown.png b/Assets/Images/ArrowDown.png
new file mode 100644
index 0000000..2ff9ce3
Binary files /dev/null and b/Assets/Images/ArrowDown.png differ
diff --git a/Assets/Images/ArrowDown.png.import b/Assets/Images/ArrowDown.png.import
new file mode 100644
index 0000000..9dab856
--- /dev/null
+++ b/Assets/Images/ArrowDown.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://66yiv4g7kfpv"
+path="res://.godot/imported/ArrowDown.png-68a8d6a1c504a39663af15622d06d1d0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/ArrowDown.png"
+dest_files=["res://.godot/imported/ArrowDown.png-68a8d6a1c504a39663af15622d06d1d0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/ArrowUp.png b/Assets/Images/ArrowUp.png
new file mode 100644
index 0000000..0b3bcf1
Binary files /dev/null and b/Assets/Images/ArrowUp.png differ
diff --git a/Assets/Images/ArrowUp.png.import b/Assets/Images/ArrowUp.png.import
new file mode 100644
index 0000000..6602599
--- /dev/null
+++ b/Assets/Images/ArrowUp.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://djxwweinn5c4i"
+path="res://.godot/imported/ArrowUp.png-6639581e2ea32ca76adabf27de0fdb0d.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/ArrowUp.png"
+dest_files=["res://.godot/imported/ArrowUp.png-6639581e2ea32ca76adabf27de0fdb0d.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/EnergySymbol.png b/Assets/Images/EnergySymbol.png
new file mode 100644
index 0000000..d132125
Binary files /dev/null and b/Assets/Images/EnergySymbol.png differ
diff --git a/Assets/Images/EnergySymbol.png.import b/Assets/Images/EnergySymbol.png.import
new file mode 100644
index 0000000..fee5e76
--- /dev/null
+++ b/Assets/Images/EnergySymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://deuxffyhsrinn"
+path="res://.godot/imported/EnergySymbol.png-9b81a59117a69ab4f281baa1004485e6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/EnergySymbol.png"
+dest_files=["res://.godot/imported/EnergySymbol.png-9b81a59117a69ab4f281baa1004485e6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/InventorySymbol.png b/Assets/Images/InventorySymbol.png
new file mode 100644
index 0000000..a27dd49
Binary files /dev/null and b/Assets/Images/InventorySymbol.png differ
diff --git a/Assets/Images/InventorySymbol.png.import b/Assets/Images/InventorySymbol.png.import
new file mode 100644
index 0000000..68252a7
--- /dev/null
+++ b/Assets/Images/InventorySymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ciehcg34et0q3"
+path="res://.godot/imported/InventorySymbol.png-992c7fa6db2b090a5926e973600828c4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/InventorySymbol.png"
+dest_files=["res://.godot/imported/InventorySymbol.png-992c7fa6db2b090a5926e973600828c4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/Batteryv1Symbol.png b/Assets/Images/Items/Batteryv1Symbol.png
new file mode 100644
index 0000000..cef33d6
Binary files /dev/null and b/Assets/Images/Items/Batteryv1Symbol.png differ
diff --git a/Assets/Images/Items/Batteryv1Symbol.png.import b/Assets/Images/Items/Batteryv1Symbol.png.import
new file mode 100644
index 0000000..659361a
--- /dev/null
+++ b/Assets/Images/Items/Batteryv1Symbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dm0w2gtcsa5l4"
+path="res://.godot/imported/Batteryv1Symbol.png-97f0543f9d2510e0a65f0e409e95accd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/Batteryv1Symbol.png"
+dest_files=["res://.godot/imported/Batteryv1Symbol.png-97f0543f9d2510e0a65f0e409e95accd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/Batteryv2Symbol.png b/Assets/Images/Items/Batteryv2Symbol.png
new file mode 100644
index 0000000..35f7b16
Binary files /dev/null and b/Assets/Images/Items/Batteryv2Symbol.png differ
diff --git a/Assets/Images/Items/Batteryv2Symbol.png.import b/Assets/Images/Items/Batteryv2Symbol.png.import
new file mode 100644
index 0000000..d000260
--- /dev/null
+++ b/Assets/Images/Items/Batteryv2Symbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dxtyg5abspy3f"
+path="res://.godot/imported/Batteryv2Symbol.png-74bff33170be1dd842e080e517fb633c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/Batteryv2Symbol.png"
+dest_files=["res://.godot/imported/Batteryv2Symbol.png-74bff33170be1dd842e080e517fb633c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/BronzeGearSymbol.png b/Assets/Images/Items/BronzeGearSymbol.png
new file mode 100644
index 0000000..5027d3e
Binary files /dev/null and b/Assets/Images/Items/BronzeGearSymbol.png differ
diff --git a/Assets/Images/Items/BronzeGearSymbol.png.import b/Assets/Images/Items/BronzeGearSymbol.png.import
new file mode 100644
index 0000000..9c3b387
--- /dev/null
+++ b/Assets/Images/Items/BronzeGearSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bkr5w2n0ur3b0"
+path="res://.godot/imported/BronzeGearSymbol.png-fbe63b91e723706590d2614f99b9e000.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/BronzeGearSymbol.png"
+dest_files=["res://.godot/imported/BronzeGearSymbol.png-fbe63b91e723706590d2614f99b9e000.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/BronzeIngotSymbol.png b/Assets/Images/Items/BronzeIngotSymbol.png
new file mode 100644
index 0000000..e33a4d4
Binary files /dev/null and b/Assets/Images/Items/BronzeIngotSymbol.png differ
diff --git a/Assets/Images/Items/BronzeIngotSymbol.png.import b/Assets/Images/Items/BronzeIngotSymbol.png.import
new file mode 100644
index 0000000..817d215
--- /dev/null
+++ b/Assets/Images/Items/BronzeIngotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dlr3wontd048w"
+path="res://.godot/imported/BronzeIngotSymbol.png-bddd21b1df9024fb16fcbe4c6b8a9aca.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/BronzeIngotSymbol.png"
+dest_files=["res://.godot/imported/BronzeIngotSymbol.png-bddd21b1df9024fb16fcbe4c6b8a9aca.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/BronzePlateSymbol.png b/Assets/Images/Items/BronzePlateSymbol.png
new file mode 100644
index 0000000..d69222b
Binary files /dev/null and b/Assets/Images/Items/BronzePlateSymbol.png differ
diff --git a/Assets/Images/Items/BronzePlateSymbol.png.import b/Assets/Images/Items/BronzePlateSymbol.png.import
new file mode 100644
index 0000000..76f55f4
--- /dev/null
+++ b/Assets/Images/Items/BronzePlateSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://f7hdh7313s3w"
+path="res://.godot/imported/BronzePlateSymbol.png-4442ba84d90f3edadf19d2c3c14b6a05.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/BronzePlateSymbol.png"
+dest_files=["res://.godot/imported/BronzePlateSymbol.png-4442ba84d90f3edadf19d2c3c14b6a05.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/BronzeRobotSymbol.png b/Assets/Images/Items/BronzeRobotSymbol.png
new file mode 100644
index 0000000..f912267
Binary files /dev/null and b/Assets/Images/Items/BronzeRobotSymbol.png differ
diff --git a/Assets/Images/Items/BronzeRobotSymbol.png.import b/Assets/Images/Items/BronzeRobotSymbol.png.import
new file mode 100644
index 0000000..f33917d
--- /dev/null
+++ b/Assets/Images/Items/BronzeRobotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://de72q2knr7qk8"
+path="res://.godot/imported/BronzeRobotSymbol.png-5ac14127f897bbbdca99f40dbb0d0fef.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/BronzeRobotSymbol.png"
+dest_files=["res://.godot/imported/BronzeRobotSymbol.png-5ac14127f897bbbdca99f40dbb0d0fef.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/BronzeRodSymbol.png b/Assets/Images/Items/BronzeRodSymbol.png
new file mode 100644
index 0000000..e65ac0a
Binary files /dev/null and b/Assets/Images/Items/BronzeRodSymbol.png differ
diff --git a/Assets/Images/Items/BronzeRodSymbol.png.import b/Assets/Images/Items/BronzeRodSymbol.png.import
new file mode 100644
index 0000000..6db7374
--- /dev/null
+++ b/Assets/Images/Items/BronzeRodSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dgmrijlje0fmm"
+path="res://.godot/imported/BronzeRodSymbol.png-08d1f6c9e8dac948725b39fb88903429.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/BronzeRodSymbol.png"
+dest_files=["res://.godot/imported/BronzeRodSymbol.png-08d1f6c9e8dac948725b39fb88903429.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/CopperGearSymbol.png b/Assets/Images/Items/CopperGearSymbol.png
new file mode 100644
index 0000000..3511cda
Binary files /dev/null and b/Assets/Images/Items/CopperGearSymbol.png differ
diff --git a/Assets/Images/Items/CopperGearSymbol.png.import b/Assets/Images/Items/CopperGearSymbol.png.import
new file mode 100644
index 0000000..38c17d8
--- /dev/null
+++ b/Assets/Images/Items/CopperGearSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bty2ioj3c1ucu"
+path="res://.godot/imported/CopperGearSymbol.png-a92c2fbfa963f12ffe44bec68e72499e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/CopperGearSymbol.png"
+dest_files=["res://.godot/imported/CopperGearSymbol.png-a92c2fbfa963f12ffe44bec68e72499e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/CopperIngotSymbol.png b/Assets/Images/Items/CopperIngotSymbol.png
new file mode 100644
index 0000000..af46f92
Binary files /dev/null and b/Assets/Images/Items/CopperIngotSymbol.png differ
diff --git a/Assets/Images/Items/CopperIngotSymbol.png.import b/Assets/Images/Items/CopperIngotSymbol.png.import
new file mode 100644
index 0000000..1507981
--- /dev/null
+++ b/Assets/Images/Items/CopperIngotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://byyayy655nu7l"
+path="res://.godot/imported/CopperIngotSymbol.png-7ab79d51a6f1dbf07a4b1341bcd49beb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/CopperIngotSymbol.png"
+dest_files=["res://.godot/imported/CopperIngotSymbol.png-7ab79d51a6f1dbf07a4b1341bcd49beb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/CopperPlateSymbol.png b/Assets/Images/Items/CopperPlateSymbol.png
new file mode 100644
index 0000000..7413c67
Binary files /dev/null and b/Assets/Images/Items/CopperPlateSymbol.png differ
diff --git a/Assets/Images/Items/CopperPlateSymbol.png.import b/Assets/Images/Items/CopperPlateSymbol.png.import
new file mode 100644
index 0000000..0cedf77
--- /dev/null
+++ b/Assets/Images/Items/CopperPlateSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://br83f2eck2vv5"
+path="res://.godot/imported/CopperPlateSymbol.png-31c7bb02b44dcdfae0428be86a4e5506.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/CopperPlateSymbol.png"
+dest_files=["res://.godot/imported/CopperPlateSymbol.png-31c7bb02b44dcdfae0428be86a4e5506.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/CopperRobotSymbol.png b/Assets/Images/Items/CopperRobotSymbol.png
new file mode 100644
index 0000000..127fa4b
Binary files /dev/null and b/Assets/Images/Items/CopperRobotSymbol.png differ
diff --git a/Assets/Images/Items/CopperRobotSymbol.png.import b/Assets/Images/Items/CopperRobotSymbol.png.import
new file mode 100644
index 0000000..547c7dd
--- /dev/null
+++ b/Assets/Images/Items/CopperRobotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c6fe6svg01p0e"
+path="res://.godot/imported/CopperRobotSymbol.png-be8d61dd9e02b11e95e04963edc69a27.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/CopperRobotSymbol.png"
+dest_files=["res://.godot/imported/CopperRobotSymbol.png-be8d61dd9e02b11e95e04963edc69a27.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/CopperRodSymbol.png b/Assets/Images/Items/CopperRodSymbol.png
new file mode 100644
index 0000000..8851540
Binary files /dev/null and b/Assets/Images/Items/CopperRodSymbol.png differ
diff --git a/Assets/Images/Items/CopperRodSymbol.png.import b/Assets/Images/Items/CopperRodSymbol.png.import
new file mode 100644
index 0000000..24e8774
--- /dev/null
+++ b/Assets/Images/Items/CopperRodSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c2nch0nunfgti"
+path="res://.godot/imported/CopperRodSymbol.png-f94be149325d690d03972ebce1c76b93.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/CopperRodSymbol.png"
+dest_files=["res://.godot/imported/CopperRodSymbol.png-f94be149325d690d03972ebce1c76b93.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/CopperWireSymbol.png b/Assets/Images/Items/CopperWireSymbol.png
new file mode 100644
index 0000000..56ef086
Binary files /dev/null and b/Assets/Images/Items/CopperWireSymbol.png differ
diff --git a/Assets/Images/Items/CopperWireSymbol.png.import b/Assets/Images/Items/CopperWireSymbol.png.import
new file mode 100644
index 0000000..2920f6c
--- /dev/null
+++ b/Assets/Images/Items/CopperWireSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cetdbqubrkgje"
+path="res://.godot/imported/CopperWireSymbol.png-65bb6c26b95184a3a094f7f5d710f60a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/CopperWireSymbol.png"
+dest_files=["res://.godot/imported/CopperWireSymbol.png-65bb6c26b95184a3a094f7f5d710f60a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/Dynamov1Symbol.png b/Assets/Images/Items/Dynamov1Symbol.png
new file mode 100644
index 0000000..a58d871
Binary files /dev/null and b/Assets/Images/Items/Dynamov1Symbol.png differ
diff --git a/Assets/Images/Items/Dynamov1Symbol.png.import b/Assets/Images/Items/Dynamov1Symbol.png.import
new file mode 100644
index 0000000..ed58c59
--- /dev/null
+++ b/Assets/Images/Items/Dynamov1Symbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dskhbotxy2kq4"
+path="res://.godot/imported/Dynamov1Symbol.png-c89186035396a835fb7a497e27745efb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/Dynamov1Symbol.png"
+dest_files=["res://.godot/imported/Dynamov1Symbol.png-c89186035396a835fb7a497e27745efb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/Dynamov2Symbol.png b/Assets/Images/Items/Dynamov2Symbol.png
new file mode 100644
index 0000000..f9d39f5
Binary files /dev/null and b/Assets/Images/Items/Dynamov2Symbol.png differ
diff --git a/Assets/Images/Items/Dynamov2Symbol.png.import b/Assets/Images/Items/Dynamov2Symbol.png.import
new file mode 100644
index 0000000..09dc2d2
--- /dev/null
+++ b/Assets/Images/Items/Dynamov2Symbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qqprre8xl8gv"
+path="res://.godot/imported/Dynamov2Symbol.png-988dba62e9bbf8e45506bade7a65a9a3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/Dynamov2Symbol.png"
+dest_files=["res://.godot/imported/Dynamov2Symbol.png-988dba62e9bbf8e45506bade7a65a9a3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/GlassBottleSymbol.png b/Assets/Images/Items/GlassBottleSymbol.png
new file mode 100644
index 0000000..e9f90f9
Binary files /dev/null and b/Assets/Images/Items/GlassBottleSymbol.png differ
diff --git a/Assets/Images/Items/GlassBottleSymbol.png.import b/Assets/Images/Items/GlassBottleSymbol.png.import
new file mode 100644
index 0000000..251c800
--- /dev/null
+++ b/Assets/Images/Items/GlassBottleSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cg8h328oygem6"
+path="res://.godot/imported/GlassBottleSymbol.png-14f58a27423e04fa8458bfd19e2ed5e9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/GlassBottleSymbol.png"
+dest_files=["res://.godot/imported/GlassBottleSymbol.png-14f58a27423e04fa8458bfd19e2ed5e9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/GlassSymbol.png b/Assets/Images/Items/GlassSymbol.png
new file mode 100644
index 0000000..ad58efe
Binary files /dev/null and b/Assets/Images/Items/GlassSymbol.png differ
diff --git a/Assets/Images/Items/GlassSymbol.png.import b/Assets/Images/Items/GlassSymbol.png.import
new file mode 100644
index 0000000..cc3646c
--- /dev/null
+++ b/Assets/Images/Items/GlassSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cxdhnq28e1j4y"
+path="res://.godot/imported/GlassSymbol.png-8ff93f7052ef11acb11bdf17ccf6dec7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/GlassSymbol.png"
+dest_files=["res://.godot/imported/GlassSymbol.png-8ff93f7052ef11acb11bdf17ccf6dec7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/Heaterv1Symbol.png b/Assets/Images/Items/Heaterv1Symbol.png
new file mode 100644
index 0000000..1f407f4
Binary files /dev/null and b/Assets/Images/Items/Heaterv1Symbol.png differ
diff --git a/Assets/Images/Items/Heaterv1Symbol.png.import b/Assets/Images/Items/Heaterv1Symbol.png.import
new file mode 100644
index 0000000..a18c9e7
--- /dev/null
+++ b/Assets/Images/Items/Heaterv1Symbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://crmmf7oa7u4sc"
+path="res://.godot/imported/Heaterv1Symbol.png-90482a89d7ddbb5b938a2cb2cf4f3740.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/Heaterv1Symbol.png"
+dest_files=["res://.godot/imported/Heaterv1Symbol.png-90482a89d7ddbb5b938a2cb2cf4f3740.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/IronGearSymbol.png b/Assets/Images/Items/IronGearSymbol.png
new file mode 100644
index 0000000..1e3f9c2
Binary files /dev/null and b/Assets/Images/Items/IronGearSymbol.png differ
diff --git a/Assets/Images/Items/IronGearSymbol.png.import b/Assets/Images/Items/IronGearSymbol.png.import
new file mode 100644
index 0000000..67aeb6f
--- /dev/null
+++ b/Assets/Images/Items/IronGearSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://balm6gackr2re"
+path="res://.godot/imported/IronGearSymbol.png-3b03dbc311f8b0d1ddaf93f0df279b6b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/IronGearSymbol.png"
+dest_files=["res://.godot/imported/IronGearSymbol.png-3b03dbc311f8b0d1ddaf93f0df279b6b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/IronIngotSymbol.png b/Assets/Images/Items/IronIngotSymbol.png
new file mode 100644
index 0000000..b70909e
Binary files /dev/null and b/Assets/Images/Items/IronIngotSymbol.png differ
diff --git a/Assets/Images/Items/IronIngotSymbol.png.import b/Assets/Images/Items/IronIngotSymbol.png.import
new file mode 100644
index 0000000..e478194
--- /dev/null
+++ b/Assets/Images/Items/IronIngotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dxg8iyfcov61w"
+path="res://.godot/imported/IronIngotSymbol.png-1f6008c56b15f33510beef185c514f88.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/IronIngotSymbol.png"
+dest_files=["res://.godot/imported/IronIngotSymbol.png-1f6008c56b15f33510beef185c514f88.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/IronPlateSymbol.png b/Assets/Images/Items/IronPlateSymbol.png
new file mode 100644
index 0000000..9bb58fc
Binary files /dev/null and b/Assets/Images/Items/IronPlateSymbol.png differ
diff --git a/Assets/Images/Items/IronPlateSymbol.png.import b/Assets/Images/Items/IronPlateSymbol.png.import
new file mode 100644
index 0000000..8ed3570
--- /dev/null
+++ b/Assets/Images/Items/IronPlateSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bda6vx7o0wxk8"
+path="res://.godot/imported/IronPlateSymbol.png-98c0dd0c49258c737a6ce27ab41d49f2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/IronPlateSymbol.png"
+dest_files=["res://.godot/imported/IronPlateSymbol.png-98c0dd0c49258c737a6ce27ab41d49f2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/IronRobotSymbol.png b/Assets/Images/Items/IronRobotSymbol.png
new file mode 100644
index 0000000..10836d1
Binary files /dev/null and b/Assets/Images/Items/IronRobotSymbol.png differ
diff --git a/Assets/Images/Items/IronRobotSymbol.png.import b/Assets/Images/Items/IronRobotSymbol.png.import
new file mode 100644
index 0000000..c6846d3
--- /dev/null
+++ b/Assets/Images/Items/IronRobotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://3d5pcunhq3sk"
+path="res://.godot/imported/IronRobotSymbol.png-490bc4073c226606ccde0f6cc18c6827.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/IronRobotSymbol.png"
+dest_files=["res://.godot/imported/IronRobotSymbol.png-490bc4073c226606ccde0f6cc18c6827.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/IronRodSymbol.png b/Assets/Images/Items/IronRodSymbol.png
new file mode 100644
index 0000000..df14c96
Binary files /dev/null and b/Assets/Images/Items/IronRodSymbol.png differ
diff --git a/Assets/Images/Items/IronRodSymbol.png.import b/Assets/Images/Items/IronRodSymbol.png.import
new file mode 100644
index 0000000..c1c4be0
--- /dev/null
+++ b/Assets/Images/Items/IronRodSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c48wylvnkggb2"
+path="res://.godot/imported/IronRodSymbol.png-5015b3836e3a1660b53461aafcf541fb.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/IronRodSymbol.png"
+dest_files=["res://.godot/imported/IronRodSymbol.png-5015b3836e3a1660b53461aafcf541fb.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/RopeSymbol.png b/Assets/Images/Items/RopeSymbol.png
new file mode 100644
index 0000000..3b2c4e9
Binary files /dev/null and b/Assets/Images/Items/RopeSymbol.png differ
diff --git a/Assets/Images/Items/RopeSymbol.png.import b/Assets/Images/Items/RopeSymbol.png.import
new file mode 100644
index 0000000..211f744
--- /dev/null
+++ b/Assets/Images/Items/RopeSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dhp6qvrdefenm"
+path="res://.godot/imported/RopeSymbol.png-8382e7e04f6f3149c2b090dfb6a13993.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/RopeSymbol.png"
+dest_files=["res://.godot/imported/RopeSymbol.png-8382e7e04f6f3149c2b090dfb6a13993.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/SandSymbol.png b/Assets/Images/Items/SandSymbol.png
new file mode 100644
index 0000000..4dec160
Binary files /dev/null and b/Assets/Images/Items/SandSymbol.png differ
diff --git a/Assets/Images/Items/SandSymbol.png.import b/Assets/Images/Items/SandSymbol.png.import
new file mode 100644
index 0000000..481ea67
--- /dev/null
+++ b/Assets/Images/Items/SandSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cjtauy0vg8ld"
+path="res://.godot/imported/SandSymbol.png-246965930709b65cba5b638940625f56.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/SandSymbol.png"
+dest_files=["res://.godot/imported/SandSymbol.png-246965930709b65cba5b638940625f56.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/SteamSymbol.png b/Assets/Images/Items/SteamSymbol.png
new file mode 100644
index 0000000..a5a14ca
Binary files /dev/null and b/Assets/Images/Items/SteamSymbol.png differ
diff --git a/Assets/Images/Items/SteamSymbol.png.import b/Assets/Images/Items/SteamSymbol.png.import
new file mode 100644
index 0000000..c3b6b81
--- /dev/null
+++ b/Assets/Images/Items/SteamSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d1lmywwvkbx1n"
+path="res://.godot/imported/SteamSymbol.png-063a5f03965dc55f7df6ac24b68e4cc9.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/SteamSymbol.png"
+dest_files=["res://.godot/imported/SteamSymbol.png-063a5f03965dc55f7df6ac24b68e4cc9.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/StoneGearSymbol.png b/Assets/Images/Items/StoneGearSymbol.png
new file mode 100644
index 0000000..1897da1
Binary files /dev/null and b/Assets/Images/Items/StoneGearSymbol.png differ
diff --git a/Assets/Images/Items/StoneGearSymbol.png.import b/Assets/Images/Items/StoneGearSymbol.png.import
new file mode 100644
index 0000000..56b2724
--- /dev/null
+++ b/Assets/Images/Items/StoneGearSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cn0l3qodxili"
+path="res://.godot/imported/StoneGearSymbol.png-f781185ac1dda6147c9ea3125dada4b6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/StoneGearSymbol.png"
+dest_files=["res://.godot/imported/StoneGearSymbol.png-f781185ac1dda6147c9ea3125dada4b6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/StonePlateSymbol.png b/Assets/Images/Items/StonePlateSymbol.png
new file mode 100644
index 0000000..93875f7
Binary files /dev/null and b/Assets/Images/Items/StonePlateSymbol.png differ
diff --git a/Assets/Images/Items/StonePlateSymbol.png.import b/Assets/Images/Items/StonePlateSymbol.png.import
new file mode 100644
index 0000000..0b213f2
--- /dev/null
+++ b/Assets/Images/Items/StonePlateSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b3en3i1rlpsnq"
+path="res://.godot/imported/StonePlateSymbol.png-b550cd33f2457f4f7f4c530ed23bd3b3.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/StonePlateSymbol.png"
+dest_files=["res://.godot/imported/StonePlateSymbol.png-b550cd33f2457f4f7f4c530ed23bd3b3.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/StoneRobotSymbol.png b/Assets/Images/Items/StoneRobotSymbol.png
new file mode 100644
index 0000000..c2aa879
Binary files /dev/null and b/Assets/Images/Items/StoneRobotSymbol.png differ
diff --git a/Assets/Images/Items/StoneRobotSymbol.png.import b/Assets/Images/Items/StoneRobotSymbol.png.import
new file mode 100644
index 0000000..d49ecfd
--- /dev/null
+++ b/Assets/Images/Items/StoneRobotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cf6xwms4nrndi"
+path="res://.godot/imported/StoneRobotSymbol.png-423e10c53fafb13af1f087db329e6763.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/StoneRobotSymbol.png"
+dest_files=["res://.godot/imported/StoneRobotSymbol.png-423e10c53fafb13af1f087db329e6763.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/TinGearSymbol.png b/Assets/Images/Items/TinGearSymbol.png
new file mode 100644
index 0000000..9426db7
Binary files /dev/null and b/Assets/Images/Items/TinGearSymbol.png differ
diff --git a/Assets/Images/Items/TinGearSymbol.png.import b/Assets/Images/Items/TinGearSymbol.png.import
new file mode 100644
index 0000000..540e632
--- /dev/null
+++ b/Assets/Images/Items/TinGearSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cc30tnu3rsn06"
+path="res://.godot/imported/TinGearSymbol.png-78172de56f55222a3161f0d3ee2e837f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/TinGearSymbol.png"
+dest_files=["res://.godot/imported/TinGearSymbol.png-78172de56f55222a3161f0d3ee2e837f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/TinIngotSymbol.png b/Assets/Images/Items/TinIngotSymbol.png
new file mode 100644
index 0000000..ef77445
Binary files /dev/null and b/Assets/Images/Items/TinIngotSymbol.png differ
diff --git a/Assets/Images/Items/TinIngotSymbol.png.import b/Assets/Images/Items/TinIngotSymbol.png.import
new file mode 100644
index 0000000..918b25d
--- /dev/null
+++ b/Assets/Images/Items/TinIngotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://kj03htpsmleu"
+path="res://.godot/imported/TinIngotSymbol.png-745e7904ffbdac4c64c87245c2de67cd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/TinIngotSymbol.png"
+dest_files=["res://.godot/imported/TinIngotSymbol.png-745e7904ffbdac4c64c87245c2de67cd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/TinPlateSymbol.png b/Assets/Images/Items/TinPlateSymbol.png
new file mode 100644
index 0000000..3394cdd
Binary files /dev/null and b/Assets/Images/Items/TinPlateSymbol.png differ
diff --git a/Assets/Images/Items/TinPlateSymbol.png.import b/Assets/Images/Items/TinPlateSymbol.png.import
new file mode 100644
index 0000000..2c54fbe
--- /dev/null
+++ b/Assets/Images/Items/TinPlateSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bc1xuqchwfil5"
+path="res://.godot/imported/TinPlateSymbol.png-65c2494d1c596428a88b2fa91d4c94be.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/TinPlateSymbol.png"
+dest_files=["res://.godot/imported/TinPlateSymbol.png-65c2494d1c596428a88b2fa91d4c94be.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/TinRobotSymbol.png b/Assets/Images/Items/TinRobotSymbol.png
new file mode 100644
index 0000000..ea4bcfc
Binary files /dev/null and b/Assets/Images/Items/TinRobotSymbol.png differ
diff --git a/Assets/Images/Items/TinRobotSymbol.png.import b/Assets/Images/Items/TinRobotSymbol.png.import
new file mode 100644
index 0000000..d83e761
--- /dev/null
+++ b/Assets/Images/Items/TinRobotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dcggjbr301ono"
+path="res://.godot/imported/TinRobotSymbol.png-09a89a4fd9ee472b3f78c7f29948133f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/TinRobotSymbol.png"
+dest_files=["res://.godot/imported/TinRobotSymbol.png-09a89a4fd9ee472b3f78c7f29948133f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Items/TinRodSymbol.png b/Assets/Images/Items/TinRodSymbol.png
new file mode 100644
index 0000000..891deb7
Binary files /dev/null and b/Assets/Images/Items/TinRodSymbol.png differ
diff --git a/Assets/Images/Items/TinRodSymbol.png.import b/Assets/Images/Items/TinRodSymbol.png.import
new file mode 100644
index 0000000..ebc92c8
--- /dev/null
+++ b/Assets/Images/Items/TinRodSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bf68dwarsq7pu"
+path="res://.godot/imported/TinRodSymbol.png-5288fb7d036ee6f9ef1f89b8947d082f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Items/TinRodSymbol.png"
+dest_files=["res://.godot/imported/TinRodSymbol.png-5288fb7d036ee6f9ef1f89b8947d082f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/MapSymbol.png b/Assets/Images/MapSymbol.png
new file mode 100644
index 0000000..c1fa909
Binary files /dev/null and b/Assets/Images/MapSymbol.png differ
diff --git a/Assets/Images/MapSymbol.png.import b/Assets/Images/MapSymbol.png.import
new file mode 100644
index 0000000..90f7d29
--- /dev/null
+++ b/Assets/Images/MapSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://d068gyi3e48cv"
+path="res://.godot/imported/MapSymbol.png-4a1f7c6e1babbd0dba8cf85fcc55cf8e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/MapSymbol.png"
+dest_files=["res://.godot/imported/MapSymbol.png-4a1f7c6e1babbd0dba8cf85fcc55cf8e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/craft.png b/Assets/Images/Nodes/craft.png
new file mode 100644
index 0000000..480d755
Binary files /dev/null and b/Assets/Images/Nodes/craft.png differ
diff --git a/Assets/Images/Nodes/craft.png.import b/Assets/Images/Nodes/craft.png.import
new file mode 100644
index 0000000..c7b7b74
--- /dev/null
+++ b/Assets/Images/Nodes/craft.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bjxwrtm0bttio"
+path="res://.godot/imported/craft.png-6ef1d3b3d2b282380072caa5b7edf88e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/craft.png"
+dest_files=["res://.godot/imported/craft.png-6ef1d3b3d2b282380072caa5b7edf88e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/explore.png b/Assets/Images/Nodes/explore.png
new file mode 100644
index 0000000..a579c46
Binary files /dev/null and b/Assets/Images/Nodes/explore.png differ
diff --git a/Assets/Images/Nodes/explore.png.import b/Assets/Images/Nodes/explore.png.import
new file mode 100644
index 0000000..3ca1658
--- /dev/null
+++ b/Assets/Images/Nodes/explore.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b6jm34qgckaxh"
+path="res://.godot/imported/explore.png-953528913d382d25c22ebda01c9eae17.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/explore.png"
+dest_files=["res://.godot/imported/explore.png-953528913d382d25c22ebda01c9eae17.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/for.png b/Assets/Images/Nodes/for.png
new file mode 100644
index 0000000..98342ba
Binary files /dev/null and b/Assets/Images/Nodes/for.png differ
diff --git a/Assets/Images/Nodes/for.png.import b/Assets/Images/Nodes/for.png.import
new file mode 100644
index 0000000..9cfa4ff
--- /dev/null
+++ b/Assets/Images/Nodes/for.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cugoibldkatry"
+path="res://.godot/imported/for.png-7e0a93012248eea32dfb05f41c7e4f81.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/for.png"
+dest_files=["res://.godot/imported/for.png-7e0a93012248eea32dfb05f41c7e4f81.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/harvest.png b/Assets/Images/Nodes/harvest.png
new file mode 100644
index 0000000..eaa0091
Binary files /dev/null and b/Assets/Images/Nodes/harvest.png differ
diff --git a/Assets/Images/Nodes/harvest.png.import b/Assets/Images/Nodes/harvest.png.import
new file mode 100644
index 0000000..6b2740b
--- /dev/null
+++ b/Assets/Images/Nodes/harvest.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://qqtpqludvgxt"
+path="res://.godot/imported/harvest.png-0cc3bd3c70f996a004117a3f7a91177a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/harvest.png"
+dest_files=["res://.godot/imported/harvest.png-0cc3bd3c70f996a004117a3f7a91177a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/if.png b/Assets/Images/Nodes/if.png
new file mode 100644
index 0000000..cd91c39
Binary files /dev/null and b/Assets/Images/Nodes/if.png differ
diff --git a/Assets/Images/Nodes/if.png.import b/Assets/Images/Nodes/if.png.import
new file mode 100644
index 0000000..59f92b7
--- /dev/null
+++ b/Assets/Images/Nodes/if.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://chh5hsjsy254n"
+path="res://.godot/imported/if.png-4751c8cb00c015bf90caf7b01ca65fcf.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/if.png"
+dest_files=["res://.godot/imported/if.png-4751c8cb00c015bf90caf7b01ca65fcf.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/maintain.png b/Assets/Images/Nodes/maintain.png
new file mode 100644
index 0000000..24071fa
Binary files /dev/null and b/Assets/Images/Nodes/maintain.png differ
diff --git a/Assets/Images/Nodes/maintain.png.import b/Assets/Images/Nodes/maintain.png.import
new file mode 100644
index 0000000..932e43d
--- /dev/null
+++ b/Assets/Images/Nodes/maintain.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0yysypsrfe7s"
+path="res://.godot/imported/maintain.png-d6e014a306fabbb6543e072f6642d4e7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/maintain.png"
+dest_files=["res://.godot/imported/maintain.png-d6e014a306fabbb6543e072f6642d4e7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/move.png b/Assets/Images/Nodes/move.png
new file mode 100644
index 0000000..4751211
Binary files /dev/null and b/Assets/Images/Nodes/move.png differ
diff --git a/Assets/Images/Nodes/move.png.import b/Assets/Images/Nodes/move.png.import
new file mode 100644
index 0000000..ae8ff66
--- /dev/null
+++ b/Assets/Images/Nodes/move.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dwen2run626at"
+path="res://.godot/imported/move.png-0fbfb070f028e7a2665763914c1c70f0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/move.png"
+dest_files=["res://.godot/imported/move.png-0fbfb070f028e7a2665763914c1c70f0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/sacrifice.png b/Assets/Images/Nodes/sacrifice.png
new file mode 100644
index 0000000..c7fa35d
Binary files /dev/null and b/Assets/Images/Nodes/sacrifice.png differ
diff --git a/Assets/Images/Nodes/sacrifice.png.import b/Assets/Images/Nodes/sacrifice.png.import
new file mode 100644
index 0000000..c30b37c
--- /dev/null
+++ b/Assets/Images/Nodes/sacrifice.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://btumhi0t4c0f0"
+path="res://.godot/imported/sacrifice.png-1291ff8e1d4c6a2ccf4900842ccf4993.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/sacrifice.png"
+dest_files=["res://.godot/imported/sacrifice.png-1291ff8e1d4c6a2ccf4900842ccf4993.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/start.png b/Assets/Images/Nodes/start.png
new file mode 100644
index 0000000..0b00549
Binary files /dev/null and b/Assets/Images/Nodes/start.png differ
diff --git a/Assets/Images/Nodes/start.png.import b/Assets/Images/Nodes/start.png.import
new file mode 100644
index 0000000..fac39f3
--- /dev/null
+++ b/Assets/Images/Nodes/start.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cktnj1a1rg5yu"
+path="res://.godot/imported/start.png-edc6bdb9e8290898275934ddb44d2834.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/start.png"
+dest_files=["res://.godot/imported/start.png-edc6bdb9e8290898275934ddb44d2834.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Nodes/while.png b/Assets/Images/Nodes/while.png
new file mode 100644
index 0000000..740ae76
Binary files /dev/null and b/Assets/Images/Nodes/while.png differ
diff --git a/Assets/Images/Nodes/while.png.import b/Assets/Images/Nodes/while.png.import
new file mode 100644
index 0000000..40a83e1
--- /dev/null
+++ b/Assets/Images/Nodes/while.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://p0b5h6kts0jo"
+path="res://.godot/imported/while.png-a59b08af0a24cbfedd7a46b9cd582957.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Nodes/while.png"
+dest_files=["res://.godot/imported/while.png-a59b08af0a24cbfedd7a46b9cd582957.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/OptionsSymbol.png b/Assets/Images/OptionsSymbol.png
new file mode 100644
index 0000000..d30533c
Binary files /dev/null and b/Assets/Images/OptionsSymbol.png differ
diff --git a/Assets/Images/OptionsSymbol.png.import b/Assets/Images/OptionsSymbol.png.import
new file mode 100644
index 0000000..510ddb7
--- /dev/null
+++ b/Assets/Images/OptionsSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b77mo4fhklnja"
+path="res://.godot/imported/OptionsSymbol.png-02147c17029ed96aee0de97970ec671c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/OptionsSymbol.png"
+dest_files=["res://.godot/imported/OptionsSymbol.png-02147c17029ed96aee0de97970ec671c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Research/BasicsSymbol.png b/Assets/Images/Research/BasicsSymbol.png
new file mode 100644
index 0000000..f851a78
Binary files /dev/null and b/Assets/Images/Research/BasicsSymbol.png differ
diff --git a/Assets/Images/Research/BasicsSymbol.png.import b/Assets/Images/Research/BasicsSymbol.png.import
new file mode 100644
index 0000000..fbebc4c
--- /dev/null
+++ b/Assets/Images/Research/BasicsSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cv76v3845c3ff"
+path="res://.godot/imported/BasicsSymbol.png-a01daba6cb03ee9cf8ccd4490920bec0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Research/BasicsSymbol.png"
+dest_files=["res://.godot/imported/BasicsSymbol.png-a01daba6cb03ee9cf8ccd4490920bec0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Research/BronzeageSymbol.png b/Assets/Images/Research/BronzeageSymbol.png
new file mode 100644
index 0000000..825c320
Binary files /dev/null and b/Assets/Images/Research/BronzeageSymbol.png differ
diff --git a/Assets/Images/Research/BronzeageSymbol.png.import b/Assets/Images/Research/BronzeageSymbol.png.import
new file mode 100644
index 0000000..8c557e9
--- /dev/null
+++ b/Assets/Images/Research/BronzeageSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://chr4p5ns0n3n5"
+path="res://.godot/imported/BronzeageSymbol.png-1285905aa4eae49f9c832663e55b2f43.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Research/BronzeageSymbol.png"
+dest_files=["res://.godot/imported/BronzeageSymbol.png-1285905aa4eae49f9c832663e55b2f43.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Research/CopperageSymbol.png b/Assets/Images/Research/CopperageSymbol.png
new file mode 100644
index 0000000..523d909
Binary files /dev/null and b/Assets/Images/Research/CopperageSymbol.png differ
diff --git a/Assets/Images/Research/CopperageSymbol.png.import b/Assets/Images/Research/CopperageSymbol.png.import
new file mode 100644
index 0000000..7ac5f13
--- /dev/null
+++ b/Assets/Images/Research/CopperageSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://jrv5xnbfcn2o"
+path="res://.godot/imported/CopperageSymbol.png-8c4ce73b4c0fc81e445b053ca318cde1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Research/CopperageSymbol.png"
+dest_files=["res://.godot/imported/CopperageSymbol.png-8c4ce73b4c0fc81e445b053ca318cde1.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Research/IronageSymbol.png b/Assets/Images/Research/IronageSymbol.png
new file mode 100644
index 0000000..3d1616a
Binary files /dev/null and b/Assets/Images/Research/IronageSymbol.png differ
diff --git a/Assets/Images/Research/IronageSymbol.png.import b/Assets/Images/Research/IronageSymbol.png.import
new file mode 100644
index 0000000..933a046
--- /dev/null
+++ b/Assets/Images/Research/IronageSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cy7c16352g6pq"
+path="res://.godot/imported/IronageSymbol.png-39224787c5463ca99d1883f6a5875248.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Research/IronageSymbol.png"
+dest_files=["res://.godot/imported/IronageSymbol.png-39224787c5463ca99d1883f6a5875248.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Research/StoneageSymbol.png b/Assets/Images/Research/StoneageSymbol.png
new file mode 100644
index 0000000..c922c77
Binary files /dev/null and b/Assets/Images/Research/StoneageSymbol.png differ
diff --git a/Assets/Images/Research/StoneageSymbol.png.import b/Assets/Images/Research/StoneageSymbol.png.import
new file mode 100644
index 0000000..254c787
--- /dev/null
+++ b/Assets/Images/Research/StoneageSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://67pb1ajfs85y"
+path="res://.godot/imported/StoneageSymbol.png-e2c2604dc51c02e3cc86b76cc1baf943.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Research/StoneageSymbol.png"
+dest_files=["res://.godot/imported/StoneageSymbol.png-e2c2604dc51c02e3cc86b76cc1baf943.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/ResearchSymbol.png b/Assets/Images/ResearchSymbol.png
new file mode 100644
index 0000000..bb00384
Binary files /dev/null and b/Assets/Images/ResearchSymbol.png differ
diff --git a/Assets/Images/ResearchSymbol.png.import b/Assets/Images/ResearchSymbol.png.import
new file mode 100644
index 0000000..a28914c
--- /dev/null
+++ b/Assets/Images/ResearchSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dt84awx33mulb"
+path="res://.godot/imported/ResearchSymbol.png-e8f43265eb41a396705bd37c5092cde8.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/ResearchSymbol.png"
+dest_files=["res://.godot/imported/ResearchSymbol.png-e8f43265eb41a396705bd37c5092cde8.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/CoalSymbol.png b/Assets/Images/Resources/CoalSymbol.png
new file mode 100644
index 0000000..3f8c5e9
Binary files /dev/null and b/Assets/Images/Resources/CoalSymbol.png differ
diff --git a/Assets/Images/Resources/CoalSymbol.png.import b/Assets/Images/Resources/CoalSymbol.png.import
new file mode 100644
index 0000000..2d8c7e2
--- /dev/null
+++ b/Assets/Images/Resources/CoalSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bilbgdoawvdlb"
+path="res://.godot/imported/CoalSymbol.png-cb64bc13e361a9ca1af5b1a23428878e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/CoalSymbol.png"
+dest_files=["res://.godot/imported/CoalSymbol.png-cb64bc13e361a9ca1af5b1a23428878e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/CopperSymbol.png b/Assets/Images/Resources/CopperSymbol.png
new file mode 100644
index 0000000..1214498
Binary files /dev/null and b/Assets/Images/Resources/CopperSymbol.png differ
diff --git a/Assets/Images/Resources/CopperSymbol.png.import b/Assets/Images/Resources/CopperSymbol.png.import
new file mode 100644
index 0000000..dc1120e
--- /dev/null
+++ b/Assets/Images/Resources/CopperSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cxmr7ph6rmjii"
+path="res://.godot/imported/CopperSymbol.png-f340270affbaa916cd32a0be9bcc50e0.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/CopperSymbol.png"
+dest_files=["res://.godot/imported/CopperSymbol.png-f340270affbaa916cd32a0be9bcc50e0.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/IronSymbol.png b/Assets/Images/Resources/IronSymbol.png
new file mode 100644
index 0000000..084050a
Binary files /dev/null and b/Assets/Images/Resources/IronSymbol.png differ
diff --git a/Assets/Images/Resources/IronSymbol.png.import b/Assets/Images/Resources/IronSymbol.png.import
new file mode 100644
index 0000000..b9cac72
--- /dev/null
+++ b/Assets/Images/Resources/IronSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bfyo2pyq4l6fb"
+path="res://.godot/imported/IronSymbol.png-880cf64b411c06d67a1c6cdc55f5bd4c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/IronSymbol.png"
+dest_files=["res://.godot/imported/IronSymbol.png-880cf64b411c06d67a1c6cdc55f5bd4c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/MushroomSymbol.png b/Assets/Images/Resources/MushroomSymbol.png
new file mode 100644
index 0000000..01260cc
Binary files /dev/null and b/Assets/Images/Resources/MushroomSymbol.png differ
diff --git a/Assets/Images/Resources/MushroomSymbol.png.import b/Assets/Images/Resources/MushroomSymbol.png.import
new file mode 100644
index 0000000..7cda7e2
--- /dev/null
+++ b/Assets/Images/Resources/MushroomSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://uunnuou4g86w"
+path="res://.godot/imported/MushroomSymbol.png-d8e1886646151fea436491a5ff2c34d7.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/MushroomSymbol.png"
+dest_files=["res://.godot/imported/MushroomSymbol.png-d8e1886646151fea436491a5ff2c34d7.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/SpiderSilkSymbol.png b/Assets/Images/Resources/SpiderSilkSymbol.png
new file mode 100644
index 0000000..c608b72
Binary files /dev/null and b/Assets/Images/Resources/SpiderSilkSymbol.png differ
diff --git a/Assets/Images/Resources/SpiderSilkSymbol.png.import b/Assets/Images/Resources/SpiderSilkSymbol.png.import
new file mode 100644
index 0000000..42db889
--- /dev/null
+++ b/Assets/Images/Resources/SpiderSilkSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://0u163vlp4lvt"
+path="res://.godot/imported/SpiderSilkSymbol.png-fc552996fac8f96f8733aa0bfdc1b096.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/SpiderSilkSymbol.png"
+dest_files=["res://.godot/imported/SpiderSilkSymbol.png-fc552996fac8f96f8733aa0bfdc1b096.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/StoneSymbol.png b/Assets/Images/Resources/StoneSymbol.png
new file mode 100644
index 0000000..969dc9c
Binary files /dev/null and b/Assets/Images/Resources/StoneSymbol.png differ
diff --git a/Assets/Images/Resources/StoneSymbol.png.import b/Assets/Images/Resources/StoneSymbol.png.import
new file mode 100644
index 0000000..380c1b5
--- /dev/null
+++ b/Assets/Images/Resources/StoneSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://cteg2fntlutjq"
+path="res://.godot/imported/StoneSymbol.png-d9f0111a93ff2b5a3f94fe679a2a672b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/StoneSymbol.png"
+dest_files=["res://.godot/imported/StoneSymbol.png-d9f0111a93ff2b5a3f94fe679a2a672b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/TinSymbol.png b/Assets/Images/Resources/TinSymbol.png
new file mode 100644
index 0000000..5704c2f
Binary files /dev/null and b/Assets/Images/Resources/TinSymbol.png differ
diff --git a/Assets/Images/Resources/TinSymbol.png.import b/Assets/Images/Resources/TinSymbol.png.import
new file mode 100644
index 0000000..d6f0fbc
--- /dev/null
+++ b/Assets/Images/Resources/TinSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://141sj37rysng"
+path="res://.godot/imported/TinSymbol.png-af9eaa6401bd268c152dae3fc14c2b0f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/TinSymbol.png"
+dest_files=["res://.godot/imported/TinSymbol.png-af9eaa6401bd268c152dae3fc14c2b0f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Resources/WaterSymbol.png b/Assets/Images/Resources/WaterSymbol.png
new file mode 100644
index 0000000..77226e4
Binary files /dev/null and b/Assets/Images/Resources/WaterSymbol.png differ
diff --git a/Assets/Images/Resources/WaterSymbol.png.import b/Assets/Images/Resources/WaterSymbol.png.import
new file mode 100644
index 0000000..519cf65
--- /dev/null
+++ b/Assets/Images/Resources/WaterSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dje86ro2e37xl"
+path="res://.godot/imported/WaterSymbol.png-971cd4fb0992dec1e056487bccbaf22c.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Resources/WaterSymbol.png"
+dest_files=["res://.godot/imported/WaterSymbol.png-971cd4fb0992dec1e056487bccbaf22c.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/RobotSymbol.png b/Assets/Images/RobotSymbol.png
new file mode 100644
index 0000000..e4ff193
Binary files /dev/null and b/Assets/Images/RobotSymbol.png differ
diff --git a/Assets/Images/RobotSymbol.png.import b/Assets/Images/RobotSymbol.png.import
new file mode 100644
index 0000000..52635e6
--- /dev/null
+++ b/Assets/Images/RobotSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://ban872p4eh4gi"
+path="res://.godot/imported/RobotSymbol.png-6ec1ad765247ddcafab83ae89504a961.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/RobotSymbol.png"
+dest_files=["res://.godot/imported/RobotSymbol.png-6ec1ad765247ddcafab83ae89504a961.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Sources/AlarmSign.aseprite b/Assets/Images/Sources/AlarmSign.aseprite
new file mode 100644
index 0000000..78237ff
Binary files /dev/null and b/Assets/Images/Sources/AlarmSign.aseprite differ
diff --git a/Assets/Images/Sources/BasicsSymbol.aseprite b/Assets/Images/Sources/BasicsSymbol.aseprite
new file mode 100644
index 0000000..b47e5b3
Binary files /dev/null and b/Assets/Images/Sources/BasicsSymbol.aseprite differ
diff --git a/Assets/Images/Sources/Batteryv1Symbol.aseprite b/Assets/Images/Sources/Batteryv1Symbol.aseprite
new file mode 100644
index 0000000..0285a32
Binary files /dev/null and b/Assets/Images/Sources/Batteryv1Symbol.aseprite differ
diff --git a/Assets/Images/Sources/Batteryv2Symbol.aseprite b/Assets/Images/Sources/Batteryv2Symbol.aseprite
new file mode 100644
index 0000000..3834f39
Binary files /dev/null and b/Assets/Images/Sources/Batteryv2Symbol.aseprite differ
diff --git a/Assets/Images/Sources/BronzeBarSymbol.aseprite b/Assets/Images/Sources/BronzeBarSymbol.aseprite
new file mode 100644
index 0000000..2fb3e71
Binary files /dev/null and b/Assets/Images/Sources/BronzeBarSymbol.aseprite differ
diff --git a/Assets/Images/Sources/BronzeCogwheelSymbol.aseprite b/Assets/Images/Sources/BronzeCogwheelSymbol.aseprite
new file mode 100644
index 0000000..61616ae
Binary files /dev/null and b/Assets/Images/Sources/BronzeCogwheelSymbol.aseprite differ
diff --git a/Assets/Images/Sources/BronzeRobotSymbol.aseprite b/Assets/Images/Sources/BronzeRobotSymbol.aseprite
new file mode 100644
index 0000000..3b694c3
Binary files /dev/null and b/Assets/Images/Sources/BronzeRobotSymbol.aseprite differ
diff --git a/Assets/Images/Sources/BronzeageSymbol.aseprite b/Assets/Images/Sources/BronzeageSymbol.aseprite
new file mode 100644
index 0000000..0a69f27
Binary files /dev/null and b/Assets/Images/Sources/BronzeageSymbol.aseprite differ
diff --git a/Assets/Images/Sources/BronzeplateSymbol.aseprite b/Assets/Images/Sources/BronzeplateSymbol.aseprite
new file mode 100644
index 0000000..d894843
Binary files /dev/null and b/Assets/Images/Sources/BronzeplateSymbol.aseprite differ
diff --git a/Assets/Images/Sources/BronzerodSymbol.aseprite b/Assets/Images/Sources/BronzerodSymbol.aseprite
new file mode 100644
index 0000000..ddb0a76
Binary files /dev/null and b/Assets/Images/Sources/BronzerodSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CoalSymbol.aseprite b/Assets/Images/Sources/CoalSymbol.aseprite
new file mode 100644
index 0000000..a465c19
Binary files /dev/null and b/Assets/Images/Sources/CoalSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperBarSymbol.aseprite b/Assets/Images/Sources/CopperBarSymbol.aseprite
new file mode 100644
index 0000000..97053b4
Binary files /dev/null and b/Assets/Images/Sources/CopperBarSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperCogwheelSymbol.aseprite b/Assets/Images/Sources/CopperCogwheelSymbol.aseprite
new file mode 100644
index 0000000..df96d39
Binary files /dev/null and b/Assets/Images/Sources/CopperCogwheelSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperRobotSymbol.aseprite b/Assets/Images/Sources/CopperRobotSymbol.aseprite
new file mode 100644
index 0000000..f8ce7f9
Binary files /dev/null and b/Assets/Images/Sources/CopperRobotSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperSymbol.aseprite b/Assets/Images/Sources/CopperSymbol.aseprite
new file mode 100644
index 0000000..a23b9e0
Binary files /dev/null and b/Assets/Images/Sources/CopperSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperWireSymbol.aseprite b/Assets/Images/Sources/CopperWireSymbol.aseprite
new file mode 100644
index 0000000..0e1cb00
Binary files /dev/null and b/Assets/Images/Sources/CopperWireSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperageSymbol.aseprite b/Assets/Images/Sources/CopperageSymbol.aseprite
new file mode 100644
index 0000000..9e96476
Binary files /dev/null and b/Assets/Images/Sources/CopperageSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperplateSymbol.aseprite b/Assets/Images/Sources/CopperplateSymbol.aseprite
new file mode 100644
index 0000000..78ab3dc
Binary files /dev/null and b/Assets/Images/Sources/CopperplateSymbol.aseprite differ
diff --git a/Assets/Images/Sources/CopperrodSymbol.aseprite b/Assets/Images/Sources/CopperrodSymbol.aseprite
new file mode 100644
index 0000000..957ab00
Binary files /dev/null and b/Assets/Images/Sources/CopperrodSymbol.aseprite differ
diff --git a/Assets/Images/Sources/Dynamov1Symbol.aseprite b/Assets/Images/Sources/Dynamov1Symbol.aseprite
new file mode 100644
index 0000000..8769e01
Binary files /dev/null and b/Assets/Images/Sources/Dynamov1Symbol.aseprite differ
diff --git a/Assets/Images/Sources/Dynamov2Symbol.aseprite b/Assets/Images/Sources/Dynamov2Symbol.aseprite
new file mode 100644
index 0000000..8b91754
Binary files /dev/null and b/Assets/Images/Sources/Dynamov2Symbol.aseprite differ
diff --git a/Assets/Images/Sources/EnergySymbol.aseprite b/Assets/Images/Sources/EnergySymbol.aseprite
new file mode 100644
index 0000000..e7c7465
Binary files /dev/null and b/Assets/Images/Sources/EnergySymbol.aseprite differ
diff --git a/Assets/Images/Sources/GlasSymbol.aseprite b/Assets/Images/Sources/GlasSymbol.aseprite
new file mode 100644
index 0000000..ae1b7e5
Binary files /dev/null and b/Assets/Images/Sources/GlasSymbol.aseprite differ
diff --git a/Assets/Images/Sources/GlasbottleSymbol.aseprite b/Assets/Images/Sources/GlasbottleSymbol.aseprite
new file mode 100644
index 0000000..2120a22
Binary files /dev/null and b/Assets/Images/Sources/GlasbottleSymbol.aseprite differ
diff --git a/Assets/Images/Sources/Heaterv1Symbol.aseprite b/Assets/Images/Sources/Heaterv1Symbol.aseprite
new file mode 100644
index 0000000..5fcd0ad
Binary files /dev/null and b/Assets/Images/Sources/Heaterv1Symbol.aseprite differ
diff --git a/Assets/Images/Sources/InventorySymbol.aseprite b/Assets/Images/Sources/InventorySymbol.aseprite
new file mode 100644
index 0000000..4e3142d
Binary files /dev/null and b/Assets/Images/Sources/InventorySymbol.aseprite differ
diff --git a/Assets/Images/Sources/IronBarSymbol.aseprite b/Assets/Images/Sources/IronBarSymbol.aseprite
new file mode 100644
index 0000000..2c73d0c
Binary files /dev/null and b/Assets/Images/Sources/IronBarSymbol.aseprite differ
diff --git a/Assets/Images/Sources/IronCogwheelSymbol.aseprite b/Assets/Images/Sources/IronCogwheelSymbol.aseprite
new file mode 100644
index 0000000..6e6d411
Binary files /dev/null and b/Assets/Images/Sources/IronCogwheelSymbol.aseprite differ
diff --git a/Assets/Images/Sources/IronRobotSymbol.aseprite b/Assets/Images/Sources/IronRobotSymbol.aseprite
new file mode 100644
index 0000000..7922aa3
Binary files /dev/null and b/Assets/Images/Sources/IronRobotSymbol.aseprite differ
diff --git a/Assets/Images/Sources/IronSymbol.aseprite b/Assets/Images/Sources/IronSymbol.aseprite
new file mode 100644
index 0000000..1177ad3
Binary files /dev/null and b/Assets/Images/Sources/IronSymbol.aseprite differ
diff --git a/Assets/Images/Sources/IronageSymbol.aseprite b/Assets/Images/Sources/IronageSymbol.aseprite
new file mode 100644
index 0000000..0b249c4
Binary files /dev/null and b/Assets/Images/Sources/IronageSymbol.aseprite differ
diff --git a/Assets/Images/Sources/IronplateSymbol.aseprite b/Assets/Images/Sources/IronplateSymbol.aseprite
new file mode 100644
index 0000000..7685d4c
Binary files /dev/null and b/Assets/Images/Sources/IronplateSymbol.aseprite differ
diff --git a/Assets/Images/Sources/IronrodSymbol.aseprite b/Assets/Images/Sources/IronrodSymbol.aseprite
new file mode 100644
index 0000000..37720f5
Binary files /dev/null and b/Assets/Images/Sources/IronrodSymbol.aseprite differ
diff --git a/Assets/Images/Sources/MapSymbol.aseprite b/Assets/Images/Sources/MapSymbol.aseprite
new file mode 100644
index 0000000..0e3e520
Binary files /dev/null and b/Assets/Images/Sources/MapSymbol.aseprite differ
diff --git a/Assets/Images/Sources/MushroomSymbol.aseprite b/Assets/Images/Sources/MushroomSymbol.aseprite
new file mode 100644
index 0000000..69142f8
Binary files /dev/null and b/Assets/Images/Sources/MushroomSymbol.aseprite differ
diff --git a/Assets/Images/Sources/OptionsSymbol.aseprite b/Assets/Images/Sources/OptionsSymbol.aseprite
new file mode 100644
index 0000000..893cbe7
Binary files /dev/null and b/Assets/Images/Sources/OptionsSymbol.aseprite differ
diff --git a/Assets/Images/Sources/ResearchSymbol.aseprite b/Assets/Images/Sources/ResearchSymbol.aseprite
new file mode 100644
index 0000000..87e39c3
Binary files /dev/null and b/Assets/Images/Sources/ResearchSymbol.aseprite differ
diff --git a/Assets/Images/Sources/RobotSymbol.aseprite b/Assets/Images/Sources/RobotSymbol.aseprite
new file mode 100644
index 0000000..fdc965e
Binary files /dev/null and b/Assets/Images/Sources/RobotSymbol.aseprite differ
diff --git a/Assets/Images/Sources/RopeSymbol.aseprite b/Assets/Images/Sources/RopeSymbol.aseprite
new file mode 100644
index 0000000..adad574
Binary files /dev/null and b/Assets/Images/Sources/RopeSymbol.aseprite differ
diff --git a/Assets/Images/Sources/SandSymbol.aseprite b/Assets/Images/Sources/SandSymbol.aseprite
new file mode 100644
index 0000000..a3a0162
Binary files /dev/null and b/Assets/Images/Sources/SandSymbol.aseprite differ
diff --git a/Assets/Images/Sources/SpiderwebSymbol.aseprite b/Assets/Images/Sources/SpiderwebSymbol.aseprite
new file mode 100644
index 0000000..6931c6f
Binary files /dev/null and b/Assets/Images/Sources/SpiderwebSymbol.aseprite differ
diff --git a/Assets/Images/Sources/SteamSymbol.aseprite b/Assets/Images/Sources/SteamSymbol.aseprite
new file mode 100644
index 0000000..940ee2f
Binary files /dev/null and b/Assets/Images/Sources/SteamSymbol.aseprite differ
diff --git a/Assets/Images/Sources/StoneCogwheelSymbol.aseprite b/Assets/Images/Sources/StoneCogwheelSymbol.aseprite
new file mode 100644
index 0000000..24c4e00
Binary files /dev/null and b/Assets/Images/Sources/StoneCogwheelSymbol.aseprite differ
diff --git a/Assets/Images/Sources/StoneRobotSymbol.aseprite b/Assets/Images/Sources/StoneRobotSymbol.aseprite
new file mode 100644
index 0000000..d7c13b2
Binary files /dev/null and b/Assets/Images/Sources/StoneRobotSymbol.aseprite differ
diff --git a/Assets/Images/Sources/StoneSymbol.aseprite b/Assets/Images/Sources/StoneSymbol.aseprite
new file mode 100644
index 0000000..87075e5
Binary files /dev/null and b/Assets/Images/Sources/StoneSymbol.aseprite differ
diff --git a/Assets/Images/Sources/StoneageSymbol.aseprite b/Assets/Images/Sources/StoneageSymbol.aseprite
new file mode 100644
index 0000000..717ead0
Binary files /dev/null and b/Assets/Images/Sources/StoneageSymbol.aseprite differ
diff --git a/Assets/Images/Sources/StoneplateSymbol.aseprite b/Assets/Images/Sources/StoneplateSymbol.aseprite
new file mode 100644
index 0000000..20f3909
Binary files /dev/null and b/Assets/Images/Sources/StoneplateSymbol.aseprite differ
diff --git a/Assets/Images/Sources/TinBarSymbol.aseprite b/Assets/Images/Sources/TinBarSymbol.aseprite
new file mode 100644
index 0000000..15645ab
Binary files /dev/null and b/Assets/Images/Sources/TinBarSymbol.aseprite differ
diff --git a/Assets/Images/Sources/TinCogwheelSymbol.aseprite b/Assets/Images/Sources/TinCogwheelSymbol.aseprite
new file mode 100644
index 0000000..f0371ab
Binary files /dev/null and b/Assets/Images/Sources/TinCogwheelSymbol.aseprite differ
diff --git a/Assets/Images/Sources/TinRobotSymbol.aseprite b/Assets/Images/Sources/TinRobotSymbol.aseprite
new file mode 100644
index 0000000..8780722
Binary files /dev/null and b/Assets/Images/Sources/TinRobotSymbol.aseprite differ
diff --git a/Assets/Images/Sources/TinSymbol.aseprite b/Assets/Images/Sources/TinSymbol.aseprite
new file mode 100644
index 0000000..df74330
Binary files /dev/null and b/Assets/Images/Sources/TinSymbol.aseprite differ
diff --git a/Assets/Images/Sources/TinplateSymbol.aseprite b/Assets/Images/Sources/TinplateSymbol.aseprite
new file mode 100644
index 0000000..56f9060
Binary files /dev/null and b/Assets/Images/Sources/TinplateSymbol.aseprite differ
diff --git a/Assets/Images/Sources/TinrodSymbol.aseprite b/Assets/Images/Sources/TinrodSymbol.aseprite
new file mode 100644
index 0000000..e51d2c3
Binary files /dev/null and b/Assets/Images/Sources/TinrodSymbol.aseprite differ
diff --git a/Assets/Images/Sources/TrashSymbol.aseprite b/Assets/Images/Sources/TrashSymbol.aseprite
new file mode 100644
index 0000000..5057545
Binary files /dev/null and b/Assets/Images/Sources/TrashSymbol.aseprite differ
diff --git a/Assets/Images/Sources/WaterSymbol.aseprite b/Assets/Images/Sources/WaterSymbol.aseprite
new file mode 100644
index 0000000..8f3739c
Binary files /dev/null and b/Assets/Images/Sources/WaterSymbol.aseprite differ
diff --git a/Assets/Images/Sources/arrowDown.aseprite b/Assets/Images/Sources/arrowDown.aseprite
new file mode 100644
index 0000000..b35d029
Binary files /dev/null and b/Assets/Images/Sources/arrowDown.aseprite differ
diff --git a/Assets/Images/Sources/arrowUp.aseprite b/Assets/Images/Sources/arrowUp.aseprite
new file mode 100644
index 0000000..3b2a8d9
Binary files /dev/null and b/Assets/Images/Sources/arrowUp.aseprite differ
diff --git a/Assets/Images/Steam/appSymbol.aseprite b/Assets/Images/Steam/appSymbol.aseprite
new file mode 100644
index 0000000..c12d4b2
Binary files /dev/null and b/Assets/Images/Steam/appSymbol.aseprite differ
diff --git a/Assets/Images/Steam/appSymbol.jpg b/Assets/Images/Steam/appSymbol.jpg
new file mode 100644
index 0000000..8673ff4
Binary files /dev/null and b/Assets/Images/Steam/appSymbol.jpg differ
diff --git a/Assets/Images/Steam/appSymbol.jpg.import b/Assets/Images/Steam/appSymbol.jpg.import
new file mode 100644
index 0000000..b251fcf
--- /dev/null
+++ b/Assets/Images/Steam/appSymbol.jpg.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dxcp2010jb2qi"
+path="res://.godot/imported/appSymbol.jpg-24ba9b726468f38f348e8807b167975f.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/appSymbol.jpg"
+dest_files=["res://.godot/imported/appSymbol.jpg-24ba9b726468f38f348e8807b167975f.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/libraryCapsule.aseprite b/Assets/Images/Steam/libraryCapsule.aseprite
new file mode 100644
index 0000000..af3ea7b
Binary files /dev/null and b/Assets/Images/Steam/libraryCapsule.aseprite differ
diff --git a/Assets/Images/Steam/libraryCapsule.png b/Assets/Images/Steam/libraryCapsule.png
new file mode 100644
index 0000000..9b79380
Binary files /dev/null and b/Assets/Images/Steam/libraryCapsule.png differ
diff --git a/Assets/Images/Steam/libraryCapsule.png.import b/Assets/Images/Steam/libraryCapsule.png.import
new file mode 100644
index 0000000..4a87bfd
--- /dev/null
+++ b/Assets/Images/Steam/libraryCapsule.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c5qpj6hfbom5a"
+path="res://.godot/imported/libraryCapsule.png-4ea02ae904995d6cdc8042c6ecb769c4.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/libraryCapsule.png"
+dest_files=["res://.godot/imported/libraryCapsule.png-4ea02ae904995d6cdc8042c6ecb769c4.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/libraryHeader.aseprite b/Assets/Images/Steam/libraryHeader.aseprite
new file mode 100644
index 0000000..3e2ce0e
Binary files /dev/null and b/Assets/Images/Steam/libraryHeader.aseprite differ
diff --git a/Assets/Images/Steam/libraryHeader.png b/Assets/Images/Steam/libraryHeader.png
new file mode 100644
index 0000000..ed6bbf8
Binary files /dev/null and b/Assets/Images/Steam/libraryHeader.png differ
diff --git a/Assets/Images/Steam/libraryHeader.png.import b/Assets/Images/Steam/libraryHeader.png.import
new file mode 100644
index 0000000..38d94fb
--- /dev/null
+++ b/Assets/Images/Steam/libraryHeader.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dofc461ev1fj0"
+path="res://.godot/imported/libraryHeader.png-200826bd71dca86e8f48425faccb30b2.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/libraryHeader.png"
+dest_files=["res://.godot/imported/libraryHeader.png-200826bd71dca86e8f48425faccb30b2.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/libraryHeroCapsule.aseprite b/Assets/Images/Steam/libraryHeroCapsule.aseprite
new file mode 100644
index 0000000..17129b0
Binary files /dev/null and b/Assets/Images/Steam/libraryHeroCapsule.aseprite differ
diff --git a/Assets/Images/Steam/libraryHeroCapsule.png b/Assets/Images/Steam/libraryHeroCapsule.png
new file mode 100644
index 0000000..09ae57d
Binary files /dev/null and b/Assets/Images/Steam/libraryHeroCapsule.png differ
diff --git a/Assets/Images/Steam/libraryHeroCapsule.png.import b/Assets/Images/Steam/libraryHeroCapsule.png.import
new file mode 100644
index 0000000..96fc85c
--- /dev/null
+++ b/Assets/Images/Steam/libraryHeroCapsule.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bpo24tmqbspnx"
+path="res://.godot/imported/libraryHeroCapsule.png-9e2d7bd620d44d187ec80c7e4924c7dd.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/libraryHeroCapsule.png"
+dest_files=["res://.godot/imported/libraryHeroCapsule.png-9e2d7bd620d44d187ec80c7e4924c7dd.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/libraryLogo.aseprite b/Assets/Images/Steam/libraryLogo.aseprite
new file mode 100644
index 0000000..055dadd
Binary files /dev/null and b/Assets/Images/Steam/libraryLogo.aseprite differ
diff --git a/Assets/Images/Steam/libraryLogo.png b/Assets/Images/Steam/libraryLogo.png
new file mode 100644
index 0000000..bdd0465
Binary files /dev/null and b/Assets/Images/Steam/libraryLogo.png differ
diff --git a/Assets/Images/Steam/libraryLogo.png.import b/Assets/Images/Steam/libraryLogo.png.import
new file mode 100644
index 0000000..88bf4a0
--- /dev/null
+++ b/Assets/Images/Steam/libraryLogo.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://b7ybas47iag78"
+path="res://.godot/imported/libraryLogo.png-7679843b6d3903deb0b458a74e6053ff.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/libraryLogo.png"
+dest_files=["res://.godot/imported/libraryLogo.png-7679843b6d3903deb0b458a74e6053ff.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/mainCapsule.aseprite b/Assets/Images/Steam/mainCapsule.aseprite
new file mode 100644
index 0000000..84fc99c
Binary files /dev/null and b/Assets/Images/Steam/mainCapsule.aseprite differ
diff --git a/Assets/Images/Steam/mainCapsule.png b/Assets/Images/Steam/mainCapsule.png
new file mode 100644
index 0000000..395321b
Binary files /dev/null and b/Assets/Images/Steam/mainCapsule.png differ
diff --git a/Assets/Images/Steam/mainCapsule.png.import b/Assets/Images/Steam/mainCapsule.png.import
new file mode 100644
index 0000000..42e37eb
--- /dev/null
+++ b/Assets/Images/Steam/mainCapsule.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bfvqa558vu1nr"
+path="res://.godot/imported/mainCapsule.png-d42be7e2f09e95405d325dd1ae9c4c89.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/mainCapsule.png"
+dest_files=["res://.godot/imported/mainCapsule.png-d42be7e2f09e95405d325dd1ae9c4c89.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/shortcutIcon.aseprite b/Assets/Images/Steam/shortcutIcon.aseprite
new file mode 100644
index 0000000..fdb3f78
Binary files /dev/null and b/Assets/Images/Steam/shortcutIcon.aseprite differ
diff --git a/Assets/Images/Steam/shortcutIcon.png b/Assets/Images/Steam/shortcutIcon.png
new file mode 100644
index 0000000..3894c2c
Binary files /dev/null and b/Assets/Images/Steam/shortcutIcon.png differ
diff --git a/Assets/Images/Steam/shortcutIcon.png.import b/Assets/Images/Steam/shortcutIcon.png.import
new file mode 100644
index 0000000..4a137b2
--- /dev/null
+++ b/Assets/Images/Steam/shortcutIcon.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c61beck318fgo"
+path="res://.godot/imported/shortcutIcon.png-e3ab8b8cc3aba6fa0a0e76331988db8e.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/shortcutIcon.png"
+dest_files=["res://.godot/imported/shortcutIcon.png-e3ab8b8cc3aba6fa0a0e76331988db8e.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/smallCapsule.aseprite b/Assets/Images/Steam/smallCapsule.aseprite
new file mode 100644
index 0000000..fe6bf39
Binary files /dev/null and b/Assets/Images/Steam/smallCapsule.aseprite differ
diff --git a/Assets/Images/Steam/smallCapsule.png b/Assets/Images/Steam/smallCapsule.png
new file mode 100644
index 0000000..b892bbd
Binary files /dev/null and b/Assets/Images/Steam/smallCapsule.png differ
diff --git a/Assets/Images/Steam/smallCapsule.png.import b/Assets/Images/Steam/smallCapsule.png.import
new file mode 100644
index 0000000..8e29e79
--- /dev/null
+++ b/Assets/Images/Steam/smallCapsule.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://crvwg2ohg2snh"
+path="res://.godot/imported/smallCapsule.png-1cf8de95032e1f24fbe8100a92e462e6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/smallCapsule.png"
+dest_files=["res://.godot/imported/smallCapsule.png-1cf8de95032e1f24fbe8100a92e462e6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/titleCapsule.aseprite b/Assets/Images/Steam/titleCapsule.aseprite
new file mode 100644
index 0000000..3e2ce0e
Binary files /dev/null and b/Assets/Images/Steam/titleCapsule.aseprite differ
diff --git a/Assets/Images/Steam/titleCapsule.png b/Assets/Images/Steam/titleCapsule.png
new file mode 100644
index 0000000..ed6bbf8
Binary files /dev/null and b/Assets/Images/Steam/titleCapsule.png differ
diff --git a/Assets/Images/Steam/titleCapsule.png.import b/Assets/Images/Steam/titleCapsule.png.import
new file mode 100644
index 0000000..34a4d92
--- /dev/null
+++ b/Assets/Images/Steam/titleCapsule.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://berb6bhk6a665"
+path="res://.godot/imported/titleCapsule.png-2ef09d7b54cddebcfd44cb73ceec5e8b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/titleCapsule.png"
+dest_files=["res://.godot/imported/titleCapsule.png-2ef09d7b54cddebcfd44cb73ceec5e8b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/Steam/verticalCapsule.aseprite b/Assets/Images/Steam/verticalCapsule.aseprite
new file mode 100644
index 0000000..1704c3f
Binary files /dev/null and b/Assets/Images/Steam/verticalCapsule.aseprite differ
diff --git a/Assets/Images/Steam/verticalCapsule.png b/Assets/Images/Steam/verticalCapsule.png
new file mode 100644
index 0000000..14db054
Binary files /dev/null and b/Assets/Images/Steam/verticalCapsule.png differ
diff --git a/Assets/Images/Steam/verticalCapsule.png.import b/Assets/Images/Steam/verticalCapsule.png.import
new file mode 100644
index 0000000..dd5e3f4
--- /dev/null
+++ b/Assets/Images/Steam/verticalCapsule.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dh3s2aw53aypr"
+path="res://.godot/imported/verticalCapsule.png-4e31f754d728f6d4bc8b7adaed4b35a1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/Steam/verticalCapsule.png"
+dest_files=["res://.godot/imported/verticalCapsule.png-4e31f754d728f6d4bc8b7adaed4b35a1.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Images/TrashSymbol.png b/Assets/Images/TrashSymbol.png
new file mode 100644
index 0000000..b3163e2
Binary files /dev/null and b/Assets/Images/TrashSymbol.png differ
diff --git a/Assets/Images/TrashSymbol.png.import b/Assets/Images/TrashSymbol.png.import
new file mode 100644
index 0000000..74c8606
--- /dev/null
+++ b/Assets/Images/TrashSymbol.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://wq8yc0u0ee33"
+path="res://.godot/imported/TrashSymbol.png-3f91ec9cd1f5cf96bc959424d5164ff1.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Images/TrashSymbol.png"
+dest_files=["res://.godot/imported/TrashSymbol.png-3f91ec9cd1f5cf96bc959424d5164ff1.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Movies/CaveBasic.png b/Assets/Movies/CaveBasic.png
new file mode 100644
index 0000000..03c15a2
Binary files /dev/null and b/Assets/Movies/CaveBasic.png differ
diff --git a/Assets/Movies/CaveBasic.png.import b/Assets/Movies/CaveBasic.png.import
new file mode 100644
index 0000000..af820a5
--- /dev/null
+++ b/Assets/Movies/CaveBasic.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://tp3w3lkitu85"
+path="res://.godot/imported/CaveBasic.png-b4270e01b12867444794be7b1160e68a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Movies/CaveBasic.png"
+dest_files=["res://.godot/imported/CaveBasic.png-b4270e01b12867444794be7b1160e68a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Movies/CaveInside.png b/Assets/Movies/CaveInside.png
new file mode 100644
index 0000000..b1e175f
Binary files /dev/null and b/Assets/Movies/CaveInside.png differ
diff --git a/Assets/Movies/CaveInside.png.import b/Assets/Movies/CaveInside.png.import
new file mode 100644
index 0000000..d65b909
--- /dev/null
+++ b/Assets/Movies/CaveInside.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://21wyfeqhci52"
+path="res://.godot/imported/CaveInside.png-159958942b70ff4b964f93154095ae6a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Movies/CaveInside.png"
+dest_files=["res://.godot/imported/CaveInside.png-159958942b70ff4b964f93154095ae6a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Movies/RoomBasic.png b/Assets/Movies/RoomBasic.png
new file mode 100644
index 0000000..f568850
Binary files /dev/null and b/Assets/Movies/RoomBasic.png differ
diff --git a/Assets/Movies/RoomBasic.png.import b/Assets/Movies/RoomBasic.png.import
new file mode 100644
index 0000000..781bb6a
--- /dev/null
+++ b/Assets/Movies/RoomBasic.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dh24miynbbdvy"
+path="res://.godot/imported/RoomBasic.png-5655f57e4e83fe25e11e705876b1dad5.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Assets/Movies/RoomBasic.png"
+dest_files=["res://.godot/imported/RoomBasic.png-5655f57e4e83fe25e11e705876b1dad5.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/Assets/Movies/Sources/CaveBasic.aseprite b/Assets/Movies/Sources/CaveBasic.aseprite
new file mode 100644
index 0000000..8c1b3cf
Binary files /dev/null and b/Assets/Movies/Sources/CaveBasic.aseprite differ
diff --git a/Assets/Movies/Sources/CaveInside.aseprite b/Assets/Movies/Sources/CaveInside.aseprite
new file mode 100644
index 0000000..b653d4f
Binary files /dev/null and b/Assets/Movies/Sources/CaveInside.aseprite differ
diff --git a/Assets/Movies/Sources/RoomBasic.aseprite b/Assets/Movies/Sources/RoomBasic.aseprite
new file mode 100644
index 0000000..b725c96
Binary files /dev/null and b/Assets/Movies/Sources/RoomBasic.aseprite differ
diff --git a/Assets/Movies/Sources/trailer.kdenlive b/Assets/Movies/Sources/trailer.kdenlive
new file mode 100644
index 0000000..102b48a
--- /dev/null
+++ b/Assets/Movies/Sources/trailer.kdenlive
@@ -0,0 +1,2066 @@
+
+
+
+
+ 23781
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-10 12-40-11.mkv
+ avformat-novalidate
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:06:36.350000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:06:36.331000000
+ Lavf61.7.100
+ 1
+ 1
+ 1
+ 3
+ 1
+ 0
+ 0
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ 0
+ 0
+ 1
+ -1
+ 4
+ {1e581ca1-97fb-437d-88aa-86cdbe94509c}
+ 0
+ 0
+ 280724308
+ c619a750a5525e300231c0deb2d21d22
+ 1
+ 5666 kB/s
+ 121
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Exploration
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="415.086" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1089.78" font="Noto Sans" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Explore the ruin
+with your robots</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 5
+ {f3f2a48b-131f-4be2-9553-3102dadbd61e}
+ 2
+ {f2785256-0170-489b-bd2b-e7804e690fff}
+ cc8416688ccb18f80bcbe75aa4e76506
+ 0
+ 1920
+ 1080
+ 0
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Research
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="178.102" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1563.75" font="Noto Sans" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Work your way through
+the available research</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 6
+ 2
+ 0ed5ce3151476daef9e84dcc0db30a92
+ 2
+ {07e31e8c-e038-4a97-902b-c14411428fac}
+ {0303a984-bf76-4b75-a903-5b225cd478de}
+ 1920
+ 1080
+ 233
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Programming
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="165.633" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1588.69" font="Noto Sans" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Program your robots
+to behave a certain way</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 7
+ 2
+ 9b7c638d6f5964b8b7b468dfd17b9322
+ 0
+ 0
+ {d7302b49-0c66-4fb0-8d64-2d9ae7b469ce}
+ {70f3cf18-2f55-4972-91c3-9f0bbfa340cf}
+ 1920
+ 1080
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Ending
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="145.75" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1628.45" font="Noto Sans" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Unlock the deepest layer
+and return home</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 8
+ 2
+ 9725c7e4d5d3c7d27c1870a14d25ef06
+ 0
+ 21
+ {8e7c656b-854c-417d-b824-dbb3d1884fc5}
+ {1f58679a-dd61-490a-ac74-c6bee54bf552}
+ 1920
+ 1080
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Thank
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="246.875" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1426.2" font="Noto Sans" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Enjoy your time and
+thank you for playing</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 9
+ 2
+ ecd704847c141abfe6d5f2a5ac4913ba
+ 0
+ 0
+ {68c6b0bc-5a30-41ee-b57e-46f27ace614b}
+ {a8b5c066-2bdb-49b5-bddd-82a0521da051}
+ 1920
+ 1080
+
+
+ 2147483647
+ continue
+ black
+ 1
+ color
+ black_track
+ rgba
+ 0
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 75
+ 1
+ 0
+
+
+
+
+
+ 75
+ 20dB
+ -1
+ volume
+ 237
+ 1
+
+
+ -1
+ panner
+ 237
+ 0.5
+ 1
+
+
+ 0
+ audiolevel
+ 1
+ 1
+
+
+
+ 23781
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-10 12-40-11.mkv
+ avformat-novalidate
+ 1
+ 3
+ 1
+ 0
+ 0
+ 0
+ -1
+ 4
+ {1e581ca1-97fb-437d-88aa-86cdbe94509c}
+ 0
+ 0
+ 280724308
+ c619a750a5525e300231c0deb2d21d22
+ 1
+ 5666 kB/s
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:06:36.350000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:06:36.331000000
+ Lavf61.7.100
+ 1
+ 1
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ was here
+ 0
+ 1
+ 0
+ 1
+
+
+ 3191
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-19 19-29-28.mkv
+ avformat-novalidate
+ 1
+ 3
+ 1
+ 0
+ 0
+ 0
+ -1
+ 10
+ {52b1ea81-ced5-4faf-9b3f-c6dc3b12eff5}
+ 0
+ 0
+ 37568002
+ 4342905d87203c892764517bd9c68cc5
+ 1
+ 5651 kB/s
+ 0
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:00:53.150000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:00:53.184000000
+ Lavf61.7.100
+ 1
+ 1
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ was here
+ 0
+ 1
+ 0
+ 1
+
+
+ 1
+
+
+ 4
+
+
+
+ 4
+
+
+
+ 10
+
+
+ 10
+
+
+
+ 1
+
+
+ 1
+ 75
+ 1
+ 0
+
+
+
+
+
+ 75
+ 20dB
+ -1
+ volume
+ 237
+ 1
+
+
+ -1
+ panner
+ 237
+ 0.5
+ 1
+
+
+ 0
+ audiolevel
+ 1
+ 1
+
+
+
+ 23781
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-10 12-40-11.mkv
+ avformat-novalidate
+ 1
+ 3
+ 1
+ 0
+ 0
+ 0
+ -1
+ 4
+ {1e581ca1-97fb-437d-88aa-86cdbe94509c}
+ 0
+ 0
+ 280724308
+ c619a750a5525e300231c0deb2d21d22
+ 1
+ 5666 kB/s
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:06:36.350000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:06:36.331000000
+ Lavf61.7.100
+ 1
+ 1
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ was here
+ 0
+ 1
+ 1
+ 0
+
+
+ 3191
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-19 19-29-28.mkv
+ avformat-novalidate
+ 1
+ 3
+ 1
+ 0
+ 0
+ 0
+ -1
+ 10
+ {52b1ea81-ced5-4faf-9b3f-c6dc3b12eff5}
+ 0
+ 0
+ 37568002
+ 4342905d87203c892764517bd9c68cc5
+ 1
+ 5651 kB/s
+ 0
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:00:53.150000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:00:53.184000000
+ Lavf61.7.100
+ 1
+ 1
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ was here
+ 0
+ 1
+ 1
+ 0
+
+
+
+ 5
+
+
+ 4
+
+
+ 6
+
+
+ 4
+
+
+ 7
+
+
+ 10
+
+
+ 10
+
+
+ 8
+
+
+ 9
+
+
+
+
+ 75
+ 1
+ 0
+
+
+
+
+
+
+
+
+ 75
+ 1
+ 0
+
+
+
+
+
+
+ 00:01:28.450
+ 5307
+ Sequenz 1
+
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ 17
+ 0
+ 4049
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ 3
+ 0
+ b82ff5c5a73e203aecd546afdbbc7420
+ 2
+ 2
+ 1
+ 0
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ 1
+ 1
+ 3323
+ 0
+ 4
+ 4
+ 1
+ 2
+ 0
+ 75
+ 9
+ [
+ {
+ "children": [
+ {
+ "data": "1:300:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "2:300:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "data": "1:1202:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "2:1202:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "data": "1:2351:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "2:2351:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "data": "1:3517:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "2:3517:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ }
+]
+
+ [
+]
+
+
+
+
+
+
+
+ 0
+ 1
+ mix
+ mix
+ 237
+ 1
+ 1
+ 1
+
+
+ 0
+ 2
+ mix
+ mix
+ 237
+ 1
+ 1
+ 1
+
+
+ 0
+ 3
+ 0
+ 0
+ 0
+ qtblend
+ qtblend
+ 237
+ 1
+
+
+ 0
+ 4
+ 0
+ 0
+ 0
+ qtblend
+ qtblend
+ 237
+ 1
+
+
+ 75
+ 20dB
+ -1
+ volume
+ 237
+ 1
+
+
+ -1
+ panner
+ 237
+ 0.5
+ 1
+
+
+
+ 3191
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-19 19-29-28.mkv
+ avformat-novalidate
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:00:53.150000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:00:53.184000000
+ Lavf61.7.100
+ 1
+ 1
+ 1
+ 3
+ 1
+ 0
+ 0
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ 0
+ 0
+ 1
+ -1
+ 10
+ {52b1ea81-ced5-4faf-9b3f-c6dc3b12eff5}
+ 0
+ 0
+ 37568002
+ 4342905d87203c892764517bd9c68cc5
+ 1
+ 5651 kB/s
+ 0
+
+
+ Sequenzen
+ 2
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ 2
+ 100
+ /media/nicola/M2SSD/StreamsToEdit/
+ 1778410037210
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ [
+ {
+ "color": "#9b59b6",
+ "comment": "Kategorie 1",
+ "index": 0
+ },
+ {
+ "color": "#3daee9",
+ "comment": "Kategorie 2",
+ "index": 1
+ },
+ {
+ "color": "#1abc9c",
+ "comment": "Kategorie 3",
+ "index": 2
+ },
+ {
+ "color": "#1cdc9a",
+ "comment": "Kategorie 4",
+ "index": 3
+ },
+ {
+ "color": "#c9ce3b",
+ "comment": "Kategorie 5",
+ "index": 4
+ },
+ {
+ "color": "#fdbc4b",
+ "comment": "Kategorie 6",
+ "index": 5
+ },
+ {
+ "color": "#f39c1f",
+ "comment": "Kategorie 7",
+ "index": 6
+ },
+ {
+ "color": "#f47750",
+ "comment": "Kategorie 8",
+ "index": 7
+ },
+ {
+ "color": "#da4453",
+ "comment": "Kategorie 9",
+ "index": 8
+ }
+]
+
+ 25.12.0
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+
+
+ atsc_1080p_60
+
+ 2000
+ 800
+ 1000
+
+ 640
+ Ultra-High Definition (4K)
+ -1
+ -1
+ 0
+ 0
+ 0
+ 0
+ 0
+ MP4-H265 (HEVC)
+ 0
+ 540
+ 960
+ 6
+ -1
+ 0
+ 0
+ -1
+ 0
+ /home/nicola/Videos/trailer.mp4
+ 30000
+ {4d4f0352-3b23-4657-91e5-f54dfca29c48}
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ 1.1
+
+ 4
+ project_bin:-1:0
+
+ 2
+ {
+ "allDockWidgets": [
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 11,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": -1,
+ "wasFloating": false
+ },
+ "uniqueName": "timeline"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "project_bin"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 3,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 3,
+ "wasFloating": false
+ },
+ "uniqueName": "library"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "subtitles"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 4,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "textedit"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "timeremap"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "markers"
+ },
+ {
+ "lastCloseReason": 1,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 0,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "screengrab"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 1,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "audiospectrum"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 3,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "clipmonitor"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 4,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 1,
+ "wasFloating": false
+ },
+ "uniqueName": "projectmonitor"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 1,
+ "wasFloating": false
+ },
+ "uniqueName": "clip_properties"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 4,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 4,
+ "wasFloating": false
+ },
+ "uniqueName": "notes_widget"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 9,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "media_browser"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 10,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "onlineresources"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 1,
+ "wasFloating": false
+ },
+ "uniqueName": "effect_stack"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": -1,
+ "wasFloating": false
+ },
+ "uniqueName": "effect_list"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": -1,
+ "wasFloating": false
+ },
+ "uniqueName": "transition_list"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 620,
+ "width": 679,
+ "x": 13,
+ "y": 68
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "undo_history"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "mixer"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 6,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "vectorscope"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 5,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "waveform"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 7,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "rgb_parade"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 8,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "histogram"
+ }
+ ],
+ "closedDockWidgets": [
+ "library",
+ "subtitles",
+ "textedit",
+ "timeremap",
+ "markers",
+ "screengrab",
+ "audiospectrum",
+ "clip_properties",
+ "notes_widget",
+ "media_browser",
+ "onlineresources",
+ "effect_list",
+ "transition_list",
+ "undo_history",
+ "mixer",
+ "vectorscope",
+ "waveform",
+ "rgb_parade",
+ "histogram"
+ ],
+ "floatingWindows": [],
+ "mainWindows": [
+ {
+ "affinities": null,
+ "geometry": {
+ "height": 1052,
+ "width": 1920,
+ "x": 1920,
+ "y": 28
+ },
+ "isVisible": true,
+ "multiSplitterLayout": {
+ "frames": {
+ "7605": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "timeline"
+ ],
+ "geometry": {
+ "height": 573,
+ "width": 1527,
+ "x": 0,
+ "y": 411
+ },
+ "id": "7605",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "timeline",
+ "options": 0
+ },
+ "7638": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "clipmonitor",
+ "library"
+ ],
+ "geometry": {
+ "height": 411,
+ "width": 661,
+ "x": 595,
+ "y": 0
+ },
+ "id": "7638",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "clipmonitor",
+ "options": 0
+ },
+ "7702": {
+ "currentTabIndex": 1,
+ "dockWidgets": [
+ "mixer",
+ "effect_stack",
+ "timeremap",
+ "subtitles"
+ ],
+ "geometry": {
+ "height": 573,
+ "width": 391,
+ "x": 1527,
+ "y": 411
+ },
+ "id": "7702",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "effect_stack",
+ "options": 0
+ },
+ "7840": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "project_bin",
+ "transition_list",
+ "effect_list",
+ "clip_properties",
+ "undo_history"
+ ],
+ "geometry": {
+ "height": 411,
+ "width": 595,
+ "x": 0,
+ "y": 0
+ },
+ "id": "7840",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "project_bin",
+ "options": 0
+ },
+ "8021": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "projectmonitor",
+ "textedit",
+ "notes_widget"
+ ],
+ "geometry": {
+ "height": 411,
+ "width": 662,
+ "x": 1256,
+ "y": 0
+ },
+ "id": "8021",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "projectmonitor",
+ "options": 0
+ }
+ },
+ "layout": {
+ "children": [
+ {
+ "children": [
+ {
+ "children": [
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 95,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524279
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 103,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524265,
+ "width": 524270
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "guestId": "7840",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 595,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524293
+ },
+ "minSize": {
+ "height": 274,
+ "width": 257
+ },
+ "percentageWithinParent": 0.3102189781021898
+ }
+ },
+ {
+ "guestId": "7638",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 661,
+ "x": 595,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524293
+ },
+ "minSize": {
+ "height": 236,
+ "width": 326
+ },
+ "percentageWithinParent": 0.34462982273201254
+ }
+ },
+ {
+ "guestId": "8021",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 662,
+ "x": 1256,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524293
+ },
+ "minSize": {
+ "height": 353,
+ "width": 503
+ },
+ "percentageWithinParent": 0.34515119916579773
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 82,
+ "x": 1832,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 167,
+ "x": 1747,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524251
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 92,
+ "x": 1822,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 81,
+ "x": 1833,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 508,
+ "width": 478,
+ "x": 1074,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 523905,
+ "width": 523960
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 508,
+ "width": 378,
+ "x": 1536,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524174,
+ "width": 524011
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 1,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 1918,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 1572879
+ },
+ "minSize": {
+ "height": 353,
+ "width": 1086
+ },
+ "percentageWithinParent": 0.4176829268292683
+ }
+ },
+ {
+ "children": [
+ {
+ "guestId": "7605",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 573,
+ "width": 1527,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524314,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 117,
+ "width": 80
+ },
+ "percentageWithinParent": 0.7961418143899895
+ }
+ },
+ {
+ "guestId": "7702",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 573,
+ "width": 391,
+ "x": 1527,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524344
+ },
+ "minSize": {
+ "height": 141,
+ "width": 141
+ },
+ "percentageWithinParent": 0.20385818561001043
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 1,
+ "sizingInfo": {
+ "geometry": {
+ "height": 573,
+ "width": 1918,
+ "x": 0,
+ "y": 411
+ },
+ "maxSizeHint": {
+ "height": 524314,
+ "width": 1048631
+ },
+ "minSize": {
+ "height": 141,
+ "width": 221
+ },
+ "percentageWithinParent": 0.5823170731707317
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 2,
+ "sizingInfo": {
+ "geometry": {
+ "height": 984,
+ "width": 1918,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 1048637,
+ "width": 1048631
+ },
+ "minSize": {
+ "height": 494,
+ "width": 1086
+ },
+ "percentageWithinParent": 1.0
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 1,
+ "sizingInfo": {
+ "geometry": {
+ "height": 984,
+ "width": 1918,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 16777215,
+ "width": 16777215
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ }
+ },
+ "normalGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "options": 0,
+ "screenIndex": 0,
+ "screenSize": {
+ "height": 1080,
+ "width": 1920
+ },
+ "uniqueName": "KdenliveKDDock",
+ "windowState": 2
+ }
+ ],
+ "screenInfo": [
+ {
+ "devicePixelRatio": 1.0,
+ "geometry": {
+ "height": 1080,
+ "width": 1920,
+ "x": 1920,
+ "y": 0
+ },
+ "index": 0,
+ "name": "DP-4"
+ },
+ {
+ "devicePixelRatio": 1.0,
+ "geometry": {
+ "height": 1080,
+ "width": 1920,
+ "x": 3840,
+ "y": 0
+ },
+ "index": 1,
+ "name": "DP-2"
+ },
+ {
+ "devicePixelRatio": 1.0,
+ "geometry": {
+ "height": 1080,
+ "width": 1920,
+ "x": 0,
+ "y": 0
+ },
+ "index": 2,
+ "name": "DP-0"
+ }
+ ],
+ "serializationVersion": 3
+}
+ 1
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
diff --git a/Assets/Movies/Sources/trailer_backup0.kdenlive b/Assets/Movies/Sources/trailer_backup0.kdenlive
new file mode 100644
index 0000000..01814d4
--- /dev/null
+++ b/Assets/Movies/Sources/trailer_backup0.kdenlive
@@ -0,0 +1,1923 @@
+
+
+
+
+ 2147483647
+ continue
+ black
+ 1
+ color
+ black_track
+ rgba
+ 0
+
+
+ 1
+
+
+ 1
+
+
+ 1
+ 75
+ 1
+ 0
+
+
+
+
+
+ 75
+ 20dB
+ -1
+ volume
+ 237
+ 1
+
+
+ -1
+ panner
+ 237
+ 0.5
+ 1
+
+
+ 0
+ audiolevel
+ 1
+ 1
+
+
+
+ 23781
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-10 12-40-11.mkv
+ avformat-novalidate
+ 1
+ 3
+ 1
+ 0
+ 0
+ 0
+ -1
+ 4
+ {1e581ca1-97fb-437d-88aa-86cdbe94509c}
+ 0
+ 0
+ 280724308
+ c619a750a5525e300231c0deb2d21d22
+ 1
+ 5666 kB/s
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:06:36.350000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:06:36.331000000
+ Lavf61.7.100
+ 1
+ 1
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ was here
+ 0
+ 1
+ 0
+ 1
+
+
+ 1
+
+
+ 4
+
+
+
+ 4
+
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
+
+ 1
+
+
+ 1
+ 75
+ 1
+ 0
+
+
+
+
+
+ 75
+ 20dB
+ -1
+ volume
+ 237
+ 1
+
+
+ -1
+ panner
+ 237
+ 0.5
+ 1
+
+
+ 0
+ audiolevel
+ 1
+ 1
+
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Exploration
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="415.086" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1089.78" font="Sans Serif" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Explore the ruin
+with your robots</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 5
+ {f3f2a48b-131f-4be2-9553-3102dadbd61e}
+ 2
+ {f2785256-0170-489b-bd2b-e7804e690fff}
+ cc8416688ccb18f80bcbe75aa4e76506
+ 0
+ 1920
+ 1080
+ 0
+
+
+ 23781
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-10 12-40-11.mkv
+ avformat-novalidate
+ 1
+ 3
+ 1
+ 0
+ 0
+ 0
+ -1
+ 4
+ {1e581ca1-97fb-437d-88aa-86cdbe94509c}
+ 0
+ 0
+ 280724308
+ c619a750a5525e300231c0deb2d21d22
+ 1
+ 5666 kB/s
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:06:36.350000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:06:36.331000000
+ Lavf61.7.100
+ 1
+ 1
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ was here
+ 0
+ 1
+ 1
+ 0
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Research
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="178.102" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1563.75" font="Sans Serif" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Work your way through
+the available research</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 6
+ 2
+ 0ed5ce3151476daef9e84dcc0db30a92
+ 0
+ {07e31e8c-e038-4a97-902b-c14411428fac}
+ {0303a984-bf76-4b75-a903-5b225cd478de}
+ 1920
+ 1080
+ 233
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Programming
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="165.633" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1588.69" font="Sans Serif" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Program your robots
+to behave a certain way</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 7
+ 2
+ 9b7c638d6f5964b8b7b468dfd17b9322
+ 0
+ 0
+ {d7302b49-0c66-4fb0-8d64-2d9ae7b469ce}
+ {70f3cf18-2f55-4972-91c3-9f0bbfa340cf}
+ 1920
+ 1080
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Ending
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="145.75" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1628.45" font="Sans Serif" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Unlock the deepest layer
+and return home</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 8
+ 2
+ 9725c7e4d5d3c7d27c1870a14d25ef06
+ 0
+ 0
+ {8e7c656b-854c-417d-b824-dbb3d1884fc5}
+ {1f58679a-dd61-490a-ac74-c6bee54bf552}
+ 1920
+ 1080
+
+
+ 300
+ pause
+
+ 1
+ 1
+ 1
+ kdenlivetitle
+ 00:00:05:00
+ Thank
+ <kdenlivetitle LC_NUMERIC="C" duration="300" height="1080" out="299" width="1920">
+ <item type="QGraphicsTextItem" z-index="0">
+ <position x="246.875" y="349">
+ <transform>1,0,0,0,1,0,0,0,1</transform>
+ </position>
+ <content alignment="4" box-height="408" box-width="1426.2" font="Sans Serif" font-color="195,195,195,255" font-italic="1" font-pixel-size="150" font-underline="0" font-weight="400" letter-spacing="0" line-spacing="0" shadow="0;#64000000;3;3;3" tab-width="80" typewriter="0;13;2;5;13">Enjoy your time and
+thank you for playing</content>
+ </item>
+ <startviewport rect="0,0,1920,1080"/>
+ <endviewport rect="0,0,1920,1080"/>
+ <background color="0,0,0,255"/>
+</kdenlivetitle>
+
+ was here
+ -1
+ 9
+ 2
+ ecd704847c141abfe6d5f2a5ac4913ba
+ 0
+ 0
+ {68c6b0bc-5a30-41ee-b57e-46f27ace614b}
+ {a8b5c066-2bdb-49b5-bddd-82a0521da051}
+ 1920
+ 1080
+
+
+
+ 5
+
+
+ 4
+
+
+ 6
+
+
+ 4
+
+
+ 7
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
+ 4
+
+
+ 8
+
+
+ 9
+
+
+
+
+ 75
+ 1
+ 0
+
+
+
+
+
+
+
+
+ 75
+ 1
+ 0
+
+
+
+
+
+
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ Sequenz 1
+ 1
+ 1
+ 2
+ 4
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ 00:01:32.500
+ 5550
+ 17
+ 3
+ 0
+ 0
+ 2
+ 1
+ 0
+ 0
+ 0
+ 4
+ 1
+ 2
+ 0
+ 75
+ 9
+ [
+ {
+ "children": [
+ {
+ "data": "1:300:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "2:300:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "data": "1:1202:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "2:1202:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "children": [
+ {
+ "data": "2:4409:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "1:4409:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "data": "2:2351:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "1:2351:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "data": "2:4342:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "1:4342:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ },
+ {
+ "children": [
+ {
+ "data": "1:4124:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ },
+ {
+ "data": "2:4124:-1",
+ "leaf": "clip",
+ "type": "Leaf"
+ }
+ ],
+ "type": "AVSplit"
+ }
+ ],
+ "type": "Normal"
+ }
+]
+
+ [
+]
+
+ 0
+ 4049
+ 0
+
+
+
+
+
+
+ 0
+ 1
+ mix
+ mix
+ 237
+ 1
+ 1
+ 1
+
+
+ 0
+ 2
+ mix
+ mix
+ 237
+ 1
+ 1
+ 1
+
+
+ 0
+ 3
+ 0
+ 0
+ 0
+ qtblend
+ qtblend
+ 237
+ 1
+
+
+ 0
+ 4
+ 0
+ 0
+ 0
+ qtblend
+ qtblend
+ 237
+ 1
+
+
+ 75
+ 20dB
+ -1
+ volume
+ 237
+ 1
+
+
+ -1
+ panner
+ 237
+ 0.5
+ 1
+
+
+
+ 23781
+ pause
+ /media/nicola/M2SSD/StreamsToEdit/2026-05-10 12-40-11.mkv
+ avformat-novalidate
+ 2
+ video
+ 60
+ 0
+ 1920
+ 1080
+ 0
+ yuv420p
+ 1
+ 709
+ 1
+ h264
+ H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
+ 0
+ 00:06:36.350000000
+ audio
+ fltp
+ 48000
+ 2
+ stereo
+ aac
+ AAC (Advanced Audio Coding)
+ 0
+ Track1
+ 00:06:36.331000000
+ Lavf61.7.100
+ 1
+ 1
+ 1
+ 3
+ 1
+ 0
+ 0
+ 60
+ 1
+ 709
+ 1
+ 2
+ 1920
+ 1080
+ 1
+ mpeg
+ 0
+ 0
+ 1
+ -1
+ 4
+ {1e581ca1-97fb-437d-88aa-86cdbe94509c}
+ 0
+ 0
+ 280724308
+ c619a750a5525e300231c0deb2d21d22
+ 1
+ 5666 kB/s
+ 121
+
+
+ Sequenzen
+ 2
+ 2
+ 100
+ /media/nicola/M2SSD/StreamsToEdit/
+ 1778410037210
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ [
+ {
+ "color": "#9b59b6",
+ "comment": "Kategorie 1",
+ "index": 0
+ },
+ {
+ "color": "#3daee9",
+ "comment": "Kategorie 2",
+ "index": 1
+ },
+ {
+ "color": "#1abc9c",
+ "comment": "Kategorie 3",
+ "index": 2
+ },
+ {
+ "color": "#1cdc9a",
+ "comment": "Kategorie 4",
+ "index": 3
+ },
+ {
+ "color": "#c9ce3b",
+ "comment": "Kategorie 5",
+ "index": 4
+ },
+ {
+ "color": "#fdbc4b",
+ "comment": "Kategorie 6",
+ "index": 5
+ },
+ {
+ "color": "#f39c1f",
+ "comment": "Kategorie 7",
+ "index": 6
+ },
+ {
+ "color": "#f47750",
+ "comment": "Kategorie 8",
+ "index": 7
+ },
+ {
+ "color": "#da4453",
+ "comment": "Kategorie 9",
+ "index": 8
+ }
+]
+
+ 25.12.0
+
+
+ atsc_1080p_60
+
+ 2000
+ 800
+ 1000
+
+ 640
+ 30000
+ {cbc17960-ea19-41fe-afeb-4fc6411fdc3f}
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ 1.1
+
+ 4
+ project_bin:-1:0
+
+ 2
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ {23f4ff0e-8383-46f2-9c67-97a60704667d}
+ {
+ "allDockWidgets": [
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 11,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": -1,
+ "wasFloating": false
+ },
+ "uniqueName": "timeline"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "project_bin"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 3,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 3,
+ "wasFloating": false
+ },
+ "uniqueName": "library"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "subtitles"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 4,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "textedit"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "timeremap"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "markers"
+ },
+ {
+ "lastCloseReason": 1,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 0,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "screengrab"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 1,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "audiospectrum"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 3,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "clipmonitor"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 4,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 1,
+ "wasFloating": false
+ },
+ "uniqueName": "projectmonitor"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 1,
+ "wasFloating": false
+ },
+ "uniqueName": "clip_properties"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 4,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 4,
+ "wasFloating": false
+ },
+ "uniqueName": "notes_widget"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 9,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "media_browser"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 10,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "onlineresources"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 1,
+ "wasFloating": false
+ },
+ "uniqueName": "effect_stack"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": -1,
+ "wasFloating": false
+ },
+ "uniqueName": "effect_list"
+ },
+ {
+ "lastCloseReason": 0,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": -1,
+ "wasFloating": false
+ },
+ "uniqueName": "transition_list"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 620,
+ "width": 679,
+ "x": 13,
+ "y": 68
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 2,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "undo_history"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 12,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 2,
+ "wasFloating": false
+ },
+ "uniqueName": "mixer"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 6,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "vectorscope"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 5,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "waveform"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 7,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "rgb_parade"
+ },
+ {
+ "lastCloseReason": 2,
+ "lastPosition": {
+ "lastFloatingGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "lastOverlayedGeometries": [],
+ "placeholders": [
+ {
+ "isFloatingWindow": false,
+ "itemIndex": 8,
+ "mainWindowUniqueName": "KdenliveKDDock"
+ }
+ ],
+ "tabIndex": 0,
+ "wasFloating": false
+ },
+ "uniqueName": "histogram"
+ }
+ ],
+ "closedDockWidgets": [
+ "library",
+ "subtitles",
+ "textedit",
+ "timeremap",
+ "markers",
+ "screengrab",
+ "audiospectrum",
+ "clip_properties",
+ "notes_widget",
+ "media_browser",
+ "onlineresources",
+ "effect_list",
+ "transition_list",
+ "undo_history",
+ "mixer",
+ "vectorscope",
+ "waveform",
+ "rgb_parade",
+ "histogram"
+ ],
+ "floatingWindows": [],
+ "mainWindows": [
+ {
+ "affinities": null,
+ "geometry": {
+ "height": 1052,
+ "width": 1920,
+ "x": 0,
+ "y": 28
+ },
+ "isVisible": true,
+ "multiSplitterLayout": {
+ "frames": {
+ "58450": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "projectmonitor",
+ "textedit",
+ "notes_widget"
+ ],
+ "geometry": {
+ "height": 411,
+ "width": 662,
+ "x": 1256,
+ "y": 0
+ },
+ "id": "58450",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "projectmonitor",
+ "options": 0
+ },
+ "58548": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "project_bin",
+ "transition_list",
+ "effect_list",
+ "clip_properties",
+ "undo_history"
+ ],
+ "geometry": {
+ "height": 411,
+ "width": 595,
+ "x": 0,
+ "y": 0
+ },
+ "id": "58548",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "project_bin",
+ "options": 0
+ },
+ "58728": {
+ "currentTabIndex": 1,
+ "dockWidgets": [
+ "mixer",
+ "effect_stack",
+ "timeremap",
+ "subtitles"
+ ],
+ "geometry": {
+ "height": 573,
+ "width": 391,
+ "x": 1527,
+ "y": 411
+ },
+ "id": "58728",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "effect_stack",
+ "options": 0
+ },
+ "58865": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "clipmonitor",
+ "library"
+ ],
+ "geometry": {
+ "height": 411,
+ "width": 661,
+ "x": 595,
+ "y": 0
+ },
+ "id": "58865",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "clipmonitor",
+ "options": 0
+ },
+ "58928": {
+ "currentTabIndex": 0,
+ "dockWidgets": [
+ "timeline"
+ ],
+ "geometry": {
+ "height": 573,
+ "width": 1527,
+ "x": 0,
+ "y": 411
+ },
+ "id": "58928",
+ "isNull": false,
+ "mainWindowUniqueName": "KdenliveKDDock",
+ "objectName": "timeline",
+ "options": 0
+ }
+ },
+ "layout": {
+ "children": [
+ {
+ "children": [
+ {
+ "children": [
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 95,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524279
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 103,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524265,
+ "width": 524270
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "guestId": "58548",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 595,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524293
+ },
+ "minSize": {
+ "height": 274,
+ "width": 257
+ },
+ "percentageWithinParent": 0.3103448275862069
+ }
+ },
+ {
+ "guestId": "58865",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 661,
+ "x": 595,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524293
+ },
+ "minSize": {
+ "height": 236,
+ "width": 326
+ },
+ "percentageWithinParent": 0.3448275862068966
+ }
+ },
+ {
+ "guestId": "58450",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 662,
+ "x": 1256,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524293
+ },
+ "minSize": {
+ "height": 353,
+ "width": 503
+ },
+ "percentageWithinParent": 0.3448275862068966
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 82,
+ "x": 1832,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 167,
+ "x": 1747,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524251
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 92,
+ "x": 1822,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 440,
+ "width": 81,
+ "x": 1833,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524287,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 508,
+ "width": 478,
+ "x": 1074,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 523905,
+ "width": 523960
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ },
+ {
+ "isContainer": false,
+ "isVisible": false,
+ "sizingInfo": {
+ "geometry": {
+ "height": 508,
+ "width": 378,
+ "x": 1536,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524174,
+ "width": 524011
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 1,
+ "sizingInfo": {
+ "geometry": {
+ "height": 411,
+ "width": 1918,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 1572879
+ },
+ "minSize": {
+ "height": 353,
+ "width": 1086
+ },
+ "percentageWithinParent": 0.4184938036224976
+ }
+ },
+ {
+ "children": [
+ {
+ "guestId": "58928",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 573,
+ "width": 1527,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524314,
+ "width": 524287
+ },
+ "minSize": {
+ "height": 117,
+ "width": 80
+ },
+ "percentageWithinParent": 0.7962382445141066
+ }
+ },
+ {
+ "guestId": "58728",
+ "isContainer": false,
+ "isVisible": true,
+ "sizingInfo": {
+ "geometry": {
+ "height": 573,
+ "width": 391,
+ "x": 1527,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 524323,
+ "width": 524293
+ },
+ "minSize": {
+ "height": 238,
+ "width": 264
+ },
+ "percentageWithinParent": 0.20376175548589343
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 1,
+ "sizingInfo": {
+ "geometry": {
+ "height": 573,
+ "width": 1918,
+ "x": 0,
+ "y": 411
+ },
+ "maxSizeHint": {
+ "height": 524314,
+ "width": 1048580
+ },
+ "minSize": {
+ "height": 238,
+ "width": 344
+ },
+ "percentageWithinParent": 0.5815061963775023
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 2,
+ "sizingInfo": {
+ "geometry": {
+ "height": 984,
+ "width": 1918,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 1048637,
+ "width": 1048580
+ },
+ "minSize": {
+ "height": 591,
+ "width": 1086
+ },
+ "percentageWithinParent": 1.0
+ }
+ }
+ ],
+ "isContainer": true,
+ "isVisible": false,
+ "orientation": 1,
+ "sizingInfo": {
+ "geometry": {
+ "height": 984,
+ "width": 1918,
+ "x": 0,
+ "y": 0
+ },
+ "maxSizeHint": {
+ "height": 16777215,
+ "width": 16777215
+ },
+ "minSize": {
+ "height": 90,
+ "width": 80
+ },
+ "percentageWithinParent": 0.0
+ }
+ }
+ },
+ "normalGeometry": {
+ "height": 0,
+ "width": 0,
+ "x": 0,
+ "y": 0
+ },
+ "options": 0,
+ "screenIndex": 2,
+ "screenSize": {
+ "height": 1080,
+ "width": 1920
+ },
+ "uniqueName": "KdenliveKDDock",
+ "windowState": 2
+ }
+ ],
+ "screenInfo": [
+ {
+ "devicePixelRatio": 1.0,
+ "geometry": {
+ "height": 1080,
+ "width": 1920,
+ "x": 1920,
+ "y": 0
+ },
+ "index": 0,
+ "name": "DP-4"
+ },
+ {
+ "devicePixelRatio": 1.0,
+ "geometry": {
+ "height": 1080,
+ "width": 1920,
+ "x": 3840,
+ "y": 0
+ },
+ "index": 1,
+ "name": "DP-2"
+ },
+ {
+ "devicePixelRatio": 1.0,
+ "geometry": {
+ "height": 1080,
+ "width": 1920,
+ "x": 0,
+ "y": 0
+ },
+ "index": 2,
+ "name": "DP-0"
+ }
+ ],
+ "serializationVersion": 3
+}
+ Ultra-High Definition (4K)
+ -1
+ -1
+ 0
+ 0
+ 0
+ 0
+ 0
+ MP4-H265 (HEVC)
+ 0
+ 540
+ 960
+ 6
+ -1
+ 0
+ 0
+ -1
+ 0
+ /home/nicola/Videos/trailer.mp4
+ 1
+
+
+
+
+
+
+
+
+
+ 1
+
+
+
diff --git a/Assets/Objects/Decorations.glb b/Assets/Objects/Decorations.glb
new file mode 100644
index 0000000..01c0668
Binary files /dev/null and b/Assets/Objects/Decorations.glb differ
diff --git a/Assets/Objects/Decorations.glb.import b/Assets/Objects/Decorations.glb.import
new file mode 100644
index 0000000..d9ea895
--- /dev/null
+++ b/Assets/Objects/Decorations.glb.import
@@ -0,0 +1,42 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://dk7gbkgg8vegy"
+path="res://.godot/imported/Decorations.glb-a1a30e084725e695f1c19d3c9fcf7bdf.scn"
+
+[deps]
+
+source_file="res://Assets/Objects/Decorations.glb"
+dest_files=["res://.godot/imported/Decorations.glb-a1a30e084725e695f1c19d3c9fcf7bdf.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+gltf/naming_version=2
+gltf/embedded_image_handling=1
diff --git a/Assets/Objects/Robot.glb b/Assets/Objects/Robot.glb
new file mode 100644
index 0000000..c839f86
Binary files /dev/null and b/Assets/Objects/Robot.glb differ
diff --git a/Assets/Objects/Robot.glb.import b/Assets/Objects/Robot.glb.import
new file mode 100644
index 0000000..ff18b2b
--- /dev/null
+++ b/Assets/Objects/Robot.glb.import
@@ -0,0 +1,42 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://bccs6xik7xv8s"
+path="res://.godot/imported/Robot.glb-0808b8534e7a8abc9f0bb2bb7f5abb78.scn"
+
+[deps]
+
+source_file="res://Assets/Objects/Robot.glb"
+dest_files=["res://.godot/imported/Robot.glb-0808b8534e7a8abc9f0bb2bb7f5abb78.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+gltf/naming_version=2
+gltf/embedded_image_handling=1
diff --git a/Assets/Objects/Tiles.glb b/Assets/Objects/Tiles.glb
new file mode 100644
index 0000000..995c141
Binary files /dev/null and b/Assets/Objects/Tiles.glb differ
diff --git a/Assets/Objects/Tiles.glb.import b/Assets/Objects/Tiles.glb.import
new file mode 100644
index 0000000..0dca587
--- /dev/null
+++ b/Assets/Objects/Tiles.glb.import
@@ -0,0 +1,42 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://cx234c37arp4m"
+path="res://.godot/imported/Tiles.glb-f224ed79b5f550e471e6ad3c274ece26.scn"
+
+[deps]
+
+source_file="res://Assets/Objects/Tiles.glb"
+dest_files=["res://.godot/imported/Tiles.glb-f224ed79b5f550e471e6ad3c274ece26.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=false
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+gltf/naming_version=2
+gltf/embedded_image_handling=1
diff --git a/Assets/Objects/TilesNew.fbx b/Assets/Objects/TilesNew.fbx
new file mode 100644
index 0000000..0c3d8ab
Binary files /dev/null and b/Assets/Objects/TilesNew.fbx differ
diff --git a/Assets/Objects/TilesNew.fbx.import b/Assets/Objects/TilesNew.fbx.import
new file mode 100644
index 0000000..8aae737
--- /dev/null
+++ b/Assets/Objects/TilesNew.fbx.import
@@ -0,0 +1,44 @@
+[remap]
+
+importer="scene"
+importer_version=1
+type="PackedScene"
+uid="uid://ccdohpxvolyvj"
+path="res://.godot/imported/TilesNew.fbx-3e21a3fed203e7e5d17b1879a769f903.scn"
+
+[deps]
+
+source_file="res://Assets/Objects/TilesNew.fbx"
+dest_files=["res://.godot/imported/TilesNew.fbx-3e21a3fed203e7e5d17b1879a769f903.scn"]
+
+[params]
+
+nodes/root_type=""
+nodes/root_name=""
+nodes/root_script=null
+nodes/apply_root_scale=true
+nodes/root_scale=1.0
+nodes/import_as_skeleton_bones=false
+nodes/use_name_suffixes=true
+nodes/use_node_type_suffixes=true
+meshes/ensure_tangents=true
+meshes/generate_lods=true
+meshes/create_shadow_meshes=true
+meshes/light_baking=1
+meshes/lightmap_texel_size=0.2
+meshes/force_disable_compression=false
+skins/use_named_skins=true
+animation/import=true
+animation/fps=30
+animation/trimming=true
+animation/remove_immutable_tracks=true
+animation/import_rest_as_RESET=false
+import_script/path=""
+materials/extract=0
+materials/extract_format=0
+materials/extract_path=""
+_subresources={}
+fbx/importer=0
+fbx/allow_geometry_helper_nodes=false
+fbx/embedded_image_handling=1
+fbx/naming_version=2
diff --git a/Assets/Objects/Tiles_brick_texture.png b/Assets/Objects/Tiles_brick_texture.png
new file mode 100644
index 0000000..3c4d368
Binary files /dev/null and b/Assets/Objects/Tiles_brick_texture.png differ
diff --git a/Assets/Objects/Tiles_brick_texture.png.import b/Assets/Objects/Tiles_brick_texture.png.import
new file mode 100644
index 0000000..19dedf2
--- /dev/null
+++ b/Assets/Objects/Tiles_brick_texture.png.import
@@ -0,0 +1,44 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dhsp6fp6gldl6"
+path.s3tc="res://.godot/imported/Tiles_brick_texture.png-92562a88e9bc574c9917403a76adebcc.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+generator_parameters={
+"md5": "fb8205b846ca10888043e00c54aca062"
+}
+
+[deps]
+
+source_file="res://Assets/Objects/Tiles_brick_texture.png"
+dest_files=["res://.godot/imported/Tiles_brick_texture.png-92562a88e9bc574c9917403a76adebcc.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/Assets/Objects/Tiles_floor_texture.png b/Assets/Objects/Tiles_floor_texture.png
new file mode 100644
index 0000000..15a3bcf
Binary files /dev/null and b/Assets/Objects/Tiles_floor_texture.png differ
diff --git a/Assets/Objects/Tiles_floor_texture.png.import b/Assets/Objects/Tiles_floor_texture.png.import
new file mode 100644
index 0000000..b56b8e5
--- /dev/null
+++ b/Assets/Objects/Tiles_floor_texture.png.import
@@ -0,0 +1,44 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://c7ksavdgkudbe"
+path.s3tc="res://.godot/imported/Tiles_floor_texture.png-80e2f41c81cda99b84005726f72df0a4.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+generator_parameters={
+"md5": "aed8f3816455a136d0d9438e35113634"
+}
+
+[deps]
+
+source_file="res://Assets/Objects/Tiles_floor_texture.png"
+dest_files=["res://.godot/imported/Tiles_floor_texture.png-80e2f41c81cda99b84005726f72df0a4.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=true
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=0
diff --git a/Assets/Recipes.json b/Assets/Recipes.json
new file mode 100644
index 0000000..63fc778
--- /dev/null
+++ b/Assets/Recipes.json
@@ -0,0 +1,864 @@
+[
+ {
+ "id": "iron_ore",
+ "inputs": [],
+ "output": {
+ "item": "iron_ore",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/IronSymbol.png",
+ "research": "iron_smelting",
+ "crafttime": 3.2,
+ "stacksize": 99
+ },
+ {
+ "id": "tin_ore",
+ "inputs": [],
+ "output": {
+ "item": "tin_ore",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/TinSymbol.png",
+ "research": "tin_working",
+ "crafttime": 2.0,
+ "stacksize": 99
+ },
+ {
+ "id": "copper_ore",
+ "inputs": [],
+ "output": {
+ "item": "copper_ore",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/CopperSymbol.png",
+ "research": "stoneage",
+ "crafttime": 1.6,
+ "stacksize": 99
+ },
+ {
+ "id": "mushroom",
+ "inputs": [],
+ "output": {
+ "item": "mushroom",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/MushroomSymbol.png",
+ "research": "basics",
+ "crafttime": 0.8,
+ "stacksize": 99
+ },
+ {
+ "id": "spider_silk",
+ "inputs": [],
+ "output": {
+ "item": "spider_silk",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/SpiderSilkSymbol.png",
+ "research": "basics",
+ "crafttime": 1.2,
+ "stacksize": 99
+ },
+ {
+ "id": "coal",
+ "inputs": [],
+ "output": {
+ "item": "coal",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/CoalSymbol.png",
+ "research": "basics",
+ "crafttime": 1.1,
+ "stacksize": 99
+ },
+ {
+ "id": "water",
+ "inputs": [],
+ "output": {
+ "item": "water",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/WaterSymbol.png",
+ "research": "basics",
+ "crafttime": 0.6,
+ "stacksize": 99
+ },
+ {
+ "id": "stone",
+ "inputs": [],
+ "output": {
+ "item": "stone",
+ "amount": 1
+ },
+ "workstation": "",
+ "texture": "res://Assets/Images/Resources/StoneSymbol.png",
+ "research": "basics",
+ "crafttime": 0.9,
+ "stacksize": 99
+ },
+ {
+ "id": "sand",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "sand",
+ "amount": 1
+ },
+ "workstation": "crusher",
+ "texture": "res://Assets/Images/Items/SandSymbol.png",
+ "research": "basic_machines",
+ "crafttime": 2.5,
+ "stacksize": 99
+ },
+ {
+ "id": "stone_gear",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 3
+ }
+ ],
+ "output": {
+ "item": "stone_gear",
+ "amount": 1
+ },
+ "workstation": "workbench",
+ "texture": "res://Assets/Images/Items/StoneGearSymbol.png",
+ "research": "stone_tools",
+ "crafttime": 2.2,
+ "stacksize": 99
+ },
+ {
+ "id": "stone_plate",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 3
+ }
+ ],
+ "output": {
+ "item": "stone_plate",
+ "amount": 1
+ },
+ "workstation": "workbench",
+ "texture": "res://Assets/Images/Items/StonePlateSymbol.png",
+ "research": "basic_machines",
+ "crafttime": 2.0,
+ "stacksize": 99
+ },
+ {
+ "id": "rope",
+ "inputs": [
+ {
+ "item": "spider_silk",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "rope",
+ "amount": 1
+ },
+ "workstation": "workbench",
+ "texture": "res://Assets/Images/Items/RopeSymbol.png",
+ "research": "stone_tools",
+ "crafttime": 1.8,
+ "stacksize": 99
+ },
+ {
+ "id": "heating_element_v1",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 4
+ }
+ ],
+ "output": {
+ "item": "heating_element_v1",
+ "amount": 1
+ },
+ "workstation": "workbench",
+ "texture": "res://Assets/Images/Items/Heaterv1Symbol.png",
+ "research": "heat_control",
+ "crafttime": 3.5,
+ "stacksize": 99
+ },
+ {
+ "id": "copper_ingot",
+ "inputs": [
+ {
+ "item": "copper_ore",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "copper_ingot",
+ "amount": 1
+ },
+ "workstation": "furnace",
+ "texture": "res://Assets/Images/Items/CopperIngotSymbol.png",
+ "research": "copper_smelting",
+ "crafttime": 4.5,
+ "stacksize": 99
+ },
+ {
+ "id": "tin_ingot",
+ "inputs": [
+ {
+ "item": "tin_ore",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "tin_ingot",
+ "amount": 1
+ },
+ "workstation": "furnace",
+ "texture": "res://Assets/Images/Items/TinIngotSymbol.png",
+ "research": "tin_working",
+ "crafttime": 5.0,
+ "stacksize": 99
+ },
+ {
+ "id": "copper_rod",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "copper_rod",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/CopperRodSymbol.png",
+ "research": "metalworking",
+ "crafttime": 3.0,
+ "stacksize": 99
+ },
+ {
+ "id": "tin_rod",
+ "inputs": [
+ {
+ "item": "tin_ingot",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "tin_rod",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/TinRodSymbol.png",
+ "research": "metalworking",
+ "crafttime": 3.2,
+ "stacksize": 99
+ },
+ {
+ "id": "copper_wire",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "copper_wire",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/CopperWireSymbol.png",
+ "research": "metalworking",
+ "crafttime": 3.4,
+ "stacksize": 99
+ },
+ {
+ "id": "copper_plate",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "copper_plate",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/CopperPlateSymbol.png",
+ "research": "metalworking",
+ "crafttime": 3.8,
+ "stacksize": 99
+ },
+ {
+ "id": "tin_plate",
+ "inputs": [
+ {
+ "item": "tin_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "tin_plate",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/TinPlateSymbol.png",
+ "research": "metalworking",
+ "crafttime": 4.0,
+ "stacksize": 99
+ },
+ {
+ "id": "copper_gear",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "copper_gear",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/CopperGearSymbol.png",
+ "research": "metalworking",
+ "crafttime": 4.2,
+ "stacksize": 99
+ },
+ {
+ "id": "tin_gear",
+ "inputs": [
+ {
+ "item": "tin_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "tin_gear",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/TinGearSymbol.png",
+ "research": "metalworking",
+ "crafttime": 4.4,
+ "stacksize": 99
+ },
+ {
+ "id": "glass",
+ "inputs": [
+ {
+ "item": "sand",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "glass",
+ "amount": 1
+ },
+ "workstation": "furnace",
+ "texture": "res://Assets/Images/Items/GlassSymbol.png",
+ "research": "glass_work",
+ "crafttime": 5.5,
+ "stacksize": 99
+ },
+ {
+ "id": "glass_bottles",
+ "inputs": [
+ {
+ "item": "glass",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "glass_bottles",
+ "amount": 1
+ },
+ "workstation": "glassblower",
+ "texture": "res://Assets/Images/Items/GlassBottleSymbol.png",
+ "research": "glass_work",
+ "crafttime": 6.5,
+ "stacksize": 99
+ },
+ {
+ "id": "fabric",
+ "inputs": [
+ {
+ "item": "rope",
+ "amount": 2
+ },
+ {
+ "item": "spider_silk",
+ "amount": 3
+ }
+ ],
+ "output": {
+ "item": "fabric",
+ "amount": 1
+ },
+ "workstation": "loom",
+ "texture": "res://Assets/Images/Items/RopeSymbol.png",
+ "research": "textiles",
+ "crafttime": 5.8,
+ "stacksize": 99
+ },
+ {
+ "id": "battery_v1",
+ "inputs": [
+ {
+ "item": "copper_rod",
+ "amount": 2
+ },
+ {
+ "item": "tin_rod",
+ "amount": 1
+ },
+ {
+ "item": "water",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "battery_v1",
+ "amount": 1
+ },
+ "workstation": "workbench",
+ "texture": "res://Assets/Images/Items/Batteryv1Symbol.png",
+ "research": "basic_electricity",
+ "crafttime": 7.5,
+ "stacksize": 99
+ },
+ {
+ "id": "dynamo_v1",
+ "inputs": [
+ {
+ "item": "battery_v1",
+ "amount": 1
+ },
+ {
+ "item": "tin_rod",
+ "amount": 2
+ },
+ {
+ "item": "copper_wire",
+ "amount": 3
+ }
+ ],
+ "output": {
+ "item": "dynamo_v1",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/Dynamov1Symbol.png",
+ "research": "basic_electricity",
+ "crafttime": 9.5,
+ "stacksize": 99
+ },
+ {
+ "id": "steam",
+ "inputs": [
+ {
+ "item": "water",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "steam",
+ "amount": 1
+ },
+ "workstation": "steam_generator_v1",
+ "texture": "res://Assets/Images/Items/SteamSymbol.png",
+ "research": "basic_electricity",
+ "crafttime": 4.0,
+ "stacksize": 99
+ },
+ {
+ "id": "bronze_ingot",
+ "inputs": [
+ {
+ "item": "tin_ingot",
+ "amount": 1
+ },
+ {
+ "item": "copper_ingot",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "bronze_ingot",
+ "amount": 1
+ },
+ "workstation": "furnace",
+ "texture": "res://Assets/Images/Items/BronzeIngotSymbol.png",
+ "research": "bronze_smelting",
+ "crafttime": 7.5,
+ "stacksize": 99
+ },
+ {
+ "id": "bronze_rod",
+ "inputs": [
+ {
+ "item": "bronze_ingot",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "bronze_rod",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/BronzeRodSymbol.png",
+ "research": "bronze_machining",
+ "crafttime": 4.8,
+ "stacksize": 99
+ },
+ {
+ "id": "bronze_plate",
+ "inputs": [
+ {
+ "item": "bronze_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "bronze_plate",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/BronzePlateSymbol.png",
+ "research": "bronze_machining",
+ "crafttime": 5.8,
+ "stacksize": 99
+ },
+ {
+ "id": "bronze_gear",
+ "inputs": [
+ {
+ "item": "bronze_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "bronze_gear",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/BronzeGearSymbol.png",
+ "research": "bronze_machining",
+ "crafttime": 6.4,
+ "stacksize": 99
+ },
+ {
+ "id": "iron_ingot",
+ "inputs": [
+ {
+ "item": "iron_ore",
+ "amount": 3
+ },
+ {
+ "item": "coal",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "iron_ingot",
+ "amount": 1
+ },
+ "workstation": "furnace",
+ "texture": "res://Assets/Images/Items/IronIngotSymbol.png",
+ "research": "iron_smelting",
+ "crafttime": 9.0,
+ "stacksize": 99
+ },
+ {
+ "id": "iron_rod",
+ "inputs": [
+ {
+ "item": "iron_ingot",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "iron_rod",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/IronRodSymbol.png",
+ "research": "iron_machining",
+ "crafttime": 5.8,
+ "stacksize": 99
+ },
+ {
+ "id": "iron_plate",
+ "inputs": [
+ {
+ "item": "iron_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "iron_plate",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/IronPlateSymbol.png",
+ "research": "iron_machining",
+ "crafttime": 7.0,
+ "stacksize": 99
+ },
+ {
+ "id": "iron_gear",
+ "inputs": [
+ {
+ "item": "iron_ingot",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "iron_gear",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/IronGearSymbol.png",
+ "research": "iron_machining",
+ "crafttime": 7.8,
+ "stacksize": 99
+ },
+ {
+ "id": "battery_v2",
+ "inputs": [
+ {
+ "item": "iron_rod",
+ "amount": 1
+ },
+ {
+ "item": "copper_rod",
+ "amount": 2
+ },
+ {
+ "item": "water",
+ "amount": 3
+ }
+ ],
+ "output": {
+ "item": "battery_v2",
+ "amount": 1
+ },
+ "workstation": "workbench",
+ "texture": "res://Assets/Images/Items/Batteryv2Symbol.png",
+ "research": "advanced_power",
+ "crafttime": 11.0,
+ "stacksize": 99
+ },
+ {
+ "id": "dynamo_v2",
+ "inputs": [
+ {
+ "item": "battery_v2",
+ "amount": 2
+ },
+ {
+ "item": "iron_rod",
+ "amount": 3
+ },
+ {
+ "item": "copper_wire",
+ "amount": 4
+ },
+ {
+ "item": "tin_plate",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "dynamo_v2",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/Dynamov2Symbol.png",
+ "research": "advanced_power",
+ "crafttime": 15.0,
+ "stacksize": 99
+ },
+ {
+ "id": "stone_robot",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 6
+ },
+ {
+ "item": "rope",
+ "amount": 3
+ },
+ {
+ "item": "stone_gear",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "stone_robot",
+ "amount": 1
+ },
+ "workstation": "workbench",
+ "texture": "res://Assets/Images/Items/StoneRobotSymbol.png",
+ "research": "stone_robotics",
+ "crafttime": 8.0,
+ "stacksize": 99
+ },
+ {
+ "id": "copper_robot",
+ "inputs": [
+ {
+ "item": "copper_plate",
+ "amount": 2
+ },
+ {
+ "item": "rope",
+ "amount": 3
+ },
+ {
+ "item": "copper_gear",
+ "amount": 2
+ },
+ {
+ "item": "battery_v1",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "copper_robot",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/CopperRobotSymbol.png",
+ "research": "copper_robotics",
+ "crafttime": 13.0,
+ "stacksize": 99
+ },
+ {
+ "id": "tin_robot",
+ "inputs": [
+ {
+ "item": "tin_plate",
+ "amount": 2
+ },
+ {
+ "item": "rope",
+ "amount": 3
+ },
+ {
+ "item": "tin_gear",
+ "amount": 2
+ },
+ {
+ "item": "battery_v1",
+ "amount": 1
+ }
+ ],
+ "output": {
+ "item": "tin_robot",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/TinRobotSymbol.png",
+ "research": "copper_robotics",
+ "crafttime": 13.5,
+ "stacksize": 99
+ },
+ {
+ "id": "bronze_robot",
+ "inputs": [
+ {
+ "item": "bronze_plate",
+ "amount": 3
+ },
+ {
+ "item": "rope",
+ "amount": 4
+ },
+ {
+ "item": "bronze_gear",
+ "amount": 2
+ },
+ {
+ "item": "battery_v1",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "bronze_robot",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/BronzeRobotSymbol.png",
+ "research": "bronze_robotics",
+ "crafttime": 18.0,
+ "stacksize": 99
+ },
+ {
+ "id": "iron_robot",
+ "inputs": [
+ {
+ "item": "iron_plate",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 6
+ },
+ {
+ "item": "iron_gear",
+ "amount": 3
+ },
+ {
+ "item": "battery_v2",
+ "amount": 2
+ }
+ ],
+ "output": {
+ "item": "iron_robot",
+ "amount": 1
+ },
+ "workstation": "anvil",
+ "texture": "res://Assets/Images/Items/IronRobotSymbol.png",
+ "research": "iron_robotics",
+ "crafttime": 25.0,
+ "stacksize": 99
+ }
+]
\ No newline at end of file
diff --git a/Assets/Research.json b/Assets/Research.json
new file mode 100644
index 0000000..6e415a5
--- /dev/null
+++ b/Assets/Research.json
@@ -0,0 +1,641 @@
+[
+ {
+ "id": "basics",
+ "inputs": [],
+ "research": "",
+ "crafttime": 1.0,
+ "texture": "res://Assets/Images/Research/BasicsSymbol.png"
+ },
+ {
+ "id": "stoneage",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 8
+ },
+ {
+ "item": "coal",
+ "amount": 4
+ }
+ ],
+ "research": "basics",
+ "crafttime": 4.0,
+ "texture": "res://Assets/Images/Research/StoneageSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_count_increase",
+ "value": 10
+ }
+ ]
+ },
+ {
+ "id": "stone_tools",
+ "inputs": [
+ {
+ "item": "stone",
+ "amount": 10
+ },
+ {
+ "item": "spider_silk",
+ "amount": 4
+ }
+ ],
+ "research": "stoneage",
+ "crafttime": 5.0,
+ "texture": "res://Assets/Images/Items/StoneGearSymbol.png"
+ },
+ {
+ "id": "basic_machines",
+ "inputs": [
+ {
+ "item": "stone_gear",
+ "amount": 3
+ },
+ {
+ "item": "rope",
+ "amount": 3
+ },
+ {
+ "item": "stone",
+ "amount": 10
+ }
+ ],
+ "research": "stone_tools",
+ "crafttime": 7.0,
+ "texture": "res://Assets/Images/Items/StoneGearSymbol.png"
+ },
+ {
+ "id": "heat_control",
+ "inputs": [
+ {
+ "item": "stone_gear",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 8
+ }
+ ],
+ "research": "basic_machines",
+ "crafttime": 8.0,
+ "texture": "res://Assets/Images/Items/Heaterv1Symbol.png"
+ },
+ {
+ "id": "glass_work",
+ "inputs": [
+ {
+ "item": "sand",
+ "amount": 6
+ },
+ {
+ "item": "heating_element_v1",
+ "amount": 2
+ },
+ {
+ "item": "coal",
+ "amount": 4
+ }
+ ],
+ "research": "heat_control",
+ "crafttime": 9.0,
+ "texture": "res://Assets/Images/Items/GlassSymbol.png"
+ },
+ {
+ "id": "stone_storage",
+ "inputs": [
+ {
+ "item": "stone_plate",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 2
+ }
+ ],
+ "research": "basic_machines",
+ "crafttime": 6.0,
+ "texture": "res://Assets/Images/Items/StonePlateSymbol.png"
+ },
+ {
+ "id": "stone_robotics",
+ "inputs": [
+ {
+ "item": "stone_gear",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 4
+ }
+ ],
+ "research": "stone_tools",
+ "crafttime": 8.0,
+ "texture": "res://Assets/Images/Items/StoneRobotSymbol.png"
+ },
+ {
+ "id": "joint_tuning",
+ "inputs": [
+ {
+ "item": "stone_gear",
+ "amount": 3
+ },
+ {
+ "item": "rope",
+ "amount": 4
+ }
+ ],
+ "research": "stone_robotics",
+ "crafttime": 10.0,
+ "texture": "res://Assets/Images/Items/StoneGearSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_speed_bonus",
+ "value": 0.1
+ },
+ {
+ "stat": "robot_minimum_efficiency_bonus",
+ "value": 0.05
+ }
+ ]
+ },
+ {
+ "id": "copperage",
+ "inputs": [
+ {
+ "item": "copper_ore",
+ "amount": 12
+ },
+ {
+ "item": "rope",
+ "amount": 4
+ }
+ ],
+ "research": "heat_control",
+ "crafttime": 12.0,
+ "texture": "res://Assets/Images/Research/CopperageSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_count_increase",
+ "value": 10
+ }
+ ]
+ },
+ {
+ "id": "copper_smelting",
+ "inputs": [
+ {
+ "item": "copper_ore",
+ "amount": 12
+ },
+ {
+ "item": "coal",
+ "amount": 6
+ }
+ ],
+ "research": "copperage",
+ "crafttime": 13.0,
+ "texture": "res://Assets/Images/Items/CopperIngotSymbol.png"
+ },
+ {
+ "id": "tin_working",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 4
+ },
+ {
+ "item": "coal",
+ "amount": 4
+ }
+ ],
+ "research": "copper_smelting",
+ "crafttime": 14.0,
+ "texture": "res://Assets/Images/Items/TinIngotSymbol.png"
+ },
+ {
+ "id": "metalworking",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 6
+ },
+ {
+ "item": "tin_ingot",
+ "amount": 4
+ },
+ {
+ "item": "stone_gear",
+ "amount": 2
+ }
+ ],
+ "research": "tin_working",
+ "crafttime": 16.0,
+ "texture": "res://Assets/Images/Items/CopperGearSymbol.png"
+ },
+ {
+ "id": "textiles",
+ "inputs": [
+ {
+ "item": "rope",
+ "amount": 8
+ },
+ {
+ "item": "spider_silk",
+ "amount": 8
+ },
+ {
+ "item": "copper_gear",
+ "amount": 2
+ }
+ ],
+ "research": "metalworking",
+ "crafttime": 15.0,
+ "texture": "res://Assets/Images/Items/RopeSymbol.png"
+ },
+ {
+ "id": "basic_electricity",
+ "inputs": [
+ {
+ "item": "copper_wire",
+ "amount": 6
+ },
+ {
+ "item": "copper_rod",
+ "amount": 3
+ },
+ {
+ "item": "tin_rod",
+ "amount": 2
+ },
+ {
+ "item": "water",
+ "amount": 4
+ }
+ ],
+ "research": "metalworking",
+ "crafttime": 18.0,
+ "texture": "res://Assets/Images/Items/Batteryv1Symbol.png"
+ },
+ {
+ "id": "copper_robotics",
+ "inputs": [
+ {
+ "item": "battery_v1",
+ "amount": 2
+ },
+ {
+ "item": "copper_gear",
+ "amount": 2
+ },
+ {
+ "item": "tin_gear",
+ "amount": 2
+ }
+ ],
+ "research": "basic_electricity",
+ "crafttime": 20.0,
+ "texture": "res://Assets/Images/Items/CopperRobotSymbol.png"
+ },
+ {
+ "id": "efficient_actuators",
+ "inputs": [
+ {
+ "item": "battery_v1",
+ "amount": 1
+ },
+ {
+ "item": "copper_wire",
+ "amount": 6
+ },
+ {
+ "item": "copper_gear",
+ "amount": 2
+ }
+ ],
+ "research": "copper_robotics",
+ "crafttime": 22.0,
+ "texture": "res://Assets/Images/Items/CopperWireSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_energy_use_reduction",
+ "value": 0.12
+ },
+ {
+ "stat": "robot_speed_bonus",
+ "value": 0.08
+ }
+ ]
+ },
+ {
+ "id": "cooling_channels",
+ "inputs": [
+ {
+ "item": "copper_plate",
+ "amount": 3
+ },
+ {
+ "item": "water",
+ "amount": 6
+ },
+ {
+ "item": "heating_element_v1",
+ "amount": 1
+ }
+ ],
+ "research": "efficient_actuators",
+ "crafttime": 21.0,
+ "texture": "res://Assets/Images/Resources/WaterSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_heat_gain_reduction",
+ "value": 0.15
+ },
+ {
+ "stat": "robot_cooling_bonus",
+ "value": 0.25
+ }
+ ]
+ },
+ {
+ "id": "bronzeage",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 6
+ },
+ {
+ "item": "tin_ingot",
+ "amount": 4
+ }
+ ],
+ "research": "basic_electricity",
+ "crafttime": 24.0,
+ "texture": "res://Assets/Images/Research/BronzeageSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_count_increase",
+ "value": 10
+ }
+ ]
+ },
+ {
+ "id": "bronze_smelting",
+ "inputs": [
+ {
+ "item": "copper_ingot",
+ "amount": 8
+ },
+ {
+ "item": "tin_ingot",
+ "amount": 4
+ },
+ {
+ "item": "coal",
+ "amount": 6
+ }
+ ],
+ "research": "bronzeage",
+ "crafttime": 26.0,
+ "texture": "res://Assets/Images/Items/BronzeIngotSymbol.png"
+ },
+ {
+ "id": "bronze_machining",
+ "inputs": [
+ {
+ "item": "bronze_ingot",
+ "amount": 8
+ },
+ {
+ "item": "copper_gear",
+ "amount": 2
+ }
+ ],
+ "research": "bronze_smelting",
+ "crafttime": 29.0,
+ "texture": "res://Assets/Images/Items/BronzeGearSymbol.png"
+ },
+ {
+ "id": "bronze_storage",
+ "inputs": [
+ {
+ "item": "bronze_plate",
+ "amount": 4
+ },
+ {
+ "item": "rope",
+ "amount": 6
+ }
+ ],
+ "research": "bronze_machining",
+ "crafttime": 27.0,
+ "texture": "res://Assets/Images/Items/BronzePlateSymbol.png"
+ },
+ {
+ "id": "bronze_robotics",
+ "inputs": [
+ {
+ "item": "bronze_gear",
+ "amount": 3
+ },
+ {
+ "item": "bronze_plate",
+ "amount": 3
+ },
+ {
+ "item": "battery_v1",
+ "amount": 2
+ }
+ ],
+ "research": "bronze_machining",
+ "crafttime": 32.0,
+ "texture": "res://Assets/Images/Items/BronzeRobotSymbol.png"
+ },
+ {
+ "id": "sealed_bearings",
+ "inputs": [
+ {
+ "item": "bronze_gear",
+ "amount": 3
+ },
+ {
+ "item": "bronze_plate",
+ "amount": 4
+ },
+ {
+ "item": "glass_bottles",
+ "amount": 2
+ }
+ ],
+ "research": "bronze_robotics",
+ "crafttime": 35.0,
+ "texture": "res://Assets/Images/Items/BronzeGearSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_maintenance_loss_reduction",
+ "value": 0.2
+ },
+ {
+ "stat": "robot_minimum_efficiency_bonus",
+ "value": 0.08
+ }
+ ]
+ },
+ {
+ "id": "ironage",
+ "inputs": [
+ {
+ "item": "bronze_ingot",
+ "amount": 8
+ },
+ {
+ "item": "dynamo_v1",
+ "amount": 2
+ },
+ {
+ "item": "battery_v1",
+ "amount": 3
+ }
+ ],
+ "research": "bronze_robotics",
+ "crafttime": 38.0,
+ "texture": "res://Assets/Images/Research/IronageSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_count_increase",
+ "value": 10
+ }
+ ]
+ },
+ {
+ "id": "iron_smelting",
+ "inputs": [
+ {
+ "item": "bronze_ingot",
+ "amount": 6
+ },
+ {
+ "item": "coal",
+ "amount": 10
+ }
+ ],
+ "research": "ironage",
+ "crafttime": 40.0,
+ "texture": "res://Assets/Images/Items/IronIngotSymbol.png"
+ },
+ {
+ "id": "iron_machining",
+ "inputs": [
+ {
+ "item": "iron_ingot",
+ "amount": 8
+ },
+ {
+ "item": "bronze_gear",
+ "amount": 3
+ }
+ ],
+ "research": "iron_smelting",
+ "crafttime": 44.0,
+ "texture": "res://Assets/Images/Items/IronGearSymbol.png"
+ },
+ {
+ "id": "advanced_power",
+ "inputs": [
+ {
+ "item": "battery_v1",
+ "amount": 3
+ },
+ {
+ "item": "iron_rod",
+ "amount": 4
+ },
+ {
+ "item": "copper_wire",
+ "amount": 6
+ }
+ ],
+ "research": "iron_machining",
+ "crafttime": 48.0,
+ "texture": "res://Assets/Images/Items/Batteryv2Symbol.png"
+ },
+ {
+ "id": "iron_robotics",
+ "inputs": [
+ {
+ "item": "battery_v2",
+ "amount": 2
+ },
+ {
+ "item": "iron_gear",
+ "amount": 3
+ },
+ {
+ "item": "iron_plate",
+ "amount": 4
+ }
+ ],
+ "research": "advanced_power",
+ "crafttime": 54.0,
+ "texture": "res://Assets/Images/Items/IronRobotSymbol.png"
+ },
+ {
+ "id": "regulated_power_cores",
+ "inputs": [
+ {
+ "item": "battery_v2",
+ "amount": 2
+ },
+ {
+ "item": "dynamo_v2",
+ "amount": 1
+ },
+ {
+ "item": "iron_rod",
+ "amount": 4
+ }
+ ],
+ "research": "iron_robotics",
+ "crafttime": 58.0,
+ "texture": "res://Assets/Images/Items/Batteryv2Symbol.png",
+ "effects": [
+ {
+ "stat": "robot_energy_use_reduction",
+ "value": 0.18
+ },
+ {
+ "stat": "robot_heat_gain_reduction",
+ "value": 0.12
+ }
+ ]
+ },
+ {
+ "id": "precision_drive_train",
+ "inputs": [
+ {
+ "item": "iron_gear",
+ "amount": 4
+ },
+ {
+ "item": "iron_plate",
+ "amount": 4
+ },
+ {
+ "item": "battery_v2",
+ "amount": 1
+ }
+ ],
+ "research": "regulated_power_cores",
+ "crafttime": 62.0,
+ "texture": "res://Assets/Images/Items/IronGearSymbol.png",
+ "effects": [
+ {
+ "stat": "robot_speed_bonus",
+ "value": 0.18
+ },
+ {
+ "stat": "robot_maintenance_loss_reduction",
+ "value": 0.12
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/Assets/Sound/Background.mmpz b/Assets/Sound/Background.mmpz
new file mode 100644
index 0000000..66326d1
Binary files /dev/null and b/Assets/Sound/Background.mmpz differ
diff --git a/Assets/Sound/Background.mp3 b/Assets/Sound/Background.mp3
new file mode 100644
index 0000000..59626d1
Binary files /dev/null and b/Assets/Sound/Background.mp3 differ
diff --git a/Assets/Sound/Background.mp3.import b/Assets/Sound/Background.mp3.import
new file mode 100644
index 0000000..680ec10
--- /dev/null
+++ b/Assets/Sound/Background.mp3.import
@@ -0,0 +1,19 @@
+[remap]
+
+importer="mp3"
+type="AudioStreamMP3"
+uid="uid://dcb746ldlm6ta"
+path="res://.godot/imported/Background.mp3-76a4369b1d8f101ad2d172afa3ab7d94.mp3str"
+
+[deps]
+
+source_file="res://Assets/Sound/Background.mp3"
+dest_files=["res://.godot/imported/Background.mp3-76a4369b1d8f101ad2d172afa3ab7d94.mp3str"]
+
+[params]
+
+loop=false
+loop_offset=0
+bpm=0
+beat_count=0
+bar_beats=4
diff --git a/Assets/Sound/alarm.wav b/Assets/Sound/alarm.wav
new file mode 100644
index 0000000..015d54d
Binary files /dev/null and b/Assets/Sound/alarm.wav differ
diff --git a/Assets/Sound/alarm.wav.import b/Assets/Sound/alarm.wav.import
new file mode 100644
index 0000000..77c46f0
--- /dev/null
+++ b/Assets/Sound/alarm.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://ddd03qe8q4icg"
+path="res://.godot/imported/alarm.wav-f44e42de412de63961d002a4b77f5a22.sample"
+
+[deps]
+
+source_file="res://Assets/Sound/alarm.wav"
+dest_files=["res://.godot/imported/alarm.wav-f44e42de412de63961d002a4b77f5a22.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/Assets/Sound/button.wav b/Assets/Sound/button.wav
new file mode 100644
index 0000000..5de39dc
Binary files /dev/null and b/Assets/Sound/button.wav differ
diff --git a/Assets/Sound/button.wav.import b/Assets/Sound/button.wav.import
new file mode 100644
index 0000000..9abf1c0
--- /dev/null
+++ b/Assets/Sound/button.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://d001edcgov542"
+path="res://.godot/imported/button.wav-63410373dbddb412b4208812587c74d5.sample"
+
+[deps]
+
+source_file="res://Assets/Sound/button.wav"
+dest_files=["res://.godot/imported/button.wav-63410373dbddb412b4208812587c74d5.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/Assets/Sound/mining.wav b/Assets/Sound/mining.wav
new file mode 100644
index 0000000..fccd40c
Binary files /dev/null and b/Assets/Sound/mining.wav differ
diff --git a/Assets/Sound/mining.wav.import b/Assets/Sound/mining.wav.import
new file mode 100644
index 0000000..b3be605
--- /dev/null
+++ b/Assets/Sound/mining.wav.import
@@ -0,0 +1,24 @@
+[remap]
+
+importer="wav"
+type="AudioStreamWAV"
+uid="uid://bo0jlfldvl3fc"
+path="res://.godot/imported/mining.wav-ccd6535c367dc93a59962ee440b970f6.sample"
+
+[deps]
+
+source_file="res://Assets/Sound/mining.wav"
+dest_files=["res://.godot/imported/mining.wav-ccd6535c367dc93a59962ee440b970f6.sample"]
+
+[params]
+
+force/8_bit=false
+force/mono=false
+force/max_rate=false
+force/max_rate_hz=44100
+edit/trim=false
+edit/normalize=false
+edit/loop_mode=0
+edit/loop_begin=0
+edit/loop_end=-1
+compress/mode=2
diff --git a/Exports/Linux/Archiv.zip b/Exports/Linux/Archiv.zip
new file mode 100644
index 0000000..c5d347c
Binary files /dev/null and b/Exports/Linux/Archiv.zip differ
diff --git a/Exports/Linux/game.x86_64 b/Exports/Linux/game.x86_64
new file mode 100755
index 0000000..5e6f059
Binary files /dev/null and b/Exports/Linux/game.x86_64 differ
diff --git a/Exports/Linux/libgodotsteam.linux.template_release.x86_64.so b/Exports/Linux/libgodotsteam.linux.template_release.x86_64.so
new file mode 100644
index 0000000..596934d
Binary files /dev/null and b/Exports/Linux/libgodotsteam.linux.template_release.x86_64.so differ
diff --git a/Exports/Linux/libsteam_api.so b/Exports/Linux/libsteam_api.so
new file mode 100644
index 0000000..9a16ad9
Binary files /dev/null and b/Exports/Linux/libsteam_api.so differ
diff --git a/Exports/Windows/Archiv.zip b/Exports/Windows/Archiv.zip
new file mode 100644
index 0000000..f5bfa55
Binary files /dev/null and b/Exports/Windows/Archiv.zip differ
diff --git a/Exports/Windows/game.exe b/Exports/Windows/game.exe
new file mode 100644
index 0000000..17f11e2
Binary files /dev/null and b/Exports/Windows/game.exe differ
diff --git a/Exports/Windows/libgodotsteam.windows.template_release.x86_64.dll b/Exports/Windows/libgodotsteam.windows.template_release.x86_64.dll
new file mode 100644
index 0000000..9e5634b
Binary files /dev/null and b/Exports/Windows/libgodotsteam.windows.template_release.x86_64.dll differ
diff --git a/Exports/Windows/steam_api64.dll b/Exports/Windows/steam_api64.dll
new file mode 100644
index 0000000..43e7291
Binary files /dev/null and b/Exports/Windows/steam_api64.dll differ
diff --git a/Prefabs/Crafting/ItemDisplay.tscn b/Prefabs/Crafting/ItemDisplay.tscn
new file mode 100644
index 0000000..a189b92
--- /dev/null
+++ b/Prefabs/Crafting/ItemDisplay.tscn
@@ -0,0 +1,40 @@
+[gd_scene format=3 uid="uid://4by1t1x6m4yt"]
+
+[ext_resource type="Script" uid="uid://qdjn5oqn6p5d" path="res://Scripts/UI/Inventory/ItemDisplay.cs" id="1_if7q5"]
+
+[node name="Item" type="PanelContainer" unique_id=247502695 node_paths=PackedStringArray("texture", "text", "amount")]
+anchors_preset = 14
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+offset_bottom = 31.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_if7q5")
+texture = NodePath("HBoxContainer/TextureRect")
+text = NodePath("HBoxContainer/ID")
+amount = NodePath("HBoxContainer/Amount")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=906487472]
+layout_mode = 2
+theme_override_constants/separation = 20
+alignment = 1
+
+[node name="TextureRect" type="TextureRect" parent="HBoxContainer" unique_id=1795632837]
+layout_mode = 2
+
+[node name="ID" type="RichTextLabel" parent="HBoxContainer" unique_id=46246913]
+layout_mode = 2
+text = "Robot #1"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Amount" type="RichTextLabel" parent="HBoxContainer" unique_id=1705181895]
+layout_mode = 2
+text = "Robot #1"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
diff --git a/Prefabs/DSL/CraftNode.tscn b/Prefabs/DSL/CraftNode.tscn
new file mode 100644
index 0000000..3772b07
--- /dev/null
+++ b/Prefabs/DSL/CraftNode.tscn
@@ -0,0 +1,50 @@
+[gd_scene format=3 uid="uid://cinn18bl736rk"]
+
+[ext_resource type="Script" uid="uid://bfosue8mejnr5" path="res://Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs" id="1_qemp1"]
+
+[node name="Craft" type="GraphNode" unique_id=908155742]
+offset_right = 158.0
+offset_bottom = 86.0
+title = "Craft"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+script = ExtResource("1_qemp1")
+
+[node name="Values" type="HBoxContainer" parent="." unique_id=1811897037]
+layout_mode = 2
+alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="Values" unique_id=1387569824]
+layout_mode = 2
+text = "Craft"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Item" type="OptionButton" parent="Values" unique_id=2066235567]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Amount" type="SpinBox" parent="Values" unique_id=827307108]
+layout_mode = 2
+size_flags_horizontal = 3
+rounded = true
+alignment = 1
+prefix = "x"
+select_all_on_focus = true
+
+[node name="RichTextLabel2" type="RichTextLabel" parent="Values" unique_id=83986734]
+layout_mode = 2
+text = "times"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
diff --git a/Prefabs/DSL/ExploreNode.tscn b/Prefabs/DSL/ExploreNode.tscn
new file mode 100644
index 0000000..aefadfe
--- /dev/null
+++ b/Prefabs/DSL/ExploreNode.tscn
@@ -0,0 +1,21 @@
+[gd_scene format=3 uid="uid://dit4u45jegwv0"]
+
+[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/ExploreNodeDisplay.cs" id="1_3kgh4"]
+
+[node name="Editor" type="GraphNode" unique_id=2133945133]
+offset_right = 67.0
+offset_bottom = 55.0
+title = "Explore"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+script = ExtResource("1_3kgh4")
+
+[node name="Control" type="Control" parent="." unique_id=869568380]
+layout_mode = 2
diff --git a/Prefabs/DSL/ForNode.tscn b/Prefabs/DSL/ForNode.tscn
new file mode 100644
index 0000000..b41d5f0
--- /dev/null
+++ b/Prefabs/DSL/ForNode.tscn
@@ -0,0 +1,67 @@
+[gd_scene format=3 uid="uid://co7op7et6is8p"]
+
+[ext_resource type="Script" uid="uid://gptqyjv5swwc" path="res://Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs" id="1_xshi5"]
+
+[node name="For" type="GraphNode" unique_id=1235671673]
+offset_right = 229.0
+offset_bottom = 129.0
+theme_override_constants/separation = 20
+title = "For"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+slot/1/left_enabled = false
+slot/1/left_type = 0
+slot/1/left_color = Color(1, 1, 1, 1)
+slot/1/left_icon = null
+slot/1/right_enabled = true
+slot/1/right_type = 0
+slot/1/right_color = Color(1, 1, 1, 1)
+slot/1/right_icon = null
+slot/1/draw_stylebox = true
+script = ExtResource("1_xshi5")
+
+[node name="Values" type="HBoxContainer" parent="." unique_id=1896004369]
+layout_mode = 2
+alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="Values" unique_id=1832504365]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.3
+text = "Repeat"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Amount" type="SpinBox" parent="Values" unique_id=1393892745]
+layout_mode = 2
+size_flags_horizontal = 3
+rounded = true
+alignment = 1
+prefix = "x"
+select_all_on_focus = true
+
+[node name="RichTextLabel2" type="RichTextLabel" parent="Values" unique_id=1394844571]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.3
+text = "times"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="." unique_id=1677882951]
+layout_mode = 2
+text = "Afterwards"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
diff --git a/Prefabs/DSL/HarvestNode.tscn b/Prefabs/DSL/HarvestNode.tscn
new file mode 100644
index 0000000..c762e67
--- /dev/null
+++ b/Prefabs/DSL/HarvestNode.tscn
@@ -0,0 +1,21 @@
+[gd_scene format=3 uid="uid://com0ou37wj2xo"]
+
+[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/HarvestNodeDisplay.cs" id="1_ve3v1"]
+
+[node name="Harvest" type="GraphNode" unique_id=139025740]
+offset_right = 69.0
+offset_bottom = 55.0
+title = "Harvest"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+script = ExtResource("1_ve3v1")
+
+[node name="Control" type="Control" parent="." unique_id=2044911208]
+layout_mode = 2
diff --git a/Prefabs/DSL/IfNode.tscn b/Prefabs/DSL/IfNode.tscn
new file mode 100644
index 0000000..6278a98
--- /dev/null
+++ b/Prefabs/DSL/IfNode.tscn
@@ -0,0 +1,91 @@
+[gd_scene format=3 uid="uid://ctmad6foidkvp"]
+
+[ext_resource type="Script" uid="uid://cngxwfcrim746" path="res://Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs" id="1_ygi5c"]
+
+[node name="If" type="GraphNode" unique_id=821127877]
+offset_right = 468.0
+offset_bottom = 129.0
+theme_override_constants/separation = 20
+title = "If"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+slot/1/left_enabled = false
+slot/1/left_type = 0
+slot/1/left_color = Color(1, 1, 1, 1)
+slot/1/left_icon = null
+slot/1/right_enabled = true
+slot/1/right_type = 0
+slot/1/right_color = Color(1, 1, 1, 1)
+slot/1/right_icon = null
+slot/1/draw_stylebox = true
+script = ExtResource("1_ygi5c")
+
+[node name="Values" type="HBoxContainer" parent="." unique_id=149543652]
+layout_mode = 2
+alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="Values" unique_id=1884502843]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "if"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Item" type="OptionButton" parent="Values" unique_id=635450884]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Comparator" type="OptionButton" parent="Values" unique_id=691603757]
+layout_mode = 2
+size_flags_horizontal = 3
+item_count = 6
+popup/item_0/text = "is equal to"
+popup/item_0/id = 0
+popup/item_1/text = "is bigger than"
+popup/item_1/id = 1
+popup/item_2/text = "is less than"
+popup/item_2/id = 2
+popup/item_3/text = "is not"
+popup/item_3/id = 3
+popup/item_4/text = "is less than or equal to"
+popup/item_4/id = 4
+popup/item_5/text = "is bigger than or equal to"
+popup/item_5/id = 5
+
+[node name="Amount" type="SpinBox" parent="Values" unique_id=561942124]
+layout_mode = 2
+size_flags_horizontal = 3
+rounded = true
+alignment = 1
+prefix = "x"
+select_all_on_focus = true
+
+[node name="RichTextLabel2" type="RichTextLabel" parent="Values" unique_id=947350412]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "execute"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="." unique_id=798764628]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "Else"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
diff --git a/Prefabs/DSL/MaintainNode.tscn b/Prefabs/DSL/MaintainNode.tscn
new file mode 100644
index 0000000..e870fbc
--- /dev/null
+++ b/Prefabs/DSL/MaintainNode.tscn
@@ -0,0 +1,21 @@
+[gd_scene format=3 uid="uid://fe7so4543q4x"]
+
+[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/MaintainNodeDisplay.cs" id="1_maintain"]
+
+[node name="Maintain" type="GraphNode" unique_id=472534776]
+offset_right = 77.0
+offset_bottom = 55.0
+title = "Maintain"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+script = ExtResource("1_maintain")
+
+[node name="Control" type="Control" parent="." unique_id=7171189]
+layout_mode = 2
diff --git a/Prefabs/DSL/MoveNode.tscn b/Prefabs/DSL/MoveNode.tscn
new file mode 100644
index 0000000..4bd75b1
--- /dev/null
+++ b/Prefabs/DSL/MoveNode.tscn
@@ -0,0 +1,57 @@
+[gd_scene format=3 uid="uid://by0khq5dmxjvm"]
+
+[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/MoveNodeDisplay.cs" id="1_mexpj"]
+
+[node name="Move" type="GraphNode" unique_id=1386049718]
+offset_right = 373.0
+offset_bottom = 86.0
+title = "Move"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+script = ExtResource("1_mexpj")
+
+[node name="Values" type="HBoxContainer" parent="." unique_id=1288550105]
+layout_mode = 2
+alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="Values" unique_id=1529203020]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "Move to:"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+justification_flags = 161
+
+[node name="CoordinateX" type="SpinBox" parent="Values" unique_id=2062502281]
+layout_mode = 2
+size_flags_horizontal = 3
+rounded = true
+alignment = 1
+prefix = "X"
+select_all_on_focus = true
+
+[node name="CoordinateY" type="SpinBox" parent="Values" unique_id=211509452]
+layout_mode = 2
+size_flags_horizontal = 3
+rounded = true
+alignment = 1
+prefix = "Y"
+select_all_on_focus = true
+
+[node name="CoordinateZ" type="SpinBox" parent="Values" unique_id=832152386]
+layout_mode = 2
+size_flags_horizontal = 3
+rounded = true
+alignment = 1
+prefix = "Z"
+select_all_on_focus = true
diff --git a/Prefabs/DSL/SacrificeNode.tscn b/Prefabs/DSL/SacrificeNode.tscn
new file mode 100644
index 0000000..6b3e40b
--- /dev/null
+++ b/Prefabs/DSL/SacrificeNode.tscn
@@ -0,0 +1,21 @@
+[gd_scene format=3 uid="uid://bxph44i8mad1i"]
+
+[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/SacrificeNodeDisplay.cs" id="1_sacrifice"]
+
+[node name="Sacrifice" type="GraphNode" unique_id=530315031]
+offset_right = 73.0
+offset_bottom = 55.0
+title = "Sacrifice"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+script = ExtResource("1_sacrifice")
+
+[node name="Control" type="Control" parent="." unique_id=632650905]
+layout_mode = 2
diff --git a/Prefabs/DSL/StartNode.tscn b/Prefabs/DSL/StartNode.tscn
new file mode 100644
index 0000000..ca03c4a
--- /dev/null
+++ b/Prefabs/DSL/StartNode.tscn
@@ -0,0 +1,21 @@
+[gd_scene format=3 uid="uid://b1y4r3s7ukmfw"]
+
+[ext_resource type="Script" path="res://Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs" id="1_start"]
+
+[node name="Start" type="GraphNode" unique_id=2100000001]
+offset_right = 65.0
+offset_bottom = 55.0
+title = "Start"
+slot/0/left_enabled = false
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+script = ExtResource("1_start")
+
+[node name="Control" type="Control" parent="." unique_id=2100000002]
+layout_mode = 2
diff --git a/Prefabs/DSL/WhileNode.tscn b/Prefabs/DSL/WhileNode.tscn
new file mode 100644
index 0000000..2423ea8
--- /dev/null
+++ b/Prefabs/DSL/WhileNode.tscn
@@ -0,0 +1,89 @@
+[gd_scene format=3 uid="uid://bwiaqvl0d4x8v"]
+
+[ext_resource type="Script" uid="uid://d3npiur46icru" path="res://Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs" id="1_q0rc7"]
+
+[node name="While" type="GraphNode" unique_id=2040226700]
+offset_right = 499.0
+offset_bottom = 129.0
+theme_override_constants/separation = 20
+title = "While"
+slot/0/left_enabled = true
+slot/0/left_type = 0
+slot/0/left_color = Color(1, 1, 1, 1)
+slot/0/left_icon = null
+slot/0/right_enabled = true
+slot/0/right_type = 0
+slot/0/right_color = Color(1, 1, 1, 1)
+slot/0/right_icon = null
+slot/0/draw_stylebox = true
+slot/1/left_enabled = false
+slot/1/left_type = 0
+slot/1/left_color = Color(1, 1, 1, 1)
+slot/1/left_icon = null
+slot/1/right_enabled = true
+slot/1/right_type = 0
+slot/1/right_color = Color(1, 1, 1, 1)
+slot/1/right_icon = null
+slot/1/draw_stylebox = true
+script = ExtResource("1_q0rc7")
+
+[node name="Values" type="HBoxContainer" parent="." unique_id=945032897]
+layout_mode = 2
+alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="Values" unique_id=336538401]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "while"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Item" type="OptionButton" parent="Values" unique_id=117577134]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Comparator" type="OptionButton" parent="Values" unique_id=539633915]
+layout_mode = 2
+size_flags_horizontal = 3
+item_count = 6
+popup/item_0/text = "is equal to"
+popup/item_0/id = 0
+popup/item_1/text = "is bigger than"
+popup/item_1/id = 1
+popup/item_2/text = "is less than"
+popup/item_2/id = 2
+popup/item_3/text = "is not"
+popup/item_3/id = 3
+popup/item_4/text = "is less than or equal to"
+popup/item_4/id = 4
+popup/item_5/text = "is bigger than or equal to"
+popup/item_5/id = 5
+
+[node name="Amount" type="SpinBox" parent="Values" unique_id=1195061461]
+layout_mode = 2
+size_flags_horizontal = 3
+rounded = true
+alignment = 1
+prefix = "x"
+select_all_on_focus = true
+
+[node name="RichTextLabel2" type="RichTextLabel" parent="Values" unique_id=345047084]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "execute"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="." unique_id=1571031910]
+layout_mode = 2
+text = "Afterwards"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
diff --git a/Prefabs/Layer.tscn b/Prefabs/Layer.tscn
new file mode 100644
index 0000000..3f0cf52
--- /dev/null
+++ b/Prefabs/Layer.tscn
@@ -0,0 +1,6 @@
+[gd_scene format=3 uid="uid://ck10wk10me3tx"]
+
+[ext_resource type="Script" path="res://Scripts/World/Layer.cs" id="1_trar1"]
+
+[node name="Node3D" type="Node3D" unique_id=724642284]
+script = ExtResource("1_trar1")
diff --git a/Prefabs/Robot/Robot.tscn b/Prefabs/Robot/Robot.tscn
new file mode 100644
index 0000000..f678455
--- /dev/null
+++ b/Prefabs/Robot/Robot.tscn
@@ -0,0 +1,17 @@
+[gd_scene format=3 uid="uid://dciwxejdji2lg"]
+
+[ext_resource type="PackedScene" uid="uid://cjae60v4c60vb" path="res://Prefabs/Robot/RobotVisual.tscn" id="2_3hvm5"]
+[ext_resource type="Script" uid="uid://e0pgy7jya41y" path="res://Scripts/Gameplay/Robots/Robot.cs" id="2_j80uv"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_vquur"]
+size = Vector3(1.1176758, 0.7307129, 1.0234375)
+
+[node name="RobotBase" type="CharacterBody3D" unique_id=2075379542]
+collision_layer = 2
+script = ExtResource("2_j80uv")
+
+[node name="RobotVisual" parent="." unique_id=516119901 instance=ExtResource("2_3hvm5")]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1405728816]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.05883789, 0.29821777, -0.01171875)
+shape = SubResource("BoxShape3D_vquur")
diff --git a/Prefabs/Robot/RobotDisplay.tscn b/Prefabs/Robot/RobotDisplay.tscn
new file mode 100644
index 0000000..00c9e43
--- /dev/null
+++ b/Prefabs/Robot/RobotDisplay.tscn
@@ -0,0 +1,53 @@
+[gd_scene format=3 uid="uid://dribqey54i62n"]
+
+[ext_resource type="Script" uid="uid://dcxom1paffp0p" path="res://Scripts/UI/Robots/RobotDisplay.cs" id="1_ltmdd"]
+
+[node name="Robot" type="PanelContainer" unique_id=247502695 node_paths=PackedStringArray("listItem", "currentScript")]
+anchors_preset = 14
+anchor_top = 0.5
+anchor_right = 1.0
+anchor_bottom = 0.5
+offset_bottom = 31.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_ltmdd")
+listItem = NodePath("HBoxContainer/ListItem")
+currentScript = NodePath("HBoxContainer/CurrentScript")
+
+[node name="HBoxContainer" type="HBoxContainer" parent="." unique_id=906487472]
+layout_mode = 2
+theme_override_constants/separation = 20
+alignment = 1
+
+[node name="ListItem" type="RichTextLabel" parent="HBoxContainer" unique_id=46246913]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.5
+text = "Robot #1"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Jump" type="Button" parent="HBoxContainer" unique_id=391426419]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "Jump to"
+
+[node name="Follow" type="Button" parent="HBoxContainer" unique_id=101092106]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.2
+text = "Follow"
+
+[node name="CurrentScript" type="RichTextLabel" parent="HBoxContainer" unique_id=425408407]
+layout_mode = 2
+size_flags_horizontal = 3
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[connection signal="pressed" from="HBoxContainer/Jump" to="." method="OnJumpToClicked"]
+[connection signal="pressed" from="HBoxContainer/Follow" to="." method="OnFollowToClicked"]
diff --git a/Prefabs/Robot/RobotVisual.tscn b/Prefabs/Robot/RobotVisual.tscn
new file mode 100644
index 0000000..e9aba3f
--- /dev/null
+++ b/Prefabs/Robot/RobotVisual.tscn
@@ -0,0 +1,5 @@
+[gd_scene format=3 uid="uid://cjae60v4c60vb"]
+
+[ext_resource type="PackedScene" uid="uid://bccs6xik7xv8s" path="res://Assets/Objects/Robot.glb" id="1_wvvfd"]
+
+[node name="Robot" unique_id=516119901 instance=ExtResource("1_wvvfd")]
diff --git a/README.md b/README.md
index 8006f11..7719969 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,83 @@
-# RuinAdventurer
+# Ruin Adventurer
+Ruin Adventurer ist ein Automatisierungs- und Ressourcenmanagement-Spiel, das im Rahmen einer Diplomarbeit entwickelt wird. Der Spieler ist in einer unterirdischen Ruinenanlage gefangen und nutzt Roboter, um die Umgebung zu erkunden, Ressourcen abzubauen, Gegenstände herzustellen und neue Ebenen freizuschalten.
+
+Das Spiel wird mit **Godot 4.6** und **C#** entwickelt und ist auf eine spielbare Early-Access-Version für Linux und Windows ausgelegt.
+
+## Spielidee
+
+Der Spieler steuert keinen direkten Charakter, sondern verwaltet die Ruine aus einer Management-Perspektive. Die eigentliche Arbeit wird durch Roboter erledigt, welche über eine visuelle DSL programmiert werden. Fortschritt entsteht durch das Zusammenspiel von Erkundung, Automatisierung, Crafting, Forschung und Survival-Mechaniken.
+
+Das langfristige Ziel ist es, tiefere Ebenen der Ruine freizuschalten und schliesslich aus der Anlage zu entkommen.
+
+## Kernsysteme
+
+- **Prozedurale Weltgenerierung** mit Wave Function Collapse
+- **Mehrere Ebenen** mit Gates und freischaltbarem Fortschritt
+- **Roboterautomatisierung** über visuelle Programmblöcke
+- **DSL-System** mit Nodes wie Move, Explore, Harvest, Craft, If, For, While, Maintain und Sacrifice
+- **Ressourcen, Inventar und Crafting** auf Basis von JSON-Daten
+- **Forschungssystem** mit freischaltbaren Rezepten, Robotereffekten und Fortschritt
+- **Survival-Aspekte** wie Hunger, Durst, Energie, Hitze und Wartung
+- **Speichern und Laden** des Spielstands in mehreren Dateien
+- **Tutorial und UI-Systeme** für Einstieg, Optionen, Karte, Inventar, Forschung und Roboterübersicht
+
+## Technische Grundprinzipien
+
+Das Projekt ist bewusst in getrennte Verantwortlichkeiten gegliedert:
+
+- `Scripts/World` enthält Weltgenerierung, Layer, Tiles, Pathfinding und Kartendarstellung.
+- `Scripts/Gameplay` enthält Roboter, Crafting, Forschung und Survival.
+- `Scripts/DSL` enthält die ausführbare Logik der Programmierblöcke.
+- `Scripts/UI` enthält die sichtbaren Fenster, Node-Editoren und Anzeigen.
+- `Scripts/Core` enthält zentrale Daten, Laden/Speichern, Ressourcen und Systemintegration.
+
+Die sichtbaren DSL-Nodes im Editor sind von den zur Laufzeit ausgeführten ProgramNodes getrennt. Beim Kompilieren werden Runtime-Kopien erstellt, damit mehrere Roboter unabhängig voneinander Programme ausführen können, ohne sich gegenseitig zu beeinflussen.
+
+## Voraussetzungen
+
+- Godot 4.6 mit .NET/C# Unterstützung
+- .NET SDK passend zur verwendeten Godot-Version
+- Linux oder Windows als Zielplattform
+
+Für lokale Steam-Tests wird eine `steam_appid.txt` verwendet. Steamworks ist im Projekt vorbereitet, aber die Kernlogik des Spiels ist nicht davon abhängig.
+
+## Starten
+
+Das Projekt kann direkt über Godot geöffnet werden:
+
+```text
+project.godot
+```
+
+Die Hauptszene wird über die Godot-Projekteinstellungen gestartet. Für Tests existiert eine eigene Testszene:
+
+```text
+Scenes/TestRunner.tscn
+```
+
+## Tests
+
+Das Projekt enthält einen einfachen eigenen TestRunner für Unit- und Integrationstests. Getestet werden unter anderem:
+
+- Inventar und Crafting
+- Forschung und Forschungseffekte
+- Survival-Logik
+- Roboterzustände
+- DSL-Nodes und Verbindungen
+- Save/Load
+- Layergenerierung
+
+Headless-Ausführung mit Godot:
+
+```bash
+Godot_v4.6.1-stable_mono_linux.x86_64 --headless --path . Scenes/TestRunner.tscn
+```
+
+## Projektstatus
+
+Das Projekt befindet sich in einer fortgeschrittenen Prototyp- bzw. Early-Access-Phase. Die wichtigsten Kernmechaniken sind umgesetzt und miteinander verbunden. Weitere Inhalte, Balancing, UI-Polish, Story-Elemente und zusätzliche Roboter-/Gebäudesysteme sind für spätere Versionen vorgesehen.
+
+## Hinweis
+
+Dieses Repository ist Teil einer Diplomarbeit. Einige Systeme sind daher bewusst auf den Projektumfang zugeschnitten und priorisieren Nachvollziehbarkeit, Testbarkeit und eine funktionierende Spielschleife gegenüber vollständigem Feature-Umfang.
diff --git a/RuinAdventurer.csproj b/RuinAdventurer.csproj
new file mode 100644
index 0000000..e457872
--- /dev/null
+++ b/RuinAdventurer.csproj
@@ -0,0 +1,7 @@
+
+
+ net8.0
+ net9.0
+ true
+
+
\ No newline at end of file
diff --git a/RuinAdventurer.csproj.lscache b/RuinAdventurer.csproj.lscache
new file mode 100644
index 0000000..90fe2a3
--- /dev/null
+++ b/RuinAdventurer.csproj.lscache
@@ -0,0 +1,458 @@
+version=1
+
+# This file caches language service data to improve the performance of C# Dev Kit.
+# It is not intended for manual editing. It can safely be deleted and will be
+# regenerated automatically. For more information, see https://aka.ms/lscache
+#
+# To control where cache files are stored, use the following VS Code setting:
+# "dotnet.projectsystem.cacheInProjectFolder": true
+
+[project]
+language=C#
+primary
+lastDtbSucceeded
+
+[properties]
+AssemblyName=RuinAdventurer
+CommandLineArgsForDesignTimeEvaluation=-langversion:12.0 -define:TRACE
+CompilerGeneratedFilesOutputPath=
+MaxSupportedLangVersion=12.0
+ProjectAssetsFile=.godot/mono/temp/obj/project.assets.json
+RootNamespace=RuinAdventurer
+RunAnalyzers=
+RunAnalyzersDuringLiveAnalysis=
+SolutionPath=RuinAdventurer.sln
+TargetFrameworkIdentifier=.NETCoreApp
+TargetPath=.godot/mono/temp/bin/Debug/RuinAdventurer.dll
+TargetRefPath=.godot/mono/temp/obj/Debug/ref/RuinAdventurer.dll
+TemporaryDependencyNodeTargetIdentifier=net8.0
+
+[commandLineArguments]
+/noconfig
+/unsafe-
+/checked-
+/nowarn:1701,1702,1701,1702
+/fullpaths
+/nostdlib+
+/errorreport:prompt
+/warn:8
+/define:GODOT;GODOT_LINUXBSD;GODOT_PC;GODOT4;GODOT4_6;GODOT4_6_1;GODOT4_OR_GREATER;GODOT4_0_OR_GREATER;GODOT4_1_OR_GREATER;GODOT4_2_OR_GREATER;GODOT4_3_OR_GREATER;GODOT4_4_OR_GREATER;GODOT4_5_OR_GREATER;GODOT4_6_OR_GREATER;GODOT4_6_0_OR_GREATER;GODOT4_6_1_OR_GREATER;TRACE;DEBUG;NET;NET8_0;NETCOREAPP;TOOLS;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER
+/highentropyva+
+/debug+
+/debug:portable
+/filealign:512
+/optimize-
+/out:.godot/mono/temp/obj/Debug/RuinAdventurer.dll
+/refout:.godot/mono/temp/obj/Debug/refint/RuinAdventurer.dll
+/target:library
+/warnaserror-
+/utf8output
+/deterministic+
+/langversion:12.0
+/warnaserror+:NU1605,SYSLIB0011
+
+[sourceFiles]
+.godot/mono/temp/obj/Debug/
+ .NETCoreApp,Version=v8.0.AssemblyAttributes.cs
+ RuinAdventurer.AssemblyInfo.cs
+addons/godotsteam_csharpbindings/Apps/
+ Dlc.cs
+ DlcDownloadProgress.cs
+ InstalledApps.cs
+ TimedTrial.cs
+addons/godotsteam_csharpbindings/Friends/
+ AvatarSize.cs
+ ChatRoomEnterResponse.cs
+ ClanActivityCounts.cs
+ ClanChatMessage.cs
+ ClanChatMessageType.cs
+ ClanOfficer.cs
+ CommunityProfileItemProperty.cs
+ CommunityProfileItemType.cs
+ Follow.cs
+ Friend.cs
+ FriendFlag.cs
+ FriendGroup.cs
+ FriendRelationship.cs
+ GameOverlayType.cs
+ GameOverlayUserType.cs
+ PersonaChange.cs
+ PersonaState.cs
+ ProfileData.cs
+ RecentPlayer.cs
+ RichPresenceKeys.cs
+ SteamGroup.cs
+ UserRestriction.cs
+addons/godotsteam_csharpbindings/GameSearch/
+ PlayerData.cs
+ PlayerResult.cs
+ SearchProgress.cs
+ SearchResult.cs
+addons/godotsteam_csharpbindings/HTMLSurface/
+ HtmlKeyModifiers.cs
+ HtmlLinkData.cs
+ HtmlMouseButton.cs
+ HtmlMouseCursor.cs
+ HtmlPageData.cs
+ HtmlScrollData.cs
+ HtmlUrlData.cs
+ HtmlWindowData.cs
+addons/godotsteam_csharpbindings/
+ Methods.cs
+ Screenshots/VRScreenshotType.cs
+ Signals.cs
+ Steam.AppLists.cs
+ Steam.AppLists.Signals.cs
+ Steam.Apps.cs
+ Steam.Apps.Signals.cs
+ Steam.cs
+ Steam.Friends.cs
+ Steam.Friends.Signals.cs
+ Steam.GameSearch.cs
+ Steam.GameSearch.Signals.cs
+ Steam.Generated.Data.cs
+ Steam.HTMLSurface.cs
+ Steam.HTMLSurface.Signals.cs
+ Steam.HTTP.cs
+ Steam.HTTP.Signals.cs
+ Steam.Input.cs
+ Steam.Input.Signals.cs
+ Steam.Inventory.cs
+ Steam.Inventory.Signals.cs
+ Steam.Matchmaking.cs
+ Steam.Matchmaking.Signals.cs
+ Steam.MatchmakingServers.cs
+ Steam.MatchmakingServers.Signals.cs
+ Steam.Music.cs
+ Steam.MusicRemote.cs
+ Steam.MusicRemote.Signals.cs
+ Steam.Networking.cs
+ Steam.Networking.Signals.cs
+ Steam.NetworkingMessages.cs
+ Steam.NetworkingMessages.Signals.cs
+ Steam.NetworkingSockets.cs
+ Steam.NetworkingSockets.Signals.cs
+ Steam.NetworkingTypes.cs
+ Steam.NetworkingUtils.cs
+ Steam.NetworkingUtils.Signals.cs
+ Steam.ParentalSettings.cs
+ Steam.ParentalSettings.Signals.cs
+ Steam.Parties.cs
+ Steam.Parties.Signals.cs
+ Steam.RemotePlay.cs
+ Steam.RemotePlay.Signals.cs
+ Steam.RemoteStorage.cs
+ Steam.RemoteStorage.Signals.cs
+ Steam.Screenshots.cs
+ Steam.Screenshots.Signals.cs
+ Steam.Signals.cs
+ Steam.UGC.cs
+ Steam.UGC.Signals.cs
+ Steam.User.cs
+ Steam.User.Signals.cs
+ Steam.UserStats.cs
+ Steam.UserStats.Signals.cs
+ Steam.Utils.cs
+ Steam.Utils.Signals.cs
+ Steam.Video.cs
+ Steam.Video.Signals.cs
+ Steam/
+ ErrorResult.cs
+ SteamInitExResult.cs
+ SteamInitExStatus.cs
+ SteamInitResult.cs
+ SteamInitStatus.cs
+ Utils/
+ ApiCallResult.cs
+ FloatingGamepadTextInputMode.cs
+ GamepadTextInputLineMode.cs
+ GamepadTextInputMode.cs
+ ImageRGBA.cs
+ ImageSize.cs
+ OverlayNotificationPosition.cs
+ TextFilteringContext.cs
+ Video/BroadcastStatus.cs
+Scripts/Core/
+ FileHandler.cs
+ GameData.cs
+ ResourceLoader.cs
+ SaveGameData.cs
+ SaveGameDataApplier.cs
+ SaveGameDataFactory.cs
+ SaveGameManager.cs
+ SoundManager.cs
+ SteamworksHandler.cs
+Scripts/DSL/
+ NodeResult.cs
+ Nodes/
+ CraftNode.cs
+ ExploreNode.cs
+ ForNode.cs
+ HarvestNode.cs
+ IfNode.cs
+ MaintainNode.cs
+ MoveNode.cs
+ ProgramNode.cs
+ SacrificeNode.cs
+ StartNode.cs
+ WhileNode.cs
+Scripts/Gameplay/Crafting/
+ CraftingResult.cs
+ GameResource.cs
+ Ingredient.cs
+ Inventory.cs
+ Item.cs
+ ItemData.cs
+Scripts/Gameplay/Research/
+ Research.cs
+ ResearchData.cs
+ ResearchEffect.cs
+ ResearchResult.cs
+ ResearchState.cs
+Scripts/Gameplay/Robots/
+ Robot.cs
+ RobotStats.cs
+ RobotTypeStats.cs
+Scripts/
+ Gameplay/Survival/SurvivalState.cs
+ Tests/
+ TestRunner.cs
+ TestRunnerHelpers.cs
+ UI/Common/
+ Camera3d.cs
+ UIHandler.cs
+ UIHandlerDisplay.cs
+ UIStyle.cs
+ UI/DSL/
+ CodingWindow.cs
+ NodeDisplay.cs
+ NodeDisplays/
+ CraftNodeDisplay.cs
+ ExploreNodeDisplay.cs
+ ForNodeDisplay.cs
+ HarvestNodeDisplay.cs
+ IfNodeDisplay.cs
+ MaintainNodeDisplay.cs
+ MoveNodeDisplay.cs
+ SacrificeNodeDisplay.cs
+ StartNodeDisplay.cs
+ WhileNodeDisplay.cs
+ NodeTooltip.cs
+ RunningProgramGraphBuilder.cs
+ ScriptConnection.cs
+ ScriptGraphCompiler.cs
+ ScriptGraphSerializer.cs
+ UI/Inventory/
+ InventoryDisplay.cs
+ ItemDisplay.cs
+ UI/Menus/
+ MainMenu.cs
+ OptionsMenu.cs
+ WorldSetup.cs
+ UI/
+ Research/ResearchList.cs
+ Robots/
+ RobotDisplay.cs
+ RobotList.cs
+ Tutorial/TutorialBubble.cs
+ World/
+ GateRequirementGenerator.cs
+ Layer.cs
+ LightHandler.cs
+ Map.cs
+ MultimeshHandler.cs
+ Pathfinding.cs
+ Placeholder.cs
+ ResourceDistributor.cs
+ Tile.cs
+ TileRenderData.cs
+ WFC.cs
+ World.cs
+
+[metadataReferences]
+/
+ godotsharp/4.6.1/lib/net8.0/GodotSharp.dll
+ godotsharpeditor/4.6.1/lib/net8.0/GodotSharpEditor.dll
+ microsoft.netcore.app.ref/8.0.21/ref/net8.0/
+ Microsoft.CSharp.dll
+ Microsoft.VisualBasic.Core.dll
+ Microsoft.VisualBasic.dll
+ Microsoft.Win32.Primitives.dll
+ Microsoft.Win32.Registry.dll
+ mscorlib.dll
+ netstandard.dll
+ System.AppContext.dll
+ System.Buffers.dll
+ System.Collections.Concurrent.dll
+ System.Collections.dll
+ System.Collections.Immutable.dll
+ System.Collections.NonGeneric.dll
+ System.Collections.Specialized.dll
+ System.ComponentModel.Annotations.dll
+ System.ComponentModel.DataAnnotations.dll
+ System.ComponentModel.dll
+ System.ComponentModel.EventBasedAsync.dll
+ System.ComponentModel.Primitives.dll
+ System.ComponentModel.TypeConverter.dll
+ System.Configuration.dll
+ System.Console.dll
+ System.Core.dll
+ System.Data.Common.dll
+ System.Data.DataSetExtensions.dll
+ System.Data.dll
+ System.Diagnostics.Contracts.dll
+ System.Diagnostics.Debug.dll
+ System.Diagnostics.DiagnosticSource.dll
+ System.Diagnostics.FileVersionInfo.dll
+ System.Diagnostics.Process.dll
+ System.Diagnostics.StackTrace.dll
+ System.Diagnostics.TextWriterTraceListener.dll
+ System.Diagnostics.Tools.dll
+ System.Diagnostics.TraceSource.dll
+ System.Diagnostics.Tracing.dll
+ System.dll
+ System.Drawing.dll
+ System.Drawing.Primitives.dll
+ System.Dynamic.Runtime.dll
+ System.Formats.Asn1.dll
+ System.Formats.Tar.dll
+ System.Globalization.Calendars.dll
+ System.Globalization.dll
+ System.Globalization.Extensions.dll
+ System.IO.Compression.Brotli.dll
+ System.IO.Compression.dll
+ System.IO.Compression.FileSystem.dll
+ System.IO.Compression.ZipFile.dll
+ System.IO.dll
+ System.IO.FileSystem.AccessControl.dll
+ System.IO.FileSystem.dll
+ System.IO.FileSystem.DriveInfo.dll
+ System.IO.FileSystem.Primitives.dll
+ System.IO.FileSystem.Watcher.dll
+ System.IO.IsolatedStorage.dll
+ System.IO.MemoryMappedFiles.dll
+ System.IO.Pipes.AccessControl.dll
+ System.IO.Pipes.dll
+ System.IO.UnmanagedMemoryStream.dll
+ System.Linq.dll
+ System.Linq.Expressions.dll
+ System.Linq.Parallel.dll
+ System.Linq.Queryable.dll
+ System.Memory.dll
+ System.Net.dll
+ System.Net.Http.dll
+ System.Net.Http.Json.dll
+ System.Net.HttpListener.dll
+ System.Net.Mail.dll
+ System.Net.NameResolution.dll
+ System.Net.NetworkInformation.dll
+ System.Net.Ping.dll
+ System.Net.Primitives.dll
+ System.Net.Quic.dll
+ System.Net.Requests.dll
+ System.Net.Security.dll
+ System.Net.ServicePoint.dll
+ System.Net.Sockets.dll
+ System.Net.WebClient.dll
+ System.Net.WebHeaderCollection.dll
+ System.Net.WebProxy.dll
+ System.Net.WebSockets.Client.dll
+ System.Net.WebSockets.dll
+ System.Numerics.dll
+ System.Numerics.Vectors.dll
+ System.ObjectModel.dll
+ System.Reflection.DispatchProxy.dll
+ System.Reflection.dll
+ System.Reflection.Emit.dll
+ System.Reflection.Emit.ILGeneration.dll
+ System.Reflection.Emit.Lightweight.dll
+ System.Reflection.Extensions.dll
+ System.Reflection.Metadata.dll
+ System.Reflection.Primitives.dll
+ System.Reflection.TypeExtensions.dll
+ System.Resources.Reader.dll
+ System.Resources.ResourceManager.dll
+ System.Resources.Writer.dll
+ System.Runtime.CompilerServices.Unsafe.dll
+ System.Runtime.CompilerServices.VisualC.dll
+ System.Runtime.dll
+ System.Runtime.Extensions.dll
+ System.Runtime.Handles.dll
+ System.Runtime.InteropServices.dll
+ System.Runtime.InteropServices.JavaScript.dll
+ System.Runtime.InteropServices.RuntimeInformation.dll
+ System.Runtime.Intrinsics.dll
+ System.Runtime.Loader.dll
+ System.Runtime.Numerics.dll
+ System.Runtime.Serialization.dll
+ System.Runtime.Serialization.Formatters.dll
+ System.Runtime.Serialization.Json.dll
+ System.Runtime.Serialization.Primitives.dll
+ System.Runtime.Serialization.Xml.dll
+ System.Security.AccessControl.dll
+ System.Security.Claims.dll
+ System.Security.Cryptography.Algorithms.dll
+ System.Security.Cryptography.Cng.dll
+ System.Security.Cryptography.Csp.dll
+ System.Security.Cryptography.dll
+ System.Security.Cryptography.Encoding.dll
+ System.Security.Cryptography.OpenSsl.dll
+ System.Security.Cryptography.Primitives.dll
+ System.Security.Cryptography.X509Certificates.dll
+ System.Security.dll
+ System.Security.Principal.dll
+ System.Security.Principal.Windows.dll
+ System.Security.SecureString.dll
+ System.ServiceModel.Web.dll
+ System.ServiceProcess.dll
+ System.Text.Encoding.CodePages.dll
+ System.Text.Encoding.dll
+ System.Text.Encoding.Extensions.dll
+ System.Text.Encodings.Web.dll
+ System.Text.Json.dll
+ System.Text.RegularExpressions.dll
+ System.Threading.Channels.dll
+ System.Threading.dll
+ System.Threading.Overlapped.dll
+ System.Threading.Tasks.Dataflow.dll
+ System.Threading.Tasks.dll
+ System.Threading.Tasks.Extensions.dll
+ System.Threading.Tasks.Parallel.dll
+ System.Threading.Thread.dll
+ System.Threading.ThreadPool.dll
+ System.Threading.Timer.dll
+ System.Transactions.dll
+ System.Transactions.Local.dll
+ System.ValueTuple.dll
+ System.Web.dll
+ System.Web.HttpUtility.dll
+ System.Windows.dll
+ System.Xml.dll
+ System.Xml.Linq.dll
+ System.Xml.ReaderWriter.dll
+ System.Xml.Serialization.dll
+ System.Xml.XDocument.dll
+ System.Xml.XmlDocument.dll
+ System.Xml.XmlSerializer.dll
+ System.Xml.XPath.dll
+ System.Xml.XPath.XDocument.dll
+ WindowsBase.dll
+
+[analyzerReferences]
+/sdk/9.0.306/Sdks/Microsoft.NET.Sdk/analyzers/
+ Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll
+ Microsoft.CodeAnalysis.NetAnalyzers.dll
+/
+ godot.sourcegenerators/4.6.1/analyzers/dotnet/cs/Godot.SourceGenerators.dll
+ microsoft.netcore.app.ref/8.0.21/analyzers/dotnet/cs/
+ Microsoft.Interop.ComInterfaceGenerator.dll
+ Microsoft.Interop.JavaScript.JSImportGenerator.dll
+ Microsoft.Interop.LibraryImportGenerator.dll
+ Microsoft.Interop.SourceGeneration.dll
+ System.Text.Json.SourceGeneration.dll
+ System.Text.RegularExpressions.Generator.dll
+
+[analyzerConfigFiles]
+.editorconfig
+.godot/mono/temp/obj/Debug/RuinAdventurer.GeneratedMSBuildEditorConfig.editorconfig
+/sdk/9.0.306/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_8_default.globalconfig
diff --git a/RuinAdventurer.sln b/RuinAdventurer.sln
new file mode 100644
index 0000000..27e6745
--- /dev/null
+++ b/RuinAdventurer.sln
@@ -0,0 +1,19 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RuinAdventurer", "RuinAdventurer.csproj", "{58DB29A5-5BE5-413A-BB24-673BF011E318}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ ExportDebug|Any CPU = ExportDebug|Any CPU
+ ExportRelease|Any CPU = ExportRelease|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {58DB29A5-5BE5-413A-BB24-673BF011E318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {58DB29A5-5BE5-413A-BB24-673BF011E318}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {58DB29A5-5BE5-413A-BB24-673BF011E318}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU
+ {58DB29A5-5BE5-413A-BB24-673BF011E318}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU
+ {58DB29A5-5BE5-413A-BB24-673BF011E318}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU
+ {58DB29A5-5BE5-413A-BB24-673BF011E318}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Scenes/Game.tscn b/Scenes/Game.tscn
new file mode 100644
index 0000000..74e8ee8
--- /dev/null
+++ b/Scenes/Game.tscn
@@ -0,0 +1,758 @@
+[gd_scene format=3 uid="uid://cgsmfi2s51cbd"]
+
+[ext_resource type="Script" uid="uid://br2udyi6t8yvf" path="res://Scripts/World/World.cs" id="1_7lihs"]
+[ext_resource type="AudioStream" uid="uid://dcb746ldlm6ta" path="res://Assets/Sound/Background.mp3" id="1_rajel"]
+[ext_resource type="AudioStream" uid="uid://d001edcgov542" path="res://Assets/Sound/button.wav" id="2_button"]
+[ext_resource type="AudioStream" uid="uid://bo0jlfldvl3fc" path="res://Assets/Sound/mining.wav" id="2_mining"]
+[ext_resource type="Script" uid="uid://c7khr6oist3ku" path="res://Scripts/UI/Common/Camera3d.cs" id="3_7lihs"]
+[ext_resource type="Script" uid="uid://gvy12mefkwax" path="res://Scripts/Core/SoundManager.cs" id="3_sound"]
+[ext_resource type="Script" uid="uid://bm7knir4552j5" path="res://Scripts/UI/Common/UIHandler.cs" id="4_fgofq"]
+[ext_resource type="Script" uid="uid://bsd6n6b06a4pe" path="res://Scripts/UI/DSL/CodingWindow.cs" id="6_7lihs"]
+[ext_resource type="Script" uid="uid://k6vlo7ulvtep" path="res://Scripts/UI/Robots/RobotList.cs" id="7_2irst"]
+[ext_resource type="PackedScene" uid="uid://cpq7ppe8bw2bq" path="res://Scenes/Options.tscn" id="8_71axn"]
+[ext_resource type="Script" uid="uid://fegfbcnlk8p5" path="res://Scripts/World/Map.cs" id="8_bf53h"]
+[ext_resource type="Texture2D" uid="uid://deuxffyhsrinn" path="res://Assets/Images/EnergySymbol.png" id="9_71axn"]
+[ext_resource type="Texture2D" uid="uid://dje86ro2e37xl" path="res://Assets/Images/Resources/WaterSymbol.png" id="10_71axn"]
+[ext_resource type="Texture2D" uid="uid://d068gyi3e48cv" path="res://Assets/Images/MapSymbol.png" id="11_3cx6b"]
+[ext_resource type="Script" uid="uid://com0u7nqag6pp" path="res://Scripts/UI/Inventory/InventoryDisplay.cs" id="11_acvyw"]
+[ext_resource type="Texture2D" uid="uid://ban872p4eh4gi" path="res://Assets/Images/RobotSymbol.png" id="11_dahhg"]
+[ext_resource type="Texture2D" uid="uid://ciehcg34et0q3" path="res://Assets/Images/InventorySymbol.png" id="11_wxwew"]
+[ext_resource type="Texture2D" uid="uid://b77mo4fhklnja" path="res://Assets/Images/OptionsSymbol.png" id="12_3so38"]
+[ext_resource type="Script" uid="uid://drscsrkfphpy7" path="res://Scripts/UI/Research/ResearchList.cs" id="12_4q8tf"]
+[ext_resource type="Texture2D" uid="uid://dt84awx33mulb" path="res://Assets/Images/ResearchSymbol.png" id="13_alh3a"]
+[ext_resource type="Texture2D" uid="uid://bmcpkt6mae2qi" path="res://Assets/Images/AlarmSign.png" id="13_x3xnh"]
+[ext_resource type="Texture2D" uid="uid://uunnuou4g86w" path="res://Assets/Images/Resources/MushroomSymbol.png" id="14_food"]
+[ext_resource type="Script" uid="uid://d0opysuqksr6l" path="res://Scripts/UI/Tutorial/TutorialBubble.cs" id="15_tutorial"]
+[ext_resource type="Texture2D" uid="uid://b6jm34qgckaxh" path="res://Assets/Images/Nodes/explore.png" id="25_8e0uq"]
+[ext_resource type="Script" uid="uid://dx5oy38ochrw2" path="res://Scripts/UI/DSL/NodeTooltip.cs" id="25_i0vto"]
+
+[sub_resource type="CompressedTexture2D" id="CompressedTexture2D_u44n3"]
+
+[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_u44n3"]
+panorama = SubResource("CompressedTexture2D_u44n3")
+
+[sub_resource type="Sky" id="Sky_u44n3"]
+sky_material = SubResource("PanoramaSkyMaterial_u44n3")
+
+[sub_resource type="Environment" id="Environment_sb48q"]
+background_mode = 1
+background_color = Color(0.27141052, 0.1874483, 0.13788113, 1)
+sky = SubResource("Sky_u44n3")
+glow_enabled = true
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_b2bpf"]
+bg_color = Color(0.053073194, 0.053073194, 0.053073194, 1)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_7lihs"]
+bg_color = Color(0, 0, 0, 0.7647059)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_fgofq"]
+bg_color = Color(0.36567047, 0.46785766, 0.7000103, 0.5176471)
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bf53h"]
+bg_color = Color(0.3106171, 0.31061712, 0.310617, 1)
+
+[node name="Main" type="Node3D" unique_id=234207355]
+
+[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1677185223]
+stream = ExtResource("1_rajel")
+volume_db = -25.0
+autoplay = true
+parameters/looping = true
+
+[node name="ButtonSound" type="AudioStreamPlayer" parent="." unique_id=696063778]
+stream = ExtResource("2_button")
+volume_db = -8.0
+
+[node name="MiningSound" type="AudioStreamPlayer" parent="." unique_id=1851503354]
+stream = ExtResource("2_mining")
+volume_db = -12.0
+
+[node name="SoundManager" type="Node" parent="." unique_id=1220134706 node_paths=PackedStringArray("buttonSound", "miningSound")]
+script = ExtResource("3_sound")
+buttonSound = NodePath("../ButtonSound")
+miningSound = NodePath("../MiningSound")
+
+[node name="World" type="Node3D" parent="." unique_id=770208789]
+script = ExtResource("1_7lihs")
+
+[node name="Camera3D" type="Camera3D" parent="." unique_id=161504606]
+transform = Transform3D(1, 0, 0, 0, 0.25881907, 0.9659258, 0, -0.9659258, 0.25881907, 30, 20, 30)
+current = true
+script = ExtResource("3_7lihs")
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="." unique_id=377970686]
+environment = SubResource("Environment_sb48q")
+
+[node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=1558432386]
+follow_viewport_enabled = true
+
+[node name="UIHandler" type="Control" parent="CanvasLayer" unique_id=1713248285 node_paths=PackedStringArray("codingWindow", "robotList", "mainCam", "map", "FPS", "options", "uiContent", "menu", "inventory", "researchList", "robotAlarm", "energyLabel", "waterLabel", "hungerLabel", "survivalStatus", "currentLayer", "deepestLayer", "unlockLayer", "gameOver")]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_right = -2.0
+offset_bottom = 2.0
+grow_horizontal = 2
+grow_vertical = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+script = ExtResource("4_fgofq")
+codingWindow = NodePath("MainUI/Content/CodingWindow")
+robotList = NodePath("MainUI/Content/RobotList")
+mainCam = NodePath("../../Camera3D")
+map = NodePath("MainUI/Content/Map")
+FPS = NodePath("MainUI/FooterContainer/HBoxContainer/FPS")
+options = NodePath("MainUI/Content/Options")
+uiContent = NodePath("MainUI/Content")
+menu = NodePath("MainUI/Content/Menu")
+inventory = NodePath("MainUI/Content/Inventory")
+researchList = NodePath("MainUI/Content/Research")
+robotAlarm = NodePath("MainUI/FooterContainer/HBoxContainer/RobotAlarm")
+energyLabel = NodePath("MainUI/HeaderContainer/VBoxContainer/RowEnergy/RichTextLabel")
+waterLabel = NodePath("MainUI/HeaderContainer/VBoxContainer/RowWater/RichTextLabel")
+hungerLabel = NodePath("MainUI/HeaderContainer/VBoxContainer/RowHunger/RichTextLabel")
+survivalStatus = NodePath("MainUI/HeaderContainer/VBoxContainer/SurvivalStatus")
+currentLayer = NodePath("MainUI/HeaderContainer/VBoxContainer/CurrentLayer")
+deepestLayer = NodePath("MainUI/HeaderContainer/VBoxContainer/DeepestLayer")
+unlockLayer = NodePath("MainUI/HeaderContainer/VBoxContainer/UnlockLayer")
+gameOver = NodePath("MainUI/Content/GameOver")
+
+[node name="MainUI" type="VBoxContainer" parent="CanvasLayer/UIHandler" unique_id=1437975209]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="HeaderContainer" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI" unique_id=1744492333]
+custom_minimum_size = Vector2(0, 48)
+layout_mode = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_b2bpf")
+
+[node name="VBoxContainer" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer" unique_id=536619770]
+layout_mode = 2
+theme_override_constants/separation = 12
+
+[node name="RowEnergy" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer" unique_id=867560906]
+layout_mode = 2
+
+[node name="TextureRect" type="TextureRect" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowEnergy" unique_id=1218525340]
+layout_mode = 2
+texture = ExtResource("9_71axn")
+expand_mode = 2
+
+[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowEnergy" unique_id=32920400]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Energy: 100/100"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="RowWater" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer" unique_id=465013052]
+layout_mode = 2
+
+[node name="TextureRect" type="TextureRect" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowWater" unique_id=1398125603]
+layout_mode = 2
+texture = ExtResource("10_71axn")
+expand_mode = 2
+
+[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowWater" unique_id=1355787203]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Thirst: 100/100"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="RowHunger" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer" unique_id=86109825]
+layout_mode = 2
+
+[node name="TextureRect" type="TextureRect" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowHunger" unique_id=866450525]
+layout_mode = 2
+texture = ExtResource("14_food")
+expand_mode = 2
+
+[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/RowHunger" unique_id=1098660752]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Hunger: 100/100"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="SurvivalStatus" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer" unique_id=1364578228]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Survival stable"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="CurrentLayer" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer" unique_id=1435047910]
+layout_mode = 2
+text = "Layer: 1/10"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="DeepestLayer" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer" unique_id=600512938]
+layout_mode = 2
+text = "Gate depth: 0"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="UnlockLayer" type="Button" parent="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer" unique_id=1538511285]
+layout_mode = 2
+tooltip_text = "Open the next gate when all requirements are available"
+text = "Open Gate"
+
+[node name="Content" type="Control" parent="CanvasLayer/UIHandler/MainUI" unique_id=45665557]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="CodingWindow" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=1576652491 node_paths=PackedStringArray("codeBlocks", "editorWindow", "availableScripts", "scriptName", "nameInput", "nodeTooltip")]
+visible = false
+layout_mode = 1
+anchors_preset = 11
+anchor_left = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -948.0
+grow_horizontal = 0
+grow_vertical = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_7lihs")
+script = ExtResource("6_7lihs")
+codeBlocks = NodePath("VBoxContainer/Scripting/CodeBlocks/VBoxContainer")
+editorWindow = NodePath("VBoxContainer/Scripting/EditorWindow/CodeContainer")
+availableScripts = NodePath("VBoxContainer/Scripting/EditorWindow/Load")
+scriptName = NodePath("VBoxContainer/Scripting/EditorWindow/Saving/ScriptName")
+nameInput = NodePath("VBoxContainer/Renaming/LineEdit")
+nodeTooltip = NodePath("../../../NodeTooltip")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" unique_id=582741975]
+layout_mode = 2
+theme_override_constants/separation = 20
+
+[node name="Title" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer" unique_id=90213926]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=32]Robot Script[/font_size]"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+
+[node name="Renaming" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer" unique_id=408911504]
+layout_mode = 2
+theme_override_constants/separation = 20
+
+[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Renaming" unique_id=1964282829]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Robot name"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="LineEdit" type="LineEdit" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Renaming" unique_id=1713423672]
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Name..."
+max_length = 24
+
+[node name="Button" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Renaming" unique_id=279251707]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Apply"
+
+[node name="CheckBox" type="CheckBox" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Renaming" unique_id=1340883792]
+layout_mode = 2
+button_pressed = true
+text = "Show on map"
+
+[node name="Scripting" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer" unique_id=1934932205]
+layout_mode = 2
+size_flags_vertical = 3
+
+[node name="CodeBlocks" type="ScrollContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting" unique_id=1196874464]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 0.5
+theme_override_styles/panel = SubResource("StyleBoxFlat_fgofq")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/CodeBlocks" unique_id=1751038712]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 10
+
+[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/CodeBlocks/VBoxContainer" unique_id=1914788051]
+layout_mode = 2
+text = "Click to add
+[DEL] to remove"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="EditorWindow" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting" unique_id=919757187]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_stretch_ratio = 2.0
+
+[node name="CodeContainer" type="GraphEdit" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow" unique_id=1600198848]
+layout_mode = 2
+size_flags_vertical = 3
+right_disconnects = true
+
+[node name="Buttons" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow" unique_id=265797151]
+layout_mode = 2
+theme_override_constants/separation = 20
+
+[node name="Clear" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons" unique_id=1191148341]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Clear"
+
+[node name="Compile" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons" unique_id=1559980287]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Compile"
+
+[node name="Saving" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow" unique_id=404804163]
+layout_mode = 2
+theme_override_constants/separation = 20
+
+[node name="ScriptName" type="LineEdit" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving" unique_id=1831524214]
+layout_mode = 2
+size_flags_horizontal = 3
+placeholder_text = "Name..."
+max_length = 24
+
+[node name="Save" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving" unique_id=599810254]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Save"
+
+[node name="Delete" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving" unique_id=1891250978]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Delete"
+
+[node name="Load" type="OptionButton" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow" unique_id=970393437]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Close" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer" unique_id=200541741]
+layout_mode = 2
+text = "Close"
+
+[node name="RobotList" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=1469962195 node_paths=PackedStringArray("robotList", "selectableRobots", "spawnRobot")]
+visible = false
+layout_mode = 1
+anchors_preset = 11
+anchor_left = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -711.0
+grow_horizontal = 0
+grow_vertical = 2
+script = ExtResource("7_2irst")
+robotList = NodePath("VBoxContainer/ScrollContainer/VBoxContainer")
+selectableRobots = NodePath("VBoxContainer/Spawning/OptionButton")
+spawnRobot = NodePath("VBoxContainer/Spawning/Button")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/RobotList" unique_id=1958138787]
+layout_mode = 2
+
+[node name="Title" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer" unique_id=1891991275]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=32]Robots[/font_size]"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+
+[node name="Spawning" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer" unique_id=243501895]
+layout_mode = 2
+theme_override_constants/separation = 26
+
+[node name="OptionButton" type="OptionButton" parent="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer/Spawning" unique_id=1230415850]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Button" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer/Spawning" unique_id=1513188650]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Spawn robot"
+
+[node name="ScrollContainer" type="ScrollContainer" parent="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer" unique_id=592644944]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_fgofq")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer/ScrollContainer" unique_id=963718788]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 10
+
+[node name="Map" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=292881873 node_paths=PackedStringArray("grid")]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+theme_override_styles/panel = SubResource("StyleBoxFlat_bf53h")
+script = ExtResource("8_bf53h")
+grid = NodePath("CenterContainer/AspectRatioContainer/GridContainer")
+
+[node name="CenterContainer" type="CenterContainer" parent="CanvasLayer/UIHandler/MainUI/Content/Map" unique_id=1126051644]
+layout_mode = 2
+
+[node name="AspectRatioContainer" type="AspectRatioContainer" parent="CanvasLayer/UIHandler/MainUI/Content/Map/CenterContainer" unique_id=871200780]
+layout_mode = 2
+
+[node name="GridContainer" type="GridContainer" parent="CanvasLayer/UIHandler/MainUI/Content/Map/CenterContainer/AspectRatioContainer" unique_id=981768520]
+layout_mode = 2
+theme_override_constants/h_separation = 10
+theme_override_constants/v_separation = 10
+
+[node name="Menu" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=370952082]
+visible = false
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -120.0
+offset_top = -125.0
+offset_right = 120.0
+offset_bottom = 125.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/Menu" unique_id=1924672355]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="Button" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer" unique_id=1938871792]
+layout_mode = 2
+text = "Continue"
+
+[node name="Button2" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer" unique_id=1298589221]
+layout_mode = 2
+text = "Options"
+
+[node name="SaveGame" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer" unique_id=64628838]
+layout_mode = 2
+text = "Save Game"
+
+[node name="LoadGame" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer" unique_id=857119875]
+layout_mode = 2
+text = "Load Game"
+
+[node name="Button3" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer" unique_id=2028306785]
+layout_mode = 2
+text = "Exit"
+
+[node name="Options" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=230632848 instance=ExtResource("8_71axn")]
+visible = false
+layout_mode = 1
+
+[node name="Inventory" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=407422598 node_paths=PackedStringArray("itemList", "inventorySpace")]
+visible = false
+layout_mode = 1
+anchors_preset = 11
+anchor_left = 1.0
+anchor_right = 1.0
+anchor_bottom = 1.0
+offset_left = -525.0
+grow_horizontal = 0
+grow_vertical = 2
+script = ExtResource("11_acvyw")
+itemList = NodePath("VBoxContainer/ScrollContainer/VBoxContainer")
+inventorySpace = NodePath("VBoxContainer/RichTextLabel")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/Inventory" unique_id=1776118554]
+layout_mode = 2
+
+[node name="Title" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/Inventory/VBoxContainer" unique_id=1356874082]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=32]Inventory[/font_size]"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+
+[node name="RichTextLabel" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/Inventory/VBoxContainer" unique_id=1572448693]
+layout_mode = 2
+text = "Storage"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+
+[node name="ScrollContainer" type="ScrollContainer" parent="CanvasLayer/UIHandler/MainUI/Content/Inventory/VBoxContainer" unique_id=1743833752]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_styles/panel = SubResource("StyleBoxFlat_fgofq")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/Inventory/VBoxContainer/ScrollContainer" unique_id=158199023]
+layout_mode = 2
+size_flags_horizontal = 3
+size_flags_vertical = 3
+theme_override_constants/separation = 10
+
+[node name="Research" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=1416471893 node_paths=PackedStringArray("researchGraph")]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("12_4q8tf")
+researchGraph = NodePath("ResearchGraph")
+
+[node name="ResearchGraph" type="GraphEdit" parent="CanvasLayer/UIHandler/MainUI/Content/Research" unique_id=414756119]
+layout_mode = 2
+
+[node name="GameOver" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI/Content" unique_id=1632783321]
+visible = false
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/GameOver" unique_id=12941042]
+layout_mode = 2
+theme_override_constants/separation = 12
+
+[node name="Title" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/GameOver/VBoxContainer" unique_id=622403000]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=64]Game Over[/font_size]"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="Content" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/Content/GameOver/VBoxContainer" unique_id=521357002]
+layout_mode = 2
+size_flags_vertical = 3
+bbcode_enabled = true
+text = "[font_size=32]You finished the game!
+
+Thank you for playing."
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/Content/GameOver/VBoxContainer" unique_id=1526010823]
+layout_mode = 2
+theme_override_constants/separation = 42
+
+[node name="Negative" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/GameOver/VBoxContainer/HBoxContainer" unique_id=1328278845]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Main Menu"
+
+[node name="Positive" type="Button" parent="CanvasLayer/UIHandler/MainUI/Content/GameOver/VBoxContainer/HBoxContainer" unique_id=912287210]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Continue"
+
+[node name="FooterContainer" type="PanelContainer" parent="CanvasLayer/UIHandler/MainUI" unique_id=1495029884]
+custom_minimum_size = Vector2(0, 48)
+layout_mode = 2
+
+[node name="HBoxContainer" type="HBoxContainer" parent="CanvasLayer/UIHandler/MainUI/FooterContainer" unique_id=1782436702]
+layout_mode = 2
+theme_override_constants/separation = 20
+alignment = 2
+
+[node name="FPS" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=2029942501]
+layout_mode = 2
+fit_content = true
+autowrap_mode = 0
+
+[node name="RAM" type="RichTextLabel" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=1663333928]
+layout_mode = 2
+fit_content = true
+autowrap_mode = 0
+
+[node name="RobotAlarm" type="TextureRect" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=439539299]
+layout_mode = 2
+texture = ExtResource("13_x3xnh")
+expand_mode = 2
+
+[node name="Inventory" type="TextureButton" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=1598826377]
+layout_mode = 2
+tooltip_text = "Inventory (I)"
+texture_normal = ExtResource("11_wxwew")
+
+[node name="Research" type="TextureButton" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=1945067079]
+layout_mode = 2
+tooltip_text = "Research (T)"
+texture_normal = ExtResource("13_alh3a")
+
+[node name="Map" type="TextureButton" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=1320346210]
+layout_mode = 2
+tooltip_text = "Map (M)"
+texture_normal = ExtResource("11_3cx6b")
+
+[node name="Robots" type="TextureButton" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=1666653447]
+layout_mode = 2
+tooltip_text = "Robots (R)"
+texture_normal = ExtResource("11_dahhg")
+
+[node name="Options" type="TextureButton" parent="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer" unique_id=1930105751]
+layout_mode = 2
+tooltip_text = "Menu (ESC)"
+texture_normal = ExtResource("12_3so38")
+
+[node name="TutorialBubble" type="PanelContainer" parent="CanvasLayer/UIHandler" unique_id=139447375 node_paths=PackedStringArray("speakerLabel", "contentLabel", "progressLabel", "nextButton", "skipButton")]
+layout_mode = 1
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_left = 24.0
+offset_top = -236.0
+offset_right = 544.0
+offset_bottom = -72.0
+grow_vertical = 0
+script = ExtResource("15_tutorial")
+speakerLabel = NodePath("MarginContainer/VBoxContainer/Header/Speaker")
+contentLabel = NodePath("MarginContainer/VBoxContainer/Content")
+progressLabel = NodePath("MarginContainer/VBoxContainer/Footer/Progress")
+nextButton = NodePath("MarginContainer/VBoxContainer/Footer/Next")
+skipButton = NodePath("MarginContainer/VBoxContainer/Footer/Skip")
+
+[node name="MarginContainer" type="MarginContainer" parent="CanvasLayer/UIHandler/TutorialBubble" unique_id=595645283]
+layout_mode = 2
+theme_override_constants/margin_left = 14
+theme_override_constants/margin_top = 12
+theme_override_constants/margin_right = 14
+theme_override_constants/margin_bottom = 12
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer" unique_id=472340302]
+layout_mode = 2
+theme_override_constants/separation = 8
+
+[node name="Header" type="HBoxContainer" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer/VBoxContainer" unique_id=1494044175]
+layout_mode = 2
+
+[node name="Speaker" type="RichTextLabel" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer/VBoxContainer/Header" unique_id=6312943]
+layout_mode = 2
+size_flags_horizontal = 3
+bbcode_enabled = true
+text = "[font_size=22]B.O.B.[/font_size]"
+fit_content = true
+autowrap_mode = 0
+
+[node name="Content" type="RichTextLabel" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer/VBoxContainer" unique_id=1383456691]
+layout_mode = 2
+size_flags_vertical = 3
+text = "Tutorial"
+fit_content = true
+autowrap_mode = 2
+
+[node name="Footer" type="HBoxContainer" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer/VBoxContainer" unique_id=1668210204]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="Progress" type="RichTextLabel" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer/VBoxContainer/Footer" unique_id=997565175]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "1/1"
+fit_content = true
+autowrap_mode = 0
+
+[node name="Skip" type="Button" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer/VBoxContainer/Footer" unique_id=1021113387]
+layout_mode = 2
+text = "Skip"
+
+[node name="Next" type="Button" parent="CanvasLayer/UIHandler/TutorialBubble/MarginContainer/VBoxContainer/Footer" unique_id=275029805]
+layout_mode = 2
+text = "Next"
+
+[node name="NodeTooltip" type="PanelContainer" parent="CanvasLayer/UIHandler" unique_id=642635777 node_paths=PackedStringArray("title", "image", "description")]
+visible = false
+layout_mode = 0
+offset_right = 40.0
+offset_bottom = 40.0
+script = ExtResource("25_i0vto")
+title = NodePath("VBoxContainer/Title")
+image = NodePath("VBoxContainer/NodeVisual")
+description = NodePath("VBoxContainer/Description")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CanvasLayer/UIHandler/NodeTooltip" unique_id=1125936946]
+layout_mode = 2
+
+[node name="Title" type="RichTextLabel" parent="CanvasLayer/UIHandler/NodeTooltip/VBoxContainer" unique_id=1945362386]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=18]Explore"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[node name="NodeVisual" type="TextureRect" parent="CanvasLayer/UIHandler/NodeTooltip/VBoxContainer" unique_id=2114186555]
+layout_mode = 2
+texture = ExtResource("25_8e0uq")
+
+[node name="Description" type="RichTextLabel" parent="CanvasLayer/UIHandler/NodeTooltip/VBoxContainer" unique_id=1191195331]
+layout_mode = 2
+text = "Using this node you can randomly explore the ruin.
+It continues exploring until all tiles were visited."
+fit_content = true
+horizontal_alignment = 1
+vertical_alignment = 1
+
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/HeaderContainer/VBoxContainer/UnlockLayer" to="CanvasLayer/UIHandler" method="UnlockLayer"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Renaming/Button" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="SaveRobotName"]
+[connection signal="toggled" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Renaming/CheckBox" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="OnMapToggled"]
+[connection signal="connection_request" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/CodeContainer" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="OnNodeConnect"]
+[connection signal="disconnection_request" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/CodeContainer" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="OnNodeDisconnect"]
+[connection signal="node_deselected" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/CodeContainer" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="OnNodeDeselect"]
+[connection signal="node_selected" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/CodeContainer" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="OnNodeSelect"]
+[connection signal="button_up" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons/Clear" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="ClearWindow"]
+[connection signal="button_up" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Buttons/Compile" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="CompileProgram"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving/Save" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="SaveProgram"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Saving/Delete" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="DeleteProgram"]
+[connection signal="item_selected" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Scripting/EditorWindow/Load" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="LoadProgram"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/CodingWindow/VBoxContainer/Close" to="CanvasLayer/UIHandler/MainUI/Content/CodingWindow" method="CloseWindow"]
+[connection signal="item_selected" from="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer/Spawning/OptionButton" to="CanvasLayer/UIHandler/MainUI/Content/RobotList" method="OnRobotSelect"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/RobotList/VBoxContainer/Spawning/Button" to="CanvasLayer/UIHandler/MainUI/Content/RobotList" method="SpawnRobot"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer/Button" to="CanvasLayer/UIHandler" method="HandleMenu"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer/Button2" to="CanvasLayer/UIHandler" method="ShowOptions"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer/SaveGame" to="CanvasLayer/UIHandler" method="SaveGame"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer/LoadGame" to="CanvasLayer/UIHandler" method="LoadGame"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/Menu/VBoxContainer/Button3" to="CanvasLayer/UIHandler" method="ExitGame"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/GameOver/VBoxContainer/HBoxContainer/Negative" to="CanvasLayer/UIHandler" method="ExitGame"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/Content/GameOver/VBoxContainer/HBoxContainer/Positive" to="CanvasLayer/UIHandler" method="HideGameOver"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer/Inventory" to="CanvasLayer/UIHandler" method="HandleInventoryButton"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer/Research" to="CanvasLayer/UIHandler" method="HandleResearchButton"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer/Map" to="CanvasLayer/UIHandler" method="HandleMapButton"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer/Robots" to="CanvasLayer/UIHandler" method="HandleRobotListButton"]
+[connection signal="pressed" from="CanvasLayer/UIHandler/MainUI/FooterContainer/HBoxContainer/Options" to="CanvasLayer/UIHandler" method="HandleMenuButton"]
diff --git a/Scenes/MainMenu.tscn b/Scenes/MainMenu.tscn
new file mode 100644
index 0000000..2aa8e0c
--- /dev/null
+++ b/Scenes/MainMenu.tscn
@@ -0,0 +1,164 @@
+[gd_scene format=3 uid="uid://dlommaelbbw2b"]
+
+[ext_resource type="Script" uid="uid://dda0bhhqspbr0" path="res://Scripts/UI/Menus/MainMenu.cs" id="1_tt5f1"]
+[ext_resource type="AudioStream" uid="uid://dcb746ldlm6ta" path="res://Assets/Sound/Background.mp3" id="2_8um5k"]
+[ext_resource type="Texture2D" uid="uid://ban872p4eh4gi" path="res://Assets/Images/RobotSymbol.png" id="2_853f1"]
+[ext_resource type="Texture2D" uid="uid://dt84awx33mulb" path="res://Assets/Images/ResearchSymbol.png" id="3_df05h"]
+[ext_resource type="Texture2D" uid="uid://dm0w2gtcsa5l4" path="res://Assets/Images/Items/Batteryv1Symbol.png" id="4_8um5k"]
+[ext_resource type="Texture2D" uid="uid://qqprre8xl8gv" path="res://Assets/Images/Items/Dynamov2Symbol.png" id="5_xim88"]
+[ext_resource type="PackedScene" uid="uid://cpq7ppe8bw2bq" path="res://Scenes/Options.tscn" id="6_options"]
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_bnhvo"]
+bg_color = Color(0.24115604, 0.24115607, 0.24115595, 1)
+border_width_top = 1
+border_width_bottom = 1
+border_color = Color(0.025273602, 0.38374466, 0.4973219, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_tt5f1"]
+bg_color = Color(0.46618086, 0.4661808, 0.4661808, 1)
+border_width_top = 1
+border_width_bottom = 1
+border_color = Color(0.025273602, 0.38374466, 0.4973219, 1)
+corner_radius_top_left = 10
+corner_radius_top_right = 10
+corner_radius_bottom_right = 10
+corner_radius_bottom_left = 10
+
+[node name="MainMenu" type="Control" unique_id=622011874 node_paths=PackedStringArray("options")]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_tt5f1")
+options = NodePath("Options")
+
+[node name="AudioStreamPlayer" type="AudioStreamPlayer" parent="." unique_id=1984828000]
+stream = ExtResource("2_8um5k")
+volume_db = -25.0
+autoplay = true
+parameters/looping = true
+
+[node name="Decoration" type="Control" parent="." unique_id=252748481]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="TextureRect" type="TextureRect" parent="Decoration" unique_id=175718935]
+layout_mode = 0
+offset_left = 19.0
+offset_top = 960.0
+offset_right = 51.0
+offset_bottom = 992.0
+scale = Vector2(3, 3)
+texture = ExtResource("2_853f1")
+
+[node name="TextureRect2" type="TextureRect" parent="Decoration" unique_id=1274351412]
+layout_mode = 0
+offset_left = 1730.0001
+offset_top = 51.0
+offset_right = 1770.0001
+offset_bottom = 91.0
+scale = Vector2(3, 3)
+texture = ExtResource("3_df05h")
+
+[node name="TextureRect3" type="TextureRect" parent="Decoration" unique_id=1496393482]
+layout_mode = 0
+offset_left = 517.0
+offset_top = 264.0
+offset_right = 557.0
+offset_bottom = 304.0
+scale = Vector2(3, 3)
+texture = ExtResource("4_8um5k")
+
+[node name="TextureRect4" type="TextureRect" parent="Decoration" unique_id=606748204]
+layout_mode = 0
+offset_left = 1443.0
+offset_top = 538.0
+offset_right = 1483.0
+offset_bottom = 578.0
+scale = Vector2(3, 3)
+texture = ExtResource("5_xim88")
+
+[node name="Panel" type="Panel" parent="." unique_id=167014122]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="CenterContainer" type="CenterContainer" parent="." unique_id=546163427]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -4.0
+offset_top = -126.5
+offset_right = 4.0
+offset_bottom = 126.5
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer" unique_id=1309259619]
+layout_mode = 2
+theme_override_constants/separation = 40
+
+[node name="RichTextLabel" type="RichTextLabel" parent="CenterContainer/VBoxContainer" unique_id=195514567]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=50]Ruin Adventurer[/font_size]"
+fit_content = true
+scroll_active = false
+autowrap_mode = 0
+horizontal_alignment = 1
+
+[node name="Spacer" type="Panel" parent="CenterContainer/VBoxContainer" unique_id=1075556994]
+layout_mode = 2
+
+[node name="btnPlay" type="Button" parent="CenterContainer/VBoxContainer" unique_id=452402808]
+custom_minimum_size = Vector2(220, 42)
+layout_mode = 2
+theme_override_styles/normal = SubResource("StyleBoxFlat_bnhvo")
+theme_override_styles/hover = SubResource("StyleBoxFlat_tt5f1")
+text = "Start Game"
+
+[node name="btnLoad" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1256807224]
+custom_minimum_size = Vector2(220, 42)
+layout_mode = 2
+theme_override_styles/normal = SubResource("StyleBoxFlat_bnhvo")
+theme_override_styles/hover = SubResource("StyleBoxFlat_tt5f1")
+text = "Load Game"
+
+[node name="btnOptions" type="Button" parent="CenterContainer/VBoxContainer" unique_id=891656915]
+custom_minimum_size = Vector2(220, 42)
+layout_mode = 2
+theme_override_styles/normal = SubResource("StyleBoxFlat_bnhvo")
+theme_override_styles/hover = SubResource("StyleBoxFlat_tt5f1")
+text = "Options"
+
+[node name="btnExit" type="Button" parent="CenterContainer/VBoxContainer" unique_id=2025231658]
+custom_minimum_size = Vector2(220, 42)
+layout_mode = 2
+theme_override_styles/normal = SubResource("StyleBoxFlat_bnhvo")
+theme_override_styles/hover = SubResource("StyleBoxFlat_tt5f1")
+text = "Exit Game"
+
+[node name="Options" parent="." instance=ExtResource("6_options")]
+visible = false
+layout_mode = 1
+
+[connection signal="button_up" from="CenterContainer/VBoxContainer/btnPlay" to="." method="OnPlayPressed"]
+[connection signal="button_up" from="CenterContainer/VBoxContainer/btnLoad" to="." method="OnLoadPressed"]
+[connection signal="button_up" from="CenterContainer/VBoxContainer/btnOptions" to="." method="OnOptionsPressed"]
+[connection signal="button_up" from="CenterContainer/VBoxContainer/btnExit" to="." method="OnQuitPressed"]
diff --git a/Scenes/MovieScene.tscn b/Scenes/MovieScene.tscn
new file mode 100644
index 0000000..4751a7a
--- /dev/null
+++ b/Scenes/MovieScene.tscn
@@ -0,0 +1,412 @@
+[gd_scene format=3 uid="uid://cgfprug5uguga"]
+
+[ext_resource type="Texture2D" uid="uid://dh24miynbbdvy" path="res://Assets/Movies/RoomBasic.png" id="1_njogg"]
+[ext_resource type="Texture2D" uid="uid://tp3w3lkitu85" path="res://Assets/Movies/CaveBasic.png" id="2_c2vvq"]
+[ext_resource type="Texture2D" uid="uid://21wyfeqhci52" path="res://Assets/Movies/CaveInside.png" id="3_c2vvq"]
+
+[sub_resource type="Animation" id="Animation_c2vvq"]
+resource_name = "Intro"
+length = 19.0
+loop_mode = 1
+tracks/0/type = "method"
+tracks/0/imported = false
+tracks/0/enabled = true
+tracks/0/path = NodePath("Intro/Room")
+tracks/0/interp = 1
+tracks/0/loop_wrap = true
+tracks/0/keys = {
+"times": PackedFloat32Array(0, 0.0001, 10, 10.0001),
+"transitions": PackedFloat32Array(1, 1, 1, 1),
+"values": [{
+"args": [&"Intro", 1.0, false],
+"method": &"play"
+}, {
+"args": [],
+"method": &"show"
+}, {
+"args": [],
+"method": &"stop"
+}, {
+"args": [],
+"method": &"hide"
+}]
+}
+tracks/1/type = "method"
+tracks/1/imported = false
+tracks/1/enabled = true
+tracks/1/path = NodePath("Intro/CaveOutside")
+tracks/1/interp = 1
+tracks/1/loop_wrap = true
+tracks/1/keys = {
+"times": PackedFloat32Array(9.966666, 10, 12, 12.033181),
+"transitions": PackedFloat32Array(1, 1, 1, 1),
+"values": [{
+"args": [],
+"method": &"show"
+}, {
+"args": [&"Intro", 1.0, false],
+"method": &"play"
+}, {
+"args": [],
+"method": &"stop"
+}, {
+"args": [],
+"method": &"hide"
+}]
+}
+tracks/2/type = "method"
+tracks/2/imported = false
+tracks/2/enabled = true
+tracks/2/path = NodePath("Intro/CaveInside")
+tracks/2/interp = 1
+tracks/2/loop_wrap = true
+tracks/2/keys = {
+"times": PackedFloat32Array(12, 12.008422),
+"transitions": PackedFloat32Array(1, 1),
+"values": [{
+"args": [&"Intro", 1.0, false],
+"method": &"play"
+}, {
+"args": [],
+"method": &"show"
+}]
+}
+
+[sub_resource type="Animation" id="Animation_oj3vx"]
+length = 0.001
+
+[sub_resource type="AnimationLibrary" id="AnimationLibrary_oj3vx"]
+_data = {
+&"Intro": SubResource("Animation_c2vvq"),
+&"RESET": SubResource("Animation_oj3vx")
+}
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_oj3vx"]
+atlas = ExtResource("1_njogg")
+region = Rect2(0, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_482wn"]
+atlas = ExtResource("1_njogg")
+region = Rect2(640, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ehh24"]
+atlas = ExtResource("1_njogg")
+region = Rect2(1280, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_v5cgf"]
+atlas = ExtResource("1_njogg")
+region = Rect2(1920, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_fktcp"]
+atlas = ExtResource("1_njogg")
+region = Rect2(2560, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_0aubd"]
+atlas = ExtResource("1_njogg")
+region = Rect2(3200, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_er3xh"]
+atlas = ExtResource("1_njogg")
+region = Rect2(3840, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_5c7l7"]
+atlas = ExtResource("1_njogg")
+region = Rect2(4480, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_72hxa"]
+atlas = ExtResource("1_njogg")
+region = Rect2(5120, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_rtxt4"]
+atlas = ExtResource("1_njogg")
+region = Rect2(5760, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_vr0oa"]
+atlas = ExtResource("1_njogg")
+region = Rect2(6400, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_1sxx2"]
+atlas = ExtResource("1_njogg")
+region = Rect2(7040, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_tpan2"]
+atlas = ExtResource("1_njogg")
+region = Rect2(7680, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_6h47n"]
+atlas = ExtResource("1_njogg")
+region = Rect2(8320, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_gx4y0"]
+atlas = ExtResource("1_njogg")
+region = Rect2(8960, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_c7imp"]
+atlas = ExtResource("1_njogg")
+region = Rect2(9600, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_o6sy3"]
+atlas = ExtResource("1_njogg")
+region = Rect2(10240, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_fa642"]
+atlas = ExtResource("1_njogg")
+region = Rect2(10880, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_q04bn"]
+atlas = ExtResource("1_njogg")
+region = Rect2(11520, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_gg1o7"]
+atlas = ExtResource("1_njogg")
+region = Rect2(12160, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_el80b"]
+atlas = ExtResource("1_njogg")
+region = Rect2(12800, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_p3ybh"]
+atlas = ExtResource("1_njogg")
+region = Rect2(13440, 0, 640, 360)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_8qa50"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_oj3vx")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_482wn")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ehh24")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_v5cgf")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_fktcp")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_0aubd")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_er3xh")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_5c7l7")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_72hxa")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_rtxt4")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_vr0oa")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_1sxx2")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_tpan2")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_6h47n")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_gx4y0")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_c7imp")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_o6sy3")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_fa642")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_q04bn")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_gg1o7")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_el80b")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_p3ybh")
+}],
+"loop": false,
+"name": &"Intro",
+"speed": 2.0
+}, {
+"frames": [],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_6bde2"]
+atlas = ExtResource("2_c2vvq")
+region = Rect2(0, 0, 640, 360)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_lnnri"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_6bde2")
+}],
+"loop": false,
+"name": &"Intro",
+"speed": 2.0
+}, {
+"frames": [],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_8qa50"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(0, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_lnnri"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(640, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_h0n42"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(1280, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_4sevv"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(1920, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ckiwx"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(2560, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_63teq"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(3200, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_lc2dr"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(3840, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_ocvb0"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(4480, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_xeiww"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(5120, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_4psnh"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(5760, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_1oj60"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(6400, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_4amjm"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(7040, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_17kmt"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(7680, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_cjnhs"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(8320, 0, 640, 360)
+
+[sub_resource type="AtlasTexture" id="AtlasTexture_t1pho"]
+atlas = ExtResource("3_c2vvq")
+region = Rect2(8960, 0, 640, 360)
+
+[sub_resource type="SpriteFrames" id="SpriteFrames_juhbr"]
+animations = [{
+"frames": [{
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_8qa50")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_lnnri")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_h0n42")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_4sevv")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ckiwx")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_63teq")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_lc2dr")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_ocvb0")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_xeiww")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_4psnh")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_1oj60")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_4amjm")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_17kmt")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_cjnhs")
+}, {
+"duration": 1.0,
+"texture": SubResource("AtlasTexture_t1pho")
+}],
+"loop": false,
+"name": &"Intro",
+"speed": 2.0
+}, {
+"frames": [],
+"loop": true,
+"name": &"default",
+"speed": 5.0
+}]
+
+[node name="MovieScene" type="Node2D" unique_id=624004114]
+
+[node name="AnimationPlayer" type="AnimationPlayer" parent="." unique_id=1314341296]
+libraries/ = SubResource("AnimationLibrary_oj3vx")
+autoplay = &"Intro"
+
+[node name="Intro" type="Node2D" parent="." unique_id=1049669092]
+position = Vector2(960, 540)
+scale = Vector2(3, 3)
+
+[node name="Room" type="AnimatedSprite2D" parent="Intro" unique_id=649123949]
+visible = false
+sprite_frames = SubResource("SpriteFrames_8qa50")
+animation = &"Intro"
+
+[node name="CaveOutside" type="AnimatedSprite2D" parent="Intro" unique_id=865295049]
+visible = false
+sprite_frames = SubResource("SpriteFrames_lnnri")
+animation = &"Intro"
+
+[node name="CaveInside" type="AnimatedSprite2D" parent="Intro" unique_id=1350292496]
+visible = false
+sprite_frames = SubResource("SpriteFrames_juhbr")
+animation = &"Intro"
diff --git a/Scenes/Options.tscn b/Scenes/Options.tscn
new file mode 100644
index 0000000..2b053d6
--- /dev/null
+++ b/Scenes/Options.tscn
@@ -0,0 +1,79 @@
+[gd_scene format=3 uid="uid://cpq7ppe8bw2bq"]
+
+[ext_resource type="Script" path="res://Scripts/UI/Menus/OptionsMenu.cs" id="1_options"]
+
+[node name="Options" type="PanelContainer" unique_id=230632848 node_paths=PackedStringArray("screenMode", "soundVolume", "lightColor")]
+custom_minimum_size = Vector2(420, 260)
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -210.0
+offset_top = -130.0
+offset_right = 210.0
+offset_bottom = 130.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_options")
+screenMode = NodePath("VBoxContainer/GridContainer/ScreenMode")
+soundVolume = NodePath("VBoxContainer/GridContainer/SoundVolume")
+lightColor = NodePath("VBoxContainer/GridContainer/LightColor")
+
+[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=1149639381]
+layout_mode = 2
+theme_override_constants/separation = 10
+
+[node name="Title" type="RichTextLabel" parent="VBoxContainer" unique_id=1694731141]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=32]Options[/font_size]"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+
+[node name="GridContainer" type="GridContainer" parent="VBoxContainer" unique_id=488511819]
+layout_mode = 2
+columns = 2
+
+[node name="ScreenModeLabel" type="RichTextLabel" parent="VBoxContainer/GridContainer"]
+layout_mode = 2
+text = "Screen"
+fit_content = true
+autowrap_mode = 0
+
+[node name="ScreenMode" type="OptionButton" parent="VBoxContainer/GridContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="SoundVolumeLabel" type="RichTextLabel" parent="VBoxContainer/GridContainer"]
+layout_mode = 2
+text = "Sound"
+fit_content = true
+autowrap_mode = 0
+
+[node name="SoundVolume" type="HSlider" parent="VBoxContainer/GridContainer"]
+layout_mode = 2
+size_flags_horizontal = 3
+max_value = 100.0
+step = 1.0
+value = 80.0
+
+[node name="LightColorLabel" type="RichTextLabel" parent="VBoxContainer/GridContainer"]
+layout_mode = 2
+text = "Light color"
+fit_content = true
+autowrap_mode = 0
+
+[node name="LightColor" type="ColorPickerButton" parent="VBoxContainer/GridContainer" unique_id=1830638845]
+layout_mode = 2
+size_flags_horizontal = 3
+
+[node name="Button" type="Button" parent="VBoxContainer" unique_id=1183264327]
+layout_mode = 2
+text = "Close"
+
+[connection signal="item_selected" from="VBoxContainer/GridContainer/ScreenMode" to="." method="OnScreenModeSelected"]
+[connection signal="value_changed" from="VBoxContainer/GridContainer/SoundVolume" to="." method="OnSoundVolumeChanged"]
+[connection signal="color_changed" from="VBoxContainer/GridContainer/LightColor" to="." method="OnLightColorChanged"]
+[connection signal="pressed" from="VBoxContainer/Button" to="." method="CloseOptions"]
diff --git a/Scenes/TestRunner.tscn b/Scenes/TestRunner.tscn
new file mode 100644
index 0000000..67ff59e
--- /dev/null
+++ b/Scenes/TestRunner.tscn
@@ -0,0 +1,6 @@
+[gd_scene format=3]
+
+[ext_resource type="Script" path="res://Scripts/Tests/TestRunner.cs" id="1_test_runner"]
+
+[node name="TestRunner" type="Node"]
+script = ExtResource("1_test_runner")
diff --git a/Scenes/WorldSetup.tscn b/Scenes/WorldSetup.tscn
new file mode 100644
index 0000000..11d64d3
--- /dev/null
+++ b/Scenes/WorldSetup.tscn
@@ -0,0 +1,75 @@
+[gd_scene format=3]
+
+[ext_resource type="Script" path="res://Scripts/UI/Menus/WorldSetup.cs" id="1_world_setup"]
+
+[node name="WorldSetup" type="Control" node_paths=PackedStringArray("seedInput", "seedPreview")]
+layout_mode = 3
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+script = ExtResource("1_world_setup")
+seedInput = NodePath("CenterContainer/PanelContainer/VBoxContainer/SeedInput")
+seedPreview = NodePath("CenterContainer/PanelContainer/VBoxContainer/SeedPreview")
+
+[node name="CenterContainer" type="CenterContainer" parent="."]
+layout_mode = 1
+anchors_preset = 15
+anchor_right = 1.0
+anchor_bottom = 1.0
+grow_horizontal = 2
+grow_vertical = 2
+
+[node name="PanelContainer" type="PanelContainer" parent="CenterContainer"]
+layout_mode = 2
+custom_minimum_size = Vector2(440, 260)
+
+[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer/PanelContainer"]
+layout_mode = 2
+theme_override_constants/separation = 14
+
+[node name="Title" type="RichTextLabel" parent="CenterContainer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+bbcode_enabled = true
+text = "[font_size=34]World Setup[/font_size]"
+fit_content = true
+autowrap_mode = 0
+horizontal_alignment = 1
+
+[node name="Description" type="RichTextLabel" parent="CenterContainer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+text = "Choose a seed for the ruin layout. Leave it empty for a random expedition."
+fit_content = true
+autowrap_mode = 2
+horizontal_alignment = 1
+
+[node name="SeedInput" type="LineEdit" parent="CenterContainer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+placeholder_text = "Seed..."
+max_length = 32
+
+[node name="SeedPreview" type="RichTextLabel" parent="CenterContainer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+text = "No seed entered. B.O.B. will pick one from the ruins."
+fit_content = true
+autowrap_mode = 2
+horizontal_alignment = 1
+
+[node name="Buttons" type="HBoxContainer" parent="CenterContainer/PanelContainer/VBoxContainer"]
+layout_mode = 2
+theme_override_constants/separation = 12
+
+[node name="Back" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/Buttons"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Back"
+
+[node name="Start" type="Button" parent="CenterContainer/PanelContainer/VBoxContainer/Buttons"]
+layout_mode = 2
+size_flags_horizontal = 3
+text = "Enter Ruins"
+
+[connection signal="text_changed" from="CenterContainer/PanelContainer/VBoxContainer/SeedInput" to="." method="OnSeedChanged"]
+[connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/Buttons/Back" to="." method="OnBackPressed"]
+[connection signal="pressed" from="CenterContainer/PanelContainer/VBoxContainer/Buttons/Start" to="." method="OnStartPressed"]
diff --git a/Scripts/Core/FileHandler.cs b/Scripts/Core/FileHandler.cs
new file mode 100644
index 0000000..84151ec
--- /dev/null
+++ b/Scripts/Core/FileHandler.cs
@@ -0,0 +1,92 @@
+using System.Collections.Generic;
+using Godot;
+
+public static class FileHandler
+{
+ private const string ScriptDirectory = "user://scripts";
+ private const string ScriptExtension = ".json";
+
+ public static void CreateScriptDirectory()
+ {
+ DirAccess.MakeDirRecursiveAbsolute(ScriptDirectory);
+ }
+
+ public static void SaveProgram(string filename, string content)
+ {
+ CreateScriptDirectory();
+ string path = GetProgramPath(filename);
+
+ FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
+ if (file == null) return;
+
+ file.StoreString(content);
+ file.Flush();
+ }
+
+ public static List LoadProgramNames()
+ {
+ CreateScriptDirectory();
+ List programs = new List();
+
+ DirAccess dir = DirAccess.Open(ScriptDirectory);
+ if (dir == null)
+ {
+ return programs;
+ }
+
+ dir.ListDirBegin();
+ while (true)
+ {
+ string fileName = dir.GetNext();
+ if (fileName == "") break;
+
+ if (dir.CurrentIsDir()) continue;
+ if (!fileName.EndsWith(ScriptExtension)) continue;
+
+ programs.Add(fileName.Replace(ScriptExtension, ""));
+ }
+ dir.ListDirEnd();
+
+ return programs;
+ }
+
+ public static string LoadProgram(string name)
+ {
+ CreateScriptDirectory();
+ string path = GetProgramPath(name);
+
+ if (!FileAccess.FileExists(path))
+ {
+ return "";
+ }
+
+ FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
+ if (file == null) return "";
+
+ return file.GetAsText();
+ }
+
+ public static bool DeleteProgram(string name)
+ {
+ CreateScriptDirectory();
+ string path = GetProgramPath(name);
+
+ if (!FileAccess.FileExists(path))
+ {
+ return false;
+ }
+
+ DirAccess dir = DirAccess.Open(ScriptDirectory);
+ if (dir == null)
+ {
+ return false;
+ }
+
+ return dir.Remove($"{name}{ScriptExtension}") == Error.Ok;
+ }
+
+ private static string GetProgramPath(string filename)
+ {
+ return $"{ScriptDirectory}/{filename}{ScriptExtension}";
+ }
+}
diff --git a/Scripts/Core/FileHandler.cs.uid b/Scripts/Core/FileHandler.cs.uid
new file mode 100644
index 0000000..42726cd
--- /dev/null
+++ b/Scripts/Core/FileHandler.cs.uid
@@ -0,0 +1 @@
+uid://y6p1ybasi0c2
diff --git a/Scripts/Core/GameData.cs b/Scripts/Core/GameData.cs
new file mode 100644
index 0000000..5223b65
--- /dev/null
+++ b/Scripts/Core/GameData.cs
@@ -0,0 +1,91 @@
+using System;
+using System.Collections.Generic;
+using Godot;
+
+public partial class GameData
+{
+ public static bool debugMode = false;
+ public static Random rand = new Random(seed);
+ public static Layer[] map;
+
+ public static int currentLayer = 0;
+ public static int visibleLayer = 0;
+ public static int lowestLayer = 0;
+
+ public static bool canMove = true;
+ public static int maxRobotCount = 10;
+ public static List robots = new List();
+ public static float robotSpeed = 10f;
+ public static float tileWidth = 6;
+ public static float tileHeight = 4;
+ public static Dictionary availableResearch = ResourceLoader.LoadResearch();
+ public static SortedDictionary availableItems = ResourceLoader.LoadItems();
+ public static SurvivalState survival = new SurvivalState();
+ public static RobotStats robotStats = new RobotStats();
+ public static Dictionary> gateUnlocks;
+ public static bool loadSaveOnStart = false;
+ public static bool showTutorial = true;
+ public static bool isPaused = false;
+
+ public static Color primaryColor = new Color("#276ac2");
+ public static Color lightColor = new Color("#7efff5");
+ public static int screenMode = 2;
+ public static float soundVolume = 0.8f;
+
+ public static int ruinSize = 10;
+ public static int layerSize = 20;
+ public static int seed = 12345;
+
+ public static Inventory inventory = new Inventory();
+
+ public static void ResetRunState()
+ {
+ ruinSize = 10;
+ layerSize = 20;
+ rand = new Random(seed);
+ survival = new SurvivalState();
+ robotStats = new RobotStats();
+ inventory = new Inventory();
+ availableResearch = ResourceLoader.LoadResearch();
+ map = null;
+ robots.Clear();
+ currentLayer = 0;
+ visibleLayer = 0;
+ lowestLayer = 0;
+ maxRobotCount = 10;
+ canMove = true;
+ isPaused = false;
+ }
+
+ public static void RebuildRobotStatsFromResearch()
+ {
+ robotStats = new RobotStats();
+ maxRobotCount = 10;
+
+ foreach (Research research in availableResearch.Values)
+ {
+ if (research.state == ResearchState.RESEARCHED)
+ {
+ robotStats.Apply(research.data.Effects);
+ }
+ }
+ }
+
+ public static bool HasSpawnableRobotInInventory()
+ {
+ foreach (Item item in inventory.items)
+ {
+ if (robotStats.RobotTypes.ContainsKey(item.data.Id) && item.currentAmount > 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool HasNoRobotRecovery()
+ {
+ return robots.Count <= 0 && !HasSpawnableRobotInInventory();
+ }
+}
diff --git a/Scripts/Core/GameData.cs.uid b/Scripts/Core/GameData.cs.uid
new file mode 100644
index 0000000..43d6f13
--- /dev/null
+++ b/Scripts/Core/GameData.cs.uid
@@ -0,0 +1 @@
+uid://dgebbx4nkktu6
diff --git a/Scripts/Core/ResourceLoader.cs b/Scripts/Core/ResourceLoader.cs
new file mode 100644
index 0000000..9e69ce3
--- /dev/null
+++ b/Scripts/Core/ResourceLoader.cs
@@ -0,0 +1,159 @@
+using Godot;
+using System.Collections.Generic;
+using System.Text.Json;
+
+public static class ResourceLoader
+{
+ private const string LayerPrefabPath = "res://Prefabs/Layer.tscn";
+ private const string RobotPrefabPath = "res://Prefabs/Robot/Robot.tscn";
+ private const string RobotDisplayPath = "res://Prefabs/Robot/RobotDisplay.tscn";
+ private const string ItemDisplayPath = "res://Prefabs/Crafting/ItemDisplay.tscn";
+ private const string RecipesPath = "res://Assets/Recipes.json";
+ private const string ResearchPath = "res://Assets/Research.json";
+
+ public static PackedScene LoadLayerPrefab()
+ {
+ return GD.Load(LayerPrefabPath);
+ }
+
+ public static PackedScene LoadRobotPrefab()
+ {
+ return GD.Load(RobotPrefabPath);
+ }
+
+ public static PackedScene LoadRobotDisplay()
+ {
+ return GD.Load(RobotDisplayPath);
+ }
+
+ public static PackedScene LoadItemDisplay()
+ {
+ return GD.Load(ItemDisplayPath);
+ }
+
+ public static Texture2D LoadPath(string path)
+ {
+ return GD.Load(path);
+ }
+
+ public static Dictionary LoadTiles()
+ {
+ Dictionary tileMeshes = new Dictionary();
+ PackedScene tileCollection = GD.Load("res://Assets/Objects/Tiles.glb");
+ Node root = tileCollection.Instantiate();
+ foreach (MeshInstance3D child in root.GetChildren())
+ {
+ tileMeshes.Add(child.Name.ToString().ToLower(), child);
+ }
+
+ return tileMeshes;
+ }
+
+ public static Dictionary LoadDecorations()
+ {
+ Dictionary decorationMeshes = new Dictionary();
+ PackedScene decorationCollection = GD.Load("res://Assets/Objects/Decorations.glb");
+ Node root = decorationCollection.Instantiate();
+ foreach (MeshInstance3D child in root.GetChildren())
+ {
+ decorationMeshes.Add(child.Name.ToString().ToLower(), child);
+ }
+
+ return decorationMeshes;
+ }
+
+ public static Dictionary LoadDSLNodes()
+ {
+ Dictionary nodes = new Dictionary()
+ {
+ { new StartNode(), GD.Load("res://Prefabs/DSL/StartNode.tscn") },
+ { new MoveNode(), GD.Load("res://Prefabs/DSL/MoveNode.tscn") },
+ { new HarvestNode(), GD.Load("res://Prefabs/DSL/HarvestNode.tscn") },
+ { new CraftNode(), GD.Load("res://Prefabs/DSL/CraftNode.tscn") },
+ { new ExploreNode(), GD.Load("res://Prefabs/DSL/ExploreNode.tscn") },
+ { new IfNode(), GD.Load("res://Prefabs/DSL/IfNode.tscn") },
+ { new ForNode(), GD.Load("res://Prefabs/DSL/ForNode.tscn") },
+ { new WhileNode(), GD.Load("res://Prefabs/DSL/WhileNode.tscn") },
+ { new MaintainNode(), GD.Load("res://Prefabs/DSL/MaintainNode.tscn") },
+ { new SacrificeNode(), GD.Load("res://Prefabs/DSL/SacrificeNode.tscn") }
+ };
+ return nodes;
+ }
+
+ public static Dictionary LoadResourceSymbols()
+ {
+ Dictionary symbols = new Dictionary()
+ {
+ { "iron_ore", GD.Load("res://Assets/Images/Resources/IronSymbol.png") },
+ { "tin_ore", GD.Load("res://Assets/Images/Resources/TinSymbol.png") },
+ { "copper_ore", GD.Load("res://Assets/Images/Resources/CopperSymbol.png") },
+ { "mushroom", GD.Load("res://Assets/Images/Resources/MushroomSymbol.png") },
+ { "spider_silk", GD.Load("res://Assets/Images/Resources/SpiderSilkSymbol.png") },
+ { "coal", GD.Load("res://Assets/Images/Resources/CoalSymbol.png") },
+ { "water", GD.Load("res://Assets/Images/Resources/WaterSymbol.png") },
+ { "stone", GD.Load("res://Assets/Images/Resources/StoneSymbol.png") },
+ };
+ return symbols;
+ }
+
+ public static Dictionary LoadResourceWeights()
+ {
+ Dictionary weights = new Dictionary()
+ {
+ { "iron_ore", new float[] { 0.05f, 1f } },
+ { "tin_ore", new float[] { 0.3f, 0.7f } },
+ { "copper_ore", new float[] { 0.3f, 0.7f } },
+ { "mushroom", new float[] { 0.3f, 0.1f } },
+ { "spider_silk", new float[] { 0.8f, 0.4f } },
+ { "coal", new float[] { 1f, 0.3f } },
+ { "water", new float[] { 0.4f, 0.2f } },
+ { "stone", new float[] { 1f, 0.5f } },
+ };
+ return weights;
+ }
+
+ public static SortedDictionary LoadItems()
+ {
+ FileAccess file = FileAccess.Open(RecipesPath, FileAccess.ModeFlags.Read);
+ if (file == null) return new SortedDictionary();
+
+ string json = file.GetAsText();
+
+ SortedDictionary result = new SortedDictionary();
+
+ List items = JsonSerializer.Deserialize>(json);
+ if (items == null) return result;
+
+ foreach (ItemData item in items)
+ {
+ result.Add(item.Id, item);
+ }
+
+ return result;
+ }
+
+ public static Dictionary LoadResearch()
+ {
+ FileAccess file = FileAccess.Open(ResearchPath, FileAccess.ModeFlags.Read);
+ if (file == null) return new Dictionary();
+
+ string json = file.GetAsText();
+
+ Dictionary result = new Dictionary();
+
+ List researches = JsonSerializer.Deserialize>(json);
+ if (researches == null) return result;
+
+ foreach (ResearchData research in researches)
+ {
+ result.Add(research.Id, new Research(research));
+ }
+
+ return result;
+ }
+
+ public static Texture2D LoadDSLTooltip(string title)
+ {
+ return GD.Load($"res://Assets/Images/Nodes/{title.ToLower()}.png");
+ }
+}
diff --git a/Scripts/Core/ResourceLoader.cs.uid b/Scripts/Core/ResourceLoader.cs.uid
new file mode 100644
index 0000000..fba65b6
--- /dev/null
+++ b/Scripts/Core/ResourceLoader.cs.uid
@@ -0,0 +1 @@
+uid://cdhftg7wcgyis
diff --git a/Scripts/Core/SaveGameData.cs b/Scripts/Core/SaveGameData.cs
new file mode 100644
index 0000000..ccf2106
--- /dev/null
+++ b/Scripts/Core/SaveGameData.cs
@@ -0,0 +1,99 @@
+using System.Collections.Generic;
+
+public class SaveGameData
+{
+ public int Seed { get; set; }
+ public int CurrentLayer { get; set; }
+ public int VisibleLayer { get; set; }
+ public int LowestLayer { get; set; }
+ public int MaxRobotCount { get; set; }
+ public bool CanMove { get; set; }
+ public SettingsSaveData Settings { get; set; }
+ public SurvivalSaveData Survival { get; set; }
+ public List Inventory { get; set; }
+ public List Research { get; set; }
+ public List Layers { get; set; }
+ public List Robots { get; set; }
+}
+
+public class SettingsSaveData
+{
+ public int ScreenMode { get; set; }
+ public float SoundVolume { get; set; }
+ public float LightColorR { get; set; }
+ public float LightColorG { get; set; }
+ public float LightColorB { get; set; }
+ public float LightColorA { get; set; }
+}
+
+public class SurvivalSaveData
+{
+ public float Hunger { get; set; }
+ public float Thirst { get; set; }
+ public float Energy { get; set; }
+ public bool IsDead { get; set; }
+ public string DeathReason { get; set; }
+ public string CurrentStatus { get; set; }
+ public double ElapsedSeconds { get; set; }
+}
+
+public class ItemSaveData
+{
+ public string Id { get; set; }
+ public int Amount { get; set; }
+}
+
+public class ResearchSaveData
+{
+ public string Id { get; set; }
+ public ResearchState State { get; set; }
+ public double ElapsedResearchTime { get; set; }
+ public bool PaidResources { get; set; }
+}
+
+public class LayerSaveData
+{
+ public int Level { get; set; }
+ public bool IsGateOpen { get; set; }
+ public bool HasContentGenerated { get; set; }
+ public List GateIngredients { get; set; }
+ public List CurrentResources { get; set; }
+ public List Tiles { get; set; }
+}
+
+public class TileSaveData
+{
+ public int X { get; set; }
+ public int Y { get; set; }
+ public string CollapsedMesh { get; set; }
+ public bool ContainsLight { get; set; }
+ public bool ContainsDecoration { get; set; }
+ public bool ContainsResource { get; set; }
+ public bool WasVisited { get; set; }
+ public ResourceSaveData Resource { get; set; }
+}
+
+public class ResourceSaveData
+{
+ public string Name { get; set; }
+ public int CurrentAmount { get; set; }
+ public int MaxAmount { get; set; }
+ public bool IsEndless { get; set; }
+ public float ExtractionSpeed { get; set; }
+ public double TimeSinceLastExtraction { get; set; }
+}
+
+public class RobotSaveData
+{
+ public string Name { get; set; }
+ public string CurrentProgram { get; set; }
+ public string CurrentMessage { get; set; }
+ public string RobotType { get; set; }
+ public float X { get; set; }
+ public float Y { get; set; }
+ public float Z { get; set; }
+ public float Heat { get; set; }
+ public float Maintenance { get; set; }
+ public bool IsCoolingDown { get; set; }
+ public bool IsBroken { get; set; }
+}
diff --git a/Scripts/Core/SaveGameData.cs.uid b/Scripts/Core/SaveGameData.cs.uid
new file mode 100644
index 0000000..db093d8
--- /dev/null
+++ b/Scripts/Core/SaveGameData.cs.uid
@@ -0,0 +1 @@
+uid://k530yuk4xt1x
diff --git a/Scripts/Core/SaveGameDataApplier.cs b/Scripts/Core/SaveGameDataApplier.cs
new file mode 100644
index 0000000..97ffa09
--- /dev/null
+++ b/Scripts/Core/SaveGameDataApplier.cs
@@ -0,0 +1,148 @@
+using Godot;
+using System.Collections.Generic;
+
+public static class SaveGameDataApplier
+{
+ public static void ApplyWorldData(SaveGameData saveGame)
+ {
+ if (saveGame == null) return;
+
+ GameData.seed = saveGame.Seed;
+ GameData.currentLayer = saveGame.CurrentLayer;
+ GameData.visibleLayer = saveGame.VisibleLayer;
+ GameData.lowestLayer = saveGame.LowestLayer;
+ GameData.maxRobotCount = saveGame.MaxRobotCount;
+ GameData.canMove = saveGame.CanMove;
+
+ ApplySettingsData(saveGame.Settings);
+ ApplySurvivalData(saveGame.Survival);
+ ApplyInventoryData(saveGame.Inventory);
+ ApplyResearchData(saveGame.Research);
+ ApplyLayerData(saveGame.Layers);
+ }
+
+ private static void ApplySurvivalData(SurvivalSaveData survival)
+ {
+ if (survival == null) return;
+
+ GameData.survival.hunger = survival.Hunger;
+ GameData.survival.thirst = survival.Thirst;
+ GameData.survival.energy = survival.Energy;
+ GameData.survival.isDead = survival.IsDead;
+ GameData.survival.deathReason = survival.DeathReason ?? "";
+ GameData.survival.currentStatus = survival.CurrentStatus ?? "";
+ GameData.survival.elapsedSeconds = survival.ElapsedSeconds;
+ }
+
+ private static void ApplySettingsData(SettingsSaveData settings)
+ {
+ if (settings == null) return;
+
+ GameData.screenMode = settings.ScreenMode;
+ GameData.soundVolume = settings.SoundVolume;
+ GameData.lightColor = new Color(
+ settings.LightColorR,
+ settings.LightColorG,
+ settings.LightColorB,
+ settings.LightColorA
+ );
+
+ ApplyScreenMode(settings.ScreenMode);
+ SoundManager.SetMasterVolume(settings.SoundVolume);
+ LightHandler.RedrawLights(GameData.lightColor);
+ }
+
+ private static void ApplyScreenMode(int screenMode)
+ {
+ switch (screenMode)
+ {
+ case 0:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
+ break;
+ case 1:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
+ break;
+ case 2:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
+ break;
+ default:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
+ break;
+ }
+ }
+
+ private static void ApplyInventoryData(List savedItems)
+ {
+ GameData.inventory = new Inventory();
+ if (savedItems == null) return;
+
+ foreach (ItemSaveData savedItem in savedItems)
+ {
+ if (!GameData.availableItems.ContainsKey(savedItem.Id)) continue;
+
+ GameData.inventory.AddItem(
+ new Item { data = GameData.availableItems[savedItem.Id] },
+ savedItem.Amount
+ );
+ }
+ }
+
+ private static void ApplyResearchData(List savedResearch)
+ {
+ if (savedResearch == null) return;
+
+ foreach (ResearchSaveData savedState in savedResearch)
+ {
+ if (!GameData.availableResearch.ContainsKey(savedState.Id)) continue;
+
+ Research research = GameData.availableResearch[savedState.Id];
+ research.state = savedState.State;
+ research.elapsedResearchTime = savedState.ElapsedResearchTime;
+ research.paidResources = savedState.PaidResources;
+ }
+
+ GameData.RebuildRobotStatsFromResearch();
+ }
+
+ private static void ApplyLayerData(List savedLayers)
+ {
+ if (savedLayers == null || GameData.map == null) return;
+
+ foreach (LayerSaveData savedLayer in savedLayers)
+ {
+ if (savedLayer.Level < 0 || savedLayer.Level >= GameData.map.Length) continue;
+
+ Layer layer = GameData.map[savedLayer.Level];
+ layer.isGateOpen = savedLayer.IsGateOpen;
+ layer.hasContentGenerated = savedLayer.HasContentGenerated;
+ layer.gateIngredients = savedLayer.GateIngredients ?? new List();
+ layer.currentResources = savedLayer.CurrentResources ?? new List();
+
+ ApplyTileData(layer, savedLayer.Tiles);
+ }
+ }
+
+ private static void ApplyTileData(Layer layer, List savedTiles)
+ {
+ if (savedTiles == null) return;
+
+ foreach (TileSaveData savedTile in savedTiles)
+ {
+ if (savedTile.X < 0 || savedTile.X >= GameData.layerSize) continue;
+ if (savedTile.Y < 0 || savedTile.Y >= GameData.layerSize) continue;
+
+ Tile tile = layer.tiles[savedTile.X, savedTile.Y];
+ tile.collapsedMesh = savedTile.CollapsedMesh;
+ tile.containsLight = savedTile.ContainsLight;
+ tile.containsDecoration = savedTile.ContainsDecoration;
+ tile.containsResource = savedTile.ContainsResource;
+ tile.wasVisited = savedTile.WasVisited;
+ tile.resource = savedTile.Resource == null ? null : GameResource.FromSaveData(savedTile.Resource);
+ tile.ContentNode.Visible = savedTile.WasVisited || (tile.collapsedMesh == "gate" && !layer.isGateOpen);
+ }
+ }
+}
diff --git a/Scripts/Core/SaveGameDataApplier.cs.uid b/Scripts/Core/SaveGameDataApplier.cs.uid
new file mode 100644
index 0000000..3c849d4
--- /dev/null
+++ b/Scripts/Core/SaveGameDataApplier.cs.uid
@@ -0,0 +1 @@
+uid://jv7k2npga7u0
diff --git a/Scripts/Core/SaveGameDataFactory.cs b/Scripts/Core/SaveGameDataFactory.cs
new file mode 100644
index 0000000..5948659
--- /dev/null
+++ b/Scripts/Core/SaveGameDataFactory.cs
@@ -0,0 +1,160 @@
+using System.Collections.Generic;
+
+public static class SaveGameDataFactory
+{
+ public static SaveGameData CreateSaveData()
+ {
+ return new SaveGameData
+ {
+ Seed = GameData.seed,
+ CurrentLayer = GameData.currentLayer,
+ VisibleLayer = GameData.visibleLayer,
+ LowestLayer = GameData.lowestLayer,
+ MaxRobotCount = GameData.maxRobotCount,
+ CanMove = GameData.canMove,
+ Settings = CreateSettingsSaveData(),
+ Survival = CreateSurvivalSaveData(),
+ Inventory = CreateInventorySaveData(),
+ Research = CreateResearchSaveData(),
+ Layers = CreateLayerSaveData(),
+ Robots = CreateRobotSaveData()
+ };
+ }
+
+ public static SaveGameData CreateCoreSaveData(SaveGameData saveGame)
+ {
+ return new SaveGameData
+ {
+ Seed = saveGame.Seed,
+ CurrentLayer = saveGame.CurrentLayer,
+ VisibleLayer = saveGame.VisibleLayer,
+ LowestLayer = saveGame.LowestLayer,
+ MaxRobotCount = saveGame.MaxRobotCount,
+ CanMove = saveGame.CanMove,
+ Settings = saveGame.Settings,
+ Survival = saveGame.Survival,
+ Inventory = saveGame.Inventory,
+ Research = new List(),
+ Layers = new List(),
+ Robots = new List()
+ };
+ }
+
+ private static SurvivalSaveData CreateSurvivalSaveData()
+ {
+ return new SurvivalSaveData
+ {
+ Hunger = GameData.survival.hunger,
+ Thirst = GameData.survival.thirst,
+ Energy = GameData.survival.energy,
+ IsDead = GameData.survival.isDead,
+ DeathReason = GameData.survival.deathReason,
+ CurrentStatus = GameData.survival.currentStatus,
+ ElapsedSeconds = GameData.survival.elapsedSeconds
+ };
+ }
+
+ private static SettingsSaveData CreateSettingsSaveData()
+ {
+ return new SettingsSaveData
+ {
+ ScreenMode = GameData.screenMode,
+ SoundVolume = GameData.soundVolume,
+ LightColorR = GameData.lightColor.R,
+ LightColorG = GameData.lightColor.G,
+ LightColorB = GameData.lightColor.B,
+ LightColorA = GameData.lightColor.A
+ };
+ }
+
+ private static List CreateInventorySaveData()
+ {
+ List result = new List();
+
+ foreach (Item item in GameData.inventory.items)
+ {
+ result.Add(new ItemSaveData
+ {
+ Id = item.data.Id,
+ Amount = item.currentAmount
+ });
+ }
+
+ return result;
+ }
+
+ private static List CreateResearchSaveData()
+ {
+ List result = new List();
+
+ foreach (Research research in GameData.availableResearch.Values)
+ {
+ result.Add(new ResearchSaveData
+ {
+ Id = research.data.Id,
+ State = research.state,
+ ElapsedResearchTime = research.elapsedResearchTime,
+ PaidResources = research.paidResources
+ });
+ }
+
+ return result;
+ }
+
+ private static List CreateLayerSaveData()
+ {
+ List result = new List();
+ if (GameData.map == null) return result;
+
+ foreach (Layer layer in GameData.map)
+ {
+ if (layer == null) continue;
+
+ result.Add(new LayerSaveData
+ {
+ Level = layer.level,
+ IsGateOpen = layer.isGateOpen,
+ HasContentGenerated = layer.hasContentGenerated,
+ GateIngredients = new List(layer.gateIngredients),
+ CurrentResources = new List(layer.currentResources),
+ Tiles = CreateTileSaveData(layer)
+ });
+ }
+
+ return result;
+ }
+
+ private static List CreateTileSaveData(Layer layer)
+ {
+ List result = new List();
+
+ foreach (Tile tile in layer.tiles)
+ {
+ result.Add(new TileSaveData
+ {
+ X = tile.GridPosition.X,
+ Y = tile.GridPosition.Y,
+ CollapsedMesh = tile.collapsedMesh,
+ ContainsLight = tile.containsLight,
+ ContainsDecoration = tile.containsDecoration,
+ ContainsResource = tile.containsResource,
+ WasVisited = tile.wasVisited,
+ Resource = tile.resource == null ? null : tile.resource.CreateSaveData()
+ });
+ }
+
+ return result;
+ }
+
+ private static List CreateRobotSaveData()
+ {
+ List result = new List();
+
+ foreach (Robot robot in GameData.robots)
+ {
+ result.Add(robot.CreateSaveData());
+ }
+
+ return result;
+ }
+}
diff --git a/Scripts/Core/SaveGameDataFactory.cs.uid b/Scripts/Core/SaveGameDataFactory.cs.uid
new file mode 100644
index 0000000..2fb1ccf
--- /dev/null
+++ b/Scripts/Core/SaveGameDataFactory.cs.uid
@@ -0,0 +1 @@
+uid://b87q17gdv4pfh
diff --git a/Scripts/Core/SaveGameManager.cs b/Scripts/Core/SaveGameManager.cs
new file mode 100644
index 0000000..f16fdf3
--- /dev/null
+++ b/Scripts/Core/SaveGameManager.cs
@@ -0,0 +1,131 @@
+using Godot;
+using System.Collections.Generic;
+using System.Text.Json;
+
+public static class SaveGameManager
+{
+ private const string SaveDirectory = "user://savegame";
+ private const string GameDataPath = SaveDirectory + "/gamedata.json";
+ private const string RobotsPath = SaveDirectory + "/robots.json";
+ private const string ResearchPath = SaveDirectory + "/research.json";
+ private const string LayerPrefix = SaveDirectory + "/layer_";
+ private const string JsonExtension = ".json";
+
+ private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions
+ {
+ WriteIndented = true
+ };
+
+ public static bool SaveExists()
+ {
+ return FileAccess.FileExists(GameDataPath);
+ }
+
+ public static void SaveGame()
+ {
+ SaveGameData saveGame = CreateSaveData();
+ CreateSaveDirectory();
+ ClearOldLayerFiles();
+
+ SaveJson(GameDataPath, SaveGameDataFactory.CreateCoreSaveData(saveGame));
+ SaveJson(RobotsPath, saveGame.Robots);
+ SaveJson(ResearchPath, saveGame.Research);
+
+ foreach (LayerSaveData layer in saveGame.Layers)
+ {
+ SaveJson(GetLayerPath(layer.Level), layer);
+ }
+ }
+
+ public static SaveGameData LoadSaveData()
+ {
+ if (!SaveExists()) return null;
+
+ SaveGameData saveGame = LoadJson(GameDataPath);
+ if (saveGame == null) return null;
+
+ saveGame.Robots = LoadJson>(RobotsPath) ?? new List();
+ saveGame.Research = LoadJson>(ResearchPath) ?? new List();
+ saveGame.Layers = LoadLayerSaveData();
+
+ return saveGame;
+ }
+
+ public static SaveGameData CreateSaveData()
+ {
+ return SaveGameDataFactory.CreateSaveData();
+ }
+
+ public static void ApplyWorldData(SaveGameData saveGame)
+ {
+ SaveGameDataApplier.ApplyWorldData(saveGame);
+ }
+
+ private static List LoadLayerSaveData()
+ {
+ List result = new List();
+
+ for (int i = 0; i < GameData.ruinSize; i++)
+ {
+ string path = GetLayerPath(i);
+ if (!FileAccess.FileExists(path)) continue;
+
+ LayerSaveData layer = LoadJson(path);
+ if (layer != null)
+ {
+ result.Add(layer);
+ }
+ }
+
+ return result;
+ }
+
+ private static string GetLayerPath(int level)
+ {
+ return LayerPrefix + level + JsonExtension;
+ }
+
+ private static void CreateSaveDirectory()
+ {
+ DirAccess.MakeDirRecursiveAbsolute(SaveDirectory);
+ }
+
+ private static void ClearOldLayerFiles()
+ {
+ DirAccess directory = DirAccess.Open(SaveDirectory);
+ if (directory == null) return;
+
+ directory.ListDirBegin();
+ while (true)
+ {
+ string fileName = directory.GetNext();
+ if (fileName == "") break;
+ if (directory.CurrentIsDir()) continue;
+ if (!fileName.StartsWith("layer_") || !fileName.EndsWith(JsonExtension)) continue;
+
+ directory.Remove(fileName);
+ }
+ directory.ListDirEnd();
+ }
+
+ private static void SaveJson(string path, T data)
+ {
+ string json = JsonSerializer.Serialize(data, JsonOptions);
+ FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
+ if (file == null) return;
+
+ file.StoreString(json);
+ file.Flush();
+ }
+
+ private static T LoadJson(string path)
+ {
+ if (!FileAccess.FileExists(path)) return default;
+
+ FileAccess file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
+ if (file == null) return default;
+
+ string json = file.GetAsText();
+ return JsonSerializer.Deserialize(json);
+ }
+}
diff --git a/Scripts/Core/SaveGameManager.cs.uid b/Scripts/Core/SaveGameManager.cs.uid
new file mode 100644
index 0000000..2058ee2
--- /dev/null
+++ b/Scripts/Core/SaveGameManager.cs.uid
@@ -0,0 +1 @@
+uid://quury78jfutk
diff --git a/Scripts/Core/SoundManager.cs b/Scripts/Core/SoundManager.cs
new file mode 100644
index 0000000..80b25eb
--- /dev/null
+++ b/Scripts/Core/SoundManager.cs
@@ -0,0 +1,44 @@
+using Godot;
+
+public partial class SoundManager : Node
+{
+ private static SoundManager instance;
+
+ [Export] private AudioStreamPlayer buttonSound;
+ [Export] private AudioStreamPlayer miningSound;
+
+ public override void _Ready()
+ {
+ instance = this;
+ }
+
+ public override void _ExitTree()
+ {
+ if (instance == this)
+ {
+ instance = null;
+ }
+ }
+
+ public static void PlayButton()
+ {
+ if (instance == null || instance.buttonSound == null) return;
+
+ instance.buttonSound.Play();
+ }
+
+ public static void PlayMining()
+ {
+ if (instance == null || instance.miningSound == null) return;
+
+ instance.miningSound.Play();
+ }
+
+ public static void SetMasterVolume(float percent)
+ {
+ percent = Mathf.Clamp(percent, 0f, 1f);
+ int busIndex = AudioServer.GetBusIndex("Master");
+ AudioServer.SetBusVolumeDb(busIndex, Mathf.LinearToDb(percent));
+ AudioServer.SetBusMute(busIndex, percent <= 0f);
+ }
+}
diff --git a/Scripts/Core/SoundManager.cs.uid b/Scripts/Core/SoundManager.cs.uid
new file mode 100644
index 0000000..9feb1a5
--- /dev/null
+++ b/Scripts/Core/SoundManager.cs.uid
@@ -0,0 +1 @@
+uid://gvy12mefkwax
diff --git a/Scripts/Core/SteamworksHandler.cs b/Scripts/Core/SteamworksHandler.cs
new file mode 100644
index 0000000..cf7654e
--- /dev/null
+++ b/Scripts/Core/SteamworksHandler.cs
@@ -0,0 +1,35 @@
+using Godot;
+using GodotSteam;
+
+public partial class SteamworksHandler : Node
+{
+ [Export] private bool enableSteam = true;
+ private bool isSteamInitialized = false;
+
+ public override void _Ready()
+ {
+ if (!enableSteam) return;
+
+ SteamInitExStatus status = Steam.SteamInitEx(false).Status;
+ if (status != 0)
+ {
+ return;
+ }
+
+ isSteamInitialized = true;
+ }
+
+ public override void _Process(double delta)
+ {
+ if (!isSteamInitialized) return;
+
+ Steam.RunCallbacks();
+ }
+
+ public override void _ExitTree()
+ {
+ if (!isSteamInitialized) return;
+
+ Steam.SteamShutdown();
+ }
+}
diff --git a/Scripts/Core/SteamworksHandler.cs.uid b/Scripts/Core/SteamworksHandler.cs.uid
new file mode 100644
index 0000000..753df3b
--- /dev/null
+++ b/Scripts/Core/SteamworksHandler.cs.uid
@@ -0,0 +1 @@
+uid://dqrdb3bvws6b6
diff --git a/Scripts/DSL/NodeResult.cs b/Scripts/DSL/NodeResult.cs
new file mode 100644
index 0000000..d4f0b63
--- /dev/null
+++ b/Scripts/DSL/NodeResult.cs
@@ -0,0 +1,7 @@
+public enum NodeResult
+{
+ SUCCESS,
+ FAILURE,
+ RUNNING,
+ CONDITIONFALSE
+}
\ No newline at end of file
diff --git a/Scripts/DSL/NodeResult.cs.uid b/Scripts/DSL/NodeResult.cs.uid
new file mode 100644
index 0000000..3d962f6
--- /dev/null
+++ b/Scripts/DSL/NodeResult.cs.uid
@@ -0,0 +1 @@
+uid://dpp7ycquabyq8
diff --git a/Scripts/DSL/Nodes/CraftNode.cs b/Scripts/DSL/Nodes/CraftNode.cs
new file mode 100644
index 0000000..411f8d9
--- /dev/null
+++ b/Scripts/DSL/Nodes/CraftNode.cs
@@ -0,0 +1,58 @@
+using Godot;
+
+public class CraftNode : ProgramNode
+{
+ public Item selectedItem;
+ public int amount;
+ public CraftNode()
+ {
+ DisplayText = "Craft";
+ TooltipText = "Crafts the selected item the selected amount of times by consuming the required ingredients from the shared inventory.";
+ }
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ if (selectedItem == null)
+ {
+ lastExecutionMessage = "No Item selected";
+ return NodeResult.FAILURE;
+ }
+
+ if (amount <= 0)
+ {
+ lastExecutionMessage = "Amount has to be at least 1";
+ return NodeResult.FAILURE;
+ }
+
+ if (!GameData.inventory.CanCraft(selectedItem.data.Inputs, amount))
+ {
+ lastExecutionMessage = "Not enough items to craft this";
+ return NodeResult.FAILURE;
+ }
+
+ switch (selectedItem.Craft(amount, delta))
+ {
+ case CraftingResult.FAILED:
+ lastExecutionMessage = "Not enough space to add item to inventory";
+ return NodeResult.FAILURE;
+ case CraftingResult.FINISHED:
+ return NodeResult.SUCCESS;
+ }
+
+ return NodeResult.RUNNING;
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ CraftNode duplicate = new CraftNode()
+ {
+ selectedItem = selectedItem,
+ amount = amount
+ };
+ return duplicate;
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}, Item: {(selectedItem == null ? "Empty" : selectedItem.data.Id)}, Amount: {amount}";
+ }
+}
diff --git a/Scripts/DSL/Nodes/CraftNode.cs.uid b/Scripts/DSL/Nodes/CraftNode.cs.uid
new file mode 100644
index 0000000..4b32399
--- /dev/null
+++ b/Scripts/DSL/Nodes/CraftNode.cs.uid
@@ -0,0 +1 @@
+uid://dcm23bpi37va
diff --git a/Scripts/DSL/Nodes/ExploreNode.cs b/Scripts/DSL/Nodes/ExploreNode.cs
new file mode 100644
index 0000000..1339a71
--- /dev/null
+++ b/Scripts/DSL/Nodes/ExploreNode.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+using Godot;
+
+public class ExploreNode : ProgramNode
+{
+ public Vector3 startPosition;
+ public Vector3I targetPosition;
+ public List pathPoints;
+
+ public ExploreNode()
+ {
+ DisplayText = "Explore";
+ TooltipText = "Explores nearby unknown tiles around the robot and reveals them for future navigation. \rContinues exploration until no unvisited tile is left.";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ if (pathPoints == null && !TrySelectTarget())
+ {
+ lastExecutionMessage = "No tiles left to explore";
+ return NodeResult.SUCCESS;
+ }
+
+ if (pathPoints == null)
+ {
+ pathPoints = new List(
+ Pathfinding.GetPath(Pathfinding.GetClosestStartPoint(robot.Position), targetPosition)
+ );
+ }
+
+ if (pathPoints.Count <= 0)
+ {
+ lastExecutionMessage = $"No path available {targetPosition}";
+ return NodeResult.FAILURE;
+ }
+
+ return MoveAlongPath(robot, delta);
+ }
+
+ private bool TrySelectTarget()
+ {
+ int safetyCounter = 0;
+ int layerRange = Math.Max(GameData.lowestLayer, 1);
+ int maximumAttempts = (int)Math.Pow(GameData.layerSize, 2) * 2;
+
+ while (safetyCounter <= maximumAttempts)
+ {
+ targetPosition = new Vector3I(
+ GameData.rand.Next(GameData.layerSize),
+ GameData.rand.Next(layerRange),
+ GameData.rand.Next(GameData.layerSize)
+ );
+ if (!GameData.map[targetPosition.Y].tiles[targetPosition.X, targetPosition.Z].wasVisited)
+ {
+ return true;
+ }
+
+ safetyCounter++;
+ }
+
+ return false;
+ }
+
+ private NodeResult MoveAlongPath(Robot robot, double delta)
+ {
+ startPosition = robot.Position;
+ Vector3 target = pathPoints[0] - startPosition;
+ float distance = target.Length();
+ float movementSpeed = robot.GetMovementSpeed();
+
+ if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
+ {
+ return FinishCurrentStep(robot);
+ }
+
+ Vector3 direction = target / distance;
+ RotateRobot(robot, direction);
+ robot.GlobalPosition += direction * (float)delta * movementSpeed;
+
+ return NodeResult.RUNNING;
+ }
+
+ private NodeResult FinishCurrentStep(Robot robot)
+ {
+ robot.Position = pathPoints[0];
+ VisitCurrentTile(robot);
+ pathPoints.RemoveAt(0);
+
+ if (pathPoints.Count > 0)
+ {
+ lastExecutionMessage = "";
+ return NodeResult.RUNNING;
+ }
+
+ lastExecutionMessage = "Current exploration finished";
+ pathPoints = null;
+ return NodeResult.RUNNING;
+ }
+
+ private void VisitCurrentTile(Robot robot)
+ {
+ Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
+ Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
+ if (!tile.wasVisited)
+ {
+ tile.VisitTile();
+ }
+ }
+
+ private void RotateRobot(Robot robot, Vector3 direction)
+ {
+ Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
+ if (lookDirection.Length() <= 0.1f) return;
+
+ robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ return new ExploreNode
+ {
+ targetPosition = targetPosition
+ };
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}";
+ }
+}
diff --git a/Scripts/DSL/Nodes/ExploreNode.cs.uid b/Scripts/DSL/Nodes/ExploreNode.cs.uid
new file mode 100644
index 0000000..d47703a
--- /dev/null
+++ b/Scripts/DSL/Nodes/ExploreNode.cs.uid
@@ -0,0 +1 @@
+uid://cj4t6w5rd0cv2
diff --git a/Scripts/DSL/Nodes/ForNode.cs b/Scripts/DSL/Nodes/ForNode.cs
new file mode 100644
index 0000000..76d432f
--- /dev/null
+++ b/Scripts/DSL/Nodes/ForNode.cs
@@ -0,0 +1,52 @@
+using Godot;
+using System.Collections.Generic;
+
+public class ForNode : ProgramNode
+{
+ public int amountExecuted;
+ public int amount;
+ public ForNode()
+ {
+ DisplayText = "For";
+ TooltipText = "Repeats the connected branch (Top slot) for the configured amount of executions before continuing through the negative output. (Bottom slot)";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ bool isConditionFulfilled = DetermineCondition();
+ amountExecuted++;
+ if (isConditionFulfilled)
+ {
+ amountExecuted = 0;
+ }
+ return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
+ }
+
+ private bool DetermineCondition()
+ {
+ return amountExecuted >= amount;
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ ForNode duplicate = new ForNode()
+ {
+ amount = amount,
+ amountExecuted = 0
+ };
+ return duplicate;
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}, AmountExecuted: {amountExecuted}, Amount: {amount}";
+ }
+
+ public override void SetNextNode(
+ List connections,
+ Dictionary availableNodes
+ )
+ {
+ SetBranchNodes(connections, availableNodes);
+ }
+}
diff --git a/Scripts/DSL/Nodes/ForNode.cs.uid b/Scripts/DSL/Nodes/ForNode.cs.uid
new file mode 100644
index 0000000..2732624
--- /dev/null
+++ b/Scripts/DSL/Nodes/ForNode.cs.uid
@@ -0,0 +1 @@
+uid://s1gp2fdow25r
diff --git a/Scripts/DSL/Nodes/HarvestNode.cs b/Scripts/DSL/Nodes/HarvestNode.cs
new file mode 100644
index 0000000..527874a
--- /dev/null
+++ b/Scripts/DSL/Nodes/HarvestNode.cs
@@ -0,0 +1,50 @@
+using Godot;
+
+public class HarvestNode : ProgramNode
+{
+ public HarvestNode()
+ {
+ DisplayText = "Harvest";
+ TooltipText = "Harvests one unit from the resource on the robot's current tile and adds the result to the shared inventory.";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
+ Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
+
+ if (!tile.containsResource || tile.resource == null)
+ {
+ lastExecutionMessage = "No resource on this tile";
+ return NodeResult.FAILURE;
+ }
+
+ if (!tile.resource.CanExtract())
+ {
+ lastExecutionMessage = "Resource is depleted and not endless or you haven't unlocked it yet";
+ return NodeResult.SUCCESS;
+ }
+
+ if (!tile.resource.Extract(delta)) return NodeResult.RUNNING;
+
+ SoundManager.PlayMining();
+ if (!GameData.inventory.AddItem(new Item { data = tile.resource.item }, 1))
+ {
+ lastExecutionMessage = "Not enough space";
+ return NodeResult.FAILURE;
+ }
+
+ lastExecutionMessage = "";
+ return NodeResult.SUCCESS;
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ return new HarvestNode();
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}";
+ }
+}
diff --git a/Scripts/DSL/Nodes/HarvestNode.cs.uid b/Scripts/DSL/Nodes/HarvestNode.cs.uid
new file mode 100644
index 0000000..f130273
--- /dev/null
+++ b/Scripts/DSL/Nodes/HarvestNode.cs.uid
@@ -0,0 +1 @@
+uid://bb8g5ukg7xnw
diff --git a/Scripts/DSL/Nodes/IfNode.cs b/Scripts/DSL/Nodes/IfNode.cs
new file mode 100644
index 0000000..e21bdbf
--- /dev/null
+++ b/Scripts/DSL/Nodes/IfNode.cs
@@ -0,0 +1,70 @@
+using Godot;
+using System.Collections.Generic;
+
+public class IfNode : ProgramNode
+{
+ public Item selectedItem;
+ public int amount;
+ public string comparator;
+ public IfNode()
+ {
+ DisplayText = "If";
+ TooltipText = "Checks an inventory condition once. If the condition is true, execution follows the main output (Top slot); otherwise it follows the negative output. (Bottom slot)";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ if (selectedItem == null)
+ {
+ lastExecutionMessage = "No Item selected";
+ return NodeResult.FAILURE;
+ }
+
+ if (comparator == null)
+ {
+ lastExecutionMessage = "No comparator selected";
+ return NodeResult.FAILURE;
+ }
+
+ bool isConditionFulfilled = DetermineCondition();
+ return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
+ }
+
+ private bool DetermineCondition()
+ {
+ int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
+ return comparator switch
+ {
+ "is bigger than" => inventoryAmount > amount,
+ "is less than" => inventoryAmount < amount,
+ "is not" => inventoryAmount != amount,
+ "is less than or equal to" => inventoryAmount <= amount,
+ "is bigger than or equal to" => inventoryAmount >= amount,
+ _ => inventoryAmount == amount
+ };
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ IfNode duplicate = new IfNode()
+ {
+ selectedItem = selectedItem,
+ amount = amount,
+ comparator = comparator
+ };
+ return duplicate;
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}, Item: {(selectedItem == null ? "Empty" : selectedItem.data.Id)}, Comparator: {comparator}, Amount: {amount}";
+ }
+
+ public override void SetNextNode(
+ List connections,
+ Dictionary availableNodes
+ )
+ {
+ SetBranchNodes(connections, availableNodes);
+ }
+}
diff --git a/Scripts/DSL/Nodes/IfNode.cs.uid b/Scripts/DSL/Nodes/IfNode.cs.uid
new file mode 100644
index 0000000..7b21bd5
--- /dev/null
+++ b/Scripts/DSL/Nodes/IfNode.cs.uid
@@ -0,0 +1 @@
+uid://cosidbtpmcj16
diff --git a/Scripts/DSL/Nodes/MaintainNode.cs b/Scripts/DSL/Nodes/MaintainNode.cs
new file mode 100644
index 0000000..9f4cca0
--- /dev/null
+++ b/Scripts/DSL/Nodes/MaintainNode.cs
@@ -0,0 +1,44 @@
+using Godot;
+
+public class MaintainNode : ProgramNode
+{
+ private const float MaintenancePerGear = 10f;
+
+ public MaintainNode()
+ {
+ DisplayText = "Maintain";
+ TooltipText = "Repairs the robot by consuming one matching gear and restoring a small amount of maintenance.";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ string gearId = GetGearId(robot);
+
+ if (!GameData.inventory.TryRemoveItem(gearId, 1))
+ {
+ lastExecutionMessage = $"Missing {ItemData.GetReadableName(gearId)}";
+ return NodeResult.FAILURE;
+ }
+
+ robot.Maintain(MaintenancePerGear);
+ lastExecutionMessage = "";
+ return NodeResult.SUCCESS;
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ MaintainNode duplicate = new MaintainNode();
+ return duplicate;
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}";
+ }
+
+ public static string GetGearId(Robot robot)
+ {
+ string robotType = robot == null ? "stone_robot" : robot.robotType;
+ return robotType.Replace("_robot", "_gear");
+ }
+}
diff --git a/Scripts/DSL/Nodes/MaintainNode.cs.uid b/Scripts/DSL/Nodes/MaintainNode.cs.uid
new file mode 100644
index 0000000..155e28f
--- /dev/null
+++ b/Scripts/DSL/Nodes/MaintainNode.cs.uid
@@ -0,0 +1 @@
+uid://bmibtsh1iq2x
diff --git a/Scripts/DSL/Nodes/MoveNode.cs b/Scripts/DSL/Nodes/MoveNode.cs
new file mode 100644
index 0000000..9e7b7e9
--- /dev/null
+++ b/Scripts/DSL/Nodes/MoveNode.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using Godot;
+
+public class MoveNode : ProgramNode
+{
+ public Vector3 startPosition;
+ public Vector3I targetPosition;
+ public List pathPoints;
+
+ public MoveNode()
+ {
+ DisplayText = "Move";
+ TooltipText = "Moves the robot to the selected map coordinate. The path must be reachable from the robot's current position.";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ Vector3I closestPosition = Pathfinding.GetClosestStartPoint(robot.Position);
+ if (pathPoints == null)
+ {
+ pathPoints = new List(Pathfinding.GetPath(closestPosition, targetPosition));
+ }
+
+ if (pathPoints.Count <= 0)
+ {
+ if ((closestPosition - targetPosition).Length() == 0)
+ {
+ lastExecutionMessage = "";
+ return NodeResult.SUCCESS;
+ }
+
+ lastExecutionMessage = "No path available";
+ return NodeResult.FAILURE;
+ }
+
+ return MoveAlongPath(robot, delta);
+ }
+
+ private NodeResult MoveAlongPath(Robot robot, double delta)
+ {
+ startPosition = robot.Position;
+ Vector3 target = pathPoints[0] - startPosition;
+ float distance = target.Length();
+ float movementSpeed = robot.GetMovementSpeed();
+
+ if (distance < 0.1f * Mathf.Sqrt(movementSpeed))
+ {
+ return FinishCurrentStep(robot);
+ }
+
+ Vector3 direction = target / distance;
+ RotateRobot(robot, direction);
+ robot.GlobalPosition += direction * (float)delta * movementSpeed;
+
+ return NodeResult.RUNNING;
+ }
+
+ private NodeResult FinishCurrentStep(Robot robot)
+ {
+ robot.Position = pathPoints[0];
+ VisitCurrentTile(robot);
+ pathPoints.RemoveAt(0);
+ lastExecutionMessage = "";
+
+ if (pathPoints.Count > 0) return NodeResult.RUNNING;
+
+ pathPoints = null;
+ return NodeResult.SUCCESS;
+ }
+
+ private void VisitCurrentTile(Robot robot)
+ {
+ Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
+ Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
+ if (!tile.wasVisited)
+ {
+ tile.VisitTile();
+ }
+ }
+
+ private void RotateRobot(Robot robot, Vector3 direction)
+ {
+ Vector3 lookDirection = new Vector3(direction.X, 0, direction.Z);
+ if (lookDirection.Length() <= 0.1f) return;
+
+ robot.LookAt(robot.GlobalPosition + lookDirection, Vector3.Up);
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ return new MoveNode
+ {
+ targetPosition = targetPosition
+ };
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}, Position: ({targetPosition.X}|{targetPosition.Y}|{targetPosition.Z})";
+ }
+}
diff --git a/Scripts/DSL/Nodes/MoveNode.cs.uid b/Scripts/DSL/Nodes/MoveNode.cs.uid
new file mode 100644
index 0000000..04d688f
--- /dev/null
+++ b/Scripts/DSL/Nodes/MoveNode.cs.uid
@@ -0,0 +1 @@
+uid://c1yd2vdej67q3
diff --git a/Scripts/DSL/Nodes/ProgramNode.cs b/Scripts/DSL/Nodes/ProgramNode.cs
new file mode 100644
index 0000000..42da444
--- /dev/null
+++ b/Scripts/DSL/Nodes/ProgramNode.cs
@@ -0,0 +1,68 @@
+using Godot;
+using System.Collections.Generic;
+
+public abstract class ProgramNode
+{
+ public ProgramNode nextNode;
+ public ProgramNode NegativeNode;
+ public string EditorNodeId;
+ public string DisplayText;
+ public string TooltipText;
+ public string lastExecutionMessage;
+
+ public abstract NodeResult Execute(Robot robot, double delta);
+ public abstract ProgramNode Duplicate();
+ public abstract string Save();
+
+ public ProgramNode DuplicateForRuntime(string editorNodeId)
+ {
+ ProgramNode duplicate = Duplicate();
+ duplicate.EditorNodeId = editorNodeId;
+ return duplicate;
+ }
+
+ public virtual void SetNextNode(
+ List connections,
+ Dictionary availableNodes
+ )
+ {
+ nextNode = null;
+
+ if (connections.Count <= 0) return;
+
+ nextNode = GetConnectedNode(connections[0], availableNodes);
+ }
+
+ protected void SetBranchNodes(
+ List connections,
+ Dictionary availableNodes
+ )
+ {
+ nextNode = null;
+ NegativeNode = null;
+
+ foreach (Godot.Collections.Dictionary connection in connections)
+ {
+ ProgramNode connectedNode = GetConnectedNode(connection, availableNodes);
+ if ((int)connection["from_port"] == 0)
+ {
+ nextNode = connectedNode;
+ }
+ else
+ {
+ NegativeNode = connectedNode;
+ }
+ }
+ }
+
+ protected ProgramNode GetConnectedNode(
+ Godot.Collections.Dictionary connection,
+ Dictionary availableNodes
+ )
+ {
+ StringName nodeName = connection["to_node"].AsStringName();
+ if (!availableNodes.ContainsKey(nodeName)) return null;
+
+ return availableNodes[nodeName];
+ }
+}
diff --git a/Scripts/DSL/Nodes/ProgramNode.cs.uid b/Scripts/DSL/Nodes/ProgramNode.cs.uid
new file mode 100644
index 0000000..1b03138
--- /dev/null
+++ b/Scripts/DSL/Nodes/ProgramNode.cs.uid
@@ -0,0 +1 @@
+uid://hc0yntn2d358
diff --git a/Scripts/DSL/Nodes/SacrificeNode.cs b/Scripts/DSL/Nodes/SacrificeNode.cs
new file mode 100644
index 0000000..1405a6a
--- /dev/null
+++ b/Scripts/DSL/Nodes/SacrificeNode.cs
@@ -0,0 +1,45 @@
+using Godot;
+
+public class SacrificeNode : ProgramNode
+{
+ public SacrificeNode()
+ {
+ DisplayText = "Sacrifice";
+ TooltipText = "Sacrifices the robot on its current tile to turn that tile's resource into an endless resource.";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ Vector3I mapIndex = Pathfinding.GetClosestStartPoint(robot.Position);
+ Tile tile = GameData.map[mapIndex.Y].tiles[mapIndex.X, mapIndex.Z];
+
+ if (!tile.containsResource || tile.resource == null)
+ {
+ lastExecutionMessage = "No resource on this tile";
+ return NodeResult.FAILURE;
+ }
+
+ if (tile.resource.IsEndless())
+ {
+ lastExecutionMessage = "Resource is already endless";
+ return NodeResult.FAILURE;
+ }
+
+ tile.resource.MakeEndless();
+ GameData.robots.Remove(robot);
+ robot.QueueFree();
+ lastExecutionMessage = "";
+ return NodeResult.SUCCESS;
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ SacrificeNode duplicate = new SacrificeNode();
+ return duplicate;
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}";
+ }
+}
diff --git a/Scripts/DSL/Nodes/SacrificeNode.cs.uid b/Scripts/DSL/Nodes/SacrificeNode.cs.uid
new file mode 100644
index 0000000..0d73163
--- /dev/null
+++ b/Scripts/DSL/Nodes/SacrificeNode.cs.uid
@@ -0,0 +1 @@
+uid://dq8e7txyldpew
diff --git a/Scripts/DSL/Nodes/StartNode.cs b/Scripts/DSL/Nodes/StartNode.cs
new file mode 100644
index 0000000..545b793
--- /dev/null
+++ b/Scripts/DSL/Nodes/StartNode.cs
@@ -0,0 +1,24 @@
+public class StartNode : ProgramNode
+{
+ public StartNode()
+ {
+ DisplayText = "Start";
+ TooltipText = "Marks where the robot begins executing the script. Every valid script needs exactly one Start node.";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ return NodeResult.SUCCESS;
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ StartNode duplicate = new StartNode();
+ return duplicate;
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}";
+ }
+}
diff --git a/Scripts/DSL/Nodes/StartNode.cs.uid b/Scripts/DSL/Nodes/StartNode.cs.uid
new file mode 100644
index 0000000..a94b41a
--- /dev/null
+++ b/Scripts/DSL/Nodes/StartNode.cs.uid
@@ -0,0 +1 @@
+uid://d3kyu2j3nxjsh
diff --git a/Scripts/DSL/Nodes/WhileNode.cs b/Scripts/DSL/Nodes/WhileNode.cs
new file mode 100644
index 0000000..f35cd62
--- /dev/null
+++ b/Scripts/DSL/Nodes/WhileNode.cs
@@ -0,0 +1,70 @@
+using Godot;
+using System.Collections.Generic;
+
+public class WhileNode : ProgramNode
+{
+ public Item selectedItem;
+ public int amount;
+ public string comparator;
+ public WhileNode()
+ {
+ DisplayText = "While";
+ TooltipText = "Repeats the connected branch (Top slot) while the configured inventory condition remains true, then continues through the negative output. (Bottom slot)";
+ }
+
+ public override NodeResult Execute(Robot robot, double delta)
+ {
+ if (selectedItem == null)
+ {
+ lastExecutionMessage = "No Item selected";
+ return NodeResult.FAILURE;
+ }
+
+ if (comparator == null)
+ {
+ lastExecutionMessage = "No comparator selected";
+ return NodeResult.FAILURE;
+ }
+
+ bool isConditionFulfilled = DetermineCondition();
+ return isConditionFulfilled ? NodeResult.SUCCESS : NodeResult.CONDITIONFALSE;
+ }
+
+ private bool DetermineCondition()
+ {
+ int inventoryAmount = GameData.inventory.GetItemAmount(selectedItem.data.Id);
+ return comparator switch
+ {
+ "is bigger than" => inventoryAmount > amount,
+ "is less than" => inventoryAmount < amount,
+ "is not" => inventoryAmount != amount,
+ "is less than or equal to" => inventoryAmount <= amount,
+ "is bigger than or equal to" => inventoryAmount >= amount,
+ _ => inventoryAmount == amount
+ };
+ }
+
+ public override ProgramNode Duplicate()
+ {
+ WhileNode duplicate = new WhileNode()
+ {
+ selectedItem = selectedItem,
+ amount = amount,
+ comparator = comparator
+ };
+ return duplicate;
+ }
+
+ public override string Save()
+ {
+ return $"Name: {DisplayText}, Item: {(selectedItem == null ? "Empty" : selectedItem.data.Id)}, Comparator: {comparator}, Amount: {amount}";
+ }
+
+ public override void SetNextNode(
+ List connections,
+ Dictionary availableNodes
+ )
+ {
+ SetBranchNodes(connections, availableNodes);
+ }
+}
diff --git a/Scripts/DSL/Nodes/WhileNode.cs.uid b/Scripts/DSL/Nodes/WhileNode.cs.uid
new file mode 100644
index 0000000..4e67bee
--- /dev/null
+++ b/Scripts/DSL/Nodes/WhileNode.cs.uid
@@ -0,0 +1 @@
+uid://hmecauiiaggc
diff --git a/Scripts/Gameplay/Crafting/CraftingResult.cs b/Scripts/Gameplay/Crafting/CraftingResult.cs
new file mode 100644
index 0000000..7f81465
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/CraftingResult.cs
@@ -0,0 +1,6 @@
+public enum CraftingResult
+{
+ FAILED,
+ CRAFTING,
+ FINISHED
+}
\ No newline at end of file
diff --git a/Scripts/Gameplay/Crafting/CraftingResult.cs.uid b/Scripts/Gameplay/Crafting/CraftingResult.cs.uid
new file mode 100644
index 0000000..4747de0
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/CraftingResult.cs.uid
@@ -0,0 +1 @@
+uid://bd7t0os166lu3
diff --git a/Scripts/Gameplay/Crafting/GameResource.cs b/Scripts/Gameplay/Crafting/GameResource.cs
new file mode 100644
index 0000000..b515022
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/GameResource.cs
@@ -0,0 +1,95 @@
+public class GameResource
+{
+ private const float NormalExtractionSpeed = 1f;
+ private const float EndlessExtractionSpeed = 4f;
+
+ public string name;
+ public ItemData item;
+
+ private int currentAmount;
+ private int maxAmount;
+ private bool isEndless;
+ private float extractionSpeed;
+ private double timeSinceLastExtraction;
+
+ public GameResource(string name)
+ {
+ this.name = name;
+ maxAmount = GameData.rand.Next(1000, 10000);
+ currentAmount = maxAmount;
+ isEndless = false;
+ extractionSpeed = NormalExtractionSpeed;
+ item = GameData.availableItems[name];
+ }
+
+ public static GameResource FromSaveData(ResourceSaveData saveData)
+ {
+ GameResource resource = new GameResource(saveData.Name)
+ {
+ currentAmount = saveData.CurrentAmount,
+ maxAmount = saveData.MaxAmount,
+ isEndless = saveData.IsEndless,
+ extractionSpeed = saveData.ExtractionSpeed,
+ timeSinceLastExtraction = saveData.TimeSinceLastExtraction
+ };
+ resource.NormalizeExtractionSpeed();
+ return resource;
+ }
+
+ public ResourceSaveData CreateSaveData()
+ {
+ return new ResourceSaveData
+ {
+ Name = name,
+ CurrentAmount = currentAmount,
+ MaxAmount = maxAmount,
+ IsEndless = isEndless,
+ ExtractionSpeed = extractionSpeed,
+ TimeSinceLastExtraction = timeSinceLastExtraction
+ };
+ }
+
+ public bool Extract(double delta)
+ {
+ timeSinceLastExtraction += delta;
+ if (timeSinceLastExtraction < extractionSpeed) return false;
+
+ timeSinceLastExtraction = 0;
+ if (isEndless) return true;
+
+ if (currentAmount <= 0) return false;
+
+ currentAmount--;
+ return true;
+ }
+
+ public bool CanExtract()
+ {
+ return (isEndless || currentAmount > 0)
+ && GameData.availableResearch[item.Research].state == ResearchState.RESEARCHED;
+ }
+
+ public void MakeEndless()
+ {
+ isEndless = true;
+ extractionSpeed = EndlessExtractionSpeed;
+ }
+
+ public bool IsEndless()
+ {
+ return isEndless;
+ }
+
+ public float GetExtractionSpeed()
+ {
+ return extractionSpeed;
+ }
+
+ private void NormalizeExtractionSpeed()
+ {
+ if (!isEndless) return;
+ if (extractionSpeed > NormalExtractionSpeed) return;
+
+ extractionSpeed = EndlessExtractionSpeed;
+ }
+}
diff --git a/Scripts/Gameplay/Crafting/GameResource.cs.uid b/Scripts/Gameplay/Crafting/GameResource.cs.uid
new file mode 100644
index 0000000..abb4096
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/GameResource.cs.uid
@@ -0,0 +1 @@
+uid://bh4m8ufiv5kt8
diff --git a/Scripts/Gameplay/Crafting/Ingredient.cs b/Scripts/Gameplay/Crafting/Ingredient.cs
new file mode 100644
index 0000000..bcaff09
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/Ingredient.cs
@@ -0,0 +1,10 @@
+using System.Text.Json.Serialization;
+
+public class Ingredient
+{
+ [JsonPropertyName("item")]
+ public string Item { get; set; }
+
+ [JsonPropertyName("amount")]
+ public int Amount { get; set; }
+}
diff --git a/Scripts/Gameplay/Crafting/Ingredient.cs.uid b/Scripts/Gameplay/Crafting/Ingredient.cs.uid
new file mode 100644
index 0000000..5ec77df
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/Ingredient.cs.uid
@@ -0,0 +1 @@
+uid://dital81qfptdr
diff --git a/Scripts/Gameplay/Crafting/Inventory.cs b/Scripts/Gameplay/Crafting/Inventory.cs
new file mode 100644
index 0000000..5d96218
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/Inventory.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Collections.Generic;
+
+public class Inventory
+{
+ public List- items = new List
- ();
+
+ public int maxInventorySize = 10;
+ public event EventHandler OnInventoryUpdate;
+
+ public bool AddItem(Item item, int amount)
+ {
+ if (GetFreeCapacity(item.data.Id, item.data.StackSize) < amount)
+ {
+ return false;
+ }
+
+ int remainingAmount = amount;
+
+ foreach (Item inventoryItem in items)
+ {
+ if (inventoryItem.data.Id != item.data.Id) continue;
+ if (inventoryItem.currentAmount >= inventoryItem.data.StackSize) continue;
+
+ int amountToAdd = Math.Min(
+ remainingAmount,
+ inventoryItem.data.StackSize - inventoryItem.currentAmount
+ );
+
+ inventoryItem.currentAmount += amountToAdd;
+ remainingAmount -= amountToAdd;
+
+ if (remainingAmount <= 0)
+ {
+ NotifyInventoryChanged();
+ return true;
+ }
+ }
+
+ while (remainingAmount > 0 && items.Count < maxInventorySize * GameData.maxRobotCount)
+ {
+ int amountToAdd = Math.Min(remainingAmount, item.data.StackSize);
+ items.Add(new Item { data = item.data, currentAmount = amountToAdd });
+ remainingAmount -= amountToAdd;
+ }
+
+ NotifyInventoryChanged();
+ return remainingAmount <= 0;
+ }
+
+ public bool CanCraft(List neededIngredients, int amount)
+ {
+ foreach (Ingredient ingredient in neededIngredients)
+ {
+ if (GetItemAmount(ingredient.Item) < ingredient.Amount * amount)
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public void RemoveItem(string id, int amount)
+ {
+ TryRemoveItem(id, amount);
+ }
+
+ public bool TryRemoveItem(string id, int amount)
+ {
+ if (GetItemAmount(id) < amount)
+ {
+ return false;
+ }
+
+ int remainingAmount = amount;
+ for (int i = items.Count - 1; i >= 0 && remainingAmount > 0; i--)
+ {
+ Item item = items[i];
+ if (item.data.Id != id) continue;
+
+ int removedAmount = Math.Min(item.currentAmount, remainingAmount);
+ item.currentAmount -= removedAmount;
+ remainingAmount -= removedAmount;
+
+ if (item.currentAmount <= 0)
+ {
+ items.RemoveAt(i);
+ }
+ }
+
+ NotifyInventoryChanged();
+ return true;
+ }
+
+ public int GetItemAmount(string id)
+ {
+ int amount = 0;
+ foreach (Item item in items)
+ {
+ if (item.data.Id == id)
+ {
+ amount += item.currentAmount;
+ }
+ }
+
+ return amount;
+ }
+
+ private void NotifyInventoryChanged()
+ {
+ OnInventoryUpdate?.Invoke(this, EventArgs.Empty);
+ }
+
+ private int GetFreeCapacity(string id, int stackSize)
+ {
+ int freeCapacity = 0;
+
+ foreach (Item item in items)
+ {
+ if (item.data.Id == id)
+ {
+ freeCapacity += stackSize - item.currentAmount;
+ }
+ }
+
+ int freeSlots = maxInventorySize * GameData.maxRobotCount - items.Count;
+ freeCapacity += freeSlots * stackSize;
+ return freeCapacity;
+ }
+}
diff --git a/Scripts/Gameplay/Crafting/Inventory.cs.uid b/Scripts/Gameplay/Crafting/Inventory.cs.uid
new file mode 100644
index 0000000..94a0184
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/Inventory.cs.uid
@@ -0,0 +1 @@
+uid://dnbdfvox75hk8
diff --git a/Scripts/Gameplay/Crafting/Item.cs b/Scripts/Gameplay/Crafting/Item.cs
new file mode 100644
index 0000000..7a9f8bb
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/Item.cs
@@ -0,0 +1,35 @@
+public class Item
+{
+ public ItemData data;
+ public int currentAmount = 0;
+ public double elapsedCraftTime = 0;
+ public int amountCrafted = 0;
+
+ public CraftingResult Craft(int amount, double delta)
+ {
+ elapsedCraftTime += delta;
+ if (elapsedCraftTime >= data.CraftTime)
+ {
+ elapsedCraftTime -= data.CraftTime;
+ if (!GameData.inventory.AddItem(this, 1))
+ {
+ return CraftingResult.FAILED;
+ }
+
+ foreach (Ingredient ingredient in data.Inputs)
+ {
+ GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount);
+ }
+
+ amountCrafted++;
+ if (amountCrafted >= amount)
+ {
+ amountCrafted = 0;
+ elapsedCraftTime = 0;
+ return CraftingResult.FINISHED;
+ }
+ }
+
+ return CraftingResult.CRAFTING;
+ }
+}
diff --git a/Scripts/Gameplay/Crafting/Item.cs.uid b/Scripts/Gameplay/Crafting/Item.cs.uid
new file mode 100644
index 0000000..590b5d7
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/Item.cs.uid
@@ -0,0 +1 @@
+uid://dpq18vk7hbakh
diff --git a/Scripts/Gameplay/Crafting/ItemData.cs b/Scripts/Gameplay/Crafting/ItemData.cs
new file mode 100644
index 0000000..895d19b
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/ItemData.cs
@@ -0,0 +1,62 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+public class ItemData
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("inputs")]
+ public List Inputs { get; set; }
+
+ [JsonPropertyName("output")]
+ public Ingredient Output { get; set; }
+
+ [JsonPropertyName("workstation")]
+ public string Workstation { get; set; }
+
+ [JsonPropertyName("texture")]
+ public string Texture { get; set; }
+
+ [JsonPropertyName("research")]
+ public string Research { get; set; }
+
+ [JsonPropertyName("crafttime")]
+ public double CraftTime { get; set; }
+
+ [JsonPropertyName("stacksize")]
+ public int StackSize { get; set; }
+
+ public string GetReadableName()
+ {
+ return GetReadableName(Id);
+ }
+
+ public static string GetReadableName(string input)
+ {
+ if (string.IsNullOrEmpty(input)) return "";
+
+ string noUnderscore = input.Replace("_", " ").ToLower();
+ return char.ToUpper(noUnderscore[0]) + noUnderscore.Substring(1);
+ }
+
+ public static string GetIndex(string readable)
+ {
+ return readable.ToLower().Replace(" ", "_");
+ }
+
+ public string GetCraftingDisplay()
+ {
+ if (Inputs.Count <= 0) return GetReadableName() + ": \r";
+
+ string result = GetReadableName() + ": \r";
+
+ foreach (Ingredient ingredient in Inputs)
+ {
+ result += $"{GetReadableName(ingredient.Item)} ({ingredient.Amount}),\r";
+ }
+
+ result = result.Remove(result.Length - 2);
+ return result;
+ }
+}
diff --git a/Scripts/Gameplay/Crafting/ItemData.cs.uid b/Scripts/Gameplay/Crafting/ItemData.cs.uid
new file mode 100644
index 0000000..4099f5c
--- /dev/null
+++ b/Scripts/Gameplay/Crafting/ItemData.cs.uid
@@ -0,0 +1 @@
+uid://cj8yubkrsdq70
diff --git a/Scripts/Gameplay/Research/Research.cs b/Scripts/Gameplay/Research/Research.cs
new file mode 100644
index 0000000..28f21ef
--- /dev/null
+++ b/Scripts/Gameplay/Research/Research.cs
@@ -0,0 +1,58 @@
+public class Research
+{
+ public ResearchData data;
+ public double elapsedResearchTime = 0;
+ public bool paidResources = false;
+ public ResearchState state;
+
+ public Research(ResearchData data)
+ {
+ this.data = data;
+ state = data.Id == "basics" ? ResearchState.RESEARCHED : ResearchState.UNDEFINED;
+ }
+
+ public ResearchResult Execute(double delta)
+ {
+ if (!paidResources)
+ {
+ if (!CanStart()) return ResearchResult.FAILED;
+
+ PayResources();
+ paidResources = true;
+ }
+
+ elapsedResearchTime += delta;
+ if (elapsedResearchTime >= data.CraftTime)
+ {
+ Complete();
+ return ResearchResult.FINISHED;
+ }
+ return ResearchResult.RESEARCHING;
+ }
+
+ public bool CanStart()
+ {
+ return GameData.inventory.CanCraft(data.Inputs, 1);
+ }
+
+ public void PayResources()
+ {
+ foreach (Ingredient ingredient in data.Inputs)
+ {
+ GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount);
+ }
+ }
+
+ public void Complete()
+ {
+ if (state == ResearchState.RESEARCHED) return;
+
+ state = ResearchState.RESEARCHED;
+ GameData.robotStats.Apply(data.Effects);
+ }
+
+ public static string GetReadableName(string input)
+ {
+ return ItemData.GetReadableName(input);
+ }
+}
diff --git a/Scripts/Gameplay/Research/Research.cs.uid b/Scripts/Gameplay/Research/Research.cs.uid
new file mode 100644
index 0000000..372f160
--- /dev/null
+++ b/Scripts/Gameplay/Research/Research.cs.uid
@@ -0,0 +1 @@
+uid://dj2epbu26v1nv
diff --git a/Scripts/Gameplay/Research/ResearchData.cs b/Scripts/Gameplay/Research/ResearchData.cs
new file mode 100644
index 0000000..1887273
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchData.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+public class ResearchData
+{
+ [JsonPropertyName("id")]
+ public string Id { get; set; }
+
+ [JsonPropertyName("inputs")]
+ public List Inputs { get; set; }
+
+ [JsonPropertyName("research")]
+ public string Research { get; set; }
+
+ [JsonPropertyName("crafttime")]
+ public double CraftTime { get; set; }
+
+ [JsonPropertyName("texture")]
+ public string Texture { get; set; }
+
+ [JsonPropertyName("effects")]
+ public List Effects { get; set; }
+
+ public string GetReadableName()
+ {
+ return ItemData.GetReadableName(Id);
+ }
+
+ public static string GetIndex(string readable)
+ {
+ return readable.ToLower().Replace(" ", "_");
+ }
+}
diff --git a/Scripts/Gameplay/Research/ResearchData.cs.uid b/Scripts/Gameplay/Research/ResearchData.cs.uid
new file mode 100644
index 0000000..0f086c7
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchData.cs.uid
@@ -0,0 +1 @@
+uid://duwl6bhiry6uh
diff --git a/Scripts/Gameplay/Research/ResearchEffect.cs b/Scripts/Gameplay/Research/ResearchEffect.cs
new file mode 100644
index 0000000..51b0746
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchEffect.cs
@@ -0,0 +1,25 @@
+using System.Text.Json.Serialization;
+
+public class ResearchEffect
+{
+ [JsonPropertyName("stat")]
+ public string Stat { get; set; }
+
+ [JsonPropertyName("value")]
+ public float Value { get; set; }
+
+ public string GetDisplayText()
+ {
+ return Stat switch
+ {
+ "robot_speed_bonus" => $"Robot speed +{Value * 100f:0}%",
+ "robot_energy_use_reduction" => $"Robot energy use -{Value * 100f:0}%",
+ "robot_heat_gain_reduction" => $"Robot heat gain -{Value * 100f:0}%",
+ "robot_cooling_bonus" => $"Robot cooling +{Value * 100f:0}%",
+ "robot_maintenance_loss_reduction" => $"Robot maintenance loss -{Value * 100f:0}%",
+ "robot_minimum_efficiency_bonus" => $"Damaged robot efficiency +{Value * 100f:0}%",
+ "robot_count_increase" => $"Allows more robots +{Value}",
+ _ => Stat
+ };
+ }
+}
diff --git a/Scripts/Gameplay/Research/ResearchEffect.cs.uid b/Scripts/Gameplay/Research/ResearchEffect.cs.uid
new file mode 100644
index 0000000..9e03b13
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchEffect.cs.uid
@@ -0,0 +1 @@
+uid://c6k1b1inavgyj
diff --git a/Scripts/Gameplay/Research/ResearchResult.cs b/Scripts/Gameplay/Research/ResearchResult.cs
new file mode 100644
index 0000000..cfaab1b
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchResult.cs
@@ -0,0 +1,6 @@
+public enum ResearchResult
+{
+ FAILED,
+ RESEARCHING,
+ FINISHED
+}
\ No newline at end of file
diff --git a/Scripts/Gameplay/Research/ResearchResult.cs.uid b/Scripts/Gameplay/Research/ResearchResult.cs.uid
new file mode 100644
index 0000000..7c4d11f
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchResult.cs.uid
@@ -0,0 +1 @@
+uid://cuav7j7m1td2h
diff --git a/Scripts/Gameplay/Research/ResearchState.cs b/Scripts/Gameplay/Research/ResearchState.cs
new file mode 100644
index 0000000..3ad4455
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchState.cs
@@ -0,0 +1,8 @@
+public enum ResearchState
+{
+ UNDEFINED,
+ RESEARCHED,
+ AVAILABLE,
+ LOCKED,
+ RESEARCHING
+}
\ No newline at end of file
diff --git a/Scripts/Gameplay/Research/ResearchState.cs.uid b/Scripts/Gameplay/Research/ResearchState.cs.uid
new file mode 100644
index 0000000..26dec14
--- /dev/null
+++ b/Scripts/Gameplay/Research/ResearchState.cs.uid
@@ -0,0 +1 @@
+uid://b57yhucbav37c
diff --git a/Scripts/Gameplay/Robots/Robot.cs b/Scripts/Gameplay/Robots/Robot.cs
new file mode 100644
index 0000000..21f9fca
--- /dev/null
+++ b/Scripts/Gameplay/Robots/Robot.cs
@@ -0,0 +1,286 @@
+using System;
+using System.Collections.Generic;
+using Godot;
+
+public partial class Robot : Node3D
+{
+ private const float EnergyUsePerSecond = 0.3f;
+ private const float HeatGainPerSecond = 2.5f;
+ private const float ActiveHeatLossPerSecond = 22f;
+ private const float IdleHeatLossPerSecond = 12f;
+ private const float CooldownTarget = 35f;
+ private const float MaintenanceLossPerSecond = 0.025f;
+
+ public bool isExecuting = false;
+
+ public ProgramNode programStartNode;
+ public ProgramNode currentNode;
+ public string currentProgram;
+ public string currentMessage = "";
+ public float heat = 0f;
+ public float maintenance = 100f;
+ public bool isCoolingDown = false;
+ public bool isBroken = false;
+ public string robotType = "iron_robot";
+ public bool showOnMap = true;
+
+ private RobotTypeStats TypeStats =>
+ GameData.robotStats.RobotTypes.TryGetValue(robotType, out RobotTypeStats stats)
+ ? stats
+ : GameData.robotStats.RobotTypes["stone_robot"];
+
+ public override void _Process(double delta)
+ {
+ if (GameData.isPaused) return;
+
+ if (isExecuting)
+ {
+ UpdateExecution(delta);
+ }
+ else
+ {
+ UpdateIdle(delta);
+ }
+
+ Visible = Math.Round(Math.Abs(Position.Y / GameData.tileHeight), 0) == GameData.visibleLayer;
+ }
+
+ private void UpdateExecution(double delta)
+ {
+ if (!CanExecute(delta)) return;
+
+ NodeResult result = currentNode.Execute(this, delta);
+ ApplyNodeResult(result);
+ }
+
+ private void ApplyNodeResult(NodeResult result)
+ {
+ switch (result)
+ {
+ case NodeResult.SUCCESS:
+ MoveToNextNode(currentNode.nextNode);
+ break;
+
+ case NodeResult.CONDITIONFALSE:
+ MoveToNextNode(currentNode.NegativeNode);
+ break;
+
+ case NodeResult.FAILURE:
+ isExecuting = false;
+ currentMessage = "(FAILED)" + currentNode.lastExecutionMessage;
+ break;
+
+ case NodeResult.RUNNING:
+ currentMessage = "";
+ break;
+ }
+ }
+
+ private void MoveToNextNode(ProgramNode nextNode)
+ {
+ currentNode = nextNode;
+ if (currentNode == null)
+ {
+ isExecuting = false;
+ }
+ }
+
+ private void UpdateIdle(double delta)
+ {
+ CoolDown(
+ delta,
+ GameData.robotStats.GetCoolingRate(IdleHeatLossPerSecond)
+ * TypeStats.CoolingMultiplier
+ );
+
+ if (currentMessage.Length <= 0)
+ {
+ currentMessage = "No script executing";
+ }
+ }
+
+ public void SetupExecution(List nodes)
+ {
+ if (nodes.Count <= 0) return;
+
+ isExecuting = true;
+ programStartNode = nodes[0];
+ currentNode = nodes[0];
+ currentMessage = "";
+ }
+
+ public void StopExecution(string message)
+ {
+ isExecuting = false;
+ programStartNode = null;
+ currentNode = null;
+ currentMessage = message;
+ }
+
+ public float GetMovementSpeed()
+ {
+ return GameData.robotStats.GetMovementSpeed(GameData.robotSpeed)
+ * TypeStats.SpeedMultiplier
+ * GetWorkEfficiency();
+ }
+
+ public float GetWorkEfficiency()
+ {
+ if (isBroken) return 0f;
+ if (maintenance >= 50f) return 1f;
+
+ float minimumEfficiency =
+ GameData.robotStats.GetMinimumEfficiency()
+ + TypeStats.MinimumEfficiencyBonus;
+
+ minimumEfficiency = Math.Clamp(minimumEfficiency, 0f, 1f);
+
+ return Math.Clamp(
+ minimumEfficiency + maintenance / 100f,
+ minimumEfficiency,
+ 1f
+ );
+ }
+
+ public void Maintain()
+ {
+ maintenance = 100f;
+ isBroken = false;
+ currentMessage = "";
+ }
+
+ public void Maintain(float amount)
+ {
+ maintenance = Math.Clamp(maintenance + amount, 0f, 100f);
+ if (maintenance > 0f)
+ {
+ isBroken = false;
+ }
+ currentMessage = "";
+ }
+
+ public RobotSaveData CreateSaveData()
+ {
+ return new RobotSaveData
+ {
+ Name = Name,
+ CurrentProgram = currentProgram,
+ CurrentMessage = currentMessage,
+ RobotType = robotType,
+ X = Position.X,
+ Y = Position.Y,
+ Z = Position.Z,
+ Heat = heat,
+ Maintenance = maintenance,
+ IsCoolingDown = isCoolingDown,
+ IsBroken = isBroken
+ };
+ }
+
+ public void LoadSaveData(RobotSaveData saveData)
+ {
+ Name = saveData.Name;
+ currentProgram = saveData.CurrentProgram;
+ currentMessage = saveData.CurrentMessage ?? "";
+ robotType = saveData.RobotType ?? "stone_robot";
+ Position = new Vector3(saveData.X, saveData.Y, saveData.Z);
+ heat = saveData.Heat;
+ maintenance = saveData.Maintenance;
+ isCoolingDown = saveData.IsCoolingDown;
+ isBroken = saveData.IsBroken;
+ }
+
+ private bool CanExecute(double delta)
+ {
+ if (GameData.survival.isDead)
+ {
+ currentMessage = "Survival failed";
+ return false;
+ }
+
+ if (isBroken)
+ {
+ currentMessage = "Maintenance required";
+ return false;
+ }
+
+ if (isCoolingDown)
+ {
+ CoolDown(
+ delta,
+ GameData.robotStats.GetCoolingRate(ActiveHeatLossPerSecond)
+ * TypeStats.CoolingMultiplier
+ );
+
+ currentMessage = $"Cooling down ({heat:0}%)";
+ return false;
+ }
+
+ if (!TryConsumeEnergy(delta))
+ {
+ currentMessage = "Not enough energy";
+ return false;
+ }
+
+ ApplyWear(delta);
+
+ if (heat >= 100f)
+ {
+ isCoolingDown = true;
+ currentMessage = "Overheated";
+ return false;
+ }
+
+ if (maintenance <= 0f)
+ {
+ isBroken = true;
+ currentMessage = "Maintenance required";
+ return false;
+ }
+
+ return true;
+ }
+
+ private bool TryConsumeEnergy(double delta)
+ {
+ float energyUse =
+ GameData.robotStats.GetEnergyUse(EnergyUsePerSecond)
+ * TypeStats.EnergyUseMultiplier
+ * (float)delta;
+
+ return GameData.survival.TryConsumeEnergy(energyUse);
+ }
+
+ private void ApplyWear(double delta)
+ {
+ heat = Math.Clamp(
+ heat + GameData.robotStats.GetHeatGain(HeatGainPerSecond)
+ * TypeStats.HeatGainMultiplier
+ * (float)delta,
+ 0f,
+ 100f
+ );
+
+ maintenance = Math.Clamp(
+ maintenance - GameData.robotStats.GetMaintenanceLoss(MaintenanceLossPerSecond)
+ * TypeStats.MaintenanceLossMultiplier
+ * (float)delta,
+ 0f,
+ 100f
+ );
+ }
+
+ private void CoolDown(double delta, float heatLossPerSecond)
+ {
+ heat = Math.Clamp(
+ heat - heatLossPerSecond * (float)delta,
+ 0f,
+ 100f
+ );
+
+ if (heat <= CooldownTarget)
+ {
+ isCoolingDown = false;
+ }
+ }
+}
diff --git a/Scripts/Gameplay/Robots/Robot.cs.uid b/Scripts/Gameplay/Robots/Robot.cs.uid
new file mode 100644
index 0000000..bc84dea
--- /dev/null
+++ b/Scripts/Gameplay/Robots/Robot.cs.uid
@@ -0,0 +1 @@
+uid://e0pgy7jya41y
diff --git a/Scripts/Gameplay/Robots/RobotStats.cs b/Scripts/Gameplay/Robots/RobotStats.cs
new file mode 100644
index 0000000..f4c1f1f
--- /dev/null
+++ b/Scripts/Gameplay/Robots/RobotStats.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+
+public class RobotStats
+{
+ public readonly Dictionary RobotTypes = new Dictionary
+ {
+ { "stone_robot", new RobotTypeStats(0.75f, 0.60f, 0.80f, 0.80f, 1.40f, -0.10f) },
+ { "copper_robot", new RobotTypeStats(1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 0.00f) },
+ { "tin_robot", new RobotTypeStats(1.00f, 1.00f, 1.00f, 1.00f, 1.00f, 0.00f) },
+ { "bronze_robot", new RobotTypeStats(1.15f, 1.10f, 0.90f, 1.10f, 0.80f, 0.05f) },
+ { "iron_robot", new RobotTypeStats(1.35f, 1.25f, 1.15f, 1.20f, 0.65f, 0.10f) }
+ };
+ private const float BaseMinimumEfficiency = 0.35f;
+
+ private float speedBonus = 0f;
+ private float energyUseReduction = 0f;
+ private float heatGainReduction = 0f;
+ private float coolingBonus = 0f;
+ private float maintenanceLossReduction = 0f;
+ private float minimumEfficiencyBonus = 0f;
+
+ public float GetMovementSpeed(float baseSpeed)
+ {
+ return baseSpeed * (1f + speedBonus);
+ }
+
+ public float GetEnergyUse(float baseEnergyUse)
+ {
+ return baseEnergyUse * (1f - Math.Clamp(energyUseReduction, 0f, 0.8f));
+ }
+
+ public float GetHeatGain(float baseHeatGain)
+ {
+ return baseHeatGain * (1f - Math.Clamp(heatGainReduction, 0f, 0.8f));
+ }
+
+ public float GetCoolingRate(float baseCoolingRate)
+ {
+ return baseCoolingRate * (1f + coolingBonus);
+ }
+
+ public float GetMaintenanceLoss(float baseMaintenanceLoss)
+ {
+ return baseMaintenanceLoss * (1f - Math.Clamp(maintenanceLossReduction, 0f, 0.8f));
+ }
+
+ public float GetMinimumEfficiency()
+ {
+ return Math.Clamp(BaseMinimumEfficiency + minimumEfficiencyBonus, BaseMinimumEfficiency, 0.85f);
+ }
+
+ public void Apply(List effects)
+ {
+ if (effects == null) return;
+
+ foreach (ResearchEffect effect in effects)
+ {
+ Apply(effect);
+ }
+ }
+
+ private void Apply(ResearchEffect effect)
+ {
+ if (effect == null) return;
+
+ switch (effect.Stat)
+ {
+ case "robot_speed_bonus":
+ speedBonus += effect.Value;
+ break;
+ case "robot_energy_use_reduction":
+ energyUseReduction += effect.Value;
+ break;
+ case "robot_heat_gain_reduction":
+ heatGainReduction += effect.Value;
+ break;
+ case "robot_cooling_bonus":
+ coolingBonus += effect.Value;
+ break;
+ case "robot_maintenance_loss_reduction":
+ maintenanceLossReduction += effect.Value;
+ break;
+ case "robot_minimum_efficiency_bonus":
+ minimumEfficiencyBonus += effect.Value;
+ break;
+ case "robot_count_increase":
+ GameData.maxRobotCount += (int)effect.Value;
+ break;
+ }
+ }
+}
diff --git a/Scripts/Gameplay/Robots/RobotStats.cs.uid b/Scripts/Gameplay/Robots/RobotStats.cs.uid
new file mode 100644
index 0000000..7e4d0ec
--- /dev/null
+++ b/Scripts/Gameplay/Robots/RobotStats.cs.uid
@@ -0,0 +1 @@
+uid://djuorq37m01xc
diff --git a/Scripts/Gameplay/Robots/RobotTypeStats.cs b/Scripts/Gameplay/Robots/RobotTypeStats.cs
new file mode 100644
index 0000000..8cd1679
--- /dev/null
+++ b/Scripts/Gameplay/Robots/RobotTypeStats.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+
+public readonly struct RobotTypeStats
+{
+ public readonly float SpeedMultiplier;
+ public readonly float EnergyUseMultiplier;
+ public readonly float HeatGainMultiplier;
+ public readonly float CoolingMultiplier;
+ public readonly float MaintenanceLossMultiplier;
+ public readonly float MinimumEfficiencyBonus;
+
+ public RobotTypeStats(
+ float speedMultiplier,
+ float energyUseMultiplier,
+ float heatGainMultiplier,
+ float coolingMultiplier,
+ float maintenanceLossMultiplier,
+ float minimumEfficiencyBonus)
+ {
+ SpeedMultiplier = speedMultiplier;
+ EnergyUseMultiplier = energyUseMultiplier;
+ HeatGainMultiplier = heatGainMultiplier;
+ CoolingMultiplier = coolingMultiplier;
+ MaintenanceLossMultiplier = maintenanceLossMultiplier;
+ MinimumEfficiencyBonus = minimumEfficiencyBonus;
+ }
+}
+
diff --git a/Scripts/Gameplay/Robots/RobotTypeStats.cs.uid b/Scripts/Gameplay/Robots/RobotTypeStats.cs.uid
new file mode 100644
index 0000000..ec5afb0
--- /dev/null
+++ b/Scripts/Gameplay/Robots/RobotTypeStats.cs.uid
@@ -0,0 +1 @@
+uid://bfmf5tb3pmmug
diff --git a/Scripts/Gameplay/Survival/SurvivalState.cs b/Scripts/Gameplay/Survival/SurvivalState.cs
new file mode 100644
index 0000000..5e92378
--- /dev/null
+++ b/Scripts/Gameplay/Survival/SurvivalState.cs
@@ -0,0 +1,158 @@
+using System;
+
+public class SurvivalState
+{
+ private const float HungerDrainPerSecond = 0.012f;
+ private const float ThirstDrainPerSecond = 0.018f;
+ private const float PassiveEnergyDrainPerSecond = 0.01f;
+ private const float AutoConsumeThreshold = 30f;
+ private const double GracePeriodSeconds = 900.0;
+
+ public float hunger = 100f;
+ public float thirst = 100f;
+ public float energy = 100f;
+
+ public float maxHunger = 100f;
+ public float maxThirst = 100f;
+ public float maxEnergy = 100f;
+
+ public bool isDead = false;
+ public string deathReason = "";
+ public string currentStatus = "";
+ public double elapsedSeconds = 0;
+ public bool gracePeriodEnabled = true;
+
+ public void Update(double delta)
+ {
+ if (isDead) return;
+
+ elapsedSeconds += delta;
+ float drainModifier = IsInGracePeriod() ? 0.35f : 1f;
+
+ hunger = Math.Clamp(hunger - HungerDrainPerSecond * drainModifier * (float)delta, 0f, maxHunger);
+ thirst = Math.Clamp(thirst - ThirstDrainPerSecond * drainModifier * (float)delta, 0f, maxThirst);
+ energy = Math.Clamp(energy - PassiveEnergyDrainPerSecond * drainModifier * (float)delta, 0f, maxEnergy);
+
+ TryAutoConsumeFood();
+ TryAutoConsumeWater();
+ TryAutoConsumeEnergy();
+ UpdateStatus();
+ CheckDeath();
+ }
+
+ public bool TryConsumeEnergy(float amount)
+ {
+ if (amount <= 0f) return true;
+ if (energy < amount) return false;
+
+ energy -= amount;
+ return true;
+ }
+
+ private void TryAutoConsumeFood()
+ {
+ if (hunger > AutoConsumeThreshold) return;
+ if (!GameData.inventory.TryRemoveItem("mushroom", 1)) return;
+
+ hunger = Math.Clamp(hunger + 45f, 0f, maxHunger);
+ }
+
+ private void TryAutoConsumeWater()
+ {
+ if (thirst > AutoConsumeThreshold) return;
+ if (!GameData.inventory.TryRemoveItem("water", 1)) return;
+
+ thirst = Math.Clamp(thirst + 50f, 0f, maxThirst);
+ }
+
+ private void TryAutoConsumeEnergy()
+ {
+ if (energy > AutoConsumeThreshold) return;
+
+ if (GameData.inventory.TryRemoveItem("battery_v2", 1))
+ {
+ energy = Math.Clamp(energy + 90f, 0f, maxEnergy);
+ return;
+ }
+
+ if (GameData.inventory.TryRemoveItem("battery_v1", 1))
+ {
+ energy = Math.Clamp(energy + 65f, 0f, maxEnergy);
+ return;
+ }
+
+ if (GameData.inventory.TryRemoveItem("steam", 1))
+ {
+ energy = Math.Clamp(energy + 40f, 0f, maxEnergy);
+ }
+
+ if (GameData.inventory.TryRemoveItem("coal", 1))
+ {
+ energy = Math.Clamp(energy + 10f, 0f, maxEnergy);
+ }
+ }
+
+ private void UpdateStatus()
+ {
+ if (hunger <= AutoConsumeThreshold)
+ {
+ currentStatus = "Food supply critical";
+ return;
+ }
+
+ if (thirst <= AutoConsumeThreshold)
+ {
+ currentStatus = "Water supply critical";
+ return;
+ }
+
+ if (energy <= AutoConsumeThreshold)
+ {
+ currentStatus = "Energy reserves critical";
+ return;
+ }
+
+ currentStatus = "Survival stable";
+ }
+
+ private void CheckDeath()
+ {
+ if (IsInGracePeriod())
+ {
+ hunger = Math.Max(hunger, 1f);
+ thirst = Math.Max(thirst, 1f);
+ energy = Math.Max(energy, 1f);
+ return;
+ }
+
+ if (hunger <= 0f)
+ {
+ KillPlayer("Starved");
+ return;
+ }
+
+ if (thirst <= 0f)
+ {
+ KillPlayer("Died of thirst");
+ return;
+ }
+
+ if (energy <= 0f)
+ {
+ KillPlayer("Life support lost power");
+ }
+ }
+
+ private void KillPlayer(string reason)
+ {
+ isDead = true;
+ deathReason = reason;
+ currentStatus = "Survival failed: " + reason;
+ GameData.canMove = false;
+ }
+
+ private bool IsInGracePeriod()
+ {
+ return gracePeriodEnabled && elapsedSeconds < GracePeriodSeconds;
+ }
+}
diff --git a/Scripts/Gameplay/Survival/SurvivalState.cs.uid b/Scripts/Gameplay/Survival/SurvivalState.cs.uid
new file mode 100644
index 0000000..357a62e
--- /dev/null
+++ b/Scripts/Gameplay/Survival/SurvivalState.cs.uid
@@ -0,0 +1 @@
+uid://fpqk4b1v48xy
diff --git a/Scripts/Tests/TestRunner.cs b/Scripts/Tests/TestRunner.cs
new file mode 100644
index 0000000..70f8ef9
--- /dev/null
+++ b/Scripts/Tests/TestRunner.cs
@@ -0,0 +1,654 @@
+using Godot;
+using System;
+using System.Collections.Generic;
+
+public partial class TestRunner : Node
+{
+ private int passedTests = 0;
+ private int failedTests = 0;
+
+ public override void _Ready()
+ {
+ Run("Inventory adds, stacks and removes items", TestInventoryStacksAndRemovesItems);
+ Run("Inventory crafting checks stacked totals", TestInventoryCanCraftAcrossStacks);
+ Run("Survival consumes stored food and water", TestSurvivalConsumesFoodAndWater);
+ Run("Survival grace period prevents early death", TestSurvivalGracePeriodPreventsEarlyDeath);
+ Run("Survival death disables movement", TestSurvivalDeathDisablesMovement);
+ Run("Robot research effects change robot stats", TestRobotResearchEffects);
+ Run("Research completion applies effects once", TestResearchCompletionAppliesEffectsOnce);
+ Run("Research execution pays resources and finishes", TestResearchExecutionPaysResourcesAndFinishes);
+ Run("Research cannot start without resources", TestResearchCannotStartWithoutResources);
+ Run("Inventory add failure keeps inventory unchanged", TestInventoryAddFailureKeepsInventoryUnchanged);
+ Run("Saved scripts can be deleted", TestSavedScriptsCanBeDeleted);
+ Run("Resource extraction and save data roundtrip", TestResourceSaveRoundtrip);
+ Run("Endless resources extract slower", TestEndlessResourcesExtractSlower);
+ Run("Robot save data roundtrip keeps robot state", TestRobotSaveRoundtrip);
+ Run("Maintain node consumes matching gear", TestMaintainNodeConsumesMatchingGear);
+ Run("Sacrifice node makes resource endless", TestSacrificeNodeMakesResourceEndless);
+ Run("No robot recovery detects loss", TestNoRobotRecoveryDetectsLoss);
+ Run("Robot item prevents no robot loss", TestRobotItemPreventsNoRobotLoss);
+ Run("Save data captures and restores options", TestSaveDataRestoresOptions);
+ Run("Save data captures and restores global state", TestSaveDataRestoresGlobalState);
+ Run("Save data restores survival timer", TestSaveDataRestoresSurvivalTimer);
+ Run("Split save files store and load data", TestSplitSaveFilesRoundtrip);
+ Run("Split save files include one file per saved layer", TestSplitSaveFilesIncludeLayerFiles);
+ Run("If node evaluates inventory comparisons", TestIfNodeEvaluatesInventoryComparisons);
+ Run("While node reports false conditions", TestWhileNodeReportsFalseConditions);
+ Run("For node stops after configured amount", TestForNodeStopsAfterConfiguredAmount);
+ Run("Runtime node keeps editor id without sharing state", TestRuntimeNodeKeepsEditorIdWithoutSharingState);
+ Run("Start node succeeds immediately", TestStartNodeSucceedsImmediately);
+ Run("Split node connections restore both branches", TestSplitNodeConnectionsRestoreBothBranches);
+ Run("Linear node connection restores next node", TestLinearNodeConnectionRestoresNextNode);
+ Run("Paused world does not drain survival", TestPausedWorldDoesNotDrainSurvival);
+ Run("Open gate hides gate content", TestOpenGateHidesGateContent);
+ Run("Layer generation succeeds above threshold", TestLayerGenerationSuccessRate);
+ Run("Item data readable names are stable", TestItemDataReadableNames);
+ Run("Resource files load core game data", TestResourceFilesLoadCoreData);
+
+ GD.Print($"Tests passed: {passedTests}, failed: {failedTests}");
+ GetTree().Quit(failedTests == 0 ? 0 : 1);
+ }
+
+ private void Run(string name, Action test)
+ {
+ try
+ {
+ GameData.ResetRunState();
+ test();
+ passedTests++;
+ GD.Print("[PASS] " + name);
+ }
+ catch (Exception exception)
+ {
+ failedTests++;
+ GD.PrintErr("[FAIL] " + name + ": " + exception.Message);
+ }
+ }
+
+ private void TestInventoryStacksAndRemovesItems()
+ {
+ ItemData stone = GameData.availableItems["stone"];
+ GameData.inventory.AddItem(new Item { data = stone }, stone.StackSize + 5);
+
+ AssertEqual(stone.StackSize + 5, GameData.inventory.GetItemAmount("stone"), "stone amount");
+ AssertEqual(2, GameData.inventory.items.Count, "stone stack count");
+
+ bool removed = GameData.inventory.TryRemoveItem("stone", stone.StackSize);
+
+ AssertTrue(removed, "remove should succeed");
+ AssertEqual(5, GameData.inventory.GetItemAmount("stone"), "remaining stone amount");
+ }
+
+ private void TestInventoryCanCraftAcrossStacks()
+ {
+ ItemData stone = GameData.availableItems["stone"];
+ stone.StackSize = 5;
+ GameData.inventory.AddItem(new Item { data = stone }, 8);
+
+ List ingredients = new List
+ {
+ new Ingredient { Item = "stone", Amount = 8 }
+ };
+
+ AssertTrue(GameData.inventory.CanCraft(ingredients, 1), "crafting should see both stacks");
+ }
+
+ private void TestSurvivalConsumesFoodAndWater()
+ {
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["mushroom"] }, 1);
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["water"] }, 1);
+ GameData.survival.hunger = 30f;
+ GameData.survival.thirst = 30f;
+
+ GameData.survival.Update(0.1);
+
+ AssertTrue(GameData.survival.hunger > 60f, "hunger should recover");
+ AssertTrue(GameData.survival.thirst > 65f, "thirst should recover");
+ AssertEqual(0, GameData.inventory.GetItemAmount("mushroom"), "mushroom consumed");
+ AssertEqual(0, GameData.inventory.GetItemAmount("water"), "water consumed");
+ }
+
+ private void TestSurvivalDeathDisablesMovement()
+ {
+ GameData.canMove = true;
+ GameData.survival.gracePeriodEnabled = false;
+ GameData.survival.energy = 0.01f;
+
+ GameData.survival.Update(2.0);
+
+ AssertTrue(GameData.survival.isDead, "survival should be dead");
+ AssertFalse(GameData.canMove, "movement should be disabled");
+ }
+
+ private void TestSurvivalGracePeriodPreventsEarlyDeath()
+ {
+ GameData.canMove = true;
+ GameData.survival.hunger = 0f;
+ GameData.survival.thirst = 0f;
+ GameData.survival.energy = 0f;
+
+ GameData.survival.Update(1.0);
+
+ AssertFalse(GameData.survival.isDead, "survival should not fail during grace period");
+ AssertTrue(GameData.canMove, "movement should stay enabled during grace period");
+ AssertTrue(GameData.survival.hunger > 0f, "hunger should be clamped above zero");
+ AssertTrue(GameData.survival.thirst > 0f, "thirst should be clamped above zero");
+ AssertTrue(GameData.survival.energy > 0f, "energy should be clamped above zero");
+ }
+
+ private void TestRobotResearchEffects()
+ {
+ RobotStats stats = new RobotStats();
+ List effects = new List
+ {
+ new ResearchEffect { Stat = "robot_speed_bonus", Value = 0.25f },
+ new ResearchEffect { Stat = "robot_energy_use_reduction", Value = 0.20f },
+ new ResearchEffect { Stat = "robot_cooling_bonus", Value = 0.50f }
+ };
+
+ stats.Apply(effects);
+
+ AssertClose(12.5f, stats.GetMovementSpeed(10f), 0.001f, "movement speed");
+ AssertClose(8f, stats.GetEnergyUse(10f), 0.001f, "energy use");
+ AssertClose(15f, stats.GetCoolingRate(10f), 0.001f, "cooling");
+ }
+
+ private void TestResearchCompletionAppliesEffectsOnce()
+ {
+ ResearchData data = new ResearchData
+ {
+ Id = "test_research",
+ Inputs = new List(),
+ Research = "basics",
+ CraftTime = 1.0,
+ Texture = "",
+ Effects = new List
+ {
+ new ResearchEffect { Stat = "robot_speed_bonus", Value = 0.10f }
+ }
+ };
+
+ Research research = new Research(data);
+ research.Complete();
+ research.Complete();
+
+ AssertClose(11f, GameData.robotStats.GetMovementSpeed(10f), 0.001f, "research effect applied once");
+ }
+
+ private void TestResourceSaveRoundtrip()
+ {
+ GameResource resource = new GameResource("stone");
+ resource.Extract(2.0);
+
+ ResourceSaveData saveData = resource.CreateSaveData();
+ GameResource loadedResource = GameResource.FromSaveData(saveData);
+
+ AssertEqual(saveData.Name, loadedResource.CreateSaveData().Name, "resource name");
+ AssertEqual(saveData.CurrentAmount, loadedResource.CreateSaveData().CurrentAmount, "resource amount");
+ }
+
+ private void TestEndlessResourcesExtractSlower()
+ {
+ GameResource resource = new GameResource("stone");
+
+ AssertTrue(resource.Extract(1.0), "normal resource should extract after one second");
+
+ resource.MakeEndless();
+
+ AssertFalse(resource.Extract(1.0), "endless resource should not extract after one second");
+ AssertTrue(resource.Extract(3.0), "endless resource should extract after four total seconds");
+ AssertTrue(resource.IsEndless(), "resource should be endless");
+ AssertTrue(resource.GetExtractionSpeed() > 1f, "endless extraction speed should be slower");
+ }
+
+ private void TestRobotSaveRoundtrip()
+ {
+ Robot robot = new Robot
+ {
+ Name = "Ada",
+ Position = new Vector3(1f, 2f, 3f),
+ heat = 44f,
+ maintenance = 55f,
+ isBroken = true,
+ isCoolingDown = true,
+ currentProgram = "Mining",
+ currentMessage = "Needs care",
+ robotType = "bronze_robot"
+ };
+
+ RobotSaveData saveData = robot.CreateSaveData();
+ Robot loadedRobot = new Robot();
+ loadedRobot.LoadSaveData(saveData);
+
+ AssertEqual("Ada", loadedRobot.Name.ToString(), "robot name");
+ AssertEqual("Mining", loadedRobot.currentProgram, "robot program");
+ AssertEqual("bronze_robot", loadedRobot.robotType, "robot type");
+ AssertClose(44f, loadedRobot.heat, 0.001f, "robot heat");
+ AssertClose(55f, loadedRobot.maintenance, 0.001f, "robot maintenance");
+ AssertTrue(loadedRobot.isBroken, "robot broken state");
+ AssertTrue(loadedRobot.isCoolingDown, "robot cooling state");
+ }
+
+ private void TestMaintainNodeConsumesMatchingGear()
+ {
+ Robot robot = new Robot
+ {
+ robotType = "copper_robot",
+ maintenance = 50f,
+ isBroken = true
+ };
+
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["copper_gear"] }, 1);
+ MaintainNode node = new MaintainNode();
+
+ AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "maintain should succeed");
+ AssertClose(60f, robot.maintenance, 0.001f, "maintenance repaired by 10");
+ AssertFalse(robot.isBroken, "robot should be usable again");
+ AssertEqual(0, GameData.inventory.GetItemAmount("copper_gear"), "gear should be consumed");
+ }
+
+ private void TestSacrificeNodeMakesResourceEndless()
+ {
+ GameData.ruinSize = 1;
+ GameData.layerSize = 1;
+ GameData.map = new Layer[1];
+ GameData.map[0] = CreateTestLayer(0, "spawn");
+ GameData.map[0].tiles[0, 0].resource = new GameResource("stone");
+ GameData.map[0].tiles[0, 0].containsResource = true;
+ Pathfinding.BuildAStarGraph();
+
+ Robot robot = new Robot
+ {
+ Position = Vector3.Zero
+ };
+ GameData.robots.Add(robot);
+
+ SacrificeNode node = new SacrificeNode();
+
+ AssertEqual(NodeResult.SUCCESS, node.Execute(robot, 0), "sacrifice should succeed");
+ AssertTrue(GameData.map[0].tiles[0, 0].resource.IsEndless(), "resource should become endless");
+ AssertEqual(0, GameData.robots.Count, "robot should be removed");
+ }
+
+ private void TestNoRobotRecoveryDetectsLoss()
+ {
+ AssertTrue(GameData.HasNoRobotRecovery(), "no robots and no robot items should be a loss");
+ }
+
+ private void TestRobotItemPreventsNoRobotLoss()
+ {
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone_robot"] }, 1);
+
+ AssertFalse(GameData.HasNoRobotRecovery(), "robot item should prevent loss");
+ }
+
+ private void TestSaveDataRestoresGlobalState()
+ {
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 12);
+ GameData.survival.hunger = 42f;
+ GameData.survival.thirst = 43f;
+ GameData.survival.energy = 44f;
+ GameData.lowestLayer = 3;
+ GameData.availableResearch["stoneage"].Complete();
+
+ SaveGameData saveData = SaveGameManager.CreateSaveData();
+
+ GameData.ResetRunState();
+ SaveGameManager.ApplyWorldData(saveData);
+
+ AssertEqual(12, GameData.inventory.GetItemAmount("stone"), "saved inventory");
+ AssertClose(42f, GameData.survival.hunger, 0.001f, "saved hunger");
+ AssertEqual(3, GameData.lowestLayer, "saved layer");
+ AssertEqual(ResearchState.RESEARCHED, GameData.availableResearch["stoneage"].state, "saved research");
+ }
+
+ private void TestSaveDataRestoresOptions()
+ {
+ GameData.screenMode = 1;
+ GameData.soundVolume = 0.35f;
+ GameData.lightColor = new Color(0.2f, 0.4f, 0.6f, 1f);
+
+ SaveGameData saveData = SaveGameManager.CreateSaveData();
+
+ GameData.screenMode = 2;
+ GameData.soundVolume = 0.8f;
+ GameData.lightColor = Colors.White;
+
+ SaveGameManager.ApplyWorldData(saveData);
+
+ AssertEqual(1, GameData.screenMode, "saved screen mode");
+ AssertClose(0.35f, GameData.soundVolume, 0.001f, "saved sound volume");
+ AssertClose(0.2f, GameData.lightColor.R, 0.001f, "saved light red");
+ AssertClose(0.4f, GameData.lightColor.G, 0.001f, "saved light green");
+ AssertClose(0.6f, GameData.lightColor.B, 0.001f, "saved light blue");
+ }
+
+ private void TestSaveDataRestoresSurvivalTimer()
+ {
+ GameData.survival.elapsedSeconds = 321.5;
+
+ SaveGameData saveData = SaveGameManager.CreateSaveData();
+
+ GameData.ResetRunState();
+ SaveGameManager.ApplyWorldData(saveData);
+
+ AssertClose(321.5f, (float)GameData.survival.elapsedSeconds, 0.001f, "saved survival timer");
+ }
+
+ private void TestResearchExecutionPaysResourcesAndFinishes()
+ {
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 5);
+ ResearchData researchData = new ResearchData
+ {
+ Id = "stone_counterweight",
+ Inputs = new List
+ {
+ new Ingredient { Item = "stone", Amount = 3 }
+ },
+ Research = "basics",
+ CraftTime = 1.0,
+ Texture = "",
+ Effects = new List()
+ };
+
+ Research research = new Research(researchData);
+ ResearchResult result = research.Execute(1.0);
+
+ AssertEqual(ResearchResult.FINISHED, result, "research result");
+ AssertEqual(2, GameData.inventory.GetItemAmount("stone"), "research cost");
+ AssertEqual(ResearchState.RESEARCHED, research.state, "research state");
+ }
+
+ private void TestInventoryAddFailureKeepsInventoryUnchanged()
+ {
+ GameData.maxRobotCount = 1;
+ GameData.inventory.maxInventorySize = 1;
+ ItemData stone = GameData.availableItems["stone"];
+
+ bool result = GameData.inventory.AddItem(new Item { data = stone }, stone.StackSize + 1);
+
+ AssertFalse(result, "add should fail");
+ AssertEqual(0, GameData.inventory.GetItemAmount("stone"), "failed add should be atomic");
+ AssertEqual(0, GameData.inventory.items.Count, "failed add should not create stacks");
+ }
+
+ private void TestSavedScriptsCanBeDeleted()
+ {
+ string scriptName = "delete_test_script";
+ FileHandler.SaveProgram(scriptName, "{\"Nodes\":[],\"Connections\":[]}");
+
+ AssertTrue(FileHandler.LoadProgramNames().Contains(scriptName), "script should be listed");
+ AssertTrue(FileHandler.DeleteProgram(scriptName), "delete should succeed");
+ AssertEqual("", FileHandler.LoadProgram(scriptName), "deleted script should be empty");
+ AssertFalse(FileHandler.DeleteProgram(scriptName), "second delete should fail");
+ }
+
+ private void TestResearchCannotStartWithoutResources()
+ {
+ ResearchData researchData = new ResearchData
+ {
+ Id = "missing_stones",
+ Inputs = new List
+ {
+ new Ingredient { Item = "stone", Amount = 3 }
+ },
+ Research = "basics",
+ CraftTime = 1.0,
+ Texture = "",
+ Effects = new List()
+ };
+
+ Research research = new Research(researchData);
+ ResearchResult result = research.Execute(1.0);
+
+ AssertFalse(research.CanStart(), "research should not be startable");
+ AssertEqual(ResearchResult.FAILED, result, "research should fail without resources");
+ AssertEqual(ResearchState.UNDEFINED, research.state, "research state should stay unchanged");
+ }
+
+ private void TestSplitSaveFilesRoundtrip()
+ {
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["water"] }, 7);
+ GameData.survival.energy = 77f;
+
+ SaveGameManager.SaveGame();
+ SaveGameData saveData = SaveGameManager.LoadSaveData();
+
+ AssertTrue(SaveGameManager.SaveExists(), "save folder should exist");
+ AssertTrue(FileAccess.FileExists("user://savegame/gamedata.json"), "gamedata file");
+ AssertTrue(FileAccess.FileExists("user://savegame/robots.json"), "robots file");
+ AssertTrue(FileAccess.FileExists("user://savegame/research.json"), "research file");
+ AssertEqual(7, saveData.Inventory[0].Amount, "saved file inventory");
+ AssertClose(77f, saveData.Survival.Energy, 0.001f, "saved file energy");
+ }
+
+ private void TestSplitSaveFilesIncludeLayerFiles()
+ {
+ GameData.ruinSize = 2;
+ GameData.map = new Layer[2];
+ GameData.map[0] = CreateTestLayer(0, "spawn");
+ GameData.map[1] = CreateTestLayer(1, "gate");
+
+ SaveGameManager.SaveGame();
+ SaveGameData saveData = SaveGameManager.LoadSaveData();
+
+ AssertTrue(FileAccess.FileExists("user://savegame/layer_0.json"), "layer 0 file");
+ AssertTrue(FileAccess.FileExists("user://savegame/layer_1.json"), "layer 1 file");
+ AssertEqual(2, saveData.Layers.Count, "loaded layer count");
+ AssertEqual("spawn", saveData.Layers[0].Tiles[0].CollapsedMesh, "layer 0 tile");
+ AssertEqual("gate", saveData.Layers[1].Tiles[0].CollapsedMesh, "layer 1 tile");
+ }
+
+ private void TestIfNodeEvaluatesInventoryComparisons()
+ {
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["stone"] }, 5);
+ IfNode node = new IfNode
+ {
+ selectedItem = new Item { data = GameData.availableItems["stone"] },
+ amount = 3,
+ comparator = "is bigger than"
+ };
+
+ AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "if condition true");
+
+ node.amount = 8;
+ AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "if condition false");
+ }
+
+ private void TestWhileNodeReportsFalseConditions()
+ {
+ GameData.inventory.AddItem(new Item { data = GameData.availableItems["water"] }, 2);
+ WhileNode node = new WhileNode
+ {
+ selectedItem = new Item { data = GameData.availableItems["water"] },
+ amount = 5,
+ comparator = "is bigger than or equal to"
+ };
+
+ AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "while condition false");
+ }
+
+ private void TestForNodeStopsAfterConfiguredAmount()
+ {
+ ForNode node = new ForNode
+ {
+ amount = 2
+ };
+
+ AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "first iteration");
+ AssertEqual(NodeResult.CONDITIONFALSE, node.Execute(null, 0), "second iteration");
+ AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "loop finished");
+ }
+
+ private void TestRuntimeNodeKeepsEditorIdWithoutSharingState()
+ {
+ MoveNode editorNode = new MoveNode
+ {
+ targetPosition = new Vector3I(1, 0, 1)
+ };
+
+ ProgramNode runtimeNode = editorNode.DuplicateForRuntime("MoveNode42");
+ MoveNode runtimeMoveNode = runtimeNode as MoveNode;
+
+ AssertFalse(ReferenceEquals(editorNode, runtimeNode), "runtime node should be a copy");
+ AssertEqual("MoveNode42", runtimeNode.EditorNodeId, "runtime node editor id");
+ AssertEqual(editorNode.targetPosition, runtimeMoveNode.targetPosition, "runtime node parameters");
+ }
+
+ private void TestStartNodeSucceedsImmediately()
+ {
+ StartNode node = new StartNode();
+
+ AssertEqual(NodeResult.SUCCESS, node.Execute(null, 0), "start should succeed");
+ AssertEqual("Name: Start", node.Save(), "start save");
+ }
+
+ private void TestSplitNodeConnectionsRestoreBothBranches()
+ {
+ MoveNode successNode = new MoveNode();
+ MoveNode negativeNode = new MoveNode();
+ System.Collections.Generic.Dictionary availableNodes =
+ new System.Collections.Generic.Dictionary
+ {
+ { new StringName("success"), successNode },
+ { new StringName("negative"), negativeNode }
+ };
+ List connections = new List
+ {
+ CreateConnection("split", 0, "success", 0),
+ CreateConnection("split", 1, "negative", 0)
+ };
+
+ IfNode ifNode = new IfNode();
+ ifNode.SetNextNode(connections, availableNodes);
+
+ AssertTrue(ReferenceEquals(successNode, ifNode.nextNode), "if success branch");
+ AssertTrue(ReferenceEquals(negativeNode, ifNode.NegativeNode), "if negative branch");
+
+ WhileNode whileNode = new WhileNode();
+ whileNode.SetNextNode(connections, availableNodes);
+
+ AssertTrue(ReferenceEquals(successNode, whileNode.nextNode), "while success branch");
+ AssertTrue(ReferenceEquals(negativeNode, whileNode.NegativeNode), "while negative branch");
+
+ ForNode forNode = new ForNode();
+ forNode.SetNextNode(connections, availableNodes);
+
+ AssertTrue(ReferenceEquals(successNode, forNode.nextNode), "for success branch");
+ AssertTrue(ReferenceEquals(negativeNode, forNode.NegativeNode), "for negative branch");
+ }
+
+ private void TestLinearNodeConnectionRestoresNextNode()
+ {
+ MoveNode targetNode = new MoveNode();
+ StartNode startNode = new StartNode();
+ System.Collections.Generic.Dictionary availableNodes =
+ new System.Collections.Generic.Dictionary
+ {
+ { new StringName("target"), targetNode }
+ };
+ List connections = new List
+ {
+ CreateConnection("start", 0, "target", 0)
+ };
+
+ startNode.SetNextNode(connections, availableNodes);
+
+ AssertTrue(ReferenceEquals(targetNode, startNode.nextNode), "linear next node");
+ AssertTrue(startNode.NegativeNode == null, "linear node should not have negative branch");
+ }
+
+ private void TestPausedWorldDoesNotDrainSurvival()
+ {
+ GameData.isPaused = true;
+ GameData.canMove = false;
+ GameData.survival.energy = 100f;
+
+ World world = new World();
+ world.UpdateGameLoop(100.0);
+
+ AssertClose(100f, GameData.survival.energy, 0.001f, "energy should not drain while paused");
+ }
+
+ private void TestOpenGateHidesGateContent()
+ {
+ Layer layer = CreateTestLayer(0, "gate");
+ layer.gateCoordinate = new Vector2I(0, 0);
+ layer.tiles[0, 0].ContentNode = new Node3D
+ {
+ Visible = true
+ };
+
+ layer.OpenGate();
+
+ AssertTrue(layer.isGateOpen, "gate should be open");
+ AssertFalse(layer.tiles[0, 0].ContentNode.Visible, "gate content should be hidden");
+ }
+
+ private void TestLayerGenerationSuccessRate()
+ {
+ const int layerCount = 1000;
+ const float minimumSuccessRate = 0.90f;
+
+ GameData.layerSize = 20;
+ WFC.FillAdjacencies();
+ Dictionary tileMeshes = ResourceLoader.LoadTiles();
+
+ int successfulLayers = 0;
+
+ for (int i = 0; i < layerCount; i++)
+ {
+ Layer layer = new Layer();
+ layer._Ready();
+ layer.SetupLayer(GameData.layerSize, 0, tileMeshes, new Vector2I());
+
+ if (IsGeneratedLayerValid(layer))
+ {
+ successfulLayers++;
+ }
+
+ layer.Free();
+ }
+
+ float successRate = successfulLayers / (float)layerCount;
+ GD.Print($"Layer generation success rate: {successfulLayers}/{layerCount} ({successRate:P2})");
+
+ AssertTrue(
+ successRate >= minimumSuccessRate,
+ $"layer generation success rate should be at least {minimumSuccessRate:P0}, got {successRate:P2}"
+ );
+ }
+
+ private void TestItemDataReadableNames()
+ {
+ AssertEqual("Iron gear", ItemData.GetReadableName("iron_gear"), "readable name");
+ AssertEqual("iron_gear", ItemData.GetIndex("Iron Gear"), "index name");
+ }
+
+ private void TestResourceFilesLoadCoreData()
+ {
+ AssertTrue(GameData.availableItems.ContainsKey("stone"), "stone item loaded");
+ AssertTrue(GameData.availableItems.ContainsKey("water"), "water item loaded");
+ AssertTrue(GameData.availableResearch.ContainsKey("basics"), "basics research loaded");
+ AssertTrue(GameData.availableResearch.ContainsKey("iron_robotics"), "iron robotics research loaded");
+
+ Dictionary dslNodes = ResourceLoader.LoadDSLNodes();
+ bool hasStartNode = false;
+ bool allNodesHaveTooltips = true;
+ foreach (ProgramNode node in dslNodes.Keys)
+ {
+ if (node is StartNode)
+ {
+ hasStartNode = true;
+ }
+
+ if (node.TooltipText == null || node.TooltipText.Length <= 0)
+ {
+ allNodesHaveTooltips = false;
+ }
+ }
+ AssertTrue(hasStartNode, "start node prefab loaded");
+ AssertTrue(allNodesHaveTooltips, "dsl nodes have tooltip text");
+ }
+
+}
diff --git a/Scripts/Tests/TestRunner.cs.uid b/Scripts/Tests/TestRunner.cs.uid
new file mode 100644
index 0000000..5db3160
--- /dev/null
+++ b/Scripts/Tests/TestRunner.cs.uid
@@ -0,0 +1 @@
+uid://bm6a1hivjtc8e
diff --git a/Scripts/Tests/TestRunnerHelpers.cs b/Scripts/Tests/TestRunnerHelpers.cs
new file mode 100644
index 0000000..89035b4
--- /dev/null
+++ b/Scripts/Tests/TestRunnerHelpers.cs
@@ -0,0 +1,101 @@
+using Godot;
+using System;
+using System.Collections.Generic;
+
+public partial class TestRunner
+{
+ private Layer CreateTestLayer(int level, string collapsedMesh)
+ {
+ Layer layer = new Layer
+ {
+ level = level,
+ currentResources = new List { "stone" },
+ gateIngredients = new List(),
+ tiles = new Tile[1, 1]
+ };
+
+ Tile tile = new Tile
+ {
+ GridPosition = new Vector2I(0, 0),
+ Position = Vector3.Zero,
+ collapsedMesh = collapsedMesh,
+ containsResource = true,
+ resource = new GameResource("stone")
+ };
+
+ layer.tiles[0, 0] = tile;
+ return layer;
+ }
+
+ private bool IsGeneratedLayerValid(Layer layer)
+ {
+ bool hasGate = false;
+
+ foreach (Tile tile in layer.tiles)
+ {
+ if (tile.collapsedMesh == null)
+ {
+ return false;
+ }
+
+ if (tile.collapsedMesh == "gate")
+ {
+ hasGate = true;
+ }
+ }
+
+ if (!hasGate)
+ {
+ return false;
+ }
+
+ return WFC.IsMapConnected(layer.tiles, 1f);
+ }
+
+ private Godot.Collections.Dictionary CreateConnection(
+ string fromNode,
+ int fromPort,
+ string toNode,
+ int toPort
+ )
+ {
+ Godot.Collections.Dictionary connection = new Godot.Collections.Dictionary();
+ connection["from_node"] = new StringName(fromNode);
+ connection["from_port"] = fromPort;
+ connection["to_node"] = new StringName(toNode);
+ connection["to_port"] = toPort;
+ return connection;
+ }
+
+ private void AssertTrue(bool value, string message)
+ {
+ if (!value)
+ {
+ throw new Exception(message);
+ }
+ }
+
+ private void AssertFalse(bool value, string message)
+ {
+ if (value)
+ {
+ throw new Exception(message);
+ }
+ }
+
+ private void AssertEqual(T expected, T actual, string message)
+ {
+ if (!EqualityComparer.Default.Equals(expected, actual))
+ {
+ throw new Exception($"{message}: expected {expected}, got {actual}");
+ }
+ }
+
+ private void AssertClose(float expected, float actual, float tolerance, string message)
+ {
+ if (Math.Abs(expected - actual) > tolerance)
+ {
+ throw new Exception($"{message}: expected {expected}, got {actual}");
+ }
+ }
+}
diff --git a/Scripts/Tests/TestRunnerHelpers.cs.uid b/Scripts/Tests/TestRunnerHelpers.cs.uid
new file mode 100644
index 0000000..eddbcee
--- /dev/null
+++ b/Scripts/Tests/TestRunnerHelpers.cs.uid
@@ -0,0 +1 @@
+uid://dn38ysswhpy04
diff --git a/Scripts/UI/Common/Camera3d.cs b/Scripts/UI/Common/Camera3d.cs
new file mode 100644
index 0000000..11045f9
--- /dev/null
+++ b/Scripts/UI/Common/Camera3d.cs
@@ -0,0 +1,59 @@
+using Godot;
+using static GameData;
+
+public partial class Camera3d : Camera3D
+{
+ [Export] public float Speed = 7.5f;
+ [Export] public float MouseSensitivity = 0.2f;
+ [Export] public float ScrollStrength = 5.0f;
+ private Robot robot;
+
+ public override void _Ready()
+ {
+ Position = new Vector3(0, 0, tileWidth / 2);
+ }
+
+ public override void _Process(double delta)
+ {
+ Control focused = GetViewport().GuiGetFocusOwner();
+
+ if (focused is LineEdit || focused is TextEdit) return;
+ if (canMove) MoveCamera(delta);
+ }
+
+ public void MoveCamera(double delta)
+ {
+ float d = (float)delta;
+
+ Vector3 rotation = RotationDegrees;
+ rotation.X = Mathf.Clamp(rotation.X, -90f, 90f);
+ RotationDegrees = rotation;
+
+ Vector3 direction = Vector3.Zero;
+ if (Input.IsActionPressed("move_forward") && Position.Z > 0) direction += Transform.Basis.Z;
+ if (Input.IsActionPressed("move_backward") && Position.Z < layerSize * 6) direction -= Transform.Basis.Z;
+ if (Input.IsActionPressed("move_left") && Position.X > 0) direction -= Transform.Basis.X;
+ if (Input.IsActionPressed("move_right") && Position.X < layerSize * 6) direction += Transform.Basis.X;
+
+ if (direction != Vector3.Zero)
+ {
+ robot = null;
+ direction = direction.Normalized() * Speed * (Input.IsActionPressed("sprint") ? 2.5f : 1) * d;
+ Translate(direction);
+ }
+ else if (robot != null)
+ {
+ Position = new Vector3(robot.Position.X, 10 - visibleLayer * 4, robot.Position.Z + 4f);
+ }
+
+ if (Position.Y != 10 - visibleLayer * 4)
+ {
+ Position = new Vector3(Position.X, 10 - visibleLayer * 4, Position.Z);
+ }
+ }
+
+ public void Follow(Robot robot)
+ {
+ this.robot = robot;
+ }
+}
diff --git a/Scripts/UI/Common/Camera3d.cs.uid b/Scripts/UI/Common/Camera3d.cs.uid
new file mode 100644
index 0000000..f28011f
--- /dev/null
+++ b/Scripts/UI/Common/Camera3d.cs.uid
@@ -0,0 +1 @@
+uid://c7khr6oist3ku
diff --git a/Scripts/UI/Common/UIHandler.cs b/Scripts/UI/Common/UIHandler.cs
new file mode 100644
index 0000000..84da30a
--- /dev/null
+++ b/Scripts/UI/Common/UIHandler.cs
@@ -0,0 +1,194 @@
+using Godot;
+
+public partial class UIHandler : Control
+{
+ [Export] CodingWindow codingWindow;
+ [Export] RobotList robotList;
+ [Export] Camera3d mainCam;
+ [Export] Map map;
+ [Export] RichTextLabel FPS;
+ [Export] PanelContainer options;
+ [Export] Control uiContent;
+ [Export] PanelContainer menu;
+ [Export] PanelContainer inventory;
+ [Export] ResearchList researchList;
+ [Export] TextureRect robotAlarm;
+ [Export] RichTextLabel energyLabel;
+ [Export] RichTextLabel waterLabel;
+ [Export] RichTextLabel hungerLabel;
+ [Export] RichTextLabel survivalStatus;
+ [Export] RichTextLabel currentLayer;
+ [Export] RichTextLabel deepestLayer;
+ [Export] Button unlockLayer;
+ [Export] PanelContainer gameOver;
+
+
+ private bool receivedRobotJumpSignal = false;
+ private bool receivedRobotFollowSignal = false;
+ public override void _Ready()
+ {
+ UIStyle.Apply(this);
+ robotList.OnRobotJumpTo += OnRobotJumpTo;
+ robotList.OnRobotFollow += OnRobotFollow;
+ }
+
+ public override void _ExitTree()
+ {
+ robotList.OnRobotJumpTo -= OnRobotJumpTo;
+ robotList.OnRobotFollow -= OnRobotFollow;
+ }
+
+ public override void _Process(double delta)
+ {
+ DisplayStats();
+ DisplayRobotAlarm();
+
+ if (IsTextInputFocused()) return;
+
+ if (Input.IsActionJustPressed("map")) HandleMapButton();
+ if (Input.IsActionJustPressed("menu")) HandleMenuButton();
+ if (Input.IsActionJustPressed("robot_list")) HandleRobotListButton();
+ if (Input.IsActionJustPressed("inventory")) HandleInventoryButton();
+ if (Input.IsActionJustPressed("research")) HandleResearchButton();
+ }
+
+ private bool IsTextInputFocused()
+ {
+ Control focused = GetViewport().GuiGetFocusOwner();
+ return focused is LineEdit || focused is TextEdit;
+ }
+
+ public void HandleMenuButton()
+ {
+ if (GameData.survival.isDead) return;
+
+ OpenUIElement(menu);
+ GameData.isPaused = menu.Visible || options.Visible;
+ }
+
+ public void HandleMenu()
+ {
+ HandleMenuButton();
+ }
+
+ public void ShowOptions()
+ {
+ if (GameData.survival.isDead) return;
+
+ menu.Hide();
+ OpenUIElement(options);
+ GameData.isPaused = options.Visible;
+ }
+
+ public void HandleMapButton()
+ {
+ if (GameData.survival.isDead) return;
+
+ OpenUIElement(map);
+ if (map.Visible) map.ShowMap();
+ }
+
+ public void HandleRobotListButton()
+ {
+ if (GameData.survival.isDead) return;
+
+ receivedRobotFollowSignal = false;
+ receivedRobotJumpSignal = false;
+ OpenUIElement(robotList);
+ }
+
+ public void HandleInventoryButton()
+ {
+ if (GameData.survival.isDead) return;
+
+ OpenUIElement(inventory);
+ }
+
+ public void HandleResearchButton()
+ {
+ if (GameData.survival.isDead) return;
+
+ OpenUIElement(researchList);
+ if (researchList.Visible) researchList.SetupGraph();
+ }
+
+ public void ExitGame()
+ {
+ GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn");
+ }
+
+ public void SaveGame()
+ {
+ SaveGameManager.SaveGame();
+ }
+
+ public void LoadGame()
+ {
+ if (!SaveGameManager.SaveExists()) return;
+
+ GameData.loadSaveOnStart = true;
+ GetTree().ChangeSceneToFile("res://Scenes/Game.tscn");
+ }
+
+ public void OpenUIElement(Control element)
+ {
+ SoundManager.PlayButton();
+ element.Visible = !element.Visible;
+ HideUIElements(element);
+ }
+
+ private void HideUIElements(Control element)
+ {
+ foreach (PanelContainer child in uiContent.GetChildren())
+ {
+ if (child == element) continue;
+ child.Visible = false;
+ }
+
+ if (element != menu && element != options)
+ {
+ GameData.isPaused = false;
+ }
+ }
+
+ private void OnRobotJumpTo(Robot robot)
+ {
+ if (receivedRobotJumpSignal) return;
+
+ receivedRobotJumpSignal = true;
+ mainCam.Position = new Vector3(robot.Position.X, mainCam.Position.Y, robot.Position.Z + 3f);
+ codingWindow.SetRobot(robot);
+ OpenUIElement(codingWindow);
+ }
+
+ private void OnRobotFollow(Robot robot)
+ {
+ if (receivedRobotFollowSignal) return;
+
+ receivedRobotFollowSignal = true;
+ mainCam.Follow(robot);
+ }
+
+ public void UnlockLayer()
+ {
+ if (GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1))
+ {
+ int openedLayer = GameData.lowestLayer;
+ foreach (Ingredient ingredient in GameData.map[GameData.lowestLayer].gateIngredients)
+ {
+ GameData.inventory.RemoveItem(ingredient.Item, ingredient.Amount);
+ }
+ GameData.lowestLayer++;
+ World world = GetNodeOrNull("/root/Main/World");
+ if (world != null)
+ {
+ world.OpenGate(openedLayer);
+ }
+ if (GameData.lowestLayer == GameData.ruinSize)
+ {
+ gameOver.Show();
+ }
+ }
+ }
+
+}
diff --git a/Scripts/UI/Common/UIHandler.cs.uid b/Scripts/UI/Common/UIHandler.cs.uid
new file mode 100644
index 0000000..6ae2d27
--- /dev/null
+++ b/Scripts/UI/Common/UIHandler.cs.uid
@@ -0,0 +1 @@
+uid://bm7knir4552j5
diff --git a/Scripts/UI/Common/UIHandlerDisplay.cs b/Scripts/UI/Common/UIHandlerDisplay.cs
new file mode 100644
index 0000000..9d6278e
--- /dev/null
+++ b/Scripts/UI/Common/UIHandlerDisplay.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Diagnostics;
+using Godot;
+
+public partial class UIHandler
+{
+ public void DisplayStats()
+ {
+ FPS.Text = Engine.GetFramesPerSecond().ToString() + " FPS";
+ DisplaySurvivalStats();
+ DisplayWorldStats();
+ DisplayLoseCondition();
+ }
+
+ 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 = $"Thirst: {GameData.survival.thirst:0}/{GameData.survival.maxThirst:0}";
+ hungerLabel.Text = $"Hunger: {GameData.survival.hunger:0}/{GameData.survival.maxHunger:0}";
+ survivalStatus.Text = GameData.survival.currentStatus;
+ survivalStatus.Modulate = GameData.survival.currentStatus.Contains("critical")
+ ? UIStyle.GetWarningColor()
+ : Colors.White;
+
+ if (GameData.survival.isDead)
+ {
+ ShowGameOver();
+ }
+ }
+
+ private void DisplayWorldStats()
+ {
+ currentLayer.Text = $"Layer: {GameData.currentLayer + 1}/{GameData.ruinSize}";
+ deepestLayer.Text = $"Gate depth: {GameData.lowestLayer}";
+ if (GameData.lowestLayer == GameData.ruinSize)
+ {
+ unlockLayer.Visible = false;
+ return;
+ }
+ unlockLayer.TooltipText = "Gate requirements:\r" + GameData.map[GameData.lowestLayer].DisplayGateIngredients();
+ unlockLayer.Disabled = !GameData.inventory.CanCraft(GameData.map[GameData.lowestLayer].gateIngredients, 1);
+ }
+
+ private void DisplayLoseCondition()
+ {
+ if (!GameData.HasNoRobotRecovery()) return;
+
+ ShowGameOver("No robots remain and no robot can be spawned from inventory.");
+ }
+
+ public void ShowGameOver()
+ {
+ ShowGameOver($"You died!\rReason: {GameData.survival.deathReason}\rBetter luck next time.");
+ }
+
+ public void ShowGameOver(string message)
+ {
+ if (gameOver.Visible) return;
+ gameOver.GetNode("./VBoxContainer/Content").Text = $"[font_size=32]{message}\r";
+ gameOver.Show();
+ }
+
+ public void HideGameOver()
+ {
+ gameOver.Hide();
+ }
+}
diff --git a/Scripts/UI/Common/UIHandlerDisplay.cs.uid b/Scripts/UI/Common/UIHandlerDisplay.cs.uid
new file mode 100644
index 0000000..35ce7e0
--- /dev/null
+++ b/Scripts/UI/Common/UIHandlerDisplay.cs.uid
@@ -0,0 +1 @@
+uid://b2q7e88lokg3l
diff --git a/Scripts/UI/Common/UIStyle.cs b/Scripts/UI/Common/UIStyle.cs
new file mode 100644
index 0000000..5f3defd
--- /dev/null
+++ b/Scripts/UI/Common/UIStyle.cs
@@ -0,0 +1,162 @@
+using Godot;
+
+public static class UIStyle
+{
+ private static readonly Color Background = new Color("#101317");
+ private static readonly Color Surface = new Color("#171d22");
+ private static readonly Color SurfaceStrong = new Color("#202a31");
+ private static readonly Color SurfaceSoft = new Color("#25343d");
+ private static readonly Color Accent = new Color("#6ec6b8");
+ private static readonly Color AccentStrong = new Color("#9be7d8");
+ private static readonly Color Warning = new Color("#d69b5a");
+ private static readonly Color Text = new Color("#e8f0ed");
+ private static readonly Color MutedText = new Color("#aab8b4");
+
+ public static void Apply(Control root)
+ {
+ if (root == null) return;
+
+ ApplyToControl(root);
+
+ foreach (Node child in root.GetChildren())
+ {
+ Control control = child as Control;
+ if (control != null)
+ {
+ Apply(control);
+ }
+ }
+ }
+
+ private static void ApplyToControl(Control control)
+ {
+ PanelContainer panel = control as PanelContainer;
+ if (panel != null)
+ {
+ StylePanel(panel);
+ }
+
+ Button button = control as Button;
+ if (button != null)
+ {
+ StyleButton(button);
+ }
+
+ LineEdit lineEdit = control as LineEdit;
+ if (lineEdit != null)
+ {
+ StyleLineEdit(lineEdit);
+ }
+
+ OptionButton optionButton = control as OptionButton;
+ if (optionButton != null)
+ {
+ StyleButton(optionButton);
+ }
+
+ RichTextLabel label = control as RichTextLabel;
+ if (label != null)
+ {
+ StyleLabel(label);
+ }
+
+ TextureButton textureButton = control as TextureButton;
+ if (textureButton != null)
+ {
+ StyleTextureButton(textureButton);
+ }
+
+ TextureRect textureRect = control as TextureRect;
+ if (textureRect != null)
+ {
+ textureRect.CustomMinimumSize = new Vector2(26, 26);
+ }
+ }
+
+ private static void StylePanel(PanelContainer panel)
+ {
+ Color color = Surface;
+
+ if (panel.Name.ToString().Contains("Header") || panel.Name.ToString().Contains("Footer"))
+ {
+ color = Background;
+ }
+
+ if (panel.Name.ToString().Contains("GameOver") || panel.Name.ToString().Contains("Menu"))
+ {
+ color = SurfaceStrong;
+ }
+
+ StyleBoxFlat style = CreateBox(color, Accent, 1, 8);
+ panel.AddThemeStyleboxOverride("panel", style);
+ }
+
+ private static void StyleButton(Button button)
+ {
+ button.CustomMinimumSize = new Vector2(112, 34);
+ button.AddThemeStyleboxOverride("normal", CreateBox(SurfaceSoft, Accent, 1, 6));
+ button.AddThemeStyleboxOverride("hover", CreateBox(new Color("#314750"), AccentStrong, 1, 6));
+ button.AddThemeStyleboxOverride("pressed", CreateBox(new Color("#1a2b30"), AccentStrong, 1, 6));
+ button.AddThemeStyleboxOverride("focus", CreateBox(new Color(0, 0, 0, 0), AccentStrong, 1, 6));
+ button.AddThemeColorOverride("font_color", Text);
+ button.AddThemeColorOverride("font_hover_color", AccentStrong);
+ button.AddThemeColorOverride("font_pressed_color", AccentStrong);
+ button.AddThemeColorOverride("font_disabled_color", MutedText);
+ }
+
+ private static void StyleLineEdit(LineEdit lineEdit)
+ {
+ lineEdit.CustomMinimumSize = new Vector2(120, 34);
+ lineEdit.AddThemeStyleboxOverride("normal", CreateBox(new Color("#11191d"), Accent, 1, 6));
+ lineEdit.AddThemeStyleboxOverride("focus", CreateBox(new Color("#142126"), AccentStrong, 1, 6));
+ lineEdit.AddThemeColorOverride("font_color", Text);
+ lineEdit.AddThemeColorOverride("font_placeholder_color", MutedText);
+ }
+
+ private static void StyleLabel(RichTextLabel label)
+ {
+ label.AddThemeColorOverride("default_color", Text);
+ label.AddThemeColorOverride("font_shadow_color", new Color("#00000080"));
+ }
+
+ private static void StyleTextureButton(TextureButton textureButton)
+ {
+ textureButton.CustomMinimumSize = new Vector2(38, 38);
+ textureButton.Modulate = Text;
+ textureButton.SelfModulate = Text;
+ textureButton.StretchMode = TextureButton.StretchModeEnum.KeepAspectCentered;
+ }
+
+ private static StyleBoxFlat CreateBox(Color color, Color borderColor, int borderWidth, int radius)
+ {
+ StyleBoxFlat style = new StyleBoxFlat
+ {
+ BgColor = color,
+ BorderColor = borderColor,
+ BorderWidthTop = borderWidth,
+ BorderWidthBottom = borderWidth,
+ BorderWidthLeft = borderWidth,
+ BorderWidthRight = borderWidth,
+ CornerRadiusTopLeft = radius,
+ CornerRadiusTopRight = radius,
+ CornerRadiusBottomLeft = radius,
+ CornerRadiusBottomRight = radius,
+ ContentMarginLeft = 8,
+ ContentMarginRight = 8,
+ ContentMarginTop = 6,
+ ContentMarginBottom = 6
+ };
+
+ return style;
+ }
+
+ public static Color GetWarningColor()
+ {
+ return Warning;
+ }
+
+ public static Color GetBorderColor()
+ {
+ return Accent;
+ }
+}
diff --git a/Scripts/UI/Common/UIStyle.cs.uid b/Scripts/UI/Common/UIStyle.cs.uid
new file mode 100644
index 0000000..4560ab9
--- /dev/null
+++ b/Scripts/UI/Common/UIStyle.cs.uid
@@ -0,0 +1 @@
+uid://dkygddm6k0hjw
diff --git a/Scripts/UI/DSL/CodingWindow.cs b/Scripts/UI/DSL/CodingWindow.cs
new file mode 100644
index 0000000..c05f933
--- /dev/null
+++ b/Scripts/UI/DSL/CodingWindow.cs
@@ -0,0 +1,347 @@
+using Godot;
+using Godot.Collections;
+using System.Collections.Generic;
+
+public partial class CodingWindow : PanelContainer
+{
+ private Robot robot;
+
+ [Export] VBoxContainer codeBlocks;
+ [Export] GraphEdit editorWindow;
+ [Export] OptionButton availableScripts;
+ [Export] LineEdit scriptName;
+ [Export] LineEdit nameInput;
+ [Export] NodeTooltip nodeTooltip;
+
+ public System.Collections.Generic.Dictionary DSLNodes;
+ public NodeDisplay selectedNode;
+ public NodeDisplay highlightedNode;
+
+ public override void _Ready()
+ {
+ DSLNodes = ResourceLoader.LoadDSLNodes();
+ GenerateCodingBlocks();
+ }
+
+ public override void _Notification(int id)
+ {
+ if (id == NotificationVisibilityChanged)
+ {
+ if (Visible) LoadWindow();
+ }
+ }
+
+ public override void _Process(double delta)
+ {
+ if (Input.IsActionJustPressed("delete_node"))
+ {
+ Control focused = GetViewport().GuiGetFocusOwner();
+
+ if (focused is LineEdit || focused is TextEdit) return;
+ if (selectedNode == null) return;
+ editorWindow.RemoveChild(selectedNode);
+ selectedNode.QueueFree();
+ }
+
+ if (robot != null && Visible && robot.isExecuting)
+ {
+ UpdateCurrentNode();
+ }
+ }
+
+ private void UpdateCurrentNode()
+ {
+ if (robot.currentNode == null)
+ {
+ ClearHighlightedNode();
+ return;
+ }
+
+ if (highlightedNode != null)
+ {
+ if (IsCurrentRuntimeNode(highlightedNode))
+ {
+ return;
+ }
+
+ ClearHighlightedNode();
+ }
+
+ foreach (Node child in editorWindow.GetChildren())
+ {
+ NodeDisplay nodeDisplay = child as NodeDisplay;
+ if (nodeDisplay == null) continue;
+
+ if (IsCurrentRuntimeNode(nodeDisplay))
+ {
+ nodeDisplay.SetHighlighted(true);
+ highlightedNode = nodeDisplay;
+ break;
+ }
+ }
+ }
+
+ private bool IsCurrentRuntimeNode(NodeDisplay nodeDisplay)
+ {
+ return robot.currentNode.EditorNodeId != null
+ && robot.currentNode.EditorNodeId.Length > 0
+ && nodeDisplay.Name.ToString() == robot.currentNode.EditorNodeId;
+ }
+
+ private void ClearHighlightedNode()
+ {
+ if (highlightedNode == null) return;
+
+ highlightedNode.SetHighlighted(false);
+ highlightedNode = null;
+ }
+
+ public void OnMapToggled(bool toggledOn)
+ {
+ if (robot == null) return;
+ robot.showOnMap = toggledOn;
+ }
+
+ public void OnNodeSelect(NodeDisplay node)
+ {
+ selectedNode = node;
+ }
+
+ public void OnNodeDeselect(NodeDisplay node)
+ {
+ selectedNode = null;
+ }
+
+ private void LoadWindow()
+ {
+ if (robot == null) return;
+
+ nameInput.Text = robot.Name;
+ SetupScriptOptions();
+ ClearWindow();
+ LoadTemporaryProgram();
+ }
+
+ private void SetupScriptOptions()
+ {
+ availableScripts.Clear();
+ availableScripts.AddItem("Select script to load...");
+
+ List scripts = FileHandler.LoadProgramNames();
+ scripts.Sort((a, b) => a.CompareTo(b));
+ foreach (string script in scripts)
+ {
+ availableScripts.AddItem(script);
+ }
+ }
+
+ public void SaveRobotName()
+ {
+ if (robot == null) return;
+
+ robot.Name = nameInput.Text;
+ }
+
+ public void CloseWindow()
+ {
+ Hide();
+ }
+
+ public void GenerateCodingBlocks()
+ {
+ foreach (ProgramNode nodeTemplate in DSLNodes.Keys)
+ {
+ Button nodeListButton = new Button
+ {
+ Name = nodeTemplate.DisplayText,
+ Text = nodeTemplate.DisplayText
+ };
+ nodeListButton.Pressed += () =>
+ {
+ AddEditorNode(nodeTemplate);
+ };
+ nodeListButton.MouseEntered += () =>
+ {
+ nodeTooltip.ShowTooltip(nodeTemplate.DisplayText, nodeTemplate.TooltipText, nodeListButton);
+ };
+ nodeListButton.MouseExited += () =>
+ {
+ nodeTooltip.HideTooltip();
+ };
+ codeBlocks.AddChild(nodeListButton);
+ }
+ }
+
+ private void AddEditorNode(ProgramNode node)
+ {
+ NodeDisplay editorDisplay = DSLNodes[node].Instantiate();
+ editorDisplay.PositionOffset = GetVisibleGraphCenter() - editorDisplay.Size / 2f;
+ editorWindow.AddChild(editorDisplay);
+ }
+
+ private Vector2 GetVisibleGraphCenter()
+ {
+ return (editorWindow.ScrollOffset + editorWindow.Size / 2f) / editorWindow.Zoom;
+ }
+
+ public void ClearWindow()
+ {
+ ClearHighlightedNode();
+ DisconnectAllNodes();
+ RemoveEditorNodes();
+ scriptName.Text = "";
+ }
+
+ private void DisconnectAllNodes()
+ {
+ foreach (Dictionary connection in editorWindow.GetConnectionList())
+ {
+ editorWindow.DisconnectNode(
+ connection["from_node"].AsStringName(),
+ (int)connection["from_port"],
+ connection["to_node"].AsStringName(),
+ (int)connection["to_port"]
+ );
+ }
+ }
+
+ private void RemoveEditorNodes()
+ {
+ foreach (Node child in editorWindow.GetChildren())
+ {
+ if (child is not GraphNode) continue;
+
+ editorWindow.RemoveChild(child);
+ child.QueueFree();
+ }
+ }
+
+ public void CompileProgram()
+ {
+ if (robot == null) return;
+
+ ScriptGraphCompiler compiler = new ScriptGraphCompiler(editorWindow);
+ string errorMessage;
+ List nodes = compiler.BuildProgram(out errorMessage);
+ if (errorMessage.Length > 0)
+ {
+ robot.StopExecution(errorMessage);
+ return;
+ }
+
+ if (nodes.Count > 0) robot.SetupExecution(nodes);
+ robot.currentProgram = GetCurrentScriptName();
+ }
+
+ private string GetCurrentScriptName()
+ {
+ if (scriptName.Text.Length > 0) return scriptName.Text;
+
+ return $"Script{availableScripts.ItemCount}";
+ }
+
+ public void SetRobot(Robot robot)
+ {
+ this.robot = robot;
+ }
+
+ public void LoadProgram(int index)
+ {
+ if (index <= 0) return;
+
+ ClearWindow();
+ string scriptContent = FileHandler.LoadProgram(availableScripts.GetItemText(index));
+ CreateSerializer().Load(scriptContent);
+ scriptName.Text = availableScripts.GetItemText(index);
+ availableScripts.Select(0);
+ }
+
+ private void AddLoadedNode(NodeDisplay nodeDisplay)
+ {
+ editorWindow.AddChild(nodeDisplay);
+ }
+
+ public void LoadTemporaryProgram()
+ {
+ if (robot == null) return;
+ ProgramNode rootNode = robot.programStartNode ?? robot.currentNode;
+ if (rootNode == null) return;
+
+ RunningProgramGraphBuilder builder = new RunningProgramGraphBuilder(
+ DSLNodes,
+ AddLoadedNode,
+ ConnectNodes
+ );
+ builder.Load(rootNode);
+
+ scriptName.Text = robot.currentProgram ?? "";
+ UpdateCurrentNode();
+ }
+
+ public void DeleteProgram()
+ {
+ string filename = scriptName.Text;
+ int selectedIndex = availableScripts.GetSelectedId();
+ if (selectedIndex > 0)
+ {
+ filename = availableScripts.GetItemText(selectedIndex);
+ }
+
+ if (filename.Length <= 0) return;
+ if (!FileHandler.DeleteProgram(filename)) return;
+
+ ClearWindow();
+ SetupScriptOptions();
+ }
+
+ public void SaveProgram()
+ {
+ string result = CreateSerializer().Save();
+ if (result.Length <= 0) return;
+
+ FileHandler.SaveProgram(GetCurrentScriptName(), result);
+ SetupScriptOptions();
+ }
+
+ public void OnNodeConnect(StringName from, int fromPort, StringName to, int toPort)
+ {
+ if (to == from) return;
+
+ ConnectNodes(from, fromPort, to, toPort);
+ }
+
+ private void ConnectNodes(StringName from, int fromPort, StringName to, int toPort)
+ {
+ if (HasOutputConnection(from, fromPort)) return;
+
+ editorWindow.ConnectNode(from, fromPort, to, toPort);
+ }
+
+ private bool HasOutputConnection(StringName from, int fromPort)
+ {
+ foreach (Dictionary connection in editorWindow.GetConnectionList())
+ {
+ if (connection["from_node"].AsStringName() != from) continue;
+ if ((int)connection["from_port"] != fromPort) continue;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private ScriptGraphSerializer CreateSerializer()
+ {
+ return new ScriptGraphSerializer(
+ editorWindow,
+ DSLNodes,
+ AddLoadedNode,
+ ConnectNodes
+ );
+ }
+
+ public void OnNodeDisconnect(StringName from, int fromPort, StringName to, int toPort)
+ {
+ editorWindow.DisconnectNode(from, fromPort, to, toPort);
+ }
+}
diff --git a/Scripts/UI/DSL/CodingWindow.cs.uid b/Scripts/UI/DSL/CodingWindow.cs.uid
new file mode 100644
index 0000000..f30af15
--- /dev/null
+++ b/Scripts/UI/DSL/CodingWindow.cs.uid
@@ -0,0 +1 @@
+uid://bsd6n6b06a4pe
diff --git a/Scripts/UI/DSL/NodeDisplay.cs b/Scripts/UI/DSL/NodeDisplay.cs
new file mode 100644
index 0000000..7988fd7
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplay.cs
@@ -0,0 +1,82 @@
+using System.Collections.Generic;
+using Godot;
+
+public partial class NodeDisplay : GraphNode
+{
+ public ProgramNode node;
+ private bool isHighlighted = false;
+
+ public override void _Ready()
+ {
+ if (node == null)
+ {
+ node = CreateProgramNode();
+ }
+
+ if (node == null) return;
+
+ SetupDisplay();
+ }
+
+ public static NodeDisplay Load(
+ string nodeName,
+ string content,
+ Dictionary DSLNodes
+ )
+ {
+ string nodeSanitized = content.Replace("\r\n", "").Trim();
+ if (nodeSanitized.Length <= 0) return null;
+ string normalizedNodeName = nodeName.Trim().ToLower();
+
+ PackedScene prefab = GetPrefab(normalizedNodeName, DSLNodes);
+ if (prefab == null) return null;
+
+ NodeDisplay result = prefab.Instantiate();
+ result.node = result.CreateProgramNode();
+ result.LoadContent(result, nodeSanitized);
+
+ return result;
+ }
+
+ protected virtual ProgramNode CreateProgramNode()
+ {
+ return null;
+ }
+
+ protected virtual void LoadContent(NodeDisplay display, string content) { }
+
+ public virtual void SetupDisplay() { }
+
+ public virtual void ReadParameters() { }
+
+ public void SetHighlighted(bool highlighted)
+ {
+ if (isHighlighted == highlighted) return;
+
+ isHighlighted = highlighted;
+ SelfModulate = highlighted ? UIStyle.GetWarningColor() : Colors.White;
+ }
+
+ public HBoxContainer GetValueContainer()
+ {
+ return GetNode("./Values");
+ }
+
+ protected HBoxContainer GetValueContainer(NodeDisplay display)
+ {
+ return display.GetValueContainer();
+ }
+
+ private static PackedScene GetPrefab(string nodeName, Dictionary DSLNodes)
+ {
+ foreach (ProgramNode programNode in DSLNodes.Keys)
+ {
+ if (programNode.DisplayText.ToLower() == nodeName)
+ {
+ return DSLNodes[programNode];
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplay.cs.uid
new file mode 100644
index 0000000..f8ba4e5
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://b6kxwmuhmruul
diff --git a/Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs
new file mode 100644
index 0000000..4090deb
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs
@@ -0,0 +1,71 @@
+using Godot;
+
+public partial class CraftNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new CraftNode();
+ }
+
+ protected override void LoadContent(NodeDisplay display, string content)
+ {
+ HBoxContainer valueContainer = GetValueContainer(display);
+ string[] parts = content.Split(",");
+ string itemString = parts[1].Replace("Item:", "").Trim();
+ if (itemString.ToLower() != "empty")
+ {
+ CraftNode craftNode = display.node as CraftNode;
+ if (craftNode != null)
+ {
+ craftNode.selectedItem = new Item { data = GameData.availableItems[itemString] };
+ }
+ }
+ string amountString = parts[2].Replace("Amount:", "").Trim();
+ valueContainer.GetNode("./Amount").Value = int.Parse(amountString);
+ }
+
+ public override void ReadParameters()
+ {
+ CraftNode craftNode = node as CraftNode;
+ if (craftNode == null) return;
+
+ HBoxContainer valueContainer = GetValueContainer();
+ OptionButton items = valueContainer.GetNode("./Item");
+ string readableItem = items.GetItemText(items.GetSelectedId()).Split(":")[0];
+ if (GameData.availableItems.ContainsKey(ItemData.GetIndex(readableItem)))
+ {
+ craftNode.selectedItem = new Item { data = GameData.availableItems[ItemData.GetIndex(readableItem)] };
+ }
+ craftNode.amount = (int)valueContainer.GetNode("./Amount").Value;
+ }
+
+ public override void SetupDisplay()
+ {
+ CraftNode craftNode = node as CraftNode;
+ if (craftNode == null) return;
+
+ OptionButton options = GetValueContainer().GetNode("./Item");
+ options.Clear();
+ options.AddItem("Select item...");
+ foreach (ItemData item in GameData.availableItems.Values)
+ {
+ if (GameData.availableResearch[item.Research].state != ResearchState.RESEARCHED) continue;
+ if (item.Inputs.Count > 0)
+ {
+ options.AddItem(item.GetCraftingDisplay());
+ }
+ }
+
+ if (craftNode.selectedItem != null)
+ {
+ for (int i = 0; i < options.ItemCount; i++)
+ {
+ if (ItemData.GetIndex(options.GetItemText(i).Split(":")[0]) == craftNode.selectedItem.data.Id)
+ {
+ options.Select(i);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs.uid
new file mode 100644
index 0000000..ea1247a
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/CraftNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://bfosue8mejnr5
diff --git a/Scripts/UI/DSL/NodeDisplays/ExploreNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/ExploreNodeDisplay.cs
new file mode 100644
index 0000000..6c2b2b6
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/ExploreNodeDisplay.cs
@@ -0,0 +1,7 @@
+public partial class ExploreNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new ExploreNode();
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/ExploreNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/ExploreNodeDisplay.cs.uid
new file mode 100644
index 0000000..335bcb6
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/ExploreNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://bbju8q0es5rcf
diff --git a/Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs
new file mode 100644
index 0000000..ae4e268
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs
@@ -0,0 +1,31 @@
+using Godot;
+
+public partial class ForNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new ForNode();
+ }
+
+ protected override void LoadContent(NodeDisplay display, string content)
+ {
+ HBoxContainer valueContainer = GetValueContainer(display);
+ string[] parts = content.Split(",");
+ string amountExecuted = parts[1].Replace("AmountExecuted:", "").Trim();
+ ForNode forNode = display.node as ForNode;
+ if (forNode != null)
+ {
+ forNode.amountExecuted = int.Parse(amountExecuted);
+ }
+ string amountString = parts[2].Replace("Amount:", "").Trim();
+ valueContainer.GetNode("./Amount").Value = int.Parse(amountString);
+ }
+
+ public override void ReadParameters()
+ {
+ ForNode forNode = node as ForNode;
+ if (forNode == null) return;
+
+ forNode.amount = (int)GetValueContainer().GetNode("./Amount").Value;
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs.uid
new file mode 100644
index 0000000..300f58d
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/ForNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://gptqyjv5swwc
diff --git a/Scripts/UI/DSL/NodeDisplays/HarvestNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/HarvestNodeDisplay.cs
new file mode 100644
index 0000000..4e758d4
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/HarvestNodeDisplay.cs
@@ -0,0 +1,7 @@
+public partial class HarvestNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new HarvestNode();
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/HarvestNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/HarvestNodeDisplay.cs.uid
new file mode 100644
index 0000000..ac794f9
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/HarvestNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://u2y14qj6oxyy
diff --git a/Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs
new file mode 100644
index 0000000..145a557
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs
@@ -0,0 +1,89 @@
+using Godot;
+
+public partial class IfNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new IfNode();
+ }
+
+ protected override void LoadContent(NodeDisplay display, string content)
+ {
+ HBoxContainer valueContainer = GetValueContainer(display);
+ string[] parts = content.Split(",");
+ string itemString = parts[1].Replace("Item:", "").Trim();
+ string comparatorString = parts[2].Replace("Comparator:", "").Trim();
+ if (itemString.ToLower() != "empty")
+ {
+ IfNode ifNode = display.node as IfNode;
+ if (ifNode != null)
+ {
+ ifNode.selectedItem = new Item { data = GameData.availableItems[itemString] };
+ ifNode.comparator = comparatorString;
+ }
+ }
+ string amountString = parts[3].Replace("Amount:", "").Trim();
+ valueContainer.GetNode("./Amount").Value = int.Parse(amountString);
+ }
+
+ public override void ReadParameters()
+ {
+ IfNode ifNode = node as IfNode;
+ if (ifNode == null) return;
+
+ HBoxContainer valueContainer = GetValueContainer();
+ OptionButton items = valueContainer.GetNode("./Item");
+ string readableItem = items.GetItemText(items.GetSelectedId()).Split(":")[0];
+ if (GameData.availableItems.ContainsKey(ItemData.GetIndex(readableItem)))
+ {
+ ifNode.selectedItem = new Item { data = GameData.availableItems[ItemData.GetIndex(readableItem)] };
+ }
+ ifNode.amount = (int)valueContainer.GetNode("./Amount").Value;
+
+ OptionButton comparators = valueContainer.GetNode("./Comparator");
+ if(comparators.GetSelectedId() == -1) return;
+ ifNode.comparator = comparators.GetItemText(comparators.GetSelectedId());
+ }
+
+ public override void SetupDisplay()
+ {
+ IfNode ifNode = node as IfNode;
+ if (ifNode == null) return;
+
+ HBoxContainer valueContainer = GetValueContainer();
+ OptionButton options = valueContainer.GetNode("./Item");
+ options.Clear();
+ options.AddItem("Select item...");
+ foreach (ItemData item in GameData.availableItems.Values)
+ {
+ if (GameData.availableResearch[item.Research].state != ResearchState.RESEARCHED) continue;
+ options.AddItem(item.GetReadableName());
+ }
+
+ if (ifNode.selectedItem != null)
+ {
+ for (int i = 0; i < options.ItemCount; i++)
+ {
+ if (ItemData.GetIndex(options.GetItemText(i).Split(":")[0]) == ifNode.selectedItem.data.Id)
+ {
+ options.Select(i);
+ break;
+ }
+ }
+ }
+
+ OptionButton comparators = valueContainer.GetNode("./Comparator");
+
+ if (ifNode.comparator != null)
+ {
+ for (int i = 0; i < comparators.ItemCount; i++)
+ {
+ if (comparators.GetItemText(i) == ifNode.comparator)
+ {
+ comparators.Select(i);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs.uid
new file mode 100644
index 0000000..d326aba
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/IfNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://cngxwfcrim746
diff --git a/Scripts/UI/DSL/NodeDisplays/MaintainNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/MaintainNodeDisplay.cs
new file mode 100644
index 0000000..4f5e35f
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/MaintainNodeDisplay.cs
@@ -0,0 +1,7 @@
+public partial class MaintainNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new MaintainNode();
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/MaintainNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/MaintainNodeDisplay.cs.uid
new file mode 100644
index 0000000..a07b8a2
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/MaintainNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://wfsar5uiexvn
diff --git a/Scripts/UI/DSL/NodeDisplays/MoveNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/MoveNodeDisplay.cs
new file mode 100644
index 0000000..ed9e2e0
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/MoveNodeDisplay.cs
@@ -0,0 +1,48 @@
+using Godot;
+
+public partial class MoveNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new MoveNode();
+ }
+
+ protected override void LoadContent(NodeDisplay display, string content)
+ {
+ HBoxContainer valueContainer = GetValueContainer(display);
+ string[] parts = content.Split(",");
+ string positionValues = parts[1].Replace("Position:", "").Replace("(", "").Replace(")", "").Trim();
+ int posX = int.Parse(positionValues.Split("|")[0]);
+ int posY = int.Parse(positionValues.Split("|")[1]);
+ int posZ = int.Parse(positionValues.Split("|")[2]);
+ valueContainer.GetNode("./CoordinateX").Value = posX;
+ valueContainer.GetNode("./CoordinateY").Value = posY;
+ valueContainer.GetNode("./CoordinateZ").Value = posZ;
+
+ MoveNode moveNode = display.node as MoveNode;
+ if (moveNode != null)
+ {
+ moveNode.targetPosition = new Vector3I(posX, posY, posZ);
+ }
+ }
+
+ public override void ReadParameters()
+ {
+ MoveNode moveNode = node as MoveNode;
+ if (moveNode == null) return;
+
+ HBoxContainer valueContainer = GetValueContainer();
+ int posX = (int)valueContainer.GetNode("./CoordinateX").Value;
+ int posY = (int)valueContainer.GetNode("./CoordinateY").Value;
+ int posZ = (int)valueContainer.GetNode("./CoordinateZ").Value;
+ moveNode.targetPosition = new Vector3I(posX, posY, posZ);
+ }
+
+ public override void SetupDisplay()
+ {
+ HBoxContainer valueContainer = GetValueContainer();
+ valueContainer.GetNode("./CoordinateX").MaxValue = GameData.layerSize;
+ valueContainer.GetNode("./CoordinateY").MaxValue = GameData.ruinSize;
+ valueContainer.GetNode("./CoordinateZ").MaxValue = GameData.layerSize;
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/MoveNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/MoveNodeDisplay.cs.uid
new file mode 100644
index 0000000..8b07eb7
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/MoveNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://bucamlwjs0mm2
diff --git a/Scripts/UI/DSL/NodeDisplays/SacrificeNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/SacrificeNodeDisplay.cs
new file mode 100644
index 0000000..608efb9
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/SacrificeNodeDisplay.cs
@@ -0,0 +1,7 @@
+public partial class SacrificeNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new SacrificeNode();
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/SacrificeNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/SacrificeNodeDisplay.cs.uid
new file mode 100644
index 0000000..d270ce1
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/SacrificeNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://cu1nghtcsenfs
diff --git a/Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs
new file mode 100644
index 0000000..230cab2
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs
@@ -0,0 +1,7 @@
+public partial class StartNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new StartNode();
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs.uid
new file mode 100644
index 0000000..70db69f
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/StartNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://c7w5380k530wb
diff --git a/Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs b/Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs
new file mode 100644
index 0000000..ff04e8e
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs
@@ -0,0 +1,89 @@
+using Godot;
+
+public partial class WhileNodeDisplay : NodeDisplay
+{
+ protected override ProgramNode CreateProgramNode()
+ {
+ return new WhileNode();
+ }
+
+ protected override void LoadContent(NodeDisplay display, string content)
+ {
+ HBoxContainer valueContainer = GetValueContainer(display);
+ string[] parts = content.Split(",");
+ string itemString = parts[1].Replace("Item:", "").Trim();
+ string comparatorString = parts[2].Replace("Comparator:", "").Trim();
+ if (itemString.ToLower() != "empty")
+ {
+ WhileNode whileNode = display.node as WhileNode;
+ if (whileNode != null)
+ {
+ whileNode.selectedItem = new Item { data = GameData.availableItems[itemString] };
+ whileNode.comparator = comparatorString;
+ }
+ }
+ string amountString = parts[3].Replace("Amount:", "").Trim();
+ valueContainer.GetNode("./Amount").Value = int.Parse(amountString);
+ }
+
+ public override void ReadParameters()
+ {
+ WhileNode whileNode = node as WhileNode;
+ if (whileNode == null) return;
+
+ HBoxContainer valueContainer = GetValueContainer();
+ OptionButton items = valueContainer.GetNode("./Item");
+ string readableItem = items.GetItemText(items.GetSelectedId()).Split(":")[0];
+ if (GameData.availableItems.ContainsKey(ItemData.GetIndex(readableItem)))
+ {
+ whileNode.selectedItem = new Item { data = GameData.availableItems[ItemData.GetIndex(readableItem)] };
+ }
+ whileNode.amount = (int)valueContainer.GetNode("./Amount").Value;
+
+ OptionButton comparators = valueContainer.GetNode("./Comparator");
+ if(comparators.GetSelectedId() == -1) return;
+ whileNode.comparator = comparators.GetItemText(comparators.GetSelectedId());
+ }
+
+ public override void SetupDisplay()
+ {
+ WhileNode whileNode = node as WhileNode;
+ if (whileNode == null) return;
+
+ HBoxContainer valueContainer = GetValueContainer();
+ OptionButton options = valueContainer.GetNode("./Item");
+ options.Clear();
+ options.AddItem("Select item...");
+ foreach (ItemData item in GameData.availableItems.Values)
+ {
+ if (GameData.availableResearch[item.Research].state != ResearchState.RESEARCHED) continue;
+ options.AddItem(item.GetReadableName());
+ }
+
+ if (whileNode.selectedItem != null)
+ {
+ for (int i = 0; i < options.ItemCount; i++)
+ {
+ if (ItemData.GetIndex(options.GetItemText(i).Split(":")[0]) == whileNode.selectedItem.data.Id)
+ {
+ options.Select(i);
+ break;
+ }
+ }
+ }
+
+ OptionButton comparators = valueContainer.GetNode("./Comparator");
+
+ if (whileNode.comparator != null)
+ {
+ for (int i = 0; i < comparators.ItemCount; i++)
+ {
+ if (comparators.GetItemText(i) == whileNode.comparator)
+ {
+ comparators.Select(i);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs.uid b/Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs.uid
new file mode 100644
index 0000000..c5fccfc
--- /dev/null
+++ b/Scripts/UI/DSL/NodeDisplays/WhileNodeDisplay.cs.uid
@@ -0,0 +1 @@
+uid://d3npiur46icru
diff --git a/Scripts/UI/DSL/NodeTooltip.cs b/Scripts/UI/DSL/NodeTooltip.cs
new file mode 100644
index 0000000..ad096a0
--- /dev/null
+++ b/Scripts/UI/DSL/NodeTooltip.cs
@@ -0,0 +1,24 @@
+using Godot;
+
+public partial class NodeTooltip : PanelContainer
+{
+ [Export] RichTextLabel title;
+ [Export] TextureRect image;
+ [Export] RichTextLabel description;
+
+ private static Vector2 spacing = new Vector2(5, 0);
+
+ public void ShowTooltip(string titleText, string descriptionText, Button button)
+ {
+ Show();
+ title.Text = titleText;
+ image.Texture = ResourceLoader.LoadDSLTooltip(titleText);
+ description.Text = descriptionText;
+ Position = button.GlobalPosition - spacing - new Vector2(Size.X, Size.Y/2);
+ }
+
+ public void HideTooltip()
+ {
+ Hide();
+ }
+}
diff --git a/Scripts/UI/DSL/NodeTooltip.cs.uid b/Scripts/UI/DSL/NodeTooltip.cs.uid
new file mode 100644
index 0000000..0bd9401
--- /dev/null
+++ b/Scripts/UI/DSL/NodeTooltip.cs.uid
@@ -0,0 +1 @@
+uid://dx5oy38ochrw2
diff --git a/Scripts/UI/DSL/RunningProgramGraphBuilder.cs b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs
new file mode 100644
index 0000000..03137f7
--- /dev/null
+++ b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs
@@ -0,0 +1,69 @@
+using Godot;
+using System;
+using System.Collections.Generic;
+
+public class RunningProgramGraphBuilder
+{
+ private readonly Dictionary dslNodes;
+ private readonly Action addNode;
+ private readonly Action connectNodes;
+
+ public RunningProgramGraphBuilder(
+ Dictionary dslNodes,
+ Action addNode,
+ Action connectNodes
+ )
+ {
+ this.dslNodes = dslNodes;
+ this.addNode = addNode;
+ this.connectNodes = connectNodes;
+ }
+
+ public void Load(ProgramNode startNode)
+ {
+ Dictionary loadedNodes =
+ new Dictionary();
+ LoadNode(startNode, loadedNodes);
+ }
+
+ private NodeDisplay LoadNode(
+ ProgramNode programNode,
+ Dictionary loadedNodes
+ )
+ {
+ if (programNode == null) return null;
+ if (loadedNodes.ContainsKey(programNode)) return loadedNodes[programNode];
+
+ NodeDisplay nodeDisplay = NodeDisplay.Load(
+ programNode.DisplayText,
+ programNode.Save(),
+ dslNodes
+ );
+ if (nodeDisplay == null) return null;
+ if (programNode.EditorNodeId != null && programNode.EditorNodeId.Length > 0)
+ {
+ nodeDisplay.Name = programNode.EditorNodeId;
+ }
+
+ addNode(nodeDisplay);
+ loadedNodes.Add(programNode, nodeDisplay);
+
+ ConnectNode(nodeDisplay, 0, programNode.nextNode, loadedNodes);
+ ConnectNode(nodeDisplay, 1, programNode.NegativeNode, loadedNodes);
+
+ return nodeDisplay;
+ }
+
+ private void ConnectNode(
+ NodeDisplay fromDisplay,
+ int fromPort,
+ ProgramNode targetNode,
+ Dictionary loadedNodes
+ )
+ {
+ NodeDisplay toDisplay = LoadNode(targetNode, loadedNodes);
+ if (toDisplay == null) return;
+
+ connectNodes(fromDisplay.Name, fromPort, toDisplay.Name, 0);
+ }
+}
diff --git a/Scripts/UI/DSL/RunningProgramGraphBuilder.cs.uid b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs.uid
new file mode 100644
index 0000000..f3ce0b9
--- /dev/null
+++ b/Scripts/UI/DSL/RunningProgramGraphBuilder.cs.uid
@@ -0,0 +1 @@
+uid://deaptod52fe2s
diff --git a/Scripts/UI/DSL/ScriptConnection.cs b/Scripts/UI/DSL/ScriptConnection.cs
new file mode 100644
index 0000000..abd93a5
--- /dev/null
+++ b/Scripts/UI/DSL/ScriptConnection.cs
@@ -0,0 +1,7 @@
+public class ScriptConnection
+{
+ public string FromNodeId;
+ public int FromPort;
+ public string ToNodeId;
+ public int ToPort;
+}
diff --git a/Scripts/UI/DSL/ScriptConnection.cs.uid b/Scripts/UI/DSL/ScriptConnection.cs.uid
new file mode 100644
index 0000000..b505b35
--- /dev/null
+++ b/Scripts/UI/DSL/ScriptConnection.cs.uid
@@ -0,0 +1 @@
+uid://cvhobhufyp2ni
diff --git a/Scripts/UI/DSL/ScriptGraphCompiler.cs b/Scripts/UI/DSL/ScriptGraphCompiler.cs
new file mode 100644
index 0000000..1b4d525
--- /dev/null
+++ b/Scripts/UI/DSL/ScriptGraphCompiler.cs
@@ -0,0 +1,113 @@
+using Godot;
+using Godot.Collections;
+using System.Collections.Generic;
+
+public class ScriptGraphCompiler
+{
+ private readonly GraphEdit editorWindow;
+ private System.Collections.Generic.Dictionary runtimeNodes;
+
+ public ScriptGraphCompiler(GraphEdit editorWindow)
+ {
+ this.editorWindow = editorWindow;
+ }
+
+ public List BuildProgram(out string errorMessage)
+ {
+ errorMessage = "";
+ NodeDisplay startNode = FindStartNode();
+ if (startNode == null)
+ {
+ errorMessage = "(FAILED) Script needs exactly one Start node";
+ return new List();
+ }
+
+ BuildRuntimeNodeLookup();
+ return BuildScriptOrder(
+ startNode,
+ new List(),
+ new HashSet()
+ );
+ }
+
+ private void BuildRuntimeNodeLookup()
+ {
+ runtimeNodes = new System.Collections.Generic.Dictionary();
+
+ for (int i = 0; i < editorWindow.GetChildCount(); i++)
+ {
+ NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
+ if (nodeDisplay == null) continue;
+
+ nodeDisplay.ReadParameters();
+ runtimeNodes.Add(
+ nodeDisplay.Name,
+ nodeDisplay.node.DuplicateForRuntime(nodeDisplay.Name.ToString())
+ );
+ }
+ }
+
+ private List BuildScriptOrder(
+ NodeDisplay node,
+ List program,
+ HashSet visitedNodes
+ )
+ {
+ if (node == null) return program;
+ if (visitedNodes.Contains(node.Name)) return program;
+ if (!runtimeNodes.ContainsKey(node.Name)) return program;
+
+ visitedNodes.Add(node.Name);
+ ProgramNode runtimeNode = runtimeNodes[node.Name];
+ program.Add(runtimeNode);
+
+ List nextConnections = GetOutgoingConnections(node);
+ if (nextConnections.Count <= 0) return program;
+
+ runtimeNode.SetNextNode(nextConnections, runtimeNodes);
+ foreach (Dictionary connection in nextConnections)
+ {
+ NodeDisplay nextNode = editorWindow.GetNodeOrNull(
+ new NodePath(connection["to_node"].AsStringName())
+ );
+ program = BuildScriptOrder(
+ nextNode,
+ program,
+ visitedNodes
+ );
+ }
+
+ return program;
+ }
+
+ private List GetOutgoingConnections(NodeDisplay node)
+ {
+ List result = new List();
+ Array connections = editorWindow.GetConnectionListFromNode(node.Name);
+ for (int i = 0; i < connections.Count; i++)
+ {
+ if (connections[i]["from_node"].AsStringName() == node.Name)
+ {
+ result.Add(connections[i]);
+ }
+ }
+
+ return result;
+ }
+
+ private NodeDisplay FindStartNode()
+ {
+ NodeDisplay startNode = null;
+ for (int i = 0; i < editorWindow.GetChildCount(); i++)
+ {
+ NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
+ if (nodeDisplay == null) continue;
+ if (nodeDisplay.node is not StartNode) continue;
+ if (startNode != null) return null;
+
+ startNode = nodeDisplay;
+ }
+
+ return startNode;
+ }
+}
diff --git a/Scripts/UI/DSL/ScriptGraphCompiler.cs.uid b/Scripts/UI/DSL/ScriptGraphCompiler.cs.uid
new file mode 100644
index 0000000..7ed291e
--- /dev/null
+++ b/Scripts/UI/DSL/ScriptGraphCompiler.cs.uid
@@ -0,0 +1 @@
+uid://d122nx50nj3wc
diff --git a/Scripts/UI/DSL/ScriptGraphSerializer.cs b/Scripts/UI/DSL/ScriptGraphSerializer.cs
new file mode 100644
index 0000000..6af51e3
--- /dev/null
+++ b/Scripts/UI/DSL/ScriptGraphSerializer.cs
@@ -0,0 +1,189 @@
+using Godot;
+using Godot.Collections;
+using System;
+public class ScriptGraphSerializer
+{
+ private readonly GraphEdit editorWindow;
+ private readonly System.Collections.Generic.Dictionary dslNodes;
+ private readonly Action addNode;
+ private readonly Action connectNodes;
+
+ public ScriptGraphSerializer(
+ GraphEdit editorWindow,
+ System.Collections.Generic.Dictionary dslNodes,
+ Action addNode,
+ Action connectNodes
+ )
+ {
+ this.editorWindow = editorWindow;
+ this.dslNodes = dslNodes;
+ this.addNode = addNode;
+ this.connectNodes = connectNodes;
+ }
+
+ public void Load(string scriptContent)
+ {
+ Variant parsedScript = Json.ParseString(scriptContent);
+ if (parsedScript.VariantType != Variant.Type.Dictionary) return;
+
+ Dictionary scriptData = parsedScript.AsGodotDictionary();
+ if (!scriptData.ContainsKey("Nodes")) return;
+
+ System.Collections.Generic.Dictionary loadedNodes =
+ new System.Collections.Generic.Dictionary();
+ Godot.Collections.Array nodes = scriptData["Nodes"].AsGodotArray();
+ for (int i = 0; i < nodes.Count; i++)
+ {
+ Dictionary nodeData = nodes[i].AsGodotDictionary();
+ NodeDisplay nodeDisplay = LoadNode(nodeData);
+ if (nodeDisplay == null) continue;
+
+ addNode(nodeDisplay);
+ RegisterLoadedNode(nodeData, nodeDisplay, loadedNodes);
+ }
+
+ LoadConnections(scriptData, loadedNodes);
+ }
+
+ public string Save()
+ {
+ Array savedNodes = BuildSavedNodes();
+ if (savedNodes.Count <= 0) return "";
+
+ Dictionary scriptData = new Dictionary();
+ scriptData["Nodes"] = savedNodes;
+ scriptData["Connections"] = BuildSavedConnections();
+
+ return Json.Stringify(scriptData);
+ }
+
+ private NodeDisplay LoadNode(Dictionary nodeData)
+ {
+ if (!nodeData.ContainsKey("Type")) return null;
+ if (!nodeData.ContainsKey("Content")) return null;
+
+ string type = nodeData["Type"].AsString();
+ string content = nodeData["Content"].AsString();
+ NodeDisplay nodeDisplay = NodeDisplay.Load(type, content, dslNodes);
+ if (nodeDisplay == null) return null;
+
+ if (nodeData.ContainsKey("Id"))
+ {
+ nodeDisplay.Name = nodeData["Id"].AsString();
+ }
+
+ if (nodeData.ContainsKey("PositionX") && nodeData.ContainsKey("PositionY"))
+ {
+ float positionX = (float)nodeData["PositionX"].AsDouble();
+ float positionY = (float)nodeData["PositionY"].AsDouble();
+ nodeDisplay.PositionOffset = new Vector2(positionX, positionY);
+ }
+
+ return nodeDisplay;
+ }
+
+ private void RegisterLoadedNode(
+ Dictionary nodeData,
+ NodeDisplay nodeDisplay,
+ System.Collections.Generic.Dictionary loadedNodes
+ )
+ {
+ string nodeId = nodeDisplay.Name.ToString();
+ if (nodeData.ContainsKey("Id"))
+ {
+ nodeId = nodeData["Id"].AsString();
+ }
+
+ if (!loadedNodes.ContainsKey(nodeId))
+ {
+ loadedNodes.Add(nodeId, nodeDisplay);
+ }
+ }
+
+ private void LoadConnections(
+ Dictionary scriptData,
+ System.Collections.Generic.Dictionary loadedNodes
+ )
+ {
+ if (!scriptData.ContainsKey("Connections")) return;
+
+ Godot.Collections.Array connectionData = scriptData["Connections"].AsGodotArray();
+ for (int i = 0; i < connectionData.Count; i++)
+ {
+ Dictionary savedConnection = connectionData[i].AsGodotDictionary();
+ if (!IsConnectionDataValid(savedConnection)) continue;
+
+ ScriptConnection connection = CreateConnection(savedConnection);
+ ConnectLoadedNodes(connection, loadedNodes);
+ }
+ }
+
+ private bool IsConnectionDataValid(Dictionary savedConnection)
+ {
+ return savedConnection.ContainsKey("From")
+ && savedConnection.ContainsKey("To")
+ && savedConnection.ContainsKey("FromPort")
+ && savedConnection.ContainsKey("ToPort");
+ }
+
+ private ScriptConnection CreateConnection(Dictionary savedConnection)
+ {
+ return new ScriptConnection
+ {
+ FromNodeId = savedConnection["From"].AsString(),
+ FromPort = savedConnection["FromPort"].AsInt32(),
+ ToNodeId = savedConnection["To"].AsString(),
+ ToPort = savedConnection["ToPort"].AsInt32()
+ };
+ }
+
+ private void ConnectLoadedNodes(
+ ScriptConnection connection,
+ System.Collections.Generic.Dictionary loadedNodes
+ )
+ {
+ if (!loadedNodes.ContainsKey(connection.FromNodeId)) return;
+ if (!loadedNodes.ContainsKey(connection.ToNodeId)) return;
+
+ NodeDisplay fromDisplay = loadedNodes[connection.FromNodeId];
+ NodeDisplay toDisplay = loadedNodes[connection.ToNodeId];
+ connectNodes(fromDisplay.Name, connection.FromPort, toDisplay.Name, connection.ToPort);
+ }
+
+ private Array BuildSavedNodes()
+ {
+ Array savedNodes = new Array();
+ for (int i = 0; i < editorWindow.GetChildCount(); i++)
+ {
+ NodeDisplay nodeDisplay = editorWindow.GetChild(i) as NodeDisplay;
+ if (nodeDisplay == null) continue;
+
+ nodeDisplay.ReadParameters();
+ Dictionary savedNode = new Dictionary();
+ savedNode["Id"] = nodeDisplay.Name.ToString();
+ savedNode["Type"] = nodeDisplay.node.DisplayText.ToLower();
+ savedNode["Content"] = nodeDisplay.node.Save();
+ savedNode["PositionX"] = nodeDisplay.PositionOffset.X;
+ savedNode["PositionY"] = nodeDisplay.PositionOffset.Y;
+ savedNodes.Add(savedNode);
+ }
+
+ return savedNodes;
+ }
+
+ private Array BuildSavedConnections()
+ {
+ Array savedConnections = new Array();
+ foreach (Dictionary connection in editorWindow.GetConnectionList())
+ {
+ Dictionary savedConnection = new Dictionary();
+ savedConnection["From"] = connection["from_node"].AsStringName().ToString();
+ savedConnection["FromPort"] = (int)connection["from_port"];
+ savedConnection["To"] = connection["to_node"].AsStringName().ToString();
+ savedConnection["ToPort"] = (int)connection["to_port"];
+ savedConnections.Add(savedConnection);
+ }
+
+ return savedConnections;
+ }
+}
diff --git a/Scripts/UI/DSL/ScriptGraphSerializer.cs.uid b/Scripts/UI/DSL/ScriptGraphSerializer.cs.uid
new file mode 100644
index 0000000..ce873a0
--- /dev/null
+++ b/Scripts/UI/DSL/ScriptGraphSerializer.cs.uid
@@ -0,0 +1 @@
+uid://dsrkrw6524c
diff --git a/Scripts/UI/Inventory/InventoryDisplay.cs b/Scripts/UI/Inventory/InventoryDisplay.cs
new file mode 100644
index 0000000..7e448e9
--- /dev/null
+++ b/Scripts/UI/Inventory/InventoryDisplay.cs
@@ -0,0 +1,71 @@
+using System;
+using Godot;
+
+public partial class InventoryDisplay : PanelContainer
+{
+ private readonly PackedScene itemDisplayPrefab = ResourceLoader.LoadItemDisplay();
+
+ [Export] VBoxContainer itemList;
+ [Export] RichTextLabel inventorySpace;
+
+ public override void _Ready()
+ {
+ GameData.inventory.OnInventoryUpdate += OnInventoryUpdate;
+ }
+
+ public override void _ExitTree()
+ {
+ GameData.inventory.OnInventoryUpdate -= OnInventoryUpdate;
+ }
+
+ public override void _Notification(int id)
+ {
+ if (id == NotificationVisibilityChanged)
+ {
+ if (!Visible) return;
+
+ ReloadItems();
+ UpdateInventorySpace();
+ }
+ }
+
+ private void UpdateInventorySpace()
+ {
+ inventorySpace.Text = $"Used space: {GameData.inventory.items.Count}/{GameData.inventory.maxInventorySize * GameData.maxRobotCount}";
+ }
+
+ public void ReloadItems()
+ {
+ ClearItems();
+
+ foreach (Item item in GameData.inventory.items)
+ {
+ itemList.AddChild(CreateItemDisplay(item));
+ }
+ }
+
+ private void ClearItems()
+ {
+ foreach (Node node in itemList.GetChildren())
+ {
+ itemList.RemoveChild(node);
+ node.QueueFree();
+ }
+ }
+
+ private ItemDisplay CreateItemDisplay(Item item)
+ {
+ ItemDisplay display = itemDisplayPrefab.Instantiate();
+ display.item = item;
+ display.text.Text = item.data.GetReadableName();
+ display.amount.Text = $"{item.currentAmount}/{item.data.StackSize}";
+ display.texture.Texture = ResourceLoader.LoadPath(item.data.Texture);
+ return display;
+ }
+
+ public void OnInventoryUpdate(object sender, EventArgs args)
+ {
+ ReloadItems();
+ UpdateInventorySpace();
+ }
+}
diff --git a/Scripts/UI/Inventory/InventoryDisplay.cs.uid b/Scripts/UI/Inventory/InventoryDisplay.cs.uid
new file mode 100644
index 0000000..3bf3401
--- /dev/null
+++ b/Scripts/UI/Inventory/InventoryDisplay.cs.uid
@@ -0,0 +1 @@
+uid://com0u7nqag6pp
diff --git a/Scripts/UI/Inventory/ItemDisplay.cs b/Scripts/UI/Inventory/ItemDisplay.cs
new file mode 100644
index 0000000..5220079
--- /dev/null
+++ b/Scripts/UI/Inventory/ItemDisplay.cs
@@ -0,0 +1,9 @@
+using Godot;
+
+public partial class ItemDisplay : PanelContainer
+{
+ [Export] public TextureRect texture;
+ [Export] public RichTextLabel text;
+ [Export] public RichTextLabel amount;
+ public Item item;
+}
diff --git a/Scripts/UI/Inventory/ItemDisplay.cs.uid b/Scripts/UI/Inventory/ItemDisplay.cs.uid
new file mode 100644
index 0000000..be1e4e3
--- /dev/null
+++ b/Scripts/UI/Inventory/ItemDisplay.cs.uid
@@ -0,0 +1 @@
+uid://qdjn5oqn6p5d
diff --git a/Scripts/UI/Menus/MainMenu.cs b/Scripts/UI/Menus/MainMenu.cs
new file mode 100644
index 0000000..985b0f0
--- /dev/null
+++ b/Scripts/UI/Menus/MainMenu.cs
@@ -0,0 +1,38 @@
+using Godot;
+
+public partial class MainMenu : Control
+{
+ [Export] private PanelContainer options;
+
+ public override void _Ready()
+ {
+ UIStyle.Apply(this);
+ }
+
+ public void OnPlayPressed()
+ {
+ GameData.loadSaveOnStart = false;
+ GetTree().ChangeSceneToFile("res://Scenes/WorldSetup.tscn");
+ }
+
+ public void OnLoadPressed()
+ {
+ if (!SaveGameManager.SaveExists()) return;
+
+ GameData.loadSaveOnStart = true;
+ GameData.showTutorial = false;
+ GetTree().ChangeSceneToFile("res://Scenes/Game.tscn");
+ }
+
+ public void OnQuitPressed()
+ {
+ GetTree().Quit();
+ }
+
+ public void OnOptionsPressed()
+ {
+ if (options == null) return;
+
+ options.Show();
+ }
+}
diff --git a/Scripts/UI/Menus/MainMenu.cs.uid b/Scripts/UI/Menus/MainMenu.cs.uid
new file mode 100644
index 0000000..213e872
--- /dev/null
+++ b/Scripts/UI/Menus/MainMenu.cs.uid
@@ -0,0 +1 @@
+uid://dda0bhhqspbr0
diff --git a/Scripts/UI/Menus/OptionsMenu.cs b/Scripts/UI/Menus/OptionsMenu.cs
new file mode 100644
index 0000000..8d36b86
--- /dev/null
+++ b/Scripts/UI/Menus/OptionsMenu.cs
@@ -0,0 +1,87 @@
+using Godot;
+
+public partial class OptionsMenu : PanelContainer
+{
+ private readonly Vector2 panelSize = new Vector2(420, 260);
+
+ [Export] private OptionButton screenMode;
+ [Export] private HSlider soundVolume;
+ [Export] private ColorPickerButton lightColor;
+
+ public override void _Ready()
+ {
+ CenterPanel();
+ SetupScreenModes();
+ screenMode.Select(GameData.screenMode);
+ soundVolume.Value = GameData.soundVolume * 100f;
+ lightColor.Color = GameData.lightColor;
+ ApplyScreenMode(GameData.screenMode);
+ SoundManager.SetMasterVolume(GameData.soundVolume);
+ }
+
+ private void SetupScreenModes()
+ {
+ screenMode.Clear();
+ screenMode.AddItem("Fullscreen");
+ screenMode.AddItem("Windowed");
+ screenMode.AddItem("Windowed Fullscreen");
+ screenMode.Select(2);
+ }
+
+ public void OnScreenModeSelected(int index)
+ {
+ GameData.screenMode = index;
+ ApplyScreenMode(index);
+ }
+
+ private void ApplyScreenMode(int index)
+ {
+ switch (index)
+ {
+ case 0:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
+ break;
+ case 1:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
+ break;
+ case 2:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Fullscreen);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, true);
+ break;
+ default:
+ DisplayServer.WindowSetMode(DisplayServer.WindowMode.Windowed);
+ DisplayServer.WindowSetFlag(DisplayServer.WindowFlags.Borderless, false);
+ break;
+ }
+ }
+
+ public void OnSoundVolumeChanged(double value)
+ {
+ GameData.soundVolume = (float)value / 100f;
+ SoundManager.SetMasterVolume(GameData.soundVolume);
+ }
+
+ public void OnLightColorChanged(Color color)
+ {
+ GameData.lightColor = color;
+ LightHandler.RedrawLights(color);
+ }
+
+ public void CloseOptions()
+ {
+ Hide();
+ GameData.isPaused = false;
+ }
+
+ private void CenterPanel()
+ {
+ CustomMinimumSize = panelSize;
+ SetAnchorsPreset(LayoutPreset.Center);
+ OffsetLeft = -panelSize.X / 2f;
+ OffsetTop = -panelSize.Y / 2f;
+ OffsetRight = panelSize.X / 2f;
+ OffsetBottom = panelSize.Y / 2f;
+ }
+}
diff --git a/Scripts/UI/Menus/OptionsMenu.cs.uid b/Scripts/UI/Menus/OptionsMenu.cs.uid
new file mode 100644
index 0000000..8314e7b
--- /dev/null
+++ b/Scripts/UI/Menus/OptionsMenu.cs.uid
@@ -0,0 +1 @@
+uid://ca1rfge0y3y4i
diff --git a/Scripts/UI/Menus/WorldSetup.cs b/Scripts/UI/Menus/WorldSetup.cs
new file mode 100644
index 0000000..a25d625
--- /dev/null
+++ b/Scripts/UI/Menus/WorldSetup.cs
@@ -0,0 +1,72 @@
+using Godot;
+using System;
+
+public partial class WorldSetup : Control
+{
+ [Export] LineEdit seedInput;
+ [Export] RichTextLabel seedPreview;
+
+ public override void _Ready()
+ {
+ UIStyle.Apply(this);
+ UpdateSeedPreview();
+ }
+
+ public void OnSeedChanged(string text)
+ {
+ UpdateSeedPreview();
+ }
+
+ public void OnStartPressed()
+ {
+ GameData.seed = GetSelectedSeed();
+ GameData.loadSaveOnStart = false;
+ GameData.showTutorial = true;
+ GetTree().ChangeSceneToFile("res://Scenes/Game.tscn");
+ }
+
+ public void OnBackPressed()
+ {
+ GetTree().ChangeSceneToFile("res://Scenes/MainMenu.tscn");
+ }
+
+ private int GetSelectedSeed()
+ {
+ string text = seedInput.Text.Trim();
+ if (text.Length <= 0)
+ {
+ return new Random().Next(1, int.MaxValue);
+ }
+
+ int numericSeed;
+ if (int.TryParse(text, out numericSeed))
+ {
+ return numericSeed;
+ }
+
+ return CreateTextSeed(text);
+ }
+
+ private int CreateTextSeed(string text)
+ {
+ int hash = 17;
+ foreach (char character in text)
+ {
+ hash = hash * 31 + character;
+ }
+
+ return Math.Abs(hash);
+ }
+
+ private void UpdateSeedPreview()
+ {
+ string text = seedInput.Text.Trim();
+ if (text.Length <= 0)
+ {
+ seedPreview.Text = "No seed entered. B.O.B. will pick one from the ruins.";
+ return;
+ }
+
+ seedPreview.Text = "Selected seed: " + GetSelectedSeed();
+ }
+}
diff --git a/Scripts/UI/Menus/WorldSetup.cs.uid b/Scripts/UI/Menus/WorldSetup.cs.uid
new file mode 100644
index 0000000..baa54aa
--- /dev/null
+++ b/Scripts/UI/Menus/WorldSetup.cs.uid
@@ -0,0 +1 @@
+uid://3xjyrxtq3rww
diff --git a/Scripts/UI/Research/ResearchList.cs b/Scripts/UI/Research/ResearchList.cs
new file mode 100644
index 0000000..01d5d0f
--- /dev/null
+++ b/Scripts/UI/Research/ResearchList.cs
@@ -0,0 +1,307 @@
+using Godot;
+using Godot.Collections;
+using System.Collections.Generic;
+using System.Text;
+
+public partial class ResearchList : PanelContainer
+{
+ [Export] private GraphEdit researchGraph;
+ private System.Collections.Generic.Dictionary nodePositions = new System.Collections.Generic.Dictionary();
+ private bool hasArrangedNodes = false;
+
+ private List currentResearch = new List();
+
+ public override void _Ready()
+ {
+ RecalculateResearchStates();
+ if (Visible) SetupGraph();
+ }
+
+ public override void _Process(double delta)
+ {
+ if (currentResearch.Count <= 0) return;
+
+ List finishedResearch = new List();
+ foreach (Research research in currentResearch)
+ {
+ ResearchResult result = research.Execute(delta);
+ if (!IsResearchFinished(research, result)) continue;
+
+ finishedResearch.Add(research);
+ }
+
+ if (finishedResearch.Count <= 0) return;
+
+ foreach (Research research in finishedResearch)
+ {
+ 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()
+ {
+ RememberNodePositions();
+ ClearGraph();
+ CreateResearchNodes();
+ CreateResearchConnections();
+
+ if (!hasArrangedNodes)
+ {
+ researchGraph.ArrangeNodes();
+ hasArrangedNodes = true;
+ }
+ }
+
+ private void RememberNodePositions()
+ {
+ foreach (Node child in researchGraph.GetChildren())
+ {
+ GraphNode node = child as GraphNode;
+ if (node == null) continue;
+
+ nodePositions[node.Name] = node.PositionOffset;
+ }
+ }
+
+ private void ClearGraph()
+ {
+ foreach (Dictionary connection in researchGraph.GetConnectionList())
+ {
+ researchGraph.DisconnectNode(
+ connection["from_node"].AsStringName(),
+ (int)connection["from_port"],
+ connection["to_node"].AsStringName(),
+ (int)connection["to_port"]
+ );
+ }
+
+ foreach (Node child in researchGraph.GetChildren())
+ {
+ if (child is not GraphNode) continue;
+
+ researchGraph.RemoveChild(child);
+ child.QueueFree();
+ }
+ }
+
+ private void CreateResearchNodes()
+ {
+ foreach (Research research in GameData.availableResearch.Values)
+ {
+ GraphNode node = CreateResearchNode(
+ research.data.Id,
+ research.data.Texture,
+ research.state
+ );
+
+ researchGraph.AddChild(node);
+ }
+ }
+
+ private void CreateResearchConnections()
+ {
+ foreach (Research research in GameData.availableResearch.Values)
+ {
+ string prerequisite = research.data.Research;
+ string current = research.data.Id;
+
+ if (string.IsNullOrEmpty(prerequisite)) continue;
+
+ if (!researchGraph.HasNode(prerequisite) || !researchGraph.HasNode(current)) continue;
+
+ researchGraph.ConnectNode(
+ prerequisite,
+ 0,
+ current,
+ 0
+ );
+ }
+ }
+
+ private GraphNode CreateResearchNode(string id, string texturePath, ResearchState state)
+ {
+ Research research = GameData.availableResearch[id];
+ Texture2D texture = GD.Load(texturePath);
+ Color stateColor = GetColorByState(state);
+ string tooltipText = GetResearchTooltip(research);
+
+ TextureRect icon = new TextureRect
+ {
+ Texture = texture,
+ StretchMode = TextureRect.StretchModeEnum.KeepAspectCentered,
+ CustomMinimumSize = new Vector2(64, 64),
+ SelfModulate = stateColor
+ };
+
+ Button button = new Button
+ {
+ Text = GetResearchButtonText(research, state),
+ Disabled = state != ResearchState.AVAILABLE || !research.CanStart(),
+ TooltipText = tooltipText
+ };
+
+ button.Pressed += () => OnResearchPressed(id);
+
+ GraphNode node = new GraphNode
+ {
+ Name = id,
+ Title = Research.GetReadableName(id),
+ SelfModulate = stateColor,
+ TooltipText = tooltipText
+ };
+
+ if (nodePositions.ContainsKey(id))
+ {
+ node.PositionOffset = nodePositions[id];
+ }
+
+ node.SetSlot(
+ 0,
+ true,
+ 0,
+ Colors.White,
+ true,
+ 0,
+ Colors.White
+ );
+
+ node.AddChild(icon);
+ node.AddChild(button);
+
+ return node;
+ }
+
+ private void OnResearchPressed(string id)
+ {
+ Research research = GameData.availableResearch[id];
+ if (!research.CanStart()) return;
+ if (currentResearch.Contains(research)) return;
+
+ research.state = ResearchState.RESEARCHING;
+ currentResearch.Add(research);
+ RecalculateResearchStates();
+ SetupGraph();
+ }
+
+ private string GetResearchButtonText(Research research, ResearchState state)
+ {
+ if (state == ResearchState.RESEARCHED) return "Done";
+ if (state == ResearchState.RESEARCHING) return "Researching";
+ if (state == ResearchState.LOCKED) return "Locked";
+ if (!research.CanStart()) return "Missing items";
+
+ return "Research";
+ }
+
+ private string GetResearchTooltip(Research research)
+ {
+ StringBuilder tooltip = new StringBuilder(Research.GetReadableName(research.data.Id));
+ tooltip.AppendLine();
+ tooltip.AppendLine();
+ tooltip.AppendLine("Costs:");
+
+ if (research.data.Inputs.Count <= 0)
+ {
+ tooltip.AppendLine("- None");
+ }
+
+ foreach (Ingredient ingredient in research.data.Inputs)
+ {
+ tooltip.Append("- ");
+ tooltip.Append(ItemData.GetReadableName(ingredient.Item));
+ tooltip.Append(": ");
+ tooltip.Append(GameData.inventory.GetItemAmount(ingredient.Item));
+ tooltip.Append("/");
+ tooltip.AppendLine(ingredient.Amount.ToString());
+ }
+
+ tooltip.AppendLine();
+ tooltip.Append("Time: ");
+ tooltip.AppendLine(research.data.CraftTime.ToString("0"));
+
+ if (research.data.Effects == null || research.data.Effects.Count <= 0)
+ {
+ return tooltip.ToString();
+ }
+
+ tooltip.AppendLine();
+ tooltip.AppendLine("Effects:");
+
+ foreach (ResearchEffect effect in research.data.Effects)
+ {
+ tooltip.Append("- ");
+ tooltip.AppendLine(effect.GetDisplayText());
+ }
+
+ return tooltip.ToString();
+ }
+
+ private void RecalculateResearchStates()
+ {
+ bool changedState = true;
+
+ while (changedState)
+ {
+ changedState = false;
+
+ foreach (Research research in GameData.availableResearch.Values)
+ {
+ ResearchState newState = GetUpdatedResearchState(research);
+
+ if (research.state == newState) continue;
+
+ research.state = newState;
+ changedState = true;
+ }
+ }
+ }
+
+ private ResearchState GetUpdatedResearchState(Research research)
+ {
+ if (research.state == ResearchState.RESEARCHED)
+ {
+ return ResearchState.RESEARCHED;
+ }
+
+ if (research.data.Research.Length <= 0)
+ {
+ return ResearchState.RESEARCHED;
+ }
+
+ if (research.state == ResearchState.RESEARCHING)
+ {
+ return ResearchState.RESEARCHING;
+ }
+
+ if (GameData.availableResearch[research.data.Research].state == ResearchState.RESEARCHED)
+ {
+ return ResearchState.AVAILABLE;
+ }
+
+ return ResearchState.LOCKED;
+ }
+
+ private Color GetColorByState(ResearchState state)
+ {
+ return state switch
+ {
+ ResearchState.AVAILABLE => Colors.White,
+ ResearchState.LOCKED => new Color(0.6f, 0.6f, 0.6f, 0.5f),
+ ResearchState.RESEARCHED => new Color(0.7f, 1f, 0.7f, 1f),
+ ResearchState.RESEARCHING => new Color(0.3f, 1f, 0.3f, 1f),
+ _ => Colors.Red
+ };
+ }
+}
diff --git a/Scripts/UI/Research/ResearchList.cs.uid b/Scripts/UI/Research/ResearchList.cs.uid
new file mode 100644
index 0000000..a7c8d99
--- /dev/null
+++ b/Scripts/UI/Research/ResearchList.cs.uid
@@ -0,0 +1 @@
+uid://drscsrkfphpy7
diff --git a/Scripts/UI/Robots/RobotDisplay.cs b/Scripts/UI/Robots/RobotDisplay.cs
new file mode 100644
index 0000000..af36980
--- /dev/null
+++ b/Scripts/UI/Robots/RobotDisplay.cs
@@ -0,0 +1,37 @@
+using Godot;
+
+public partial class RobotDisplay : PanelContainer
+{
+ [Export] public RichTextLabel listItem;
+ [Export] public RichTextLabel currentScript;
+ [Signal]
+ public delegate void OnRobotJumpToEventHandler(Robot robot);
+ [Signal]
+ public delegate void OnRobotFollowEventHandler(Robot robot);
+ public Robot robot;
+
+ public override void _Process(double delta)
+ {
+ string status = GetStatusText();
+ if (status != currentScript.Text)
+ {
+ currentScript.Text = status;
+ }
+ }
+
+ private string GetStatusText()
+ {
+ string programName = robot.currentProgram ?? "";
+ return $"{programName} | Heat {robot.heat:0}% | Maintenance {robot.maintenance:0}%";
+ }
+
+ public void OnJumpToClicked()
+ {
+ EmitSignal(SignalName.OnRobotJumpTo, robot);
+ }
+
+ public void OnFollowToClicked()
+ {
+ EmitSignal(SignalName.OnRobotFollow, robot);
+ }
+}
diff --git a/Scripts/UI/Robots/RobotDisplay.cs.uid b/Scripts/UI/Robots/RobotDisplay.cs.uid
new file mode 100644
index 0000000..d1a5e3c
--- /dev/null
+++ b/Scripts/UI/Robots/RobotDisplay.cs.uid
@@ -0,0 +1 @@
+uid://dcxom1paffp0p
diff --git a/Scripts/UI/Robots/RobotList.cs b/Scripts/UI/Robots/RobotList.cs
new file mode 100644
index 0000000..e087110
--- /dev/null
+++ b/Scripts/UI/Robots/RobotList.cs
@@ -0,0 +1,118 @@
+using Godot;
+
+public partial class RobotList : PanelContainer
+{
+ [Export] VBoxContainer robotList;
+ [Export] OptionButton selectableRobots;
+ [Export] Button spawnRobot;
+ [Signal]
+ public delegate void OnRobotJumpToEventHandler(Robot robot);
+ [Signal]
+ public delegate void OnRobotFollowEventHandler(Robot robot);
+ private PackedScene robotDisplayPrefab;
+ private string spawnId = "";
+
+ public override void _Ready()
+ {
+ robotDisplayPrefab = ResourceLoader.LoadRobotDisplay();
+ }
+
+ public override void _Notification(int id)
+ {
+ if (id == NotificationVisibilityChanged)
+ {
+ if (Visible)
+ {
+ ReloadRobots();
+ ReloadSelectableRobots();
+ }
+ }
+ }
+
+ public void ReloadRobots()
+ {
+ ClearRobotList();
+
+ foreach (Robot robotObject in GameData.robots)
+ {
+ robotList.AddChild(CreateRobotDisplay(robotObject));
+ }
+ }
+
+ private void ClearRobotList()
+ {
+ foreach (Node node in robotList.GetChildren())
+ {
+ robotList.RemoveChild(node);
+ node.QueueFree();
+ }
+ }
+
+ private RobotDisplay CreateRobotDisplay(Robot robotObject)
+ {
+ RobotDisplay display = robotDisplayPrefab.Instantiate();
+ display.robot = robotObject;
+ display.listItem.Text = robotObject.Name;
+ display.OnRobotJumpTo += HandleRobotJumpTo;
+ display.OnRobotFollow += HandleRobotFollow;
+ return display;
+ }
+
+ private void HandleRobotJumpTo(Robot robot)
+ {
+ EmitSignal(SignalName.OnRobotJumpTo, robot);
+ Visible = false;
+ }
+
+ private void HandleRobotFollow(Robot robot)
+ {
+ EmitSignal(SignalName.OnRobotFollow, robot);
+ Visible = false;
+ }
+
+ public void ReloadSelectableRobots()
+ {
+ selectableRobots.Clear();
+ if (GameData.robots.Count >= GameData.maxRobotCount)
+ {
+ selectableRobots.AddItem("You can't have more robots currently!");
+ selectableRobots.Disabled = true;
+ spawnRobot.Disabled = true;
+ return;
+ }
+ selectableRobots.Disabled = false;
+ spawnRobot.Disabled = false;
+ selectableRobots.AddItem("Select robot...");
+ foreach (Item item in GameData.inventory.items)
+ {
+ if (GameData.robotStats.RobotTypes.ContainsKey(item.data.Id))
+ {
+ selectableRobots.AddItem(item.data.GetReadableName());
+ }
+ }
+ }
+
+ public void SpawnRobot()
+ {
+ if (spawnId.Length <= 0) return;
+
+ GameData.inventory.RemoveItem(spawnId, 1);
+ Robot robot = ResourceLoader.LoadRobotPrefab().Instantiate