diff --git a/README.md b/README.md index e228e7b..536afab 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ Discord: https://discord.gg/5gP22447kc Matrix: https://matrix.to/#/#straywild:matrix.makearmy.io -Repo: https://forge.makearmy.io/woofmeow/straywild +Repo: https://forge.makearmy.io/woofmeow/netfishing -Releases: https://forge.makearmy.io/woofmeow/straywild/releases +Releases: https://forge.makearmy.io/woofmeow/netfishing/releases ## Installing release builds diff --git a/art/exported/creatures/2502.png b/art/exported/creatures/2502.png deleted file mode 100644 index 467431b..0000000 Binary files a/art/exported/creatures/2502.png and /dev/null differ diff --git a/art/exported/creatures/2502.png.import b/art/exported/creatures/2502.png.import deleted file mode 100644 index 17bcad9..0000000 --- a/art/exported/creatures/2502.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://busj0feq2dncu" -path.s3tc="res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.s3tc.ctex" -path.etc2="res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2502.png" -dest_files=["res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.s3tc.ctex", "res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2504.png b/art/exported/creatures/2504.png deleted file mode 100644 index d0279d8..0000000 Binary files a/art/exported/creatures/2504.png and /dev/null differ diff --git a/art/exported/creatures/2504.png.import b/art/exported/creatures/2504.png.import deleted file mode 100644 index 73fa801..0000000 --- a/art/exported/creatures/2504.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://4a7c1aat4777" -path.s3tc="res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.s3tc.ctex" -path.etc2="res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2504.png" -dest_files=["res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.s3tc.ctex", "res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2505.png b/art/exported/creatures/2505.png deleted file mode 100644 index 7463c25..0000000 Binary files a/art/exported/creatures/2505.png and /dev/null differ diff --git a/art/exported/creatures/2505.png.import b/art/exported/creatures/2505.png.import deleted file mode 100644 index 130cedb..0000000 --- a/art/exported/creatures/2505.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://b8u3lw242yr3c" -path.s3tc="res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.s3tc.ctex" -path.etc2="res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2505.png" -dest_files=["res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.s3tc.ctex", "res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2508.png b/art/exported/creatures/2508.png deleted file mode 100644 index 4cefefd..0000000 Binary files a/art/exported/creatures/2508.png and /dev/null differ diff --git a/art/exported/creatures/2508.png.import b/art/exported/creatures/2508.png.import deleted file mode 100644 index 0fdbbe4..0000000 --- a/art/exported/creatures/2508.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://c2k6ghkng6081" -path.s3tc="res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.s3tc.ctex" -path.etc2="res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2508.png" -dest_files=["res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.s3tc.ctex", "res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2601.png b/art/exported/creatures/2601.png deleted file mode 100644 index f7565d8..0000000 Binary files a/art/exported/creatures/2601.png and /dev/null differ diff --git a/art/exported/creatures/2601.png.import b/art/exported/creatures/2601.png.import deleted file mode 100644 index 7444643..0000000 --- a/art/exported/creatures/2601.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://d14jvhurcfryb" -path.s3tc="res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.s3tc.ctex" -path.etc2="res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2601.png" -dest_files=["res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.s3tc.ctex", "res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2801.png b/art/exported/creatures/2801.png deleted file mode 100644 index ad8294d..0000000 Binary files a/art/exported/creatures/2801.png and /dev/null differ diff --git a/art/exported/creatures/2801.png.import b/art/exported/creatures/2801.png.import deleted file mode 100644 index 512f812..0000000 --- a/art/exported/creatures/2801.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cc7el34sfad78" -path.s3tc="res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.s3tc.ctex" -path.etc2="res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2801.png" -dest_files=["res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.s3tc.ctex", "res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2802.png b/art/exported/creatures/2802.png deleted file mode 100644 index 4190610..0000000 Binary files a/art/exported/creatures/2802.png and /dev/null differ diff --git a/art/exported/creatures/2802.png.import b/art/exported/creatures/2802.png.import deleted file mode 100644 index 4d014d1..0000000 --- a/art/exported/creatures/2802.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bawecr4tv52p1" -path.s3tc="res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.s3tc.ctex" -path.etc2="res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2802.png" -dest_files=["res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.s3tc.ctex", "res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2803.png b/art/exported/creatures/2803.png deleted file mode 100644 index 6360f26..0000000 Binary files a/art/exported/creatures/2803.png and /dev/null differ diff --git a/art/exported/creatures/2803.png.import b/art/exported/creatures/2803.png.import deleted file mode 100644 index 5096057..0000000 --- a/art/exported/creatures/2803.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cjwm07240ehr2" -path.s3tc="res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.s3tc.ctex" -path.etc2="res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2803.png" -dest_files=["res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.s3tc.ctex", "res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2804.png b/art/exported/creatures/2804.png deleted file mode 100644 index 90952fd..0000000 Binary files a/art/exported/creatures/2804.png and /dev/null differ diff --git a/art/exported/creatures/2804.png.import b/art/exported/creatures/2804.png.import deleted file mode 100644 index e6147c2..0000000 --- a/art/exported/creatures/2804.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dgcrfxo1k4juj" -path.s3tc="res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.s3tc.ctex" -path.etc2="res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2804.png" -dest_files=["res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.s3tc.ctex", "res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2806.png b/art/exported/creatures/2806.png deleted file mode 100644 index acb930e..0000000 Binary files a/art/exported/creatures/2806.png and /dev/null differ diff --git a/art/exported/creatures/2806.png.import b/art/exported/creatures/2806.png.import deleted file mode 100644 index 9d444cd..0000000 --- a/art/exported/creatures/2806.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://c5be8db5ppkq4" -path.s3tc="res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.s3tc.ctex" -path.etc2="res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2806.png" -dest_files=["res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.s3tc.ctex", "res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/2807.png b/art/exported/creatures/2807.png deleted file mode 100644 index 5c2a6da..0000000 Binary files a/art/exported/creatures/2807.png and /dev/null differ diff --git a/art/exported/creatures/2807.png.import b/art/exported/creatures/2807.png.import deleted file mode 100644 index af51ff5..0000000 --- a/art/exported/creatures/2807.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bdus4qo0ebs3e" -path.s3tc="res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.s3tc.ctex" -path.etc2="res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/2807.png" -dest_files=["res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.s3tc.ctex", "res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3002.png b/art/exported/creatures/3002.png deleted file mode 100644 index 2ae7e73..0000000 Binary files a/art/exported/creatures/3002.png and /dev/null differ diff --git a/art/exported/creatures/3002.png.import b/art/exported/creatures/3002.png.import deleted file mode 100644 index e193cfd..0000000 --- a/art/exported/creatures/3002.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://b68vlgtclvkfg" -path.s3tc="res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.s3tc.ctex" -path.etc2="res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3002.png" -dest_files=["res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.s3tc.ctex", "res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3102.png b/art/exported/creatures/3102.png deleted file mode 100644 index fe4d8fb..0000000 Binary files a/art/exported/creatures/3102.png and /dev/null differ diff --git a/art/exported/creatures/3102.png.import b/art/exported/creatures/3102.png.import deleted file mode 100644 index 47e9c80..0000000 --- a/art/exported/creatures/3102.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://c4d3f28rf86bb" -path.s3tc="res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.s3tc.ctex" -path.etc2="res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3102.png" -dest_files=["res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.s3tc.ctex", "res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3103.png b/art/exported/creatures/3103.png deleted file mode 100644 index 3d3d615..0000000 Binary files a/art/exported/creatures/3103.png and /dev/null differ diff --git a/art/exported/creatures/3103.png.import b/art/exported/creatures/3103.png.import deleted file mode 100644 index 9743e89..0000000 --- a/art/exported/creatures/3103.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cjtncgnb0hcsj" -path.s3tc="res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.s3tc.ctex" -path.etc2="res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3103.png" -dest_files=["res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.s3tc.ctex", "res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3104.png b/art/exported/creatures/3104.png deleted file mode 100644 index 425932e..0000000 Binary files a/art/exported/creatures/3104.png and /dev/null differ diff --git a/art/exported/creatures/3104.png.import b/art/exported/creatures/3104.png.import deleted file mode 100644 index fbc70d7..0000000 --- a/art/exported/creatures/3104.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dlme86lrrhyg8" -path.s3tc="res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.s3tc.ctex" -path.etc2="res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3104.png" -dest_files=["res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.s3tc.ctex", "res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3107.png b/art/exported/creatures/3107.png deleted file mode 100644 index 7dee611..0000000 Binary files a/art/exported/creatures/3107.png and /dev/null differ diff --git a/art/exported/creatures/3107.png.import b/art/exported/creatures/3107.png.import deleted file mode 100644 index 241bac9..0000000 --- a/art/exported/creatures/3107.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://csv1dsgvifh4l" -path.s3tc="res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.s3tc.ctex" -path.etc2="res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3107.png" -dest_files=["res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.s3tc.ctex", "res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3201.png b/art/exported/creatures/3201.png deleted file mode 100644 index bcc639b..0000000 Binary files a/art/exported/creatures/3201.png and /dev/null differ diff --git a/art/exported/creatures/3201.png.import b/art/exported/creatures/3201.png.import deleted file mode 100644 index a8d6a43..0000000 --- a/art/exported/creatures/3201.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cd8u0ow8cqsbh" -path.s3tc="res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.s3tc.ctex" -path.etc2="res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3201.png" -dest_files=["res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.s3tc.ctex", "res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3401.png b/art/exported/creatures/3401.png deleted file mode 100644 index c14c2ae..0000000 Binary files a/art/exported/creatures/3401.png and /dev/null differ diff --git a/art/exported/creatures/3401.png.import b/art/exported/creatures/3401.png.import deleted file mode 100644 index 096d0fb..0000000 --- a/art/exported/creatures/3401.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://begsj5tj035e1" -path.s3tc="res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.s3tc.ctex" -path.etc2="res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3401.png" -dest_files=["res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.s3tc.ctex", "res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/3902.png b/art/exported/creatures/3902.png deleted file mode 100644 index d231239..0000000 Binary files a/art/exported/creatures/3902.png and /dev/null differ diff --git a/art/exported/creatures/3902.png.import b/art/exported/creatures/3902.png.import deleted file mode 100644 index a8e5440..0000000 --- a/art/exported/creatures/3902.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://ddc556gcby50v" -path.s3tc="res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.s3tc.ctex" -path.etc2="res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/3902.png" -dest_files=["res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.s3tc.ctex", "res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/4101.png b/art/exported/creatures/4101.png deleted file mode 100644 index 74a068b..0000000 Binary files a/art/exported/creatures/4101.png and /dev/null differ diff --git a/art/exported/creatures/4101.png.import b/art/exported/creatures/4101.png.import deleted file mode 100644 index d906da5..0000000 --- a/art/exported/creatures/4101.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bahd6nks2g7ik" -path.s3tc="res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.s3tc.ctex" -path.etc2="res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/4101.png" -dest_files=["res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.s3tc.ctex", "res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/5001.png b/art/exported/creatures/5001.png deleted file mode 100644 index cf39422..0000000 Binary files a/art/exported/creatures/5001.png and /dev/null differ diff --git a/art/exported/creatures/5001.png.import b/art/exported/creatures/5001.png.import deleted file mode 100644 index c87c852..0000000 --- a/art/exported/creatures/5001.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://byil5mr6w3j1q" -path.s3tc="res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.s3tc.ctex" -path.etc2="res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/5001.png" -dest_files=["res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.s3tc.ctex", "res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/5902.png b/art/exported/creatures/5902.png deleted file mode 100644 index 809057d..0000000 Binary files a/art/exported/creatures/5902.png and /dev/null differ diff --git a/art/exported/creatures/5902.png.import b/art/exported/creatures/5902.png.import deleted file mode 100644 index 2c8a5fb..0000000 --- a/art/exported/creatures/5902.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://0k4qnt7wusam" -path.s3tc="res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.s3tc.ctex" -path.etc2="res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/5902.png" -dest_files=["res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.s3tc.ctex", "res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/5903.png b/art/exported/creatures/5903.png deleted file mode 100644 index 74a8af6..0000000 Binary files a/art/exported/creatures/5903.png and /dev/null differ diff --git a/art/exported/creatures/5903.png.import b/art/exported/creatures/5903.png.import deleted file mode 100644 index 22946e8..0000000 --- a/art/exported/creatures/5903.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bjb8gqbta2oxw" -path.s3tc="res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.s3tc.ctex" -path.etc2="res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/5903.png" -dest_files=["res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.s3tc.ctex", "res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/5904.png b/art/exported/creatures/5904.png deleted file mode 100644 index 924ba4b..0000000 Binary files a/art/exported/creatures/5904.png and /dev/null differ diff --git a/art/exported/creatures/5904.png.import b/art/exported/creatures/5904.png.import deleted file mode 100644 index 23458ae..0000000 --- a/art/exported/creatures/5904.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://dtapwjgdbcgr7" -path.s3tc="res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.s3tc.ctex" -path.etc2="res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/5904.png" -dest_files=["res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.s3tc.ctex", "res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6001.png b/art/exported/creatures/6001.png deleted file mode 100644 index 9219597..0000000 Binary files a/art/exported/creatures/6001.png and /dev/null differ diff --git a/art/exported/creatures/6001.png.import b/art/exported/creatures/6001.png.import deleted file mode 100644 index a63cf85..0000000 --- a/art/exported/creatures/6001.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://c0164i40101lq" -path.s3tc="res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.s3tc.ctex" -path.etc2="res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6001.png" -dest_files=["res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.s3tc.ctex", "res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6406.png b/art/exported/creatures/6406.png deleted file mode 100644 index 15c3b83..0000000 Binary files a/art/exported/creatures/6406.png and /dev/null differ diff --git a/art/exported/creatures/6406.png.import b/art/exported/creatures/6406.png.import deleted file mode 100644 index ac060ee..0000000 --- a/art/exported/creatures/6406.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://gnlehenln3kp" -path.s3tc="res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.s3tc.ctex" -path.etc2="res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6406.png" -dest_files=["res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.s3tc.ctex", "res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6601.png b/art/exported/creatures/6601.png deleted file mode 100644 index 93c05c0..0000000 Binary files a/art/exported/creatures/6601.png and /dev/null differ diff --git a/art/exported/creatures/6601.png.import b/art/exported/creatures/6601.png.import deleted file mode 100644 index f5facf7..0000000 --- a/art/exported/creatures/6601.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://jauudro8rgay" -path.s3tc="res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.s3tc.ctex" -path.etc2="res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6601.png" -dest_files=["res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.s3tc.ctex", "res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6801.png b/art/exported/creatures/6801.png deleted file mode 100644 index bcdf70b..0000000 Binary files a/art/exported/creatures/6801.png and /dev/null differ diff --git a/art/exported/creatures/6801.png.import b/art/exported/creatures/6801.png.import deleted file mode 100644 index c257a50..0000000 --- a/art/exported/creatures/6801.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cuik8y4tqqetk" -path.s3tc="res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.s3tc.ctex" -path.etc2="res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6801.png" -dest_files=["res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.s3tc.ctex", "res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6807.png b/art/exported/creatures/6807.png deleted file mode 100644 index 270004f..0000000 Binary files a/art/exported/creatures/6807.png and /dev/null differ diff --git a/art/exported/creatures/6807.png.import b/art/exported/creatures/6807.png.import deleted file mode 100644 index 026d3d9..0000000 --- a/art/exported/creatures/6807.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cu5s2rxrc0dh5" -path.s3tc="res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.s3tc.ctex" -path.etc2="res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6807.png" -dest_files=["res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.s3tc.ctex", "res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6901.png b/art/exported/creatures/6901.png deleted file mode 100644 index 0c34e37..0000000 Binary files a/art/exported/creatures/6901.png and /dev/null differ diff --git a/art/exported/creatures/6901.png.import b/art/exported/creatures/6901.png.import deleted file mode 100644 index 651f116..0000000 --- a/art/exported/creatures/6901.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://umjy47x208xb" -path.s3tc="res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.s3tc.ctex" -path.etc2="res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6901.png" -dest_files=["res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.s3tc.ctex", "res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6902.png b/art/exported/creatures/6902.png deleted file mode 100644 index 3d4517e..0000000 Binary files a/art/exported/creatures/6902.png and /dev/null differ diff --git a/art/exported/creatures/6902.png.import b/art/exported/creatures/6902.png.import deleted file mode 100644 index 35f84df..0000000 --- a/art/exported/creatures/6902.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://csdl6u3uvft45" -path.s3tc="res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.s3tc.ctex" -path.etc2="res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6902.png" -dest_files=["res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.s3tc.ctex", "res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/art/exported/creatures/6904.png b/art/exported/creatures/6904.png deleted file mode 100644 index f2e8a7b..0000000 Binary files a/art/exported/creatures/6904.png and /dev/null differ diff --git a/art/exported/creatures/6904.png.import b/art/exported/creatures/6904.png.import deleted file mode 100644 index 75051e3..0000000 --- a/art/exported/creatures/6904.png.import +++ /dev/null @@ -1,42 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://bqh7d4v8jrbpr" -path.s3tc="res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.s3tc.ctex" -path.etc2="res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.etc2.ctex" -metadata={ -"imported_formats": ["s3tc_bptc", "etc2_astc"], -"vram_texture": true -} - -[deps] - -source_file="res://art/exported/creatures/6904.png" -dest_files=["res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.s3tc.ctex", "res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.etc2.ctex"] - -[params] - -compress/mode=2 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/uastc_level=0 -compress/rdo_quality_loss=0.0 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/channel_remap/red=0 -process/channel_remap/green=1 -process/channel_remap/blue=2 -process/channel_remap/alpha=3 -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=0 diff --git a/docs/ATTRIBUTION.md b/docs/ATTRIBUTION.md index d8676f0..c235601 100644 --- a/docs/ATTRIBUTION.md +++ b/docs/ATTRIBUTION.md @@ -142,10 +142,8 @@ environment and UI artwork, item artwork, finished fish and creature artwork, and character customization pattern and channel-map artwork. Voyager's incorporated original work includes 3D assets, the provisional fish placeholder artwork, the title and dusk music identified above, and the robot -animalese tones. The current starter motorcoach exterior was supplied by a -contributing artist who released its copyright to the project; the original -and cleaned source files are retained in the owners' source-work archive. -Specific contributor exceptions are recorded below and in the in-game credits. +animalese tones. Specific contributor exceptions are recorded below and in the +in-game credits. These credits intentionally describe work by creator and contribution family instead of maintaining a second file-by-file asset catalog. Runtime resources diff --git a/docs/README-PLAYTEST.txt b/docs/README-PLAYTEST.txt index f187182..15e3698 100644 --- a/docs/README-PLAYTEST.txt +++ b/docs/README-PLAYTEST.txt @@ -1,8 +1,8 @@ straywild -v0.20.2-alpha -Alpha 0.20.2 +v0.19.0-alpha +Alpha 0.19.0 -Thank you for supporting this private testing release. +Thank you for trying this early private playtest. straywild is an early multiplayer fishing game. This alpha includes local progression, player-hosted and dedicated multiplayer sessions, public room @@ -83,8 +83,6 @@ FEATURES TO TRY - Fishing, net gathering, and beach digging - Equipment and consumable use from the hotbar - Fishing Shop sell tray, wallet updates, and persistent upgrades -- Portable RV homes, multiplayer visits, RV storage, and the early decor - purchasing and placement prototype - Seeded generated worlds and the authored starter-island layout - Generated beaches, ponds, lakes, complete rivers, denser forests, varied elevation, and closed coastal cliffs @@ -119,6 +117,5 @@ KNOWN ALPHA LIMITATIONS networks may prevent some players from joining. - Multiplayer, dedicated-server deployment, UI, and balance are still alpha. - Some fish and character artwork remains temporary. -- RV exteriors and decor use temporary prototype geometry and presentation. - There is no installer or automatic updater. - Windows builds are unsigned. diff --git a/docs/README.md b/docs/README.md index 444ebc7..5327b30 100644 --- a/docs/README.md +++ b/docs/README.md @@ -17,10 +17,10 @@ for local README files. ## Related repositories -- [Discovery service API](https://forge.makearmy.io/woofmeow/straywild-discovery-server/src/branch/main/docs/API.md) +- [Discovery service API](https://forge.makearmy.io/woofmeow/netfishing-discovery-server/src/branch/main/docs/API.md) stays with the service implementation and defines its versioned HTTP contract. -- [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/straywild-dedicated-server/src/branch/main/README.md) +- [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/netfishing-dedicated-server/src/branch/main/README.md) stays with its installer and update scripts. It consumes this repository's attribution and legal notices from the exact pinned game commit instead of maintaining copies. diff --git a/export_presets.cfg b/export_presets.cfg index a49f01f..05b0918 100644 --- a/export_presets.cfg +++ b/export_presets.cfg @@ -9,7 +9,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.20.2-alpha/windows-x86_64/straywild.exe" +export_path="builds/v0.19.0-alpha/windows-x86_64/straywild.exe" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -35,11 +35,11 @@ application/modify_resources=true application/icon="res://art/exported/system_icons/straywild.ico" application/console_wrapper_icon="" application/icon_interpolation=0 -application/file_version="0.20.2.0" -application/product_version="0.20.2.0" +application/file_version="0.19.0.0" +application/product_version="0.19.0.0" application/company_name="Woofmeow" application/product_name="straywild" -application/file_description="straywild v0.20.2-alpha" +application/file_description="straywild v0.19.0-alpha" application/copyright="Copyright © 2026 Woofmeow" application/trademarks="straywild and Woofmeow branding is reserved; see TRADEMARKS.md" application/export_angle=0 @@ -62,7 +62,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.20.2-alpha/linux-arm64/straywild.arm64" +export_path="builds/v0.19.0-alpha/linux-arm64/straywild.arm64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -92,7 +92,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.20.2-alpha/macos/straywild.zip" +export_path="builds/v0.19.0-alpha/macos/straywild.zip" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -108,8 +108,8 @@ custom_template/release="" application/bundle_identifier="io.woofmeow.netfishing" application/icon="res://art/exported/system_icons/straywild_1024.png" application/icon_interpolation=0 -application/short_version="0.20.2" -application/version="0.20.2" +application/short_version="0.19.0" +application/version="0.19.0" application/architecture="universal" codesign/enable=false notarization/enable=false @@ -125,7 +125,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*,tests/*" -export_path="builds/v0.20.2-alpha/server-linux-x86_64/straywildServer.x86_64" +export_path="builds/v0.19.0-alpha/server-linux-x86_64/straywildServer.x86_64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -155,7 +155,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.20.2-alpha/android/straywild.apk" +export_path="builds/v0.19.0-alpha/android/straywild.apk" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -176,8 +176,8 @@ architectures/armeabi-v7a=false architectures/arm64-v8a=true architectures/x86=false architectures/x86_64=false -version/code=200200 -version/name="v0.20.2-alpha" +version/code=190000 +version/name="v0.19.0-alpha" package/unique_name="io.woofmeow.netfishing" package/name="straywild" package/signed=true @@ -214,7 +214,7 @@ custom_features="" export_filter="all_resources" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" exclude_filter="builds/*,scripts/*" -export_path="builds/v0.20.2-alpha/linux-x86_64/straywild.x86_64" +export_path="builds/v0.19.0-alpha/linux-x86_64/straywild.x86_64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" diff --git a/fish/species/bluegill/bluegill.tres b/fish/species/bluegill/bluegill.tres index 181b969..1ad5656 100644 --- a/fish/species/bluegill/bluegill.tres +++ b/fish/species/bluegill/bluegill.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fish/day_availability.tres" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3102.png" id="4_texture"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [resource] script = ExtResource("1_fish_data") diff --git a/fish/species/bream_common/bream_common.tres b/fish/species/bream_common/bream_common.tres index cc44fef..1c7398c 100644 --- a/fish/species/bream_common/bream_common.tres +++ b/fish/species/bream_common/bream_common.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2502.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/chub_creek/chub_creek.tres b/fish/species/chub_creek/chub_creek.tres index 778e9da..1739712 100644 --- a/fish/species/chub_creek/chub_creek.tres +++ b/fish/species/chub_creek/chub_creek.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2802.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/chub_european/chub_european.tres b/fish/species/chub_european/chub_european.tres index 16f43a1..a218aec 100644 --- a/fish/species/chub_european/chub_european.tres +++ b/fish/species/chub_european/chub_european.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2505.png" id="4_texture"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [sub_resource type="Resource" id="ChubEuropeanAvailability"] script = ExtResource("3_availability_script") diff --git a/fish/species/chub_lake/chub_lake.tres b/fish/species/chub_lake/chub_lake.tres index 1dc2d9a..6da6b43 100644 --- a/fish/species/chub_lake/chub_lake.tres +++ b/fish/species/chub_lake/chub_lake.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2807.png" id="4_texture"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [sub_resource type="Resource" id="ChubLakeAvailability"] script = ExtResource("3_availability_script") diff --git a/fish/species/clam_manila/clam_manila.tres b/fish/species/clam_manila/clam_manila.tres index 2b3fe73..20e3f8a 100644 --- a/fish/species/clam_manila/clam_manila.tres +++ b/fish/species/clam_manila/clam_manila.tres @@ -2,7 +2,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6406.png" id="3_texture"] +[ext_resource type="Texture2D" path="res://fish/species/clam_manila/clam_manila.png" id="3_texture"] [resource] script = ExtResource("1_fish_data") diff --git a/fish/species/crab_blue/crab_blue.tres b/fish/species/crab_blue/crab_blue.tres index b715522..2ee3279 100644 --- a/fish/species/crab_blue/crab_blue.tres +++ b/fish/species/crab_blue/crab_blue.tres @@ -1,11 +1,9 @@ -[gd_resource type="Resource" script_class="FishData" load_steps=5 format=3] +[gd_resource type="Resource" script_class="FishData" load_steps=4 format=3] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3902.png" id="99_creature_texture"] - [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") allow_night = false @@ -14,7 +12,7 @@ allow_night = false script = ExtResource("1_fish_data") id = &"crab_blue" display_name = "blue crab" -active = true +active = false catalog_number = 3902 collection_group = &"Marine crabs" logbook_fact = "blue crabs swim with flattened rear legs shaped like paddles. bright blue claws and an olive shell conceal a quick, defensive temperament." @@ -32,5 +30,3 @@ weight_max_lb = 1.1 sell_value_min = 3 sell_value_max = 8 sell_value_curve = 0.95 - -display_texture = ExtResource("99_creature_texture") diff --git a/fish/species/crappie_white/crappie_white.tres b/fish/species/crappie_white/crappie_white.tres index 6766f1e..7806c37 100644 --- a/fish/species/crappie_white/crappie_white.tres +++ b/fish/species/crappie_white/crappie_white.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3107.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/crayfish_red_swamp/crayfish_red_swamp.tres b/fish/species/crayfish_red_swamp/crayfish_red_swamp.tres index 6c055d2..b53448d 100644 --- a/fish/species/crayfish_red_swamp/crayfish_red_swamp.tres +++ b/fish/species/crayfish_red_swamp/crayfish_red_swamp.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2601.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/croaker_atlantic/croaker_atlantic.tres b/fish/species/croaker_atlantic/croaker_atlantic.tres index d0fb147..c35e7de 100644 --- a/fish/species/croaker_atlantic/croaker_atlantic.tres +++ b/fish/species/croaker_atlantic/croaker_atlantic.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6801.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/croaker_yellowfin/croaker_yellowfin.tres b/fish/species/croaker_yellowfin/croaker_yellowfin.tres index 1e282da..84885f9 100644 --- a/fish/species/croaker_yellowfin/croaker_yellowfin.tres +++ b/fish/species/croaker_yellowfin/croaker_yellowfin.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6807.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres b/fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres index b758404..483565c 100644 --- a/fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres +++ b/fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/5904.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/goby_round/goby_round.tres b/fish/species/goby_round/goby_round.tres index 3a8ffee..d8edf5e 100644 --- a/fish/species/goby_round/goby_round.tres +++ b/fish/species/goby_round/goby_round.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3401.png" id="4_texture"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [sub_resource type="Resource" id="RoundGobyAvailability"] script = ExtResource("3_availability_script") diff --git a/fish/species/goldfish/goldfish.tres b/fish/species/goldfish/goldfish.tres index 58c7934..444741b 100644 --- a/fish/species/goldfish/goldfish.tres +++ b/fish/species/goldfish/goldfish.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3002.png" id="4_texture"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [sub_resource type="Resource" id="GoldfishAvailability"] script = ExtResource("3_availability_script") diff --git a/fish/species/green_sunfish/green_sunfish.tres b/fish/species/green_sunfish/green_sunfish.tres index 1733035..fdd1ddd 100644 --- a/fish/species/green_sunfish/green_sunfish.tres +++ b/fish/species/green_sunfish/green_sunfish.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3103.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/grunt_bluestriped/grunt_bluestriped.tres b/fish/species/grunt_bluestriped/grunt_bluestriped.tres index 5f247a8..87e9215 100644 --- a/fish/species/grunt_bluestriped/grunt_bluestriped.tres +++ b/fish/species/grunt_bluestriped/grunt_bluestriped.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/5902.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/grunt_french/grunt_french.tres b/fish/species/grunt_french/grunt_french.tres index 8e8161e..da0bd90 100644 --- a/fish/species/grunt_french/grunt_french.tres +++ b/fish/species/grunt_french/grunt_french.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/5903.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres b/fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres index 6d7fe32..9512381 100644 --- a/fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres +++ b/fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6601.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/minnow_fathead/minnow_fathead.tres b/fish/species/minnow_fathead/minnow_fathead.tres index c4a3aff..cf00159 100644 --- a/fish/species/minnow_fathead/minnow_fathead.tres +++ b/fish/species/minnow_fathead/minnow_fathead.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2804.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/porgy_scup/porgy_scup.tres b/fish/species/porgy_scup/porgy_scup.tres index ad96831..59dd833 100644 --- a/fish/species/porgy_scup/porgy_scup.tres +++ b/fish/species/porgy_scup/porgy_scup.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6904.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/pumpkinseed/pumpkinseed.tres b/fish/species/pumpkinseed/pumpkinseed.tres index 7a4f090..7928e4f 100644 --- a/fish/species/pumpkinseed/pumpkinseed.tres +++ b/fish/species/pumpkinseed/pumpkinseed.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3104.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/roach_common/roach_common.tres b/fish/species/roach_common/roach_common.tres index c12d380..8814f1c 100644 --- a/fish/species/roach_common/roach_common.tres +++ b/fish/species/roach_common/roach_common.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2504.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/rockfish_black/rockfish_black.tres b/fish/species/rockfish_black/rockfish_black.tres index c37b427..f26a41c 100644 --- a/fish/species/rockfish_black/rockfish_black.tres +++ b/fish/species/rockfish_black/rockfish_black.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6901.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/rudd/rudd.tres b/fish/species/rudd/rudd.tres index 5de8d57..ae031ac 100644 --- a/fish/species/rudd/rudd.tres +++ b/fish/species/rudd/rudd.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2508.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/sea_bass_black/sea_bass_black.tres b/fish/species/sea_bass_black/sea_bass_black.tres index 7a5b567..1c0eabd 100644 --- a/fish/species/sea_bass_black/sea_bass_black.tres +++ b/fish/species/sea_bass_black/sea_bass_black.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6902.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/shiner_common/shiner_common.tres b/fish/species/shiner_common/shiner_common.tres index 5a8a2f3..562daf6 100644 --- a/fish/species/shiner_common/shiner_common.tres +++ b/fish/species/shiner_common/shiner_common.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2801.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/shiner_emerald/shiner_emerald.tres b/fish/species/shiner_emerald/shiner_emerald.tres index 8a39e5c..eeb3653 100644 --- a/fish/species/shiner_emerald/shiner_emerald.tres +++ b/fish/species/shiner_emerald/shiner_emerald.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2803.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/shiner_golden/shiner_golden.tres b/fish/species/shiner_golden/shiner_golden.tres index af57441..5ab924f 100644 --- a/fish/species/shiner_golden/shiner_golden.tres +++ b/fish/species/shiner_golden/shiner_golden.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/2806.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/smelt_rainbow/smelt_rainbow.tres b/fish/species/smelt_rainbow/smelt_rainbow.tres index 34691f9..6a0d08a 100644 --- a/fish/species/smelt_rainbow/smelt_rainbow.tres +++ b/fish/species/smelt_rainbow/smelt_rainbow.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/4101.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fish/species/snapper_lane/snapper_lane.tres b/fish/species/snapper_lane/snapper_lane.tres index d91444f..0288e2d 100644 --- a/fish/species/snapper_lane/snapper_lane.tres +++ b/fish/species/snapper_lane/snapper_lane.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/6001.png" id="4_texture"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [sub_resource type="Resource" id="SnapperLaneAvailability"] script = ExtResource("3_availability_script") diff --git a/fish/species/sunfish/sunfish.tres b/fish/species/sunfish/sunfish.tres index 5efd181..a8e2290 100644 --- a/fish/species/sunfish/sunfish.tres +++ b/fish/species/sunfish/sunfish.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fish/day_availability.tres" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/5001.png" id="4_texture"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [resource] script = ExtResource("1_fish_data") diff --git a/fish/species/yellow_perch/yellow_perch.tres b/fish/species/yellow_perch/yellow_perch.tres index 3e0501f..5098a1e 100644 --- a/fish/species/yellow_perch/yellow_perch.tres +++ b/fish/species/yellow_perch/yellow_perch.tres @@ -3,7 +3,7 @@ [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] -[ext_resource type="Texture2D" path="res://art/exported/creatures/3201.png" id="99_fish_placeholder"] +[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [sub_resource type="Resource" id="Availability"] script = ExtResource("3_availability") diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index fdfad53..2559af5 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -41,6 +41,8 @@ const FishingSurfaceSampleType = preload( const FishingSurfaceResolverType = preload( "res://fishing/fishing_surface_resolver.gd" ) +const ArtShopStockType = preload("res://economy/art_shop_stock.gd") +const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd") const WorldTimeServiceType = preload("res://world/world_time_service.gd") const WorldWeatherServiceType = preload( "res://world/world_weather_service.gd" @@ -132,7 +134,6 @@ const ALTERNATE_REEL_ACTION: StringName = &"reel_alternate" var state: FishingState = FishingState.READY var _external_input_blocked: bool = false var _gameplay_input_enabled: bool = false -var _fishing_environment_enabled: bool = true var _local_menu_input_owners: Dictionary[StringName, bool] = {} var _local_player: PlayerType var _local_inventory: FishInventoryType @@ -149,7 +150,6 @@ var _network_session: NetworkSessionType var _network_fishing: NetworkFishingServiceType var _world_time: WorldTimeServiceType var _world_weather: WorldWeatherServiceType -var _fishable_water_regions_resolver := Callable() var _active_player: PlayerType var _state_time_remaining: float = 0.0 var _cast_charge: float = 0.0 @@ -222,7 +222,6 @@ func setup( network_fishing: NetworkFishingServiceType = null, world_time: WorldTimeServiceType = null, world_weather: WorldWeatherServiceType = null, - fishable_water_regions_resolver: Callable = Callable(), ) -> void: _local_player = local_player _local_inventory = local_inventory @@ -243,7 +242,6 @@ func setup( _network_fishing = network_fishing _world_time = world_time _world_weather = world_weather - _fishable_water_regions_resolver = fishable_water_regions_resolver if ( _network_session != null and not _network_session.state_changed.is_connected( @@ -398,22 +396,6 @@ func set_gameplay_input_enabled(enabled: bool) -> void: reset_transient_state() -func set_fishing_environment_enabled(enabled: bool) -> void: - if _fishing_environment_enabled == enabled: - return - _fishing_environment_enabled = enabled - if enabled: - _new_cast_press_armed = not Input.is_action_pressed("fish_primary") - return - if _network_fishing != null and _network_fishing.has_local_attempt(): - _network_fishing.cancel_local_attempt("") - reset_transient_state() - - -func is_fishing_environment_enabled() -> bool: - return _fishing_environment_enabled - - func reset_transient_state(restore_movement: bool = true) -> void: var local_state_was_active: bool = ( state != FishingState.READY or _active_player != null @@ -548,7 +530,6 @@ func _process(delta: float) -> void: func _unhandled_input(event: InputEvent) -> void: if ( not _gameplay_input_enabled - or not _fishing_environment_enabled or _external_input_blocked or not _local_menu_input_owners.is_empty() ): @@ -561,23 +542,51 @@ func _unhandled_input(event: InputEvent) -> void: alternate_reel and state == FishingState.FIGHTING ): return - # Fishing owns the primary action only after a rod starts a cast, or while - # completing an already-active fishing sequence. An empty hand, decor, - # gathering tools, and other items must remain available to their own world - # interaction controllers. - if state == FishingState.READY and not has_active_fishing_rod(): - return # A held keyboard key emits echo events. Treat the alternate reel exactly # like a mouse button: one barrier hit per physical press, while the normal # held-input path continues driving free reeling between barriers. if event is InputEventKey and event.echo: get_viewport().set_input_as_handled() return + var selected_item: ItemDataType = _get_active_item() + if ( + state == FishingState.READY + and selected_item != null + and FishingShopStockType.is_gathering_tool(selected_item.item_id) + ): + return if event.is_pressed(): match state: FishingState.READY: if not _new_cast_press_armed: return + var active_item: ItemDataType = _get_active_item() + if ( + active_item != null + and ( + active_item.category == ItemDataType.Category.CONSUMABLE + or ( + active_item.item_id == PlayerItemEffectsType.FISH_FINDER_ID + ) + ) + ): + if _network_item_use != null: + _network_item_use.request_use(active_item.item_id) + elif _item_effects.use_item(active_item, _local_bag): + status_changed.emit(_item_effects.get_feedback( + active_item.item_id + )) + elif ( + active_item.item_id == PlayerItemEffectsType.FISH_FINDER_ID + ): + local_speech_requested.emit( + PlayerItemEffectsType.FISH_FINDER_DEAD_MESSAGE + ) + get_viewport().set_input_as_handled() + return + if not has_active_fishing_rod(): + get_viewport().set_input_as_handled() + return if ( _is_inventory_full() ): @@ -649,34 +658,6 @@ func _unhandled_input(event: InputEvent) -> void: get_viewport().set_input_as_handled() -func try_use_active_non_fishing_item() -> bool: - if ( - not _gameplay_input_enabled - or _external_input_blocked - or not _local_menu_input_owners.is_empty() - or state != FishingState.READY - ): - return false - var active_item: ItemDataType = _get_active_item() - if ( - active_item == null - or ( - active_item.category != ItemDataType.Category.CONSUMABLE - and active_item.item_id != PlayerItemEffectsType.FISH_FINDER_ID - ) - ): - return false - if _network_item_use != null: - _network_item_use.request_use(active_item.item_id) - elif _item_effects.use_item(active_item, _local_bag): - status_changed.emit(_item_effects.get_feedback(active_item.item_id)) - elif active_item.item_id == PlayerItemEffectsType.FISH_FINDER_ID: - local_speech_requested.emit( - PlayerItemEffectsType.FISH_FINDER_DEAD_MESSAGE - ) - return true - - func _is_reel_input_pressed() -> bool: return ( Input.is_action_pressed("fish_primary") @@ -684,10 +665,6 @@ func _is_reel_input_pressed() -> bool: ) -func is_reel_input_active() -> bool: - return state == FishingState.FIGHTING and _is_reel_input_pressed() - - func _begin_aiming(player: PlayerType) -> void: if state != FishingState.READY or _active_player != null: return @@ -1687,47 +1664,6 @@ func build_network_context( return _build_fishing_context(region) -func is_species_available_now(fish: FishDataType) -> bool: - if fish == null or not fish.is_selectable(): - return false - var season := ( - _world_time.get_season() - if _world_time != null - else CalendarSeason.UNKNOWN - ) - if not fish.is_available_in_season(season): - return false - if fish.collection_method != FishDataType.CollectionMethod.FISHING: - return true - for region: FishableWaterRegionType in _get_logbook_water_regions(): - if ( - region == null - or region.fish_pool == null - or region.fish_pool.get_fish_by_id(fish.id) == null - ): - continue - var context := _build_fishing_context(region) - if not fish.is_allowed_in_water(context.water_type): - continue - if fish.availability == null or fish.availability.is_available(context): - return true - return false - - -func _get_logbook_water_regions() -> Array[FishableWaterRegionType]: - var regions: Array[FishableWaterRegionType] = [] - if not _fishable_water_regions_resolver.is_valid(): - return regions - var resolved: Variant = _fishable_water_regions_resolver.call() - if resolved is not Array: - return regions - for value: Variant in resolved: - var region := value as FishableWaterRegionType - if region != null and is_instance_valid(region): - regions.append(region) - return regions - - func _get_active_bait_tags() -> Array[StringName]: if _local_player == null or _item_catalog == null: return [] diff --git a/gathering/catalog/crab_blue.tres b/gathering/catalog/crab_blue.tres index 705948e..1ccabe8 100644 --- a/gathering/catalog/crab_blue.tres +++ b/gathering/catalog/crab_blue.tres @@ -10,8 +10,5 @@ catch_data = ExtResource("2_catch") required_tool_id = &"crab_net" surface_materials = Array[StringName]([&"sand"]) minimum_surface_y = -0.8 -surface_water_relation = 2 population = 4 -surface_population_per_100_square_meters = 0.4 -maximum_surface_population = 16 sprite_pixel_size = 0.0005 diff --git a/gathering/catalog/crab_brown.tres b/gathering/catalog/crab_brown.tres index b27bb7e..83d20c8 100644 --- a/gathering/catalog/crab_brown.tres +++ b/gathering/catalog/crab_brown.tres @@ -10,10 +10,7 @@ catch_data = ExtResource("2_catch") required_tool_id = &"crab_net" surface_materials = Array[StringName]([&"sand"]) minimum_surface_y = -0.44 -surface_water_relation = 1 population = 2 -surface_population_per_100_square_meters = 0.15 -maximum_surface_population = 24 movement_speed = 0.35 roam_radius = 3.5 scare_radius = 2.8 diff --git a/gathering/gatherable_data.gd b/gathering/gatherable_data.gd index 41de7fe..d60dcc9 100644 --- a/gathering/gatherable_data.gd +++ b/gathering/gatherable_data.gd @@ -10,12 +10,6 @@ enum PresentationMode { WATER_SPURT, } -enum SurfaceWaterRelation { - ANY, - ABOVE_SALT_WATER, - BELOW_SALT_WATER, -} - @export var type_id: StringName @export var catch_data: FishDataType @export var required_tool_id: StringName @@ -23,20 +17,12 @@ enum SurfaceWaterRelation { @export var diggable_area_id: StringName @export var spawn_anchor_set_id: StringName @export_range(-100.0, 100.0, 0.01) var minimum_surface_y: float = 0.08 -@export_range(-100.0, 100.0, 0.01) var maximum_surface_y: float = 100.0 -@export var surface_water_relation := SurfaceWaterRelation.ANY -@export_range(0.0, 2.0, 0.01) var waterline_margin: float = 0.01 @export_range(0, 64, 1) var population: int = 0 ## Anchored gatherables can scale with authored/generated attachment geometry. ## Zero preserves the fixed population above. @export_range(0.0, 1.0, 0.01) var spawn_anchor_occupancy_ratio := 0.0 ## Zero leaves the anchor count as the only ceiling. @export_range(0, 256, 1) var maximum_anchor_population := 0 -## Optional density scaling for large generated surfaces. The authored -## population remains the minimum while the maximum keeps large maps bounded. -@export_range(0.0, 8.0, 0.01) -var surface_population_per_100_square_meters := 0.0 -@export_range(0, 256, 1) var maximum_surface_population := 0 @export var presentation_mode: PresentationMode = PresentationMode.VISIBLE_CREATURE @export var requires_sneaking: bool = true @export_range(0.0, 120.0, 0.1) var active_lifetime_seconds: float = 0.0 @@ -91,7 +77,6 @@ func is_valid() -> bool: or uses_spawn_anchors or not surface_materials.is_empty() ) - and maximum_surface_y >= minimum_surface_y and ( not uses_diggable_area or catch_data.collection_method @@ -102,10 +87,6 @@ func is_valid() -> bool: maximum_anchor_population == 0 or maximum_anchor_population >= population ) - and ( - maximum_surface_population == 0 - or maximum_surface_population >= population - ) and movement_parameters_valid and _quality_multipliers_are_valid( quality_movement_speed_multipliers @@ -145,24 +126,6 @@ func target_population_for_anchor_count(anchor_count: int) -> int: return target -func target_population_for_surface_area(surface_area: float) -> int: - if surface_population_per_100_square_meters <= 0.0: - return population - if surface_area <= 0.0: - return 0 - var target := maxi( - population, - ceili( - surface_area - * surface_population_per_100_square_meters - / 100.0 - ), - ) - if maximum_surface_population > 0: - target = mini(target, maximum_surface_population) - return target - - func can_be_scared() -> bool: return not is_stationary_spawn() and scare_radius > 0.0 diff --git a/gathering/world_gatherable.gd b/gathering/world_gatherable.gd index 1a71477..8a07ba0 100644 --- a/gathering/world_gatherable.gd +++ b/gathering/world_gatherable.gd @@ -29,16 +29,12 @@ func configure( entity_id = configured_entity_id data = configured_data type_id = data.type_id if data != null else StringName() - # Establish the authoritative transform before constructing transient visuals. - # Effects emitted before this point would begin at the default world origin. - apply_network_state(position, yaw, true) if data != null and data.is_stationary_hotspot(): _ensure_water_spurt_visual() _water_spurt_elapsed = ( float(abs(hash(entity_id)) % 1000) / 1000.0 * WATER_SPURT_INTERVAL_SECONDS ) - _emit_water_spurt() else: _ensure_visual() if data != null and data.catch_data != null and _sprite != null: @@ -54,6 +50,7 @@ func configure( ) ) _sprite.rotation_degrees.x = data.sprite_tilt_degrees + apply_network_state(position, yaw, true) func apply_network_state( @@ -150,6 +147,7 @@ func _ensure_water_spurt_visual() -> void: hole.mesh = hole_mesh hole.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF _water_spurt_root.add_child(hole) + _emit_water_spurt() func _emit_water_spurt() -> void: @@ -161,7 +159,6 @@ func _emit_water_spurt() -> void: particles.amount = 6 particles.lifetime = 0.42 particles.one_shot = true - particles.local_coords = true particles.explosiveness = 1.0 particles.direction = Vector3.UP particles.spread = 32.0 diff --git a/homes/assets/starter_motorcoach.glb b/homes/assets/starter_motorcoach.glb deleted file mode 100644 index becd54e..0000000 Binary files a/homes/assets/starter_motorcoach.glb and /dev/null differ diff --git a/homes/assets/starter_motorcoach.glb.import b/homes/assets/starter_motorcoach.glb.import deleted file mode 100644 index 5cba35b..0000000 --- a/homes/assets/starter_motorcoach.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://bhph22caipcj6" -path="res://.godot/imported/starter_motorcoach.glb-153681ef1e622a556fba4df576dc6f84.scn" - -[deps] - -source_file="res://homes/assets/starter_motorcoach.glb" -dest_files=["res://.godot/imported/starter_motorcoach.glb-153681ef1e622a556fba4df576dc6f84.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/homes/assets/starter_rv_interior.glb b/homes/assets/starter_rv_interior.glb deleted file mode 100644 index 21608ea..0000000 Binary files a/homes/assets/starter_rv_interior.glb and /dev/null differ diff --git a/homes/assets/starter_rv_interior.glb.import b/homes/assets/starter_rv_interior.glb.import deleted file mode 100644 index 947870a..0000000 --- a/homes/assets/starter_rv_interior.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://b815y7svs3ern" -path="res://.godot/imported/starter_rv_interior.glb-9ad8a345e9e831e82874b4b276341224.scn" - -[deps] - -source_file="res://homes/assets/starter_rv_interior.glb" -dest_files=["res://.godot/imported/starter_rv_interior.glb-9ad8a345e9e831e82874b4b276341224.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/homes/assets/tent_interior.glb b/homes/assets/tent_interior.glb deleted file mode 100644 index 3ba2b76..0000000 Binary files a/homes/assets/tent_interior.glb and /dev/null differ diff --git a/homes/assets/tent_interior.glb.import b/homes/assets/tent_interior.glb.import deleted file mode 100644 index fc01ba7..0000000 --- a/homes/assets/tent_interior.glb.import +++ /dev/null @@ -1,45 +0,0 @@ -[remap] - -importer="scene" -importer_version=1 -type="PackedScene" -uid="uid://dkfqs051shmxe" -path="res://.godot/imported/tent_interior.glb-576cc47c2c633f7ae635651598663518.scn" - -[deps] - -source_file="res://homes/assets/tent_interior.glb" -dest_files=["res://.godot/imported/tent_interior.glb-576cc47c2c633f7ae635651598663518.scn"] - -[params] - -nodes/root_type="" -nodes/root_name="" -nodes/root_script=null -mesh_library/use_node_names_as_mesh_names=false -array_mesh/deduplicate_surfaces=true -nodes/apply_root_scale=true -nodes/root_scale=1.0 -nodes/import_as_skeleton_bones=false -nodes/use_name_suffixes=true -nodes/use_node_type_suffixes=true -meshes/ensure_tangents=true -meshes/generate_lods=true -meshes/create_shadow_meshes=true -meshes/light_baking=1 -meshes/lightmap_texel_size=0.2 -meshes/force_disable_compression=false -skins/use_named_skins=true -animation/import=true -animation/fps=30 -animation/trimming=false -animation/remove_immutable_tracks=true -animation/import_rest_as_RESET=false -import_script/path="" -materials/extract=0 -materials/extract_format=0 -materials/extract_path="" -_subresources={} -gltf/naming_version=2 -gltf/embedded_image_handling=1 -gltf/texture_map_mode=1 diff --git a/homes/decor_catalog.gd b/homes/decor_catalog.gd deleted file mode 100644 index 14e5e29..0000000 --- a/homes/decor_catalog.gd +++ /dev/null @@ -1,59 +0,0 @@ -class_name DecorCatalog -extends RefCounted - -const BASIC_CUBE_ID: StringName = &"basic_cube" -const MAX_OWNED_PER_PRODUCT: int = 32 - -const _PRODUCTS: Dictionary = { - BASIC_CUBE_ID: { - "display_name": "basic cube", - "description": "a simple piece of decor for testing your RV layout.", - "price": 1, - "footprint": Vector2(1.0, 0.8), - "height": 0.8, - }, -} - - -static func get_product_ids() -> Array[StringName]: - var ids: Array[StringName] = [] - for value: Variant in _PRODUCTS.keys(): - ids.append(StringName(str(value))) - ids.sort_custom(func(first: StringName, second: StringName) -> bool: - return String(first) < String(second) - ) - return ids - - -static func has_product(product_id: StringName) -> bool: - return _PRODUCTS.has(product_id) - - -static func get_product(product_id: StringName) -> Dictionary: - var value: Variant = _PRODUCTS.get(product_id) - return ( - (value as Dictionary).duplicate(true) - if typeof(value) == TYPE_DICTIONARY - else {} - ) - - -static func get_display_name(product_id: StringName) -> String: - return str(get_product(product_id).get("display_name", product_id)) - - -static func get_description(product_id: StringName) -> String: - return str(get_product(product_id).get("description", "")) - - -static func get_price(product_id: StringName) -> int: - return int(get_product(product_id).get("price", -1)) - - -static func get_footprint(product_id: StringName) -> Vector2: - var value: Variant = get_product(product_id).get("footprint", Vector2.ZERO) - return value as Vector2 if value is Vector2 else Vector2.ZERO - - -static func get_height(product_id: StringName) -> float: - return float(get_product(product_id).get("height", 0.0)) diff --git a/homes/decor_catalog.gd.uid b/homes/decor_catalog.gd.uid deleted file mode 100644 index 10d2530..0000000 --- a/homes/decor_catalog.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bvgwmexas2gps diff --git a/homes/home_decor_controller.gd b/homes/home_decor_controller.gd deleted file mode 100644 index f67207f..0000000 --- a/homes/home_decor_controller.gd +++ /dev/null @@ -1,512 +0,0 @@ -class_name HomeDecorController -extends Node - -const DecorCatalogType = preload("res://homes/decor_catalog.gd") -const DRAG_RESPONSE: float = 22.0 -const DRAG_POSITION_EPSILON: float = 0.005 - -var _player: Player -var _hotbar: PlayerHotbar -var _bag: PlayerBag -var _home_state: PlayerHomeState -var _network_home: NetworkHomeService -var _home_world: HomeWorldService -var _fishing_spot: FishingSpot -var _toolbar: HomeDecorToolbar -var _mode: PlayerHomeState.PlacementMode = PlayerHomeState.PlacementMode.GRID -var _selected_instance_id: String = "" -var _drag_instance_id: String = "" -var _drag_owner_fingerprint: String = "" -var _drag_pointer_offset: Vector3 = Vector3.ZERO -var _drag_original_position: Vector3 = Vector3.ZERO -var _drag_original_yaw: float = 0.0 -var _drag_target_position: Vector3 = Vector3.ZERO -var _drag_target_yaw: float = 0.0 -var _drag_target_valid: bool = false - - -func setup( - player: Player, - hotbar: PlayerHotbar, - bag: PlayerBag, - home_state: PlayerHomeState, - network_home: NetworkHomeService, - home_world: HomeWorldService, - fishing_spot: FishingSpot, - toolbar: HomeDecorToolbar, -) -> void: - _player = player - _hotbar = hotbar - _bag = bag - _home_state = home_state - _network_home = network_home - _home_world = home_world - _fishing_spot = fishing_spot - _toolbar = toolbar - if not _network_home.local_space_changed.is_connected( - _on_local_space_changed - ): - _network_home.local_space_changed.connect(_on_local_space_changed) - if not _network_home.local_manifest_rejected.is_connected( - _on_local_manifest_rejected - ): - _network_home.local_manifest_rejected.connect( - _on_local_manifest_rejected - ) - if not _home_state.changed.is_connected(_on_home_changed): - _home_state.changed.connect(_on_home_changed) - if not _toolbar.placement_mode_requested.is_connected( - _on_placement_mode_requested - ): - _toolbar.placement_mode_requested.connect( - _on_placement_mode_requested - ) - _toolbar.rotate_requested.connect(_rotate_selected) - _toolbar.store_requested.connect(_store_selected) - _toolbar.cancel_requested.connect(_clear_selection) - _toolbar.set_mode(_mode) - _refresh_toolbar() - - -func begin_decorating() -> void: - _refresh_toolbar() - if _toolbar.visible: - _toolbar.report_status( - "use held decor to place it; click or drag placed decor" - ) - - -func _input(event: InputEvent) -> void: - if _drag_instance_id.is_empty(): - return - var pointer_motion := event as InputEventMouseMotion - if pointer_motion != null: - _update_drag(pointer_motion.position) - get_viewport().set_input_as_handled() - return - var pointer_button := event as InputEventMouseButton - if ( - pointer_button != null - and pointer_button.button_index == MOUSE_BUTTON_LEFT - and not pointer_button.pressed - ): - # A gameplay menu can take ownership between the press and release. Do - # not let that release finalize decor behind the menu; preserve the - # original transform instead. - if _can_decorate(): - _finish_drag() - else: - _cancel_drag() - get_viewport().set_input_as_handled() - - -func _unhandled_input(event: InputEvent) -> void: - var pointer_button := event as InputEventMouseButton - if ( - pointer_button != null - and pointer_button.button_index == MOUSE_BUTTON_LEFT - ): - if not pointer_button.pressed or not _can_decorate(): - return - if _begin_drag(pointer_button.position): - get_viewport().set_input_as_handled() - return - var pointer_decor_id: StringName = _get_selected_decor_id() - if ( - not pointer_decor_id.is_empty() - and _bag.get_quantity(pointer_decor_id) > 0 - ): - _place_carried_decor(pointer_decor_id) - get_viewport().set_input_as_handled() - return - if not _can_decorate() or not event.is_action_pressed("fish_primary"): - return - if event is InputEventKey and event.echo: - return - var decor_id: StringName = _get_selected_decor_id() - if not decor_id.is_empty() and _bag.get_quantity(decor_id) > 0: - _place_carried_decor(decor_id) - get_viewport().set_input_as_handled() - - -func _process(delta: float) -> void: - if _toolbar != null and _toolbar.visible: - _toolbar.set_interactive(_can_decorate()) - if _drag_instance_id.is_empty(): - return - # Menus and other gameplay input handoffs must cancel an active drag before - # evaluating a held/released pointer. This prevents a same-frame TAB press - # followed by left-button release from committing a move behind the UI. - if not _can_decorate(): - _cancel_drag() - return - if not Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT): - _finish_drag() - return - if not _drag_target_valid: - return - var current: Dictionary = _home_world.get_decor_preview_transform( - _drag_owner_fingerprint, _drag_instance_id - ) - if current.is_empty(): - _cancel_drag() - return - var current_position: Vector3 = current["position"] - var current_yaw: float = float(current["yaw"]) - var blend: float = 1.0 - exp(-DRAG_RESPONSE * delta) - _home_world.set_decor_preview_transform( - _drag_owner_fingerprint, - _drag_instance_id, - current_position.lerp(_drag_target_position, blend), - lerp_angle(current_yaw, _drag_target_yaw, blend), - ) - - -func _begin_drag(screen_position: Vector2) -> bool: - if _network_home.has_pending_local_decor_mutation(): - _toolbar.report_status("waiting for the previous decor update") - return false - var owner_fingerprint: String = ( - _network_home.get_local_home_owner_fingerprint() - ) - var camera: Camera3D = _player.get_active_gameplay_camera() - var clicked_instance: String = _home_world.find_decor_instance_at_screen( - owner_fingerprint, camera, screen_position - ) - if clicked_instance.is_empty(): - return false - var placement: Dictionary = _home_state.get_placement(clicked_instance) - if placement.is_empty(): - return false - var original_position: Vector3 = _placement_position(placement) - if not original_position.is_finite(): - return false - if not _home_world.set_decor_manipulation_active( - owner_fingerprint, clicked_instance, true - ): - return false - var pointer_position: Vector3 = _home_world.get_floor_position_from_screen( - owner_fingerprint, camera, screen_position - ) - _drag_instance_id = clicked_instance - _drag_owner_fingerprint = owner_fingerprint - _drag_original_position = original_position - _drag_original_yaw = float(placement.get("yaw", 0.0)) - _drag_target_position = original_position - _drag_target_yaw = _drag_original_yaw - _drag_target_valid = true - _drag_pointer_offset = ( - original_position - pointer_position - if pointer_position.is_finite() - else Vector3.ZERO - ) - _drag_pointer_offset.y = 0.0 - _select_instance(clicked_instance) - _toolbar.report_status("drag decor, then release to place it") - return true - - -func _update_drag(screen_position: Vector2) -> void: - if not _can_decorate(): - _cancel_drag() - return - var camera: Camera3D = _player.get_active_gameplay_camera() - var pointer_position: Vector3 = _home_world.get_floor_position_from_screen( - _drag_owner_fingerprint, camera, screen_position - ) - if not pointer_position.is_finite(): - # Keep the most recent valid target when the pointer briefly leaves the - # RV floor. Releasing there should place at that target, not throw the - # decor all the way back to where the drag began. - _toolbar.report_status("keep the pointer over the RV floor") - return - var placement: Dictionary = _home_state.get_placement(_drag_instance_id) - if placement.is_empty(): - _clear_selection() - return - var candidate: Vector3 = pointer_position + _drag_pointer_offset - candidate.y = 0.0 - var normalized: Dictionary = _network_home.get_valid_local_decor_transform( - _drag_instance_id, - candidate, - float(placement.get("yaw", _drag_original_yaw)), - _mode, - ) - if normalized.is_empty(): - # A densely furnished area may have no nearby solution. Preserve the - # last valid preview so the interaction remains stable while the player - # moves the pointer toward open space. - _toolbar.report_status("holding at the nearest available position") - return - _drag_target_position = normalized["position"] - _drag_target_yaw = float(normalized["yaw"]) - _drag_target_valid = true - _toolbar.report_status("release to place decor") - - -func _finish_drag() -> void: - var instance_id: String = _drag_instance_id - var owner_fingerprint: String = _drag_owner_fingerprint - var original_position: Vector3 = _drag_original_position - var original_yaw: float = _drag_original_yaw - var target_position: Vector3 = _drag_target_position - var target_yaw: float = _drag_target_yaw - var target_valid: bool = _drag_target_valid - var placement: Dictionary = _home_state.get_placement(instance_id) - var decor_id := StringName(str(placement.get("decor_id", ""))) - if ( - not target_valid - or decor_id.is_empty() - or not _home_world.is_decor_placement_clear_of_players( - owner_fingerprint, decor_id, target_position, target_yaw - ) - ): - _home_world.set_decor_preview_transform( - owner_fingerprint, - instance_id, - original_position, - original_yaw, - ) - _home_world.set_decor_manipulation_active( - owner_fingerprint, instance_id, false - ) - _reset_drag_state() - _toolbar.report_status( - "a player is in the way" - if target_valid - else "that space is not available" - ) - return - _home_world.set_decor_preview_transform( - owner_fingerprint, instance_id, target_position, target_yaw - ) - if ( - original_position.distance_to(target_position) - <= DRAG_POSITION_EPSILON - and is_zero_approx(angle_difference(original_yaw, target_yaw)) - ): - _home_world.set_decor_manipulation_active( - owner_fingerprint, instance_id, false - ) - _reset_drag_state() - _select_instance(instance_id) - return - if _network_home.update_local_decor( - instance_id, target_position, target_yaw, _mode - ): - _home_world.set_decor_manipulation_active( - owner_fingerprint, instance_id, false - ) - _reset_drag_state() - _toolbar.report_status("decor moved") - return - _home_world.set_decor_preview_transform( - owner_fingerprint, - instance_id, - original_position, - original_yaw, - ) - _home_world.set_decor_manipulation_active( - owner_fingerprint, instance_id, false - ) - _reset_drag_state() - _toolbar.report_status("that space is not available") - - -func _cancel_drag() -> void: - if _drag_instance_id.is_empty(): - return - _home_world.set_decor_preview_transform( - _drag_owner_fingerprint, - _drag_instance_id, - _drag_original_position, - _drag_original_yaw, - ) - _home_world.set_decor_manipulation_active( - _drag_owner_fingerprint, _drag_instance_id, false - ) - _reset_drag_state() - - -func _reset_drag_state() -> void: - _drag_instance_id = "" - _drag_owner_fingerprint = "" - _drag_pointer_offset = Vector3.ZERO - _drag_original_position = Vector3.ZERO - _drag_original_yaw = 0.0 - _drag_target_position = Vector3.ZERO - _drag_target_yaw = 0.0 - _drag_target_valid = false - - -static func _placement_position(placement: Dictionary) -> Vector3: - if placement.is_empty(): - return Vector3.INF - var values: Array = placement.get("position", []) - if values.size() != 3: - return Vector3.INF - return Vector3( - float(values[0]), float(values[1]), float(values[2]) - ) - - -func _place_carried_decor(decor_id: StringName) -> void: - if _network_home.has_pending_local_decor_mutation(): - _toolbar.report_status("waiting for the previous decor update") - return - var owner_fingerprint: String = ( - _network_home.get_local_home_owner_fingerprint() - ) - # RV transitions keep the gameplay root axis-aligned so its transform can be - # synchronized independently from the presentation. Use the visual facing - # direction instead; otherwise carried decor is always placed along world -Z - # instead of in front of the character the player sees. - var forward: Vector3 = _player.get_facing_direction() - var global_position: Vector3 = _player.global_position + forward * 1.45 - var local_position: Vector3 = _home_world.interior_global_to_local( - owner_fingerprint, global_position - ) - var placement_yaw: float = atan2(-forward.x, -forward.z) - var instance_id: String = _network_home.place_local_decor( - decor_id, local_position, placement_yaw, _mode - ) - if instance_id.is_empty(): - _toolbar.report_status("make room in front of you to place this decor") - return - # A local host can reject its own optimistic manifest synchronously. Do not - # claim success after that rollback or leave the player looking for decor that - # was never accepted into the room. - if _home_state.get_placement(instance_id).is_empty(): - _toolbar.report_status("decor placement was not accepted") - return - _select_instance(instance_id) - _toolbar.report_status("decor placed") - - -func _select_instance(instance_id: String) -> void: - var placement: Dictionary = _home_state.get_placement(instance_id) - if placement.is_empty(): - _clear_selection() - return - _selected_instance_id = instance_id - var decor_id := StringName(str(placement.get("decor_id", ""))) - _toolbar.set_selection( - instance_id, DecorCatalogType.get_display_name(decor_id) - ) - - -func _rotate_selected() -> void: - if not _can_decorate() or _selected_instance_id.is_empty(): - return - if _network_home.has_pending_local_decor_mutation(): - _toolbar.report_status("waiting for the previous decor update") - return - var placement: Dictionary = _home_state.get_placement( - _selected_instance_id - ) - if placement.is_empty(): - _clear_selection() - return - var values: Array = placement.get("position", []) - if values.size() != 3: - return - var position := Vector3( - float(values[0]), float(values[1]), float(values[2]) - ) - if _network_home.update_local_decor( - _selected_instance_id, - position, - float(placement.get("yaw", 0.0)) + PI * 0.5, - _mode, - ): - _toolbar.report_status("decor rotated") - else: - _toolbar.report_status("not enough room to rotate here") - - -func _store_selected() -> void: - if _selected_instance_id.is_empty(): - return - if _network_home.has_pending_local_decor_mutation(): - _toolbar.report_status("waiting for the previous decor update") - return - if _network_home.remove_local_decor(_selected_instance_id): - _clear_selection() - _toolbar.report_status("decor packed into storage") - else: - _toolbar.report_status("inventory and storage are full") - - -func _clear_selection() -> void: - _cancel_drag() - _selected_instance_id = "" - if _toolbar != null: - _toolbar.set_selection("") - - -func _on_placement_mode_requested(mode: int) -> void: - _mode = mode as PlayerHomeState.PlacementMode - - -func _on_local_space_changed( - _space_id: StringName, - _owner_fingerprint: String, -) -> void: - _clear_selection() - _refresh_toolbar() - - -func _on_local_manifest_rejected(message: String) -> void: - _clear_selection() - if _toolbar != null and _toolbar.visible: - _toolbar.report_status(message) - - -func _on_home_changed() -> void: - if ( - not _selected_instance_id.is_empty() - and _home_state.get_placement(_selected_instance_id).is_empty() - ): - _clear_selection() - _refresh_toolbar() - - -func _refresh_toolbar() -> void: - if _toolbar == null: - return - var inside_home: bool = _is_inside_home_space() - _toolbar.set_active(inside_home) - _toolbar.set_interactive(_can_decorate()) - if not _toolbar.visible: - _clear_selection() - elif not _can_decorate(): - _toolbar.report_status("decor tools pause while another menu is open") - - -func _is_inside_home_space() -> bool: - return ( - _network_home != null - and String(_network_home.get_local_space()).begins_with("rv:") - ) - - -func _can_decorate() -> bool: - return ( - _player != null - and _network_home != null - and _network_home.can_local_decorate() - and _fishing_spot != null - and _fishing_spot.can_open_fishing_shop() - and _player.is_movement_enabled() - ) - - -func _get_selected_decor_id() -> StringName: - if ( - _hotbar == null - or _hotbar.get_selected_assignment_kind() - != PlayerHotbar.AssignmentKind.ITEM - ): - return StringName() - var item_id: StringName = _hotbar.get_selected_item_id() - return item_id if DecorCatalogType.has_product(item_id) else StringName() diff --git a/homes/home_decor_controller.gd.uid b/homes/home_decor_controller.gd.uid deleted file mode 100644 index 1778125..0000000 --- a/homes/home_decor_controller.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://b5j0mqcvhmi54 diff --git a/homes/home_world_service.gd b/homes/home_world_service.gd deleted file mode 100644 index 4ec0645..0000000 --- a/homes/home_world_service.gd +++ /dev/null @@ -1,1504 +0,0 @@ -class_name HomeWorldService -extends Node3D - -const NetworkHomeProtocolType = preload( - "res://network/network_home_protocol.gd" -) -const PlayerHomeStateType = preload("res://homes/player_home_state.gd") -const DecorCatalogType = preload("res://homes/decor_catalog.gd") -const STARTER_MOTORCOACH_SCENE: PackedScene = preload( - "res://homes/assets/starter_motorcoach.glb" -) -const TENT_INTERIOR_SCENE: PackedScene = preload( - "res://homes/assets/tent_interior.glb" -) -const STARTER_RV_INTERIOR_SCENE: PackedScene = preload( - "res://homes/assets/starter_rv_interior.glb" -) -const HOME_PROMPT_FONT: Font = preload("res://ui/fonts/Tuffy_Bold.otf") - -const RV_EXTERIOR_BODY_SIZE := Vector3(11.2, 5.0, 4.7) -const RV_EXTERIOR_BODY_CENTER_Y: float = 2.7 -const RV_EXTERIOR_DOOR_LOCAL := Vector3(-0.4, 1.7, 2.96) -const RV_EXTERIOR_SPAWN_LOCAL := Vector3(-0.4, 0.25, 3.95) -const TENT_EXTERIOR_BODY_SIZE := Vector3(4.6, 3.0, 4.6) -const TENT_EXTERIOR_BODY_CENTER_Y: float = 1.5 -const TENT_EXTERIOR_DOOR_LOCAL := Vector3(0.0, 1.0, 2.3) -const TENT_EXTERIOR_SPAWN_LOCAL := Vector3(0.0, 0.25, 3.3) -const RV_EXTERIOR_PROMPT_LOCAL := Vector3(-0.4, 3.72, 3.02) -const TENT_EXTERIOR_PROMPT_LOCAL := Vector3(0.0, 2.48, 2.36) -const HOME_PROMPT_SIZE := Vector2(0.95, 0.28) -const HOME_PROMPT_CORNER_RADIUS: float = 0.08 -const HOME_PROMPT_TEXT_PIXEL_SIZE: float = 0.0045 -const HOME_PROMPT_TEXT_COLOR := Color("c3dfe6") -const HOME_PROMPT_BACKGROUND_COLOR := Color("0a2d3a") -const EXTERIOR_INTERACTION_DISTANCE: float = 2.7 -const INTERIOR_EXIT_DISTANCE: float = 2.1 -const INTERIOR_ORIGIN := Vector3(1000.0, 300.0, 1000.0) -const INTERIOR_SPACING: float = 14.0 -const INTERIOR_MAX_SPAWN_SLOTS: int = 64 -const TENT_INTERIOR_SIZE := Vector2(4.0, 4.0) -const STARTER_RV_INTERIOR_SIZE := Vector2(4.0, 6.0) -const MIN_EXTERIOR_TRIANGLE_AREA: float = 1.0 -const MIN_EXTERIOR_SEPARATION: float = 14.6 -const EXTERIOR_FOOTPRINT_HALF_EXTENTS := Vector2(6.65, 3.73) -const EXTERIOR_SURFACE_INDEX_CELL_SIZE: float = 5.0 -const EXTERIOR_MAX_SURFACE_HEIGHT_DELTA: float = 0.24 -const EXTERIOR_CLEARANCE_HEIGHT: float = 5.5 -const WORLD_GEOMETRY_COLLISION_LAYER: int = 1 -const DECOR_SELECTION_COLLISION_LAYER: int = 1 << 8 -# Includes the player's 0.45 m capsule radius plus enough movement margin for -# a full low-tick-rate frame before a newly solid placement can be reached. -const PLAYER_DECOR_CLEARANCE_RADIUS: float = 0.8 -const BASIC_CUBE_SIZE := Vector3(1.0, 0.8, 0.8) - -var _world: TestWorld -var _spawn_service: PlayerSpawnService -var _session: NetworkSession -var _dedicated_runtime: bool = false -var _exteriors_root: Node3D -var _interiors_root: Node3D -var _presentations_by_fingerprint: Dictionary[String, Dictionary] = {} -var _local_space: StringName = NetworkHomeProtocolType.WORLD_SPACE -var _decor_under_manipulation: Dictionary[String, bool] = {} -var _pending_solid_decor: Dictionary[String, Dictionary] = {} - - -func setup( - world: TestWorld, - spawn_service: PlayerSpawnService, - session: NetworkSession, - dedicated_runtime: bool, -) -> void: - _world = world - _spawn_service = spawn_service - _session = session - _dedicated_runtime = dedicated_runtime - _ensure_roots() - set_process(true) - - -func is_world_ready() -> bool: - return _world != null and _world.is_world_ready() - - -func clear_presentations() -> void: - _presentations_by_fingerprint.clear() - _decor_under_manipulation.clear() - _pending_solid_decor.clear() - if is_instance_valid(_exteriors_root): - for child: Node in _exteriors_root.get_children(): - child.queue_free() - if is_instance_valid(_interiors_root): - for child: Node in _interiors_root.get_children(): - child.queue_free() - - -func remove_presentation(owner_fingerprint: String) -> void: - var presentation: Dictionary = _presentations_by_fingerprint.get( - owner_fingerprint, {} - ) - if presentation.is_empty(): - return - _clear_decor_collision_tracking(owner_fingerprint) - _free_presentation_nodes(presentation) - _presentations_by_fingerprint.erase(owner_fingerprint) - - -func apply_manifest(data: Dictionary) -> bool: - if ( - not is_world_ready() - or not NetworkHomeProtocolType.validate_manifest_broadcast(data) - ): - return false - _ensure_roots() - var fingerprint: String = str(data["owner_fingerprint"]) - var prior: Dictionary = _presentations_by_fingerprint.get(fingerprint, {}) - var prior_instance_ids: Dictionary[String, bool] = {} - if not prior.is_empty(): - var prior_home: Dictionary = prior.get("home", {}) - for value: Variant in prior_home.get("placements", []): - if typeof(value) == TYPE_DICTIONARY: - prior_instance_ids[str( - (value as Dictionary).get("instance_id", "") - )] = true - if not prior.is_empty(): - _clear_decor_collision_tracking(fingerprint) - _free_presentation_nodes(prior) - var exterior_transform := NetworkHomeProtocolType.array_to_transform( - data["exterior_transform"] - ) - var interior_index: int = int(data["interior_index"]) - var home_data: Dictionary = PlayerHomeStateType.sanitize_save_data( - data["home"] - ) - var exterior := _build_exterior( - fingerprint, exterior_transform, home_data - ) - var interior := _build_interior(fingerprint, interior_index, home_data) - var presentation: Dictionary = { - "owner_peer_id": int(data["owner_peer_id"]), - "fingerprint": fingerprint, - "home": home_data, - "exterior_transform": exterior_transform, - "interior_index": interior_index, - "exterior": exterior, - "interior": interior, - } - _presentations_by_fingerprint[fingerprint] = presentation - _refresh_local_visibility() - if not prior.is_empty(): - for value: Variant in home_data.get("placements", []): - if typeof(value) != TYPE_DICTIONARY: - continue - var instance_id: String = str( - (value as Dictionary).get("instance_id", "") - ) - if not instance_id.is_empty() and not prior_instance_ids.has( - instance_id - ): - call_deferred( - "_spawn_decor_poof", fingerprint, instance_id - ) - return true - - -func choose_exterior_transform( - owner_peer_id: int, - reserved_transforms: Array[Transform3D], -) -> Transform3D: - var spawn_transform: Transform3D = ( - _world.get_player_spawn_transform() - if _world != null else Transform3D.IDENTITY - ) - var triangles: Array[PackedVector3Array] = ( - _world.get_spawn_surface_triangles( - [&"grass_lite", &"grass", &"sand"], -0.5 - ) - if _world != null else [] - ) - if not triangles.is_empty(): - var surface_index: Dictionary = _build_surface_index(triangles) - var strict_result: Transform3D = _find_exterior_transform( - owner_peer_id, - triangles, - surface_index, - spawn_transform, - reserved_transforms, - true, - true, - ) - if strict_result != Transform3D.IDENTITY: - return strict_result - var relaxed_result: Transform3D = _find_exterior_transform( - owner_peer_id, - triangles, - surface_index, - spawn_transform, - reserved_transforms, - false, - true, - ) - if relaxed_result != Transform3D.IDENTITY: - return relaxed_result - var dry_result: Transform3D = _find_exterior_transform( - owner_peer_id, - triangles, - surface_index, - spawn_transform, - reserved_transforms, - false, - false, - ) - if dry_result != Transform3D.IDENTITY: - push_warning( - "Home exterior used a dry fallback with obstructed clearance." - ) - return dry_result - var emergency_result: Transform3D = _find_exterior_transform( - owner_peer_id, - triangles, - surface_index, - spawn_transform, - [], - false, - false, - ) - if emergency_result != Transform3D.IDENTITY: - push_warning( - "Home exterior used an accessible emergency placement." - ) - return emergency_result - push_error("No dry, level home exterior footprint was available.") - # The authored spawn is the only universally reachable point. Never strand - # a home on an unchecked elevation when malformed terrain offers no site. - return spawn_transform - - -func _find_exterior_transform( - owner_peer_id: int, - triangles: Array[PackedVector3Array], - surface_index: Dictionary, - spawn_transform: Transform3D, - reserved_transforms: Array[Transform3D], - require_spawn_clearance: bool, - require_obstacle_clearance: bool, -) -> Transform3D: - var triangle_count: int = triangles.size() - var start: int = posmod(owner_peer_id * 37, triangle_count) - var step: int = _coprime_step(triangle_count, 29) - for offset: int in triangle_count: - var triangle: PackedVector3Array = triangles[ - (start + offset * step) % triangle_count - ] - if not _is_exterior_candidate_triangle(triangle): - continue - var position: Vector3 = ( - triangle[0] + triangle[1] + triangle[2] - ) / 3.0 - if ( - require_spawn_clearance - and position.distance_to(spawn_transform.origin) < 9.0 - ): - continue - if _too_close_to_reserved(position, reserved_transforms): - continue - var yaw: float = float(posmod(owner_peer_id + offset, 4)) * PI * 0.5 - var grounded_position: Vector3 = _ground_exterior_footprint( - position, yaw, triangles, surface_index - ) - if not grounded_position.is_finite(): - continue - if ( - _world != null - and not _world.is_home_exterior_position_accessible( - grounded_position - ) - ): - continue - if ( - require_obstacle_clearance - and not _exterior_volume_is_clear(grounded_position, yaw) - ): - continue - return Transform3D(Basis(Vector3.UP, yaw), grounded_position) - return Transform3D.IDENTITY - - -func _is_exterior_candidate_triangle(triangle: PackedVector3Array) -> bool: - if triangle.size() != 3: - return false - var normal: Vector3 = (triangle[1] - triangle[0]).cross( - triangle[2] - triangle[0] - ) - var area: float = normal.length() * 0.5 - return ( - area >= MIN_EXTERIOR_TRIANGLE_AREA - and absf(normal.normalized().y) >= 0.94 - ) - - -func _ground_exterior_footprint( - position: Vector3, - yaw: float, - triangles: Array[PackedVector3Array], - surface_index: Dictionary, -) -> Vector3: - var heights: Array[float] = [] - for local_sample: Vector2 in _exterior_footprint_samples(): - var rotated_sample: Vector3 = Vector3( - local_sample.x, 0.0, local_sample.y - ).rotated(Vector3.UP, yaw) - var sample := Vector2( - position.x + rotated_sample.x, - position.z + rotated_sample.z, - ) - var height: float = _surface_height_at( - sample, position.y, triangles, surface_index - ) - if not is_finite(height): - return Vector3.INF - heights.append(height) - var minimum_height: float = heights.min() - var maximum_height: float = heights.max() - if maximum_height - minimum_height > EXTERIOR_MAX_SURFACE_HEIGHT_DELTA: - return Vector3.INF - return Vector3(position.x, maximum_height, position.z) - - -func _exterior_footprint_samples() -> Array[Vector2]: - var samples: Array[Vector2] = [] - for x_factor: float in [-1.0, -0.5, 0.0, 0.5, 1.0]: - for z_factor: float in [-1.0, 0.0, 1.0]: - samples.append(Vector2( - EXTERIOR_FOOTPRINT_HALF_EXTENTS.x * x_factor, - EXTERIOR_FOOTPRINT_HALF_EXTENTS.y * z_factor, - )) - return samples - - -func _build_surface_index( - triangles: Array[PackedVector3Array], -) -> Dictionary: - var result: Dictionary = {} - for triangle_index: int in triangles.size(): - var triangle: PackedVector3Array = triangles[triangle_index] - if triangle.size() != 3: - continue - var minimum_x: float = minf( - triangle[0].x, minf(triangle[1].x, triangle[2].x) - ) - var maximum_x: float = maxf( - triangle[0].x, maxf(triangle[1].x, triangle[2].x) - ) - var minimum_z: float = minf( - triangle[0].z, minf(triangle[1].z, triangle[2].z) - ) - var maximum_z: float = maxf( - triangle[0].z, maxf(triangle[1].z, triangle[2].z) - ) - var minimum_cell := Vector2i( - floori(minimum_x / EXTERIOR_SURFACE_INDEX_CELL_SIZE), - floori(minimum_z / EXTERIOR_SURFACE_INDEX_CELL_SIZE), - ) - var maximum_cell := Vector2i( - floori(maximum_x / EXTERIOR_SURFACE_INDEX_CELL_SIZE), - floori(maximum_z / EXTERIOR_SURFACE_INDEX_CELL_SIZE), - ) - for cell_x: int in range(minimum_cell.x, maximum_cell.x + 1): - for cell_y: int in range(minimum_cell.y, maximum_cell.y + 1): - var cell := Vector2i(cell_x, cell_y) - var bucket: Array = result.get(cell, []) - bucket.append(triangle_index) - result[cell] = bucket - return result - - -func _surface_height_at( - point: Vector2, - preferred_height: float, - triangles: Array[PackedVector3Array], - surface_index: Dictionary, -) -> float: - var cell := Vector2i( - floori(point.x / EXTERIOR_SURFACE_INDEX_CELL_SIZE), - floori(point.y / EXTERIOR_SURFACE_INDEX_CELL_SIZE), - ) - var best_height: float = INF - var best_delta: float = INF - for value: Variant in surface_index.get(cell, []): - var triangle: PackedVector3Array = triangles[int(value)] - var height: float = _triangle_height_at(point, triangle) - if not is_finite(height): - continue - var delta: float = absf(height - preferred_height) - if delta < best_delta: - best_delta = delta - best_height = height - return best_height - - -func _triangle_height_at( - point: Vector2, - triangle: PackedVector3Array, -) -> float: - var a := Vector2(triangle[0].x, triangle[0].z) - var b := Vector2(triangle[1].x, triangle[1].z) - var c := Vector2(triangle[2].x, triangle[2].z) - var denominator: float = ( - (b.y - c.y) * (a.x - c.x) - + (c.x - b.x) * (a.y - c.y) - ) - if absf(denominator) <= 0.000001: - return INF - var weight_a: float = ( - (b.y - c.y) * (point.x - c.x) - + (c.x - b.x) * (point.y - c.y) - ) / denominator - var weight_b: float = ( - (c.y - a.y) * (point.x - c.x) - + (a.x - c.x) * (point.y - c.y) - ) / denominator - var weight_c: float = 1.0 - weight_a - weight_b - const EDGE_EPSILON: float = 0.001 - if ( - weight_a < -EDGE_EPSILON - or weight_b < -EDGE_EPSILON - or weight_c < -EDGE_EPSILON - ): - return INF - return ( - weight_a * triangle[0].y - + weight_b * triangle[1].y - + weight_c * triangle[2].y - ) - - -func _exterior_volume_is_clear(position: Vector3, yaw: float) -> bool: - if not is_inside_tree() or get_world_3d() == null: - return true - var shape := BoxShape3D.new() - shape.size = Vector3( - EXTERIOR_FOOTPRINT_HALF_EXTENTS.x * 2.0, - EXTERIOR_CLEARANCE_HEIGHT, - EXTERIOR_FOOTPRINT_HALF_EXTENTS.y * 2.0, - ) - var query := PhysicsShapeQueryParameters3D.new() - query.shape = shape - query.transform = Transform3D( - Basis(Vector3.UP, yaw), - position + Vector3.UP * (EXTERIOR_CLEARANCE_HEIGHT * 0.5 + 0.2), - ) - query.collision_mask = 1 - query.collide_with_areas = false - query.collide_with_bodies = true - return get_world_3d().direct_space_state.intersect_shape(query, 1).is_empty() - - -func _coprime_step(value: int, preferred_step: int) -> int: - var step: int = mini(preferred_step, maxi(value - 1, 1)) - while step > 1 and _greatest_common_divisor(step, value) != 1: - step -= 1 - return step - - -func _greatest_common_divisor(first: int, second: int) -> int: - var a: int = absi(first) - var b: int = absi(second) - while b != 0: - var remainder: int = a % b - a = b - b = remainder - return a - - -func get_exterior_transform(owner_fingerprint: String) -> Transform3D: - var presentation: Dictionary = _presentations_by_fingerprint.get( - owner_fingerprint, {} - ) - return presentation.get("exterior_transform", Transform3D.IDENTITY) - - -func get_exterior_prompt_position(owner_fingerprint: String) -> Vector3: - return get_exterior_transform(owner_fingerprint) * ( - _exterior_prompt_local(owner_fingerprint) - ) - - -func set_exterior_prompt_visible( - owner_fingerprint: String, - requested_visible: bool, - action_text: String = "enter home", -) -> void: - for fingerprint: String in _presentations_by_fingerprint: - var presentation: Dictionary = _presentations_by_fingerprint[fingerprint] - var exterior: Node3D = presentation.get("exterior") - if not is_instance_valid(exterior): - continue - var prompt := exterior.get_node_or_null("DoorPrompt") as Node3D - if prompt == null: - continue - prompt.visible = ( - requested_visible - and fingerprint == owner_fingerprint - and not _dedicated_runtime - ) - if prompt.visible: - var label := prompt.get_node_or_null("Message") as Label3D - if label != null: - label.text = action_text - - -func get_exterior_spawn_transform(owner_fingerprint: String) -> Transform3D: - var exterior_transform: Transform3D = get_exterior_transform( - owner_fingerprint - ) - # Exterior local +Z leads away from the doorway. Player visuals face local - # -Z, so turn the authoritative spawn basis around to start the day facing - # outward instead of looking back into the home. - var outward_spawn_basis: Basis = ( - exterior_transform.basis * Basis(Vector3.UP, PI) - ) - return Transform3D( - outward_spawn_basis, - exterior_transform * _exterior_spawn_local(owner_fingerprint), - ) - - -func get_interior_spawn_transform( - owner_fingerprint: String, - visitor_offset: int = 0, -) -> Transform3D: - # `visitor_offset` is allocated by the host as a compact room-local slot. - # Keep it bounded as a last line of defense if a future caller accidentally - # supplies an engine peer ID instead of a slot. - var visitor_slot: int = posmod(visitor_offset, INTERIOR_MAX_SPAWN_SLOTS) - var exit_direction: Vector3 = get_interior_exit_direction(owner_fingerprint) - var lane_offset: float = ( - float(posmod(visitor_slot, 4)) - 1.5 - ) * 0.28 - var row_offset: float = ( - float(floori(float(visitor_slot) / 4.0)) * 0.28 - ) - # Spawn just through the authored doorway. Local presentation then walks the - # avatar farther into the room before returning movement control. - var spawn_origin: Vector3 = ( - get_interior_exit_position(owner_fingerprint) - + exit_direction * 0.42 - - exit_direction * row_offset - + Vector3(0.0, 0.0, lane_offset) - ) - spawn_origin.y = get_interior_anchor(owner_fingerprint).y - return Transform3D(Basis.IDENTITY, spawn_origin) - - -func get_interior_anchor(owner_fingerprint: String) -> Vector3: - var presentation: Dictionary = _presentations_by_fingerprint.get( - owner_fingerprint, {} - ) - var index: int = int(presentation.get("interior_index", 0)) - return INTERIOR_ORIGIN + Vector3(float(index) * INTERIOR_SPACING, 0.0, 0.0) - - -func is_avatar_near_exterior(avatar: Player, owner_fingerprint: String) -> bool: - if avatar == null or not _presentations_by_fingerprint.has(owner_fingerprint): - return false - var transform: Transform3D = get_exterior_transform(owner_fingerprint) - var door_position: Vector3 = ( - transform * _exterior_door_local(owner_fingerprint) - ) - return avatar.global_position.distance_to(door_position) <= ( - EXTERIOR_INTERACTION_DISTANCE - ) - - -func is_avatar_near_interior_exit( - avatar: Player, - owner_fingerprint: String, -) -> bool: - if avatar == null: - return false - return avatar.global_position.distance_to( - get_interior_exit_position(owner_fingerprint) - ) <= INTERIOR_EXIT_DISTANCE - - -func has_avatar_crossed_interior_exit_threshold( - avatar: Player, - owner_fingerprint: String, -) -> bool: - if avatar == null: - return false - var local_position: Vector3 = interior_global_to_local( - owner_fingerprint, - avatar.global_position, - ) - var threshold_position: Vector3 = interior_global_to_local( - owner_fingerprint, - get_interior_exit_position(owner_fingerprint), - ) - var exit_direction: Vector3 = get_interior_exit_direction( - owner_fingerprint - ) - return ( - (local_position - threshold_position).dot(exit_direction) >= 0.0 - and absf(local_position.z - threshold_position.z) <= 0.85 - and local_position.y >= -0.25 - and local_position.y <= 2.25 - ) - - -func get_interior_exit_direction(owner_fingerprint: String) -> Vector3: - return ( - Vector3.LEFT - if _home_tier(owner_fingerprint) - >= PlayerHomeStateType.STARTER_RV_TIER - else Vector3.RIGHT - ) - - -func get_interior_exit_position(owner_fingerprint: String) -> Vector3: - var tier: int = _home_tier(owner_fingerprint) - return get_interior_anchor(owner_fingerprint) + ( - Vector3(-STARTER_RV_INTERIOR_SIZE.x * 0.5 + 0.6, 0.35, 0.0) - if tier >= PlayerHomeStateType.STARTER_RV_TIER - else Vector3(TENT_INTERIOR_SIZE.x * 0.5 - 0.55, 0.35, 0.0) - ) - - -func get_interior_prompt_position(owner_fingerprint: String) -> Vector3: - return get_interior_exit_position(owner_fingerprint) + Vector3.UP * 1.2 - - -func interior_local_to_global( - owner_fingerprint: String, - local_position: Vector3, -) -> Vector3: - return get_interior_anchor(owner_fingerprint) + local_position - - -func interior_global_to_local( - owner_fingerprint: String, - global_position: Vector3, -) -> Vector3: - return global_position - get_interior_anchor(owner_fingerprint) - - -func get_floor_position_from_screen( - owner_fingerprint: String, - camera: Camera3D, - screen_position: Vector2, -) -> Vector3: - if camera == null or owner_fingerprint.is_empty(): - return Vector3.INF - var ray_origin: Vector3 = camera.project_ray_origin(screen_position) - var ray_direction: Vector3 = camera.project_ray_normal(screen_position) - if absf(ray_direction.y) <= 0.0001: - return Vector3.INF - var floor_y: float = get_interior_anchor(owner_fingerprint).y + 0.1 - var distance: float = (floor_y - ray_origin.y) / ray_direction.y - if distance <= 0.0: - return Vector3.INF - return interior_global_to_local( - owner_fingerprint, - ray_origin + ray_direction * distance, - ) - - -func find_decor_instance_at_screen( - owner_fingerprint: String, - camera: Camera3D, - screen_position: Vector2, -) -> String: - if camera == null or owner_fingerprint.is_empty(): - return "" - var ray_origin: Vector3 = camera.project_ray_origin(screen_position) - var ray_end: Vector3 = ray_origin + camera.project_ray_normal( - screen_position - ) * 50.0 - var query := PhysicsRayQueryParameters3D.create(ray_origin, ray_end) - query.collision_mask = DECOR_SELECTION_COLLISION_LAYER - query.collide_with_areas = false - var result: Dictionary = camera.get_world_3d().direct_space_state.intersect_ray( - query - ) - var collider: Object = result.get("collider") - if collider is Node and (collider as Node).has_meta(&"decor_instance_id"): - if str((collider as Node).get_meta(&"decor_owner_fingerprint")) == ( - owner_fingerprint - ): - return str((collider as Node).get_meta(&"decor_instance_id")) - return "" - - -func get_decor_preview_transform( - owner_fingerprint: String, - instance_id: String, -) -> Dictionary: - var decor: Node3D = _get_decor_presentation( - owner_fingerprint, instance_id - ) - if decor == null: - return {} - return { - "position": Vector3(decor.position.x, 0.0, decor.position.z), - "yaw": decor.rotation.y, - } - - -func set_decor_preview_transform( - owner_fingerprint: String, - instance_id: String, - position: Vector3, - yaw_radians: float, -) -> bool: - var decor: Node3D = _get_decor_presentation( - owner_fingerprint, instance_id - ) - if decor == null or not position.is_finite() or not is_finite(yaw_radians): - return false - decor.position = Vector3(position.x, 0.18, position.z) - decor.rotation.y = yaw_radians - return true - - -func set_decor_manipulation_active( - owner_fingerprint: String, - instance_id: String, - active: bool, -) -> bool: - var decor: Node3D = _get_decor_presentation( - owner_fingerprint, instance_id - ) - if decor == null: - return false - var body: StaticBody3D = decor.get_node_or_null( - "BasicCubeCollision" - ) as StaticBody3D - if body == null: - return false - var key: String = _decor_collision_key( - owner_fingerprint, instance_id - ) - if active: - _decor_under_manipulation[key] = true - _pending_solid_decor.erase(key) - body.collision_layer = DECOR_SELECTION_COLLISION_LAYER - return true - _decor_under_manipulation.erase(key) - _queue_decor_world_collision( - owner_fingerprint, - instance_id, - StringName(str(decor.get_meta(&"decor_id", ""))), - body, - ) - return true - - -func is_decor_placement_clear_of_players( - owner_fingerprint: String, - decor_id: StringName, - local_position: Vector3, - yaw_radians: float, -) -> bool: - if ( - owner_fingerprint.is_empty() - or not DecorCatalogType.has_product(decor_id) - or not local_position.is_finite() - or not is_finite(yaw_radians) - ): - return false - if _spawn_service == null: - return true - var global_center: Vector3 = interior_local_to_global( - owner_fingerprint, local_position - ) - var half_size: Vector2 = DecorCatalogType.get_footprint(decor_id) * 0.5 - for peer_id: int in _spawn_service.get_peer_ids(): - var avatar: Player = _spawn_service.get_avatar(peer_id) - if avatar == null or not is_instance_valid(avatar): - continue - var offset := Vector2( - avatar.global_position.x - global_center.x, - avatar.global_position.z - global_center.z, - ).rotated(-yaw_radians) - var nearest := Vector2( - clampf(offset.x, -half_size.x, half_size.x), - clampf(offset.y, -half_size.y, half_size.y), - ) - if offset.distance_squared_to(nearest) < ( - PLAYER_DECOR_CLEARANCE_RADIUS - * PLAYER_DECOR_CLEARANCE_RADIUS - ): - return false - return true - - -func _get_decor_presentation( - owner_fingerprint: String, - instance_id: String, -) -> Node3D: - if owner_fingerprint.is_empty() or instance_id.is_empty(): - return null - var presentation: Dictionary = _presentations_by_fingerprint.get( - owner_fingerprint, {} - ) - var interior: Node3D = presentation.get("interior") - if not is_instance_valid(interior): - return null - return interior.get_node_or_null( - "Decor/%s" % instance_id - ) as Node3D - - -func find_nearby_exterior_owner(avatar: Player) -> String: - if avatar == null: - return "" - var closest: String = "" - var closest_distance: float = EXTERIOR_INTERACTION_DISTANCE - for fingerprint: String in _presentations_by_fingerprint: - var transform: Transform3D = get_exterior_transform(fingerprint) - var distance: float = avatar.global_position.distance_to( - transform * _exterior_door_local(fingerprint) - ) - if distance <= closest_distance: - closest = fingerprint - closest_distance = distance - return closest - - -func _home_tier(owner_fingerprint: String) -> int: - var presentation: Dictionary = _presentations_by_fingerprint.get( - owner_fingerprint, {} - ) - var home_data: Dictionary = presentation.get("home", {}) - return int(home_data.get("tier", PlayerHomeStateType.STARTER_TIER)) - - -func _exterior_door_local(owner_fingerprint: String) -> Vector3: - return ( - RV_EXTERIOR_DOOR_LOCAL - if _home_tier(owner_fingerprint) - >= PlayerHomeStateType.STARTER_RV_TIER - else TENT_EXTERIOR_DOOR_LOCAL - ) - - -func _exterior_spawn_local(owner_fingerprint: String) -> Vector3: - return ( - RV_EXTERIOR_SPAWN_LOCAL - if _home_tier(owner_fingerprint) - >= PlayerHomeStateType.STARTER_RV_TIER - else TENT_EXTERIOR_SPAWN_LOCAL - ) - - -func _exterior_prompt_local(owner_fingerprint: String) -> Vector3: - return ( - RV_EXTERIOR_PROMPT_LOCAL - if _home_tier(owner_fingerprint) - >= PlayerHomeStateType.STARTER_RV_TIER - else TENT_EXTERIOR_PROMPT_LOCAL - ) - - -func set_local_space(space_id: StringName) -> void: - _local_space = space_id - _refresh_local_visibility() - - -func get_manifest(owner_fingerprint: String) -> Dictionary: - var presentation: Dictionary = _presentations_by_fingerprint.get( - owner_fingerprint, {} - ) - return (presentation.get("home", {}) as Dictionary).duplicate(true) - - -func _process(_delta: float) -> void: - _refresh_pending_decor_collisions() - if not _dedicated_runtime: - _refresh_nearest_wall() - - -func _queue_decor_world_collision( - owner_fingerprint: String, - instance_id: String, - decor_id: StringName, - body: StaticBody3D, -) -> void: - if ( - owner_fingerprint.is_empty() - or instance_id.is_empty() - or not DecorCatalogType.has_product(decor_id) - or body == null - ): - return - var key: String = _decor_collision_key( - owner_fingerprint, instance_id - ) - body.collision_layer = DECOR_SELECTION_COLLISION_LAYER - _pending_solid_decor[key] = { - "owner_fingerprint": owner_fingerprint, - "instance_id": instance_id, - "decor_id": decor_id, - "body": body, - } - - -func _refresh_pending_decor_collisions() -> void: - if _pending_solid_decor.is_empty(): - return - var keys: Array[String] = [] - keys.assign(_pending_solid_decor.keys()) - for key: String in keys: - if _decor_under_manipulation.has(key): - continue - var pending: Dictionary = _pending_solid_decor.get(key, {}) - var body: StaticBody3D = pending.get("body") as StaticBody3D - var owner_fingerprint: String = str( - pending.get("owner_fingerprint", "") - ) - var instance_id: String = str(pending.get("instance_id", "")) - var decor_id := StringName(str(pending.get("decor_id", ""))) - var decor: Node3D = _get_decor_presentation( - owner_fingerprint, instance_id - ) - if body == null or decor == null or not is_instance_valid(body): - _pending_solid_decor.erase(key) - continue - if not is_decor_placement_clear_of_players( - owner_fingerprint, - decor_id, - Vector3(decor.position.x, 0.0, decor.position.z), - decor.rotation.y, - ): - continue - body.collision_layer = ( - WORLD_GEOMETRY_COLLISION_LAYER - | DECOR_SELECTION_COLLISION_LAYER - ) - _pending_solid_decor.erase(key) - - -func _clear_decor_collision_tracking(owner_fingerprint: String) -> void: - var prefix: String = "%s/" % owner_fingerprint - for key: String in _decor_under_manipulation.keys(): - if key.begins_with(prefix): - _decor_under_manipulation.erase(key) - for key: String in _pending_solid_decor.keys(): - if key.begins_with(prefix): - _pending_solid_decor.erase(key) - - -static func _decor_collision_key( - owner_fingerprint: String, - instance_id: String, -) -> String: - return "%s/%s" % [owner_fingerprint, instance_id] - - -func _ensure_roots() -> void: - if not is_instance_valid(_exteriors_root): - _exteriors_root = Node3D.new() - _exteriors_root.name = "RVExteriors" - add_child(_exteriors_root) - if not is_instance_valid(_interiors_root): - _interiors_root = Node3D.new() - _interiors_root.name = "RVInteriors" - add_child(_interiors_root) - - -func _build_exterior( - fingerprint: String, - world_transform: Transform3D, - home_data: Dictionary, -) -> Node3D: - var root := Node3D.new() - root.name = "Home_%s" % fingerprint.left(8) - _exteriors_root.add_child(root) - root.global_transform = world_transform - var tier: int = int(home_data.get( - "tier", PlayerHomeStateType.STARTER_TIER - )) - if tier >= PlayerHomeStateType.STARTER_RV_TIER: - if not _dedicated_runtime: - var motorcoach: Node3D = ( - STARTER_MOTORCOACH_SCENE.instantiate() as Node3D - ) - if motorcoach != null: - motorcoach.name = "StarterMotorcoach" - root.add_child(motorcoach) - else: - push_error( - "The starter motorcoach exterior could not be instantiated." - ) - _add_box_collision( - root, - "Body", - RV_EXTERIOR_BODY_SIZE, - Vector3(0.0, RV_EXTERIOR_BODY_CENTER_Y, 0.0), - ) - else: - if not _dedicated_runtime: - _add_tent_exterior(root) - _add_box_collision( - root, - "Body", - TENT_EXTERIOR_BODY_SIZE, - Vector3(0.0, TENT_EXTERIOR_BODY_CENTER_Y, 0.0), - ) - if not _dedicated_runtime: - _add_exterior_prompt(root, tier) - root.visible = not _dedicated_runtime - return root - - -func _build_interior( - fingerprint: String, - interior_index: int, - home_data: Dictionary, -) -> Node3D: - var root := Node3D.new() - root.name = "HomeInterior_%s" % fingerprint.left(8) - root.position = INTERIOR_ORIGIN + Vector3( - float(interior_index) * INTERIOR_SPACING, 0.0, 0.0 - ) - _interiors_root.add_child(root) - var tier: int = int(home_data.get( - "tier", PlayerHomeStateType.STARTER_TIER - )) - var scene: PackedScene = ( - STARTER_RV_INTERIOR_SCENE - if tier >= PlayerHomeStateType.STARTER_RV_TIER - else TENT_INTERIOR_SCENE - ) - var shell: Node3D = scene.instantiate() as Node3D - if shell == null: - push_error("The home interior shell could not be instantiated.") - else: - shell.name = "InteriorShell" - root.add_child(shell) - _prepare_imported_interior(shell) - var decor := Node3D.new() - decor.name = "Decor" - root.add_child(decor) - _rebuild_decor(decor, home_data, fingerprint) - root.visible = false - if _dedicated_runtime: - _set_geometry_visible(root, false) - return root - - -func _add_tent_exterior(parent: Node3D) -> void: - var tent := MeshInstance3D.new() - tent.name = "StarterTent" - var mesh := CylinderMesh.new() - mesh.top_radius = 0.0 - mesh.bottom_radius = 3.05 - mesh.height = 3.0 - mesh.radial_segments = 4 - mesh.rings = 1 - mesh.cap_top = false - mesh.material = _material(Color("9a7049")) - tent.mesh = mesh - tent.position.y = 1.5 - tent.rotation.y = PI * 0.25 - parent.add_child(tent) - _add_box( - parent, - "TentDoor", - Vector3(1.25, 1.85, 0.04), - Vector3(0.0, 0.95, 2.18), - Color("173b32"), - false, - ) - - -func _add_exterior_prompt(parent: Node3D, tier: int) -> void: - var prompt := Node3D.new() - prompt.name = "DoorPrompt" - prompt.position = ( - RV_EXTERIOR_PROMPT_LOCAL - if tier >= PlayerHomeStateType.STARTER_RV_TIER - else TENT_EXTERIOR_PROMPT_LOCAL - ) - prompt.visible = false - parent.add_child(prompt) - - var background := MeshInstance3D.new() - background.name = "Background" - background.mesh = _rounded_rectangle_mesh( - HOME_PROMPT_SIZE, - HOME_PROMPT_CORNER_RADIUS, - ) - background.material_override = _prompt_material( - HOME_PROMPT_BACKGROUND_COLOR - ) - prompt.add_child(background) - - var label := Label3D.new() - label.name = "Message" - label.text = "enter home" - label.position.z = 0.012 - label.font = HOME_PROMPT_FONT - label.font_size = 28 - label.pixel_size = HOME_PROMPT_TEXT_PIXEL_SIZE - label.modulate = HOME_PROMPT_TEXT_COLOR - label.outline_size = 0 - label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - label.width = HOME_PROMPT_SIZE.x / HOME_PROMPT_TEXT_PIXEL_SIZE - label.double_sided = true - prompt.add_child(label) - - -func _rounded_rectangle_mesh(size: Vector2, radius: float) -> ArrayMesh: - var half_size: Vector2 = size * 0.5 - var clamped_radius: float = minf( - radius, - minf(half_size.x, half_size.y), - ) - var corners: Array[Vector2] = [ - Vector2(half_size.x - clamped_radius, half_size.y - clamped_radius), - Vector2(-half_size.x + clamped_radius, half_size.y - clamped_radius), - Vector2(-half_size.x + clamped_radius, -half_size.y + clamped_radius), - Vector2(half_size.x - clamped_radius, -half_size.y + clamped_radius), - ] - var start_angles: Array[float] = [0.0, PI * 0.5, PI, PI * 1.5] - var perimeter: Array[Vector2] = [] - const CORNER_SEGMENTS: int = 4 - for corner_index: int in corners.size(): - for segment_index: int in range(CORNER_SEGMENTS + 1): - var angle: float = start_angles[corner_index] + ( - PI * 0.5 * float(segment_index) / float(CORNER_SEGMENTS) - ) - perimeter.append( - corners[corner_index] - + Vector2(cos(angle), sin(angle)) * clamped_radius - ) - var surface := SurfaceTool.new() - surface.begin(Mesh.PRIMITIVE_TRIANGLES) - for index: int in perimeter.size(): - var first: Vector2 = perimeter[index] - var second: Vector2 = perimeter[(index + 1) % perimeter.size()] - for point: Vector2 in [Vector2.ZERO, first, second]: - surface.set_normal(Vector3.FORWARD) - surface.add_vertex(Vector3(point.x, point.y, 0.0)) - return surface.commit() - - -func _prompt_material(color: Color) -> StandardMaterial3D: - var material := StandardMaterial3D.new() - material.albedo_color = color - material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED - material.roughness = 1.0 - material.metallic = 0.0 - material.cull_mode = BaseMaterial3D.CULL_DISABLED - return material - - -func _prepare_imported_interior(shell: Node3D) -> void: - for value: Node in shell.find_children("*", "MeshInstance3D", true, false): - var mesh_instance := value as MeshInstance3D - if mesh_instance == null or mesh_instance.mesh == null: - continue - for surface_index: int in mesh_instance.mesh.get_surface_count(): - var source_material: Material = mesh_instance.get_active_material( - surface_index - ) - var material := source_material as StandardMaterial3D - if material == null: - continue - var flat_material := material.duplicate() as StandardMaterial3D - flat_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED - flat_material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST - flat_material.roughness = 1.0 - flat_material.metallic = 0.0 - mesh_instance.set_surface_override_material( - surface_index, flat_material - ) - _add_imported_mesh_collision(mesh_instance) - - -func _add_imported_mesh_collision(mesh_instance: MeshInstance3D) -> void: - var parent: Node = mesh_instance.get_parent() - if parent == null or mesh_instance.mesh == null: - return - var shape: ConcavePolygonShape3D = mesh_instance.mesh.create_trimesh_shape() - if shape == null: - return - var body := StaticBody3D.new() - body.name = "%sCollision" % mesh_instance.name - body.transform = mesh_instance.transform - var collision := CollisionShape3D.new() - collision.shape = shape - body.add_child(collision) - parent.add_child(body) - - -func _rebuild_decor( - parent: Node3D, - home_data: Dictionary, - owner_fingerprint: String, -) -> void: - for value: Variant in home_data.get("placements", []): - if typeof(value) != TYPE_DICTIONARY: - continue - var placement: Dictionary = value - var decor_id := StringName(str(placement["decor_id"])) - if decor_id != DecorCatalogType.BASIC_CUBE_ID: - continue - var position_values: Array = placement["position"] - var placement_root := Node3D.new() - placement_root.name = str(placement["instance_id"]) - placement_root.position = Vector3( - float(position_values[0]), 0.18, float(position_values[2]) - ) - placement_root.rotation.y = float(placement["yaw"]) - placement_root.set_meta( - &"decor_instance_id", str(placement["instance_id"]) - ) - placement_root.set_meta( - &"decor_owner_fingerprint", owner_fingerprint - ) - placement_root.set_meta(&"decor_id", String(decor_id)) - parent.add_child(placement_root) - var body := _add_box( - placement_root, "BasicCube", BASIC_CUBE_SIZE, - Vector3(0.0, BASIC_CUBE_SIZE.y * 0.5, 0.0), - Color("b77a55"), true, - ) - if body != null: - body.collision_layer = DECOR_SELECTION_COLLISION_LAYER - body.set_meta( - &"decor_instance_id", str(placement["instance_id"]) - ) - body.set_meta( - &"decor_owner_fingerprint", owner_fingerprint - ) - body.set_meta(&"decor_id", String(decor_id)) - _queue_decor_world_collision( - owner_fingerprint, - str(placement["instance_id"]), - decor_id, - body, - ) - _add_box( - placement_root, - "BasicCubeFront", - Vector3(BASIC_CUBE_SIZE.x * 0.82, BASIC_CUBE_SIZE.y * 0.68, 0.025), - Vector3( - 0.0, - BASIC_CUBE_SIZE.y * 0.5, - -BASIC_CUBE_SIZE.z * 0.5 - 0.013, - ), - Color("2897a8"), - false, - ) - - -func _add_wall( - parent: Node3D, - node_name: String, - size: Vector3, - position: Vector3, -) -> void: - _add_box(parent, node_name, size, position, Color("d6caaa"), true) - - -func _add_box( - parent: Node3D, - node_name: String, - size: Vector3, - position: Vector3, - color: Color, - with_collision: bool, - invert_faces: bool = false, -) -> StaticBody3D: - var mesh_instance := MeshInstance3D.new() - mesh_instance.name = node_name - var mesh := BoxMesh.new() - mesh.size = size - mesh.material = _material(color) - if invert_faces: - mesh.flip_faces = true - mesh_instance.mesh = mesh - mesh_instance.position = position - parent.add_child(mesh_instance) - if with_collision: - return _add_box_collision(parent, node_name, size, position) - return null - - -func _add_box_collision( - parent: Node3D, - node_name: String, - size: Vector3, - position: Vector3, -) -> StaticBody3D: - var body := StaticBody3D.new() - body.name = "%sCollision" % node_name - body.position = position - var collision := CollisionShape3D.new() - var shape := BoxShape3D.new() - shape.size = size - collision.shape = shape - body.add_child(collision) - parent.add_child(body) - return body - - -func _spawn_decor_poof( - owner_fingerprint: String, - instance_id: String, -) -> void: - var presentation: Dictionary = _presentations_by_fingerprint.get( - owner_fingerprint, {} - ) - var interior: Node3D = presentation.get("interior") - if not is_instance_valid(interior): - return - var placement_root: Node3D = interior.get_node_or_null( - "Decor/%s" % instance_id - ) - if placement_root == null: - return - var poof := CPUParticles3D.new() - poof.name = "DecorPoof" - poof.amount = 12 - poof.lifetime = 0.45 - poof.one_shot = true - poof.explosiveness = 0.9 - poof.direction = Vector3.UP - poof.spread = 80.0 - poof.initial_velocity_min = 0.7 - poof.initial_velocity_max = 1.5 - poof.scale_amount_min = 0.08 - poof.scale_amount_max = 0.16 - var particle_mesh := SphereMesh.new() - particle_mesh.radius = 0.06 - particle_mesh.height = 0.12 - particle_mesh.material = _material(Color("d9c5a3")) - poof.mesh = particle_mesh - placement_root.add_child(poof) - poof.emitting = true - get_tree().create_timer(poof.lifetime + 0.1).timeout.connect( - poof.queue_free - ) - - -func _material(color: Color) -> StandardMaterial3D: - var material := StandardMaterial3D.new() - material.albedo_color = color - material.roughness = 1.0 - material.shading_mode = BaseMaterial3D.SHADING_MODE_PER_VERTEX - return material - - -func _free_presentation_nodes(presentation: Dictionary) -> void: - for key: String in ["exterior", "interior"]: - var node: Node = presentation.get(key) - if is_instance_valid(node): - node.queue_free() - - -func _refresh_local_visibility() -> void: - var active_fingerprint: String = "" - if String(_local_space).begins_with("rv:"): - active_fingerprint = String(_local_space).trim_prefix("rv:") - if _world != null: - _world.set_local_home_interior_presentation_active( - not active_fingerprint.is_empty() - ) - for fingerprint: String in _presentations_by_fingerprint: - var presentation: Dictionary = _presentations_by_fingerprint[fingerprint] - var interior: Node3D = presentation.get("interior") - if is_instance_valid(interior): - interior.visible = ( - not _dedicated_runtime and fingerprint == active_fingerprint - ) - var exterior: Node3D = presentation.get("exterior") - if is_instance_valid(exterior): - exterior.visible = ( - not _dedicated_runtime and active_fingerprint.is_empty() - ) - var local_player: Player = ( - _spawn_service.get_local_player() - if _spawn_service != null else null - ) - if local_player == null: - return - var excluded_wall_rids: Array[RID] = [] - if not active_fingerprint.is_empty(): - var active: Dictionary = _presentations_by_fingerprint.get( - active_fingerprint, {} - ) - var active_interior: Node3D = active.get("interior") - if is_instance_valid(active_interior): - for wall_name: String in [ - "WallNorth", "WallSouth", "WallWest", "WallEast", - ]: - var body := active_interior.find_child( - "%sCollision" % wall_name, true, false - ) as StaticBody3D - if body != null: - excluded_wall_rids.append(body.get_rid()) - local_player.set_rv_interior_camera_mode( - not active_fingerprint.is_empty(), excluded_wall_rids - ) - - -func _refresh_nearest_wall() -> void: - if not String(_local_space).begins_with("rv:") or _spawn_service == null: - return - var fingerprint: String = String(_local_space).trim_prefix("rv:") - var presentation: Dictionary = _presentations_by_fingerprint.get( - fingerprint, {} - ) - var interior: Node3D = presentation.get("interior") - var player: Player = _spawn_service.get_local_player() - if not is_instance_valid(interior) or player == null: - return - var camera: Camera3D = player.get_active_gameplay_camera() - if camera == null: - return - var local_camera: Vector3 = interior.to_local(camera.global_position) - var local_view_direction: Vector3 = ( - interior.global_basis.inverse() * -camera.global_basis.z - ).normalized() - var horizontal_view_weight: float = absf(local_view_direction.x) - var depth_view_weight: float = absf(local_view_direction.z) - var tier: int = int((presentation.get("home", {}) as Dictionary).get( - "tier", PlayerHomeStateType.STARTER_TIER - )) - var interior_size: Vector2 = ( - STARTER_RV_INTERIOR_SIZE - if tier >= PlayerHomeStateType.STARTER_RV_TIER - else TENT_INTERIOR_SIZE - ) - var hidden_walls: Dictionary[String, bool] = {} - var outside_horizontal_wall: bool = ( - absf(local_camera.x) >= interior_size.x * 0.5 - 0.05 - ) - var outside_depth_wall: bool = ( - absf(local_camera.z) >= interior_size.y * 0.5 - 0.05 - ) - # At a diagonal, both near walls occupy the view. Use the camera's viewing - # direction to hide both instead of choosing only the dominant position - # axis and leaving the adjacent wall across the screen. - if ( - outside_horizontal_wall - and horizontal_view_weight >= depth_view_weight * 0.35 - ): - hidden_walls[ - "WallEast" if local_camera.x >= 0.0 else "WallWest" - ] = true - if ( - outside_depth_wall - and depth_view_weight >= horizontal_view_weight * 0.35 - ): - hidden_walls[ - "WallSouth" if local_camera.z >= 0.0 else "WallNorth" - ] = true - if hidden_walls.is_empty(): - var fallback_wall: String = ( - "WallEast" if local_camera.x >= 0.0 else "WallWest" - ) - if depth_view_weight > horizontal_view_weight: - fallback_wall = ( - "WallSouth" if local_camera.z >= 0.0 else "WallNorth" - ) - hidden_walls[fallback_wall] = true - for wall_name: String in [ - "WallNorth", "WallSouth", "WallWest", "WallEast", - ]: - var wall := interior.find_child( - wall_name, true, false - ) as MeshInstance3D - if wall != null: - wall.visible = not hidden_walls.has(wall_name) - for child: Node in interior.find_children( - "%s*" % wall_name, "GeometryInstance3D", true, false - ): - if ( - child is GeometryInstance3D - and String(child.name).begins_with(wall_name) - ): - (child as GeometryInstance3D).visible = ( - not hidden_walls.has(wall_name) - ) - - -func _too_close_to_reserved( - position: Vector3, - reserved_transforms: Array[Transform3D], -) -> bool: - for transform: Transform3D in reserved_transforms: - if position.distance_to(transform.origin) < MIN_EXTERIOR_SEPARATION: - return true - return false - - -func _set_geometry_visible(root: Node, visible: bool) -> void: - for node: Node in root.find_children("*", "GeometryInstance3D", true, false): - (node as GeometryInstance3D).visible = visible diff --git a/homes/home_world_service.gd.uid b/homes/home_world_service.gd.uid deleted file mode 100644 index 5505d06..0000000 --- a/homes/home_world_service.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cy2kjoxhwxujd diff --git a/homes/player_home_state.gd b/homes/player_home_state.gd deleted file mode 100644 index a89d6a3..0000000 --- a/homes/player_home_state.gd +++ /dev/null @@ -1,841 +0,0 @@ -class_name PlayerHomeState -extends Node - -signal changed - -const DecorCatalogType = preload("res://homes/decor_catalog.gd") - -const HOME_SCHEMA_VERSION: int = 1 -const STARTER_TIER: int = 0 -const STARTER_EXTERIOR_ID: StringName = &"starter_tent" -const STARTER_RV_TIER: int = 1 -const STARTER_RV_EXTERIOR_ID: StringName = &"starter_motorcoach" -const MAX_TIER: int = STARTER_RV_TIER -const HOME_UPGRADE_COSTS: Array[int] = [50] -const STORAGE_CAPACITIES: Array[int] = [9, 18] -const MAX_PLACEMENTS: int = 128 -const MAX_INSTANCE_ID_LENGTH: int = 96 -const MAX_REVISION: int = 2147483647 -const GRID_SIZE: float = 0.5 -const FREE_PLACEMENT_SEARCH_STEP: float = 0.1 -const FREE_PLACEMENT_SEARCH_ANGLES: int = 24 -const NEAREST_PLACEMENT_SEARCH_DISTANCE: float = 2.0 -const TENT_INTERIOR_HALF_EXTENTS := Vector2(1.91, 1.91) -const STARTER_RV_INTERIOR_HALF_EXTENTS := Vector2(1.91, 2.91) -const PLACEMENT_EDGE_PADDING: float = 0.02 - -enum Privacy { - OPEN, - CLOSED, -} - -enum PlacementMode { - GRID, - FREE, -} - -var _tier: int = STARTER_TIER -var _exterior_id: StringName = STARTER_EXTERIOR_ID -var _privacy: Privacy = Privacy.OPEN -var _owned_decor: Dictionary[StringName, int] = {} -var _placements: Array[Dictionary] = [] -var _revision: int = 0 -var _next_instance_sequence: int = 1 - - -func get_tier() -> int: - return _tier - - -func get_exterior_id() -> StringName: - return _exterior_id - - -func get_storage_capacity() -> int: - return storage_capacity_for_tier(_tier) - - -func get_next_upgrade_cost() -> int: - if _tier >= MAX_TIER: - return -1 - return HOME_UPGRADE_COSTS[_tier] - - -func get_next_storage_capacity() -> int: - if _tier >= MAX_TIER: - return -1 - return storage_capacity_for_tier(_tier + 1) - - -func upgrade_to_next_tier() -> bool: - if _tier >= MAX_TIER: - return false - _tier += 1 - _exterior_id = exterior_id_for_tier(_tier) - _touch() - return true - - -static func exterior_id_for_tier(tier: int) -> StringName: - return ( - STARTER_RV_EXTERIOR_ID - if tier >= STARTER_RV_TIER - else STARTER_EXTERIOR_ID - ) - - -static func storage_capacity_for_tier(tier: int) -> int: - return STORAGE_CAPACITIES[clampi(tier, STARTER_TIER, MAX_TIER)] - - -static func interior_half_extents_for_tier(tier: int) -> Vector2: - return ( - STARTER_RV_INTERIOR_HALF_EXTENTS - if tier >= STARTER_RV_TIER - else TENT_INTERIOR_HALF_EXTENTS - ) - - -func get_privacy() -> Privacy: - return _privacy - - -func get_revision() -> int: - return _revision - - -func get_owned_count(decor_id: StringName) -> int: - return int(_owned_decor.get(decor_id, 0)) - - -func get_placed_count(decor_id: StringName) -> int: - var count: int = 0 - for placement: Dictionary in _placements: - if StringName(str(placement.get("decor_id", ""))) == decor_id: - count += 1 - return count - - -func get_available_count(decor_id: StringName) -> int: - return maxi( - get_owned_count(decor_id) - get_placed_count(decor_id), - 0, - ) - - -func get_owned_decor() -> Dictionary[StringName, int]: - return _owned_decor.duplicate() - - -func get_placements() -> Array[Dictionary]: - var result: Array[Dictionary] = [] - for placement: Dictionary in _placements: - result.append(placement.duplicate(true)) - return result - - -func get_placement(instance_id: String) -> Dictionary: - var index: int = _placement_index(instance_id) - return _placements[index].duplicate(true) if index >= 0 else {} - - -func can_place_decor( - decor_id: StringName, - position: Vector3, - yaw_radians: float, - mode: PlacementMode, -) -> bool: - if ( - get_available_count(decor_id) <= 0 - or _placements.size() >= MAX_PLACEMENTS - or not _valid_mode(mode) - ): - return false - return not get_nearest_valid_placement_transform( - decor_id, - position, - yaw_radians, - mode, - "", - ).is_empty() - - -func set_privacy(value: Privacy) -> bool: - if value < Privacy.OPEN or value > Privacy.CLOSED: - return false - if _privacy == value: - return true - _privacy = value - _touch() - return true - - -func add_owned_decor( - decor_id: StringName, - quantity: int = 1, -) -> bool: - if ( - not DecorCatalogType.has_product(decor_id) - or quantity <= 0 - ): - return false - var current: int = get_owned_count(decor_id) - if current + quantity > DecorCatalogType.MAX_OWNED_PER_PRODUCT: - return false - _owned_decor[decor_id] = current + quantity - _touch() - return true - - -func remove_owned_decor( - decor_id: StringName, - quantity: int = 1, -) -> bool: - if quantity <= 0: - return false - var current: int = get_owned_count(decor_id) - if current - quantity < get_placed_count(decor_id): - return false - var remaining: int = current - quantity - if remaining < 0: - return false - if remaining == 0: - _owned_decor.erase(decor_id) - else: - _owned_decor[decor_id] = remaining - _touch() - return true - - -func place_decor( - decor_id: StringName, - position: Vector3, - yaw_radians: float, - mode: PlacementMode, -) -> String: - if ( - get_available_count(decor_id) <= 0 - or _placements.size() >= MAX_PLACEMENTS - or not _valid_mode(mode) - ): - return "" - var normalized := get_nearest_valid_placement_transform( - decor_id, - position, - yaw_radians, - mode, - "", - ) - if normalized.is_empty(): - return "" - var instance_id := _make_instance_id() - var placement: Dictionary = { - "instance_id": instance_id, - "decor_id": String(decor_id), - "mode": int(mode), - "position": _vector3_to_array(normalized["position"]), - "yaw": float(normalized["yaw"]), - } - _placements.append(placement) - _touch() - return instance_id - - -func update_placement( - instance_id: String, - position: Vector3, - yaw_radians: float, - mode: PlacementMode, -) -> bool: - var index: int = _placement_index(instance_id) - var normalized: Dictionary = get_valid_update_transform( - instance_id, position, yaw_radians, mode - ) - if index < 0 or normalized.is_empty(): - return false - var placement: Dictionary = _placements[index].duplicate(true) - placement["mode"] = int(mode) - placement["position"] = _vector3_to_array(normalized["position"]) - placement["yaw"] = float(normalized["yaw"]) - _placements[index] = placement - _touch() - return true - - -func get_valid_update_transform( - instance_id: String, - position: Vector3, - yaw_radians: float, - mode: PlacementMode, - candidate_validator: Callable = Callable(), -) -> Dictionary: - var index: int = _placement_index(instance_id) - if index < 0 or not _valid_mode(mode): - return {} - var decor_id := StringName( - str(_placements[index].get("decor_id", "")) - ) - return get_nearest_valid_placement_transform( - decor_id, - position, - yaw_radians, - mode, - instance_id, - candidate_validator, - ) - - -func get_nearest_valid_placement_transform( - decor_id: StringName, - position: Vector3, - yaw_radians: float, - mode: PlacementMode, - ignored_instance_id: String = "", - candidate_validator: Callable = Callable(), -) -> Dictionary: - var normalized: Dictionary = normalized_placement_transform( - decor_id, position, yaw_radians, mode, _tier - ) - if normalized.is_empty(): - return {} - if ( - not _overlaps_existing( - decor_id, normalized, ignored_instance_id - ) - and _candidate_is_accepted(normalized, candidate_validator) - ): - return normalized - return ( - _find_nearest_grid_transform( - decor_id, - normalized, - ignored_instance_id, - candidate_validator, - ) - if mode == PlacementMode.GRID - else _find_nearest_free_transform( - decor_id, - normalized, - ignored_instance_id, - candidate_validator, - ) - ) - - -func remove_placement(instance_id: String) -> bool: - var index: int = _placement_index(instance_id) - if index < 0: - return false - _placements.remove_at(index) - _touch() - return true - - -func reset_to_defaults() -> void: - _tier = STARTER_TIER - _exterior_id = STARTER_EXTERIOR_ID - _privacy = Privacy.OPEN - _owned_decor.clear() - _placements.clear() - _revision = 0 - _next_instance_sequence = 1 - changed.emit() - - -func to_save_data() -> Dictionary: - var owned: Dictionary = {} - var ids: Array[StringName] = [] - ids.assign(_owned_decor.keys()) - ids.sort_custom(func(first: StringName, second: StringName) -> bool: - return String(first) < String(second) - ) - for decor_id: StringName in ids: - owned[String(decor_id)] = _owned_decor[decor_id] - return { - "schema_version": HOME_SCHEMA_VERSION, - "tier": _tier, - "exterior_id": String(_exterior_id), - "privacy": int(_privacy), - "owned_decor": owned, - "placements": get_placements(), - "revision": _revision, - "next_instance_sequence": _next_instance_sequence, - } - - -func restore_from_save_data(data: Dictionary) -> bool: - var sanitized: Dictionary = sanitize_save_data(data) - if sanitized.is_empty(): - return false - _tier = int(sanitized["tier"]) - _exterior_id = exterior_id_for_tier(_tier) - _privacy = int(sanitized["privacy"]) - _owned_decor.clear() - for key: Variant in (sanitized["owned_decor"] as Dictionary): - _owned_decor[StringName(str(key))] = int( - (sanitized["owned_decor"] as Dictionary)[key] - ) - _placements.clear() - for value: Variant in sanitized["placements"] as Array: - _placements.append((value as Dictionary).duplicate(true)) - _revision = int(sanitized["revision"]) - _next_instance_sequence = int(sanitized["next_instance_sequence"]) - changed.emit() - return true - - -static func default_save_data() -> Dictionary: - return { - "schema_version": HOME_SCHEMA_VERSION, - "tier": STARTER_TIER, - "exterior_id": String(STARTER_EXTERIOR_ID), - "privacy": int(Privacy.OPEN), - "owned_decor": {}, - "placements": [], - "revision": 0, - "next_instance_sequence": 1, - } - - -static func sanitize_save_data(data: Dictionary) -> Dictionary: - if ( - not _is_integer_value(data.get("schema_version")) - or int(data.get("schema_version", -1)) != HOME_SCHEMA_VERSION - or not _is_integer_value(data.get("tier")) - or typeof(data.get("exterior_id")) not in [ - TYPE_STRING, TYPE_STRING_NAME, - ] - or not _is_integer_value(data.get("privacy")) - or typeof(data.get("owned_decor")) != TYPE_DICTIONARY - or typeof(data.get("placements")) != TYPE_ARRAY - or not _is_integer_value(data.get("revision")) - or not _is_integer_value(data.get("next_instance_sequence")) - ): - return {} - var tier: int = int(data["tier"]) - var exterior_id: String = str(data["exterior_id"]) - var privacy: int = int(data["privacy"]) - var revision: int = int(data["revision"]) - var next_sequence: int = int(data["next_instance_sequence"]) - if ( - tier < STARTER_TIER - or tier > MAX_TIER - or exterior_id.is_empty() - or exterior_id.length() > 96 - or privacy < Privacy.OPEN - or privacy > Privacy.CLOSED - or revision < 0 - or revision > MAX_REVISION - or next_sequence < 1 - or next_sequence > MAX_REVISION - ): - return {} - exterior_id = String(exterior_id_for_tier(tier)) - var sanitized_owned: Dictionary = {} - var owned: Dictionary = data["owned_decor"] - for key: Variant in owned: - if typeof(key) not in [TYPE_STRING, TYPE_STRING_NAME]: - return {} - var decor_id := StringName(str(key)) - var quantity_value: Variant = owned[key] - if ( - not DecorCatalogType.has_product(decor_id) - or not _is_integer_value(quantity_value) - or int(quantity_value) <= 0 - or int(quantity_value) > DecorCatalogType.MAX_OWNED_PER_PRODUCT - ): - return {} - sanitized_owned[String(decor_id)] = int(quantity_value) - var values: Array = data["placements"] - if values.size() > MAX_PLACEMENTS: - return {} - var sanitized_placements: Array[Dictionary] = [] - var seen_instance_ids: Dictionary[String, bool] = {} - var placed_counts: Dictionary[StringName, int] = {} - for value: Variant in values: - if typeof(value) != TYPE_DICTIONARY: - return {} - var sanitized: Dictionary = sanitize_placement(value, tier) - if sanitized.is_empty(): - return {} - var instance_id: String = str(sanitized["instance_id"]) - var decor_id := StringName(str(sanitized["decor_id"])) - if seen_instance_ids.has(instance_id): - return {} - seen_instance_ids[instance_id] = true - placed_counts[decor_id] = int( - placed_counts.get(decor_id, 0) - ) + 1 - if ( - int(placed_counts[decor_id]) - > int(sanitized_owned.get(String(decor_id), 0)) - ): - return {} - for previous: Dictionary in sanitized_placements: - if placements_overlap(previous, sanitized): - return {} - sanitized_placements.append(sanitized) - return { - "schema_version": HOME_SCHEMA_VERSION, - "tier": tier, - "exterior_id": exterior_id, - "privacy": privacy, - "owned_decor": sanitized_owned, - "placements": sanitized_placements, - "revision": revision, - "next_instance_sequence": next_sequence, - } - - -static func sanitize_placement( - value: Dictionary, - home_tier: int = STARTER_TIER, -) -> Dictionary: - for key: String in [ - "instance_id", "decor_id", "mode", "position", "yaw", - ]: - if not value.has(key): - return {} - if ( - typeof(value["instance_id"]) != TYPE_STRING - or typeof(value["decor_id"]) not in [ - TYPE_STRING, TYPE_STRING_NAME, - ] - or not _is_integer_value(value["mode"]) - or typeof(value["position"]) != TYPE_ARRAY - or typeof(value["yaw"]) not in [TYPE_FLOAT, TYPE_INT] - ): - return {} - var instance_id: String = value["instance_id"] - var decor_id := StringName(str(value["decor_id"])) - var mode: int = int(value["mode"]) - var position_values: Array = value["position"] - var yaw: float = float(value["yaw"]) - if ( - instance_id.is_empty() - or instance_id.length() > MAX_INSTANCE_ID_LENGTH - or not DecorCatalogType.has_product(decor_id) - or not _valid_mode(mode) - or position_values.size() != 3 - or not is_finite(yaw) - ): - return {} - for component: Variant in position_values: - if typeof(component) not in [TYPE_FLOAT, TYPE_INT]: - return {} - var position := Vector3( - float(position_values[0]), - float(position_values[1]), - float(position_values[2]), - ) - var normalized := normalized_placement_transform( - decor_id, - position, - yaw, - mode as PlacementMode, - home_tier, - ) - if normalized.is_empty(): - return {} - return { - "instance_id": instance_id, - "decor_id": String(decor_id), - "mode": mode, - "position": _vector3_to_array(normalized["position"]), - "yaw": float(normalized["yaw"]), - } - - -static func _is_integer_value(value: Variant) -> bool: - if typeof(value) == TYPE_INT: - return true - if typeof(value) != TYPE_FLOAT: - return false - var number: float = float(value) - return is_finite(number) and is_equal_approx(number, roundf(number)) - - -static func normalized_placement_transform( - decor_id: StringName, - position: Vector3, - yaw_radians: float, - mode: PlacementMode, - home_tier: int = STARTER_TIER, -) -> Dictionary: - if ( - not DecorCatalogType.has_product(decor_id) - or not position.is_finite() - or not is_finite(yaw_radians) - or not _valid_mode(mode) - ): - return {} - var normalized_position := Vector3(position.x, 0.0, position.z) - var normalized_yaw: float = wrapf(yaw_radians, -PI, PI) - if mode == PlacementMode.GRID: - normalized_position.x = snappedf(normalized_position.x, GRID_SIZE) - normalized_position.z = snappedf(normalized_position.z, GRID_SIZE) - normalized_yaw = snappedf(normalized_yaw, PI * 0.5) - var footprint: Vector2 = DecorCatalogType.get_footprint(decor_id) - var cosine: float = absf(cos(normalized_yaw)) - var sine: float = absf(sin(normalized_yaw)) - var half_size := Vector2( - (footprint.x * cosine + footprint.y * sine) * 0.5, - (footprint.x * sine + footprint.y * cosine) * 0.5, - ) - var interior_half_extents := interior_half_extents_for_tier(home_tier) - var maximum_x: float = ( - interior_half_extents.x - half_size.x - PLACEMENT_EDGE_PADDING - ) - var maximum_z: float = ( - interior_half_extents.y - half_size.y - PLACEMENT_EDGE_PADDING - ) - if maximum_x < 0.0 or maximum_z < 0.0: - return {} - # Clamp after grid snapping so a grid-placed item can still sit flush at a - # wall even when the wall itself is not on an exact grid interval. - normalized_position.x = clampf( - normalized_position.x, -maximum_x, maximum_x - ) - normalized_position.z = clampf( - normalized_position.z, -maximum_z, maximum_z - ) - return { - "position": normalized_position, - "yaw": normalized_yaw, - } - - -func _find_nearest_grid_transform( - decor_id: StringName, - origin: Dictionary, - ignored_instance_id: String, - candidate_validator: Callable, -) -> Dictionary: - var origin_position: Vector3 = origin["position"] - var yaw: float = float(origin["yaw"]) - var maximum_ring: int = ceili( - NEAREST_PLACEMENT_SEARCH_DISTANCE / GRID_SIZE - ) - var seen: Dictionary[String, bool] = {} - for ring: int in range(1, maximum_ring + 1): - var nearest: Dictionary = {} - var nearest_distance_squared: float = INF - for x_offset: int in range(-ring, ring + 1): - for z_offset: int in range(-ring, ring + 1): - if maxi(absi(x_offset), absi(z_offset)) != ring: - continue - var candidate: Dictionary = normalized_placement_transform( - decor_id, - origin_position + Vector3( - float(x_offset) * GRID_SIZE, - 0.0, - float(z_offset) * GRID_SIZE, - ), - yaw, - PlacementMode.GRID, - _tier, - ) - if candidate.is_empty() or not _mark_candidate_unseen( - candidate, seen - ): - continue - if _overlaps_existing( - decor_id, candidate, ignored_instance_id - ) or not _candidate_is_accepted( - candidate, candidate_validator - ): - continue - var candidate_position: Vector3 = candidate["position"] - var distance_squared: float = origin_position.distance_squared_to( - candidate_position - ) - if distance_squared < nearest_distance_squared: - nearest = candidate - nearest_distance_squared = distance_squared - if not nearest.is_empty(): - return nearest - return {} - - -func _find_nearest_free_transform( - decor_id: StringName, - origin: Dictionary, - ignored_instance_id: String, - candidate_validator: Callable, -) -> Dictionary: - var origin_position: Vector3 = origin["position"] - var yaw: float = float(origin["yaw"]) - var maximum_ring: int = ceili( - NEAREST_PLACEMENT_SEARCH_DISTANCE / FREE_PLACEMENT_SEARCH_STEP - ) - var seen: Dictionary[String, bool] = {} - for ring: int in range(1, maximum_ring + 1): - var radius: float = float(ring) * FREE_PLACEMENT_SEARCH_STEP - var nearest: Dictionary = {} - var nearest_distance_squared: float = INF - for angle_index: int in FREE_PLACEMENT_SEARCH_ANGLES: - var angle: float = ( - TAU * float(angle_index) / float(FREE_PLACEMENT_SEARCH_ANGLES) - ) - var candidate: Dictionary = normalized_placement_transform( - decor_id, - origin_position + Vector3( - cos(angle) * radius, 0.0, sin(angle) * radius - ), - yaw, - PlacementMode.FREE, - _tier, - ) - if candidate.is_empty() or not _mark_candidate_unseen( - candidate, seen - ): - continue - if _overlaps_existing( - decor_id, candidate, ignored_instance_id - ) or not _candidate_is_accepted( - candidate, candidate_validator - ): - continue - var candidate_position: Vector3 = candidate["position"] - var distance_squared: float = origin_position.distance_squared_to( - candidate_position - ) - if distance_squared < nearest_distance_squared: - nearest = candidate - nearest_distance_squared = distance_squared - if not nearest.is_empty(): - return nearest - return {} - - -static func _mark_candidate_unseen( - candidate: Dictionary, - seen: Dictionary[String, bool], -) -> bool: - var position: Vector3 = candidate["position"] - var key := "%d:%d" % [ - roundi(position.x * 10000.0), - roundi(position.z * 10000.0), - ] - if seen.has(key): - return false - seen[key] = true - return true - - -static func _candidate_is_accepted( - candidate: Dictionary, - validator: Callable, -) -> bool: - return not validator.is_valid() or bool(validator.call(candidate)) - - -static func placements_overlap(first: Dictionary, second: Dictionary) -> bool: - var first_id := StringName(str(first.get("decor_id", ""))) - var second_id := StringName(str(second.get("decor_id", ""))) - if ( - not DecorCatalogType.has_product(first_id) - or not DecorCatalogType.has_product(second_id) - ): - return true - var first_position: Vector3 = _array_to_vector3(first.get("position", [])) - var second_position: Vector3 = _array_to_vector3(second.get("position", [])) - var first_yaw: float = float(first.get("yaw", 0.0)) - var second_yaw: float = float(second.get("yaw", 0.0)) - var first_half: Vector2 = ( - DecorCatalogType.get_footprint(first_id) * 0.5 - ) - var second_half: Vector2 = ( - DecorCatalogType.get_footprint(second_id) * 0.5 - ) - var first_axes: Array[Vector2] = _placement_axes(first_yaw) - var second_axes: Array[Vector2] = _placement_axes(second_yaw) - var delta := Vector2( - second_position.x - first_position.x, - second_position.z - first_position.z, - ) - for axis: Vector2 in [ - first_axes[0], first_axes[1], second_axes[0], second_axes[1], - ]: - var first_radius: float = ( - absf(axis.dot(first_axes[0])) * first_half.x - + absf(axis.dot(first_axes[1])) * first_half.y - ) - var second_radius: float = ( - absf(axis.dot(second_axes[0])) * second_half.x - + absf(axis.dot(second_axes[1])) * second_half.y - ) - if absf(delta.dot(axis)) >= ( - first_radius + second_radius - 0.001 - ): - return false - return true - - -func _make_instance_id() -> String: - var result := "decor-%d-%d" % [ - Time.get_ticks_usec(), - _next_instance_sequence, - ] - _next_instance_sequence = mini( - _next_instance_sequence + 1, - MAX_REVISION, - ) - return result - - -func _placement_index(instance_id: String) -> int: - if instance_id.is_empty(): - return -1 - for index: int in _placements.size(): - if str(_placements[index].get("instance_id", "")) == instance_id: - return index - return -1 - - -func _overlaps_existing( - decor_id: StringName, - normalized: Dictionary, - ignored_instance_id: String, -) -> bool: - var candidate: Dictionary = { - "decor_id": String(decor_id), - "position": _vector3_to_array(normalized["position"]), - "yaw": float(normalized["yaw"]), - } - for placement: Dictionary in _placements: - if str(placement.get("instance_id", "")) == ignored_instance_id: - continue - if placements_overlap(candidate, placement): - return true - return false - - -func _touch() -> void: - _revision = mini(_revision + 1, MAX_REVISION) - changed.emit() - - -static func _valid_mode(value: int) -> bool: - return value >= PlacementMode.GRID and value <= PlacementMode.FREE - - -static func _vector3_to_array(value: Vector3) -> Array[float]: - return [value.x, value.y, value.z] - - -static func _array_to_vector3(value: Variant) -> Vector3: - if typeof(value) != TYPE_ARRAY or (value as Array).size() != 3: - return Vector3.INF - var components: Array = value - return Vector3( - float(components[0]), - float(components[1]), - float(components[2]), - ) - - -static func _placement_axes(yaw: float) -> Array[Vector2]: - var cosine: float = cos(yaw) - var sine: float = sin(yaw) - return [Vector2(cosine, sine), Vector2(-sine, cosine)] diff --git a/homes/player_home_state.gd.uid b/homes/player_home_state.gd.uid deleted file mode 100644 index 37836c6..0000000 --- a/homes/player_home_state.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ggfdoak2ixxx diff --git a/inventory/player_bag.gd b/inventory/player_bag.gd index d94845d..e33f1e4 100644 --- a/inventory/player_bag.gd +++ b/inventory/player_bag.gd @@ -60,28 +60,6 @@ func add_item(item_id: StringName, quantity: int = 1) -> bool: return true -func add_item_to_storage(item_id: StringName, quantity: int = 1) -> bool: - var existing: OwnedItemType = get_owned_item(item_id) - if existing != null: - return add_item(item_id, quantity) - var item: ItemDataType = _resolve_available_item(item_id) - if ( - item == null - or quantity <= 0 - or quantity > (item.max_stack if item.stackable else 1) - or _inventory_layout == null - or not _inventory_layout.can_accept_item_in_storage(item_id) - ): - return false - var owned := OwnedItemType.new() - owned.item_id = item_id - owned.quantity = quantity - owned.storage_slot = _first_available_storage_slot(item, _items) - _items.append(owned) - contents_changed.emit() - return true - - func can_add_item(item_id: StringName, quantity: int = 1) -> bool: var item: ItemDataType = _resolve_available_item(item_id) if item == null or quantity <= 0: diff --git a/inventory/player_hotbar.gd b/inventory/player_hotbar.gd index 36068ea..b7d5197 100644 --- a/inventory/player_hotbar.gd +++ b/inventory/player_hotbar.gd @@ -314,25 +314,6 @@ func get_fish_slots() -> Array[StringName]: return _fish_slots.duplicate() -func find_item_slot(item_id: StringName) -> int: - return _slots.find(item_id) - - -func find_first_empty_slot() -> int: - for slot_index: int in SLOT_COUNT: - if get_assignment_kind(slot_index) != AssignmentKind.EMPTY: - continue - if ( - _inventory_layout == null - or _inventory_layout.get_key_at( - PlayerInventoryLayout.InventoryContainer.HOTBAR, - slot_index, - ).is_empty() - ): - return slot_index - return -1 - - func replace_state( slots: Array[StringName], selected_slot: int, diff --git a/inventory/player_inventory_layout.gd b/inventory/player_inventory_layout.gd index bce726e..244e10b 100644 --- a/inventory/player_inventory_layout.gd +++ b/inventory/player_inventory_layout.gd @@ -37,7 +37,6 @@ var _fish_inventory: FishInventoryType var _item_catalog: ItemCatalogType var _storage_capacity: PlayerCoolerCapacityType var _placements: Dictionary[String, Dictionary] = {} -var _prepared_item_placements: Dictionary[String, Dictionary] = {} var _reconciling: bool = false var _backpack_level: int = 0 @@ -73,44 +72,7 @@ func can_accept_item(item_id: StringName) -> bool: if not is_managed_item(item_id): return true var key := item_key(item_id) - return ( - _placements.has(key) - or _is_prepared_item_placement_available(key) - or _first_free_slot(InventoryContainer.INVENTORY) >= 0 - ) - - -func prepare_new_item_placement( - item_id: StringName, - target_container: InventoryContainer, - target_slot: int, -) -> bool: - if not is_managed_item(item_id): - return false - var key := item_key(item_id) - if ( - _placements.has(key) - or not _is_slot_valid(target_container, target_slot) - or not _key_at(target_container, target_slot).is_empty() - or _prepared_slot_is_occupied(target_container, target_slot) - ): - return false - _prepared_item_placements[key] = { - "container": int(target_container), - "slot": target_slot, - } - return true - - -func cancel_prepared_item_placement(item_id: StringName) -> void: - _prepared_item_placements.erase(item_key(item_id)) - - -func can_accept_item_in_storage(item_id: StringName) -> bool: - if not is_managed_item(item_id): - return true - var key := item_key(item_id) - return _placements.has(key) or _first_free_slot(InventoryContainer.STORAGE) >= 0 + return _placements.has(key) or _first_free_slot(InventoryContainer.INVENTORY) >= 0 func can_accept_catch(catch_id: StringName = StringName()) -> bool: @@ -370,7 +332,6 @@ func restore_from_save_data(data: Dictionary) -> bool: "container": container, "slot": slot, } - _prepared_item_placements.clear() _placements = restored _reconcile(true) return true @@ -379,7 +340,6 @@ func restore_from_save_data(data: Dictionary) -> bool: func reset_to_defaults() -> void: var capacity_changed := _backpack_level != 0 _backpack_level = 0 - _prepared_item_placements.clear() _placements.clear() _reconcile(true) if capacity_changed: @@ -442,17 +402,8 @@ func _reconcile(force_signal: bool) -> void: for key: String in expected: if _placements.has(key): continue - var prepared: Dictionary = _prepared_item_placements.get(key, {}) var target_container := InventoryContainer.INVENTORY - var target_slot: int = -1 - if _is_prepared_item_placement_available(key): - target_container = int( - prepared["container"] - ) as InventoryContainer - target_slot = int(prepared["slot"]) - _prepared_item_placements.erase(key) - if target_slot < 0: - target_slot = _first_free_slot(target_container) + var target_slot := _first_free_slot(target_container) if target_slot < 0: target_container = InventoryContainer.STORAGE target_slot = _first_free_slot(target_container) @@ -533,37 +484,6 @@ func _first_free_slot(container: InventoryContainer) -> int: return -1 -func _is_prepared_item_placement_available(key: String) -> bool: - var placement: Dictionary = _prepared_item_placements.get(key, {}) - if placement.is_empty(): - return false - var container: int = int(placement.get("container", -1)) - var slot: int = int(placement.get("slot", -1)) - if ( - container not in [ - InventoryContainer.INVENTORY, - InventoryContainer.STORAGE, - InventoryContainer.HOTBAR, - ] - or not _is_slot_valid(container as InventoryContainer, slot) - ): - return false - return _key_at(container as InventoryContainer, slot).is_empty() - - -func _prepared_slot_is_occupied( - container: InventoryContainer, - slot: int, -) -> bool: - for placement: Dictionary in _prepared_item_placements.values(): - if ( - int(placement.get("container", -1)) == int(container) - and int(placement.get("slot", -1)) == slot - ): - return true - return false - - func _key_at(container: InventoryContainer, slot: int) -> String: for key: String in _placements: var placement: Dictionary = _placements[key] diff --git a/items/catalog/basic_cube.tres b/items/catalog/basic_cube.tres deleted file mode 100644 index 86a2cf9..0000000 --- a/items/catalog/basic_cube.tres +++ /dev/null @@ -1,17 +0,0 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] - -[ext_resource type="Script" path="res://items/item_data.gd" id="1_item"] -[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="2_icon"] - -[resource] -script = ExtResource("1_item") -item_id = &"basic_cube" -display_name = "basic cube" -description = "a simple piece of decor for testing your RV layout." -category = 6 -icon = ExtResource("2_icon") -stackable = true -max_stack = 32 -usable = false -equippable = true -hotbar_allowed = true diff --git a/items/catalog/item_catalog.tres b/items/catalog/item_catalog.tres index 2f15e0a..63ef3ea 100644 --- a/items/catalog/item_catalog.tres +++ b/items/catalog/item_catalog.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" script_class="ItemCatalog" load_steps=42 format=3] +[gd_resource type="Resource" script_class="ItemCatalog" load_steps=41 format=3] [ext_resource type="Script" path="res://items/item_catalog.gd" id="1_script"] [ext_resource type="Resource" path="res://items/catalog/basic_fishing_rod.tres" id="2_rod"] @@ -40,8 +40,7 @@ [ext_resource type="Resource" path="res://items/catalog/crab_net.tres" id="39_crab_net"] [ext_resource type="Resource" path="res://items/catalog/standard_shovel.tres" id="40_shovel"] [ext_resource type="Resource" path="res://items/catalog/fishing_net.tres" id="41_fishing_net"] -[ext_resource type="Resource" path="res://items/catalog/basic_cube.tres" id="42_basic_cube"] [resource] script = ExtResource("1_script") -items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("14_sardine"), ExtResource("13_anchovy"), ExtResource("15_roe"), ExtResource("16_standby"), ExtResource("17_batteries"), ExtResource("18_cardboard"), ExtResource("19_pond"), ExtResource("20_river"), ExtResource("21_lake"), ExtResource("22_salt"), ExtResource("23_whisker"), ExtResource("24_reef"), ExtResource("25_moonbeam"), ExtResource("26_sun"), ExtResource("27_rain"), ExtResource("28_fog"), ExtResource("29_guide"), ExtResource("30_small"), ExtResource("31_heavy"), ExtResource("32_rocket"), ExtResource("33_lucky"), ExtResource("34_showboat"), ExtResource("35_deep"), ExtResource("36_oddity"), ExtResource("37_aurora"), ExtResource("38_magnet"), ExtResource("39_crab_net"), ExtResource("40_shovel"), ExtResource("41_fishing_net"), ExtResource("42_basic_cube")] +items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("14_sardine"), ExtResource("13_anchovy"), ExtResource("15_roe"), ExtResource("16_standby"), ExtResource("17_batteries"), ExtResource("18_cardboard"), ExtResource("19_pond"), ExtResource("20_river"), ExtResource("21_lake"), ExtResource("22_salt"), ExtResource("23_whisker"), ExtResource("24_reef"), ExtResource("25_moonbeam"), ExtResource("26_sun"), ExtResource("27_rain"), ExtResource("28_fog"), ExtResource("29_guide"), ExtResource("30_small"), ExtResource("31_heavy"), ExtResource("32_rocket"), ExtResource("33_lucky"), ExtResource("34_showboat"), ExtResource("35_deep"), ExtResource("36_oddity"), ExtResource("37_aurora"), ExtResource("38_magnet"), ExtResource("39_crab_net"), ExtResource("40_shovel"), ExtResource("41_fishing_net")] diff --git a/main/main.gd b/main/main.gd index 324c268..442c97c 100644 --- a/main/main.gd +++ b/main/main.gd @@ -37,12 +37,6 @@ const FishingShopType = preload("res://ui/fishing_shop.gd") const FishingShopInteractionType = preload( "res://world/fishing_shop_interaction.gd" ) -const DecorShopInteractionType = preload( - "res://world/decor_shop_interaction.gd" -) -const RVUpgradeInteractionType = preload( - "res://world/rv_upgrade_interaction.gd" -) const PlayerStorageInteractionType = preload( "res://world/player_storage_interaction.gd" ) @@ -76,7 +70,6 @@ const NetworkFishingServiceType = preload( "res://network/network_fishing_service.gd" ) const PlayerHotbarType = preload("res://inventory/player_hotbar.gd") -const DecorCatalogType = preload("res://homes/decor_catalog.gd") const NetworkSaleServiceType = preload( "res://network/network_sale_service.gd" ) @@ -130,14 +123,6 @@ const GatheringControllerType = preload( const NetworkWorldSpawnServiceType = preload( "res://network/network_world_spawn_service.gd" ) -const PlayerHomeStateType = preload("res://homes/player_home_state.gd") -const HomeWorldServiceType = preload("res://homes/home_world_service.gd") -const NetworkHomeServiceType = preload( - "res://network/network_home_service.gd" -) -const HomeDecorControllerType = preload( - "res://homes/home_decor_controller.gd" -) const TITLE_MUSIC_SILENCE_DB: float = -80.0 const TITLE_MUSIC_PATH: String = ( @@ -151,13 +136,6 @@ const PLAYER_MENU_PATTERN_SCROLL_VELOCITY := Vector2(-7.0, -5.0) const SHOP_PATTERN_SCALE: float = 1.75 const DEDICATED_IDLE_PHYSICS_TICKS_PER_SECOND: int = 10 const DEDICATED_ACTIVE_PHYSICS_TICKS_PER_SECOND: int = 30 -const HOME_ENTRY_INPUT_OWNER: StringName = &"home_entry_transition" -const HOME_EXIT_INPUT_OWNER: StringName = &"home_exit_transition" -const HOME_EXIT_FADE_GENERATION: int = -104729 -const HOME_ENTRY_WALK_DISTANCE: float = 1.05 -const HOME_ENTRY_WALK_DURATION: float = 0.55 -const HOME_EXIT_WALK_DISTANCE: float = 1.25 -const HOME_EXIT_WALK_DURATION: float = 0.62 @export var fish_catalog: FishPoolType @export var pelican_buyer_profile: FishBuyerProfileType @@ -175,7 +153,6 @@ const HOME_EXIT_WALK_DURATION: float = 0.62 @onready var _game_ui: GameUIType = %GameUI @onready var _water_recovery: WaterRecoveryControllerType = %WaterRecovery @onready var _save_manager: PlayerSaveManagerType = %PlayerSaveManager -@onready var _home_state: PlayerHomeStateType = %PlayerHomeState @onready var _settings_manager: PlayerSettingsManagerType = %PlayerSettingsManager @onready var _controller_mapping_manager: ControllerMappingManagerType = ( %ControllerMappingManager @@ -235,10 +212,6 @@ const HOME_EXIT_WALK_DURATION: float = 0.62 ) @onready var _network_sale: NetworkSaleServiceType = %NetworkSaleService @onready var _network_shop: NetworkShopServiceType = %NetworkShopService -@onready var _network_home: NetworkHomeServiceType = %NetworkHomeService -@onready var _home_decor_controller: HomeDecorControllerType = ( - %HomeDecorController -) @onready var _network_item_use: NetworkItemUseServiceType = ( %NetworkItemUseService ) @@ -261,7 +234,6 @@ const HOME_EXIT_WALK_DURATION: float = 0.62 @onready var _players_root: Node3D = $Players @onready var _surface_drawings_root: Node3D = $SurfaceDrawings @onready var _world_gatherables_root: Node3D = $WorldGatherables -@onready var _home_world: HomeWorldServiceType = %HomeWorldService @onready var _title_background: ColorRect = %TitleBackground @onready var _player_menu_backdrop: ColorRect = %PlayerMenuBackdrop @onready var _shop_backdrop: ColorRect = %ShopBackdrop @@ -269,8 +241,6 @@ const HOME_EXIT_WALK_DURATION: float = 0.62 var _gameplay_started: bool = false var _save_slots := PlayerSaveSlotCatalogType.new() var _shop_interaction: FishingShopInteractionType -var _decor_shop_interaction: DecorShopInteractionType -var _rv_upgrade_shop_interaction: RVUpgradeInteractionType var _storage_interaction: PlayerStorageInteractionType var _title_music_tween: Tween var _title_music_transition_generation: int = 0 @@ -301,12 +271,6 @@ var _pending_existing_root_path := "" var _local_recovery_attempt_id: String = "" var _dedicated_runtime: bool = false var _dedicated_metrics_accumulator: float = 0.0 -var _home_exit_transition_active: bool = false -var _home_exit_waiting_for_network: bool = false -var _home_exit_start_position: Vector3 = Vector3.ZERO -var _home_exit_walk_tween: Tween -var _home_entry_transition_active: bool = false -var _home_entry_walk_tween: Tween @onready var _shoreline_ambience: ShorelineAmbience = %ShorelineAmbience var _rain_ambience: RainAmbienceType @@ -760,7 +724,6 @@ func _initialize_application(dedicated: bool) -> void: _player.wallet, _asset_reservations ) - _player.cooler_capacity.bind_home_state(_home_state) _player.bag.setup(item_catalog) _player.inventory_layout.setup( _player.bag, @@ -777,24 +740,14 @@ func _initialize_application(dedicated: bool) -> void: _player.inventory_layout, ) _shop_interaction = _test_world.get_fishing_shop() - _decor_shop_interaction = _test_world.get_decor_shop() - _rv_upgrade_shop_interaction = _test_world.get_rv_upgrade_shop() _storage_interaction = _test_world.get_player_storage() if not dedicated: - if not _home_state.changed.is_connected(_on_home_state_changed): - _home_state.changed.connect(_on_home_state_changed) _shop_interaction.setup_local_player(_player) _shop_interaction.local_player_range_changed.connect( _on_shop_range_changed ) - _refresh_shop_npc_speech_state() - _decor_shop_interaction.setup_local_player(_player) - _decor_shop_interaction.local_player_range_changed.connect( - _on_decor_shop_range_changed - ) - _rv_upgrade_shop_interaction.setup_local_player(_player) - _rv_upgrade_shop_interaction.local_player_range_changed.connect( - _on_rv_upgrade_shop_range_changed + _game_ui.set_shop_npc_player_in_range( + _shop_interaction.is_local_player_in_range() ) _storage_interaction.setup_local_player(_player) _storage_interaction.local_player_range_changed.connect( @@ -817,23 +770,6 @@ func _initialize_application(dedicated: bool) -> void: _world_weather, _player_jobs, _player, - _home_state, - ) - _home_world.setup( - _test_world, - _player_spawn_service, - _network_session, - dedicated, - ) - _network_home.setup( - _network_session, - _player_spawn_service, - _home_state, - _save_manager, - _home_world, - _player.bag, - _player.inventory_layout, - _player.hotbar, ) _player_jobs.set_save_manager(_save_manager) _save_manager.set_autosave_enabled(false) @@ -967,10 +903,6 @@ func _initialize_application(dedicated: bool) -> void: _save_manager, _asset_reservations, _player.inventory_layout, - _home_state, - _network_home, - _decor_shop_interaction, - _rv_upgrade_shop_interaction, ) _fishing_spot.setup( _player, @@ -988,7 +920,6 @@ func _initialize_application(dedicated: bool) -> void: _network_fishing, _world_time, _world_weather, - Callable(_test_world, "get_fishable_water_regions"), ) if dedicated: return @@ -1010,14 +941,11 @@ func _initialize_application(dedicated: bool) -> void: _player.fishing_upgrades, _shop_interaction, _storage_interaction, - _decor_shop_interaction, - _rv_upgrade_shop_interaction, _player.item_effects, _player.cooler_capacity, _network_session, _network_sale, _network_shop, - _home_state, _network_chat, _network_profile, _player_spawn_service, @@ -1035,27 +963,6 @@ func _initialize_application(dedicated: bool) -> void: _test_world.get_world_environment(), _test_world.get_sun(), ) - _home_decor_controller.setup( - _player, - _player.hotbar, - _player.bag, - _home_state, - _network_home, - _home_world, - _fishing_spot, - _game_ui.get_home_decor_toolbar(), - ) - _game_ui.get_player_storage().decorate_requested.connect( - _home_decor_controller.begin_decorating - ) - _network_home.local_space_changed.connect(_on_local_home_space_changed) - _network_home.local_transition_finished.connect( - _on_local_home_transition_finished - ) - _game_ui.get_screen_fade().transition_completed.connect( - _on_home_exit_fade_transition_completed - ) - _game_ui.rv_storage_requested.connect(_on_rv_storage_requested) _game_ui.setup_data_and_identity( _data_root, _identity_backups, @@ -1099,8 +1006,7 @@ func _initialize_application(dedicated: bool) -> void: _game_ui, _game_ui.get_screen_fade(), _test_world.get_player_water_triggers(), - _test_world.get_safe_respawn_points(), - Callable(_test_world, "get_water_recovery_position"), + _test_world.get_safe_respawn_points() ) _ui_pixelation.effective_pixel_size_changed.connect( _on_effective_ui_pixel_size_changed @@ -1567,12 +1473,6 @@ func _input(event: InputEvent) -> void: if _game_ui.get_player_storage().consume_escape(): get_viewport().set_input_as_handled() return - if _game_ui.get_decor_shop().consume_escape(): - get_viewport().set_input_as_handled() - return - if _game_ui.get_rv_upgrade_shop().consume_escape(): - get_viewport().set_input_as_handled() - return if fishing_shop.consume_escape(): get_viewport().set_input_as_handled() return @@ -1671,54 +1571,19 @@ func _is_pause_open_request(event: InputEvent) -> bool: func _unhandled_input(event: InputEvent) -> void: - if ( - _gameplay_started - and event.is_action_pressed("fish_primary") - and not (event is InputEventKey and event.echo) - and _fishing_spot.try_use_active_non_fishing_item() - ): - get_viewport().set_input_as_handled() - return if ( not _gameplay_started or not event.is_action_pressed("interact") or (event is InputEventKey and event.echo) ): return - var home_owner: String = _network_home.get_local_home_owner_fingerprint() - if not home_owner.is_empty(): - if ( - _network_home.can_local_decorate() - and _fishing_spot.can_open_fishing_shop() - and _game_ui.get_player_storage().open_storage_from_home(true) - ): - _refresh_home_door_prompt({}) - get_viewport().set_input_as_handled() - return - var nearby_home_owner: String = _home_world.find_nearby_exterior_owner( - _player - ) - if not nearby_home_owner.is_empty(): - if _network_home.request_enter(nearby_home_owner): - _refresh_home_door_prompt({}) - get_viewport().set_input_as_handled() - return if ( - _rv_upgrade_shop_interaction != null - and _rv_upgrade_shop_interaction.is_local_player_in_range() + _storage_interaction != null + and _storage_interaction.is_local_player_in_range() and _fishing_spot.can_open_fishing_shop() - and _game_ui.get_rv_upgrade_shop().open_shop() + and _game_ui.get_player_storage().open_storage() ): - _game_ui.set_shop_prompt_visible(false) - get_viewport().set_input_as_handled() - return - if ( - _decor_shop_interaction != null - and _decor_shop_interaction.is_local_player_in_range() - and _fishing_spot.can_open_fishing_shop() - and _game_ui.get_decor_shop().open_shop() - ): - _game_ui.set_shop_prompt_visible(false) + _game_ui.set_storage_prompt_visible(false) get_viewport().set_input_as_handled() return if ( @@ -1741,48 +1606,37 @@ func _process(delta: float) -> void: ) _print_dedicated_metrics() return - _maybe_begin_home_exit_transition() - var show_rv_upgrade_prompt := _can_show_rv_upgrade_shop_prompt() - var show_decor_shop_prompt := _can_show_decor_shop_prompt() - var show_shop_prompt := ( - show_rv_upgrade_prompt - or show_decor_shop_prompt - or _can_show_shop_prompt() - ) + var show_shop_prompt := _can_show_shop_prompt() var shop_prompt_anchor := ( - _rv_upgrade_shop_interaction.get_prompt_anchor_position() - if show_rv_upgrade_prompt - else ( - _decor_shop_interaction.get_prompt_anchor_position() - if show_decor_shop_prompt - else ( - _shop_interaction.get_prompt_anchor_position() - if _shop_interaction != null else Vector3.ZERO - ) - ) + _shop_interaction.get_prompt_anchor_position() + if _shop_interaction != null + else Vector3.ZERO ) _game_ui.set_shop_prompt_visible( show_shop_prompt, shop_prompt_anchor, ) - var home_prompt := _get_home_prompt() - _refresh_home_door_prompt(home_prompt) - _game_ui.set_rv_storage_button_visible( - _can_show_rv_storage_button() + var show_storage_prompt := _can_show_storage_prompt() + var storage_prompt_anchor := ( + _storage_interaction.get_prompt_anchor_position() + if _storage_interaction != null + else Vector3.ZERO + ) + _game_ui.set_storage_prompt_visible( + show_storage_prompt, + storage_prompt_anchor, ) func _apply_runtime_settings(settings: PlayerSettingsType) -> void: if settings == null: return - var current_window_mode: DisplayServer.WindowMode = ( - DisplayServer.window_get_mode() + var requested_window_mode: DisplayServer.WindowMode = ( + DisplayServer.WINDOW_MODE_FULLSCREEN + if settings.fullscreen_enabled + else DisplayServer.WINDOW_MODE_WINDOWED ) - var requested_window_mode: DisplayServer.WindowMode = resolve_window_mode( - settings.fullscreen_enabled, - current_window_mode, - ) - if current_window_mode != requested_window_mode: + if DisplayServer.window_get_mode() != requested_window_mode: DisplayServer.window_set_mode(requested_window_mode) _apply_world_pixelation(settings.world_pixel_size) _ui_pixelation.set_pixel_size(settings.ui_pixel_size) @@ -1809,25 +1663,6 @@ func _apply_runtime_settings(settings: PlayerSettingsType) -> void: ) -static func resolve_window_mode( - fullscreen_enabled: bool, - current_mode: DisplayServer.WindowMode, -) -> DisplayServer.WindowMode: - var is_fullscreen: bool = current_mode in [ - DisplayServer.WINDOW_MODE_FULLSCREEN, - DisplayServer.WINDOW_MODE_EXCLUSIVE_FULLSCREEN, - ] - if fullscreen_enabled: - return ( - current_mode - if is_fullscreen - else DisplayServer.WINDOW_MODE_FULLSCREEN - ) - if is_fullscreen: - return DisplayServer.WINDOW_MODE_WINDOWED - return current_mode - - func _apply_world_pixelation(pixel_size: int) -> void: var root_viewport: Viewport = get_viewport() root_viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_NEAREST @@ -2509,9 +2344,17 @@ func _on_remote_recovery_requested( peer_id, "Fishing attempt ended." ) - var target_position := _test_world.get_water_recovery_position( - entry_position - ) + var target_position: Vector3 = _test_world.get_player_spawn_transform().origin + var nearest_distance: float = INF + for point: SafeRespawnPoint in _test_world.get_safe_respawn_points(): + if point == null or not point.enabled: + continue + var distance: float = point.get_horizontal_distance_squared( + entry_position + ) + if distance < nearest_distance: + nearest_distance = distance + target_position = point.global_position target_position.y += _water_recovery.respawn_height_offset avatar.global_position = target_position avatar.velocity = Vector3.ZERO @@ -2584,13 +2427,11 @@ func _apply_world( if _application_initialized: _refresh_active_world_bindings() _network_world_spawns.refresh_world_context() - _network_home.refresh_world_presentations() if _application_initialized and not _dedicated_runtime: _water_recovery.update_world_context( spawn_transform, _test_world.get_player_water_triggers(), _test_world.get_safe_respawn_points(), - Callable(_test_world, "get_water_recovery_position"), ) _shoreline_ambience.configure( _player, @@ -2614,30 +2455,10 @@ func _refresh_active_world_bindings() -> void: _storage_interaction.local_player_range_changed.disconnect( _on_storage_range_changed ) - if is_instance_valid(_decor_shop_interaction): - if _decor_shop_interaction.local_player_range_changed.is_connected( - _on_decor_shop_range_changed - ): - _decor_shop_interaction.local_player_range_changed.disconnect( - _on_decor_shop_range_changed - ) - if is_instance_valid(_rv_upgrade_shop_interaction): - if _rv_upgrade_shop_interaction.local_player_range_changed.is_connected( - _on_rv_upgrade_shop_range_changed - ): - _rv_upgrade_shop_interaction.local_player_range_changed.disconnect( - _on_rv_upgrade_shop_range_changed - ) _shop_interaction = _test_world.get_fishing_shop() - _decor_shop_interaction = _test_world.get_decor_shop() - _rv_upgrade_shop_interaction = _test_world.get_rv_upgrade_shop() _storage_interaction = _test_world.get_player_storage() _network_sale.set_shop_interaction(_shop_interaction) _network_shop.set_shop_interaction(_shop_interaction) - _network_shop.set_decor_shop_interaction(_decor_shop_interaction) - _network_shop.set_rv_upgrade_shop_interaction( - _rv_upgrade_shop_interaction - ) if _dedicated_runtime: return if _shop_interaction != null: @@ -2650,21 +2471,9 @@ func _refresh_active_world_bindings() -> void: _storage_interaction.local_player_range_changed.connect( _on_storage_range_changed ) - if _decor_shop_interaction != null: - _decor_shop_interaction.setup_local_player(_player) - _decor_shop_interaction.local_player_range_changed.connect( - _on_decor_shop_range_changed - ) - if _rv_upgrade_shop_interaction != null: - _rv_upgrade_shop_interaction.setup_local_player(_player) - _rv_upgrade_shop_interaction.local_player_range_changed.connect( - _on_rv_upgrade_shop_range_changed - ) _game_ui.set_world_interactions( _shop_interaction, _storage_interaction, - _decor_shop_interaction, - _rv_upgrade_shop_interaction, ) _on_shop_range_changed( _shop_interaction != null @@ -2674,14 +2483,6 @@ func _refresh_active_world_bindings() -> void: _storage_interaction != null and _storage_interaction.is_local_player_in_range() ) - _on_decor_shop_range_changed( - _decor_shop_interaction != null - and _decor_shop_interaction.is_local_player_in_range() - ) - _on_rv_upgrade_shop_range_changed( - _rv_upgrade_shop_interaction != null - and _rv_upgrade_shop_interaction.is_local_player_in_range() - ) func _on_water_recovery_starting() -> void: @@ -2695,8 +2496,6 @@ func _on_water_recovery_starting() -> void: _game_ui.close_player_menu_for_water_recovery() _game_ui.get_pause_menu().close_for_water_recovery() _game_ui.get_fishing_shop().close_for_water_recovery() - _game_ui.get_decor_shop().close_for_water_recovery() - _game_ui.get_rv_upgrade_shop().close_for_water_recovery() _game_ui.get_player_storage().close_for_water_recovery() @@ -2745,7 +2544,6 @@ func _on_active_hotbar_item_changed( and item != null and item.is_available() and _player.bag.owns_item(item_id) - and not String(_network_home.get_local_space()).begins_with("rv:") ) var active_is_catching_net: bool = ( item_id == FishingShopStockType.CRAB_NET_ID @@ -2759,12 +2557,6 @@ func _on_active_hotbar_item_changed( and item.is_available() and _player.bag.owns_item(item_id) ) - var active_is_decor: bool = ( - DecorCatalogType.has_product(item_id) - and item != null - and item.is_available() - and _player.bag.owns_item(item_id) - ) _player.set_active_fishing_rod( item as FishingRodDataType if active_is_rod else null, true, @@ -2775,7 +2567,6 @@ func _on_active_hotbar_item_changed( ) _player.set_active_catching_net(active_is_catching_net) _player.set_active_shovel(active_is_shovel) - _player.set_active_decor_package(active_is_decor) _game_ui.set_surface_drawing_hotbar_selected(active_is_art_kit) _network_item_use.submit_local_equipped(item_id, active_is_rod or ( item != null and _player.bag.owns_item(item_id) @@ -2895,8 +2686,6 @@ func _finish_quit() -> void: func _exit_tree() -> void: - _cancel_home_entry_transition() - _cancel_home_exit_transition(true) if _dedicated_runtime and _network_session != null: _network_session.disconnect_session("Dedicated server stopping.") if _title_music_tween != null: @@ -2914,224 +2703,16 @@ func _exit_tree() -> void: func _on_shop_range_changed(in_range: bool) -> void: - _refresh_shop_npc_speech_state() + _game_ui.set_shop_npc_player_in_range(in_range) if not in_range: _game_ui.get_fishing_shop().close_for_range_exit() - _game_ui.set_shop_prompt_visible(_can_show_any_shop_prompt()) + _game_ui.set_shop_prompt_visible(_can_show_shop_prompt()) func _on_storage_range_changed(in_range: bool) -> void: if not in_range: _game_ui.get_player_storage().close_for_range_exit() - var prompt := _get_home_prompt() - _refresh_home_door_prompt(prompt) - _game_ui.set_rv_storage_button_visible( - _can_show_rv_storage_button() - ) - - -func _on_decor_shop_range_changed(in_range: bool) -> void: - _refresh_shop_npc_speech_state() - if not in_range: - _game_ui.get_decor_shop().close_for_range_exit() - _game_ui.set_shop_prompt_visible(_can_show_any_shop_prompt()) - - -func _on_rv_upgrade_shop_range_changed(in_range: bool) -> void: - _refresh_shop_npc_speech_state() - if not in_range: - _game_ui.get_rv_upgrade_shop().close_for_range_exit() - _game_ui.set_shop_prompt_visible(_can_show_any_shop_prompt()) - - -func _on_home_state_changed() -> void: - _refresh_shop_npc_speech_state() - - -func _on_local_home_space_changed( - space_id: StringName, - owner_fingerprint: String, -) -> void: - var inside_rv: bool = String(space_id).begins_with("rv:") - _refresh_active_hotbar_item() - _fishing_spot.set_fishing_environment_enabled(not inside_rv) - _game_ui.set_rv_interior_camera_mode( - inside_rv - ) - _game_ui.set_rv_storage_button_visible( - _can_show_rv_storage_button() - ) - if inside_rv and not _home_exit_transition_active: - _begin_home_entry_transition(owner_fingerprint) - elif not inside_rv and _home_entry_transition_active: - _cancel_home_entry_transition() - - -func _begin_home_entry_transition(owner_fingerprint: String) -> void: - if owner_fingerprint.is_empty() or _home_entry_transition_active: - return - _home_entry_transition_active = true - _refresh_home_door_prompt({}) - _player.set_local_input_suppressed(HOME_ENTRY_INPUT_OWNER, true) - _player.set_camera_input_suppressed(HOME_ENTRY_INPUT_OWNER, true) - var inward_direction: Vector3 = ( - -_home_world.get_interior_exit_direction(owner_fingerprint) - ) - _player.face_world_direction(inward_direction) - _player.begin_animation_action(PlayerType.CHARACTER_WALKING_ANIMATION) - _home_entry_walk_tween = create_tween() - _home_entry_walk_tween.set_trans(Tween.TRANS_SINE) - _home_entry_walk_tween.set_ease(Tween.EASE_IN_OUT) - _home_entry_walk_tween.tween_property( - _player, - "global_position", - _player.global_position + inward_direction * HOME_ENTRY_WALK_DISTANCE, - HOME_ENTRY_WALK_DURATION, - ) - _home_entry_walk_tween.finished.connect( - _cancel_home_entry_transition, - CONNECT_ONE_SHOT, - ) - - -func _cancel_home_entry_transition() -> void: - if _home_entry_walk_tween != null and _home_entry_walk_tween.is_valid(): - _home_entry_walk_tween.kill() - _home_entry_walk_tween = null - if is_instance_valid(_player): - _player.end_animation_action() - _player.set_local_input_suppressed(HOME_ENTRY_INPUT_OWNER, false) - _player.set_camera_input_suppressed(HOME_ENTRY_INPUT_OWNER, false) - _home_entry_transition_active = false - - -func _maybe_begin_home_exit_transition() -> void: - if ( - _home_exit_transition_active - or _home_entry_transition_active - or not _gameplay_started - or _water_recovery.is_recovery_active() - or not _fishing_spot.can_open_fishing_shop() - or _game_ui.get_player_storage().visible - or _game_ui.get_fishing_shop().visible - or _game_ui.get_decor_shop().visible - or _game_ui.get_rv_upgrade_shop().visible - ): - return - var owner_fingerprint: String = ( - _network_home.get_local_home_owner_fingerprint() - ) - if ( - owner_fingerprint.is_empty() - or not _home_world.has_avatar_crossed_interior_exit_threshold( - _player, - owner_fingerprint, - ) - ): - return - _begin_home_exit_transition(owner_fingerprint) - - -func _begin_home_exit_transition(owner_fingerprint: String) -> void: - _home_exit_transition_active = true - _home_exit_waiting_for_network = false - _home_exit_start_position = _player.global_position - _refresh_home_door_prompt({}) - _player.set_local_input_suppressed(HOME_EXIT_INPUT_OWNER, true) - _player.set_camera_input_suppressed(HOME_EXIT_INPUT_OWNER, true) - var exit_direction: Vector3 = _home_world.get_interior_exit_direction( - owner_fingerprint - ) - _player.face_world_direction(exit_direction) - _player.begin_animation_action(PlayerType.CHARACTER_WALKING_ANIMATION) - _home_exit_walk_tween = create_tween() - _home_exit_walk_tween.set_trans(Tween.TRANS_SINE) - _home_exit_walk_tween.set_ease(Tween.EASE_IN_OUT) - _home_exit_walk_tween.tween_property( - _player, - "global_position", - _player.global_position + exit_direction * HOME_EXIT_WALK_DISTANCE, - HOME_EXIT_WALK_DURATION, - ) - _home_exit_walk_tween.finished.connect( - _on_home_exit_walk_finished, - CONNECT_ONE_SHOT, - ) - - -func _on_home_exit_walk_finished() -> void: - _home_exit_walk_tween = null - if not _home_exit_transition_active: - return - # End the walking action on the same frame translation ends. The fade and - # authoritative relocation may take longer, but a stationary avatar must - # never keep cycling its walk animation during that wait. - _player.end_animation_action() - _game_ui.get_screen_fade().fade_to_black(HOME_EXIT_FADE_GENERATION) - - -func _on_home_exit_fade_transition_completed( - generation: int, - faded_to_black: bool, -) -> void: - if ( - generation != HOME_EXIT_FADE_GENERATION - or not _home_exit_transition_active - ): - return - if faded_to_black: - # A local host can apply the authoritative transition synchronously from - # request_exit(). Mark this first so its completion signal is not lost. - _home_exit_waiting_for_network = true - var request_started: bool = _network_home.request_exit() - if not request_started: - _home_exit_waiting_for_network = false - _player.global_position = _home_exit_start_position - _game_ui.get_screen_fade().fade_from_black( - HOME_EXIT_FADE_GENERATION - ) - return - _cancel_home_exit_transition(false) - - -func _on_local_home_transition_finished( - accepted: bool, - _message: String, -) -> void: - if ( - not _home_exit_transition_active - or not _home_exit_waiting_for_network - ): - return - _home_exit_waiting_for_network = false - if not accepted: - _player.global_position = _home_exit_start_position - _game_ui.get_screen_fade().fade_from_black(HOME_EXIT_FADE_GENERATION) - - -func _cancel_home_exit_transition(reset_fade: bool) -> void: - if _home_exit_walk_tween != null and _home_exit_walk_tween.is_valid(): - _home_exit_walk_tween.kill() - _home_exit_walk_tween = null - if is_instance_valid(_player): - _player.end_animation_action() - _player.set_local_input_suppressed(HOME_EXIT_INPUT_OWNER, false) - _player.set_camera_input_suppressed(HOME_EXIT_INPUT_OWNER, false) - if ( - reset_fade - and is_instance_valid(_game_ui) - and is_instance_valid(_game_ui.get_screen_fade()) - ): - _game_ui.get_screen_fade().reset_immediately() - _home_exit_transition_active = false - _home_exit_waiting_for_network = false - _home_exit_start_position = Vector3.ZERO - - -func _on_rv_storage_requested() -> void: - if not _can_show_rv_storage_button(): - return - _game_ui.get_player_storage().open_storage_from_home(true) + _game_ui.set_storage_prompt_visible(_can_show_storage_prompt()) func _can_show_shop_prompt() -> bool: @@ -3140,141 +2721,18 @@ func _can_show_shop_prompt() -> bool: and _shop_interaction != null and _shop_interaction.is_local_player_in_range() and not _game_ui.get_fishing_shop().visible - and not _game_ui.get_decor_shop().visible - and not _game_ui.get_rv_upgrade_shop().visible and not _water_recovery.is_recovery_active() and _fishing_spot.can_open_fishing_shop() ) -func _can_show_decor_shop_prompt() -> bool: - return ( - _gameplay_started - and _decor_shop_interaction != null - and _decor_shop_interaction.is_local_player_in_range() - and not _game_ui.get_decor_shop().visible - and not _game_ui.get_fishing_shop().visible - and not _game_ui.get_rv_upgrade_shop().visible - and not _water_recovery.is_recovery_active() - and _fishing_spot.can_open_fishing_shop() - ) - - -func _can_show_rv_upgrade_shop_prompt() -> bool: - return ( - _gameplay_started - and _rv_upgrade_shop_interaction != null - and _rv_upgrade_shop_interaction.is_local_player_in_range() - and not _game_ui.get_rv_upgrade_shop().visible - and not _game_ui.get_fishing_shop().visible - and not _game_ui.get_decor_shop().visible - and not _water_recovery.is_recovery_active() - and _fishing_spot.can_open_fishing_shop() - ) - - -func _can_show_any_shop_prompt() -> bool: - return ( - _can_show_shop_prompt() - or _can_show_decor_shop_prompt() - or _can_show_rv_upgrade_shop_prompt() - ) - - -func _is_any_shop_npc_in_range() -> bool: - return ( - ( - _shop_interaction != null - and _shop_interaction.is_local_player_in_range() - ) - or ( - _decor_shop_interaction != null - and _decor_shop_interaction.is_local_player_in_range() - ) - or ( - _rv_upgrade_shop_interaction != null - and _rv_upgrade_shop_interaction.is_local_player_in_range() - ) - ) - - -func _refresh_shop_npc_speech_state() -> void: - var interaction := _get_active_shop_npc_interaction() - var dialogue_text: String = "" - if interaction != null: - dialogue_text = interaction.get_dialogue_text(_home_state.get_tier()) - _game_ui.set_shop_npc_player_in_range( - interaction != null, - dialogue_text, - ) - - -func _get_active_shop_npc_interaction() -> FishingShopInteractionType: - # Match the interaction and prompt priority used above: RV, decor, fishing. - if ( - _rv_upgrade_shop_interaction != null - and _rv_upgrade_shop_interaction.is_local_player_in_range() - ): - return _rv_upgrade_shop_interaction - if ( - _decor_shop_interaction != null - and _decor_shop_interaction.is_local_player_in_range() - ): - return _decor_shop_interaction - if ( - _shop_interaction != null - and _shop_interaction.is_local_player_in_range() - ): - return _shop_interaction - return null - - func _can_show_storage_prompt() -> bool: - return not _get_home_prompt().is_empty() - - -func _refresh_home_door_prompt(prompt: Dictionary) -> void: - # Home entry belongs to the doorway in world space. Keep the retired HUD - # prompt hidden so it cannot overlap the compact door-mounted sign. - _game_ui.set_storage_prompt_visible(false) - _home_world.set_exterior_prompt_visible( - str(prompt.get("owner_fingerprint", "")), - not prompt.is_empty(), - str(prompt.get("label", "enter home")), - ) - - -func _can_show_rv_storage_button() -> bool: return ( _gameplay_started - and not _network_home.get_local_home_owner_fingerprint().is_empty() - and _network_home.can_local_decorate() + and _storage_interaction != null + and _storage_interaction.is_local_player_in_range() + and not _game_ui.get_player_storage().visible + and not _game_ui.get_fishing_shop().visible and not _water_recovery.is_recovery_active() and _fishing_spot.can_open_fishing_shop() ) - - -func _get_home_prompt() -> Dictionary: - if ( - not _gameplay_started - or _game_ui.get_player_storage().visible - or _game_ui.get_fishing_shop().visible - or _game_ui.get_decor_shop().visible - or _game_ui.get_rv_upgrade_shop().visible - or _water_recovery.is_recovery_active() - or not _fishing_spot.can_open_fishing_shop() - ): - return {} - var owner_fingerprint: String = ( - _network_home.get_local_home_owner_fingerprint() - ) - if not owner_fingerprint.is_empty(): - return {} - var nearby_owner: String = _home_world.find_nearby_exterior_owner(_player) - if nearby_owner.is_empty(): - return {} - return { - "label": "enter home", - "anchor": _home_world.get_exterior_prompt_position(nearby_owner), - "owner_fingerprint": nearby_owner, - } diff --git a/main/main.tscn b/main/main.tscn index 778a1d4..c1380d6 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -58,10 +58,6 @@ [ext_resource type="Resource" path="res://gathering/catalog/gatherable_catalog.tres" id="58_gatherable_catalog"] [ext_resource type="Script" path="res://network/network_world_spawn_service.gd" id="59_world_spawns"] [ext_resource type="Script" path="res://gathering/gathering_controller.gd" id="60_gathering_controller"] -[ext_resource type="Script" path="res://homes/player_home_state.gd" id="61_home_state"] -[ext_resource type="Script" path="res://homes/home_world_service.gd" id="62_home_world"] -[ext_resource type="Script" path="res://network/network_home_service.gd" id="63_network_home"] -[ext_resource type="Script" path="res://homes/home_decor_controller.gd" id="64_home_decor_controller"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_title_grass_native"] shader = ExtResource("42_menu_pattern") @@ -263,14 +259,6 @@ script = ExtResource("22_network_sale") unique_name_in_owner = true script = ExtResource("23_network_shop") -[node name="NetworkHomeService" type="Node" parent="."] -unique_name_in_owner = true -script = ExtResource("63_network_home") - -[node name="HomeDecorController" type="Node" parent="."] -unique_name_in_owner = true -script = ExtResource("64_home_decor_controller") - [node name="NetworkItemUseService" type="Node" parent="." unique_id=255154670] unique_name_in_owner = true script = ExtResource("24_network_item") @@ -334,10 +322,6 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.3999481, 12.07081) [node name="WorldGatherables" type="Node3D" parent="."] -[node name="HomeWorldService" type="Node3D" parent="."] -unique_name_in_owner = true -script = ExtResource("62_home_world") - [node name="GatheringController" type="Node3D" parent="."] unique_name_in_owner = true script = ExtResource("60_gathering_controller") @@ -354,10 +338,6 @@ script = ExtResource("8_recovery") unique_name_in_owner = true script = ExtResource("9_save") -[node name="PlayerHomeState" type="Node" parent="."] -unique_name_in_owner = true -script = ExtResource("61_home_state") - [node name="PlayerSettingsManager" type="Node" parent="." unique_id=906991991] unique_name_in_owner = true script = ExtResource("10_settings") diff --git a/main/texture_sampling_policy.gd b/main/texture_sampling_policy.gd index 35a92a0..822299c 100644 --- a/main/texture_sampling_policy.gd +++ b/main/texture_sampling_policy.gd @@ -15,12 +15,8 @@ func _enter_tree() -> void: func _on_node_added(node: Node) -> void: enforce_node(node) # Some runtime presenters assign their mesh or material immediately after - # add_child(). Recheck those 3D resource owners once deferred so the newly - # assigned resources are covered too. CanvasItems keep their filter setting - # when their texture changes, so deferring every dynamic UI node only floods - # the message queue during large inventory and catalog refreshes. - if _needs_deferred_resource_check(node): - call_deferred("_enforce_deferred_node", weakref(node)) + # add_child(). Recheck once deferred so those resources are covered too. + call_deferred("_enforce_deferred_node", weakref(node)) func _enforce_existing_tree() -> void: @@ -33,15 +29,6 @@ func _enforce_deferred_node(node_ref: WeakRef) -> void: enforce_node(node) -static func _needs_deferred_resource_check(node: Node) -> bool: - return ( - node is GeometryInstance3D - or node is MultiMeshInstance3D - or node is GPUParticles3D - or node is CPUParticles3D - ) - - static func enforce_subtree(node: Node) -> void: enforce_node(node) for child: Node in node.get_children(): diff --git a/network/network_home_protocol.gd b/network/network_home_protocol.gd deleted file mode 100644 index 96756e9..0000000 --- a/network/network_home_protocol.gd +++ /dev/null @@ -1,188 +0,0 @@ -class_name NetworkHomeProtocol -extends RefCounted - -const PlayerHomeStateType = preload("res://homes/player_home_state.gd") - -const CAPABILITY: StringName = &"portable_home_v1" -const RELIABLE_CHANNEL: int = NetworkProtocol.HOME_RELIABLE_CHANNEL -const MAX_ID_LENGTH: int = 96 -const MAX_MESSAGE_LENGTH: int = 160 -const MAX_TRANSITION_COORDINATE: float = 10000.0 -const WORLD_SPACE: StringName = &"world" - -enum TransitionAction { - ENTER, - EXIT, -} - - -static func make_space_id(owner_fingerprint: String) -> StringName: - return StringName("rv:%s" % owner_fingerprint) - - -static func manifest_digest(home_data: Dictionary) -> String: - var sanitized: Dictionary = PlayerHomeStateType.sanitize_save_data(home_data) - if sanitized.is_empty(): - return "" - var canonical_owned: Array[Dictionary] = [] - var owned: Dictionary = sanitized["owned_decor"] - var owned_ids: Array[String] = [] - for value: Variant in owned.keys(): - owned_ids.append(str(value)) - owned_ids.sort() - for decor_id: String in owned_ids: - canonical_owned.append({ - "id": decor_id, - "quantity": int(owned[decor_id]), - }) - var placements: Array[Dictionary] = [] - for value: Variant in sanitized["placements"]: - placements.append((value as Dictionary).duplicate(true)) - placements.sort_custom(func(first: Dictionary, second: Dictionary) -> bool: - return str(first["instance_id"]) < str(second["instance_id"]) - ) - var canonical: Dictionary = { - "schema_version": int(sanitized["schema_version"]), - "tier": int(sanitized["tier"]), - "exterior_id": str(sanitized["exterior_id"]), - "privacy": int(sanitized["privacy"]), - "owned": canonical_owned, - "placements": placements, - "revision": int(sanitized["revision"]), - "next_instance_sequence": int(sanitized["next_instance_sequence"]), - } - return JSON.stringify(canonical).sha256_text() - - -static func manifest_signature_fields(data: Dictionary) -> Array: - return [ - str(data.get("session_id", "")), - str(data.get("request_id", "")), - str(data.get("owner_fingerprint", "")), - int((data.get("home", {}) as Dictionary).get("revision", -1)), - str(data.get("digest", "")), - ] - - -static func validate_manifest_request(value: Variant) -> bool: - if typeof(value) != TYPE_DICTIONARY: - return false - var data: Dictionary = value - if ( - not _valid_id(data.get("request_id")) - or not _valid_id(data.get("session_id")) - or not NetworkIdentityCrypto.valid_fingerprint( - data.get("owner_fingerprint") - ) - or typeof(data.get("home")) != TYPE_DICTIONARY - or typeof(data.get("digest")) != TYPE_STRING - or str(data["digest"]).length() != 64 - or typeof(data.get("sender_signature")) != TYPE_PACKED_BYTE_ARRAY - ): - return false - var sanitized: Dictionary = PlayerHomeStateType.sanitize_save_data( - data["home"] - ) - return ( - not sanitized.is_empty() - and manifest_digest(sanitized) == str(data["digest"]) - ) - - -static func validate_manifest_broadcast(value: Variant) -> bool: - if typeof(value) != TYPE_DICTIONARY: - return false - var data: Dictionary = value - return ( - typeof(data.get("owner_peer_id")) == TYPE_INT - and int(data["owner_peer_id"]) > 0 - and NetworkIdentityCrypto.valid_fingerprint( - data.get("owner_fingerprint") - ) - and typeof(data.get("home")) == TYPE_DICTIONARY - and not PlayerHomeStateType.sanitize_save_data(data["home"]).is_empty() - and _valid_transform(data.get("exterior_transform")) - and typeof(data.get("interior_index")) == TYPE_INT - and int(data["interior_index"]) >= 0 - and int(data["interior_index"]) < 128 - ) - - -static func transition_signature_fields(data: Dictionary) -> Array: - return [ - str(data.get("session_id", "")), - str(data.get("request_id", "")), - int(data.get("action", -1)), - str(data.get("owner_fingerprint", "")), - ] - - -static func validate_transition_request(value: Variant) -> bool: - if typeof(value) != TYPE_DICTIONARY: - return false - var data: Dictionary = value - return ( - _valid_id(data.get("request_id")) - and _valid_id(data.get("session_id")) - and typeof(data.get("action")) == TYPE_INT - and int(data["action"]) >= TransitionAction.ENTER - and int(data["action"]) <= TransitionAction.EXIT - and NetworkIdentityCrypto.valid_fingerprint( - data.get("owner_fingerprint") - ) - and typeof(data.get("sender_signature")) == TYPE_PACKED_BYTE_ARRAY - ) - - -static func transform_to_array(value: Transform3D) -> Array[float]: - return [ - value.basis.x.x, value.basis.x.y, value.basis.x.z, - value.basis.y.x, value.basis.y.y, value.basis.y.z, - value.basis.z.x, value.basis.z.y, value.basis.z.z, - value.origin.x, value.origin.y, value.origin.z, - ] - - -static func array_to_transform(value: Variant) -> Transform3D: - if not validate_transition_transform(value): - return Transform3D() - var fields: Array = value - return Transform3D( - Basis( - Vector3(float(fields[0]), float(fields[1]), float(fields[2])), - Vector3(float(fields[3]), float(fields[4]), float(fields[5])), - Vector3(float(fields[6]), float(fields[7]), float(fields[8])), - ), - Vector3(float(fields[9]), float(fields[10]), float(fields[11])), - ) - - -static func validate_transition_transform(value: Variant) -> bool: - if not _valid_transform(value): - return false - var fields: Array = value - return ( - absf(float(fields[9])) <= MAX_TRANSITION_COORDINATE - and absf(float(fields[10])) <= MAX_TRANSITION_COORDINATE - and absf(float(fields[11])) <= MAX_TRANSITION_COORDINATE - ) - - -static func _valid_transform(value: Variant) -> bool: - if typeof(value) != TYPE_ARRAY or (value as Array).size() != 12: - return false - for component: Variant in value: - if ( - typeof(component) not in [TYPE_FLOAT, TYPE_INT] - or not is_finite(float(component)) - ): - return false - return true - - -static func _valid_id(value: Variant) -> bool: - return ( - typeof(value) == TYPE_STRING - and not str(value).is_empty() - and str(value).length() <= MAX_ID_LENGTH - ) diff --git a/network/network_home_protocol.gd.uid b/network/network_home_protocol.gd.uid deleted file mode 100644 index 24b6aae..0000000 --- a/network/network_home_protocol.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://fl3qwiluejhm diff --git a/network/network_home_service.gd b/network/network_home_service.gd deleted file mode 100644 index 4885961..0000000 --- a/network/network_home_service.gd +++ /dev/null @@ -1,1450 +0,0 @@ -class_name NetworkHomeService -extends Node - -const NetworkHomeProtocolType = preload( - "res://network/network_home_protocol.gd" -) -const PlayerHomeStateType = preload("res://homes/player_home_state.gd") -const DecorCatalogType = preload("res://homes/decor_catalog.gd") -const OwnedItemType = preload("res://items/owned_item.gd") - -signal local_transition_finished(accepted: bool, message: String) -signal local_space_changed(space_id: StringName, owner_fingerprint: String) -signal local_manifest_rejected(message: String) - -const MAX_MANIFESTS: int = 128 -const MAX_PENDING_LOCAL_DECOR_TRANSACTIONS: int = 16 - -var _session: NetworkSession -var _spawn_service: PlayerSpawnService -var _local_home: PlayerHomeStateType -var _save_manager: PlayerSaveManager -var _world_service: HomeWorldService -var _bag: PlayerBag -var _inventory_layout: PlayerInventoryLayout -var _hotbar: PlayerHotbar -var _manifests_by_fingerprint: Dictionary[String, Dictionary] = {} -var _fingerprint_by_peer: Dictionary[int, String] = {} -var _authorized_owned_counts: Dictionary[int, Dictionary] = {} -var _authorization_result_owner: Dictionary[String, int] = {} -var _authorized_home_tiers: Dictionary[int, int] = {} -var _home_upgrade_result_owner: Dictionary[String, int] = {} -var _interior_slots_by_space: Dictionary[StringName, Dictionary] = {} -var _next_interior_index: int = 0 -var _suppress_local_submission: bool = false -var _publish_queued: bool = false -var _last_submitted_local_home_revision: int = -1 -var _pending_local_decor_transactions: Dictionary[int, Dictionary] = {} - - -func setup( - session: NetworkSession, - spawn_service: PlayerSpawnService, - local_home: PlayerHomeStateType, - save_manager: PlayerSaveManager, - world_service: HomeWorldService, - bag: PlayerBag, - inventory_layout: PlayerInventoryLayout, - hotbar: PlayerHotbar, -) -> void: - _session = session - _spawn_service = spawn_service - _local_home = local_home - _save_manager = save_manager - _world_service = world_service - _bag = bag - _inventory_layout = inventory_layout - _hotbar = hotbar - if not _session.state_changed.is_connected(_on_session_state_changed): - _session.state_changed.connect(_on_session_state_changed) - if not _session.join_authenticated.is_connected(_on_join_authenticated): - _session.join_authenticated.connect(_on_join_authenticated) - if not _session.peer_authenticated.is_connected(_on_peer_authenticated): - _session.peer_authenticated.connect(_on_peer_authenticated) - if not _session.peer_removed.is_connected(_on_peer_removed): - _session.peer_removed.connect(_on_peer_removed) - if not _local_home.changed.is_connected(_on_local_home_changed): - _local_home.changed.connect(_on_local_home_changed) - - -func get_local_space() -> StringName: - if _session == null or not _session.is_gameplay_session_active(): - return NetworkHomeProtocolType.WORLD_SPACE - return _session.get_peer_space(_session.get_local_peer_id()) - - -func get_local_home_owner_fingerprint() -> String: - var space_id: String = String(get_local_space()) - return space_id.trim_prefix("rv:") if space_id.begins_with("rv:") else "" - - -func can_local_decorate() -> bool: - return ( - get_local_home_owner_fingerprint() - == _session.get_local_identity_fingerprint() - ) - - -func has_pending_local_decor_mutation() -> bool: - return not _pending_local_decor_transactions.is_empty() - - -func get_authoritative_owned_count( - peer_id: int, - decor_id: StringName, -) -> int: - var authorized: Dictionary = _authorized_owned_counts.get(peer_id, {}) - if authorized.has(String(decor_id)): - return int(authorized[String(decor_id)]) - var fingerprint: String = _fingerprint_for_peer(peer_id) - var manifest: Dictionary = _manifests_by_fingerprint.get(fingerprint, {}) - if manifest.is_empty(): - return -1 - var home: Dictionary = manifest.get("home", {}) - var owned: Dictionary = home.get("owned_decor", {}) - return int(owned.get(String(decor_id), 0)) - - -func get_authoritative_home_tier(peer_id: int) -> int: - if _authorized_home_tiers.has(peer_id): - return int(_authorized_home_tiers[peer_id]) - var fingerprint: String = _fingerprint_for_peer(peer_id) - var manifest: Dictionary = _manifests_by_fingerprint.get(fingerprint, {}) - if manifest.is_empty(): - return -1 - var home: Dictionary = manifest.get("home", {}) - return int(home.get("tier", -1)) - - -func place_local_decor( - decor_id: StringName, - position: Vector3, - yaw_radians: float, - mode: PlayerHomeStateType.PlacementMode, -) -> String: - if ( - not can_local_decorate() - or has_pending_local_decor_mutation() - or _bag == null - or _bag.get_quantity(decor_id) <= 0 - or _world_service == null - ): - return "" - var owner_fingerprint: String = ( - _session.get_local_identity_fingerprint() - ) - var normalized: Dictionary = ( - _local_home.get_nearest_valid_placement_transform( - decor_id, - position, - yaw_radians, - mode, - "", - Callable( - self, "_candidate_clear_of_players" - ).bind(owner_fingerprint, decor_id), - ) - ) - if normalized.is_empty(): - return "" - var transaction: Dictionary = _capture_local_decor_transaction() - if transaction.is_empty(): - return "" - _suppress_local_submission = true - var instance_id: String = _local_home.place_decor( - decor_id, - normalized["position"], - float(normalized["yaw"]), - mode, - ) - if instance_id.is_empty(): - _suppress_local_submission = false - return "" - if not _bag.remove_item(decor_id, 1): - _local_home.remove_placement(instance_id) - _suppress_local_submission = false - return "" - _suppress_local_submission = false - if not _commit_local_decor_transaction(transaction): - return "" - return instance_id - - -func remove_local_decor(instance_id: String) -> bool: - if ( - not can_local_decorate() - or has_pending_local_decor_mutation() - or _bag == null - or _inventory_layout == null - or _hotbar == null - ): - return false - var placement: Dictionary = _local_home.get_placement(instance_id) - if placement.is_empty(): - return false - var transaction: Dictionary = _capture_local_decor_transaction() - if transaction.is_empty(): - return false - var decor_id := StringName(str(placement["decor_id"])) - var target_hotbar_slot: int = _hotbar.find_item_slot(decor_id) - if target_hotbar_slot < 0: - target_hotbar_slot = _hotbar.find_first_empty_slot() - var prepared_hotbar_placement: bool = false - if ( - target_hotbar_slot >= 0 - and _inventory_layout.get_container( - PlayerInventoryLayout.EntryKind.ITEM, - decor_id, - ) < 0 - ): - prepared_hotbar_placement = ( - _inventory_layout.prepare_new_item_placement( - decor_id, - PlayerInventoryLayout.InventoryContainer.HOTBAR, - target_hotbar_slot, - ) - ) - var returned_to_inventory: bool = _bag.add_item(decor_id, 1) - if not returned_to_inventory: - if prepared_hotbar_placement: - _inventory_layout.cancel_prepared_item_placement(decor_id) - returned_to_inventory = _bag.add_item_to_storage(decor_id, 1) - if not returned_to_inventory: - if prepared_hotbar_placement: - _inventory_layout.cancel_prepared_item_placement(decor_id) - return false - if target_hotbar_slot >= 0: - _hotbar.assign_item(target_hotbar_slot, decor_id) - _hotbar.select_slot(target_hotbar_slot) - _suppress_local_submission = true - if not _local_home.remove_placement(instance_id): - _suppress_local_submission = false - _restore_local_decor_transaction(transaction) - return false - _suppress_local_submission = false - if not _commit_local_decor_transaction(transaction): - return false - return true - - -func update_local_decor( - instance_id: String, - position: Vector3, - yaw_radians: float, - mode: PlayerHomeStateType.PlacementMode, -) -> bool: - if has_pending_local_decor_mutation(): - return false - var normalized: Dictionary = get_valid_local_decor_transform( - instance_id, position, yaw_radians, mode - ) - if normalized.is_empty(): - return false - var transaction: Dictionary = _capture_local_decor_transaction() - if transaction.is_empty(): - return false - _suppress_local_submission = true - if not _local_home.update_placement( - instance_id, - normalized["position"], - float(normalized["yaw"]), - mode, - ): - _suppress_local_submission = false - return false - _suppress_local_submission = false - if not _commit_local_decor_transaction(transaction): - return false - return true - - -func get_valid_local_decor_transform( - instance_id: String, - position: Vector3, - yaw_radians: float, - mode: PlayerHomeStateType.PlacementMode, -) -> Dictionary: - if not can_local_decorate() or _world_service == null: - return {} - var placement: Dictionary = _local_home.get_placement(instance_id) - if placement.is_empty(): - return {} - var decor_id := StringName(str(placement.get("decor_id", ""))) - return _local_home.get_valid_update_transform( - instance_id, - position, - yaw_radians, - mode, - Callable( - self, "_candidate_clear_of_players" - ).bind( - _session.get_local_identity_fingerprint(), decor_id - ), - ) - - -func _candidate_clear_of_players( - candidate: Dictionary, - owner_fingerprint: String, - decor_id: StringName, -) -> bool: - return ( - _world_service != null - and _world_service.is_decor_placement_clear_of_players( - owner_fingerprint, - decor_id, - candidate["position"], - float(candidate["yaw"]), - ) - ) - - -func request_enter(owner_fingerprint: String) -> bool: - if ( - _session == null - or not _session.is_gameplay_session_active() - or not _manifests_by_fingerprint.has(owner_fingerprint) - or get_local_space() != NetworkHomeProtocolType.WORLD_SPACE - ): - return false - return _submit_transition( - NetworkHomeProtocolType.TransitionAction.ENTER, - owner_fingerprint, - ) - - -func request_exit() -> bool: - var owner_fingerprint: String = get_local_home_owner_fingerprint() - if owner_fingerprint.is_empty(): - return false - return _submit_transition( - NetworkHomeProtocolType.TransitionAction.EXIT, - owner_fingerprint, - ) - - -func authorize_decor_purchase( - peer_id: int, - product_id: StringName, - resulting_count: int, - result_id: String, -) -> void: - if not _session.is_host() or peer_id <= 0 or result_id.is_empty(): - return - var counts: Dictionary = _authorized_owned_counts.get(peer_id, {}) - counts[String(product_id)] = resulting_count - _authorized_owned_counts[peer_id] = counts - _authorization_result_owner[result_id] = peer_id - - -func finalize_decor_purchase( - peer_id: int, - result_id: String, - applied: bool, -) -> void: - if _authorization_result_owner.get(result_id, 0) != peer_id: - return - _authorization_result_owner.erase(result_id) - if applied: - return - _authorized_owned_counts.erase(peer_id) - - -func authorize_home_upgrade( - peer_id: int, - resulting_tier: int, - result_id: String, -) -> void: - if ( - not _session.is_host() - or peer_id <= 0 - or result_id.is_empty() - or resulting_tier < PlayerHomeStateType.STARTER_TIER - or resulting_tier > PlayerHomeStateType.MAX_TIER - ): - return - _authorized_home_tiers[peer_id] = resulting_tier - _home_upgrade_result_owner[result_id] = peer_id - - -func finalize_home_upgrade( - peer_id: int, - result_id: String, - applied: bool, -) -> void: - if _home_upgrade_result_owner.get(result_id, 0) != peer_id: - return - _home_upgrade_result_owner.erase(result_id) - if not applied: - _authorized_home_tiers.erase(peer_id) - - -func refresh_world_presentations() -> void: - if _world_service == null: - return - _world_service.clear_presentations() - if not _world_service.is_world_ready(): - return - if not _session.is_host(): - for manifest: Dictionary in _manifests_by_fingerprint.values(): - _world_service.apply_manifest(manifest) - return - var local_fingerprint: String = _session.get_local_identity_fingerprint() - if ( - not _session.is_dedicated_host() - and not _manifests_by_fingerprint.has(local_fingerprint) - ): - publish_local_home() - var fingerprints: Array[String] = [] - fingerprints.assign(_manifests_by_fingerprint.keys()) - fingerprints.sort() - var reserved: Array[Transform3D] = [] - for fingerprint: String in fingerprints: - var manifest: Dictionary = _manifests_by_fingerprint[fingerprint] - var transform: Transform3D = _world_service.choose_exterior_transform( - int(manifest["owner_peer_id"]), reserved - ) - reserved.append(transform) - manifest["exterior_transform"] = ( - NetworkHomeProtocolType.transform_to_array(transform) - ) - _manifests_by_fingerprint[fingerprint] = manifest - _broadcast_manifest(manifest) - for fingerprint: String in fingerprints: - var manifest: Dictionary = _manifests_by_fingerprint[fingerprint] - _spawn_peer_at_exterior( - int(manifest["owner_peer_id"]), fingerprint - ) - - -func publish_local_home( - allow_pending_decor_transaction: bool = false, -) -> bool: - if ( - _session == null - or not _session.is_gameplay_session_active() - or _session.is_dedicated_host() - or _local_home == null - ): - return false - # A purchase can update the owned decor count while an optimistic placement - # is waiting on the host. Hold that follow-up manifest until the placement is - # accepted or rolled back; otherwise it can carry the still-unverified - # placement and turn one rejection into a second stale rejection. - if ( - not allow_pending_decor_transaction - and has_pending_local_decor_mutation() - ): - _publish_queued = true - return true - if _session.is_host() and ( - _world_service == null or not _world_service.is_world_ready() - ): - return false - var home_data: Dictionary = _local_home.to_save_data() - var revision: int = int(home_data.get("revision", -1)) - if revision >= 0 and revision == _last_submitted_local_home_revision: - return true - var request: Dictionary = { - "request_id": _new_id("home"), - "session_id": _session.get_session_id(), - "owner_fingerprint": _session.get_local_identity_fingerprint(), - "home": home_data, - "digest": NetworkHomeProtocolType.manifest_digest(home_data), - } - request["sender_signature"] = _session.sign_local_action( - "portable_home_manifest", - NetworkHomeProtocolType.manifest_signature_fields(request), - ) - _last_submitted_local_home_revision = revision - if _session.is_host(): - _process_manifest_request(_session.get_local_peer_id(), request) - else: - submit_home_manifest.rpc_id(1, request) - return true - - -@rpc( - "any_peer", "call_remote", "reliable", - NetworkHomeProtocolType.RELIABLE_CHANNEL, -) -func submit_home_manifest(data: Dictionary) -> void: - var sender_id: int = multiplayer.get_remote_sender_id() - if ( - _session == null - or not _session.is_host() - or not _session.is_authenticated_peer(sender_id) - ): - return - _process_manifest_request(sender_id, data) - - -func _process_manifest_request(peer_id: int, data: Dictionary) -> void: - if ( - not NetworkHomeProtocolType.validate_manifest_request(data) - or str(data["session_id"]) != _session.get_session_id() - ): - _reject_manifest(peer_id, "The home update was invalid.") - return - var record := _session.get_peer_record(peer_id) - var fingerprint: String = str(data["owner_fingerprint"]) - if ( - record == null - or record.identity_fingerprint != fingerprint - or not _session.verify_peer_action( - peer_id, - "portable_home_manifest", - NetworkHomeProtocolType.manifest_signature_fields(data), - data["sender_signature"], - ) - ): - _reject_manifest(peer_id, "The home owner could not be verified.") - return - var home_data: Dictionary = PlayerHomeStateType.sanitize_save_data( - data["home"] - ) - var prior: Dictionary = _manifests_by_fingerprint.get(fingerprint, {}) - var is_first_manifest: bool = prior.is_empty() - if not _valid_manifest_revision(peer_id, prior, home_data): - _reject_manifest(peer_id, "The home update was rejected by the host.") - return - if not _moved_decor_clear_of_players(fingerprint, prior, home_data): - _reject_manifest(peer_id, "A player is in the way of that decor.") - return - var manifest: Dictionary - if prior.is_empty(): - if _manifests_by_fingerprint.size() >= MAX_MANIFESTS: - _reject_manifest(peer_id, "This room cannot place another home.") - return - var reserved: Array[Transform3D] = [] - for existing: Dictionary in _manifests_by_fingerprint.values(): - reserved.append(NetworkHomeProtocolType.array_to_transform( - existing["exterior_transform"] - )) - var exterior_transform: Transform3D = ( - _world_service.choose_exterior_transform(peer_id, reserved) - ) - manifest = { - "owner_peer_id": peer_id, - "owner_fingerprint": fingerprint, - "home": home_data, - "exterior_transform": ( - NetworkHomeProtocolType.transform_to_array(exterior_transform) - ), - "interior_index": _next_interior_index, - } - _next_interior_index += 1 - else: - manifest = prior.duplicate(true) - manifest["home"] = home_data - manifest["owner_peer_id"] = peer_id - _manifests_by_fingerprint[fingerprint] = manifest - _fingerprint_by_peer[peer_id] = fingerprint - _consume_owned_authorizations(peer_id, home_data) - if peer_id == _session.get_local_peer_id(): - _discard_local_decor_transactions_through( - int(home_data.get("revision", -1)) - ) - if _last_submitted_local_home_revision == int( - home_data.get("revision", -1) - ): - _last_submitted_local_home_revision = -1 - _flush_queued_local_home_publish() - _broadcast_manifest(manifest) - if is_first_manifest: - _spawn_peer_at_exterior(peer_id, fingerprint) - - -func _valid_manifest_revision( - peer_id: int, - prior_manifest: Dictionary, - home_data: Dictionary, -) -> bool: - if prior_manifest.is_empty(): - # A portable RV is owned by the player's signed local save. A new host has - # no prior room ledger with which to compare its first manifest. - return true - var prior_home: Dictionary = prior_manifest["home"] - var prior_tier: int = int(prior_home["tier"]) - var next_tier: int = int(home_data["tier"]) - var tier_is_authorized: bool = ( - next_tier != prior_tier - and int(_authorized_home_tiers.get(peer_id, -1)) == next_tier - ) - if ( - int(home_data["revision"]) <= int(prior_home["revision"]) - or (next_tier != prior_tier and not tier_is_authorized) - or str(home_data["exterior_id"]) - != str(PlayerHomeStateType.exterior_id_for_tier(next_tier)) - ): - return false - var prior_owned: Dictionary = prior_home["owned_decor"] - var next_owned: Dictionary = home_data["owned_decor"] - var all_ids: Dictionary[String, bool] = {} - for value: Variant in prior_owned.keys(): - all_ids[str(value)] = true - for value: Variant in next_owned.keys(): - all_ids[str(value)] = true - var authorized: Dictionary = _authorized_owned_counts.get(peer_id, {}) - for product_id: String in all_ids: - var prior_count: int = int(prior_owned.get(product_id, 0)) - var next_count: int = int(next_owned.get(product_id, 0)) - if prior_count == next_count: - continue - if next_count < prior_count or int(authorized.get(product_id, -1)) != next_count: - return false - return true - - -func _moved_decor_clear_of_players( - owner_fingerprint: String, - prior_manifest: Dictionary, - home_data: Dictionary, -) -> bool: - if _world_service == null: - return false - var prior_by_instance: Dictionary[String, Dictionary] = {} - if not prior_manifest.is_empty(): - var prior_home: Dictionary = prior_manifest.get("home", {}) - for value: Variant in prior_home.get("placements", []): - if typeof(value) != TYPE_DICTIONARY: - continue - var prior_placement: Dictionary = value - prior_by_instance[str( - prior_placement.get("instance_id", "") - )] = prior_placement - for value: Variant in home_data.get("placements", []): - if typeof(value) != TYPE_DICTIONARY: - return false - var placement: Dictionary = value - var instance_id: String = str(placement.get("instance_id", "")) - var prior_placement: Dictionary = prior_by_instance.get( - instance_id, {} - ) - if ( - not prior_placement.is_empty() - and _same_decor_transform(prior_placement, placement) - ): - continue - var decor_id := StringName(str(placement.get("decor_id", ""))) - var position: Vector3 = _placement_position(placement) - if ( - not position.is_finite() - or not _world_service.is_decor_placement_clear_of_players( - owner_fingerprint, - decor_id, - position, - float(placement.get("yaw", 0.0)), - ) - ): - return false - return true - - -static func _same_decor_transform( - first: Dictionary, - second: Dictionary, -) -> bool: - if str(first.get("decor_id", "")) != str(second.get("decor_id", "")): - return false - var first_position: Vector3 = _placement_position(first) - var second_position: Vector3 = _placement_position(second) - return ( - first_position.is_equal_approx(second_position) - and is_equal_approx( - float(first.get("yaw", 0.0)), - float(second.get("yaw", 0.0)), - ) - ) - - -static func _placement_position(placement: Dictionary) -> Vector3: - var values: Array = placement.get("position", []) - if values.size() != 3: - return Vector3.INF - return Vector3( - float(values[0]), float(values[1]), float(values[2]) - ) - - -func _consume_owned_authorizations(peer_id: int, home_data: Dictionary) -> void: - if ( - _authorized_home_tiers.has(peer_id) - and int(home_data["tier"]) == int(_authorized_home_tiers[peer_id]) - ): - _authorized_home_tiers.erase(peer_id) - var authorized: Dictionary = _authorized_owned_counts.get(peer_id, {}) - if authorized.is_empty(): - return - var owned: Dictionary = home_data["owned_decor"] - for product_id: String in authorized.keys(): - if int(owned.get(product_id, -1)) == int(authorized[product_id]): - authorized.erase(product_id) - if authorized.is_empty(): - _authorized_owned_counts.erase(peer_id) - else: - _authorized_owned_counts[peer_id] = authorized - - -func _broadcast_manifest(manifest: Dictionary) -> void: - if _world_service != null and _world_service.is_world_ready(): - _world_service.apply_manifest(manifest) - for peer_id: int in _session.get_authenticated_peer_ids(): - if peer_id == 1: - continue - receive_home_manifest.rpc_id(peer_id, manifest) - - -@rpc( - "authority", "call_remote", "reliable", - NetworkHomeProtocolType.RELIABLE_CHANNEL, -) -func receive_home_manifest(data: Dictionary) -> void: - if not NetworkHomeProtocolType.validate_manifest_broadcast(data): - return - var fingerprint: String = str(data["owner_fingerprint"]) - _manifests_by_fingerprint[fingerprint] = data.duplicate(true) - _fingerprint_by_peer[int(data["owner_peer_id"])] = fingerprint - if fingerprint == _session.get_local_identity_fingerprint(): - var home: Dictionary = data.get("home", {}) - _discard_local_decor_transactions_through( - int(home.get("revision", -1)) - ) - if _last_submitted_local_home_revision == int( - home.get("revision", -1) - ): - _last_submitted_local_home_revision = -1 - _flush_queued_local_home_publish() - if _world_service != null and _world_service.is_world_ready(): - _world_service.apply_manifest(data) - - -func _reject_manifest(peer_id: int, message: String) -> void: - var fingerprint: String = _fingerprint_for_peer(peer_id) - var canonical: Dictionary = _manifests_by_fingerprint.get(fingerprint, {}) - if peer_id == _session.get_local_peer_id(): - _apply_manifest_rejection(message, canonical) - else: - receive_home_manifest_rejection.rpc_id(peer_id, message, canonical) - - -@rpc( - "authority", "call_remote", "reliable", - NetworkHomeProtocolType.RELIABLE_CHANNEL, -) -func receive_home_manifest_rejection( - message: String, - canonical: Dictionary, -) -> void: - _apply_manifest_rejection(message, canonical) - - -func _apply_manifest_rejection(message: String, canonical: Dictionary) -> void: - _last_submitted_local_home_revision = -1 - var canonical_home: Dictionary = canonical.get("home", {}) - var canonical_revision: int = int(canonical_home.get("revision", -1)) - var transaction: Dictionary = _get_local_decor_rejection_transaction( - canonical_revision - ) - if canonical.is_empty() or not NetworkHomeProtocolType.validate_manifest_broadcast(canonical): - if not transaction.is_empty(): - _restore_local_decor_transaction(transaction) - _pending_local_decor_transactions.clear() - if _save_manager != null and not _save_manager.save_if_dirty(): - push_warning("Unable to save the restored RV decor state.") - _flush_queued_local_home_publish() - local_manifest_rejected.emit(message) - return - # A shop result can be accepted locally while its own home manifest is still - # in flight. A delayed rejection for an earlier placement must not erase that - # accepted purchase just because the host's canonical manifest predates it. - var locally_owned_decor: Dictionary[StringName, int] = ( - _capture_local_owned_decor_counts() - ) - # Only carry ownership forward across an actual pending decor-action - # rejection. A standalone rejected manifest must remain authoritative rather - # than repeatedly re-submitting an unverified owned-count increase. - var has_owned_decor_increase: bool = ( - not transaction.is_empty() - and _has_local_owned_decor_increase( - locally_owned_decor, - canonical_home, - ) - ) - _suppress_local_submission = true - var restored: bool = _local_home.restore_from_save_data(canonical_home) - var owned_decor_restored: bool = ( - restored - and ( - not has_owned_decor_increase - or _restore_local_owned_decor_increases(locally_owned_decor) - ) - ) - var reconciled: bool = ( - restored - and owned_decor_restored - and _reconcile_local_decor_inventory(transaction) - ) - _suppress_local_submission = false - _discard_local_decor_transactions_after(canonical_revision) - if not reconciled: - push_error("Unable to reconcile RV decor after a host rejection.") - elif _save_manager != null and not _save_manager.save_if_dirty(): - push_warning("Unable to save the restored RV decor state.") - receive_home_manifest(canonical) - if reconciled and has_owned_decor_increase: - # Submit the preserved purchase as a fresh revision after applying the - # canonical placement rollback. This is deliberately separate from the - # rejected action, so the host can validate the already-authorized count. - if not publish_local_home(): - push_warning("Unable to publish the restored RV decor ownership.") - local_manifest_rejected.emit(message) - - -func _capture_local_decor_transaction() -> Dictionary: - if ( - _local_home == null - or _bag == null - or _inventory_layout == null - or _hotbar == null - ): - return {} - var saved_items: Array[OwnedItemType] = [] - for item: OwnedItemType in _bag.get_all_items(): - saved_items.append(item.duplicate_record()) - return { - "home": _local_home.to_save_data(), - "bag_items": saved_items, - "layout": _inventory_layout.to_save_data(), - "hotbar_slots": _hotbar.get_slots(), - "hotbar_fish_slots": _hotbar.get_fish_slots(), - "selected_hotbar_slot": _hotbar.get_selected_slot(), - } - - -func _capture_local_owned_decor_counts() -> Dictionary[StringName, int]: - var counts: Dictionary[StringName, int] = {} - if _local_home == null: - return counts - for decor_id: StringName in DecorCatalogType.get_product_ids(): - counts[decor_id] = _local_home.get_owned_count(decor_id) - return counts - - -func _has_local_owned_decor_increase( - local_counts: Dictionary[StringName, int], - canonical_home: Dictionary, -) -> bool: - var canonical_owned: Dictionary = canonical_home.get("owned_decor", {}) - for decor_id: StringName in DecorCatalogType.get_product_ids(): - if int(local_counts.get(decor_id, 0)) > int( - canonical_owned.get(String(decor_id), 0) - ): - return true - return false - - -func _restore_local_owned_decor_increases( - local_counts: Dictionary[StringName, int], -) -> bool: - if _local_home == null: - return false - for decor_id: StringName in DecorCatalogType.get_product_ids(): - var local_count: int = int(local_counts.get(decor_id, 0)) - var canonical_count: int = _local_home.get_owned_count(decor_id) - while canonical_count < local_count: - if not _local_home.add_owned_decor(decor_id, 1): - return false - canonical_count += 1 - return true - - -func _commit_local_decor_transaction(transaction: Dictionary) -> bool: - if _save_manager == null or not _save_manager.save_if_dirty(): - _restore_local_decor_transaction(transaction) - return false - var revision: int = _local_home.get_revision() - _pending_local_decor_transactions[revision] = transaction.duplicate(true) - _trim_pending_local_decor_transactions() - if publish_local_home(true): - return true - _pending_local_decor_transactions.erase(revision) - _restore_local_decor_transaction(transaction) - _flush_queued_local_home_publish() - # The optimistic mutation was already written before we attempted to publish - # it. If the session ended or could not accept the manifest, immediately - # persist the restored state so a restart cannot resurrect the rejected - # placement or consume its decor item. - if _save_manager != null and not _save_manager.save_if_dirty(): - push_warning("Unable to save the restored RV decor state.") - return false - - -func _restore_local_decor_transaction(transaction: Dictionary) -> bool: - if ( - transaction.is_empty() - or _local_home == null - or _bag == null - or _inventory_layout == null - or _hotbar == null - ): - return false - var home_data: Variant = transaction.get("home", {}) - var layout_data: Variant = transaction.get("layout", {}) - var stored_items: Variant = transaction.get("bag_items", []) - var stored_slots: Variant = transaction.get("hotbar_slots", []) - var stored_fish_slots: Variant = transaction.get("hotbar_fish_slots", []) - if ( - typeof(home_data) != TYPE_DICTIONARY - or typeof(layout_data) != TYPE_DICTIONARY - or typeof(stored_items) != TYPE_ARRAY - or typeof(stored_slots) != TYPE_ARRAY - or typeof(stored_fish_slots) != TYPE_ARRAY - ): - return false - var items: Array[OwnedItemType] = [] - for value: Variant in stored_items as Array: - if not value is OwnedItemType: - return false - items.append((value as OwnedItemType).duplicate_record()) - var slots: Array[StringName] = [] - for value: Variant in stored_slots as Array: - slots.append(StringName(str(value))) - var fish_slots: Array[StringName] = [] - for value: Variant in stored_fish_slots as Array: - fish_slots.append(StringName(str(value))) - var was_suppressed: bool = _suppress_local_submission - _suppress_local_submission = true - var home_restored: bool = _local_home.restore_from_save_data( - home_data as Dictionary - ) - var bag_restored: bool = _bag.replace_all_items(items) - var layout_restored: bool = _inventory_layout.restore_from_save_data( - layout_data as Dictionary - ) - var hotbar_restored: bool = _hotbar.replace_state( - slots, - int(transaction.get("selected_hotbar_slot", 0)), - fish_slots, - false, - ) - _suppress_local_submission = was_suppressed - return home_restored and bag_restored and layout_restored and hotbar_restored - - -func _reconcile_local_decor_inventory(transaction: Dictionary) -> bool: - if _bag == null or _local_home == null: - return false - var preferred_slots: Dictionary[StringName, int] = ( - _decor_hotbar_slots_from_transaction(transaction) - ) - for decor_id: StringName in DecorCatalogType.get_product_ids(): - var target_quantity: int = _local_home.get_available_count(decor_id) - var current_quantity: int = _bag.get_quantity(decor_id) - if current_quantity > target_quantity: - if not _bag.remove_item( - decor_id, current_quantity - target_quantity - ): - return false - continue - while current_quantity < target_quantity: - var preferred_slot: int = int( - preferred_slots.get(decor_id, -1) - ) - if not _restore_decor_item_to_bag(decor_id, preferred_slot): - return false - current_quantity += 1 - return true - - -func _decor_hotbar_slots_from_transaction( - transaction: Dictionary, -) -> Dictionary[StringName, int]: - var result: Dictionary[StringName, int] = {} - var stored_slots: Variant = transaction.get("hotbar_slots", []) - if typeof(stored_slots) != TYPE_ARRAY: - return result - for slot_index: int in range((stored_slots as Array).size()): - var decor_id := StringName(str((stored_slots as Array)[slot_index])) - if DecorCatalogType.has_product(decor_id): - result[decor_id] = slot_index - return result - - -func _restore_decor_item_to_bag( - decor_id: StringName, - preferred_hotbar_slot: int, -) -> bool: - if _bag == null: - return false - var prepared_slot: int = -1 - if ( - _inventory_layout != null - and _hotbar != null - and _inventory_layout.get_container( - PlayerInventoryLayout.EntryKind.ITEM, decor_id - ) < 0 - ): - var candidate_slot: int = preferred_hotbar_slot - if candidate_slot < 0: - candidate_slot = _hotbar.find_first_empty_slot() - if candidate_slot >= 0 and _inventory_layout.prepare_new_item_placement( - decor_id, - PlayerInventoryLayout.InventoryContainer.HOTBAR, - candidate_slot, - ): - prepared_slot = candidate_slot - var restored: bool = _bag.add_item(decor_id, 1) - if not restored: - if prepared_slot >= 0: - _inventory_layout.cancel_prepared_item_placement(decor_id) - if _bag.get_owned_item(decor_id) == null: - restored = _bag.add_item_to_storage(decor_id, 1) - if restored and prepared_slot >= 0: - _hotbar.assign_item(prepared_slot, decor_id) - _hotbar.select_slot(prepared_slot) - return restored - - -func _get_local_decor_rejection_transaction( - canonical_revision: int, -) -> Dictionary: - var revisions: Array[int] = [] - revisions.assign(_pending_local_decor_transactions.keys()) - revisions.sort() - for revision: int in revisions: - if canonical_revision < 0 or revision > canonical_revision: - return _pending_local_decor_transactions[revision].duplicate(true) - return {} - - -func _discard_local_decor_transactions_through(revision: int) -> void: - for pending_revision: int in _pending_local_decor_transactions.keys(): - if pending_revision <= revision: - _pending_local_decor_transactions.erase(pending_revision) - - -func _discard_local_decor_transactions_after(revision: int) -> void: - for pending_revision: int in _pending_local_decor_transactions.keys(): - if pending_revision > revision: - _pending_local_decor_transactions.erase(pending_revision) - - -func _trim_pending_local_decor_transactions() -> void: - var revisions: Array[int] = [] - revisions.assign(_pending_local_decor_transactions.keys()) - revisions.sort() - while revisions.size() > MAX_PENDING_LOCAL_DECOR_TRANSACTIONS: - _pending_local_decor_transactions.erase(revisions.pop_front()) - - -func _submit_transition(action: int, owner_fingerprint: String) -> bool: - var request: Dictionary = { - "request_id": _new_id("home_transition"), - "session_id": _session.get_session_id(), - "action": action, - "owner_fingerprint": owner_fingerprint, - } - request["sender_signature"] = _session.sign_local_action( - "portable_home_transition", - NetworkHomeProtocolType.transition_signature_fields(request), - ) - if _session.is_host(): - _process_transition_request(_session.get_local_peer_id(), request) - else: - submit_home_transition.rpc_id(1, request) - return true - - -@rpc( - "any_peer", "call_remote", "reliable", - NetworkHomeProtocolType.RELIABLE_CHANNEL, -) -func submit_home_transition(data: Dictionary) -> void: - var sender_id: int = multiplayer.get_remote_sender_id() - if ( - not _session.is_host() - or not _session.is_authenticated_peer(sender_id) - ): - return - _process_transition_request(sender_id, data) - - -func _process_transition_request(peer_id: int, data: Dictionary) -> void: - if ( - not NetworkHomeProtocolType.validate_transition_request(data) - or str(data["session_id"]) != _session.get_session_id() - or not _session.verify_peer_action( - peer_id, - "portable_home_transition", - NetworkHomeProtocolType.transition_signature_fields(data), - data["sender_signature"], - ) - ): - _send_transition_rejection(peer_id, "The home transition was invalid.") - return - var owner_fingerprint: String = str(data["owner_fingerprint"]) - var action: int = int(data["action"]) - var avatar: Player = _spawn_service.get_avatar(peer_id) - var manifest: Dictionary = _manifests_by_fingerprint.get( - owner_fingerprint, {} - ) - if avatar == null or manifest.is_empty(): - _send_transition_rejection(peer_id, "That home is unavailable.") - return - if action == NetworkHomeProtocolType.TransitionAction.ENTER: - var record := _session.get_peer_record(peer_id) - var owner_is_local: bool = ( - record != null and record.identity_fingerprint == owner_fingerprint - ) - var privacy: int = int((manifest["home"] as Dictionary)["privacy"]) - if ( - (not owner_is_local and privacy != PlayerHomeStateType.Privacy.OPEN) - or _session.get_peer_space(peer_id) - != NetworkHomeProtocolType.WORLD_SPACE - or not _world_service.is_avatar_near_exterior( - avatar, owner_fingerprint - ) - ): - _send_transition_rejection(peer_id, "You cannot enter that home.") - return - var interior_space: StringName = ( - NetworkHomeProtocolType.make_space_id(owner_fingerprint) - ) - var visitor_slot: int = _reserve_interior_slot( - interior_space, peer_id - ) - _apply_host_transition( - peer_id, - interior_space, - owner_fingerprint, - _world_service.get_interior_spawn_transform( - owner_fingerprint, visitor_slot - ), - ) - return - if ( - _session.get_peer_space(peer_id) - != NetworkHomeProtocolType.make_space_id(owner_fingerprint) - or not _world_service.is_avatar_near_interior_exit( - avatar, owner_fingerprint - ) - ): - _send_transition_rejection(peer_id, "Move closer to the home door.") - return - _apply_host_transition( - peer_id, - NetworkHomeProtocolType.WORLD_SPACE, - owner_fingerprint, - _world_service.get_exterior_spawn_transform(owner_fingerprint), - ) - - -func _spawn_peer_at_exterior( - peer_id: int, - owner_fingerprint: String, -) -> void: - if ( - not _session.is_host() - or _session.get_peer_space(peer_id) - != NetworkHomeProtocolType.WORLD_SPACE - or _spawn_service.get_avatar(peer_id) == null - ): - return - _apply_host_transition( - peer_id, - NetworkHomeProtocolType.WORLD_SPACE, - owner_fingerprint, - _world_service.get_exterior_spawn_transform(owner_fingerprint), - ) - - -func _apply_host_transition( - peer_id: int, - space_id: StringName, - owner_fingerprint: String, - transform: Transform3D, -) -> void: - var avatar: Player = _spawn_service.get_avatar(peer_id) - var movement_sequence: int = 0 - if avatar != null: - movement_sequence = int( - avatar.make_network_snapshot(peer_id).get( - "acknowledged_input", 0 - ) - ) - var transform_data: Array[float] = ( - NetworkHomeProtocolType.transform_to_array(transform) - ) - if not NetworkHomeProtocolType.validate_transition_transform( - transform_data - ): - push_error("Refused an out-of-bounds RV transition transform.") - return - var previous_space: StringName = _session.get_peer_space(peer_id) - if ( - String(previous_space).begins_with("rv:") - and previous_space != space_id - ): - _release_interior_slot(previous_space, peer_id) - var result: Dictionary = { - "peer_id": peer_id, - "space_id": String(space_id), - "owner_fingerprint": owner_fingerprint, - "transform": transform_data, - "movement_sequence": movement_sequence, - } - _apply_transition_result(result) - for recipient_id: int in _session.get_authenticated_peer_ids(): - if recipient_id == 1: - continue - receive_home_transition.rpc_id(recipient_id, result) - - -@rpc( - "authority", "call_remote", "reliable", - NetworkHomeProtocolType.RELIABLE_CHANNEL, -) -func receive_home_transition(data: Dictionary) -> void: - if ( - typeof(data.get("peer_id")) != TYPE_INT - or typeof(data.get("space_id")) not in [TYPE_STRING, TYPE_STRING_NAME] - or typeof(data.get("movement_sequence", 0)) != TYPE_INT - or int(data.get("movement_sequence", 0)) < 0 - or not NetworkIdentityCrypto.valid_fingerprint( - data.get("owner_fingerprint") - ) - or not NetworkHomeProtocolType.validate_transition_transform( - data.get("transform") - ) - ): - return - _apply_transition_result(data) - - -func _apply_transition_result(data: Dictionary) -> void: - var peer_id: int = int(data["peer_id"]) - var space_id := StringName(str(data["space_id"])) - if not NetworkHomeProtocolType.validate_transition_transform( - data.get("transform") - ): - return - var target_transform: Transform3D = ( - NetworkHomeProtocolType.array_to_transform(data["transform"]) - ) - if not _session.set_peer_space(peer_id, space_id): - return - if not _session.apply_authoritative_space_transition( - peer_id, - target_transform, - int(data.get("movement_sequence", 0)), - ): - return - if peer_id == _session.get_local_peer_id(): - _world_service.set_local_space(space_id) - local_space_changed.emit(space_id, str(data["owner_fingerprint"])) - local_transition_finished.emit(true, "") - _refresh_peer_visibility() - - -func _send_transition_rejection(peer_id: int, message: String) -> void: - if peer_id == _session.get_local_peer_id(): - local_transition_finished.emit(false, message) - else: - receive_home_transition_rejection.rpc_id(peer_id, message) - - -@rpc( - "authority", "call_remote", "reliable", - NetworkHomeProtocolType.RELIABLE_CHANNEL, -) -func receive_home_transition_rejection(message: String) -> void: - local_transition_finished.emit( - false, message.left(NetworkHomeProtocolType.MAX_MESSAGE_LENGTH) - ) - - -func _refresh_peer_visibility() -> void: - var local_peer_id: int = _session.get_local_peer_id() - if local_peer_id <= 0: - return - for peer_id: int in _spawn_service.get_peer_ids(): - if peer_id == local_peer_id: - continue - _spawn_service.set_peer_presentation_visible( - peer_id, _session.peers_share_space(local_peer_id, peer_id) - ) - - -func _on_local_home_changed() -> void: - if _suppress_local_submission or _publish_queued: - return - _publish_queued = true - call_deferred("_publish_queued_home") - - -func _publish_queued_home() -> void: - if has_pending_local_decor_mutation(): - return - _publish_queued = false - if _save_manager != null: - _save_manager.save_if_dirty() - publish_local_home() - - -func _flush_queued_local_home_publish() -> void: - if not _publish_queued or has_pending_local_decor_mutation(): - return - call_deferred("_publish_queued_home") - - -func _on_join_authenticated() -> void: - publish_local_home() - - -func _on_peer_authenticated(peer_id: int, _display_name: String) -> void: - if not _session.is_host(): - return - for manifest: Dictionary in _manifests_by_fingerprint.values(): - receive_home_manifest.rpc_id(peer_id, manifest) - for existing_peer_id: int in _session.get_authenticated_peer_ids(): - if existing_peer_id == peer_id: - continue - var avatar: Player = _spawn_service.get_avatar(existing_peer_id) - if avatar == null: - continue - var space_id: StringName = _session.get_peer_space(existing_peer_id) - var owner_fingerprint: String = ( - String(space_id).trim_prefix("rv:") - if String(space_id).begins_with("rv:") - else _fingerprint_for_peer(existing_peer_id) - ) - if owner_fingerprint.is_empty(): - continue - receive_home_transition.rpc_id(peer_id, { - "peer_id": existing_peer_id, - "space_id": String(space_id), - "owner_fingerprint": owner_fingerprint, - "transform": NetworkHomeProtocolType.transform_to_array( - avatar.global_transform - ), - }) - - -func _on_peer_removed(peer_id: int) -> void: - _release_peer_interior_slots(peer_id) - var fingerprint: String = _fingerprint_by_peer.get(peer_id, "") - _fingerprint_by_peer.erase(peer_id) - _authorized_owned_counts.erase(peer_id) - _authorized_home_tiers.erase(peer_id) - for result_id: String in _home_upgrade_result_owner.keys(): - if int(_home_upgrade_result_owner[result_id]) == peer_id: - _home_upgrade_result_owner.erase(result_id) - if fingerprint.is_empty(): - return - if _session.is_host(): - for occupant_id: int in _session.get_authenticated_peer_ids(): - if ( - occupant_id == peer_id - or _session.get_peer_space(occupant_id) - != NetworkHomeProtocolType.make_space_id(fingerprint) - ): - continue - _apply_host_transition( - occupant_id, - NetworkHomeProtocolType.WORLD_SPACE, - fingerprint, - _world_service.get_exterior_spawn_transform(fingerprint), - ) - _manifests_by_fingerprint.erase(fingerprint) - _world_service.remove_presentation(fingerprint) - if _session.is_host(): - for recipient_id: int in _session.get_authenticated_peer_ids(): - if recipient_id != 1: - receive_home_removed.rpc_id(recipient_id, fingerprint) - - -@rpc( - "authority", "call_remote", "reliable", - NetworkHomeProtocolType.RELIABLE_CHANNEL, -) -func receive_home_removed(owner_fingerprint: String) -> void: - if not NetworkIdentityCrypto.valid_fingerprint(owner_fingerprint): - return - _manifests_by_fingerprint.erase(owner_fingerprint) - _world_service.remove_presentation(owner_fingerprint) - - -func _on_session_state_changed(state: NetworkSession.State) -> void: - if state in [NetworkSession.State.PRIVATE_HOST, NetworkSession.State.OPEN_HOST]: - if ( - not _session.is_dedicated_host() - and _world_service != null - and _world_service.is_world_ready() - ): - publish_local_home() - return - if state in [ - NetworkSession.State.INACTIVE, - NetworkSession.State.DISCONNECTING, - NetworkSession.State.CONNECTION_FAILED, - NetworkSession.State.SERVER_LOST, - ]: - _manifests_by_fingerprint.clear() - _fingerprint_by_peer.clear() - _authorized_owned_counts.clear() - _authorization_result_owner.clear() - _authorized_home_tiers.clear() - _home_upgrade_result_owner.clear() - _publish_queued = false - _last_submitted_local_home_revision = -1 - _pending_local_decor_transactions.clear() - _interior_slots_by_space.clear() - _next_interior_index = 0 - _world_service.clear_presentations() - _world_service.set_local_space(NetworkHomeProtocolType.WORLD_SPACE) - local_space_changed.emit(NetworkHomeProtocolType.WORLD_SPACE, "") - - -func _fingerprint_for_peer(peer_id: int) -> String: - if _fingerprint_by_peer.has(peer_id): - return _fingerprint_by_peer[peer_id] - var record := _session.get_peer_record(peer_id) - return record.identity_fingerprint if record != null else "" - - -func _reserve_interior_slot(space_id: StringName, peer_id: int) -> int: - var slots: Dictionary = _interior_slots_by_space.get(space_id, {}) - if slots.has(peer_id): - return int(slots[peer_id]) - var used_slots: Dictionary[int, bool] = {} - for value: Variant in slots.values(): - used_slots[int(value)] = true - var slot: int = 0 - while used_slots.has(slot): - slot += 1 - slots[peer_id] = slot - _interior_slots_by_space[space_id] = slots - return slot - - -func _release_interior_slot(space_id: StringName, peer_id: int) -> void: - var slots: Dictionary = _interior_slots_by_space.get(space_id, {}) - if slots.is_empty(): - return - slots.erase(peer_id) - if slots.is_empty(): - _interior_slots_by_space.erase(space_id) - else: - _interior_slots_by_space[space_id] = slots - - -func _release_peer_interior_slots(peer_id: int) -> void: - for space_id: StringName in _interior_slots_by_space.keys(): - _release_interior_slot(space_id, peer_id) - - -func _new_id(prefix: String) -> String: - return "%s:%s" % [ - prefix, Crypto.new().generate_random_bytes(16).hex_encode(), - ] diff --git a/network/network_home_service.gd.uid b/network/network_home_service.gd.uid deleted file mode 100644 index be233ed..0000000 --- a/network/network_home_service.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://ong8kug36ku7 diff --git a/network/network_item_use_service.gd b/network/network_item_use_service.gd index 024eca9..cf2d5f9 100644 --- a/network/network_item_use_service.gd +++ b/network/network_item_use_service.gd @@ -5,7 +5,6 @@ const FishingRodDataType = preload("res://items/fishing_rod_data.gd") const ArtShopStockType = preload("res://economy/art_shop_stock.gd") const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd") -const DecorCatalogType = preload("res://homes/decor_catalog.gd") const MAX_LEDGER_ENTRIES: int = 64 @@ -395,10 +394,6 @@ func _apply_equipped(data: Dictionary) -> void: item_id == FishingShopStockType.STANDARD_SHOVEL_ID and bool(data["owns_item"]), ) - avatar.set_active_decor_package( - DecorCatalogType.has_product(item_id) - and bool(data["owns_item"]), - ) equipped_state_changed.emit( peer_id, StringName(str(data["item_id"])), int(data["category"]) ) diff --git a/network/network_protocol.gd b/network/network_protocol.gd index 5b3ca89..20be022 100644 --- a/network/network_protocol.gd +++ b/network/network_protocol.gd @@ -3,7 +3,7 @@ extends RefCounted const WorldLayoutType = preload("res://world/world_layout.gd") -const PROTOCOL_VERSION: int = 12 +const PROTOCOL_VERSION: int = 11 const GAME_BUILD: String = "prealpha" const MAX_GAME_VERSION_LENGTH: int = 64 const MAX_DISPLAY_NAME_LENGTH: int = 24 @@ -17,9 +17,9 @@ const MAX_SIGNATURE_LENGTH: int = 2048 # 8 reliable ordered session chat, 9 reliable private session mail, # 10 observer-only fishing summaries, 11 reliable movement animation state, # 12 reliable fish showcase state, 13 reliable world-spawn events, -# 14 reliable artwork state, 15 world-spawn snapshots, 16 reliable fishing -# lifecycle, and 17 reliable portable-home state. Bulk world/art payloads must -# not head-of-line block held items, fishing, animation, or home transitions. +# 14 reliable artwork state, 15 world-spawn snapshots, and 16 reliable +# fishing lifecycle. Bulk world/art payloads must not head-of-line block held +# items, fishing, or animation presentation. const SALE_RELIABLE_CHANNEL: int = 5 const SHOP_RELIABLE_CHANNEL: int = 6 const ITEM_RELIABLE_CHANNEL: int = 7 @@ -30,8 +30,7 @@ const SHOWCASE_RELIABLE_CHANNEL: int = 12 const WORLD_SPAWN_RELIABLE_CHANNEL: int = 13 const DRAWING_RELIABLE_CHANNEL: int = 14 const FISHING_RELIABLE_CHANNEL: int = 16 -const HOME_RELIABLE_CHANNEL: int = 17 -const ENET_CHANNEL_COUNT: int = 18 +const ENET_CHANNEL_COUNT: int = 17 const SURFACE_DRAWING_CAPABILITY: String = "surface_drawing_v2" const ART_SHOP_CAPABILITY: String = "art_shop_v1" const BACKPACK_SHOP_CAPABILITY: String = "backpack_shop_v1" @@ -46,8 +45,6 @@ const WORLD_LAYOUT_CAPABILITY: String = "world_layout_v1" const FRIENDS_CAPABILITY: String = "friends_v1" const MOVEMENT_RECONCILIATION_CAPABILITY: String = "movement_reconciliation_v2" const FISHING_REPLICATION_CAPABILITY: String = "fishing_replication_v2" -const HOME_CAPABILITY: String = "portable_home_v1" -const HOME_UPGRADE_CAPABILITY: String = "home_upgrade_v1" const DEFAULT_WORLD_SEED: int = 13001 const MAX_WORLD_SEED: int = 2147483646 @@ -205,8 +202,6 @@ static func make_client_hello( FRIENDS_CAPABILITY, MOVEMENT_RECONCILIATION_CAPABILITY, FISHING_REPLICATION_CAPABILITY, - HOME_CAPABILITY, - HOME_UPGRADE_CAPABILITY, ]), "cosmetic_snapshot": cosmetic_snapshot, "identity_fingerprint": identity_fingerprint, @@ -347,8 +342,6 @@ static func make_server_hello( FRIENDS_CAPABILITY, MOVEMENT_RECONCILIATION_CAPABILITY, FISHING_REPLICATION_CAPABILITY, - HOME_CAPABILITY, - HOME_UPGRADE_CAPABILITY, "chat_v1", "mail_v1", "profile_v1", diff --git a/network/network_session.gd b/network/network_session.gd index 7c259e0..8b99fd6 100644 --- a/network/network_session.gd +++ b/network/network_session.gd @@ -28,7 +28,6 @@ const MAX_PENDING_MOVEMENT_INPUTS: int = 96 const ANIMATION_REFRESH_INTERVAL: float = 1.0 const MAX_MOVEMENT_INPUT_SEQUENCE: int = 2147483647 const MAX_MOVEMENT_ONE_WAY_TRANSIT_SECONDS: float = 0.25 -const SPACE_TRANSITION_SNAPSHOT_GUARD_DISTANCE: float = 12.0 const MOVEMENT_FLAG_JUMP: int = 1 << 0 const MOVEMENT_FLAG_SPRINT: int = 1 << 1 @@ -125,9 +124,6 @@ var _pending_movement_inputs: Array[Dictionary] = [] var _snapshot_accumulator: float = 0.0 var _movement_snapshot_tick: int = 0 var _last_local_snapshot_received_msec: int = 0 -var _local_space_transition_guard_active: bool = false -var _local_space_transition_guard_position: Vector3 = Vector3.ZERO -var _local_space_transition_minimum_ack: int = 0 var _animation_refresh_accumulator: float = 0.0 var _last_animation_state_by_peer: Dictionary[int, Dictionary] = {} var _pending_animation_state_by_peer: Dictionary[int, Dictionary] = {} @@ -145,7 +141,6 @@ var _last_server_protocol_version: int = 0 var _last_server_world_seed: int = NetworkProtocol.DEFAULT_WORLD_SEED var _last_server_world_layout: StringName = WorldLayout.GENERATED var _server_capabilities: PackedStringArray = PackedStringArray() -var _peer_space_ids: Dictionary[int, StringName] = {} var _profile_ready: bool = false var _player_identity: PlayerIdentityStore var _host_identity: HostIdentityStore @@ -339,8 +334,6 @@ func _register_player_host() -> void: NetworkProtocol.FRIENDS_CAPABILITY, NetworkProtocol.MOVEMENT_RECONCILIATION_CAPABILITY, NetworkProtocol.FISHING_REPLICATION_CAPABILITY, - NetworkProtocol.HOME_CAPABILITY, - NetworkProtocol.HOME_UPGRADE_CAPABILITY, ]), ) _registry.update_appearance(1, _local_appearance_snapshot) @@ -668,8 +661,6 @@ func supports_server_capability(capability: StringName) -> bool: NetworkProtocol.FRIENDS_CAPABILITY, NetworkProtocol.MOVEMENT_RECONCILIATION_CAPABILITY, NetworkProtocol.FISHING_REPLICATION_CAPABILITY, - NetworkProtocol.HOME_CAPABILITY, - NetworkProtocol.HOME_UPGRADE_CAPABILITY, "chat_v1", "mail_v1", "profile_v1", @@ -694,55 +685,6 @@ func get_authenticated_peer_ids() -> Array[int]: return _registry.get_peer_ids() -func set_peer_space(peer_id: int, space_id: StringName) -> bool: - if not _registry.has_peer(peer_id) or space_id.is_empty(): - return false - _peer_space_ids[peer_id] = space_id - return true - - -func get_peer_space(peer_id: int) -> StringName: - return _peer_space_ids.get(peer_id, &"world") - - -func peers_share_space(first_peer_id: int, second_peer_id: int) -> bool: - return get_peer_space(first_peer_id) == get_peer_space(second_peer_id) - - -func apply_authoritative_space_transition( - peer_id: int, - target_transform: Transform3D, - acknowledged_input: int, -) -> bool: - if not target_transform.origin.is_finite(): - return false - var local_peer_id: int = get_local_peer_id() - var local_transition: bool = peer_id == local_peer_id - var avatar: Player = _spawn_service.get_avatar(peer_id) - # Remote home transitions can arrive before their player-spawn message. Keep - # the new space assignment so the later spawn inherits correct visibility; - # only a missing local avatar is a transition failure. - if avatar == null: - return not local_transition - avatar.apply_network_space_transition(target_transform, local_transition) - if ( - local_transition - and state == State.JOINED_CLIENT - ): - # Movement snapshots use another ENet channel and a pre-transition audit - # can therefore arrive after the reliable home transition. Clear local - # prediction and reject those old-world snapshots until the host publishes - # its first audit from the new space. - _pending_movement_inputs.clear() - _last_local_snapshot_received_msec = 0 - _last_input_state_hash = 0 - _idle_input_accumulator = IDLE_INPUT_INTERVAL - _local_space_transition_guard_active = true - _local_space_transition_guard_position = target_transform.origin - _local_space_transition_minimum_ack = maxi(acknowledged_input, 0) - return true - - func get_peer_rtt_ms(peer_id: int) -> int: if peer_id == 1: return 0 @@ -1088,7 +1030,6 @@ func _on_peer_disconnected(peer_id: int) -> void: _pending_identity_challenges.erase(peer_id) _authenticated_identity_cache.erase(peer_id) _operator_peer_ids.erase(peer_id) - _peer_space_ids.erase(peer_id) _last_animation_state_by_peer.erase(peer_id) _pending_animation_state_by_peer.erase(peer_id) var recovery_attempt: String = _recovery_attempts.get(peer_id, "") @@ -1372,7 +1313,6 @@ func submit_client_hello(data: Dictionary) -> void: for required_capability: String in [ NetworkProtocol.MOVEMENT_RECONCILIATION_CAPABILITY, NetworkProtocol.FISHING_REPLICATION_CAPABILITY, - NetworkProtocol.HOME_CAPABILITY, ]: if required_capability not in client_capabilities: _reject_peer( @@ -1606,7 +1546,6 @@ func receive_server_hello(data: Dictionary) -> void: for required_capability: String in [ NetworkProtocol.MOVEMENT_RECONCILIATION_CAPABILITY, NetworkProtocol.FISHING_REPLICATION_CAPABILITY, - NetworkProtocol.HOME_CAPABILITY, ]: if required_capability not in _server_capabilities: _teardown_peer() @@ -1639,8 +1578,6 @@ func receive_server_hello(data: Dictionary) -> void: NetworkProtocol.FRIENDS_CAPABILITY, NetworkProtocol.MOVEMENT_RECONCILIATION_CAPABILITY, NetworkProtocol.FISHING_REPLICATION_CAPABILITY, - NetworkProtocol.HOME_CAPABILITY, - NetworkProtocol.HOME_UPGRADE_CAPABILITY, ]), ) _registry.update_appearance(local_peer_id, _local_appearance_snapshot) @@ -1686,7 +1623,6 @@ func receive_peer_despawn(peer_id: int) -> void: if state != State.JOINED_CLIENT: return _operator_peer_ids.erase(peer_id) - _peer_space_ids.erase(peer_id) _last_animation_state_by_peer.erase(peer_id) _pending_animation_state_by_peer.erase(peer_id) _registry.remove_peer(peer_id) @@ -2311,8 +2247,6 @@ func _broadcast_movement_snapshots() -> void: continue var snapshots: Array = [] for subject_id: int in peer_ids: - if not peers_share_space(recipient_id, subject_id): - continue var subject_avatar: Player = _spawn_service.get_avatar(subject_id) if subject_avatar == null: continue @@ -2381,7 +2315,7 @@ func _broadcast_movement_animation_updates(peer_ids: Array[int]) -> void: _animation_refresh_accumulator, ANIMATION_REFRESH_INTERVAL, ) - var updates_by_subject: Dictionary[int, Array] = {} + var updates: Array = [] for subject_id: int in peer_ids: var avatar: Player = _spawn_service.get_avatar(subject_id) if avatar == null: @@ -2399,18 +2333,12 @@ func _broadcast_movement_animation_updates(peer_ids: Array[int]) -> void: _last_animation_state_by_peer[subject_id] = state.duplicate(true) var encoded: Array = _encode_movement_animation(subject_id, state) if not encoded.is_empty(): - updates_by_subject[subject_id] = encoded - if updates_by_subject.is_empty(): + updates.append(encoded) + if updates.is_empty(): return for recipient_id: int in peer_ids: if recipient_id == 1: continue - var updates: Array = [] - for subject_id: int in updates_by_subject: - if peers_share_space(recipient_id, subject_id): - updates.append(updates_by_subject[subject_id]) - if updates.is_empty(): - continue receive_movement_animations.rpc_id(recipient_id, updates) _movement_animation_packets_sent += 1 _movement_animation_states_sent += updates.size() @@ -2610,8 +2538,6 @@ func receive_movement_snapshots(encoded_snapshots: Array) -> void: if avatar == null: continue if peer_id == local_peer_id: - if _reject_stale_space_transition_snapshot(snapshot): - continue _discard_acknowledged_movement_inputs( int(snapshot.get("acknowledged_input", 0)) ) @@ -2629,33 +2555,6 @@ func receive_movement_snapshots(encoded_snapshots: Array) -> void: ) -func _reject_stale_space_transition_snapshot(snapshot: Dictionary) -> bool: - if not _local_space_transition_guard_active: - return false - var position_values: Array = snapshot.get("position", []) - if position_values.size() != 3: - return true - var position := Vector3( - float(position_values[0]), - float(position_values[1]), - float(position_values[2]), - ) - var acknowledged_input: int = int( - snapshot.get("acknowledged_input", 0) - ) - if acknowledged_input < _local_space_transition_minimum_ack: - return true - if acknowledged_input > _local_space_transition_minimum_ack: - _local_space_transition_guard_active = false - return false - if position.distance_to(_local_space_transition_guard_position) > ( - SPACE_TRANSITION_SNAPSHOT_GUARD_DISTANCE - ): - return true - _local_space_transition_guard_active = false - return false - - func _discard_acknowledged_movement_inputs(acknowledged_sequence: int) -> void: while ( not _pending_movement_inputs.is_empty() @@ -2984,15 +2883,11 @@ func _teardown_peer() -> void: _snapshot_accumulator = 0.0 _movement_snapshot_tick = 0 _last_local_snapshot_received_msec = 0 - _local_space_transition_guard_active = false - _local_space_transition_guard_position = Vector3.ZERO - _local_space_transition_minimum_ack = 0 _animation_refresh_accumulator = 0.0 _last_animation_state_by_peer.clear() _pending_animation_state_by_peer.clear() _last_local_animation_action_signature.clear() _server_capabilities = PackedStringArray() - _peer_space_ids.clear() _server_identity_fingerprint = "" _server_identity_public_key = "" _session_identity_keys.clear() diff --git a/network/network_shop_protocol.gd b/network/network_shop_protocol.gd index e58556e..2de9cc1 100644 --- a/network/network_shop_protocol.gd +++ b/network/network_shop_protocol.gd @@ -4,8 +4,6 @@ extends RefCounted const CAPABILITY: StringName = &"shop_v1" const ART_CAPABILITY: StringName = &"art_shop_v1" const BACKPACK_CAPABILITY: StringName = &"backpack_shop_v1" -const DECOR_CAPABILITY: StringName = &"portable_home_v1" -const HOME_UPGRADE_CAPABILITY: StringName = &"home_upgrade_v1" const RELIABLE_CHANNEL: int = NetworkProtocol.SHOP_RELIABLE_CHANNEL const MAX_ID_LENGTH: int = 96 const MAX_MESSAGE_LENGTH: int = 160 @@ -16,11 +14,10 @@ enum ProductCategory { ROD, REEL_SPEED_UPGRADE, BARRIER_POWER_UPGRADE, - HOME_UPGRADE, + COOLER_CAPACITY_UPGRADE, ART_KIT, ART_UPGRADE, BACKPACK_CAPACITY_UPGRADE, - DECOR, } diff --git a/network/network_shop_service.gd b/network/network_shop_service.gd index e4d6f9c..6f31c40 100644 --- a/network/network_shop_service.gd +++ b/network/network_shop_service.gd @@ -8,14 +8,11 @@ const ItemDataType = preload("res://items/item_data.gd") const FishingRodDataType = preload("res://items/fishing_rod_data.gd") const OwnedItemType = preload("res://items/owned_item.gd") const ArtShopStockType = preload("res://economy/art_shop_stock.gd") -const DecorCatalogType = preload("res://homes/decor_catalog.gd") -const FISHING_SHOP_ID: StringName = &"main_fishing_shop" -const DECOR_SHOP_ID: StringName = &"rv_decor_shop" -const RV_UPGRADE_SHOP_ID: StringName = &"rv_upgrade_shop" +const SHOP_ID: StringName = &"main_fishing_shop" const REEL_PRODUCT_ID: StringName = &"reel_speed_upgrade" const BARRIER_PRODUCT_ID: StringName = &"barrier_power_upgrade" -const HOME_UPGRADE_PRODUCT_ID: StringName = &"starter_rv_upgrade" +const COOLER_PRODUCT_ID: StringName = &"cooler_capacity_upgrade" const BACKPACK_PRODUCT_ID: StringName = &"backpack_capacity_upgrade" const MAX_LEDGER_ENTRIES_PER_PEER: int = 64 @@ -34,8 +31,6 @@ var _session: NetworkSession var _spawn_service: PlayerSpawnService var _network_fishing: NetworkFishingService var _interaction: FishingShopInteraction -var _decor_interaction: DecorShopInteraction -var _rv_upgrade_interaction: FishingShopInteraction var _wallet: PlayerWallet var _bag: PlayerBag var _item_catalog: ItemCatalog @@ -52,8 +47,6 @@ var _received_results: Dictionary[String, bool] = {} var _applied_results: Dictionary[String, bool] = {} var _pending_local_request: Dictionary = {} var _reservations: PlayerAssetReservationService -var _home_state: PlayerHomeState -var _network_home: NetworkHomeService func setup( @@ -70,10 +63,6 @@ func setup( save_manager: PlayerSaveManager, reservations: PlayerAssetReservationService, inventory_layout: PlayerInventoryLayout = null, - home_state: PlayerHomeState = null, - network_home: NetworkHomeService = null, - decor_interaction: DecorShopInteraction = null, - rv_upgrade_interaction: FishingShopInteraction = null, ) -> void: _session = session _spawn_service = spawn_service @@ -88,10 +77,6 @@ func setup( _save_manager = save_manager _reservations = reservations _inventory_layout = inventory_layout - _home_state = home_state - _network_home = network_home - _decor_interaction = decor_interaction - _rv_upgrade_interaction = rv_upgrade_interaction if not _session.peer_removed.is_connected(_on_peer_removed): _session.peer_removed.connect(_on_peer_removed) if not _session.state_changed.is_connected(_on_session_state_changed): @@ -102,18 +87,6 @@ func set_shop_interaction(interaction: FishingShopInteraction) -> void: _interaction = interaction -func set_decor_shop_interaction( - interaction: DecorShopInteraction, -) -> void: - _decor_interaction = interaction - - -func set_rv_upgrade_shop_interaction( - interaction: FishingShopInteraction, -) -> void: - _rv_upgrade_interaction = interaction - - func can_request_purchase() -> bool: return ( _session != null @@ -151,32 +124,6 @@ func can_request_backpack_purchase() -> bool: ) -func can_request_decor_purchase() -> bool: - return ( - can_request_purchase() - and _home_state != null - and ( - _session.is_host() - or _session.supports_server_capability( - NetworkShopProtocol.DECOR_CAPABILITY - ) - ) - ) - - -func can_request_home_upgrade() -> bool: - return ( - can_request_purchase() - and _home_state != null - and ( - _session.is_host() - or _session.supports_server_capability( - NetworkShopProtocol.HOME_UPGRADE_CAPABILITY - ) - ) - ) - - func is_local_purchase_pending() -> bool: return not _pending_local_request.is_empty() @@ -227,12 +174,12 @@ func request_barrier_power_upgrade() -> String: ) -func request_home_upgrade() -> String: +func request_cooler_capacity_upgrade() -> String: return _request_purchase( - HOME_UPGRADE_PRODUCT_ID, - NetworkShopProtocol.ProductCategory.HOME_UPGRADE, + COOLER_PRODUCT_ID, + NetworkShopProtocol.ProductCategory.COOLER_CAPACITY_UPGRADE, 1, - _home_state.get_tier() if _home_state != null else 0 + _cooler_capacity.get_level() if _cooler_capacity != null else 0 ) @@ -246,15 +193,6 @@ func request_backpack_capacity_upgrade() -> String: ) -func request_decor(product_id: StringName) -> String: - return _request_purchase( - product_id, - NetworkShopProtocol.ProductCategory.DECOR, - 1, - _home_state.get_owned_count(product_id) if _home_state != null else 0, - ) - - func request_art_kit() -> String: return _request_purchase( ArtShopStockType.ART_KIT_ITEM_ID, @@ -331,30 +269,11 @@ func _request_purchase( product_id, category, 0, 0, ) return "" - if ( - category == NetworkShopProtocol.ProductCategory.HOME_UPGRADE - and not can_request_home_upgrade() - ): - local_purchase_finished.emit( - "", false, "RV upgrades require a newer server.", - product_id, category, 0, 0, - ) - return "" - if ( - category == NetworkShopProtocol.ProductCategory.DECOR - and not can_request_decor_purchase() - ): - local_purchase_finished.emit( - "", false, "Decor require a newer server.", - product_id, category, 0, 0, - ) - return "" var request_id: String = _new_id("shop") - var shop_id: StringName = _shop_id_for_category(category) var request: Dictionary = { "request_id": request_id, "session_id": _session.get_session_id(), - "shop_id": str(shop_id), + "shop_id": str(SHOP_ID), "product_id": str(product_id), "category": category, "quantity": quantity, @@ -426,38 +345,16 @@ func _handle_purchase_request(peer_id: int, data: Dictionary) -> void: _rejected_result(data, "A purchase is already pending.") ) return - var requested_shop_id := StringName(str(data["shop_id"])) - if not _is_shop_available_for_peer(peer_id, requested_shop_id): + if not _is_shop_available_for_peer(peer_id): var avatar: Player = _spawn_service.get_avatar(peer_id) - var requested_interaction: FishingShopInteraction = ( - _interaction_for_shop(requested_shop_id) - ) var message: String = ( "The shop is unavailable." - if avatar == null or requested_interaction == null + if avatar == null or _interaction == null else "Move closer to the shop." ) _record_and_send(peer_id, _rejected_result(data, message)) return var result: Dictionary = _build_authoritative_result(peer_id, data) - if ( - bool(result.get("accepted", false)) - and _network_home != null - ): - match int(result.get("category", -1)): - NetworkShopProtocol.ProductCategory.DECOR: - _network_home.authorize_decor_purchase( - peer_id, - StringName(str(result["product_id"])), - int(result["resulting_state"]), - str(result["result_id"]), - ) - NetworkShopProtocol.ProductCategory.HOME_UPGRADE: - _network_home.authorize_home_upgrade( - peer_id, - int(result["resulting_state"]), - str(result["result_id"]), - ) _pending_by_peer[peer_id] = request_id _record_and_send(peer_id, result) @@ -471,29 +368,10 @@ func _build_authoritative_result( var quantity: int = request["quantity"] var wallet_balance: int = request["wallet_balance"] var current_state: int = request["current_state"] - if ( - category == NetworkShopProtocol.ProductCategory.DECOR - and _network_home != null - ): - var authoritative_count: int = ( - _network_home.get_authoritative_owned_count(peer_id, product_id) - ) - if authoritative_count >= 0: - current_state = authoritative_count - elif ( - category == NetworkShopProtocol.ProductCategory.HOME_UPGRADE - and _network_home != null - ): - var authoritative_tier: int = ( - _network_home.get_authoritative_home_tier(peer_id) - ) - if authoritative_tier >= 0: - current_state = authoritative_tier var cost: int = -1 var resulting_state: int = current_state var rejection: String = "" - var expected_shop_id: StringName = _shop_id_for_category(category) - if StringName(str(request["shop_id"])) != expected_shop_id: + if StringName(str(request["shop_id"])) != SHOP_ID: rejection = "The shop is unavailable." elif quantity < 1: rejection = "Purchase could not be completed." @@ -606,14 +484,18 @@ func _build_authoritative_result( ] ) resulting_state = current_state + 1 - NetworkShopProtocol.ProductCategory.HOME_UPGRADE: + NetworkShopProtocol.ProductCategory.COOLER_CAPACITY_UPGRADE: if ( - product_id != HOME_UPGRADE_PRODUCT_ID - or current_state >= PlayerHomeState.MAX_TIER + product_id != COOLER_PRODUCT_ID + or current_state >= PlayerCoolerCapacity.MAX_LEVEL ): rejection = "Upgrade is already at maximum." else: - cost = PlayerHomeState.HOME_UPGRADE_COSTS[current_state] + cost = ( + PlayerCoolerCapacity.EXPANSION_COSTS[ + current_state + ] + ) resulting_state = current_state + 1 NetworkShopProtocol.ProductCategory.BACKPACK_CAPACITY_UPGRADE: if ( @@ -663,19 +545,6 @@ func _build_authoritative_result( resulting_state = PlayerArtUnlocks.resulting_mask( current_state, product_id ) - NetworkShopProtocol.ProductCategory.DECOR: - cost = DecorCatalogType.get_price(product_id) - if ( - not DecorCatalogType.has_product(product_id) - or quantity != 1 - or current_state < 0 - or current_state - >= DecorCatalogType.MAX_OWNED_PER_PRODUCT - or cost < 0 - ): - rejection = "Decor is unavailable." - else: - resulting_state = current_state + 1 if rejection.is_empty() and (cost < 0 or wallet_balance < cost): rejection = "Insufficient funds." if not rejection.is_empty(): @@ -798,9 +667,6 @@ func _apply_purchase_result(data: Dictionary) -> void: if _inventory_layout != null else 0 ) var art_snapshot: int = _art_unlocks.get_unlock_mask() - var home_snapshot: Dictionary = ( - _home_state.to_save_data() if _home_state != null else {} - ) var applied: bool = _apply_local_product(data) if not applied or not _save_manager.save_if_dirty(): _bag.replace_all_items(bag_snapshot) @@ -810,8 +676,6 @@ func _apply_purchase_result(data: Dictionary) -> void: if _inventory_layout != null: _inventory_layout.restore_backpack_level(backpack_snapshot) _art_unlocks.restore_mask(art_snapshot) - if _home_state != null and not home_snapshot.is_empty(): - _home_state.restore_from_save_data(home_snapshot) _wallet.restore_balance(wallet_snapshot) _save_manager.save_if_dirty() _fail_local_apply(data, "Purchase could not be completed.") @@ -832,7 +696,6 @@ func _validate_local_result(data: Dictionary) -> String: or _cooler_capacity == null or _art_unlocks == null or _save_manager == null - or _home_state == null or _wallet.get_balance() != int(data["expected_wallet"]) or str(data["product_id"]) != str(_pending_local_request.get("product_id", "")) @@ -907,12 +770,10 @@ func _validate_local_result(data: Dictionary) -> String: return "Purchase could not be completed." if _upgrades.get_next_barrier_power_cost() != cost: return "Purchase could not be completed." - NetworkShopProtocol.ProductCategory.HOME_UPGRADE: - if ( - product_id != HOME_UPGRADE_PRODUCT_ID - or _home_state.get_tier() != expected_state - or _home_state.get_next_upgrade_cost() != cost - ): + NetworkShopProtocol.ProductCategory.COOLER_CAPACITY_UPGRADE: + if _cooler_capacity.get_level() != expected_state: + return "Purchase could not be completed." + if _cooler_capacity.get_next_cost() != cost: return "Purchase could not be completed." NetworkShopProtocol.ProductCategory.BACKPACK_CAPACITY_UPGRADE: if ( @@ -945,20 +806,6 @@ func _validate_local_result(data: Dictionary) -> String: or cost != ArtShopStockType.UPGRADE_PRICE ): return "Purchase could not be completed." - NetworkShopProtocol.ProductCategory.DECOR: - if ( - not DecorCatalogType.has_product(product_id) - or _home_state.get_owned_count(product_id) != expected_state - or resulting_state != expected_state + 1 - or cost != DecorCatalogType.get_price(product_id) - or expected_state >= DecorCatalogType.MAX_OWNED_PER_PRODUCT - or not _bag.can_add_item(product_id, 1) - ): - return ( - "Your inventory is full." - if not _bag.can_add_item(product_id, 1) - else "Purchase could not be completed." - ) _: return "Purchase could not be completed." return "" @@ -982,11 +829,8 @@ func _apply_local_product(data: Dictionary) -> bool: return _upgrades.purchase_reel_speed(_wallet) NetworkShopProtocol.ProductCategory.BARRIER_POWER_UPGRADE: return _upgrades.purchase_barrier_power(_wallet) - NetworkShopProtocol.ProductCategory.HOME_UPGRADE: - return ( - _wallet.debit(int(data["total_cost"])) - and _home_state.upgrade_to_next_tier() - ) + NetworkShopProtocol.ProductCategory.COOLER_CAPACITY_UPGRADE: + return _cooler_capacity.purchase(_wallet) NetworkShopProtocol.ProductCategory.BACKPACK_CAPACITY_UPGRADE: return ( _inventory_layout != null @@ -1001,12 +845,6 @@ func _apply_local_product(data: Dictionary) -> bool: return _art_unlocks.purchase_product( product_id, _wallet, int(data["total_cost"]) ) - NetworkShopProtocol.ProductCategory.DECOR: - return ( - _wallet.debit(int(data["total_cost"])) - and _bag.add_item(product_id, 1) - and _home_state.add_owned_decor(product_id, 1) - ) return false @@ -1083,7 +921,7 @@ func _handle_acknowledgement( peer_id: int, result_id: String, request_id: String, - applied: bool, + _applied: bool, message: String, ) -> void: if ( @@ -1095,31 +933,20 @@ func _handle_acknowledgement( or _result_owners.get(result_id, 0) != peer_id ): return - if _network_home != null: - _network_home.finalize_decor_purchase( - peer_id, result_id, applied - ) - _network_home.finalize_home_upgrade( - peer_id, result_id, applied - ) _acknowledged_results[result_id] = true _bound_dictionary(_acknowledged_results) if _pending_by_peer.get(peer_id, "") == request_id: _pending_by_peer.erase(peer_id) -func _is_shop_available_for_peer( - peer_id: int, - shop_id: StringName, -) -> bool: - var interaction: FishingShopInteraction = _interaction_for_shop(shop_id) +func _is_shop_available_for_peer(peer_id: int) -> bool: if ( _session == null or not _session.is_host() or not _session.is_gameplay_session_active() or _spawn_service == null - or interaction == null - or not is_instance_valid(interaction) + or _interaction == null + or not is_instance_valid(_interaction) ): return false var avatar: Player = _spawn_service.get_avatar(peer_id) @@ -1130,28 +957,10 @@ func _is_shop_available_for_peer( _network_fishing == null or not _network_fishing.has_peer_attempt(peer_id) ) - and interaction.is_avatar_in_range(avatar) + and _interaction.is_avatar_in_range(avatar) ) -func _shop_id_for_category(category: int) -> StringName: - match category: - NetworkShopProtocol.ProductCategory.DECOR: - return DECOR_SHOP_ID - NetworkShopProtocol.ProductCategory.HOME_UPGRADE: - return RV_UPGRADE_SHOP_ID - return FISHING_SHOP_ID - - -func _interaction_for_shop(shop_id: StringName) -> FishingShopInteraction: - match shop_id: - DECOR_SHOP_ID: - return _decor_interaction - RV_UPGRADE_SHOP_ID: - return _rv_upgrade_interaction - return _interaction - - func _on_peer_removed(peer_id: int) -> void: _request_ledgers.erase(peer_id) _pending_by_peer.erase(peer_id) diff --git a/network/network_surface_drawing_service.gd b/network/network_surface_drawing_service.gd index 485dfaa..c322e5b 100644 --- a/network/network_surface_drawing_service.gd +++ b/network/network_surface_drawing_service.gd @@ -250,11 +250,7 @@ func activate( _pointer_screen_position = _clamped_pointer_position( initial_pointer_position ) - # Art-kit activation can occur while the prior right-click camera drag is - # still held. Stop that drag before recording a return mode, then restore - # normal camera availability so right-click continues to work while drawing. - _stop_camera_drag_for_art_kit() - _prior_mouse_mode = _get_restorable_mouse_mode(Input.mouse_mode) + _prior_mouse_mode = Input.mouse_mode Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _update_aim() _refresh_stencil_visibility() @@ -270,10 +266,6 @@ func deactivate() -> void: _eraser_mode = false _clear_armed_guide_action(false) _camera_look_active = false - # Do not leave a drag captured when drawing is closed by a menu, hotbar - # change, or session transition. The next camera drag must start on a fresh - # physical right-click press. - _stop_camera_drag_for_art_kit() _reset_stroke() _selected_canvas_id = "" _hovered_canvas_id = "" @@ -281,24 +273,10 @@ func deactivate() -> void: _aim_hit.clear() _hide_previews() _refresh_stencil_visibility() - Input.mouse_mode = _get_restorable_mouse_mode(_prior_mouse_mode) + Input.mouse_mode = _prior_mouse_mode _emit_hud_state("") -func _stop_camera_drag_for_art_kit() -> void: - if _local_player == null or not is_instance_valid(_local_player): - return - var camera_input_enabled: bool = _local_player.is_camera_input_enabled() - _local_player.set_camera_input_enabled(false) - _local_player.set_camera_input_enabled(camera_input_enabled) - - -func _get_restorable_mouse_mode(mouse_mode: Input.MouseMode) -> Input.MouseMode: - if mouse_mode == Input.MOUSE_MODE_CAPTURED: - return Input.MOUSE_MODE_VISIBLE - return mouse_mode - - func is_active() -> bool: return _active diff --git a/network/network_world_spawn_service.gd b/network/network_world_spawn_service.gd index 8981237..a45ad37 100644 --- a/network/network_world_spawn_service.gd +++ b/network/network_world_spawn_service.gd @@ -43,7 +43,6 @@ var _entity_revisions: Dictionary = {} var _surface_triangles: Dictionary = {} var _surface_areas: Dictionary = {} var _surface_total_areas: Dictionary = {} -var _surface_height_ranges: Dictionary = {} var _spawn_anchor_positions: Dictionary = {} var _respawns: Array[Dictionary] = [] var _next_respawn_by_type: Dictionary[StringName, float] = {} @@ -323,14 +322,10 @@ func _cache_spawn_surface(entry: GatherableDataType) -> void: if not entry.diggable_area_id.is_empty(): triangles = _world.get_diggable_area_triangles(entry.diggable_area_id) else: - var height_range := _resolved_surface_height_range(entry) triangles = _world.get_spawn_surface_triangles( entry.surface_materials, - height_range.x, - 0.6, - height_range.y, + entry.minimum_surface_y, ) - _surface_height_ranges[entry.type_id] = height_range var areas := PackedFloat32Array() var total_area: float = 0.0 for triangle: PackedVector3Array in triangles: @@ -524,18 +519,9 @@ func _sample_surface_position( if triangles.is_empty() or total_area <= 0.0: return Vector3(INF, INF, INF) var validates_diggable_surface := not entry.diggable_area_id.is_empty() - var height_range: Vector2 = _surface_height_ranges.get( - entry.type_id, - Vector2(entry.minimum_surface_y, entry.maximum_surface_y), - ) - var validates_height_range := _surface_height_ranges.has(entry.type_id) var attempts: int = ( 48 - if ( - validates_diggable_surface - or validates_height_range - or is_finite(maximum_distance) - ) + if validates_diggable_surface or is_finite(maximum_distance) else 1 ) var fallback: Vector3 = Vector3(INF, INF, INF) @@ -565,20 +551,9 @@ func _sample_surface_position( point, ) ) - var valid_height := ( - not validates_height_range - or ( - point.y > height_range.x - and point.y < height_range.y - ) - ) - if ( - within_requested_distance - and valid_diggable_surface - and valid_height - ): + if within_requested_distance and valid_diggable_surface: return point - if validates_diggable_surface or validates_height_range: + if validates_diggable_surface: return Vector3(INF, INF, INF) if origin.is_finite() and is_finite(maximum_distance): return origin @@ -1430,12 +1405,8 @@ func _reconcile_seasonal_population() -> void: func _target_population(entry: GatherableDataType) -> int: - if entry == null: - return 0 - if entry.spawn_anchor_set_id.is_empty(): - return entry.target_population_for_surface_area( - float(_surface_total_areas.get(entry.type_id, 0.0)) - ) + if entry == null or entry.spawn_anchor_set_id.is_empty(): + return entry.population if entry != null else 0 var anchors: PackedVector3Array = _spawn_anchor_positions.get( entry.type_id, PackedVector3Array(), @@ -1443,25 +1414,6 @@ func _target_population(entry: GatherableDataType) -> int: return entry.target_population_for_anchor_count(anchors.size()) -func _resolved_surface_height_range(entry: GatherableDataType) -> Vector2: - var minimum_y := entry.minimum_surface_y - var maximum_y := entry.maximum_surface_y - var saltwater_height := _world.get_saltwater_surface_height() - if is_finite(saltwater_height): - match entry.surface_water_relation: - GatherableDataType.SurfaceWaterRelation.ABOVE_SALT_WATER: - minimum_y = maxf( - minimum_y, - saltwater_height + entry.waterline_margin, - ) - GatherableDataType.SurfaceWaterRelation.BELOW_SALT_WATER: - maximum_y = minf( - maximum_y, - saltwater_height - entry.waterline_margin, - ) - return Vector2(minimum_y, maximum_y) - - func _population_for_type(type_id: StringName) -> int: var count: int = 0 for state: Dictionary in _entities.values(): @@ -1493,7 +1445,6 @@ func _clear_world() -> void: _surface_triangles.clear() _surface_areas.clear() _surface_total_areas.clear() - _surface_height_ranges.clear() _spawn_anchor_positions.clear() _respawns.clear() _next_respawn_by_type.clear() diff --git a/player/player.gd b/player/player.gd index 78dac3d..64d4ffe 100644 --- a/player/player.gd +++ b/player/player.gd @@ -90,7 +90,6 @@ const CATCH_SHOWCASE_INPUT_OWNER: StringName = &"catch_showcase" # safe IDs so newer clients can extend it, but Player only presents actions # explicitly approved by this catalog. const NETWORK_ANIMATION_ACTION_IDS: Array[StringName] = [ - CHARACTER_WALKING_ANIMATION, CHARACTER_CASTING_ANIMATION, CHARACTER_NET_DRAW_ANIMATION, CHARACTER_NET_STRIKE_ANIMATION, @@ -428,20 +427,12 @@ class ShowcaseCameraSnapshot: @export_range(0.0, 1.0, 0.01) var controller_camera_deadzone: float = 0.2 @export var invert_camera_y: bool = false var _swap_hotbar_camera_scroll: bool = false -var _rv_interior_camera_mode: bool = false -var _rv_interior_environment: Environment -var _rv_previous_camera_environment: Environment @export_range(-89.0, 0.0, 0.5) var minimum_pitch_degrees: float = -65.0 @export_range(0.0, 89.0, 0.5) var maximum_pitch_degrees: float = 45.0 @export var minimum_zoom: float = 2.0 @export var maximum_zoom: float = 8.0 @export var zoom_step: float = 0.75 @export var zoom_smoothing: float = 12.0 -@export_range(0.0, 1.0, 0.01) var fishing_reel_zoom_amount: float = 0.22 -@export_range(0.0, 1.5, 0.01) var fishing_barrier_break_zoom: float = 0.38 -@export_range(0.05, 1.0, 0.01) var fishing_break_zoom_decay: float = 0.34 -@export_range(0.0, 0.2, 0.005) var fishing_barrier_hit_shake: float = 0.018 -@export_range(0.0, 0.3, 0.005) var fishing_barrier_break_shake: float = 0.045 @export_range(0.1, 12.0, 0.1) var controller_zoom_speed: float = 4.0 @export_range(0.0, 1.0, 0.01) var controller_trigger_threshold: float = 0.55 @export_range(1.0, 40.0, 0.5) var free_camera_speed: float = 10.0 @@ -480,8 +471,6 @@ var _held_fish_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04) var _held_fish_sprite: Sprite3D var _held_art_kit_display: Node3D var _held_art_kit_sprite: Sprite3D -var _held_decor_package_display: Node3D -var _held_decor_package_visible: bool = false var _catch_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04) var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity")) @@ -503,12 +492,6 @@ var _remote_recovery_presentation_active: bool = false var _remote_recovery_visual_origin: Vector3 var _remote_recovery_elapsed: float = 0.0 var _target_zoom: float = 5.0 -var _fishing_reel_camera_active: bool = false -var _fishing_break_zoom_offset: float = 0.0 -var _fishing_camera_shake_remaining: float = 0.0 -var _fishing_camera_shake_duration: float = 0.0 -var _fishing_camera_shake_strength: float = 0.0 -var _fishing_camera_shake_phase: float = 0.0 var _showcase_rod_visibility: bool = true var _showcase_net_visibility: bool = false var _showcase_shovel_visibility: bool = false @@ -1009,7 +992,6 @@ func _initialize_held_item_attachment() -> void: _held_art_kit_sprite = attachment.get_node( "HeldArtKitDisplay/HeldArtKitSprite" ) as Sprite3D - _held_decor_package_display = _build_decor_package_display(attachment) _catch_display = attachment.get_node("CatchDisplay") as Node3D _catch_attachment_offset = _catch_display.position _catch_sprite = attachment.get_node("CatchDisplay/CatchSprite") as Sprite3D @@ -1174,7 +1156,6 @@ func _process(delta: float) -> void: ) if not local_control_enabled: return - _update_fishing_camera_effects(delta) if _free_camera_active: if _is_camera_input_enabled(): _rotate_free_camera( @@ -1203,68 +1184,13 @@ func _process(delta: float) -> void: ) var zoom_weight: float = 1.0 - exp(-zoom_smoothing * delta) - var fishing_zoom_offset: float = ( - fishing_reel_zoom_amount - if _fishing_reel_camera_active - else 0.0 - ) + _fishing_break_zoom_offset - var effective_target_zoom: float = clampf( - _target_zoom - fishing_zoom_offset, - 1.25 if _rv_interior_camera_mode else minimum_zoom, - 16.0 if _rv_interior_camera_mode else maximum_zoom, - ) _spring_arm.spring_length = lerpf( _spring_arm.spring_length, - effective_target_zoom, + _target_zoom, zoom_weight ) -func set_fishing_reel_camera_active(active: bool) -> void: - _fishing_reel_camera_active = active - - -func play_fishing_barrier_camera_feedback(barrier_broken: bool) -> void: - if not local_control_enabled or _free_camera_active: - return - _fishing_camera_shake_strength = ( - fishing_barrier_break_shake - if barrier_broken - else fishing_barrier_hit_shake - ) - _fishing_camera_shake_duration = 0.20 if barrier_broken else 0.10 - _fishing_camera_shake_remaining = _fishing_camera_shake_duration - if barrier_broken: - _fishing_break_zoom_offset = fishing_barrier_break_zoom - - -func _update_fishing_camera_effects(delta: float) -> void: - _fishing_break_zoom_offset = move_toward( - _fishing_break_zoom_offset, - 0.0, - ( - fishing_barrier_break_zoom - / maxf(fishing_break_zoom_decay, 0.05) - ) * delta, - ) - if _fishing_camera_shake_remaining <= 0.0: - _camera.h_offset = 0.0 - _camera.v_offset = 0.0 - return - _fishing_camera_shake_remaining = maxf( - _fishing_camera_shake_remaining - delta, - 0.0, - ) - _fishing_camera_shake_phase += delta * 92.0 - var decay: float = ( - _fishing_camera_shake_remaining - / maxf(_fishing_camera_shake_duration, 0.001) - ) - var strength: float = _fishing_camera_shake_strength * decay - _camera.h_offset = sin(_fishing_camera_shake_phase) * strength - _camera.v_offset = cos(_fishing_camera_shake_phase * 1.31) * strength * 0.7 - - func _update_blink(delta: float) -> void: if _fighting_visual_active: return @@ -1396,7 +1322,9 @@ func _update_character_animation() -> void: and supports_network_animation_action(animation_action_id) and _character_animation_player.has_animation(animation_action_id) ) - var held_show_item_visible: bool = _has_held_show_item() + var held_show_item_visible: bool = ( + _held_fish_visible or _held_art_kit_visible + ) var requested_animation: Array[StringName] = [] if _pocket_visual_active: requested_animation = [_pocket_visual_animation] @@ -1838,11 +1766,7 @@ func _unhandled_input(event: InputEvent) -> void: var mouse_zoom_in: bool = ( event is InputEventMouseButton - and ( - (not event.shift_pressed) - if _rv_interior_camera_mode - else event.shift_pressed != _swap_hotbar_camera_scroll - ) + and event.shift_pressed != _swap_hotbar_camera_scroll and event.is_action_pressed("camera_zoom_in") ) var controller_zoom_in: bool = ( @@ -1851,11 +1775,7 @@ func _unhandled_input(event: InputEvent) -> void: ) var mouse_zoom_out: bool = ( event is InputEventMouseButton - and ( - (not event.shift_pressed) - if _rv_interior_camera_mode - else event.shift_pressed != _swap_hotbar_camera_scroll - ) + and event.shift_pressed != _swap_hotbar_camera_scroll and event.is_action_pressed("camera_zoom_out") ) var controller_zoom_out: bool = ( @@ -2043,11 +1963,7 @@ func get_active_gameplay_camera() -> Camera3D: func _set_target_zoom(value: float) -> void: - _target_zoom = clampf( - value, - 1.25 if _rv_interior_camera_mode else minimum_zoom, - 16.0 if _rv_interior_camera_mode else maximum_zoom, - ) + _target_zoom = clampf(value, minimum_zoom, maximum_zoom) func _apply_axis_deadzone(value: float) -> float: @@ -2161,37 +2077,6 @@ func reset_network_movement_state() -> void: _local_prediction_largest_error = 0.0 -func apply_network_space_transition( - target_transform: Transform3D, - align_local_camera: bool, -) -> void: - if not target_transform.origin.is_finite(): - return - # A room transition is a discontinuity, not ordinary movement. Preserve the - # host's input acknowledgement while clearing every motion, animation, and - # reconciliation state that could pull the avatar toward the previous space. - var acknowledged_input: int = _last_network_input_sequence - reset_network_movement_state() - _last_network_input_sequence = acknowledged_input - _apply_network_casting(false) - velocity = Vector3.ZERO - global_transform = Transform3D(Basis.IDENTITY, target_transform.origin) - var target_yaw: float = target_transform.basis.get_euler().y - _visuals.rotation.y = target_yaw - _network_camera_yaw = target_yaw - _network_target_position = global_position - _network_target_velocity = Vector3.ZERO - _network_target_visual_yaw = target_yaw - _network_snapshot_age = 0.0 - _network_snapshot_ready = true - _authoritative_movement_history.clear() - if align_local_camera: - _set_camera_dragging(false) - if _free_camera_active: - _set_free_camera_active(false) - _camera_yaw.rotation.y = target_yaw - - func capture_network_input(sequence: int) -> Dictionary: if _sitting_intent_pending and _sitting_intent_sequence < 0: _sitting_intent_sequence = sequence @@ -3120,19 +3005,6 @@ func set_camera_active(active: bool) -> void: _camera.current = active -func face_world_direction(direction: Vector3) -> void: - var horizontal_direction := Vector3(direction.x, 0.0, direction.z) - if horizontal_direction.is_zero_approx() or _visuals == null: - return - horizontal_direction = horizontal_direction.normalized() - var target_world_yaw: float = atan2( - -horizontal_direction.x, - -horizontal_direction.z, - ) - _visuals.rotation.y = target_world_yaw - global_rotation.y - _network_target_visual_yaw = _visuals.rotation.y - - func apply_camera_settings( new_mouse_sensitivity: float, new_controller_sensitivity: float, @@ -3149,49 +3021,6 @@ func apply_camera_settings( _swap_hotbar_camera_scroll = swap_hotbar_camera_scroll -func set_rv_interior_camera_mode( - enabled: bool, - excluded_wall_rids: Array[RID] = [], -) -> void: - if enabled != _rv_interior_camera_mode: - if enabled: - _rv_previous_camera_environment = _camera.environment - _camera.environment = _get_rv_interior_environment() - if _free_camera != null: - _free_camera.environment = _camera.environment - else: - _camera.environment = _rv_previous_camera_environment - if _free_camera != null: - _free_camera.environment = _camera.environment - _rv_previous_camera_environment = null - _rv_interior_camera_mode = enabled - if _spring_arm == null: - return - _spring_arm.clear_excluded_objects() - if enabled: - for collider_rid: RID in excluded_wall_rids: - if collider_rid.is_valid(): - _spring_arm.add_excluded_object(collider_rid) - _set_target_zoom(_target_zoom) - - -func _get_rv_interior_environment() -> Environment: - if _rv_interior_environment != null: - return _rv_interior_environment - _rv_interior_environment = Environment.new() - _rv_interior_environment.background_mode = Environment.BG_COLOR - _rv_interior_environment.background_color = Color("020506") - _rv_interior_environment.ambient_light_source = ( - Environment.AMBIENT_SOURCE_COLOR - ) - _rv_interior_environment.ambient_light_color = Color("d6caaa") - _rv_interior_environment.ambient_light_energy = 1.0 - _rv_interior_environment.reflected_light_source = ( - Environment.REFLECTION_SOURCE_DISABLED - ) - return _rv_interior_environment - - func is_camera_input_enabled() -> bool: return _camera_input_enabled @@ -3318,23 +3147,6 @@ func set_active_shovel(should_show: bool) -> void: ) -func set_active_decor_package(should_show: bool) -> void: - var changed: bool = _held_decor_package_visible != should_show - _held_decor_package_visible = should_show - if _held_decor_package_display != null: - _held_decor_package_display.visible = should_show - if should_show: - _fishing_rod.visible = false - if _catching_net != null: - _catching_net.visible = false - if _shovel != null: - _shovel.visible = false - elif changed: - _apply_active_rod_visibility() - if changed: - _update_character_animation() - - func _apply_fishing_rod_model(rod: FishingRodDataType) -> void: var next_id: StringName = rod.item_id if rod != null else StringName() if next_id == _active_fishing_rod_id: @@ -3507,52 +3319,7 @@ func set_held_fish( func _has_held_show_item() -> bool: - return ( - _held_fish_visible - or _held_art_kit_visible - or _held_decor_package_visible - ) - - -func _build_decor_package_display(parent: Node3D) -> Node3D: - var display := Node3D.new() - display.name = "HeldDecorPackageDisplay" - display.position = Vector3(0.0, 0.1, 0.04) - display.rotation = Vector3(0.15, 0.0, -0.55) - display.visible = false - parent.add_child(display) - - var sack := MeshInstance3D.new() - sack.name = "Sack" - var sack_mesh := SphereMesh.new() - sack_mesh.radius = 0.22 - sack_mesh.height = 0.48 - sack_mesh.radial_segments = 8 - sack_mesh.rings = 4 - sack_mesh.material = _decor_package_material(Color("a87843")) - sack.mesh = sack_mesh - display.add_child(sack) - - var tie := MeshInstance3D.new() - tie.name = "Tie" - var tie_mesh := CylinderMesh.new() - tie_mesh.top_radius = 0.1 - tie_mesh.bottom_radius = 0.12 - tie_mesh.height = 0.08 - tie_mesh.radial_segments = 8 - tie_mesh.material = _decor_package_material(Color("513829")) - tie.mesh = tie_mesh - tie.position = Vector3(0.0, 0.22, 0.0) - display.add_child(tie) - return display - - -func _decor_package_material(color: Color) -> StandardMaterial3D: - var material := StandardMaterial3D.new() - material.albedo_color = color - material.roughness = 1.0 - material.shading_mode = BaseMaterial3D.SHADING_MODE_PER_VERTEX - return material + return _held_fish_visible or _held_art_kit_visible func _clear_held_fish() -> void: diff --git a/progression/player_cooler_capacity.gd b/progression/player_cooler_capacity.gd index bbd0b87..b8c62be 100644 --- a/progression/player_cooler_capacity.gd +++ b/progression/player_cooler_capacity.gd @@ -2,77 +2,62 @@ class_name PlayerCoolerCapacity extends Node const PlayerWalletType = preload("res://economy/player_wallet.gd") -const PlayerHomeStateType = preload("res://homes/player_home_state.gd") signal capacity_changed(level: int, capacity: int) -## Compatibility adapter for saves and systems that still refer to the old -## cooler-capacity object. Storage is now derived exclusively from the player's -## home tier; this object is not independently purchasable. +## Kept under the historical class name so existing saves and network product +## identifiers migrate without losing their purchased level. This progression +## now controls the player's private storage box rather than carried catches. const CAPACITIES: Array[int] = [9, 18, 27, 36, 45, 54, 63, 72] const EXPANSION_COSTS: Array[int] = [75, 175, 400, 850, 1500, 2500, 4000] const MAX_LEVEL: int = 7 const MAX_CAPACITY: int = 72 -var _home_state: PlayerHomeStateType -var _legacy_capacity_level: int = 0 - - -func bind_home_state(home_state: PlayerHomeStateType) -> void: - if _home_state == home_state: - return - if ( - _home_state != null - and _home_state.changed.is_connected(_on_home_changed) - ): - _home_state.changed.disconnect(_on_home_changed) - _home_state = home_state - if ( - _home_state != null - and not _home_state.changed.is_connected(_on_home_changed) - ): - _home_state.changed.connect(_on_home_changed) - _on_home_changed() +var _capacity_level: int = 0 func get_level() -> int: - return _legacy_capacity_level + return _capacity_level func get_capacity() -> int: - if _home_state != null: - # A new home upgrade grants its authored capacity. Older saves retain any - # separately purchased storage level so reducing the first RV tier cannot - # hide or discard items that were already placed in later slots. - return maxi( - _home_state.get_storage_capacity(), - CAPACITIES[clampi(_legacy_capacity_level, 0, MAX_LEVEL)], - ) - return CAPACITIES[clampi(_legacy_capacity_level, 0, MAX_LEVEL)] + return CAPACITIES[_capacity_level] func get_next_capacity() -> int: - return -1 + if _capacity_level >= MAX_LEVEL: + return -1 + return CAPACITIES[_capacity_level + 1] func get_next_cost() -> int: - return -1 + if _capacity_level >= MAX_LEVEL: + return -1 + return EXPANSION_COSTS[_capacity_level] -func can_purchase(_wallet: PlayerWalletType) -> bool: - return false +func can_purchase(wallet: PlayerWalletType) -> bool: + var cost: int = get_next_cost() + return wallet != null and cost >= 0 and wallet.can_afford(cost) -func purchase(_wallet: PlayerWalletType) -> bool: - return false +func purchase(wallet: PlayerWalletType) -> bool: + if not can_purchase(wallet): + return false + var cost: int = get_next_cost() + if not wallet.debit(cost): + return false + _capacity_level += 1 + capacity_changed.emit(_capacity_level, get_capacity()) + return true func restore_level(level: int) -> bool: var validated: int = clampi(level, 0, MAX_LEVEL) - var changed: bool = _legacy_capacity_level != validated - _legacy_capacity_level = validated + var changed: bool = _capacity_level != validated + _capacity_level = validated if changed: - capacity_changed.emit(get_level(), get_capacity()) + capacity_changed.emit(_capacity_level, get_capacity()) return true @@ -81,8 +66,4 @@ func reset_to_defaults() -> void: func to_save_data() -> Dictionary: - return {"capacity_level": get_level()} - - -func _on_home_changed() -> void: - capacity_changed.emit(get_level(), get_capacity()) + return {"capacity_level": _capacity_level} diff --git a/project.godot b/project.godot index 5bcbb4d..d202817 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="straywild" -config/version="0.20.2-alpha" +config/version="0.19.0-alpha" run/main_scene="res://main/main.tscn" config/features=PackedStringArray("4.7", "GL Compatibility") config/icon="res://art/exported/system_icons/straywild_256.png" @@ -260,7 +260,7 @@ alternate_fishing={ [network] -discovery/base_url="https://discovery.straywild.io/patron-test" +discovery/base_url="https://discovery.straywild.io" [physics] diff --git a/save/player_save_manager.gd b/save/player_save_manager.gd index c41f861..1ad74a1 100644 --- a/save/player_save_manager.gd +++ b/save/player_save_manager.gd @@ -34,9 +34,8 @@ const WorldWeatherServiceType = preload( const WorldLayoutType = preload("res://world/world_layout.gd") const PlayerJobServiceType = preload("res://jobs/player_job_service.gd") const PlayerType = preload("res://player/player.gd") -const PlayerHomeStateType = preload("res://homes/player_home_state.gd") -const SAVE_VERSION: int = 11 +const SAVE_VERSION: int = 10 const LEGACY_SAVE_FILENAME := "player_save.json" const ARCHIVE_EXTENSION := ".nfsave" const BASIC_ROD_ID: StringName = &"basic_fishing_rod" @@ -77,7 +76,6 @@ class LoadSnapshot: WorldWeatherServiceType.SUNNY_DURATION_RANGE.x ) var jobs_data: Dictionary = PlayerJobServiceType.default_save_data() - var home_data: Dictionary = PlayerHomeStateType.default_save_data() @export_range(0.05, 5.0, 0.05) var autosave_delay: float = 0.5 @@ -98,7 +96,6 @@ var _world_time: WorldTimeServiceType var _world_weather: WorldWeatherServiceType var _jobs: PlayerJobServiceType var _player: PlayerType -var _home_state: PlayerHomeStateType var _autosave_timer: Timer var _is_configured: bool = false var _is_restoring: bool = false @@ -178,7 +175,6 @@ func setup( world_weather: WorldWeatherServiceType, jobs: PlayerJobServiceType, player: PlayerType, - home_state: PlayerHomeStateType, ) -> void: _inventory = inventory _collection_log = collection_log @@ -196,7 +192,6 @@ func setup( _world_weather = world_weather _jobs = jobs _player = player - _home_state = home_state _is_configured = ( _inventory != null and _collection_log != null @@ -214,7 +209,6 @@ func setup( and _world_weather != null and _jobs != null and _player != null - and _home_state != null ) if not _is_configured: push_error("PlayerSaveManager setup is missing required references.") @@ -261,8 +255,6 @@ func setup( _player.active_bait_changed.connect(_on_active_tackle_changed) if not _player.active_lure_changed.is_connected(_on_active_tackle_changed): _player.active_lure_changed.connect(_on_active_tackle_changed) - if not _home_state.changed.is_connected(_mark_dirty): - _home_state.changed.connect(_mark_dirty) func load_player_data() -> bool: @@ -338,11 +330,6 @@ func load_player_data() -> bool: snapshot.reel_speed_level, snapshot.barrier_power_level ) - # Home tier is now the authority for private-storage capacity, so restore it - # before the compatibility capacity adapter and inventory layout. - var home_restored: bool = _home_state.restore_from_save_data( - snapshot.home_data - ) var cooler_restored: bool = _cooler_capacity.restore_level( snapshot.cooler_capacity_level ) @@ -389,14 +376,13 @@ func load_player_data() -> bool: or not world_time_restored or not world_weather_restored or not jobs_restored - or not home_restored ): push_error( ( "Validated player save could not be restored: " + "inventory=%s collection=%s wallet=%s bag=%s tackle=%s hotbar=%s " + "upgrades=%s cooler=%s layout=%s art=%s experience=%s " - + "time=%s weather=%s jobs=%s home=%s" + + "time=%s weather=%s jobs=%s" ) % [ inventory_restored, @@ -413,7 +399,6 @@ func load_player_data() -> bool: world_time_restored, world_weather_restored, jobs_restored, - home_restored, ] ) return false @@ -914,7 +899,6 @@ func _build_save_dictionary() -> Dictionary: ), }, "jobs": _jobs.to_save_data(), - "home": _home_state.to_save_data(), } @@ -929,7 +913,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: or typeof(save_data.get("inventory_layout")) != TYPE_DICTIONARY or typeof(save_data.get("experience")) != TYPE_DICTIONARY or typeof(save_data.get("jobs")) != TYPE_DICTIONARY - or typeof(save_data.get("home")) != TYPE_DICTIONARY ): return null var wallet_data: Dictionary = save_data["wallet"] @@ -941,7 +924,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: var inventory_layout_data: Dictionary = save_data["inventory_layout"] var experience_data: Dictionary = save_data["experience"] var jobs_data: Dictionary = save_data["jobs"] - var home_data: Dictionary = save_data["home"] var world_data: Dictionary = {} if typeof(save_data.get("world")) == TYPE_DICTIONARY: world_data = save_data["world"] @@ -1024,12 +1006,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: if not PlayerJobServiceType.validate_save_data(jobs_data): return null snapshot.jobs_data = jobs_data.duplicate(true) - var sanitized_home: Dictionary = PlayerHomeStateType.sanitize_save_data( - home_data - ) - if sanitized_home.is_empty(): - return null - snapshot.home_data = sanitized_home if world_data.has("time_hours"): snapshot.world_time_hours = _read_world_time_hours( world_data["time_hours"] @@ -1326,26 +1302,6 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot: PlayerCoolerCapacityType.MAX_LEVEL, "cooler.capacity_level" ) - # Before homes had upgrade tiers, storage was purchased separately. Preserve - # those saves by granting the starter RV to anybody who owned at least one - # legacy expansion. PlayerCoolerCapacity also retains the purchased legacy - # capacity so entries beyond the new RV's first 18 slots remain accessible. - if ( - snapshot.cooler_capacity_level > 0 - and int(snapshot.home_data.get("tier", 0)) - == PlayerHomeStateType.STARTER_TIER - ): - var migrated_home: Dictionary = home_data.duplicate(true) - migrated_home["tier"] = PlayerHomeStateType.STARTER_RV_TIER - migrated_home["exterior_id"] = String( - PlayerHomeStateType.STARTER_RV_EXTERIOR_ID - ) - var sanitized_migrated_home := ( - PlayerHomeStateType.sanitize_save_data(migrated_home) - ) - if sanitized_migrated_home.is_empty(): - return null - snapshot.home_data = sanitized_migrated_home snapshot.art_unlock_mask = _read_integer( art_data.get("unlock_mask"), 0, @@ -1384,8 +1340,6 @@ func _migrate_save( migrated = _migrate_version_8_to_9(migrated) 9: migrated = _migrate_version_9_to_10(migrated) - 10: - migrated = _migrate_version_10_to_11(migrated) _: return {} if migrated.is_empty(): @@ -1643,13 +1597,6 @@ func _migrate_version_9_to_10(data: Dictionary) -> Dictionary: return migrated -func _migrate_version_10_to_11(data: Dictionary) -> Dictionary: - var migrated: Dictionary = data.duplicate(true) - migrated["home"] = PlayerHomeStateType.default_save_data() - migrated["save_version"] = 11 - return migrated - - func _mark_dirty() -> void: if ( _is_restoring @@ -1919,7 +1866,6 @@ func _restore_defaults() -> void: _world_layout = WorldLayoutType.GENERATED _world_seed = DEFAULT_WORLD_SEED _jobs.reset_to_defaults() - _home_state.reset_to_defaults() _is_restoring = false _is_dirty = false diff --git a/scripts/build_playtest.sh b/scripts/build_playtest.sh index d54ba70..1c74943 100755 --- a/scripts/build_playtest.sh +++ b/scripts/build_playtest.sh @@ -4,14 +4,14 @@ set -euo pipefail readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" -readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.20.2-alpha" +readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.19.0-alpha" readonly WINDOWS_DIR="${BUILD_ROOT}/windows-x86_64" readonly LINUX_DIR="${BUILD_ROOT}/linux-x86_64" readonly README_SOURCE="${PROJECT_ROOT}/docs/README-PLAYTEST.txt" readonly SOURCE_COMMIT="$(git -C "${PROJECT_ROOT}" rev-parse HEAD)" -readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/straywild" -readonly WINDOWS_ZIP="${BUILD_ROOT}/straywild-v0.20.2-alpha-windows-x86_64.zip" -readonly LINUX_ZIP="${BUILD_ROOT}/straywild-v0.20.2-alpha-linux-x86_64.zip" +readonly SOURCE_URL="https://forge.makearmy.io/woofmeow/netfishing" +readonly WINDOWS_ZIP="${BUILD_ROOT}/straywild-v0.19.0-alpha-windows-x86_64.zip" +readonly LINUX_ZIP="${BUILD_ROOT}/straywild-v0.19.0-alpha-linux-x86_64.zip" readonly GODOT_BIN="${GODOT_BIN:-godot}" if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then @@ -77,9 +77,6 @@ straywild code is licensed under GPL-3.0-or-later. Source repository: ${SOURCE_URL} Source revision at build time: ${SOURCE_COMMIT} -This private testing release is distributed with an exact corresponding -source archive named straywild-v0.20.2-alpha-source.tar.gz. - If this package includes uncommitted development changes, it must not be publicly redistributed until the exact corresponding source is also made available. diff --git a/scripts/build_portmaster.sh b/scripts/build_portmaster.sh index 2d9dcbb..0487b5b 100755 --- a/scripts/build_portmaster.sh +++ b/scripts/build_portmaster.sh @@ -193,7 +193,7 @@ cat >"${GAME_ROOT}/licenses/SOURCE-CODE.md" </dev/null readonly TOP_LEVELS="$( unzip -Z1 "${ARCHIVE}" | cut -d/ -f1 | sort -u )" -readonly EXPECTED_TOP_LEVELS="$(printf '%s\n' straywild straywild.sh)" +readonly EXPECTED_TOP_LEVELS="$(printf '%s\n' straywild.sh straywild)" if [[ "${TOP_LEVELS}" != "${EXPECTED_TOP_LEVELS}" ]]; then echo "Unexpected PortMaster archive roots:" >&2 printf '%s\n' "${TOP_LEVELS}" >&2 diff --git a/scripts/run_validations.sh b/scripts/run_validations.sh index ff108a8..5d15b8e 100755 --- a/scripts/run_validations.sh +++ b/scripts/run_validations.sh @@ -87,7 +87,6 @@ readonly -a NETWORK_TESTS=( "tests/fish_showcase_multiplayer_validation.gd" "tests/friend_multiplayer_validation.gd" "tests/fishing_multiplayer_validation.gd" - "tests/home_spawn_multiplayer_validation.gd" "tests/job_multiplayer_validation.gd" "tests/movement_multiplayer_validation.gd" "tests/operator_multiplayer_validation.gd" diff --git a/tests/art_tools_validation.gd b/tests/art_tools_validation.gd index 146fb4e..3b9d6a3 100644 --- a/tests/art_tools_validation.gd +++ b/tests/art_tools_validation.gd @@ -19,15 +19,9 @@ func _run() -> void: for _frame: int in 8: await process_frame assert(bool(main.get("_application_initialized"))) - assert(bool(main.call( - "_apply_world", WorldLayout.STARTER_ISLAND, 1, true - ))) - assert(bool(main.call( - "_prepare_host_world", WorldLayout.STARTER_ISLAND, 1 - ))) assert(bool(main.call("_prepare_private_host"))) var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) + assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") await physics_frame await physics_frame @@ -100,7 +94,11 @@ func _run() -> void: assert(keyboard_mapping.reset_mapping()) await process_frame var shop_prompt_key := game_ui.get_node("%ShopPromptKey") as Label + var storage_prompt_message := game_ui.get_node( + "%StoragePromptMessage" + ) as Label assert(shop_prompt_key.text == "E") + assert(storage_prompt_message.text == "E open storage") var remapped_interact := InputEventKey.new() remapped_interact.physical_keycode = KEY_F remapped_interact.pressed = true @@ -110,6 +108,7 @@ func _run() -> void: )) await process_frame assert(shop_prompt_key.text == "F") + assert(storage_prompt_message.text == "F open storage") assert(keyboard_mapping.reset_mapping()) await process_frame assert(toolbar.get_parent() == chat_ui.get_parent()) @@ -417,12 +416,6 @@ func _run() -> void: ) assert(normal_camera.is_position_behind(chat_anchor)) assert(not free_camera.is_position_behind(chat_anchor)) - var shop_prompt := game_ui.get_node("%ShopPrompt") as Control - var storage_prompt := game_ui.get_node("%StoragePrompt") as Control - game_ui.set_shop_prompt_visible(true, chat_anchor) - game_ui.set_storage_prompt_visible(true, chat_anchor) - assert(shop_prompt.visible) - assert(storage_prompt.visible) chat_ui.show_local_speech("hello there friend") await process_frame assert(bool(player.get("_speech_mouth_active"))) diff --git a/tests/controller_menu_accessibility_validation.gd b/tests/controller_menu_accessibility_validation.gd index 37e43d5..aa2f9e2 100644 --- a/tests/controller_menu_accessibility_validation.gd +++ b/tests/controller_menu_accessibility_validation.gd @@ -1356,24 +1356,6 @@ func _validate_player_menu_nested_controller_back() -> void: == LogbookPageType.ControllerZone.ENTRIES, "Global player-menu Back did not return Logbook to creature selection.", ) - logbook_page.call("_set_index_open", true, false) - logbook_page.set( - "_controller_zone", - LogbookPageType.ControllerZone.INDEX, - ) - _expect( - bool(menu.call("consume_escape")), - "Global player-menu Back did not consume the Logbook index zone.", - ) - _expect( - menu.visible and not bool(logbook_page.get("_index_open")), - "Global player-menu Back did not return from the Logbook index page.", - ) - _expect( - int(logbook_page.get("_controller_zone")) - == LogbookPageType.ControllerZone.TABS, - "Global player-menu Back did not return Logbook index focus to tabs.", - ) var net_page := menu.get("_the_net_page") as Control net_page.set("_active", true) diff --git a/tests/economy_regression_validation.gd b/tests/economy_regression_validation.gd index 0728d82..ec80d1b 100644 --- a/tests/economy_regression_validation.gd +++ b/tests/economy_regression_validation.gd @@ -31,14 +31,9 @@ func _run() -> void: for _frame: int in 8: await process_frame assert(bool(main.get("_application_initialized"))) - var session := main.get_node("%NetworkSession") as NetworkSession - assert(bool(main.call( - "_apply_world", WorldLayout.STARTER_ISLAND, 1, true - ))) - assert(session.set_host_world(WorldLayout.STARTER_ISLAND, 1)) assert(bool(main.call("_prepare_private_host"))) var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) + assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") for _frame: int in 8: await process_frame @@ -47,13 +42,14 @@ func _run() -> void: var catalog := main.get("fish_catalog") as FishPool var sale_service := main.get_node("%NetworkSaleService") as NetworkSaleService var shop_service := main.get_node("%NetworkShopService") as NetworkShopService + var session := main.get_node("%NetworkSession") as NetworkSession var reservations := ( main.get_node("%PlayerAssetReservationService") as PlayerAssetReservationService ) assert(player != null) assert(catalog != null and catalog.candidates.size() == 316) - assert(LogbookCatalog.ordered_species(catalog.candidates).size() == 314) + assert(LogbookCatalog.ordered_species(catalog.candidates).size() == 313) assert(sale_service != null) assert(shop_service != null) assert(session != null and session.is_host()) @@ -82,7 +78,7 @@ func _run() -> void: main, player, catalog, sale_service, reservations ) _test_host_backpack_purchase(player, shop_service) - await _test_host_home_upgrade(main, player, shop_service) + _test_host_storage_purchase(player, shop_service) assert(not sale_service.is_local_sale_pending()) assert(not shop_service.is_local_purchase_pending()) @@ -175,6 +171,7 @@ func _run_multiplayer_client() -> void: assert(player.inventory_layout.restore_backpack_level( PlayerInventoryLayout.MAX_BACKPACK_LEVEL )) + assert(player.cooler_capacity.restore_level(PlayerCoolerCapacity.MAX_LEVEL)) player.inventory.set_inventory_layout(null) var catch_ids: Array[StringName] = [] for fish: FishData in catalog.candidates: @@ -201,6 +198,7 @@ func _run_multiplayer_client() -> void: assert(not player.inventory.contains_catch_id(catch_id)) assert(not sale_service.is_local_sale_pending()) assert(player.inventory_layout.restore_backpack_level(0)) + assert(player.cooler_capacity.restore_level(0)) var interaction := main.get("_shop_interaction") as FishingShopInteraction player.global_position = interaction.global_position @@ -556,58 +554,30 @@ func _test_host_backpack_purchase( for expected_capacity: int in [18, 27, 36]: _shop_result.clear() assert(not shop_service.request_backpack_capacity_upgrade().is_empty()) - assert( - not _shop_result.is_empty() and bool(_shop_result[1]), - "Backpack purchase failed: %s" % [_shop_result], - ) + assert(not _shop_result.is_empty() and bool(_shop_result[1])) assert(layout.get_inventory_capacity() == expected_capacity) assert(player.wallet.get_balance() == balance_before - total_cost) assert(layout.get_next_backpack_cost() == -1) -func _test_host_home_upgrade( - main: Node, +func _test_host_storage_purchase( player: Player, shop_service: NetworkShopService, ) -> void: - var interaction := main.get( - "_rv_upgrade_shop_interaction" - ) as RVUpgradeInteraction - var home_state := main.get("_home_state") as PlayerHomeState var capacity := player.cooler_capacity - assert(interaction != null and home_state != null) - player.global_position = interaction.global_position - for _frame: int in 4: - await physics_frame - assert(interaction.is_avatar_in_range(player)) - assert(home_state.get_tier() == PlayerHomeState.STARTER_TIER) - assert( - interaction.get_dialogue_text(home_state.get_tier()) - == RVUpgradeInteraction.TENT_DIALOGUE_TEXT - ) assert(capacity.get_capacity() == 9) - var upgrade_cost: int = home_state.get_next_upgrade_cost() - assert(upgrade_cost == PlayerHomeState.HOME_UPGRADE_COSTS.front()) - assert(upgrade_cost == 50) - if player.wallet.get_balance() < upgrade_cost: - assert(player.wallet.credit( - upgrade_cost - player.wallet.get_balance() - )) + var total_cost: int = 0 + for cost: int in PlayerCoolerCapacity.EXPANSION_COSTS: + total_cost += cost + if player.wallet.get_balance() < total_cost: + assert(player.wallet.credit(total_cost - player.wallet.get_balance())) var balance_before := player.wallet.get_balance() - _shop_result.clear() - assert(not shop_service.request_home_upgrade().is_empty()) - assert( - not _shop_result.is_empty() and bool(_shop_result[1]), - "Home purchase failed: %s" % [_shop_result], - ) - assert(home_state.get_tier() == PlayerHomeState.STARTER_RV_TIER) - assert( - interaction.get_dialogue_text(home_state.get_tier()) - == RVUpgradeInteraction.RV_DIALOGUE_TEXT - ) - assert(capacity.get_capacity() == 18) - assert(player.wallet.get_balance() == balance_before - upgrade_cost) - assert(home_state.get_next_upgrade_cost() == -1) + for expected_capacity: int in [18, 27, 36, 45, 54, 63, 72]: + _shop_result.clear() + assert(not shop_service.request_cooler_capacity_upgrade().is_empty()) + assert(not _shop_result.is_empty() and bool(_shop_result[1])) + assert(capacity.get_capacity() == expected_capacity) + assert(player.wallet.get_balance() == balance_before - total_cost) assert(capacity.get_next_cost() == -1) diff --git a/tests/fish_catalog_content_validation.gd b/tests/fish_catalog_content_validation.gd index 82f5604..3b31005 100644 --- a/tests/fish_catalog_content_validation.gd +++ b/tests/fish_catalog_content_validation.gd @@ -40,17 +40,39 @@ const StarterRegionScene = preload( const SharedFishPlaceholder: Texture2D = preload( "res://art/exported/creatures/fish_placeholder.png" ) -const FINAL_CREATURE_CATALOG_NUMBERS: Array[int] = [ - 101, 102, 103, 104, 105, 106, 107, 201, 403, 701, 704, 801, - 802, 805, 806, 808, 2502, 2504, 2505, 2508, 2601, 2801, 2802, - 2803, 2804, 2806, 2807, 3002, 3102, 3103, 3104, 3107, 3201, - 3401, 3902, 3906, 4101, 4401, 4701, 5001, 5702, 5901, 5902, - 5903, 5904, 6001, 6103, 6406, 6601, 6801, 6804, 6807, 6901, - 6902, 6904, 6905, 7101, 7403, 7506, 7508, -] +const FINAL_CREATURE_TEXTURE_PATHS: Dictionary[StringName, String] = { + &"bowfin": "res://art/exported/creatures/101.png", + &"sturgeon_lake": "res://art/exported/creatures/102.png", + &"gar_longnose": "res://art/exported/creatures/103.png", + &"paddlefish": "res://art/exported/creatures/104.png", + &"sturgeon_shovelnose": "res://art/exported/creatures/105.png", + &"gar_spotted": "res://art/exported/creatures/106.png", + &"lungfish_west_african": "res://art/exported/creatures/107.png", + &"mudskipper_atlantic": "res://art/exported/creatures/201.png", + &"flounder_winter": "res://art/exported/creatures/403.png", + &"mackerel_atlantic": "res://art/exported/creatures/701.png", + &"mackerel_chub": "res://art/exported/creatures/704.png", + &"sand_lance_american": "res://art/exported/creatures/801.png", + &"menhaden_atlantic": "res://art/exported/creatures/802.png", + &"anchovy_european": "res://art/exported/creatures/805.png", + &"sardine_european": "res://art/exported/creatures/806.png", + &"anchovy_northern": "res://art/exported/creatures/808.png", + &"crab_brown": "res://art/exported/creatures/3906.png", + &"salmon_atlantic": "res://art/exported/creatures/4401.png", + &"mahi_mahi": "res://art/exported/creatures/4701.png", + &"clownfish_ocellaris": "res://art/exported/creatures/5702.png", + &"chromis_blue_green": "res://art/exported/creatures/5901.png", + &"hogfish": "res://art/exported/creatures/6103.png", + &"mullet_striped": "res://art/exported/creatures/6804.png", + &"sheepshead": "res://art/exported/creatures/6905.png", + &"milkfish": "res://art/exported/creatures/7101.png", + &"ladyfish": "res://art/exported/creatures/7403.png", + &"tilapia_nile": "res://art/exported/creatures/7506.png", + &"betta_siamese": "res://art/exported/creatures/7508.png", +} -const ACTIVE_CATALOG_COUNT: int = 314 -const INACTIVE_CATALOG_COUNT: int = 2 +const ACTIVE_CATALOG_COUNT: int = 313 +const INACTIVE_CATALOG_COUNT: int = 3 const FISHING_SPECIES_COUNT: int = 310 const GENERATED_POND_COUNT: int = 33 const GENERATED_LAKE_COUNT: int = 20 @@ -213,15 +235,10 @@ func _validate_catalog_and_pools() -> void: else: inactive_count += 1 assert(not fish.is_selectable()) - if fish.catalog_number not in FINAL_CREATURE_CATALOG_NUMBERS: - assert(fish.display_texture == null) - if fish.catalog_number in FINAL_CREATURE_CATALOG_NUMBERS: - var expected_path := ( - "res://art/exported/creatures/%d.png" - % fish.catalog_number - ) + assert(fish.display_texture == null) + if FINAL_CREATURE_TEXTURE_PATHS.has(fish.id): var expected_texture: Texture2D = load( - expected_path + FINAL_CREATURE_TEXTURE_PATHS[fish.id] ) as Texture2D assert(expected_texture != null) assert(fish.display_texture == expected_texture) @@ -229,14 +246,14 @@ func _validate_catalog_and_pools() -> void: if fish.collection_method == FishDataType.CollectionMethod.FISHING: fishing_count += 1 assert(fish.active) - if fish.catalog_number not in FINAL_CREATURE_CATALOG_NUMBERS: + if not FINAL_CREATURE_TEXTURE_PATHS.has(fish.id): assert(fish.display_texture == SharedFishPlaceholder) placeholder_fishing_count += 1 assert(active_count == ACTIVE_CATALOG_COUNT) assert(inactive_count == INACTIVE_CATALOG_COUNT) assert(fishing_count == FISHING_SPECIES_COUNT) - assert(final_art_count == FINAL_CREATURE_CATALOG_NUMBERS.size()) - assert(placeholder_fishing_count == 253) + assert(final_art_count == FINAL_CREATURE_TEXTURE_PATHS.size()) + assert(placeholder_fishing_count == 283) var chum: FishDataType = Catalog.get_fish_by_id(&"salmon_chum") assert(chum != null) assert(chum.get_season_text() == "fall") @@ -274,7 +291,7 @@ func _validate_catalog_and_pools() -> void: ) == chum ) seasonal_collection.free() - var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"crab_dungeness") + var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"crab_blue") assert(inactive_fish != null and not inactive_fish.active) var inactive_pool := FishPoolType.new() inactive_pool.candidates = [inactive_fish] diff --git a/tests/fish_hotbar_showcase_validation.gd b/tests/fish_hotbar_showcase_validation.gd index af3644b..753f480 100644 --- a/tests/fish_hotbar_showcase_validation.gd +++ b/tests/fish_hotbar_showcase_validation.gd @@ -104,10 +104,7 @@ func _run() -> void: var hotbar_data: Dictionary = parsed["hotbar"] assert(typeof(hotbar_data.get("fish_slots")) == TYPE_ARRAY) assert(str((hotbar_data["fish_slots"] as Array)[1]) == fish_catch.catch_id) - assert(int(parsed["save_version"]) == 11) - assert(not PlayerHomeState.sanitize_save_data( - parsed.get("home", {}) - ).is_empty()) + assert(int(parsed["save_version"]) == 10) assert( int(parsed["experience"]["total_experience"]) == 125 @@ -200,8 +197,8 @@ func _run() -> void: var invalid_state: Dictionary = valid_state.duplicate(true) invalid_state["display_scale"] = 1000.0 assert(not NetworkFishShowcaseProtocol.validate_state(invalid_state)) - assert(NetworkProtocol.PROTOCOL_VERSION == 12) - assert(NetworkProtocol.ENET_CHANNEL_COUNT == 18) + assert(NetworkProtocol.PROTOCOL_VERSION == 11) + assert(NetworkProtocol.ENET_CHANNEL_COUNT == 17) assert( NetworkProtocol.FISH_QUALITY_CAPABILITY == "fish_quality_v1" diff --git a/tests/fish_quality_validation.gd b/tests/fish_quality_validation.gd index 99a2169..b6e6bfd 100644 --- a/tests/fish_quality_validation.gd +++ b/tests/fish_quality_validation.gd @@ -26,8 +26,8 @@ func _run() -> void: _validate_collection_mastery() _validate_version_four_migration() _validate_fishing_protocol_v2() - assert(NetworkProtocol.PROTOCOL_VERSION == 12) - assert(NetworkProtocol.ENET_CHANNEL_COUNT == 18) + assert(NetworkProtocol.PROTOCOL_VERSION == 11) + assert(NetworkProtocol.ENET_CHANNEL_COUNT == 17) assert(NetworkProtocol.MOVEMENT_ANIMATION_CHANNEL == 11) assert( NetworkProtocol.MOVEMENT_RECONCILIATION_CAPABILITY @@ -485,7 +485,7 @@ func _validate_version_four_migration() -> void: version_four, 4, ) - assert(int(migrated.get("save_version", -1)) == 11) + assert(int(migrated.get("save_version", -1)) == 10) assert(int((migrated["experience"] as Dictionary)["total_experience"]) == 0) assert( is_equal_approx( @@ -509,7 +509,4 @@ func _validate_version_four_migration() -> void: assert( PlayerJobService.validate_save_data(migrated.get("jobs", {})) ) - assert(not PlayerHomeState.sanitize_save_data( - migrated.get("home", {}) - ).is_empty()) manager.queue_free() diff --git a/tests/fishing_authority_validation.gd b/tests/fishing_authority_validation.gd index b3c17dd..e7c87bb 100644 --- a/tests/fishing_authority_validation.gd +++ b/tests/fishing_authority_validation.gd @@ -265,15 +265,6 @@ func _run() -> void: assert(bool(water_recovery.get("_prior_movement_enabled"))) assert(bool(water_recovery.get("_prior_camera_input_enabled"))) assert(not pause_menu.visible) - var test_world := main.get_node("TestWorld") as TestWorld - assert(test_world != null) - var recovery_entry := water_recovery.get("_entry_position") as Vector3 - var resolved_recovery := water_recovery.call( - "_resolve_recovery_position" - ) as Vector3 - assert(resolved_recovery.is_equal_approx( - test_world.get_water_recovery_position(recovery_entry) - )) water_recovery.call("_finish_recovery") assert(player.is_movement_enabled()) diff --git a/tests/fishing_multiplayer_validation.gd b/tests/fishing_multiplayer_validation.gd index 9d7ef7f..73de1bd 100644 --- a/tests/fishing_multiplayer_validation.gd +++ b/tests/fishing_multiplayer_validation.gd @@ -63,38 +63,12 @@ func _run_host() -> void: var spawn_service := main.get_node( "%PlayerSpawnService" ) as PlayerSpawnService - var network_home := main.get_node( - "%NetworkHomeService" - ) as NetworkHomeService - var home_world := main.get_node("%HomeWorldService") as HomeWorldService var remote_avatar: Player = spawn_service.get_avatar(remote_peer_id) assert(remote_avatar != null) assert(remote_avatar.bag.get_quantity(&"worms") == 0) - var remote_record := session.get_peer_record(remote_peer_id) - assert(remote_record != null) - assert(await _wait_for_home( - network_home, remote_record.identity_fingerprint - )) - var exterior_spawn: Transform3D = home_world.get_exterior_spawn_transform( - remote_record.identity_fingerprint - ) - var exterior_deadline: int = Time.get_ticks_msec() + 5000 - while ( - Time.get_ticks_msec() < exterior_deadline - and remote_avatar.global_position.distance_to(exterior_spawn.origin) > 0.25 - ): - await physics_frame - assert(remote_avatar.global_position.distance_to(exterior_spawn.origin) <= 0.25) - # Give the client enough local frames to observe the initial RV-door spawn - # before this test publishes its separate fishing-position teleport. - for _frame: int in 30: - await physics_frame - var cast_setup: Dictionary = await _find_clear_fishing_setup( - main, remote_avatar - ) - assert(not cast_setup.is_empty(), "no clear fishing setup was found") - var expected_position: Vector3 = cast_setup["position"] - var expected_direction: Vector3 = cast_setup["direction"] + remote_avatar.global_position = Vector3(-0.5, 3.95, 2.1) + var remote_visuals := remote_avatar.get_node("Visuals") as Node3D + remote_visuals.rotation.y = PI * 0.5 session.publish_authoritative_teleport(remote_peer_id) var aiming_deadline: int = Time.get_ticks_msec() + 15000 while ( @@ -108,7 +82,7 @@ func _run_host() -> void: == Player.FishingVisualPhase.CASTING ) var remote_position_error := ( - remote_avatar.global_position - expected_position + remote_avatar.global_position - Vector3(-0.5, 3.95, 2.1) ) remote_position_error.y = 0.0 assert( @@ -116,7 +90,7 @@ func _run_host() -> void: "remote casting position drifted: %s" % remote_avatar.global_position ) assert( - remote_avatar.get_facing_direction().dot(expected_direction) > 0.99, + remote_avatar.get_facing_direction().dot(Vector3.LEFT) > 0.99, "remote casting facing drifted: %s" % remote_avatar.get_facing_direction() ) @@ -222,23 +196,6 @@ func _run_client() -> void: joined = true break assert(joined) - var network_home := main.get_node( - "%NetworkHomeService" - ) as NetworkHomeService - var home_world := main.get_node("%HomeWorldService") as HomeWorldService - var local_fingerprint: String = session.get_local_identity_fingerprint() - assert(await _wait_for_home(network_home, local_fingerprint)) - var player := main.get("_player") as Player - var exterior_spawn: Transform3D = ( - home_world.get_exterior_spawn_transform(local_fingerprint) - ) - var home_spawn_deadline: int = Time.get_ticks_msec() + 5000 - while ( - Time.get_ticks_msec() < home_spawn_deadline - and player.global_position.distance_to(exterior_spawn.origin) > 0.25 - ): - await physics_frame - assert(player.global_position.distance_to(exterior_spawn.origin) <= 0.25) var service := main.get_node( "%NetworkFishingService" @@ -249,6 +206,7 @@ func _run_client() -> void: cast_rejections.append(message) ) var fishing_spot := main.get_node("%FishingSpot") as FishingSpotType + var player := main.get("_player") as Player var item_catalog := main.get("item_catalog") as ItemCatalog var worms: ItemData = item_catalog.get_item_by_id(&"worms") if player.bag.get_quantity(&"worms") == 0: @@ -259,20 +217,15 @@ func _run_client() -> void: "Visuals/CharacterRig/AnimationPlayer" ) as AnimationPlayer assert(character_animation_player.has_animation(&"retract_sit")) - # The host chooses and publishes a clear target after both RV manifests have - # settled. Waiting for that authoritative teleport also coordinates the - # animation-observation window between these two independent processes. - var placement_deadline: int = Time.get_ticks_msec() + 10000 + var placement_deadline: int = Time.get_ticks_msec() + 5000 while ( Time.get_ticks_msec() < placement_deadline - and player.global_position.distance_to(exterior_spawn.origin) <= 1.0 + and player.global_position.distance_to(Vector3(-0.5, 3.95, 2.1)) + > 0.25 ): await physics_frame - assert(player.global_position.distance_to(exterior_spawn.origin) > 1.0) - for _frame: int in 2: - await physics_frame - var expected_position: Vector3 = player.global_position - var expected_direction: Vector3 = player.get_facing_direction() + assert(player.global_position.distance_to(Vector3(-0.5, 3.95, 2.1)) <= 0.25) + assert(player.get_facing_direction().dot(Vector3.LEFT) > 0.99) player.set_casting_visual() assert( @@ -288,10 +241,7 @@ func _run_client() -> void: assert(player.is_sitting()) for _frame: int in 15: await physics_frame - assert(fishing_spot.state == FishingSpotType.FishingState.READY) - assert(fishing_spot.get("_active_player") == null) fishing_spot.call("_begin_aiming", player) - assert(fishing_spot.state == FishingSpotType.FishingState.AIMING_CAST) assert(player.is_movement_enabled()) assert( int(player.get("_fishing_visual_phase")) @@ -300,7 +250,6 @@ func _run_client() -> void: var cast_charge: float = 0.0 var target: Vector3 = Vector3.ZERO var cast_origin: Vector3 = fishing_spot.get("_cast_origin_position") - var target_diagnostics: Array[String] = [] for charge_step: int in range(1, 21): var candidate_charge := float(charge_step) / 20.0 fishing_spot.set("_cast_charge", candidate_charge) @@ -309,33 +258,14 @@ func _run_client() -> void: var candidate_surface := fishing_spot.get( "_aim_surface_sample" ) as FishingSurfaceSample - target_diagnostics.append( - "charge=%.2f surface=%s path=%s target=%s" % [ - candidate_charge, - candidate_surface.is_fishable(), - fishing_spot.get("_cast_path_is_clear"), - candidate_target, - ] - ) if ( candidate_surface.is_fishable() - and bool(fishing_spot.get("_cast_path_is_clear")) + and fishing_spot.is_cast_path_clear(cast_origin, candidate_target) ): cast_charge = candidate_charge target = candidate_target break - assert( - cast_charge > 0.0, - ( - "no clear fishable test target was found; position=%s facing=%s " - + "expected=%s diagnostics=%s" - ) % [ - player.global_position, - player.get_facing_direction(), - expected_direction, - target_diagnostics, - ], - ) + assert(cast_charge > 0.0, "no clear fishable test target was found") var evidence: Dictionary = fishing_spot.call("_build_network_evidence") fishing_spot.set("state", FishingSpotType.FishingState.CASTING) player.set_movement_enabled(false) @@ -458,79 +388,3 @@ func _create_initialized_main() -> Node: await process_frame assert(bool(main.get("_application_initialized"))) return main - - -func _wait_for_home( - network_home: NetworkHomeService, - fingerprint: String, -) -> bool: - var deadline: int = Time.get_ticks_msec() + 10000 - while Time.get_ticks_msec() < deadline: - await process_frame - var manifests: Dictionary = network_home.get( - "_manifests_by_fingerprint" - ) - if manifests.has(fingerprint): - return true - return false - - -func _find_clear_fishing_setup(main: Node, player: Player) -> Dictionary: - var world := main.get_node("TestWorld") as TestWorld - var fishing_spot := main.get_node("%FishingSpot") as FishingSpotType - var regions: Array[FishableWaterRegion] = world.get_fishable_water_regions() - for region: FishableWaterRegion in regions: - var body := region.get_parent() as WaterBodyAuthoring - if body == null or body.surface_size.x <= 0.0 or body.surface_size.y <= 0.0: - continue - var center: Vector3 = region.global_position - var half_size: Vector2 = body.surface_size * 0.5 - var candidate_offsets: Array[Vector3] = [ - Vector3(half_size.x + 0.8, 0.0, 0.0), - Vector3(-half_size.x - 0.8, 0.0, 0.0), - Vector3(0.0, 0.0, half_size.y + 0.8), - Vector3(0.0, 0.0, -half_size.y - 0.8), - ] - for offset: Vector3 in candidate_offsets: - var position := Vector3( - center.x + offset.x, - region.get_surface_height() + 1.44, - center.z + offset.z, - ) - var direction: Vector3 = center - position - direction.y = 0.0 - direction = direction.normalized() - player.global_position = position - var visuals := player.get_node("Visuals") as Node3D - visuals.global_rotation.y = atan2(-direction.x, -direction.z) - for _frame: int in 2: - await physics_frame - var cast_origin: Vector3 = player.get_cast_origin_position() - var launch_position: Vector3 = player.get_fishing_rod_tip().global_position - for charge_step: int in range(1, 21): - var cast_charge := float(charge_step) / 20.0 - var cast_distance := lerpf( - fishing_spot.minimum_cast_distance, - fishing_spot.maximum_cast_distance, - cast_charge, - ) - var query_position := cast_origin + direction * cast_distance - var sample: FishingSurfaceSample = ( - fishing_spot.resolve_fishing_surface( - query_position, cast_origin.y - ) - ) - var target: Vector3 = sample.get_bobber_position( - fishing_spot.solid_bobber_clearance - ) - if ( - sample.is_fishable() - and fishing_spot.is_cast_path_clear( - launch_position, target - ) - ): - return { - "position": player.global_position, - "direction": direction, - } - return {} diff --git a/tests/gathering_showcase_validation.gd b/tests/gathering_showcase_validation.gd index 51d6f38..7099f7c 100644 --- a/tests/gathering_showcase_validation.gd +++ b/tests/gathering_showcase_validation.gd @@ -21,13 +21,9 @@ func _run() -> void: assert(bool(main.get("_application_initialized"))) var session := main.get_node("%NetworkSession") as NetworkSession - assert(bool(main.call( - "_apply_world", WorldLayout.STARTER_ISLAND, 1, true - ))) - assert(session.set_host_world(WorldLayout.STARTER_ISLAND, 1)) assert(session.start_private_host(TEST_PORT)) var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) + assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") for _frame: int in 8: await process_frame @@ -42,7 +38,7 @@ func _run() -> void: and game_ui != null and catalog != null ) - fishing_spot.minimum_showcase_duration = 1.0 + fishing_spot.minimum_showcase_duration = 0.15 assert(player.bag.add_item(&"crab_net")) assert(player.hotbar.assign_item(0, &"crab_net")) assert(player.hotbar.select_slot(0)) @@ -85,7 +81,7 @@ func _run() -> void: await process_frame assert(fishing_spot.get("_pending_catch") == crab_catch) Input.action_release("fish_primary") - fishing_spot.set("_showcase_input_lock_remaining", 0.0) + await create_timer(0.2).timeout await process_frame assert(bool(fishing_spot.get("_put_away_press_armed"))) Input.parse_input_event(pocket_event) diff --git a/tests/generated_world_runtime_validation.gd b/tests/generated_world_runtime_validation.gd index 076f1c9..f487ae7 100644 --- a/tests/generated_world_runtime_validation.gd +++ b/tests/generated_world_runtime_validation.gd @@ -12,9 +12,6 @@ const GeneratedLakePool: FishPool = preload( const GeneratedRiverPool: FishPool = preload( "res://fish/pools/generated_river_pool.tres" ) -const Gatherables: GatherableCatalog = preload( - "res://gathering/catalog/gatherable_catalog.tres" -) const PrecipitationOcclusionType = preload( "res://world/environment/precipitation_occlusion.gd" ) @@ -405,7 +402,7 @@ func _validate_generated_region( + generator.stacked_elevated_placement_keys().size() ) ) - _validate_elevated_cliff_feature(region, generator) + _validate_elevated_cliff_feature(generator) var shop := region.get_node("Interactables/FishingShopWorld") as Node3D var storage := region.get_node("Interactables/PlayerStorageBox") as Node3D @@ -539,12 +536,6 @@ func _validate_generated_region( -INF, 0.35, ) - _validate_crab_spawn_surfaces(region) - _validate_water_recovery_positions( - region, - grass_prop_surface_triangles, - sand_prop_surface_triangles, - ) var anchors := region.get_node( "GatherableAnchors/ReachableTreeTrunks" ) as GatherableAnchorSet3D @@ -695,7 +686,6 @@ func _validate_generated_region( func _validate_elevated_cliff_feature( - region: GeneratedWorldRegion, generator: TerrainChunkGenerator, ) -> void: var elevated_coordinates: Dictionary[Vector2i, StringName] = {} @@ -829,27 +819,6 @@ func _validate_elevated_cliff_feature( or generator.placement_keys()[low_index].begins_with("chunk_spawn@") ) assert(found_primary_ramp == not primary_is_third_tier) - var primary_surface_height := ( - generator.elevated_cliff_level_height - * (2.0 if primary_is_third_tier else 1.0) - ) - var primary_surface_position := generator.to_global( - generator.chunk_position(feature_center) - + Vector3.UP * primary_surface_height - ) - assert( - region.is_home_exterior_position_accessible(primary_surface_position) - == not primary_is_third_tier - ) - assert(region.is_home_exterior_position_accessible(generator.to_global( - generator.chunk_position(feature_center) - ))) - # A higher surface sharing a ramp-accessible base cell is still invalid - # unless that elevation has its own authored ramp connection. - assert(not region.is_home_exterior_position_accessible(generator.to_global( - generator.chunk_position(feature_center) - + Vector3.UP * (generator.elevated_cliff_level_height * 2.0) - ))) var generated := generator.get_generated_chunks_root() assert(generated != null) @@ -1277,58 +1246,6 @@ func _validate_decoration_transform( ) -func _validate_crab_spawn_surfaces(region: GeneratedWorldRegion) -> void: - var brown := Gatherables.get_entry(&"crab_brown") - var blue := Gatherables.get_entry(&"crab_blue") - assert(brown != null and blue != null) - assert(is_equal_approx( - region.get_saltwater_surface_height(), - GeneratedWorldRegion.WATER_HEIGHT, - )) - var brown_minimum := maxf( - brown.minimum_surface_y, - GeneratedWorldRegion.WATER_HEIGHT + brown.waterline_margin, - ) - var blue_maximum := minf( - blue.maximum_surface_y, - GeneratedWorldRegion.WATER_HEIGHT - blue.waterline_margin, - ) - assert(brown_minimum > blue_maximum) - var brown_triangles := region.get_spawn_surface_triangles( - brown.surface_materials, - brown_minimum, - 0.6, - brown.maximum_surface_y, - ) - var blue_triangles := region.get_spawn_surface_triangles( - blue.surface_materials, - blue.minimum_surface_y, - 0.6, - blue_maximum, - ) - assert(not brown_triangles.is_empty()) - assert(not blue_triangles.is_empty()) - assert( - brown.target_population_for_surface_area( - _surface_triangle_area(brown_triangles) - ) > brown.population - ) - assert( - blue.target_population_for_surface_area( - _surface_triangle_area(blue_triangles) - ) > blue.population - ) - - -func _surface_triangle_area(triangles: Array[PackedVector3Array]) -> float: - var area := 0.0 - for triangle: PackedVector3Array in triangles: - area += (triangle[1] - triangle[0]).cross( - triangle[2] - triangle[0] - ).length() * 0.5 - return area - - func _triangle_surface_height_at( position: Vector3, triangles: Array[PackedVector3Array], @@ -1351,76 +1268,6 @@ func _triangle_surface_height_at( return highest -func _validate_water_recovery_positions( - region: GeneratedWorldRegion, - grass_triangles: Array[PackedVector3Array], - sand_triangles: Array[PackedVector3Array], -) -> void: - var fresh_water_root := region.get_node( - "WaterBodies/FreshWaterBodies" - ) as Node3D - assert(fresh_water_root != null and fresh_water_root.get_child_count() > 0) - var fresh_water := fresh_water_root.get_child(0) as WaterBodyAuthoring - assert(fresh_water != null) - var entry_position := fresh_water.global_position - var fallback_position := region.get_player_spawn_transform().origin - var recovery_position := region.get_water_recovery_position( - entry_position, - fallback_position, - ) - assert(recovery_position.is_finite()) - assert( - _horizontal_distance_squared(entry_position, recovery_position) - < _horizontal_distance_squared(entry_position, fallback_position), - "Fresh-water recovery returned the distant generated-world spawn.", - ) - assert( - recovery_position.y - > GeneratedWorldRegion.WATER_HEIGHT - + GeneratedWorldRegion.WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE - ) - var recovery_triangles: Array[PackedVector3Array] = [] - recovery_triangles.append_array(grass_triangles) - recovery_triangles.append_array(sand_triangles) - var center_height := _triangle_surface_height_at( - recovery_position, - recovery_triangles, - ) - assert(center_height > -INF) - assert(absf(center_height - recovery_position.y) <= 0.01) - for direction: Vector2 in GeneratedWorldRegion.PROP_SURFACE_SAMPLE_DIRECTIONS: - var footprint_position := recovery_position + Vector3( - direction.x * GeneratedWorldRegion.WATER_RECOVERY_FOOTPRINT_RADIUS, - 0.0, - direction.y * GeneratedWorldRegion.WATER_RECOVERY_FOOTPRINT_RADIUS, - ) - var footprint_height := _triangle_surface_height_at( - footprint_position, - recovery_triangles, - ) - assert(footprint_height > -INF) - assert( - absf(footprint_height - recovery_position.y) - <= GeneratedWorldRegion.PROP_MAXIMUM_SUPPORT_HEIGHT_DIFFERENCE - ) - assert( - region.get_water_recovery_position( - entry_position, - fallback_position, - ).is_equal_approx(recovery_position) - ) - assert( - region.get_water_recovery_position( - Vector3(INF, 0.0, 0.0), - fallback_position, - ).is_equal_approx(fallback_position) - ) - - -func _horizontal_distance_squared(a: Vector3, b: Vector3) -> float: - return Vector2(a.x - b.x, a.z - b.z).length_squared() - - func _validate_tree_gatherable_anchors( region: GeneratedWorldRegion, decorations: Node3D, diff --git a/tests/home_spawn_multiplayer_validation.gd b/tests/home_spawn_multiplayer_validation.gd deleted file mode 100644 index 2855eb2..0000000 --- a/tests/home_spawn_multiplayer_validation.gd +++ /dev/null @@ -1,201 +0,0 @@ -extends SceneTree - -const MainScene: PackedScene = preload("res://main/main.tscn") -const TEST_PORT: int = 18222 - - -func _initialize() -> void: - call_deferred("_run") - - -func _run() -> void: - var arguments: PackedStringArray = OS.get_cmdline_user_args() - if arguments.has("host"): - await _run_host() - return - if arguments.has("client"): - await _run_client() - return - push_error("Home spawn validation needs host or client mode.") - quit(1) - - -func _run_host() -> void: - var main: Node = await _create_initialized_main() - var session := main.get_node("%NetworkSession") as NetworkSession - var network_home := main.get_node( - "%NetworkHomeService" - ) as NetworkHomeService - var home_world := main.get_node("%HomeWorldService") as HomeWorldService - var spawn_service := main.get_node( - "%PlayerSpawnService" - ) as PlayerSpawnService - var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(bool(main.call( - "_apply_world", WorldLayout.STARTER_ISLAND, 1, true - ))) - assert(bool(main.call( - "_prepare_host_world", WorldLayout.STARTER_ISLAND, 1 - ))) - assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) - assert(session.start_private_host(TEST_PORT)) - main.call("_enter_gameplay") - assert(session.set_host_open(true)) - - var host_fingerprint: String = session.get_local_identity_fingerprint() - assert(await _wait_for_home(network_home, host_fingerprint)) - var host_avatar: Player = spawn_service.get_avatar(1) - var host_spawn: Transform3D = home_world.get_exterior_spawn_transform( - host_fingerprint - ) - assert(_same_door_position(host_avatar.global_position, host_spawn.origin)) - assert(_faces_away_from_home(host_avatar, home_world, host_fingerprint)) - assert(network_home.request_enter(host_fingerprint)) - await physics_frame - host_avatar.global_position = home_world.get_interior_exit_position( - host_fingerprint - ) - assert(network_home.request_exit()) - await physics_frame - assert(_same_door_position(host_avatar.global_position, host_spawn.origin)) - assert(_faces_away_from_home(host_avatar, home_world, host_fingerprint)) - - var remote_peer_id: int = await _wait_for_remote_peer(session) - assert(remote_peer_id > 1) - var remote_record := session.get_peer_record(remote_peer_id) - var remote_fingerprint: String = remote_record.identity_fingerprint - assert(await _wait_for_home(network_home, remote_fingerprint)) - var remote_avatar: Player = spawn_service.get_avatar(remote_peer_id) - var remote_spawn: Transform3D = home_world.get_exterior_spawn_transform( - remote_fingerprint - ) - assert(_same_door_position( - remote_avatar.global_position, remote_spawn.origin - )) - assert(_faces_away_from_home( - remote_avatar, home_world, remote_fingerprint - )) - - var disconnect_deadline: int = Time.get_ticks_msec() + 12000 - while ( - Time.get_ticks_msec() < disconnect_deadline - and session.is_authenticated_peer(remote_peer_id) - ): - await process_frame - assert(not session.is_authenticated_peer(remote_peer_id)) - print("Home spawn multiplayer host validation: PASS") - await _cleanup(main, session) - - -func _run_client() -> void: - var main: Node = await _create_initialized_main() - main.call( - "_on_title_join_game_requested", - "127.0.0.1:%d" % TEST_PORT, - ) - var session := main.get_node("%NetworkSession") as NetworkSession - var network_home := main.get_node( - "%NetworkHomeService" - ) as NetworkHomeService - var home_world := main.get_node("%HomeWorldService") as HomeWorldService - var deadline: int = Time.get_ticks_msec() + 20000 - while Time.get_ticks_msec() < deadline: - await process_frame - if session.state == NetworkSession.State.VERIFYING_SERVER_IDENTITY: - main.call("_confirm_server_trust") - if session.is_joined_client() and bool(main.get("_gameplay_started")): - break - assert(session.is_joined_client()) - var fingerprint: String = session.get_local_identity_fingerprint() - assert(await _wait_for_home(network_home, fingerprint)) - var player := main.get("_player") as Player - var exterior_spawn: Transform3D = ( - home_world.get_exterior_spawn_transform(fingerprint) - ) - deadline = Time.get_ticks_msec() + 5000 - while ( - Time.get_ticks_msec() < deadline - and not _same_door_position( - player.global_position, exterior_spawn.origin - ) - ): - await physics_frame - assert(_same_door_position( - player.global_position, exterior_spawn.origin - )) - assert(_faces_away_from_home(player, home_world, fingerprint)) - print("Home spawn multiplayer client validation: PASS") - await _cleanup(main, session) - - -func _create_initialized_main() -> Node: - root.size = Vector2i(1280, 720) - var main: Node = MainScene.instantiate() - root.add_child(main) - for _frame: int in 4: - await process_frame - if not bool(main.get("_application_initialized")): - main.call("_activate_selected_data_path", "", true) - for _frame: int in 8: - await process_frame - assert(bool(main.get("_application_initialized"))) - return main - - -func _wait_for_home( - network_home: NetworkHomeService, - fingerprint: String, -) -> bool: - var deadline: int = Time.get_ticks_msec() + 10000 - while Time.get_ticks_msec() < deadline: - await process_frame - var manifests: Dictionary = network_home.get( - "_manifests_by_fingerprint" - ) - if manifests.has(fingerprint): - return true - return false - - -func _faces_away_from_home( - avatar: Player, - home_world: HomeWorldService, - fingerprint: String, -) -> bool: - var exterior_basis: Basis = home_world.get_exterior_transform( - fingerprint - ).basis - var outward_direction := exterior_basis.z - outward_direction.y = 0.0 - if outward_direction.is_zero_approx(): - return false - return avatar.get_facing_direction().dot( - outward_direction.normalized() - ) >= 0.99 - - -func _wait_for_remote_peer(session: NetworkSession) -> int: - var deadline: int = Time.get_ticks_msec() + 20000 - while Time.get_ticks_msec() < deadline: - await process_frame - for peer_id: int in session.get_authenticated_peer_ids(): - if peer_id != session.get_local_peer_id(): - return peer_id - return 0 - - -func _same_door_position(first: Vector3, second: Vector3) -> bool: - return ( - Vector2(first.x, first.z).distance_to(Vector2(second.x, second.z)) - < 0.08 - and absf(first.y - second.y) < 0.3 - ) - - -func _cleanup(main: Node, session: NetworkSession) -> void: - session.disconnect_session("") - main.queue_free() - for _frame: int in 4: - await process_frame - await create_timer(0.1).timeout - quit() diff --git a/tests/home_spawn_multiplayer_validation.gd.uid b/tests/home_spawn_multiplayer_validation.gd.uid deleted file mode 100644 index 06150f2..0000000 --- a/tests/home_spawn_multiplayer_validation.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bndtkip5p6xma diff --git a/tests/job_system_validation.gd b/tests/job_system_validation.gd index 65b7164..1fe5ceb 100644 --- a/tests/job_system_validation.gd +++ b/tests/job_system_validation.gd @@ -21,17 +21,13 @@ func _run() -> void: assert(bool(main.get("_application_initialized"))) var session := main.get_node("%NetworkSession") as NetworkSession assert(session != null) - assert(bool(main.call( - "_apply_world", WorldLayout.STARTER_ISLAND, 1, true - ))) - assert(session.set_host_world(WorldLayout.STARTER_ISLAND, 1)) var occupied_port := PacketPeerUDP.new() assert(occupied_port.bind(18137, "127.0.0.1") == OK) assert(session.start_private_host(18137, 3)) assert(session.get_host_port() == 18138) occupied_port.close() var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) + assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") await physics_frame await physics_frame @@ -209,11 +205,8 @@ func _run() -> void: ) assert(bool(decoded.get("ok", false))) var save_data: Dictionary = decoded["data"] - assert(int(save_data.get("save_version", -1)) == 11) + assert(int(save_data.get("save_version", -1)) == 10) assert(PlayerJobService.validate_save_data(save_data.get("jobs", {}))) - assert(not PlayerHomeState.sanitize_save_data( - save_data.get("home", {}) - ).is_empty()) _validate_pause_session_switch(main, session) main.queue_free() diff --git a/tests/late_join_multiplayer_validation.gd b/tests/late_join_multiplayer_validation.gd index 6ea544f..3813486 100644 --- a/tests/late_join_multiplayer_validation.gd +++ b/tests/late_join_multiplayer_validation.gd @@ -90,12 +90,11 @@ func _run_first_client() -> void: player.toggle_sitting() assert(player.is_sitting()) assert(player.bag.add_item(&"crab_net")) - # Slot zero begins selected with the starter rod. Replacing that slot does - # not emit a selection change, so use the next slot to exercise the same - # equipped-item path a player uses in normal gameplay. - assert(player.hotbar.assign_item(1, &"crab_net")) - assert(player.hotbar.select_slot(1)) - assert(await _wait_for_local_net(main, player)) + assert(player.hotbar.assign_item(0, &"crab_net")) + assert(player.hotbar.select_slot(0)) + for _frame: int in 2: + await process_frame + assert(bool(player.get("_active_item_is_net"))) assert(player.begin_animation_action(ACTIVE_ACTION)) await create_timer(0.5).timeout assert(StringName(str(player.get("_animation_action_id"))) == ACTIVE_ACTION) @@ -186,29 +185,6 @@ func _wait_for_registry_size(session: NetworkSession, expected: int) -> bool: return false -func _wait_for_local_net(main: Node, player: Player) -> bool: - var deadline: int = Time.get_ticks_msec() + 10000 - while Time.get_ticks_msec() < deadline: - # A joined client can finish its world transition one frame after the - # hotbar selection signal. Refresh through the production path until the - # ready fishing state accepts the equipped item. - main.call("_refresh_active_hotbar_item") - await process_frame - if bool(player.get("_active_item_is_net")): - return true - var fishing_spot: Node = main.get_node("%FishingSpot") - print( - "net equip wait timed out: ", - { - "fishing_state": int(fishing_spot.get("state")), - "selected_slot": player.hotbar.get_selected_slot(), - "selected_item": String(player.hotbar.get_selected_item_id()), - "owns_net": player.bag.owns_item(&"crab_net"), - }, - ) - return false - - func _history_contains(service: NetworkChatService, body: String) -> bool: return service.get_history().any( func(message: Dictionary) -> bool: diff --git a/tests/logbook_runtime_validation.gd b/tests/logbook_runtime_validation.gd index e33e551..75fc47f 100644 --- a/tests/logbook_runtime_validation.gd +++ b/tests/logbook_runtime_validation.gd @@ -2,7 +2,6 @@ extends SceneTree const MainScene = preload("res://main/main.tscn") const FishCatchType = preload("res://fish/fish_catch.gd") -const MENU_TRANSITION_SETTLE_SECONDS := 3.0 func _initialize() -> void: @@ -37,16 +36,12 @@ func _run() -> void: var logbook := player_menu.get_node("%CatalogLogbook") as LogbookPage var backdrop := main.get_node("%PlayerMenuBackdrop") as ColorRect var hotbar := game_ui.get_node("%Hotbar") as Control - var fishing_spot := main.get("_fishing_spot") as FishingSpot - assert(logbook.get("_fishing_spot") == fishing_spot) - assert(logbook.get("_world_time") == main.get("_world_time")) - assert(logbook.get("_world_weather") == main.get("_world_weather")) var shortcut := InputEventKey.new() shortcut.pressed = true shortcut.physical_keycode = KEY_L assert(bool(player_menu.call("_handle_direct_page_shortcut", shortcut))) - await create_timer(MENU_TRANSITION_SETTLE_SECONDS).timeout + await create_timer(2.2).timeout assert(player_menu.visible) assert(logbook.visible) assert(backdrop.visible) @@ -56,18 +51,6 @@ func _run() -> void: var entry_buttons: Dictionary = logbook.get("_entry_buttons") assert(entry_buttons.size() == 108) await _capture_if_requested("-unknown") - logbook.call("_set_index_open", true, false) - await process_frame - assert(bool(logbook.get("_index_open"))) - assert((logbook.get("_index_body") as Control).visible) - await _capture_if_requested("-index") - logbook.set_compact_presentation(true) - await process_frame - await _capture_if_requested("-index-compact") - logbook.set_compact_presentation(false) - await process_frame - logbook.call("_set_index_open", false, false) - await process_frame logbook.call( "_select_category", WaterType.Type.FRESH_WATER ) @@ -85,29 +68,13 @@ func _run() -> void: logbook.call("_select_entry", &"bluegill", &"bluegill") await create_timer(0.2).timeout await _capture_if_requested("-known") - var bluegill := ( - main.get("fish_catalog") as FishPool - ).get_fish_by_id(&"bluegill") - var bluegill_available_now := fishing_spot.is_species_available_now(bluegill) - var index_options := logbook.get("_index_options") as Dictionary - index_options["availability"] = ( - LogbookCatalog.AvailabilityFilter.AVAILABLE_NOW - ) - logbook.notify_availability_context_changed() - await process_frame - assert( - (logbook.get("_entry_buttons") as Dictionary).has(&"bluegill") - == bluegill_available_now - ) - logbook.call("_reset_index") - await process_frame assert(bool(player_menu.call("_handle_direct_page_shortcut", shortcut))) - await create_timer(MENU_TRANSITION_SETTLE_SECONDS).timeout + await create_timer(2.2).timeout assert(not player_menu.visible) assert(bool(player_menu.call("_handle_direct_page_shortcut", shortcut))) - await create_timer(MENU_TRANSITION_SETTLE_SECONDS).timeout + await create_timer(2.2).timeout player_menu.call( "_show_section_immediate", PlayerMenu.Section.PROFILE ) @@ -143,7 +110,7 @@ func _validate_save_round_trip( assert(catalog != null) assert(catalog.candidates.size() == 316) var active_species := LogbookCatalog.ordered_species(catalog.candidates) - assert(active_species.size() == 314) + assert(active_species.size() == 313) # Keep this runtime save fixture within maximum player capacity while the # focused catalog validation exercises every active species individually. var round_trip_species: Array[FishData] = [] @@ -164,14 +131,12 @@ func _validate_save_round_trip( player.inventory_layout.restore_backpack_level( PlayerInventoryLayout.MAX_BACKPACK_LEVEL ) - var home_state := main.get("_home_state") as PlayerHomeState - assert(home_state != null) - assert(home_state.upgrade_to_next_tier()) + player.cooler_capacity.restore_level(PlayerCoolerCapacity.MAX_LEVEL) assert( player.inventory_layout.get_backpack_level() == PlayerInventoryLayout.MAX_BACKPACK_LEVEL ) - assert(player.cooler_capacity.get_capacity() == 18) + assert(player.cooler_capacity.get_level() == PlayerCoolerCapacity.MAX_LEVEL) player.inventory.set_inventory_layout(null) for index: int in 4: _add_test_catch(player, round_trip_species[index]) diff --git a/tests/logbook_validation.gd b/tests/logbook_validation.gd index 67747a5..4810e0a 100644 --- a/tests/logbook_validation.gd +++ b/tests/logbook_validation.gd @@ -17,8 +17,6 @@ const CatalogResource: FishPoolType = preload( "res://fish/pools/fish_catalog.tres" ) -var _query_available_id: StringName - func _initialize() -> void: call_deferred("_run") @@ -37,7 +35,6 @@ func _run() -> void: quit() return _validate_catalog() - _validate_catalog_query() await _validate_page() print("Logbook validation: PASS") quit() @@ -46,7 +43,7 @@ func _run() -> void: func _validate_catalog() -> void: assert(CatalogResource.candidates.size() == 316) var ordered := LogbookCatalog.ordered_species(CatalogResource.candidates) - assert(ordered.size() == 314) + assert(ordered.size() == 313) var previous_number: int = 0 var catalog_numbers: Dictionary[int, bool] = {} for fish: FishDataType in CatalogResource.candidates: @@ -71,348 +68,6 @@ func _validate_catalog() -> void: ) -func _validate_catalog_query() -> void: - var collection := CollectionLogType.new() - var inventory := FishInventoryType.new() - var fresh_species: Array[FishDataType] = [] - for fish: FishDataType in LogbookCatalog.ordered_species( - CatalogResource.candidates - ): - if LogbookCatalog.category_for(fish) == LogbookCatalog.Category.FRESH_WATER: - fresh_species.append(fish) - assert(fresh_species.size() == 108) - var first := fresh_species[0] - var second := fresh_species[1] - var unknown := fresh_species[2] - for quality: int in FishQualityType.TIER_COUNT: - collection.record_catch(first.id, quality) - collection.record_catch(second.id, FishQualityType.Tier.BORING) - var held := _make_test_catch(first, true) - assert(inventory.add_catch(held)) - - var options := _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - var filtered := LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.size() == 2) - assert(first in filtered and second in filtered) - - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.size() == fresh_species.size() - 2) - options["sort"] = LogbookCatalog.SortMode.CATALOG_NUMBER - options["descending"] = true - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.front() == fresh_species.back()) - assert(filtered.back() == unknown) - - options["show"] = LogbookCatalog.DiscoveryFilter.INCOMPLETE - options["descending"] = false - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [second]) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["sort"] = LogbookCatalog.SortMode.CATCH_COUNT - options["descending"] = true - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first, second]) - - options = _default_query_options() - options["search"] = unknown.display_name - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered.is_empty()) - options["search"] = str(unknown.catalog_number) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [unknown]) - - options = _default_query_options() - options["holding"] = LogbookCatalog.HoldingFilter.FAVORITED - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first]) - options["holding"] = LogbookCatalog.HoldingFilter.IN_COOLER - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first]) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["group"] = String(first.collection_group) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.collection_group == first.collection_group) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["rarity"] = ( - int(first.rarity) + LogbookCatalog.RarityFilter.COMMON - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.rarity == first.rarity) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["method"] = ( - int(first.collection_method) + LogbookCatalog.MethodFilter.FISHING - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.collection_method == first.collection_method) - - var first_season := -1 - for season: int in 4: - if first.is_available_in_season(season): - first_season = season - break - assert(first_season >= 0) - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["season"] = ( - first_season + LogbookCatalog.SeasonFilter.SPRING - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - for fish: FishDataType in filtered: - assert(fish.is_available_in_season(first_season)) - - var first_is_available_by_day := ( - first.availability == null or first.availability.allow_day - ) - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["time"] = ( - LogbookCatalog.TimeFilter.DAY - if first_is_available_by_day - else LogbookCatalog.TimeFilter.NIGHT - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(first in filtered) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.CAUGHT - options["sort"] = LogbookCatalog.SortMode.QUALITY_PROGRESS - options["descending"] = true - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered == [first, second]) - for sort_mode: LogbookCatalog.SortMode in [ - LogbookCatalog.SortMode.NAME, - LogbookCatalog.SortMode.RARITY, - LogbookCatalog.SortMode.VALUE, - LogbookCatalog.SortMode.WEIGHT, - ]: - options = _default_query_options() - options["sort"] = sort_mode - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(filtered[0] in [first, second]) - assert(filtered[1] in [first, second]) - assert(filtered[0] != filtered[1]) - assert(filtered[2] == unknown) - - _query_available_id = second.id - options = _default_query_options() - options["availability"] = ( - LogbookCatalog.AvailabilityFilter.AVAILABLE_NOW - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - Callable(self, "_query_species_available_now"), - ) - assert(filtered == [second]) - - _query_available_id = unknown.id - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - options["rarity"] = ( - int(unknown.rarity) + LogbookCatalog.RarityFilter.COMMON - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - Callable(self, "_query_species_available_now"), - ) - assert(filtered == [unknown]) - - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - options["group"] = String(unknown.collection_group) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(unknown in filtered) - assert( - String(unknown.collection_group) - in LogbookCatalog.available_groups( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - ) - ) - - var unknown_is_available_by_day := ( - unknown.availability == null or unknown.availability.allow_day - ) - options = _default_query_options() - options["show"] = LogbookCatalog.DiscoveryFilter.MISSING - options["time"] = ( - LogbookCatalog.TimeFilter.DAY - if unknown_is_available_by_day - else LogbookCatalog.TimeFilter.NIGHT - ) - filtered = LogbookCatalog.filtered_species( - CatalogResource.candidates, - LogbookCatalog.Category.FRESH_WATER, - collection, - inventory, - options, - ) - assert(unknown in filtered) - inventory.free() - collection.free() - - -func _default_query_options() -> Dictionary: - return { - "search": "", - "show": LogbookCatalog.DiscoveryFilter.ALL, - "sort": LogbookCatalog.SortMode.CATALOG_NUMBER, - "descending": false, - "availability": LogbookCatalog.AvailabilityFilter.ANY, - "season": LogbookCatalog.SeasonFilter.ANY, - "time": LogbookCatalog.TimeFilter.ANY, - "rarity": LogbookCatalog.RarityFilter.ANY, - "method": LogbookCatalog.MethodFilter.ANY, - "holding": LogbookCatalog.HoldingFilter.ANY, - "group": "", - } - - -func _make_test_catch(fish: FishDataType, favorited: bool) -> FishCatchType: - var fish_catch := FishCatchType.new() - fish_catch.fish = fish - fish_catch.fish_id = fish.id - fish_catch.weight_lb = fish.get_minimum_weight() - fish_catch.display_scale = fish.get_display_scale_for_weight( - fish_catch.weight_lb - ) - fish_catch.quality = FishQualityType.Tier.BORING - fish_catch.sale_value = fish.get_sale_value_for_weight(fish_catch.weight_lb) - fish_catch.is_favorited = favorited - fish_catch.ensure_identity() - return fish_catch - - -func _query_species_available_now(fish: FishDataType) -> bool: - return fish != null and fish.id == _query_available_id - - func _validate_page() -> void: var collection := CollectionLogType.new() var inventory := FishInventoryType.new() @@ -436,10 +91,6 @@ func _validate_page() -> void: assert(outer_margin.get_theme_constant("margin_right") == 160) assert(outer_margin.get_theme_constant("margin_bottom") == 106) assert(is_equal_approx(book.offset_top, 65.0)) - page.call("_set_index_open", true, false) - await process_frame - _validate_index_content_bounds(page) - page.call("_set_index_open", false, false) page.set_compact_presentation(false) await process_frame assert(outer_margin.get_theme_constant("margin_left") == 52) @@ -507,39 +158,6 @@ func _validate_page() -> void: ) var initial_detail_body := page.get("_detail_body") as VBoxContainer assert(not initial_detail_body.get_parent() is ScrollContainer) - var index_tab := page.get("_index_tab") as Button - var index_body := page.get("_index_body") as VBoxContainer - assert(index_tab != null and index_body != null) - assert(index_tab.size == LogbookPage.INDEX_TAB_SIZE) - assert( - not index_tab.get_global_rect().intersects( - (category_tabs.back() as Button).get_global_rect() - ) - ) - page.call("_set_index_open", true, false) - await process_frame - assert(index_body.visible) - assert(not initial_detail_body.visible) - assert((page.get("_index_option_buttons") as Dictionary).size() == 10) - _validate_index_content_bounds(page) - page.call( - "_cycle_index_option", - &"show", - 1, - ) - await process_frame - assert((page.get("_entry_buttons") as Dictionary).is_empty()) - assert( - (page.get("_empty_state") as Label).text - == "No entries match this index." - ) - page.call("_reset_index") - await process_frame - assert((page.get("_entry_buttons") as Dictionary).size() == 108) - page.call("_set_index_open", false, false) - await process_frame - assert(not index_body.visible) - assert(initial_detail_body.visible) assert( page.call("_entry_label_text", "flathead catfish") == "flathead\ncatfish" @@ -640,10 +258,7 @@ func _validate_page() -> void: page.call("_select_category", LogbookCatalog.Category.SHELLFISH) await create_timer(0.25).timeout - assert((page.get("_entry_buttons") as Dictionary).size() == 3) - assert( - (page.get("_entry_buttons") as Dictionary).has(&"unknown_3902") - ) + assert((page.get("_entry_buttons") as Dictionary).size() == 2) assert( (page.get("_entry_buttons") as Dictionary).has(&"unknown_3906") ) @@ -843,19 +458,8 @@ func _validate_page() -> void: (page.get("_portrait_overlay_backdrop") as Button).pressed.emit() await process_frame - var live_index_options := page.get("_index_options") as Dictionary - live_index_options["holding"] = LogbookCatalog.HoldingFilter.IN_COOLER - page.call("_refresh_catalog") - await process_frame - assert( - (page.get("_entry_buttons") as Dictionary).keys() - == [&"bluegill"] - ) inventory.remove_catch_by_id(fish_catch.catch_id) await process_frame - assert((page.get("_entry_buttons") as Dictionary).is_empty()) - page.call("_reset_index") - await process_frame assert(not _detail_text(page).contains("number owned")) page.queue_free() collection.queue_free() @@ -870,54 +474,6 @@ func _detail_text(page: LogbookPage) -> String: return "\n".join(values) -func _validate_index_content_bounds(page: LogbookPage) -> void: - var body := page.get("_index_body") as VBoxContainer - assert(body != null and body.visible) - var right_page := body.get_parent() as Control - assert(right_page != null) - var page_rect := right_page.get_global_rect() - var body_rect := body.get_global_rect() - var protected_rect := Rect2( - page_rect.position + Vector2( - LogbookPage.INDEX_CONTENT_LEFT_INSET, - LogbookPage.INDEX_CONTENT_TOP_INSET, - ), - page_rect.size - Vector2( - LogbookPage.INDEX_CONTENT_LEFT_INSET - + LogbookPage.INDEX_CONTENT_RIGHT_INSET, - LogbookPage.INDEX_CONTENT_TOP_INSET - + LogbookPage.INDEX_CONTENT_BOTTOM_INSET, - ), - ) - assert( - body_rect.position.x - >= page_rect.position.x + LogbookPage.INDEX_CONTENT_LEFT_INSET - 1.0 - ) - assert( - body_rect.end.x - <= page_rect.end.x - LogbookPage.INDEX_CONTENT_RIGHT_INSET + 1.0 - ) - assert( - body_rect.position.y >= page_rect.position.y, - "Index starts above the authored page: body=%s page=%s" - % [body_rect, page_rect], - ) - assert( - body_rect.end.y <= page_rect.end.y, - "Index extends below the authored page: body=%s page=%s" - % [body_rect, page_rect], - ) - var controls: Array = page.get("_index_controls") as Array - for value: Variant in controls: - var control := value as Control - if control != null and control.visible: - assert( - protected_rect.grow(2.0).encloses(control.get_global_rect()), - "Index control crossed the authored right-page boundary: %s" - % control.name, - ) - - func _descendant_label_text(parent: Node) -> String: var values: PackedStringArray = [] for node: Node in parent.find_children("*", "Label", true, false): diff --git a/tests/movement_multiplayer_validation.gd b/tests/movement_multiplayer_validation.gd index 05a668d..7073bea 100644 --- a/tests/movement_multiplayer_validation.gd +++ b/tests/movement_multiplayer_validation.gd @@ -593,22 +593,15 @@ func _run_host() -> void: var main: Node = await _create_initialized_main() var session := main.get_node("%NetworkSession") as NetworkSession var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(bool(main.call( - "_apply_world", + assert(session.set_host_world( WorldLayout.STARTER_ISLAND, NetworkProtocol.DEFAULT_WORLD_SEED, - true, - ))) - assert(bool(main.call( - "_prepare_host_world", - WorldLayout.STARTER_ISLAND, - NetworkProtocol.DEFAULT_WORLD_SEED, - ))) + )) + assert(session.start_private_host(TEST_PORT)) assert(save_manager.initialize_new_game( NetworkProtocol.DEFAULT_WORLD_SEED, WorldLayout.STARTER_ISLAND, )) - assert(session.start_private_host(TEST_PORT)) main.call("_enter_gameplay") for _frame: int in 4: await physics_frame @@ -620,40 +613,31 @@ func _run_host() -> void: # to settle before emitting the one-shot locomotion/action sequence. await create_timer(3.0).timeout var player := main.get("_player") as Player - var world := main.get_node("TestWorld") as TestWorld player.configure_network_remote(true) - await _reset_locomotion_test_position(player, session, world) - var input_sequence: int = 1 - input_sequence = await _sustain_locomotion_input( - player, input_sequence, 1.5, true - ) - await _reset_locomotion_test_position(player, session, world) - input_sequence = await _sustain_locomotion_input( - player, input_sequence, 1.5, false - ) - await _reset_locomotion_test_position(player, session, world) - input_sequence = await _sustain_locomotion_input( - player, input_sequence, 1.5, false, true, true - ) - input_sequence = await _sustain_locomotion_input( - player, input_sequence, 1.5, false, false, true - ) + player.apply_authoritative_network_input(_movement_input(1, true)) + await create_timer(2.5).timeout + player.apply_authoritative_network_input(_movement_input(2, false)) + await create_timer(2.5).timeout player.apply_authoritative_network_input( - _movement_input(input_sequence, false, false, true, &"draw", 1) + _movement_input(3, false, true, true) ) - input_sequence += 1 - await create_timer(1.0).timeout - player.apply_authoritative_network_input( - _movement_input( - input_sequence, false, false, true, &"strike", 2, true - ) - ) - input_sequence += 1 await create_timer(2.0).timeout player.apply_authoritative_network_input( - _movement_input(input_sequence, false, false, false, &"", 3) + _movement_input(4, false, false, true) ) - var disconnect_deadline: int = Time.get_ticks_msec() + 15000 + await create_timer(2.0).timeout + player.apply_authoritative_network_input( + _movement_input(5, false, false, true, &"draw", 1) + ) + await create_timer(1.0).timeout + player.apply_authoritative_network_input( + _movement_input(6, false, false, true, &"strike", 2, true) + ) + await create_timer(2.0).timeout + player.apply_authoritative_network_input( + _movement_input(7, false, false, false, &"", 3) + ) + var disconnect_deadline: int = Time.get_ticks_msec() + 8000 while ( Time.get_ticks_msec() < disconnect_deadline and session.is_authenticated_peer(remote_peer_id) @@ -702,27 +686,12 @@ func _run_client() -> void: var saw_net_strike_paused: bool = false var saw_animation_advance: bool = false var saw_dust: bool = false - var observed_animations: Dictionary = {} - var observed_locomotion_states: Dictionary = {} - var maximum_sneak_speed: float = 0.0 var previous_animation: StringName = &"" var previous_animation_position: float = -1.0 - var observation_deadline: int = Time.get_ticks_msec() + 22000 + var observation_deadline: int = Time.get_ticks_msec() + 18000 while Time.get_ticks_msec() < observation_deadline: await process_frame var current_animation: StringName = animation_player.current_animation - observed_animations[current_animation] = true - var target_locomotion_state: int = int(host_avatar.get( - "_network_target_locomotion_state" - )) - observed_locomotion_states[target_locomotion_state] = true - if target_locomotion_state == Player.LocomotionState.SNEAKING: - maximum_sneak_speed = maxf( - maximum_sneak_speed, - Vector2( - host_avatar.velocity.x, host_avatar.velocity.z - ).length(), - ) saw_running = saw_running or current_animation.begins_with("running") saw_walking = saw_walking or current_animation.begins_with("walking") saw_sneaking = saw_sneaking or current_animation == &"sneaking" @@ -763,44 +732,15 @@ func _run_client() -> void: and saw_dust ): break - var missing_observations: Array[String] = [] - if not saw_running: - missing_observations.append("running") - if not saw_walking: - missing_observations.append("walking") - if not saw_sneaking: - missing_observations.append("sneaking") - if not saw_idle_sneak: - missing_observations.append("idle_sneak") - if not saw_net_draw: - missing_observations.append("net_draw") - if not saw_net_strike: - missing_observations.append("net_strike") - if not saw_net_strike_paused: - missing_observations.append("net_strike_paused") - if not saw_animation_advance: - missing_observations.append("animation_advance") - if not saw_dust: - missing_observations.append("sprint_dust") - if not missing_observations.is_empty(): - push_error( - ( - "Missing movement observations %s; observed animations: %s; " - + "locomotion states: %s; maximum sneak speed: %.3f" - ) % [ - missing_observations, - observed_animations.keys(), - observed_locomotion_states.keys(), - maximum_sneak_speed, - ] - ) - session.disconnect_session("") - main.queue_free() - for _frame: int in 4: - await process_frame - await create_timer(0.1).timeout - quit(1) - return + assert(saw_running) + assert(saw_walking) + assert(saw_sneaking) + assert(saw_idle_sneak) + assert(saw_net_draw) + assert(saw_net_strike) + assert(saw_net_strike_paused) + assert(saw_animation_advance) + assert(saw_dust) print("Movement multiplayer client validation: PASS") session.disconnect_session("") main.queue_free() @@ -838,39 +778,6 @@ func _movement_input( } -func _sustain_locomotion_input( - player: Player, - starting_sequence: int, - duration_seconds: float, - sprinting: bool, - moving: bool = true, - sneaking: bool = false, -) -> int: - var sequence: int = starting_sequence - var deadline: int = ( - Time.get_ticks_msec() + roundi(duration_seconds * 1000.0) - ) - while Time.get_ticks_msec() < deadline: - player.apply_authoritative_network_input( - _movement_input(sequence, sprinting, moving, sneaking) - ) - sequence += 1 - await create_timer(0.1).timeout - return sequence - - -func _reset_locomotion_test_position( - player: Player, - session: NetworkSession, - world: TestWorld, -) -> void: - player.velocity = Vector3.ZERO - player.global_transform = world.get_player_spawn_transform() - session.publish_authoritative_teleport(1) - for _frame: int in 4: - await physics_frame - - func _create_initialized_main() -> Node: root.size = Vector2i(1280, 720) var main: Node = MainScene.instantiate() diff --git a/tests/player_experience_validation.gd b/tests/player_experience_validation.gd index a76a3dd..5e9dbef 100644 --- a/tests/player_experience_validation.gd +++ b/tests/player_experience_validation.gd @@ -143,7 +143,7 @@ func _validate_save_migration() -> void: version_five, 5, ) - assert(int(migrated.get("save_version", -1)) == 11) + assert(int(migrated.get("save_version", -1)) == 10) var experience_data: Dictionary = migrated.get("experience", {}) assert(int(experience_data.get("total_experience", -1)) == 0) var world_data: Dictionary = migrated.get("world", {}) @@ -155,9 +155,6 @@ func _validate_save_migration() -> void: assert( PlayerJobService.validate_save_data(migrated.get("jobs", {})) ) - assert(not PlayerHomeState.sanitize_save_data( - migrated.get("home", {}) - ).is_empty()) manager.queue_free() diff --git a/tests/progression_archive_validation.gd b/tests/progression_archive_validation.gd index 30f0df6..c3f9569 100644 --- a/tests/progression_archive_validation.gd +++ b/tests/progression_archive_validation.gd @@ -22,24 +22,11 @@ func _run() -> void: var save_manager := main.get("_save_manager") as PlayerSaveManager var player := main.get("_player") as Player var data_root := main.get("_data_root") as PlayerDataRoot - var home_state := main.get("_home_state") as PlayerHomeState - assert( - save_manager != null - and player != null - and data_root != null - and home_state != null - ) + assert(save_manager != null and player != null and data_root != null) assert(save_manager.initialize_new_game(864209)) save_manager.set_autosave_enabled(true) assert(player.wallet.restore_balance(4321)) assert(player.bag.add_item(&"coffee")) - assert(home_state.add_owned_decor(&"basic_cube")) - assert(not home_state.place_decor( - &"basic_cube", - Vector3.ZERO, - 0.0, - PlayerHomeState.PlacementMode.GRID, - ).is_empty()) assert(save_manager.save_now()) var save_path: String = str(save_manager.get("_save_path")) @@ -51,14 +38,7 @@ func _run() -> void: _assert_opaque(save_path) var decoded: Dictionary = ProgressionSaveCodec.read_local_save(save_path) assert(bool(decoded.get("ok", false))) - assert(int((decoded["data"] as Dictionary)["save_version"]) == 11) - assert(not PlayerHomeState.sanitize_save_data( - (decoded["data"] as Dictionary).get("home", {}) - ).is_empty()) - assert( - ((decoded["data"] as Dictionary)["home"] as Dictionary) - .get("placements", []).size() == 1 - ) + assert(int((decoded["data"] as Dictionary)["save_version"]) == 10) var legacy_container_path: String = data_root.progression_backup_directory().path_join( "netfishing-container.nfsave" ) @@ -94,8 +74,6 @@ func _run() -> void: assert(save_manager.load_player_data()) assert(player.wallet.get_balance() == 4321) assert(save_manager.get_world_seed() == 864209) - assert(home_state.get_owned_count(&"basic_cube") == 1) - assert(home_state.get_placed_count(&"basic_cube") == 1) var legacy_data: Dictionary = ( ProgressionSaveCodec.read_local_save(save_path)["data"] diff --git a/tests/surface_drawing_multiplayer_validation.gd b/tests/surface_drawing_multiplayer_validation.gd index f79700e..d0508b2 100644 --- a/tests/surface_drawing_multiplayer_validation.gd +++ b/tests/surface_drawing_multiplayer_validation.gd @@ -27,12 +27,10 @@ func _run_host() -> void: assert(bool(main.call( "_apply_world", WorldLayout.STARTER_ISLAND, 1, true ))) - assert(bool(main.call( - "_prepare_host_world", WorldLayout.STARTER_ISLAND, 1 - ))) - var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) + assert(session.set_host_world(WorldLayout.STARTER_ISLAND, 1)) assert(session.start_private_host(TEST_PORT)) + var save_manager := main.get("_save_manager") as PlayerSaveManager + assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") await physics_frame await physics_frame @@ -64,40 +62,6 @@ func _run_host() -> void: remote_peer_id ) assert(remote_record != null) - var network_home := main.get_node( - "%NetworkHomeService" - ) as NetworkHomeService - var home_world := main.get_node("%HomeWorldService") as HomeWorldService - var spawn_service := main.get_node( - "%PlayerSpawnService" - ) as PlayerSpawnService - assert(await _wait_for_home( - network_home, remote_record.identity_fingerprint - )) - var remote_avatar: Player = spawn_service.get_avatar(remote_peer_id) - assert(remote_avatar != null) - var exterior_spawn: Transform3D = home_world.get_exterior_spawn_transform( - remote_record.identity_fingerprint - ) - var exterior_deadline: int = Time.get_ticks_msec() + 5000 - while ( - Time.get_ticks_msec() < exterior_deadline - and remote_avatar.global_position.distance_to(exterior_spawn.origin) > 0.25 - ): - await physics_frame - assert(remote_avatar.global_position.distance_to(exterior_spawn.origin) <= 0.25) - var canvas_state: Dictionary = service.get_canvas_state(canvas_id) - var canvas_origin: Vector3 = SurfaceDrawingProtocol.array_to_vector( - canvas_state["origin"] - ) - remote_avatar.global_position = Vector3( - canvas_origin.x + 1.0, - player.global_position.y, - canvas_origin.z, - ) - session.publish_authoritative_teleport(remote_peer_id) - for _frame: int in 4: - await physics_frame assert(session.peer_supports_capability( remote_peer_id, SurfaceDrawingProtocol.CAPABILITY )) @@ -188,9 +152,6 @@ func _run_host() -> void: ): await process_frame assert(service.get_canvas_ids().size() == 1) - # Keep the second stamp observable on the client before the ban removes all - # artwork carrying that participant fingerprint. - await create_timer(1.0).timeout var player_list := main.get_node( "%NetworkPlayerListService" ) as NetworkPlayerListService @@ -260,20 +221,6 @@ func _run_client() -> void: var canvas_id: String = service.get_canvas_ids()[0] var snapshot: Dictionary = service.get_canvas_state(canvas_id) assert(str(_find_cell(snapshot, 7, 7).get("color_id", "")) == "coral") - var canvas_origin: Vector3 = SurfaceDrawingProtocol.array_to_vector( - snapshot["origin"] - ) - var placement_deadline: int = Time.get_ticks_msec() + 10000 - while ( - Time.get_ticks_msec() < placement_deadline - and player.global_position.distance_to(canvas_origin) - > NetworkSurfaceDrawingService.MAX_DRAW_DISTANCE + 2.0 - ): - await physics_frame - assert( - player.global_position.distance_to(canvas_origin) - <= NetworkSurfaceDrawingService.MAX_DRAW_DISTANCE + 2.0 - ) assert(service.request_cell_edits(canvas_id, [{ "x": 7, "y": 7, @@ -434,18 +381,3 @@ func _create_initialized_main() -> Node: await process_frame assert(bool(main.get("_application_initialized"))) return main - - -func _wait_for_home( - network_home: NetworkHomeService, - fingerprint: String, -) -> bool: - var deadline: int = Time.get_ticks_msec() + 10000 - while Time.get_ticks_msec() < deadline: - await process_frame - var manifests: Dictionary = network_home.get( - "_manifests_by_fingerprint" - ) - if manifests.has(fingerprint): - return true - return false diff --git a/tests/surface_drawing_runtime_validation.gd b/tests/surface_drawing_runtime_validation.gd index 85110cd..e04afc2 100644 --- a/tests/surface_drawing_runtime_validation.gd +++ b/tests/surface_drawing_runtime_validation.gd @@ -20,13 +20,9 @@ func _run() -> void: await process_frame assert(bool(main.get("_application_initialized"))) var session := main.get_node("%NetworkSession") as NetworkSession - assert(bool(main.call( - "_apply_world", WorldLayout.STARTER_ISLAND, 1, true - ))) - assert(session.set_host_world(WorldLayout.STARTER_ISLAND, 1)) assert(session.start_private_host(TEST_PORT)) var save_manager := main.get("_save_manager") as PlayerSaveManager - assert(save_manager.initialize_new_game(1, WorldLayout.STARTER_ISLAND)) + assert(save_manager.initialize_new_game()) main.call("_enter_gameplay") await physics_frame await physics_frame diff --git a/tests/unified_inventory_validation.gd b/tests/unified_inventory_validation.gd index 6928969..e9a0d85 100644 --- a/tests/unified_inventory_validation.gd +++ b/tests/unified_inventory_validation.gd @@ -35,41 +35,6 @@ func _run() -> void: hotbar.setup(bag, ItemCatalogResource, catches, layout) assert(PlayerInventoryLayout.INVENTORY_CAPACITIES == [9, 18, 27, 36]) assert(PlayerCoolerCapacity.CAPACITIES == [9, 18, 27, 36, 45, 54, 63, 72]) - var basic_cube: ItemData = ItemCatalogResource.get_item_by_id(&"basic_cube") - assert(basic_cube != null) - assert(basic_cube.icon != null) - assert(basic_cube.hotbar_allowed) - assert(bag.add_item(&"basic_cube")) - assert(layout.is_item_in_inventory(&"basic_cube")) - assert(hotbar.assign_item(0, &"basic_cube")) - assert(hotbar.get_item_id(0) == &"basic_cube") - assert( - layout.get_container( - PlayerInventoryLayout.EntryKind.ITEM, - &"basic_cube", - ) == PlayerInventoryLayout.InventoryContainer.HOTBAR - ) - assert(hotbar.clear_slot(0)) - assert(layout.is_item_in_inventory(&"basic_cube")) - assert(bag.remove_item(&"basic_cube")) - var packed_decor_slot: int = hotbar.find_first_empty_slot() - assert(packed_decor_slot == 0) - assert(layout.prepare_new_item_placement( - &"basic_cube", - PlayerInventoryLayout.InventoryContainer.HOTBAR, - packed_decor_slot, - )) - assert(bag.add_item(&"basic_cube")) - assert( - layout.get_container( - PlayerInventoryLayout.EntryKind.ITEM, - &"basic_cube", - ) == PlayerInventoryLayout.InventoryContainer.HOTBAR - ) - assert(hotbar.assign_item(packed_decor_slot, &"basic_cube")) - assert(hotbar.select_slot(packed_decor_slot)) - assert(hotbar.get_selected_item_id() == &"basic_cube") - assert(bag.remove_item(&"basic_cube")) assert(bag.add_item(&"coffee", 3)) assert(layout.get_inventory_count() == 1) diff --git a/tests/world_layout_validation.gd b/tests/world_layout_validation.gd index e6eec39..dd40fcf 100644 --- a/tests/world_layout_validation.gd +++ b/tests/world_layout_validation.gd @@ -100,14 +100,6 @@ func _validate_world_switching() -> void: assert(world.get_fishing_shop() != null) assert(world.get_player_storage() != null) assert(not world.get_fishable_water_regions().is_empty()) - var starter_safe_points := world.get_safe_respawn_points() - assert(not starter_safe_points.is_empty()) - var authored_safe_point: SafeRespawnPoint = starter_safe_points.back() - assert( - world.get_water_recovery_position( - authored_safe_point.global_position - ).is_equal_approx(authored_safe_point.global_position) - ) assert(world.activate_world(WorldLayout.GENERATED, 24680)) assert(world.get_world_layout() == WorldLayout.GENERATED) assert(world.get_generation_seed() == 24680) diff --git a/tests/world_spawn_multiplayer_validation.gd b/tests/world_spawn_multiplayer_validation.gd index f5ffd2e..45be081 100644 --- a/tests/world_spawn_multiplayer_validation.gd +++ b/tests/world_spawn_multiplayer_validation.gd @@ -2,10 +2,9 @@ extends SceneTree const MainScene: PackedScene = preload("res://main/main.tscn") const TEST_PORT: int = 18170 -const EXPECTED_POPULATION: int = 12 +const EXPECTED_POPULATION: int = 8 const EXPECTED_BY_TYPE: Dictionary = { &"crab_brown": 2, - &"crab_blue": 4, &"clam_manila": 3, &"beetle_stag_common": 3, } @@ -162,10 +161,6 @@ func _validate_population( (position as Vector3).y >= entry.minimum_surface_y - 0.001 ) - if entry.type_id == &"crab_brown": - assert((position as Vector3).y > -0.45) - elif entry.type_id == &"crab_blue": - assert((position as Vector3).y < -0.45) if expect_authoritative_state: var quality: int = int(state.get("quality", -1)) assert(FishQuality.is_valid(quality)) @@ -201,10 +196,7 @@ func _validate_respawn_budget(service: Node) -> void: false, true, ) - assert( - (service.get("_entities") as Dictionary).size() - == EXPECTED_POPULATION - crab_ids.size() - ) + assert((service.get("_entities") as Dictionary).size() == 6) var now: float = Time.get_ticks_msec() / 1000.0 var respawns: Array = service.get("_respawns") @@ -217,27 +209,18 @@ func _validate_respawn_budget(service: Node) -> void: respawns[index] = respawn service.call("_update_respawns") - assert( - (service.get("_entities") as Dictionary).size() - == EXPECTED_POPULATION - 1 - ) + assert((service.get("_entities") as Dictionary).size() == 7) assert((service.get("_respawns") as Array).size() == 1) var next_by_type: Dictionary = service.get("_next_respawn_by_type") var next_allowed: float = float(next_by_type.get(&"crab_brown", 0.0)) assert(next_allowed - now >= 179.0) service.call("_update_respawns") - assert( - (service.get("_entities") as Dictionary).size() - == EXPECTED_POPULATION - 1 - ) + assert((service.get("_entities") as Dictionary).size() == 7) assert((service.get("_respawns") as Array).size() == 1) next_by_type[&"crab_brown"] = now - 1.0 service.call("_update_respawns") - assert( - (service.get("_entities") as Dictionary).size() - == EXPECTED_POPULATION - ) + assert((service.get("_entities") as Dictionary).size() == 8) assert((service.get("_respawns") as Array).is_empty()) _validate_population(service, true) diff --git a/tests/world_spawn_protocol_validation.gd b/tests/world_spawn_protocol_validation.gd index e300808..a8365a1 100644 --- a/tests/world_spawn_protocol_validation.gd +++ b/tests/world_spawn_protocol_validation.gd @@ -16,18 +16,14 @@ const CalendarSeasonType = preload("res://world/calendar_season.gd") func _initialize() -> void: - call_deferred(&"_run") - - -func _run() -> void: - assert(NetworkProtocol.PROTOCOL_VERSION == 12) + assert(NetworkProtocol.PROTOCOL_VERSION == 11) assert( NetworkWorldSpawnProtocol.CAPABILITY == NetworkProtocol.WORLD_SPAWN_CAPABILITY ) assert(NetworkWorldSpawnProtocol.SNAPSHOT_ENTITIES_PER_ENVELOPE <= 4) _validate_catalog_statuses() - await _validate_billboard_presentation() + _validate_billboard_presentation() _validate_envelopes() print("World spawn protocol validation: PASS") quit() @@ -55,17 +51,6 @@ func _validate_catalog_statuses() -> void: assert(is_equal_approx(brown.minimum_respawn_spacing_seconds, 180.0)) assert(brown.required_tool_id == &"crab_net") assert(is_equal_approx(brown.minimum_surface_y, -0.44)) - assert( - brown.surface_water_relation - == GatherableData.SurfaceWaterRelation.ABOVE_SALT_WATER - ) - assert(is_equal_approx( - brown.surface_population_per_100_square_meters, - 0.15, - )) - assert(brown.maximum_surface_population == 24) - assert(brown.target_population_for_surface_area(100.0) == 2) - assert(brown.target_population_for_surface_area(7500.0) == 12) assert(FishCatalog.get_fish_by_id(&"crab_brown") == brown.catch_data) assert(not brown.catch_data.is_fishable()) var clam: GatherableData = Gatherables.get_entry(&"clam_manila") @@ -97,27 +82,11 @@ func _validate_catalog_statuses() -> void: assert(FishCatalog.get_fish_by_id(&"beetle_stag_common") == beetle.catch_data) assert(not beetle.catch_data.is_fishable()) - var blue: GatherableData = Gatherables.get_entry(&"crab_blue") - assert(blue != null and blue.is_available()) - assert(blue.catch_data.active) - assert(blue.is_available(CalendarSeasonType.Season.SUMMER)) - assert(blue.is_available(CalendarSeasonType.Season.FALL)) - assert(not blue.is_available(CalendarSeasonType.Season.WINTER)) - assert( - blue.surface_water_relation - == GatherableData.SurfaceWaterRelation.BELOW_SALT_WATER - ) - assert(is_equal_approx( - blue.surface_population_per_100_square_meters, - 0.4, - )) - assert(blue.maximum_surface_population == 16) - assert(blue.target_population_for_surface_area(100.0) == 4) - assert(blue.target_population_for_surface_area(2250.0) == 9) - assert(FishCatalog.get_fish_by_id(&"crab_blue") == blue.catch_data) - assert(not blue.catch_data.is_fishable()) - - for type_id: StringName in [&"crab_ghost", &"crab_dungeness"]: + for type_id: StringName in [ + &"crab_ghost", + &"crab_blue", + &"crab_dungeness", + ]: var entry: GatherableData = Gatherables.get_entry(type_id) assert(entry != null) assert(entry.is_valid()) @@ -136,9 +105,8 @@ func _validate_catalog_statuses() -> void: ) var available: Array[GatherableData] = Gatherables.get_available_entries() - assert(available.size() == 4) + assert(available.size() == 3) assert(available.has(brown)) - assert(available.has(blue)) assert(available.has(clam)) assert(available.has(beetle)) var winter_available: Array[GatherableData] = ( @@ -146,7 +114,6 @@ func _validate_catalog_statuses() -> void: ) assert(winter_available.has(brown)) assert(winter_available.has(clam)) - assert(not winter_available.has(blue)) assert(not winter_available.has(beetle)) var rng := RandomNumberGenerator.new() rng.seed = 24680 @@ -181,27 +148,8 @@ func _validate_billboard_presentation() -> void: assert(not sprite.shaded) gatherable.free() - var unconfigured_hotspot := WorldGatherableType.new() - unconfigured_hotspot.call("_ensure_water_spurt_visual") - assert( - unconfigured_hotspot.get_node_or_null( - "WaterSpurt/WaterDroplets" - ) == null - ) - unconfigured_hotspot.free() - - var clam: GatherableData = Gatherables.get_entry(&"clam_manila") - assert(clam != null) var hotspot := WorldGatherableType.new() - root.add_child(hotspot) - var configured_position := Vector3(24.0, 0.3, -11.0) - hotspot.configure( - "world:clam-presentation-regression", - clam, - configured_position, - 0.0, - ) - assert(hotspot.global_position.is_equal_approx(configured_position)) + hotspot.call("_ensure_water_spurt_visual") var hole := hotspot.get_node("WaterSpurt/BurrowMark") as MeshInstance3D assert(hole != null) assert(hole.cast_shadow == GeometryInstance3D.SHADOW_CASTING_SETTING_OFF) @@ -210,18 +158,6 @@ func _validate_billboard_presentation() -> void: assert(hole_material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED) assert(hole_material.transparency == BaseMaterial3D.TRANSPARENCY_DISABLED) assert(is_equal_approx(hole_material.albedo_color.a, 1.0)) - var particles := hotspot.get_node( - "WaterSpurt/WaterDroplets" - ) as CPUParticles3D - assert(particles != null and particles.emitting) - assert(particles.local_coords) - assert(particles.global_position.is_equal_approx( - configured_position + Vector3.UP * 0.035 - )) - hotspot.set_process(false) - await create_timer(0.6).timeout - await process_frame - assert(hotspot.get_node_or_null("WaterSpurt/WaterDroplets") == null) hotspot.queue_free() diff --git a/ui/decor_shop.gd b/ui/decor_shop.gd deleted file mode 100644 index d05d9eb..0000000 --- a/ui/decor_shop.gd +++ /dev/null @@ -1,331 +0,0 @@ -class_name DecorShop -extends Control - -const INPUT_OWNER: StringName = &"decor_shop" -const DecorCatalogType = preload("res://homes/decor_catalog.gd") -const UtilityPageStyleType = preload("res://ui/utility_page_style.gd") - -signal menu_visibility_changed(is_open: bool) -signal menu_exit_started - -var _player: Player -var _wallet: PlayerWallet -var _fishing_spot: FishingSpot -var _interaction: DecorShopInteraction -var _bag: PlayerBag -var _home_state: PlayerHomeState -var _network_shop: NetworkShopService -var _wallet_label: Label -var _owned_label: Label -var _feedback: Label -var _buy_button: Button -var _close_button: Button -var _transaction_pending: bool = false -var _prior_movement_enabled: bool = true -var _prior_camera_enabled: bool = true -var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE - - -func _ready() -> void: - UtilityPageStyleType.apply_page(self) - _build_ui() - hide() - - -func setup( - player: Player, - wallet: PlayerWallet, - fishing_spot: FishingSpot, - interaction: DecorShopInteraction, - bag: PlayerBag, - home_state: PlayerHomeState, - network_shop: NetworkShopService, -) -> void: - _player = player - _wallet = wallet - _fishing_spot = fishing_spot - _interaction = interaction - _bag = bag - _home_state = home_state - _network_shop = network_shop - if not _wallet.balance_changed.is_connected(_on_wallet_changed): - _wallet.balance_changed.connect(_on_wallet_changed) - if not _bag.contents_changed.is_connected(_refresh): - _bag.contents_changed.connect(_refresh) - if not _home_state.changed.is_connected(_refresh): - _home_state.changed.connect(_refresh) - if ( - _network_shop != null - and not _network_shop.local_purchase_pending.is_connected( - _on_purchase_pending - ) - ): - _network_shop.local_purchase_pending.connect(_on_purchase_pending) - _network_shop.local_purchase_finished.connect(_on_purchase_finished) - _refresh() - - -func set_world_interaction(interaction: DecorShopInteraction) -> void: - _interaction = interaction - - -func open_shop() -> bool: - if ( - visible - or _player == null - or _wallet == null - or _fishing_spot == null - or _interaction == null - or not _interaction.is_local_player_in_range() - or not _fishing_spot.can_open_fishing_shop() - ): - return false - _prior_movement_enabled = _player.is_movement_enabled() - _prior_camera_enabled = _player.is_camera_input_enabled() - _player.set_movement_enabled(false) - # Release any active right-click drag first. The shop must not remember the - # drag's transient captured mode because release may happen inside the UI. - _player.set_camera_input_enabled(false) - _prior_mouse_mode = _get_restorable_mouse_mode(Input.mouse_mode) - _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) - Input.mouse_mode = Input.MOUSE_MODE_VISIBLE - _transaction_pending = ( - _network_shop != null - and _network_shop.is_local_purchase_pending() - ) - _feedback.text = "" - _refresh() - show() - _buy_button.call_deferred("grab_focus") - menu_visibility_changed.emit(true) - return true - - -func close_shop(restore_controls: bool = true) -> void: - if not visible: - return - menu_exit_started.emit() - get_viewport().gui_release_focus() - hide() - if _fishing_spot != null: - _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) - if restore_controls and _player != null: - _player.set_movement_enabled(_prior_movement_enabled) - _player.set_camera_input_enabled(_prior_camera_enabled) - Input.mouse_mode = _prior_mouse_mode - menu_visibility_changed.emit(false) - - -func consume_escape() -> bool: - if not visible: - return false - close_shop() - return true - - -func close_for_range_exit() -> void: - close_shop() - - -func close_for_water_recovery() -> void: - close_shop(false) - - -func close_for_session_end() -> void: - close_shop(false) - - -func _get_restorable_mouse_mode(mouse_mode: Input.MouseMode) -> Input.MouseMode: - if mouse_mode == Input.MOUSE_MODE_CAPTURED: - return Input.MOUSE_MODE_VISIBLE - return mouse_mode - - -func _unhandled_input(event: InputEvent) -> void: - if not visible or not event.is_action_pressed("ui_cancel"): - return - close_shop() - get_viewport().set_input_as_handled() - - -func _purchase_cube() -> void: - if _network_shop == null or _transaction_pending: - return - var request_id: String = _network_shop.request_decor( - DecorCatalogType.BASIC_CUBE_ID - ) - if request_id.is_empty(): - _refresh() - - -func _on_purchase_pending(_request_id: String) -> void: - _transaction_pending = true - _feedback.text = "purchasing…" - _refresh() - - -func _on_purchase_finished( - _request_id: String, - accepted: bool, - message: String, - product_id: StringName, - category: int, - _quantity: int, - total_cost: int, -) -> void: - if ( - category != NetworkShopProtocol.ProductCategory.DECOR - or product_id != DecorCatalogType.BASIC_CUBE_ID - ): - return - _transaction_pending = false - _feedback.text = ( - "basic cube purchased · %d fishcoin spent" % total_cost - if accepted - else message.to_lower() - ) - _refresh() - - -func _on_wallet_changed(_balance: int, _delta: int) -> void: - _refresh() - - -func _refresh() -> void: - if _wallet_label == null: - return - var balance: int = _wallet.get_balance() if _wallet != null else 0 - var owned: int = ( - _home_state.get_owned_count(DecorCatalogType.BASIC_CUBE_ID) - if _home_state != null else 0 - ) - var carried: int = ( - _bag.get_quantity(DecorCatalogType.BASIC_CUBE_ID) - if _bag != null else 0 - ) - _wallet_label.text = "%d fishcoins" % balance - _owned_label.text = "owned %d · packed %d" % [owned, carried] - _buy_button.disabled = ( - _transaction_pending - or _wallet == null - or not _wallet.can_afford( - DecorCatalogType.get_price(DecorCatalogType.BASIC_CUBE_ID) - ) - or _bag == null - or not _bag.can_add_item(DecorCatalogType.BASIC_CUBE_ID, 1) - or owned >= DecorCatalogType.MAX_OWNED_PER_PRODUCT - ) - - -func _build_ui() -> void: - set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - z_index = 83 - mouse_filter = Control.MOUSE_FILTER_STOP - var blocker := ColorRect.new() - blocker.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - blocker.color = Color(0.02, 0.08, 0.10, 0.55) - blocker.mouse_filter = Control.MOUSE_FILTER_STOP - add_child(blocker) - - var panel := PanelContainer.new() - panel.set_anchors_preset(Control.PRESET_CENTER) - panel.position = Vector2(-440.0, -250.0) - panel.size = Vector2(880.0, 500.0) - panel.add_theme_stylebox_override( - "panel", - UtilityPageStyleType.rounded_style( - UtilityPageStyleType.OCEAN_PANEL_DEEP, 24 - ), - ) - add_child(panel) - var margin := MarginContainer.new() - for side: StringName in [ - &"margin_left", &"margin_top", &"margin_right", &"margin_bottom", - ]: - margin.add_theme_constant_override(side, 28) - panel.add_child(margin) - var layout := VBoxContainer.new() - layout.add_theme_constant_override("separation", 18) - margin.add_child(layout) - - var header := HBoxContainer.new() - header.add_theme_constant_override("separation", 12) - layout.add_child(header) - var title := Label.new() - title.text = "decor" - title.add_theme_font_size_override("font_size", 38) - title.size_flags_horizontal = Control.SIZE_EXPAND_FILL - header.add_child(title) - _wallet_label = Label.new() - _wallet_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT - _wallet_label.add_theme_font_size_override("font_size", 22) - header.add_child(_wallet_label) - - var item_panel := PanelContainer.new() - item_panel.size_flags_vertical = Control.SIZE_EXPAND_FILL - item_panel.add_theme_stylebox_override( - "panel", - UtilityPageStyleType.rounded_style( - UtilityPageStyleType.OCEAN_FIELD, 18 - ), - ) - layout.add_child(item_panel) - var item_margin := MarginContainer.new() - for side: StringName in [ - &"margin_left", &"margin_top", &"margin_right", &"margin_bottom", - ]: - item_margin.add_theme_constant_override(side, 24) - item_panel.add_child(item_margin) - var item_row := HBoxContainer.new() - item_row.add_theme_constant_override("separation", 28) - item_margin.add_child(item_row) - var preview := Panel.new() - preview.custom_minimum_size = Vector2(150.0, 150.0) - preview.add_theme_stylebox_override( - "panel", - UtilityPageStyleType.rounded_style(Color("b77a55"), 12), - ) - item_row.add_child(preview) - var details := VBoxContainer.new() - details.size_flags_horizontal = Control.SIZE_EXPAND_FILL - details.add_theme_constant_override("separation", 10) - item_row.add_child(details) - var item_name := Label.new() - item_name.text = DecorCatalogType.get_display_name( - DecorCatalogType.BASIC_CUBE_ID - ) - item_name.add_theme_font_size_override("font_size", 32) - details.add_child(item_name) - var description := Label.new() - description.text = DecorCatalogType.get_description( - DecorCatalogType.BASIC_CUBE_ID - ) - description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - description.add_theme_font_size_override("font_size", 20) - description.size_flags_vertical = Control.SIZE_EXPAND_FILL - details.add_child(description) - _owned_label = Label.new() - _owned_label.add_theme_font_size_override("font_size", 18) - details.add_child(_owned_label) - _buy_button = Button.new() - _buy_button.text = "buy · 1 fishcoin" - _buy_button.custom_minimum_size = Vector2(260.0, 58.0) - UtilityPageStyleType.apply_ocean_button(_buy_button) - _buy_button.pressed.connect(_purchase_cube) - details.add_child(_buy_button) - - var footer := HBoxContainer.new() - footer.add_theme_constant_override("separation", 12) - layout.add_child(footer) - _feedback = Label.new() - _feedback.size_flags_horizontal = Control.SIZE_EXPAND_FILL - _feedback.add_theme_font_size_override("font_size", 18) - footer.add_child(_feedback) - _close_button = Button.new() - _close_button.text = "close" - _close_button.custom_minimum_size = Vector2(150.0, 50.0) - UtilityPageStyleType.apply_ocean_button(_close_button) - _close_button.pressed.connect(close_shop) - footer.add_child(_close_button) - _buy_button.focus_neighbor_bottom = _buy_button.get_path_to(_close_button) - _close_button.focus_neighbor_top = _close_button.get_path_to(_buy_button) diff --git a/ui/decor_shop.gd.uid b/ui/decor_shop.gd.uid deleted file mode 100644 index ed65203..0000000 --- a/ui/decor_shop.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://810ck3mvbn5v diff --git a/ui/decor_shop.tscn b/ui/decor_shop.tscn deleted file mode 100644 index 2b26f04..0000000 --- a/ui/decor_shop.tscn +++ /dev/null @@ -1,14 +0,0 @@ -[gd_scene load_steps=2 format=3] - -[ext_resource type="Script" path="res://ui/decor_shop.gd" id="1_script"] - -[node name="DecorShop" type="Control"] -unique_name_in_owner = true -visible = false -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_script") diff --git a/ui/fishing_danger_vignette.gdshader b/ui/fishing_danger_vignette.gdshader deleted file mode 100644 index 8eee3df..0000000 --- a/ui/fishing_danger_vignette.gdshader +++ /dev/null @@ -1,10 +0,0 @@ -shader_type canvas_item; - -uniform float intensity : hint_range(0.0, 1.0) = 0.0; - -void fragment() { - vec2 centered = UV * 2.0 - 1.0; - centered.x *= 0.56; - float edge = smoothstep(0.42, 1.02, length(centered)); - COLOR = vec4(0.16, 0.018, 0.025, edge * intensity * 0.72); -} diff --git a/ui/fishing_danger_vignette.gdshader.uid b/ui/fishing_danger_vignette.gdshader.uid deleted file mode 100644 index 578ec8b..0000000 --- a/ui/fishing_danger_vignette.gdshader.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c1qcj3m4b10x4 diff --git a/ui/fishing_shop.gd b/ui/fishing_shop.gd index 7cc8fa8..ad8247c 100644 --- a/ui/fishing_shop.gd +++ b/ui/fishing_shop.gd @@ -15,6 +15,9 @@ const PlayerFishingUpgradesType = preload( "res://progression/player_fishing_upgrades.gd" ) const PlayerType = preload("res://player/player.gd") +const PlayerCoolerCapacityType = preload( + "res://progression/player_cooler_capacity.gd" +) const PlayerArtUnlocksType = preload( "res://progression/player_art_unlocks.gd" ) @@ -151,6 +154,9 @@ const ROD_PRICE_ICON_SIZE: float = 22.0 @onready var _barrier_price_bubble: PanelContainer = %BarrierPriceBubble @onready var _barrier_purchase: Button = %BarrierPurchase @onready var _supplies_list: VBoxContainer = %SuppliesList +@onready var _cooler_cost: CurrencyAmount = %CoolerCost +@onready var _cooler_price_bubble: PanelContainer = %CoolerPriceBubble +@onready var _cooler_purchase: Button = %CoolerPurchase @onready var _backpack_cost: CurrencyAmount = %BackpackCost @onready var _backpack_price_bubble: PanelContainer = %BackpackPriceBubble @onready var _backpack_purchase: Button = %BackpackPurchase @@ -166,6 +172,7 @@ var _inventory: FishInventory var _hotbar: PlayerHotbar var _inventory_layout: PlayerInventoryLayout var _item_catalog: ItemCatalogType +var _cooler_capacity: PlayerCoolerCapacityType var _art_unlocks: PlayerArtUnlocksType var _network_shop: NetworkShopService var _network_sale: NetworkSaleService @@ -195,6 +202,7 @@ func _ready() -> void: %CloseButton.pressed.connect(close_shop) _reel_purchase.pressed.connect(_purchase_reel_speed) _barrier_purchase.pressed.connect(_purchase_barrier_power) + _cooler_purchase.pressed.connect(_purchase_cooler_capacity) _backpack_purchase.pressed.connect(_purchase_backpack_capacity) @@ -345,6 +353,7 @@ func _focus_shop_section() -> void: for upgrade_button: Button in [ _reel_purchase, _barrier_purchase, + _cooler_purchase, _backpack_purchase, ]: if not upgrade_button.disabled: @@ -500,6 +509,7 @@ func _apply_shop_styles() -> void: for panel: PanelContainer in [ _reel_price_bubble, _barrier_price_bubble, + _cooler_price_bubble, _backpack_price_bubble, ]: var price_style := UtilityPageStyleType.rounded_style( @@ -516,6 +526,7 @@ func _apply_shop_styles() -> void: for price: CurrencyAmount in [ _reel_cost, _barrier_cost, + _cooler_cost, _backpack_cost, ]: var amount_label := price.get_amount_label() @@ -527,6 +538,7 @@ func _apply_shop_styles() -> void: %CloseButton, _reel_purchase, _barrier_purchase, + _cooler_purchase, _backpack_purchase, ]: UtilityPageStyleType.apply_ocean_button(button) @@ -547,6 +559,7 @@ func setup( hotbar: PlayerHotbar, inventory_layout: PlayerInventoryLayout, item_catalog: ItemCatalogType, + cooler_capacity: PlayerCoolerCapacityType, art_unlocks: PlayerArtUnlocksType, network_shop: NetworkShopService, network_sale: NetworkSaleService, @@ -563,6 +576,7 @@ func setup( _hotbar = hotbar _inventory_layout = inventory_layout _item_catalog = item_catalog + _cooler_capacity = cooler_capacity _art_unlocks = art_unlocks _network_shop = network_shop _network_sale = network_sale @@ -598,6 +612,10 @@ func setup( _upgrades.upgrades_changed.connect(_on_upgrades_changed) if not _bag.contents_changed.is_connected(_on_bag_changed): _bag.contents_changed.connect(_on_bag_changed) + if not _cooler_capacity.capacity_changed.is_connected( + _on_cooler_capacity_changed + ): + _cooler_capacity.capacity_changed.connect(_on_cooler_capacity_changed) if not _inventory_layout.backpack_capacity_changed.is_connected( _on_backpack_capacity_changed ): @@ -636,14 +654,11 @@ func open_shop() -> bool: ) _prior_movement_enabled = _player.is_movement_enabled() _prior_camera_enabled = _player.is_camera_input_enabled() - _player.set_movement_enabled(false) - # Releasing an active camera drag before the snapshot prevents the close - # path from restoring a captured pointer after its matching button release - # has been consumed by the shop. - _player.set_camera_input_enabled(false) - _prior_mouse_mode = _get_restorable_mouse_mode(Input.mouse_mode) + _prior_mouse_mode = Input.mouse_mode _snapshot_stored = true _mouse_snapshot_stored = true + _player.set_movement_enabled(false) + _player.set_camera_input_enabled(false) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _set_feedback("") @@ -818,6 +833,7 @@ func _refresh_all() -> void: _refresh_wallet() _refresh_upgrades() _refresh_supplies() + _refresh_cooler_capacity() _refresh_backpack_capacity() @@ -1568,6 +1584,41 @@ func _add_supply_icon_tile( return tile_host +func _refresh_cooler_capacity() -> void: + if _cooler_capacity == null: + return + var level: int = _cooler_capacity.get_level() + var cost: int = _cooler_capacity.get_next_cost() + var next_capacity: int = _cooler_capacity.get_next_capacity() + _cooler_purchase.disabled = ( + cost < 0 + or _transaction_in_progress + or _closing + or _network_shop == null + or not _network_shop.can_request_purchase() + or not _cooler_capacity.can_purchase(_wallet) + ) + var cooler_effect: String + if cost < 0: + cooler_effect = "%d slots · maximum level" % ( + _cooler_capacity.get_capacity() + ) + _cooler_price_bubble.hide() + else: + _cooler_price_bubble.show() + cooler_effect = "%d → %d slots" % [ + _cooler_capacity.get_capacity(), + next_capacity, + ] + _cooler_cost.set_amount(cost) + _cooler_purchase.tooltip_text = _upgrade_tooltip( + "storage capacity", + level, + cooler_effect, + ) + _cooler_purchase.accessibility_name = _cooler_purchase.tooltip_text + + func _refresh_backpack_capacity() -> void: if _inventory_layout == null: return @@ -1669,6 +1720,23 @@ func _purchase_art_upgrade(product_id: StringName) -> void: _network_shop.request_art_upgrade(product_id) +func _purchase_cooler_capacity() -> void: + if _transaction_in_progress or not _is_transaction_context_valid(): + _set_feedback("unable to complete purchase.") + return + var cost: int = _cooler_capacity.get_next_cost() + if cost < 0: + _set_feedback("Upgrade is already at maximum.") + return + if not _wallet.can_afford(cost): + _set_feedback("Insufficient funds.") + return + if _network_shop == null: + _set_feedback("Purchase could not be completed.") + return + _network_shop.request_cooler_capacity_upgrade() + + func _purchase_backpack_capacity() -> void: if _transaction_in_progress or not _is_transaction_context_valid(): _set_feedback("unable to complete purchase.") @@ -1789,6 +1857,7 @@ func _on_wallet_changed(_balance: int, _delta: int) -> void: _refresh_wallet() _refresh_upgrades() _refresh_supplies() + _refresh_cooler_capacity() _refresh_backpack_capacity() @@ -1803,6 +1872,10 @@ func _on_bag_changed() -> void: _refresh_supplies() +func _on_cooler_capacity_changed(_level: int, _capacity: int) -> void: + _refresh_cooler_capacity() + + func _on_backpack_capacity_changed(_level: int, _capacity: int) -> void: _refresh_backpack_capacity() @@ -1824,9 +1897,3 @@ func _apply_mouse_close_policy(reason: CloseReason) -> void: CloseReason.TEARDOWN: pass _mouse_snapshot_stored = false - - -func _get_restorable_mouse_mode(mouse_mode: Input.MouseMode) -> Input.MouseMode: - if mouse_mode == Input.MOUSE_MODE_CAPTURED: - return Input.MOUSE_MODE_VISIBLE - return mouse_mode diff --git a/ui/fishing_shop.tscn b/ui/fishing_shop.tscn index 66fcbd9..70ccec1 100644 --- a/ui/fishing_shop.tscn +++ b/ui/fishing_shop.tscn @@ -257,6 +257,35 @@ layout_mode = 2 amount = 100 icon_size = 24.0 +[node name="CoolerTile" type="Control" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid"] +custom_minimum_size = Vector2(144, 168) +layout_mode = 2 +mouse_filter = 2 + +[node name="CoolerPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/CoolerTile"] +unique_name_in_owner = true +offset_right = 144.0 +offset_bottom = 144.0 +tooltip_text = "storage capacity" +icon = ExtResource("7_close") +expand_icon = true +icon_max_width = 92 + +[node name="CoolerPriceBubble" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/CoolerTile"] +unique_name_in_owner = true +z_index = 2 +offset_left = 22.0 +offset_top = 128.0 +offset_right = 122.0 +offset_bottom = 160.0 +mouse_filter = 2 + +[node name="CoolerCost" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/CoolerTile/CoolerPriceBubble" instance=ExtResource("8_currency")] +unique_name_in_owner = true +layout_mode = 2 +amount = 75 +icon_size = 24.0 + [node name="BackpackTile" type="Control" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid"] custom_minimum_size = Vector2(144, 168) layout_mode = 2 diff --git a/ui/game_ui.gd b/ui/game_ui.gd index 55a07eb..6e4241b 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -20,8 +20,6 @@ const TitleScreenType = preload("res://ui/title_screen.gd") const PauseMenuType = preload("res://ui/pause_menu.gd") const NetworkSessionType = preload("res://network/network_session.gd") const FishingShopType = preload("res://ui/fishing_shop.gd") -const DecorShopType = preload("res://ui/decor_shop.gd") -const RVUpgradeShopType = preload("res://ui/rv_upgrade_shop.gd") const SettingsPanelType = preload("res://ui/settings_panel.gd") const PlayerFishingUpgradesType = preload( "res://progression/player_fishing_upgrades.gd" @@ -29,14 +27,7 @@ const PlayerFishingUpgradesType = preload( const ShopInteractionType = preload( "res://world/fishing_shop_interaction.gd" ) -const DecorShopInteractionType = preload( - "res://world/decor_shop_interaction.gd" -) -const RVUpgradeInteractionType = preload( - "res://world/rv_upgrade_interaction.gd" -) const PlayerStorageType = preload("res://ui/player_storage.gd") -const HomeDecorToolbarType = preload("res://ui/home_decor_toolbar.gd") const PlayerStorageInteractionType = preload( "res://world/player_storage_interaction.gd" ) @@ -80,9 +71,6 @@ const MAIN_BUBBLE_PROFILE: BubbleMenuProfile = preload( const ACTIVE_BAIT_EMPTY_ICON: Texture2D = preload( "res://ui/icons/pictograms/x_dark.png" ) -const RV_STORAGE_ICON: Texture2D = preload( - "res://ui/icons/tab_menu/inventory_simple.png" -) signal pixelation_settings_visibility_changed(is_visible: bool) signal crisp_reset_focus_requested @@ -94,7 +82,6 @@ signal virtual_pointer_mode_changed(is_active: bool) signal social_prompt_accepted signal social_prompt_declined signal data_root_changed -signal rv_storage_requested const VIRTUAL_MOUSE_INPUT_OWNER: StringName = &"controller_virtual_mouse" const EMOTE_RADIAL_CAMERA_OWNER: StringName = &"emote_radial_menu" @@ -133,7 +120,6 @@ const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0 @onready var _barrier_health: Label = %BarrierHealth @onready var _showcase_details: Label = %ShowcaseDetails @onready var _fishing_panel: PanelContainer = %FishingPanel -@onready var _fishing_danger_vignette: ColorRect = %FishingDangerVignette @onready var _experience_panel: PanelContainer = %ExperienceProgressPanel @onready var _experience_level_label: Label = %ExperienceLevelLabel @onready var _experience_award_label: Label = %ExperienceAwardLabel @@ -149,8 +135,6 @@ const SHOP_ANIMALESE_VOICE_ID: String = "natural" const SHOP_ANIMALESE_BASE_PITCH: float = 1.08 const SHOP_SPEECH_CHARACTERS_PER_SECOND: float = 28.0 const SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS: int = 5000 -const FISHING_DANGER_START_GAP: float = 0.15 -const FISHING_DANGER_FULL_GAP: float = 0.025 @onready var _canonical_stage: Control = %CanonicalStage @@ -161,11 +145,7 @@ const FISHING_DANGER_FULL_GAP: float = 0.025 @onready var _social_prompt: BubbleConfirmationPage = %SocialPrompt @onready var _hotbar_ui: HotbarUIType = %Hotbar @onready var _fishing_shop: FishingShopType = %FishingShop -@onready var _decor_shop: DecorShopType = %DecorShop -@onready var _rv_upgrade_shop: RVUpgradeShopType = %RVUpgradeShop @onready var _player_storage: PlayerStorageType = %PlayerStorage -@onready var _home_decor_toolbar: HomeDecorToolbarType = %HomeDecorToolbar -@onready var _rv_storage_button: Button = %RVStorageButton @onready var _storage_prompt: PanelContainer = %StoragePrompt @onready var _storage_prompt_message: Label = %StoragePromptMessage @onready var _shop_prompt: Control = %ShopPrompt @@ -215,8 +195,6 @@ var _chat_input_open: bool = false var _player_menu_hotbar_visible: bool = false var _main_shop_buyer: FishBuyerProfileType var _shop_interaction: ShopInteractionType -var _decor_shop_interaction: DecorShopInteractionType -var _rv_upgrade_shop_interaction: RVUpgradeInteractionType var _storage_interaction: PlayerStorageInteractionType var _surface_drawing: NetworkSurfaceDrawingService var _surface_drawing_hotbar_selected: bool = false @@ -243,23 +221,14 @@ var _controller_text_entry_request: Callable var _controller_text_entry_is_open: Callable var _controller_text_entry_close: Callable var _restore_chat_keyboard_after_fishing: bool = false -var _rv_storage_button_requested_visible: bool = false -var _previous_barrier_health := PackedInt32Array() -var _barrier_feedback_initialized: bool = false -var _fishing_danger_target: float = 0.0 -var _fishing_danger_intensity: float = 0.0 -var _fishing_danger_pulse_time: float = 0.0 -var _catch_track_shake_tween: Tween func _ready() -> void: _prioritize_surface_drawing_pointer_input() _bite_prompt_button.pressed.connect(_on_bite_prompt_pressed) - _rv_storage_button.pressed.connect(_on_rv_storage_button_pressed) _social_prompt.confirmed.connect(_accept_social_prompt) _social_prompt.cancelled.connect(_decline_social_prompt) _apply_active_bait_indicator_style() - _apply_rv_storage_button_style() _refresh_active_bait_indicator() _refresh_interaction_prompt_bindings() # Reward feedback must remain above full-screen canonical menus. Keeping the @@ -291,9 +260,6 @@ func _ready() -> void: _player_menu.controller_hotbar_management_ended.connect( _on_controller_hotbar_management_ended ) - _player_menu.controller_text_entry_requested.connect( - request_controller_text_entry_for - ) _title_settings_panel.panel_visibility_changed.connect( _on_settings_visibility_changed ) @@ -377,14 +343,11 @@ func setup( fishing_upgrades: PlayerFishingUpgradesType, shop_interaction: ShopInteractionType, storage_interaction: PlayerStorageInteractionType, - decor_shop_interaction: DecorShopInteractionType, - rv_upgrade_shop_interaction: RVUpgradeInteractionType, item_effects: PlayerItemEffectsType, cooler_capacity: PlayerCoolerCapacityType, network_session: NetworkSessionType, network_sale_service: NetworkSaleService, network_shop_service: NetworkShopService, - home_state: PlayerHomeState, network_chat_service: NetworkChatService, network_profile: NetworkProfilePreferences, spawn_service: PlayerSpawnService, @@ -496,7 +459,6 @@ func setup( discovery, player_jobs, world_time, - world_weather, world_environment, world_sun, ) @@ -522,28 +484,12 @@ func setup( hotbar, inventory_layout, item_catalog, + cooler_capacity, art_unlocks, network_shop_service, network_sale_service, reservations, ) - _decor_shop.setup( - player, - wallet, - fishing_spot, - decor_shop_interaction, - bag, - home_state, - network_shop_service, - ) - _rv_upgrade_shop.setup( - player, - wallet, - fishing_spot, - rv_upgrade_shop_interaction, - home_state, - network_shop_service, - ) _player_storage.setup( player, fishing_spot, @@ -559,10 +505,6 @@ func setup( ) _fishing_shop.menu_visibility_changed.connect(_on_shop_visibility_changed) _fishing_shop.menu_exit_started.connect(_on_shop_exit_started) - _decor_shop.menu_visibility_changed.connect(_on_shop_visibility_changed) - _decor_shop.menu_exit_started.connect(_on_shop_exit_started) - _rv_upgrade_shop.menu_visibility_changed.connect(_on_shop_visibility_changed) - _rv_upgrade_shop.menu_exit_started.connect(_on_shop_exit_started) _fishing_shop.sell_fish_requested.connect(_on_shop_sell_fish_requested) _fishing_shop.shop_cooler_return_requested.connect( _on_shop_cooler_return_requested @@ -572,8 +514,6 @@ func setup( ) _main_shop_buyer = main_shop_buyer _shop_interaction = shop_interaction - _decor_shop_interaction = decor_shop_interaction - _rv_upgrade_shop_interaction = rv_upgrade_shop_interaction _storage_interaction = storage_interaction _surface_drawing = surface_drawing if ( @@ -596,17 +536,11 @@ func setup( func set_world_interactions( shop_interaction: ShopInteractionType, storage_interaction: PlayerStorageInteractionType, - decor_shop_interaction: DecorShopInteractionType, - rv_upgrade_shop_interaction: RVUpgradeInteractionType, ) -> void: _shop_interaction = shop_interaction _storage_interaction = storage_interaction - _decor_shop_interaction = decor_shop_interaction - _rv_upgrade_shop_interaction = rv_upgrade_shop_interaction _fishing_shop.set_world_interaction(shop_interaction) _player_storage.set_world_interaction(storage_interaction) - _decor_shop.set_world_interaction(decor_shop_interaction) - _rv_upgrade_shop.set_world_interaction(rv_upgrade_shop_interaction) func _prioritize_surface_drawing_pointer_input() -> void: @@ -1568,13 +1502,6 @@ func _process(delta: float) -> void: _update_virtual_mouse(delta) _update_controller_menu_scroll(delta) _update_experience_bubble_position() - _update_fishing_danger_vignette(delta) - _try_start_shop_npc_speech() - if _player != null: - _player.set_fishing_reel_camera_active( - _fishing_spot != null - and _fishing_spot.is_reel_input_active() - ) func _update_controller_menu_scroll(delta: float) -> void: @@ -1724,34 +1651,6 @@ func _apply_active_bait_indicator_style() -> void: _active_bait_button.add_theme_color_override(state, Color.WHITE) -func _apply_rv_storage_button_style() -> void: - var normal_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_normal_style() - var hover_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_hover_style() - var pressed_style: StyleBoxFlat = MAIN_BUBBLE_PROFILE.make_pressed_style() - for style: StyleBoxFlat in [normal_style, hover_style, pressed_style]: - style.set_corner_radius_all(29) - style.content_margin_left = 9.0 - style.content_margin_top = 9.0 - style.content_margin_right = 9.0 - style.content_margin_bottom = 9.0 - _rv_storage_button.icon = RV_STORAGE_ICON - _rv_storage_button.add_theme_stylebox_override("normal", normal_style) - _rv_storage_button.add_theme_stylebox_override("hover", hover_style) - _rv_storage_button.add_theme_stylebox_override("focus", hover_style) - _rv_storage_button.add_theme_stylebox_override("pressed", pressed_style) - for state: StringName in [ - &"icon_normal_color", - &"icon_hover_color", - &"icon_focus_color", - &"icon_pressed_color", - ]: - _rv_storage_button.add_theme_color_override(state, Color.WHITE) - - -func _on_rv_storage_button_pressed() -> void: - rv_storage_requested.emit() - - func _refresh_active_bait_indicator() -> void: _refresh_active_bait_indicator_visibility() if not is_node_ready(): @@ -1815,21 +1714,17 @@ func set_gameplay_ui_enabled(enabled: bool) -> void: if not enabled: _gameplay_hud_hidden = false _refresh_active_bait_indicator_visibility() - _refresh_rv_storage_button_visibility() if enabled: _try_start_shop_npc_speech() _refresh_gameplay_hud_visibility() _refresh_chat_availability() if not enabled: - _home_decor_toolbar.set_active(false) _end_virtual_mouse() _close_radial_menus() if _surface_drawing != null: _surface_drawing.deactivate() close_player_menu_for_session_end() _fishing_shop.close_for_session_end() - _decor_shop.close_for_session_end() - _rv_upgrade_shop.close_for_session_end() _player_storage.close_for_session_end() _fishing_panel.visible = false _shop_prompt.hide() @@ -1850,7 +1745,6 @@ func set_gameplay_hud_hidden(hidden: bool) -> void: _quick_radial_menu.set_hud_hidden(hidden) _refresh_gameplay_hud_visibility() _refresh_active_bait_indicator_visibility() - _refresh_rv_storage_button_visibility() _refresh_hotbar_visibility() _refresh_chat_availability() @@ -1966,47 +1860,13 @@ func get_fishing_shop() -> FishingShopType: return _fishing_shop -func get_decor_shop() -> DecorShopType: - return _decor_shop - - -func get_rv_upgrade_shop() -> RVUpgradeShopType: - return _rv_upgrade_shop - - func get_player_storage() -> PlayerStorageType: return _player_storage -func get_home_decor_toolbar() -> HomeDecorToolbarType: - return _home_decor_toolbar - - -func set_rv_interior_camera_mode(enabled: bool) -> void: - _hotbar_ui.set_rv_interior_camera_mode(enabled) - - -func set_rv_storage_button_visible(requested_visible: bool) -> void: - _rv_storage_button_requested_visible = requested_visible - _refresh_rv_storage_button_visibility() - - -func _refresh_rv_storage_button_visibility() -> void: - _rv_storage_button.visible = ( - _rv_storage_button_requested_visible - and _gameplay_ui_enabled - and not _gameplay_hud_hidden - and not _system_menu_open - and not _player_menu_open - and not _shop_open - and not _storage_open - ) - - func set_storage_prompt_visible( requested_visible: bool, world_anchor: Vector3 = Vector3(0.0, INF, 0.0), - action_text: String = "open storage", ) -> void: if not _storage_prompt.has_meta(&"styled"): _storage_prompt.set_meta(&"styled", true) @@ -2015,7 +1875,6 @@ func set_storage_prompt_visible( ) style.anti_aliasing = false _storage_prompt.add_theme_stylebox_override("panel", style) - _storage_prompt_message.text = action_text _storage_prompt.visible = ( requested_visible and _gameplay_ui_enabled @@ -2033,7 +1892,7 @@ func _position_storage_prompt(world_anchor: Vector3) -> void: if _player == null: _storage_prompt.hide() return - var camera := _player.get_active_gameplay_camera() + var camera := _player.get_gameplay_camera() if camera == null or camera.is_position_behind(world_anchor): _storage_prompt.hide() return @@ -2068,19 +1927,8 @@ func set_shop_prompt_visible( _position_shop_prompt(world_anchor) -func set_shop_npc_player_in_range( - in_range: bool, - dialogue_text: String = "", -) -> void: - var dialogue_changed: bool = ( - not dialogue_text.is_empty() - and _shop_prompt_message.text != dialogue_text - ) - if dialogue_changed: - _shop_prompt_message.text = dialogue_text - _shop_prompt_message.visible_characters = -1 - _shop_npc_spoken_for_current_visit = false - if _shop_npc_player_in_range == in_range and not dialogue_changed: +func set_shop_npc_player_in_range(in_range: bool) -> void: + if _shop_npc_player_in_range == in_range: return _shop_npc_player_in_range = in_range if not in_range: @@ -2096,10 +1944,10 @@ func _try_start_shop_npc_speech() -> void: or not _gameplay_ui_enabled ): return + _shop_npc_spoken_for_current_visit = true var now_msec: int = Time.get_ticks_msec() if now_msec < _shop_npc_next_speech_msec: return - _shop_npc_spoken_for_current_visit = true _shop_npc_next_speech_msec = ( now_msec + SHOP_NPC_SPEECH_COOLDOWN_MILLISECONDS ) @@ -2158,7 +2006,7 @@ func _position_shop_prompt(world_anchor: Vector3) -> void: if _player == null: _shop_prompt.hide() return - var camera: Camera3D = _player.get_active_gameplay_camera() + var camera: Camera3D = _player.get_gameplay_camera() if camera == null or camera.is_position_behind(world_anchor): _shop_prompt.hide() return @@ -2362,12 +2210,6 @@ func _on_catch_display_changed( and _fishing_spot != null and _fishing_spot.is_fighting() ) - _update_fishing_feedback( - progress, - chase_progress, - barrier_health, - encounter_visible, - ) _green_catch_progress.value = progress * 100.0 _red_chase_progress.value = maxf(chase_progress, 0.0) * 100.0 _catch_track.visible = encounter_visible @@ -2404,93 +2246,6 @@ func _on_catch_display_changed( _refresh_fishing_panel_visibility() -func _update_fishing_feedback( - progress: float, - chase_progress: float, - barrier_health: PackedInt32Array, - encounter_visible: bool, -) -> void: - if not encounter_visible: - _previous_barrier_health.clear() - _barrier_feedback_initialized = false - _fishing_danger_target = 0.0 - return - if _barrier_feedback_initialized: - var comparison_count: int = mini( - _previous_barrier_health.size(), - barrier_health.size(), - ) - var barrier_hit: bool = false - var barrier_broken: bool = false - for barrier_index: int in range(comparison_count): - if ( - barrier_health[barrier_index] - >= _previous_barrier_health[barrier_index] - ): - continue - barrier_hit = true - barrier_broken = ( - barrier_broken - or barrier_health[barrier_index] <= 0 - ) - if barrier_hit: - _play_barrier_feedback(barrier_broken) - _previous_barrier_health = barrier_health.duplicate() - _barrier_feedback_initialized = true - var chase_gap: float = progress - maxf(chase_progress, 0.0) - _fishing_danger_target = clampf( - inverse_lerp( - FISHING_DANGER_START_GAP, - FISHING_DANGER_FULL_GAP, - chase_gap, - ), - 0.0, - 1.0, - ) - - -func _play_barrier_feedback(barrier_broken: bool) -> void: - if _player != null: - _player.play_fishing_barrier_camera_feedback(barrier_broken) - if _catch_track_shake_tween != null: - _catch_track_shake_tween.kill() - _catch_track.rotation = 0.0 - var tilt: float = 0.018 if barrier_broken else 0.008 - var beat: float = 0.038 if barrier_broken else 0.028 - _catch_track_shake_tween = create_tween() - _catch_track_shake_tween.tween_property( - _catch_track, "rotation", -tilt, beat - ) - _catch_track_shake_tween.tween_property( - _catch_track, "rotation", tilt, beat - ) - _catch_track_shake_tween.tween_property( - _catch_track, "rotation", -tilt * 0.45, beat - ) - _catch_track_shake_tween.tween_property( - _catch_track, "rotation", 0.0, beat - ) - - -func _update_fishing_danger_vignette(delta: float) -> void: - _fishing_danger_pulse_time += delta - _fishing_danger_intensity = lerpf( - _fishing_danger_intensity, - _fishing_danger_target, - 1.0 - exp(-8.0 * delta), - ) - var pulse: float = ( - 0.88 + sin(_fishing_danger_pulse_time * 5.5) * 0.12 - ) - var shown_intensity: float = _fishing_danger_intensity * pulse - _fishing_danger_vignette.visible = shown_intensity > 0.01 - var vignette_material := ( - _fishing_danger_vignette.material as ShaderMaterial - ) - if vignette_material != null: - vignette_material.set_shader_parameter("intensity", shown_intensity) - - func _update_barrier_markers( positions: PackedFloat32Array, health: PackedInt32Array, @@ -3003,7 +2758,6 @@ func _refresh_hotbar_visibility() -> void: func _emit_interactive_pointer_ui_changed() -> void: _refresh_active_bait_indicator_visibility() - _refresh_rv_storage_button_visibility() interactive_pointer_ui_changed.emit( _system_menu_open or _player_menu_open diff --git a/ui/game_ui.tscn b/ui/game_ui.tscn index c5dea31..483433a 100644 --- a/ui/game_ui.tscn +++ b/ui/game_ui.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=27 format=3] +[gd_scene load_steps=22 format=3] [ext_resource type="Script" path="res://ui/game_ui.gd" id="1_ui"] [ext_resource type="PackedScene" path="res://ui/player_menu.tscn" id="2_menu"] @@ -15,13 +15,6 @@ [ext_resource type="Script" path="res://ui/controller_virtual_cursor.gd" id="13_cursor"] [ext_resource type="PackedScene" path="res://ui/player_storage.tscn" id="14_storage"] [ext_resource type="PackedScene" path="res://ui/components/bubble_menu/bubble_confirmation_page.tscn" id="15_social_prompt"] -[ext_resource type="PackedScene" path="res://ui/decor_shop.tscn" id="16_decor_shop"] -[ext_resource type="PackedScene" path="res://ui/home_decor_toolbar.tscn" id="17_decor_toolbar"] -[ext_resource type="PackedScene" path="res://ui/rv_upgrade_shop.tscn" id="18_rv_upgrade_shop"] -[ext_resource type="Shader" path="res://ui/fishing_danger_vignette.gdshader" id="19_fishing_vignette"] - -[sub_resource type="ShaderMaterial" id="ShaderMaterial_fishing_vignette"] -shader = ExtResource("19_fishing_vignette") [sub_resource type="StyleBoxFlat" id="StyleBox_chase_background"] bg_color = Color(0.032, 0.118, 0.15, 1) @@ -91,20 +84,6 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 2 -[node name="FishingDangerVignette" type="ColorRect" parent="UIRoot/CanonicalStage/GameplayTransientHUD"] -unique_name_in_owner = true -visible = false -z_index = 50 -layout_mode = 1 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -mouse_filter = 2 -material = SubResource("ShaderMaterial_fishing_vignette") -color = Color(1, 1, 1, 1) - [node name="ActiveBaitIndicator" type="Control" parent="UIRoot/CanonicalStage/GameplayTransientHUD"] unique_name_in_owner = true z_index = 54 @@ -331,7 +310,6 @@ offset_right = 420.0 offset_bottom = -124.0 grow_horizontal = 2 grow_vertical = 0 -pivot_offset = Vector2(420, 18) mouse_filter = 2 [node name="GreenCatchProgress" type="ProgressBar" parent="UIRoot/CanonicalStage/GameplayTransientHUD/CatchTrack"] @@ -460,7 +438,7 @@ theme_override_constants/margin_bottom = 8 unique_name_in_owner = true layout_mode = 2 theme_override_font_sizes/font_size = 18 -text = "Listen, you may suck at fishing but I've got some things that might help." +text = "Hey, if you've got any fish I've got cash! OwO" horizontal_alignment = 1 vertical_alignment = 1 autowrap_mode = 2 @@ -494,40 +472,9 @@ polygon = PackedVector2Array(-8, 0, 8, 0, 0, 10) [node name="FishingShop" parent="UIRoot/CanonicalStage" instance=ExtResource("8_shop")] unique_name_in_owner = true -[node name="DecorShop" parent="UIRoot/CanonicalStage" instance=ExtResource("16_decor_shop")] -unique_name_in_owner = true - -[node name="RVUpgradeShop" parent="UIRoot/CanonicalStage" instance=ExtResource("18_rv_upgrade_shop")] -unique_name_in_owner = true - -[node name="HomeDecorToolbar" parent="UIRoot/CanonicalStage" instance=ExtResource("17_decor_toolbar")] -unique_name_in_owner = true - [node name="PlayerStorage" parent="UIRoot/CanonicalStage" instance=ExtResource("14_storage")] unique_name_in_owner = true -[node name="RVStorageButton" type="Button" parent="UIRoot/CanonicalStage/GameplayTransientHUD"] -unique_name_in_owner = true -visible = false -z_index = 57 -layout_mode = 1 -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -74.0 -offset_top = 16.0 -offset_right = -16.0 -offset_bottom = 74.0 -grow_horizontal = 0 -mouse_default_cursor_shape = 2 -focus_mode = 0 -tooltip_text = "open RV storage" -theme = ExtResource("3_theme") -texture_filter = 1 -expand_icon = true -icon_alignment = 1 -vertical_icon_alignment = 1 - [node name="StoragePrompt" type="PanelContainer" parent="UIRoot/CanonicalStage/GameplayTransientHUD"] unique_name_in_owner = true visible = false diff --git a/ui/home_decor_toolbar.gd b/ui/home_decor_toolbar.gd deleted file mode 100644 index 50ae444..0000000 --- a/ui/home_decor_toolbar.gd +++ /dev/null @@ -1,158 +0,0 @@ -class_name HomeDecorToolbar -extends PanelContainer - -signal placement_mode_requested(mode: int) -signal rotate_requested -signal store_requested -signal cancel_requested - -var _mode_button: Button -var _selection_label: Label -var _rotate_button: Button -var _store_button: Button -var _clear_button: Button -var _mode: PlayerHomeState.PlacementMode = PlayerHomeState.PlacementMode.GRID -var _interactive: bool = true -var _selection_active: bool = false - - -func _ready() -> void: - _build_ui() - hide() - - -func set_active(active: bool) -> void: - if visible == active: - return - visible = active - - -func set_interactive(interactive: bool) -> void: - _interactive = interactive - _refresh_button_interactivity() - - -func set_mode(mode: PlayerHomeState.PlacementMode) -> void: - _mode = mode - if _mode_button != null: - _mode_button.text = ( - "grid" - if mode == PlayerHomeState.PlacementMode.GRID - else "free" - ) - _mode_button.tooltip_text = "placement mode: %s" % _mode_button.text - - -func set_selection(instance_id: String, display_name: String = "") -> void: - var selected: bool = not instance_id.is_empty() - _selection_active = selected - if _selection_label != null: - _selection_label.text = ( - "selected: %s" % display_name - if selected else "use held decor to place; click or drag to edit" - ) - _refresh_button_interactivity() - - -func report_status(message: String) -> void: - if _selection_label != null: - _selection_label.text = message - - -func _build_ui() -> void: - set_anchors_preset(Control.PRESET_TOP_RIGHT) - offset_left = -500.0 - offset_top = 0.0 - offset_right = 0.0 - offset_bottom = 106.0 - custom_minimum_size = Vector2(500.0, 0.0) - z_index = 85 - mouse_filter = Control.MOUSE_FILTER_STOP - var panel_style: StyleBoxFlat = UtilityPageStyle.panel_style() - panel_style.set_corner_radius_all(10) - panel_style.content_margin_left = 6.0 - panel_style.content_margin_top = 6.0 - panel_style.content_margin_right = 6.0 - panel_style.content_margin_bottom = 6.0 - add_theme_stylebox_override("panel", panel_style) - var margin := MarginContainer.new() - for side: StringName in [ - &"margin_left", &"margin_top", &"margin_right", &"margin_bottom", - ]: - margin.add_theme_constant_override(side, 6) - add_child(margin) - var layout := VBoxContainer.new() - layout.add_theme_constant_override("separation", 5) - margin.add_child(layout) - var row := HBoxContainer.new() - row.add_theme_constant_override("separation", 5) - layout.add_child(row) - var title := Label.new() - title.text = "decor" - title.custom_minimum_size = Vector2(70.0, 46.0) - title.vertical_alignment = VERTICAL_ALIGNMENT_CENTER - title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - title.add_theme_font_size_override("font_size", 19) - row.add_child(title) - _mode_button = _add_tool_button(row, "grid", "switch placement mode", 82.0) - _mode_button.pressed.connect(_toggle_mode) - _rotate_button = _add_tool_button(row, "rotate", "rotate selected decor", 92.0) - _rotate_button.pressed.connect(rotate_requested.emit) - _store_button = _add_tool_button(row, "pack", "pack selected decor", 82.0) - _store_button.pressed.connect(store_requested.emit) - _clear_button = _add_tool_button(row, "clear", "clear selection", 82.0) - _clear_button.pressed.connect(cancel_requested.emit) - _selection_label = Label.new() - _selection_label.text = "use held decor to place; click or drag to edit" - _selection_label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - _selection_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _selection_label.add_theme_font_size_override("font_size", 15) - layout.add_child(_selection_label) - _refresh_button_interactivity() - - -func _add_tool_button( - parent: Container, - button_text: String, - button_tooltip: String, - minimum_width: float, -) -> Button: - var button := Button.new() - button.text = button_text - button.tooltip_text = button_tooltip - button.accessibility_name = button_tooltip - button.custom_minimum_size = Vector2(minimum_width, 46.0) - button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - UtilityPageStyle.apply_ocean_button(button) - for style_name: StringName in [ - &"normal", &"hover", &"pressed", &"focus", &"disabled", - ]: - var existing: StyleBox = button.get_theme_stylebox(style_name) - var rounded := existing.duplicate() as StyleBoxFlat - if rounded == null: - continue - rounded.set_corner_radius_all(20) - button.add_theme_stylebox_override(style_name, rounded) - parent.add_child(button) - return button - - -func _refresh_button_interactivity() -> void: - if _mode_button != null: - _mode_button.disabled = not _interactive - if _rotate_button != null: - _rotate_button.disabled = not _interactive or not _selection_active - if _store_button != null: - _store_button.disabled = not _interactive or not _selection_active - if _clear_button != null: - _clear_button.disabled = not _interactive or not _selection_active - - -func _toggle_mode() -> void: - _mode = ( - PlayerHomeState.PlacementMode.FREE - if _mode == PlayerHomeState.PlacementMode.GRID - else PlayerHomeState.PlacementMode.GRID - ) - set_mode(_mode) - placement_mode_requested.emit(int(_mode)) diff --git a/ui/home_decor_toolbar.gd.uid b/ui/home_decor_toolbar.gd.uid deleted file mode 100644 index b1bb23b..0000000 --- a/ui/home_decor_toolbar.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://bmtlixw3wief3 diff --git a/ui/home_decor_toolbar.tscn b/ui/home_decor_toolbar.tscn deleted file mode 100644 index 671308d..0000000 --- a/ui/home_decor_toolbar.tscn +++ /dev/null @@ -1,8 +0,0 @@ -[gd_scene load_steps=2 format=3] - -[ext_resource type="Script" path="res://ui/home_decor_toolbar.gd" id="1_script"] - -[node name="HomeDecorToolbar" type="PanelContainer"] -unique_name_in_owner = true -visible = false -script = ExtResource("1_script") diff --git a/ui/hotbar.gd b/ui/hotbar.gd index 5f54217..24a9aa8 100644 --- a/ui/hotbar.gd +++ b/ui/hotbar.gd @@ -37,7 +37,6 @@ var _slots: Array[BubbleHotbarSlotType] = [] var _gameplay_input_enabled: bool = false var _drag_enabled: bool = false var _swap_hotbar_camera_scroll: bool = false -var _rv_interior_camera_mode: bool = false var _motion_elapsed: float = 0.0 var _compact_layout: bool = false var _player_menu_context: bool = false @@ -100,10 +99,6 @@ func set_swap_hotbar_camera_scroll(enabled: bool) -> void: _swap_hotbar_camera_scroll = enabled -func set_rv_interior_camera_mode(enabled: bool) -> void: - _rv_interior_camera_mode = enabled - - func set_drag_enabled(enabled: bool) -> void: _drag_enabled = enabled for slot: BubbleHotbarSlotType in _slots: @@ -313,11 +308,7 @@ func _unhandled_input(event: InputEvent) -> void: if ( event is InputEventMouseButton and event.pressed - and ( - event.shift_pressed - if _rv_interior_camera_mode - else event.shift_pressed == _swap_hotbar_camera_scroll - ) + and event.shift_pressed == _swap_hotbar_camera_scroll ): if event.button_index == MOUSE_BUTTON_WHEEL_UP: _hotbar.cycle_selection(-1) diff --git a/ui/logbook_catalog.gd b/ui/logbook_catalog.gd index ac7a910..d5dc639 100644 --- a/ui/logbook_catalog.gd +++ b/ui/logbook_catalog.gd @@ -2,9 +2,6 @@ class_name LogbookCatalog extends RefCounted const FishDataType = preload("res://fish/fish_data.gd") -const FishInventoryType = preload("res://inventory/fish_inventory.gd") -const CollectionLogType = preload("res://collection/collection_log.gd") -const CalendarSeasonType = preload("res://world/calendar_season.gd") enum Category { FRESH_WATER, @@ -13,66 +10,6 @@ enum Category { SHELLFISH, } -enum DiscoveryFilter { - ALL, - CAUGHT, - MISSING, - INCOMPLETE, -} - -enum SortMode { - CATALOG_NUMBER, - NAME, - RARITY, - CATCH_COUNT, - QUALITY_PROGRESS, - VALUE, - WEIGHT, -} - -enum AvailabilityFilter { - ANY, - AVAILABLE_NOW, -} - -enum SeasonFilter { - ANY, - CURRENT, - SPRING, - SUMMER, - FALL, - WINTER, -} - -enum TimeFilter { - ANY, - CURRENT, - DAY, - NIGHT, -} - -enum RarityFilter { - ANY, - COMMON, - UNCOMMON, - RARE, - EPIC, - LEGENDARY, -} - -enum MethodFilter { - ANY, - FISHING, - NET, - DIGGING, -} - -enum HoldingFilter { - ANY, - IN_COOLER, - FAVORITED, -} - static func category_for(fish: FishDataType) -> Category: if fish == null: @@ -136,278 +73,3 @@ static func ordered_species( return a.catalog_number < b.catalog_number ) return ordered - - -static func filtered_species( - candidates: Array[FishDataType], - category: Category, - collection_log: CollectionLogType, - inventory: FishInventoryType, - options: Dictionary, - available_now_resolver: Callable = Callable(), - current_season: int = CalendarSeasonType.UNKNOWN, - current_is_night: bool = false, -) -> Array[FishDataType]: - var filtered: Array[FishDataType] = [] - for fish: FishDataType in ordered_species(candidates): - if category_for(fish) != category: - continue - if not _matches_filters( - fish, - collection_log, - inventory, - options, - available_now_resolver, - current_season, - current_is_night, - ): - continue - filtered.append(fish) - var sort_mode := int(options.get("sort", SortMode.CATALOG_NUMBER)) - var descending := bool(options.get("descending", false)) - filtered.sort_custom( - func(first: FishDataType, second: FishDataType) -> bool: - return _species_less_than( - first, - second, - collection_log, - sort_mode, - descending, - ) - ) - return filtered - - -static func available_groups( - candidates: Array[FishDataType], - category: Category, - _collection_log: CollectionLogType, -) -> Array[String]: - var groups: Array[String] = [] - for fish: FishDataType in ordered_species(candidates): - if category_for(fish) != category: - continue - var group := _group_label(fish) - if not group.is_empty() and group not in groups: - groups.append(group) - groups.sort_custom( - func(first: String, second: String) -> bool: - return first.naturalnocasecmp_to(second) < 0 - ) - return groups - - -static func quality_progress( - collection_log: CollectionLogType, - fish_id: StringName, -) -> int: - if collection_log == null: - return 0 - var mask: int = collection_log.get_quality_mask(fish_id) - var count: int = 0 - while mask > 0: - count += mask & 1 - mask >>= 1 - return count - - -static func _matches_filters( - fish: FishDataType, - collection_log: CollectionLogType, - inventory: FishInventoryType, - options: Dictionary, - available_now_resolver: Callable, - current_season: int, - current_is_night: bool, -) -> bool: - var discovered := ( - collection_log != null and collection_log.has_discovered(fish.id) - ) - match int(options.get("show", DiscoveryFilter.ALL)): - DiscoveryFilter.CAUGHT: - if not discovered: - return false - DiscoveryFilter.MISSING: - if discovered: - return false - DiscoveryFilter.INCOMPLETE: - if ( - not discovered - or collection_log.has_mastered(fish.id) - ): - return false - - var search_text := str(options.get("search", "")).strip_edges().to_lower() - if not search_text.is_empty(): - var number_text := str(catalog_number(fish)) - var number_query := search_text.trim_prefix("#") - var number_matches := ( - number_query.is_valid_int() - and number_text == number_query - ) - var identity_matches := false - if discovered: - identity_matches = " ".join([ - fish.display_name, - _group_label(fish), - fish.get_habitat_label(), - ]).to_lower().contains(search_text) - if not number_matches and not identity_matches: - return false - - if ( - int(options.get("availability", AvailabilityFilter.ANY)) - == AvailabilityFilter.AVAILABLE_NOW - ): - if available_now_resolver.is_valid(): - var resolved: Variant = available_now_resolver.call(fish) - if typeof(resolved) != TYPE_BOOL or not bool(resolved): - return false - elif not _matches_season(fish, current_season): - return false - - var season_filter := int(options.get("season", SeasonFilter.ANY)) - if season_filter != SeasonFilter.ANY: - var requested_season := current_season - if season_filter >= SeasonFilter.SPRING: - requested_season = season_filter - SeasonFilter.SPRING - if not _matches_season(fish, requested_season): - return false - - var time_filter := int(options.get("time", TimeFilter.ANY)) - if time_filter != TimeFilter.ANY: - var requested_night := current_is_night - if time_filter == TimeFilter.DAY: - requested_night = false - elif time_filter == TimeFilter.NIGHT: - requested_night = true - if not _matches_time(fish, requested_night): - return false - - var rarity_filter := int(options.get("rarity", RarityFilter.ANY)) - if ( - rarity_filter != RarityFilter.ANY - and fish.rarity != rarity_filter - RarityFilter.COMMON - ): - return false - - var method_filter := int(options.get("method", MethodFilter.ANY)) - if ( - method_filter != MethodFilter.ANY - and fish.collection_method != method_filter - MethodFilter.FISHING - ): - return false - - var holding_filter := int(options.get("holding", HoldingFilter.ANY)) - if holding_filter != HoldingFilter.ANY: - var favorites_only := holding_filter == HoldingFilter.FAVORITED - if not _has_current_catch(inventory, fish.id, favorites_only): - return false - - var requested_group := str(options.get("group", "")) - if ( - not requested_group.is_empty() - and _group_label(fish).nocasecmp_to(requested_group) != 0 - ): - return false - return true - - -static func _species_less_than( - first: FishDataType, - second: FishDataType, - collection_log: CollectionLogType, - sort_mode: int, - descending: bool, -) -> bool: - var first_discovered := ( - collection_log != null and collection_log.has_discovered(first.id) - ) - var second_discovered := ( - collection_log != null and collection_log.has_discovered(second.id) - ) - if sort_mode != SortMode.CATALOG_NUMBER: - if first_discovered != second_discovered: - return first_discovered - if not first_discovered and not second_discovered: - return _catalog_less_than(first, second) - - var comparison: int = 0 - match sort_mode: - SortMode.NAME: - comparison = first.display_name.naturalnocasecmp_to( - second.display_name - ) - SortMode.RARITY: - comparison = first.rarity - second.rarity - SortMode.CATCH_COUNT: - comparison = ( - collection_log.get_catch_count(first.id) - - collection_log.get_catch_count(second.id) - ) - SortMode.QUALITY_PROGRESS: - comparison = ( - quality_progress(collection_log, first.id) - - quality_progress(collection_log, second.id) - ) - SortMode.VALUE: - comparison = first.sell_value_max - second.sell_value_max - SortMode.WEIGHT: - comparison = _compare_floats( - first.get_maximum_weight(), - second.get_maximum_weight(), - ) - _: - comparison = first.catalog_number - second.catalog_number - if comparison != 0: - return comparison > 0 if descending else comparison < 0 - return _catalog_less_than(first, second) - - -static func _catalog_less_than(first: FishDataType, second: FishDataType) -> bool: - if first.catalog_number == second.catalog_number: - return String(first.id) < String(second.id) - return first.catalog_number < second.catalog_number - - -static func _compare_floats(first: float, second: float) -> int: - if is_equal_approx(first, second): - return 0 - return -1 if first < second else 1 - - -static func _matches_season(fish: FishDataType, season: int) -> bool: - return fish.is_available_in_season(season) - - -static func _matches_time(fish: FishDataType, requested_night: bool) -> bool: - if ( - fish.collection_method != FishDataType.CollectionMethod.FISHING - or fish.availability == null - ): - return true - return ( - fish.availability.allow_night - if requested_night - else fish.availability.allow_day - ) - - -static func _has_current_catch( - inventory: FishInventoryType, - fish_id: StringName, - favorites_only: bool, -) -> bool: - if inventory == null: - return false - for fish_catch in inventory.get_catches_by_fish_id(fish_id): - if fish_catch != null and (not favorites_only or fish_catch.is_favorited): - return true - return false - - -static func _group_label(fish: FishDataType) -> String: - if fish == null: - return "" - var group := String(fish.collection_group).strip_edges() - return group if not group.is_empty() else fish.get_habitat_label() diff --git a/ui/logbook_page.gd b/ui/logbook_page.gd index cc490a0..6319dd6 100644 --- a/ui/logbook_page.gd +++ b/ui/logbook_page.gd @@ -6,21 +6,10 @@ const FishDataType = preload("res://fish/fish_data.gd") const FishQualityType = preload("res://fish/fish_quality.gd") const FishInventoryType = preload("res://inventory/fish_inventory.gd") const FishPoolType = preload("res://fish/fish_pool.gd") -const FishingSpotType = preload("res://fishing/fishing_spot.gd") -const WorldTimeServiceType = preload("res://world/world_time_service.gd") -const WorldWeatherServiceType = preload( - "res://world/world_weather_service.gd" -) const SILHOUETTE_SHADER: Shader = preload( "res://ui/logbook_silhouette.gdshader" ) const OrganizerTabType = preload("res://ui/components/organizer_tab.gd") -const ControllerFocusNavigationType = preload( - "res://ui/controller_focus_navigation.gd" -) -const NOTEPAD_INK_ACTION_SCENE: PackedScene = preload( - "res://ui/components/bubble_menu/notepad_ink_action.tscn" -) const InventoryNotepadType = preload( "res://ui/components/inventory_notepad.gd" ) @@ -59,8 +48,6 @@ const LOGBOOK_TAB_LEFT_INSET: float = 92.0 const LOGBOOK_TAB_SIZE := Vector2(108.0, 38.0) const LOGBOOK_TAB_SEPARATION: float = 6.0 const LOGBOOK_TAB_TEXT_SIDE_INSET: float = 10.0 -const INDEX_TAB_SIZE := Vector2(88.0, 38.0) -const INDEX_TAB_RIGHT_INSET: float = 72.0 const PAGE_CONTENT_SCALE: float = 0.97 const WIDE_OUTER_MARGINS := Rect2(52.0, 85.0, 52.0, 18.0) const COMPACT_OUTER_MARGINS := Rect2(160.0, 85.0, 160.0, 106.0) @@ -74,53 +61,17 @@ const DETAIL_SECTION_SEPARATION: int = 10 const DETAIL_QUALITY_SECTION_SIZE := Vector2(300.0, 104.0) const DETAIL_STATS_SECTION_SIZE := Vector2(400.0, 160.0) const DETAIL_STATS_ROW_SEPARATION: int = 8 -const INDEX_OPTION_HEIGHT: float = 34.0 -const INDEX_CONTENT_LEFT_INSET: float = 44.0 -const INDEX_CONTENT_TOP_INSET: float = 4.0 -const INDEX_CONTENT_RIGHT_INSET: float = 22.0 -const INDEX_CONTENT_BOTTOM_INSET: float = 10.0 -const INDEX_SHOW_LABELS := [ - "all entries", "caught", "missing", "needs quality", -] -const INDEX_SORT_LABELS := [ - "catalog no.", "name", "rarity", "number caught", - "quality progress", "value", "weight", -] -const INDEX_AVAILABILITY_LABELS := [ - "any time", "available now", -] -const INDEX_SEASON_LABELS := [ - "any season", "current season", "spring", "summer", "fall", "winter", -] -const INDEX_TIME_LABELS := [ - "any time", "current time", "day", "night", -] -const INDEX_RARITY_LABELS := [ - "any rarity", "common", "uncommon", "rare", "epic", "legendary", -] -const INDEX_METHOD_LABELS := [ - "any method", "fishing", "netting", "digging", -] -const INDEX_HOLDING_LABELS := [ - "any ownership", "in cooler", "favorited", -] - -signal controller_text_entry_requested(control: Control) enum ControllerZone { TABS, ENTRIES, DETAILS, - INDEX, OVERLAY, } var _collection_log: CollectionLogType var _inventory: FishInventoryType var _catalog: FishPoolType -var _fishing_spot: FishingSpotType -var _world_time: WorldTimeServiceType -var _world_weather: WorldWeatherServiceType var _category: LogbookCatalog.Category = ( LogbookCatalog.Category.FRESH_WATER ) @@ -160,31 +111,6 @@ var _portrait_overlay_stats_view: Control var _portrait_overlay_stats: GridContainer var _overlay_return_focus: Control var _controller_zone: ControllerZone = ControllerZone.TABS -var _index_tab: OrganizerTabType -var _index_body: VBoxContainer -var _index_search: LineEdit -var _index_summary: Label -var _index_note: Label -var _index_reset: Button -var _index_option_buttons: Dictionary[StringName, Button] = {} -var _index_controls: Array[Control] = [] -var _index_open: bool = false -var _index_update_in_progress: bool = false -var _index_visible_count: int = 0 -var _index_category_total: int = 0 -var _index_options: Dictionary = { - "search": "", - "show": LogbookCatalog.DiscoveryFilter.ALL, - "sort": LogbookCatalog.SortMode.CATALOG_NUMBER, - "descending": false, - "availability": LogbookCatalog.AvailabilityFilter.ANY, - "season": LogbookCatalog.SeasonFilter.ANY, - "time": LogbookCatalog.TimeFilter.ANY, - "rarity": LogbookCatalog.RarityFilter.ANY, - "method": LogbookCatalog.MethodFilter.ANY, - "holding": LogbookCatalog.HoldingFilter.ANY, - "group": "", -} func _ready() -> void: @@ -198,16 +124,10 @@ func setup( collection_log: CollectionLogType, inventory: FishInventoryType, catalog: FishPoolType, - fishing_spot: FishingSpotType = null, - world_time: WorldTimeServiceType = null, - world_weather: WorldWeatherServiceType = null, ) -> void: _collection_log = collection_log _inventory = inventory _catalog = catalog - _fishing_spot = fishing_spot - _world_time = world_time - _world_weather = world_weather if not _collection_log.fish_discovered.is_connected( _on_fish_discovered ): @@ -218,28 +138,12 @@ func setup( _collection_log.collection_changed.connect(_on_collection_changed) if not _inventory.catches_changed.is_connected(_on_inventory_changed): _inventory.catches_changed.connect(_on_inventory_changed) - if ( - _world_time != null - and not _world_time.time_changed.is_connected( - _on_logbook_context_changed - ) - ): - _world_time.time_changed.connect(_on_logbook_context_changed) - _world_time.calendar_date_changed.connect(_on_logbook_context_changed) - if ( - _world_weather != null - and not _world_weather.weather_changed.is_connected( - _on_logbook_context_changed - ) - ): - _world_weather.weather_changed.connect(_on_logbook_context_changed) _refresh_catalog() func set_compact_presentation(compact: bool) -> void: _compact_presentation = compact _apply_presentation_margins() - _apply_index_presentation() func activate() -> void: @@ -276,17 +180,6 @@ func set_interactive(value: bool) -> void: if _interactive and not tab.button_pressed else Control.MOUSE_FILTER_IGNORE ) - if _index_tab != null: - _index_tab.focus_mode = ( - Control.FOCUS_ALL - if _interactive and _controller_zone == ControllerZone.TABS - else Control.FOCUS_NONE - ) - _index_tab.mouse_filter = ( - Control.MOUSE_FILTER_STOP - if _interactive - else Control.MOUSE_FILTER_IGNORE - ) for entry: Button in _entry_buttons.values(): entry.focus_mode = ( Control.FOCUS_ALL @@ -306,19 +199,6 @@ func set_interactive(value: bool) -> void: if _interactive and _controller_zone == ControllerZone.DETAILS else Control.FOCUS_NONE ) - for index_control: Control in _index_controls: - if not is_instance_valid(index_control): - continue - index_control.focus_mode = ( - Control.FOCUS_ALL - if _interactive and _controller_zone == ControllerZone.INDEX - else Control.FOCUS_NONE - ) - index_control.mouse_filter = ( - Control.MOUSE_FILTER_STOP - if _interactive and _index_open - else Control.MOUSE_FILTER_IGNORE - ) if _portrait_overlay_backdrop != null: _portrait_overlay_backdrop.focus_mode = ( Control.FOCUS_ALL @@ -334,9 +214,6 @@ func focus_initial() -> void: _portrait_overlay_backdrop.grab_focus() return if _controller_zone == ControllerZone.TABS: - if _index_open and _index_tab != null: - _index_tab.grab_focus() - return var category_tab_index: int = _category_tab_categories.find(_category) if category_tab_index >= 0: _category_tabs[category_tab_index].grab_focus() @@ -345,10 +222,6 @@ func focus_initial() -> void: if not _detail_buttons.is_empty(): _detail_buttons.front().grab_focus() return - if _controller_zone == ControllerZone.INDEX: - if _index_search != null: - _index_search.grab_focus() - return var selected := _entry_buttons.get(_selected_entry_key) as Button if selected != null: selected.grab_focus() @@ -374,8 +247,6 @@ func handle_controller_input(event: InputEvent) -> bool: _hide_portrait_overlay() ControllerZone.DETAILS: _set_controller_zone(ControllerZone.ENTRIES) - ControllerZone.INDEX: - _set_index_open(false) ControllerZone.ENTRIES: _set_controller_zone(ControllerZone.TABS) ControllerZone.TABS: @@ -383,43 +254,15 @@ func handle_controller_input(event: InputEvent) -> bool: return true if _controller_zone == ControllerZone.TABS: if event.is_action_pressed("ui_left"): - if _index_open: - _set_index_open(false, false) - _category_tabs.back().grab_focus() - return true _select_adjacent_controller_category(-1) return true if event.is_action_pressed("ui_right"): - if ( - _category_tab_categories.find(_category) - == _category_tab_categories.size() - 1 - ): - _set_index_open(true, false) - _index_tab.grab_focus() - return true _select_adjacent_controller_category(1) return true if accept_pressed: - if _index_open: - _set_controller_zone(ControllerZone.INDEX) - elif not _entry_buttons.is_empty(): + if not _entry_buttons.is_empty(): _set_controller_zone(ControllerZone.ENTRIES) return true - if _controller_zone == ControllerZone.INDEX: - var focus_owner := get_viewport().gui_get_focus_owner() - if accept_pressed and focus_owner == _index_search: - controller_text_entry_requested.emit(_index_search) - return true - if event.is_action_pressed("ui_left"): - var previous_key := _index_key_for_control(focus_owner) - if not previous_key.is_empty(): - _cycle_index_option(previous_key, -1) - return true - if event.is_action_pressed("ui_right") or accept_pressed: - var next_key := _index_key_for_control(focus_owner) - if not next_key.is_empty(): - _cycle_index_option(next_key, 1) - return true if _controller_zone == ControllerZone.ENTRIES and accept_pressed: if not _selected_entry_key.is_empty() and not _detail_buttons.is_empty(): _set_controller_zone(ControllerZone.DETAILS) @@ -486,20 +329,6 @@ func _build_interface() -> void: tabs.add_child(tab) _category_tabs.append(tab) _category_tab_categories.append(category) - _index_tab = OrganizerTabType.new() - _index_tab.name = "CatalogIndexTab" - _index_tab.anchor_left = 1.0 - _index_tab.anchor_right = 1.0 - _index_tab.offset_left = -INDEX_TAB_RIGHT_INSET - INDEX_TAB_SIZE.x - _index_tab.offset_top = 37.0 - _index_tab.offset_right = -INDEX_TAB_RIGHT_INSET - _index_tab.offset_bottom = 37.0 + INDEX_TAB_SIZE.y - _index_tab.text = "index" - _index_tab.palette_index = 0 - _index_tab.tooltip_text = "Open catalog index" - _index_tab.accessibility_name = "Catalog index" - _index_tab.pressed.connect(_toggle_index_page) - stack.add_child(_index_tab) _configure_category_focus() var book := Control.new() @@ -589,176 +418,9 @@ func _build_interface() -> void: right_page.add_child(_detail_body) _apply_page_content_scale(_detail_body) _show_no_selection() - _build_index_page(right_page) _build_portrait_overlay() -func _build_index_page(right_page: Control) -> void: - _index_body = VBoxContainer.new() - _index_body.name = "CatalogIndex" - _index_body.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - _index_body.offset_left = INDEX_CONTENT_LEFT_INSET - _index_body.offset_top = INDEX_CONTENT_TOP_INSET - _index_body.offset_right = -INDEX_CONTENT_RIGHT_INSET - _index_body.offset_bottom = -INDEX_CONTENT_BOTTOM_INSET - _index_body.add_theme_constant_override("separation", 4) - _index_body.visible = false - right_page.add_child(_index_body) - _apply_page_content_scale(_index_body) - _apply_index_presentation() - - var heading := _label("catalog index", 28) - heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _index_body.add_child(heading) - _index_summary = _field_label("", 14) - _index_summary.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _index_summary.add_theme_color_override("font_color", MUTED_INK) - _index_body.add_child(_index_summary) - - var search_stack := VBoxContainer.new() - search_stack.add_theme_constant_override("separation", 0) - _index_body.add_child(search_stack) - var search_label := _field_label("find by name, group, or catalog no.", 13) - search_label.add_theme_color_override("font_color", MUTED_INK) - search_stack.add_child(search_label) - _index_search = LineEdit.new() - _index_search.name = "CatalogIndexSearch" - _index_search.custom_minimum_size.y = 34.0 - _index_search.placeholder_text = "write here..." - _index_search.max_length = 48 - _index_search.add_theme_font_override( - "font", InventoryNotepadType.HANDWRITTEN_FONT - ) - _index_search.add_theme_font_size_override("font_size", 18) - _index_search.add_theme_color_override("font_color", INK) - _index_search.add_theme_color_override("caret_color", INK) - _index_search.add_theme_color_override( - "font_placeholder_color", Color(MUTED_INK, 0.72) - ) - for style_name: StringName in [ - &"normal", &"focus", &"read_only", - ]: - _index_search.add_theme_stylebox_override( - style_name, - _index_search_style(style_name == &"focus"), - ) - _index_search.text_changed.connect(_on_index_search_changed) - search_stack.add_child(_index_search) - _index_controls.append(_index_search) - - var options_grid := GridContainer.new() - options_grid.columns = 2 - options_grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL - options_grid.add_theme_constant_override("h_separation", 18) - options_grid.add_theme_constant_override("v_separation", 2) - _index_body.add_child(options_grid) - for descriptor: Dictionary in [ - {"key": &"show", "label": "show"}, - {"key": &"rarity", "label": "rarity"}, - {"key": &"sort", "label": "sort by"}, - {"key": &"availability", "label": "availability"}, - {"key": &"descending", "label": "order"}, - {"key": &"season", "label": "season"}, - {"key": &"method", "label": "method"}, - {"key": &"time", "label": "time of day"}, - {"key": &"holding", "label": "ownership"}, - {"key": &"group", "label": "collection group"}, - ]: - options_grid.add_child(_make_index_option( - descriptor["key"], - str(descriptor["label"]), - )) - - var footer := HBoxContainer.new() - footer.alignment = BoxContainer.ALIGNMENT_CENTER - _index_body.add_child(footer) - _index_reset = NOTEPAD_INK_ACTION_SCENE.instantiate() as Button - _index_reset.name = "CatalogIndexReset" - _index_reset.custom_minimum_size = Vector2(126.0, 34.0) - _index_reset.text = "clear index" - _apply_index_value_font(_index_reset) - _index_reset.tooltip_text = "Clear every catalog filter" - _index_reset.accessibility_name = "Clear catalog index filters" - _index_reset.pressed.connect(_reset_index) - footer.add_child(_index_reset) - _index_controls.append(_index_reset) - - _index_note = _field_label( - "silhouettes keep names hidden in filtered results", - 12, - ) - _index_note.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _index_note.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - _index_note.add_theme_color_override("font_color", Color(MUTED_INK, 0.82)) - _index_body.add_child(_index_note) - _update_index_controls() - _configure_index_focus.call_deferred() - - -func _make_index_option(key: StringName, label_text: String) -> Control: - var stack := VBoxContainer.new() - stack.size_flags_horizontal = Control.SIZE_EXPAND_FILL - stack.add_theme_constant_override("separation", -3) - var label := _field_label(label_text, 12) - label.add_theme_color_override("font_color", MUTED_INK) - stack.add_child(label) - var button := NOTEPAD_INK_ACTION_SCENE.instantiate() as Button - button.name = "CatalogIndex%s" % String(key).capitalize().replace(" ", "") - button.custom_minimum_size = Vector2(0.0, INDEX_OPTION_HEIGHT) - button.size_flags_horizontal = Control.SIZE_EXPAND_FILL - button.clip_text = true - _apply_index_value_font(button) - button.tooltip_text = "Change %s; use left or right on a controller" % label_text - button.pressed.connect(_cycle_index_option.bind(key, 1)) - button.gui_input.connect(_on_index_option_gui_input.bind(key)) - stack.add_child(button) - _index_option_buttons[key] = button - _index_controls.append(button) - return stack - - -func _apply_index_value_font(button: Button) -> void: - button.add_theme_font_override( - "font", InventoryNotepadType.HANDWRITTEN_FONT - ) - button.add_theme_font_size_override("font_size", 18) - for color_name: StringName in [ - &"font_color", - &"font_hover_color", - &"font_focus_color", - &"font_pressed_color", - ]: - button.add_theme_color_override(color_name, INK) - - -func _index_search_style(focused: bool) -> StyleBoxFlat: - var style := StyleBoxFlat.new() - style.bg_color = Color(1.0, 0.97, 0.82, 0.12 if focused else 0.0) - style.border_color = Color(INK, 0.72 if focused else 0.42) - style.border_width_bottom = 2 - style.content_margin_left = 6 - style.content_margin_right = 22 - style.content_margin_top = 2 - style.content_margin_bottom = 3 - return style - - -func _configure_index_focus() -> void: - var controls: Array[Control] = [] - for control: Control in _index_controls: - if control != null and is_instance_valid(control): - controls.append(control) - if not controls.is_empty(): - ControllerFocusNavigationType.configure_spatial_neighbors(controls) - - -func _apply_index_presentation() -> void: - if _index_body == null: - return - var scale_value := 0.82 if _compact_presentation else PAGE_CONTENT_SCALE - _index_body.scale = Vector2.ONE * scale_value - - func _apply_presentation_margins() -> void: if _outer_margin == null: return @@ -886,30 +548,11 @@ func _refresh_catalog() -> void: var preserved_entry_key: StringName = _selected_entry_key _clear_entries() var species: Array[FishDataType] = [] - var category_total: int = 0 if _catalog != null: - for fish: FishDataType in LogbookCatalog.ordered_species( - _catalog.candidates - ): - if LogbookCatalog.category_for(fish) == _category: - category_total += 1 - var available_now_resolver := Callable() - if _fishing_spot != null and is_instance_valid(_fishing_spot): - available_now_resolver = Callable( - _fishing_spot, - "is_species_available_now", - ) - species = LogbookCatalog.filtered_species( - _catalog.candidates, - _category, - _collection_log, - _inventory, - _index_options, - available_now_resolver, - _current_season(), - _current_is_night(), - ) + species = LogbookCatalog.ordered_species(_catalog.candidates) for fish: FishDataType in species: + if LogbookCatalog.category_for(fish) != _category: + continue var discovered: bool = _collection_log.has_discovered(fish.id) var entry: Button = _make_entry(fish, discovered) var selection_key: StringName = ( @@ -929,11 +572,7 @@ func _refresh_catalog() -> void: ) ) _catalog_grid.add_child(entry) - _empty_state.text = ( - "No entries match this index." - if _index_has_active_criteria() - else LogbookCatalog.empty_state(_category) - ) + _empty_state.text = LogbookCatalog.empty_state(_category) _empty_state.visible = _entry_buttons.is_empty() _catalog_scroll.visible = not _entry_buttons.is_empty() if ( @@ -950,269 +589,10 @@ func _refresh_catalog() -> void: _selected_entry_key = StringName() _refresh_selection_styles() _refresh_details(false) - _update_index_controls(species.size(), category_total) set_interactive(_interactive) _refresh_catalog_scroll_indicators.call_deferred() -func _toggle_index_page() -> void: - _set_index_open(not _index_open) - - -func _set_index_open(open: bool, restore_focus: bool = true) -> void: - _index_open = open - if _index_tab != null: - _index_tab.set_selected(open) - _index_tab.tooltip_text = ( - "Return to catch record" if open else "Open catalog index" - ) - if _index_body != null: - _index_body.visible = open - if _detail_body != null: - _detail_body.visible = not open - if not open: - _refresh_details(false) - _update_index_controls() - if restore_focus and _active and _interactive: - if open: - _controller_zone = ControllerZone.INDEX - set_interactive(true) - _index_search.grab_focus() - elif _controller_zone == ControllerZone.INDEX: - _controller_zone = ControllerZone.TABS - set_interactive(true) - _index_tab.grab_focus() - - -func _on_index_search_changed(value: String) -> void: - if _index_update_in_progress: - return - _index_options["search"] = value - _refresh_catalog() - - -func _cycle_index_option(key: StringName, direction: int = 1) -> void: - if not _index_options.has(key): - return - if key == &"descending": - _index_options[key] = not bool(_index_options[key]) - elif key == &"group": - var groups: Array[String] = [""] - if _catalog != null: - groups.append_array(LogbookCatalog.available_groups( - _catalog.candidates, - _category, - _collection_log, - )) - var current_index := groups.find(str(_index_options[key])) - if current_index < 0: - current_index = 0 - _index_options[key] = groups[wrapi( - current_index + direction, - 0, - groups.size(), - )] - else: - var option_count := _index_option_count(key) - if option_count <= 0: - return - _index_options[key] = wrapi( - int(_index_options[key]) + direction, - 0, - option_count, - ) - _refresh_catalog() - - -func _on_index_option_gui_input( - event: InputEvent, - key: StringName, -) -> void: - var mouse_event := event as InputEventMouseButton - if mouse_event == null or not mouse_event.pressed: - return - if mouse_event.button_index in [MOUSE_BUTTON_RIGHT, MOUSE_BUTTON_WHEEL_UP]: - _cycle_index_option(key, -1) - get_viewport().set_input_as_handled() - elif mouse_event.button_index == MOUSE_BUTTON_WHEEL_DOWN: - _cycle_index_option(key, 1) - get_viewport().set_input_as_handled() - - -func _index_key_for_control(control: Control) -> StringName: - for key: StringName in _index_option_buttons: - if _index_option_buttons[key] == control: - return key - return StringName() - - -func _reset_index() -> void: - _index_options = { - "search": "", - "show": LogbookCatalog.DiscoveryFilter.ALL, - "sort": LogbookCatalog.SortMode.CATALOG_NUMBER, - "descending": false, - "availability": LogbookCatalog.AvailabilityFilter.ANY, - "season": LogbookCatalog.SeasonFilter.ANY, - "time": LogbookCatalog.TimeFilter.ANY, - "rarity": LogbookCatalog.RarityFilter.ANY, - "method": LogbookCatalog.MethodFilter.ANY, - "holding": LogbookCatalog.HoldingFilter.ANY, - "group": "", - } - _index_update_in_progress = true - _index_search.text = "" - _index_update_in_progress = false - _refresh_catalog() - - -func _update_index_controls( - visible_count: int = -1, - category_total: int = -1, -) -> void: - if visible_count >= 0: - _index_visible_count = visible_count - if category_total >= 0: - _index_category_total = category_total - if _index_summary != null: - _index_summary.text = "%d shown of %d" % [ - _index_visible_count, - _index_category_total, - ] - for key: StringName in _index_option_buttons: - var button := _index_option_buttons[key] - if button == null: - continue - button.text = _index_option_label(key) - button.accessibility_name = "%s: %s" % [ - String(key).replace("_", " "), - button.text, - ] - if _index_tab != null: - var marked := _index_has_active_criteria() - _index_tab.text = "index •" if marked else "index" - _index_tab.accessibility_name = ( - "Catalog index, filters active" if marked else "Catalog index" - ) - if _index_reset != null: - _index_reset.disabled = not _index_has_active_criteria() - - -func _index_option_label(key: StringName) -> String: - match key: - &"show": - return INDEX_SHOW_LABELS[int(_index_options[key])] - &"sort": - return INDEX_SORT_LABELS[int(_index_options[key])] - &"availability": - return INDEX_AVAILABILITY_LABELS[int(_index_options[key])] - &"season": - return INDEX_SEASON_LABELS[int(_index_options[key])] - &"time": - return INDEX_TIME_LABELS[int(_index_options[key])] - &"rarity": - return INDEX_RARITY_LABELS[int(_index_options[key])] - &"method": - return INDEX_METHOD_LABELS[int(_index_options[key])] - &"holding": - return INDEX_HOLDING_LABELS[int(_index_options[key])] - &"group": - var group := str(_index_options[key]) - return group if not group.is_empty() else "any group" - &"descending": - var descending := bool(_index_options[key]) - match int(_index_options["sort"]): - LogbookCatalog.SortMode.NAME: - return "z–a" if descending else "a–z" - LogbookCatalog.SortMode.CATALOG_NUMBER: - return "high–low" if descending else "low–high" - LogbookCatalog.SortMode.RARITY: - return ( - "rare–common" if descending else "common–rare" - ) - LogbookCatalog.SortMode.VALUE, LogbookCatalog.SortMode.WEIGHT: - return "high–low" if descending else "low–high" - _: - return "most first" if descending else "least first" - return "any" - - -func _index_option_count(key: StringName) -> int: - match key: - &"show": - return INDEX_SHOW_LABELS.size() - &"sort": - return INDEX_SORT_LABELS.size() - &"availability": - return INDEX_AVAILABILITY_LABELS.size() - &"season": - return INDEX_SEASON_LABELS.size() - &"time": - return INDEX_TIME_LABELS.size() - &"rarity": - return INDEX_RARITY_LABELS.size() - &"method": - return INDEX_METHOD_LABELS.size() - &"holding": - return INDEX_HOLDING_LABELS.size() - return 0 - - -func _index_has_active_criteria() -> bool: - return ( - not str(_index_options["search"]).is_empty() - or int(_index_options["show"]) - != LogbookCatalog.DiscoveryFilter.ALL - or int(_index_options["sort"]) - != LogbookCatalog.SortMode.CATALOG_NUMBER - or bool(_index_options["descending"]) - or int(_index_options["availability"]) - != LogbookCatalog.AvailabilityFilter.ANY - or int(_index_options["season"]) - != LogbookCatalog.SeasonFilter.ANY - or int(_index_options["time"]) - != LogbookCatalog.TimeFilter.ANY - or int(_index_options["rarity"]) - != LogbookCatalog.RarityFilter.ANY - or int(_index_options["method"]) - != LogbookCatalog.MethodFilter.ANY - or int(_index_options["holding"]) - != LogbookCatalog.HoldingFilter.ANY - or not str(_index_options["group"]).is_empty() - ) - - -func _current_season() -> int: - return ( - _world_time.get_season() - if _world_time != null - else CalendarSeason.UNKNOWN - ) - - -func _current_is_night() -> bool: - return _world_time != null and _world_time.is_night_period() - - -func notify_availability_context_changed() -> void: - _on_logbook_context_changed() - - -func _on_logbook_context_changed( - _first: Variant = null, - _second: Variant = null, -) -> void: - if ( - int(_index_options["availability"]) - == LogbookCatalog.AvailabilityFilter.AVAILABLE_NOW - or int(_index_options["season"]) - == LogbookCatalog.SeasonFilter.CURRENT - or int(_index_options["time"]) - == LogbookCatalog.TimeFilter.CURRENT - ): - _refresh_catalog() - - func _make_entry(fish: FishDataType, discovered: bool) -> Button: var entry := Button.new() entry.custom_minimum_size = CATALOG_ENTRY_SIZE @@ -1283,11 +663,8 @@ func _add_entry_content( func _select_category(category: LogbookCatalog.Category) -> void: if category == _category: - if _index_open: - _set_index_open(false) return _category = category - _set_index_open(false, false) _selected_id = StringName() _selected_entry_key = StringName() for index: int in _category_tabs.size(): @@ -1306,11 +683,6 @@ func _configure_category_focus() -> void: tab.focus_neighbor_right = tab.get_path_to( _category_tabs[mini(index + 1, _category_tabs.size() - 1)] ) - if not _category_tabs.is_empty() and _index_tab != null: - var final_tab: Button = _category_tabs.back() - final_tab.focus_neighbor_right = final_tab.get_path_to(_index_tab) - _index_tab.focus_neighbor_left = _index_tab.get_path_to(final_tab) - _index_tab.focus_neighbor_right = _index_tab.get_path_to(_index_tab) func _animate_tab_entry() -> void: @@ -1318,15 +690,11 @@ func _animate_tab_entry() -> void: (_category_tabs[index] as OrganizerTabType).animate_entrance( float(index) * 0.025 ) - if _index_tab != null: - _index_tab.animate_entrance(float(_category_tabs.size()) * 0.025) func _settle_tabs_for_close() -> void: for tab: OrganizerTabType in _category_tabs: tab.settle_for_close() - if _index_tab != null: - _index_tab.settle_for_close() func _begin_category_transition() -> void: @@ -1382,12 +750,9 @@ func _set_category_content_alpha(alpha: float) -> void: _empty_state.modulate.a = alpha if _detail_body != null: _detail_body.modulate.a = alpha - if _index_body != null: - _index_body.modulate.a = alpha func _select_entry(entry_key: StringName, fish_id: StringName) -> void: - _set_index_open(false, false) if fish_id.is_empty(): _selected_id = StringName() _selected_entry_key = entry_key @@ -1919,9 +1284,7 @@ func _on_collection_changed() -> void: func _on_inventory_changed() -> void: - # Ownership filters and favorite-backed views depend on the live cooler, - # while rebuilding the catalog also keeps the selected detail current. - _refresh_catalog() + _refresh_details(false) func _unknown_selection_key(fish: FishDataType) -> StringName: diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 95263f5..3c5dc77 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -105,7 +105,6 @@ signal controller_hotbar_management_requested(initial_slot: int) signal controller_hotbar_management_ended signal menu_exit_started signal shop_cooler_modal_changed(is_open: bool) -signal controller_text_entry_requested(control: Control) enum Section { COOLER, @@ -410,9 +409,6 @@ func _ready() -> void: _logbook_tab.pressed.connect( _show_section.bind(Section.LOGBOOK) ) - _catalog_logbook.controller_text_entry_requested.connect( - controller_text_entry_requested.emit - ) _the_net_tab.pressed.connect(_show_section.bind(Section.NET)) _mail_tab.pressed.connect(_show_section.bind(Section.MAIL)) _profile_tab.pressed.connect(_show_section.bind(Section.PROFILE)) @@ -560,7 +556,6 @@ func setup( discovery: DiscoveryClient, player_jobs: PlayerJobService, world_time: WorldTimeService, - world_weather: WorldWeatherService, world_environment: WorldEnvironment, world_sun: DirectionalLight3D, ) -> void: @@ -594,14 +589,7 @@ func setup( world_sun, ) _players_page.setup(network_player_list, discovery) - _catalog_logbook.setup( - collection_log, - inventory, - catalog, - fishing_spot, - world_time, - world_weather, - ) + _catalog_logbook.setup(collection_log, inventory, catalog) _the_net_page.setup(player_jobs, world_time) _network_mail_service.unread_count_changed.connect( _on_mail_unread_count_changed @@ -1559,14 +1547,11 @@ func open_menu() -> void: _cancel_page_tween() _prior_movement_enabled = _player.is_movement_enabled() _prior_camera_input_enabled = _player.is_camera_input_enabled() - _player.set_movement_enabled(false) - # Ending an active right-click camera drag restores its ordinary pointer - # mode. Snapshot after that handoff so this menu never restores the drag's - # transient captured mode when it closes. - _player.set_camera_input_enabled(false) - _prior_mouse_mode = _get_restorable_mouse_mode(Input.mouse_mode) + _prior_mouse_mode = Input.mouse_mode _control_snapshot_stored = true _mouse_snapshot_stored = true + _player.set_movement_enabled(false) + _player.set_camera_input_enabled(false) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) Input.mouse_mode = Input.MOUSE_MODE_VISIBLE visible = true @@ -1778,16 +1763,6 @@ func _apply_mouse_close_policy(reason: CloseReason) -> void: _mouse_snapshot_stored = false -func _get_restorable_mouse_mode(mouse_mode: Input.MouseMode) -> Input.MouseMode: - # Mouse capture belongs to an in-progress camera drag, never to the player - # menu itself. A held right button can be released while this menu owns - # input, so restoring CAPTURED here would leave the pointer hidden without - # an active drag to release it. - if mouse_mode == Input.MOUSE_MODE_CAPTURED: - return Input.MOUSE_MODE_VISIBLE - return mouse_mode - - func _on_bite_activated() -> void: if visible: close_menu(CloseReason.BITE_STARTED) @@ -2575,10 +2550,7 @@ func _on_tackle_button_gui_input( call_deferred("_release_pointer_focus", button) if ( mouse_event == null - or mouse_event.button_index not in [ - MOUSE_BUTTON_LEFT, - MOUSE_BUTTON_RIGHT, - ] + or mouse_event.button_index != MOUSE_BUTTON_RIGHT or not mouse_event.pressed ): return @@ -2807,7 +2779,6 @@ func _toggle_active_tackle() -> void: func _on_active_bait_changed(_item_id: StringName) -> void: _update_tackle_detail() - _catalog_logbook.notify_availability_context_changed() func _on_active_lure_changed(_item_id: StringName) -> void: @@ -3762,7 +3733,6 @@ func _refresh_all() -> void: func _on_bag_changed() -> void: _refresh_bag() _refresh_tackle_box() - _catalog_logbook.notify_availability_context_changed() func _on_hotbar_changed() -> void: diff --git a/ui/player_storage.gd b/ui/player_storage.gd index e5369a4..25eb0d4 100644 --- a/ui/player_storage.gd +++ b/ui/player_storage.gd @@ -4,7 +4,6 @@ extends Control const INPUT_OWNER: StringName = &"player_storage" signal menu_visibility_changed(is_open: bool) -signal decorate_requested var _player: Player var _fishing_spot: FishingSpot @@ -15,9 +14,6 @@ var _storage_grid: GeneralInventoryGrid var _inventory_count: Label var _storage_count: Label var _feedback: Label -var _title: Label -var _decorate_button: Button -var _home_access: bool = false var _prior_movement_enabled: bool = true var _prior_camera_enabled: bool = true var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE @@ -79,42 +75,20 @@ func set_world_interaction(interaction: PlayerStorageInteraction) -> void: func open_storage() -> bool: - return _open_storage(false) - - -func open_storage_from_home(allow_decorating: bool = true) -> bool: - return _open_storage(true, allow_decorating) - - -func _open_storage( - home_access: bool, - allow_decorating: bool = false, -) -> bool: if ( visible or _player == null + or _interaction == null + or not _interaction.is_local_player_in_range() or _fishing_spot == null or not _fishing_spot.can_open_fishing_shop() - or ( - not home_access - and ( - _interaction == null - or not _interaction.is_local_player_in_range() - ) - ) ): return false - _home_access = home_access - _title.text = "home storage" if home_access else "private storage" - _decorate_button.visible = home_access and allow_decorating _prior_movement_enabled = _player.is_movement_enabled() _prior_camera_enabled = _player.is_camera_input_enabled() + _prior_mouse_mode = Input.mouse_mode _player.set_movement_enabled(false) - # End an in-progress right-click camera drag before taking the snapshot. - # A held button can be released while this page owns input, so captured mode - # is never a valid mouse mode to restore when the page closes. _player.set_camera_input_enabled(false) - _prior_mouse_mode = _get_restorable_mouse_mode(Input.mouse_mode) _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) Input.mouse_mode = Input.MOUSE_MODE_VISIBLE _feedback.text = "select an item to move it · drag to choose a slot" @@ -138,13 +112,10 @@ func close_storage(restore_controls: bool = true) -> void: _player.set_movement_enabled(_prior_movement_enabled) _player.set_camera_input_enabled(_prior_camera_enabled) Input.mouse_mode = _prior_mouse_mode - _home_access = false menu_visibility_changed.emit(false) func close_for_range_exit() -> void: - if _home_access: - return close_storage() @@ -156,12 +127,6 @@ func close_for_session_end() -> void: close_storage(false) -func _get_restorable_mouse_mode(mouse_mode: Input.MouseMode) -> Input.MouseMode: - if mouse_mode == Input.MOUSE_MODE_CAPTURED: - return Input.MOUSE_MODE_VISIBLE - return mouse_mode - - func consume_escape() -> bool: if not visible: return false @@ -283,20 +248,12 @@ func _build_ui() -> void: var header := HBoxContainer.new() layout.add_child(header) - _title = Label.new() - _title.text = "private storage" - _title.add_theme_font_size_override("font_size", 32) - _title.add_theme_color_override("font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY) - _title.size_flags_horizontal = Control.SIZE_EXPAND_FILL - header.add_child(_title) - _decorate_button = Button.new() - _decorate_button.text = "decorate home" - _decorate_button.tooltip_text = "Place decor inside your home" - _decorate_button.custom_minimum_size = Vector2(160.0, 48.0) - UtilityPageStyle.apply_ocean_button(_decorate_button) - _decorate_button.hide() - _decorate_button.pressed.connect(_request_decorating) - header.add_child(_decorate_button) + var title := Label.new() + title.text = "private storage" + title.add_theme_font_size_override("font_size", 32) + title.add_theme_color_override("font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY) + title.size_flags_horizontal = Control.SIZE_EXPAND_FILL + header.add_child(title) var close := Button.new() close.text = "×" close.tooltip_text = "Close storage" @@ -328,13 +285,6 @@ func _build_ui() -> void: layout.add_child(_feedback) -func _request_decorating() -> void: - if not _home_access: - return - close_storage() - decorate_requested.emit() - - func _build_column(parent: HBoxContainer, title_text: String) -> Dictionary: var panel := PanelContainer.new() panel.custom_minimum_size = Vector2(550.0, 0.0) diff --git a/ui/rv_upgrade_shop.gd b/ui/rv_upgrade_shop.gd deleted file mode 100644 index 11c0a4d..0000000 --- a/ui/rv_upgrade_shop.gd +++ /dev/null @@ -1,330 +0,0 @@ -class_name RVUpgradeShop -extends Control - -const INPUT_OWNER: StringName = &"rv_upgrade_shop" -const UtilityPageStyleType = preload("res://ui/utility_page_style.gd") -const CurrencyPresentationType = preload("res://ui/currency_presentation.gd") - -signal menu_visibility_changed(is_open: bool) -signal menu_exit_started - -var _player: Player -var _wallet: PlayerWallet -var _fishing_spot: FishingSpot -var _interaction: RVUpgradeInteraction -var _home_state: PlayerHomeState -var _network_shop: NetworkShopService -var _wallet_amount: CurrencyAmount -var _price_amount: CurrencyAmount -var _home_label: Label -var _storage_label: Label -var _feedback: Label -var _upgrade_button: Button -var _close_button: Button -var _transaction_pending: bool = false -var _prior_movement_enabled: bool = true -var _prior_camera_enabled: bool = true -var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE - - -func _ready() -> void: - UtilityPageStyleType.apply_page(self) - _build_ui() - hide() - - -func setup( - player: Player, - wallet: PlayerWallet, - fishing_spot: FishingSpot, - interaction: RVUpgradeInteraction, - home_state: PlayerHomeState, - network_shop: NetworkShopService, -) -> void: - _player = player - _wallet = wallet - _fishing_spot = fishing_spot - _interaction = interaction - _home_state = home_state - _network_shop = network_shop - if not _wallet.balance_changed.is_connected(_on_wallet_changed): - _wallet.balance_changed.connect(_on_wallet_changed) - if not _home_state.changed.is_connected(_refresh): - _home_state.changed.connect(_refresh) - if ( - _network_shop != null - and not _network_shop.local_purchase_pending.is_connected( - _on_purchase_pending - ) - ): - _network_shop.local_purchase_pending.connect(_on_purchase_pending) - _network_shop.local_purchase_finished.connect(_on_purchase_finished) - _refresh() - - -func set_world_interaction(interaction: RVUpgradeInteraction) -> void: - _interaction = interaction - - -func open_shop() -> bool: - if ( - visible - or _player == null - or _wallet == null - or _fishing_spot == null - or _interaction == null - or not _interaction.is_local_player_in_range() - or not _fishing_spot.can_open_fishing_shop() - ): - return false - _prior_movement_enabled = _player.is_movement_enabled() - _prior_camera_enabled = _player.is_camera_input_enabled() - _player.set_movement_enabled(false) - _player.set_camera_input_enabled(false) - _prior_mouse_mode = _restorable_mouse_mode(Input.mouse_mode) - _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true) - Input.mouse_mode = Input.MOUSE_MODE_VISIBLE - _transaction_pending = ( - _network_shop != null - and _network_shop.is_local_purchase_pending() - ) - _feedback.text = "" - _refresh() - show() - _upgrade_button.call_deferred("grab_focus") - menu_visibility_changed.emit(true) - return true - - -func close_shop(restore_controls: bool = true) -> void: - if not visible: - return - menu_exit_started.emit() - get_viewport().gui_release_focus() - hide() - if _fishing_spot != null: - _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) - if restore_controls and _player != null: - _player.set_movement_enabled(_prior_movement_enabled) - _player.set_camera_input_enabled(_prior_camera_enabled) - Input.mouse_mode = _prior_mouse_mode - menu_visibility_changed.emit(false) - - -func consume_escape() -> bool: - if not visible: - return false - close_shop() - return true - - -func close_for_range_exit() -> void: - close_shop() - - -func close_for_water_recovery() -> void: - close_shop(false) - - -func close_for_session_end() -> void: - close_shop(false) - - -func _unhandled_input(event: InputEvent) -> void: - if not visible or not event.is_action_pressed("ui_cancel"): - return - close_shop() - get_viewport().set_input_as_handled() - - -func _purchase_upgrade() -> void: - if _network_shop == null or _transaction_pending: - return - var request_id: String = _network_shop.request_home_upgrade() - if request_id.is_empty(): - _refresh() - - -func _on_purchase_pending(_request_id: String) -> void: - _transaction_pending = true - _feedback.text = "upgrading…" - _refresh() - - -func _on_purchase_finished( - _request_id: String, - accepted: bool, - message: String, - product_id: StringName, - category: int, - _quantity: int, - _total_cost: int, -) -> void: - if ( - category != NetworkShopProtocol.ProductCategory.HOME_UPGRADE - or product_id != NetworkShopService.HOME_UPGRADE_PRODUCT_ID - ): - return - _transaction_pending = false - _feedback.text = "starter RV unlocked" if accepted else message.to_lower() - _refresh() - - -func _on_wallet_changed(_balance: int, _delta: int) -> void: - _refresh() - - -func _refresh() -> void: - if _wallet_amount == null: - return - var balance: int = _wallet.get_balance() if _wallet != null else 0 - var tier: int = _home_state.get_tier() if _home_state != null else 0 - var current_capacity: int = ( - _home_state.get_storage_capacity() if _home_state != null else 9 - ) - var next_capacity: int = ( - _home_state.get_next_storage_capacity() if _home_state != null else -1 - ) - var cost: int = ( - _home_state.get_next_upgrade_cost() if _home_state != null else -1 - ) - _wallet_amount.set_amount(balance) - _price_amount.set_amount(maxi(cost, 0)) - if tier >= PlayerHomeState.MAX_TIER: - _home_label.text = "starter RV" - _storage_label.text = "storage · %d slots" % current_capacity - _upgrade_button.text = "unlocked" - _upgrade_button.disabled = true - _price_amount.visible = false - else: - _home_label.text = "tent → starter RV" - _storage_label.text = "storage · %d → %d slots" % [ - current_capacity, next_capacity, - ] - _upgrade_button.text = "upgrade" - _upgrade_button.disabled = ( - _transaction_pending - or _wallet == null - or not _wallet.can_afford(cost) - or _network_shop == null - or not _network_shop.can_request_home_upgrade() - ) - _price_amount.visible = true - - -func _restorable_mouse_mode(mouse_mode: Input.MouseMode) -> Input.MouseMode: - return ( - Input.MOUSE_MODE_VISIBLE - if mouse_mode == Input.MOUSE_MODE_CAPTURED - else mouse_mode - ) - - -func _build_ui() -> void: - set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - z_index = 83 - mouse_filter = Control.MOUSE_FILTER_STOP - var blocker := ColorRect.new() - blocker.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - blocker.color = Color(0.02, 0.08, 0.10, 0.55) - blocker.mouse_filter = Control.MOUSE_FILTER_STOP - add_child(blocker) - - var panel := PanelContainer.new() - panel.set_anchors_preset(Control.PRESET_CENTER) - panel.position = Vector2(-390.0, -225.0) - panel.size = Vector2(780.0, 450.0) - panel.add_theme_stylebox_override( - "panel", - UtilityPageStyleType.rounded_style( - UtilityPageStyleType.OCEAN_PANEL_DEEP, 24 - ), - ) - add_child(panel) - var margin := MarginContainer.new() - for side: StringName in [ - &"margin_left", &"margin_top", &"margin_right", &"margin_bottom", - ]: - margin.add_theme_constant_override(side, 28) - panel.add_child(margin) - var layout := VBoxContainer.new() - layout.add_theme_constant_override("separation", 18) - margin.add_child(layout) - - var header := HBoxContainer.new() - header.add_theme_constant_override("separation", 12) - layout.add_child(header) - var title := Label.new() - title.text = "home upgrades" - title.add_theme_font_size_override("font_size", 38) - title.size_flags_horizontal = Control.SIZE_EXPAND_FILL - header.add_child(title) - _wallet_amount = CurrencyPresentationType.instantiate_amount(0, 24.0) - header.add_child(_wallet_amount) - - var offer := PanelContainer.new() - offer.size_flags_vertical = Control.SIZE_EXPAND_FILL - offer.add_theme_stylebox_override( - "panel", - UtilityPageStyleType.rounded_style( - UtilityPageStyleType.OCEAN_FIELD, 18 - ), - ) - layout.add_child(offer) - var offer_margin := MarginContainer.new() - for side: StringName in [ - &"margin_left", &"margin_top", &"margin_right", &"margin_bottom", - ]: - offer_margin.add_theme_constant_override(side, 28) - offer.add_child(offer_margin) - var details := VBoxContainer.new() - details.alignment = BoxContainer.ALIGNMENT_CENTER - details.add_theme_constant_override("separation", 14) - offer_margin.add_child(details) - _home_label = Label.new() - _home_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _home_label.add_theme_font_size_override("font_size", 34) - details.add_child(_home_label) - var description := Label.new() - description.text = ( - "A weatherproof home with a larger interior and built-in storage." - ) - description.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - description.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART - description.add_theme_font_size_override("font_size", 20) - details.add_child(description) - _storage_label = Label.new() - _storage_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER - _storage_label.add_theme_font_size_override("font_size", 22) - details.add_child(_storage_label) - var purchase_row := HBoxContainer.new() - purchase_row.alignment = BoxContainer.ALIGNMENT_CENTER - purchase_row.add_theme_constant_override("separation", 10) - details.add_child(purchase_row) - _upgrade_button = Button.new() - _upgrade_button.custom_minimum_size = Vector2(220.0, 60.0) - UtilityPageStyleType.apply_ocean_button(_upgrade_button) - _upgrade_button.pressed.connect(_purchase_upgrade) - purchase_row.add_child(_upgrade_button) - _price_amount = CurrencyPresentationType.instantiate_amount(0, 24.0) - purchase_row.add_child(_price_amount) - - var footer := HBoxContainer.new() - footer.add_theme_constant_override("separation", 12) - layout.add_child(footer) - _feedback = Label.new() - _feedback.size_flags_horizontal = Control.SIZE_EXPAND_FILL - _feedback.add_theme_font_size_override("font_size", 18) - footer.add_child(_feedback) - _close_button = Button.new() - _close_button.text = "close" - _close_button.custom_minimum_size = Vector2(150.0, 50.0) - UtilityPageStyleType.apply_ocean_button(_close_button) - _close_button.pressed.connect(close_shop) - footer.add_child(_close_button) - _upgrade_button.focus_neighbor_bottom = _upgrade_button.get_path_to( - _close_button - ) - _close_button.focus_neighbor_top = _close_button.get_path_to( - _upgrade_button - ) diff --git a/ui/rv_upgrade_shop.gd.uid b/ui/rv_upgrade_shop.gd.uid deleted file mode 100644 index ddd043e..0000000 --- a/ui/rv_upgrade_shop.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://b5pnsuph7fxrd diff --git a/ui/rv_upgrade_shop.tscn b/ui/rv_upgrade_shop.tscn deleted file mode 100644 index 22abf58..0000000 --- a/ui/rv_upgrade_shop.tscn +++ /dev/null @@ -1,14 +0,0 @@ -[gd_scene load_steps=2 format=3] - -[ext_resource type="Script" path="res://ui/rv_upgrade_shop.gd" id="1_script"] - -[node name="RVUpgradeShop" type="Control"] -unique_name_in_owner = true -visible = false -layout_mode = 3 -anchors_preset = 15 -anchor_right = 1.0 -anchor_bottom = 1.0 -grow_horizontal = 2 -grow_vertical = 2 -script = ExtResource("1_script") diff --git a/ui/title_screen.tscn b/ui/title_screen.tscn index 22f00f2..d1fc210 100644 --- a/ui/title_screen.tscn +++ b/ui/title_screen.tscn @@ -213,7 +213,7 @@ unique_name_in_owner = true layout_mode = 2 theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1) theme_override_font_sizes/font_size = 22 -text = "v0.20.2-alpha" +text = "v0.19.0-alpha" horizontal_alignment = 1 [node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"] diff --git a/world/decor_shop_interaction.gd b/world/decor_shop_interaction.gd deleted file mode 100644 index a325134..0000000 --- a/world/decor_shop_interaction.gd +++ /dev/null @@ -1,10 +0,0 @@ -class_name DecorShopInteraction -extends FishingShopInteraction - -const DECOR_DIALOGUE_TEXT: String = ( - "You gonna decorate that hot box or what? I've got some new decor in, take a look." -) - - -func get_dialogue_text(_home_tier: int = -1) -> String: - return DECOR_DIALOGUE_TEXT diff --git a/world/decor_shop_interaction.gd.uid b/world/decor_shop_interaction.gd.uid deleted file mode 100644 index 955738c..0000000 --- a/world/decor_shop_interaction.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://u70jgwedk7ym diff --git a/world/fishing_shop_interaction.gd b/world/fishing_shop_interaction.gd index 9628ee6..d912eca 100644 --- a/world/fishing_shop_interaction.gd +++ b/world/fishing_shop_interaction.gd @@ -3,10 +3,6 @@ extends Area3D signal local_player_range_changed(in_range: bool) -const DIALOGUE_TEXT: String = ( - "Listen, you may suck at fishing but I've got some things that might help." -) - @export_node_path("Node3D") var look_character_path: NodePath = ^"../Shopkeeper" var _local_player: Player @@ -41,10 +37,6 @@ func is_avatar_in_range(avatar: Player) -> bool: ) -func get_dialogue_text(_home_tier: int = -1) -> String: - return DIALOGUE_TEXT - - func get_prompt_anchor_position() -> Vector3: if _look_character != null: return _look_character.get_head_anchor_position() diff --git a/world/generation/generated_world_region.gd b/world/generation/generated_world_region.gd index 61484fd..aa27a65 100644 --- a/world/generation/generated_world_region.gd +++ b/world/generation/generated_world_region.gd @@ -9,12 +9,6 @@ const FishingShopInteractionType = preload( const PlayerStorageInteractionType = preload( "res://world/player_storage_interaction.gd" ) -const DecorShopInteractionType = preload( - "res://world/decor_shop_interaction.gd" -) -const RVUpgradeInteractionType = preload( - "res://world/rv_upgrade_interaction.gd" -) const PrecipitationOcclusionType = preload( "res://world/environment/precipitation_occlusion.gd" ) @@ -49,14 +43,6 @@ const PROP_MINIMUM_GROUND_CLEARANCE := 0.05 const PROP_MAXIMUM_SUPPORT_HEIGHT_DIFFERENCE := 0.35 const PROP_CHANCE_SCALE := 10000 const PROP_SELECTION_WEIGHT_SCALE := 1000 -const WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE := 0.05 -const WATER_RECOVERY_MINIMUM_UP_DOT := 0.6 -const WATER_RECOVERY_FOOTPRINT_RADIUS := 0.6 -const WATER_RECOVERY_INSET_STEP := 0.25 -const WATER_RECOVERY_MAXIMUM_INSET := 1.5 -const WATER_RECOVERY_SEARCH_EXPANSIONS: Array[float] = [1.5, 4.0, 12.0] -const HOME_GROUND_HEIGHT_TOLERANCE := 0.5 -const HOME_ELEVATION_HEIGHT_TOLERANCE := 0.6 const PROP_SURFACE_SAMPLE_DIRECTIONS: Array[Vector2] = [ Vector2(1.0, 0.0), Vector2(0.70710678, 0.70710678), @@ -88,16 +74,8 @@ const PROCEDURAL_PROP_GROUPS: Array[StringName] = [ @onready var _player_storage: PlayerStorageInteractionType = ( $Interactables/PlayerStorageBox/InteractionArea ) -@onready var _decor_shop: DecorShopInteractionType = ( - $Interactables/DecorShopWorld/InteractionArea -) -@onready var _rv_upgrade_shop: RVUpgradeInteractionType = ( - $Interactables/RVUpgradeShopWorld/InteractionArea -) @onready var _shop_root: Node3D = %FishingShopWorld @onready var _storage_root: Node3D = %PlayerStorageBox -@onready var _decor_shop_root: Node3D = %DecorShopWorld -@onready var _rv_upgrade_shop_root: Node3D = %RVUpgradeShopWorld @onready var _decorations: Node3D = %Decorations @onready var _tree_anchors: GatherableAnchorSet3D = %ReachableTreeTrunks @onready var _diggable_beach: DiggableArea3D = %DiggableBeach @@ -112,8 +90,6 @@ var _placed_prop_clearance_radii: Array[float] = [] var _placed_prop_groups: Array[StringName] = [] var _placed_group_coordinates: Dictionary[StringName, Array] = {} var _biome_assignments: Dictionary[Vector2i, StringName] = {} -var _water_recovery_triangles_by_coordinate: Dictionary[Vector2i, Array] = {} -var _home_accessible_elevations_by_coordinate: Dictionary[Vector2i, Array] = {} func _ready() -> void: @@ -165,14 +141,6 @@ func get_player_storage() -> PlayerStorageInteractionType: return _player_storage -func get_decor_shop() -> DecorShopInteractionType: - return _decor_shop - - -func get_rv_upgrade_shop() -> RVUpgradeInteractionType: - return _rv_upgrade_shop - - func get_saltwater_shoreline_mesh() -> MeshInstance3D: return _shoreline_reference @@ -198,7 +166,6 @@ func get_spawn_surface_triangles( material_names: Array[StringName], minimum_global_y: float, minimum_up_dot: float = 0.6, - maximum_global_y: float = 100.0, ) -> Array[PackedVector3Array]: var triangles: Array[PackedVector3Array] = [] if material_names.is_empty(): @@ -219,62 +186,13 @@ func get_spawn_surface_triangles( mesh.surface_get_arrays(surface_index), minimum_global_y, minimum_up_dot, - maximum_global_y, ) return triangles -func is_home_exterior_position_accessible(position: Vector3) -> bool: - var local_position := _generator.to_local(position) - if local_position.y <= HOME_GROUND_HEIGHT_TOLERANCE: - return true - var coordinate := _generator.coordinate_for_local_position(local_position) - for value: Variant in _home_accessible_elevations_by_coordinate.get( - coordinate, - [], - ): - if absf(local_position.y - float(value)) <= ( - HOME_ELEVATION_HEIGHT_TOLERANCE - ): - return true - return false - - -func get_water_recovery_position( - entry_position: Vector3, - fallback_position: Vector3, -) -> Vector3: - if ( - not entry_position.is_finite() - or _water_recovery_triangles_by_coordinate.is_empty() - ): - return super(entry_position, fallback_position) - var nearest_surface_distance := _nearest_recovery_surface_distance( - entry_position - ) - if not is_finite(nearest_surface_distance): - return super(entry_position, fallback_position) - for expansion: float in WATER_RECOVERY_SEARCH_EXPANSIONS: - var maximum_distance := nearest_surface_distance + expansion - var result: Variant = _nearest_safe_recovery_surface_position( - entry_position, - maximum_distance, - ) - if result is Vector3: - return result as Vector3 - var map_wide_result: Variant = _nearest_safe_recovery_surface_position( - entry_position, - get_playable_half_extents().length() * 2.0, - ) - if map_wide_result is Vector3: - return map_wide_result as Vector3 - return super(entry_position, fallback_position) - - func _on_generation_completed(summary: Dictionary) -> void: _diggable_beach.invalidate_surface_cache() var records: Array[Dictionary] = _generator.placement_records() - _cache_home_accessible_elevations(records) _assign_biomes(records, summary) var spawn_position := Vector3.ZERO _clear_children(_decorations) @@ -407,10 +325,6 @@ func _on_generation_completed(summary: Dictionary) -> void: grass_surface_triangles_by_coordinate, sand_surface_triangles_by_coordinate, ) - _cache_water_recovery_surfaces( - grass_surface_triangles_by_coordinate, - sand_surface_triangles_by_coordinate, - ) _place_spawn_amenities(spawn_position) _configure_fresh_water(records) _configure_diggable_area() @@ -421,97 +335,6 @@ func _on_generation_completed(summary: Dictionary) -> void: world_generated.emit(_current_seed, summary) -func _cache_home_accessible_elevations( - records: Array[Dictionary], -) -> void: - _home_accessible_elevations_by_coordinate.clear() - var records_by_coordinate: Dictionary[Vector2i, Dictionary] = {} - var elevation_tiers: Dictionary[int, bool] = {} - for record: Dictionary in records: - var coordinate: Vector2i = record.get( - "coordinate", - Vector2i(-1, -1), - ) - records_by_coordinate[coordinate] = record - for tier: int in _elevation_tiers_for_tags( - record.get("tags", PackedStringArray()) - ): - elevation_tiers[tier] = true - var visited: Dictionary[String, bool] = {} - for tier: int in elevation_tiers: - var elevation_tag := "elevation_%d" % tier - for coordinate: Vector2i in records_by_coordinate: - var visit_key := _home_elevation_visit_key(coordinate, tier) - if visited.has(visit_key): - continue - var record: Dictionary = records_by_coordinate[coordinate] - var tags: PackedStringArray = record.get( - "tags", - PackedStringArray(), - ) - if elevation_tag not in tags: - continue - var component: Array[Vector2i] = [] - var pending: Array[Vector2i] = [coordinate] - var has_ramp := false - while not pending.is_empty(): - var current: Vector2i = pending.pop_back() - var current_key := _home_elevation_visit_key(current, tier) - if visited.has(current_key): - continue - var current_record: Dictionary = records_by_coordinate.get( - current, - {}, - ) - var current_tags: PackedStringArray = current_record.get( - "tags", - PackedStringArray(), - ) - if elevation_tag not in current_tags: - continue - visited[current_key] = true - component.append(current) - has_ramp = has_ramp or "ramp" in current_tags - for offset: Vector2i in [ - Vector2i.LEFT, - Vector2i.RIGHT, - Vector2i.UP, - Vector2i.DOWN, - ]: - pending.append(current + offset) - if not has_ramp: - continue - var surface_height := ( - float(tier - 1) * _generator.elevated_cliff_level_height - ) - for component_coordinate: Vector2i in component: - var heights: Array = ( - _home_accessible_elevations_by_coordinate.get( - component_coordinate, - [], - ) - ) - heights.append(surface_height) - _home_accessible_elevations_by_coordinate[ - component_coordinate - ] = heights - - -func _elevation_tiers_for_tags(tags: PackedStringArray) -> Array[int]: - var tiers: Array[int] = [] - for tag_value: String in tags: - if not tag_value.begins_with("elevation_"): - continue - var suffix := tag_value.trim_prefix("elevation_") - if suffix.is_valid_int(): - tiers.append(suffix.to_int()) - return tiers - - -func _home_elevation_visit_key(coordinate: Vector2i, tier: int) -> String: - return "%d:%d:%d" % [tier, coordinate.x, coordinate.y] - - func _mark_precipitation_occluders() -> void: for prop: Node in _decorations.get_children(): var prop_group := prop.get_meta(&"terrain_prop_group", &"") as StringName @@ -563,10 +386,6 @@ func _place_spawn_amenities(center: Vector3) -> void: _shop_root.rotation.y = PI _storage_root.position = center + Vector3(-2.4, 0.0, 1.2) _storage_root.rotation.y = PI * 0.5 - _decor_shop_root.position = center + Vector3(-2.4, 0.0, 1.2) - _decor_shop_root.rotation.y = PI * 0.5 - _rv_upgrade_shop_root.position = center + Vector3(-2.35, 0.0, -1.35) - _rv_upgrade_shop_root.rotation.y = PI func _configure_static_water() -> void: @@ -1430,7 +1249,6 @@ func _terrain_surface_triangles_by_coordinate( mesh_instance.mesh.surface_get_arrays(surface_index), -INF, 0.35, - 100.0, ) if triangles.is_empty(): continue @@ -1443,203 +1261,6 @@ func _terrain_surface_triangles_by_coordinate( return triangles_by_coordinate -func _cache_water_recovery_surfaces( - grass_triangles: Dictionary[Vector2i, Array], - sand_triangles: Dictionary[Vector2i, Array], -) -> void: - _water_recovery_triangles_by_coordinate.clear() - _append_water_recovery_surfaces(grass_triangles) - _append_water_recovery_surfaces(sand_triangles) - - -func _append_water_recovery_surfaces( - source: Dictionary[Vector2i, Array], -) -> void: - for coordinate: Vector2i in source: - var eligible: Array = _water_recovery_triangles_by_coordinate.get( - coordinate, - [], - ) - for triangle: PackedVector3Array in source[coordinate]: - if triangle.size() != 3: - continue - if minf( - triangle[0].y, - minf(triangle[1].y, triangle[2].y), - ) <= WATER_HEIGHT + WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE: - continue - var cross := (triangle[1] - triangle[0]).cross( - triangle[2] - triangle[0] - ) - if ( - cross.length_squared() <= 0.0000001 - or absf(cross.normalized().dot(Vector3.UP)) - < WATER_RECOVERY_MINIMUM_UP_DOT - ): - continue - eligible.append(triangle) - if not eligible.is_empty(): - _water_recovery_triangles_by_coordinate[coordinate] = eligible - - -func _nearest_recovery_surface_distance(entry_position: Vector3) -> float: - var entry_horizontal := Vector2(entry_position.x, entry_position.z) - var nearest_distance_squared := INF - for coordinate: Vector2i in _water_recovery_triangles_by_coordinate: - for triangle: PackedVector3Array in ( - _water_recovery_triangles_by_coordinate[coordinate] - ): - var closest := _closest_horizontal_point_on_triangle( - entry_horizontal, - triangle, - ) - nearest_distance_squared = minf( - nearest_distance_squared, - entry_horizontal.distance_squared_to(closest), - ) - return sqrt(nearest_distance_squared) - - -func _nearest_safe_recovery_surface_position( - entry_position: Vector3, - maximum_distance: float, -) -> Variant: - var entry_horizontal := Vector2(entry_position.x, entry_position.z) - var maximum_distance_squared := maximum_distance * maximum_distance - var best_distance_squared := INF - var best_position: Variant = null - for coordinate: Vector2i in _water_recovery_triangles_by_coordinate: - for triangle: PackedVector3Array in ( - _water_recovery_triangles_by_coordinate[coordinate] - ): - var closest := _closest_horizontal_point_on_triangle( - entry_horizontal, - triangle, - ) - if ( - entry_horizontal.distance_squared_to(closest) - > maximum_distance_squared - ): - continue - var centroid := Vector2( - (triangle[0].x + triangle[1].x + triangle[2].x) / 3.0, - (triangle[0].z + triangle[1].z + triangle[2].z) / 3.0, - ) - var inset_distance := minf( - closest.distance_to(centroid), - WATER_RECOVERY_MAXIMUM_INSET, - ) - var inset_steps := ceili( - inset_distance / WATER_RECOVERY_INSET_STEP - ) - for inset_index: int in inset_steps + 1: - var candidate_horizontal := closest.move_toward( - centroid, - minf( - float(inset_index) * WATER_RECOVERY_INSET_STEP, - inset_distance, - ), - ) - var distance_squared := entry_horizontal.distance_squared_to( - candidate_horizontal - ) - if ( - distance_squared > maximum_distance_squared - or distance_squared >= best_distance_squared - ): - continue - var candidate := Vector3( - candidate_horizontal.x, - entry_position.y, - candidate_horizontal.y, - ) - var safe_position: Variant = _safe_recovery_surface_position( - candidate - ) - if safe_position is Vector3: - best_distance_squared = distance_squared - best_position = safe_position - return best_position - - -func _safe_recovery_surface_position(candidate: Vector3) -> Variant: - var nearby_triangles := _water_recovery_surface_triangles_near(candidate) - var surface_height := _surface_height_at(candidate, nearby_triangles) - if ( - surface_height <= WATER_HEIGHT + WATER_RECOVERY_MINIMUM_GROUND_CLEARANCE - or not _surface_supports_prop_footprint( - candidate, - surface_height, - WATER_RECOVERY_FOOTPRINT_RADIUS, - nearby_triangles, - ) - or not _has_prop_clearance( - candidate, - WATER_RECOVERY_FOOTPRINT_RADIUS, - ) - ): - return null - candidate.y = surface_height - return candidate - - -func _water_recovery_surface_triangles_near( - position: Vector3, -) -> Array[PackedVector3Array]: - var result: Array[PackedVector3Array] = [] - if _generator.catalog == null or _generator.catalog.chunk_size <= 0.0: - return result - var local_position := _generator.to_local(position) - var half_grid := Vector2( - float(_generator.grid_size.x - 1) * 0.5, - float(_generator.grid_size.y - 1) * 0.5, - ) - var coordinate := Vector2i( - roundi(local_position.x / _generator.catalog.chunk_size + half_grid.x), - roundi(local_position.z / _generator.catalog.chunk_size + half_grid.y), - ) - for offset_x: int in range(-1, 2): - for offset_y: int in range(-1, 2): - var nearby_coordinate := coordinate + Vector2i(offset_x, offset_y) - result.append_array( - _water_recovery_triangles_by_coordinate.get( - nearby_coordinate, - [], - ) - ) - return result - - -func _closest_horizontal_point_on_triangle( - point: Vector2, - triangle: PackedVector3Array, -) -> Vector2: - var polygon := PackedVector2Array([ - Vector2(triangle[0].x, triangle[0].z), - Vector2(triangle[1].x, triangle[1].z), - Vector2(triangle[2].x, triangle[2].z), - ]) - if Geometry2D.is_point_in_polygon(point, polygon): - return point - var closest := Geometry2D.get_closest_point_to_segment( - point, - polygon[0], - polygon[1], - ) - var nearest_distance_squared := point.distance_squared_to(closest) - for edge_index: int in range(1, 3): - var edge_closest := Geometry2D.get_closest_point_to_segment( - point, - polygon[edge_index], - polygon[(edge_index + 1) % 3], - ) - var distance_squared := point.distance_squared_to(edge_closest) - if distance_squared < nearest_distance_squared: - nearest_distance_squared = distance_squared - closest = edge_closest - return closest - - func _terrain_coordinate_for(mesh_instance: MeshInstance3D) -> Vector2i: var current: Node = mesh_instance while current != null and current != _generator: @@ -1778,7 +1399,6 @@ func _append_surface_triangles( arrays: Array, minimum_global_y: float, minimum_up_dot: float, - maximum_global_y: float, ) -> void: if arrays.size() <= Mesh.ARRAY_INDEX: return @@ -1788,28 +1408,10 @@ func _append_surface_triangles( return if indices.is_empty(): for index: int in range(0, vertices.size() - 2, 3): - _append_triangle( - result, - mesh_instance, - vertices[index], - vertices[index + 1], - vertices[index + 2], - minimum_global_y, - minimum_up_dot, - maximum_global_y, - ) + _append_triangle(result, mesh_instance, vertices[index], vertices[index + 1], vertices[index + 2], minimum_global_y, minimum_up_dot) return for index: int in range(0, indices.size() - 2, 3): - _append_triangle( - result, - mesh_instance, - vertices[indices[index]], - vertices[indices[index + 1]], - vertices[indices[index + 2]], - minimum_global_y, - minimum_up_dot, - maximum_global_y, - ) + _append_triangle(result, mesh_instance, vertices[indices[index]], vertices[indices[index + 1]], vertices[indices[index + 2]], minimum_global_y, minimum_up_dot) func _append_triangle( @@ -1820,15 +1422,11 @@ func _append_triangle( local_c: Vector3, minimum_global_y: float, minimum_up_dot: float, - maximum_global_y: float, ) -> void: var a := mesh_instance.to_global(local_a) var b := mesh_instance.to_global(local_b) var c := mesh_instance.to_global(local_c) - if ( - maxf(a.y, maxf(b.y, c.y)) <= minimum_global_y - or minf(a.y, minf(b.y, c.y)) >= maximum_global_y - ): + if minf(a.y, minf(b.y, c.y)) <= minimum_global_y: return var cross := (b - a).cross(c - a) if cross.length_squared() <= 0.0000001: diff --git a/world/generation/generated_world_region.tscn b/world/generation/generated_world_region.tscn index c996b88..47ee6bc 100644 --- a/world/generation/generated_world_region.tscn +++ b/world/generation/generated_world_region.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=16 format=3] +[gd_scene load_steps=14 format=3] [ext_resource type="Script" path="res://world/generation/generated_world_region.gd" id="1_region"] [ext_resource type="Script" path="res://world/generation/terrain_chunk_generator.gd" id="2_generator"] @@ -13,8 +13,6 @@ [ext_resource type="Resource" path="res://fish/pools/starter_pond_pool.tres" id="11_pond_pool"] [ext_resource type="Resource" path="res://world/generation/props/terrain_prop_catalog.tres" id="12_prop_catalog"] [ext_resource type="Resource" path="res://world/generation/biomes/terrain_biome_catalog.tres" id="13_biome_catalog"] -[ext_resource type="PackedScene" path="res://world/interactables/decor_shop_world.tscn" id="14_decor_shop"] -[ext_resource type="PackedScene" path="res://world/interactables/rv_upgrade_shop_world.tscn" id="15_rv_upgrade"] [node name="GeneratedWorldRegion" type="Node3D"] script = ExtResource("1_region") @@ -100,12 +98,6 @@ unique_name_in_owner = true [node name="PlayerStorageBox" parent="Interactables" instance=ExtResource("6_storage")] unique_name_in_owner = true -[node name="DecorShopWorld" parent="Interactables" instance=ExtResource("14_decor_shop")] -unique_name_in_owner = true - -[node name="RVUpgradeShopWorld" parent="Interactables" instance=ExtResource("15_rv_upgrade")] -unique_name_in_owner = true - [node name="Decorations" type="Node3D" parent="."] unique_name_in_owner = true diff --git a/world/generation/terrain_chunk_generator.gd b/world/generation/terrain_chunk_generator.gd index 64a8bd2..49bf693 100644 --- a/world/generation/terrain_chunk_generator.gd +++ b/world/generation/terrain_chunk_generator.gd @@ -4414,19 +4414,6 @@ func chunk_position(coordinate: Vector2i) -> Vector3: ) -func coordinate_for_local_position(position: Vector3) -> Vector2i: - if catalog == null or catalog.chunk_size <= 0.0: - return Vector2i(-1, -1) - var half_grid := Vector2( - float(grid_size.x - 1) * 0.5, - float(grid_size.y - 1) * 0.5, - ) - return Vector2i( - roundi(position.x / catalog.chunk_size + half_grid.x), - roundi(position.z / catalog.chunk_size + half_grid.y), - ) - - func get_generated_chunks_root() -> Node3D: return _generated_chunks diff --git a/world/interactables/decor_shop_world.tscn b/world/interactables/decor_shop_world.tscn deleted file mode 100644 index 7f94ec4..0000000 --- a/world/interactables/decor_shop_world.tscn +++ /dev/null @@ -1,35 +0,0 @@ -[gd_scene load_steps=5 format=3] - -[ext_resource type="Script" path="res://world/decor_shop_interaction.gd" id="1_shop"] -[ext_resource type="PackedScene" path="res://art/exported/characters/base/straywild_base_character.glb" id="2_character"] -[ext_resource type="Script" path="res://world/world_character_display.gd" id="3_display"] - -[sub_resource type="SphereShape3D" id="InteractionShape"] -resource_local_to_scene = true -radius = 2.1 - -[node name="DecorShopWorld" type="Node3D"] - -[node name="Shopkeeper" parent="." instance=ExtResource("2_character")] -script = ExtResource("3_display") -species_id = "round" -fur_color_id = "brown" -ears_id = "bear" -tail_id = "bear" -eyes_id = "tired" -nose_id = "triangle_pink" -mouth_id = "simple_smile" -head_look_enabled = true - -[node name="AnimationPlayer" parent="Shopkeeper"] -autoplay = "idle" - -[node name="InteractionArea" type="Area3D" parent="."] -unique_name_in_owner = true -position = Vector3(0, 1, -0.3) -collision_layer = 0 -collision_mask = 2 -script = ExtResource("1_shop") - -[node name="InteractionShape" type="CollisionShape3D" parent="InteractionArea"] -shape = SubResource("InteractionShape") diff --git a/world/interactables/fishing_shop_world.tscn b/world/interactables/fishing_shop_world.tscn index 040b558..760f3b6 100644 --- a/world/interactables/fishing_shop_world.tscn +++ b/world/interactables/fishing_shop_world.tscn @@ -6,7 +6,7 @@ [sub_resource type="SphereShape3D" id="InteractionShape"] resource_local_to_scene = true -radius = 2.1 +radius = 3.0 [node name="FishingShopWorld" type="Node3D"] diff --git a/world/interactables/player_storage_box.tscn b/world/interactables/player_storage_box.tscn index 008f0ac..4bf9a8e 100644 --- a/world/interactables/player_storage_box.tscn +++ b/world/interactables/player_storage_box.tscn @@ -27,8 +27,6 @@ size = Vector3(1.62, 0.86, 1.02) radius = 2.3 [node name="PlayerStorageBox" type="Node3D"] -process_mode = 4 -visible = false [node name="Body" type="MeshInstance3D" parent="."] position = Vector3(0, 0.35, 0) @@ -46,7 +44,6 @@ collision_mask = 0 [node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D"] position = Vector3(0, 0.43, 0) -disabled = true shape = SubResource("CollisionShape") [node name="InteractionArea" type="Area3D" parent="."] @@ -57,7 +54,6 @@ collision_mask = 2 script = ExtResource("1_interaction") [node name="CollisionShape3D" type="CollisionShape3D" parent="InteractionArea"] -disabled = true shape = SubResource("InteractionShape") [node name="PromptAnchor" type="Marker3D" parent="."] diff --git a/world/interactables/rv_upgrade_shop_world.tscn b/world/interactables/rv_upgrade_shop_world.tscn deleted file mode 100644 index 9fd0ed2..0000000 --- a/world/interactables/rv_upgrade_shop_world.tscn +++ /dev/null @@ -1,36 +0,0 @@ -[gd_scene load_steps=5 format=3] - -[ext_resource type="Script" path="res://world/rv_upgrade_interaction.gd" id="1_shop"] -[ext_resource type="PackedScene" path="res://art/exported/characters/base/straywild_base_character.glb" id="2_character"] -[ext_resource type="Script" path="res://world/world_character_display.gd" id="3_display"] - -[sub_resource type="SphereShape3D" id="InteractionShape"] -resource_local_to_scene = true -radius = 2.1 - -[node name="RVUpgradeShopWorld" type="Node3D"] - -[node name="Shopkeeper" parent="." instance=ExtResource("2_character")] -script = ExtResource("3_display") -species_id = "pointy" -fur_color_id = "teal" -ears_id = "pointy_long" -tail_id = "fox" -eyes_id = "interested" -nose_id = "triangle_small" -mouth_id = "cheeky_smile" -head_look_enabled = true - -[node name="AnimationPlayer" parent="Shopkeeper"] -autoplay = "idle" - -[node name="InteractionArea" type="Area3D" parent="."] -unique_name_in_owner = true -position = Vector3(0, 1, -0.3) -collision_layer = 0 -collision_mask = 2 -script = ExtResource("1_shop") - -[node name="InteractionShape" type="CollisionShape3D" parent="InteractionArea"] -shape = SubResource("InteractionShape") -vertical_icon_alignment = 1 diff --git a/world/regions/starter_island_region.gd b/world/regions/starter_island_region.gd index c690e4b..3af3010 100644 --- a/world/regions/starter_island_region.gd +++ b/world/regions/starter_island_region.gd @@ -8,12 +8,6 @@ const FishingShopInteractionType = preload( const PlayerStorageInteractionType = preload( "res://world/player_storage_interaction.gd" ) -const DecorShopInteractionType = preload( - "res://world/decor_shop_interaction.gd" -) -const RVUpgradeInteractionType = preload( - "res://world/rv_upgrade_interaction.gd" -) const FOLIAGE_WIND_SHADER: Shader = preload( "res://world/materials/foliage_wind.gdshader" ) @@ -55,14 +49,6 @@ var fishing_shop_path: NodePath = ( var player_storage_path: NodePath = ( ^"Interactables/PlayerStorageBox/InteractionArea" ) -@export_node_path("Area3D") -var decor_shop_path: NodePath = ( - ^"Interactables/DecorShopWorld/InteractionArea" -) -@export_node_path("Area3D") -var rv_upgrade_shop_path: NodePath = ( - ^"Interactables/RVUpgradeShopWorld/InteractionArea" -) @export_group("Terrain Collision") # The imported GLB is the collision authority. Rebuild once per region load so # newly authored terrain and props cannot retain a stale saved fallback shape. @@ -98,14 +84,6 @@ func get_player_storage() -> PlayerStorageInteractionType: return get_node_or_null(player_storage_path) as PlayerStorageInteractionType -func get_decor_shop() -> DecorShopInteractionType: - return get_node_or_null(decor_shop_path) as DecorShopInteractionType - - -func get_rv_upgrade_shop() -> RVUpgradeInteractionType: - return get_node_or_null(rv_upgrade_shop_path) as RVUpgradeInteractionType - - func get_saltwater_shoreline_mesh() -> MeshInstance3D: return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D @@ -161,7 +139,6 @@ func get_spawn_surface_triangles( material_names: Array[StringName], minimum_global_y: float, minimum_up_dot: float = 0.6, - maximum_global_y: float = 100.0, ) -> Array[PackedVector3Array]: var triangles: Array[PackedVector3Array] = [] if material_names.is_empty(): @@ -200,7 +177,6 @@ func get_spawn_surface_triangles( vertices[vertex_index + 2], minimum_global_y, minimum_up_dot, - maximum_global_y, ) else: for index_offset: int in range(0, indices.size() - 2, 3): @@ -212,7 +188,6 @@ func get_spawn_surface_triangles( vertices[indices[index_offset + 2]], minimum_global_y, minimum_up_dot, - maximum_global_y, ) return triangles @@ -225,14 +200,14 @@ func _append_spawn_triangle( local_c: Vector3, minimum_global_y: float, minimum_up_dot: float, - maximum_global_y: float, ) -> void: var a: Vector3 = mesh_instance.to_global(local_a) var b: Vector3 = mesh_instance.to_global(local_b) var c: Vector3 = mesh_instance.to_global(local_c) if ( - maxf(a.y, maxf(b.y, c.y)) <= minimum_global_y - or minf(a.y, minf(b.y, c.y)) >= maximum_global_y + a.y <= minimum_global_y + or b.y <= minimum_global_y + or c.y <= minimum_global_y ): return var cross: Vector3 = (b - a).cross(c - a) diff --git a/world/regions/starter_island_region.tscn b/world/regions/starter_island_region.tscn index 7682491..8f4bc4b 100644 --- a/world/regions/starter_island_region.tscn +++ b/world/regions/starter_island_region.tscn @@ -18,8 +18,6 @@ [ext_resource type="Script" path="res://world/water/shoreline_ribbon_config.gd" id="22_shoreline_config"] [ext_resource type="Script" path="res://world/digging/diggable_area_3d.gd" id="23_diggable_area"] [ext_resource type="Script" path="res://world/gathering/gatherable_anchor_set_3d.gd" id="24_gatherable_anchors"] -[ext_resource type="PackedScene" path="res://world/interactables/decor_shop_world.tscn" id="25_decor_shop"] -[ext_resource type="PackedScene" path="res://world/interactables/rv_upgrade_shop_world.tscn" id="26_rv_upgrade"] [sub_resource type="ConcavePolygonShape3D" id="ConcavePolygonShape3D_4e4k5"] data = PackedVector3Array(10.033899, 3.3913999, 3.3446, 13.3786, 3.3913999, 0, 13.3786, 3.3913999, 3.3446, 10.033899, 3.3913999, 3.3446, 10.033899, 3.3913999, 0, 13.3786, 3.3913999, 0, 6.6893, 3.3913999, 3.3446, 10.033899, 3.3913999, 0, 10.033899, 3.3913999, 3.3446, 6.6893, 3.3913999, 3.3446, 6.6893, 3.3913999, 0, 10.033899, 3.3913999, 0, 6.6893, 3.3913999, 0, 10.033899, 3.3913999, -3.3446, 10.033899, 3.3913999, 0, 6.6893, 3.3913999, 6.6893, 6.6893, 3.3913999, 3.3446, 10.033899, 3.3913999, 3.3446, 6.6893, 3.3913999, 6.6893, 10.033899, 3.3913999, 3.3446, 10.033899, 3.3913999, 6.6893, 3.3446, 3.3913999, 3.3446, 6.6893, 3.3913999, 0, 6.6893, 3.3913999, 3.3446, 6.6893, 3.3913999, 10.033899, 6.6893, 3.3913999, 6.6893, 10.033899, 3.3913999, 6.6893, 6.6893, 3.3913999, 10.033899, 10.033899, 3.3913999, 6.6893, 10.033899, 3.3913999, 10.033899, 3.3446, 3.3913999, 6.6893, 6.6893, 3.3913999, 3.3446, 6.6893, 3.3913999, 6.6893, 3.3446, 3.3913999, 6.6893, 3.3446, 3.3913999, 3.3446, 6.6893, 3.3913999, 3.3446, 3.3446, 3.3913999, 10.033899, 6.6893, 3.3913999, 6.6893, 6.6893, 3.3913999, 10.033899, 3.3446, 3.3913999, 10.033899, 3.3446, 3.3913999, 6.6893, 6.6893, 3.3913999, 6.6893, 0, 3.3913999, 6.6893, 3.3446, 3.3913999, 3.3446, 3.3446, 3.3913999, 6.6893, 0, 3.3913999, 6.6893, 0, 3.3913999, 3.3446, 3.3446, 3.3913999, 3.3446, 0, 3.3913999, 10.033899, 0, 3.3913999, 6.6893, 3.3446, 3.3913999, 6.6893, 0, 3.3913999, 10.033899, 3.3446, 3.3913999, 6.6893, 3.3446, 3.3913999, 10.033899, -3.3446, 3.3913999, 13.3786, -3.3446, 3.3913999, 10.033899, 0, 3.3913999, 10.033899, -3.3446, 3.3913999, 13.3786, 0, 3.3913999, 10.033899, 0, 3.3913999, 13.3786, 0, 3.3913999, 13.3786, 0, 3.3913999, 10.033899, 3.3446, 3.3913999, 10.033899, 0, 3.3913999, 13.3786, 3.3446, 3.3913999, 10.033899, 3.3446, 3.3913999, 13.3786, 3.3446, 3.3913999, 13.3786, 3.3446, 3.3913999, 10.033899, 6.6893, 3.3913999, 10.033899, 3.3446, 3.3913999, 13.3786, 6.6893, 3.3913999, 10.033899, 6.6893, 3.3913999, 13.3786, 3.9148, 3.3913999, 16.514599, 3.3446, 3.3913999, 13.3786, 6.6893, 3.3913999, 13.3786, 3.3446, 3.3913999, 13.3786, 3.9148, 3.3913999, 16.514599, 3.6399, 3.3913999, 16.2405, 3.9148, 3.3913999, 16.514599, 6.6893, 3.3913999, 13.3786, 6.6893, 3.3913999, 16.7232, 6.6893, 3.3913999, 16.7232, 6.6949, 3.3913999, 18.9914, 6.6893, 3.3913999, 18.9861, 6.6949, 3.3913999, 18.9914, 6.6893, 3.3913999, 16.7232, 10.3743, 3.3913999, 16.7232, 6.6949, 3.3913999, 18.9914, 10.3743, 3.3913999, 16.7232, 9.1275, 3.3913999, 18.830599, 10.033899, 3.3913999, -10.033899, 13.3786, 3.3913999, -13.3786, 13.3786, 3.3913999, -10.033899, 13.3786, 3.3913999, -13.3786, 16.7232, 3.3913999, -10.033899, 13.3786, 3.3913999, -10.033899, 16.7232, 3.3913999, -10.033899, 19.798899, 3.3913999, -13.1096, 19.798899, 3.3913999, -10.033899, 16.7232, 3.3913999, -10.033899, 16.7232, 3.3913999, -13.1096, 19.798899, 3.3913999, -13.1096, 16.7232, 3.3913999, -10.033899, 16.4543, 3.3913999, -13.3786, 16.7232, 3.3913999, -13.1096, 16.7232, 3.3913999, -10.033899, 13.3786, 3.3913999, -13.3786, 16.4543, 3.3913999, -13.3786, 13.3786, 3.3913999, -6.6893, 13.3786, 3.3913999, -10.033899, 16.7232, 3.3913999, -10.033899, 13.3786, 3.3913999, -6.6893, 16.7232, 3.3913999, -10.033899, 16.7232, 3.3913999, -6.6893, 10.033899, 3.3913999, -6.6893, 13.3786, 3.3913999, -10.033899, 13.3786, 3.3913999, -6.6893, 10.033899, 3.3913999, -6.6893, 10.033899, 3.3913999, -10.033899, 13.3786, 3.3913999, -10.033899, 16.4543, 3.3913999, -13.3786, 16.495699, 3.3913999, -16.459, 16.495699, 3.3913999, -13.42, 16.4543, 3.3913999, -13.3786, 12.738999, 3.3913999, -15.894899, 16.495699, 3.3913999, -16.459, 12.738999, 3.3913999, -15.894899, 16.4543, 3.3913999, -13.3786, 13.3786, 3.3913999, -13.3786, 13.3786, 3.3913999, -13.3786, 12.460899, 3.3913999, -16.1674, 12.738999, 3.3913999, -15.894899, 13.3786, 3.3913999, -13.3786, 10.033899, 3.3913999, -16.7232, 12.460899, 3.3913999, -16.1674, 10.033899, 3.3913999, -10.033899, 10.033899, 3.3913999, -13.3786, 13.3786, 3.3913999, -13.3786, 10.033899, 3.3913999, -16.7232, 13.3786, 3.3913999, -13.3786, 10.033899, 3.3913999, -13.3786, 6.6893, 3.3913999, -13.3786, 10.033899, 3.3913999, -16.7232, 10.033899, 3.3913999, -13.3786, 6.6893, 3.3913999, -13.3786, 6.6893, 3.3913999, -16.7232, 10.033899, 3.3913999, -16.7232, 6.6893, 3.3913999, -10.033899, 10.033899, 3.3913999, -13.3786, 10.033899, 3.3913999, -10.033899, 6.6893, 3.3913999, -10.033899, 6.6893, 3.3913999, -13.3786, 10.033899, 3.3913999, -13.3786, 6.6893, 3.3913999, -6.6893, 6.6893, 3.3913999, -10.033899, 10.033899, 3.3913999, -10.033899, 6.6893, 3.3913999, -6.6893, 10.033899, 3.3913999, -10.033899, 10.033899, 3.3913999, -6.6893, 4.0225, 3.3913999, -8.7143, 6.6893, 3.3913999, -13.3786, 6.6893, 3.3913999, -10.033899, 4.6821, 3.3913999, -13.3786, 6.6893, 3.3913999, -16.7232, 6.6893, 3.3913999, -13.3786, 4.0225, 3.3913999, -8.7143, 4.6821, 3.3913999, -13.3786, 6.6893, 3.3913999, -13.3786, 4.6821, 3.3913999, -13.3786, 3.8388, 3.3913999, -16.7232, 6.6893, 3.3913999, -16.7232, 4.6821, 3.3913999, -13.3786, 4.0225, 3.3913999, -8.7143, 4.6821, 3.3913999, -13.3786, 4.6821, 3.3913999, -13.3786, 4.6821, 3.3913999, -13.3786, 4.4131, 3.2091, -13.357699, 4.6821, 3.3913999, -13.3786, 4.4131, 3.2091, -13.357699, 4.4131, 3.2091, -13.357699, 3.3446, 3.3913999, -6.6893, 4.0225, 3.3913999, -8.7143, 6.6893, 3.3913999, -10.033899, 3.3446, 3.3913999, -6.6893, 6.6893, 3.3913999, -10.033899, 6.6893, 3.3913999, -6.6893, 0, 3.3913999, -6.6893, 4.0225, 3.3913999, -8.7143, 3.3446, 3.3913999, -6.6893, 0, 3.3913999, -6.6893, 0, 3.3913999, -8.7143, 4.0225, 3.3913999, -8.7143, 3.3446, 3.3913999, -3.3446, 3.3446, 3.3913999, -6.6893, 6.6893, 3.3913999, -6.6893, 0, 3.3913999, -6.6893, -3.9101999, 3.3913999, -9.8592, 0, 3.3913999, -8.7143, 0, 3.3913999, -8.7143, -3.9101999, 3.3913999, -9.8592, 0, 3.3913999, -8.7143, 0, 3.3913999, -3.3446, 0, 3.3913999, -6.6893, 3.3446, 3.3913999, -6.6893, 0, 3.3913999, -3.3446, 3.3446, 3.3913999, -6.6893, 3.3446, 3.3913999, -3.3446, -3.6378, 3.3913999, -2.0792, 0, 3.3913999, -6.6893, 0, 3.3913999, -3.3446, -2.9566998, 3.3913999, 0, -3.6378, 3.3913999, -2.0792, 0, 3.3913999, -3.3446, -3.6378, 3.3913999, -2.0792, -3.3446, 3.3913999, -6.6893, 0, 3.3913999, -6.6893, -3.9101999, 3.3913999, -9.8592, 0, 3.3913999, -6.6893, -3.3446, 3.3913999, -6.6893, -6.6893, 3.3913999, -3.7326, -3.3446, 3.3913999, -6.6893, -3.6378, 3.3913999, -2.0792, -2.9566998, 3.3913999, 0, 0, 3.3913999, -3.3446, 0, 3.3913999, 0, 0, 3.3913999, 0, 0, 3.3913999, -3.3446, 3.3446, 3.3913999, -3.3446, -2.9566998, 3.3913999, 3.3446, -2.9566998, 3.3913999, 0, 0, 3.3913999, 0, -2.9566998, 3.3913999, 3.3446, 0, 3.3913999, 0, 0, 3.3913999, 3.3446, -6.6893, 3.3913999, -3.7326, -6.6893, 3.3913999, -6.6893, -3.3446, 3.3913999, -6.6893, -6.6893, 3.3913999, -6.6893, -3.9101999, 3.3913999, -9.8592, -3.3446, 3.3913999, -6.6893, -10.033899, 3.3913999, -3.7326, -6.6893, 3.3913999, -6.6893, -6.6893, 3.3913999, -3.7326, 0, 3.3913999, 3.3446, 0, 3.3913999, 0, 3.3446, 3.3913999, 0, 0, 3.3913999, 0, 3.3446, 3.3913999, -3.3446, 3.3446, 3.3913999, 0, 0, 3.3913999, 3.3446, 3.3446, 3.3913999, 0, 3.3446, 3.3913999, 3.3446, 3.3446, 3.3913999, 3.3446, 3.3446, 3.3913999, 0, 6.6893, 3.3913999, 0, 3.3446, 3.3913999, 0, 3.3446, 3.3913999, -3.3446, 6.6893, 3.3913999, -3.3446, 3.3446, 3.3913999, 0, 6.6893, 3.3913999, -3.3446, 6.6893, 3.3913999, 0, 3.3446, 3.3913999, -3.3446, 6.6893, 3.3913999, -6.6893, 6.6893, 3.3913999, -3.3446, 6.6893, 3.3913999, 0, 6.6893, 3.3913999, -3.3446, 10.033899, 3.3913999, -3.3446, 6.6893, 3.3913999, -3.3446, 6.6893, 3.3913999, -6.6893, 10.033899, 3.3913999, -6.6893, 6.6893, 3.3913999, -3.3446, 10.033899, 3.3913999, -6.6893, 10.033899, 3.3913999, -3.3446, -6.6893, 3.3913999, -6.6893, -6.6893, 3.3913999, -10.033899, -3.9101999, 3.3913999, -9.8592, -6.6893, 3.3913999, -10.033899, -3.9101999, 3.3913999, -12.538199, -3.9101999, 3.3913999, -9.8592, -10.033899, 3.3913999, -3.7326, -10.033899, 3.3913999, -6.6893, -6.6893, 3.3913999, -6.6893, -10.033899, 3.3913999, -6.6893, -6.6893, 3.3913999, -10.033899, -6.6893, 3.3913999, -6.6893, -13.3786, 3.3913999, -3.7326, -10.033899, 3.3913999, -6.6893, -10.033899, 3.3913999, -3.7326, -6.6893, 3.3913999, -10.033899, -6.6893, 3.3913999, -12.538199, -3.9101999, 3.3913999, -12.538199, -13.3786, 3.3913999, -3.7326, -13.3786, 3.3913999, -6.6893, -10.033899, 3.3913999, -6.6893, -16.191599, 3.3913999, -3.0971, -13.3786, 3.3913999, -6.6893, -13.3786, 3.3913999, -3.7326, -10.033899, 3.3913999, -6.6893, -10.033899, 3.3913999, -10.033899, -6.6893, 3.3913999, -10.033899, -13.3786, 3.3913999, -6.6893, -10.033899, 3.3913999, -10.033899, -10.033899, 3.3913999, -6.6893, -16.191599, 3.3913999, -3.0971, -16.7232, 3.3913999, -6.6893, -13.3786, 3.3913999, -6.6893, -13.3786, 3.3913999, -6.6893, -13.3786, 3.3913999, -10.033899, -10.033899, 3.3913999, -10.033899, -19.7545, 3.3913999, -2.7851999, -16.7232, 3.3913999, -6.6893, -16.191599, 3.3913999, -3.0971, -19.7545, 3.3913999, -2.7851999, -19.280699, 3.3913999, -6.6893, -16.7232, 3.3913999, -6.6893, -17.1112, 3.3913999, 0, -19.7545, 3.3913999, -2.7851999, -16.191599, 3.3913999, -3.0971, -17.1112, 3.3913999, 0, -19.9511, 3.3913999, -2.5834, -19.7545, 3.3913999, -2.7851999, -19.9511, 3.3913999, -2.5834, -17.1112, 3.3913999, 0, -20.0679, 3.3913999, 0, -20.0679, 3.3913999, 3.3446, -20.0679, 3.3913999, 0, -17.1112, 3.3913999, 0, -20.0679, 3.3913999, 3.3446, -17.1112, 3.3913999, 0, -17.1112, 3.3913999, 3.3044999, -13.3786, 3.3913999, -10.033899, -10.699599, 3.3913999, -13.175799, -10.033899, 3.3913999, -10.033899, -10.699599, 3.3913999, -13.175799, -6.6893, 3.3913999, -10.033899, -10.033899, 3.3913999, -10.033899, -13.3786, 3.3913999, -10.033899, -13.3786, 3.3913999, -13.3786, -10.699599, 3.3913999, -13.175799, -6.6893, 3.3913999, -10.033899, -10.699599, 3.3913999, -13.175799, -6.6893, 3.3913999, -12.538199, -13.3786, 3.3913999, -13.3786, -10.699599, 3.3913999, -15.835, -10.699599, 3.3913999, -13.175799, -13.3786, 3.3913999, -13.3786, -13.3786, 3.3913999, -15.4154, -10.699599, 3.3913999, -15.835, -6.6893, 3.3913999, -12.538199, -10.699599, 3.3913999, -13.175799, -6.6893, 3.3913999, -12.538199, -6.6893, 3.3913999, -12.538199, -6.6893, 3.3913999, -12.538199, -6.6893, 3.175, -12.867, -6.6893, 3.3913999, -12.538199, -6.6893, 3.175, -12.867, -6.6893, 3.175, -12.867, -20.0679, 3.3913999, 6.6893, -20.0679, 3.3913999, 3.3446, -17.1112, 3.3913999, 3.3044999, -22.5541, 3.3913999, 9.2058, -22.3151, 3.3913999, 6.6893, -20.0679, 3.3913999, 6.6893, -22.5541, 3.3913999, 9.2058, -20.0679, 3.3913999, 6.6893, -20.0679, 3.3913999, 9.167399, -20.0679, 3.3913999, 9.167399, -20.0679, 3.3913999, 6.6893, -16.7232, 3.3913999, 6.6893, -20.0679, 3.3913999, 6.6893, -17.1112, 3.3913999, 3.3044999, -16.7232, 3.3913999, 6.6893, -20.0679, 3.3913999, 9.167399, -16.7232, 3.3913999, 6.6893, -16.285099, 3.3913999, 9.5865, -16.7232, 3.3913999, 6.6893, -16.053999, 3.3913999, 9.8082, -16.285099, 3.3913999, 9.5865, -16.053999, 3.3913999, 9.8082, -16.7232, 3.3913999, 6.6893, -13.1355, 3.3913999, 6.7549996, -17.1112, 3.3913999, 3.3044999, -13.1355, 3.3913999, 6.7549996, -16.7232, 3.3913999, 6.6893, -16.053999, 3.3913999, 9.8082, -13.1355, 3.3913999, 6.7549996, -13.3786, 3.3913999, 10.033899, -13.1355, 3.3913999, 6.7549996, -17.1112, 3.3913999, 3.3044999, -14.877199, 3.3913999, 4.6657, -14.877199, 3.3913999, 4.6657, -17.1112, 3.3913999, 3.3044999, -15.582299, 3.3913999, 4.0366, -13.1355, 3.3913999, 6.7549996, -14.877199, 3.3913999, 4.6657, -14.0948, 3.3913999, 5.5073, -13.3786, 3.3913999, 10.033899, -13.1355, 3.3913999, 6.7549996, -10.033899, 3.3913999, 7.0772, -13.3786, 3.3913999, 10.033899, -10.033899, 3.3913999, 7.0772, -10.033899, 3.3913999, 10.033899, -10.033899, 3.3913999, 10.033899, -10.033899, 3.3913999, 7.0772, -6.6893, 3.3913999, 7.0772, -10.033899, 3.3913999, 10.033899, -6.6893, 3.3913999, 7.0772, -6.6893, 3.3913999, 10.033899, -10.033899, 3.0033998, 6.6893, -13.1355, 3.3913999, 6.7549996, -12.747499, 3.0033998, 6.3671, -10.033899, 3.0033998, 6.6893, -10.033899, 3.3913999, 7.0772, -13.1355, 3.3913999, 6.7549996, -2.5674999, 2.4164, 7.6215, -6.6893, 3.3913999, 7.0772, -6.6893, 3.0033998, 6.6893, -2.5674999, 2.4164, 7.6215, -2.1794999, 2.8042998, 8.009399, -6.6893, 3.3913999, 7.0772, -12.747499, 3.0033998, 6.3671, -14.0948, 3.3913999, 5.5073, -13.6942, 3.0033998, 5.1798, -12.747499, 3.0033998, 6.3671, -13.1355, 3.3913999, 6.7549996, -14.0948, 3.3913999, 5.5073, -6.6893, 3.0033998, 6.6893, -10.033899, 3.3913999, 7.0772, -10.033899, 3.0033998, 6.6893, -6.6893, 3.0033998, 6.6893, -6.6893, 3.3913999, 7.0772, -10.033899, 3.3913999, 7.0772, -16.7232, 3.0033998, 0, -16.191599, 3.3913999, -3.0971, -15.8036995, 3.0033998, -2.7091, -16.7232, 3.0033998, 0, -17.1112, 3.3913999, 0, -16.191599, 3.3913999, -3.0971, -13.3786, 3.3913999, -3.7326, -15.8036995, 3.0033998, -2.7091, -16.191599, 3.3913999, -3.0971, -13.3786, 3.3913999, -3.7326, -13.3786, 3.0033998, -3.3446, -15.8036995, 3.0033998, -2.7091, -2.9566998, 3.3913999, 0, -4.0257998, 3.0033998, -1.6911999, -3.6378, 3.3913999, -2.0792, -2.9566998, 3.3913999, 0, -3.3446, 3.0033998, 0, -4.0257998, 3.0033998, -1.6911999, -3.6378, 3.3913999, -2.0792, -6.6893, 3.0033998, -3.3446, -6.6893, 3.3913999, -3.7326, -3.6378, 3.3913999, -2.0792, -4.0257998, 3.0033998, -1.6911999, -6.6893, 3.0033998, -3.3446, -10.033899, 3.3913999, -3.7326, -13.3786, 3.0033998, -3.3446, -13.3786, 3.3913999, -3.7326, -10.033899, 3.3913999, -3.7326, -10.033899, 3.0033998, -3.3446, -13.3786, 3.0033998, -3.3446, -6.6893, 3.3913999, -3.7326, -10.033899, 3.0033998, -3.3446, -10.033899, 3.3913999, -3.7326, -6.6893, 3.3913999, -3.7326, -6.6893, 3.0033998, -3.3446, -10.033899, 3.0033998, -3.3446, -16.7232, 3.0033998, 2.9166, -17.1112, 3.3913999, 0, -16.7232, 3.0033998, 0, -16.7232, 3.0033998, 2.9166, -17.1112, 3.3913999, 3.3044999, -17.1112, 3.3913999, 0, -14.4892, 3.0033998, 4.2778, -15.582299, 3.3913999, 4.0366, -15.372, 3.0033998, 3.6132998, -14.4892, 3.0033998, 4.2778, -14.877199, 3.3913999, 4.6657, -15.582299, 3.3913999, 4.0366, -2.9566998, 3.3913999, 3.3446, -3.3446, 3.0033998, 0, -2.9566998, 3.3913999, 0, -2.9566998, 3.3913999, 3.3446, -3.3446, 3.0033998, 3.3446, -3.3446, 3.0033998, 0, -2.1794999, 2.8042998, 8.009399, -2.1397, 3.0033998, 5.1795, -1.7832999, 3.3913999, 5.2162, -2.1794999, 2.8042998, 8.009399, -2.5674999, 2.4164, 7.6215, -2.1397, 3.0033998, 5.1795, -6.6893, 3.3913999, 10.033899, -2.1794999, 2.8042998, 8.009399, -3.3446, 3.3913999, 10.033899, -6.6893, 3.3913999, 10.033899, -6.6893, 3.3913999, 7.0772, -2.1794999, 2.8042998, 8.009399, -1.7832999, 3.3913999, 5.2162, 0, 3.3913999, 6.6893, -2.1794999, 2.8042998, 8.009399, 0, 3.3913999, 6.6893, -1.7832999, 3.3913999, 5.2162, -2.9566998, 3.3913999, 3.3446, 0, 3.3913999, 6.6893, -2.9566998, 3.3913999, 3.3446, 0, 3.3913999, 3.3446, -3.3446, 3.3913999, 10.033899, 0, 3.3913999, 6.6893, 0, 3.3913999, 10.033899, -3.3446, 3.3913999, 10.033899, -2.1794999, 2.8042998, 8.009399, 0, 3.3913999, 6.6893, -2.1397, 3.0033998, 5.1795, -2.9566998, 3.3913999, 3.3446, -1.7832999, 3.3913999, 5.2162, -2.1397, 3.0033998, 5.1795, -3.3446, 3.0033998, 3.3446, -2.9566998, 3.3913999, 3.3446, -14.0948, 3.3913999, 5.5073, -14.4892, 3.0033998, 4.2778, -13.6942, 3.0033998, 5.1798, -14.0948, 3.3913999, 5.5073, -14.877199, 3.3913999, 4.6657, -14.4892, 3.0033998, 4.2778, -15.582299, 3.3913999, 4.0366, -16.7232, 3.0033998, 2.9166, -15.372, 3.0033998, 3.6132998, -15.582299, 3.3913999, 4.0366, -17.1112, 3.3913999, 3.3044999, -16.7232, 3.0033998, 2.9166, 20.198, 3.1490998, -10.033899, 19.798899, 3.3913999, -13.1096, 20.1539, 3.1299, -13.399799, 20.198, 3.1490998, -10.033899, 19.798899, 3.3913999, -10.033899, 19.798899, 3.3913999, -13.1096, 20.1539, 3.1299, -13.399799, 16.7232, 3.3913999, -13.1096, 16.8204, 3.138, -13.447599, 20.1539, 3.1299, -13.399799, 19.798899, 3.3913999, -13.1096, 16.7232, 3.3913999, -13.1096, 16.756899, 3.1650999, -16.7286, 12.738999, 3.3913999, -15.894899, 12.7471, 3.1766999, -16.1988, 16.756899, 3.1650999, -16.7286, 16.495699, 3.3913999, -16.459, 12.738999, 3.3913999, -15.894899, 16.8051, 3.1778, -13.479, 16.495699, 3.3913999, -16.459, 16.756899, 3.1650999, -16.7286, 16.8051, 3.1778, -13.479, 16.495699, 3.3913999, -13.42, 16.495699, 3.3913999, -16.459, 16.4543, 3.3913999, -13.3786, 16.8204, 3.138, -13.447599, 16.7232, 3.3913999, -13.1096, 16.8204, 3.138, -13.447599, 16.4543, 3.3913999, -13.3786, 16.495699, 3.3913999, -13.42, 16.8204, 3.138, -13.447599, 16.495699, 3.3913999, -13.42, 16.8051, 3.1778, -13.479, 10.033899, 3.0523999, -19.0294, 6.6893, 3.2782998, -18.727999, 6.6893, 3.0440998, -19.036999, 10.033899, 3.0523999, -19.0294, 10.033899, 3.2784998, -18.6997, 6.6893, 3.2782998, -18.727999, 12.723599, 2.9213, -19.2902, 10.033899, 3.2784998, -18.6997, 10.033899, 3.0523999, -19.0294, 12.723599, 2.9213, -19.2902, 12.446099, 3.1699998, -18.9401, 10.033899, 3.2784998, -18.6997, 12.446099, 3.1699998, -18.9401, 12.747, 3.1771, -16.1991, 12.460899, 3.3913999, -16.1674, 12.446099, 3.1699998, -18.9401, 12.723599, 2.9213, -19.2902, 12.747, 3.1771, -16.1991, 12.460899, 3.3913999, -16.1674, 10.033899, 3.2784998, -18.6997, 12.446099, 3.1699998, -18.9401, 10.033899, 3.3913999, -16.7232, 10.033899, 3.2784998, -18.6997, 12.460899, 3.3913999, -16.1674, 10.033899, 3.3913999, -16.7232, 12.460899, 3.3913999, -16.1674, 12.460899, 3.3913999, -16.1674, 6.6893, 3.3913999, -16.7232, 10.033899, 3.2784998, -18.6997, 10.033899, 3.3913999, -16.7232, 6.6893, 3.3913999, -16.7232, 6.6893, 3.2782998, -18.727999, 10.033899, 3.2784998, -18.6997, 12.738999, 3.3913999, -15.894899, 12.747, 3.1771, -16.1991, 12.7471, 3.1766999, -16.1988, 12.738999, 3.3913999, -15.894899, 12.460899, 3.3913999, -16.1674, 12.747, 3.1771, -16.1991, 12.460899, 3.3913999, -16.1674, 12.738999, 3.3913999, -15.894899, 12.460899, 3.3913999, -16.1674, 3.8388, 3.3913999, -16.7232, 4.4783998, 3.1067, -18.2665, 4.6443, 3.2897, -17.9799, 3.8388, 3.3913999, -16.7232, 3.5895, 3.2033, -16.7232, 4.4783998, 3.1067, -18.2665, 4.6821, 3.3913999, -13.3786, 3.5895, 3.2033, -16.7232, 3.8388, 3.3913999, -16.7232, 4.6821, 3.3913999, -13.3786, 4.4131, 3.2091, -13.357699, 3.5895, 3.2033, -16.7232, 6.6640997, 3.0702, -19.018099, 4.6443, 3.2897, -17.9799, 4.4783998, 3.1067, -18.2665, 6.6640997, 3.0702, -19.018099, 6.6705, 3.2772, -18.741, 4.6443, 3.2897, -17.9799, 4.6443, 3.2897, -17.9799, 6.6893, 3.3913999, -16.7232, 3.8388, 3.3913999, -16.7232, 6.6893, 3.3913999, -16.7232, 4.6443, 3.2897, -17.9799, 6.6705, 3.2772, -18.741, 6.6893, 3.3913999, -16.7232, 6.6705, 3.2772, -18.741, 6.6893, 3.2782998, -18.727999, 6.6893, 3.2782998, -18.727999, 6.6640997, 3.0702, -19.018099, 6.6893, 3.0440998, -19.036999, 6.6893, 3.2782998, -18.727999, 6.6705, 3.2772, -18.741, 6.6640997, 3.0702, -19.018099, 3.7529, 3.1712, -9.0494995, 0, 3.3913999, -8.7143, 0, 3.179, -9.0652, 3.7529, 3.1712, -9.0494995, 4.0225, 3.3913999, -8.7143, 0, 3.3913999, -8.7143, 4.0225, 3.3913999, -8.7143, 4.4131, 3.2091, -13.357699, 4.6821, 3.3913999, -13.3786, 4.0225, 3.3913999, -8.7143, 3.7529, 3.1712, -9.0494995, 4.4131, 3.2091, -13.357699, -3.6538, 3.1585999, -12.813399, -6.6893, 3.3913999, -12.538199, -6.6893, 3.175, -12.867, -3.6538, 3.1585999, -12.813399, -3.9101999, 3.3913999, -12.538199, -6.6893, 3.3913999, -12.538199, -3.6076, 3.1676, -10.0696, -3.9101999, 3.3913999, -12.538199, -3.6538, 3.1585999, -12.813399, -3.6076, 3.1676, -10.0696, -3.9101999, 3.3913999, -9.8592, -3.9101999, 3.3913999, -12.538199, -0.0081, 3.1864998, -9.063499, -3.9101999, 3.3913999, -9.8592, -3.6076, 3.1676, -10.0696, -0.0081, 3.1864998, -9.063499, 0, 3.3913999, -8.7143, -3.9101999, 3.3913999, -9.8592, 0, 3.3913999, -8.7143, -0.0081, 3.1864998, -9.063499, 0, 3.179, -9.0652, 0, 3.3913999, -8.7143, 0, 3.3913999, -8.7143, -0.0081, 3.1864998, -9.063499, -10.4043, 3.1608999, -13.4295, -10.699599, 3.3913999, -15.835, -10.4319, 3.1557999, -16.1556, -10.4043, 3.1608999, -13.4295, -10.699599, 3.3913999, -13.175799, -10.699599, 3.3913999, -15.835, -10.4319, 3.1557999, -16.1556, -13.3786, 3.3913999, -15.4154, -13.3786, 3.1771998, -15.7625, -10.4319, 3.1557999, -16.1556, -10.699599, 3.3913999, -15.835, -13.3786, 3.3913999, -15.4154, -10.699599, 3.3913999, -13.175799, -6.6893, 3.175, -12.867, -6.6893, 3.3913999, -12.538199, -10.699599, 3.3913999, -13.175799, -10.4043, 3.1608999, -13.4295, -6.6893, 3.175, -12.867, -16.130499, 3.6227, -13.623799, -16.0243, 3.3509998, -15.752, -15.7941, 3.5591, -15.4641, -16.130499, 3.6227, -13.623799, -16.3753, 3.4225, -13.6277, -16.0243, 3.3509998, -15.752, -13.3911, 3.1892, -15.757, -15.7941, 3.5591, -15.4641, -16.0243, 3.3509998, -15.752, -13.3911, 3.1892, -15.757, -13.396, 3.3925, -15.4333, -15.7941, 3.5591, -15.4641, -15.7941, 3.5591, -15.4641, -13.3786, 3.3913999, -13.3786, -16.130499, 3.6227, -13.623799, -13.3786, 3.3913999, -13.3786, -15.7941, 3.5591, -15.4641, -13.396, 3.3925, -15.4333, -13.3786, 3.3913999, -13.3786, -13.396, 3.3925, -15.4333, -13.3786, 3.3913999, -15.4154, -13.3786, 3.3913999, -15.4154, -13.3911, 3.1892, -15.757, -13.3786, 3.1771998, -15.7625, -13.3786, 3.3913999, -15.4154, -13.396, 3.3925, -15.4333, -13.3911, 3.1892, -15.757, -16.3753, 3.4225, -13.6277, -16.1241, 3.3832998, -9.5097, -16.4131, 3.1706, -9.788899, -16.3753, 3.4225, -13.6277, -16.130499, 3.6227, -13.623799, -16.1241, 3.3832998, -9.5097, -16.7232, 3.3913999, -6.6893, -13.3786, 3.3913999, -10.033899, -13.3786, 3.3913999, -6.6893, -16.7232, 3.3913999, -6.6893, -16.1241, 3.3832998, -9.5097, -13.3786, 3.3913999, -10.033899, -16.1241, 3.3832998, -9.5097, -13.3786, 3.3913999, -13.3786, -13.3786, 3.3913999, -10.033899, -16.1241, 3.3832998, -9.5097, -16.130499, 3.6227, -13.623799, -13.3786, 3.3913999, -13.3786, -16.130499, 3.6227, -13.623799, -16.1241, 3.3832998, -9.5097, -16.130499, 3.6227, -13.623799, -16.130499, 3.6227, -13.623799, -16.3753, 3.4225, -13.6277, -16.3753, 3.4225, -13.6277, -16.130499, 3.6227, -13.623799, -16.130499, 3.6227, -13.623799, -16.3753, 3.4225, -13.6277, -19.7545, 3.3913999, -2.7851999, -19.515, 3.2122, -6.6893, -19.280699, 3.3913999, -6.6893, -19.7545, 3.3913999, -2.7851999, -19.9489, 3.2063, -2.8082, -19.515, 3.2122, -6.6893, -19.280699, 3.3913999, -6.6893, -19.6161, 3.2075999, -9.7116995, -19.3965, 3.3909, -9.5092, -19.280699, 3.3913999, -6.6893, -19.515, 3.2122, -6.6893, -19.6161, 3.2075999, -9.7116995, -19.280699, 3.3913999, -6.6893, -16.1241, 3.3832998, -9.5097, -16.7232, 3.3913999, -6.6893, -19.280699, 3.3913999, -6.6893, -19.3965, 3.3909, -9.5092, -16.1241, 3.3832998, -9.5097, -16.1241, 3.3832998, -9.5097, -19.6237, 3.1655998, -9.757899, -16.4131, 3.1706, -9.788899, -19.6237, 3.1655998, -9.757899, -16.1241, 3.3832998, -9.5097, -19.3965, 3.3909, -9.5092, -19.6237, 3.1655998, -9.757899, -19.3965, 3.3909, -9.5092, -19.6161, 3.2075999, -9.7116995, -22.8657, 3.4937, 3.3613, -23.3676, 3.4459999, 0.1372, -23.170399, 3.6251, 0.122999996, -22.8657, 3.4937, 3.3613, -23.0809, 3.3083, 3.3615, -23.3676, 3.4459999, 0.1372, -23.170399, 3.6251, 0.122999996, -23.390299, 3.3349998, -2.6938999, -23.192099, 3.5300999, -2.4668999, -23.170399, 3.6251, 0.122999996, -23.3676, 3.4459999, 0.1372, -23.390299, 3.3349998, -2.6938999, -23.192099, 3.5300999, -2.4668999, -19.9489, 3.2063, -2.8082, -19.9511, 3.3913999, -2.5834, -23.192099, 3.5300999, -2.4668999, -23.390299, 3.3349998, -2.6938999, -19.9489, 3.2063, -2.8082, -23.192099, 3.5300999, -2.4668999, -20.0679, 3.3913999, 0, -23.170399, 3.6251, 0.122999996, -20.0679, 3.3913999, 0, -23.192099, 3.5300999, -2.4668999, -19.9511, 3.3913999, -2.5834, -19.9511, 3.3913999, -2.5834, -23.192099, 3.5300999, -2.4668999, -19.9511, 3.3913999, -2.5834, -22.8657, 3.4937, 3.3613, -20.0679, 3.3913999, 0, -20.0679, 3.3913999, 3.3446, -22.8657, 3.4937, 3.3613, -23.170399, 3.6251, 0.122999996, -20.0679, 3.3913999, 0, -19.7545, 3.3913999, -2.7851999, -19.9489, 3.2063, -2.8082, -19.9489, 3.2063, -2.8082, -19.7545, 3.3913999, -2.7851999, -19.9511, 3.3913999, -2.5834, -19.9489, 3.2063, -2.8082, -19.9511, 3.3913999, -2.5834, -19.7545, 3.3913999, -2.7851999, -19.9511, 3.3913999, -2.5834, -23.0777, 3.3197, 3.3734, -22.3151, 3.3913999, 6.6893, -22.563799, 3.2219, 6.6893, -23.0777, 3.3197, 3.3734, -22.8764, 3.4937, 3.3741, -22.3151, 3.3913999, 6.6893, -22.8764, 3.4937, 3.3741, -20.0679, 3.3913999, 6.6893, -22.3151, 3.3913999, 6.6893, -20.0679, 3.3913999, 6.6893, -22.8764, 3.4937, 3.3741, -22.8657, 3.4937, 3.3613, -20.0679, 3.3913999, 6.6893, -22.8657, 3.4937, 3.3613, -20.0679, 3.3913999, 3.3446, -22.8657, 3.4937, 3.3613, -23.0777, 3.3197, 3.3734, -23.0809, 3.3083, 3.3615, -22.8657, 3.4937, 3.3613, -22.8764, 3.4937, 3.3741, -23.0777, 3.3197, 3.3734, -16.285099, 3.3913999, 9.5865, -20.0679, 3.1943, 9.407499, -20.0679, 3.3913999, 9.167399, -16.285099, 3.3913999, 9.5865, -16.2818, 3.1913998, 9.8056, -20.0679, 3.1943, 9.407499, -22.791399, 3.2124999, 9.4432, -20.0679, 3.3913999, 9.167399, -20.0679, 3.1943, 9.407499, -22.791399, 3.2124999, 9.4432, -22.5541, 3.3913999, 9.2058, -20.0679, 3.3913999, 9.167399, -22.5541, 3.3913999, 9.2058, -22.563799, 3.2219, 6.6893, -22.3151, 3.3913999, 6.6893, -22.5541, 3.3913999, 9.2058, -22.791399, 3.2124999, 9.4432, -22.563799, 3.2219, 6.6893, -22.791399, 3.2124999, 9.4432, -22.5541, 3.3913999, 9.2058, -22.791399, 3.2124999, 9.4432, -13.3786, 3.3922, 12.571899, -16.076399, 3.04, 12.735, -15.847199, 3.2614, 12.4973, -13.3786, 3.3922, 12.571899, -13.3786, 3.194, 12.8089, -16.076399, 3.04, 12.735, -15.847199, 3.2614, 12.4973, -16.2818, 3.1913998, 9.8056, -16.053999, 3.3913999, 9.8082, -15.847199, 3.2614, 12.4973, -16.076399, 3.04, 12.735, -16.2818, 3.1913998, 9.8056, -15.847199, 3.2614, 12.4973, -13.3786, 3.3913999, 10.033899, -13.3786, 3.3922, 12.571899, -15.847199, 3.2614, 12.4973, -16.053999, 3.3913999, 9.8082, -13.3786, 3.3913999, 10.033899, -16.053999, 3.3913999, 9.8082, -15.847199, 3.2614, 12.4973, -16.053999, 3.3913999, 9.8082, -16.285099, 3.3913999, 9.5865, -16.2818, 3.1913998, 9.8056, -16.2818, 3.1913998, 9.8056, -16.285099, 3.3913999, 9.5865, -16.053999, 3.3913999, 9.8082, -16.2818, 3.1913998, 9.8056, -16.053999, 3.3913999, 9.8082, -16.285099, 3.3913999, 9.5865, -16.053999, 3.3913999, 9.8082, -5.9742, 3.3994, 12.8817, -10.033899, 2.9384, 12.4537, -10.033899, 3.2142, 12.1061, -5.9742, 3.3994, 12.8817, -6.2748, 3.126, 13.122, -10.033899, 2.9384, 12.4537, -10.033899, 3.2142, 12.1061, -13.3786, 3.194, 12.8089, -13.3786, 3.3922, 12.571899, -10.033899, 3.2142, 12.1061, -10.033899, 2.9384, 12.4537, -13.3786, 3.194, 12.8089, -10.033899, 3.2142, 12.1061, -6.6893, 3.3913999, 10.033899, -5.9742, 3.3994, 12.8817, -10.033899, 3.2142, 12.1061, -10.033899, 3.3913999, 10.033899, -6.6893, 3.3913999, 10.033899, -13.3786, 3.3922, 12.571899, -10.033899, 3.3913999, 10.033899, -10.033899, 3.2142, 12.1061, -13.3786, 3.3922, 12.571899, -13.3786, 3.3913999, 10.033899, -10.033899, 3.3913999, 10.033899, -13.3786, 3.3913999, 10.033899, -13.3786, 3.3922, 12.571899, -13.3786, 3.3922, 12.571899, -5.9742, 3.3994, 12.8817, -3.3446, 3.3913999, 10.033899, -3.3446, 3.3913999, 13.3786, -5.9742, 3.3994, 12.8817, -6.6893, 3.3913999, 10.033899, -3.3446, 3.3913999, 10.033899, -13.3786, 3.3922, 12.571899, -13.3786, 3.194, 12.8089, -13.3786, 3.194, 12.8089, -13.3786, 3.3922, 12.571899, -13.3786, 3.3922, 12.571899, -13.3786, 3.194, 12.8089, 3.6399, 3.3913999, 16.2405, 0, 2.9636998, 16.0179, 0, 3.2243998, 15.7093, 3.6399, 3.3913999, 16.2405, 3.6425998, 3.1413999, 16.5127, 0, 2.9636998, 16.0179, 0, 3.2243998, 15.7093, -3.3446, 3.1497998, 15.914599, -3.3446, 3.3913999, 15.6011, 0, 3.2243998, 15.7093, 0, 2.9636998, 16.0179, -3.3446, 3.1497998, 15.914599, -6.2558, 3.1427999, 16.4098, -3.3446, 3.3913999, 15.6011, -3.3446, 3.1497998, 15.914599, -6.2558, 3.1427999, 16.4098, -5.9737997, 3.392, 16.087599, -3.3446, 3.3913999, 15.6011, -3.3446, 3.3913999, 15.6011, 0, 3.3913999, 13.3786, 0, 3.2243998, 15.7093, -3.3446, 3.3913999, 15.6011, -3.3446, 3.3913999, 13.3786, 0, 3.3913999, 13.3786, 0, 3.2243998, 15.7093, 3.3446, 3.3913999, 13.3786, 3.6399, 3.3913999, 16.2405, 0, 3.2243998, 15.7093, 0, 3.3913999, 13.3786, 3.3446, 3.3913999, 13.3786, -5.9737997, 3.392, 16.087599, -3.3446, 3.3913999, 13.3786, -3.3446, 3.3913999, 15.6011, -5.9737997, 3.392, 16.087599, -5.9742, 3.3994, 12.8817, -3.3446, 3.3913999, 13.3786, -6.2748, 3.1260998, 16.4145, -5.9737997, 3.392, 16.087599, -6.2558, 3.1427999, 16.4098, -5.9737997, 3.392, 16.087599, -6.2748, 3.1260998, 16.4145, -6.2748, 3.126, 13.122, -5.9737997, 3.392, 16.087599, -6.2748, 3.126, 13.122, -5.9742, 3.3994, 12.8817, 6.6893, 3.3913999, 18.9861, 3.9266999, 3.5967, 19.6223, 4.1273, 3.7335, 19.3804, 6.6893, 3.3913999, 18.9861, 6.6893, 3.2050998, 19.228699, 3.9266999, 3.5967, 19.6223, 4.1273, 3.7335, 19.3804, 3.6536, 3.2078998, 16.5669, 3.8651, 3.3981998, 16.5647, 4.1273, 3.7335, 19.3804, 3.9266999, 3.5967, 19.6223, 3.6536, 3.2078998, 16.5669, 3.8651, 3.3981998, 16.5647, 6.6893, 3.3913999, 18.9861, 4.1273, 3.7335, 19.3804, 6.6893, 3.3913999, 18.9861, 3.8651, 3.3981998, 16.5647, 3.9148, 3.3913999, 16.514599, 6.6893, 3.3913999, 18.9861, 3.9148, 3.3913999, 16.514599, 6.6893, 3.3913999, 16.7232, 3.9148, 3.3913999, 16.514599, 3.6425998, 3.1413999, 16.5127, 3.6399, 3.3913999, 16.2405, 3.6425998, 3.1413999, 16.5127, 3.9148, 3.3913999, 16.514599, 3.8651, 3.3981998, 16.5647, 3.6425998, 3.1413999, 16.5127, 3.8651, 3.3981998, 16.5647, 3.6536, 3.2078998, 16.5669, 9.3066, 3.2186, 19.0653, 10.3743, 3.3913999, 16.7232, 10.5913, 3.1985998, 16.7232, 9.3066, 3.2186, 19.0653, 9.1275, 3.3913999, 18.830599, 10.3743, 3.3913999, 16.7232, 9.1275, 3.3913999, 18.830599, 6.6991997, 3.2148, 19.2255, 6.6949, 3.3913999, 18.9914, 9.1275, 3.3913999, 18.830599, 9.3066, 3.2186, 19.0653, 6.6991997, 3.2148, 19.2255, 6.6893, 3.3913999, 18.9861, 6.6991997, 3.2148, 19.2255, 6.6893, 3.2050998, 19.228699, 6.6893, 3.3913999, 18.9861, 6.6949, 3.3913999, 18.9914, 6.6991997, 3.2148, 19.2255, 9.8131, 3.3546, 13.1979, 10.5893, 3.2117, 16.7106, 10.403999, 3.3911998, 16.6938, 9.8131, 3.3546, 13.1979, 10.0546, 3.1885, 13.4071, 10.5893, 3.2117, 16.7106, 6.6893, 3.3913999, 13.3786, 10.3743, 3.3913999, 16.7232, 6.6893, 3.3913999, 16.7232, 10.3743, 3.3913999, 16.7232, 6.6893, 3.3913999, 13.3786, 9.8131, 3.3546, 13.1979, 10.3743, 3.3913999, 16.7232, 9.8131, 3.3546, 13.1979, 10.403999, 3.3911998, 16.6938, 6.6893, 3.3913999, 13.3786, 10.033899, 3.3913999, 10.033899, 9.8131, 3.3546, 13.1979, 6.6893, 3.3913999, 13.3786, 6.6893, 3.3913999, 10.033899, 10.033899, 3.3913999, 10.033899, 10.3743, 3.3913999, 16.7232, 10.5893, 3.2117, 16.7106, 10.5913, 3.1985998, 16.7232, 10.3743, 3.3913999, 16.7232, 10.403999, 3.3911998, 16.6938, 10.5893, 3.2117, 16.7106, 13.1839, 3.2744, 6.6893, 13.5511, 3.1797, 6.6893, 13.4036, 3.0749998, 6.6893, 13.1839, 3.2744, 6.6893, 13.3786, 3.2744, 6.496, 13.5511, 3.1797, 6.6893, 12.9758, 3.2036, 10.033899, 13.1839, 3.2744, 6.6893, 13.4036, 3.0749998, 6.6893, 12.9758, 3.2036, 10.033899, 12.7325, 3.3913999, 10.033899, 13.1839, 3.2744, 6.6893, 13.2631, 3.5611, 13.4043, 12.7325, 3.3913999, 10.033899, 12.9758, 3.2036, 10.033899, 13.2631, 3.5611, 13.4043, 13.025499, 3.6999, 13.1985, 12.7325, 3.3913999, 10.033899, 10.033899, 3.3913999, 10.033899, 13.1839, 3.2744, 6.6893, 12.7325, 3.3913999, 10.033899, 10.033899, 3.3913999, 10.033899, 10.033899, 3.3913999, 6.6893, 13.1839, 3.2744, 6.6893, 9.8131, 3.3546, 13.1979, 12.7325, 3.3913999, 10.033899, 13.025499, 3.6999, 13.1985, 9.8131, 3.3546, 13.1979, 10.033899, 3.3913999, 10.033899, 12.7325, 3.3913999, 10.033899, 10.033899, 3.3913999, 3.3446, 13.1839, 3.2744, 6.6893, 10.033899, 3.3913999, 6.6893, 13.1839, 3.2744, 6.6893, 10.033899, 3.3913999, 3.3446, 13.3786, 3.3913999, 3.3446, 13.1839, 3.2744, 6.6893, 13.3786, 3.3913999, 3.3446, 13.3786, 3.2744, 6.496, 13.025499, 3.6999, 13.1985, 10.0546, 3.1885, 13.4071, 9.8131, 3.3546, 13.1979, 13.025499, 3.6999, 13.1985, 13.2631, 3.5611, 13.4043, 10.0546, 3.1885, 13.4071, 13.2631, 3.5611, 13.4043, 13.2631, 3.5611, 13.4043, 13.025499, 3.6999, 13.1985, 14.0489, 3.0042, -3.3446, 13.1206, 3.3028998, -3.3446, 13.3786, 3.3028998, -3.6027, 14.0489, 3.0042, -3.3446, 13.3786, 3.3028998, -3.0865998, 13.1206, 3.3028998, -3.3446, 16.7232, 3.1513, -3.602, 14.0489, 3.0042, -3.3446, 13.3786, 3.3028998, -3.6027, 16.7232, 3.1513, -3.602, 16.7127, 2.8734, -3.3446, 14.0489, 3.0042, -3.3446, 13.3786, 3.3028998, -3.6027, 16.7232, 3.3913999, -6.6893, 16.7232, 3.1513, -3.602, 13.3786, 3.3028998, -3.6027, 13.3786, 3.3913999, -6.6893, 16.7232, 3.3913999, -6.6893, 10.033899, 3.3913999, -6.6893, 13.1206, 3.3028998, -3.3446, 10.033899, 3.3913999, -3.3446, 13.1206, 3.3028998, -3.3446, 10.033899, 3.3913999, -6.6893, 13.3786, 3.3913999, -6.6893, 13.1206, 3.3028998, -3.3446, 13.3786, 3.3913999, -6.6893, 13.3786, 3.3028998, -3.6027, 20.0679, 0, 0, 14.0489, 3.0042, -3.3446, 20.961199, 0, -3.3446, 20.0679, 0, 3.3446, 14.0489, 3.0042, -3.3446, 20.0679, 0, 0, 14.0489, 3.0042, -3.3446, 13.3786, 3.3913999, 0, 13.3786, 3.3028998, -3.0865998, 14.0489, 3.0042, -3.3446, 13.3786, 3.2744, 6.496, 13.3786, 3.3913999, 0, 14.0489, 3.0042, -3.3446, 20.0679, 0, 3.3446, 13.3786, 3.2744, 6.496, 13.3786, 3.3913999, 0, 13.3786, 3.2744, 6.496, 13.3786, 3.3913999, 3.3446, 13.3786, 3.2744, 6.496, 20.0679, 0, 3.3446, 16.7232, 1.5715, 6.6893, 16.7232, 1.5715, 6.6893, 20.0679, 0, 3.3446, 20.0679, -0.124199994, 6.6893, 13.3786, 3.2744, 6.496, 16.7232, 1.5715, 6.6893, 13.5511, 3.1797, 6.6893, 10.033899, 3.3913999, -3.3446, 13.3786, 3.3913999, 0, 10.033899, 3.3913999, 0, 13.3786, 3.3913999, 0, 10.033899, 3.3913999, -3.3446, 13.1206, 3.3028998, -3.3446, 13.3786, 3.3913999, 0, 13.1206, 3.3028998, -3.3446, 13.3786, 3.3028998, -3.0865998, 19.831999, 3.3902998, -10.0008, 20.147, 3.0588999, -6.6893, 19.8322, 3.2891998, -6.6893, 19.831999, 3.3902998, -10.0008, 20.1796, 3.182, -9.995999, 20.147, 3.0588999, -6.6893, 19.8322, 3.2891998, -6.6893, 20.1349, 2.7974, -3.3783998, 19.8322, 3.0491, -3.602, 19.8322, 3.2891998, -6.6893, 20.147, 3.0588999, -6.6893, 20.1349, 2.7974, -3.3783998, 16.7232, 3.1513, -3.602, 19.8322, 3.2891998, -6.6893, 19.8322, 3.0491, -3.602, 16.7232, 3.1513, -3.602, 16.7232, 3.3913999, -6.6893, 19.8322, 3.2891998, -6.6893, 16.7232, 3.3913999, -10.033899, 19.8322, 3.2891998, -6.6893, 16.7232, 3.3913999, -6.6893, 19.8322, 3.2891998, -6.6893, 16.7232, 3.3913999, -10.033899, 19.798899, 3.3913999, -10.033899, 19.8322, 3.2891998, -6.6893, 19.798899, 3.3913999, -10.033899, 19.831999, 3.3902998, -10.0008, 20.198, 3.1490998, -10.033899, 19.831999, 3.3902998, -10.0008, 19.798899, 3.3913999, -10.033899, 20.198, 3.1490998, -10.033899, 20.1796, 3.182, -9.995999, 19.831999, 3.3902998, -10.0008, 20.144999, 2.7605999, -3.3446, 19.8322, 3.0491, -3.602, 20.1349, 2.7974, -3.3783998, 19.8322, 3.0491, -3.602, 20.144999, 2.7605999, -3.3446, 16.7127, 2.8734, -3.3446, 19.8322, 3.0491, -3.602, 16.7127, 2.8734, -3.3446, 16.7232, 3.1513, -3.602, 15.0508995, 0, 6.6893, 16.7232, 1.5715, 6.6893, 16.7232, 0, 6.6893, 16.7232, 0, 6.6893, 16.7232, 1.5715, 6.6893, 20.0679, -0.124199994, 6.6893, 15.0508995, 0, 6.6893, 13.5511, 3.1797, 6.6893, 16.7232, 1.5715, 6.6893, 15.0508995, 0, 6.6893, 13.4036, 3.0749998, 6.6893, 13.5511, 3.1797, 6.6893, 13.4036, 3.0749998, 6.6893, 15.0508995, 0, 6.6893, 13.8046, 0, 6.6893, -16.7232, 0.6137, 0, -15.7546, 0.6137, 3.5372, -16.7232, 0.6137, 2.9166, -15.7546, 0.6137, 3.5372, -16.7232, 0.6137, 0, -13.3786, 0.6137, 0, -16.7232, 0.6137, 0, -15.8036995, 0.6137, -2.7091, -13.3786, 0.6137, -3.3446, -16.7232, 0.6137, 0, -13.3786, 0.6137, -3.3446, -13.3786, 0.6137, 0, -13.3786, 0.6137, 0, -13.3786, 0.6137, -3.3446, -10.033899, 0.6137, -3.3446, -15.7546, 0.6137, 3.5372, -13.3786, 0.6137, 0, -14.4892, 0.6137, 4.2778, -13.3786, 0.6137, 0, -10.033899, 0.6137, -3.3446, -10.033899, 0.6137, 0, -14.4892, 0.6137, 4.2778, -13.3786, 0.6137, 0, -10.033899, 0.6137, 0, -10.033899, 0.6137, 0, -10.033899, 0.6137, -3.3446, -6.6893, 0.6137, -3.3446, -10.033899, 0.6137, 0, -6.6893, 0.6137, -3.3446, -6.6893, 0.6137, 0, -6.6893, 0.6137, 0, -6.6893, 0.6137, -3.3446, -4.0257998, 0.6137, -1.6911999, -6.6893, 0.6137, 0, -4.0257998, 0.6137, -1.6911999, -3.3446, 0.6137, 0, -10.033899, 0.6137, 3.3446, -10.033899, 0.6137, 0, -6.6893, 0.6137, 0, -14.4892, 0.6137, 4.2778, -10.033899, 0.6137, 0, -10.033899, 0.6137, 3.3446, -6.6893, 0.6137, 3.3446, -6.6893, 0.6137, 0, -3.3446, 0.6137, 0, -10.033899, 0.6137, 3.3446, -6.6893, 0.6137, 0, -6.6893, 0.6137, 3.3446, -6.6893, 0.6137, 3.3446, -3.3446, 0.6137, 0, -3.3446, 0.6137, 3.3446, -10.033899, 0.6137, 6.6893, -14.4892, 0.6137, 4.2778, -10.033899, 0.6137, 3.3446, -10.033899, 0.6137, 6.6893, -10.033899, 0.6137, 3.3446, -6.6893, 0.6137, 3.3446, -10.033899, 0.6137, 6.6893, -6.6893, 0.6137, 3.3446, -6.6893, 0.6137, 6.6893, -10.033899, 0.6137, 6.6893, -13.677099, 0.6137, 5.2616, -14.4892, 0.6137, 4.2778, -13.677099, 0.6137, 5.2616, -10.033899, 0.6137, 6.6893, -12.747499, 0.6137, 6.3671, -6.6893, 0.6137, 3.3446, -2.5674999, 0.0266, 7.6215, -6.6893, 0.6137, 6.6893, -2.5674999, 0.0266, 7.6215, -6.6893, 0.6137, 3.3446, -3.3446, 0.6137, 3.3446, -2.5674999, 0.0266, 7.6215, -3.3446, 0.6137, 3.3446, -2.1193, 0.6137, 5.2736998, -16.7232, 3.0033998, 2.9166, -15.7546, 0.6137, 3.5372, -15.372, 3.0033998, 3.6132998, -16.7232, 3.0033998, 2.9166, -16.7232, 0.6137, 2.9166, -15.7546, 0.6137, 3.5372, -14.4892, 3.0033998, 4.2778, -13.677099, 0.6137, 5.2616, -13.6942, 3.0033998, 5.1798, -14.4892, 3.0033998, 4.2778, -14.4892, 0.6137, 4.2778, -13.677099, 0.6137, 5.2616, -15.8036995, 3.0033998, -2.7091, -16.7232, 0.6137, 0, -16.7232, 3.0033998, 0, -15.8036995, 3.0033998, -2.7091, -15.8036995, 0.6137, -2.7091, -16.7232, 0.6137, 0, -16.7232, 3.0033998, 0, -16.7232, 0.6137, 2.9166, -16.7232, 3.0033998, 2.9166, -16.7232, 3.0033998, 0, -16.7232, 0.6137, 0, -16.7232, 0.6137, 2.9166, -3.3446, 3.0033998, 0, -4.0257998, 0.6137, -1.6911999, -4.0257998, 3.0033998, -1.6911999, -3.3446, 3.0033998, 0, -3.3446, 0.6137, 0, -4.0257998, 0.6137, -1.6911999, -3.3446, 3.0033998, 3.3446, -3.3446, 0.6137, 0, -3.3446, 3.0033998, 0, -3.3446, 3.0033998, 3.3446, -3.3446, 0.6137, 3.3446, -3.3446, 0.6137, 0, -12.747499, 3.0033998, 6.3671, -10.033899, 0.6137, 6.6893, -10.033899, 3.0033998, 6.6893, -12.747499, 3.0033998, 6.3671, -12.747499, 0.6137, 6.3671, -10.033899, 0.6137, 6.6893, -10.033899, 3.0033998, -3.3446, -13.3786, 0.6137, -3.3446, -13.3786, 3.0033998, -3.3446, -10.033899, 3.0033998, -3.3446, -10.033899, 0.6137, -3.3446, -13.3786, 0.6137, -3.3446, -6.6893, 3.0033998, -3.3446, -10.033899, 0.6137, -3.3446, -10.033899, 3.0033998, -3.3446, -6.6893, 3.0033998, -3.3446, -6.6893, 0.6137, -3.3446, -10.033899, 0.6137, -3.3446, -13.3786, 3.0033998, -3.3446, -15.8036995, 0.6137, -2.7091, -15.8036995, 3.0033998, -2.7091, -13.3786, 3.0033998, -3.3446, -13.3786, 0.6137, -3.3446, -15.8036995, 0.6137, -2.7091, -4.0257998, 3.0033998, -1.6911999, -6.6893, 0.6137, -3.3446, -6.6893, 3.0033998, -3.3446, -4.0257998, 3.0033998, -1.6911999, -4.0257998, 0.6137, -1.6911999, -6.6893, 0.6137, -3.3446, -6.6893, 3.0033998, 6.6893, -2.5674999, 0.0266, 7.6215, -2.5674999, 2.4164, 7.6215, -6.6893, 3.0033998, 6.6893, -6.6893, 0.6137, 6.6893, -2.5674999, 0.0266, 7.6215, -10.033899, 3.0033998, 6.6893, -6.6893, 0.6137, 6.6893, -6.6893, 3.0033998, 6.6893, -10.033899, 3.0033998, 6.6893, -10.033899, 0.6137, 6.6893, -6.6893, 0.6137, 6.6893, -2.5674999, 2.4164, 7.6215, -2.1193, 0.6137, 5.2736998, -2.1397, 3.0033998, 5.1795, -2.5674999, 2.4164, 7.6215, -2.5674999, 0.0266, 7.6215, -2.1193, 0.6137, 5.2736998, -2.1193, 0.6137, 5.2736998, -3.3446, 3.0033998, 3.3446, -2.1397, 3.0033998, 5.1795, -2.1193, 0.6137, 5.2736998, -3.3446, 0.6137, 3.3446, -3.3446, 3.0033998, 3.3446, -13.677099, 0.6137, 5.2616, -12.747499, 3.0033998, 6.3671, -13.6942, 3.0033998, 5.1798, -13.677099, 0.6137, 5.2616, -12.747499, 0.6137, 6.3671, -12.747499, 3.0033998, 6.3671, -15.7546, 0.6137, 3.5372, -14.4892, 3.0033998, 4.2778, -15.372, 3.0033998, 3.6132998, -15.7546, 0.6137, 3.5372, -14.4892, 0.6137, 4.2778, -14.4892, 3.0033998, 4.2778, 21.1831, 0, -13.6544, 16.8204, 3.138, -13.447599, 18.0241, 0, -14.3019, 21.1831, 0, -13.6544, 20.1539, 3.1299, -13.399799, 16.8204, 3.138, -13.447599, 21.8888, 0, -10.033899, 20.1539, 3.1299, -13.399799, 21.1831, 0, -13.6544, 21.8888, 0, -10.033899, 20.198, 3.1490998, -10.033899, 20.1539, 3.1299, -13.399799, 17.2285, 0, -16.8036, 12.7471, 3.1766999, -16.1988, 13.7014, 0, -17.416399, 17.2285, 0, -16.8036, 16.756899, 3.1650999, -16.7286, 12.7471, 3.1766999, -16.1988, 16.8204, 3.138, -13.447599, 17.2285, 0, -16.8036, 18.0241, 0, -14.3019, 17.2285, 0, -16.8036, 16.8204, 3.138, -13.447599, 16.8051, 3.1778, -13.479, 17.2285, 0, -16.8036, 16.8051, 3.1778, -13.479, 16.756899, 3.1650999, -16.7286, 12.7471, 3.1766999, -16.1988, 13.3786, 0, -20.5992, 13.7014, 0, -17.416399, 13.3786, 0, -20.5992, 12.7471, 3.1766999, -16.1988, 12.747, 3.1771, -16.1991, 13.3786, 0, -20.5992, 12.747, 3.1771, -16.1991, 12.723599, 2.9213, -19.2902, 13.3786, 0, -20.5992, 10.033899, 3.0523999, -19.0294, 10.033899, 0, -20.467, 13.3786, 0, -20.5992, 12.723599, 2.9213, -19.2902, 10.033899, 3.0523999, -19.0294, 10.033899, 0, -20.467, 6.6893, 3.0440998, -19.036999, 6.6893, 0, -20.1318, 10.033899, 0, -20.467, 10.033899, 3.0523999, -19.0294, 6.6893, 3.0440998, -19.036999, 3.0140998, 0, -16.7232, 4.4131, 3.2091, -13.357699, 3.3446, -0.3989, -12.9443, 3.0140998, 0, -16.7232, 3.5895, 3.2033, -16.7232, 4.4131, 3.2091, -13.357699, 6.6893, 3.0440998, -19.036999, 3.3446, 0, -20.5992, 6.6893, 0, -20.1318, 3.3446, 0, -20.5992, 6.6893, 3.0440998, -19.036999, 6.6640997, 3.0702, -19.018099, 3.3446, 0, -20.5992, 6.6640997, 3.0702, -19.018099, 4.4783998, 3.1067, -18.2665, 3.3446, 0, -20.5992, 3.5895, 3.2033, -16.7232, 3.0140998, 0, -16.7232, 3.3446, 0, -20.5992, 4.4783998, 3.1067, -18.2665, 3.5895, 3.2033, -16.7232, 2.8241, 0, -10.4594, 0, 3.179, -9.0652, 0, 0, -10.7654, 2.8241, 0, -10.4594, 3.7529, 3.1712, -9.0494995, 0, 3.179, -9.0652, 3.3446, -0.3989, -12.9443, 3.7529, 3.1712, -9.0494995, 2.8241, 0, -10.4594, 3.3446, -0.3989, -12.9443, 4.4131, 3.2091, -13.357699, 3.7529, 3.1712, -9.0494995, 4.4131, 3.2091, -13.357699, 3.3446, -0.3989, -12.9443, 4.4131, 3.2091, -13.357699, -2.6297, 0, -10.5745, -3.6538, 3.1585999, -12.813399, -3.3446, 0, -13.3786, -2.6297, 0, -10.5745, -3.6076, 3.1676, -10.0696, -3.6538, 3.1585999, -12.813399, -3.3446, 0, -13.3786, -6.6893, 3.175, -12.867, -6.6893, 0, -14.2637, -3.3446, 0, -13.3786, -3.6538, 3.1585999, -12.813399, -6.6893, 3.175, -12.867, 0, 3.179, -9.0652, -2.6297, 0, -10.5745, 0, 0, -10.7654, -2.6297, 0, -10.5745, 0, 3.179, -9.0652, -0.0081, 3.1864998, -9.063499, -2.6297, 0, -10.5745, -0.0081, 3.1864998, -9.063499, -3.6076, 3.1676, -10.0696, -6.6893, 0, -14.2637, -10.4043, 3.1608999, -13.4295, -9.6168995, 0, -14.127899, -6.6893, 0, -14.2637, -6.6893, 3.175, -12.867, -10.4043, 3.1608999, -13.4295, -6.6893, 3.175, -12.867, -6.6893, 0, -14.2637, -6.6893, 3.175, -12.867, -9.6168995, 0, -14.127899, -10.4319, 3.1557999, -16.1556, -10.033899, 0, -16.7232, -9.6168995, 0, -14.127899, -10.4043, 3.1608999, -13.4295, -10.4319, 3.1557999, -16.1556, -10.033899, 0, -16.7232, -13.3786, 3.1771998, -15.7625, -13.3786, 0, -17.3364, -10.033899, 0, -16.7232, -10.4319, 3.1557999, -16.1556, -13.3786, 3.1771998, -15.7625, -16.7232, 0, -16.7232, -16.3753, 3.4225, -13.6277, -16.7232, 0, -13.3786, -16.7232, 0, -16.7232, -16.0243, 3.3509998, -15.752, -16.3753, 3.4225, -13.6277, -13.3786, 3.1771998, -15.7625, -16.7232, 0, -16.7232, -13.3786, 0, -17.3364, -16.7232, 0, -16.7232, -13.3786, 3.1771998, -15.7625, -13.3911, 3.1892, -15.757, -16.7232, 0, -16.7232, -13.3911, 3.1892, -15.757, -16.0243, 3.3509998, -15.752, -17.275, 0, -10.5169, -19.6237, 3.1655998, -9.757899, -20.0679, 0, -10.033899, -17.275, 0, -10.5169, -16.4131, 3.1706, -9.788899, -19.6237, 3.1655998, -9.757899, -16.7232, 0, -13.3786, -16.4131, 3.1706, -9.788899, -17.275, 0, -10.5169, -16.7232, 0, -13.3786, -16.3753, 3.4225, -13.6277, -16.4131, 3.1706, -9.788899, -16.3753, 3.4225, -13.6277, -16.7232, 0, -13.3786, -16.3753, 3.4225, -13.6277, -20.3764, 0, -6.6893, -19.9489, 3.2063, -2.8082, -20.0679, -0.3484, -3.5669, -20.3764, 0, -6.6893, -19.515, 3.2122, -6.6893, -19.9489, 3.2063, -2.8082, -19.6237, 3.1655998, -9.757899, -20.3764, 0, -6.6893, -20.0679, 0, -10.033899, -20.3764, 0, -6.6893, -19.6237, 3.1655998, -9.757899, -19.6161, 3.2075999, -9.7116995, -20.3764, 0, -6.6893, -19.6161, 3.2075999, -9.7116995, -19.515, 3.2122, -6.6893, -23.412498, -0.2913, 0.26119998, -23.0809, 3.3083, 3.3615, -23.412498, 0, 3.3446, -23.412498, -0.2913, 0.26119998, -23.3676, 3.4459999, 0.1372, -23.0809, 3.3083, 3.3615, -23.412498, 0, -3.3446, -23.3676, 3.4459999, 0.1372, -23.412498, -0.2913, 0.26119998, -23.412498, 0, -3.3446, -23.390299, 3.3349998, -2.6938999, -23.3676, 3.4459999, 0.1372, -20.0679, -0.3484, -3.5669, -23.390299, 3.3349998, -2.6938999, -23.412498, 0, -3.3446, -20.0679, -0.3484, -3.5669, -19.9489, 3.2063, -2.8082, -23.390299, 3.3349998, -2.6938999, -19.9489, 3.2063, -2.8082, -20.0679, -0.3484, -3.5669, -19.9489, 3.2063, -2.8082, -23.0809, 3.3083, 3.3615, -23.8078, 0, 6.6893, -23.412498, 0, 3.3446, -23.8078, 0, 6.6893, -23.0809, 3.3083, 3.3615, -23.0777, 3.3197, 3.3734, -23.8078, 0, 6.6893, -23.0777, 3.3197, 3.3734, -22.563799, 3.2219, 6.6893, -23.8078, 0, 6.6893, -22.791399, 3.2124999, 9.4432, -23.412498, 0, 10.033899, -23.8078, 0, 6.6893, -22.563799, 3.2219, 6.6893, -22.791399, 3.2124999, 9.4432, -23.412498, 0, 10.033899, -20.0679, 3.1943, 9.407499, -20.0679, 0, 10.033899, -23.412498, 0, 10.033899, -22.791399, 3.2124999, 9.4432, -20.0679, 3.1943, 9.407499, -22.791399, 3.2124999, 9.4432, -23.412498, 0, 10.033899, -22.791399, 3.2124999, 9.4432, -20.0679, 0, 10.033899, -16.2818, 3.1913998, 9.8056, -16.7232, 0, 10.033899, -20.0679, 0, 10.033899, -20.0679, 3.1943, 9.407499, -16.2818, 3.1913998, 9.8056, -16.7232, 0, 13.3786, -13.3786, 3.194, 12.8089, -13.3786, 0, 13.3786, -16.7232, 0, 13.3786, -16.076399, 3.04, 12.735, -13.3786, 3.194, 12.8089, -16.7232, 0, 10.033899, -16.076399, 3.04, 12.735, -16.7232, 0, 13.3786, -16.7232, 0, 10.033899, -16.2818, 3.1913998, 9.8056, -16.076399, 3.04, 12.735, -16.2818, 3.1913998, 9.8056, -16.7232, 0, 10.033899, -16.2818, 3.1913998, 9.8056, -13.3786, 0, 13.3786, -10.033899, 2.9384, 12.4537, -10.033899, 0, 13.3786, -13.3786, 0, 13.3786, -13.3786, 3.194, 12.8089, -10.033899, 2.9384, 12.4537, -13.3786, 3.194, 12.8089, -13.3786, 0, 13.3786, -13.3786, 3.194, 12.8089, -10.033899, 0, 13.3786, -6.2748, 3.126, 13.122, -6.6893, 0, 13.3786, -10.033899, 0, 13.3786, -10.033899, 2.9384, 12.4537, -6.2748, 3.126, 13.122, -6.6893, 0, 13.3786, -6.2748, 3.1260998, 16.4145, -6.6893, 0, 16.7232, -6.6893, 0, 13.3786, -6.2748, 3.126, 13.122, -6.2748, 3.1260998, 16.4145, -3.3446, 0, 16.7232, 0, 2.9636998, 16.0179, 0, 0, 16.7232, -3.3446, 0, 16.7232, -3.3446, 3.1497998, 15.914599, 0, 2.9636998, 16.0179, 0, 0, 16.7232, 3.6425998, 3.1413999, 16.5127, 3.3446, 0, 16.7232, 0, 0, 16.7232, 0, 2.9636998, 16.0179, 3.6425998, 3.1413999, 16.5127, -6.2748, 3.1260998, 16.4145, -3.3446, 0, 16.7232, -6.6893, 0, 16.7232, -3.3446, 0, 16.7232, -6.2748, 3.1260998, 16.4145, -6.2558, 3.1427999, 16.4098, -3.3446, 0, 16.7232, -6.2558, 3.1427999, 16.4098, -3.3446, 3.1497998, 15.914599, 3.3446, 0, 20.0679, 6.6893, 3.2050998, 19.228699, 6.6893, 0, 20.0679, 3.3446, 0, 20.0679, 3.9266999, 3.5967, 19.6223, 6.6893, 3.2050998, 19.228699, 3.6425998, 3.1413999, 16.5127, 3.3446, 0, 20.0679, 3.3446, 0, 16.7232, 3.3446, 0, 20.0679, 3.6425998, 3.1413999, 16.5127, 3.6536, 3.2078998, 16.5669, 3.3446, 0, 20.0679, 3.6536, 3.2078998, 16.5669, 3.9266999, 3.5967, 19.6223, 6.6893, 3.2050998, 19.228699, 10.572599, 0, 20.0679, 6.6893, 0, 20.0679, 10.572599, 0, 20.0679, 6.6893, 3.2050998, 19.228699, 6.6991997, 3.2148, 19.2255, 10.572599, 0, 20.0679, 6.6991997, 3.2148, 19.2255, 9.3066, 3.2186, 19.0653, 10.572599, 0, 20.0679, 10.5913, 3.1985998, 16.7232, 10.572599, 0, 16.7232, 10.572599, 0, 20.0679, 9.3066, 3.2186, 19.0653, 10.5913, 3.1985998, 16.7232, 10.5913, 3.1985998, 16.7232, 10.572599, 0, 13.918799, 10.572599, 0, 16.7232, 10.572599, 0, 13.918799, 10.5913, 3.1985998, 16.7232, 10.5893, 3.2117, 16.7106, 10.572599, 0, 13.918799, 10.5893, 3.2117, 16.7106, 10.0546, 3.1885, 13.4071, 10.572599, 0, 13.918799, 13.2631, 3.5611, 13.4043, 13.8046, 0, 13.918799, 10.572599, 0, 13.918799, 10.0546, 3.1885, 13.4071, 13.2631, 3.5611, 13.4043, 13.8046, 0, 13.918799, 12.9758, 3.2036, 10.033899, 13.8046, 0, 10.033899, 13.8046, 0, 13.918799, 13.2631, 3.5611, 13.4043, 12.9758, 3.2036, 10.033899, 13.8046, 0, 13.918799, 13.2631, 3.5611, 13.4043, 13.2631, 3.5611, 13.4043, 13.8046, 0, 10.033899, 13.4036, 3.0749998, 6.6893, 13.8046, 0, 6.6893, 13.8046, 0, 10.033899, 12.9758, 3.2036, 10.033899, 13.4036, 3.0749998, 6.6893, 20.961199, 0, -3.3446, 16.7127, 2.8734, -3.3446, 20.144999, 2.7605999, -3.3446, 20.961199, 0, -3.3446, 14.0489, 3.0042, -3.3446, 16.7127, 2.8734, -3.3446, 20.144999, 2.7605999, -3.3446, 21.2349, 0, -6.6893, 20.961199, 0, -3.3446, 21.2349, 0, -6.6893, 20.144999, 2.7605999, -3.3446, 20.1349, 2.7974, -3.3783998, 21.2349, 0, -6.6893, 20.1349, 2.7974, -3.3783998, 20.147, 3.0588999, -6.6893, 20.147, 3.0588999, -6.6893, 21.8888, 0, -10.033899, 21.2349, 0, -6.6893, 21.8888, 0, -10.033899, 20.147, 3.0588999, -6.6893, 20.1796, 3.182, -9.995999, 21.8888, 0, -10.033899, 20.1796, 3.182, -9.995999, 20.198, 3.1490998, -10.033899, 23.412498, 0, -26.7572, 26.7572, -0.2593, -23.412498, 23.412498, 0, -23.412498, 26.7572, -0.2593, -23.412498, 23.412498, 0, -26.7572, 26.7572, -0.2593, -25.084799, 26.7572, -0.2593, -25.084799, 23.412498, 0, -26.7572, 26.7572, -0.2593, -26.7572, 26.7572, -0.2593, -21.7402, 23.412498, 0, -23.412498, 26.7572, -0.2593, -23.412498, 26.7572, -0.2593, -20.0679, 23.412498, 0, -23.412498, 26.7572, -0.2593, -21.7402, 23.412498, 0, -23.412498, 26.7572, -0.2593, -20.0679, 23.412498, 0, -20.0679, 26.7572, -0.2593, -18.3955, 23.412498, 0, -20.0679, 26.7572, -0.2593, -20.0679, 26.7572, -0.2593, -16.7232, 23.412498, 0, -20.0679, 26.7572, -0.2593, -18.3955, 23.412498, 0, -20.0679, 26.7572, -0.2593, -16.7232, 23.412498, 0, -16.7232, 26.7572, -0.2593, -15.0508995, 23.412498, 0, -16.7232, 26.7572, -0.2593, -16.7232, 26.7572, -0.2593, -13.3786, 23.412498, 0, -16.7232, 26.7572, -0.2593, -15.0508995, 23.412498, 0, -16.7232, 26.7572, -0.2593, -13.3786, 23.412498, 0, -13.3786, 26.7572, -0.2593, -11.7063, 23.412498, 0, -13.3786, 26.7572, -0.2593, -13.3786, 26.7572, -0.2593, -10.033899, 23.412498, 0, -13.3786, 26.7572, -0.2593, -11.7063, 23.412498, 0, -13.3786, 26.7572, -0.2593, -10.033899, 23.412498, 0, -10.033899, 26.7572, -0.2593, -8.3616, 23.412498, 0, -10.033899, 26.7572, -0.2593, -10.033899, 26.7572, -0.2593, -6.6893, 23.412498, 0, -10.033899, 26.7572, -0.2593, -8.3616, 23.412498, 0, -10.033899, 26.7572, -0.2593, -6.6893, 23.412498, 0, -6.6893, 26.7572, -0.2593, -5.0169997, 23.412498, 0, -6.6893, 26.7572, -0.2593, -6.6893, 26.7572, -0.2593, -3.3446, 23.412498, 0, -6.6893, 26.7572, -0.2593, -5.0169997, 23.412498, 0, -6.6893, 26.7572, -0.2593, -3.3446, 23.412498, 0, -3.3446, 26.7572, -0.2593, -1.6723, 23.412498, 0, -3.3446, 26.7572, -0.2593, -3.3446, 26.7572, -0.2593, 0, 23.412498, 0, -3.3446, 26.7572, -0.2593, -1.6723, 23.412498, 0, -3.3446, 26.7572, -0.2593, 0, 23.412498, 0, 0, 26.7572, -0.2593, 1.6723, 23.412498, 0, 0, 26.7572, -0.2593, 0, 26.7572, -0.2593, 3.3446, 23.412498, 0, 0, 26.7572, -0.2593, 1.6723, 23.412498, 0, 0, 26.7572, -0.2593, 3.3446, 23.412498, 0, 3.3446, 26.7572, -0.2593, 5.0169997, 23.412498, 0, 3.3446, 26.7572, -0.2593, 3.3446, 26.7572, -0.2593, 6.6893, 23.412498, 0, 3.3446, 26.7572, -0.2593, 5.0169997, 23.412498, 0, 3.3446, 26.7572, -0.2593, 6.6893, 23.412498, 0, 6.6893, 26.7572, -0.2593, 8.3616, 23.412498, 0, 6.6893, 26.7572, -0.2593, 6.6893, 26.7572, -0.2593, 10.033899, 23.412498, 0, 6.6893, 26.7572, -0.2593, 8.3616, 23.412498, 0, 6.6893, 26.7572, -0.2593, 10.033899, 23.412498, 0, 10.033899, 26.7572, -0.2593, 11.7063, 23.412498, 0, 10.033899, 26.7572, -0.2593, 10.033899, 26.7572, -0.2593, 13.3786, 23.412498, 0, 10.033899, 26.7572, -0.2593, 11.7063, 23.412498, 0, 10.033899, 26.7572, -0.2593, 13.3786, 23.412498, 0, 13.3786, 26.7572, -0.2593, 15.0508995, 23.412498, 0, 13.3786, 26.7572, -0.2593, 13.3786, 26.7572, -0.2593, 16.7232, 23.412498, 0, 13.3786, 26.7572, -0.2593, 15.0508995, 23.412498, 0, 13.3786, 26.7572, -0.2593, 16.7232, 23.412498, 0, 16.7232, 26.7572, -0.2593, 18.3955, 23.412498, 0, 16.7232, 26.7572, -0.2593, 16.7232, 26.7572, -0.2593, 20.0679, 23.412498, 0, 16.7232, 26.7572, -0.2593, 18.3955, 23.412498, 0, 16.7232, 26.7572, -0.2593, 20.0679, 23.412498, 0, 20.0679, 26.7572, -0.2593, 21.7402, 23.412498, 0, 20.0679, 26.7572, -0.2593, 20.0679, 26.7572, -0.2593, 23.412498, 23.412498, 0, 20.0679, 26.7572, -0.2593, 21.7402, 23.412498, 0, 20.0679, 26.7572, -0.2593, 23.412498, 23.412498, 0, 23.412498, 26.7572, -0.2593, 25.084799, 23.412498, 0, 23.412498, 26.7572, -0.2593, 23.412498, 26.7572, -0.2593, 26.7572, 23.412498, 0, 23.412498, 26.7572, -0.2593, 25.084799, 23.412498, 0, 23.412498, 26.7572, -0.2593, 26.7572, 23.412498, 0, 26.7572, -3.3446, 0, -23.412498, 0, 0, -26.7572, 0, 0, -23.412498, 0, 0, -23.412498, 0, 0, -26.7572, 3.3446, 0, -26.7572, -3.3446, 0, -23.412498, -3.3446, 0, -26.7572, 0, 0, -26.7572, 0, 0, -23.412498, 3.3446, 0, -26.7572, 3.3446, 0, -23.412498, 3.3446, 0, -23.412498, 3.3446, 0, -26.7572, 6.6893, 0, -26.7572, -3.3446, 0, -20.0679, -3.3446, 0, -23.412498, 0, 0, -23.412498, 3.3446, 0, -23.412498, 6.6893, 0, -26.7572, 6.6893, 0, -23.412498, 6.6893, 0, -23.412498, 6.6893, 0, -26.7572, 10.033899, 0, -26.7572, 0, 0, -20.0679, 0, 0, -23.412498, 3.3446, 0, -23.412498, -3.3446, 0, -20.0679, 0, 0, -23.412498, 0, 0, -20.0679, 3.3446, 0, -20.5992, 3.3446, 0, -23.412498, 6.6893, 0, -23.412498, 0, 0, -20.0679, 3.3446, 0, -23.412498, 3.3446, 0, -20.5992, 3.3446, 0, -20.5992, 6.6893, 0, -23.412498, 6.6893, 0, -20.1318, 6.6893, 0, -23.412498, 10.033899, 0, -26.7572, 10.033899, 0, -23.412498, 6.6893, 0, -20.1318, 6.6893, 0, -23.412498, 10.033899, 0, -23.412498, 10.033899, 0, -23.412498, 10.033899, 0, -26.7572, 13.3786, 0, -26.7572, 6.6893, 0, -20.1318, 10.033899, 0, -23.412498, 10.033899, 0, -20.467, 10.033899, 0, -23.412498, 13.3786, 0, -26.7572, 13.3786, 0, -23.412498, 10.033899, 0, -20.467, 10.033899, 0, -23.412498, 13.3786, 0, -23.412498, 13.3786, 0, -23.412498, 13.3786, 0, -26.7572, 16.7232, 0, -26.7572, 10.033899, 0, -20.467, 13.3786, 0, -23.412498, 13.3786, 0, -20.5992, 13.3786, 0, -23.412498, 16.7232, 0, -26.7572, 16.7232, 0, -23.412498, 13.3786, 0, -20.5992, 13.3786, 0, -23.412498, 16.7232, 0, -23.412498, 16.7232, 0, -23.412498, 16.7232, 0, -26.7572, 20.0679, 0, -26.7572, 13.3786, 0, -20.5992, 16.7232, 0, -23.412498, 16.7232, 0, -20.0679, 13.7014, 0, -17.416399, 13.3786, 0, -20.5992, 16.7232, 0, -20.0679, 13.7014, 0, -17.416399, 16.7232, 0, -20.0679, 17.2285, 0, -16.8036, 16.7232, 0, -23.412498, 20.0679, 0, -26.7572, 20.0679, 0, -23.412498, 16.7232, 0, -20.0679, 16.7232, 0, -23.412498, 20.0679, 0, -23.412498, 20.0679, 0, -23.412498, 20.0679, 0, -26.7572, 23.412498, 0, -26.7572, 20.0679, 0, -23.412498, 23.412498, 0, -26.7572, 23.412498, 0, -23.412498, 16.7232, 0, -20.0679, 20.0679, 0, -23.412498, 20.0679, 0, -20.0679, 20.0679, 0, -20.0679, 20.0679, 0, -23.412498, 23.412498, 0, -23.412498, 17.2285, 0, -16.8036, 16.7232, 0, -20.0679, 20.0679, 0, -20.0679, 20.0679, 0, -20.0679, 23.412498, 0, -23.412498, 23.412498, 0, -20.0679, 17.2285, 0, -16.8036, 20.0679, 0, -20.0679, 20.0679, 0, -16.7232, 20.0679, 0, -16.7232, 20.0679, 0, -20.0679, 23.412498, 0, -20.0679, 18.0241, 0, -14.3019, 17.2285, 0, -16.8036, 20.0679, 0, -16.7232, 20.0679, 0, -16.7232, 23.412498, 0, -20.0679, 23.412498, 0, -16.7232, 18.0241, 0, -14.3019, 20.0679, 0, -16.7232, 21.1831, 0, -13.6544, 21.1831, 0, -13.6544, 20.0679, 0, -16.7232, 23.412498, 0, -16.7232, 21.1831, 0, -13.6544, 23.412498, 0, -16.7232, 23.412498, 0, -13.3786, 21.8888, 0, -10.033899, 21.1831, 0, -13.6544, 23.412498, 0, -13.3786, 21.8888, 0, -10.033899, 23.412498, 0, -13.3786, 23.412498, 0, -10.033899, 21.2349, 0, -6.6893, 21.8888, 0, -10.033899, 23.412498, 0, -10.033899, 21.2349, 0, -6.6893, 23.412498, 0, -10.033899, 23.412498, 0, -6.6893, 20.961199, 0, -3.3446, 21.2349, 0, -6.6893, 23.412498, 0, -6.6893, 20.961199, 0, -3.3446, 23.412498, 0, -6.6893, 23.412498, 0, -3.3446, 20.0679, 0, 0, 20.961199, 0, -3.3446, 23.412498, 0, -3.3446, 20.0679, 0, 0, 23.412498, 0, -3.3446, 23.412498, 0, 0, 20.0679, 0, 3.3446, 20.0679, 0, 0, 23.412498, 0, 0, 20.0679, 0, 3.3446, 23.412498, 0, 0, 23.412498, 0, 3.3446, 10.572599, 0, 16.7232, 13.8046, 0, 13.918799, 13.3786, 0, 16.7232, 10.572599, 0, 16.7232, 10.572599, 0, 13.918799, 13.8046, 0, 13.918799, 10.572599, 0, 20.0679, 10.572599, 0, 16.7232, 13.3786, 0, 16.7232, 13.3786, 0, 16.7232, 13.8046, 0, 13.918799, 15.0508995, 0, 13.3786, 10.572599, 0, 20.0679, 13.3786, 0, 16.7232, 13.3786, 0, 20.0679, 13.8046, 0, 13.918799, 15.0508995, 0, 10.033899, 15.0508995, 0, 13.3786, 13.8046, 0, 13.918799, 13.8046, 0, 10.033899, 15.0508995, 0, 10.033899, 13.8046, 0, 10.033899, 15.0508995, 0, 6.6893, 15.0508995, 0, 10.033899, 13.8046, 0, 10.033899, 13.8046, 0, 6.6893, 15.0508995, 0, 6.6893, 15.0508995, 0, 10.033899, 15.0508995, 0, 6.6893, 16.7232, 0, 6.6893, 15.0508995, 0, 10.033899, 16.7232, 0, 6.6893, 16.7232, 0, 10.033899, 15.0508995, 0, 13.3786, 15.0508995, 0, 10.033899, 16.7232, 0, 10.033899, 13.3786, 0, 16.7232, 15.0508995, 0, 13.3786, 15.0508995, 0, 16.7232, 13.3786, 0, 20.0679, 13.3786, 0, 16.7232, 15.0508995, 0, 16.7232, 15.0508995, 0, 13.3786, 16.7232, 0, 10.033899, 16.7232, 0, 13.3786, 15.0508995, 0, 16.7232, 15.0508995, 0, 13.3786, 16.7232, 0, 13.3786, 16.7232, 0, 13.3786, 16.7232, 0, 10.033899, 20.0679, 0, 10.033899, 16.7232, 0, 13.3786, 20.0679, 0, 10.033899, 20.0679, 0, 13.3786, 20.0679, 0, 13.3786, 20.0679, 0, 10.033899, 23.412498, 0, 10.033899, 20.0679, 0, 13.3786, 23.412498, 0, 10.033899, 23.412498, 0, 13.3786, 16.7232, 0, 16.7232, 16.7232, 0, 13.3786, 20.0679, 0, 13.3786, 15.0508995, 0, 16.7232, 16.7232, 0, 13.3786, 16.7232, 0, 16.7232, 20.0679, 0, 16.7232, 20.0679, 0, 13.3786, 23.412498, 0, 13.3786, 16.7232, 0, 16.7232, 20.0679, 0, 13.3786, 20.0679, 0, 16.7232, 20.0679, 0, 16.7232, 23.412498, 0, 13.3786, 23.412498, 0, 16.7232, 15.0508995, 0, 20.0679, 15.0508995, 0, 16.7232, 16.7232, 0, 16.7232, 13.3786, 0, 20.0679, 15.0508995, 0, 16.7232, 15.0508995, 0, 20.0679, 16.7232, 0, 20.0679, 16.7232, 0, 16.7232, 20.0679, 0, 16.7232, 15.0508995, 0, 20.0679, 16.7232, 0, 16.7232, 16.7232, 0, 20.0679, 20.0679, 0, 20.0679, 20.0679, 0, 16.7232, 23.412498, 0, 16.7232, 16.7232, 0, 20.0679, 20.0679, 0, 16.7232, 20.0679, 0, 20.0679, 20.0679, 0, 20.0679, 23.412498, 0, 16.7232, 23.412498, 0, 20.0679, 20.0679, 0, 23.412498, 20.0679, 0, 20.0679, 23.412498, 0, 20.0679, 20.0679, 0, 23.412498, 23.412498, 0, 20.0679, 23.412498, 0, 23.412498, 20.0679, 0, 26.7572, 20.0679, 0, 23.412498, 23.412498, 0, 23.412498, 20.0679, 0, 26.7572, 23.412498, 0, 23.412498, 23.412498, 0, 26.7572, 16.7232, 0, 26.7572, 20.0679, 0, 23.412498, 20.0679, 0, 26.7572, 16.7232, 0, 23.412498, 20.0679, 0, 20.0679, 20.0679, 0, 23.412498, 16.7232, 0, 26.7572, 16.7232, 0, 23.412498, 20.0679, 0, 23.412498, 16.7232, 0, 23.412498, 16.7232, 0, 20.0679, 20.0679, 0, 20.0679, 15.0508995, 0, 26.7572, 16.7232, 0, 23.412498, 16.7232, 0, 26.7572, 15.0508995, 0, 23.412498, 16.7232, 0, 20.0679, 16.7232, 0, 23.412498, 15.0508995, 0, 26.7572, 15.0508995, 0, 23.412498, 16.7232, 0, 23.412498, 15.0508995, 0, 23.412498, 15.0508995, 0, 20.0679, 16.7232, 0, 20.0679, 13.3786, 0, 26.7572, 15.0508995, 0, 23.412498, 15.0508995, 0, 26.7572, 13.3786, 0, 23.412498, 15.0508995, 0, 20.0679, 15.0508995, 0, 23.412498, 13.3786, 0, 26.7572, 13.3786, 0, 23.412498, 15.0508995, 0, 23.412498, 13.3786, 0, 23.412498, 13.3786, 0, 20.0679, 15.0508995, 0, 20.0679, 10.033899, 0, 26.7572, 13.3786, 0, 23.412498, 13.3786, 0, 26.7572, 10.033899, 0, 23.412498, 13.3786, 0, 20.0679, 13.3786, 0, 23.412498, 10.033899, 0, 26.7572, 10.033899, 0, 23.412498, 13.3786, 0, 23.412498, 10.033899, 0, 23.412498, 10.572599, 0, 20.0679, 13.3786, 0, 20.0679, 6.6893, 0, 26.7572, 10.033899, 0, 23.412498, 10.033899, 0, 26.7572, 6.6893, 0, 23.412498, 10.572599, 0, 20.0679, 10.033899, 0, 23.412498, 6.6893, 0, 26.7572, 6.6893, 0, 23.412498, 10.033899, 0, 23.412498, 6.6893, 0, 23.412498, 6.6893, 0, 20.0679, 10.572599, 0, 20.0679, 3.3446, 0, 26.7572, 6.6893, 0, 23.412498, 6.6893, 0, 26.7572, 3.3446, 0, 23.412498, 6.6893, 0, 20.0679, 6.6893, 0, 23.412498, 3.3446, 0, 26.7572, 3.3446, 0, 23.412498, 6.6893, 0, 23.412498, 3.3446, 0, 23.412498, 3.3446, 0, 20.0679, 6.6893, 0, 20.0679, 0, 0, 26.7572, 3.3446, 0, 23.412498, 3.3446, 0, 26.7572, 0, 0, 26.7572, 0, 0, 23.412498, 3.3446, 0, 23.412498, 0, 0, 23.412498, 3.3446, 0, 20.0679, 3.3446, 0, 23.412498, -3.3446, 0, 26.7572, 0, 0, 23.412498, 0, 0, 26.7572, 0, 0, 23.412498, 0, 0, 20.0679, 3.3446, 0, 20.0679, 0, 0, 20.0679, 3.3446, 0, 16.7232, 3.3446, 0, 20.0679, 0, 0, 20.0679, 0, 0, 16.7232, 3.3446, 0, 16.7232, -3.3446, 0, 26.7572, -3.3446, 0, 23.412498, 0, 0, 23.412498, -3.3446, 0, 23.412498, 0, 0, 20.0679, 0, 0, 23.412498, -6.6893, 0, 26.7572, -3.3446, 0, 23.412498, -3.3446, 0, 26.7572, -3.3446, 0, 20.0679, 0, 0, 16.7232, 0, 0, 20.0679, -3.3446, 0, 23.412498, -3.3446, 0, 20.0679, 0, 0, 20.0679, -3.3446, 0, 20.0679, -3.3446, 0, 16.7232, 0, 0, 16.7232, -6.6893, 0, 26.7572, -6.6893, 0, 23.412498, -3.3446, 0, 23.412498, -6.6893, 0, 23.412498, -3.3446, 0, 20.0679, -3.3446, 0, 23.412498, -10.033899, 0, 26.7572, -6.6893, 0, 23.412498, -6.6893, 0, 26.7572, -6.6893, 0, 20.0679, -3.3446, 0, 16.7232, -3.3446, 0, 20.0679, -6.6893, 0, 23.412498, -6.6893, 0, 20.0679, -3.3446, 0, 20.0679, -6.6893, 0, 20.0679, -6.6893, 0, 16.7232, -3.3446, 0, 16.7232, -10.033899, 0, 26.7572, -10.033899, 0, 23.412498, -6.6893, 0, 23.412498, -10.033899, 0, 23.412498, -6.6893, 0, 20.0679, -6.6893, 0, 23.412498, -13.3786, 0, 26.7572, -10.033899, 0, 23.412498, -10.033899, 0, 26.7572, -10.033899, 0, 20.0679, -6.6893, 0, 16.7232, -6.6893, 0, 20.0679, -10.033899, 0, 23.412498, -10.033899, 0, 20.0679, -6.6893, 0, 20.0679, -13.3786, 0, 26.7572, -13.3786, 0, 23.412498, -10.033899, 0, 23.412498, -13.3786, 0, 23.412498, -10.033899, 0, 20.0679, -10.033899, 0, 23.412498, -16.7232, 0, 26.7572, -13.3786, 0, 23.412498, -13.3786, 0, 26.7572, -10.033899, 0, 20.0679, -10.033899, 0, 16.7232, -6.6893, 0, 16.7232, -10.033899, 0, 16.7232, -6.6893, 0, 13.3786, -6.6893, 0, 16.7232, -10.033899, 0, 16.7232, -10.033899, 0, 13.3786, -6.6893, 0, 13.3786, -13.3786, 0, 20.0679, -10.033899, 0, 16.7232, -10.033899, 0, 20.0679, -13.3786, 0, 23.412498, -13.3786, 0, 20.0679, -10.033899, 0, 20.0679, -13.3786, 0, 16.7232, -10.033899, 0, 13.3786, -10.033899, 0, 16.7232, -13.3786, 0, 20.0679, -13.3786, 0, 16.7232, -10.033899, 0, 16.7232, -13.3786, 0, 16.7232, -13.3786, 0, 13.3786, -10.033899, 0, 13.3786, -16.7232, 0, 26.7572, -16.7232, 0, 23.412498, -13.3786, 0, 23.412498, -16.7232, 0, 23.412498, -13.3786, 0, 20.0679, -13.3786, 0, 23.412498, -20.0679, 0, 26.7572, -16.7232, 0, 23.412498, -16.7232, 0, 26.7572, -16.7232, 0, 20.0679, -13.3786, 0, 16.7232, -13.3786, 0, 20.0679, -16.7232, 0, 23.412498, -16.7232, 0, 20.0679, -13.3786, 0, 20.0679, -16.7232, 0, 16.7232, -13.3786, 0, 13.3786, -13.3786, 0, 16.7232, -16.7232, 0, 20.0679, -16.7232, 0, 16.7232, -13.3786, 0, 16.7232, -16.7232, 0, 16.7232, -16.7232, 0, 13.3786, -13.3786, 0, 13.3786, -20.0679, 0, 26.7572, -20.0679, 0, 23.412498, -16.7232, 0, 23.412498, -20.0679, 0, 23.412498, -16.7232, 0, 20.0679, -16.7232, 0, 23.412498, -23.412498, 0, 26.7572, -20.0679, 0, 23.412498, -20.0679, 0, 26.7572, -20.0679, 0, 20.0679, -16.7232, 0, 16.7232, -16.7232, 0, 20.0679, -20.0679, 0, 23.412498, -20.0679, 0, 20.0679, -16.7232, 0, 20.0679, -23.412498, 0, 26.7572, -23.412498, 0, 23.412498, -20.0679, 0, 23.412498, -23.412498, 0, 23.412498, -20.0679, 0, 20.0679, -20.0679, 0, 23.412498, -26.7572, 0, 26.7572, -23.412498, 0, 23.412498, -23.412498, 0, 26.7572, -26.7572, 0, 26.7572, -26.7572, 0, 23.412498, -23.412498, 0, 23.412498, -26.7572, 0, 23.412498, -23.412498, 0, 20.0679, -23.412498, 0, 23.412498, -23.412498, 0, 23.412498, -23.412498, 0, 20.0679, -20.0679, 0, 20.0679, -26.7572, 0, 23.412498, -26.7572, 0, 20.0679, -23.412498, 0, 20.0679, -20.0679, 0, 20.0679, -20.0679, 0, 16.7232, -16.7232, 0, 16.7232, -23.412498, 0, 20.0679, -20.0679, 0, 16.7232, -20.0679, 0, 20.0679, -20.0679, 0, 16.7232, -16.7232, 0, 13.3786, -16.7232, 0, 16.7232, -26.7572, 0, 20.0679, -23.412498, 0, 16.7232, -23.412498, 0, 20.0679, -23.412498, 0, 20.0679, -23.412498, 0, 16.7232, -20.0679, 0, 16.7232, -26.7572, 0, 20.0679, -26.7572, 0, 16.7232, -23.412498, 0, 16.7232, -20.0679, 0, 16.7232, -20.0679, 0, 13.3786, -16.7232, 0, 13.3786, -23.412498, 0, 16.7232, -20.0679, 0, 13.3786, -20.0679, 0, 16.7232, -20.0679, 0, 13.3786, -16.7232, 0, 10.033899, -16.7232, 0, 13.3786, -20.0679, 0, 13.3786, -20.0679, 0, 10.033899, -16.7232, 0, 10.033899, -23.412498, 0, 16.7232, -23.412498, 0, 13.3786, -20.0679, 0, 13.3786, -23.412498, 0, 13.3786, -20.0679, 0, 10.033899, -20.0679, 0, 13.3786, -26.7572, 0, 16.7232, -23.412498, 0, 13.3786, -23.412498, 0, 16.7232, -23.412498, 0, 13.3786, -23.412498, 0, 10.033899, -20.0679, 0, 10.033899, -26.7572, 0, 16.7232, -26.7572, 0, 13.3786, -23.412498, 0, 13.3786, -26.7572, 0, 13.3786, -23.412498, 0, 10.033899, -23.412498, 0, 13.3786, -26.7572, 0, 13.3786, -26.7572, 0, 10.033899, -23.412498, 0, 10.033899, -26.7572, 0, 10.033899, -23.8078, 0, 6.6893, -23.412498, 0, 10.033899, -26.7572, 0, 10.033899, -26.7572, 0, 6.6893, -23.8078, 0, 6.6893, -26.7572, 0, 6.6893, -23.412498, 0, 3.3446, -23.8078, 0, 6.6893, -26.7572, 0, 6.6893, -26.7572, 0, 3.3446, -23.412498, 0, 3.3446, -16.7232, 0, -23.412498, -13.3786, 0, -26.7572, -13.3786, 0, -23.412498, -13.3786, 0, -23.412498, -13.3786, 0, -26.7572, -10.033899, 0, -26.7572, -16.7232, 0, -23.412498, -16.7232, 0, -26.7572, -13.3786, 0, -26.7572, -13.3786, 0, -23.412498, -10.033899, 0, -26.7572, -10.033899, 0, -23.412498, -10.033899, 0, -23.412498, -10.033899, 0, -26.7572, -6.6893, 0, -26.7572, -16.7232, 0, -20.0679, -16.7232, 0, -23.412498, -13.3786, 0, -23.412498, -10.033899, 0, -23.412498, -6.6893, 0, -26.7572, -6.6893, 0, -23.412498, -6.6893, 0, -23.412498, -6.6893, 0, -26.7572, -3.3446, 0, -26.7572, -6.6893, 0, -23.412498, -3.3446, 0, -26.7572, -3.3446, 0, -23.412498, -13.3786, 0, -20.0679, -13.3786, 0, -23.412498, -10.033899, 0, -23.412498, -16.7232, 0, -20.0679, -13.3786, 0, -23.412498, -13.3786, 0, -20.0679, -10.033899, 0, -20.0679, -10.033899, 0, -23.412498, -6.6893, 0, -23.412498, -13.3786, 0, -20.0679, -10.033899, 0, -23.412498, -10.033899, 0, -20.0679, -6.6893, 0, -20.0679, -6.6893, 0, -23.412498, -3.3446, 0, -23.412498, -10.033899, 0, -20.0679, -6.6893, 0, -23.412498, -6.6893, 0, -20.0679, -6.6893, 0, -20.0679, -3.3446, 0, -23.412498, -3.3446, 0, -20.0679, -13.3786, 0, -17.3364, -13.3786, 0, -20.0679, -10.033899, 0, -20.0679, -13.3786, 0, -17.3364, -10.033899, 0, -20.0679, -10.033899, 0, -16.7232, -10.033899, 0, -16.7232, -10.033899, 0, -20.0679, -6.6893, 0, -20.0679, -16.7232, 0, -16.7232, -13.3786, 0, -20.0679, -13.3786, 0, -17.3364, -16.7232, 0, -16.7232, -16.7232, 0, -20.0679, -13.3786, 0, -20.0679, -10.033899, 0, -16.7232, -6.6893, 0, -20.0679, -6.6893, 0, -16.7232, -6.6893, 0, -16.7232, -6.6893, 0, -20.0679, -3.3446, 0, -20.0679, -9.6168995, 0, -14.127899, -10.033899, 0, -16.7232, -6.6893, 0, -16.7232, -9.6168995, 0, -14.127899, -6.6893, 0, -16.7232, -6.6893, 0, -14.2637, -6.6893, 0, -16.7232, -3.3446, 0, -20.0679, -3.3446, 0, -16.7232, -6.6893, 0, -14.2637, -6.6893, 0, -16.7232, -3.3446, 0, -16.7232, -3.3446, 0, -16.7232, -3.3446, 0, -20.0679, 0, 0, -20.0679, -6.6893, 0, -14.2637, -3.3446, 0, -16.7232, -3.3446, 0, -13.3786, -3.3446, 0, -16.7232, 0, 0, -20.0679, 0, 0, -16.7232, -3.3446, 0, -13.3786, -3.3446, 0, -16.7232, 0, 0, -16.7232, 0, 0, -16.7232, 0, 0, -20.0679, 3.3446, 0, -20.5992, 0, 0, -16.7232, 3.3446, 0, -20.5992, 3.0140998, 0, -16.7232, -3.3446, 0, -13.3786, 0, 0, -16.7232, 0, 0, -13.3786, -2.6297, 0, -10.5745, -3.3446, 0, -13.3786, 0, 0, -13.3786, -2.6297, 0, -10.5745, 0, 0, -13.3786, 0, 0, -10.7654, -20.0679, 0, -16.7232, -16.7232, 0, -20.0679, -16.7232, 0, -16.7232, -20.0679, 0, -16.7232, -20.0679, 0, -20.0679, -16.7232, 0, -20.0679, -20.0679, 0, -20.0679, -16.7232, 0, -23.412498, -16.7232, 0, -20.0679, -20.0679, 0, -13.3786, -20.0679, 0, -16.7232, -16.7232, 0, -16.7232, -20.0679, 0, -13.3786, -16.7232, 0, -16.7232, -16.7232, 0, -13.3786, -23.412498, 0, -16.7232, -20.0679, 0, -20.0679, -20.0679, 0, -16.7232, -20.0679, 0, -10.033899, -20.0679, 0, -13.3786, -16.7232, 0, -13.3786, -20.0679, 0, -10.033899, -16.7232, 0, -13.3786, -17.275, 0, -10.5169, -23.412498, 0, -13.3786, -20.0679, 0, -16.7232, -20.0679, 0, -13.3786, -23.412498, 0, -13.3786, -23.412498, 0, -16.7232, -20.0679, 0, -16.7232, -23.412498, 0, -10.033899, -20.0679, 0, -13.3786, -20.0679, 0, -10.033899, -23.412498, 0, -10.033899, -23.412498, 0, -13.3786, -20.0679, 0, -13.3786, -26.7572, 0, -10.033899, -23.412498, 0, -13.3786, -23.412498, 0, -10.033899, -26.7572, 0, -10.033899, -26.7572, 0, -13.3786, -23.412498, 0, -13.3786, -26.7572, 0, -13.3786, -23.412498, 0, -16.7232, -23.412498, 0, -13.3786, -26.7572, 0, -6.6893, -26.7572, 0, -10.033899, -23.412498, 0, -10.033899, -26.7572, 0, -13.3786, -26.7572, 0, -16.7232, -23.412498, 0, -16.7232, -26.7572, 0, -6.6893, -23.412498, 0, -10.033899, -23.412498, 0, -6.6893, -23.412498, 0, -6.6893, -23.412498, 0, -10.033899, -20.0679, 0, -10.033899, -23.412498, 0, -6.6893, -20.0679, 0, -10.033899, -20.3764, 0, -6.6893, -26.7572, 0, -3.3446, -26.7572, 0, -6.6893, -23.412498, 0, -6.6893, -26.7572, 0, -3.3446, -23.412498, 0, -6.6893, -23.412498, 0, -3.3446, -26.7572, 0, -16.7232, -23.412498, 0, -20.0679, -23.412498, 0, -16.7232, -23.412498, 0, -16.7232, -23.412498, 0, -20.0679, -20.0679, 0, -20.0679, -26.7572, 0, -16.7232, -26.7572, 0, -20.0679, -23.412498, 0, -20.0679, -23.412498, 0, -20.0679, -20.0679, 0, -23.412498, -20.0679, 0, -20.0679, -20.0679, 0, -20.0679, -20.0679, 0, -23.412498, -16.7232, 0, -23.412498, -20.0679, 0, -23.412498, -16.7232, 0, -26.7572, -16.7232, 0, -23.412498, -20.0679, 0, -23.412498, -20.0679, 0, -26.7572, -16.7232, 0, -26.7572, -23.412498, 0, -20.0679, -23.412498, 0, -23.412498, -20.0679, 0, -23.412498, -23.412498, 0, -23.412498, -20.0679, 0, -26.7572, -20.0679, 0, -23.412498, -26.7572, 0, -20.0679, -23.412498, 0, -23.412498, -23.412498, 0, -20.0679, -23.412498, 0, -23.412498, -23.412498, 0, -26.7572, -20.0679, 0, -26.7572, -26.7572, 0, -20.0679, -26.7572, 0, -23.412498, -23.412498, 0, -23.412498, -26.7572, 0, -23.412498, -23.412498, 0, -26.7572, -23.412498, 0, -23.412498, -26.7572, 0, -23.412498, -26.7572, 0, -26.7572, -23.412498, 0, -26.7572, -23.412498, 0, -3.3446, -20.3764, 0, -6.6893, -20.0679, -0.3484, -3.5669, -23.412498, 0, -3.3446, -23.412498, 0, -6.6893, -20.3764, 0, -6.6893, 16.7232, 0, 10.033899, 20.0679, -0.124199994, 6.6893, 20.0679, 0, 10.033899, 16.7232, 0, 10.033899, 16.7232, 0, 6.6893, 20.0679, -0.124199994, 6.6893, 20.0679, 0, 10.033899, 23.412498, 0, 6.6893, 23.412498, 0, 10.033899, 20.0679, 0, 10.033899, 20.0679, -0.124199994, 6.6893, 23.412498, 0, 6.6893, -26.7572, 0, 3.3446, -23.412498, -0.2913, 0.26119998, -23.412498, 0, 3.3446, -26.7572, 0, 3.3446, -26.7572, 0, 0, -23.412498, -0.2913, 0.26119998, -26.7572, 0, 0, -23.412498, 0, -3.3446, -23.412498, -0.2913, 0.26119998, -26.7572, 0, 0, -26.7572, 0, -3.3446, -23.412498, 0, -3.3446, 0, 0, -13.3786, 3.0140998, 0, -16.7232, 3.3446, -0.3989, -12.9443, 0, 0, -13.3786, 0, 0, -16.7232, 3.0140998, 0, -16.7232, 0, 0, -10.7654, 3.3446, -0.3989, -12.9443, 2.8241, 0, -10.4594, 0, 0, -10.7654, 0, 0, -13.3786, 3.3446, -0.3989, -12.9443, 20.0679, -0.124199994, 6.6893, 23.412498, 0, 3.3446, 23.412498, 0, 6.6893, 20.0679, -0.124199994, 6.6893, 20.0679, 0, 3.3446, 23.412498, 0, 3.3446, 51.2184, -3.9413, -15.0508995, 54.072197, -3.9413, -13.3786, 51.2184, -3.9413, -13.3786, 51.2184, -3.9413, -13.3786, 54.072197, -3.9413, -13.3786, 54.072197, -3.9413, -11.7063, 51.2184, -3.9413, -15.0508995, 54.072197, -3.9413, -15.0508995, 54.072197, -3.9413, -13.3786, 51.2184, -3.9413, -13.3786, 54.072197, -3.9413, -11.7063, 51.2184, -3.9413, -11.7063, 51.2184, -3.9413, -11.7063, 54.072197, -3.9413, -11.7063, 54.072197, -3.9413, -10.033899, 48.3645, -3.9413, -15.0508995, 51.2184, -3.9413, -15.0508995, 51.2184, -3.9413, -13.3786, 51.2184, -3.9413, -11.7063, 54.072197, -3.9413, -10.033899, 51.2184, -3.9413, -10.033899, 51.2184, -3.9413, -10.033899, 54.072197, -3.9413, -10.033899, 54.072197, -3.9413, -8.3616, 48.3645, -3.9413, -13.3786, 51.2184, -3.9413, -13.3786, 51.2184, -3.9413, -11.7063, 48.3645, -3.9413, -15.0508995, 51.2184, -3.9413, -13.3786, 48.3645, -3.9413, -13.3786, 48.3645, -3.9413, -11.7063, 51.2184, -3.9413, -11.7063, 51.2184, -3.9413, -10.033899, 48.3645, -3.9413, -13.3786, 51.2184, -3.9413, -11.7063, 48.3645, -3.9413, -11.7063, 51.2184, -3.9413, -10.033899, 54.072197, -3.9413, -8.3616, 51.2184, -3.9413, -8.3616, 51.2184, -3.9413, -8.3616, 54.072197, -3.9413, -8.3616, 54.072197, -3.9413, -6.6893, 48.3645, -3.9413, -11.7063, 51.2184, -3.9413, -10.033899, 48.3645, -3.9413, -10.033899, 48.3645, -3.9413, -10.033899, 51.2184, -3.9413, -10.033899, 51.2184, -3.9413, -8.3616, 45.5107, -3.9413, -13.3786, 48.3645, -3.9413, -13.3786, 48.3645, -3.9413, -11.7063, 45.5107, -3.9413, -11.7063, 48.3645, -3.9413, -11.7063, 48.3645, -3.9413, -10.033899, 45.5107, -3.9413, -13.3786, 48.3645, -3.9413, -11.7063, 45.5107, -3.9413, -11.7063, 51.2184, -3.9413, -8.3616, 54.072197, -3.9413, -6.6893, 51.2184, -3.9413, -6.6893, 51.2184, -3.9413, -6.6893, 54.072197, -3.9413, -6.6893, 54.072197, -3.9413, -5.0169997, 48.3645, -3.9413, -10.033899, 51.2184, -3.9413, -8.3616, 48.3645, -3.9413, -8.3616, 48.3645, -3.9413, -8.3616, 51.2184, -3.9413, -8.3616, 51.2184, -3.9413, -6.6893, 45.5107, -3.9413, -11.7063, 48.3645, -3.9413, -10.033899, 45.5107, -3.9413, -10.033899, 45.5107, -3.9413, -10.033899, 48.3645, -3.9413, -10.033899, 48.3645, -3.9413, -8.3616, 51.2184, -3.9413, -6.6893, 54.072197, -3.9413, -5.0169997, 51.2184, -3.9413, -5.0169997, 51.2184, -3.9413, -5.0169997, 54.072197, -3.9413, -5.0169997, 54.072197, -3.9413, -3.3446, 48.3645, -3.9413, -8.3616, 51.2184, -3.9413, -6.6893, 48.3645, -3.9413, -6.6893, 48.3645, -3.9413, -6.6893, 51.2184, -3.9413, -6.6893, 51.2184, -3.9413, -5.0169997, 45.5107, -3.9413, -10.033899, 48.3645, -3.9413, -8.3616, 45.5107, -3.9413, -8.3616, 45.5107, -3.9413, -8.3616, 48.3645, -3.9413, -8.3616, 48.3645, -3.9413, -6.6893, 51.2184, -3.9413, -5.0169997, 54.072197, -3.9413, -3.3446, 51.2184, -3.9413, -3.3446, 51.2184, -3.9413, -3.3446, 54.072197, -3.9413, -3.3446, 54.072197, -3.9413, -1.6723, 48.3645, -3.9413, -6.6893, 51.2184, -3.9413, -5.0169997, 48.3645, -3.9413, -5.0169997, 48.3645, -3.9413, -5.0169997, 51.2184, -3.9413, -5.0169997, 51.2184, -3.9413, -3.3446, 45.5107, -3.9413, -8.3616, 48.3645, -3.9413, -6.6893, 45.5107, -3.9413, -6.6893, 45.5107, -3.9413, -6.6893, 48.3645, -3.9413, -6.6893, 48.3645, -3.9413, -5.0169997, 51.2184, -3.9413, -3.3446, 54.072197, -3.9413, -1.6723, 51.2184, -3.9413, -1.6723, 51.2184, -3.9413, -1.6723, 54.072197, -3.9413, -1.6723, 54.072197, -3.9413, 0, 48.3645, -3.9413, -5.0169997, 51.2184, -3.9413, -3.3446, 48.3645, -3.9413, -3.3446, 48.3645, -3.9413, -3.3446, 51.2184, -3.9413, -3.3446, 51.2184, -3.9413, -1.6723, 45.5107, -3.9413, -6.6893, 48.3645, -3.9413, -5.0169997, 45.5107, -3.9413, -5.0169997, 45.5107, -3.9413, -5.0169997, 48.3645, -3.9413, -5.0169997, 48.3645, -3.9413, -3.3446, 51.2184, -3.9413, -1.6723, 54.072197, -3.9413, 0, 51.2184, -3.9413, 0, 51.2184, -3.9413, 0, 54.072197, -3.9413, 0, 54.072197, -3.9413, 1.6723, 48.3645, -3.9413, -3.3446, 51.2184, -3.9413, -1.6723, 48.3645, -3.9413, -1.6723, 48.3645, -3.9413, -1.6723, 51.2184, -3.9413, -1.6723, 51.2184, -3.9413, 0, 45.5107, -3.9413, -5.0169997, 48.3645, -3.9413, -3.3446, 45.5107, -3.9413, -3.3446, 45.5107, -3.9413, -3.3446, 48.3645, -3.9413, -3.3446, 48.3645, -3.9413, -1.6723, 51.2184, -3.9413, 0, 54.072197, -3.9413, 1.6723, 51.2184, -3.9413, 1.6723, 51.2184, -3.9413, 1.6723, 54.072197, -3.9413, 1.6723, 54.072197, -3.9413, 3.3446, 48.3645, -3.9413, -1.6723, 51.2184, -3.9413, 0, 48.3645, -3.9413, 0, 48.3645, -3.9413, 0, 51.2184, -3.9413, 0, 51.2184, -3.9413, 1.6723, 45.5107, -3.9413, -3.3446, 48.3645, -3.9413, -1.6723, 45.5107, -3.9413, -1.6723, 45.5107, -3.9413, -1.6723, 48.3645, -3.9413, -1.6723, 48.3645, -3.9413, 0, 51.2184, -3.9413, 1.6723, 54.072197, -3.9413, 3.3446, 51.2184, -3.9413, 3.3446, 51.2184, -3.9413, 3.3446, 54.072197, -3.9413, 3.3446, 54.072197, -3.9413, 5.0169997, 48.3645, -3.9413, 0, 51.2184, -3.9413, 1.6723, 48.3645, -3.9413, 1.6723, 48.3645, -3.9413, 1.6723, 51.2184, -3.9413, 1.6723, 51.2184, -3.9413, 3.3446, 45.5107, -3.9413, -1.6723, 48.3645, -3.9413, 0, 45.5107, -3.9413, 0, 45.5107, -3.9413, 0, 48.3645, -3.9413, 0, 48.3645, -3.9413, 1.6723, 51.2184, -3.9413, 3.3446, 54.072197, -3.9413, 5.0169997, 51.2184, -3.9413, 5.0169997, 51.2184, -3.9413, 5.0169997, 54.072197, -3.9413, 5.0169997, 54.072197, -3.9413, 6.6893, 48.3645, -3.9413, 1.6723, 51.2184, -3.9413, 3.3446, 48.3645, -3.9413, 3.3446, 48.3645, -3.9413, 3.3446, 51.2184, -3.9413, 3.3446, 51.2184, -3.9413, 5.0169997, 45.5107, -3.9413, 0, 48.3645, -3.9413, 1.6723, 45.5107, -3.9413, 1.6723, 45.5107, -3.9413, 1.6723, 48.3645, -3.9413, 1.6723, 48.3645, -3.9413, 3.3446, 51.2184, -3.9413, 5.0169997, 54.072197, -3.9413, 6.6893, 51.2184, -3.9413, 6.6893, 51.2184, -3.9413, 6.6893, 54.072197, -3.9413, 6.6893, 54.072197, -3.9413, 8.3616, 48.3645, -3.9413, 3.3446, 51.2184, -3.9413, 5.0169997, 48.3645, -3.9413, 5.0169997, 48.3645, -3.9413, 5.0169997, 51.2184, -3.9413, 5.0169997, 51.2184, -3.9413, 6.6893, 45.5107, -3.9413, 1.6723, 48.3645, -3.9413, 3.3446, 45.5107, -3.9413, 3.3446, 45.5107, -3.9413, 3.3446, 48.3645, -3.9413, 3.3446, 48.3645, -3.9413, 5.0169997, 51.2184, -3.9413, 6.6893, 54.072197, -3.9413, 8.3616, 51.2184, -3.9413, 8.3616, 51.2184, -3.9413, 8.3616, 54.072197, -3.9413, 8.3616, 54.072197, -3.9413, 10.033899, 48.3645, -3.9413, 5.0169997, 51.2184, -3.9413, 6.6893, 48.3645, -3.9413, 6.6893, 48.3645, -3.9413, 6.6893, 51.2184, -3.9413, 6.6893, 51.2184, -3.9413, 8.3616, 45.5107, -3.9413, 3.3446, 48.3645, -3.9413, 5.0169997, 45.5107, -3.9413, 5.0169997, 45.5107, -3.9413, 5.0169997, 48.3645, -3.9413, 5.0169997, 48.3645, -3.9413, 6.6893, 51.2184, -3.9413, 8.3616, 54.072197, -3.9413, 10.033899, 51.2184, -3.9413, 10.033899, 51.2184, -3.9413, 10.033899, 54.072197, -3.9413, 10.033899, 54.072197, -3.9413, 11.7063, 48.3645, -3.9413, 6.6893, 51.2184, -3.9413, 8.3616, 48.3645, -3.9413, 8.3616, 48.3645, -3.9413, 8.3616, 51.2184, -3.9413, 8.3616, 51.2184, -3.9413, 10.033899, 45.5107, -3.9413, 5.0169997, 48.3645, -3.9413, 6.6893, 45.5107, -3.9413, 6.6893, 45.5107, -3.9413, 6.6893, 48.3645, -3.9413, 6.6893, 48.3645, -3.9413, 8.3616, 51.2184, -3.9413, 10.033899, 54.072197, -3.9413, 11.7063, 51.2184, -3.9413, 11.7063, 51.2184, -3.9413, 11.7063, 54.072197, -3.9413, 11.7063, 54.072197, -3.9413, 13.3786, 48.3645, -3.9413, 8.3616, 51.2184, -3.9413, 10.033899, 48.3645, -3.9413, 10.033899, 48.3645, -3.9413, 10.033899, 51.2184, -3.9413, 10.033899, 51.2184, -3.9413, 11.7063, 45.5107, -3.9413, 6.6893, 48.3645, -3.9413, 8.3616, 45.5107, -3.9413, 8.3616, 45.5107, -3.9413, 8.3616, 48.3645, -3.9413, 8.3616, 48.3645, -3.9413, 10.033899, 51.2184, -3.9413, 11.7063, 54.072197, -3.9413, 13.3786, 51.2184, -3.9413, 13.3786, 51.2184, -3.9413, 13.3786, 54.072197, -3.9413, 13.3786, 54.072197, -3.9413, 15.0508995, 48.3645, -3.9413, 10.033899, 51.2184, -3.9413, 11.7063, 48.3645, -3.9413, 11.7063, 48.3645, -3.9413, 11.7063, 51.2184, -3.9413, 11.7063, 51.2184, -3.9413, 13.3786, 45.5107, -3.9413, 8.3616, 48.3645, -3.9413, 10.033899, 45.5107, -3.9413, 10.033899, 45.5107, -3.9413, 10.033899, 48.3645, -3.9413, 10.033899, 48.3645, -3.9413, 11.7063, 51.2184, -3.9413, 13.3786, 54.072197, -3.9413, 15.0508995, 51.2184, -3.9413, 15.0508995, 51.2184, -3.9413, 15.0508995, 54.072197, -3.9413, 15.0508995, 54.072197, -3.9413, 16.7232, 48.3645, -3.9413, 11.7063, 51.2184, -3.9413, 13.3786, 48.3645, -3.9413, 13.3786, 48.3645, -3.9413, 13.3786, 51.2184, -3.9413, 13.3786, 51.2184, -3.9413, 15.0508995, 45.5107, -3.9413, 10.033899, 48.3645, -3.9413, 11.7063, 45.5107, -3.9413, 11.7063, 45.5107, -3.9413, 11.7063, 48.3645, -3.9413, 11.7063, 48.3645, -3.9413, 13.3786, 51.2184, -3.9413, 15.0508995, 54.072197, -3.9413, 16.7232, 51.2184, -3.9413, 16.7232, 51.2184, -3.9413, 16.7232, 54.072197, -3.9413, 16.7232, 54.072197, -3.9413, 18.3955, 48.3645, -3.9413, 13.3786, 51.2184, -3.9413, 15.0508995, 48.3645, -3.9413, 15.0508995, 48.3645, -3.9413, 15.0508995, 51.2184, -3.9413, 15.0508995, 51.2184, -3.9413, 16.7232, 45.5107, -3.9413, 11.7063, 48.3645, -3.9413, 13.3786, 45.5107, -3.9413, 13.3786, 45.5107, -3.9413, 13.3786, 48.3645, -3.9413, 13.3786, 48.3645, -3.9413, 15.0508995, 51.2184, -3.9413, 16.7232, 54.072197, -3.9413, 18.3955, 51.2184, -3.9413, 18.3955, 51.2184, -3.9413, 18.3955, 54.072197, -3.9413, 18.3955, 54.072197, -3.9413, 20.0679, 48.3645, -3.9413, 15.0508995, 51.2184, -3.9413, 16.7232, 48.3645, -3.9413, 16.7232, 48.3645, -3.9413, 16.7232, 51.2184, -3.9413, 16.7232, 51.2184, -3.9413, 18.3955, 45.5107, -3.9413, 13.3786, 48.3645, -3.9413, 15.0508995, 45.5107, -3.9413, 15.0508995, 45.5107, -3.9413, 15.0508995, 48.3645, -3.9413, 15.0508995, 48.3645, -3.9413, 16.7232, 51.2184, -3.9413, 18.3955, 54.072197, -3.9413, 20.0679, 51.2184, -3.9413, 20.0679, 51.2184, -3.9413, 20.0679, 54.072197, -3.9413, 20.0679, 54.072197, -3.9413, 21.7402, 48.3645, -3.9413, 16.7232, 51.2184, -3.9413, 18.3955, 48.3645, -3.9413, 18.3955, 48.3645, -3.9413, 18.3955, 51.2184, -3.9413, 18.3955, 51.2184, -3.9413, 20.0679, 45.5107, -3.9413, 15.0508995, 48.3645, -3.9413, 16.7232, 45.5107, -3.9413, 16.7232, 45.5107, -3.9413, 16.7232, 48.3645, -3.9413, 16.7232, 48.3645, -3.9413, 18.3955, 51.2184, -3.9413, 20.0679, 54.072197, -3.9413, 21.7402, 51.2184, -3.9413, 21.7402, 51.2184, -3.9413, 21.7402, 54.072197, -3.9413, 21.7402, 54.072197, -3.9413, 23.412498, 48.3645, -3.9413, 18.3955, 51.2184, -3.9413, 20.0679, 48.3645, -3.9413, 20.0679, 48.3645, -3.9413, 20.0679, 51.2184, -3.9413, 20.0679, 51.2184, -3.9413, 21.7402, 45.5107, -3.9413, 16.7232, 48.3645, -3.9413, 18.3955, 45.5107, -3.9413, 18.3955, 45.5107, -3.9413, 18.3955, 48.3645, -3.9413, 18.3955, 48.3645, -3.9413, 20.0679, 51.2184, -3.9413, 21.7402, 54.072197, -3.9413, 23.412498, 51.2184, -3.9413, 23.412498, 51.2184, -3.9413, 23.412498, 54.072197, -3.9413, 23.412498, 54.072197, -3.9413, 25.084799, 48.3645, -3.9413, 20.0679, 51.2184, -3.9413, 21.7402, 48.3645, -3.9413, 21.7402, 48.3645, -3.9413, 21.7402, 51.2184, -3.9413, 21.7402, 51.2184, -3.9413, 23.412498, 45.5107, -3.9413, 18.3955, 48.3645, -3.9413, 20.0679, 45.5107, -3.9413, 20.0679, 45.5107, -3.9413, 20.0679, 48.3645, -3.9413, 20.0679, 48.3645, -3.9413, 21.7402, 51.2184, -3.9413, 23.412498, 54.072197, -3.9413, 25.084799, 51.2184, -3.9413, 25.084799, 51.2184, -3.9413, 25.084799, 54.072197, -3.9413, 25.084799, 54.072197, -3.9413, 26.7572, 51.2184, -3.9413, 25.084799, 54.072197, -3.9413, 26.7572, 51.2184, -3.9413, 26.7572, 48.3645, -3.9413, 23.412498, 51.2184, -3.9413, 23.412498, 51.2184, -3.9413, 25.084799, 48.3645, -3.9413, 21.7402, 51.2184, -3.9413, 23.412498, 48.3645, -3.9413, 23.412498, 48.3645, -3.9413, 25.084799, 51.2184, -3.9413, 25.084799, 51.2184, -3.9413, 26.7572, 48.3645, -3.9413, 23.412498, 51.2184, -3.9413, 25.084799, 48.3645, -3.9413, 25.084799, 48.3645, -3.9413, 25.084799, 51.2184, -3.9413, 26.7572, 48.3645, -3.9413, 26.7572, 45.5107, -3.9413, 21.7402, 48.3645, -3.9413, 21.7402, 48.3645, -3.9413, 23.412498, 45.5107, -3.9413, 20.0679, 48.3645, -3.9413, 21.7402, 45.5107, -3.9413, 21.7402, 45.5107, -3.9413, 23.412498, 48.3645, -3.9413, 23.412498, 48.3645, -3.9413, 25.084799, 45.5107, -3.9413, 21.7402, 48.3645, -3.9413, 23.412498, 45.5107, -3.9413, 23.412498, 45.5107, -3.9413, 25.084799, 48.3645, -3.9413, 25.084799, 48.3645, -3.9413, 26.7572, 45.5107, -3.9413, 23.412498, 48.3645, -3.9413, 25.084799, 45.5107, -3.9413, 25.084799, 45.5107, -3.9413, 25.084799, 48.3645, -3.9413, 26.7572, 45.5107, -3.9413, 26.7572, 42.6568, -3.9413, 25.084799, 45.5107, -3.9413, 25.084799, 45.5107, -3.9413, 26.7572, 42.6568, -3.9413, 25.084799, 45.5107, -3.9413, 26.7572, 42.6568, -3.9413, 26.7572, 39.7973, -3.9413, 25.084799, 42.6568, -3.9413, 25.084799, 42.6568, -3.9413, 26.7572, 39.7973, -3.9413, 25.084799, 42.6568, -3.9413, 26.7572, 39.7973, -3.9413, 26.7572, 39.7973, -3.9413, 23.412498, 42.6568, -3.9413, 25.084799, 39.7973, -3.9413, 25.084799, 42.6568, -3.9413, 23.412498, 45.5107, -3.9413, 25.084799, 42.6568, -3.9413, 25.084799, 39.7973, -3.9413, 23.412498, 42.6568, -3.9413, 23.412498, 42.6568, -3.9413, 25.084799, 42.6568, -3.9413, 23.412498, 45.5107, -3.9413, 23.412498, 45.5107, -3.9413, 25.084799, 39.7973, -3.9413, 21.7402, 42.6568, -3.9413, 23.412498, 39.7973, -3.9413, 23.412498, 42.6568, -3.9413, 21.7402, 45.5107, -3.9413, 23.412498, 42.6568, -3.9413, 23.412498, 39.7973, -3.9413, 21.7402, 42.6568, -3.9413, 21.7402, 42.6568, -3.9413, 23.412498, 42.6568, -3.9413, 21.7402, 45.5107, -3.9413, 21.7402, 45.5107, -3.9413, 23.412498, 39.7973, -3.9413, 20.0679, 42.6568, -3.9413, 21.7402, 39.7973, -3.9413, 21.7402, 42.6568, -3.9413, 20.0679, 45.5107, -3.9413, 21.7402, 42.6568, -3.9413, 21.7402, 39.7973, -3.9413, 20.0679, 42.6568, -3.9413, 20.0679, 42.6568, -3.9413, 21.7402, 42.6568, -3.9413, 20.0679, 45.5107, -3.9413, 20.0679, 45.5107, -3.9413, 21.7402, 39.7973, -3.9413, 18.3955, 42.6568, -3.9413, 20.0679, 39.7973, -3.9413, 20.0679, 42.6568, -3.9413, 18.3955, 45.5107, -3.9413, 20.0679, 42.6568, -3.9413, 20.0679, 39.7973, -3.9413, 18.3955, 42.6568, -3.9413, 18.3955, 42.6568, -3.9413, 20.0679, 42.6568, -3.9413, 18.3955, 45.5107, -3.9413, 18.3955, 45.5107, -3.9413, 20.0679, 39.7973, -3.9413, 16.7232, 42.6568, -3.9413, 18.3955, 39.7973, -3.9413, 18.3955, 42.6568, -3.9413, 16.7232, 45.5107, -3.9413, 18.3955, 42.6568, -3.9413, 18.3955, 39.7973, -3.9413, 16.7232, 42.6568, -3.9413, 16.7232, 42.6568, -3.9413, 18.3955, 42.6568, -3.9413, 16.7232, 45.5107, -3.9413, 16.7232, 45.5107, -3.9413, 18.3955, 39.7973, -3.9413, 15.0508995, 42.6568, -3.9413, 16.7232, 39.7973, -3.9413, 16.7232, 42.6568, -3.9413, 15.0508995, 45.5107, -3.9413, 16.7232, 42.6568, -3.9413, 16.7232, 39.7973, -3.9413, 15.0508995, 42.6568, -3.9413, 15.0508995, 42.6568, -3.9413, 16.7232, 42.6568, -3.9413, 15.0508995, 45.5107, -3.9413, 15.0508995, 45.5107, -3.9413, 16.7232, 39.7973, -3.9413, 13.3786, 42.6568, -3.9413, 15.0508995, 39.7973, -3.9413, 15.0508995, 42.6568, -3.9413, 13.3786, 45.5107, -3.9413, 15.0508995, 42.6568, -3.9413, 15.0508995, 39.7973, -3.9413, 13.3786, 42.6568, -3.9413, 13.3786, 42.6568, -3.9413, 15.0508995, 42.6568, -3.9413, 13.3786, 45.5107, -3.9413, 13.3786, 45.5107, -3.9413, 15.0508995, 39.7973, -3.9413, 11.7063, 42.6568, -3.9413, 13.3786, 39.7973, -3.9413, 13.3786, 42.6568, -3.9413, 11.7063, 45.5107, -3.9413, 13.3786, 42.6568, -3.9413, 13.3786, 39.7973, -3.9413, 11.7063, 42.6568, -3.9413, 11.7063, 42.6568, -3.9413, 13.3786, 42.6568, -3.9413, 11.7063, 45.5107, -3.9413, 11.7063, 45.5107, -3.9413, 13.3786, 39.7973, -3.9413, 10.033899, 42.6568, -3.9413, 11.7063, 39.7973, -3.9413, 11.7063, 42.6568, -3.9413, 10.033899, 45.5107, -3.9413, 11.7063, 42.6568, -3.9413, 11.7063, 39.7973, -3.9413, 10.033899, 42.6568, -3.9413, 10.033899, 42.6568, -3.9413, 11.7063, 42.6568, -3.9413, 10.033899, 45.5107, -3.9413, 10.033899, 45.5107, -3.9413, 11.7063, 39.7973, -3.9413, 8.3616, 42.6568, -3.9413, 10.033899, 39.7973, -3.9413, 10.033899, 42.6568, -3.9413, 8.3616, 45.5107, -3.9413, 10.033899, 42.6568, -3.9413, 10.033899, 39.7973, -3.9413, 8.3616, 42.6568, -3.9413, 8.3616, 42.6568, -3.9413, 10.033899, 42.6568, -3.9413, 8.3616, 45.5107, -3.9413, 8.3616, 45.5107, -3.9413, 10.033899, 39.7973, -3.9413, 6.6893, 42.6568, -3.9413, 8.3616, 39.7973, -3.9413, 8.3616, 42.6568, -3.9413, 6.6893, 45.5107, -3.9413, 8.3616, 42.6568, -3.9413, 8.3616, 39.7973, -3.9413, 6.6893, 42.6568, -3.9413, 6.6893, 42.6568, -3.9413, 8.3616, 42.6568, -3.9413, 6.6893, 45.5107, -3.9413, 6.6893, 45.5107, -3.9413, 8.3616, 39.7973, -3.9413, 5.0169997, 42.6568, -3.9413, 6.6893, 39.7973, -3.9413, 6.6893, 42.6568, -3.9413, 5.0169997, 45.5107, -3.9413, 6.6893, 42.6568, -3.9413, 6.6893, 39.7973, -3.9413, 5.0169997, 42.6568, -3.9413, 5.0169997, 42.6568, -3.9413, 6.6893, 42.6568, -3.9413, 5.0169997, 45.5107, -3.9413, 5.0169997, 45.5107, -3.9413, 6.6893, 39.7973, -3.9413, 3.3446, 42.6568, -3.9413, 5.0169997, 39.7973, -3.9413, 5.0169997, 42.6568, -3.9413, 3.3446, 45.5107, -3.9413, 5.0169997, 42.6568, -3.9413, 5.0169997, 39.7973, -3.9413, 3.3446, 42.6568, -3.9413, 3.3446, 42.6568, -3.9413, 5.0169997, 42.6568, -3.9413, 3.3446, 45.5107, -3.9413, 3.3446, 45.5107, -3.9413, 5.0169997, 39.7973, -3.9413, 1.6723, 42.6568, -3.9413, 3.3446, 39.7973, -3.9413, 3.3446, 42.6568, -3.9413, 1.6723, 45.5107, -3.9413, 3.3446, 42.6568, -3.9413, 3.3446, 39.7973, -3.9413, 1.6723, 42.6568, -3.9413, 1.6723, 42.6568, -3.9413, 3.3446, 42.6568, -3.9413, 1.6723, 45.5107, -3.9413, 1.6723, 45.5107, -3.9413, 3.3446, 39.7973, -3.9413, 0, 42.6568, -3.9413, 1.6723, 39.7973, -3.9413, 1.6723, 42.6568, -3.9413, 0, 45.5107, -3.9413, 1.6723, 42.6568, -3.9413, 1.6723, 39.7973, -3.9413, 0, 42.6568, -3.9413, 0, 42.6568, -3.9413, 1.6723, 42.6568, -3.9413, 0, 45.5107, -3.9413, 0, 45.5107, -3.9413, 1.6723, 39.7973, -3.9413, -1.6723, 42.6568, -3.9413, 0, 39.7973, -3.9413, 0, 42.6568, -3.9413, -1.6723, 45.5107, -3.9413, 0, 42.6568, -3.9413, 0, 39.7973, -3.9413, -1.6723, 42.6568, -3.9413, -1.6723, 42.6568, -3.9413, 0, 42.6568, -3.9413, -1.6723, 45.5107, -3.9413, -1.6723, 45.5107, -3.9413, 0, 39.7973, -3.9413, -3.3446, 42.6568, -3.9413, -1.6723, 39.7973, -3.9413, -1.6723, 42.6568, -3.9413, -3.3446, 45.5107, -3.9413, -1.6723, 42.6568, -3.9413, -1.6723, 39.7973, -3.9413, -3.3446, 42.6568, -3.9413, -3.3446, 42.6568, -3.9413, -1.6723, 42.6568, -3.9413, -3.3446, 45.5107, -3.9413, -3.3446, 45.5107, -3.9413, -1.6723, 39.7973, -3.9413, -5.0169997, 42.6568, -3.9413, -3.3446, 39.7973, -3.9413, -3.3446, 42.6568, -3.9413, -5.0169997, 45.5107, -3.9413, -3.3446, 42.6568, -3.9413, -3.3446, 39.7973, -3.9413, -5.0169997, 42.6568, -3.9413, -5.0169997, 42.6568, -3.9413, -3.3446, 42.6568, -3.9413, -5.0169997, 45.5107, -3.9413, -5.0169997, 45.5107, -3.9413, -3.3446, 39.7973, -3.9413, -6.6893, 42.6568, -3.9413, -5.0169997, 39.7973, -3.9413, -5.0169997, 42.6568, -3.9413, -6.6893, 45.5107, -3.9413, -5.0169997, 42.6568, -3.9413, -5.0169997, 39.7973, -3.9413, -6.6893, 42.6568, -3.9413, -6.6893, 42.6568, -3.9413, -5.0169997, 42.6568, -3.9413, -6.6893, 45.5107, -3.9413, -6.6893, 45.5107, -3.9413, -5.0169997, 39.7973, -3.9413, -8.3616, 42.6568, -3.9413, -6.6893, 39.7973, -3.9413, -6.6893, 42.6568, -3.9413, -8.3616, 45.5107, -3.9413, -6.6893, 42.6568, -3.9413, -6.6893, 39.7973, -3.9413, -8.3616, 42.6568, -3.9413, -8.3616, 42.6568, -3.9413, -6.6893, 42.6568, -3.9413, -8.3616, 45.5107, -3.9413, -8.3616, 45.5107, -3.9413, -6.6893, 39.7973, -3.9413, -10.033899, 42.6568, -3.9413, -8.3616, 39.7973, -3.9413, -8.3616, 42.6568, -3.9413, -10.033899, 45.5107, -3.9413, -8.3616, 42.6568, -3.9413, -8.3616, 39.7973, -3.9413, -10.033899, 42.6568, -3.9413, -10.033899, 42.6568, -3.9413, -8.3616, 42.6568, -3.9413, -10.033899, 45.5107, -3.9413, -10.033899, 45.5107, -3.9413, -8.3616, 39.7973, -3.9413, -11.7063, 42.6568, -3.9413, -10.033899, 39.7973, -3.9413, -10.033899, 42.6568, -3.9413, -11.7063, 45.5107, -3.9413, -10.033899, 42.6568, -3.9413, -10.033899, 39.7973, -3.9413, -11.7063, 42.6568, -3.9413, -11.7063, 42.6568, -3.9413, -10.033899, 42.6568, -3.9413, -11.7063, 45.5107, -3.9413, -11.7063, 45.5107, -3.9413, -10.033899, 39.7973, -3.9413, -13.3786, 42.6568, -3.9413, -11.7063, 39.7973, -3.9413, -11.7063, 42.6568, -3.9413, -13.3786, 45.5107, -3.9413, -11.7063, 42.6568, -3.9413, -11.7063, 39.7973, -3.9413, -13.3786, 42.6568, -3.9413, -13.3786, 42.6568, -3.9413, -11.7063, 42.6568, -3.9413, -13.3786, 45.5107, -3.9413, -13.3786, 45.5107, -3.9413, -11.7063, 39.7973, -3.9413, -15.0508995, 42.6568, -3.9413, -13.3786, 39.7973, -3.9413, -13.3786, 39.7973, -3.9413, -15.0508995, 42.6568, -3.9413, -15.0508995, 42.6568, -3.9413, -13.3786, 42.6568, -3.9413, -15.0508995, 45.5107, -3.9413, -13.3786, 42.6568, -3.9413, -13.3786, 39.7973, -3.9413, -16.7232, 42.6568, -3.9413, -15.0508995, 39.7973, -3.9413, -15.0508995, 42.6568, -3.9413, -15.0508995, 45.5107, -3.9413, -15.0508995, 45.5107, -3.9413, -13.3786, 45.5107, -3.9413, -15.0508995, 48.3645, -3.9413, -13.3786, 45.5107, -3.9413, -13.3786, 39.7973, -3.9413, -16.7232, 42.6568, -3.9413, -16.7232, 42.6568, -3.9413, -15.0508995, 42.6568, -3.9413, -16.7232, 45.5107, -3.9413, -15.0508995, 42.6568, -3.9413, -15.0508995, 39.7973, -3.9413, -18.3955, 42.6568, -3.9413, -16.7232, 39.7973, -3.9413, -16.7232, 45.5107, -3.9413, -15.0508995, 48.3645, -3.9413, -15.0508995, 48.3645, -3.9413, -13.3786, 39.7973, -3.9413, -18.3955, 42.6568, -3.9413, -18.3955, 42.6568, -3.9413, -16.7232, 42.6568, -3.9413, -16.7232, 45.5107, -3.9413, -16.7232, 45.5107, -3.9413, -15.0508995, 45.5107, -3.9413, -16.7232, 48.3645, -3.9413, -15.0508995, 45.5107, -3.9413, -15.0508995, 42.6568, -3.9413, -18.3955, 45.5107, -3.9413, -16.7232, 42.6568, -3.9413, -16.7232, 39.7973, -3.9413, -20.0679, 42.6568, -3.9413, -18.3955, 39.7973, -3.9413, -18.3955, 45.5107, -3.9413, -16.7232, 48.3645, -3.9413, -16.7232, 48.3645, -3.9413, -15.0508995, 48.3645, -3.9413, -16.7232, 51.2184, -3.9413, -15.0508995, 48.3645, -3.9413, -15.0508995, 42.6568, -3.9413, -18.3955, 45.5107, -3.9413, -18.3955, 45.5107, -3.9413, -16.7232, 45.5107, -3.9413, -18.3955, 48.3645, -3.9413, -16.7232, 45.5107, -3.9413, -16.7232, 39.7973, -3.9413, -20.0679, 42.6568, -3.9413, -20.0679, 42.6568, -3.9413, -18.3955, 42.6568, -3.9413, -20.0679, 45.5107, -3.9413, -18.3955, 42.6568, -3.9413, -18.3955, 39.7973, -3.9413, -21.7402, 42.6568, -3.9413, -20.0679, 39.7973, -3.9413, -20.0679, 48.3645, -3.9413, -16.7232, 51.2184, -3.9413, -16.7232, 51.2184, -3.9413, -15.0508995, 51.2184, -3.9413, -16.7232, 54.072197, -3.9413, -15.0508995, 51.2184, -3.9413, -15.0508995, 51.2184, -3.9413, -16.7232, 54.072197, -3.9413, -16.7232, 54.072197, -3.9413, -15.0508995, 48.3645, -3.9413, -18.3955, 51.2184, -3.9413, -16.7232, 48.3645, -3.9413, -16.7232, 45.5107, -3.9413, -18.3955, 48.3645, -3.9413, -18.3955, 48.3645, -3.9413, -16.7232, 51.2184, -3.9413, -18.3955, 54.072197, -3.9413, -16.7232, 51.2184, -3.9413, -16.7232, 48.3645, -3.9413, -18.3955, 51.2184, -3.9413, -18.3955, 51.2184, -3.9413, -16.7232, 51.2184, -3.9413, -18.3955, 54.072197, -3.9413, -18.3955, 54.072197, -3.9413, -16.7232, 45.5107, -3.9413, -20.0679, 48.3645, -3.9413, -18.3955, 45.5107, -3.9413, -18.3955, 42.6568, -3.9413, -20.0679, 45.5107, -3.9413, -20.0679, 45.5107, -3.9413, -18.3955, 48.3645, -3.9413, -20.0679, 51.2184, -3.9413, -18.3955, 48.3645, -3.9413, -18.3955, 45.5107, -3.9413, -20.0679, 48.3645, -3.9413, -20.0679, 48.3645, -3.9413, -18.3955, 51.2184, -3.9413, -20.0679, 54.072197, -3.9413, -18.3955, 51.2184, -3.9413, -18.3955, 48.3645, -3.9413, -20.0679, 51.2184, -3.9413, -20.0679, 51.2184, -3.9413, -18.3955, 51.2184, -3.9413, -20.0679, 54.072197, -3.9413, -20.0679, 54.072197, -3.9413, -18.3955, 39.7973, -3.9413, -21.7402, 42.6568, -3.9413, -21.7402, 42.6568, -3.9413, -20.0679, 42.6568, -3.9413, -21.7402, 45.5107, -3.9413, -20.0679, 42.6568, -3.9413, -20.0679, 39.7973, -3.9413, -23.412498, 42.6568, -3.9413, -21.7402, 39.7973, -3.9413, -21.7402, 45.5107, -3.9413, -21.7402, 48.3645, -3.9413, -20.0679, 45.5107, -3.9413, -20.0679, 42.6568, -3.9413, -21.7402, 45.5107, -3.9413, -21.7402, 45.5107, -3.9413, -20.0679, 39.7973, -3.9413, -23.412498, 42.6568, -3.9413, -23.412498, 42.6568, -3.9413, -21.7402, 42.6568, -3.9413, -23.412498, 45.5107, -3.9413, -21.7402, 42.6568, -3.9413, -21.7402, 39.7973, -3.9413, -25.084799, 42.6568, -3.9413, -23.412498, 39.7973, -3.9413, -23.412498, 45.5107, -3.9413, -21.7402, 48.3645, -3.9413, -21.7402, 48.3645, -3.9413, -20.0679, 48.3645, -3.9413, -21.7402, 51.2184, -3.9413, -20.0679, 48.3645, -3.9413, -20.0679, 42.6568, -3.9413, -23.412498, 45.5107, -3.9413, -23.412498, 45.5107, -3.9413, -21.7402, 45.5107, -3.9413, -23.412498, 48.3645, -3.9413, -21.7402, 45.5107, -3.9413, -21.7402, 39.7973, -3.9413, -25.084799, 42.6568, -3.9413, -25.084799, 42.6568, -3.9413, -23.412498, 42.6568, -3.9413, -25.084799, 45.5107, -3.9413, -23.412498, 42.6568, -3.9413, -23.412498, 39.7973, -3.9413, -26.7572, 42.6568, -3.9413, -25.084799, 39.7973, -3.9413, -25.084799, 39.7973, -3.9413, -26.7572, 42.6568, -3.9413, -26.7572, 42.6568, -3.9413, -25.084799, 42.6568, -3.9413, -26.7572, 45.5107, -3.9413, -25.084799, 42.6568, -3.9413, -25.084799, 42.6568, -3.9413, -25.084799, 45.5107, -3.9413, -25.084799, 45.5107, -3.9413, -23.412498, 42.6568, -3.9413, -26.7572, 45.5107, -3.9413, -26.7572, 45.5107, -3.9413, -25.084799, 45.5107, -3.9413, -23.412498, 48.3645, -3.9413, -23.412498, 48.3645, -3.9413, -21.7402, 45.5107, -3.9413, -25.084799, 48.3645, -3.9413, -23.412498, 45.5107, -3.9413, -23.412498, 45.5107, -3.9413, -26.7572, 48.3645, -3.9413, -25.084799, 45.5107, -3.9413, -25.084799, 45.5107, -3.9413, -25.084799, 48.3645, -3.9413, -25.084799, 48.3645, -3.9413, -23.412498, 45.5107, -3.9413, -26.7572, 48.3645, -3.9413, -26.7572, 48.3645, -3.9413, -25.084799, 48.3645, -3.9413, -21.7402, 51.2184, -3.9413, -21.7402, 51.2184, -3.9413, -20.0679, 48.3645, -3.9413, -23.412498, 51.2184, -3.9413, -21.7402, 48.3645, -3.9413, -21.7402, 51.2184, -3.9413, -21.7402, 54.072197, -3.9413, -20.0679, 51.2184, -3.9413, -20.0679, 51.2184, -3.9413, -21.7402, 54.072197, -3.9413, -21.7402, 54.072197, -3.9413, -20.0679, 48.3645, -3.9413, -23.412498, 51.2184, -3.9413, -23.412498, 51.2184, -3.9413, -21.7402, 51.2184, -3.9413, -23.412498, 54.072197, -3.9413, -21.7402, 51.2184, -3.9413, -21.7402, 48.3645, -3.9413, -25.084799, 51.2184, -3.9413, -23.412498, 48.3645, -3.9413, -23.412498, 51.2184, -3.9413, -23.412498, 54.072197, -3.9413, -23.412498, 54.072197, -3.9413, -21.7402, 48.3645, -3.9413, -26.7572, 51.2184, -3.9413, -25.084799, 48.3645, -3.9413, -25.084799, 48.3645, -3.9413, -25.084799, 51.2184, -3.9413, -25.084799, 51.2184, -3.9413, -23.412498, 51.2184, -3.9413, -25.084799, 54.072197, -3.9413, -23.412498, 51.2184, -3.9413, -23.412498, 48.3645, -3.9413, -26.7572, 51.2184, -3.9413, -26.7572, 51.2184, -3.9413, -25.084799, 51.2184, -3.9413, -25.084799, 54.072197, -3.9413, -25.084799, 54.072197, -3.9413, -23.412498, 51.2184, -3.9413, -26.7572, 54.072197, -3.9413, -25.084799, 51.2184, -3.9413, -25.084799, 51.2184, -3.9413, -26.7572, 54.072197, -3.9413, -26.7572, 54.072197, -3.9413, -25.084799, 29.679499, -0.6044, -15.0508995, 32.4649, -1.1213, -13.3786, 29.679499, -0.6044, -13.3786, 29.679499, -0.6044, -13.3786, 32.4649, -1.1213, -13.3786, 32.4649, -1.1213, -11.7063, 29.679499, -0.6044, -15.0508995, 32.4649, -1.1213, -15.0508995, 32.4649, -1.1213, -13.3786, 29.679499, -0.6044, -13.3786, 32.4649, -1.1213, -11.7063, 29.679499, -0.6044, -11.7063, 29.679499, -0.6044, -16.7232, 32.4649, -1.1213, -15.0508995, 29.679499, -0.6044, -15.0508995, 29.679499, -0.6044, -11.7063, 32.4649, -1.1213, -11.7063, 32.4649, -1.1213, -10.033899, 29.679499, -0.6044, -16.7232, 32.4649, -1.1213, -16.7232, 32.4649, -1.1213, -15.0508995, 29.679499, -0.6044, -11.7063, 32.4649, -1.1213, -10.033899, 29.679499, -0.6044, -10.033899, 29.679499, -0.6044, -18.3955, 32.4649, -1.1213, -16.7232, 29.679499, -0.6044, -16.7232, 29.679499, -0.6044, -10.033899, 32.4649, -1.1213, -10.033899, 32.4649, -1.1213, -8.3616, 29.679499, -0.6044, -18.3955, 32.4649, -1.1213, -18.3955, 32.4649, -1.1213, -16.7232, 29.679499, -0.6044, -10.033899, 32.4649, -1.1213, -8.3616, 29.679499, -0.6044, -8.3616, 29.679499, -0.6044, -20.0679, 32.4649, -1.1213, -18.3955, 29.679499, -0.6044, -18.3955, 29.679499, -0.6044, -8.3616, 32.4649, -1.1213, -8.3616, 32.4649, -1.1213, -6.6893, 29.679499, -0.6044, -20.0679, 32.4649, -1.1213, -20.0679, 32.4649, -1.1213, -18.3955, 29.679499, -0.6044, -8.3616, 32.4649, -1.1213, -6.6893, 29.679499, -0.6044, -6.6893, 29.679499, -0.6044, -21.7402, 32.4649, -1.1213, -20.0679, 29.679499, -0.6044, -20.0679, 29.679499, -0.6044, -6.6893, 32.4649, -1.1213, -6.6893, 32.4649, -1.1213, -5.0169997, 29.679499, -0.6044, -21.7402, 32.4649, -1.1213, -21.7402, 32.4649, -1.1213, -20.0679, 29.679499, -0.6044, -6.6893, 32.4649, -1.1213, -5.0169997, 29.679499, -0.6044, -5.0169997, 29.679499, -0.6044, -23.412498, 32.4649, -1.1213, -21.7402, 29.679499, -0.6044, -21.7402, 29.679499, -0.6044, -5.0169997, 32.4649, -1.1213, -5.0169997, 32.4649, -1.1213, -3.3446, 29.679499, -0.6044, -23.412498, 32.4649, -1.1213, -23.412498, 32.4649, -1.1213, -21.7402, 29.679499, -0.6044, -5.0169997, 32.4649, -1.1213, -3.3446, 29.679499, -0.6044, -3.3446, 29.679499, -0.6044, -25.084799, 32.4649, -1.1213, -23.412498, 29.679499, -0.6044, -23.412498, 29.679499, -0.6044, -3.3446, 32.4649, -1.1213, -3.3446, 32.4649, -1.1213, -1.6723, 29.679499, -0.6044, -25.084799, 32.4649, -1.1213, -25.084799, 32.4649, -1.1213, -23.412498, 29.679499, -0.6044, -26.7572, 32.4649, -1.1213, -25.084799, 29.679499, -0.6044, -25.084799, 29.679499, -0.6044, -26.7572, 32.4649, -1.1213, -26.7572, 32.4649, -1.1213, -25.084799, 29.679499, -0.6044, -3.3446, 32.4649, -1.1213, -1.6723, 29.679499, -0.6044, -1.6723, 29.679499, -0.6044, -1.6723, 32.4649, -1.1213, -1.6723, 32.4649, -1.1213, 0, 29.679499, -0.6044, -1.6723, 32.4649, -1.1213, 0, 29.679499, -0.6044, 0, 29.679499, -0.6044, 0, 32.4649, -1.1213, 0, 32.4649, -1.1213, 1.6723, 29.679499, -0.6044, 0, 32.4649, -1.1213, 1.6723, 29.679499, -0.6044, 1.6723, 29.679499, -0.6044, 1.6723, 32.4649, -1.1213, 1.6723, 32.4649, -1.1213, 3.3446, 29.679499, -0.6044, 1.6723, 32.4649, -1.1213, 3.3446, 29.679499, -0.6044, 3.3446, 29.679499, -0.6044, 3.3446, 32.4649, -1.1213, 3.3446, 32.4649, -1.1213, 5.0169997, 29.679499, -0.6044, 3.3446, 32.4649, -1.1213, 5.0169997, 29.679499, -0.6044, 5.0169997, 29.679499, -0.6044, 5.0169997, 32.4649, -1.1213, 5.0169997, 32.4649, -1.1213, 6.6893, 29.679499, -0.6044, 5.0169997, 32.4649, -1.1213, 6.6893, 29.679499, -0.6044, 6.6893, 29.679499, -0.6044, 6.6893, 32.4649, -1.1213, 6.6893, 32.4649, -1.1213, 8.3616, 29.679499, -0.6044, 6.6893, 32.4649, -1.1213, 8.3616, 29.679499, -0.6044, 8.3616, 29.679499, -0.6044, 8.3616, 32.4649, -1.1213, 8.3616, 32.4649, -1.1213, 10.033899, 29.679499, -0.6044, 8.3616, 32.4649, -1.1213, 10.033899, 29.679499, -0.6044, 10.033899, 29.679499, -0.6044, 10.033899, 32.4649, -1.1213, 10.033899, 32.4649, -1.1213, 11.7063, 29.679499, -0.6044, 10.033899, 32.4649, -1.1213, 11.7063, 29.679499, -0.6044, 11.7063, 29.679499, -0.6044, 11.7063, 32.4649, -1.1213, 11.7063, 32.4649, -1.1213, 13.3786, 29.679499, -0.6044, 11.7063, 32.4649, -1.1213, 13.3786, 29.679499, -0.6044, 13.3786, 29.679499, -0.6044, 13.3786, 32.4649, -1.1213, 13.3786, 32.4649, -1.1213, 15.0508995, 29.679499, -0.6044, 13.3786, 32.4649, -1.1213, 15.0508995, 29.679499, -0.6044, 15.0508995, 29.679499, -0.6044, 15.0508995, 32.4649, -1.1213, 15.0508995, 32.4649, -1.1213, 16.7232, 29.679499, -0.6044, 15.0508995, 32.4649, -1.1213, 16.7232, 29.679499, -0.6044, 16.7232, 29.679499, -0.6044, 16.7232, 32.4649, -1.1213, 16.7232, 32.4649, -1.1213, 18.3955, 29.679499, -0.6044, 16.7232, 32.4649, -1.1213, 18.3955, 29.679499, -0.6044, 18.3955, 29.679499, -0.6044, 18.3955, 32.4649, -1.1213, 18.3955, 32.4649, -1.1213, 20.0679, 29.679499, -0.6044, 18.3955, 32.4649, -1.1213, 20.0679, 29.679499, -0.6044, 20.0679, 29.679499, -0.6044, 20.0679, 32.4649, -1.1213, 20.0679, 32.4649, -1.1213, 21.7402, 29.679499, -0.6044, 20.0679, 32.4649, -1.1213, 21.7402, 29.679499, -0.6044, 21.7402, 29.679499, -0.6044, 21.7402, 32.4649, -1.1213, 21.7402, 32.4649, -1.1213, 23.412498, 29.679499, -0.6044, 21.7402, 32.4649, -1.1213, 23.412498, 29.679499, -0.6044, 23.412498, 29.679499, -0.6044, 23.412498, 32.4649, -1.1213, 23.412498, 32.4649, -1.1213, 25.084799, 29.679499, -0.6044, 23.412498, 32.4649, -1.1213, 25.084799, 29.679499, -0.6044, 25.084799, 29.679499, -0.6044, 25.084799, 32.4649, -1.1213, 25.084799, 32.4649, -1.1213, 26.7572, 29.679499, -0.6044, 25.084799, 32.4649, -1.1213, 26.7572, 29.679499, -0.6044, 26.7572, 32.4649, -1.1213, -15.0508995, 39.7973, -3.9413, -13.3786, 32.4649, -1.1213, -13.3786, 32.4649, -1.1213, -13.3786, 39.7973, -3.9413, -13.3786, 39.7973, -3.9413, -11.7063, 32.4649, -1.1213, -15.0508995, 39.7973, -3.9413, -15.0508995, 39.7973, -3.9413, -13.3786, 32.4649, -1.1213, -13.3786, 39.7973, -3.9413, -11.7063, 32.4649, -1.1213, -11.7063, 32.4649, -1.1213, -16.7232, 39.7973, -3.9413, -15.0508995, 32.4649, -1.1213, -15.0508995, 32.4649, -1.1213, -11.7063, 39.7973, -3.9413, -11.7063, 39.7973, -3.9413, -10.033899, 32.4649, -1.1213, -16.7232, 39.7973, -3.9413, -16.7232, 39.7973, -3.9413, -15.0508995, 32.4649, -1.1213, -11.7063, 39.7973, -3.9413, -10.033899, 32.4649, -1.1213, -10.033899, 32.4649, -1.1213, -18.3955, 39.7973, -3.9413, -16.7232, 32.4649, -1.1213, -16.7232, 32.4649, -1.1213, -10.033899, 39.7973, -3.9413, -10.033899, 39.7973, -3.9413, -8.3616, 32.4649, -1.1213, -18.3955, 39.7973, -3.9413, -18.3955, 39.7973, -3.9413, -16.7232, 32.4649, -1.1213, -10.033899, 39.7973, -3.9413, -8.3616, 32.4649, -1.1213, -8.3616, 32.4649, -1.1213, -20.0679, 39.7973, -3.9413, -18.3955, 32.4649, -1.1213, -18.3955, 32.4649, -1.1213, -8.3616, 39.7973, -3.9413, -8.3616, 39.7973, -3.9413, -6.6893, 32.4649, -1.1213, -20.0679, 39.7973, -3.9413, -20.0679, 39.7973, -3.9413, -18.3955, 32.4649, -1.1213, -8.3616, 39.7973, -3.9413, -6.6893, 32.4649, -1.1213, -6.6893, 32.4649, -1.1213, -21.7402, 39.7973, -3.9413, -20.0679, 32.4649, -1.1213, -20.0679, 32.4649, -1.1213, -6.6893, 39.7973, -3.9413, -6.6893, 39.7973, -3.9413, -5.0169997, 32.4649, -1.1213, -21.7402, 39.7973, -3.9413, -21.7402, 39.7973, -3.9413, -20.0679, 32.4649, -1.1213, -6.6893, 39.7973, -3.9413, -5.0169997, 32.4649, -1.1213, -5.0169997, 32.4649, -1.1213, -23.412498, 39.7973, -3.9413, -21.7402, 32.4649, -1.1213, -21.7402, 32.4649, -1.1213, -5.0169997, 39.7973, -3.9413, -5.0169997, 39.7973, -3.9413, -3.3446, 32.4649, -1.1213, -23.412498, 39.7973, -3.9413, -23.412498, 39.7973, -3.9413, -21.7402, 32.4649, -1.1213, -5.0169997, 39.7973, -3.9413, -3.3446, 32.4649, -1.1213, -3.3446, 32.4649, -1.1213, -25.084799, 39.7973, -3.9413, -23.412498, 32.4649, -1.1213, -23.412498, 32.4649, -1.1213, -3.3446, 39.7973, -3.9413, -3.3446, 39.7973, -3.9413, -1.6723, 32.4649, -1.1213, -25.084799, 39.7973, -3.9413, -25.084799, 39.7973, -3.9413, -23.412498, 32.4649, -1.1213, -26.7572, 39.7973, -3.9413, -25.084799, 32.4649, -1.1213, -25.084799, 32.4649, -1.1213, -26.7572, 39.7973, -3.9413, -26.7572, 39.7973, -3.9413, -25.084799, 32.4649, -1.1213, -3.3446, 39.7973, -3.9413, -1.6723, 32.4649, -1.1213, -1.6723, 32.4649, -1.1213, -1.6723, 39.7973, -3.9413, -1.6723, 39.7973, -3.9413, 0, 32.4649, -1.1213, -1.6723, 39.7973, -3.9413, 0, 32.4649, -1.1213, 0, 32.4649, -1.1213, 0, 39.7973, -3.9413, 0, 39.7973, -3.9413, 1.6723, 32.4649, -1.1213, 0, 39.7973, -3.9413, 1.6723, 32.4649, -1.1213, 1.6723, 32.4649, -1.1213, 1.6723, 39.7973, -3.9413, 1.6723, 39.7973, -3.9413, 3.3446, 32.4649, -1.1213, 1.6723, 39.7973, -3.9413, 3.3446, 32.4649, -1.1213, 3.3446, 32.4649, -1.1213, 3.3446, 39.7973, -3.9413, 3.3446, 39.7973, -3.9413, 5.0169997, 32.4649, -1.1213, 3.3446, 39.7973, -3.9413, 5.0169997, 32.4649, -1.1213, 5.0169997, 32.4649, -1.1213, 5.0169997, 39.7973, -3.9413, 5.0169997, 39.7973, -3.9413, 6.6893, 32.4649, -1.1213, 5.0169997, 39.7973, -3.9413, 6.6893, 32.4649, -1.1213, 6.6893, 32.4649, -1.1213, 6.6893, 39.7973, -3.9413, 6.6893, 39.7973, -3.9413, 8.3616, 32.4649, -1.1213, 6.6893, 39.7973, -3.9413, 8.3616, 32.4649, -1.1213, 8.3616, 32.4649, -1.1213, 8.3616, 39.7973, -3.9413, 8.3616, 39.7973, -3.9413, 10.033899, 32.4649, -1.1213, 8.3616, 39.7973, -3.9413, 10.033899, 32.4649, -1.1213, 10.033899, 32.4649, -1.1213, 10.033899, 39.7973, -3.9413, 10.033899, 39.7973, -3.9413, 11.7063, 32.4649, -1.1213, 10.033899, 39.7973, -3.9413, 11.7063, 32.4649, -1.1213, 11.7063, 32.4649, -1.1213, 11.7063, 39.7973, -3.9413, 11.7063, 39.7973, -3.9413, 13.3786, 32.4649, -1.1213, 11.7063, 39.7973, -3.9413, 13.3786, 32.4649, -1.1213, 13.3786, 32.4649, -1.1213, 13.3786, 39.7973, -3.9413, 13.3786, 39.7973, -3.9413, 15.0508995, 32.4649, -1.1213, 13.3786, 39.7973, -3.9413, 15.0508995, 32.4649, -1.1213, 15.0508995, 32.4649, -1.1213, 15.0508995, 39.7973, -3.9413, 15.0508995, 39.7973, -3.9413, 16.7232, 32.4649, -1.1213, 15.0508995, 39.7973, -3.9413, 16.7232, 32.4649, -1.1213, 16.7232, 32.4649, -1.1213, 16.7232, 39.7973, -3.9413, 16.7232, 39.7973, -3.9413, 18.3955, 32.4649, -1.1213, 16.7232, 39.7973, -3.9413, 18.3955, 32.4649, -1.1213, 18.3955, 32.4649, -1.1213, 18.3955, 39.7973, -3.9413, 18.3955, 39.7973, -3.9413, 20.0679, 32.4649, -1.1213, 18.3955, 39.7973, -3.9413, 20.0679, 32.4649, -1.1213, 20.0679, 32.4649, -1.1213, 20.0679, 39.7973, -3.9413, 20.0679, 39.7973, -3.9413, 21.7402, 32.4649, -1.1213, 20.0679, 39.7973, -3.9413, 21.7402, 32.4649, -1.1213, 21.7402, 32.4649, -1.1213, 21.7402, 39.7973, -3.9413, 21.7402, 39.7973, -3.9413, 23.412498, 32.4649, -1.1213, 21.7402, 39.7973, -3.9413, 23.412498, 32.4649, -1.1213, 23.412498, 32.4649, -1.1213, 23.412498, 39.7973, -3.9413, 23.412498, 39.7973, -3.9413, 25.084799, 32.4649, -1.1213, 23.412498, 39.7973, -3.9413, 25.084799, 32.4649, -1.1213, 25.084799, 32.4649, -1.1213, 25.084799, 39.7973, -3.9413, 25.084799, 39.7973, -3.9413, 26.7572, 32.4649, -1.1213, 25.084799, 39.7973, -3.9413, 26.7572, 32.4649, -1.1213, 26.7572, 26.7572, -0.2593, 16.7232, 29.679499, -0.6044, 18.3955, 26.7572, -0.2593, 18.3955, 26.7572, -0.2593, 18.3955, 29.679499, -0.6044, 18.3955, 29.679499, -0.6044, 20.0679, 26.7572, -0.2593, 16.7232, 29.679499, -0.6044, 16.7232, 29.679499, -0.6044, 18.3955, 26.7572, -0.2593, 18.3955, 29.679499, -0.6044, 20.0679, 26.7572, -0.2593, 20.0679, 26.7572, -0.2593, 15.0508995, 29.679499, -0.6044, 16.7232, 26.7572, -0.2593, 16.7232, 26.7572, -0.2593, 20.0679, 29.679499, -0.6044, 20.0679, 29.679499, -0.6044, 21.7402, 26.7572, -0.2593, 15.0508995, 29.679499, -0.6044, 15.0508995, 29.679499, -0.6044, 16.7232, 26.7572, -0.2593, 20.0679, 29.679499, -0.6044, 21.7402, 26.7572, -0.2593, 21.7402, 26.7572, -0.2593, 13.3786, 29.679499, -0.6044, 15.0508995, 26.7572, -0.2593, 15.0508995, 26.7572, -0.2593, 21.7402, 29.679499, -0.6044, 21.7402, 29.679499, -0.6044, 23.412498, 26.7572, -0.2593, 13.3786, 29.679499, -0.6044, 13.3786, 29.679499, -0.6044, 15.0508995, 26.7572, -0.2593, 21.7402, 29.679499, -0.6044, 23.412498, 26.7572, -0.2593, 23.412498, 26.7572, -0.2593, 11.7063, 29.679499, -0.6044, 13.3786, 26.7572, -0.2593, 13.3786, 26.7572, -0.2593, 23.412498, 29.679499, -0.6044, 23.412498, 29.679499, -0.6044, 25.084799, 26.7572, -0.2593, 11.7063, 29.679499, -0.6044, 11.7063, 29.679499, -0.6044, 13.3786, 26.7572, -0.2593, 23.412498, 29.679499, -0.6044, 25.084799, 26.7572, -0.2593, 25.084799, 26.7572, -0.2593, 25.084799, 29.679499, -0.6044, 25.084799, 29.679499, -0.6044, 26.7572, 26.7572, -0.2593, 25.084799, 29.679499, -0.6044, 26.7572, 26.7572, -0.2593, 26.7572, 26.7572, -0.2593, 10.033899, 29.679499, -0.6044, 11.7063, 26.7572, -0.2593, 11.7063, 26.7572, -0.2593, 10.033899, 29.679499, -0.6044, 10.033899, 29.679499, -0.6044, 11.7063, 26.7572, -0.2593, 8.3616, 29.679499, -0.6044, 10.033899, 26.7572, -0.2593, 10.033899, 26.7572, -0.2593, 8.3616, 29.679499, -0.6044, 8.3616, 29.679499, -0.6044, 10.033899, 26.7572, -0.2593, 6.6893, 29.679499, -0.6044, 8.3616, 26.7572, -0.2593, 8.3616, 26.7572, -0.2593, 6.6893, 29.679499, -0.6044, 6.6893, 29.679499, -0.6044, 8.3616, 26.7572, -0.2593, 5.0169997, 29.679499, -0.6044, 6.6893, 26.7572, -0.2593, 6.6893, 26.7572, -0.2593, 5.0169997, 29.679499, -0.6044, 5.0169997, 29.679499, -0.6044, 6.6893, 26.7572, -0.2593, 3.3446, 29.679499, -0.6044, 5.0169997, 26.7572, -0.2593, 5.0169997, 26.7572, -0.2593, 3.3446, 29.679499, -0.6044, 3.3446, 29.679499, -0.6044, 5.0169997, 26.7572, -0.2593, 1.6723, 29.679499, -0.6044, 3.3446, 26.7572, -0.2593, 3.3446, 26.7572, -0.2593, 1.6723, 29.679499, -0.6044, 1.6723, 29.679499, -0.6044, 3.3446, 26.7572, -0.2593, 0, 29.679499, -0.6044, 1.6723, 26.7572, -0.2593, 1.6723, 26.7572, -0.2593, 0, 29.679499, -0.6044, 0, 29.679499, -0.6044, 1.6723, 26.7572, -0.2593, -1.6723, 29.679499, -0.6044, 0, 26.7572, -0.2593, 0, 26.7572, -0.2593, -1.6723, 29.679499, -0.6044, -1.6723, 29.679499, -0.6044, 0, 26.7572, -0.2593, -3.3446, 29.679499, -0.6044, -1.6723, 26.7572, -0.2593, -1.6723, 26.7572, -0.2593, -3.3446, 29.679499, -0.6044, -3.3446, 29.679499, -0.6044, -1.6723, 26.7572, -0.2593, -5.0169997, 29.679499, -0.6044, -3.3446, 26.7572, -0.2593, -3.3446, 26.7572, -0.2593, -5.0169997, 29.679499, -0.6044, -5.0169997, 29.679499, -0.6044, -3.3446, 26.7572, -0.2593, -6.6893, 29.679499, -0.6044, -5.0169997, 26.7572, -0.2593, -5.0169997, 26.7572, -0.2593, -6.6893, 29.679499, -0.6044, -6.6893, 29.679499, -0.6044, -5.0169997, 26.7572, -0.2593, -8.3616, 29.679499, -0.6044, -6.6893, 26.7572, -0.2593, -6.6893, 26.7572, -0.2593, -8.3616, 29.679499, -0.6044, -8.3616, 29.679499, -0.6044, -6.6893, 26.7572, -0.2593, -10.033899, 29.679499, -0.6044, -8.3616, 26.7572, -0.2593, -8.3616, 26.7572, -0.2593, -10.033899, 29.679499, -0.6044, -10.033899, 29.679499, -0.6044, -8.3616, 26.7572, -0.2593, -11.7063, 29.679499, -0.6044, -10.033899, 26.7572, -0.2593, -10.033899, 26.7572, -0.2593, -11.7063, 29.679499, -0.6044, -11.7063, 29.679499, -0.6044, -10.033899, 26.7572, -0.2593, -13.3786, 29.679499, -0.6044, -11.7063, 26.7572, -0.2593, -11.7063, 26.7572, -0.2593, -13.3786, 29.679499, -0.6044, -13.3786, 29.679499, -0.6044, -11.7063, 26.7572, -0.2593, -15.0508995, 29.679499, -0.6044, -13.3786, 26.7572, -0.2593, -13.3786, 26.7572, -0.2593, -15.0508995, 29.679499, -0.6044, -15.0508995, 29.679499, -0.6044, -13.3786, 26.7572, -0.2593, -16.7232, 29.679499, -0.6044, -15.0508995, 26.7572, -0.2593, -15.0508995, 26.7572, -0.2593, -16.7232, 29.679499, -0.6044, -16.7232, 29.679499, -0.6044, -15.0508995, 26.7572, -0.2593, -18.3955, 29.679499, -0.6044, -16.7232, 26.7572, -0.2593, -16.7232, 26.7572, -0.2593, -18.3955, 29.679499, -0.6044, -18.3955, 29.679499, -0.6044, -16.7232, 26.7572, -0.2593, -20.0679, 29.679499, -0.6044, -18.3955, 26.7572, -0.2593, -18.3955, 26.7572, -0.2593, -20.0679, 29.679499, -0.6044, -20.0679, 29.679499, -0.6044, -18.3955, 26.7572, -0.2593, -21.7402, 29.679499, -0.6044, -20.0679, 26.7572, -0.2593, -20.0679, 26.7572, -0.2593, -21.7402, 29.679499, -0.6044, -21.7402, 29.679499, -0.6044, -20.0679, 26.7572, -0.2593, -23.412498, 29.679499, -0.6044, -21.7402, 26.7572, -0.2593, -21.7402, 26.7572, -0.2593, -23.412498, 29.679499, -0.6044, -23.412498, 29.679499, -0.6044, -21.7402, 26.7572, -0.2593, -25.084799, 29.679499, -0.6044, -23.412498, 26.7572, -0.2593, -23.412498, 26.7572, -0.2593, -25.084799, 29.679499, -0.6044, -25.084799, 29.679499, -0.6044, -23.412498, 26.7572, -0.2593, -26.7572, 29.679499, -0.6044, -25.084799, 26.7572, -0.2593, -25.084799, 26.7572, -0.2593, -26.7572, 29.679499, -0.6044, -26.7572, 29.679499, -0.6044, -25.084799) @@ -290,11 +288,3 @@ transform = Transform3D(-0.9976046, 0, -0.06917416, 0, 1, 0, 0.06917416, 0, -0.9 [node name="PlayerStorageBox" parent="Interactables" instance=ExtResource("8_storage")] position = Vector3(-3.25, 3.42, 11.25) - -[node name="DecorShopWorld" parent="Interactables" instance=ExtResource("25_decor_shop")] -position = Vector3(-3.25, 3.42, 11.25) -rotation = Vector3(0, 1.5707964, 0) - -[node name="RVUpgradeShopWorld" parent="Interactables" instance=ExtResource("26_rv_upgrade")] -position = Vector3(-6.25, 3.42, 9) -rotation = Vector3(0, 1.5707964, 0) diff --git a/world/regions/world_region.gd b/world/regions/world_region.gd index d054938..a0730e4 100644 --- a/world/regions/world_region.gd +++ b/world/regions/world_region.gd @@ -18,14 +18,6 @@ func get_fishing_shop() -> FishingShopInteraction: return null -func get_decor_shop() -> DecorShopInteraction: - return null - - -func get_rv_upgrade_shop() -> RVUpgradeInteraction: - return null - - func get_player_storage() -> PlayerStorageInteraction: return null @@ -46,24 +38,10 @@ func get_spawn_surface_triangles( _material_names: Array[StringName], _minimum_global_y: float, _minimum_up_dot: float = 0.6, - _maximum_global_y: float = 100.0, ) -> Array[PackedVector3Array]: return [] -func get_saltwater_surface_height() -> float: - for water_region: FishableWaterRegion in get_fishable_water_regions(): - if water_region.water_type == WaterType.Type.SALT_WATER: - return water_region.get_surface_height() - return NAN - - -## Authored regions are assumed to expose only deliberately reachable home -## sites. Procedural regions override this to reject isolated elevations. -func is_home_exterior_position_accessible(_position: Vector3) -> bool: - return true - - func get_fishable_water_regions() -> Array[FishableWaterRegion]: var regions: Array[FishableWaterRegion] = [] var root: Node = get_node_or_null(fishable_water_root) @@ -94,24 +72,6 @@ func get_safe_respawn_points() -> Array[SafeRespawnPoint]: return points -func get_water_recovery_position( - entry_position: Vector3, - fallback_position: Vector3, -) -> Vector3: - if not entry_position.is_finite(): - return fallback_position - var target_position := fallback_position - var nearest_distance := INF - for point: SafeRespawnPoint in get_safe_respawn_points(): - if point == null or not is_instance_valid(point) or not point.enabled: - continue - var distance := point.get_horizontal_distance_squared(entry_position) - if distance < nearest_distance: - nearest_distance = distance - target_position = point.global_position - return target_position - - func get_diggable_areas() -> Array[DiggableArea3D]: var areas: Array[DiggableArea3D] = [] var root: Node = get_node_or_null(diggable_area_root) diff --git a/world/rv_upgrade_interaction.gd b/world/rv_upgrade_interaction.gd deleted file mode 100644 index fb24246..0000000 --- a/world/rv_upgrade_interaction.gd +++ /dev/null @@ -1,15 +0,0 @@ -class_name RVUpgradeInteraction -extends FishingShopInteraction - -const TENT_DIALOGUE_TEXT: String = ( - "That tent is looking awful cramped. Can I talk you into an upgrade?" -) -const RV_DIALOGUE_TEXT: String = "How's the new RV treating you?" - - -func get_dialogue_text(home_tier: int = -1) -> String: - return ( - RV_DIALOGUE_TEXT - if home_tier >= PlayerHomeState.STARTER_RV_TIER - else TENT_DIALOGUE_TEXT - ) diff --git a/world/rv_upgrade_interaction.gd.uid b/world/rv_upgrade_interaction.gd.uid deleted file mode 100644 index cb57804..0000000 --- a/world/rv_upgrade_interaction.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://c0h08goorv2of diff --git a/world/test_world.gd b/world/test_world.gd index 2e25206..d9d18dc 100644 --- a/world/test_world.gd +++ b/world/test_world.gd @@ -7,12 +7,6 @@ const FishingShopInteractionType = preload( const PlayerStorageInteractionType = preload( "res://world/player_storage_interaction.gd" ) -const DecorShopInteractionType = preload( - "res://world/decor_shop_interaction.gd" -) -const RVUpgradeInteractionType = preload( - "res://world/rv_upgrade_interaction.gd" -) const WorldLayoutType = preload("res://world/world_layout.gd") const GENERATED_REGION_SCENE: PackedScene = preload( "res://world/generation/generated_world_region.tscn" @@ -37,7 +31,6 @@ var _world_layout: StringName = WorldLayoutType.GENERATED var _world_seed: int = PlayerSaveManager.DEFAULT_WORLD_SEED var _light_performance_profile: bool = false var _dedicated_simulation: bool = false -var _local_home_interior_presentation_active: bool = false func get_player_water_triggers() -> Array[PlayerWaterTrigger]: @@ -56,16 +49,6 @@ func get_safe_respawn_points() -> Array[SafeRespawnPoint]: ) -func get_water_recovery_position(entry_position: Vector3) -> Vector3: - if _active_region == null: - return global_position - var fallback_position := get_player_spawn_transform().origin - return _active_region.get_water_recovery_position( - entry_position, - fallback_position, - ) - - func get_fishing_shop() -> FishingShopInteractionType: return _active_region.get_fishing_shop() @@ -74,14 +57,6 @@ func get_player_storage() -> PlayerStorageInteractionType: return _active_region.get_player_storage() -func get_decor_shop() -> DecorShopInteractionType: - return _active_region.get_decor_shop() - - -func get_rv_upgrade_shop() -> RVUpgradeInteractionType: - return _active_region.get_rv_upgrade_shop() - - func get_player_spawn_transform() -> Transform3D: return _active_region.get_player_spawn_transform() @@ -100,12 +75,6 @@ func set_light_performance_profile(enabled: bool) -> void: _active_region.set_light_performance_profile(enabled) -func set_local_home_interior_presentation_active(enabled: bool) -> void: - _local_home_interior_presentation_active = enabled - if _active_region != null and not _dedicated_simulation: - _active_region.visible = not enabled - - func set_dedicated_simulation(enabled: bool) -> void: _dedicated_simulation = enabled if _active_region != null: @@ -177,29 +146,10 @@ func get_saltwater_shoreline_mesh() -> MeshInstance3D: func get_spawn_surface_triangles( material_names: Array[StringName], minimum_global_y: float, - minimum_up_dot: float = 0.6, - maximum_global_y: float = 100.0, ) -> Array[PackedVector3Array]: return _active_region.get_spawn_surface_triangles( material_names, minimum_global_y, - minimum_up_dot, - maximum_global_y, - ) - - -func get_saltwater_surface_height() -> float: - return ( - _active_region.get_saltwater_surface_height() - if _active_region != null - else NAN - ) - - -func is_home_exterior_position_accessible(position: Vector3) -> bool: - return ( - _active_region != null - and _active_region.is_home_exterior_position_accessible(position) ) @@ -312,8 +262,6 @@ func _replace_active_region(layout: StringName, seed: int) -> bool: _active_region.set_light_performance_profile(_light_performance_profile) if _dedicated_simulation: _set_region_presentation_enabled(_active_region, false) - else: - _active_region.visible = not _local_home_interior_presentation_active return true diff --git a/world/water_recovery_controller.gd b/world/water_recovery_controller.gd index 0d0c5aa..9626881 100644 --- a/world/water_recovery_controller.gd +++ b/world/water_recovery_controller.gd @@ -25,7 +25,6 @@ var _game_ui: GameUI var _screen_fade: ScreenFade var _water_triggers: Array[PlayerWaterTrigger] = [] var _safe_points: Array[SafeRespawnPoint] = [] -var _recovery_position_resolver := Callable() var _initial_spawn_transform: Transform3D var _entry_position: Vector3 var _bob_base_position: Vector3 @@ -44,7 +43,6 @@ func setup( screen_fade: ScreenFade, water_triggers: Array[PlayerWaterTrigger], safe_points: Array[SafeRespawnPoint], - recovery_position_resolver: Callable = Callable(), ) -> void: _player = player _fishing_spot = fishing_spot @@ -52,7 +50,6 @@ func setup( _screen_fade = screen_fade _water_triggers = water_triggers _safe_points = safe_points - _recovery_position_resolver = recovery_position_resolver _initial_spawn_transform = player.global_transform for water_trigger: PlayerWaterTrigger in _water_triggers: if ( @@ -76,7 +73,6 @@ func update_world_context( initial_spawn_transform: Transform3D, water_triggers: Array[PlayerWaterTrigger], safe_points: Array[SafeRespawnPoint], - recovery_position_resolver: Callable = Callable(), ) -> void: for water_trigger: PlayerWaterTrigger in _water_triggers: if ( @@ -92,7 +88,6 @@ func update_world_context( _initial_spawn_transform = initial_spawn_transform _water_triggers = water_triggers _safe_points = safe_points - _recovery_position_resolver = recovery_position_resolver for water_trigger: PlayerWaterTrigger in _water_triggers: if ( water_trigger != null @@ -188,7 +183,17 @@ func _on_fade_transition_completed( func _respawn_player() -> void: - var target_position := _resolve_recovery_position() + var target_position: Vector3 = _initial_spawn_transform.origin + var nearest_distance: float = INF + for point: SafeRespawnPoint in _safe_points: + if point == null or not is_instance_valid(point) or not point.enabled: + continue + var distance: float = point.get_horizontal_distance_squared( + _entry_position + ) + if distance < nearest_distance: + nearest_distance = distance + target_position = point.global_position target_position.y += respawn_height_offset var respawn_transform: Transform3D = _player.global_transform respawn_transform.basis = _recovery_root_basis @@ -199,23 +204,6 @@ func _respawn_player() -> void: local_respawn_completed.emit(_entry_position) -func _resolve_recovery_position() -> Vector3: - if _recovery_position_resolver.is_valid(): - var resolved: Variant = _recovery_position_resolver.call(_entry_position) - if resolved is Vector3 and (resolved as Vector3).is_finite(): - return resolved as Vector3 - var target_position := _initial_spawn_transform.origin - var nearest_distance := INF - for point: SafeRespawnPoint in _safe_points: - if point == null or not is_instance_valid(point) or not point.enabled: - continue - var distance := point.get_horizontal_distance_squared(_entry_position) - if distance < nearest_distance: - nearest_distance = distance - target_position = point.global_position - return target_position - - func _finish_recovery() -> void: _player.restore_gameplay_orientation_after_recovery() _player.set_water_recovery_active(false)