From 661386254b3bdced7db41e61518e2847f20afa70 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 20:28:26 +0100 Subject: [PATCH 01/46] Make `HyperspaceSceneSwitcher` saveable --- screens/game/game.tscn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/screens/game/game.tscn b/screens/game/game.tscn index 1b98cd42..0ff38209 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -160,7 +160,7 @@ texture_margin_bottom = 10.0 [node name="Game" type="Node3D"] -[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." node_paths=PackedStringArray("hyperdrive_system")] +[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." node_paths=PackedStringArray("hyperdrive_system") groups=["saveable"]] script = ExtResource("4_yyrkm") hyperdrive_system = NodePath("Sol/PlayerCorvette/HyperdriveSystem") calendar = SubResource("Resource_jc1pg") From 3eee46c32b3c446cfc638b8439ac057e72ba51e0 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 20:28:40 +0100 Subject: [PATCH 02/46] Introduce star_system_instance reusable scene --- galaxy/star_system/star_system_instance.tscn | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 galaxy/star_system/star_system_instance.tscn diff --git a/galaxy/star_system/star_system_instance.tscn b/galaxy/star_system/star_system_instance.tscn new file mode 100644 index 00000000..5e2eb18e --- /dev/null +++ b/galaxy/star_system/star_system_instance.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=3 uid="uid://fxemun7o6rix"] + +[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_isso2"] + +[node name="StarSystemInstance" type="Node3D" groups=["saveable"]] +script = ExtResource("1_isso2") From a00361e791332671bf4d6789782a4fc4dcb20ddf Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 20:35:10 +0100 Subject: [PATCH 03/46] Manually hack scene files to inherit from star_system_instance --- galaxy/star_system/scenes/alpha_centauri.tscn | 11 +++++------ galaxy/star_system/scenes/barnard's_star.tscn | 11 +++++------ galaxy/star_system/scenes/nova_lumina.tscn | 15 +++++++-------- galaxy/star_system/scenes/sirius.tscn | 11 +++++------ galaxy/star_system/scenes/sol.tscn | 19 +++++++++---------- galaxy/star_system/scenes/thalassa.tscn | 15 +++++++-------- galaxy/star_system/scenes/wolf_359.tscn | 11 +++++------ galaxy/star_system/scenes/zephyria.tscn | 9 ++++----- 8 files changed, 47 insertions(+), 55 deletions(-) diff --git a/galaxy/star_system/scenes/alpha_centauri.tscn b/galaxy/star_system/scenes/alpha_centauri.tscn index 4d4da494..9f7b15ef 100644 --- a/galaxy/star_system/scenes/alpha_centauri.tscn +++ b/galaxy/star_system/scenes/alpha_centauri.tscn @@ -1,20 +1,19 @@ [gd_scene load_steps=6 format=3 uid="uid://cyoasaafloxke"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_hu4s2"] [ext_resource type="PackedScene" uid="uid://cji43wyk7116p" path="res://stars/base_star.tscn" id="1_hyeo5"] [ext_resource type="Resource" uid="uid://cs1x8gyt6a7kw" path="res://galaxy/star_system/star_systems/alpha_centauri.tres" id="2_ex383"] [ext_resource type="PackedScene" uid="uid://bqhgqgsbi7ofa" path="res://stars/main_sequence/star_class_k.tscn" id="4_daplr"] [ext_resource type="PackedScene" uid="uid://d27pdcik2lwf1" path="res://stars/main_sequence/star_class_m.tscn" id="5_gjyye"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[node name="Alpha Centauri" type="Node3D"] -script = ExtResource("1_hu4s2") +[node name="Alpha Centauri" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_ex383") -[node name="Rigil Kentaurus" parent="." instance=ExtResource("1_hyeo5")] +[node name="Rigil Kentaurus" parent="." index="0" instance=ExtResource("1_hyeo5")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -10.5019, 2.08165e-12, -4.44388) -[node name="Toliman" parent="." instance=ExtResource("4_daplr")] +[node name="Toliman" parent="." index="1" instance=ExtResource("4_daplr")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 6.83202, 2.08165e-12, 3.46602) -[node name="Proxima Centauri" parent="." instance=ExtResource("5_gjyye")] +[node name="Proxima Centauri" parent="." index="2" instance=ExtResource("5_gjyye")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 18.8751, 2.08165e-12, -15.3346) diff --git a/galaxy/star_system/scenes/barnard's_star.tscn b/galaxy/star_system/scenes/barnard's_star.tscn index 0ff284de..37cc32cf 100644 --- a/galaxy/star_system/scenes/barnard's_star.tscn +++ b/galaxy/star_system/scenes/barnard's_star.tscn @@ -1,19 +1,18 @@ [gd_scene load_steps=6 format=3 uid="uid://cghdtnx2qen2u"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_pqej6"] [ext_resource type="Resource" uid="uid://shiglva7yxl0" path="res://galaxy/star_system/star_systems/barnard's_star.tres" id="2_y4tl8"] [ext_resource type="PackedScene" uid="uid://d27pdcik2lwf1" path="res://stars/main_sequence/star_class_m.tscn" id="3_cqasn"] [ext_resource type="PackedScene" uid="uid://ccdkamqw03rk7" path="res://fx/asteroids/multi_asteroid_field.tscn" id="5_x1i1i"] [ext_resource type="PackedScene" uid="uid://culoat6jnbwc8" path="res://actors/ai/pirate_frigate.tscn" id="9_xdjbl"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[node name="Barnard\'s Star" type="Node3D"] -script = ExtResource("1_pqej6") +[node name="Barnard\'s Star" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_y4tl8") -[node name="Star Class M" parent="." instance=ExtResource("3_cqasn")] +[node name="Star Class M" parent="." index="0" instance=ExtResource("3_cqasn")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 2.65429, 2.08165e-12, 11.1402) -[node name="PirateFrigate" parent="." instance=ExtResource("9_xdjbl")] +[node name="PirateFrigate" parent="." index="1" instance=ExtResource("9_xdjbl")] transform = Transform3D(0.866897, -1.28496e-16, 0.498488, 3.48787e-16, 1, -3.48787e-16, -0.498488, 4.76228e-16, 0.866897, -8.39, 2.08165e-12, 1.15) -[node name="AsteroidField" parent="." instance=ExtResource("5_x1i1i")] +[node name="AsteroidField" parent="." index="2" instance=ExtResource("5_x1i1i")] diff --git a/galaxy/star_system/scenes/nova_lumina.tscn b/galaxy/star_system/scenes/nova_lumina.tscn index 9860bb68..ea8f9b13 100644 --- a/galaxy/star_system/scenes/nova_lumina.tscn +++ b/galaxy/star_system/scenes/nova_lumina.tscn @@ -1,6 +1,5 @@ [gd_scene load_steps=12 format=3 uid="uid://di4weo7brixhh"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_fii8b"] [ext_resource type="Resource" uid="uid://ku5qjeo4jlkt" path="res://galaxy/star_system/star_systems/nova_lumina.tres" id="2_etutf"] [ext_resource type="PackedScene" uid="uid://d1sevq56wdchx" path="res://stars/main_sequence/star_class_b.tscn" id="3_kweak"] [ext_resource type="PackedScene" uid="uid://b04hfgkcuq7k6" path="res://planet/planet_instance.tscn" id="3_qge20"] @@ -11,34 +10,34 @@ [ext_resource type="Texture2D" uid="uid://ceccjdfupojph" path="res://planet/sprites/planet_30.png" id="8_7sr4w"] [ext_resource type="Texture2D" uid="uid://evirpay5c0bq" path="res://planet/sprites/planet_48.png" id="9_w331x"] [ext_resource type="Texture2D" uid="uid://b2qu75vy8ncsx" path="res://planet/sprites/planet_07.png" id="11_o76pq"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[node name="Nova Lumina" type="Node3D"] -script = ExtResource("1_fii8b") +[node name="Nova Lumina" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_etutf") -[node name="Star Class B" parent="." instance=ExtResource("3_kweak")] +[node name="Star Class B" parent="." index="0" instance=ExtResource("3_kweak")] -[node name="Auroris" parent="." instance=ExtResource("3_qge20")] +[node name="Auroris" parent="." index="1" instance=ExtResource("3_qge20")] transform = Transform3D(2, 0, 0, 0, 1, 0, 0, 0, 2, -1.1093, 0, -11.1902) planet = ExtResource("5_kad6o") [node name="Sprite3D" parent="Auroris" index="1"] texture = ExtResource("8_7sr4w") -[node name="Lumina Prime" parent="." instance=ExtResource("3_qge20")] +[node name="Lumina Prime" parent="." index="2" instance=ExtResource("3_qge20")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -16.7791, 0, -2.37476) planet = ExtResource("4_mwi45") [node name="Sprite3D" parent="Lumina Prime" index="1"] texture = ExtResource("6_cyr6t") -[node name="Stellara" parent="." instance=ExtResource("3_qge20")] +[node name="Stellara" parent="." index="3" instance=ExtResource("3_qge20")] transform = Transform3D(2, 0, 0, 0, 1, 0, 0, 0, 2, -12.3399, 0, 15.6569) [node name="Sprite3D" parent="Stellara" index="1"] texture = ExtResource("9_w331x") -[node name="Novus" parent="." instance=ExtResource("3_qge20")] +[node name="Novus" parent="." index="4" instance=ExtResource("3_qge20")] transform = Transform3D(0.75, 0, 0, 0, 1, 0, 0, 0, 0.75, 13.69, 0, 19.5674) planet = ExtResource("6_anm5q") diff --git a/galaxy/star_system/scenes/sirius.tscn b/galaxy/star_system/scenes/sirius.tscn index 164d52de..4453531e 100644 --- a/galaxy/star_system/scenes/sirius.tscn +++ b/galaxy/star_system/scenes/sirius.tscn @@ -1,19 +1,18 @@ [gd_scene load_steps=6 format=3 uid="uid://dnq7vmwax8u7v"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_ibjbp"] [ext_resource type="Resource" uid="uid://clcig3aieyop5" path="res://galaxy/star_system/star_systems/sirius.tres" id="2_dc37m"] [ext_resource type="PackedScene" uid="uid://dkq1xlrnnxrtr" path="res://stars/main_sequence/star_class_a.tscn" id="3_30bsr"] [ext_resource type="PackedScene" uid="uid://clmjmlce3ufwg" path="res://stars/dwarfs/star_white_dwarf.tscn" id="4_0khyd"] [ext_resource type="PackedScene" uid="uid://ccdkamqw03rk7" path="res://fx/asteroids/multi_asteroid_field.tscn" id="5_aah50"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[node name="Sirius" type="Node3D"] -script = ExtResource("1_ibjbp") +[node name="Sirius" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_dc37m") -[node name="Sirius A" parent="." instance=ExtResource("3_30bsr")] +[node name="Sirius A" parent="." index="0" instance=ExtResource("3_30bsr")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -11.864, 0, -0.751286) -[node name="Sirius B" parent="." instance=ExtResource("4_0khyd")] +[node name="Sirius B" parent="." index="1" instance=ExtResource("4_0khyd")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.76327, 2.08165e-12, 3.25557) -[node name="AsteroidField" parent="." instance=ExtResource("5_aah50")] +[node name="AsteroidField" parent="." index="2" instance=ExtResource("5_aah50")] diff --git a/galaxy/star_system/scenes/sol.tscn b/galaxy/star_system/scenes/sol.tscn index 623bacaa..e9cc864d 100644 --- a/galaxy/star_system/scenes/sol.tscn +++ b/galaxy/star_system/scenes/sol.tscn @@ -1,6 +1,5 @@ [gd_scene load_steps=18 format=3 uid="uid://2mdsbbko7baw"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_6tnek"] [ext_resource type="PackedScene" uid="uid://cji43wyk7116p" path="res://stars/base_star.tscn" id="1_j8iws"] [ext_resource type="Resource" uid="uid://cew4x137v08q" path="res://galaxy/star_system/star_systems/sol.tres" id="2_gs8wx"] [ext_resource type="PackedScene" uid="uid://b04hfgkcuq7k6" path="res://planet/planet_instance.tscn" id="3_7gpxn"] @@ -14,6 +13,7 @@ [ext_resource type="Script" path="res://mechanics/combat/hull.gd" id="8_ibfoc"] [ext_resource type="Script" path="res://mechanics/power/battery.gd" id="9_w4i7k"] [ext_resource type="Script" path="res://actors/ai/ai_navigation.gd" id="10_2xj23"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] [sub_resource type="Resource" id="Resource_oen30"] resource_local_to_scene = true @@ -36,38 +36,37 @@ script = ExtResource("9_w4i7k") max_power = 300.0 power = 300.0 -[node name="Sol" type="Node3D"] -script = ExtResource("1_6tnek") +[node name="Sol" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_gs8wx") -[node name="Star" parent="." instance=ExtResource("1_j8iws")] +[node name="Star" parent="." index="0" instance=ExtResource("1_j8iws")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5, 2.08165e-12, -3) -[node name="Mercury" parent="." instance=ExtResource("3_7gpxn")] +[node name="Mercury" parent="." index="1" instance=ExtResource("3_7gpxn")] transform = Transform3D(0.71043, 0, 0, 0, 1, 0, 0, 0, 0.71043, 10.4139, 0, -4.93606) [node name="Sprite3D" parent="Mercury" index="1"] texture = ExtResource("5_hnp7v") -[node name="Venus" parent="." instance=ExtResource("3_7gpxn")] +[node name="Venus" parent="." index="2" instance=ExtResource("3_7gpxn")] transform = Transform3D(0.71043, 0, 0, 0, 1, 0, 0, 0, 0.71043, 12.5408, 0, 0.72235) [node name="Sprite3D" parent="Venus" index="1"] transform = Transform3D(-0.993252, 0, -0.115977, 0, 1, 0, 0.115977, 0, -0.993252, 0, -1, 0) texture = ExtResource("5_k6fyb") -[node name="Earth" parent="." instance=ExtResource("3_7gpxn")] +[node name="Earth" parent="." index="3" instance=ExtResource("3_7gpxn")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 7.38638, 0, 6.14048) planet = ExtResource("3_qvwl4") -[node name="Mars" parent="." instance=ExtResource("3_7gpxn")] +[node name="Mars" parent="." index="4" instance=ExtResource("3_7gpxn")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 11.0726, 0, 12.0475) planet = ExtResource("4_b0o1w") [node name="Sprite3D" parent="Mars" index="1"] texture = ExtResource("4_afp06") -[node name="Freighter" parent="." instance=ExtResource("6_uk08c")] +[node name="Freighter" parent="." index="5" instance=ExtResource("6_uk08c")] transform = Transform3D(0.759975, 0, -0.649952, 0, 1, 0, 0.649952, 0, 0.759975, -5.22228, 0, -4.59493) physics_material_override = null @@ -88,7 +87,7 @@ battery = SubResource("Resource_eyrkq") battery = SubResource("Resource_eyrkq") shield = SubResource("Resource_oen30") -[node name="AINavigation" type="Node3D" parent="Freighter" node_paths=PackedStringArray("rigid_body_thruster", "rigid_body_direction")] +[node name="AINavigation" type="Node3D" parent="Freighter" index="8" node_paths=PackedStringArray("rigid_body_thruster", "rigid_body_direction")] script = ExtResource("10_2xj23") rigid_body_thruster = NodePath("../RigidBodyThruster") rigid_body_direction = NodePath("../RigidBodyDirection") diff --git a/galaxy/star_system/scenes/thalassa.tscn b/galaxy/star_system/scenes/thalassa.tscn index 64280762..f0c4e24f 100644 --- a/galaxy/star_system/scenes/thalassa.tscn +++ b/galaxy/star_system/scenes/thalassa.tscn @@ -1,6 +1,5 @@ [gd_scene load_steps=11 format=3 uid="uid://b7ego5luosh2x"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_rmrq5"] [ext_resource type="Resource" uid="uid://cu1wn7ldbgeej" path="res://galaxy/star_system/star_systems/thalassa.tres" id="2_5t60p"] [ext_resource type="PackedScene" uid="uid://bqhgqgsbi7ofa" path="res://stars/main_sequence/star_class_k.tscn" id="3_i27l2"] [ext_resource type="PackedScene" uid="uid://d27pdcik2lwf1" path="res://stars/main_sequence/star_class_m.tscn" id="4_flex8"] @@ -10,18 +9,18 @@ [ext_resource type="Texture2D" uid="uid://03ncc8x5o5p" path="res://planet/sprites/planet_06.png" id="7_ih7yq"] [ext_resource type="Texture2D" uid="uid://c0155fkdfnehk" path="res://planet/sprites/planet_47.png" id="8_o1nyb"] [ext_resource type="Texture2D" uid="uid://7ou2232hojfu" path="res://planet/sprites/planet_08.png" id="10_7gbi5"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[node name="Thalassa" type="Node3D"] -script = ExtResource("1_rmrq5") +[node name="Thalassa" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_5t60p") -[node name="Star Class K" parent="." instance=ExtResource("3_i27l2")] +[node name="Star Class K" parent="." index="0" instance=ExtResource("3_i27l2")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -2.34809, 2.08165e-12, 1.56886) -[node name="Star Class M" parent="." instance=ExtResource("4_flex8")] +[node name="Star Class M" parent="." index="1" instance=ExtResource("4_flex8")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 5.4962, 2.08165e-12, 3.09616) -[node name="Gaia Prime" parent="." instance=ExtResource("5_p806y")] +[node name="Gaia Prime" parent="." index="2" instance=ExtResource("5_p806y")] transform = Transform3D(1.5, 0, 0, 0, 1, 0, 0, 0, 1.5, -2.33977, 0, -9.75337) planet = ExtResource("6_a1qqt") @@ -29,7 +28,7 @@ planet = ExtResource("6_a1qqt") transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -1, 0) texture = ExtResource("7_ih7yq") -[node name="Proteus" parent="." instance=ExtResource("5_p806y")] +[node name="Proteus" parent="." index="3" instance=ExtResource("5_p806y")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -17.4024, 1.36424e-12, -5.03147) [node name="Sprite3D" parent="Proteus" index="1"] @@ -37,7 +36,7 @@ transform = Transform3D(-0.403865, 0, 0.914819, 0, 1, 0, -0.914819, 0, -0.403865 pixel_size = 0.002 texture = ExtResource("8_o1nyb") -[node name="Aeolus" parent="." instance=ExtResource("5_p806y")] +[node name="Aeolus" parent="." index="4" instance=ExtResource("5_p806y")] transform = Transform3D(1.2, 0, 0, 0, 1, 0, 0, 0, 1.2, -15.733, 3.8147e-06, -15.8206) planet = ExtResource("7_ffl6i") diff --git a/galaxy/star_system/scenes/wolf_359.tscn b/galaxy/star_system/scenes/wolf_359.tscn index 7bca324d..5444ffd8 100644 --- a/galaxy/star_system/scenes/wolf_359.tscn +++ b/galaxy/star_system/scenes/wolf_359.tscn @@ -1,19 +1,18 @@ [gd_scene load_steps=5 format=3 uid="uid://d3qpe4ne3bgww"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_sih83"] [ext_resource type="Resource" uid="uid://di0bekcy5g0ya" path="res://galaxy/star_system/star_systems/wolf_359.tres" id="2_byjpf"] [ext_resource type="PackedScene" uid="uid://d27pdcik2lwf1" path="res://stars/main_sequence/star_class_m.tscn" id="3_wjxxu"] [ext_resource type="PackedScene" uid="uid://culoat6jnbwc8" path="res://actors/ai/pirate_frigate.tscn" id="4_tean3"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[node name="Wolf 359" type="Node3D"] -script = ExtResource("1_sih83") +[node name="Wolf 359" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_byjpf") -[node name="Star Class M" parent="." instance=ExtResource("3_wjxxu")] +[node name="Star Class M" parent="." index="0" instance=ExtResource("3_wjxxu")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -7.65186, 2.08165e-12, 2.24275) -[node name="PirateFrigate" parent="." instance=ExtResource("4_tean3")] +[node name="PirateFrigate" parent="." index="1" instance=ExtResource("4_tean3")] transform = Transform3D(-0.287762, -2.33666e-16, -0.957702, 3.48787e-16, 1, -3.48787e-16, 0.957702, -4.34402e-16, -0.287762, -7.88217, 0, -3.99797) -[node name="PirateFrigate2" parent="." instance=ExtResource("4_tean3")] +[node name="PirateFrigate2" parent="." index="2" instance=ExtResource("4_tean3")] transform = Transform3D(-0.69814, 4.9322e-16, 0.715961, 3.48787e-16, 1, -3.48787e-16, -0.715961, 6.21573e-18, -0.69814, 5.40376, 0, 0.771966) diff --git a/galaxy/star_system/scenes/zephyria.tscn b/galaxy/star_system/scenes/zephyria.tscn index 101b78aa..af234a95 100644 --- a/galaxy/star_system/scenes/zephyria.tscn +++ b/galaxy/star_system/scenes/zephyria.tscn @@ -1,20 +1,19 @@ [gd_scene load_steps=7 format=3 uid="uid://cy4rio5cdb5xy"] -[ext_resource type="Script" path="res://galaxy/star_system/star_system_instance.gd" id="1_2vv4n"] [ext_resource type="Resource" uid="uid://bowucldo27rjd" path="res://galaxy/star_system/star_systems/zephyria.tres" id="2_q8gm3"] [ext_resource type="PackedScene" uid="uid://cfvdpnqnalspg" path="res://stars/giants/star_red_giant.tscn" id="3_7is3b"] [ext_resource type="PackedScene" uid="uid://b04hfgkcuq7k6" path="res://planet/planet_instance.tscn" id="4_qafen"] [ext_resource type="Resource" uid="uid://crj6o7sd5pos2" path="res://planet/planets/aethoria.tres" id="5_ls4hq"] [ext_resource type="Texture2D" uid="uid://cf0g36mpu7enh" path="res://planet/sprites/planet_03.png" id="6_lxy5c"] +[ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[node name="Zephyria" type="Node3D"] -script = ExtResource("1_2vv4n") +[node name="Zephyria" instance=ExtResource("star_system_instance")] star_system = ExtResource("2_q8gm3") -[node name="Red Giant" parent="." instance=ExtResource("3_7is3b")] +[node name="Red Giant" parent="." index="0" instance=ExtResource("3_7is3b")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 11.9103, 2.08165e-12, -10.3797) -[node name="Aethoria" parent="." instance=ExtResource("4_qafen")] +[node name="Aethoria" parent="." index="1" instance=ExtResource("4_qafen")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -8.8458, 0, -0.921964) planet = ExtResource("5_ls4hq") From 786f92ce0c5d18c88716e0d328a9583826a8ca43 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 20:42:14 +0100 Subject: [PATCH 04/46] Making various nodes (probably the wrong ones) saveable --- screens/game/game.tscn | 6 +++--- ships/freighter/freighter.tscn | 1 + ships/ship.tscn | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/screens/game/game.tscn b/screens/game/game.tscn index 0ff38209..c0bb9df5 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -209,11 +209,11 @@ shield = SubResource("Resource_p0y82") [node name="Blaster" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="9"] battery = SubResource("Resource_jcr0a") -[node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] +[node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" groups=["saveable"]] script = ExtResource("16_6olqp") hyperdrive = SubResource("Resource_xnk78") -[node name="Player" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" node_paths=PackedStringArray("hyperspace_scene_switcher", "message_log") instance=ExtResource("16_lqltb")] +[node name="Player" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" node_paths=PackedStringArray("hyperspace_scene_switcher", "message_log") groups=["saveable"] instance=ExtResource("16_lqltb")] hyperspace_scene_switcher = NodePath("../../..") message_log = NodePath("../../../../InGameGUI/MarginContainer/HBoxContainer/MessageLog") bank_account = SubResource("Resource_j1y8s") @@ -234,7 +234,7 @@ pitch_scale = 2.0 [node name="AudioListener3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player" index="4"] current = false -[node name="RigidBodyCargo" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] +[node name="RigidBodyCargo" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" groups=["saveable"]] script = ExtResource("10_w7kut") cargo_hold = SubResource("Resource_bsv1l") diff --git a/ships/freighter/freighter.tscn b/ships/freighter/freighter.tscn index ffbb9cc4..9d2bbb2c 100644 --- a/ships/freighter/freighter.tscn +++ b/ships/freighter/freighter.tscn @@ -28,6 +28,7 @@ max_integrity = 150.0 integrity = 150.0 recharge_rate = 5.0 power_efficiency = 1.0 +only_recharge_above = 0.2 [sub_resource type="Resource" id="Resource_t05wf"] resource_local_to_scene = true diff --git a/ships/ship.tscn b/ships/ship.tscn index 94720000..d758e5c0 100644 --- a/ships/ship.tscn +++ b/ships/ship.tscn @@ -12,7 +12,7 @@ [sub_resource type="PhysicsMaterial" id="PhysicsMaterial_gsr85"] -[node name="Ship" type="RigidBody3D" node_paths=PackedStringArray("combat_object", "targeting_system", "rigid_body_thruster", "rigid_body_direction", "power_management_unit", "radar_object") groups=["ships"]] +[node name="Ship" type="RigidBody3D" node_paths=PackedStringArray("combat_object", "targeting_system", "rigid_body_thruster", "rigid_body_direction", "power_management_unit", "radar_object") groups=["saveable", "ships"]] collision_mask = 2 axis_lock_linear_y = true axis_lock_angular_x = true From 6e2d47b0a79ac544c5139a8368da47b9785f13c1 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 20:45:47 +0100 Subject: [PATCH 05/46] Introduce `SaveableResource` --- utils/saveable_resource.gd | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 utils/saveable_resource.gd diff --git a/utils/saveable_resource.gd b/utils/saveable_resource.gd new file mode 100644 index 00000000..60fd2c46 --- /dev/null +++ b/utils/saveable_resource.gd @@ -0,0 +1,12 @@ +extends Resource +class_name SaveableResource + +## The base class for any resource that should have its current state saved into any save game files, instead of being recreated in its initial state when a game is loaded. +## +## Resources that do not inherit from SaveableResource will not be saved to disk. + +func save_to_dict() -> Dictionary: + return {} + +func load_from_dict(_dict: Dictionary) -> void: + pass From 26d2372933b7b94b2fefc3be8ad2f262b44d44e8 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 21:10:29 +0100 Subject: [PATCH 06/46] Default saving/loading behaviors for `SaveableResource` --- utils/saveable_resource.gd | 64 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/utils/saveable_resource.gd b/utils/saveable_resource.gd index 60fd2c46..2e18bdb3 100644 --- a/utils/saveable_resource.gd +++ b/utils/saveable_resource.gd @@ -5,8 +5,66 @@ class_name SaveableResource ## ## Resources that do not inherit from SaveableResource will not be saved to disk. +## Saves this resource to a JSON-compatible dictionary. +## +## The default implementation should be suitable to serialize primitives and other [SaveableResource]s, but can be overridden to serialize properties of other types. Only properties tagged as [constant PROPERTY_USAGE_STORAGE] will be saved. func save_to_dict() -> Dictionary: - return {} + var save_dict := {} + for property: Dictionary in self.get_property_list(): + var property_name: String = property.name + var usage_flags: PropertyUsageFlags = property.usage + if usage_flags & PROPERTY_USAGE_STORAGE == 0: + continue + + var type: Variant.Type = property.type + match type: + TYPE_RID, TYPE_CALLABLE, TYPE_SIGNAL: + push_warning("save_to_dict: Cannot save property ", property_name, " of type ", type) + + TYPE_OBJECT: + var classname: StringName = property["class_name"] + if ClassDB.is_parent_class(classname, &"SaveableResource"): + var nested_resource: SaveableResource = self.get(property_name) + save_dict[property_name] = nested_resource.save_to_dict() + else: + push_warning("save_to_dict: Cannot save property ", property_name, " of class ", classname) + + _: + save_dict[property_name] = self.get(property_name) + + return save_dict + +## Loads this resource's values from a JSON dictionary. +## +## The default implementation should be suitable to load primitives and other [SaveableResource]s, but can be overridden to load properties of other types. Only properties tagged as [constant PROPERTY_USAGE_STORAGE] will be loaded. +func load_from_dict(dict: Dictionary) -> void: + for property: Dictionary in self.get_property_list(): + var property_name: String = property.name + if not dict.has(property_name): + push_warning("load_from_dict: Missing property ", property_name) + continue + + var usage_flags: PropertyUsageFlags = property.usage + if usage_flags & PROPERTY_USAGE_STORAGE == 0: + push_warning("load_from_dict: Ignoring property ", property_name, " not marked for storage") + continue + + var value: Variant = dict[property_name] + var type: Variant.Type = property.type + match type: + TYPE_OBJECT: + var value_dict := value as Dictionary + if not value_dict: + push_warning("load_from_dict: Object property ", property_name, " was not serialized as a dictionary") + continue + + var classname: StringName = property["class_name"] + if ClassDB.is_parent_class(classname, &"SaveableResource"): + var nested_resource: SaveableResource = ClassDB.instantiate(classname) + nested_resource.load_from_dict(value_dict) + self.set(property_name, nested_resource) + else: + push_warning("load_from_dict: Cannot load property ", property_name, " of class ", classname) -func load_from_dict(_dict: Dictionary) -> void: - pass + _: + self.set(property_name, value) From 8e533dc5dcc61305527d04b6de7982bdd176f190 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 21:12:15 +0100 Subject: [PATCH 07/46] Mark some mechanics resources saveable --- mechanics/combat/hull.gd | 2 +- mechanics/combat/shield.gd | 2 +- mechanics/economy/bank_account.gd | 6 +++--- mechanics/economy/cargo_hold.gd | 2 +- mechanics/hyperspace/hyperdrive.gd | 2 +- mechanics/power/battery.gd | 2 +- mechanics/time/calendar.gd | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mechanics/combat/hull.gd b/mechanics/combat/hull.gd index 76e1437c..40a6fc0d 100644 --- a/mechanics/combat/hull.gd +++ b/mechanics/combat/hull.gd @@ -1,4 +1,4 @@ -extends Resource +extends SaveableResource class_name Hull ## Represents a hull that can take damage. diff --git a/mechanics/combat/shield.gd b/mechanics/combat/shield.gd index d5dd12ba..d80ed654 100644 --- a/mechanics/combat/shield.gd +++ b/mechanics/combat/shield.gd @@ -1,4 +1,4 @@ -extends Resource +extends SaveableResource class_name Shield ## Represents shields that can take damage. diff --git a/mechanics/economy/bank_account.gd b/mechanics/economy/bank_account.gd index b67d6bf9..7de8bd33 100644 --- a/mechanics/economy/bank_account.gd +++ b/mechanics/economy/bank_account.gd @@ -1,4 +1,4 @@ -extends Resource +extends SaveableResource class_name BankAccount ## Represents a bank account or digital wallet of currencies. @@ -31,9 +31,9 @@ func withdraw_up_to(currency: Currency, amount: float) -> float: ## Withdraws exactly [param amount] of [param currency] from the account. ## ## If [param allow_negative] is set, the account can go into negative balance; otherwise, an attempt to draw down more than the balance will fail and return false. -func withdraw_exactly(currency: Currency, amount: float, allow_negative: bool=false) -> bool: +func withdraw_exactly(currency: Currency, amount: float, allow_negative: bool = false) -> bool: var available_amount: float = self.currencies.get(currency, 0.0) - if available_amount - amount <= - Currency.EPSILON and not allow_negative: + if available_amount - amount <= -Currency.EPSILON and not allow_negative: return false if is_equal_approx(available_amount, amount): diff --git a/mechanics/economy/cargo_hold.gd b/mechanics/economy/cargo_hold.gd index dad7e6f6..bb6e1fb9 100644 --- a/mechanics/economy/cargo_hold.gd +++ b/mechanics/economy/cargo_hold.gd @@ -1,4 +1,4 @@ -extends Resource +extends SaveableResource class_name CargoHold ## Represents a cargo hold on a ship, containing commodities for trade. diff --git a/mechanics/hyperspace/hyperdrive.gd b/mechanics/hyperspace/hyperdrive.gd index 9ece8595..b803e8b2 100644 --- a/mechanics/hyperspace/hyperdrive.gd +++ b/mechanics/hyperspace/hyperdrive.gd @@ -1,4 +1,4 @@ -extends Resource +extends SaveableResource class_name Hyperdrive ## Represents a hyperspace drive and its fuel. diff --git a/mechanics/power/battery.gd b/mechanics/power/battery.gd index ef8a26c7..525d59c3 100644 --- a/mechanics/power/battery.gd +++ b/mechanics/power/battery.gd @@ -1,4 +1,4 @@ -extends Resource +extends SaveableResource class_name Battery ## Represents a battery that can hold power and be recharged, and from which power can be consumed. diff --git a/mechanics/time/calendar.gd b/mechanics/time/calendar.gd index 2f01274e..9338af39 100644 --- a/mechanics/time/calendar.gd +++ b/mechanics/time/calendar.gd @@ -1,4 +1,4 @@ -extends Resource +extends SaveableResource class_name Calendar ## The in-game clock and calendar for the Merc universe. From 59868da3500790fab86a85ad38882f9f6e543d2a Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 21:54:10 +0100 Subject: [PATCH 08/46] Basic node saving/loading, where paths match --- utils/save_game.gd | 60 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 utils/save_game.gd diff --git a/utils/save_game.gd b/utils/save_game.gd new file mode 100644 index 00000000..945949db --- /dev/null +++ b/utils/save_game.gd @@ -0,0 +1,60 @@ +class_name SaveGame + +## The group name used for nodes that can be saved and loaded. +const SAVEABLE_GROUP = "saveable" + +## Saves all [i]saveable[/i] nodes in the scene tree to a file at the given path. +static func save(scene_tree: SceneTree, path: String) -> Error: + var save_dict := save_tree_to_dict(scene_tree) + var json := JSON.stringify(save_dict, "\t", false) + var file := FileAccess.open(path, FileAccess.WRITE) + if not file: + return FileAccess.get_open_error() + + file.store_string(json) + file.close() + return OK + +## Serializes all [i]saveable[/i] nodes in the scene tree to a JSON-compatible dictionary. +static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: + var saveable_nodes := scene_tree.get_nodes_in_group(SAVEABLE_GROUP) + var save_dict := {} + for node in saveable_nodes: + var node_path := node.get_path() + save_dict[node_path] = node.call("save_to_dict") + + return save_dict + +## Loads saveable nodes from a file into the scene tree, merging with existing nodes. +static func load(scene_tree: SceneTree, path: String) -> Error: + var file := FileAccess.open(path, FileAccess.READ) + if not file: + return FileAccess.get_open_error() + + var parser := JSON.new() + var json := file.get_as_text() + var error := parser.parse(json) + if error != OK: + return error + + var dict := parser.get_data() as Dictionary + if not dict: + return ERR_PARSE_ERROR + + load_tree_from_dict(scene_tree, dict) + return OK + +## Loads saveable nodes from a JSON dictionary into the scene tree, merging with existing nodes. +static func load_tree_from_dict(scene_tree: SceneTree, dict: Dictionary) -> void: + for node_path: NodePath in dict: + var existing_node := scene_tree.root.get_node_or_null(node_path) + if existing_node == null: + push_error("Missing nodes are not supported right now") + continue + + if not existing_node.is_in_group(SAVEABLE_GROUP): + push_warning("Loaded node ", node_path, " is not marked as saveable, refusing to load it") + continue + + var save_dict: Dictionary = dict[node_path] + existing_node.call("load_from_dict", save_dict) From 85bfc28a1f8a79b276771698d0c6dda5fcacb541 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 22:28:12 +0100 Subject: [PATCH 09/46] Utility to get a parent of a `NodePath` --- utils/node_utils.gd | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 utils/node_utils.gd diff --git a/utils/node_utils.gd b/utils/node_utils.gd new file mode 100644 index 00000000..83e0725f --- /dev/null +++ b/utils/node_utils.gd @@ -0,0 +1,10 @@ +class_name NodeUtils + +## Returns the [NodePath] to the parent of the given node path. +static func get_parent_path(node_path: NodePath) -> NodePath: + var array := PackedStringArray() + array.resize(node_path.get_name_count() - 1) + for i in range(array.size()): + array.set(i, node_path.get_name(i)) + + return NodePath("/"+"/".join(array)) From f61d1cc3fe0451c516ff50d361f47f1f6929d5f9 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Wed, 31 Jul 2024 22:33:01 +0100 Subject: [PATCH 10/46] Load nodes from scenes, where possible --- utils/save_game.gd | 54 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/utils/save_game.gd b/utils/save_game.gd index 945949db..88799f73 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -3,6 +3,9 @@ class_name SaveGame ## The group name used for nodes that can be saved and loaded. const SAVEABLE_GROUP = "saveable" +## A private key used to store a node's [member Node.scene_file_path] when saving. +const _SCENE_FILE_PATH_KEY = "__scene_file_path" + ## Saves all [i]saveable[/i] nodes in the scene tree to a file at the given path. static func save(scene_tree: SceneTree, path: String) -> Error: var save_dict := save_tree_to_dict(scene_tree) @@ -21,7 +24,9 @@ static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: var save_dict := {} for node in saveable_nodes: var node_path := node.get_path() - save_dict[node_path] = node.call("save_to_dict") + var node_dict := {_SCENE_FILE_PATH_KEY: node.scene_file_path} + node_dict.merge(node.call("save_to_dict") as Dictionary) + save_dict[node_path] = node_dict return save_dict @@ -46,15 +51,46 @@ static func load(scene_tree: SceneTree, path: String) -> Error: ## Loads saveable nodes from a JSON dictionary into the scene tree, merging with existing nodes. static func load_tree_from_dict(scene_tree: SceneTree, dict: Dictionary) -> void: - for node_path: NodePath in dict: - var existing_node := scene_tree.root.get_node_or_null(node_path) - if existing_node == null: - push_error("Missing nodes are not supported right now") - continue + var node_paths: Array[NodePath] = [] + dict.keys().assign(node_paths) + + # Load nodes in order of depth, to ensure parents are loaded before children. + # We'll use this to instantiate any missing nodes along the way. + node_paths.sort_custom(func(a: NodePath, b: NodePath) -> bool: + return a.get_name_count() < b.get_name_count()) + + for node_path in node_paths: + var save_dict: Dictionary = dict[node_path] + save_dict = save_dict.duplicate() + + var scene_path: String = save_dict.get(_SCENE_FILE_PATH_KEY, "") + save_dict.erase(_SCENE_FILE_PATH_KEY) + if scene_path: + scene_path = scene_path.simplify_path() + if not scene_path.is_absolute_path() or not scene_path.is_valid_filename() \ + or not scene_path.begins_with("res://") or not scene_path.ends_with(".tscn"): + push_warning("Invalid scene path ", scene_path, " for node ", node_path, ", ignoring") + scene_path = "" + + var node := scene_tree.root.get_node_or_null(node_path) + if node == null: + var parent_path := NodeUtils.get_parent_path(node_path) + var parent_node := scene_tree.root.get_node_or_null(parent_path) + if not parent_node: + push_error("Could not find parent for node ", node_path, " in order to load it") + continue + + if not scene_path: + push_error("Node ", node_path, " doesn't have a scene file, so cannot be loaded by instantiation") + continue + + var scene: PackedScene = load(scene_path) + node = scene.instantiate() + node.add_to_group(SAVEABLE_GROUP) + parent_node.add_child(node) - if not existing_node.is_in_group(SAVEABLE_GROUP): + if not node.is_in_group(SAVEABLE_GROUP): push_warning("Loaded node ", node_path, " is not marked as saveable, refusing to load it") continue - var save_dict: Dictionary = dict[node_path] - existing_node.call("load_from_dict", save_dict) + node.call("load_from_dict", save_dict) From c0fc7ec5bf13dab4e854c0ea7e5c0aad87d929ed Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 13:47:46 +0100 Subject: [PATCH 11/46] WIP `SaveGame` interfaces --- actors/player.gd | 10 +++++++++- galaxy/star_system/star_system_instance.gd | 8 ++++++++ mechanics/hyperspace/hyperdrive_system.gd | 8 ++++++++ mechanics/hyperspace/hyperspace_scene_switcher.gd | 8 ++++++++ screens/game/game.tscn | 2 +- ships/ship.gd | 8 ++++++++ 6 files changed, 42 insertions(+), 2 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index 954c53db..885c3e40 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -286,8 +286,16 @@ func _physics_process(_delta: float) -> void: var desired_direction := self._absolute_input_direction() self.ship.rigid_body_direction.direction = desired_direction - var current_direction := - self.ship.transform.basis.z + var current_direction := -self.ship.transform.basis.z if desired_direction != Vector3.ZERO and desired_direction.angle_to(current_direction) <= ABSOLUTE_DIRECTION_TOLERANCE_RAD: self.ship.rigid_body_thruster.throttle = desired_direction.length() else: self.ship.rigid_body_thruster.throttle = 0.0 + +## See [SaveGame]. +func save_to_dict() -> Dictionary: + return {} + +## See [SaveGame]. +func load_from_dict(dict: Dictionary) -> void: + pass diff --git a/galaxy/star_system/star_system_instance.gd b/galaxy/star_system/star_system_instance.gd index 9d7f17ec..e4f46313 100644 --- a/galaxy/star_system/star_system_instance.gd +++ b/galaxy/star_system/star_system_instance.gd @@ -13,3 +13,11 @@ static func star_system_instance_for_node(node: Node) -> StarSystemInstance: return null return node + +## See [SaveGame]. +func save_to_dict() -> Dictionary: + return {} + +## See [SaveGame]. +func load_from_dict(dict: Dictionary) -> void: + pass diff --git a/mechanics/hyperspace/hyperdrive_system.gd b/mechanics/hyperspace/hyperdrive_system.gd index 32345061..990cc7f9 100644 --- a/mechanics/hyperspace/hyperdrive_system.gd +++ b/mechanics/hyperspace/hyperdrive_system.gd @@ -50,3 +50,11 @@ func current_system() -> StarSystem: return null return instance.star_system + +## See [SaveGame]. +func save_to_dict() -> Dictionary: + return {} + +## See [SaveGame]. +func load_from_dict(dict: Dictionary) -> void: + pass diff --git a/mechanics/hyperspace/hyperspace_scene_switcher.gd b/mechanics/hyperspace/hyperspace_scene_switcher.gd index 6dd36d89..55ab5c58 100644 --- a/mechanics/hyperspace/hyperspace_scene_switcher.gd +++ b/mechanics/hyperspace/hyperspace_scene_switcher.gd @@ -69,3 +69,11 @@ func load_jump_destination() -> void: func finish_jump() -> void: assert(self.hyperdrive_system.jumping, "No jump in progress") self.hyperdrive_system.jumping = false + +## See [SaveGame]. +func save_to_dict() -> Dictionary: + return {} + +## See [SaveGame]. +func load_from_dict(dict: Dictionary) -> void: + pass diff --git a/screens/game/game.tscn b/screens/game/game.tscn index c0bb9df5..be95e41e 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -234,7 +234,7 @@ pitch_scale = 2.0 [node name="AudioListener3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player" index="4"] current = false -[node name="RigidBodyCargo" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" groups=["saveable"]] +[node name="RigidBodyCargo" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("10_w7kut") cargo_hold = SubResource("Resource_bsv1l") diff --git a/ships/ship.gd b/ships/ship.gd index a1489f06..3f571771 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -35,3 +35,11 @@ class_name Ship func _to_string() -> String: return "Ship:%s (%s)" % [self.name, self.combat_object] + +## See [SaveGame]. +func save_to_dict() -> Dictionary: + return {} + +## See [SaveGame]. +func load_from_dict(dict: Dictionary) -> void: + pass From efe80e35df49bf32f8ddb97afcf044a6501cbfdb Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 13:59:19 +0100 Subject: [PATCH 12/46] Hoist all saveable resources to the `Ship` level --- screens/game/game.tscn | 65 ++++++++++++++++++++++++++++---- ships/corvette01/corvette01.tscn | 35 +++++++++-------- ships/freighter/freighter.tscn | 29 +++++++------- ships/frigate03/frigate03.tscn | 28 ++++++++------ ships/ship.gd | 18 +++++++++ 5 files changed, 127 insertions(+), 48 deletions(-) diff --git a/screens/game/game.tscn b/screens/game/game.tscn index be95e41e..04fd3b87 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=70 format=3 uid="uid://sunhu71swcs2"] +[gd_scene load_steps=76 format=3 uid="uid://sunhu71swcs2"] [ext_resource type="Environment" uid="uid://b7t3opwr35xen" path="res://fx/environment.tres" id="1_pqled"] [ext_resource type="Script" path="res://mechanics/hyperspace/hyperspace_scene_switcher.gd" id="4_yyrkm"] @@ -54,6 +54,27 @@ [sub_resource type="Resource" id="Resource_jc1pg"] script = ExtResource("46_cos1s") +[sub_resource type="Resource" id="Resource_p7sh0"] +resource_local_to_scene = true +script = ExtResource("14_bcf1o") +max_integrity = 300.0 +integrity = 300.0 + +[sub_resource type="Resource" id="Resource_dhynb"] +resource_local_to_scene = true +script = ExtResource("13_xvthq") +max_integrity = 150.0 +integrity = 150.0 +recharge_rate = 5.0 +power_efficiency = 1.0 +only_recharge_above = 0.2 + +[sub_resource type="Resource" id="Resource_ubt7x"] +resource_local_to_scene = true +script = ExtResource("15_sjw8q") +max_power = 300.0 +power = 300.0 + [sub_resource type="Resource" id="Resource_rwdpp"] resource_local_to_scene = true script = ExtResource("13_xvthq") @@ -75,11 +96,38 @@ script = ExtResource("15_sjw8q") max_power = 300.0 power = 300.0 +[sub_resource type="Resource" id="Resource_ffax2"] +resource_local_to_scene = true +script = ExtResource("14_bcf1o") +max_integrity = 40.0 +integrity = 40.0 + +[sub_resource type="Resource" id="Resource_75c8j"] +resource_local_to_scene = true +script = ExtResource("13_xvthq") +max_integrity = 80.0 +integrity = 80.0 +recharge_rate = 5.0 +power_efficiency = 1.0 +only_recharge_above = 0.2 + +[sub_resource type="Resource" id="Resource_xpwk4"] +resource_local_to_scene = true +script = ExtResource("15_sjw8q") +max_power = 100.0 +power = 100.0 + [sub_resource type="Resource" id="Resource_bsv1l"] script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} +[sub_resource type="Resource" id="Resource_xnk78"] +resource_local_to_scene = true +script = ExtResource("13_q2g24") +max_fuel = 6.0 +fuel = 6.0 + [sub_resource type="Resource" id="Resource_p0y82"] resource_local_to_scene = true script = ExtResource("13_xvthq") @@ -101,12 +149,6 @@ script = ExtResource("15_sjw8q") max_power = 100.0 power = 100.0 -[sub_resource type="Resource" id="Resource_xnk78"] -resource_local_to_scene = true -script = ExtResource("13_q2g24") -max_fuel = 6.0 -fuel = 6.0 - [sub_resource type="Resource" id="Resource_j1y8s"] script = ExtResource("42_g774c") currencies = { @@ -167,6 +209,11 @@ calendar = SubResource("Resource_jc1pg") [node name="Sol" parent="HyperspaceSceneSwitcher" instance=ExtResource("17_wjgyf")] +[node name="Freighter" parent="HyperspaceSceneSwitcher/Sol" index="5"] +hull = SubResource("Resource_p7sh0") +shield = SubResource("Resource_dhynb") +battery = SubResource("Resource_ubt7x") + [node name="CombatObject" parent="HyperspaceSceneSwitcher/Sol/Freighter" index="2"] shield = SubResource("Resource_rwdpp") hull = SubResource("Resource_f0l3y") @@ -187,7 +234,11 @@ shield = SubResource("Resource_rwdpp") [node name="PlayerCorvette" parent="HyperspaceSceneSwitcher/Sol" node_paths=PackedStringArray("hyperdrive_system") instance=ExtResource("12_bj83b")] physics_material_override = null hyperdrive_system = NodePath("HyperdriveSystem") +hull = SubResource("Resource_ffax2") +shield = SubResource("Resource_75c8j") +battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") +hyperdrive = SubResource("Resource_xnk78") [node name="CombatObject" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="3"] shield = SubResource("Resource_p0y82") diff --git a/ships/corvette01/corvette01.tscn b/ships/corvette01/corvette01.tscn index f6a7a17f..51e1e13e 100644 --- a/ships/corvette01/corvette01.tscn +++ b/ships/corvette01/corvette01.tscn @@ -17,12 +17,11 @@ [ext_resource type="Script" path="res://mechanics/combat/weapons/weapon_mount.gd" id="15_douxu"] [ext_resource type="Resource" uid="uid://cw1tu3qdxdgfi" path="res://mechanics/combat/weapons/blaster/blaster.tres" id="16_17v50"] -[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7odoe"] -radius = 0.362506 -height = 3.94075 - -[sub_resource type="BoxShape3D" id="BoxShape3D_28kwx"] -size = Vector3(0.361725, 0.247406, 0.331055) +[sub_resource type="Resource" id="Resource_5twpq"] +resource_local_to_scene = true +script = ExtResource("5_uewxf") +max_integrity = 40.0 +integrity = 40.0 [sub_resource type="Resource" id="Resource_alskh"] resource_local_to_scene = true @@ -33,23 +32,24 @@ recharge_rate = 5.0 power_efficiency = 1.0 only_recharge_above = 0.2 -[sub_resource type="Resource" id="Resource_5twpq"] +[sub_resource type="Resource" id="Resource_ftwkb"] resource_local_to_scene = true -script = ExtResource("5_uewxf") -max_integrity = 40.0 -integrity = 40.0 +script = ExtResource("9_8bmn0") +max_power = 100.0 +power = 100.0 + +[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_7odoe"] +radius = 0.362506 +height = 3.94075 + +[sub_resource type="BoxShape3D" id="BoxShape3D_28kwx"] +size = Vector3(0.361725, 0.247406, 0.331055) [sub_resource type="Resource" id="Resource_fijlt"] script = ExtResource("8_4hne6") turning_rate = 8.0 power_consumption_rate = 5.0 -[sub_resource type="Resource" id="Resource_ftwkb"] -resource_local_to_scene = true -script = ExtResource("9_8bmn0") -max_power = 100.0 -power = 100.0 - [sub_resource type="Resource" id="Resource_7vep7"] script = ExtResource("10_ffov7") thrust_force = 200.0 @@ -63,6 +63,9 @@ rate_of_power = 5.0 mass = 10.0 shield_recharger = NodePath("ShieldRecharger") weapon_mounts = [NodePath("Blaster")] +hull = SubResource("Resource_5twpq") +shield = SubResource("Resource_alskh") +battery = SubResource("Resource_ftwkb") [node name="MeshInstance3D" type="MeshInstance3D" parent="." index="0"] transform = Transform3D(-0.2, 6.97574e-17, -1.74846e-08, 6.97574e-17, 0.2, -6.97574e-17, 1.74846e-08, -6.97574e-17, -0.2, 0, 0, 0) diff --git a/ships/freighter/freighter.tscn b/ships/freighter/freighter.tscn index 9d2bbb2c..743c47eb 100644 --- a/ships/freighter/freighter.tscn +++ b/ships/freighter/freighter.tscn @@ -17,9 +17,11 @@ [ext_resource type="PackedScene" uid="uid://dbc2usdmy5wf3" path="res://fx/engine_glow/engine_glow.tscn" id="12_8a1b6"] [ext_resource type="Shader" path="res://fx/engine_glow/engine_glow.gdshader" id="13_0qykh"] -[sub_resource type="CylinderShape3D" id="CylinderShape3D_23xxj"] -height = 3.88505 -radius = 0.973661 +[sub_resource type="Resource" id="Resource_t05wf"] +resource_local_to_scene = true +script = ExtResource("4_n1hc8") +max_integrity = 300.0 +integrity = 300.0 [sub_resource type="Resource" id="Resource_ln8ln"] resource_local_to_scene = true @@ -30,11 +32,15 @@ recharge_rate = 5.0 power_efficiency = 1.0 only_recharge_above = 0.2 -[sub_resource type="Resource" id="Resource_t05wf"] +[sub_resource type="Resource" id="Resource_lo26u"] resource_local_to_scene = true -script = ExtResource("4_n1hc8") -max_integrity = 300.0 -integrity = 300.0 +script = ExtResource("8_w8qvs") +max_power = 300.0 +power = 300.0 + +[sub_resource type="CylinderShape3D" id="CylinderShape3D_23xxj"] +height = 3.88505 +radius = 0.973661 [sub_resource type="FastNoiseLite" id="FastNoiseLite_pmc4q"] @@ -64,12 +70,6 @@ script = ExtResource("8_ngx4b") turning_rate = 0.5 power_consumption_rate = 5.0 -[sub_resource type="Resource" id="Resource_lo26u"] -resource_local_to_scene = true -script = ExtResource("8_w8qvs") -max_power = 300.0 -power = 300.0 - [sub_resource type="Resource" id="Resource_q12ue"] script = ExtResource("10_8eiax") thrust_force = 60.0 @@ -93,6 +93,9 @@ rate_of_power = 3.0 [node name="Freighter" node_paths=PackedStringArray("shield_recharger") instance=ExtResource("1_r6j4w")] mass = 150.0 shield_recharger = NodePath("ShieldRecharger") +hull = SubResource("Resource_t05wf") +shield = SubResource("Resource_ln8ln") +battery = SubResource("Resource_lo26u") [node name="Model" parent="." index="0" instance=ExtResource("2_qliud")] transform = Transform3D(-1, 3.48787e-16, -8.74228e-08, 3.48787e-16, 1, -3.48787e-16, 8.74228e-08, -3.48787e-16, -1, 0, 0, 0) diff --git a/ships/frigate03/frigate03.tscn b/ships/frigate03/frigate03.tscn index 639c3dc9..3ce67f12 100644 --- a/ships/frigate03/frigate03.tscn +++ b/ships/frigate03/frigate03.tscn @@ -19,8 +19,11 @@ [ext_resource type="Script" path="res://mechanics/combat/weapons/weapon_mount.gd" id="16_xra8r"] [ext_resource type="Resource" uid="uid://cw1tu3qdxdgfi" path="res://mechanics/combat/weapons/blaster/blaster.tres" id="17_h1u5w"] -[sub_resource type="BoxShape3D" id="BoxShape3D_ou7uu"] -size = Vector3(1.98299, 0.832642, 1.04932) +[sub_resource type="Resource" id="Resource_am8dk"] +resource_local_to_scene = true +script = ExtResource("4_6j23n") +max_integrity = 100.0 +integrity = 100.0 [sub_resource type="Resource" id="Resource_rw3nb"] resource_local_to_scene = true @@ -29,12 +32,16 @@ max_integrity = 100.0 integrity = 100.0 recharge_rate = 10.0 power_efficiency = 1.0 +only_recharge_above = 0.2 -[sub_resource type="Resource" id="Resource_am8dk"] +[sub_resource type="Resource" id="Resource_p601o"] resource_local_to_scene = true -script = ExtResource("4_6j23n") -max_integrity = 100.0 -integrity = 100.0 +script = ExtResource("9_7c238") +max_power = 300.0 +power = 300.0 + +[sub_resource type="BoxShape3D" id="BoxShape3D_ou7uu"] +size = Vector3(1.98299, 0.832642, 1.04932) [sub_resource type="FastNoiseLite" id="FastNoiseLite_pmc4q"] @@ -64,12 +71,6 @@ script = ExtResource("8_74gsh") turning_rate = 1.0 power_consumption_rate = 5.0 -[sub_resource type="Resource" id="Resource_p601o"] -resource_local_to_scene = true -script = ExtResource("9_7c238") -max_power = 300.0 -power = 300.0 - [sub_resource type="Resource" id="Resource_1r5ug"] script = ExtResource("10_6f48k") thrust_force = 100.0 @@ -94,6 +95,9 @@ rate_of_power = 2.0 mass = 100.0 shield_recharger = NodePath("ShieldRecharger") weapon_mounts = [NodePath("Blaster")] +hull = SubResource("Resource_am8dk") +shield = SubResource("Resource_rw3nb") +battery = SubResource("Resource_p601o") [node name="MeshInstance3D" type="MeshInstance3D" parent="." index="0"] transform = Transform3D(-0.4, 1.39515e-16, 3.49691e-08, 1.39515e-16, 0.4, -1.39515e-16, -3.49691e-08, -1.39515e-16, -0.4, 1.40849e-16, -1.40849e-16, -0.199324) diff --git a/ships/ship.gd b/ships/ship.gd index 3f571771..1b4bb497 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -1,6 +1,8 @@ extends RigidBody3D class_name Ship +# NODES + ## The [CombatObject] representing this ship. @export var combat_object: CombatObject @@ -30,9 +32,25 @@ class_name Ship ## Any weapons mounted on this ship. @export var weapon_mounts: Array[WeaponMount] +# SAVEABLE RESOURCES + +## The hull of the ship. +## +## Connect to [signal Hull.hull_destroyed] to be notified when the ship is destroyed. +@export var hull: Hull + +## An optional shield protecting the ship. +@export var shield: Shield + +## The [Battery] powering the ship. +@export var battery: Battery + ## An optional cargo hold for this ship. @export var cargo_hold: CargoHold +## An optional hyperdrive for this ship. +@export var hyperdrive: Hyperdrive + func _to_string() -> String: return "Ship:%s (%s)" % [self.name, self.combat_object] From 4a8fc27d48722dcb2746942e117267907c0bd7c8 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:05:18 +0100 Subject: [PATCH 13/46] Remove exported `SaveableResource`s from ship sub-nodes, and configure them from the `Ship` --- mechanics/combat/combat_object.gd | 36 +++++---- mechanics/combat/shield_recharger.gd | 13 +++- mechanics/combat/weapons/weapon_mount.gd | 2 +- mechanics/hyperspace/hyperdrive_system.gd | 2 +- mechanics/physics/rigid_body_direction.gd | 6 +- mechanics/physics/rigid_body_thruster.gd | 6 +- mechanics/power/power_management_unit.gd | 15 ++-- screens/game/game.tscn | 90 +---------------------- ships/corvette01/corvette01.tscn | 8 -- ships/freighter/freighter.tscn | 7 -- ships/frigate03/frigate03.tscn | 8 -- ships/ship.gd | 17 +++++ 12 files changed, 70 insertions(+), 140 deletions(-) diff --git a/mechanics/combat/combat_object.gd b/mechanics/combat/combat_object.gd index 05fd1fa7..c284d951 100644 --- a/mechanics/combat/combat_object.gd +++ b/mechanics/combat/combat_object.gd @@ -11,9 +11,6 @@ class_name CombatObject ## A scene to render as the visualization of this object in the target info panel. @export var target_view: PackedScene -## An optional shield protecting the object. -@export var shield: Shield - ## An optional mesh to render as the object's shields, when the shields are hit. ## ## This is accomplished by keeping the mesh at full transparency most of the time, then animating it to fully opaque and back when hit. The mesh's material must honor transparency. @@ -27,16 +24,28 @@ class_name CombatObject @export var shield_flash_off_transition: Tween.TransitionType = Tween.TRANS_CUBIC @export var shield_flash_off_ease: Tween.EaseType = Tween.EASE_IN -## The hull of the object. -## -## Connect to [signal Hull.hull_destroyed] to be notified when the CombatObject is destroyed. -@export var hull: Hull - ## An optional scene to instantiate at the object's [member Node3D.global_transform] when destroyed. ## ## The root must be a [Node3D]. @export var destruction: PackedScene +## The hull of the object. +## +## Connect to [signal Hull.hull_destroyed] to be notified when the CombatObject is destroyed. +var hull: Hull: + set(value): + if value == hull: + return + + if hull: + hull.hull_destroyed.disconnect(_on_hull_destroyed) + hull = value + if hull: + hull.hull_destroyed.connect(_on_hull_destroyed) + +## An optional shield protecting the object. +var shield: Shield + ## Fires when this object is targeted, or stops being targeted, by a new [TargetingSystem]. ## ## See [method get_targeted_by] @@ -45,10 +54,6 @@ signal targeted_by_changed(combat_object: CombatObject) var _shield_tween: Tween var _targeted_by: Array[TargetingSystem] = [] -func _ready() -> void: - if self.destruction: - self.hull.hull_destroyed.connect(_on_hull_destroyed) - func _enter_tree() -> void: if self.shield_mesh_instance: self.shield_mesh_instance.transparency = 1.0 @@ -65,7 +70,7 @@ func get_targeted_by() -> Array[TargetingSystem]: ## ## This [b]must not[/b] be called twice for the same targeting system, without an intervening removal. func add_targeted_by(targeting_system: TargetingSystem) -> void: - assert(self._targeted_by.find(targeting_system) == - 1, "Duplicate add_targeted_by with the same targeting system") + assert(self._targeted_by.find(targeting_system) == -1, "Duplicate add_targeted_by with the same targeting system") self._targeted_by.push_back(targeting_system) self.targeted_by_changed.emit(self) @@ -75,7 +80,7 @@ func add_targeted_by(targeting_system: TargetingSystem) -> void: func remove_targeted_by(targeting_system: TargetingSystem) -> void: # Targets are more likely to change at the end of the list, so search in reverse var index := self._targeted_by.rfind(targeting_system) - assert(index != - 1, "remove_targeted_by called with a targeting system that is not targeting this object") + assert(index != -1, "remove_targeted_by called with a targeting system that is not targeting this object") self._targeted_by.remove_at(index) self.targeted_by_changed.emit(self) @@ -106,6 +111,9 @@ static func damage_combat_object_inside(node: Node, dmg: Damage) -> bool: return true func _on_hull_destroyed(destroyed_hull: Hull) -> void: + if not self.destruction: + return + assert(destroyed_hull == self.hull) var parent := self.get_parent() diff --git a/mechanics/combat/shield_recharger.gd b/mechanics/combat/shield_recharger.gd index dcdf8499..ffb17568 100644 --- a/mechanics/combat/shield_recharger.gd +++ b/mechanics/combat/shield_recharger.gd @@ -3,10 +3,17 @@ class_name ShieldRecharger ## Automatically charges a [Shield] from a [Battery] over time. -@export var battery: Battery -@export var shield: Shield +var battery: Battery: + set(value): + battery = value + self._maybe_toggle_enabled() -func _ready() -> void: +var shield: Shield: + set(value): + shield = value + self._maybe_toggle_enabled() + +func _maybe_toggle_enabled() -> void: var should_enable := self.battery != null \ and self.shield != null \ and not is_zero_approx(self.shield.recharge_rate) \ diff --git a/mechanics/combat/weapons/weapon_mount.gd b/mechanics/combat/weapons/weapon_mount.gd index 841d36a7..cc9c2691 100644 --- a/mechanics/combat/weapons/weapon_mount.gd +++ b/mechanics/combat/weapons/weapon_mount.gd @@ -9,7 +9,7 @@ class_name WeaponMount @export var weapon: Weapon ## The [Battery] to power the weapon from. -@export var battery: Battery +var battery: Battery ## The tick (in microseconds) in which the weapon was last fired. var _last_fired_usec: int = -1 diff --git a/mechanics/hyperspace/hyperdrive_system.gd b/mechanics/hyperspace/hyperdrive_system.gd index 990cc7f9..6960c59a 100644 --- a/mechanics/hyperspace/hyperdrive_system.gd +++ b/mechanics/hyperspace/hyperdrive_system.gd @@ -6,7 +6,7 @@ class_name HyperdriveSystem ## [b]This script expects the parent node to be a [Ship].[/b] ## The hyperdrive to use. -@export var hyperdrive: Hyperdrive +var hyperdrive: Hyperdrive ## Whether a jump is currently being performed. ## diff --git a/mechanics/physics/rigid_body_direction.gd b/mechanics/physics/rigid_body_direction.gd index c6612fb6..8d1a6051 100644 --- a/mechanics/physics/rigid_body_direction.gd +++ b/mechanics/physics/rigid_body_direction.gd @@ -8,9 +8,6 @@ class_name RigidBodyDirection ## The thruster determining the spin speed. @export var spin_thruster: SpinThruster -## The [Battery] to power the thruster from. -@export var battery: Battery - ## A vector representing the direction to rotate toward, or zero to stop rotating. @export var direction: Vector3 = Vector3.ZERO: set(value): @@ -19,6 +16,9 @@ class_name RigidBodyDirection ## The [RigidBody3D] to rotate. @onready var _rigid_body := get_parent() as RigidBody3D +## The [Battery] to power the thruster from. +var battery: Battery + func _physics_process(delta: float) -> void: if is_zero_approx(self.battery.power) or self.direction.is_zero_approx(): return diff --git a/mechanics/physics/rigid_body_thruster.gd b/mechanics/physics/rigid_body_thruster.gd index 111dd292..1ec9e6d7 100644 --- a/mechanics/physics/rigid_body_thruster.gd +++ b/mechanics/physics/rigid_body_thruster.gd @@ -7,9 +7,6 @@ class_name RigidBodyThruster @export var thruster: Thruster -## The [Battery] to power the thruster from. -@export var battery: Battery - ## An audio clip to play while the thruster is active. ## ## This audio stream should be a looping sound. Rather than restarting from the beginning each time the thruster is used, the audio stream is paused and resumed, and evenutally loops. @@ -23,6 +20,9 @@ class_name RigidBodyThruster @export var animation_transition: Tween.TransitionType = Tween.TRANS_CUBIC @export var animation_ease: Tween.EaseType = Tween.EASE_IN_OUT +## The [Battery] to power the thruster from. +var battery: Battery + ## The current level of throttle (where 1.0 is full throttle), which corresponds to the magnitude of the thrust to apply, as well as the amount of power to be consumed. var throttle: float: set(value): diff --git a/mechanics/power/power_management_unit.gd b/mechanics/power/power_management_unit.gd index 57ee5802..0b33d937 100644 --- a/mechanics/power/power_management_unit.gd +++ b/mechanics/power/power_management_unit.gd @@ -3,14 +3,17 @@ class_name PowerManagementUnit ## Manages power generation and consumption. -## A battery storing power. -@export var battery: Battery - ## An optional power generator to automatically recharge the battery. -@export var power_generator: PowerGenerator +@export var power_generator: PowerGenerator: + set(value): + power_generator = value + self.set_physics_process(self.battery != null and power_generator != null) -func _ready() -> void: - self.set_physics_process(self.battery != null and self.power_generator != null) +## A battery storing power. +var battery: Battery: + set(value): + battery = value + self.set_physics_process(battery != null and self.power_generator != null) func _physics_process(delta: float) -> void: self.battery.recharge(self.power_generator.rate_of_power * delta) diff --git a/screens/game/game.tscn b/screens/game/game.tscn index 04fd3b87..d1c0dec6 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=76 format=3 uid="uid://sunhu71swcs2"] +[gd_scene load_steps=70 format=3 uid="uid://sunhu71swcs2"] [ext_resource type="Environment" uid="uid://b7t3opwr35xen" path="res://fx/environment.tres" id="1_pqled"] [ext_resource type="Script" path="res://mechanics/hyperspace/hyperspace_scene_switcher.gd" id="4_yyrkm"] @@ -75,27 +75,6 @@ script = ExtResource("15_sjw8q") max_power = 300.0 power = 300.0 -[sub_resource type="Resource" id="Resource_rwdpp"] -resource_local_to_scene = true -script = ExtResource("13_xvthq") -max_integrity = 150.0 -integrity = 150.0 -recharge_rate = 5.0 -power_efficiency = 1.0 -only_recharge_above = 0.2 - -[sub_resource type="Resource" id="Resource_f0l3y"] -resource_local_to_scene = true -script = ExtResource("14_bcf1o") -max_integrity = 300.0 -integrity = 300.0 - -[sub_resource type="Resource" id="Resource_pv148"] -resource_local_to_scene = true -script = ExtResource("15_sjw8q") -max_power = 300.0 -power = 300.0 - [sub_resource type="Resource" id="Resource_ffax2"] resource_local_to_scene = true script = ExtResource("14_bcf1o") @@ -122,33 +101,12 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_xnk78"] +[sub_resource type="Resource" id="Resource_6ari8"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 fuel = 6.0 -[sub_resource type="Resource" id="Resource_p0y82"] -resource_local_to_scene = true -script = ExtResource("13_xvthq") -max_integrity = 80.0 -integrity = 80.0 -recharge_rate = 5.0 -power_efficiency = 1.0 -only_recharge_above = 0.2 - -[sub_resource type="Resource" id="Resource_rdvmt"] -resource_local_to_scene = true -script = ExtResource("14_bcf1o") -max_integrity = 40.0 -integrity = 40.0 - -[sub_resource type="Resource" id="Resource_jcr0a"] -resource_local_to_scene = true -script = ExtResource("15_sjw8q") -max_power = 100.0 -power = 100.0 - [sub_resource type="Resource" id="Resource_j1y8s"] script = ExtResource("42_g774c") currencies = { @@ -204,8 +162,8 @@ texture_margin_bottom = 10.0 [node name="HyperspaceSceneSwitcher" type="Node3D" parent="." node_paths=PackedStringArray("hyperdrive_system") groups=["saveable"]] script = ExtResource("4_yyrkm") -hyperdrive_system = NodePath("Sol/PlayerCorvette/HyperdriveSystem") calendar = SubResource("Resource_jc1pg") +hyperdrive_system = NodePath("Sol/PlayerCorvette/HyperdriveSystem") [node name="Sol" parent="HyperspaceSceneSwitcher" instance=ExtResource("17_wjgyf")] @@ -214,23 +172,6 @@ hull = SubResource("Resource_p7sh0") shield = SubResource("Resource_dhynb") battery = SubResource("Resource_ubt7x") -[node name="CombatObject" parent="HyperspaceSceneSwitcher/Sol/Freighter" index="2"] -shield = SubResource("Resource_rwdpp") -hull = SubResource("Resource_f0l3y") - -[node name="RigidBodyDirection" parent="HyperspaceSceneSwitcher/Sol/Freighter" index="3"] -battery = SubResource("Resource_pv148") - -[node name="RigidBodyThruster" parent="HyperspaceSceneSwitcher/Sol/Freighter" index="4"] -battery = SubResource("Resource_pv148") - -[node name="PowerManagementUnit" parent="HyperspaceSceneSwitcher/Sol/Freighter" index="5"] -battery = SubResource("Resource_pv148") - -[node name="ShieldRecharger" parent="HyperspaceSceneSwitcher/Sol/Freighter" index="7"] -battery = SubResource("Resource_pv148") -shield = SubResource("Resource_rwdpp") - [node name="PlayerCorvette" parent="HyperspaceSceneSwitcher/Sol" node_paths=PackedStringArray("hyperdrive_system") instance=ExtResource("12_bj83b")] physics_material_override = null hyperdrive_system = NodePath("HyperdriveSystem") @@ -238,31 +179,10 @@ hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_xnk78") - -[node name="CombatObject" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="3"] -shield = SubResource("Resource_p0y82") -hull = SubResource("Resource_rdvmt") - -[node name="RigidBodyDirection" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="4"] -battery = SubResource("Resource_jcr0a") - -[node name="RigidBodyThruster" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="5"] -battery = SubResource("Resource_jcr0a") - -[node name="PowerManagementUnit" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="6"] -battery = SubResource("Resource_jcr0a") - -[node name="ShieldRecharger" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="8"] -battery = SubResource("Resource_jcr0a") -shield = SubResource("Resource_p0y82") - -[node name="Blaster" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" index="9"] -battery = SubResource("Resource_jcr0a") +hyperdrive = SubResource("Resource_6ari8") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" groups=["saveable"]] script = ExtResource("16_6olqp") -hyperdrive = SubResource("Resource_xnk78") [node name="Player" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" node_paths=PackedStringArray("hyperspace_scene_switcher", "message_log") groups=["saveable"] instance=ExtResource("16_lqltb")] hyperspace_scene_switcher = NodePath("../../..") @@ -707,8 +627,6 @@ text = "30 FPS horizontal_alignment = 2 script = ExtResource("37_e11x7") -[connection signal="jump_destination_loaded" from="HyperspaceSceneSwitcher" to="HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player" method="_on_jump_destination_loaded"] -[connection signal="jump_destination_loaded" from="HyperspaceSceneSwitcher" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/SystemName" method="_on_jump_destination_loaded"] [connection signal="jump_destination_changed" from="HyperspaceSceneSwitcher/Sol/PlayerCorvette/HyperdriveSystem" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/PanelContainer/VBoxContainer/JumpDestinationName" method="_on_jump_destination_changed"] [connection signal="jumping_changed" from="HyperspaceSceneSwitcher/Sol/PlayerCorvette/HyperdriveSystem" to="MainCameraTransform/MainCamera/HyperspaceEffect" method="_on_jumping_changed"] [connection signal="hull_changed" from="HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/PlayerVitalsContainer/VBoxContainer/PlayerVitals" method="_on_player_hull_changed"] diff --git a/ships/corvette01/corvette01.tscn b/ships/corvette01/corvette01.tscn index 51e1e13e..d725d09a 100644 --- a/ships/corvette01/corvette01.tscn +++ b/ships/corvette01/corvette01.tscn @@ -83,20 +83,16 @@ shape = SubResource("BoxShape3D_28kwx") [node name="CombatObject" parent="." index="3" node_paths=PackedStringArray("shield_mesh_instance")] combat_name = "Corvette" target_view = ExtResource("3_v410k") -shield = SubResource("Resource_alskh") shield_mesh_instance = NodePath("ShieldEffect") -hull = SubResource("Resource_5twpq") destruction = ExtResource("2_xcc1t") [node name="ShieldEffect" parent="CombatObject" index="1" instance=ExtResource("10_js72b")] [node name="RigidBodyDirection" parent="." index="4"] spin_thruster = SubResource("Resource_fijlt") -battery = SubResource("Resource_ftwkb") [node name="RigidBodyThruster" parent="." index="5" node_paths=PackedStringArray("thruster_audio")] thruster = SubResource("Resource_7vep7") -battery = SubResource("Resource_ftwkb") thruster_audio = NodePath("AudioStreamPlayer3D") [node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="RigidBodyThruster" index="0"] @@ -115,18 +111,14 @@ cast_shadow = 0 gi_mode = 2 [node name="PowerManagementUnit" parent="." index="6"] -battery = SubResource("Resource_ftwkb") power_generator = SubResource("Resource_csuav") [node name="ShieldRecharger" type="Node3D" parent="." index="8"] script = ExtResource("14_mp2y5") -battery = SubResource("Resource_ftwkb") -shield = SubResource("Resource_alskh") [node name="Blaster" type="Node3D" parent="." index="9"] transform = Transform3D(1, -1.21652e-31, 0, -1.21652e-31, 1, 0, 0, 0, 1, 1.62168e-16, -1.62168e-16, -0.464948) script = ExtResource("15_douxu") weapon = ExtResource("16_17v50") -battery = SubResource("Resource_ftwkb") [editable path="CombatObject/TargetOverlay"] diff --git a/ships/freighter/freighter.tscn b/ships/freighter/freighter.tscn index 743c47eb..2c84f808 100644 --- a/ships/freighter/freighter.tscn +++ b/ships/freighter/freighter.tscn @@ -107,9 +107,7 @@ shape = SubResource("CylinderShape3D_23xxj") [node name="CombatObject" parent="." index="2" node_paths=PackedStringArray("shield_mesh_instance")] combat_name = "Freighter" target_view = ExtResource("3_4e8m5") -shield = SubResource("Resource_ln8ln") shield_mesh_instance = NodePath("ShieldEffect") -hull = SubResource("Resource_t05wf") destruction = ExtResource("5_bgpiu") [node name="Left" parent="CombatObject/TargetOverlay" index="0"] @@ -126,12 +124,10 @@ mesh = SubResource("SphereMesh_cm3ef") [node name="RigidBodyDirection" parent="." index="3"] spin_thruster = SubResource("Resource_rvmag") -battery = SubResource("Resource_lo26u") [node name="RigidBodyThruster" parent="." index="4" node_paths=PackedStringArray("thruster_audio")] transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1.91653) thruster = SubResource("Resource_q12ue") -battery = SubResource("Resource_lo26u") thruster_audio = NodePath("AudioStreamPlayer3D") [node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="RigidBodyThruster" index="0"] @@ -152,12 +148,9 @@ transform = Transform3D(1, -3.48787e-16, 3.48787e-16, 3.48787e-16, 1, -3.48787e- mesh = SubResource("QuadMesh_2khek") [node name="PowerManagementUnit" parent="." index="5"] -battery = SubResource("Resource_lo26u") power_generator = SubResource("Resource_ku887") [node name="ShieldRecharger" type="Node3D" parent="." index="7"] script = ExtResource("5_s17ip") -battery = SubResource("Resource_lo26u") -shield = SubResource("Resource_ln8ln") [editable path="CombatObject/TargetOverlay"] diff --git a/ships/frigate03/frigate03.tscn b/ships/frigate03/frigate03.tscn index 3ce67f12..270e2e8d 100644 --- a/ships/frigate03/frigate03.tscn +++ b/ships/frigate03/frigate03.tscn @@ -111,9 +111,7 @@ shape = SubResource("BoxShape3D_ou7uu") [node name="CombatObject" parent="." index="2" node_paths=PackedStringArray("shield_mesh_instance")] combat_name = "Frigate" target_view = ExtResource("3_bcien") -shield = SubResource("Resource_rw3nb") shield_mesh_instance = NodePath("ShieldEffect") -hull = SubResource("Resource_am8dk") destruction = ExtResource("2_xcc1t") [node name="ShieldEffect" parent="CombatObject" index="1" instance=ExtResource("11_lwnic")] @@ -130,11 +128,9 @@ pixel_size = 0.011 [node name="RigidBodyDirection" parent="." index="3"] spin_thruster = SubResource("Resource_likrw") -battery = SubResource("Resource_p601o") [node name="RigidBodyThruster" parent="." index="4" node_paths=PackedStringArray("thruster_audio")] thruster = SubResource("Resource_1r5ug") -battery = SubResource("Resource_p601o") thruster_audio = NodePath("AudioStreamPlayer3D") [node name="AudioStreamPlayer3D" type="AudioStreamPlayer3D" parent="RigidBodyThruster" index="0"] @@ -147,18 +143,14 @@ transform = Transform3D(0.433459, -4.84158e-17, 1.6528e-16, 1.51185e-16, 0.13881 mesh = SubResource("QuadMesh_j7d31") [node name="PowerManagementUnit" parent="." index="5"] -battery = SubResource("Resource_p601o") power_generator = SubResource("Resource_0m3cu") [node name="ShieldRecharger" type="Node3D" parent="." index="7"] script = ExtResource("15_ik8m4") -battery = SubResource("Resource_p601o") -shield = SubResource("Resource_rw3nb") [node name="Blaster" type="Node3D" parent="." index="8"] transform = Transform3D(1, -1.21652e-31, 0, -1.21652e-31, 1, 0, 0, 0, 1, 3.78906e-16, -3.78906e-16, -1.08635) script = ExtResource("16_xra8r") weapon = ExtResource("17_h1u5w") -battery = SubResource("Resource_p601o") [editable path="CombatObject/TargetOverlay"] diff --git a/ships/ship.gd b/ships/ship.gd index 1b4bb497..0ca04ccd 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -51,6 +51,23 @@ class_name Ship ## An optional hyperdrive for this ship. @export var hyperdrive: Hyperdrive +func _ready() -> void: + self.combat_object.hull = self.hull + self.combat_object.shield = self.shield + self.rigid_body_thruster.battery = self.battery + self.rigid_body_direction.battery = self.battery + self.power_management_unit.battery = self.battery + + if self.shield_recharger: + self.shield_recharger.shield = self.shield + self.shield_recharger.battery = self.battery + + if self.hyperdrive_system: + self.hyperdrive_system.hyperdrive = self.hyperdrive + + for weapon_mount in self.weapon_mounts: + weapon_mount.battery = self.battery + func _to_string() -> String: return "Ship:%s (%s)" % [self.name, self.combat_object] From 2992e926e6e74fc538e46d1f80190b7f245313de Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:08:03 +0100 Subject: [PATCH 14/46] Directly access resources off of `Ship` when needed --- actors/ai/archetypes/pirate.gd | 8 ++++---- actors/player.gd | 22 +++++++++++----------- screens/landing/landing.gd | 14 +++++++------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/actors/ai/archetypes/pirate.gd b/actors/ai/archetypes/pirate.gd index 32f23b7b..c88de3ee 100644 --- a/actors/ai/archetypes/pirate.gd +++ b/actors/ai/archetypes/pirate.gd @@ -50,8 +50,8 @@ func _ready() -> void: self._connect_notifications.call_deferred() func _connect_notifications() -> void: - self._ship.combat_object.hull.changed.connect(_on_damage_received) - self._ship.combat_object.shield.changed.connect(_on_damage_received) + self._ship.hull.changed.connect(_on_damage_received) + self._ship.shield.changed.connect(_on_damage_received) func _select_new_patrol_target() -> void: self._patrol_target = MathUtils.random_unit_vector() * self.patrol_radius @@ -74,12 +74,12 @@ func _desired_direction() -> Vector3: var target := self._ship.targeting_system.target if target: var target_direction := (target.global_transform.origin - self._ship.global_transform.origin).normalized() - return target_direction if self._current_state != State.RETREAT else - target_direction + return target_direction if self._current_state != State.RETREAT else -target_direction else: return (self._patrol_target - self._ship.global_transform.origin).normalized() func _pointing_in_direction(direction: Vector3) -> bool: - var current_direction := - self._ship.global_transform.basis.z + var current_direction := -self._ship.global_transform.basis.z return current_direction.angle_to(direction) <= self.direction_tolerance func _patrol_behavior(_delta: float) -> void: diff --git a/actors/player.gd b/actors/player.gd index 885c3e40..4f9aed2e 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -67,17 +67,17 @@ const MAX_LANDING_VELOCITY = 4.0 func _ready() -> void: self._rigid_body_turner = RigidBodyTurner.new() self._rigid_body_turner.spin_thruster = self.ship.rigid_body_direction.spin_thruster - self._rigid_body_turner.battery = self.ship.rigid_body_direction.battery + self._rigid_body_turner.battery = self.ship.battery self.ship.add_child.call_deferred(self._rigid_body_turner) self.ship.radar_object.iff = RadarObject.IFF.SELF self.ship.targeting_system.is_player = true - self.ship.combat_object.hull.changed.connect(_on_hull_changed) - self.ship.combat_object.hull.hull_destroyed.connect(_on_hull_destroyed) - self.ship.combat_object.shield.changed.connect(_on_shield_changed) - self.ship.power_management_unit.battery.changed.connect(_on_power_changed) + self.ship.hull.changed.connect(_on_hull_changed) + self.ship.hull.hull_destroyed.connect(_on_hull_destroyed) + self.ship.shield.changed.connect(_on_shield_changed) + self.ship.battery.changed.connect(_on_power_changed) + self.ship.hyperdrive.changed.connect(_on_hyperdrive_changed) self.ship.targeting_system.target_changed.connect(_on_target_changed) - self.ship.hyperdrive_system.hyperdrive.changed.connect(_on_hyperdrive_changed) InputEventBroadcaster.input_event.connect(_on_broadcasted_input_event) @@ -88,16 +88,16 @@ func _ready() -> void: self._on_hyperdrive_changed() func _on_hull_changed() -> void: - self.hull_changed.emit(self, self.ship.combat_object.hull) + self.hull_changed.emit(self, self.ship.hull) func _on_shield_changed() -> void: - self.shield_changed.emit(self, self.ship.combat_object.shield) + self.shield_changed.emit(self, self.ship.shield) func _on_power_changed() -> void: - self.power_changed.emit(self, self.ship.power_management_unit.battery) + self.power_changed.emit(self, self.ship.battery) func _on_hull_destroyed(hull: Hull) -> void: - assert(hull == self.ship.combat_object.hull, "Received hull_destroyed signal from incorrect hull") + assert(hull == self.ship.hull, "Received hull_destroyed signal from incorrect hull") self.ship_destroyed.emit(self) func _on_target_changed(targeting_system: TargetingSystem) -> void: @@ -109,7 +109,7 @@ func _on_jump_destination_loaded(_new_system_instance: StarSystemInstance) -> vo self.ship.targeting_system.target = null func _on_hyperdrive_changed() -> void: - self.hyperdrive_changed.emit(self, self.ship.hyperdrive_system.hyperdrive) + self.hyperdrive_changed.emit(self, self.ship.hyperdrive) func _next_system_connection() -> StarSystem: var current_destination_name: Variant = null diff --git a/screens/landing/landing.gd b/screens/landing/landing.gd index 44c5c7cd..b20845d4 100644 --- a/screens/landing/landing.gd +++ b/screens/landing/landing.gd @@ -28,15 +28,15 @@ var _hyperdrive: Hyperdrive var _trading_window: TradingWindow = null func _ready() -> void: - self._hyperdrive = self.player.ship.hyperdrive_system.hyperdrive + self._hyperdrive = self.player.ship.hyperdrive self.title = self.planet.name - self.bar_button.visible = (self.planet.facilities&Planet.BAR) - self.trading_button.visible = (self.planet.facilities&Planet.TRADING) - self.missions_button.visible = (self.planet.facilities&Planet.MISSIONS) - self.outfitter_button.visible = (self.planet.facilities&Planet.OUTFITTER) - self.shipyard_button.visible = (self.planet.facilities&Planet.SHIPYARD) - self.refuel_button.visible = (self.planet.facilities&Planet.REFUEL) + self.bar_button.visible = (self.planet.facilities & Planet.BAR) + self.trading_button.visible = (self.planet.facilities & Planet.TRADING) + self.missions_button.visible = (self.planet.facilities & Planet.MISSIONS) + self.outfitter_button.visible = (self.planet.facilities & Planet.OUTFITTER) + self.shipyard_button.visible = (self.planet.facilities & Planet.SHIPYARD) + self.refuel_button.visible = (self.planet.facilities & Planet.REFUEL) self.landscape_image.texture = self.planet.landscape_image self.description_label.text = self.planet.description From 7aa958b1a0750612da81b3638c97e1a6c1363f27 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:13:02 +0100 Subject: [PATCH 15/46] Hoist `cargo_hold`/`RigidBodyCargo` too --- mechanics/physics/rigid_body_cargo.gd | 2 +- screens/game/game.tscn | 8 ++++---- ships/ship.gd | 6 ++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mechanics/physics/rigid_body_cargo.gd b/mechanics/physics/rigid_body_cargo.gd index a9459772..36f2e0d6 100644 --- a/mechanics/physics/rigid_body_cargo.gd +++ b/mechanics/physics/rigid_body_cargo.gd @@ -4,7 +4,7 @@ class_name RigidBodyCargo ## Attaches to a [RigidBody3D] to adjust its mass based on a [CargoHold]. ## The cargo hold. -@export var cargo_hold: CargoHold +var cargo_hold: CargoHold @onready var _rigid_body := get_parent() as RigidBody3D diff --git a/screens/game/game.tscn b/screens/game/game.tscn index d1c0dec6..28816717 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -101,7 +101,7 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_6ari8"] +[sub_resource type="Resource" id="Resource_eokwm"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 @@ -172,14 +172,15 @@ hull = SubResource("Resource_p7sh0") shield = SubResource("Resource_dhynb") battery = SubResource("Resource_ubt7x") -[node name="PlayerCorvette" parent="HyperspaceSceneSwitcher/Sol" node_paths=PackedStringArray("hyperdrive_system") instance=ExtResource("12_bj83b")] +[node name="PlayerCorvette" parent="HyperspaceSceneSwitcher/Sol" node_paths=PackedStringArray("hyperdrive_system", "rigid_body_cargo") instance=ExtResource("12_bj83b")] physics_material_override = null hyperdrive_system = NodePath("HyperdriveSystem") +rigid_body_cargo = NodePath("RigidBodyCargo") hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_6ari8") +hyperdrive = SubResource("Resource_eokwm") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" groups=["saveable"]] script = ExtResource("16_6olqp") @@ -207,7 +208,6 @@ current = false [node name="RigidBodyCargo" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("10_w7kut") -cargo_hold = SubResource("Resource_bsv1l") [node name="WorldEnvironment" type="WorldEnvironment" parent="."] environment = ExtResource("1_pqled") diff --git a/ships/ship.gd b/ships/ship.gd index 0ca04ccd..2957b141 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -32,6 +32,9 @@ class_name Ship ## Any weapons mounted on this ship. @export var weapon_mounts: Array[WeaponMount] +## An optional [RigidBodyCargo] for affecting this ship's physics based on its [member cargo_hold]. +@export var rigid_body_cargo: RigidBodyCargo + # SAVEABLE RESOURCES ## The hull of the ship. @@ -67,6 +70,9 @@ func _ready() -> void: for weapon_mount in self.weapon_mounts: weapon_mount.battery = self.battery + + if self.rigid_body_cargo: + self.rigid_body_cargo.cargo_hold = self.cargo_hold func _to_string() -> String: return "Ship:%s (%s)" % [self.name, self.combat_object] From bb5f2fbac9a93b6a9066ce725f698fd6eefe982c Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:24:26 +0100 Subject: [PATCH 16/46] `Ship` saving and loading --- ships/ship.gd | 14 ++++++++++++-- utils/save_game.gd | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/ships/ship.gd b/ships/ship.gd index 2957b141..6446e4c9 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -79,8 +79,18 @@ func _to_string() -> String: ## See [SaveGame]. func save_to_dict() -> Dictionary: - return {} + var result := {} + SaveGame.save_resource_property_into_dict(self, result, "hull") + SaveGame.save_resource_property_into_dict(self, result, "battery") + SaveGame.save_resource_property_into_dict(self, result, "shield") + SaveGame.save_resource_property_into_dict(self, result, "hyperdrive") + SaveGame.save_resource_property_into_dict(self, result, "cargo_hold") + return result ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: - pass + SaveGame.load_resource_property_from_dict(self, dict, "hull") + SaveGame.load_resource_property_from_dict(self, dict, "battery") + SaveGame.load_resource_property_from_dict(self, dict, "shield") + SaveGame.load_resource_property_from_dict(self, dict, "hyperdrive") + SaveGame.load_resource_property_from_dict(self, dict, "cargo_hold") diff --git a/utils/save_game.gd b/utils/save_game.gd index 88799f73..3106c433 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -94,3 +94,18 @@ static func load_tree_from_dict(scene_tree: SceneTree, dict: Dictionary) -> void continue node.call("load_from_dict", save_dict) + +static func load_resource_property_from_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: + var resource: SaveableResource = object.get(property_name) + if not resource: + return + + var value: Dictionary = dict[property_name] + resource.load_from_dict(value) + +static func save_resource_property_into_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: + var resource: SaveableResource = object.get(property_name) + if not resource: + return + + dict[property_name] = resource.save_to_dict() From eea48797017d40448f35aed34d6404624e84a8eb Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:25:55 +0100 Subject: [PATCH 17/46] Add physics properties to `Ship` serialization --- ships/ship.gd | 8 ++++++++ utils/save_game.gd | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/ships/ship.gd b/ships/ship.gd index 6446e4c9..0c979734 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -85,6 +85,10 @@ func save_to_dict() -> Dictionary: SaveGame.save_resource_property_into_dict(self, result, "shield") SaveGame.save_resource_property_into_dict(self, result, "hyperdrive") SaveGame.save_resource_property_into_dict(self, result, "cargo_hold") + + result["transform"] = SaveGame.serialize_transform(self.transform) + result["linear_velocity"] = SaveGame.serialize_vector3(self.linear_velocity) + result["angular_velocity"] = SaveGame.serialize_vector3(self.angular_velocity) return result ## See [SaveGame]. @@ -94,3 +98,7 @@ func load_from_dict(dict: Dictionary) -> void: SaveGame.load_resource_property_from_dict(self, dict, "shield") SaveGame.load_resource_property_from_dict(self, dict, "hyperdrive") SaveGame.load_resource_property_from_dict(self, dict, "cargo_hold") + + self.transform = SaveGame.deserialize_transform(dict["transform"]) + self.linear_velocity = SaveGame.deserialize_vector3(dict["linear_velocity"]) + self.angular_velocity = SaveGame.deserialize_vector3(dict["angular_velocity"]) diff --git a/utils/save_game.gd b/utils/save_game.gd index 3106c433..2c01a935 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -109,3 +109,41 @@ static func save_resource_property_into_dict(object: Object, dict: Dictionary, p return dict[property_name] = resource.save_to_dict() + +static func serialize_vector3(vector: Vector3) -> Array[float]: + return [vector.x, vector.y, vector.z] + +static func deserialize_vector3(value: Variant) -> Vector3: + var array: Array[float] = value + return Vector3(array[0], array[1], array[2]) + +static func serialize_basis(basis: Basis) -> Array[Array]: + return [ + serialize_vector3(basis.x), + serialize_vector3(basis.y), + serialize_vector3(basis.z), + ] + +static func deserialize_basis(value: Variant) -> Basis: + var array: Array[Array] = value + return Basis( + deserialize_vector3(array[0]), + deserialize_vector3(array[1]), + deserialize_vector3(array[2]), + ) + +static func serialize_transform(transform: Transform3D) -> Dictionary: + return { + "origin": serialize_vector3(transform.origin), + "basis": serialize_basis(transform.basis), + } + +static func deserialize_transform(value: Variant) -> Transform3D: + var dict: Dictionary = value + var basis: Array[Array] = dict["basis"] + var origin: Array[float] = dict["origin"] + + return Transform3D( + deserialize_basis(basis), + deserialize_vector3(origin), + ) From f77cd3ce3be46c469ffd42c9afc40cb7b0c732a1 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:26:02 +0100 Subject: [PATCH 18/46] SaveGame implementation for `Player` --- actors/player.gd | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index 4f9aed2e..82f7288a 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -294,8 +294,12 @@ func _physics_process(_delta: float) -> void: ## See [SaveGame]. func save_to_dict() -> Dictionary: - return {} + var result := {} + SaveGame.save_resource_property_into_dict(self, result, "bank_account") + SaveGame.save_resource_property_into_dict(self, result, "calendar") + return result ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: - pass + SaveGame.load_resource_property_from_dict(self, dict, "bank_account") + SaveGame.load_resource_property_from_dict(self, dict, "calendar") From f47993291b2a53b61b77aa8aef556c008262ce55 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:36:29 +0100 Subject: [PATCH 19/46] Don't need to save `HyperdriveSystem`, now that the `Hyperdrive` is saved --- mechanics/hyperspace/hyperdrive_system.gd | 8 -------- screens/game/game.tscn | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/mechanics/hyperspace/hyperdrive_system.gd b/mechanics/hyperspace/hyperdrive_system.gd index 6960c59a..659c8630 100644 --- a/mechanics/hyperspace/hyperdrive_system.gd +++ b/mechanics/hyperspace/hyperdrive_system.gd @@ -50,11 +50,3 @@ func current_system() -> StarSystem: return null return instance.star_system - -## See [SaveGame]. -func save_to_dict() -> Dictionary: - return {} - -## See [SaveGame]. -func load_from_dict(dict: Dictionary) -> void: - pass diff --git a/screens/game/game.tscn b/screens/game/game.tscn index 28816717..c0be556d 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -182,7 +182,7 @@ battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") hyperdrive = SubResource("Resource_eokwm") -[node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" groups=["saveable"]] +[node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("16_6olqp") [node name="Player" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette" node_paths=PackedStringArray("hyperspace_scene_switcher", "message_log") groups=["saveable"] instance=ExtResource("16_lqltb")] From 8777d89556a6e4c2af811eede44abbad2ed9cf90 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 14:38:36 +0100 Subject: [PATCH 20/46] Make `Player` responsible for configuring `HyperspaceSceneSwitcher` --- actors/player.gd | 3 +++ mechanics/hyperspace/hyperspace_scene_switcher.gd | 4 ++-- screens/game/game.tscn | 10 ++++------ 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index 82f7288a..ce2bcf93 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -65,6 +65,9 @@ const MAX_LANDING_DISTANCE = 2.0 const MAX_LANDING_VELOCITY = 4.0 func _ready() -> void: + self.hyperspace_scene_switcher.calendar = self.calendar + self.hyperspace_scene_switcher.hyperdrive_system = self.ship.hyperdrive_system + self._rigid_body_turner = RigidBodyTurner.new() self._rigid_body_turner.spin_thruster = self.ship.rigid_body_direction.spin_thruster self._rigid_body_turner.battery = self.ship.battery diff --git a/mechanics/hyperspace/hyperspace_scene_switcher.gd b/mechanics/hyperspace/hyperspace_scene_switcher.gd index 55ab5c58..7e4cbb45 100644 --- a/mechanics/hyperspace/hyperspace_scene_switcher.gd +++ b/mechanics/hyperspace/hyperspace_scene_switcher.gd @@ -6,10 +6,10 @@ class_name HyperspaceSceneSwitcher ## This node must contain exactly one [StarSystemInstance] child at all times. ## The player's [HyperdriveSystem]. -@export var hyperdrive_system: HyperdriveSystem +var hyperdrive_system: HyperdriveSystem ## The game [Calendar] to update when jumping, to represent time passing. -@export var calendar: Calendar +var calendar: Calendar ## The approximate number of days that should pass with each hyperspace jump. const HYPERSPACE_APPROXIMATE_TRAVEL_DAYS = 3 diff --git a/screens/game/game.tscn b/screens/game/game.tscn index c0be556d..f451f65b 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -51,9 +51,6 @@ [ext_resource type="Script" path="res://screens/game/hud/time_label.gd" id="45_4kcdo"] [ext_resource type="Script" path="res://mechanics/time/calendar.gd" id="46_cos1s"] -[sub_resource type="Resource" id="Resource_jc1pg"] -script = ExtResource("46_cos1s") - [sub_resource type="Resource" id="Resource_p7sh0"] resource_local_to_scene = true script = ExtResource("14_bcf1o") @@ -113,6 +110,9 @@ currencies = { ExtResource("11_cir2n"): 10000 } +[sub_resource type="Resource" id="Resource_jc1pg"] +script = ExtResource("46_cos1s") + [sub_resource type="QuadMesh" id="QuadMesh_7oe51"] material = ExtResource("7_gr6ce") size = Vector2(50, 50) @@ -160,10 +160,8 @@ texture_margin_bottom = 10.0 [node name="Game" type="Node3D"] -[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." node_paths=PackedStringArray("hyperdrive_system") groups=["saveable"]] +[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." groups=["saveable"]] script = ExtResource("4_yyrkm") -calendar = SubResource("Resource_jc1pg") -hyperdrive_system = NodePath("Sol/PlayerCorvette/HyperdriveSystem") [node name="Sol" parent="HyperspaceSceneSwitcher" instance=ExtResource("17_wjgyf")] From ad80074c311273522a0d63fbccf93b5b3a91f47f Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 15:11:02 +0100 Subject: [PATCH 21/46] Saving/loading for star systems (I think) --- galaxy/star_system/star_system_instance.gd | 4 ++- .../hyperspace/hyperspace_scene_switcher.gd | 35 +++++++++++++++++-- utils/save_game.gd | 6 +++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/galaxy/star_system/star_system_instance.gd b/galaxy/star_system/star_system_instance.gd index e4f46313..ab5a5ddc 100644 --- a/galaxy/star_system/star_system_instance.gd +++ b/galaxy/star_system/star_system_instance.gd @@ -16,8 +16,10 @@ static func star_system_instance_for_node(node: Node) -> StarSystemInstance: ## See [SaveGame]. func save_to_dict() -> Dictionary: + # Nothing to do, we'll reload from the packed scene. return {} ## See [SaveGame]. -func load_from_dict(dict: Dictionary) -> void: +func load_from_dict(_dict: Dictionary) -> void: + # Nothing to do, we'll reload from the packed scene. pass diff --git a/mechanics/hyperspace/hyperspace_scene_switcher.gd b/mechanics/hyperspace/hyperspace_scene_switcher.gd index 7e4cbb45..8e64cc10 100644 --- a/mechanics/hyperspace/hyperspace_scene_switcher.gd +++ b/mechanics/hyperspace/hyperspace_scene_switcher.gd @@ -21,10 +21,13 @@ signal jump_destination_loaded(new_system_instance: StarSystemInstance) var _loaded_system_nodes: Dictionary = {} func _ready() -> void: - var current_system_instance: StarSystemInstance = self.get_child(0) + var current_system_instance := self._get_current_system_instance() var current_system := current_system_instance.star_system self._loaded_system_nodes[current_system.name] = current_system_instance +func _get_current_system_instance() -> StarSystemInstance: + return self.get_child(0) + func start_jump() -> bool: assert(not self.hyperdrive_system.jumping, "Jump already in progress") @@ -70,10 +73,36 @@ func finish_jump() -> void: assert(self.hyperdrive_system.jumping, "No jump in progress") self.hyperdrive_system.jumping = false +## See [SaveGame]. +func before_save() -> void: + # Re-add all systems to the scene tree, so they all get saved. + for system_name: StringName in self._loaded_system_nodes: + var node: Node = self._loaded_system_nodes[system_name] + if not node.is_inside_tree(): + self.add_child(node) + +## See [SaveGame]. +func after_save() -> void: + self._remove_saved_or_loaded_children() + ## See [SaveGame]. func save_to_dict() -> Dictionary: - return {} + var result := {} + result["current_system"] = self._get_current_system_instance().star_system.name + + # Do NOT save the calendar, as this node doesn't own it. + return result ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: - pass + assert(self._get_current_system_instance().star_system.name == dict["current_system"], "Current system mismatch after loading") + +func _remove_saved_or_loaded_children() -> void: + var current_system_instance := self._get_current_system_instance() + + var count := self.get_child_count() + for i in range(count - 1, 0, -1): + var child := self.get_child(i) + self.remove_child(child) + + assert(self._get_current_system_instance() == current_system_instance, "Current system unexpectedly changed after removing children") diff --git a/utils/save_game.gd b/utils/save_game.gd index 2c01a935..8a19f6a8 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -20,6 +20,9 @@ static func save(scene_tree: SceneTree, path: String) -> Error: ## Serializes all [i]saveable[/i] nodes in the scene tree to a JSON-compatible dictionary. static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: + # Hook to allow nodes to prepare for saving. + scene_tree.call_group(SAVEABLE_GROUP, "before_save") + var saveable_nodes := scene_tree.get_nodes_in_group(SAVEABLE_GROUP) var save_dict := {} for node in saveable_nodes: @@ -27,7 +30,8 @@ static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: var node_dict := {_SCENE_FILE_PATH_KEY: node.scene_file_path} node_dict.merge(node.call("save_to_dict") as Dictionary) save_dict[node_path] = node_dict - + + scene_tree.call_group_flags(SceneTree.GROUP_CALL_REVERSE, SAVEABLE_GROUP, "after_save") return save_dict ## Loads saveable nodes from a file into the scene tree, merging with existing nodes. From 79628e4869fe672047c650e2634fc9a985a3c362 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 15:44:27 +0100 Subject: [PATCH 22/46] Load game screen --- screens/main_menu/main_menu.gd | 20 +++++++++--- screens/main_menu/main_menu.tscn | 13 ++++++-- screens/save_games/load_game.gd | 46 ++++++++++++++++++++++++++ screens/save_games/load_game.tscn | 54 +++++++++++++++++++++++++++++++ utils/save_game.gd | 3 ++ 5 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 screens/save_games/load_game.gd create mode 100644 screens/save_games/load_game.tscn diff --git a/screens/main_menu/main_menu.gd b/screens/main_menu/main_menu.gd index fe6dd28b..d1c53828 100644 --- a/screens/main_menu/main_menu.gd +++ b/screens/main_menu/main_menu.gd @@ -1,9 +1,11 @@ extends Control +class_name MainMenu enum ToggleState { - NONE = -1, - SETTINGS = 0, - LICENSES = 1, + NONE, + LOAD_GAME, + SETTINGS, + LICENSES, } # We choose to have a string reference to the game scene here because we need @@ -14,6 +16,9 @@ const MAIN_GAME_SCENE = "res://screens/game/game.tscn" @export var content_container: Container +@export var load_game_button: Button +@export var load_game_scene: PackedScene + @export var settings_button: Button @export var settings_scene: PackedScene @@ -28,11 +33,13 @@ var _current_view: Control = null func _ready() -> void: # TODO: Use a custom Resource for this? self._buttons_by_state = { + ToggleState.LOAD_GAME: self.load_game_button, ToggleState.SETTINGS: self.settings_button, ToggleState.LICENSES: self.licenses_button, } self._scenes_by_state = { + ToggleState.LOAD_GAME: self.load_game_scene, ToggleState.SETTINGS: self.settings_scene, ToggleState.LICENSES: self.licenses_scene, } @@ -54,7 +61,10 @@ func _set_currently_toggled(index: ToggleState) -> void: self._current_view = node func _on_new_game_button_pressed() -> void: - get_tree().change_scene_to_file(MAIN_GAME_SCENE) + self.get_tree().change_scene_to_file(MAIN_GAME_SCENE) + +func _on_load_game_button_pressed() -> void: + self._set_currently_toggled(ToggleState.LOAD_GAME) func _on_settings_button_pressed() -> void: self._set_currently_toggled(ToggleState.SETTINGS) @@ -63,4 +73,4 @@ func _on_licenses_button_pressed() -> void: self._set_currently_toggled(ToggleState.LICENSES) func _on_exit_button_pressed() -> void: - get_tree().quit() + self.get_tree().quit() diff --git a/screens/main_menu/main_menu.tscn b/screens/main_menu/main_menu.tscn index 097f8e71..3d9aa1b3 100644 --- a/screens/main_menu/main_menu.tscn +++ b/screens/main_menu/main_menu.tscn @@ -1,6 +1,7 @@ -[gd_scene load_steps=10 format=3 uid="uid://btc568gwt7fuc"] +[gd_scene load_steps=11 format=3 uid="uid://btc568gwt7fuc"] [ext_resource type="Texture2D" uid="uid://b3edwsa85u8fx" path="res://screens/shared_ui/images/splash_screen.png" id="1_hdxn3"] +[ext_resource type="PackedScene" uid="uid://cbk7pta41y2nh" path="res://screens/save_games/load_game.tscn" id="2_b02o5"] [ext_resource type="Script" path="res://screens/main_menu/main_menu.gd" id="2_dwbyb"] [ext_resource type="PackedScene" uid="uid://210831aopuew" path="res://screens/main_menu/main_menu_button.tscn" id="2_kk5nj"] [ext_resource type="PackedScene" uid="uid://dxfmf433s5ab3" path="res://screens/settings/settings.tscn" id="2_xomwj"] @@ -17,7 +18,7 @@ pressed = true [sub_resource type="Shortcut" id="Shortcut_qfyly"] events = [SubResource("InputEventAction_ceokg")] -[node name="MainMenu" type="Control" node_paths=PackedStringArray("content_container", "settings_button", "licenses_button")] +[node name="MainMenu" type="Control" node_paths=PackedStringArray("content_container", "load_game_button", "settings_button", "licenses_button")] layout_mode = 3 anchors_preset = 15 anchor_right = 1.0 @@ -26,6 +27,8 @@ grow_horizontal = 2 grow_vertical = 2 script = ExtResource("2_dwbyb") content_container = NodePath("PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/ContentContainer") +load_game_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/LoadGameButton") +load_game_scene = ExtResource("2_b02o5") settings_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/SettingsButton") settings_scene = ExtResource("2_xomwj") licenses_button = NodePath("PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/LicensesButton") @@ -80,6 +83,11 @@ theme_override_constants/separation = 36 [node name="NewGameButton" parent="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" instance=ExtResource("2_kk5nj")] layout_mode = 2 +[node name="LoadGameButton" parent="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" instance=ExtResource("2_kk5nj")] +layout_mode = 2 +toggle_mode = true +text = "LOAD GAME" + [node name="SettingsButton" parent="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer" instance=ExtResource("2_kk5nj")] layout_mode = 2 toggle_mode = true @@ -122,6 +130,7 @@ text = "(version number)" script = ExtResource("6_ucu8b") [connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/NewGameButton" to="." method="_on_new_game_button_pressed"] +[connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/LoadGameButton" to="." method="_on_load_game_button_pressed"] [connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/SettingsButton" to="." method="_on_settings_button_pressed"] [connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/LicensesButton" to="." method="_on_licenses_button_pressed"] [connection signal="pressed" from="PanelContainer/MarginContainer/VBoxContainer/MarginContainer/HSplitContainer/CenterContainer/PanelContainer/MarginContainer/VBoxContainer/ExitButton" to="." method="_on_exit_button_pressed"] diff --git a/screens/save_games/load_game.gd b/screens/save_games/load_game.gd new file mode 100644 index 00000000..42937c91 --- /dev/null +++ b/screens/save_games/load_game.gd @@ -0,0 +1,46 @@ +extends Control + +@export var save_game_list: ItemList +@export var load_button: Button + +var _selected_save_game: String = "": + set(value): + _selected_save_game = value + self.load_button.disabled = not value + +func _ready() -> void: + for save_name in self._get_save_game_names(): + self.save_game_list.add_item(save_name) + +func _get_save_game_names() -> Array[String]: + var dir := DirAccess.open(SaveGame.SAVE_GAMES_DIRECTORY) + if not dir: + return [] + + var paths: Array[String] = [] + + dir.list_dir_begin() + var file_name := dir.get_next() + while file_name: + if not dir.current_is_dir() and file_name.ends_with(".json"): + paths.append(file_name.substr(0, file_name.length() - 5)) + + file_name = dir.get_next() + + return paths + +func _on_item_selected(index: int) -> void: + self._selected_save_game = self.save_game_list.get_item_text(index) + +func _on_empty_click(_at_position: Vector2, mouse_button_index: int) -> void: + if mouse_button_index != 0: + return + + self.save_game_list.deselect_all() + self._selected_save_game = "" + +func _on_load_pressed() -> void: + var path := SaveGame.SAVE_GAMES_DIRECTORY.path_join(self._selected_save_game + ".json") + var scene_tree := self.get_tree() + scene_tree.change_scene_to_file(MainMenu.MAIN_GAME_SCENE) + SaveGame.load(scene_tree, path) diff --git a/screens/save_games/load_game.tscn b/screens/save_games/load_game.tscn new file mode 100644 index 00000000..30aba8c1 --- /dev/null +++ b/screens/save_games/load_game.tscn @@ -0,0 +1,54 @@ +[gd_scene load_steps=2 format=3 uid="uid://cbk7pta41y2nh"] + +[ext_resource type="Script" path="res://screens/save_games/load_game.gd" id="1_lq0b6"] + +[node name="Load Game" type="Control" node_paths=PackedStringArray("save_game_list", "load_button")] +layout_mode = 3 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +script = ExtResource("1_lq0b6") +save_game_list = NodePath("PanelContainer/CenterContainer/VBoxContainer/ScrollContainer/SaveGamesList") +load_button = NodePath("PanelContainer/CenterContainer/VBoxContainer/LoadButton") + +[node name="PanelContainer" type="PanelContainer" parent="."] +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="PanelContainer"] +layout_mode = 2 + +[node name="VBoxContainer" type="VBoxContainer" parent="PanelContainer/CenterContainer"] +layout_mode = 2 + +[node name="Label" type="Label" parent="PanelContainer/CenterContainer/VBoxContainer"] +layout_mode = 2 +text = "Saved games" +uppercase = true + +[node name="ScrollContainer" type="ScrollContainer" parent="PanelContainer/CenterContainer/VBoxContainer"] +custom_minimum_size = Vector2(400, 300) +layout_mode = 2 +size_flags_vertical = 3 + +[node name="SaveGamesList" type="ItemList" parent="PanelContainer/CenterContainer/VBoxContainer/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 3 +auto_height = true + +[node name="LoadButton" type="Button" parent="PanelContainer/CenterContainer/VBoxContainer"] +layout_mode = 2 +size_flags_horizontal = 8 +disabled = true +text = "LOAD" + +[connection signal="empty_clicked" from="PanelContainer/CenterContainer/VBoxContainer/ScrollContainer/SaveGamesList" to="." method="_on_empty_click"] +[connection signal="item_selected" from="PanelContainer/CenterContainer/VBoxContainer/ScrollContainer/SaveGamesList" to="." method="_on_item_selected"] +[connection signal="pressed" from="PanelContainer/CenterContainer/VBoxContainer/LoadButton" to="." method="_on_load_pressed"] diff --git a/utils/save_game.gd b/utils/save_game.gd index 8a19f6a8..a5bcb868 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -3,6 +3,9 @@ class_name SaveGame ## The group name used for nodes that can be saved and loaded. const SAVEABLE_GROUP = "saveable" +## The directory to save and load games to/from. +const SAVE_GAMES_DIRECTORY = "user://save_games/" + ## A private key used to store a node's [member Node.scene_file_path] when saving. const _SCENE_FILE_PATH_KEY = "__scene_file_path" From 5b7eb3cf74b400de322a38a42e8d3be24bb620a6 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 15:45:09 +0100 Subject: [PATCH 23/46] Fix `RigidBodyCargo._ready` --- mechanics/physics/rigid_body_cargo.gd | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mechanics/physics/rigid_body_cargo.gd b/mechanics/physics/rigid_body_cargo.gd index 36f2e0d6..d6ccdce9 100644 --- a/mechanics/physics/rigid_body_cargo.gd +++ b/mechanics/physics/rigid_body_cargo.gd @@ -4,7 +4,16 @@ class_name RigidBodyCargo ## Attaches to a [RigidBody3D] to adjust its mass based on a [CargoHold]. ## The cargo hold. -var cargo_hold: CargoHold +var cargo_hold: CargoHold: + set(value): + if value == cargo_hold: + return + + if cargo_hold: + cargo_hold.changed.disconnect(_on_cargo_changed) + cargo_hold = value + if cargo_hold: + cargo_hold.changed.connect(_on_cargo_changed) @onready var _rigid_body := get_parent() as RigidBody3D @@ -17,8 +26,5 @@ var _added_mass: float = 0.0: _added_mass = value self._rigid_body.mass += _added_mass -func _ready() -> void: - self.cargo_hold.changed.connect(_on_cargo_changed) - func _on_cargo_changed() -> void: self._added_mass = self.cargo_hold.get_mass() From 31828fe24ba72eac47cf5e2dde85c2a09ce832a7 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:17:41 +0100 Subject: [PATCH 24/46] Saving/loading logging --- utils/save_game.gd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/utils/save_game.gd b/utils/save_game.gd index a5bcb868..f076502c 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -19,6 +19,7 @@ static func save(scene_tree: SceneTree, path: String) -> Error: file.store_string(json) file.close() + print("Saved game to: ", path) return OK ## Serializes all [i]saveable[/i] nodes in the scene tree to a JSON-compatible dictionary. @@ -54,6 +55,7 @@ static func load(scene_tree: SceneTree, path: String) -> Error: return ERR_PARSE_ERROR load_tree_from_dict(scene_tree, dict) + print("Loaded game from: ", path) return OK ## Loads saveable nodes from a JSON dictionary into the scene tree, merging with existing nodes. From 689e98670034531478abd23e9b0bbcc04b94f265 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:17:47 +0100 Subject: [PATCH 25/46] Autosave on exit (for now) --- screens/game/exit_dialog/exit_dialog.gd | 1 + 1 file changed, 1 insertion(+) diff --git a/screens/game/exit_dialog/exit_dialog.gd b/screens/game/exit_dialog/exit_dialog.gd index 4c1068de..85b30ba5 100644 --- a/screens/game/exit_dialog/exit_dialog.gd +++ b/screens/game/exit_dialog/exit_dialog.gd @@ -3,6 +3,7 @@ extends ConfirmationDialog @export var main_menu_scene: PackedScene func _on_confirmed() -> void: + SaveGame.save(self.get_tree(), SaveGame.SAVE_GAMES_DIRECTORY.path_join("autosave.json")) get_tree().change_scene_to_packed(self.main_menu_scene) func _on_canceled() -> void: From 097347a8a320c4f6dc45b77d0f521c8e04695824 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:17:57 +0100 Subject: [PATCH 26/46] Don't save properties inherited from `Resource` --- utils/saveable_resource.gd | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/saveable_resource.gd b/utils/saveable_resource.gd index 2e18bdb3..6cc13333 100644 --- a/utils/saveable_resource.gd +++ b/utils/saveable_resource.gd @@ -9,9 +9,15 @@ class_name SaveableResource ## ## The default implementation should be suitable to serialize primitives and other [SaveableResource]s, but can be overridden to serialize properties of other types. Only properties tagged as [constant PROPERTY_USAGE_STORAGE] will be saved. func save_to_dict() -> Dictionary: + var ignore_properties := ClassDB.class_get_property_list(&"Resource").map(func(property: Dictionary) -> String: return property["name"]) + ignore_properties.push_back("script") + var save_dict := {} for property: Dictionary in self.get_property_list(): var property_name: String = property.name + if property_name in ignore_properties: + continue + var usage_flags: PropertyUsageFlags = property.usage if usage_flags & PROPERTY_USAGE_STORAGE == 0: continue From 41e3f2d54761eaa3598e8806cf4a86244d31e7f1 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:18:25 +0100 Subject: [PATCH 27/46] Spurious editor changes --- screens/game/game.tscn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/screens/game/game.tscn b/screens/game/game.tscn index f451f65b..74177e00 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -98,7 +98,7 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_eokwm"] +[sub_resource type="Resource" id="Resource_f4ln0"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 @@ -178,7 +178,7 @@ hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_eokwm") +hyperdrive = SubResource("Resource_f4ln0") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("16_6olqp") From 85dd43b0277dc9e92cfd5ed91f4f2c2571aa065d Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:19:55 +0100 Subject: [PATCH 28/46] Error logging --- screens/game/exit_dialog/exit_dialog.gd | 5 ++++- screens/save_games/load_game.gd | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/screens/game/exit_dialog/exit_dialog.gd b/screens/game/exit_dialog/exit_dialog.gd index 85b30ba5..21860283 100644 --- a/screens/game/exit_dialog/exit_dialog.gd +++ b/screens/game/exit_dialog/exit_dialog.gd @@ -3,7 +3,10 @@ extends ConfirmationDialog @export var main_menu_scene: PackedScene func _on_confirmed() -> void: - SaveGame.save(self.get_tree(), SaveGame.SAVE_GAMES_DIRECTORY.path_join("autosave.json")) + var result := SaveGame.save(self.get_tree(), SaveGame.SAVE_GAMES_DIRECTORY.path_join("autosave.json")) + if result != Error.OK: + push_error("Failed to save game: %s" % result) + get_tree().change_scene_to_packed(self.main_menu_scene) func _on_canceled() -> void: diff --git a/screens/save_games/load_game.gd b/screens/save_games/load_game.gd index 42937c91..be37dad2 100644 --- a/screens/save_games/load_game.gd +++ b/screens/save_games/load_game.gd @@ -43,4 +43,7 @@ func _on_load_pressed() -> void: var path := SaveGame.SAVE_GAMES_DIRECTORY.path_join(self._selected_save_game + ".json") var scene_tree := self.get_tree() scene_tree.change_scene_to_file(MainMenu.MAIN_GAME_SCENE) - SaveGame.load(scene_tree, path) + + var result := SaveGame.load(scene_tree, path) + if result != Error.OK: + push_error("Failed to load game: %s" % result) From e0a67e2baa53d3afc865c55ebeee4da5cfc824b6 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:24:08 +0100 Subject: [PATCH 29/46] Create save game directory if it doesn't exist, simplify callsites --- screens/game/exit_dialog/exit_dialog.gd | 2 +- screens/save_games/load_game.gd | 22 ++--------------- utils/save_game.gd | 32 ++++++++++++++++++++++--- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/screens/game/exit_dialog/exit_dialog.gd b/screens/game/exit_dialog/exit_dialog.gd index 21860283..eccad1bc 100644 --- a/screens/game/exit_dialog/exit_dialog.gd +++ b/screens/game/exit_dialog/exit_dialog.gd @@ -3,7 +3,7 @@ extends ConfirmationDialog @export var main_menu_scene: PackedScene func _on_confirmed() -> void: - var result := SaveGame.save(self.get_tree(), SaveGame.SAVE_GAMES_DIRECTORY.path_join("autosave.json")) + var result := SaveGame.save(self.get_tree(), "autosave") if result != Error.OK: push_error("Failed to save game: %s" % result) diff --git a/screens/save_games/load_game.gd b/screens/save_games/load_game.gd index be37dad2..1aa3091c 100644 --- a/screens/save_games/load_game.gd +++ b/screens/save_games/load_game.gd @@ -9,26 +9,9 @@ var _selected_save_game: String = "": self.load_button.disabled = not value func _ready() -> void: - for save_name in self._get_save_game_names(): + for save_name in SaveGame.get_save_game_names(): self.save_game_list.add_item(save_name) -func _get_save_game_names() -> Array[String]: - var dir := DirAccess.open(SaveGame.SAVE_GAMES_DIRECTORY) - if not dir: - return [] - - var paths: Array[String] = [] - - dir.list_dir_begin() - var file_name := dir.get_next() - while file_name: - if not dir.current_is_dir() and file_name.ends_with(".json"): - paths.append(file_name.substr(0, file_name.length() - 5)) - - file_name = dir.get_next() - - return paths - func _on_item_selected(index: int) -> void: self._selected_save_game = self.save_game_list.get_item_text(index) @@ -40,10 +23,9 @@ func _on_empty_click(_at_position: Vector2, mouse_button_index: int) -> void: self._selected_save_game = "" func _on_load_pressed() -> void: - var path := SaveGame.SAVE_GAMES_DIRECTORY.path_join(self._selected_save_game + ".json") var scene_tree := self.get_tree() scene_tree.change_scene_to_file(MainMenu.MAIN_GAME_SCENE) - var result := SaveGame.load(scene_tree, path) + var result := SaveGame.load(scene_tree, self._selected_save_game) if result != Error.OK: push_error("Failed to load game: %s" % result) diff --git a/utils/save_game.gd b/utils/save_game.gd index f076502c..39747dc8 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -9,8 +9,33 @@ const SAVE_GAMES_DIRECTORY = "user://save_games/" ## A private key used to store a node's [member Node.scene_file_path] when saving. const _SCENE_FILE_PATH_KEY = "__scene_file_path" -## Saves all [i]saveable[/i] nodes in the scene tree to a file at the given path. -static func save(scene_tree: SceneTree, path: String) -> Error: +## Returns the names of all existing save games. +static func get_save_game_names() -> Array[String]: + var dir := DirAccess.open(SaveGame.SAVE_GAMES_DIRECTORY) + if not dir: + return [] + + var paths: Array[String] = [] + + dir.list_dir_begin() + var file_name := dir.get_next() + while file_name: + if not dir.current_is_dir() and file_name.ends_with(".json"): + paths.append(file_name.substr(0, file_name.length() - 5)) + + file_name = dir.get_next() + + return paths + +## Saves all [i]saveable[/i] nodes in the scene tree to a file with the given name. +static func save(scene_tree: SceneTree, name: String) -> Error: + if not DirAccess.dir_exists_absolute(SAVE_GAMES_DIRECTORY): + var error := DirAccess.make_dir_recursive_absolute(SAVE_GAMES_DIRECTORY) + if error != OK: + return error + + var path := SAVE_GAMES_DIRECTORY.path_join(name + ".json") + var save_dict := save_tree_to_dict(scene_tree) var json := JSON.stringify(save_dict, "\t", false) var file := FileAccess.open(path, FileAccess.WRITE) @@ -39,7 +64,8 @@ static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: return save_dict ## Loads saveable nodes from a file into the scene tree, merging with existing nodes. -static func load(scene_tree: SceneTree, path: String) -> Error: +static func load(scene_tree: SceneTree, name: String) -> Error: + var path := SAVE_GAMES_DIRECTORY.path_join(name + ".json") var file := FileAccess.open(path, FileAccess.READ) if not file: return FileAccess.get_open_error() From ef5df7367846fc7108311a36026b05a7f40e9b2e Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:56:30 +0100 Subject: [PATCH 30/46] Debug logging --- utils/save_game.gd | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/utils/save_game.gd b/utils/save_game.gd index 39747dc8..7f7a259b 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -56,6 +56,7 @@ static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: var save_dict := {} for node in saveable_nodes: var node_path := node.get_path() + print("Saving node: ", node_path) var node_dict := {_SCENE_FILE_PATH_KEY: node.scene_file_path} node_dict.merge(node.call("save_to_dict") as Dictionary) save_dict[node_path] = node_dict @@ -86,37 +87,40 @@ static func load(scene_tree: SceneTree, name: String) -> Error: ## Loads saveable nodes from a JSON dictionary into the scene tree, merging with existing nodes. static func load_tree_from_dict(scene_tree: SceneTree, dict: Dictionary) -> void: - var node_paths: Array[NodePath] = [] - dict.keys().assign(node_paths) + var paths := dict.keys().duplicate() # Load nodes in order of depth, to ensure parents are loaded before children. # We'll use this to instantiate any missing nodes along the way. - node_paths.sort_custom(func(a: NodePath, b: NodePath) -> bool: - return a.get_name_count() < b.get_name_count()) + paths.sort_custom(func(a: String, b: String) -> bool: + return NodePath(a).get_name_count() < NodePath(b).get_name_count()) - for node_path in node_paths: - var save_dict: Dictionary = dict[node_path] + for path: String in paths: + print("Loading node: ", path) + + var save_dict: Dictionary = dict[path] save_dict = save_dict.duplicate() var scene_path: String = save_dict.get(_SCENE_FILE_PATH_KEY, "") save_dict.erase(_SCENE_FILE_PATH_KEY) if scene_path: scene_path = scene_path.simplify_path() - if not scene_path.is_absolute_path() or not scene_path.is_valid_filename() \ - or not scene_path.begins_with("res://") or not scene_path.ends_with(".tscn"): - push_warning("Invalid scene path ", scene_path, " for node ", node_path, ", ignoring") + if not scene_path.is_absolute_path() or not scene_path.begins_with("res://") or not scene_path.ends_with(".tscn"): + push_warning("Invalid scene path ", scene_path, " for node ", path, ", ignoring") scene_path = "" + var node_path := NodePath(path) var node := scene_tree.root.get_node_or_null(node_path) if node == null: + print("Instantiating node ", path, " from scene ", scene_path) var parent_path := NodeUtils.get_parent_path(node_path) var parent_node := scene_tree.root.get_node_or_null(parent_path) if not parent_node: - push_error("Could not find parent for node ", node_path, " in order to load it") + push_error("Could not find parent ", parent_path, " for node ", path, " in order to load it") + print("Tree:\n", scene_tree.root.get_tree_string()) continue if not scene_path: - push_error("Node ", node_path, " doesn't have a scene file, so cannot be loaded by instantiation") + push_error("Node ", path, " doesn't have a scene file, so cannot be loaded by instantiation") continue var scene: PackedScene = load(scene_path) @@ -125,7 +129,7 @@ static func load_tree_from_dict(scene_tree: SceneTree, dict: Dictionary) -> void parent_node.add_child(node) if not node.is_in_group(SAVEABLE_GROUP): - push_warning("Loaded node ", node_path, " is not marked as saveable, refusing to load it") + push_warning("Loaded node ", path, " is not marked as saveable, refusing to load it") continue node.call("load_from_dict", save_dict) From ac9c3fa6f236dd2f80a05a4c197507f2c16e3176 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 16:56:43 +0100 Subject: [PATCH 31/46] Auto-select first save on load screen --- screens/save_games/load_game.gd | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/screens/save_games/load_game.gd b/screens/save_games/load_game.gd index 1aa3091c..17016ff2 100644 --- a/screens/save_games/load_game.gd +++ b/screens/save_games/load_game.gd @@ -11,6 +11,10 @@ var _selected_save_game: String = "": func _ready() -> void: for save_name in SaveGame.get_save_game_names(): self.save_game_list.add_item(save_name) + + if self.save_game_list.item_count > 0: + self.save_game_list.select(0) + self._selected_save_game = self.save_game_list.get_item_text(0) func _on_item_selected(index: int) -> void: self._selected_save_game = self.save_game_list.get_item_text(index) From c2b73726b894829371777f1ebcc8e666403987dd Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 17:04:09 +0100 Subject: [PATCH 32/46] Change `SaveGame` into an autoload to support async game loading --- project.godot | 1 + screens/game/exit_dialog/exit_dialog.gd | 2 +- screens/save_games/load_game.gd | 5 +-- utils/save_game.gd | 48 +++++++++++++++---------- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/project.godot b/project.godot index c44bc6ee..750d357b 100644 --- a/project.godot +++ b/project.godot @@ -23,6 +23,7 @@ config/icon="res://screens/shared_ui/images/icon.png" UserPreferences="*res://utils/user_preferences.gd" InputEventBroadcaster="*res://utils/input_event_broadcaster.gd" +SaveGame="*res://utils/save_game.gd" [debug] diff --git a/screens/game/exit_dialog/exit_dialog.gd b/screens/game/exit_dialog/exit_dialog.gd index eccad1bc..86676fdd 100644 --- a/screens/game/exit_dialog/exit_dialog.gd +++ b/screens/game/exit_dialog/exit_dialog.gd @@ -3,7 +3,7 @@ extends ConfirmationDialog @export var main_menu_scene: PackedScene func _on_confirmed() -> void: - var result := SaveGame.save(self.get_tree(), "autosave") + var result := SaveGame.save("autosave") if result != Error.OK: push_error("Failed to save game: %s" % result) diff --git a/screens/save_games/load_game.gd b/screens/save_games/load_game.gd index 17016ff2..656cd024 100644 --- a/screens/save_games/load_game.gd +++ b/screens/save_games/load_game.gd @@ -29,7 +29,4 @@ func _on_empty_click(_at_position: Vector2, mouse_button_index: int) -> void: func _on_load_pressed() -> void: var scene_tree := self.get_tree() scene_tree.change_scene_to_file(MainMenu.MAIN_GAME_SCENE) - - var result := SaveGame.load(scene_tree, self._selected_save_game) - if result != Error.OK: - push_error("Failed to load game: %s" % result) + SaveGame.load_async_after_scene_change(self._selected_save_game) diff --git a/utils/save_game.gd b/utils/save_game.gd index 7f7a259b..d81da9da 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -1,4 +1,4 @@ -class_name SaveGame +extends Node ## The group name used for nodes that can be saved and loaded. const SAVEABLE_GROUP = "saveable" @@ -10,7 +10,7 @@ const SAVE_GAMES_DIRECTORY = "user://save_games/" const _SCENE_FILE_PATH_KEY = "__scene_file_path" ## Returns the names of all existing save games. -static func get_save_game_names() -> Array[String]: +func get_save_game_names() -> Array[String]: var dir := DirAccess.open(SaveGame.SAVE_GAMES_DIRECTORY) if not dir: return [] @@ -28,15 +28,15 @@ static func get_save_game_names() -> Array[String]: return paths ## Saves all [i]saveable[/i] nodes in the scene tree to a file with the given name. -static func save(scene_tree: SceneTree, name: String) -> Error: +func save(filename: String) -> Error: if not DirAccess.dir_exists_absolute(SAVE_GAMES_DIRECTORY): var error := DirAccess.make_dir_recursive_absolute(SAVE_GAMES_DIRECTORY) if error != OK: return error - var path := SAVE_GAMES_DIRECTORY.path_join(name + ".json") + var path := SAVE_GAMES_DIRECTORY.path_join(filename + ".json") - var save_dict := save_tree_to_dict(scene_tree) + var save_dict := self.save_tree_to_dict() var json := JSON.stringify(save_dict, "\t", false) var file := FileAccess.open(path, FileAccess.WRITE) if not file: @@ -48,7 +48,9 @@ static func save(scene_tree: SceneTree, name: String) -> Error: return OK ## Serializes all [i]saveable[/i] nodes in the scene tree to a JSON-compatible dictionary. -static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: +func save_tree_to_dict() -> Dictionary: + var scene_tree := self.get_tree() + # Hook to allow nodes to prepare for saving. scene_tree.call_group(SAVEABLE_GROUP, "before_save") @@ -64,9 +66,17 @@ static func save_tree_to_dict(scene_tree: SceneTree) -> Dictionary: scene_tree.call_group_flags(SceneTree.GROUP_CALL_REVERSE, SAVEABLE_GROUP, "after_save") return save_dict +## Loads a saved game after the scene tree has finished a scene change that is in progress. +func load_async_after_scene_change(filename: String) -> void: + await self.get_tree().node_added + + var result := self.load(filename) + if result != Error.OK: + push_error("Failed to load game: %s" % result) + ## Loads saveable nodes from a file into the scene tree, merging with existing nodes. -static func load(scene_tree: SceneTree, name: String) -> Error: - var path := SAVE_GAMES_DIRECTORY.path_join(name + ".json") +func load(filename: String) -> Error: + var path := SAVE_GAMES_DIRECTORY.path_join(filename + ".json") var file := FileAccess.open(path, FileAccess.READ) if not file: return FileAccess.get_open_error() @@ -81,12 +91,14 @@ static func load(scene_tree: SceneTree, name: String) -> Error: if not dict: return ERR_PARSE_ERROR - load_tree_from_dict(scene_tree, dict) + print("Loading game from: ", path) + self.load_tree_from_dict(dict) print("Loaded game from: ", path) return OK ## Loads saveable nodes from a JSON dictionary into the scene tree, merging with existing nodes. -static func load_tree_from_dict(scene_tree: SceneTree, dict: Dictionary) -> void: +func load_tree_from_dict(dict: Dictionary) -> void: + var scene_tree := self.get_tree() var paths := dict.keys().duplicate() # Load nodes in order of depth, to ensure parents are loaded before children. @@ -134,7 +146,7 @@ static func load_tree_from_dict(scene_tree: SceneTree, dict: Dictionary) -> void node.call("load_from_dict", save_dict) -static func load_resource_property_from_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: +func load_resource_property_from_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: var resource: SaveableResource = object.get(property_name) if not resource: return @@ -142,28 +154,28 @@ static func load_resource_property_from_dict(object: Object, dict: Dictionary, p var value: Dictionary = dict[property_name] resource.load_from_dict(value) -static func save_resource_property_into_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: +func save_resource_property_into_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: var resource: SaveableResource = object.get(property_name) if not resource: return dict[property_name] = resource.save_to_dict() -static func serialize_vector3(vector: Vector3) -> Array[float]: +func serialize_vector3(vector: Vector3) -> Array[float]: return [vector.x, vector.y, vector.z] -static func deserialize_vector3(value: Variant) -> Vector3: +func deserialize_vector3(value: Variant) -> Vector3: var array: Array[float] = value return Vector3(array[0], array[1], array[2]) -static func serialize_basis(basis: Basis) -> Array[Array]: +func serialize_basis(basis: Basis) -> Array[Array]: return [ serialize_vector3(basis.x), serialize_vector3(basis.y), serialize_vector3(basis.z), ] -static func deserialize_basis(value: Variant) -> Basis: +func deserialize_basis(value: Variant) -> Basis: var array: Array[Array] = value return Basis( deserialize_vector3(array[0]), @@ -171,13 +183,13 @@ static func deserialize_basis(value: Variant) -> Basis: deserialize_vector3(array[2]), ) -static func serialize_transform(transform: Transform3D) -> Dictionary: +func serialize_transform(transform: Transform3D) -> Dictionary: return { "origin": serialize_vector3(transform.origin), "basis": serialize_basis(transform.basis), } -static func deserialize_transform(value: Variant) -> Transform3D: +func deserialize_transform(value: Variant) -> Transform3D: var dict: Dictionary = value var basis: Array[Array] = dict["basis"] var origin: Array[float] = dict["origin"] From a4a05898f047ad4324759cb0cdc3e09a1c8eb5e0 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 17:05:58 +0100 Subject: [PATCH 33/46] Only try loading properties that were saved --- utils/saveable_resource.gd | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/utils/saveable_resource.gd b/utils/saveable_resource.gd index 6cc13333..8c8ac165 100644 --- a/utils/saveable_resource.gd +++ b/utils/saveable_resource.gd @@ -44,12 +44,16 @@ func save_to_dict() -> Dictionary: ## ## The default implementation should be suitable to load primitives and other [SaveableResource]s, but can be overridden to load properties of other types. Only properties tagged as [constant PROPERTY_USAGE_STORAGE] will be loaded. func load_from_dict(dict: Dictionary) -> void: + var properties_by_name := {} for property: Dictionary in self.get_property_list(): - var property_name: String = property.name - if not dict.has(property_name): - push_warning("load_from_dict: Missing property ", property_name) + properties_by_name[property.name] = property + + for property_name: String in dict: + if not properties_by_name.has(property_name): + push_error("load_from_dict: Saved property ", property_name, " not found on object") continue + var property: Dictionary = properties_by_name[property_name] var usage_flags: PropertyUsageFlags = property.usage if usage_flags & PROPERTY_USAGE_STORAGE == 0: push_warning("load_from_dict: Ignoring property ", property_name, " not marked for storage") From 04f580655ee7cd2a1b20d5abdf6f05b1c0efdd34 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 17:08:14 +0100 Subject: [PATCH 34/46] Fix geometry deserialization --- utils/save_game.gd | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/utils/save_game.gd b/utils/save_game.gd index d81da9da..ff511e34 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -165,8 +165,12 @@ func serialize_vector3(vector: Vector3) -> Array[float]: return [vector.x, vector.y, vector.z] func deserialize_vector3(value: Variant) -> Vector3: - var array: Array[float] = value - return Vector3(array[0], array[1], array[2]) + var array: Array = value + + var x: float = array[0] + var y: float = array[0] + var z: float = array[0] + return Vector3(x, y, z) func serialize_basis(basis: Basis) -> Array[Array]: return [ @@ -176,7 +180,7 @@ func serialize_basis(basis: Basis) -> Array[Array]: ] func deserialize_basis(value: Variant) -> Basis: - var array: Array[Array] = value + var array: Array = value return Basis( deserialize_vector3(array[0]), deserialize_vector3(array[1]), @@ -191,8 +195,8 @@ func serialize_transform(transform: Transform3D) -> Dictionary: func deserialize_transform(value: Variant) -> Transform3D: var dict: Dictionary = value - var basis: Array[Array] = dict["basis"] - var origin: Array[float] = dict["origin"] + var basis: Array = dict["basis"] + var origin: Array = dict["origin"] return Transform3D( deserialize_basis(basis), From f2e2a3b4e600d8252a996160b4e2dadea42663bc Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 17:22:09 +0100 Subject: [PATCH 35/46] Override saving and loading for dicts of resources --- mechanics/economy/bank_account.gd | 20 ++++++++++++++++++++ mechanics/economy/cargo_hold.gd | 23 +++++++++++++++++++++++ utils/resource_utils.gd | 10 ++++++++++ utils/save_game.gd | 7 +------ utils/saveable_resource.gd | 2 ++ 5 files changed, 56 insertions(+), 6 deletions(-) create mode 100644 utils/resource_utils.gd diff --git a/mechanics/economy/bank_account.gd b/mechanics/economy/bank_account.gd index 7de8bd33..55e138da 100644 --- a/mechanics/economy/bank_account.gd +++ b/mechanics/economy/bank_account.gd @@ -43,3 +43,23 @@ func withdraw_exactly(currency: Currency, amount: float, allow_negative: bool = self.emit_changed() return true + +# Overridden because dictionaries of resources do not serialize correctly. +func save_to_dict() -> Dictionary: + var saved_currencies := {} + for currency: Currency in self.currencies: + saved_currencies[currency.resource_path] = self.currencies[currency] + + return { + "currencies": saved_currencies, + } + +func load_from_dict(dict: Dictionary) -> void: + self.currencies.clear() + + var saved_currencies: Dictionary = dict["currencies"] + for currency_path: String in saved_currencies: + var currency: Currency = ResourceUtils.safe_load_resource(currency_path, "tres") + self.currencies[currency] = saved_currencies[currency_path] + + self.emit_changed() diff --git a/mechanics/economy/cargo_hold.gd b/mechanics/economy/cargo_hold.gd index bb6e1fb9..082f4d40 100644 --- a/mechanics/economy/cargo_hold.gd +++ b/mechanics/economy/cargo_hold.gd @@ -92,3 +92,26 @@ func remove_exactly(commodity: Commodity, amount: int) -> bool: self.emit_changed() return true + +# Overridden because dictionaries of resources do not serialize correctly. +func save_to_dict() -> Dictionary: + var result := {} + result["max_volume"] = self.max_volume + + var saved_commodities := {} + for commodity: Commodity in self.commodities: + saved_commodities[commodity.resource_path] = self.commodities[commodity] + + result["commodities"] = saved_commodities + return result + +func load_from_dict(dict: Dictionary) -> void: + self.max_volume = dict["max_volume"] + self.commodities.clear() + + var saved_commodities: Dictionary = dict["commodities"] + for commodity_path: String in saved_commodities: + var commodity: Commodity = ResourceUtils.safe_load_resource(commodity_path, "tres") + self.commodities[commodity] = saved_commodities[commodity_path] + + self.emit_changed() diff --git a/utils/resource_utils.gd b/utils/resource_utils.gd new file mode 100644 index 00000000..935a206f --- /dev/null +++ b/utils/resource_utils.gd @@ -0,0 +1,10 @@ +class_name ResourceUtils + +## Attempts to load a resource from a path, restricting the loading to the game's bundle, to avoid dynamic code injection from the user's filesystem. +static func safe_load_resource(path: String, type: String) -> Resource: + path = path.simplify_path() + if not path.is_absolute_path() or not path.begins_with("res://") or not path.ends_with(".%s" % type): + push_warning("Invalid resource path ", path, ", ignoring") + return null + + return load(path) diff --git a/utils/save_game.gd b/utils/save_game.gd index ff511e34..2dc4ac41 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -114,11 +114,6 @@ func load_tree_from_dict(dict: Dictionary) -> void: var scene_path: String = save_dict.get(_SCENE_FILE_PATH_KEY, "") save_dict.erase(_SCENE_FILE_PATH_KEY) - if scene_path: - scene_path = scene_path.simplify_path() - if not scene_path.is_absolute_path() or not scene_path.begins_with("res://") or not scene_path.ends_with(".tscn"): - push_warning("Invalid scene path ", scene_path, " for node ", path, ", ignoring") - scene_path = "" var node_path := NodePath(path) var node := scene_tree.root.get_node_or_null(node_path) @@ -135,7 +130,7 @@ func load_tree_from_dict(dict: Dictionary) -> void: push_error("Node ", path, " doesn't have a scene file, so cannot be loaded by instantiation") continue - var scene: PackedScene = load(scene_path) + var scene: PackedScene = ResourceUtils.safe_load_resource(scene_path, "tscn") node = scene.instantiate() node.add_to_group(SAVEABLE_GROUP) parent_node.add_child(node) diff --git a/utils/saveable_resource.gd b/utils/saveable_resource.gd index 8c8ac165..e325f0c9 100644 --- a/utils/saveable_resource.gd +++ b/utils/saveable_resource.gd @@ -78,3 +78,5 @@ func load_from_dict(dict: Dictionary) -> void: _: self.set(property_name, value) + + self.emit_changed() From 1328b541fc5367b49e62420e7e2e15b7024962d1 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 17:25:49 +0100 Subject: [PATCH 36/46] Fix vector3 deserialization --- utils/save_game.gd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/save_game.gd b/utils/save_game.gd index 2dc4ac41..b80debbb 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -163,8 +163,8 @@ func deserialize_vector3(value: Variant) -> Vector3: var array: Array = value var x: float = array[0] - var y: float = array[0] - var z: float = array[0] + var y: float = array[1] + var z: float = array[2] return Vector3(x, y, z) func serialize_basis(basis: Basis) -> Array[Array]: From e4c165e501831296a813d8228062a9c9b7f21010 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 19:17:57 +0100 Subject: [PATCH 37/46] `jump_destination_loaded` signals got disconnected somehow --- screens/game/game.tscn | 2 ++ 1 file changed, 2 insertions(+) diff --git a/screens/game/game.tscn b/screens/game/game.tscn index 74177e00..e3f39a40 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -625,6 +625,8 @@ text = "30 FPS horizontal_alignment = 2 script = ExtResource("37_e11x7") +[connection signal="jump_destination_loaded" from="HyperspaceSceneSwitcher" to="HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player" method="_on_jump_destination_loaded"] +[connection signal="jump_destination_loaded" from="HyperspaceSceneSwitcher" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/SystemName" method="_on_jump_destination_loaded"] [connection signal="jump_destination_changed" from="HyperspaceSceneSwitcher/Sol/PlayerCorvette/HyperdriveSystem" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/PanelContainer/VBoxContainer/JumpDestinationName" method="_on_jump_destination_changed"] [connection signal="jumping_changed" from="HyperspaceSceneSwitcher/Sol/PlayerCorvette/HyperdriveSystem" to="MainCameraTransform/MainCamera/HyperspaceEffect" method="_on_jumping_changed"] [connection signal="hull_changed" from="HyperspaceSceneSwitcher/Sol/PlayerCorvette/Player" to="InGameGUI/MarginContainer/HBoxContainer/Sidebar/PlayerVitalsContainer/VBoxContainer/PlayerVitals" method="_on_player_hull_changed"] From 81f3be2139de64ea931b98a60212195054948d4c Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 19:42:33 +0100 Subject: [PATCH 38/46] Give `HyperspaceSceneSwitcher` a direct reference to the player --- actors/player.gd | 3 -- .../hyperspace/hyperspace_scene_switcher.gd | 36 ++++++++++--------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index ce2bcf93..82f7288a 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -65,9 +65,6 @@ const MAX_LANDING_DISTANCE = 2.0 const MAX_LANDING_VELOCITY = 4.0 func _ready() -> void: - self.hyperspace_scene_switcher.calendar = self.calendar - self.hyperspace_scene_switcher.hyperdrive_system = self.ship.hyperdrive_system - self._rigid_body_turner = RigidBodyTurner.new() self._rigid_body_turner.spin_thruster = self.ship.rigid_body_direction.spin_thruster self._rigid_body_turner.battery = self.ship.battery diff --git a/mechanics/hyperspace/hyperspace_scene_switcher.gd b/mechanics/hyperspace/hyperspace_scene_switcher.gd index 8e64cc10..e4622007 100644 --- a/mechanics/hyperspace/hyperspace_scene_switcher.gd +++ b/mechanics/hyperspace/hyperspace_scene_switcher.gd @@ -5,11 +5,8 @@ class_name HyperspaceSceneSwitcher ## ## This node must contain exactly one [StarSystemInstance] child at all times. -## The player's [HyperdriveSystem]. -var hyperdrive_system: HyperdriveSystem - -## The game [Calendar] to update when jumping, to represent time passing. -var calendar: Calendar +## The player. +@export var player: Player ## The approximate number of days that should pass with each hyperspace jump. const HYPERSPACE_APPROXIMATE_TRAVEL_DAYS = 3 @@ -17,10 +14,15 @@ const HYPERSPACE_APPROXIMATE_TRAVEL_DAYS = 3 ## Fires when the hyperspace destination has been loaded and added to the current scene, but before the full visual effect has finished. signal jump_destination_loaded(new_system_instance: StarSystemInstance) +## The player's [HyperdriveSystem]. +var _hyperdrive_system: HyperdriveSystem + ## Used to keep systems around in memory, so their state is remembered. var _loaded_system_nodes: Dictionary = {} func _ready() -> void: + self._hyperdrive_system = self.player.ship.hyperdrive_system + var current_system_instance := self._get_current_system_instance() var current_system := current_system_instance.star_system self._loaded_system_nodes[current_system.name] = current_system_instance @@ -29,24 +31,24 @@ func _get_current_system_instance() -> StarSystemInstance: return self.get_child(0) func start_jump() -> bool: - assert(not self.hyperdrive_system.jumping, "Jump already in progress") + assert(not self._hyperdrive_system.jumping, "Jump already in progress") - if not self.hyperdrive_system.hyperdrive.consume_for_jump(): + if not self._hyperdrive_system.hyperdrive.consume_for_jump(): return false - if not self._loaded_system_nodes.has(self.hyperdrive_system.jump_destination.name): - ResourceLoader.load_threaded_request(self.hyperdrive_system.jump_destination.scene_path) + if not self._loaded_system_nodes.has(self._hyperdrive_system.jump_destination.name): + ResourceLoader.load_threaded_request(self._hyperdrive_system.jump_destination.scene_path) - self.hyperdrive_system.jumping = true + self._hyperdrive_system.jumping = true return true func load_jump_destination() -> void: - assert(self.hyperdrive_system.jumping, "Expected a jump to be in progress") + assert(self._hyperdrive_system.jumping, "Expected a jump to be in progress") - var destination := self.hyperdrive_system.jump_destination + var destination := self._hyperdrive_system.jump_destination assert(destination, "Expected to have a jump destination") - var player_ship := self.hyperdrive_system.ship + var player_ship := self._hyperdrive_system.ship var previous_system_instance := StarSystemInstance.star_system_instance_for_node(player_ship) previous_system_instance.remove_child(player_ship) self.remove_child(previous_system_instance) @@ -64,14 +66,14 @@ func load_jump_destination() -> void: star_system_instance.add_child(player_ship) self.add_child(star_system_instance) - calendar.pass_approximate_days(HYPERSPACE_APPROXIMATE_TRAVEL_DAYS) + self.player.calendar.pass_approximate_days(HYPERSPACE_APPROXIMATE_TRAVEL_DAYS) - self.hyperdrive_system.jump_destination = null + self._hyperdrive_system.jump_destination = null self.jump_destination_loaded.emit(star_system_instance) func finish_jump() -> void: - assert(self.hyperdrive_system.jumping, "No jump in progress") - self.hyperdrive_system.jumping = false + assert(self._hyperdrive_system.jumping, "No jump in progress") + self._hyperdrive_system.jumping = false ## See [SaveGame]. func before_save() -> void: From 0c8e92cbf16b56115554d26a565ba128b663a337 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 20:24:53 +0100 Subject: [PATCH 39/46] WIP player loading hacks --- actors/player.gd | 14 +++-- .../hyperspace/hyperspace_scene_switcher.gd | 62 ++++++++++++++++--- screens/game/game.tscn | 7 ++- utils/save_game.gd | 10 ++- 4 files changed, 74 insertions(+), 19 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index 82f7288a..8c78c04f 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -74,18 +74,22 @@ func _ready() -> void: self.ship.hull.changed.connect(_on_hull_changed) self.ship.hull.hull_destroyed.connect(_on_hull_destroyed) - self.ship.shield.changed.connect(_on_shield_changed) self.ship.battery.changed.connect(_on_power_changed) - self.ship.hyperdrive.changed.connect(_on_hyperdrive_changed) self.ship.targeting_system.target_changed.connect(_on_target_changed) InputEventBroadcaster.input_event.connect(_on_broadcasted_input_event) # Initial notifications so the UI can update. self._on_hull_changed() - self._on_shield_changed() self._on_power_changed() - self._on_hyperdrive_changed() + + if self.ship.shield: + self.ship.shield.changed.connect(_on_shield_changed) + self._on_shield_changed() + + if self.ship.hyperdrive: + self._on_hyperdrive_changed() + self.ship.hyperdrive.changed.connect(_on_hyperdrive_changed) func _on_hull_changed() -> void: self.hull_changed.emit(self, self.ship.hull) @@ -252,7 +256,7 @@ func _absolute_input_direction() -> Vector3: return Vector3(input_direction.x, 0, input_direction.y) func _physics_process(_delta: float) -> void: - if self.ship.hyperdrive_system.jumping: + if not self.ship.hyperdrive_system or self.ship.hyperdrive_system.jumping: return if Input.is_action_pressed("jump"): diff --git a/mechanics/hyperspace/hyperspace_scene_switcher.gd b/mechanics/hyperspace/hyperspace_scene_switcher.gd index e4622007..259a02db 100644 --- a/mechanics/hyperspace/hyperspace_scene_switcher.gd +++ b/mechanics/hyperspace/hyperspace_scene_switcher.gd @@ -20,6 +20,9 @@ var _hyperdrive_system: HyperdriveSystem ## Used to keep systems around in memory, so their state is remembered. var _loaded_system_nodes: Dictionary = {} +## Used only when loading the game, to remember what the current system is. +var _current_system_after_load: String = "" + func _ready() -> void: self._hyperdrive_system = self.player.ship.hyperdrive_system @@ -85,7 +88,14 @@ func before_save() -> void: ## See [SaveGame]. func after_save() -> void: - self._remove_saved_or_loaded_children() + var current_system_instance := self._get_current_system_instance() + + var count := self.get_child_count() + for i in range(count - 1, 0, -1): + var child := self.get_child(i) + self.remove_child(child) + + assert(self._get_current_system_instance() == current_system_instance, "Current system unexpectedly changed after removing children") ## See [SaveGame]. func save_to_dict() -> Dictionary: @@ -97,14 +107,46 @@ func save_to_dict() -> Dictionary: ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: - assert(self._get_current_system_instance().star_system.name == dict["current_system"], "Current system mismatch after loading") + self._current_system_after_load = dict["current_system"] -func _remove_saved_or_loaded_children() -> void: - var current_system_instance := self._get_current_system_instance() +## See [SaveGame]. +func after_load() -> void: + self._fixup_player() + self._fixup_current_system() - var count := self.get_child_count() - for i in range(count - 1, 0, -1): - var child := self.get_child(i) - self.remove_child(child) - - assert(self._get_current_system_instance() == current_system_instance, "Current system unexpectedly changed after removing children") +## Replaces any [Player] node that was instantiated during load with the one we have a direct reference to. +## +## This is necessary because there are many connections directly to/from the Player object, configured in the editor, and we also want to make sure it has all of its customized sub-nodes as created there too. +func _fixup_player() -> void: + var player_nodes := self.find_children("*", "Player", true, false) + assert(player_nodes.size() <= 2, "Expected no more than two player nodes after loading") + player_nodes.erase(self.player) + + if not player_nodes: + return + + var player_node: Player = player_nodes[0] + print("Replacing loaded player ", player_node.get_path(), " with ", self.player.get_path()) + + var parent := player_node.ship.get_parent() + self.player.ship.reparent(parent) + parent.move_child(self.player.ship, player_node.ship.get_index()) + + # HACK: We have to re-update the player's properties, since the node we're removing is the one that actually loaded this data from disk. + self.player.load_from_dict(player_node.save_to_dict()) + self.player.ship.load_from_dict(player_node.ship.save_to_dict()) + + player_node.ship.queue_free() + +func _fixup_current_system() -> void: + assert(self._current_system_after_load, "Expected current system to be set after load") + + self._loaded_system_nodes.clear() + for star_system_instance: StarSystemInstance in self.get_children(): + var star_system := star_system_instance.star_system + self._loaded_system_nodes[star_system.name] = star_system_instance + + if star_system.name != self._current_system_after_load: + self.remove_child(star_system_instance) + + assert(self._get_current_system_instance().star_system.name == self._current_system_after_load, "Current system mismatch after loading") diff --git a/screens/game/game.tscn b/screens/game/game.tscn index e3f39a40..9d3f71c0 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -98,7 +98,7 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_f4ln0"] +[sub_resource type="Resource" id="Resource_vastl"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 @@ -160,8 +160,9 @@ texture_margin_bottom = 10.0 [node name="Game" type="Node3D"] -[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." groups=["saveable"]] +[node name="HyperspaceSceneSwitcher" type="Node3D" parent="." node_paths=PackedStringArray("player") groups=["saveable"]] script = ExtResource("4_yyrkm") +player = NodePath("Sol/PlayerCorvette/Player") [node name="Sol" parent="HyperspaceSceneSwitcher" instance=ExtResource("17_wjgyf")] @@ -178,7 +179,7 @@ hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_f4ln0") +hyperdrive = SubResource("Resource_vastl") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("16_6olqp") diff --git a/utils/save_game.gd b/utils/save_game.gd index b80debbb..564a507b 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -99,6 +99,8 @@ func load(filename: String) -> Error: ## Loads saveable nodes from a JSON dictionary into the scene tree, merging with existing nodes. func load_tree_from_dict(dict: Dictionary) -> void: var scene_tree := self.get_tree() + scene_tree.call_group(SAVEABLE_GROUP, "before_load") + var paths := dict.keys().duplicate() # Load nodes in order of depth, to ensure parents are loaded before children. @@ -123,7 +125,6 @@ func load_tree_from_dict(dict: Dictionary) -> void: var parent_node := scene_tree.root.get_node_or_null(parent_path) if not parent_node: push_error("Could not find parent ", parent_path, " for node ", path, " in order to load it") - print("Tree:\n", scene_tree.root.get_tree_string()) continue if not scene_path: @@ -132,6 +133,7 @@ func load_tree_from_dict(dict: Dictionary) -> void: var scene: PackedScene = ResourceUtils.safe_load_resource(scene_path, "tscn") node = scene.instantiate() + node.name = node_path.get_name(node_path.get_name_count() - 1) node.add_to_group(SAVEABLE_GROUP) parent_node.add_child(node) @@ -140,6 +142,12 @@ func load_tree_from_dict(dict: Dictionary) -> void: continue node.call("load_from_dict", save_dict) + + self._call_after_load.call_deferred() + +func _call_after_load() -> void: + var scene_tree := self.get_tree() + scene_tree.call_group_flags(SceneTree.GROUP_CALL_REVERSE, SAVEABLE_GROUP, "after_load") func load_resource_property_from_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: var resource: SaveableResource = object.get(property_name) From a2f161fa8df889863ee49cf333045e3b3e562de8 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 20:27:38 +0100 Subject: [PATCH 40/46] Fix `Calendar` serialization --- mechanics/time/calendar.gd | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mechanics/time/calendar.gd b/mechanics/time/calendar.gd index 9338af39..565c1fe5 100644 --- a/mechanics/time/calendar.gd +++ b/mechanics/time/calendar.gd @@ -64,3 +64,12 @@ func get_gst() -> String: var millicycles := int((current_cycle - floorf(current_cycle)) * 1000) return "%03d.%03d.%03d GST" % [kilocycles, cycles, millicycles] + +func save_to_dict() -> Dictionary: + var result := {} + result["cycle"] = self.get_current_cycle() + return result + +func load_from_dict(dict: Dictionary) -> void: + self._calendar_start_ticks_msec = Time.get_ticks_msec() + self._base_cycle = dict["cycle"] From 828dde8fee507bc0eac398a8918b06588e8b7325 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 20:32:58 +0100 Subject: [PATCH 41/46] Fix player missing a `Calendar` --- actors/player.gd | 2 ++ actors/player.tscn | 7 ++++++- screens/game/game.tscn | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index 8c78c04f..c6517ebb 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -301,9 +301,11 @@ func save_to_dict() -> Dictionary: var result := {} SaveGame.save_resource_property_into_dict(self, result, "bank_account") SaveGame.save_resource_property_into_dict(self, result, "calendar") + print("Saved player to dict: ", result) return result ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: + print("Loading player from dict: ", dict) SaveGame.load_resource_property_from_dict(self, dict, "bank_account") SaveGame.load_resource_property_from_dict(self, dict, "calendar") diff --git a/actors/player.tscn b/actors/player.tscn index 1fc72537..dc85fa3c 100644 --- a/actors/player.tscn +++ b/actors/player.tscn @@ -1,19 +1,24 @@ -[gd_scene load_steps=6 format=3 uid="uid://cxlg0yj8cjbrf"] +[gd_scene load_steps=8 format=3 uid="uid://cxlg0yj8cjbrf"] [ext_resource type="Script" path="res://actors/player.gd" id="1_qxs3b"] [ext_resource type="PackedScene" uid="uid://d1kbmfvjs6nrv" path="res://screens/landing/landing.tscn" id="2_2bp8c"] [ext_resource type="AudioStream" uid="uid://b5h867m6j0kb2" path="res://screens/landing/audio/LandingGear.ogg" id="2_lqyst"] [ext_resource type="Script" path="res://mechanics/economy/bank_account.gd" id="3_0316y"] +[ext_resource type="Script" path="res://mechanics/time/calendar.gd" id="4_x2dfd"] [sub_resource type="Resource" id="Resource_bycam"] script = ExtResource("3_0316y") currencies = {} +[sub_resource type="Resource" id="Resource_8aheh"] +script = ExtResource("4_x2dfd") + [node name="Player" type="Node3D" node_paths=PackedStringArray("takeoff_sound")] script = ExtResource("1_qxs3b") landing_scene = ExtResource("2_2bp8c") takeoff_sound = NodePath("TakeoffSound") bank_account = SubResource("Resource_bycam") +calendar = SubResource("Resource_8aheh") [node name="MinimapCameraRemoteTransform" type="RemoteTransform3D" parent="."] update_rotation = false diff --git a/screens/game/game.tscn b/screens/game/game.tscn index 9d3f71c0..22541864 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -98,7 +98,7 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_vastl"] +[sub_resource type="Resource" id="Resource_s00m3"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 @@ -179,7 +179,7 @@ hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_vastl") +hyperdrive = SubResource("Resource_s00m3") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("16_6olqp") From d1e2dc703b5ca8ba1787041dba49252a8deb2601 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 20:39:04 +0100 Subject: [PATCH 42/46] Logging --- actors/player.gd | 2 -- ships/ship.gd | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index c6517ebb..8c78c04f 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -301,11 +301,9 @@ func save_to_dict() -> Dictionary: var result := {} SaveGame.save_resource_property_into_dict(self, result, "bank_account") SaveGame.save_resource_property_into_dict(self, result, "calendar") - print("Saved player to dict: ", result) return result ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: - print("Loading player from dict: ", dict) SaveGame.load_resource_property_from_dict(self, dict, "bank_account") SaveGame.load_resource_property_from_dict(self, dict, "calendar") diff --git a/ships/ship.gd b/ships/ship.gd index 0c979734..a03dae8f 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -89,10 +89,12 @@ func save_to_dict() -> Dictionary: result["transform"] = SaveGame.serialize_transform(self.transform) result["linear_velocity"] = SaveGame.serialize_vector3(self.linear_velocity) result["angular_velocity"] = SaveGame.serialize_vector3(self.angular_velocity) + print("Saved ship to dict: ", result) return result ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: + print("Loading ship from dict: ", dict) SaveGame.load_resource_property_from_dict(self, dict, "hull") SaveGame.load_resource_property_from_dict(self, dict, "battery") SaveGame.load_resource_property_from_dict(self, dict, "shield") From d5906c85717299b9f0bbb90895bbd32c8032fad2 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 21:01:57 +0100 Subject: [PATCH 43/46] Better solution to player reloading: just save at its initial node path --- actors/player.gd | 11 +++++++ .../hyperspace/hyperspace_scene_switcher.gd | 30 +++++-------------- screens/game/game.tscn | 4 +-- ships/ship.gd | 3 ++ utils/save_game.gd | 5 +++- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/actors/player.gd b/actors/player.gd index 8c78c04f..4f1568d6 100644 --- a/actors/player.gd +++ b/actors/player.gd @@ -50,6 +50,9 @@ var landing_target: PlanetInstance = null: if landing_target: landing_target.targeted_by_player = true +## Used to save and restore the player object correctly across launches. +var save_node_path_override: NodePath + ## Created to turn the [Ship] when using the relative control scheme. var _rigid_body_turner: RigidBodyTurner @@ -65,6 +68,10 @@ const MAX_LANDING_DISTANCE = 2.0 const MAX_LANDING_VELOCITY = 4.0 func _ready() -> void: + if not self.save_node_path_override: + self.save_node_path_override = self.get_path() + self.ship.save_node_path_override = self.ship.get_path() + self._rigid_body_turner = RigidBodyTurner.new() self._rigid_body_turner.spin_thruster = self.ship.rigid_body_direction.spin_thruster self._rigid_body_turner.battery = self.ship.battery @@ -108,6 +115,10 @@ func _on_target_changed(targeting_system: TargetingSystem) -> void: self.target_changed.emit(self, targeting_system.target) func _on_jump_destination_loaded(_new_system_instance: StarSystemInstance) -> void: + if not self.ship.hyperdrive_system.jumping: + # A bit of a hack to ignore this notification when reloading from a saved game, while allowing it to propagate to other nodes (e.g., UI). + return + self._reset_velocity() self.ship.position = MathUtils.random_unit_vector() * HYPERSPACE_ARRIVAL_RADIUS self.ship.targeting_system.target = null diff --git a/mechanics/hyperspace/hyperspace_scene_switcher.gd b/mechanics/hyperspace/hyperspace_scene_switcher.gd index 259a02db..70ecec54 100644 --- a/mechanics/hyperspace/hyperspace_scene_switcher.gd +++ b/mechanics/hyperspace/hyperspace_scene_switcher.gd @@ -111,33 +111,17 @@ func load_from_dict(dict: Dictionary) -> void: ## See [SaveGame]. func after_load() -> void: - self._fixup_player() self._fixup_current_system() + self._fixup_player() -## Replaces any [Player] node that was instantiated during load with the one we have a direct reference to. -## -## This is necessary because there are many connections directly to/from the Player object, configured in the editor, and we also want to make sure it has all of its customized sub-nodes as created there too. -func _fixup_player() -> void: - var player_nodes := self.find_children("*", "Player", true, false) - assert(player_nodes.size() <= 2, "Expected no more than two player nodes after loading") - player_nodes.erase(self.player) - - if not player_nodes: - return - - var player_node: Player = player_nodes[0] - print("Replacing loaded player ", player_node.get_path(), " with ", self.player.get_path()) - - var parent := player_node.ship.get_parent() - self.player.ship.reparent(parent) - parent.move_child(self.player.ship, player_node.ship.get_index()) - - # HACK: We have to re-update the player's properties, since the node we're removing is the one that actually loaded this data from disk. - self.player.load_from_dict(player_node.save_to_dict()) - self.player.ship.load_from_dict(player_node.ship.save_to_dict()) + # Update any dependent properties + self.jump_destination_loaded.emit(self._get_current_system_instance()) - player_node.ship.queue_free() +## The [Player] node is always saved to its initial system, so we need to move it to the current system. +func _fixup_player() -> void: + self.player.ship.reparent(self._get_current_system_instance(), false) +## Remove inactive scenes to match the saved state. func _fixup_current_system() -> void: assert(self._current_system_after_load, "Expected current system to be set after load") diff --git a/screens/game/game.tscn b/screens/game/game.tscn index 22541864..dab3b6ca 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -98,7 +98,7 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_s00m3"] +[sub_resource type="Resource" id="Resource_tgh0p"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 @@ -179,7 +179,7 @@ hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_s00m3") +hyperdrive = SubResource("Resource_tgh0p") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("16_6olqp") diff --git a/ships/ship.gd b/ships/ship.gd index a03dae8f..fac52959 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -54,6 +54,9 @@ class_name Ship ## An optional hyperdrive for this ship. @export var hyperdrive: Hyperdrive +## Used to save and restore the player's ship to the same node path across launches. +var save_node_path_override: NodePath + func _ready() -> void: self.combat_object.hull = self.hull self.combat_object.shield = self.shield diff --git a/utils/save_game.gd b/utils/save_game.gd index 564a507b..7bf21022 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -57,7 +57,10 @@ func save_tree_to_dict() -> Dictionary: var saveable_nodes := scene_tree.get_nodes_in_group(SAVEABLE_GROUP) var save_dict := {} for node in saveable_nodes: - var node_path := node.get_path() + var node_path: Variant = node.get("save_node_path_override") + if not node_path: + node_path = node.get_path() + print("Saving node: ", node_path) var node_dict := {_SCENE_FILE_PATH_KEY: node.scene_file_path} node_dict.merge(node.call("save_to_dict") as Dictionary) From 14246349b30e73a942e80622c0d514f2d8de60b7 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 21:02:44 +0100 Subject: [PATCH 44/46] Remove some extraneous logging --- ships/ship.gd | 2 -- 1 file changed, 2 deletions(-) diff --git a/ships/ship.gd b/ships/ship.gd index fac52959..8fd33b3f 100644 --- a/ships/ship.gd +++ b/ships/ship.gd @@ -92,12 +92,10 @@ func save_to_dict() -> Dictionary: result["transform"] = SaveGame.serialize_transform(self.transform) result["linear_velocity"] = SaveGame.serialize_vector3(self.linear_velocity) result["angular_velocity"] = SaveGame.serialize_vector3(self.angular_velocity) - print("Saved ship to dict: ", result) return result ## See [SaveGame]. func load_from_dict(dict: Dictionary) -> void: - print("Loading ship from dict: ", dict) SaveGame.load_resource_property_from_dict(self, dict, "hull") SaveGame.load_resource_property_from_dict(self, dict, "battery") SaveGame.load_resource_property_from_dict(self, dict, "shield") From d3985fdf2e373e0051e0a3303be2157ab76003f5 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 21:09:36 +0100 Subject: [PATCH 45/46] Persist AI state --- actors/ai/ai_navigation.gd | 32 ++++++++++++++++++------- actors/ai/archetypes/pirate.gd | 20 +++++++++++++--- actors/ai/archetypes/pirate.tscn | 2 +- galaxy/star_system/scenes/sol.tscn | 38 ++++++++++-------------------- screens/game/game.tscn | 4 ++-- 5 files changed, 56 insertions(+), 40 deletions(-) diff --git a/actors/ai/ai_navigation.gd b/actors/ai/ai_navigation.gd index 41aef412..f360eaed 100644 --- a/actors/ai/ai_navigation.gd +++ b/actors/ai/ai_navigation.gd @@ -22,12 +22,14 @@ class_name AINavigation @export var auto_start: bool = true ## State machine for this AI. +## +## Note that these values are saved via [SaveGame], so be careful not to break backwards compatibility! enum State { - IDLE, - ROTATING_TO_ACCELERATE, - ACCELERATING_TOWARD_DESTINATION, - ROTATING_TO_DECELERATE, - DECELERATING_TO_STOP, + IDLE = 0, + ROTATING_TO_ACCELERATE = 1, + ACCELERATING_TOWARD_DESTINATION = 2, + ROTATING_TO_DECELERATE = 3, + DECELERATING_TO_STOP = 4, } var navigating: bool: @@ -88,7 +90,7 @@ func _accelerate_toward_destination() -> void: var stopping_distance := self._calculate_stopping_distance(self._rigid_body.linear_velocity.length()) # Account for rotation time in stopping distance - var rotation_time := self._estimate_rotation_time( - self._rigid_body.linear_velocity.normalized()) + var rotation_time := self._estimate_rotation_time(-self._rigid_body.linear_velocity.normalized()) var rotation_distance := self._rigid_body.linear_velocity.length() * rotation_time stopping_distance += rotation_distance @@ -121,7 +123,7 @@ func _decelerate_to_stop() -> void: self.destination_reached.emit(self) func _pointing_in_direction(direction: Vector3) -> bool: - var current_direction := - self._rigid_body.global_transform.basis.z + var current_direction := -self._rigid_body.global_transform.basis.z return current_direction.angle_to(direction) <= self.direction_tolerance func _calculate_stopping_distance(current_velocity: float) -> float: @@ -129,10 +131,24 @@ func _calculate_stopping_distance(current_velocity: float) -> float: return (current_velocity * current_velocity) / (2 * acceleration) func _estimate_rotation_time(target_direction: Vector3) -> float: - var current_direction := - self._rigid_body.global_transform.basis.z + var current_direction := -self._rigid_body.global_transform.basis.z var angle_to_rotate := current_direction.angle_to(target_direction) return angle_to_rotate / self.rigid_body_direction.spin_thruster.turning_rate func set_destination(new_destination: Vector3) -> void: self.destination = new_destination self.navigating = true + +## See [SaveGame]. +func save_to_dict() -> Dictionary: + var result := {} + result["navigating"] = self.navigating + result["state"] = self._state + result["target_direction"] = SaveGame.serialize_vector3(self._target_direction) + return result + +## See [SaveGame]. +func load_from_dict(dict: Dictionary) -> void: + self.navigating = dict["navigating"] + self._state = dict["state"] + self._target_direction = SaveGame.deserialize_vector3(dict["target_direction"]) diff --git a/actors/ai/archetypes/pirate.gd b/actors/ai/archetypes/pirate.gd index c88de3ee..5cdf4f3a 100644 --- a/actors/ai/archetypes/pirate.gd +++ b/actors/ai/archetypes/pirate.gd @@ -27,15 +27,17 @@ class_name Pirate @export var patrol_target_tolerance: float = 1.0 ## State machine for this AI. How it behaves will depend on which state it's in at any given time. +## +## Note that these values are saved via [SaveGame], so be careful not to break backwards compatibility! enum State { ## Patrolling around, waiting to detect a target. - PATROL, + PATROL = 0, ## Engaging a target. - ENGAGE, + ENGAGE = 1, ## Retreating from the target. - RETREAT, + RETREAT = 2, } @onready var _ship := self.get_parent() as Ship @@ -165,3 +167,15 @@ func _on_damage_received() -> void: if attacker != self._ship.targeting_system.target: self._ship.targeting_system.target = attacker self._current_state = State.ENGAGE + +## See [SaveGame]. +func save_to_dict() -> Dictionary: + var result := {} + result["current_state"] = self._current_state + result["patrol_target"] = SaveGame.serialize_vector3(self._patrol_target) + return result + +## See [SaveGame]. +func load_from_dict(dict: Dictionary) -> void: + self._current_state = dict["current_state"] + self._patrol_target = SaveGame.deserialize_vector3(dict["patrol_target"]) diff --git a/actors/ai/archetypes/pirate.tscn b/actors/ai/archetypes/pirate.tscn index 86c92366..b2dd5750 100644 --- a/actors/ai/archetypes/pirate.tscn +++ b/actors/ai/archetypes/pirate.tscn @@ -2,5 +2,5 @@ [ext_resource type="Script" path="res://actors/ai/archetypes/pirate.gd" id="1_ndoop"] -[node name="Pirate" type="Node3D"] +[node name="Pirate" type="Node3D" groups=["saveable"]] script = ExtResource("1_ndoop") diff --git a/galaxy/star_system/scenes/sol.tscn b/galaxy/star_system/scenes/sol.tscn index e9cc864d..15f60e37 100644 --- a/galaxy/star_system/scenes/sol.tscn +++ b/galaxy/star_system/scenes/sol.tscn @@ -15,7 +15,13 @@ [ext_resource type="Script" path="res://actors/ai/ai_navigation.gd" id="10_2xj23"] [ext_resource type="PackedScene" uid="uid://fxemun7o6rix" path="res://galaxy/star_system/star_system_instance.tscn" id="star_system_instance"] -[sub_resource type="Resource" id="Resource_oen30"] +[sub_resource type="Resource" id="Resource_ckhac"] +resource_local_to_scene = true +script = ExtResource("8_ibfoc") +max_integrity = 300.0 +integrity = 300.0 + +[sub_resource type="Resource" id="Resource_pna33"] resource_local_to_scene = true script = ExtResource("7_pkdss") max_integrity = 150.0 @@ -24,13 +30,7 @@ recharge_rate = 5.0 power_efficiency = 1.0 only_recharge_above = 0.2 -[sub_resource type="Resource" id="Resource_yterj"] -resource_local_to_scene = true -script = ExtResource("8_ibfoc") -max_integrity = 300.0 -integrity = 300.0 - -[sub_resource type="Resource" id="Resource_eyrkq"] +[sub_resource type="Resource" id="Resource_qur2d"] resource_local_to_scene = true script = ExtResource("9_w4i7k") max_power = 300.0 @@ -69,25 +69,11 @@ texture = ExtResource("4_afp06") [node name="Freighter" parent="." index="5" instance=ExtResource("6_uk08c")] transform = Transform3D(0.759975, 0, -0.649952, 0, 1, 0, 0.649952, 0, 0.759975, -5.22228, 0, -4.59493) physics_material_override = null +hull = SubResource("Resource_ckhac") +shield = SubResource("Resource_pna33") +battery = SubResource("Resource_qur2d") -[node name="CombatObject" parent="Freighter" index="2"] -shield = SubResource("Resource_oen30") -hull = SubResource("Resource_yterj") - -[node name="RigidBodyDirection" parent="Freighter" index="3"] -battery = SubResource("Resource_eyrkq") - -[node name="RigidBodyThruster" parent="Freighter" index="4"] -battery = SubResource("Resource_eyrkq") - -[node name="PowerManagementUnit" parent="Freighter" index="5"] -battery = SubResource("Resource_eyrkq") - -[node name="ShieldRecharger" parent="Freighter" index="7"] -battery = SubResource("Resource_eyrkq") -shield = SubResource("Resource_oen30") - -[node name="AINavigation" type="Node3D" parent="Freighter" index="8" node_paths=PackedStringArray("rigid_body_thruster", "rigid_body_direction")] +[node name="AINavigation" type="Node3D" parent="Freighter" index="8" node_paths=PackedStringArray("rigid_body_thruster", "rigid_body_direction") groups=["saveable"]] script = ExtResource("10_2xj23") rigid_body_thruster = NodePath("../RigidBodyThruster") rigid_body_direction = NodePath("../RigidBodyDirection") diff --git a/screens/game/game.tscn b/screens/game/game.tscn index dab3b6ca..bfed9e0c 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -98,7 +98,7 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_tgh0p"] +[sub_resource type="Resource" id="Resource_201k7"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 @@ -179,7 +179,7 @@ hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_tgh0p") +hyperdrive = SubResource("Resource_201k7") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("16_6olqp") From 035b2d469f4da1f5412b06babdb452140a879761 Mon Sep 17 00:00:00 2001 From: Justin Spahr-Summers Date: Sun, 4 Aug 2024 21:17:18 +0100 Subject: [PATCH 46/46] Remove packed scene nodes that weren't saved --- screens/game/game.tscn | 4 ++-- utils/save_game.gd | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/screens/game/game.tscn b/screens/game/game.tscn index bfed9e0c..c861cd5f 100644 --- a/screens/game/game.tscn +++ b/screens/game/game.tscn @@ -98,7 +98,7 @@ script = ExtResource("11_ag0i7") max_volume = 10.0 commodities = {} -[sub_resource type="Resource" id="Resource_201k7"] +[sub_resource type="Resource" id="Resource_80tvv"] resource_local_to_scene = true script = ExtResource("13_q2g24") max_fuel = 6.0 @@ -179,7 +179,7 @@ hull = SubResource("Resource_ffax2") shield = SubResource("Resource_75c8j") battery = SubResource("Resource_xpwk4") cargo_hold = SubResource("Resource_bsv1l") -hyperdrive = SubResource("Resource_201k7") +hyperdrive = SubResource("Resource_80tvv") [node name="HyperdriveSystem" type="Node3D" parent="HyperspaceSceneSwitcher/Sol/PlayerCorvette"] script = ExtResource("16_6olqp") diff --git a/utils/save_game.gd b/utils/save_game.gd index 7bf21022..3ce9e776 100644 --- a/utils/save_game.gd +++ b/utils/save_game.gd @@ -111,6 +111,7 @@ func load_tree_from_dict(dict: Dictionary) -> void: paths.sort_custom(func(a: String, b: String) -> bool: return NodePath(a).get_name_count() < NodePath(b).get_name_count()) + var loaded_nodes: Array[Node] = [] for path: String in paths: print("Loading node: ", path) @@ -122,7 +123,7 @@ func load_tree_from_dict(dict: Dictionary) -> void: var node_path := NodePath(path) var node := scene_tree.root.get_node_or_null(node_path) - if node == null: + if not node: print("Instantiating node ", path, " from scene ", scene_path) var parent_path := NodeUtils.get_parent_path(node_path) var parent_node := scene_tree.root.get_node_or_null(parent_path) @@ -145,11 +146,19 @@ func load_tree_from_dict(dict: Dictionary) -> void: continue node.call("load_from_dict", save_dict) + loaded_nodes.push_back(node) - self._call_after_load.call_deferred() + self._finish_load.call_deferred(loaded_nodes) -func _call_after_load() -> void: +func _finish_load(loaded_nodes: Array[Node]) -> void: var scene_tree := self.get_tree() + for node in scene_tree.get_nodes_in_group(SAVEABLE_GROUP): + if node in loaded_nodes: + continue + + print("Removing node which wasn't saved: ", node.get_path()) + node.queue_free() + scene_tree.call_group_flags(SceneTree.GROUP_CALL_REVERSE, SAVEABLE_GROUP, "after_load") func load_resource_property_from_dict(object: Object, dict: Dictionary, property_name: StringName) -> void: