diff --git a/README.md b/README.md index b3d3e3f..47caefa 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,22 @@ save migrations may continue to change before a stable release. NETfishing is developed and published by **Woofmeow**, the independent team of co-owners publicly credited as Voyager and Endeavour. +## Installing release builds + +Download and extract the archive for your platform from the official release +page, then run NETfishing from the extracted platform folder. + +### macOS + +Current macOS builds are not signed or notarized. macOS may block the first +launch because it cannot verify the developer. Control-click the NETfishing +application, choose **Open**, then confirm **Open**. If that option is not +available, attempt to open the application once, then use **System Settings → +Privacy & Security → Open Anyway**. + +Only bypass this warning for a NETfishing build downloaded from an official +project release. + ## Requirements - Godot 4.7.x with the GL Compatibility renderer diff --git a/art/exported/characters/faces/eyes/eyes_alligator_extra.png.import b/art/exported/characters/faces/eyes/eyes_alligator_extra.png.import index c9348c7..6be8860 100644 --- a/art/exported/characters/faces/eyes/eyes_alligator_extra.png.import +++ b/art/exported/characters/faces/eyes/eyes_alligator_extra.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://c4rj1r1doawb4" -path="res://.godot/imported/eyes_alligator_extra.png-30730001eeef37c24fac0158f6613bd7.ctex" +path.s3tc="res://.godot/imported/eyes_alligator_extra.png-30730001eeef37c24fac0158f6613bd7.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_alligator_extra.png-30730001eeef37c24fac0158f6613bd7.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_alligator_extra.png" -dest_files=["res://.godot/imported/eyes_alligator_extra.png-30730001eeef37c24fac0158f6613bd7.ctex"] +dest_files=["res://.godot/imported/eyes_alligator_extra.png-30730001eeef37c24fac0158f6613bd7.s3tc.ctex", "res://.godot/imported/eyes_alligator_extra.png-30730001eeef37c24fac0158f6613bd7.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_alligator_eyes.png.import b/art/exported/characters/faces/eyes/eyes_alligator_eyes.png.import index 7f2eb8d..410a5ef 100644 --- a/art/exported/characters/faces/eyes/eyes_alligator_eyes.png.import +++ b/art/exported/characters/faces/eyes/eyes_alligator_eyes.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://br2h5sfqmool8" -path="res://.godot/imported/eyes_alligator_eyes.png-843d36964858cdf7ffafe1972ffed681.ctex" +path.s3tc="res://.godot/imported/eyes_alligator_eyes.png-843d36964858cdf7ffafe1972ffed681.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_alligator_eyes.png-843d36964858cdf7ffafe1972ffed681.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_alligator_eyes.png" -dest_files=["res://.godot/imported/eyes_alligator_eyes.png-843d36964858cdf7ffafe1972ffed681.ctex"] +dest_files=["res://.godot/imported/eyes_alligator_eyes.png-843d36964858cdf7ffafe1972ffed681.s3tc.ctex", "res://.godot/imported/eyes_alligator_eyes.png-843d36964858cdf7ffafe1972ffed681.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_angry.png.import b/art/exported/characters/faces/eyes/eyes_angry.png.import index 3be46d5..ee3d3df 100644 --- a/art/exported/characters/faces/eyes/eyes_angry.png.import +++ b/art/exported/characters/faces/eyes/eyes_angry.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b1ieqa8qos1vk" -path="res://.godot/imported/eyes_angry.png-a114dbeb3a8a14b8d1d86dd5a614b228.ctex" +path.s3tc="res://.godot/imported/eyes_angry.png-a114dbeb3a8a14b8d1d86dd5a614b228.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_angry.png-a114dbeb3a8a14b8d1d86dd5a614b228.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_angry.png" -dest_files=["res://.godot/imported/eyes_angry.png-a114dbeb3a8a14b8d1d86dd5a614b228.ctex"] +dest_files=["res://.godot/imported/eyes_angry.png-a114dbeb3a8a14b8d1d86dd5a614b228.s3tc.ctex", "res://.godot/imported/eyes_angry.png-a114dbeb3a8a14b8d1d86dd5a614b228.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_anime_blue.png.import b/art/exported/characters/faces/eyes/eyes_anime_blue.png.import index 443c8d0..6baa979 100644 --- a/art/exported/characters/faces/eyes/eyes_anime_blue.png.import +++ b/art/exported/characters/faces/eyes/eyes_anime_blue.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://2ecbidhb33vy" -path="res://.godot/imported/eyes_anime_blue.png-a86f72e375fc30cac2cd8320813c7c6c.ctex" +path.s3tc="res://.godot/imported/eyes_anime_blue.png-a86f72e375fc30cac2cd8320813c7c6c.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_anime_blue.png-a86f72e375fc30cac2cd8320813c7c6c.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_anime_blue.png" -dest_files=["res://.godot/imported/eyes_anime_blue.png-a86f72e375fc30cac2cd8320813c7c6c.ctex"] +dest_files=["res://.godot/imported/eyes_anime_blue.png-a86f72e375fc30cac2cd8320813c7c6c.s3tc.ctex", "res://.godot/imported/eyes_anime_blue.png-a86f72e375fc30cac2cd8320813c7c6c.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_anime_green.png.import b/art/exported/characters/faces/eyes/eyes_anime_green.png.import index a1be2b0..9c2a60c 100644 --- a/art/exported/characters/faces/eyes/eyes_anime_green.png.import +++ b/art/exported/characters/faces/eyes/eyes_anime_green.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bwrxrtiv375ne" -path="res://.godot/imported/eyes_anime_green.png-a789a8f21c23e3085bca8992b3a3fc25.ctex" +path.s3tc="res://.godot/imported/eyes_anime_green.png-a789a8f21c23e3085bca8992b3a3fc25.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_anime_green.png-a789a8f21c23e3085bca8992b3a3fc25.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_anime_green.png" -dest_files=["res://.godot/imported/eyes_anime_green.png-a789a8f21c23e3085bca8992b3a3fc25.ctex"] +dest_files=["res://.godot/imported/eyes_anime_green.png-a789a8f21c23e3085bca8992b3a3fc25.s3tc.ctex", "res://.godot/imported/eyes_anime_green.png-a789a8f21c23e3085bca8992b3a3fc25.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_beady.png.import b/art/exported/characters/faces/eyes/eyes_beady.png.import index 829845c..ce4ddea 100644 --- a/art/exported/characters/faces/eyes/eyes_beady.png.import +++ b/art/exported/characters/faces/eyes/eyes_beady.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bahypju2dturm" -path="res://.godot/imported/eyes_beady.png-18d3b3ff0818d80f773d4f72d0b0061e.ctex" +path.s3tc="res://.godot/imported/eyes_beady.png-18d3b3ff0818d80f773d4f72d0b0061e.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_beady.png-18d3b3ff0818d80f773d4f72d0b0061e.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_beady.png" -dest_files=["res://.godot/imported/eyes_beady.png-18d3b3ff0818d80f773d4f72d0b0061e.ctex"] +dest_files=["res://.godot/imported/eyes_beady.png-18d3b3ff0818d80f773d4f72d0b0061e.s3tc.ctex", "res://.godot/imported/eyes_beady.png-18d3b3ff0818d80f773d4f72d0b0061e.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_beady_extra.png.import b/art/exported/characters/faces/eyes/eyes_beady_extra.png.import index 2951456..29babb1 100644 --- a/art/exported/characters/faces/eyes/eyes_beady_extra.png.import +++ b/art/exported/characters/faces/eyes/eyes_beady_extra.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bqgdrdc0hpi7s" -path="res://.godot/imported/eyes_beady_extra.png-d07d79345b1d22e55a1cf140f7682248.ctex" +path.s3tc="res://.godot/imported/eyes_beady_extra.png-d07d79345b1d22e55a1cf140f7682248.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_beady_extra.png-d07d79345b1d22e55a1cf140f7682248.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_beady_extra.png" -dest_files=["res://.godot/imported/eyes_beady_extra.png-d07d79345b1d22e55a1cf140f7682248.ctex"] +dest_files=["res://.godot/imported/eyes_beady_extra.png-d07d79345b1d22e55a1cf140f7682248.s3tc.ctex", "res://.godot/imported/eyes_beady_extra.png-d07d79345b1d22e55a1cf140f7682248.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_beady_extra_shine.png.import b/art/exported/characters/faces/eyes/eyes_beady_extra_shine.png.import index fe258f0..047869d 100644 --- a/art/exported/characters/faces/eyes/eyes_beady_extra_shine.png.import +++ b/art/exported/characters/faces/eyes/eyes_beady_extra_shine.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://mvjcdk84ko6f" -path="res://.godot/imported/eyes_beady_extra_shine.png-c72a35f216a8c1a06122f7bd743ca6a4.ctex" +path.s3tc="res://.godot/imported/eyes_beady_extra_shine.png-c72a35f216a8c1a06122f7bd743ca6a4.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_beady_extra_shine.png-c72a35f216a8c1a06122f7bd743ca6a4.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_beady_extra_shine.png" -dest_files=["res://.godot/imported/eyes_beady_extra_shine.png-c72a35f216a8c1a06122f7bd743ca6a4.ctex"] +dest_files=["res://.godot/imported/eyes_beady_extra_shine.png-c72a35f216a8c1a06122f7bd743ca6a4.s3tc.ctex", "res://.godot/imported/eyes_beady_extra_shine.png-c72a35f216a8c1a06122f7bd743ca6a4.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_beady_eyebrows.png.import b/art/exported/characters/faces/eyes/eyes_beady_eyebrows.png.import index 4368bec..63a4b2f 100644 --- a/art/exported/characters/faces/eyes/eyes_beady_eyebrows.png.import +++ b/art/exported/characters/faces/eyes/eyes_beady_eyebrows.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://dp4sm67vn52um" -path="res://.godot/imported/eyes_beady_eyebrows.png-4f63e1a7ca603e9daf2d37a1d182e93f.ctex" +path.s3tc="res://.godot/imported/eyes_beady_eyebrows.png-4f63e1a7ca603e9daf2d37a1d182e93f.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_beady_eyebrows.png-4f63e1a7ca603e9daf2d37a1d182e93f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_beady_eyebrows.png" -dest_files=["res://.godot/imported/eyes_beady_eyebrows.png-4f63e1a7ca603e9daf2d37a1d182e93f.ctex"] +dest_files=["res://.godot/imported/eyes_beady_eyebrows.png-4f63e1a7ca603e9daf2d37a1d182e93f.s3tc.ctex", "res://.godot/imported/eyes_beady_eyebrows.png-4f63e1a7ca603e9daf2d37a1d182e93f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_beady_whites.png.import b/art/exported/characters/faces/eyes/eyes_beady_whites.png.import index 2b6680e..8503f0e 100644 --- a/art/exported/characters/faces/eyes/eyes_beady_whites.png.import +++ b/art/exported/characters/faces/eyes/eyes_beady_whites.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cw34wh51s5sxi" -path="res://.godot/imported/eyes_beady_whites.png-87ce5dcbdfca9ca43a6f7c5ddca77bfa.ctex" +path.s3tc="res://.godot/imported/eyes_beady_whites.png-87ce5dcbdfca9ca43a6f7c5ddca77bfa.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_beady_whites.png-87ce5dcbdfca9ca43a6f7c5ddca77bfa.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_beady_whites.png" -dest_files=["res://.godot/imported/eyes_beady_whites.png-87ce5dcbdfca9ca43a6f7c5ddca77bfa.ctex"] +dest_files=["res://.godot/imported/eyes_beady_whites.png-87ce5dcbdfca9ca43a6f7c5ddca77bfa.s3tc.ctex", "res://.godot/imported/eyes_beady_whites.png-87ce5dcbdfca9ca43a6f7c5ddca77bfa.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_carrot_eyes.png.import b/art/exported/characters/faces/eyes/eyes_carrot_eyes.png.import index 80ef07a..6444d02 100644 --- a/art/exported/characters/faces/eyes/eyes_carrot_eyes.png.import +++ b/art/exported/characters/faces/eyes/eyes_carrot_eyes.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://vn7mkjkx80ix" -path="res://.godot/imported/eyes_carrot_eyes.png-4d4af86c340a0dd0633449a9c6cc1bae.ctex" +path.s3tc="res://.godot/imported/eyes_carrot_eyes.png-4d4af86c340a0dd0633449a9c6cc1bae.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_carrot_eyes.png-4d4af86c340a0dd0633449a9c6cc1bae.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_carrot_eyes.png" -dest_files=["res://.godot/imported/eyes_carrot_eyes.png-4d4af86c340a0dd0633449a9c6cc1bae.ctex"] +dest_files=["res://.godot/imported/eyes_carrot_eyes.png-4d4af86c340a0dd0633449a9c6cc1bae.s3tc.ctex", "res://.godot/imported/eyes_carrot_eyes.png-4d4af86c340a0dd0633449a9c6cc1bae.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_cat_real.png.import b/art/exported/characters/faces/eyes/eyes_cat_real.png.import index 2a4cbd3..25675c9 100644 --- a/art/exported/characters/faces/eyes/eyes_cat_real.png.import +++ b/art/exported/characters/faces/eyes/eyes_cat_real.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://sbxopn55femq" -path="res://.godot/imported/eyes_cat_real.png-13c9b6d280ac1000d177e74cbd33f07f.ctex" +path.s3tc="res://.godot/imported/eyes_cat_real.png-13c9b6d280ac1000d177e74cbd33f07f.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_cat_real.png-13c9b6d280ac1000d177e74cbd33f07f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_cat_real.png" -dest_files=["res://.godot/imported/eyes_cat_real.png-13c9b6d280ac1000d177e74cbd33f07f.ctex"] +dest_files=["res://.godot/imported/eyes_cat_real.png-13c9b6d280ac1000d177e74cbd33f07f.s3tc.ctex", "res://.godot/imported/eyes_cat_real.png-13c9b6d280ac1000d177e74cbd33f07f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_cat_real_hunter.png.import b/art/exported/characters/faces/eyes/eyes_cat_real_hunter.png.import index 9d9b065..ab1c3f5 100644 --- a/art/exported/characters/faces/eyes/eyes_cat_real_hunter.png.import +++ b/art/exported/characters/faces/eyes/eyes_cat_real_hunter.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cg1l01m0ae2oa" -path="res://.godot/imported/eyes_cat_real_hunter.png-6eb486fc423d7e3fa84f71aa30ec04f3.ctex" +path.s3tc="res://.godot/imported/eyes_cat_real_hunter.png-6eb486fc423d7e3fa84f71aa30ec04f3.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_cat_real_hunter.png-6eb486fc423d7e3fa84f71aa30ec04f3.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_cat_real_hunter.png" -dest_files=["res://.godot/imported/eyes_cat_real_hunter.png-6eb486fc423d7e3fa84f71aa30ec04f3.ctex"] +dest_files=["res://.godot/imported/eyes_cat_real_hunter.png-6eb486fc423d7e3fa84f71aa30ec04f3.s3tc.ctex", "res://.godot/imported/eyes_cat_real_hunter.png-6eb486fc423d7e3fa84f71aa30ec04f3.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_close.png.import b/art/exported/characters/faces/eyes/eyes_close.png.import index 25986e6..e40b902 100644 --- a/art/exported/characters/faces/eyes/eyes_close.png.import +++ b/art/exported/characters/faces/eyes/eyes_close.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://kbigjk76ejx8" -path="res://.godot/imported/eyes_close.png-13a7da90295acf57ea489edcf26fc555.ctex" +path.s3tc="res://.godot/imported/eyes_close.png-13a7da90295acf57ea489edcf26fc555.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_close.png-13a7da90295acf57ea489edcf26fc555.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_close.png" -dest_files=["res://.godot/imported/eyes_close.png-13a7da90295acf57ea489edcf26fc555.ctex"] +dest_files=["res://.godot/imported/eyes_close.png-13a7da90295acf57ea489edcf26fc555.s3tc.ctex", "res://.godot/imported/eyes_close.png-13a7da90295acf57ea489edcf26fc555.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_closed.png.import b/art/exported/characters/faces/eyes/eyes_closed.png.import index af51540..325eb74 100644 --- a/art/exported/characters/faces/eyes/eyes_closed.png.import +++ b/art/exported/characters/faces/eyes/eyes_closed.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b2ufsni8mnpmq" -path="res://.godot/imported/eyes_closed.png-80789498475c2cfbcb6e3ac14e067a76.ctex" +path.s3tc="res://.godot/imported/eyes_closed.png-80789498475c2cfbcb6e3ac14e067a76.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_closed.png-80789498475c2cfbcb6e3ac14e067a76.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_closed.png" -dest_files=["res://.godot/imported/eyes_closed.png-80789498475c2cfbcb6e3ac14e067a76.ctex"] +dest_files=["res://.godot/imported/eyes_closed.png-80789498475c2cfbcb6e3ac14e067a76.s3tc.ctex", "res://.godot/imported/eyes_closed.png-80789498475c2cfbcb6e3ac14e067a76.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_cute.png.import b/art/exported/characters/faces/eyes/eyes_cute.png.import index c8d9f09..b19f9ef 100644 --- a/art/exported/characters/faces/eyes/eyes_cute.png.import +++ b/art/exported/characters/faces/eyes/eyes_cute.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b5455dbwse02f" -path="res://.godot/imported/eyes_cute.png-e2eabbdf5281c15927e85f1e2f22b127.ctex" +path.s3tc="res://.godot/imported/eyes_cute.png-e2eabbdf5281c15927e85f1e2f22b127.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_cute.png-e2eabbdf5281c15927e85f1e2f22b127.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_cute.png" -dest_files=["res://.godot/imported/eyes_cute.png-e2eabbdf5281c15927e85f1e2f22b127.ctex"] +dest_files=["res://.godot/imported/eyes_cute.png-e2eabbdf5281c15927e85f1e2f22b127.s3tc.ctex", "res://.godot/imported/eyes_cute.png-e2eabbdf5281c15927e85f1e2f22b127.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_cute_close.png.import b/art/exported/characters/faces/eyes/eyes_cute_close.png.import index 5b51dc2..9a3c9b2 100644 --- a/art/exported/characters/faces/eyes/eyes_cute_close.png.import +++ b/art/exported/characters/faces/eyes/eyes_cute_close.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://lp21yuhfqbc1" -path="res://.godot/imported/eyes_cute_close.png-7a8a1b4e10864cfef604b9ced31a3f0c.ctex" +path.s3tc="res://.godot/imported/eyes_cute_close.png-7a8a1b4e10864cfef604b9ced31a3f0c.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_cute_close.png-7a8a1b4e10864cfef604b9ced31a3f0c.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_cute_close.png" -dest_files=["res://.godot/imported/eyes_cute_close.png-7a8a1b4e10864cfef604b9ced31a3f0c.ctex"] +dest_files=["res://.godot/imported/eyes_cute_close.png-7a8a1b4e10864cfef604b9ced31a3f0c.s3tc.ctex", "res://.godot/imported/eyes_cute_close.png-7a8a1b4e10864cfef604b9ced31a3f0c.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_huge.png.import b/art/exported/characters/faces/eyes/eyes_huge.png.import index 2c449bd..7b55837 100644 --- a/art/exported/characters/faces/eyes/eyes_huge.png.import +++ b/art/exported/characters/faces/eyes/eyes_huge.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://vb1eaupu8fpm" -path="res://.godot/imported/eyes_huge.png-9747926f369b1134a94639907057a08d.ctex" +path.s3tc="res://.godot/imported/eyes_huge.png-9747926f369b1134a94639907057a08d.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_huge.png-9747926f369b1134a94639907057a08d.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_huge.png" -dest_files=["res://.godot/imported/eyes_huge.png-9747926f369b1134a94639907057a08d.ctex"] +dest_files=["res://.godot/imported/eyes_huge.png-9747926f369b1134a94639907057a08d.s3tc.ctex", "res://.godot/imported/eyes_huge.png-9747926f369b1134a94639907057a08d.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_interested.png.import b/art/exported/characters/faces/eyes/eyes_interested.png.import index 061fe34..7f3a996 100644 --- a/art/exported/characters/faces/eyes/eyes_interested.png.import +++ b/art/exported/characters/faces/eyes/eyes_interested.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b3481384do6m4" -path="res://.godot/imported/eyes_interested.png-5e8a34c0afaf7b4543f1b892af2227c7.ctex" +path.s3tc="res://.godot/imported/eyes_interested.png-5e8a34c0afaf7b4543f1b892af2227c7.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_interested.png-5e8a34c0afaf7b4543f1b892af2227c7.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_interested.png" -dest_files=["res://.godot/imported/eyes_interested.png-5e8a34c0afaf7b4543f1b892af2227c7.ctex"] +dest_files=["res://.godot/imported/eyes_interested.png-5e8a34c0afaf7b4543f1b892af2227c7.s3tc.ctex", "res://.godot/imported/eyes_interested.png-5e8a34c0afaf7b4543f1b892af2227c7.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_jackolantern.png.import b/art/exported/characters/faces/eyes/eyes_jackolantern.png.import index c447cdd..0515c3f 100644 --- a/art/exported/characters/faces/eyes/eyes_jackolantern.png.import +++ b/art/exported/characters/faces/eyes/eyes_jackolantern.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cdqqfoss11muq" -path="res://.godot/imported/eyes_jackolantern.png-16dc8591f3bd827988f8fcb90bbced59.ctex" +path.s3tc="res://.godot/imported/eyes_jackolantern.png-16dc8591f3bd827988f8fcb90bbced59.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_jackolantern.png-16dc8591f3bd827988f8fcb90bbced59.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_jackolantern.png" -dest_files=["res://.godot/imported/eyes_jackolantern.png-16dc8591f3bd827988f8fcb90bbced59.ctex"] +dest_files=["res://.godot/imported/eyes_jackolantern.png-16dc8591f3bd827988f8fcb90bbced59.s3tc.ctex", "res://.godot/imported/eyes_jackolantern.png-16dc8591f3bd827988f8fcb90bbced59.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_jackolantern_small.png.import b/art/exported/characters/faces/eyes/eyes_jackolantern_small.png.import index 8de58a2..3c8fb9b 100644 --- a/art/exported/characters/faces/eyes/eyes_jackolantern_small.png.import +++ b/art/exported/characters/faces/eyes/eyes_jackolantern_small.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://6n6riqe60s2f" -path="res://.godot/imported/eyes_jackolantern_small.png-7608ab74472a3edb79fcddb1cafbcc2f.ctex" +path.s3tc="res://.godot/imported/eyes_jackolantern_small.png-7608ab74472a3edb79fcddb1cafbcc2f.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_jackolantern_small.png-7608ab74472a3edb79fcddb1cafbcc2f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_jackolantern_small.png" -dest_files=["res://.godot/imported/eyes_jackolantern_small.png-7608ab74472a3edb79fcddb1cafbcc2f.ctex"] +dest_files=["res://.godot/imported/eyes_jackolantern_small.png-7608ab74472a3edb79fcddb1cafbcc2f.s3tc.ctex", "res://.godot/imported/eyes_jackolantern_small.png-7608ab74472a3edb79fcddb1cafbcc2f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_punk_purple.png.import b/art/exported/characters/faces/eyes/eyes_punk_purple.png.import index 547a560..5e58771 100644 --- a/art/exported/characters/faces/eyes/eyes_punk_purple.png.import +++ b/art/exported/characters/faces/eyes/eyes_punk_purple.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b2c784my766ag" -path="res://.godot/imported/eyes_punk_purple.png-fc0ccb3a5f1d2c23f2f367bb306d1ada.ctex" +path.s3tc="res://.godot/imported/eyes_punk_purple.png-fc0ccb3a5f1d2c23f2f367bb306d1ada.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_punk_purple.png-fc0ccb3a5f1d2c23f2f367bb306d1ada.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_punk_purple.png" -dest_files=["res://.godot/imported/eyes_punk_purple.png-fc0ccb3a5f1d2c23f2f367bb306d1ada.ctex"] +dest_files=["res://.godot/imported/eyes_punk_purple.png-fc0ccb3a5f1d2c23f2f367bb306d1ada.s3tc.ctex", "res://.godot/imported/eyes_punk_purple.png-fc0ccb3a5f1d2c23f2f367bb306d1ada.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_sexy_blue.png.import b/art/exported/characters/faces/eyes/eyes_sexy_blue.png.import index 7d2046d..dc29314 100644 --- a/art/exported/characters/faces/eyes/eyes_sexy_blue.png.import +++ b/art/exported/characters/faces/eyes/eyes_sexy_blue.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://drajn1tk8gq4a" -path="res://.godot/imported/eyes_sexy_blue.png-f99ecb6d89cb6c453161f6ad9fd5f802.ctex" +path.s3tc="res://.godot/imported/eyes_sexy_blue.png-f99ecb6d89cb6c453161f6ad9fd5f802.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_sexy_blue.png-f99ecb6d89cb6c453161f6ad9fd5f802.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_sexy_blue.png" -dest_files=["res://.godot/imported/eyes_sexy_blue.png-f99ecb6d89cb6c453161f6ad9fd5f802.ctex"] +dest_files=["res://.godot/imported/eyes_sexy_blue.png-f99ecb6d89cb6c453161f6ad9fd5f802.s3tc.ctex", "res://.godot/imported/eyes_sexy_blue.png-f99ecb6d89cb6c453161f6ad9fd5f802.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_sexy_brown.png.import b/art/exported/characters/faces/eyes/eyes_sexy_brown.png.import index a2b30f1..3abd188 100644 --- a/art/exported/characters/faces/eyes/eyes_sexy_brown.png.import +++ b/art/exported/characters/faces/eyes/eyes_sexy_brown.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://c38u5tdksowpw" -path="res://.godot/imported/eyes_sexy_brown.png-e77cd183e70234d3ee7772ed2837cf34.ctex" +path.s3tc="res://.godot/imported/eyes_sexy_brown.png-e77cd183e70234d3ee7772ed2837cf34.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_sexy_brown.png-e77cd183e70234d3ee7772ed2837cf34.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_sexy_brown.png" -dest_files=["res://.godot/imported/eyes_sexy_brown.png-e77cd183e70234d3ee7772ed2837cf34.ctex"] +dest_files=["res://.godot/imported/eyes_sexy_brown.png-e77cd183e70234d3ee7772ed2837cf34.s3tc.ctex", "res://.godot/imported/eyes_sexy_brown.png-e77cd183e70234d3ee7772ed2837cf34.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_spider.png.import b/art/exported/characters/faces/eyes/eyes_spider.png.import index 3b137b9..b274e66 100644 --- a/art/exported/characters/faces/eyes/eyes_spider.png.import +++ b/art/exported/characters/faces/eyes/eyes_spider.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bevrdbag3eixb" -path="res://.godot/imported/eyes_spider.png-4cbe85913f69207b18d4b23095aca596.ctex" +path.s3tc="res://.godot/imported/eyes_spider.png-4cbe85913f69207b18d4b23095aca596.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_spider.png-4cbe85913f69207b18d4b23095aca596.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_spider.png" -dest_files=["res://.godot/imported/eyes_spider.png-4cbe85913f69207b18d4b23095aca596.ctex"] +dest_files=["res://.godot/imported/eyes_spider.png-4cbe85913f69207b18d4b23095aca596.s3tc.ctex", "res://.godot/imported/eyes_spider.png-4cbe85913f69207b18d4b23095aca596.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_surprised.png.import b/art/exported/characters/faces/eyes/eyes_surprised.png.import index 0d7907b..d83d6c5 100644 --- a/art/exported/characters/faces/eyes/eyes_surprised.png.import +++ b/art/exported/characters/faces/eyes/eyes_surprised.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://chvod4aqeqn10" -path="res://.godot/imported/eyes_surprised.png-21858dc3f7520b1423e3d0aff59b6896.ctex" +path.s3tc="res://.godot/imported/eyes_surprised.png-21858dc3f7520b1423e3d0aff59b6896.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_surprised.png-21858dc3f7520b1423e3d0aff59b6896.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_surprised.png" -dest_files=["res://.godot/imported/eyes_surprised.png-21858dc3f7520b1423e3d0aff59b6896.ctex"] +dest_files=["res://.godot/imported/eyes_surprised.png-21858dc3f7520b1423e3d0aff59b6896.s3tc.ctex", "res://.godot/imported/eyes_surprised.png-21858dc3f7520b1423e3d0aff59b6896.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_tired.png.import b/art/exported/characters/faces/eyes/eyes_tired.png.import index 9de4b0f..70a45ab 100644 --- a/art/exported/characters/faces/eyes/eyes_tired.png.import +++ b/art/exported/characters/faces/eyes/eyes_tired.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bimnlu17t6u6v" -path="res://.godot/imported/eyes_tired.png-752efd150fd50cc078ee25c8bbbeeb36.ctex" +path.s3tc="res://.godot/imported/eyes_tired.png-752efd150fd50cc078ee25c8bbbeeb36.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_tired.png-752efd150fd50cc078ee25c8bbbeeb36.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_tired.png" -dest_files=["res://.godot/imported/eyes_tired.png-752efd150fd50cc078ee25c8bbbeeb36.ctex"] +dest_files=["res://.godot/imported/eyes_tired.png-752efd150fd50cc078ee25c8bbbeeb36.s3tc.ctex", "res://.godot/imported/eyes_tired.png-752efd150fd50cc078ee25c8bbbeeb36.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_tired_flat.png.import b/art/exported/characters/faces/eyes/eyes_tired_flat.png.import index 5a2ec55..831ff6d 100644 --- a/art/exported/characters/faces/eyes/eyes_tired_flat.png.import +++ b/art/exported/characters/faces/eyes/eyes_tired_flat.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b4hmrvoblu3cl" -path="res://.godot/imported/eyes_tired_flat.png-c441cfd39c6147e6a13989a4dd86be05.ctex" +path.s3tc="res://.godot/imported/eyes_tired_flat.png-c441cfd39c6147e6a13989a4dd86be05.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_tired_flat.png-c441cfd39c6147e6a13989a4dd86be05.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_tired_flat.png" -dest_files=["res://.godot/imported/eyes_tired_flat.png-c441cfd39c6147e6a13989a4dd86be05.ctex"] +dest_files=["res://.godot/imported/eyes_tired_flat.png-c441cfd39c6147e6a13989a4dd86be05.s3tc.ctex", "res://.godot/imported/eyes_tired_flat.png-c441cfd39c6147e6a13989a4dd86be05.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/eyes/eyes_upset.png.import b/art/exported/characters/faces/eyes/eyes_upset.png.import index 0c455d4..a0b51d5 100644 --- a/art/exported/characters/faces/eyes/eyes_upset.png.import +++ b/art/exported/characters/faces/eyes/eyes_upset.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://blm1i6xyjclp5" -path="res://.godot/imported/eyes_upset.png-a8509f19cb25f577e84ebe86d28a31b1.ctex" +path.s3tc="res://.godot/imported/eyes_upset.png-a8509f19cb25f577e84ebe86d28a31b1.s3tc.ctex" +path.etc2="res://.godot/imported/eyes_upset.png-a8509f19cb25f577e84ebe86d28a31b1.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/eyes/eyes_upset.png" -dest_files=["res://.godot/imported/eyes_upset.png-a8509f19cb25f577e84ebe86d28a31b1.ctex"] +dest_files=["res://.godot/imported/eyes_upset.png-a8509f19cb25f577e84ebe86d28a31b1.s3tc.ctex", "res://.godot/imported/eyes_upset.png-a8509f19cb25f577e84ebe86d28a31b1.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_3.png.import b/art/exported/characters/faces/mouth/mouth_3.png.import index eab9f48..3da8cbf 100644 --- a/art/exported/characters/faces/mouth/mouth_3.png.import +++ b/art/exported/characters/faces/mouth/mouth_3.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b5wnxcm61q4ou" -path="res://.godot/imported/mouth_3.png-9fd9edc312c3b2c33df59da33d62bb30.ctex" +path.s3tc="res://.godot/imported/mouth_3.png-9fd9edc312c3b2c33df59da33d62bb30.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_3.png-9fd9edc312c3b2c33df59da33d62bb30.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_3.png" -dest_files=["res://.godot/imported/mouth_3.png-9fd9edc312c3b2c33df59da33d62bb30.ctex"] +dest_files=["res://.godot/imported/mouth_3.png-9fd9edc312c3b2c33df59da33d62bb30.s3tc.ctex", "res://.godot/imported/mouth_3.png-9fd9edc312c3b2c33df59da33d62bb30.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_3_cheeky.png.import b/art/exported/characters/faces/mouth/mouth_3_cheeky.png.import index a53b280..f212e57 100644 --- a/art/exported/characters/faces/mouth/mouth_3_cheeky.png.import +++ b/art/exported/characters/faces/mouth/mouth_3_cheeky.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://dhaec7illng60" -path="res://.godot/imported/mouth_3_cheeky.png-1c9ae1bcb18ff29fe44e13b55b692f57.ctex" +path.s3tc="res://.godot/imported/mouth_3_cheeky.png-1c9ae1bcb18ff29fe44e13b55b692f57.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_3_cheeky.png-1c9ae1bcb18ff29fe44e13b55b692f57.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_3_cheeky.png" -dest_files=["res://.godot/imported/mouth_3_cheeky.png-1c9ae1bcb18ff29fe44e13b55b692f57.ctex"] +dest_files=["res://.godot/imported/mouth_3_cheeky.png-1c9ae1bcb18ff29fe44e13b55b692f57.s3tc.ctex", "res://.godot/imported/mouth_3_cheeky.png-1c9ae1bcb18ff29fe44e13b55b692f57.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_buck teeth.png.import b/art/exported/characters/faces/mouth/mouth_buck teeth.png.import index 8909223..6e837c8 100644 --- a/art/exported/characters/faces/mouth/mouth_buck teeth.png.import +++ b/art/exported/characters/faces/mouth/mouth_buck teeth.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://dmvxpg0jbiyqw" -path="res://.godot/imported/mouth_buck teeth.png-84be0f368b98b8640d5cc51d8a9b956f.ctex" +path.s3tc="res://.godot/imported/mouth_buck teeth.png-84be0f368b98b8640d5cc51d8a9b956f.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_buck teeth.png-84be0f368b98b8640d5cc51d8a9b956f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_buck teeth.png" -dest_files=["res://.godot/imported/mouth_buck teeth.png-84be0f368b98b8640d5cc51d8a9b956f.ctex"] +dest_files=["res://.godot/imported/mouth_buck teeth.png-84be0f368b98b8640d5cc51d8a9b956f.s3tc.ctex", "res://.godot/imported/mouth_buck teeth.png-84be0f368b98b8640d5cc51d8a9b956f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_cheeky_smile.png.import b/art/exported/characters/faces/mouth/mouth_cheeky_smile.png.import index 926ef6c..dce960a 100644 --- a/art/exported/characters/faces/mouth/mouth_cheeky_smile.png.import +++ b/art/exported/characters/faces/mouth/mouth_cheeky_smile.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://8tdtfnv3bgft" -path="res://.godot/imported/mouth_cheeky_smile.png-d55ec6f5fd7e73fd8793d524475f9f1f.ctex" +path.s3tc="res://.godot/imported/mouth_cheeky_smile.png-d55ec6f5fd7e73fd8793d524475f9f1f.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_cheeky_smile.png-d55ec6f5fd7e73fd8793d524475f9f1f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_cheeky_smile.png" -dest_files=["res://.godot/imported/mouth_cheeky_smile.png-d55ec6f5fd7e73fd8793d524475f9f1f.ctex"] +dest_files=["res://.godot/imported/mouth_cheeky_smile.png-d55ec6f5fd7e73fd8793d524475f9f1f.s3tc.ctex", "res://.godot/imported/mouth_cheeky_smile.png-d55ec6f5fd7e73fd8793d524475f9f1f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_cheeky_smile_onesided.png.import b/art/exported/characters/faces/mouth/mouth_cheeky_smile_onesided.png.import index 0b1450a..f500e42 100644 --- a/art/exported/characters/faces/mouth/mouth_cheeky_smile_onesided.png.import +++ b/art/exported/characters/faces/mouth/mouth_cheeky_smile_onesided.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://8xyawmp5gru8" -path="res://.godot/imported/mouth_cheeky_smile_onesided.png-366c346f7be47b1521677b84666a672b.ctex" +path.s3tc="res://.godot/imported/mouth_cheeky_smile_onesided.png-366c346f7be47b1521677b84666a672b.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_cheeky_smile_onesided.png-366c346f7be47b1521677b84666a672b.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_cheeky_smile_onesided.png" -dest_files=["res://.godot/imported/mouth_cheeky_smile_onesided.png-366c346f7be47b1521677b84666a672b.ctex"] +dest_files=["res://.godot/imported/mouth_cheeky_smile_onesided.png-366c346f7be47b1521677b84666a672b.s3tc.ctex", "res://.godot/imported/mouth_cheeky_smile_onesided.png-366c346f7be47b1521677b84666a672b.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_jackolantern.png.import b/art/exported/characters/faces/mouth/mouth_jackolantern.png.import index 1756a04..6c0861a 100644 --- a/art/exported/characters/faces/mouth/mouth_jackolantern.png.import +++ b/art/exported/characters/faces/mouth/mouth_jackolantern.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://ck4bbdnpf6ekd" -path="res://.godot/imported/mouth_jackolantern.png-7f9373a2b59e617edd25d92ef63041f0.ctex" +path.s3tc="res://.godot/imported/mouth_jackolantern.png-7f9373a2b59e617edd25d92ef63041f0.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_jackolantern.png-7f9373a2b59e617edd25d92ef63041f0.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_jackolantern.png" -dest_files=["res://.godot/imported/mouth_jackolantern.png-7f9373a2b59e617edd25d92ef63041f0.ctex"] +dest_files=["res://.godot/imported/mouth_jackolantern.png-7f9373a2b59e617edd25d92ef63041f0.s3tc.ctex", "res://.godot/imported/mouth_jackolantern.png-7f9373a2b59e617edd25d92ef63041f0.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_jowls.png.import b/art/exported/characters/faces/mouth/mouth_jowls.png.import index 1af65fe..7f5823b 100644 --- a/art/exported/characters/faces/mouth/mouth_jowls.png.import +++ b/art/exported/characters/faces/mouth/mouth_jowls.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://dyynmci0hf3vq" -path="res://.godot/imported/mouth_jowls.png-88cd6159c02a5b4eee74b98dc8248dcd.ctex" +path.s3tc="res://.godot/imported/mouth_jowls.png-88cd6159c02a5b4eee74b98dc8248dcd.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_jowls.png-88cd6159c02a5b4eee74b98dc8248dcd.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_jowls.png" -dest_files=["res://.godot/imported/mouth_jowls.png-88cd6159c02a5b4eee74b98dc8248dcd.ctex"] +dest_files=["res://.godot/imported/mouth_jowls.png-88cd6159c02a5b4eee74b98dc8248dcd.s3tc.ctex", "res://.godot/imported/mouth_jowls.png-88cd6159c02a5b4eee74b98dc8248dcd.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_jowls_brock.png.import b/art/exported/characters/faces/mouth/mouth_jowls_brock.png.import index d2ec5c4..0461534 100644 --- a/art/exported/characters/faces/mouth/mouth_jowls_brock.png.import +++ b/art/exported/characters/faces/mouth/mouth_jowls_brock.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://duajvlqrieray" -path="res://.godot/imported/mouth_jowls_brock.png-de76cffdce72eb30289c44bcb2e68ce6.ctex" +path.s3tc="res://.godot/imported/mouth_jowls_brock.png-de76cffdce72eb30289c44bcb2e68ce6.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_jowls_brock.png-de76cffdce72eb30289c44bcb2e68ce6.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_jowls_brock.png" -dest_files=["res://.godot/imported/mouth_jowls_brock.png-de76cffdce72eb30289c44bcb2e68ce6.ctex"] +dest_files=["res://.godot/imported/mouth_jowls_brock.png-de76cffdce72eb30289c44bcb2e68ce6.s3tc.ctex", "res://.godot/imported/mouth_jowls_brock.png-de76cffdce72eb30289c44bcb2e68ce6.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_jowls_simple.png.import b/art/exported/characters/faces/mouth/mouth_jowls_simple.png.import index f6cf4a0..fc1ad41 100644 --- a/art/exported/characters/faces/mouth/mouth_jowls_simple.png.import +++ b/art/exported/characters/faces/mouth/mouth_jowls_simple.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://c37gty8itl5oe" -path="res://.godot/imported/mouth_jowls_simple.png-0586400ebfd9ef98c9067f0ebbe730be.ctex" +path.s3tc="res://.godot/imported/mouth_jowls_simple.png-0586400ebfd9ef98c9067f0ebbe730be.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_jowls_simple.png-0586400ebfd9ef98c9067f0ebbe730be.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_jowls_simple.png" -dest_files=["res://.godot/imported/mouth_jowls_simple.png-0586400ebfd9ef98c9067f0ebbe730be.ctex"] +dest_files=["res://.godot/imported/mouth_jowls_simple.png-0586400ebfd9ef98c9067f0ebbe730be.s3tc.ctex", "res://.godot/imported/mouth_jowls_simple.png-0586400ebfd9ef98c9067f0ebbe730be.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_kissy.png.import b/art/exported/characters/faces/mouth/mouth_kissy.png.import index ff0e1c1..1281fa1 100644 --- a/art/exported/characters/faces/mouth/mouth_kissy.png.import +++ b/art/exported/characters/faces/mouth/mouth_kissy.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://druncaq416lvu" -path="res://.godot/imported/mouth_kissy.png-52ec7a06301d8506497442f0ea0054a0.ctex" +path.s3tc="res://.godot/imported/mouth_kissy.png-52ec7a06301d8506497442f0ea0054a0.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_kissy.png-52ec7a06301d8506497442f0ea0054a0.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_kissy.png" -dest_files=["res://.godot/imported/mouth_kissy.png-52ec7a06301d8506497442f0ea0054a0.ctex"] +dest_files=["res://.godot/imported/mouth_kissy.png-52ec7a06301d8506497442f0ea0054a0.s3tc.ctex", "res://.godot/imported/mouth_kissy.png-52ec7a06301d8506497442f0ea0054a0.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_kissy_lips.png.import b/art/exported/characters/faces/mouth/mouth_kissy_lips.png.import index 2141904..3778c31 100644 --- a/art/exported/characters/faces/mouth/mouth_kissy_lips.png.import +++ b/art/exported/characters/faces/mouth/mouth_kissy_lips.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://babmei7rq8uaf" -path="res://.godot/imported/mouth_kissy_lips.png-a90d7b9649dbba8cc17232f59291a2c8.ctex" +path.s3tc="res://.godot/imported/mouth_kissy_lips.png-a90d7b9649dbba8cc17232f59291a2c8.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_kissy_lips.png-a90d7b9649dbba8cc17232f59291a2c8.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_kissy_lips.png" -dest_files=["res://.godot/imported/mouth_kissy_lips.png-a90d7b9649dbba8cc17232f59291a2c8.ctex"] +dest_files=["res://.godot/imported/mouth_kissy_lips.png-a90d7b9649dbba8cc17232f59291a2c8.s3tc.ctex", "res://.godot/imported/mouth_kissy_lips.png-a90d7b9649dbba8cc17232f59291a2c8.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_lil_c.png.import b/art/exported/characters/faces/mouth/mouth_lil_c.png.import index 1a7c28d..06518f1 100644 --- a/art/exported/characters/faces/mouth/mouth_lil_c.png.import +++ b/art/exported/characters/faces/mouth/mouth_lil_c.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bfafrjmy6liy6" -path="res://.godot/imported/mouth_lil_c.png-e65a240e587f99537a3a9f1d3f241323.ctex" +path.s3tc="res://.godot/imported/mouth_lil_c.png-e65a240e587f99537a3a9f1d3f241323.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_lil_c.png-e65a240e587f99537a3a9f1d3f241323.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_lil_c.png" -dest_files=["res://.godot/imported/mouth_lil_c.png-e65a240e587f99537a3a9f1d3f241323.ctex"] +dest_files=["res://.godot/imported/mouth_lil_c.png-e65a240e587f99537a3a9f1d3f241323.s3tc.ctex", "res://.godot/imported/mouth_lil_c.png-e65a240e587f99537a3a9f1d3f241323.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_open_ah.png.import b/art/exported/characters/faces/mouth/mouth_open_ah.png.import index 5f8c2e7..f37ae3a 100644 --- a/art/exported/characters/faces/mouth/mouth_open_ah.png.import +++ b/art/exported/characters/faces/mouth/mouth_open_ah.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://44cqu18imydc" -path="res://.godot/imported/mouth_open_ah.png-2710d397264f57dbe8f9bac82dd4b0de.ctex" +path.s3tc="res://.godot/imported/mouth_open_ah.png-2710d397264f57dbe8f9bac82dd4b0de.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_open_ah.png-2710d397264f57dbe8f9bac82dd4b0de.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_open_ah.png" -dest_files=["res://.godot/imported/mouth_open_ah.png-2710d397264f57dbe8f9bac82dd4b0de.ctex"] +dest_files=["res://.godot/imported/mouth_open_ah.png-2710d397264f57dbe8f9bac82dd4b0de.s3tc.ctex", "res://.godot/imported/mouth_open_ah.png-2710d397264f57dbe8f9bac82dd4b0de.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_open_oh.png.import b/art/exported/characters/faces/mouth/mouth_open_oh.png.import index e4602aa..b5e7a1d 100644 --- a/art/exported/characters/faces/mouth/mouth_open_oh.png.import +++ b/art/exported/characters/faces/mouth/mouth_open_oh.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://c78hwv0t3ph47" -path="res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.ctex" +path.s3tc="res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_open_oh.png" -dest_files=["res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.ctex"] +dest_files=["res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.s3tc.ctex", "res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_open_small.png.import b/art/exported/characters/faces/mouth/mouth_open_small.png.import index 9b15e51..5a83a6e 100644 --- a/art/exported/characters/faces/mouth/mouth_open_small.png.import +++ b/art/exported/characters/faces/mouth/mouth_open_small.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bn68lfabdo7ga" -path="res://.godot/imported/mouth_open_small.png-30e9081c075d21159d6563a981b15378.ctex" +path.s3tc="res://.godot/imported/mouth_open_small.png-30e9081c075d21159d6563a981b15378.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_open_small.png-30e9081c075d21159d6563a981b15378.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_open_small.png" -dest_files=["res://.godot/imported/mouth_open_small.png-30e9081c075d21159d6563a981b15378.ctex"] +dest_files=["res://.godot/imported/mouth_open_small.png-30e9081c075d21159d6563a981b15378.s3tc.ctex", "res://.godot/imported/mouth_open_small.png-30e9081c075d21159d6563a981b15378.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_open_smile.png.import b/art/exported/characters/faces/mouth/mouth_open_smile.png.import index 06e410b..46b3100 100644 --- a/art/exported/characters/faces/mouth/mouth_open_smile.png.import +++ b/art/exported/characters/faces/mouth/mouth_open_smile.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b12jrc4ao3r16" -path="res://.godot/imported/mouth_open_smile.png-2561ffea934645e2eac40ab3a0a6395a.ctex" +path.s3tc="res://.godot/imported/mouth_open_smile.png-2561ffea934645e2eac40ab3a0a6395a.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_open_smile.png-2561ffea934645e2eac40ab3a0a6395a.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_open_smile.png" -dest_files=["res://.godot/imported/mouth_open_smile.png-2561ffea934645e2eac40ab3a0a6395a.ctex"] +dest_files=["res://.godot/imported/mouth_open_smile.png-2561ffea934645e2eac40ab3a0a6395a.s3tc.ctex", "res://.godot/imported/mouth_open_smile.png-2561ffea934645e2eac40ab3a0a6395a.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_p_tounge.png.import b/art/exported/characters/faces/mouth/mouth_p_tounge.png.import index 1a791ce..a49c3ae 100644 --- a/art/exported/characters/faces/mouth/mouth_p_tounge.png.import +++ b/art/exported/characters/faces/mouth/mouth_p_tounge.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://nt1q6gpij6y3" -path="res://.godot/imported/mouth_p_tounge.png-f2e1b9018a58fa3c500b05c19941a1f3.ctex" +path.s3tc="res://.godot/imported/mouth_p_tounge.png-f2e1b9018a58fa3c500b05c19941a1f3.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_p_tounge.png-f2e1b9018a58fa3c500b05c19941a1f3.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_p_tounge.png" -dest_files=["res://.godot/imported/mouth_p_tounge.png-f2e1b9018a58fa3c500b05c19941a1f3.ctex"] +dest_files=["res://.godot/imported/mouth_p_tounge.png-f2e1b9018a58fa3c500b05c19941a1f3.s3tc.ctex", "res://.godot/imported/mouth_p_tounge.png-f2e1b9018a58fa3c500b05c19941a1f3.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_sad.png.import b/art/exported/characters/faces/mouth/mouth_sad.png.import index bfe50b3..a0cce86 100644 --- a/art/exported/characters/faces/mouth/mouth_sad.png.import +++ b/art/exported/characters/faces/mouth/mouth_sad.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://camwvowsgxnlw" -path="res://.godot/imported/mouth_sad.png-ced7361b391677e61acbb1877a8e7156.ctex" +path.s3tc="res://.godot/imported/mouth_sad.png-ced7361b391677e61acbb1877a8e7156.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_sad.png-ced7361b391677e61acbb1877a8e7156.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_sad.png" -dest_files=["res://.godot/imported/mouth_sad.png-ced7361b391677e61acbb1877a8e7156.ctex"] +dest_files=["res://.godot/imported/mouth_sad.png-ced7361b391677e61acbb1877a8e7156.s3tc.ctex", "res://.godot/imported/mouth_sad.png-ced7361b391677e61acbb1877a8e7156.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_sigh.png.import b/art/exported/characters/faces/mouth/mouth_sigh.png.import index 928a5d2..3199031 100644 --- a/art/exported/characters/faces/mouth/mouth_sigh.png.import +++ b/art/exported/characters/faces/mouth/mouth_sigh.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b0842jxg3m1ca" -path="res://.godot/imported/mouth_sigh.png-f58b2992f2709483fde720834a5e8820.ctex" +path.s3tc="res://.godot/imported/mouth_sigh.png-f58b2992f2709483fde720834a5e8820.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_sigh.png-f58b2992f2709483fde720834a5e8820.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_sigh.png" -dest_files=["res://.godot/imported/mouth_sigh.png-f58b2992f2709483fde720834a5e8820.ctex"] +dest_files=["res://.godot/imported/mouth_sigh.png-f58b2992f2709483fde720834a5e8820.s3tc.ctex", "res://.godot/imported/mouth_sigh.png-f58b2992f2709483fde720834a5e8820.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_simple_smile.png.import b/art/exported/characters/faces/mouth/mouth_simple_smile.png.import index 82bd51a..c71a778 100644 --- a/art/exported/characters/faces/mouth/mouth_simple_smile.png.import +++ b/art/exported/characters/faces/mouth/mouth_simple_smile.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://nafybekpya31" -path="res://.godot/imported/mouth_simple_smile.png-029050835f365c4976c1b4e62edbc7c8.ctex" +path.s3tc="res://.godot/imported/mouth_simple_smile.png-029050835f365c4976c1b4e62edbc7c8.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_simple_smile.png-029050835f365c4976c1b4e62edbc7c8.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_simple_smile.png" -dest_files=["res://.godot/imported/mouth_simple_smile.png-029050835f365c4976c1b4e62edbc7c8.ctex"] +dest_files=["res://.godot/imported/mouth_simple_smile.png-029050835f365c4976c1b4e62edbc7c8.s3tc.ctex", "res://.godot/imported/mouth_simple_smile.png-029050835f365c4976c1b4e62edbc7c8.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_slanted.png.import b/art/exported/characters/faces/mouth/mouth_slanted.png.import index 01efb87..80611ac 100644 --- a/art/exported/characters/faces/mouth/mouth_slanted.png.import +++ b/art/exported/characters/faces/mouth/mouth_slanted.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://nuj6ednwjawn" -path="res://.godot/imported/mouth_slanted.png-cb0a217a5bb11b80e53b1a6e3e94f43d.ctex" +path.s3tc="res://.godot/imported/mouth_slanted.png-cb0a217a5bb11b80e53b1a6e3e94f43d.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_slanted.png-cb0a217a5bb11b80e53b1a6e3e94f43d.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_slanted.png" -dest_files=["res://.godot/imported/mouth_slanted.png-cb0a217a5bb11b80e53b1a6e3e94f43d.ctex"] +dest_files=["res://.godot/imported/mouth_slanted.png-cb0a217a5bb11b80e53b1a6e3e94f43d.s3tc.ctex", "res://.godot/imported/mouth_slanted.png-cb0a217a5bb11b80e53b1a6e3e94f43d.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_squigle.png.import b/art/exported/characters/faces/mouth/mouth_squigle.png.import index 980021f..22c7de7 100644 --- a/art/exported/characters/faces/mouth/mouth_squigle.png.import +++ b/art/exported/characters/faces/mouth/mouth_squigle.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://calw27urbe3hk" -path="res://.godot/imported/mouth_squigle.png-263a4acb09e9f29aff38899ef1aac53f.ctex" +path.s3tc="res://.godot/imported/mouth_squigle.png-263a4acb09e9f29aff38899ef1aac53f.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_squigle.png-263a4acb09e9f29aff38899ef1aac53f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_squigle.png" -dest_files=["res://.godot/imported/mouth_squigle.png-263a4acb09e9f29aff38899ef1aac53f.ctex"] +dest_files=["res://.godot/imported/mouth_squigle.png-263a4acb09e9f29aff38899ef1aac53f.s3tc.ctex", "res://.godot/imported/mouth_squigle.png-263a4acb09e9f29aff38899ef1aac53f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_vampy.png.import b/art/exported/characters/faces/mouth/mouth_vampy.png.import index d191c95..cd3d5c0 100644 --- a/art/exported/characters/faces/mouth/mouth_vampy.png.import +++ b/art/exported/characters/faces/mouth/mouth_vampy.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bdpe0lrftqjkq" -path="res://.godot/imported/mouth_vampy.png-0f16a1970484a70927f4063fa1bab422.ctex" +path.s3tc="res://.godot/imported/mouth_vampy.png-0f16a1970484a70927f4063fa1bab422.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_vampy.png-0f16a1970484a70927f4063fa1bab422.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_vampy.png" -dest_files=["res://.godot/imported/mouth_vampy.png-0f16a1970484a70927f4063fa1bab422.ctex"] +dest_files=["res://.godot/imported/mouth_vampy.png-0f16a1970484a70927f4063fa1bab422.s3tc.ctex", "res://.godot/imported/mouth_vampy.png-0f16a1970484a70927f4063fa1bab422.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_whiskers.png.import b/art/exported/characters/faces/mouth/mouth_whiskers.png.import index 7fe6438..799b0f1 100644 --- a/art/exported/characters/faces/mouth/mouth_whiskers.png.import +++ b/art/exported/characters/faces/mouth/mouth_whiskers.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cpxwladf5vobh" -path="res://.godot/imported/mouth_whiskers.png-27b3d6495cd4a57de394a2ad8eb67d39.ctex" +path.s3tc="res://.godot/imported/mouth_whiskers.png-27b3d6495cd4a57de394a2ad8eb67d39.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_whiskers.png-27b3d6495cd4a57de394a2ad8eb67d39.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_whiskers.png" -dest_files=["res://.godot/imported/mouth_whiskers.png-27b3d6495cd4a57de394a2ad8eb67d39.ctex"] +dest_files=["res://.godot/imported/mouth_whiskers.png-27b3d6495cd4a57de394a2ad8eb67d39.s3tc.ctex", "res://.godot/imported/mouth_whiskers.png-27b3d6495cd4a57de394a2ad8eb67d39.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_y.png.import b/art/exported/characters/faces/mouth/mouth_y.png.import index 989fa6b..916f91f 100644 --- a/art/exported/characters/faces/mouth/mouth_y.png.import +++ b/art/exported/characters/faces/mouth/mouth_y.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://6w65l4rjbgq" -path="res://.godot/imported/mouth_y.png-08338d9ca7fd2d139dd64add4385096d.ctex" +path.s3tc="res://.godot/imported/mouth_y.png-08338d9ca7fd2d139dd64add4385096d.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_y.png-08338d9ca7fd2d139dd64add4385096d.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_y.png" -dest_files=["res://.godot/imported/mouth_y.png-08338d9ca7fd2d139dd64add4385096d.ctex"] +dest_files=["res://.godot/imported/mouth_y.png-08338d9ca7fd2d139dd64add4385096d.s3tc.ctex", "res://.godot/imported/mouth_y.png-08338d9ca7fd2d139dd64add4385096d.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_y_small.png.import b/art/exported/characters/faces/mouth/mouth_y_small.png.import index 205579e..80dedf9 100644 --- a/art/exported/characters/faces/mouth/mouth_y_small.png.import +++ b/art/exported/characters/faces/mouth/mouth_y_small.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cchv8fvnqbtm2" -path="res://.godot/imported/mouth_y_small.png-493215adfb2e980f01f69152d24a8ef4.ctex" +path.s3tc="res://.godot/imported/mouth_y_small.png-493215adfb2e980f01f69152d24a8ef4.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_y_small.png-493215adfb2e980f01f69152d24a8ef4.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_y_small.png" -dest_files=["res://.godot/imported/mouth_y_small.png-493215adfb2e980f01f69152d24a8ef4.ctex"] +dest_files=["res://.godot/imported/mouth_y_small.png-493215adfb2e980f01f69152d24a8ef4.s3tc.ctex", "res://.godot/imported/mouth_y_small.png-493215adfb2e980f01f69152d24a8ef4.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/mouth/mouth_y_small_pink.png.import b/art/exported/characters/faces/mouth/mouth_y_small_pink.png.import index f8237f1..efe6ac8 100644 --- a/art/exported/characters/faces/mouth/mouth_y_small_pink.png.import +++ b/art/exported/characters/faces/mouth/mouth_y_small_pink.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://dvqdmhhiet5ix" -path="res://.godot/imported/mouth_y_small_pink.png-febc93e05453be68458eff8bab3d21c1.ctex" +path.s3tc="res://.godot/imported/mouth_y_small_pink.png-febc93e05453be68458eff8bab3d21c1.s3tc.ctex" +path.etc2="res://.godot/imported/mouth_y_small_pink.png-febc93e05453be68458eff8bab3d21c1.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/mouth/mouth_y_small_pink.png" -dest_files=["res://.godot/imported/mouth_y_small_pink.png-febc93e05453be68458eff8bab3d21c1.ctex"] +dest_files=["res://.godot/imported/mouth_y_small_pink.png-febc93e05453be68458eff8bab3d21c1.s3tc.ctex", "res://.godot/imported/mouth_y_small_pink.png-febc93e05453be68458eff8bab3d21c1.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_cat_real.png.import b/art/exported/characters/faces/noses/nose_cat_real.png.import index aaedb17..8e1a496 100644 --- a/art/exported/characters/faces/noses/nose_cat_real.png.import +++ b/art/exported/characters/faces/noses/nose_cat_real.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://5n0kihgof38n" -path="res://.godot/imported/nose_cat_real.png-25c7e8464e384043f0969f191e2c2950.ctex" +path.s3tc="res://.godot/imported/nose_cat_real.png-25c7e8464e384043f0969f191e2c2950.s3tc.ctex" +path.etc2="res://.godot/imported/nose_cat_real.png-25c7e8464e384043f0969f191e2c2950.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_cat_real.png" -dest_files=["res://.godot/imported/nose_cat_real.png-25c7e8464e384043f0969f191e2c2950.ctex"] +dest_files=["res://.godot/imported/nose_cat_real.png-25c7e8464e384043f0969f191e2c2950.s3tc.ctex", "res://.godot/imported/nose_cat_real.png-25c7e8464e384043f0969f191e2c2950.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_clown.png.import b/art/exported/characters/faces/noses/nose_clown.png.import index a9afce1..1a642e3 100644 --- a/art/exported/characters/faces/noses/nose_clown.png.import +++ b/art/exported/characters/faces/noses/nose_clown.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cc0nb34t7helb" -path="res://.godot/imported/nose_clown.png-bfa33a86cf06540d45ba440868a39bb3.ctex" +path.s3tc="res://.godot/imported/nose_clown.png-bfa33a86cf06540d45ba440868a39bb3.s3tc.ctex" +path.etc2="res://.godot/imported/nose_clown.png-bfa33a86cf06540d45ba440868a39bb3.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_clown.png" -dest_files=["res://.godot/imported/nose_clown.png-bfa33a86cf06540d45ba440868a39bb3.ctex"] +dest_files=["res://.godot/imported/nose_clown.png-bfa33a86cf06540d45ba440868a39bb3.s3tc.ctex", "res://.godot/imported/nose_clown.png-bfa33a86cf06540d45ba440868a39bb3.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_dog_real.png.import b/art/exported/characters/faces/noses/nose_dog_real.png.import index 9fe82cc..316c80e 100644 --- a/art/exported/characters/faces/noses/nose_dog_real.png.import +++ b/art/exported/characters/faces/noses/nose_dog_real.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b8jwky86fl3ky" -path="res://.godot/imported/nose_dog_real.png-f065da766aead62076c01fa08fbffb2a.ctex" +path.s3tc="res://.godot/imported/nose_dog_real.png-f065da766aead62076c01fa08fbffb2a.s3tc.ctex" +path.etc2="res://.godot/imported/nose_dog_real.png-f065da766aead62076c01fa08fbffb2a.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_dog_real.png" -dest_files=["res://.godot/imported/nose_dog_real.png-f065da766aead62076c01fa08fbffb2a.ctex"] +dest_files=["res://.godot/imported/nose_dog_real.png-f065da766aead62076c01fa08fbffb2a.s3tc.ctex", "res://.godot/imported/nose_dog_real.png-f065da766aead62076c01fa08fbffb2a.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_dog_simple.png.import b/art/exported/characters/faces/noses/nose_dog_simple.png.import index f61b74a..344af2e 100644 --- a/art/exported/characters/faces/noses/nose_dog_simple.png.import +++ b/art/exported/characters/faces/noses/nose_dog_simple.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://b6d4ubqjijjux" -path="res://.godot/imported/nose_dog_simple.png-fdc0a92e3e2c82271db9ea87256058a7.ctex" +path.s3tc="res://.godot/imported/nose_dog_simple.png-fdc0a92e3e2c82271db9ea87256058a7.s3tc.ctex" +path.etc2="res://.godot/imported/nose_dog_simple.png-fdc0a92e3e2c82271db9ea87256058a7.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_dog_simple.png" -dest_files=["res://.godot/imported/nose_dog_simple.png-fdc0a92e3e2c82271db9ea87256058a7.ctex"] +dest_files=["res://.godot/imported/nose_dog_simple.png-fdc0a92e3e2c82271db9ea87256058a7.s3tc.ctex", "res://.godot/imported/nose_dog_simple.png-fdc0a92e3e2c82271db9ea87256058a7.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_dragon.png.import b/art/exported/characters/faces/noses/nose_dragon.png.import index 0ba2695..92d98eb 100644 --- a/art/exported/characters/faces/noses/nose_dragon.png.import +++ b/art/exported/characters/faces/noses/nose_dragon.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://8n1bhbyt5id6" -path="res://.godot/imported/nose_dragon.png-6c98cf6f5692ec43ffe43650115cd010.ctex" +path.s3tc="res://.godot/imported/nose_dragon.png-6c98cf6f5692ec43ffe43650115cd010.s3tc.ctex" +path.etc2="res://.godot/imported/nose_dragon.png-6c98cf6f5692ec43ffe43650115cd010.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_dragon.png" -dest_files=["res://.godot/imported/nose_dragon.png-6c98cf6f5692ec43ffe43650115cd010.ctex"] +dest_files=["res://.godot/imported/nose_dragon.png-6c98cf6f5692ec43ffe43650115cd010.s3tc.ctex", "res://.godot/imported/nose_dragon.png-6c98cf6f5692ec43ffe43650115cd010.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_hippo.png.import b/art/exported/characters/faces/noses/nose_hippo.png.import index 0206809..3e5736f 100644 --- a/art/exported/characters/faces/noses/nose_hippo.png.import +++ b/art/exported/characters/faces/noses/nose_hippo.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bmfa44uf6u4or" -path="res://.godot/imported/nose_hippo.png-059f63ab00597454d369b0d51b5712a6.ctex" +path.s3tc="res://.godot/imported/nose_hippo.png-059f63ab00597454d369b0d51b5712a6.s3tc.ctex" +path.etc2="res://.godot/imported/nose_hippo.png-059f63ab00597454d369b0d51b5712a6.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_hippo.png" -dest_files=["res://.godot/imported/nose_hippo.png-059f63ab00597454d369b0d51b5712a6.ctex"] +dest_files=["res://.godot/imported/nose_hippo.png-059f63ab00597454d369b0d51b5712a6.s3tc.ctex", "res://.godot/imported/nose_hippo.png-059f63ab00597454d369b0d51b5712a6.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_human.png.import b/art/exported/characters/faces/noses/nose_human.png.import index 2381527..486fa00 100644 --- a/art/exported/characters/faces/noses/nose_human.png.import +++ b/art/exported/characters/faces/noses/nose_human.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cuthoacwggro3" -path="res://.godot/imported/nose_human.png-337d9ba4f724d6241a6fa9a307a30008.ctex" +path.s3tc="res://.godot/imported/nose_human.png-337d9ba4f724d6241a6fa9a307a30008.s3tc.ctex" +path.etc2="res://.godot/imported/nose_human.png-337d9ba4f724d6241a6fa9a307a30008.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_human.png" -dest_files=["res://.godot/imported/nose_human.png-337d9ba4f724d6241a6fa9a307a30008.ctex"] +dest_files=["res://.godot/imported/nose_human.png-337d9ba4f724d6241a6fa9a307a30008.s3tc.ctex", "res://.godot/imported/nose_human.png-337d9ba4f724d6241a6fa9a307a30008.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_nostrils.png.import b/art/exported/characters/faces/noses/nose_nostrils.png.import index 8e9c447..68ae74a 100644 --- a/art/exported/characters/faces/noses/nose_nostrils.png.import +++ b/art/exported/characters/faces/noses/nose_nostrils.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bvu2lox5px3ab" -path="res://.godot/imported/nose_nostrils.png-9e1c2b5c8e7adb019d72a39b1e805781.ctex" +path.s3tc="res://.godot/imported/nose_nostrils.png-9e1c2b5c8e7adb019d72a39b1e805781.s3tc.ctex" +path.etc2="res://.godot/imported/nose_nostrils.png-9e1c2b5c8e7adb019d72a39b1e805781.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_nostrils.png" -dest_files=["res://.godot/imported/nose_nostrils.png-9e1c2b5c8e7adb019d72a39b1e805781.ctex"] +dest_files=["res://.godot/imported/nose_nostrils.png-9e1c2b5c8e7adb019d72a39b1e805781.s3tc.ctex", "res://.godot/imported/nose_nostrils.png-9e1c2b5c8e7adb019d72a39b1e805781.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_piercing.png.import b/art/exported/characters/faces/noses/nose_piercing.png.import index 3584e75..d50d3e0 100644 --- a/art/exported/characters/faces/noses/nose_piercing.png.import +++ b/art/exported/characters/faces/noses/nose_piercing.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://3w37sblwbd8b" -path="res://.godot/imported/nose_piercing.png-0c3316905c9f379988cc52353c88569c.ctex" +path.s3tc="res://.godot/imported/nose_piercing.png-0c3316905c9f379988cc52353c88569c.s3tc.ctex" +path.etc2="res://.godot/imported/nose_piercing.png-0c3316905c9f379988cc52353c88569c.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_piercing.png" -dest_files=["res://.godot/imported/nose_piercing.png-0c3316905c9f379988cc52353c88569c.ctex"] +dest_files=["res://.godot/imported/nose_piercing.png-0c3316905c9f379988cc52353c88569c.s3tc.ctex", "res://.godot/imported/nose_piercing.png-0c3316905c9f379988cc52353c88569c.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_pig_snout.png.import b/art/exported/characters/faces/noses/nose_pig_snout.png.import index 5e34829..9ead70a 100644 --- a/art/exported/characters/faces/noses/nose_pig_snout.png.import +++ b/art/exported/characters/faces/noses/nose_pig_snout.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bvran6dr0u22l" -path="res://.godot/imported/nose_pig_snout.png-40e08884db61f222d4371c6c6a798bca.ctex" +path.s3tc="res://.godot/imported/nose_pig_snout.png-40e08884db61f222d4371c6c6a798bca.s3tc.ctex" +path.etc2="res://.godot/imported/nose_pig_snout.png-40e08884db61f222d4371c6c6a798bca.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_pig_snout.png" -dest_files=["res://.godot/imported/nose_pig_snout.png-40e08884db61f222d4371c6c6a798bca.ctex"] +dest_files=["res://.godot/imported/nose_pig_snout.png-40e08884db61f222d4371c6c6a798bca.s3tc.ctex", "res://.godot/imported/nose_pig_snout.png-40e08884db61f222d4371c6c6a798bca.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_pig_snout_pink.png.import b/art/exported/characters/faces/noses/nose_pig_snout_pink.png.import index 52ab860..c66209b 100644 --- a/art/exported/characters/faces/noses/nose_pig_snout_pink.png.import +++ b/art/exported/characters/faces/noses/nose_pig_snout_pink.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cyigqco0dp8ph" -path="res://.godot/imported/nose_pig_snout_pink.png-da6fcfc06a8aa02d6ad664e2ed669558.ctex" +path.s3tc="res://.godot/imported/nose_pig_snout_pink.png-da6fcfc06a8aa02d6ad664e2ed669558.s3tc.ctex" +path.etc2="res://.godot/imported/nose_pig_snout_pink.png-da6fcfc06a8aa02d6ad664e2ed669558.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_pig_snout_pink.png" -dest_files=["res://.godot/imported/nose_pig_snout_pink.png-da6fcfc06a8aa02d6ad664e2ed669558.ctex"] +dest_files=["res://.godot/imported/nose_pig_snout_pink.png-da6fcfc06a8aa02d6ad664e2ed669558.s3tc.ctex", "res://.godot/imported/nose_pig_snout_pink.png-da6fcfc06a8aa02d6ad664e2ed669558.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_squidward.png.import b/art/exported/characters/faces/noses/nose_squidward.png.import index dbba070..ab07b93 100644 --- a/art/exported/characters/faces/noses/nose_squidward.png.import +++ b/art/exported/characters/faces/noses/nose_squidward.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://ilvb5vre4kv7" -path="res://.godot/imported/nose_squidward.png-579e15cf6acfb402abda29a8a555d504.ctex" +path.s3tc="res://.godot/imported/nose_squidward.png-579e15cf6acfb402abda29a8a555d504.s3tc.ctex" +path.etc2="res://.godot/imported/nose_squidward.png-579e15cf6acfb402abda29a8a555d504.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_squidward.png" -dest_files=["res://.godot/imported/nose_squidward.png-579e15cf6acfb402abda29a8a555d504.ctex"] +dest_files=["res://.godot/imported/nose_squidward.png-579e15cf6acfb402abda29a8a555d504.s3tc.ctex", "res://.godot/imported/nose_squidward.png-579e15cf6acfb402abda29a8a555d504.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_squidward_wide.png.import b/art/exported/characters/faces/noses/nose_squidward_wide.png.import index 92d6c8a..cf307ce 100644 --- a/art/exported/characters/faces/noses/nose_squidward_wide.png.import +++ b/art/exported/characters/faces/noses/nose_squidward_wide.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://dtisqywq2rv14" -path="res://.godot/imported/nose_squidward_wide.png-fca231f913f03b97c273c870770f7948.ctex" +path.s3tc="res://.godot/imported/nose_squidward_wide.png-fca231f913f03b97c273c870770f7948.s3tc.ctex" +path.etc2="res://.godot/imported/nose_squidward_wide.png-fca231f913f03b97c273c870770f7948.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_squidward_wide.png" -dest_files=["res://.godot/imported/nose_squidward_wide.png-fca231f913f03b97c273c870770f7948.ctex"] +dest_files=["res://.godot/imported/nose_squidward_wide.png-fca231f913f03b97c273c870770f7948.s3tc.ctex", "res://.godot/imported/nose_squidward_wide.png-fca231f913f03b97c273c870770f7948.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_triangle.png.import b/art/exported/characters/faces/noses/nose_triangle.png.import index bc7aa21..ba494a2 100644 --- a/art/exported/characters/faces/noses/nose_triangle.png.import +++ b/art/exported/characters/faces/noses/nose_triangle.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://bitins3gtewe" -path="res://.godot/imported/nose_triangle.png-018974a6bbf68652b8c5533c2675cc2f.ctex" +path.s3tc="res://.godot/imported/nose_triangle.png-018974a6bbf68652b8c5533c2675cc2f.s3tc.ctex" +path.etc2="res://.godot/imported/nose_triangle.png-018974a6bbf68652b8c5533c2675cc2f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_triangle.png" -dest_files=["res://.godot/imported/nose_triangle.png-018974a6bbf68652b8c5533c2675cc2f.ctex"] +dest_files=["res://.godot/imported/nose_triangle.png-018974a6bbf68652b8c5533c2675cc2f.s3tc.ctex", "res://.godot/imported/nose_triangle.png-018974a6bbf68652b8c5533c2675cc2f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_triangle_pink.png.import b/art/exported/characters/faces/noses/nose_triangle_pink.png.import index 56712ab..eff0bb1 100644 --- a/art/exported/characters/faces/noses/nose_triangle_pink.png.import +++ b/art/exported/characters/faces/noses/nose_triangle_pink.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://oi8qhxbtijmk" -path="res://.godot/imported/nose_triangle_pink.png-051ce6df5e7fd5932f19fd353123d7d1.ctex" +path.s3tc="res://.godot/imported/nose_triangle_pink.png-051ce6df5e7fd5932f19fd353123d7d1.s3tc.ctex" +path.etc2="res://.godot/imported/nose_triangle_pink.png-051ce6df5e7fd5932f19fd353123d7d1.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_triangle_pink.png" -dest_files=["res://.godot/imported/nose_triangle_pink.png-051ce6df5e7fd5932f19fd353123d7d1.ctex"] +dest_files=["res://.godot/imported/nose_triangle_pink.png-051ce6df5e7fd5932f19fd353123d7d1.s3tc.ctex", "res://.godot/imported/nose_triangle_pink.png-051ce6df5e7fd5932f19fd353123d7d1.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_triangle_small.png.import b/art/exported/characters/faces/noses/nose_triangle_small.png.import index a659077..e704938 100644 --- a/art/exported/characters/faces/noses/nose_triangle_small.png.import +++ b/art/exported/characters/faces/noses/nose_triangle_small.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://cedgrca26ponk" -path="res://.godot/imported/nose_triangle_small.png-64129c5fc69bf7095f684c2b7de2855b.ctex" +path.s3tc="res://.godot/imported/nose_triangle_small.png-64129c5fc69bf7095f684c2b7de2855b.s3tc.ctex" +path.etc2="res://.godot/imported/nose_triangle_small.png-64129c5fc69bf7095f684c2b7de2855b.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_triangle_small.png" -dest_files=["res://.godot/imported/nose_triangle_small.png-64129c5fc69bf7095f684c2b7de2855b.ctex"] +dest_files=["res://.godot/imported/nose_triangle_small.png-64129c5fc69bf7095f684c2b7de2855b.s3tc.ctex", "res://.godot/imported/nose_triangle_small.png-64129c5fc69bf7095f684c2b7de2855b.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_triangle_small_pink.png.import b/art/exported/characters/faces/noses/nose_triangle_small_pink.png.import index c875ba4..b3060e6 100644 --- a/art/exported/characters/faces/noses/nose_triangle_small_pink.png.import +++ b/art/exported/characters/faces/noses/nose_triangle_small_pink.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://0kird82ka1ug" -path="res://.godot/imported/nose_triangle_small_pink.png-fba736a5f1806c1286f1d3040592933b.ctex" +path.s3tc="res://.godot/imported/nose_triangle_small_pink.png-fba736a5f1806c1286f1d3040592933b.s3tc.ctex" +path.etc2="res://.godot/imported/nose_triangle_small_pink.png-fba736a5f1806c1286f1d3040592933b.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_triangle_small_pink.png" -dest_files=["res://.godot/imported/nose_triangle_small_pink.png-fba736a5f1806c1286f1d3040592933b.ctex"] +dest_files=["res://.godot/imported/nose_triangle_small_pink.png-fba736a5f1806c1286f1d3040592933b.s3tc.ctex", "res://.godot/imported/nose_triangle_small_pink.png-fba736a5f1806c1286f1d3040592933b.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/art/exported/characters/faces/noses/nose_under.png.import b/art/exported/characters/faces/noses/nose_under.png.import index 5bf860f..d0c87ea 100644 --- a/art/exported/characters/faces/noses/nose_under.png.import +++ b/art/exported/characters/faces/noses/nose_under.png.import @@ -3,19 +3,21 @@ importer="texture" type="CompressedTexture2D" uid="uid://u0ih35v73j5h" -path="res://.godot/imported/nose_under.png-269bae96966b3c6367d43ac9bd5b2a3f.ctex" +path.s3tc="res://.godot/imported/nose_under.png-269bae96966b3c6367d43ac9bd5b2a3f.s3tc.ctex" +path.etc2="res://.godot/imported/nose_under.png-269bae96966b3c6367d43ac9bd5b2a3f.etc2.ctex" metadata={ -"vram_texture": false +"imported_formats": ["s3tc_bptc", "etc2_astc"], +"vram_texture": true } [deps] source_file="res://art/exported/characters/faces/noses/nose_under.png" -dest_files=["res://.godot/imported/nose_under.png-269bae96966b3c6367d43ac9bd5b2a3f.ctex"] +dest_files=["res://.godot/imported/nose_under.png-269bae96966b3c6367d43ac9bd5b2a3f.s3tc.ctex", "res://.godot/imported/nose_under.png-269bae96966b3c6367d43ac9bd5b2a3f.etc2.ctex"] [params] -compress/mode=0 +compress/mode=2 compress/high_quality=false compress/lossy_quality=0.7 compress/uastc_level=0 @@ -36,5 +38,5 @@ process/premult_alpha=false process/normal_map_invert_y=false process/hdr_as_srgb=false process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 +process/size_limit=512 +detect_3d/compress_to=0 diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 63d3b08..a3853b4 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -265,6 +265,12 @@ snapshots, so do not casually rename them. Preserve the established RGBA transparent canvas. Restart a development build after adding an image; rebuild exports to package new `res://` files. +The tracked import policy keeps these facial-feature textures at a maximum +runtime size of 512×512, uses VRAM compression, and does not generate mipmaps. +The source PNGs remain unchanged. Run the texture import normalizer shown below +after adding facial artwork so low-memory builds do not retain full-resolution +copies of the entire customization catalog. + ### Texture sampling NETfishing artwork always uses nearest-neighbor sampling. Do not enable linear, diff --git a/docs/PORTMASTER.md b/docs/PORTMASTER.md index 2cd6038..70f7c0e 100644 --- a/docs/PORTMASTER.md +++ b/docs/PORTMASTER.md @@ -140,30 +140,52 @@ following in the installed game: - A single button never cancels auto-map. - Holding both bumpers together for 1.25 seconds cancels auto-map. -## Low-end performance profile +## PortMaster performance profile -The launcher keeps the normal game profile on unknown and stronger hardware. -It enables the low-end profile only when the Linux device tree reports one of -the identifiers used by Allwinner H616/H700 XX handhelds: +Every PortMaster launch defaults to the light performance profile, regardless +of device family. This reflects the low-end hardware that makes up most of the +PortMaster ecosystem and keeps the canonical package conservative by default. -- `allwinner,h616` -- `sun50iw9p1` -- `allwinner,sun50i-h700` - -The low-end profile passes these Godot options: +The light profile passes these Godot options: - `--single-window` - `--disable-vsync` - `--max-fps 30` - `--audio-output-latency 40` -It also passes `NETFISHING_LOW_END=1`. The game renders the 3D world at 75% -linear resolution with nearest-neighbor scaling, reducing 3D pixel work by -about 44% while the separately rendered UI retains its canonical resolution. +It also passes `NETFISHING_PERFORMANCE_PROFILE=light` and +`NETFISHING_LOW_END=1`. The game renders the 3D world at 50% linear resolution +with nearest-neighbor scaling, reducing 3D pixel work by 75% while the +separately rendered UI retains its canonical resolution. The light profile +also: -Do not use Godot's low processor mode for this profile. It reduces idle CPU -usage by sleeping between updates and is not a game-performance optimization. +- disables the additional full-screen world-pixelation pass; +- replaces animated depth-aware water shaders with opaque, per-vertex water; +- disables ocean surface motion; +- reduces rain to 256 particles simulated at 15 FPS; and +- replaces procedural sky clouds and 81 moving local cloud patches with one + flat cloud ceiling. -Before adding a device family, record its exact NUL-separated device-tree -`compatible` value from hardware. Do not infer detection from a retail product -name alone. +The normal profile retains the full visual presentation. + +To opt a capable device into the normal profile, create the persistent file +`netfishing/conf/performance_profile` containing exactly: + +```text +normal +``` + +On a typical muOS installation, the full path is: + +```text +/mnt/mmc/ports/netfishing/conf/performance_profile +``` + +Set the file to `light`, or remove it, to restore the default. An externally +provided `NETFISHING_PERFORMANCE_PROFILE=normal` or `light` environment value +takes precedence over the persistent file. Invalid values safely fall back to +the light profile and are reported in `netfishing/log.txt`. + +Do not use Godot's low processor mode for the light profile. It reduces idle +CPU usage by sleeping between updates and is not a game-performance +optimization. diff --git a/docs/README-PLAYTEST.txt b/docs/README-PLAYTEST.txt index a73faf0..c9d8e61 100644 --- a/docs/README-PLAYTEST.txt +++ b/docs/README-PLAYTEST.txt @@ -1,6 +1,6 @@ NETfishing -v0.6.8-alpha -Alpha 0.6.8 +v0.8.1-alpha +Alpha 0.8.1 Thank you for trying this early private playtest. diff --git a/docs/design/.gdignore b/docs/design/.gdignore new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/docs/design/.gdignore @@ -0,0 +1 @@ + diff --git a/docs/design/fish_catalog_proposal.csv b/docs/design/fish_catalog_proposal.csv new file mode 100644 index 0000000..b583915 --- /dev/null +++ b/docs/design/fish_catalog_proposal.csv @@ -0,0 +1,314 @@ +catalog_number,display_name,collection_group,water_type,spawn_area,day_rule,rain_rule,fog_rule,rarity,base_catch_weight,weight_min_lb,weight_max_lb,sell_value_min,sell_value_max,sell_value_curve,required_bait_tags,required_event_tags,forbidden_event_tags,logbook_fact,active_status +101,bowfin,Ancient freshwater fish,Fresh Water,pond,Night,Any,Required,Rare,0.28,1,20,13,30,0.85,,,,bowfin can gulp air at the surface when warm backwaters run low on oxygen. a long dorsal fin ripples as they stalk prey.,inactive +102,lake sturgeon,Ancient freshwater fish,Fresh Water,lake,Night,Any,Any,Epic,0.09,10,180,36,82,0.8,,,,lake sturgeon sweep the bottom with four sensitive barbels before extending a tube-like mouth to feed. they can live for decades.,inactive +103,longnose gar,Ancient freshwater fish,Fresh Water,river,Day,Any,Any,Rare,0.24,2,35,14,34,0.85,,,,longnose gar wait near the surface before snapping sideways at passing fish. their narrow jaws carry rows of fine needle-like teeth.,inactive +104,paddlefish,Ancient freshwater fish,Fresh Water,river,Day,Required,Any,Epic,0.1,10,120,34,76,0.82,,,,"paddlefish cruise open rivers with their mouths wide, filtering tiny plankton from the water. the broad snout senses faint electrical signals.",inactive +105,shovelnose sturgeon,Ancient freshwater fish,Fresh Water,river,Night,Any,Any,Rare,0.22,1,8,12,25,0.9,,,,shovelnose sturgeon hug flowing river bottoms and probe for insect larvae. a flattened snout helps them hold position in strong current.,inactive +106,spotted gar,Ancient freshwater fish,Fresh Water,pond,Day,Any,Required,Rare,0.2,1,15,13,29,0.88,,,,spotted gar linger among vegetation where their mottled bodies break up their outline. they rise to gulp air as well as breathing through gills.,inactive +107,West African lungfish,Ancient freshwater fish,Fresh Water,pond,Night,Required,Any,Rare,0.3,1,10,12,28,0.86,,,,"west african lungfish breathe air with paired lungs. when floodplains dry, they seal themselves in a mud cocoon until water returns.",inactive +201,atlantic mudskipper,Brackish oddities,Salt Water,ocean,Day,Required,Any,Uncommon,1,0.03,0.5,5,11,0.95,,,,atlantic mudskippers use sturdy fins to hop and crawl across exposed mud. moist skin and mouth linings let them breathe out of water.,inactive +202,largescale foureyes,Brackish oddities,Fresh Water | Salt Water,river,Day,Any,Any,Rare,0.32,0.05,1,10,23,0.95,,,,largescale foureyes cruise with each eye divided across the waterline. one half watches above while the other scans below.,inactive +301,red mullet,Coastal bottom fish,Salt Water,ocean,Day,Any,Any,Uncommon,0.9,0.1,3,5,11,0.92,,,,red mullet comb sandy bottoms with two chin barbels that taste hidden prey. their foraging leaves winding trails in the sediment.,inactive +401,European plaice,Coastal flatfish,Salt Water,ocean,Day,Any,Any,Common,1.8,0.2,10,4,10,0.9,,,,european plaice lie flat on the seafloor with both eyes on the same side. orange spots help distinguish them from neighboring flatfish.,inactive +402,summer flounder,Coastal flatfish,Salt Water,ocean,Day,Any,Any,Uncommon,0.9,0.3,15,6,16,0.88,,,,summer flounder bury themselves in sand with only their eyes exposed. a sudden burst carries them upward to seize passing prey.,inactive +403,winter flounder,Coastal flatfish,Salt Water,ocean,Day,Forbidden,Any,Common,1.8,0.2,8,4,9,0.9,,,,winter flounder favor cool bays and estuaries where they rest on muddy bottoms. their eyes sit on the right side of the body.,inactive +501,dugong,Coastal marine mammals,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.011,500,1100,72,165,0.77,,,,"dugongs graze seagrass meadows with a downturned, bristled snout. they are the only fully marine members of the sirenian family.",inactive +502,harbor seal,Coastal marine mammals,Salt Water,ocean,Day,Any,Required,Legendary,0.018,120,370,60,140,0.79,,,,harbor seals rest on rocky shores and sandbars between hunting trips. sensitive whiskers can trace the water trails left by moving fish.,inactive +503,sea otter,Coastal marine mammals,Salt Water,ocean,Day,Any,Any,Legendary,0.02,30,100,55,125,0.84,,,,sea otters float on their backs while opening shellfish with stones. dense fur traps insulating air because they lack a thick blubber layer.,inactive +504,walrus,Coastal marine mammals,Salt Water,ocean,Day,Any,Required,Legendary,0.012,800,3700,74,170,0.76,,,,walruses sweep the seafloor for clams and other soft-bodied prey. long tusks help them haul onto ice and settle disputes.,inactive +505,West Indian manatee,Coastal marine mammals,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.012,440,1300,70,160,0.77,,,,west indian manatees graze aquatic plants in warm coastal water and rivers. they surface regularly to breathe through nostrils atop the snout.,inactive +601,bay pipefish,Coastal oddities,Salt Water,ocean,Day,Any,Any,Uncommon,0.9,0.005,0.04,4,9,1,,,,"bay pipefish hide upright among seagrass, matching the blades with their slender bodies. males carry developing eggs in a brood pouch.",inactive +602,lined seahorse,Coastal oddities,Salt Water,ocean,Day,Forbidden,Any,Rare,0.28,0.005,0.05,9,20,1,,,,lined seahorses anchor to grass and coral with curling tails. the male broods eggs in a pouch and releases fully formed young.,inactive +603,monkfish,Coastal oddities,Salt Water,ocean,Night,Any,Required,Rare,0.25,2,55,14,35,0.84,,,,monkfish hide against the bottom and wave a fleshy lure above a cavernous mouth. a sudden gulp draws fish and crustaceans inward.,inactive +604,tripletail,Coastal oddities,Salt Water,ocean,Day,Any,Required,Rare,0.28,1,30,13,31,0.85,,,,"tripletail often drift sideways near floating debris, resembling a loose leaf. rounded rear fins create the three-tailed silhouette.",inactive +701,atlantic mackerel,Coastal pelagic fish,Salt Water,ocean,Any,Any,Any,Common,0.5,0.5,5,4,9,0.9,,,,atlantic mackerel travel in fast schools across the north atlantic and feed on plankton and small schooling fish.,active +702,bluefish,Coastal pelagic fish,Salt Water,ocean,Day,Any,Any,Uncommon,0.85,0.5,25,7,19,0.85,,,,bluefish race in coordinated schools while pursuing smaller fish. sharp teeth and powerful jaws make their feeding bursts especially fierce.,inactive +703,cero mackerel,Coastal pelagic fish,Salt Water,ocean,Any,Any,Any,Uncommon,0.35,1,15,7,15,0.9,,,,cero mackerel are streamlined coastal hunters. sharp teeth help them slash through schools of smaller fish.,active +704,chub mackerel,Coastal pelagic fish,Salt Water,ocean,Any,Any,Any,Common,0.5,0.5,4,4,9,0.9,,,,chub mackerel school near the surface and follow plankton blooms. their dark wavy bars help identify them at a glance.,active +705,crevalle jack,Coastal pelagic fish,Salt Water,ocean,Day,Required,Any,Uncommon,0.8,1,35,7,20,0.84,,,,"crevalle jacks patrol warm coasts in fast-moving schools. their deep bodies and forked tails deliver long, powerful runs.",inactive +706,king mackerel,Coastal pelagic fish,Salt Water,ocean,Any,Any,Any,Uncommon,0.3,3,40,10,24,0.9,,,,king mackerel are swift coastal predators. their pointed teeth and long body are built for sudden bursts of speed.,active +707,spanish mackerel,Coastal pelagic fish,Salt Water,ocean,Any,Any,Any,Uncommon,0.35,1,15,8,18,0.9,,,,spanish mackerel hunt in warm coastal waters and often travel in loose schools while chasing baitfish.,active +708,talang queenfish,Coastal pelagic fish,Salt Water,ocean,Day,Any,Any,Uncommon,0.8,0.5,20,7,18,0.86,,,,talang queenfish chase schooling prey near warm coasts and estuaries. hooked teeth and a deeply forked tail suit their rapid attacks.,inactive +801,American sand lance,Coastal schooling fish,Salt Water,ocean,Day,Any,Any,Common,3,0.02,0.4,1,3,1,,,,american sand lance dive headfirst into loose sand when danger approaches. immense schools carry energy from plankton to larger predators.,inactive +802,atlantic menhaden,Coastal schooling fish,Salt Water,ocean,Day,Any,Any,Common,3,0.1,1.5,2,5,0.98,,,,"atlantic menhaden strain plankton from coastal water with fine gill rakers. dense schools feed whales, seabirds, and predatory fish.",inactive +803,black pomfret,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Uncommon,0.3,1,15,8,18,0.9,,,,"black pomfret have a deep, laterally compressed body that lets them maneuver neatly through open water.",active +804,chinese pomfret,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Uncommon,0.3,1,20,9,20,0.9,,,,"chinese pomfret are silvery schooling fish with a tall, flattened body and long sickle-shaped fins.",active +805,european anchovy,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Common,0.5,0.05,0.25,2,4,0.9,,,,european anchovies form dense schools and feed by filtering tiny plankton from coastal water.,active +806,European pilchard,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Common,3.5,0.05,0.6,2,4,1,,,,european pilchards form shimmering schools along atlantic and mediterranean coasts. they feed mainly on plankton near the surface.,inactive +807,golden pomfret,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Rare,0.18,2,25,14,30,0.9,,,,golden pomfret are warm-water swimmers whose golden sheen is most noticeable along the fins and flanks.,active +808,northern anchovy,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Common,0.5,0.05,0.35,2,4,0.9,,,,northern anchovies gather in huge schools along the pacific coast and filter plankton with their fine gill rakers.,active +809,Pacific sardine,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Common,3.5,0.05,0.5,2,4,1,,,,"pacific sardines follow cool, productive water in enormous schools. their movements shift as currents and plankton supplies change.",inactive +810,white pomfret,Coastal schooling fish,Salt Water,ocean,Any,Any,Any,Common,0.45,0.5,10,5,12,0.9,,,,"white pomfret gather over coastal grounds and use their compact, deep bodies to turn quickly while feeding.",active +901,blacktip shark,Coastal sharks,Salt Water,ocean,Day,Required,Any,Epic,0.1,15,150,32,75,0.8,,,,blacktip sharks are named for the dark markings on their fins. they hunt schooling fish in warm shallows and sometimes leap clear of the water.,inactive +902,great hammerhead shark,Coastal sharks,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.035,50,1000,62,145,0.78,,,,great hammerheads sweep their wide heads over the bottom while searching for rays. widely spaced senses help pinpoint hidden prey.,inactive +903,great white shark,Coastal sharks,Salt Water,ocean,Day,Any,Any,Legendary,0.025,100,1000,70,150,0.76,,,,great white sharks conserve heat in their swimming muscles while crossing cool seas. rows of serrated teeth are replaced throughout life.,inactive +904,tiger shark,Coastal sharks,Salt Water,ocean,Night,Required,Any,Epic,0.06,50,1000,50,120,0.8,,,,tiger sharks carry dark vertical bars that fade as they mature. broad jaws let them investigate an unusually varied range of prey.,inactive +905,whale shark,Coastal sharks,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.025,500,45000,70,160,0.76,,,,whale sharks are enormous filter feeders covered in unique spot patterns. they cruise with open mouths to collect plankton and tiny fish.,inactive +1001,burbot,Cold freshwater fish,Fresh Water,lake,Night,Any,Required,Rare,0.24,0.5,18,12,28,0.86,,,,burbot are the only freshwater members of the cod family. they become most active in cold darkness and may spawn beneath winter ice.,inactive +1002,cisco,Cold freshwater fish,Fresh Water,lake,Day,Any,Any,Common,2.2,0.1,3,3,7,0.95,,,,cisco school in the cool open water of northern lakes. these slim relatives of salmon feed on plankton and become prey for larger fish.,inactive +1003,lake whitefish,Cold freshwater fish,Fresh Water,lake,Any,Any,Any,Common,1.5,0.5,12,4,11,0.9,,,,"lake whitefish browse deep, cold bottoms with small downward-pointing mouths. large schools move toward shallows to spawn in autumn.",inactive +1101,arctic char,Cold freshwater salmonids,Fresh Water | Salt Water,lake,Any,Forbidden,Any,Rare,0.3,1,25,14,32,0.86,,,,arctic char thrive farther north than any other freshwater fish. some remain in lakes while others migrate between rivers and the sea.,inactive +1102,arctic grayling,Cold freshwater salmonids,Fresh Water,river,Day,Any,Forbidden,Uncommon,0.75,0.3,6,6,14,0.9,,,,"arctic grayling carry a tall, colorful dorsal fin like a sail. they feed on drifting insects in cold, clear rivers and lakes.",inactive +1103,brown trout,Cold freshwater salmonids,Fresh Water,river,Night,Any,Any,Uncommon,0.8,0.5,25,7,20,0.86,,,,"brown trout often shelter by day and hunt more freely after dark. adaptable populations occupy streams, lakes, and even coastal water.",inactive +1104,cutthroat trout,Cold freshwater salmonids,Fresh Water,river,Any,Any,Any,Uncommon,0.4,0.25,10,5,12,0.9,,,,"cutthroat trout take their name from the red slash beneath the jaw. many populations rely on cold, well-oxygenated streams.",active +1105,golden trout,Cold freshwater salmonids,Fresh Water,lake,Any,Any,Any,Rare,0.2,0.2,3,8,16,0.9,,,,"golden trout evolved in clear, high-elevation california streams. their bright flanks stand out against rocky alpine water.",active +1106,rainbow trout,Cold freshwater salmonids,Fresh Water,river,Any,Any,Any,Uncommon,0.45,0.5,20,5,11,0.9,,,,rainbow trout are adaptable stream hunters. the pink lateral stripe is especially vivid when the fish is in breeding condition.,active +1201,atlantic wolffish,Cold reef fish,Salt Water,ocean,Any,Forbidden,Any,Rare,0.25,2,40,14,34,0.84,,,,atlantic wolffish use heavy jaws and blunt teeth to crush shellfish and urchins. natural antifreeze proteins help them endure icy water.,inactive +1202,cusk,Cold reef fish,Salt Water,ocean,Night,Any,Any,Uncommon,0.75,0.5,30,7,19,0.85,,,,"cusk move slowly across cold rocky bottoms, sheltering in cracks and caves. a single long fin runs around much of the rear body.",inactive +1203,spotted wolffish,Cold reef fish,Salt Water,ocean,Any,Any,Required,Epic,0.11,3,50,26,60,0.84,,,,spotted wolffish crush hard-shelled prey with broad molar-like teeth. their pale spots stand out against a dark cold-water body.,inactive +1301,atlantic herring,Cold schooling fish,Salt Water,ocean,Any,Any,Any,Common,3,0.1,1.5,2,5,0.98,,,,atlantic herring gather in immense schools that move between feeding and spawning grounds. they filter plankton with fine gill rakers.,inactive +1302,capelin,Cold schooling fish,Salt Water,ocean,Any,Any,Required,Uncommon,1,0.03,0.2,4,8,1,,,,"capelin are small polar fish that feed whales, seabirds, and larger fish. many schools roll onto beaches or shallows to release their eggs.",inactive +1303,European sprat,Cold schooling fish,Salt Water,ocean,Any,Any,Any,Common,3.2,0.03,0.3,1,3,1,,,,european sprat form compact schools in northern coastal seas. these tiny herring relatives feed on plankton near the surface.,inactive +1304,pacific herring,Cold schooling fish,Salt Water,ocean,Any,Required,Any,Common,2.8,0.1,1.5,2,5,0.98,,,,pacific herring return to coastal shallows to coat plants and rocks with eggs. their oily schools support entire marine food webs.,inactive +1401,Alaska pollock,Cold shelf fish,Salt Water,ocean,Any,Any,Any,Common,2.5,0.3,8,3,8,0.92,,,,alaska pollock form vast schools over cold northern shelves. they feed on plankton and fish while supporting many larger predators.,inactive +1402,atlantic cod,Cold shelf fish,Salt Water,ocean,Any,Any,Any,Common,1.8,1,55,5,18,0.85,,,,"atlantic cod carry a pale lateral line and one chin barbel. they search cold seafloors for crustaceans, mollusks, and smaller fish.",inactive +1403,Atlantic pollock,Cold shelf fish,Salt Water,ocean,Day,Any,Forbidden,Uncommon,0.9,1,35,7,18,0.86,,,,atlantic pollock are swift relatives of cod with dark backs and pale lateral lines. adults gather around rocky banks and wrecks.,inactive +1404,Dover sole,Cold shelf fish,Salt Water,ocean,Night,Any,Any,Uncommon,0.8,0.2,8,6,14,0.9,,,,dover sole glide over deep muddy bottoms of the eastern pacific. both eyes migrate to the right side as the young fish becomes flat.,inactive +1405,haddock,Cold shelf fish,Salt Water,ocean,Day,Any,Any,Common,2,0.5,20,4,11,0.9,,,,haddock are recognized by a dark shoulder mark and black lateral line. schools forage over cold seafloors for small bottom animals.,inactive +1406,pacific cod,Cold shelf fish,Salt Water,ocean,Any,Any,Any,Common,1.8,1,45,5,17,0.85,,,,pacific cod use a chin barbel to investigate cold shelf bottoms. seasonal schools follow prey between deeper water and coastal spawning grounds.,inactive +1407,pacific hake,Cold shelf fish,Salt Water,ocean,Night,Any,Any,Common,1.7,0.3,8,3,9,0.92,,,,pacific hake rise from deep water at night to feed on krill and fish. large schools migrate along the west coast with changing seasons.,inactive +1408,silver hake,Cold shelf fish,Salt Water,ocean,Night,Any,Any,Common,1.7,0.2,5,3,8,0.92,,,,silver hake hunt fish and crustaceans over sandy atlantic shelves. they move upward after dark and return toward the bottom by day.,inactive +1501,chambered nautilus,Cuttlefish and nautiluses,Salt Water,ocean,Night,Any,Required,Legendary,0.05,0.5,3,34,76,0.92,,,,chambered nautiluses regulate buoyancy with gas-filled shell chambers. dozens of small tentacles feel for food in dark reef slopes.,inactive +1502,common cuttlefish,Cuttlefish and nautiluses,Salt Water,ocean,Day,Any,Any,Uncommon,0.55,0.2,12,7,17,0.9,,,,common cuttlefish flash changing colors and patterns through specialized skin cells. an internal cuttlebone controls buoyancy.,inactive +1503,flamboyant cuttlefish,Cuttlefish and nautiluses,Salt Water,ocean,Day,Required,Any,Epic,0.1,0.05,0.4,26,58,0.96,,,,flamboyant cuttlefish walk along the seafloor on their arms and fins. vivid displays warn predators of potent toxins.,inactive +1601,blueline tilefish,Deep shelf fish,Salt Water,ocean,Day,Any,Any,Uncommon,0.7,1,20,7,18,0.86,,,,blueline tilefish shelter in burrows and rocky openings along deep shelves. a narrow blue stripe traces the edge of the dorsal fin.,inactive +1602,golden tilefish,Deep shelf fish,Salt Water,ocean,Day,Any,Any,Rare,0.22,2,50,15,37,0.84,,,,golden tilefish excavate burrows in soft clay along deep ocean slopes. their bright yellow spots contrast with blue-green backs.,inactive +1603,Pacific ocean perch,Deep shelf fish,Salt Water,ocean,Any,Any,Any,Uncommon,0.8,0.3,12,7,17,0.88,,,,pacific ocean perch hover around deep rocky reefs in the north pacific. these slow-growing fish give birth to live young.,inactive +1604,Patagonian toothfish,Deep shelf fish,Salt Water,ocean,Night,Any,Any,Epic,0.09,10,220,32,78,0.8,,,,"patagonian toothfish patrol cold, deep southern water with large canine teeth. natural antifreeze proteins protect their blood from freezing.",inactive +1605,sablefish,Deep shelf fish,Salt Water,ocean,Night,Any,Any,Rare,0.22,2,40,15,36,0.84,,,,"sablefish roam deep north pacific slopes and can live for many decades. soft dark bodies conceal rich, oil-filled flesh.",inactive +1606,wreckfish,Deep shelf fish,Salt Water,ocean,Any,Any,Required,Epic,0.1,10,180,31,76,0.8,,,,"wreckfish gather around caves, ledges, and shipwrecks in deep water. powerful jaws let them ambush fish and large crustaceans.",inactive +1701,giant oarfish,Deep-sea legends,Salt Water,ocean,Night,Any,Required,Legendary,0.035,20,600,60,140,0.78,,,,"giant oarfish drift vertically through deep open water, rowing with a crimson dorsal fin. their ribbon-like bodies can reach remarkable lengths.",inactive +1702,West Indian Ocean coelacanth,Deep-sea legends,Salt Water,ocean,Night,Any,Required,Legendary,0.03,20,200,65,145,0.8,,,,"west indian ocean coelacanths shelter in deep volcanic caves by day. fleshy lobed fins move in an alternating, limb-like rhythm.",inactive +1801,black seadevil,Deep-sea oddities,Salt Water,ocean,Night,Any,Any,Legendary,0.05,0.1,2,48,105,0.92,,,,black seadevils lure prey with a glowing organ suspended above the mouth. tiny males permanently attach to much larger females.,inactive +1802,common fangtooth,Deep-sea oddities,Salt Water,ocean,Night,Any,Any,Epic,0.1,0.05,0.5,25,56,0.96,,,,common fangtooths carry outsized teeth that fit into pockets in the roof of the mouth. they migrate upward from deep water after dark.,inactive +1803,gulper eel,Deep-sea oddities,Salt Water,ocean,Night,Any,Required,Legendary,0.04,0.5,18,44,92,0.86,,,,gulper eels unfold enormous mouths to engulf prey in the deep sea. a whip-like tail ends in a small light-producing organ.,inactive +1804,Pacific barreleye,Deep-sea oddities,Salt Water,ocean,Night,Any,Required,Legendary,0.045,0.2,2.5,42,88,0.9,,,,pacific barreleyes peer upward through transparent shields covering the head. tubular eyes rotate forward when the fish turns to feed.,inactive +1805,smooth-head blobfish,Deep-sea oddities,Salt Water,ocean,Night,Any,Required,Legendary,0.04,0.5,5,42,90,0.86,,,,smooth-head blobfish rest on deep slopes where pressure supports their soft bodies. they conserve energy by waiting for edible matter nearby.,inactive +1806,spotted lanternfish,Deep-sea oddities,Salt Water,ocean,Night,Any,Any,Rare,0.24,0.05,0.5,14,30,0.95,,,,spotted lanternfish carry rows of light-producing organs along their bodies. immense schools rise toward the surface each night.,inactive +1901,crown-of-thorns sea star,Echinoderms,Salt Water,ocean,Night,Required,Any,Epic,0.1,0.5,7,23,52,0.91,,,,crown-of-thorns sea stars crawl over reefs and digest coral tissue outside the body. venomous spines discourage many predators.,inactive +1902,giant California sea cucumber,Echinoderms,Salt Water,ocean,Night,Any,Required,Rare,0.24,0.5,5,13,29,0.93,,,,giant california sea cucumbers sweep organic particles from the seafloor with branching tentacles. soft tube feet anchor their long bodies.,inactive +1903,purple sea urchin,Echinoderms,Salt Water,ocean,Day,Any,Any,Uncommon,0.75,0.05,1,5,12,0.96,,,,purple sea urchins scrape algae from rocks with a five-toothed feeding structure. dense groups can clear kelp from entire reefs.,inactive +1904,sand dollar,Echinoderms,Salt Water,ocean,Day,Any,Forbidden,Uncommon,0.8,0.01,0.2,4,10,0.98,,,,sand dollars burrow just beneath sandy seafloors and filter tiny food particles. the familiar star pattern marks their five-part body plan.,inactive +1905,sunflower sea star,Echinoderms,Salt Water,ocean,Day,Any,Required,Rare,0.25,0.5,13,14,32,0.89,,,,"sunflower sea stars move quickly on thousands of tube feet and may carry many arms. they hunt urchins, clams, and other bottom animals.",inactive +2001,axolotl,Freshwater amphibians,Fresh Water,lake,Night,Any,Required,Legendary,0.04,0.1,0.7,36,78,0.96,,,,axolotls remain aquatic and keep feathery external gills throughout adulthood. lost limbs and parts of several organs can regenerate.,inactive +2002,hellbender,Freshwater amphibians,Fresh Water,river,Night,Required,Any,Epic,0.1,0.5,5.5,24,54,0.91,,,,"hellbenders hide beneath large rocks in cool, clean rivers. folds of loose skin absorb oxygen while the flattened body grips the current.",inactive +2101,guppy,Freshwater aquarium fish,Fresh Water,pond,Day,Any,Any,Common,1.5,0.01,0.15,2,5,0.98,,,,"guppies are livebearers whose young arrive ready to swim. males display bright colors while females often favor larger, less flashy bodies.",inactive +2102,neon tetra,Freshwater aquarium fish,Fresh Water,pond,Night,Any,Any,Uncommon,0.9,0.01,0.08,4,8,0.98,,,,"neon tetras school through shaded forest streams. an iridescent blue stripe remains visible in the dim, tannin-stained water.",inactive +2103,zebrafish,Freshwater aquarium fish,Fresh Water,pond,Day,Any,Any,Common,1.35,0.01,0.12,2,5,0.98,,,,"zebrafish carry horizontal blue stripes from gill to tail. hardy schools thrive in shallow streams, flooded fields, and quiet pools.",inactive +2201,largemouth bass,Freshwater bass,Fresh Water,pond,Any,Any,Any,Common,2.5,0.5,18,4,12,0.9,,,,largemouth bass wait among weeds and timber before lunging at prey. the upper jaw extends beyond the eye when the mouth is closed.,inactive +2202,smallmouth bass,Freshwater bass,Fresh Water,river,Day,Any,Any,Uncommon,1.2,0.5,12,6,15,0.9,,,,smallmouth bass favor clear water with rock and moving current. bronze flanks and dark vertical bars distinguish them from largemouth bass.,inactive +2301,bigmouth buffalo,Freshwater bottom fish,Fresh Water,river,Any,Any,Required,Epic,0.12,8,80,28,63,0.82,,,,bigmouth buffalo filter plankton with fine gill rakers instead of rooting for prey. some individuals survive for more than a century.,inactive +2302,golden redhorse,Freshwater bottom fish,Fresh Water,river,Day,Any,Any,Uncommon,0.9,0.5,8,6,14,0.9,,,,"golden redhorses probe flowing river bottoms with fleshy, downward-facing mouths. coppery scales and reddish fins give them a warm glow.",inactive +2303,quillback,Freshwater bottom fish,Fresh Water,river,Day,Any,Any,Uncommon,0.8,0.5,15,6,16,0.88,,,,quillbacks carry a long first dorsal ray that rises like a quill. small mouths gather insect larvae and other food from river and lake bottoms.,inactive +2304,smallmouth buffalo,Freshwater bottom fish,Fresh Water,river,Any,Any,Any,Rare,0.3,3,45,14,34,0.84,,,,"smallmouth buffalo use thick lips to graze bottom-dwelling food in warm rivers. their deep, heavy bodies form a pronounced arch behind the head.",inactive +2305,white sucker,Freshwater bottom fish,Fresh Water,river,Any,Any,Any,Common,2,0.3,8,3,8,0.9,,,,white suckers vacuum insects and organic matter from cool freshwater bottoms. spring runs carry large groups into shallow streams.,inactive +2401,black bullhead,Freshwater catfish,Fresh Water,pond,Night,Any,Any,Uncommon,1,0.3,6,5,11,0.92,,,,"black bullheads tolerate warm, cloudy water with little oxygen. stout spines protect the dorsal and pectoral fins from predators.",inactive +2402,blue catfish,Freshwater catfish,Fresh Water,river,Night,Any,Any,Uncommon,1.25,3,12,6,9,0.9,,,,a deep fork in the tail inspired the blue catfish's scientific name. it rests deep by daylight and becomes more active after dark.,active +2403,brown bullhead,Freshwater catfish,Fresh Water,pond,Night,Any,Any,Common,2.3,0.2,4,3,7,0.95,,,,brown bullheads search muddy bottoms with eight sensitive barbels. males guard nests and herd newly hatched young in a dark moving ball.,inactive +2404,channel catfish,Freshwater catfish,Fresh Water,pond,Night,Any,Any,Common,2,1.5,7,4,7,1,,,,"a channel catfish can taste through skin all over its body, especially near its gills and whiskers. the male watches the eggs.",active +2405,flathead catfish,Freshwater catfish,Fresh Water,river,Night,Any,Any,Uncommon,1,3,14,7,10,0.9,,,,"flatheads favor hideouts made by logs, rocks, ledges, and other underwater structure. as they grow, fish dominate their menu.",active +2406,tadpole madtom,Freshwater catfish,Fresh Water,river,Night,Any,Any,Rare,0.35,0.02,0.15,8,16,0.95,,,,tadpole madtoms are tiny nocturnal catfish that shelter beneath vegetation and debris. venomous fin spines discourage larger predators.,inactive +2407,white catfish,Freshwater catfish,Fresh Water,pond,Night,Any,Any,Common,2,1,5,4,6,1,,,,white catfish began along the atlantic coast between new york and florida. people later carried them far beyond that home range.,active +2408,yellow bullhead,Freshwater catfish,Fresh Water,pond,Night,Required,Any,Common,2,0.2,3.5,3,7,0.95,,,,yellow bullheads use pale chin barbels to distinguish food in murky shallows. both parents may guard the nest and schooling young.,inactive +2501,common barbel,Freshwater coarse fish,Fresh Water,river,Night,Required,Any,Rare,0.28,1,25,13,31,0.85,,,,common barbels hold close to fast river bottoms with streamlined bodies and broad fins. four mouth barbels locate food among stones.,inactive +2502,common bream,Freshwater coarse fish,Fresh Water,lake,Any,Any,Any,Common,1.8,0.3,15,4,12,0.88,,,,"common bream gather in slow rivers and lakes with silvery, deep-sided bodies. protruding mouths sift insect larvae from soft bottoms.",inactive +2503,common carp,Freshwater coarse fish,Fresh Water,pond,Any,Any,Any,Rare,1.5,3,18,10,10,0.85,,,,whisker-like barbels help a common carp investigate the bottom. nosing through sediment can cloud the water and loosen plants.,active +2504,common roach,Freshwater coarse fish,Fresh Water,pond,Any,Any,Any,Common,2.8,0.1,4,2,6,0.95,,,,"common roach thrive in many lakes and rivers, often schooling near vegetation. red fins and a red-orange eye brighten their silver bodies.",inactive +2505,european chub,Freshwater coarse fish,Fresh Water,river,Any,Any,Any,Common,0.55,0.2,6,2,4,0.9,,,,"european chub are adaptable river fish. adults often patrol shallows and feed on insects, small fish, and fruit.",active +2506,fallfish,Freshwater coarse fish,Fresh Water,river,Day,Required,Any,Uncommon,0.9,0.2,3.5,5,10,0.92,,,,fallfish are large minnows that build broad stone nests in flowing water. other stream fish often reuse the gravel mounds for spawning.,inactive +2507,ide,Freshwater coarse fish,Fresh Water,river,Day,Any,Any,Uncommon,0.8,0.3,9,6,14,0.9,,,,"ide move between rivers, lakes, and brackish coastal water in loose schools. adults eat insects, crustaceans, plants, and small fish.",inactive +2508,rudd,Freshwater coarse fish,Fresh Water,pond,Day,Any,Any,Common,2.4,0.1,4.5,3,7,0.95,,,,"rudd feed near the surface with an upturned mouth and red fins. golden-green bodies disappear easily among warm, weedy shallows.",inactive +2509,tench,Freshwater coarse fish,Fresh Water,pond,Night,Any,Required,Uncommon,0.75,0.5,12,7,17,0.88,,,,"tench browse quiet, muddy bottoms with two small mouth barbels. a thick coat of slime protects their olive-green scales.",inactive +2601,red swamp crayfish,Freshwater crustaceans,Fresh Water,pond,Night,Required,Any,Common,1.1,0.03,0.25,3,7,0.98,,,,"red swamp crayfish burrow into wet ground when water recedes. adaptable populations feed on plants, carrion, and small aquatic animals.",inactive +2701,alligator gar,Freshwater giants,Fresh Water,river,Night,Any,Any,Legendary,0.055,20,300,55,125,0.8,,,,alligator gar combine a broad tooth-filled snout with heavy armor-like scales. a vascular swim bladder lets them breathe air at the surface.,inactive +2702,arapaima,Freshwater giants,Fresh Water,river,Day,Any,Any,Legendary,0.04,40,400,60,140,0.78,,,,arapaima must surface frequently to gulp air through a specialized swim bladder. large armored scales protect their long river-going bodies.,inactive +2703,goliath tigerfish,Freshwater giants,Fresh Water,river,Day,Any,Any,Legendary,0.035,20,150,58,132,0.8,,,,goliath tigerfish patrol powerful african rivers with interlocking dagger-like teeth. a muscular forked tail drives sudden attacks.,inactive +2704,muskellunge,Freshwater giants,Fresh Water,lake,Day,Any,Required,Epic,0.11,8,60,32,72,0.82,,,,muskellunge wait motionless among weeds before exploding toward prey. pale bodies carry bars or spots that fade into broken underwater light.,inactive +2801,common shiner,Freshwater minnows,Fresh Water,river,Day,Any,Any,Common,4,0.03,0.3,1,3,1,,,,common shiners flash silver in clear streams and small rivers. breeding males develop rosy fins and a blue-violet sheen.,inactive +2802,creek chub,Freshwater minnows,Fresh Water,river,Day,Any,Any,Common,3.2,0.05,0.8,2,4,1,,,,creek chubs gather around pools and undercut banks in small streams. males build gravel ridges where several fish may spawn.,inactive +2803,emerald shiner,Freshwater minnows,Fresh Water,lake,Any,Any,Any,Common,3.5,0.03,0.25,1,3,1,,,,emerald shiners form bright surface schools in large lakes and rivers. slender bodies feed on plankton and drifting insects.,inactive +2804,fathead minnow,Freshwater minnows,Fresh Water,pond,Day,Any,Any,Common,4.5,0.02,0.2,1,2,1,,,,"fathead minnows tolerate warm, muddy ponds where many fish struggle. males guard eggs laid beneath rocks, logs, and other cover.",inactive +2805,flame chub,Freshwater minnows,Fresh Water,river,Any,Any,Any,Uncommon,0.4,0.05,0.15,3,6,0.9,,,,"flame chubs prefer cool, clear streams. their bright breeding colors make a small fish easy to spot in spring water.",active +2806,golden shiner,Freshwater minnows,Fresh Water,pond,Day,Any,Any,Common,3.8,0.05,1,2,4,1,,,,golden shiners school around dense vegetation in warm water. an upturned mouth takes insects and plankton from near the surface.,inactive +2807,lake chub,Freshwater minnows,Fresh Water,lake,Any,Any,Any,Common,0.55,0.05,0.5,2,4,0.9,,,,lake chubs school in cold northern water and use their small mouths to pick insects and other tiny prey from the current.,active +2901,electric eel,Freshwater oddities,Fresh Water,river,Night,Required,Any,Epic,0.1,1,44,25,60,0.82,,,,electric eels navigate and hunt with electrical pulses produced along most of the body. they must surface regularly to breathe air.,inactive +2902,freshwater drum,Freshwater oddities,Fresh Water,river,Night,Required,Any,Uncommon,0.7,1,35,7,20,0.85,,,,freshwater drum crush snails and mussels with powerful throat teeth. muscles vibrating against the swim bladder produce a low drumming sound.,inactive +2903,goldeye,Freshwater oddities,Fresh Water,river,Night,Any,Required,Rare,0.3,0.3,6,12,25,0.9,,,,goldeye patrol turbid rivers with large light-sensitive eyes and a toothed tongue. they rise toward the surface to hunt insects and small fish.,inactive +2904,mooneye,Freshwater oddities,Fresh Water,river,Night,Any,Any,Uncommon,0.85,0.2,4,5,11,0.92,,,,mooneyes flash silver in clear rivers and lakes while feeding near the surface. their large eyes suit evening hunts for insects and small fish.,inactive +2905,weather loach,Freshwater oddities,Fresh Water,pond,Any,Required,Any,Uncommon,0.85,0.05,0.7,5,11,0.95,,,,weather loaches gulp air and pass it through the intestine when oxygen runs low. restless activity often increases as air pressure falls.,inactive +3001,bubble-eye goldfish,Freshwater ornamental,Fresh Water,pond,Any,Any,Any,Uncommon,0.35,0.1,0.35,3,7,0.9,,,,"bubble-eye goldfish have delicate fluid-filled sacs beneath their eyes, making careful handling especially important.",active +3002,goldfish,Freshwater ornamental,Fresh Water,pond,Any,Any,Any,Common,0.5,0.1,4,1,3,0.9,,,,"goldfish can tolerate a surprisingly wide range of conditions. their wild relatives often live in slow, weedy water.",active +3003,koi,Freshwater ornamental,Fresh Water,pond,Day,Any,Forbidden,Rare,0.28,0.5,35,14,34,0.87,,,,koi are colorful domesticated forms of common carp selected over many generations. each fish carries a distinctive arrangement of patches and scales.,inactive +3101,black crappie,Freshwater panfish,Fresh Water,pond,Any,Any,Required,Uncommon,1,0.2,4,5,11,0.9,,,,black crappie gather around submerged wood and vegetation in clear water. irregular dark speckles scatter across their deep silver bodies.,inactive +3102,bluegill,Freshwater panfish,Fresh Water,pond,Day,Any,Any,Common,10,0.3,2,3,3,1,,,,"bluegill fathers sweep shallow bowls into the bottom for nests, then stand guard. whole neighborhoods of nests can crowd together.",active +3103,green sunfish,Freshwater panfish,Fresh Water,pond,Day,Any,Any,Common,3.2,0.1,1.5,2,5,1,,,,"green sunfish tolerate small, warm, cloudy pools better than many relatives. a large mouth lets them tackle insects, crayfish, and fish.",inactive +3104,pumpkinseed,Freshwater panfish,Fresh Water,pond,Day,Any,Any,Common,3,0.1,1.3,2,5,1,,,,pumpkinseeds use compact teeth to crush snails and other hard prey. blue facial lines and an orange gill spot brighten their round bodies.,inactive +3105,redear sunfish,Freshwater panfish,Fresh Water,pond,Day,Any,Any,Uncommon,1.1,0.2,3,4,9,0.95,,,,"redear sunfish specialize in snails, crushing shells with strong throat teeth. a red or orange margin marks the dark flap behind the gill.",inactive +3106,warmouth,Freshwater panfish,Fresh Water,pond,Night,Required,Any,Uncommon,0.8,0.2,2,5,10,0.95,,,,"warmouth hide in weeds, roots, and murky backwaters before ambushing prey. red eyes and dark cheek stripes frame a notably large mouth.",inactive +3107,white crappie,Freshwater panfish,Fresh Water,lake,Night,Any,Any,Common,1.7,0.2,4.5,4,9,0.9,,,,"white crappie favor warm, cloudy water and gather around structure. vertical bars cross their silver sides instead of scattered speckles.",inactive +3201,yellow perch,Freshwater perch,Fresh Water,lake,Day,Any,Any,Common,2.2,0.2,4,3,8,0.9,,,,yellow perch travel in schools through cool lakes and slow rivers. dark vertical bars and orange lower fins make them easy to recognize.,inactive +3301,chain pickerel,Freshwater predators,Fresh Water,pond,Day,Required,Any,Uncommon,0.8,0.5,9,6,15,0.9,,,,chain pickerel hover among vegetation before darting at fish and frogs. a chain-like pattern breaks up their green flanks.,inactive +3302,grass pickerel,Freshwater predators,Fresh Water,pond,Day,Any,Any,Common,2.1,0.1,1.5,2,6,0.95,,,,"grass pickerel remain small enough to hunt in narrow, weedy creeks and marshes. dark bars camouflage their slim bodies among stems.",inactive +3303,northern pike,Freshwater predators,Fresh Water,lake,Day,Any,Any,Uncommon,0.75,1,30,8,24,0.85,,,,northern pike are solitary ambush hunters built for sudden acceleration. pale bean-shaped spots cover their long green bodies.,inactive +3304,sauger,Freshwater predators,Fresh Water,river,Any,Any,Any,Uncommon,0.35,0.5,8,6,12,0.9,,,,"sauger favor turbid rivers and reservoirs. their mottled backs blend into the dim, shifting light near the bottom.",active +3305,saugeye,Freshwater predators,Fresh Water,lake,Any,Any,Any,Rare,0.2,0.5,12,9,18,0.9,,,,saugeye are a fertile hybrid of sauger and walleye. they combine traits from both parent species and often grow quickly.,active +3306,walleye,Freshwater predators,Fresh Water,lake,Any,Any,Any,Uncommon,0.4,0.5,15,7,15,0.9,,,,walleye have light-sensitive eyes that help them hunt in murky water and around dusk. their reflective layer gives the eyes a pale glow.,active +3401,round goby,Freshwater small fish,Fresh Water,lake,Any,Any,Any,Common,8,0.1,0.6,2,3,1,,,,round gobies use fused pelvic fins like a suction cup to hold onto rocks. they are small fish with a famously large appetite.,active +3501,crystal jelly,Jellies,Salt Water,ocean,Night,Any,Forbidden,Rare,0.24,0.05,4,15,34,0.93,,,,crystal jellies are nearly transparent except for glowing points around the bell. their green fluorescent protein transformed biological research.,inactive +3502,lion's mane jelly,Jellies,Salt Water,ocean,Night,Any,Required,Epic,0.11,1,200,24,58,0.82,,,,lion's mane jellies trail dense curtains of tentacles through cold seas. the bell darkens from yellow to deep red as the animal grows.,inactive +3503,moon jelly,Jellies,Salt Water,ocean,Night,Any,Any,Uncommon,0.7,0.05,5,6,14,0.94,,,,moon jellies pulse gently with four horseshoe-shaped organs visible through the bell. short tentacles sweep plankton toward the mouth.,inactive +3504,Pacific sea nettle,Jellies,Salt Water,ocean,Day,Any,Forbidden,Rare,0.28,0.2,12,14,32,0.9,,,,pacific sea nettles carry rust-colored stripes across pale bells. long tentacles and frilled mouth arms capture plankton and small fish.,inactive +3505,sea wasp,Jellies,Salt Water,ocean,Day,Required,Any,Legendary,0.035,0.2,6,42,90,0.9,,,,sea wasps have box-shaped bells and clusters of eyes along the lower edge. their long tentacles deliver exceptionally potent venom.,inactive +3601,African pompano,Large coastal predators,Salt Water,ocean,Any,Any,Any,Rare,0.26,2,50,14,35,0.84,,,,african pompano develop long streaming fin rays while young. adults become deep-bodied silver hunters around reefs and offshore structure.,inactive +3602,cobia,Large coastal predators,Salt Water,ocean,Day,Any,Any,Rare,0.24,5,80,15,40,0.82,,,,"cobia often follow sharks, rays, and floating structures in warm seas. a broad dark stripe runs along their long, powerful bodies.",inactive +3603,giant trevally,Large coastal predators,Salt Water,ocean,Day,Any,Any,Epic,0.1,10,150,32,78,0.8,,,,giant trevally hunt reef edges with bursts of speed and force. large individuals sometimes charge prey from surf and shallow lagoons.,inactive +3604,greater amberjack,Large coastal predators,Salt Water,ocean,Any,Any,Any,Rare,0.25,5,90,15,42,0.82,,,,"greater amberjacks circle reefs, wrecks, and offshore platforms. a dark stripe crosses the eye of their sturdy torpedo-shaped bodies.",inactive +3605,lesser amberjack,Large coastal predators,Salt Water,ocean,Any,Any,Any,Uncommon,0.75,1,20,7,18,0.86,,,,lesser amberjacks school around deep reefs and floating objects. an enlarged eye suits their preference for dimmer water.,inactive +3701,atlantic halibut,Large flatfish,Salt Water,ocean,Any,Any,Any,Epic,0.1,10,300,32,82,0.8,,,,atlantic halibut are immense right-eyed flatfish of cold northern seas. adults ambush fish and squid from deep rocky or sandy bottoms.,inactive +3702,pacific halibut,Large flatfish,Salt Water,ocean,Any,Any,Any,Epic,0.1,10,350,34,86,0.8,,,,pacific halibut lie camouflaged on cold continental shelves. powerful flat bodies can travel long distances between feeding and spawning grounds.,inactive +3703,turbot,Large flatfish,Salt Water,ocean,Any,Any,Required,Rare,0.24,1,35,14,34,0.85,,,,"turbot have broad, nearly circular bodies covered with bony bumps instead of scales. they bury into sand before ambushing fish and crustaceans.",inactive +3801,American lobster,Lobsters and shrimp,Salt Water,ocean,Night,Any,Required,Rare,0.28,0.5,44,16,42,0.86,,,,american lobsters shelter in rocky crevices and emerge to forage along the bottom. one claw crushes while the other cuts and grips.,inactive +3802,Antarctic krill,Lobsters and shrimp,Salt Water,ocean,Night,Any,Any,Rare,0.3,0.001,0.01,10,22,0.99,,,,"antarctic krill form enormous swarms beneath southern seas and ice. these small crustaceans feed whales, seals, penguins, and fish.",inactive +3803,Caribbean spiny lobster,Lobsters and shrimp,Salt Water,ocean,Night,Any,Forbidden,Rare,0.25,0.5,26,15,38,0.87,,,,caribbean spiny lobsters rely on long antennae and a thorny shell instead of large claws. groups march together across the seafloor.,inactive +3804,peacock mantis shrimp,Lobsters and shrimp,Salt Water,ocean,Day,Required,Any,Epic,0.1,0.05,0.4,22,50,0.96,,,,peacock mantis shrimp strike hard-shelled prey with club-like limbs. complex eyes move independently and detect colors beyond human vision.,inactive +3805,skunk cleaner shrimp,Lobsters and shrimp,Salt Water,ocean,Day,Any,Forbidden,Uncommon,0.75,0.01,0.15,5,11,0.98,,,,skunk cleaner shrimp advertise at reef cleaning stations with bright white antennae. visiting fish pause while parasites are picked away.,inactive +3901,Atlantic ghost crab,Marine crabs,Salt Water,ocean,Night,Any,Forbidden,Rare,0.28,0.02,0.5,11,24,0.97,,,,atlantic ghost crabs dash across beaches on long pale legs and retreat into deep burrows. raised eyes scan nearly every direction.,inactive +3902,blue crab,Marine crabs,Salt Water,ocean,Day,Any,Any,Common,1.15,0.1,1.1,3,8,0.95,,,,"blue crabs swim with flattened rear legs shaped like paddles. bright blue claws and an olive shell conceal a quick, defensive temperament.",inactive +3903,Dungeness crab,Marine crabs,Salt Water,ocean,Day,Any,Required,Uncommon,0.65,0.5,3,6,14,0.92,,,,dungeness crabs bury beneath sand with only their eyes and antennae exposed. broad toothed shells protect them along cool pacific coasts.,inactive +3904,Japanese spider crab,Marine crabs,Salt Water,ocean,Night,Any,Any,Legendary,0.045,5,44,38,88,0.84,,,,japanese spider crabs carry the longest leg span of any living arthropod. adults roam deep seafloors around japan.,inactive +3905,red king crab,Marine crabs,Salt Water,ocean,Night,Any,Required,Epic,0.11,2,24,24,58,0.85,,,,"red king crabs stride over cold northern bottoms on long armored legs. despite the name, they are closer to hermit crabs than true crabs.",inactive +4001,american eel,Migratory eels,Fresh Water | Salt Water,river,Night,Required,Any,Rare,0.25,0.3,8,12,27,0.88,,,,american eels hatch far out in the sargasso sea before entering freshwater rivers. adults later reverse the journey to spawn once.,inactive +4002,european eel,Migratory eels,Fresh Water | Salt Water,river,Night,Any,Required,Rare,0.2,0.3,10,13,30,0.88,,,,european eels cross the atlantic as transparent larvae before entering rivers and lakes. mature silver eels return to sea to reproduce.,inactive +4101,rainbow smelt,Migratory forage fish,Fresh Water | Salt Water,lake,Night,Any,Any,Common,2.5,0.05,0.8,2,5,1,,,,"rainbow smelt move between coastal water and rivers, though some remain landlocked. cucumber-like aromas cling to freshly caught fish.",inactive +4201,eulachon,Ocean-run forage fish,Fresh Water | Salt Water,river,Night,Required,Any,Uncommon,0.9,0.05,0.5,4,9,0.98,,,,eulachon return from the pacific to spawn in coastal rivers. their oil-rich bodies once served as candles when dried and fitted with wicks.,inactive +4301,white sturgeon,Ocean-run giants,Fresh Water | Salt Water,river,Night,Any,Any,Legendary,0.045,20,500,58,135,0.78,,,,white sturgeon are the largest freshwater fish in north america. armored plates line bodies built for long lives in rivers and estuaries.,inactive +4401,atlantic salmon,Ocean-run salmon,Salt Water,ocean,Any,Any,Any,Epic,0.65,8,40,20,42,0.85,,,,"atlantic salmon can return from the ocean to the river where they hatched. unlike many salmon, some survive to make the trip again.",active +4402,chum salmon,Ocean-run salmon,Salt Water,ocean,Any,Any,Any,Uncommon,2.5,6,25,10,22,0.9,,,,chum salmon travel immense distances and develop striking bars when they return to fresh water to spawn.,active +4403,coho salmon,Ocean-run salmon,Salt Water,ocean,Any,Any,Any,Rare,1.4,5,30,14,28,0.88,,,,coho salmon are agile hunters known for powerful leaps. young coho may spend more than a year growing in streams.,active +4404,pink salmon,Ocean-run salmon,Salt Water,ocean,Any,Any,Any,Uncommon,3,3,12,8,15,0.92,,,,pink salmon usually follow a strict two-year life cycle. spawning males develop the hump that inspired their nickname.,active +4405,sockeye salmon,Ocean-run salmon,Salt Water,ocean,Any,Any,Any,Rare,1.6,4,15,12,24,0.9,,,,sockeye salmon feed heavily on plankton at sea. adults turn vivid red as they return inland to spawn.,active +4501,steelhead trout,Ocean-run salmonids,Fresh Water,river,Any,Any,Any,Rare,0.2,2,35,10,20,0.9,,,,steelhead are the ocean-going form of rainbow trout. adults can leave fresh water and later return to spawn.,active +4601,black marlin,Oceanic billfish,Salt Water,ocean,Any,Any,Any,Epic,0.08,80,700,45,90,0.9,,,,black marlin are powerful billfish that spend much of their lives in warm open ocean and can make blistering runs.,active +4602,blue marlin,Oceanic billfish,Salt Water,ocean,Any,Any,Any,Legendary,0.05,100,1000,60,120,0.9,,,,blue marlin are highly migratory open-ocean hunters. their long bill helps them stun prey before turning back to feed.,active +4603,sailfish,Oceanic billfish,Salt Water,ocean,Any,Any,Any,Epic,0.1,30,200,38,78,0.9,,,,sailfish raise their enormous dorsal fin while herding baitfish. they are among the fastest fish in the sea.,active +4604,swordfish,Oceanic billfish,Salt Water,ocean,Any,Any,Any,Legendary,0.06,50,1000,55,110,0.9,,,,"swordfish are fast, wide-ranging predators with a flattened bill. they can cross deep water and warm surface layers in one day.",active +4605,white marlin,Oceanic billfish,Salt Water,ocean,Any,Any,Any,Epic,0.1,30,180,35,70,0.9,,,,white marlin favor warm offshore water and use their rounded dorsal fin and bill to weave through schools of prey.,active +4701,mahi-mahi,Oceanic pelagic fish,Salt Water,ocean,Day,Any,Forbidden,Rare,0.28,2,50,15,38,0.84,,,,"mahi-mahi flash brilliant blue, green, and gold while chasing prey near the surface. they grow quickly and gather around floating objects.",inactive +4702,opah,Oceanic pelagic fish,Salt Water,ocean,Day,Any,Forbidden,Epic,0.09,20,600,36,88,0.8,,,,"opah circulate warm blood through much of the body, an unusual trait among fish. round silver flanks glow with red fins and pale spots.",inactive +4703,wahoo,Oceanic pelagic fish,Salt Water,ocean,Day,Any,Any,Epic,0.11,10,100,30,70,0.82,,,,wahoo are streamlined open-ocean hunters with rows of sharp teeth. blue vertical bars flicker across their sides during fast pursuits.,inactive +4801,albacore tuna,Oceanic tuna,Salt Water,ocean,Any,Any,Any,Rare,1.5,10,45,12,24,0.85,,,,albacore have unusually long pectoral fins. their warm muscles help them cruise across wide stretches of open ocean.,active +4802,bigeye tuna,Oceanic tuna,Salt Water,ocean,Any,Any,Any,Epic,0.55,20,120,22,48,0.8,,,,"bigeye tuna make daily trips between deep, cool water and warmer surface layers. their large eyes suit those dim depths.",active +4803,bluefin tuna,Oceanic tuna,Salt Water,ocean,Any,Any,Any,Legendary,0.18,30,250,35,90,0.78,,,,bluefin tuna are powerful ocean travelers. heat retained in their swimming muscles lets them thrive in surprisingly cold seas.,active +4804,skipjack tuna,Oceanic tuna,Salt Water,ocean,Any,Any,Any,Uncommon,3,5,25,8,16,0.9,,,,skipjack travel in fast-moving schools and have dark stripes along their lower sides. they rarely stop swimming.,active +4805,yellowfin tuna,Oceanic tuna,Salt Water,ocean,Any,Any,Any,Rare,1,15,80,16,36,0.82,,,,yellowfin are named for their bright yellow fins and finlets. large schools often gather with other open-ocean hunters.,active +4901,common octopus,Octopuses,Salt Water,ocean,Night,Any,Any,Uncommon,0.65,0.2,22,7,18,0.87,,,,common octopuses squeeze through narrow cracks and solve problems while hunting reefs. skin cells rapidly change color and texture.,inactive +4902,day octopus,Octopuses,Salt Water,ocean,Day,Any,Forbidden,Rare,0.24,0.2,15,15,36,0.88,,,,"day octopuses forage across tropical reefs while the sun is high. shifting bars and spots communicate alarm, camouflage, and threat.",inactive +4903,giant Pacific octopus,Octopuses,Salt Water,ocean,Night,Any,Required,Epic,0.1,5,200,28,70,0.81,,,,giant pacific octopuses explore cold rocky dens with thousands of arm suckers. females guard their eggs without feeding until they hatch.,inactive +4904,greater blue-ringed octopus,Octopuses,Salt Water,ocean,Day,Required,Any,Legendary,0.04,0.05,0.3,38,82,0.96,,,,greater blue-ringed octopuses reveal vivid rings when threatened. a tiny beak can deliver venom strong enough to stop breathing.,inactive +5001,sunfish,Open-ocean giants,Salt Water,ocean,Day,Any,Any,Common,7,0.4,2.5,3,3,1,,,,ocean sunfish are built more like enormous swimming heads than ordinary fish. they visit the surface and turn sideways to warm up.,active +5101,barndoor skate,Rays and skates,Salt Water,ocean,Any,Any,Required,Epic,0.1,5,150,28,68,0.8,,,,barndoor skates glide over cold atlantic bottoms with broad wing-like fins. rows of thorny spines protect the back and tail.,inactive +5102,giant manta ray,Rays and skates,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.025,100,1000,65,150,0.76,,,,giant manta rays funnel plankton into wide mouths with two flexible head fins. each belly carries a unique pattern of dark spots.,inactive +5103,largetooth sawfish,Rays and skates,Salt Water,ocean,Night,Required,Any,Legendary,0.035,25,1300,58,135,0.78,,,,largetooth sawfish detect hidden prey with a long sensory rostrum. a swift sideways sweep stuns fish and exposes animals buried in mud.,inactive +5104,ocellate river stingray,Rays and skates,Fresh Water,river,Day,Required,Any,Epic,0.09,2,35,28,65,0.82,,,,ocellate river stingrays carry bright eye-like spots across a round disc. they hide beneath sand in south american rivers.,inactive +5105,southern stingray,Rays and skates,Salt Water,ocean,Day,Any,Any,Rare,0.23,2,80,15,40,0.82,,,,southern stingrays bury in warm sand with only their eyes and spiracles visible. crushing plates break open clams and crustaceans.,inactive +5201,red lionfish,Reef ambush fish,Salt Water,ocean,Night,Any,Any,Rare,0.3,0.2,3,11,25,0.92,,,,red lionfish spread ornate venomous spines when threatened. broad fins herd smaller fish into corners before a rapid gulp.,inactive +5202,red scorpionfish,Reef ambush fish,Salt Water,ocean,Night,Any,Any,Rare,0.3,0.2,7,11,25,0.9,,,,red scorpionfish vanish against rocky reefs through mottled color and skin flaps. venomous dorsal spines punish careless predators.,inactive +5301,green moray,Reef eels,Salt Water,ocean,Night,Any,Any,Rare,0.28,1,30,13,32,0.86,,,,green morays appear green because yellow mucus coats blue-gray skin. a second set of jaws pulls captured prey deeper into the throat.,inactive +5401,peacock flounder,Reef flatfish,Salt Water,ocean,Day,Any,Any,Rare,0.3,0.2,5,11,24,0.9,,,,peacock flounders change their spots and colors to match tropical sand. both eyes scan above while the flat body remains concealed.,inactive +5501,blue tang,Reef grazers,Salt Water,ocean,Day,Any,Any,Uncommon,0.9,0.05,1.5,5,11,0.95,,,,blue tangs graze algae from coral reefs with small close-set teeth. sharp folding spines near the tail give surgeonfish their name.,inactive +5502,rainbow parrotfish,Reef grazers,Salt Water,ocean,Day,Any,Any,Epic,0.12,2,40,25,58,0.84,,,,rainbow parrotfish scrape algae from reefs with fused beak-like teeth. crushed coral passes through them and returns to the reef as sand.,inactive +5503,stoplight parrotfish,Reef grazers,Salt Water,ocean,Day,Any,Any,Rare,0.28,0.5,15,12,29,0.88,,,,stoplight parrotfish change color dramatically as they mature. powerful dental plates bite algae from hard reef surfaces.,inactive +5504,yellow tang,Reef grazers,Salt Water,ocean,Day,Forbidden,Any,Rare,0.35,0.05,0.8,9,20,0.96,,,,yellow tangs browse algae across hawaiian reefs during daylight. at night their bright bodies darken and show a pale horizontal stripe.,inactive +5601,atlantic trumpetfish,Reef oddities,Salt Water,ocean,Day,Any,Any,Rare,0.3,0.1,2,10,24,0.94,,,,atlantic trumpetfish hover head-down among corals and sea fans. long tubular snouts expand suddenly to draw in small fish.,inactive +5602,gray triggerfish,Reef oddities,Salt Water,ocean,Day,Any,Any,Uncommon,0.8,0.3,12,7,17,0.88,,,,"gray triggerfish lock a stout dorsal spine upright when wedged into shelter. strong teeth crack barnacles, crabs, and shellfish.",inactive +5603,guineafowl puffer,Reef oddities,Salt Water,ocean,Day,Any,Any,Rare,0.3,0.2,5,11,26,0.9,,,,guineafowl puffers swallow water to inflate when threatened. adults may shift from a spotted dark pattern to a vivid yellow form.,inactive +5604,longhorn cowfish,Reef oddities,Salt Water,ocean,Day,Any,Required,Epic,0.13,0.1,2,23,52,0.94,,,,longhorn cowfish wear a rigid box of fused scales with horns above the eyes. small fins steer the angular body with surprising precision.,inactive +5605,queen triggerfish,Reef oddities,Salt Water,ocean,Day,Any,Forbidden,Rare,0.25,0.5,15,14,32,0.88,,,,queen triggerfish blow jets of water to uncover buried prey and flip sea urchins. vivid blue lines radiate across the face.,inactive +5606,red cornetfish,Reef oddities,Salt Water,ocean,Day,Any,Required,Epic,0.11,0.2,4,24,55,0.92,,,,red cornetfish stalk reefs with extremely long bodies and tubular snouts. a trailing filament extends from the center of the tail.,inactive +5607,spotted porcupinefish,Reef oddities,Salt Water,ocean,Night,Any,Required,Epic,0.12,0.5,7,24,54,0.9,,,,spotted porcupinefish inflate until long spines stand outward. fused teeth form a strong beak for crushing shelled prey.,inactive +5608,yellow boxfish,Reef oddities,Salt Water,ocean,Day,Any,Any,Rare,0.32,0.03,0.8,10,22,0.96,,,,yellow boxfish juveniles are bright cubes covered in black spots. rigid body armor leaves small fins to provide careful steering.,inactive +5701,copperband butterflyfish,Reef ornamentals,Salt Water,ocean,Day,Any,Forbidden,Rare,0.32,0.03,0.4,9,21,0.98,,,,copperband butterflyfish probe reef cracks with long narrow snouts. bold orange bands and a false eyespot confuse predators.,inactive +5702,ocellaris clownfish,Reef ornamentals,Salt Water,ocean,Day,Any,Any,Uncommon,1,0.01,0.15,5,10,1,,,,ocellaris clownfish shelter safely among stinging anemone tentacles. each group is led by a female that develops from the largest male.,inactive +5703,queen angelfish,Reef ornamentals,Salt Water,ocean,Day,Any,Any,Rare,0.3,0.1,2,11,25,0.94,,,,queen angelfish graze sponges from warm atlantic reefs. a blue-ringed spot on the forehead resembles a tiny crown.,inactive +5801,Atlantic goliath grouper,Reef predators,Salt Water,ocean,Any,Any,Required,Legendary,0.05,20,800,50,125,0.78,,,,"atlantic goliath groupers occupy reefs, wrecks, and mangrove roots. enormous mouths pull prey inward with a sudden pressure drop.",inactive +5802,bluefin trevally,Reef predators,Salt Water,ocean,Day,Any,Forbidden,Rare,0.3,1,25,14,32,0.86,,,,bluefin trevally hunt reef fish in coordinated groups. electric blue fins and scattered blue spots contrast with a golden body.,inactive +5803,giant grouper,Reef predators,Salt Water,ocean,Any,Any,Required,Legendary,0.045,20,880,52,128,0.78,,,,giant groupers become massive ambush predators around indo-pacific reefs. juveniles carry bold black-and-yellow markings.,inactive +5804,great barracuda,Reef predators,Salt Water,ocean,Day,Any,Required,Epic,0.12,5,80,28,65,0.82,,,,great barracudas hover motionless before accelerating toward prey. long jaws carry two distinct rows of sharp teeth.,inactive +5805,gulf grouper,Reef predators,Salt Water,ocean,Any,Any,Any,Uncommon,0.3,2,40,10,24,0.9,,,,gulf grouper are ambush predators that use a sudden gulp to pull prey into their cavernous mouths.,active +5806,Nassau grouper,Reef predators,Salt Water,ocean,Any,Any,Forbidden,Epic,0.12,2,55,25,62,0.82,,,,nassau groupers shift their bars and colors with mood and surroundings. adults gather at traditional reef sites to spawn.,inactive +5807,red grouper,Reef predators,Salt Water,ocean,Any,Any,Any,Rare,0.2,2,30,16,36,0.9,,,,red grouper excavate shelters in reef rubble. the hollows they make can become hiding places for many smaller animals.,active +5901,blue-green chromis,Reef schooling fish,Salt Water,ocean,Day,Any,Any,Common,2.8,0.01,0.25,2,4,1,,,,blue-green chromis form loose clouds above branching coral. the school dives into narrow shelter whenever a predator approaches.,inactive +5902,bluestriped grunt,Reef schooling fish,Salt Water,ocean,Day,Any,Any,Common,2,0.1,3,3,7,0.94,,,,bluestriped grunts shelter on reefs by day and spread over seagrass to feed at night. grinding throat teeth produce audible grunts.,inactive +5903,French grunt,Reef schooling fish,Salt Water,ocean,Day,Any,Any,Common,2,0.1,3.5,3,7,0.94,,,,french grunts gather in yellow-striped schools around reefs and wrecks. they travel into nearby sand and grass beds after dark.,inactive +5904,sergeant major,Reef schooling fish,Salt Water,ocean,Day,Any,Any,Common,2.5,0.02,0.5,2,5,0.98,,,,sergeant majors guard purple egg patches on hard reef surfaces. five dark bars cross their yellow and silver bodies.,inactive +6001,lane snapper,Reef snapper,Salt Water,ocean,Any,Any,Any,Common,0.45,0.5,5,5,11,0.9,,,,lane snapper use reef structure for cover and hunt small crustaceans and fish when the light begins to fade.,active +6002,mangrove snapper,Reef snapper,Salt Water,ocean,Any,Any,Any,Uncommon,0.35,0.5,12,7,16,0.9,,,,"mangrove snapper shelter among roots and docks when young, then move toward reefs as they grow.",active +6003,mutton snapper,Reef snapper,Salt Water,ocean,Any,Any,Any,Rare,0.2,2,30,12,28,0.9,,,,"mutton snapper have canine teeth that help them pick crabs, shrimp, and other hard-shelled prey from the reef.",active +6004,red snapper,Reef snapper,Salt Water,ocean,Any,Any,Any,Uncommon,0.35,1,25,8,19,0.9,,,,"red snapper gather around reefs, wrecks, and ledges. their diet includes small fish, shrimp, and squid.",active +6101,ballan wrasse,Reef wrasses,Salt Water,ocean,Day,Any,Any,Uncommon,0.8,0.2,10,6,15,0.9,,,,ballan wrasse crush shellfish and sea urchins with strong throat teeth. thick lips and mottled colors suit rocky european reefs.,inactive +6102,cleaner wrasse,Reef wrasses,Salt Water,ocean,Day,Any,Forbidden,Rare,0.35,0.01,0.2,9,20,1,,,,cleaner wrasses advertise with a bright stripe and dance at reef stations. larger fish wait while parasites are picked from skin and gills.,inactive +6103,hogfish,Reef wrasses,Salt Water,ocean,Day,Any,Any,Rare,0.3,0.5,20,12,30,0.86,,,,hogfish root through sand with long pig-like snouts in search of shellfish. adults can change from female to male as social groups shift.,inactive +6201,green sea turtle,Sea turtles,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.018,65,500,62,145,0.78,,,,green sea turtles graze seagrass meadows with finely serrated jaws. adults migrate long distances between feeding and nesting grounds.,inactive +6202,hawksbill sea turtle,Sea turtles,Salt Water,ocean,Day,Required,Any,Legendary,0.014,75,250,66,150,0.8,,,,hawksbill sea turtles reach into reef crevices with narrow hooked beaks. a diet rich in sponges helps shape coral communities.,inactive +6203,leatherback sea turtle,Sea turtles,Salt Water,ocean,Night,Any,Required,Legendary,0.012,500,2000,75,175,0.76,,,,leatherback sea turtles cross entire oceans while hunting jellies. a flexible ridged shell and internal heat help them enter cold water.,inactive +6204,loggerhead sea turtle,Sea turtles,Salt Water,ocean,Day,Any,Any,Legendary,0.016,150,1200,68,155,0.77,,,,loggerhead sea turtles crush crabs and mollusks with massive jaws. females often return near their birth beaches to nest.,inactive +6301,leafy seadragon,Seahorses and seadragons,Salt Water,ocean,Day,Any,Forbidden,Epic,0.1,0.1,0.5,24,55,0.94,,,,leafy seadragons drift among seaweed with elaborate skin lobes that resemble leaves. males carry eggs beneath the tail.,inactive +6302,weedy seadragon,Seahorses and seadragons,Salt Water,ocean,Day,Any,Required,Rare,0.22,0.1,0.6,16,38,0.93,,,,weedy seadragons rely on slender bodies and small leaf-like lobes for camouflage. males brood rows of eggs on the underside of the tail.,inactive +6401,black-lip pearl oyster,Shellfish,Salt Water,ocean,Day,Any,Any,Rare,0.22,0.1,5,14,40,0.91,,,,black-lip pearl oysters line their shells with dark-edged nacre. a trapped irritant can become coated in the same pearly material.,inactive +6402,giant clam,Shellfish,Salt Water,ocean,Day,Any,Forbidden,Epic,0.1,5,500,28,72,0.8,,,,giant clams host photosynthetic algae inside brightly colored mantles. heavy shells remain anchored to one reef spot throughout adulthood.,inactive +6403,Pacific geoduck,Shellfish,Salt Water,ocean,Day,Any,Required,Rare,0.25,0.5,15,14,34,0.88,,,,pacific geoducks bury their shells deep while a long siphon reaches the seafloor. these clams can survive for well over a century.,inactive +6404,queen conch,Shellfish,Salt Water,ocean,Day,Any,Forbidden,Rare,0.24,1,5,15,36,0.91,,,,queen conchs graze algae with a long flexible snout. adults develop a broad flared shell lip and move by vaulting on one muscular foot.,inactive +6405,red abalone,Shellfish,Salt Water,ocean,Day,Any,Required,Epic,0.09,0.2,12,24,56,0.88,,,,red abalones cling to pacific rocks with broad muscular feet. rows of shell holes release water that has passed over the gills.,inactive +6501,bigfin reef squid,Squid,Salt Water,ocean,Day,Any,Forbidden,Rare,0.3,0.2,5,13,30,0.91,,,,bigfin reef squid hover in coordinated groups around warm reefs. shifting colors and body patterns signal to nearby squid.,inactive +6502,colossal squid,Squid,Salt Water,ocean,Night,Required,Any,Legendary,0.015,400,1100,82,190,0.76,,,,colossal squid carry swiveling hooks on their arms and tentacles. enormous eyes gather faint light in the deep southern ocean.,inactive +6503,giant squid,Squid,Salt Water,ocean,Night,Any,Required,Legendary,0.02,100,600,70,165,0.77,,,,giant squid hunt deep ocean prey with two long feeding tentacles. the largest eyes in the animal world watch for movement in darkness.,inactive +6504,Humboldt squid,Squid,Salt Water,ocean,Night,Any,Any,Epic,0.09,5,110,26,64,0.82,,,,humboldt squid flash red and white while hunting in large groups. powerful beaks and grasping suckers handle fish and other squid.,inactive +6601,ballyhoo,Surface ocean fish,Salt Water,ocean,Day,Any,Any,Common,2.5,0.05,1,2,5,0.98,,,,ballyhoo skim warm ocean surfaces with an extended lower jaw. tight schools scatter into quick leaps when predators strike.,inactive +6602,houndfish,Surface ocean fish,Salt Water,ocean,Day,Any,Any,Rare,0.3,0.5,10,11,25,0.88,,,,houndfish hunt near the surface with long jaws filled with fine teeth. powerful leaps can carry their narrow bodies far above the water.,inactive +6603,tropical flyingfish,Surface ocean fish,Salt Water,ocean,Day,Forbidden,Any,Rare,0.35,0.05,1.5,10,22,0.94,,,,tropical flyingfish accelerate underwater before spreading enlarged fins above the surface. glides help them escape pursuing predators.,inactive +6701,striped bass,Temperate coastal,Salt Water,ocean,Any,Any,Any,Uncommon,4,1.5,8,7,7,0.9,,,,"striped bass split their lives between salt and fresh water, returning upriver to spawn. a long-lived striper may see three decades.",active +6801,atlantic croaker,Temperate estuary,Salt Water,ocean,Night,Required,Any,Common,2.1,0.1,5,3,8,0.92,,,,atlantic croakers vibrate muscles against the swim bladder to make a croaking sound. chin barbels locate worms in sandy bays.,inactive +6802,black drum,Temperate estuary,Salt Water,ocean,Night,Any,Any,Uncommon,0.7,1,70,8,27,0.82,,,,black drum crush oysters and crabs with rounded throat teeth. large adults develop dark vertical bars that fade with age.,inactive +6803,red drum,Temperate estuary,Salt Water,ocean,Any,Required,Any,Uncommon,0.75,1,45,8,23,0.84,,,,red drum use powerful tails to patrol surf and estuaries. one or more black spots near the tail may confuse predators about which end is the head.,inactive +6804,striped mullet,Temperate estuary,Fresh Water | Salt Water,ocean,Day,Any,Any,Common,2.5,0.3,8,3,8,0.92,,,,striped mullet leap clear of the water and travel in coastal schools. they graze algae and detritus with specialized digestive systems.,inactive +6805,weakfish,Temperate estuary,Salt Water,ocean,Night,Any,Required,Rare,0.32,0.5,18,12,29,0.86,,,,weakfish are named for delicate mouth tissue that tears easily around a hook. iridescent spots scatter across their bronze backs.,inactive +6806,white perch,Temperate estuary,Fresh Water | Salt Water,river,Any,Any,Any,Uncommon,1.1,0.2,3.5,5,11,0.9,,,,white perch tolerate fresh and brackish water along the atlantic coast. deep silver bodies gather in schools around river mouths.,inactive +6807,yellowfin croaker,Temperate estuary,Salt Water,ocean,Any,Any,Any,Common,2,0.1,4,3,8,0.92,,,,yellowfin croakers search sandy surf zones with a small chin barbel. internal muscles produce croaks and drumming calls.,inactive +6901,black rockfish,Temperate reef fish,Salt Water,ocean,Any,Any,Any,Common,1.7,0.3,10,4,11,0.9,,,,"black rockfish gather above reefs and kelp along the north pacific coast. like other rockfish, they give birth to live young.",inactive +6902,black sea bass,Temperate reef fish,Salt Water,ocean,Day,Any,Any,Common,1.7,0.3,8,4,10,0.9,,,,"black sea bass shelter around reefs, piers, and wrecks. many begin life as female and later become male.",inactive +6903,canary rockfish,Temperate reef fish,Salt Water,ocean,Day,Any,Any,Uncommon,0.7,0.5,12,7,17,0.88,,,,canary rockfish glow orange-yellow over deep pacific reefs. these long-lived fish mature slowly and give birth to live young.,inactive +6904,scup,Temperate reef fish,Salt Water,ocean,Day,Any,Any,Common,2.2,0.1,5,3,8,0.92,,,,"scup use small strong teeth to crush shellfish and other bottom prey. deep silver bodies gather around piers, reefs, and eelgrass.",inactive +6905,sheepshead,Temperate reef fish,Salt Water,ocean,Day,Any,Any,Uncommon,0.75,0.5,20,7,18,0.86,,,,sheepshead carry flat human-like front teeth for biting barnacles and crabs. dark bars cross their high silver bodies.,inactive +6906,tautog,Temperate reef fish,Salt Water,ocean,Day,Any,Any,Uncommon,0.8,0.5,20,7,18,0.86,,,,tautog pry mussels and crabs from rocky structure with thick lips and strong teeth. adults return to familiar shelters year after year.,inactive +6907,yelloweye rockfish,Temperate reef fish,Salt Water,ocean,Any,Any,Required,Epic,0.09,2,35,28,64,0.84,,,,yelloweye rockfish can live for more than a century on deep pacific reefs. bright golden eyes stand out against orange-red bodies.,inactive +7001,lingcod,Temperate reef predators,Salt Water,ocean,Any,Any,Any,Rare,0.25,2,60,14,38,0.83,,,,"lingcod guard rocky dens and ambush fish with a huge tooth-lined mouth. despite the name, they are greenling relatives rather than cod.",inactive +7101,milkfish,Tropical coastal fish,Salt Water,ocean,Day,Any,Any,Common,1.8,0.5,25,4,12,0.88,,,,milkfish graze algae and tiny organisms in warm coastal shallows. deeply forked tails drive large schools through lagoons and estuaries.,inactive +7201,roosterfish,Tropical coastal predators,Salt Water,ocean,Day,Any,Any,Epic,0.11,5,100,30,70,0.82,,,,roosterfish raise seven long dorsal spines while chasing prey through tropical surf. dark bands cross their sleek silver bodies.,inactive +7301,atlantic tarpon,Tropical estuary,Fresh Water | Salt Water,ocean,Any,Required,Any,Epic,0.09,10,180,34,82,0.8,,,,atlantic tarpon gulp air with a lung-like swim bladder when coastal water runs low on oxygen. giant silver scales flash during leaps.,inactive +7302,barramundi,Tropical estuary,Fresh Water | Salt Water,river,Night,Required,Any,Rare,0.24,2,45,15,38,0.84,,,,barramundi move between rivers and coasts and can breathe in low-oxygen water. many mature first as males and later become females.,inactive +7303,common snook,Tropical estuary,Salt Water,ocean,Night,Required,Any,Rare,0.3,1,30,13,32,0.85,,,,common snook wait around mangrove roots and current seams to ambush prey. a bold black lateral line divides their silver flanks.,inactive +7401,bonefish,Tropical flats fish,Salt Water,ocean,Day,Any,Forbidden,Rare,0.25,0.5,15,13,31,0.86,,,,bonefish sweep tropical flats in silvery schools and root for buried prey. their speed and pale bodies make them difficult to follow in shallow water.,inactive +7402,Florida pompano,Tropical flats fish,Salt Water,ocean,Day,Any,Forbidden,Uncommon,0.85,0.5,9,7,16,0.88,,,,"florida pompano search warm surf for crabs, clams, and sand fleas. short blunt snouts lead into deep, silvery bodies.",inactive +7403,ladyfish,Tropical flats fish,Salt Water,ocean,Day,Required,Any,Common,1.7,0.2,8,4,10,0.9,,,,ladyfish race through warm shallows after schooling prey and often leap when pursued. a deeply forked tail powers their slender bodies.,inactive +7404,permit,Tropical flats fish,Salt Water,ocean,Day,Forbidden,Any,Epic,0.11,3,50,27,62,0.84,,,,permit browse tropical flats for crabs and shellfish with rubbery mouths. tall silver bodies carry long sickle-shaped dorsal and anal fins.,inactive +7501,clown knifefish,Tropical freshwater fish,Fresh Water,river,Night,Any,Required,Rare,0.25,1,25,16,38,0.86,,,,clown knifefish undulate one long belly fin to glide forward or backward. dark eye-like spots line their silver nocturnal bodies.,inactive +7502,clown loach,Tropical freshwater fish,Fresh Water,river,Day,Required,Any,Rare,0.32,0.1,2,10,22,0.9,,,,clown loaches rest in groups and make clicking sounds while feeding. orange bodies carry three broad black bands and small eye spines.,inactive +7503,discus,Tropical freshwater fish,Fresh Water,river,Day,Any,Required,Epic,0.12,0.1,1,24,52,0.92,,,,"discus move through quiet amazon backwaters with round, flattened bodies. parents feed newly hatched young with nutrient-rich skin mucus.",inactive +7504,freshwater angelfish,Tropical freshwater fish,Fresh Water,river,Day,Any,Any,Rare,0.3,0.05,0.5,10,22,0.95,,,,freshwater angelfish slip between tall plants on narrow triangular bodies. long fins and vertical bars resemble stems and shadows.,inactive +7505,giant gourami,Tropical freshwater fish,Fresh Water,lake,Day,Any,Any,Rare,0.25,2,20,13,31,0.86,,,,giant gouramis breathe surface air through a specialized labyrinth organ. adults graze plants but also investigate insects and small animals.,inactive +7506,Nile tilapia,Tropical freshwater fish,Fresh Water,lake,Day,Any,Any,Common,2.2,0.3,8,3,9,0.9,,,,nile tilapia thrive in warm lakes and rivers on a varied diet. females protect eggs and young inside their mouths.,inactive +7507,oscar,Tropical freshwater fish,Fresh Water,river,Day,Any,Any,Uncommon,0.8,0.3,4,6,14,0.9,,,,oscars are large south american cichlids that recognize routines and defend territories. a tail eyespot may redirect attacks away from the head.,inactive +7508,Siamese fighting fish,Tropical freshwater fish,Fresh Water,pond,Day,Required,Any,Uncommon,1,0.01,0.15,5,10,1,,,,siamese fighting fish breathe surface air with a labyrinth organ. males build bubble nests and guard the eggs beneath them.,inactive +7509,tambaqui,Tropical freshwater fish,Fresh Water,river,Day,Required,Any,Epic,0.1,8,80,30,68,0.82,,,,tambaqui follow seasonal floods into forests to feed on fruit and seeds. strong square teeth crack tough plant material.,inactive +7510,walking catfish,Tropical freshwater fish,Fresh Water,pond,Night,Required,Any,Rare,0.3,0.5,5,11,24,0.9,,,,walking catfish wriggle across wet ground using stiff pectoral spines for support. an air-breathing organ helps them survive the journey.,inactive +7601,giant snakehead,Tropical freshwater predators,Fresh Water,river,Night,Any,Required,Epic,0.1,3,45,28,64,0.84,,,,giant snakeheads breathe air and guard schools of young near the surface. long bodies launch sudden attacks from flooded vegetation.,inactive +7602,golden dorado,Tropical freshwater predators,Fresh Water,river,Day,Required,Any,Epic,0.1,5,55,29,66,0.82,,,,"golden dorado chase fish through powerful south american rivers. muscular tails, heavy jaws, and golden scales suit life in fast current.",inactive +7603,golden mahseer,Tropical freshwater predators,Fresh Water,river,Day,Required,Any,Epic,0.09,8,90,32,72,0.82,,,,golden mahseer migrate through swift mountain rivers to reach spawning grounds. large scales shine bronze beneath reddish fins.,inactive +7604,peacock bass,Tropical freshwater predators,Fresh Water,river,Day,Any,Any,Rare,0.25,1,25,15,36,0.84,,,,peacock bass are aggressive south american cichlids rather than true bass. adults guard nests and hunt fish in warm clear water.,inactive +7605,red-bellied piranha,Tropical freshwater predators,Fresh Water,river,Any,Required,Any,Rare,0.3,0.2,4,12,27,0.9,,,,"red-bellied piranhas communicate with barks and drum-like sounds. schools provide protection as they search for fish, insects, and carrion.",inactive +7606,silver arowana,Tropical freshwater predators,Fresh Water,river,Day,Any,Required,Epic,0.11,1,15,25,56,0.86,,,,silver arowanas patrol the surface with upturned mouths and long bodies. powerful jumps capture insects and small animals above the water.,inactive +7701,blue whale,Whales and dolphins,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.005,100000,330000,120,300,0.72,,,,blue whales are the largest animals known to have lived. pleated throats expand around vast mouthfuls of water that baleen filters for krill.,inactive +7702,common bottlenose dolphin,Whales and dolphins,Salt Water,ocean,Day,Any,Forbidden,Legendary,0.012,330,1400,75,180,0.76,,,,"common bottlenose dolphins coordinate with whistles, clicks, and body signals. echolocation reveals prey hidden in dark or cloudy water.",inactive +7703,humpback whale,Whales and dolphins,Salt Water,ocean,Day,Any,Required,Legendary,0.008,40000,90000,105,260,0.73,,,,humpback whales use long pectoral fins while turning and feeding. males produce layered songs that change across breeding seasons.,inactive +7704,killer whale,Whales and dolphins,Salt Water,ocean,Day,Required,Any,Legendary,0.008,3000,12000,95,230,0.74,,,,killer whales live in close family groups with distinct calls and hunting traditions. different populations specialize in different prey.,inactive +7705,sperm whale,Whales and dolphins,Salt Water,ocean,Night,Any,Required,Legendary,0.006,15000,125000,115,280,0.72,,,,sperm whales make deep dives to hunt squid with powerful echolocation clicks. their enormous square heads contain a waxy spermaceti organ.,inactive diff --git a/docs/design/fish_catalog_proposal_final.csv b/docs/design/fish_catalog_proposal_final.csv new file mode 100644 index 0000000..e0b402f --- /dev/null +++ b/docs/design/fish_catalog_proposal_final.csv @@ -0,0 +1,314 @@ +catalog_number,id,implementation_status,active_status,art_status,fish_resource_path,display_texture_path,catch_profile_id +101,bowfin,implemented,inactive,missing icon,res://fish/species/bowfin/bowfin.tres,,common +102,sturgeon_lake,implemented,inactive,missing icon,res://fish/species/sturgeon_lake/sturgeon_lake.tres,,common +103,gar_longnose,implemented,inactive,missing icon,res://fish/species/gar_longnose/gar_longnose.tres,,common +104,paddlefish,implemented,inactive,missing icon,res://fish/species/paddlefish/paddlefish.tres,,common +105,sturgeon_shovelnose,implemented,inactive,missing icon,res://fish/species/sturgeon_shovelnose/sturgeon_shovelnose.tres,,common +106,gar_spotted,implemented,inactive,missing icon,res://fish/species/gar_spotted/gar_spotted.tres,,common +107,lungfish_west_african,implemented,inactive,missing icon,res://fish/species/lungfish_west_african/lungfish_west_african.tres,,common +201,mudskipper_atlantic,implemented,inactive,missing icon,res://fish/species/mudskipper_atlantic/mudskipper_atlantic.tres,,common +202,foureyes_largescale,implemented,inactive,missing icon,res://fish/species/foureyes_largescale/foureyes_largescale.tres,,common +301,mullet_red,implemented,inactive,missing icon,res://fish/species/mullet_red/mullet_red.tres,,common +401,plaice_european,implemented,inactive,missing icon,res://fish/species/plaice_european/plaice_european.tres,,common +402,flounder_summer,implemented,inactive,missing icon,res://fish/species/flounder_summer/flounder_summer.tres,,common +403,flounder_winter,implemented,inactive,missing icon,res://fish/species/flounder_winter/flounder_winter.tres,,common +501,dugong,implemented,inactive,missing icon,res://fish/species/dugong/dugong.tres,,common +502,seal_harbor,implemented,inactive,missing icon,res://fish/species/seal_harbor/seal_harbor.tres,,common +503,sea_otter,implemented,inactive,missing icon,res://fish/species/sea_otter/sea_otter.tres,,common +504,walrus,implemented,inactive,missing icon,res://fish/species/walrus/walrus.tres,,common +505,manatee_west_indian,implemented,inactive,missing icon,res://fish/species/manatee_west_indian/manatee_west_indian.tres,,common +601,pipefish_bay,implemented,inactive,missing icon,res://fish/species/pipefish_bay/pipefish_bay.tres,,common +602,seahorse_lined,implemented,inactive,missing icon,res://fish/species/seahorse_lined/seahorse_lined.tres,,common +603,monkfish,implemented,inactive,missing icon,res://fish/species/monkfish/monkfish.tres,,common +604,tripletail,implemented,inactive,missing icon,res://fish/species/tripletail/tripletail.tres,,common +701,mackerel_atlantic,implemented,active,complete,res://fish/species/mackerel_atlantic/mackerel_atlantic.tres,res://fish/species/mackerel_atlantic/fish_mackerel_atlantic.png,common +702,bluefish,implemented,inactive,missing icon,res://fish/species/bluefish/bluefish.tres,,common +703,mackerel_cero,implemented,active,complete,res://fish/species/mackerel_cero/mackerel_cero.tres,res://fish/species/mackerel_cero/fish_mackerel_cero.png,common +704,mackerel_chub,implemented,active,complete,res://fish/species/mackerel_chub/mackerel_chub.tres,res://fish/species/mackerel_chub/fish_mackerel_chub.png,common +705,jack_crevalle,implemented,inactive,missing icon,res://fish/species/jack_crevalle/jack_crevalle.tres,,common +706,mackerel_king,implemented,active,complete,res://fish/species/mackerel_king/mackerel_king.tres,res://fish/species/mackerel_king/fish_mackerel_king.png,common +707,mackerel_spanish,implemented,active,complete,res://fish/species/mackerel_spanish/mackerel_spanish.tres,res://fish/species/mackerel_spanish/fish_mackerel_spanish.png,common +708,queenfish_talang,implemented,inactive,missing icon,res://fish/species/queenfish_talang/queenfish_talang.tres,,common +801,sand_lance_american,implemented,inactive,missing icon,res://fish/species/sand_lance_american/sand_lance_american.tres,,common +802,menhaden_atlantic,implemented,inactive,missing icon,res://fish/species/menhaden_atlantic/menhaden_atlantic.tres,,common +803,pomfret_black,implemented,active,complete,res://fish/species/pomfret_black/pomfret_black.tres,res://fish/species/pomfret_black/fish_pomfret_black.png,common +804,pomfret_chinese,implemented,active,complete,res://fish/species/pomfret_chinese/pomfret_chinese.tres,res://fish/species/pomfret_chinese/fish_pomfret_chinese.png,common +805,anchovy_european,implemented,active,complete,res://fish/species/anchovy_european/anchovy_european.tres,res://fish/species/anchovy_european/fish_anchovy_european.png,common +806,sardine_european,implemented,inactive,missing icon,res://fish/species/sardine_european/sardine_european.tres,,common +807,pomfret_golden,implemented,active,complete,res://fish/species/pomfret_golden/pomfret_golden.tres,res://fish/species/pomfret_golden/fish_pomfret_golden.png,common +808,anchovy_northern,implemented,active,complete,res://fish/species/anchovy_northern/anchovy_northern.tres,res://fish/species/anchovy_northern/fish_anchovy_northern.png,common +809,sardine_pacific,implemented,inactive,missing icon,res://fish/species/sardine_pacific/sardine_pacific.tres,,common +810,pomfret_white,implemented,active,complete,res://fish/species/pomfret_white/pomfret_white.tres,res://fish/species/pomfret_white/fish_pomfret_white.png,common +901,shark_blacktip,implemented,inactive,missing icon,res://fish/species/shark_blacktip/shark_blacktip.tres,,common +902,shark_great_hammerhead,implemented,inactive,missing icon,res://fish/species/shark_great_hammerhead/shark_great_hammerhead.tres,,common +903,shark_great_white,implemented,inactive,missing icon,res://fish/species/shark_great_white/shark_great_white.tres,,common +904,shark_tiger,implemented,inactive,missing icon,res://fish/species/shark_tiger/shark_tiger.tres,,common +905,shark_whale,implemented,inactive,missing icon,res://fish/species/shark_whale/shark_whale.tres,,common +1001,burbot,implemented,inactive,missing icon,res://fish/species/burbot/burbot.tres,,common +1002,cisco,implemented,inactive,missing icon,res://fish/species/cisco/cisco.tres,,common +1003,whitefish_lake,implemented,inactive,missing icon,res://fish/species/whitefish_lake/whitefish_lake.tres,,common +1101,char_arctic,implemented,inactive,missing icon,res://fish/species/char_arctic/char_arctic.tres,,common +1102,grayling_arctic,implemented,inactive,missing icon,res://fish/species/grayling_arctic/grayling_arctic.tres,,common +1103,trout_brown,implemented,inactive,missing icon,res://fish/species/trout_brown/trout_brown.tres,,common +1104,trout_cutthroat,implemented,active,complete,res://fish/species/trout_cutthroat/trout_cutthroat.tres,res://fish/species/trout_cutthroat/fish_trout_cutthroat.png,common +1105,trout_golden,implemented,active,complete,res://fish/species/trout_golden/trout_golden.tres,res://fish/species/trout_golden/fish_trout_golden.png,common +1106,trout_rainbow,implemented,active,complete,res://fish/species/trout_rainbow/trout_rainbow.tres,res://fish/species/trout_rainbow/fish_trout_rainbow.png,common +1201,wolffish_atlantic,implemented,inactive,missing icon,res://fish/species/wolffish_atlantic/wolffish_atlantic.tres,,common +1202,cusk,implemented,inactive,missing icon,res://fish/species/cusk/cusk.tres,,common +1203,wolffish_spotted,implemented,inactive,missing icon,res://fish/species/wolffish_spotted/wolffish_spotted.tres,,common +1301,herring_atlantic,implemented,inactive,missing icon,res://fish/species/herring_atlantic/herring_atlantic.tres,,common +1302,capelin,implemented,inactive,missing icon,res://fish/species/capelin/capelin.tres,,common +1303,sprat_european,implemented,inactive,missing icon,res://fish/species/sprat_european/sprat_european.tres,,common +1304,herring_pacific,implemented,inactive,missing icon,res://fish/species/herring_pacific/herring_pacific.tres,,common +1401,pollock_alaska,implemented,inactive,missing icon,res://fish/species/pollock_alaska/pollock_alaska.tres,,common +1402,cod_atlantic,implemented,inactive,missing icon,res://fish/species/cod_atlantic/cod_atlantic.tres,,common +1403,pollock_atlantic,implemented,inactive,missing icon,res://fish/species/pollock_atlantic/pollock_atlantic.tres,,common +1404,sole_dover,implemented,inactive,missing icon,res://fish/species/sole_dover/sole_dover.tres,,common +1405,haddock,implemented,inactive,missing icon,res://fish/species/haddock/haddock.tres,,common +1406,cod_pacific,implemented,inactive,missing icon,res://fish/species/cod_pacific/cod_pacific.tres,,common +1407,hake_pacific,implemented,inactive,missing icon,res://fish/species/hake_pacific/hake_pacific.tres,,common +1408,hake_silver,implemented,inactive,missing icon,res://fish/species/hake_silver/hake_silver.tres,,common +1501,nautilus_chambered,implemented,inactive,missing icon,res://fish/species/nautilus_chambered/nautilus_chambered.tres,,common +1502,cuttlefish_common,implemented,inactive,missing icon,res://fish/species/cuttlefish_common/cuttlefish_common.tres,,common +1503,cuttlefish_flamboyant,implemented,inactive,missing icon,res://fish/species/cuttlefish_flamboyant/cuttlefish_flamboyant.tres,,common +1601,tilefish_blueline,implemented,inactive,missing icon,res://fish/species/tilefish_blueline/tilefish_blueline.tres,,common +1602,tilefish_golden,implemented,inactive,missing icon,res://fish/species/tilefish_golden/tilefish_golden.tres,,common +1603,ocean_perch_pacific,implemented,inactive,missing icon,res://fish/species/ocean_perch_pacific/ocean_perch_pacific.tres,,common +1604,sea_bass_chilean,implemented,inactive,missing icon,res://fish/species/sea_bass_chilean/sea_bass_chilean.tres,,common +1605,sablefish,implemented,inactive,missing icon,res://fish/species/sablefish/sablefish.tres,,common +1606,wreckfish,implemented,inactive,missing icon,res://fish/species/wreckfish/wreckfish.tres,,common +1701,oarfish_giant,implemented,inactive,missing icon,res://fish/species/oarfish_giant/oarfish_giant.tres,,common +1702,coelacanth_west_indian,implemented,inactive,missing icon,res://fish/species/coelacanth_west_indian/coelacanth_west_indian.tres,,common +1801,anglerfish_black_seadevil,implemented,inactive,missing icon,res://fish/species/anglerfish_black_seadevil/anglerfish_black_seadevil.tres,,common +1802,fangtooth_common,implemented,inactive,missing icon,res://fish/species/fangtooth_common/fangtooth_common.tres,,common +1803,gulper_eel,implemented,inactive,missing icon,res://fish/species/gulper_eel/gulper_eel.tres,,common +1804,barreleye_pacific,implemented,inactive,missing icon,res://fish/species/barreleye_pacific/barreleye_pacific.tres,,common +1805,blobfish_smooth_head,implemented,inactive,missing icon,res://fish/species/blobfish_smooth_head/blobfish_smooth_head.tres,,common +1806,lanternfish_spotted,implemented,inactive,missing icon,res://fish/species/lanternfish_spotted/lanternfish_spotted.tres,,common +1901,sea_star_crown_of_thorns,implemented,inactive,missing icon,res://fish/species/sea_star_crown_of_thorns/sea_star_crown_of_thorns.tres,,common +1902,sea_cucumber_giant_california,implemented,inactive,missing icon,res://fish/species/sea_cucumber_giant_california/sea_cucumber_giant_california.tres,,common +1903,urchin_purple,implemented,inactive,missing icon,res://fish/species/urchin_purple/urchin_purple.tres,,common +1904,sand_dollar,implemented,inactive,missing icon,res://fish/species/sand_dollar/sand_dollar.tres,,common +1905,sea_star_sunflower,implemented,inactive,missing icon,res://fish/species/sea_star_sunflower/sea_star_sunflower.tres,,common +2001,axolotl,implemented,inactive,missing icon,res://fish/species/axolotl/axolotl.tres,,common +2002,hellbender,implemented,inactive,missing icon,res://fish/species/hellbender/hellbender.tres,,common +2101,guppy,implemented,inactive,missing icon,res://fish/species/guppy/guppy.tres,,common +2102,tetra_neon,implemented,inactive,missing icon,res://fish/species/tetra_neon/tetra_neon.tres,,common +2103,danio_zebra,implemented,inactive,missing icon,res://fish/species/danio_zebra/danio_zebra.tres,,common +2201,largemouth_bass,implemented,inactive,missing icon,res://fish/species/largemouth_bass/largemouth_bass.tres,,common +2202,smallmouth_bass,implemented,inactive,missing icon,res://fish/species/smallmouth_bass/smallmouth_bass.tres,,common +2301,buffalo_bigmouth,implemented,inactive,missing icon,res://fish/species/buffalo_bigmouth/buffalo_bigmouth.tres,,common +2302,redhorse_golden,implemented,inactive,missing icon,res://fish/species/redhorse_golden/redhorse_golden.tres,,common +2303,quillback,implemented,inactive,missing icon,res://fish/species/quillback/quillback.tres,,common +2304,buffalo_smallmouth,implemented,inactive,missing icon,res://fish/species/buffalo_smallmouth/buffalo_smallmouth.tres,,common +2305,sucker_white,implemented,inactive,missing icon,res://fish/species/sucker_white/sucker_white.tres,,common +2401,bullhead_black,implemented,inactive,missing icon,res://fish/species/bullhead_black/bullhead_black.tres,,common +2402,catfish_blue,implemented,active,complete,res://fish/species/catfish_blue/catfish_blue.tres,res://fish/species/catfish_blue/fish_catfish_blue.png,bass +2403,bullhead_brown,implemented,inactive,missing icon,res://fish/species/bullhead_brown/bullhead_brown.tres,,common +2404,catfish_channel,implemented,active,complete,res://fish/species/catfish_channel/catfish_channel.tres,res://fish/species/catfish_channel/fish_catfish_channel.png,common +2405,catfish_flathead,implemented,active,complete,res://fish/species/catfish_flathead/catfish_flathead.tres,res://fish/species/catfish_flathead/fish_catfish_flathead.png,bass +2406,madtom_tadpole,implemented,inactive,missing icon,res://fish/species/madtom_tadpole/madtom_tadpole.tres,,common +2407,catfish_white,implemented,active,complete,res://fish/species/catfish_white/catfish_white.tres,res://fish/species/catfish_white/fish_catfish_white.png,common +2408,bullhead_yellow,implemented,inactive,missing icon,res://fish/species/bullhead_yellow/bullhead_yellow.tres,,common +2501,barbel_common,implemented,inactive,missing icon,res://fish/species/barbel_common/barbel_common.tres,,common +2502,bream_common,implemented,inactive,missing icon,res://fish/species/bream_common/bream_common.tres,,common +2503,carp,implemented,active,complete,res://fish/species/carp/carp.tres,res://fish/species/carp/fish_carp_common.png,carp +2504,roach_common,implemented,inactive,missing icon,res://fish/species/roach_common/roach_common.tres,,common +2505,chub_european,implemented,active,complete,res://fish/species/chub_european/chub_european.tres,res://fish/species/chub_european/fish_chub_european.png,common +2506,fallfish,implemented,inactive,missing icon,res://fish/species/fallfish/fallfish.tres,,common +2507,ide,implemented,inactive,missing icon,res://fish/species/ide/ide.tres,,common +2508,rudd,implemented,inactive,missing icon,res://fish/species/rudd/rudd.tres,,common +2509,tench,implemented,inactive,missing icon,res://fish/species/tench/tench.tres,,common +2601,crayfish_red_swamp,implemented,inactive,missing icon,res://fish/species/crayfish_red_swamp/crayfish_red_swamp.tres,,common +2701,gar_alligator,implemented,inactive,missing icon,res://fish/species/gar_alligator/gar_alligator.tres,,common +2702,arapaima,implemented,inactive,missing icon,res://fish/species/arapaima/arapaima.tres,,common +2703,tigerfish_goliath,implemented,inactive,missing icon,res://fish/species/tigerfish_goliath/tigerfish_goliath.tres,,common +2704,muskellunge,implemented,inactive,missing icon,res://fish/species/muskellunge/muskellunge.tres,,common +2801,shiner_common,implemented,inactive,missing icon,res://fish/species/shiner_common/shiner_common.tres,,common +2802,chub_creek,implemented,inactive,missing icon,res://fish/species/chub_creek/chub_creek.tres,,common +2803,shiner_emerald,implemented,inactive,missing icon,res://fish/species/shiner_emerald/shiner_emerald.tres,,common +2804,minnow_fathead,implemented,inactive,missing icon,res://fish/species/minnow_fathead/minnow_fathead.tres,,common +2805,chub_flame,implemented,active,complete,res://fish/species/chub_flame/chub_flame.tres,res://fish/species/chub_flame/fish_chub_flame.png,common +2806,shiner_golden,implemented,inactive,missing icon,res://fish/species/shiner_golden/shiner_golden.tres,,common +2807,chub_lake,implemented,active,complete,res://fish/species/chub_lake/chub_lake.tres,res://fish/species/chub_lake/fish_chub_lake.png,common +2901,electric_eel,implemented,inactive,missing icon,res://fish/species/electric_eel/electric_eel.tres,,common +2902,freshwater_drum,implemented,inactive,missing icon,res://fish/species/freshwater_drum/freshwater_drum.tres,,common +2903,goldeye,implemented,inactive,missing icon,res://fish/species/goldeye/goldeye.tres,,common +2904,mooneye,implemented,inactive,missing icon,res://fish/species/mooneye/mooneye.tres,,common +2905,loach_weather,implemented,inactive,missing icon,res://fish/species/loach_weather/loach_weather.tres,,common +3001,goldfish_bubbleeye,implemented,active,complete,res://fish/species/goldfish_bubbleeye/goldfish_bubbleeye.tres,res://fish/species/goldfish_bubbleeye/fish_goldfish_bubbleeye.png,common +3002,goldfish,implemented,active,complete,res://fish/species/goldfish/goldfish.tres,res://fish/species/goldfish/fish_goldfish.png,common +3003,koi,implemented,inactive,missing icon,res://fish/species/koi/koi.tres,,common +3101,crappie_black,implemented,inactive,missing icon,res://fish/species/crappie_black/crappie_black.tres,,common +3102,bluegill,implemented,active,complete,res://fish/species/bluegill/bluegill.tres,res://fish/species/bluegill/fish_bluegill.png,common +3103,green_sunfish,implemented,inactive,missing icon,res://fish/species/green_sunfish/green_sunfish.tres,,common +3104,pumpkinseed,implemented,inactive,missing icon,res://fish/species/pumpkinseed/pumpkinseed.tres,,common +3105,redear_sunfish,implemented,inactive,missing icon,res://fish/species/redear_sunfish/redear_sunfish.tres,,common +3106,warmouth,implemented,inactive,missing icon,res://fish/species/warmouth/warmouth.tres,,common +3107,crappie_white,implemented,inactive,missing icon,res://fish/species/crappie_white/crappie_white.tres,,common +3201,yellow_perch,implemented,inactive,missing icon,res://fish/species/yellow_perch/yellow_perch.tres,,common +3301,pickerel_chain,implemented,inactive,missing icon,res://fish/species/pickerel_chain/pickerel_chain.tres,,common +3302,pickerel_grass,implemented,inactive,missing icon,res://fish/species/pickerel_grass/pickerel_grass.tres,,common +3303,pike_northern,implemented,inactive,missing icon,res://fish/species/pike_northern/pike_northern.tres,,common +3304,sauger,implemented,active,complete,res://fish/species/sauger/sauger.tres,res://fish/species/sauger/fish_sauger.png,common +3305,saugeye,implemented,active,complete,res://fish/species/saugeye/saugeye.tres,res://fish/species/saugeye/fish_saugeye.png,common +3306,walleye,implemented,active,complete,res://fish/species/walleye/walleye.tres,res://fish/species/walleye/fish_walleye.png,common +3401,goby_round,implemented,active,complete,res://fish/species/goby_round/goby_round.tres,res://fish/species/goby_round/fish_goby_round.png,common +3501,jelly_crystal,implemented,inactive,missing icon,res://fish/species/jelly_crystal/jelly_crystal.tres,,common +3502,jelly_lions_mane,implemented,inactive,missing icon,res://fish/species/jelly_lions_mane/jelly_lions_mane.tres,,common +3503,jelly_moon,implemented,inactive,missing icon,res://fish/species/jelly_moon/jelly_moon.tres,,common +3504,jelly_pacific_sea_nettle,implemented,inactive,missing icon,res://fish/species/jelly_pacific_sea_nettle/jelly_pacific_sea_nettle.tres,,common +3505,jelly_sea_wasp,implemented,inactive,missing icon,res://fish/species/jelly_sea_wasp/jelly_sea_wasp.tres,,common +3601,pompano_african,implemented,inactive,missing icon,res://fish/species/pompano_african/pompano_african.tres,,common +3602,cobia,implemented,inactive,missing icon,res://fish/species/cobia/cobia.tres,,common +3603,trevally_giant,implemented,inactive,missing icon,res://fish/species/trevally_giant/trevally_giant.tres,,common +3604,amberjack_greater,implemented,inactive,missing icon,res://fish/species/amberjack_greater/amberjack_greater.tres,,common +3605,amberjack_lesser,implemented,inactive,missing icon,res://fish/species/amberjack_lesser/amberjack_lesser.tres,,common +3701,halibut_atlantic,implemented,inactive,missing icon,res://fish/species/halibut_atlantic/halibut_atlantic.tres,,common +3702,halibut_pacific,implemented,inactive,missing icon,res://fish/species/halibut_pacific/halibut_pacific.tres,,common +3703,turbot,implemented,inactive,missing icon,res://fish/species/turbot/turbot.tres,,common +3801,lobster_american,implemented,inactive,missing icon,res://fish/species/lobster_american/lobster_american.tres,,common +3802,krill_antarctic,implemented,inactive,missing icon,res://fish/species/krill_antarctic/krill_antarctic.tres,,common +3803,lobster_caribbean_spiny,implemented,inactive,missing icon,res://fish/species/lobster_caribbean_spiny/lobster_caribbean_spiny.tres,,common +3804,shrimp_peacock_mantis,implemented,inactive,missing icon,res://fish/species/shrimp_peacock_mantis/shrimp_peacock_mantis.tres,,common +3805,shrimp_skunk_cleaner,implemented,inactive,missing icon,res://fish/species/shrimp_skunk_cleaner/shrimp_skunk_cleaner.tres,,common +3901,crab_ghost,implemented,inactive,missing icon,res://fish/species/crab_ghost/crab_ghost.tres,,common +3902,crab_blue,implemented,inactive,missing icon,res://fish/species/crab_blue/crab_blue.tres,,common +3903,crab_dungeness,implemented,inactive,missing icon,res://fish/species/crab_dungeness/crab_dungeness.tres,,common +3904,crab_japanese_spider,implemented,inactive,missing icon,res://fish/species/crab_japanese_spider/crab_japanese_spider.tres,,common +3905,crab_red_king,implemented,inactive,missing icon,res://fish/species/crab_red_king/crab_red_king.tres,,common +4001,eel_american,implemented,inactive,missing icon,res://fish/species/eel_american/eel_american.tres,,common +4002,eel_european,implemented,inactive,missing icon,res://fish/species/eel_european/eel_european.tres,,common +4101,smelt_rainbow,implemented,inactive,missing icon,res://fish/species/smelt_rainbow/smelt_rainbow.tres,,common +4201,smelt_eulachon,implemented,inactive,missing icon,res://fish/species/smelt_eulachon/smelt_eulachon.tres,,common +4301,sturgeon_white,implemented,inactive,missing icon,res://fish/species/sturgeon_white/sturgeon_white.tres,,common +4401,salmon_atlantic,implemented,active,complete,res://fish/species/salmon_atlantic/salmon_atlantic.tres,res://fish/species/salmon_atlantic/fish_salmon_atlantic.png,bass +4402,salmon_chum,implemented,active,complete,res://fish/species/salmon_chum/salmon_chum.tres,res://fish/species/salmon_chum/fish_salmon_chum.png,bass +4403,salmon_coho,implemented,active,complete,res://fish/species/salmon_coho/salmon_coho.tres,res://fish/species/salmon_coho/fish_salmon_coho.png,bass +4404,salmon_pink,implemented,active,complete,res://fish/species/salmon_pink/salmon_pink.tres,res://fish/species/salmon_pink/fish_salmon_pink.png,bass +4405,salmon_sockeye,implemented,active,complete,res://fish/species/salmon_sockeye/salmon_sockeye.tres,res://fish/species/salmon_sockeye/fish_salmon_sockeye.png,bass +4501,trout_steelhead,implemented,active,complete,res://fish/species/trout_steelhead/trout_steelhead.tres,res://fish/species/trout_steelhead/fish_trout_steelhead.png,common +4601,marlin_black,implemented,active,complete,res://fish/species/marlin_black/marlin_black.tres,res://fish/species/marlin_black/fish_marlin_black.png,common +4602,marlin_blue,implemented,active,complete,res://fish/species/marlin_blue/marlin_blue.tres,res://fish/species/marlin_blue/fish_marlin_blue.png,common +4603,sailfish,implemented,active,complete,res://fish/species/sailfish/sailfish.tres,res://fish/species/sailfish/fish_sailfish.png,common +4604,swordfish,implemented,active,complete,res://fish/species/swordfish/swordfish.tres,res://fish/species/swordfish/fish_swordfish.png,common +4605,marlin_white,implemented,active,complete,res://fish/species/marlin_white/marlin_white.tres,res://fish/species/marlin_white/fish_marlin_white.png,common +4701,mahi_mahi,implemented,inactive,missing icon,res://fish/species/mahi_mahi/mahi_mahi.tres,,common +4702,opah,implemented,inactive,missing icon,res://fish/species/opah/opah.tres,,common +4703,wahoo,implemented,inactive,missing icon,res://fish/species/wahoo/wahoo.tres,,common +4801,tuna_albacore,implemented,active,complete,res://fish/species/tuna_albacore/tuna_albacore.tres,res://fish/species/tuna_albacore/fish_tuna_albacore.png,bass +4802,tuna_bigeye,implemented,active,complete,res://fish/species/tuna_bigeye/tuna_bigeye.tres,res://fish/species/tuna_bigeye/fish_tuna_bigeye.png,bass +4803,tuna_bluefin,implemented,active,complete,res://fish/species/tuna_bluefin/tuna_bluefin.tres,res://fish/species/tuna_bluefin/fish_tuna_bluefin.png,bass +4804,tuna_skipjack,implemented,active,complete,res://fish/species/tuna_skipjack/tuna_skipjack.tres,res://fish/species/tuna_skipjack/fish_tuna_skipjack.png,bass +4805,tuna_yellowfin,implemented,active,complete,res://fish/species/tuna_yellowfin/tuna_yellowfin.tres,res://fish/species/tuna_yellowfin/fish_tuna_yellowfin.png,bass +4901,octopus_common,implemented,inactive,missing icon,res://fish/species/octopus_common/octopus_common.tres,,common +4902,octopus_day,implemented,inactive,missing icon,res://fish/species/octopus_day/octopus_day.tres,,common +4903,octopus_giant_pacific,implemented,inactive,missing icon,res://fish/species/octopus_giant_pacific/octopus_giant_pacific.tres,,common +4904,octopus_greater_blue_ringed,implemented,inactive,missing icon,res://fish/species/octopus_greater_blue_ringed/octopus_greater_blue_ringed.tres,,common +5001,sunfish,implemented,active,complete,res://fish/species/sunfish/sunfish.tres,res://fish/species/sunfish/fish_sunfish.png,common +5101,skate_barndoor,implemented,inactive,missing icon,res://fish/species/skate_barndoor/skate_barndoor.tres,,common +5102,manta_ray_giant,implemented,inactive,missing icon,res://fish/species/manta_ray_giant/manta_ray_giant.tres,,common +5103,sawfish_largetooth,implemented,inactive,missing icon,res://fish/species/sawfish_largetooth/sawfish_largetooth.tres,,common +5104,stingray_ocellate_river,implemented,inactive,missing icon,res://fish/species/stingray_ocellate_river/stingray_ocellate_river.tres,,common +5105,stingray_southern,implemented,inactive,missing icon,res://fish/species/stingray_southern/stingray_southern.tres,,common +5201,lionfish_red,implemented,inactive,missing icon,res://fish/species/lionfish_red/lionfish_red.tres,,common +5202,scorpionfish_red,implemented,inactive,missing icon,res://fish/species/scorpionfish_red/scorpionfish_red.tres,,common +5301,moray_green,implemented,inactive,missing icon,res://fish/species/moray_green/moray_green.tres,,common +5401,flounder_peacock,implemented,inactive,missing icon,res://fish/species/flounder_peacock/flounder_peacock.tres,,common +5501,surgeonfish_blue,implemented,inactive,missing icon,res://fish/species/surgeonfish_blue/surgeonfish_blue.tres,,common +5502,parrotfish_rainbow,implemented,inactive,missing icon,res://fish/species/parrotfish_rainbow/parrotfish_rainbow.tres,,common +5503,parrotfish_stoplight,implemented,inactive,missing icon,res://fish/species/parrotfish_stoplight/parrotfish_stoplight.tres,,common +5504,tang_yellow,implemented,inactive,missing icon,res://fish/species/tang_yellow/tang_yellow.tres,,common +5601,trumpetfish_atlantic,implemented,inactive,missing icon,res://fish/species/trumpetfish_atlantic/trumpetfish_atlantic.tres,,common +5602,triggerfish_gray,implemented,inactive,missing icon,res://fish/species/triggerfish_gray/triggerfish_gray.tres,,common +5603,pufferfish_guineafowl,implemented,inactive,missing icon,res://fish/species/pufferfish_guineafowl/pufferfish_guineafowl.tres,,common +5604,cowfish_longhorn,implemented,inactive,missing icon,res://fish/species/cowfish_longhorn/cowfish_longhorn.tres,,common +5605,triggerfish_queen,implemented,inactive,missing icon,res://fish/species/triggerfish_queen/triggerfish_queen.tres,,common +5606,cornetfish_red,implemented,inactive,missing icon,res://fish/species/cornetfish_red/cornetfish_red.tres,,common +5607,porcupinefish_spotted,implemented,inactive,missing icon,res://fish/species/porcupinefish_spotted/porcupinefish_spotted.tres,,common +5608,boxfish_yellow,implemented,inactive,missing icon,res://fish/species/boxfish_yellow/boxfish_yellow.tres,,common +5701,butterflyfish_copperband,implemented,inactive,missing icon,res://fish/species/butterflyfish_copperband/butterflyfish_copperband.tres,,common +5702,clownfish_ocellaris,implemented,inactive,missing icon,res://fish/species/clownfish_ocellaris/clownfish_ocellaris.tres,,common +5703,angelfish_queen,implemented,inactive,missing icon,res://fish/species/angelfish_queen/angelfish_queen.tres,,common +5801,grouper_atlantic_goliath,implemented,inactive,missing icon,res://fish/species/grouper_atlantic_goliath/grouper_atlantic_goliath.tres,,common +5802,trevally_bluefin,implemented,inactive,missing icon,res://fish/species/trevally_bluefin/trevally_bluefin.tres,,common +5803,grouper_giant,implemented,inactive,missing icon,res://fish/species/grouper_giant/grouper_giant.tres,,common +5804,barracuda_great,implemented,inactive,missing icon,res://fish/species/barracuda_great/barracuda_great.tres,,common +5805,grouper_gulf,implemented,active,complete,res://fish/species/grouper_gulf/grouper_gulf.tres,res://fish/species/grouper_gulf/fish_grouper_gulf.png,common +5806,grouper_nassau,implemented,inactive,missing icon,res://fish/species/grouper_nassau/grouper_nassau.tres,,common +5807,grouper_red,implemented,active,complete,res://fish/species/grouper_red/grouper_red.tres,res://fish/species/grouper_red/fish_grouper_red.png,common +5901,chromis_blue_green,implemented,inactive,missing icon,res://fish/species/chromis_blue_green/chromis_blue_green.tres,,common +5902,grunt_bluestriped,implemented,inactive,missing icon,res://fish/species/grunt_bluestriped/grunt_bluestriped.tres,,common +5903,grunt_french,implemented,inactive,missing icon,res://fish/species/grunt_french/grunt_french.tres,,common +5904,damselfish_sergeant_major,implemented,inactive,missing icon,res://fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres,,common +6001,snapper_lane,implemented,active,complete,res://fish/species/snapper_lane/snapper_lane.tres,res://fish/species/snapper_lane/fish_snapper_lane.png,common +6002,snapper_mangrove,implemented,active,complete,res://fish/species/snapper_mangrove/snapper_mangrove.tres,res://fish/species/snapper_mangrove/fish_snapper_mangrove.png,common +6003,snapper_mutton,implemented,active,complete,res://fish/species/snapper_mutton/snapper_mutton.tres,res://fish/species/snapper_mutton/fish_snapper_mutton.png,common +6004,snapper_red,implemented,active,complete,res://fish/species/snapper_red/snapper_red.tres,res://fish/species/snapper_red/fish_snapper_red.png,common +6101,wrasse_ballan,implemented,inactive,missing icon,res://fish/species/wrasse_ballan/wrasse_ballan.tres,,common +6102,wrasse_cleaner,implemented,inactive,missing icon,res://fish/species/wrasse_cleaner/wrasse_cleaner.tres,,common +6103,hogfish,implemented,inactive,missing icon,res://fish/species/hogfish/hogfish.tres,,common +6201,turtle_green,implemented,inactive,missing icon,res://fish/species/turtle_green/turtle_green.tres,,common +6202,turtle_hawksbill,implemented,inactive,missing icon,res://fish/species/turtle_hawksbill/turtle_hawksbill.tres,,common +6203,turtle_leatherback,implemented,inactive,missing icon,res://fish/species/turtle_leatherback/turtle_leatherback.tres,,common +6204,turtle_loggerhead,implemented,inactive,missing icon,res://fish/species/turtle_loggerhead/turtle_loggerhead.tres,,common +6301,seadragon_leafy,implemented,inactive,missing icon,res://fish/species/seadragon_leafy/seadragon_leafy.tres,,common +6302,seadragon_weedy,implemented,inactive,missing icon,res://fish/species/seadragon_weedy/seadragon_weedy.tres,,common +6401,oyster_black_lip_pearl,implemented,inactive,missing icon,res://fish/species/oyster_black_lip_pearl/oyster_black_lip_pearl.tres,,common +6402,clam_giant,implemented,inactive,missing icon,res://fish/species/clam_giant/clam_giant.tres,,common +6403,clam_geoduck,implemented,inactive,missing icon,res://fish/species/clam_geoduck/clam_geoduck.tres,,common +6404,conch_queen,implemented,inactive,missing icon,res://fish/species/conch_queen/conch_queen.tres,,common +6405,abalone_red,implemented,inactive,missing icon,res://fish/species/abalone_red/abalone_red.tres,,common +6501,squid_bigfin_reef,implemented,inactive,missing icon,res://fish/species/squid_bigfin_reef/squid_bigfin_reef.tres,,common +6502,squid_colossal,implemented,inactive,missing icon,res://fish/species/squid_colossal/squid_colossal.tres,,common +6503,squid_giant,implemented,inactive,missing icon,res://fish/species/squid_giant/squid_giant.tres,,common +6504,squid_humboldt,implemented,inactive,missing icon,res://fish/species/squid_humboldt/squid_humboldt.tres,,common +6601,halfbeak_ballyhoo,implemented,inactive,missing icon,res://fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres,,common +6602,needlefish_hound,implemented,inactive,missing icon,res://fish/species/needlefish_hound/needlefish_hound.tres,,common +6603,flyingfish_tropical,implemented,inactive,missing icon,res://fish/species/flyingfish_tropical/flyingfish_tropical.tres,,common +6701,bass,implemented,active,complete,res://fish/species/bass/bass.tres,res://fish/species/bass/fish_bass_striped.png,bass +6801,croaker_atlantic,implemented,inactive,missing icon,res://fish/species/croaker_atlantic/croaker_atlantic.tres,,common +6802,black_drum,implemented,inactive,missing icon,res://fish/species/black_drum/black_drum.tres,,common +6803,red_drum,implemented,inactive,missing icon,res://fish/species/red_drum/red_drum.tres,,common +6804,mullet_striped,implemented,inactive,missing icon,res://fish/species/mullet_striped/mullet_striped.tres,,common +6805,weakfish,implemented,inactive,missing icon,res://fish/species/weakfish/weakfish.tres,,common +6806,white_perch,implemented,inactive,missing icon,res://fish/species/white_perch/white_perch.tres,,common +6807,croaker_yellowfin,implemented,inactive,missing icon,res://fish/species/croaker_yellowfin/croaker_yellowfin.tres,,common +6901,rockfish_black,implemented,inactive,missing icon,res://fish/species/rockfish_black/rockfish_black.tres,,common +6902,sea_bass_black,implemented,inactive,missing icon,res://fish/species/sea_bass_black/sea_bass_black.tres,,common +6903,rockfish_canary,implemented,inactive,missing icon,res://fish/species/rockfish_canary/rockfish_canary.tres,,common +6904,porgy_scup,implemented,inactive,missing icon,res://fish/species/porgy_scup/porgy_scup.tres,,common +6905,sheepshead,implemented,inactive,missing icon,res://fish/species/sheepshead/sheepshead.tres,,common +6906,tautog,implemented,inactive,missing icon,res://fish/species/tautog/tautog.tres,,common +6907,rockfish_yelloweye,implemented,inactive,missing icon,res://fish/species/rockfish_yelloweye/rockfish_yelloweye.tres,,common +7001,lingcod,implemented,inactive,missing icon,res://fish/species/lingcod/lingcod.tres,,common +7101,milkfish,implemented,inactive,missing icon,res://fish/species/milkfish/milkfish.tres,,common +7201,roosterfish,implemented,inactive,missing icon,res://fish/species/roosterfish/roosterfish.tres,,common +7301,tarpon_atlantic,implemented,inactive,missing icon,res://fish/species/tarpon_atlantic/tarpon_atlantic.tres,,common +7302,barramundi,implemented,inactive,missing icon,res://fish/species/barramundi/barramundi.tres,,common +7303,snook_common,implemented,inactive,missing icon,res://fish/species/snook_common/snook_common.tres,,common +7401,bonefish,implemented,inactive,missing icon,res://fish/species/bonefish/bonefish.tres,,common +7402,pompano_florida,implemented,inactive,missing icon,res://fish/species/pompano_florida/pompano_florida.tres,,common +7403,ladyfish,implemented,inactive,missing icon,res://fish/species/ladyfish/ladyfish.tres,,common +7404,permit,implemented,inactive,missing icon,res://fish/species/permit/permit.tres,,common +7501,knifefish_clown,implemented,inactive,missing icon,res://fish/species/knifefish_clown/knifefish_clown.tres,,common +7502,loach_clown,implemented,inactive,missing icon,res://fish/species/loach_clown/loach_clown.tres,,common +7503,discus,implemented,inactive,missing icon,res://fish/species/discus/discus.tres,,common +7504,angelfish_freshwater,implemented,inactive,missing icon,res://fish/species/angelfish_freshwater/angelfish_freshwater.tres,,common +7505,gourami_giant,implemented,inactive,missing icon,res://fish/species/gourami_giant/gourami_giant.tres,,common +7506,tilapia_nile,implemented,inactive,missing icon,res://fish/species/tilapia_nile/tilapia_nile.tres,,common +7507,oscar,implemented,inactive,missing icon,res://fish/species/oscar/oscar.tres,,common +7508,betta_siamese,implemented,inactive,missing icon,res://fish/species/betta_siamese/betta_siamese.tres,,common +7509,tambaqui,implemented,inactive,missing icon,res://fish/species/tambaqui/tambaqui.tres,,common +7510,catfish_walking,implemented,inactive,missing icon,res://fish/species/catfish_walking/catfish_walking.tres,,common +7601,snakehead_giant,implemented,inactive,missing icon,res://fish/species/snakehead_giant/snakehead_giant.tres,,common +7602,dorado_golden,implemented,inactive,missing icon,res://fish/species/dorado_golden/dorado_golden.tres,,common +7603,mahseer_golden,implemented,inactive,missing icon,res://fish/species/mahseer_golden/mahseer_golden.tres,,common +7604,peacock_bass,implemented,inactive,missing icon,res://fish/species/peacock_bass/peacock_bass.tres,,common +7605,piranha_red_bellied,implemented,inactive,missing icon,res://fish/species/piranha_red_bellied/piranha_red_bellied.tres,,common +7606,arowana_silver,implemented,inactive,missing icon,res://fish/species/arowana_silver/arowana_silver.tres,,common +7701,whale_blue,implemented,inactive,missing icon,res://fish/species/whale_blue/whale_blue.tres,,common +7702,dolphin_bottlenose,implemented,inactive,missing icon,res://fish/species/dolphin_bottlenose/dolphin_bottlenose.tres,,common +7703,whale_humpback,implemented,inactive,missing icon,res://fish/species/whale_humpback/whale_humpback.tres,,common +7704,whale_killer,implemented,inactive,missing icon,res://fish/species/whale_killer/whale_killer.tres,,common +7705,whale_sperm,implemented,inactive,missing icon,res://fish/species/whale_sperm/whale_sperm.tres,,common diff --git a/economy/art_shop_stock.gd b/economy/art_shop_stock.gd index acc5382..ff84d09 100644 --- a/economy/art_shop_stock.gd +++ b/economy/art_shop_stock.gd @@ -49,7 +49,7 @@ static func get_display_name(product_id: StringName) -> String: static func get_description(product_id: StringName) -> String: if product_id == ART_KIT_ITEM_ID: - return "Press P in the game world to paint!" + return "Select the Art Kit in the Hotbar to paint." var color_id: StringName = PlayerArtUnlocks.color_id_for_product(product_id) if not color_id.is_empty(): return "Unlocks this marker color in the Paint UI." diff --git a/economy/fishing_shop_stock.gd b/economy/fishing_shop_stock.gd index 87795ad..e8104f4 100644 --- a/economy/fishing_shop_stock.gd +++ b/economy/fishing_shop_stock.gd @@ -3,11 +3,14 @@ extends RefCounted const ItemCatalogType = preload("res://items/item_catalog.gd") const ItemDataType = preload("res://items/item_data.gd") +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") const PlayerBagType = preload("res://inventory/player_bag.gd") const PlayerWalletType = preload("res://economy/player_wallet.gd") const WORM_MAX_STACK: int = 10 const FISH_FINDER_ID: StringName = &"fish_finder" +const MAGNET_ID: StringName = &"magnet" const BATTERIES_ID: StringName = &"batteries" +const CRAB_NET_ID: StringName = &"crab_net" const ITEM_PRICES: Dictionary[StringName, int] = { &"worms": 1, @@ -22,7 +25,9 @@ const ITEM_PRICES: Dictionary[StringName, int] = { &"energy_drink": 35, &"snack": 30, FISH_FINDER_ID: 500, + MAGNET_ID: 250, BATTERIES_ID: 15, + CRAB_NET_ID: 50, } const BAIT_UNLOCK_PRICES: Dictionary[StringName, int] = { &"snails": 400, @@ -42,6 +47,8 @@ const ITEM_ORDER: Array[StringName] = [ &"luminous_roe", &"the_standby", FISH_FINDER_ID, + MAGNET_ID, + CRAB_NET_ID, &"coffee", &"energy_drink", &"snack", @@ -57,6 +64,25 @@ static func get_stock_item_ids() -> Array[StringName]: return ITEM_ORDER.duplicate() +static func get_rod_stock( + catalog: ItemCatalogType, +) -> Array[FishingRodDataType]: + var rods: Array[FishingRodDataType] = [] + if catalog == null: + return rods + for item: ItemDataType in catalog.get_available_items(): + var rod := item as FishingRodDataType + if rod != null and rod.is_shop_available(): + rods.append(rod) + rods.sort_custom( + func(left: FishingRodDataType, right: FishingRodDataType) -> bool: + if left.shop_order == right.shop_order: + return str(left.item_id) < str(right.item_id) + return left.shop_order < right.shop_order + ) + return rods + + static func get_unlock_price(item_id: StringName) -> int: return BAIT_UNLOCK_PRICES.get(item_id, -1) @@ -81,7 +107,13 @@ static func is_permanent_unlock( ) -> bool: return ( item != null - and (item.is_lure() or item_id == FISH_FINDER_ID) + and ( + item.is_lure() + or item_id == FISH_FINDER_ID + or item_id == MAGNET_ID + or item_id == CRAB_NET_ID + or item is FishingRodDataType + ) ) @@ -111,6 +143,8 @@ static func purchase_one( if wallet == null or bag == null or catalog == null: return false var item: ItemDataType = catalog.get_item_by_id(item_id) + if item == null or not item.is_available(): + return false var bait_topoff: bool = is_bait_topoff(item_id) var permanent_unlock: bool = is_permanent_unlock(item_id, item) var quantity: int = get_purchase_quantity( @@ -118,15 +152,20 @@ static func purchase_one( bag.get_quantity(item_id), item.max_stack if item != null else WORM_MAX_STACK, ) - var price: int = get_purchase_cost( - item_id, - quantity, - bag.is_bait_unlocked(item_id), + var rod := item as FishingRodDataType + var price: int = ( + rod.shop_price + if rod != null + else get_purchase_cost( + item_id, + quantity, + bag.is_bait_unlocked(item_id), + ) ) if ( price < 0 - or item == null - or not item.is_valid() + or not item.is_available() + or (rod != null and not rod.is_shop_available()) or ( item.category != ItemDataType.Category.CONSUMABLE and not bait_topoff diff --git a/export_presets.cfg b/export_presets.cfg index cb2bb67..fe08e15 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.6.8-alpha/windows-x86_64/NETfishing.exe" +export_path="builds/v0.8.1-alpha/windows-x86_64/NETfishing.exe" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -35,11 +35,11 @@ application/modify_resources=true application/icon="res://art/exported/system_icons/netfishing.ico" application/console_wrapper_icon="" application/icon_interpolation=4 -application/file_version="0.6.8.0" -application/product_version="0.6.8.0" +application/file_version="0.8.1.0" +application/product_version="0.8.1.0" application/company_name="Woofmeow" application/product_name="NETfishing" -application/file_description="NETfishing v0.6.8-alpha" +application/file_description="NETfishing v0.8.1-alpha" application/copyright="Copyright © 2026 Woofmeow" application/trademarks="NETfishing 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.6.8-alpha/linux-arm64/NETfishing.arm64" +export_path="builds/v0.8.1-alpha/linux-arm64/NETfishing.arm64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" @@ -78,8 +78,8 @@ custom_template/release="" debug/export_console_wrapper=0 binary_format/embed_pck=false binary_format/architecture="arm64" -texture_format/s3tc_bptc=true -texture_format/etc2_astc=false +texture_format/s3tc_bptc=false +texture_format/etc2_astc=true [preset.4] @@ -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.6.8-alpha/macos/NETfishing.zip" +export_path="builds/v0.8.1-alpha/macos/NETfishing.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/netfishing_1024.png" application/icon_interpolation=0 -application/short_version="0.6.8" -application/version="0.6.8" +application/short_version="0.8.1" +application/version="0.8.1" 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.6.8-alpha/server-linux-x86_64/NETfishingServer.x86_64" +export_path="builds/v0.8.1-alpha/server-linux-x86_64/NETfishingServer.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.6.8-alpha/android/NETfishing.apk" +export_path="builds/v0.8.1-alpha/android/NETfishing.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=60800 -version/name="v0.6.8-alpha" +version/code=80100 +version/name="v0.8.1-alpha" package/unique_name="io.woofmeow.netfishing" package/name="NETfishing" 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.6.8-alpha/linux-x86_64/NETfishing.x86_64" +export_path="builds/v0.8.1-alpha/linux-x86_64/NETfishing.x86_64" patches=PackedStringArray() encryption_include_filters="" encryption_exclude_filters="" diff --git a/fish/fish_data.gd b/fish/fish_data.gd index 2bb99af..c34a953 100644 --- a/fish/fish_data.gd +++ b/fish/fish_data.gd @@ -24,16 +24,37 @@ enum Rarity { LEGENDARY, } +enum CollectionMethod { + FISHING, + NET, + DIGGING, +} + +enum LogbookSection { + AUTOMATIC, + SHELLFISH, +} + @export var id: StringName @export var display_name: String +@export_category("Developer Catalog") +## Disabled species remain resolvable for saves but cannot be newly selected. +@export var active: bool = true +@export_range(1, 999999, 1) var catalog_number: int = 1 +@export var collection_group: StringName +@export_multiline var logbook_fact: String +@export var collection_method: CollectionMethod = CollectionMethod.FISHING +@export var logbook_section: LogbookSection = LogbookSection.AUTOMATIC +@export var habitat_label: String = "" +@export_category("Fishing") @export_flags("Fresh Water", "Salt Water") var allowed_water_types: int = WaterType.ALL_FISHABLE_MASK @export var rarity: Rarity = Rarity.COMMON @export_range(0.0, 1000.0, 0.01) var base_catch_weight: float = 1.0 @export var catch_profile: CatchDifficultyProfileType @export var availability: FishAvailabilityType -@export_range(0.01, 1000.0, 0.01) var weight_min_lb: float = 0.5 -@export_range(0.01, 1000.0, 0.01) var weight_max_lb: float = 1.0 +@export_range(0.001, 500000.0, 0.001) var weight_min_lb: float = 0.5 +@export_range(0.001, 500000.0, 0.001) var weight_max_lb: float = 1.0 # Retained in resource files for compatibility with existing authored data; # runtime presentation is derived from absolute weight below. @export_range(0.01, 20.0, 0.01) var display_scale_min: float = 0.8 @@ -46,6 +67,10 @@ var allowed_water_types: int = WaterType.ALL_FISHABLE_MASK func is_selectable() -> bool: + return active and is_valid_catalog_entry() + + +func is_valid_catalog_entry() -> bool: return ( not id.is_empty() and base_catch_weight > 0.0 @@ -61,6 +86,16 @@ func is_selectable() -> bool: ) +func is_fishable() -> bool: + return is_selectable() and collection_method == CollectionMethod.FISHING + + +func get_habitat_label() -> String: + if not habitat_label.strip_edges().is_empty(): + return habitat_label.strip_edges().to_lower() + return WaterType.label(get_primary_water_type()).to_lower() + + func is_allowed_in_water(type: WaterType.Type) -> bool: return (allowed_water_types & WaterType.mask_for(type)) != 0 @@ -74,7 +109,7 @@ func get_primary_water_type() -> WaterType.Type: func get_minimum_weight() -> float: - return maxf(weight_min_lb, 0.01) + return maxf(weight_min_lb, 0.001) func get_maximum_weight() -> float: diff --git a/fish/fish_selector.gd b/fish/fish_selector.gd index 506a906..de906f8 100644 --- a/fish/fish_selector.gd +++ b/fish/fish_selector.gd @@ -7,10 +7,12 @@ const FishDataType = preload("res://fish/fish_data.gd") const FishPoolType = preload("res://fish/fish_pool.gd") const FishQualityType = preload("res://fish/fish_quality.gd") const FishingContextType = preload("res://fishing/fishing_context.gd") +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") var undiscovered_weight_multiplier: float = 1.5 var rarity_weight_multipliers: Array[float] = [] var quality_weight_multipliers: Array[float] = [] +var active_rod: FishingRodDataType var use_deterministic_test_seed: bool = false var deterministic_test_seed: int = 24680 var selection_seed: int = 0 @@ -44,7 +46,7 @@ func select_fish( var weights: Array[float] = [] var total_weight: float = 0.0 for fish: FishDataType in pool.candidates: - if fish == null or not fish.is_selectable(): + if fish == null or not fish.is_fishable(): continue if not fish.is_allowed_in_water(context.water_type): continue @@ -55,8 +57,16 @@ func select_fish( # quality and rarity bands globally; required tags remain authoritative. if not collection_log.has_discovered(fish.id): final_weight *= maxf(undiscovered_weight_multiplier, 0.0) + if active_rod != null: + final_weight *= maxf( + active_rod.undiscovered_weight_multiplier, + 0.0, + ) if fish.rarity >= 0 and fish.rarity < rarity_weight_multipliers.size(): final_weight *= maxf(rarity_weight_multipliers[fish.rarity], 0.0) + if active_rod != null: + final_weight *= active_rod.get_rarity_multiplier(fish.rarity) + final_weight *= active_rod.get_affinity_multiplier(fish, context) if final_weight <= 0.0: continue all_fish.append(fish) @@ -87,6 +97,8 @@ func _roll_rarity(context: FishingContextType) -> int: for index: int in range(weights.size()): if index < rarity_weight_multipliers.size(): weights[index] *= maxf(rarity_weight_multipliers[index], 0.0) + if active_rod != null: + weights[index] *= active_rod.get_rarity_multiplier(index) var total: float = 0.0 for weight: float in weights: total += weight @@ -107,9 +119,13 @@ func create_catch(fish: FishDataType, bait_tags: Array[StringName] = []) -> Fish caught_fish.fish = fish caught_fish.fish_id = fish.id caught_fish.ensure_identity() - caught_fish.weight_lb = _rng.randf_range( + var weight_roll: float = _biased_unit_roll( + active_rod.weight_roll_bias if active_rod != null else 0.0 + ) + caught_fish.weight_lb = lerpf( fish.get_minimum_weight(), - fish.get_maximum_weight() + fish.get_maximum_weight(), + weight_roll, ) caught_fish.display_scale = fish.get_display_scale_for_weight( caught_fish.weight_lb @@ -118,6 +134,11 @@ func create_catch(fish: FishDataType, bait_tags: Array[StringName] = []) -> Fish var quality_multipliers: Array[float] = FishQualityType.roll_weights_for_bait(effective_bait) for quality: int in range(mini(quality_multipliers.size(), quality_weight_multipliers.size())): quality_multipliers[quality] *= maxf(quality_weight_multipliers[quality], 0.0) + for quality: int in range(quality_multipliers.size()): + if active_rod != null: + quality_multipliers[quality] *= active_rod.get_quality_multiplier( + quality + ) caught_fish.quality = FishQualityType.roll( _rng, quality_multipliers, @@ -127,3 +148,14 @@ func create_catch(fish: FishDataType, bait_tags: Array[StringName] = []) -> Fish caught_fish.quality, ) return caught_fish + + +func _biased_unit_roll(bias: float) -> float: + var roll: float = _rng.randf() + var safe_bias: float = clampf(bias, -0.5, 0.5) + if is_zero_approx(safe_bias): + return roll + var exponent: float = 1.0 / (1.0 + absf(safe_bias) * 2.0) + if safe_bias > 0.0: + return pow(roll, exponent) + return 1.0 - pow(1.0 - roll, exponent) diff --git a/fish/pools/fish_catalog.tres b/fish/pools/fish_catalog.tres index 73609c8..e2f7385 100644 --- a/fish/pools/fish_catalog.tres +++ b/fish/pools/fish_catalog.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" load_steps=55 format=3] +[gd_resource type="Resource" load_steps=315 format=3] [ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"] [ext_resource type="Resource" path="res://fish/species/bluegill/bluegill.tres" id="2_bluegill"] @@ -54,7 +54,268 @@ [ext_resource type="Resource" path="res://fish/species/trout_rainbow/trout_rainbow.tres" id="52_trout_rainbow"] [ext_resource type="Resource" path="res://fish/species/trout_steelhead/trout_steelhead.tres" id="53_trout_steelhead"] [ext_resource type="Resource" path="res://fish/species/walleye/walleye.tres" id="54_walleye"] +[ext_resource type="Resource" path="res://fish/species/bowfin/bowfin.tres" id="55_bowfin"] +[ext_resource type="Resource" path="res://fish/species/sturgeon_lake/sturgeon_lake.tres" id="56_sturgeon_lake"] +[ext_resource type="Resource" path="res://fish/species/gar_longnose/gar_longnose.tres" id="57_gar_longnose"] +[ext_resource type="Resource" path="res://fish/species/paddlefish/paddlefish.tres" id="58_paddlefish"] +[ext_resource type="Resource" path="res://fish/species/sturgeon_shovelnose/sturgeon_shovelnose.tres" id="59_sturgeon_shovelnose"] +[ext_resource type="Resource" path="res://fish/species/gar_spotted/gar_spotted.tres" id="60_gar_spotted"] +[ext_resource type="Resource" path="res://fish/species/lungfish_west_african/lungfish_west_african.tres" id="61_lungfish_west_african"] +[ext_resource type="Resource" path="res://fish/species/mudskipper_atlantic/mudskipper_atlantic.tres" id="62_mudskipper_atlantic"] +[ext_resource type="Resource" path="res://fish/species/foureyes_largescale/foureyes_largescale.tres" id="63_foureyes_largescale"] +[ext_resource type="Resource" path="res://fish/species/mullet_red/mullet_red.tres" id="64_mullet_red"] +[ext_resource type="Resource" path="res://fish/species/plaice_european/plaice_european.tres" id="65_plaice_european"] +[ext_resource type="Resource" path="res://fish/species/flounder_summer/flounder_summer.tres" id="66_flounder_summer"] +[ext_resource type="Resource" path="res://fish/species/flounder_winter/flounder_winter.tres" id="67_flounder_winter"] +[ext_resource type="Resource" path="res://fish/species/dugong/dugong.tres" id="68_dugong"] +[ext_resource type="Resource" path="res://fish/species/seal_harbor/seal_harbor.tres" id="69_seal_harbor"] +[ext_resource type="Resource" path="res://fish/species/sea_otter/sea_otter.tres" id="70_sea_otter"] +[ext_resource type="Resource" path="res://fish/species/walrus/walrus.tres" id="71_walrus"] +[ext_resource type="Resource" path="res://fish/species/manatee_west_indian/manatee_west_indian.tres" id="72_manatee_west_indian"] +[ext_resource type="Resource" path="res://fish/species/pipefish_bay/pipefish_bay.tres" id="73_pipefish_bay"] +[ext_resource type="Resource" path="res://fish/species/seahorse_lined/seahorse_lined.tres" id="74_seahorse_lined"] +[ext_resource type="Resource" path="res://fish/species/monkfish/monkfish.tres" id="75_monkfish"] +[ext_resource type="Resource" path="res://fish/species/tripletail/tripletail.tres" id="76_tripletail"] +[ext_resource type="Resource" path="res://fish/species/bluefish/bluefish.tres" id="77_bluefish"] +[ext_resource type="Resource" path="res://fish/species/jack_crevalle/jack_crevalle.tres" id="78_jack_crevalle"] +[ext_resource type="Resource" path="res://fish/species/queenfish_talang/queenfish_talang.tres" id="79_queenfish_talang"] +[ext_resource type="Resource" path="res://fish/species/sand_lance_american/sand_lance_american.tres" id="80_sand_lance_american"] +[ext_resource type="Resource" path="res://fish/species/menhaden_atlantic/menhaden_atlantic.tres" id="81_menhaden_atlantic"] +[ext_resource type="Resource" path="res://fish/species/sardine_european/sardine_european.tres" id="82_sardine_european"] +[ext_resource type="Resource" path="res://fish/species/sardine_pacific/sardine_pacific.tres" id="83_sardine_pacific"] +[ext_resource type="Resource" path="res://fish/species/shark_blacktip/shark_blacktip.tres" id="84_shark_blacktip"] +[ext_resource type="Resource" path="res://fish/species/shark_great_hammerhead/shark_great_hammerhead.tres" id="85_shark_great_hammerhead"] +[ext_resource type="Resource" path="res://fish/species/shark_great_white/shark_great_white.tres" id="86_shark_great_white"] +[ext_resource type="Resource" path="res://fish/species/shark_tiger/shark_tiger.tres" id="87_shark_tiger"] +[ext_resource type="Resource" path="res://fish/species/shark_whale/shark_whale.tres" id="88_shark_whale"] +[ext_resource type="Resource" path="res://fish/species/burbot/burbot.tres" id="89_burbot"] +[ext_resource type="Resource" path="res://fish/species/cisco/cisco.tres" id="90_cisco"] +[ext_resource type="Resource" path="res://fish/species/whitefish_lake/whitefish_lake.tres" id="91_whitefish_lake"] +[ext_resource type="Resource" path="res://fish/species/char_arctic/char_arctic.tres" id="92_char_arctic"] +[ext_resource type="Resource" path="res://fish/species/grayling_arctic/grayling_arctic.tres" id="93_grayling_arctic"] +[ext_resource type="Resource" path="res://fish/species/trout_brown/trout_brown.tres" id="94_trout_brown"] +[ext_resource type="Resource" path="res://fish/species/wolffish_atlantic/wolffish_atlantic.tres" id="95_wolffish_atlantic"] +[ext_resource type="Resource" path="res://fish/species/cusk/cusk.tres" id="96_cusk"] +[ext_resource type="Resource" path="res://fish/species/wolffish_spotted/wolffish_spotted.tres" id="97_wolffish_spotted"] +[ext_resource type="Resource" path="res://fish/species/herring_atlantic/herring_atlantic.tres" id="98_herring_atlantic"] +[ext_resource type="Resource" path="res://fish/species/capelin/capelin.tres" id="99_capelin"] +[ext_resource type="Resource" path="res://fish/species/sprat_european/sprat_european.tres" id="100_sprat_european"] +[ext_resource type="Resource" path="res://fish/species/herring_pacific/herring_pacific.tres" id="101_herring_pacific"] +[ext_resource type="Resource" path="res://fish/species/pollock_alaska/pollock_alaska.tres" id="102_pollock_alaska"] +[ext_resource type="Resource" path="res://fish/species/cod_atlantic/cod_atlantic.tres" id="103_cod_atlantic"] +[ext_resource type="Resource" path="res://fish/species/pollock_atlantic/pollock_atlantic.tres" id="104_pollock_atlantic"] +[ext_resource type="Resource" path="res://fish/species/sole_dover/sole_dover.tres" id="105_sole_dover"] +[ext_resource type="Resource" path="res://fish/species/haddock/haddock.tres" id="106_haddock"] +[ext_resource type="Resource" path="res://fish/species/cod_pacific/cod_pacific.tres" id="107_cod_pacific"] +[ext_resource type="Resource" path="res://fish/species/hake_pacific/hake_pacific.tres" id="108_hake_pacific"] +[ext_resource type="Resource" path="res://fish/species/hake_silver/hake_silver.tres" id="109_hake_silver"] +[ext_resource type="Resource" path="res://fish/species/nautilus_chambered/nautilus_chambered.tres" id="110_nautilus_chambered"] +[ext_resource type="Resource" path="res://fish/species/cuttlefish_common/cuttlefish_common.tres" id="111_cuttlefish_common"] +[ext_resource type="Resource" path="res://fish/species/cuttlefish_flamboyant/cuttlefish_flamboyant.tres" id="112_cuttlefish_flamboyant"] +[ext_resource type="Resource" path="res://fish/species/tilefish_blueline/tilefish_blueline.tres" id="113_tilefish_blueline"] +[ext_resource type="Resource" path="res://fish/species/tilefish_golden/tilefish_golden.tres" id="114_tilefish_golden"] +[ext_resource type="Resource" path="res://fish/species/ocean_perch_pacific/ocean_perch_pacific.tres" id="115_ocean_perch_pacific"] +[ext_resource type="Resource" path="res://fish/species/sea_bass_chilean/sea_bass_chilean.tres" id="116_sea_bass_chilean"] +[ext_resource type="Resource" path="res://fish/species/sablefish/sablefish.tres" id="117_sablefish"] +[ext_resource type="Resource" path="res://fish/species/wreckfish/wreckfish.tres" id="118_wreckfish"] +[ext_resource type="Resource" path="res://fish/species/oarfish_giant/oarfish_giant.tres" id="119_oarfish_giant"] +[ext_resource type="Resource" path="res://fish/species/coelacanth_west_indian/coelacanth_west_indian.tres" id="120_coelacanth_west_indian"] +[ext_resource type="Resource" path="res://fish/species/anglerfish_black_seadevil/anglerfish_black_seadevil.tres" id="121_anglerfish_black_seadevil"] +[ext_resource type="Resource" path="res://fish/species/fangtooth_common/fangtooth_common.tres" id="122_fangtooth_common"] +[ext_resource type="Resource" path="res://fish/species/gulper_eel/gulper_eel.tres" id="123_gulper_eel"] +[ext_resource type="Resource" path="res://fish/species/barreleye_pacific/barreleye_pacific.tres" id="124_barreleye_pacific"] +[ext_resource type="Resource" path="res://fish/species/blobfish_smooth_head/blobfish_smooth_head.tres" id="125_blobfish_smooth_head"] +[ext_resource type="Resource" path="res://fish/species/lanternfish_spotted/lanternfish_spotted.tres" id="126_lanternfish_spotted"] +[ext_resource type="Resource" path="res://fish/species/sea_star_crown_of_thorns/sea_star_crown_of_thorns.tres" id="127_sea_star_crown_of_thorns"] +[ext_resource type="Resource" path="res://fish/species/sea_cucumber_giant_california/sea_cucumber_giant_california.tres" id="128_sea_cucumber_giant_california"] +[ext_resource type="Resource" path="res://fish/species/urchin_purple/urchin_purple.tres" id="129_urchin_purple"] +[ext_resource type="Resource" path="res://fish/species/sand_dollar/sand_dollar.tres" id="130_sand_dollar"] +[ext_resource type="Resource" path="res://fish/species/sea_star_sunflower/sea_star_sunflower.tres" id="131_sea_star_sunflower"] +[ext_resource type="Resource" path="res://fish/species/axolotl/axolotl.tres" id="132_axolotl"] +[ext_resource type="Resource" path="res://fish/species/hellbender/hellbender.tres" id="133_hellbender"] +[ext_resource type="Resource" path="res://fish/species/guppy/guppy.tres" id="134_guppy"] +[ext_resource type="Resource" path="res://fish/species/tetra_neon/tetra_neon.tres" id="135_tetra_neon"] +[ext_resource type="Resource" path="res://fish/species/danio_zebra/danio_zebra.tres" id="136_danio_zebra"] +[ext_resource type="Resource" path="res://fish/species/largemouth_bass/largemouth_bass.tres" id="137_largemouth_bass"] +[ext_resource type="Resource" path="res://fish/species/smallmouth_bass/smallmouth_bass.tres" id="138_smallmouth_bass"] +[ext_resource type="Resource" path="res://fish/species/buffalo_bigmouth/buffalo_bigmouth.tres" id="139_buffalo_bigmouth"] +[ext_resource type="Resource" path="res://fish/species/redhorse_golden/redhorse_golden.tres" id="140_redhorse_golden"] +[ext_resource type="Resource" path="res://fish/species/quillback/quillback.tres" id="141_quillback"] +[ext_resource type="Resource" path="res://fish/species/buffalo_smallmouth/buffalo_smallmouth.tres" id="142_buffalo_smallmouth"] +[ext_resource type="Resource" path="res://fish/species/sucker_white/sucker_white.tres" id="143_sucker_white"] +[ext_resource type="Resource" path="res://fish/species/bullhead_black/bullhead_black.tres" id="144_bullhead_black"] +[ext_resource type="Resource" path="res://fish/species/bullhead_brown/bullhead_brown.tres" id="145_bullhead_brown"] +[ext_resource type="Resource" path="res://fish/species/madtom_tadpole/madtom_tadpole.tres" id="146_madtom_tadpole"] +[ext_resource type="Resource" path="res://fish/species/bullhead_yellow/bullhead_yellow.tres" id="147_bullhead_yellow"] +[ext_resource type="Resource" path="res://fish/species/barbel_common/barbel_common.tres" id="148_barbel_common"] +[ext_resource type="Resource" path="res://fish/species/bream_common/bream_common.tres" id="149_bream_common"] +[ext_resource type="Resource" path="res://fish/species/roach_common/roach_common.tres" id="150_roach_common"] +[ext_resource type="Resource" path="res://fish/species/fallfish/fallfish.tres" id="151_fallfish"] +[ext_resource type="Resource" path="res://fish/species/ide/ide.tres" id="152_ide"] +[ext_resource type="Resource" path="res://fish/species/rudd/rudd.tres" id="153_rudd"] +[ext_resource type="Resource" path="res://fish/species/tench/tench.tres" id="154_tench"] +[ext_resource type="Resource" path="res://fish/species/crayfish_red_swamp/crayfish_red_swamp.tres" id="155_crayfish_red_swamp"] +[ext_resource type="Resource" path="res://fish/species/gar_alligator/gar_alligator.tres" id="156_gar_alligator"] +[ext_resource type="Resource" path="res://fish/species/arapaima/arapaima.tres" id="157_arapaima"] +[ext_resource type="Resource" path="res://fish/species/tigerfish_goliath/tigerfish_goliath.tres" id="158_tigerfish_goliath"] +[ext_resource type="Resource" path="res://fish/species/muskellunge/muskellunge.tres" id="159_muskellunge"] +[ext_resource type="Resource" path="res://fish/species/shiner_common/shiner_common.tres" id="160_shiner_common"] +[ext_resource type="Resource" path="res://fish/species/chub_creek/chub_creek.tres" id="161_chub_creek"] +[ext_resource type="Resource" path="res://fish/species/shiner_emerald/shiner_emerald.tres" id="162_shiner_emerald"] +[ext_resource type="Resource" path="res://fish/species/minnow_fathead/minnow_fathead.tres" id="163_minnow_fathead"] +[ext_resource type="Resource" path="res://fish/species/shiner_golden/shiner_golden.tres" id="164_shiner_golden"] +[ext_resource type="Resource" path="res://fish/species/electric_eel/electric_eel.tres" id="165_electric_eel"] +[ext_resource type="Resource" path="res://fish/species/freshwater_drum/freshwater_drum.tres" id="166_freshwater_drum"] +[ext_resource type="Resource" path="res://fish/species/goldeye/goldeye.tres" id="167_goldeye"] +[ext_resource type="Resource" path="res://fish/species/mooneye/mooneye.tres" id="168_mooneye"] +[ext_resource type="Resource" path="res://fish/species/loach_weather/loach_weather.tres" id="169_loach_weather"] +[ext_resource type="Resource" path="res://fish/species/koi/koi.tres" id="170_koi"] +[ext_resource type="Resource" path="res://fish/species/crappie_black/crappie_black.tres" id="171_crappie_black"] +[ext_resource type="Resource" path="res://fish/species/green_sunfish/green_sunfish.tres" id="172_green_sunfish"] +[ext_resource type="Resource" path="res://fish/species/pumpkinseed/pumpkinseed.tres" id="173_pumpkinseed"] +[ext_resource type="Resource" path="res://fish/species/redear_sunfish/redear_sunfish.tres" id="174_redear_sunfish"] +[ext_resource type="Resource" path="res://fish/species/warmouth/warmouth.tres" id="175_warmouth"] +[ext_resource type="Resource" path="res://fish/species/crappie_white/crappie_white.tres" id="176_crappie_white"] +[ext_resource type="Resource" path="res://fish/species/yellow_perch/yellow_perch.tres" id="177_yellow_perch"] +[ext_resource type="Resource" path="res://fish/species/pickerel_chain/pickerel_chain.tres" id="178_pickerel_chain"] +[ext_resource type="Resource" path="res://fish/species/pickerel_grass/pickerel_grass.tres" id="179_pickerel_grass"] +[ext_resource type="Resource" path="res://fish/species/pike_northern/pike_northern.tres" id="180_pike_northern"] +[ext_resource type="Resource" path="res://fish/species/jelly_crystal/jelly_crystal.tres" id="181_jelly_crystal"] +[ext_resource type="Resource" path="res://fish/species/jelly_lions_mane/jelly_lions_mane.tres" id="182_jelly_lions_mane"] +[ext_resource type="Resource" path="res://fish/species/jelly_moon/jelly_moon.tres" id="183_jelly_moon"] +[ext_resource type="Resource" path="res://fish/species/jelly_pacific_sea_nettle/jelly_pacific_sea_nettle.tres" id="184_jelly_pacific_sea_nettle"] +[ext_resource type="Resource" path="res://fish/species/jelly_sea_wasp/jelly_sea_wasp.tres" id="185_jelly_sea_wasp"] +[ext_resource type="Resource" path="res://fish/species/pompano_african/pompano_african.tres" id="186_pompano_african"] +[ext_resource type="Resource" path="res://fish/species/cobia/cobia.tres" id="187_cobia"] +[ext_resource type="Resource" path="res://fish/species/trevally_giant/trevally_giant.tres" id="188_trevally_giant"] +[ext_resource type="Resource" path="res://fish/species/amberjack_greater/amberjack_greater.tres" id="189_amberjack_greater"] +[ext_resource type="Resource" path="res://fish/species/amberjack_lesser/amberjack_lesser.tres" id="190_amberjack_lesser"] +[ext_resource type="Resource" path="res://fish/species/halibut_atlantic/halibut_atlantic.tres" id="191_halibut_atlantic"] +[ext_resource type="Resource" path="res://fish/species/halibut_pacific/halibut_pacific.tres" id="192_halibut_pacific"] +[ext_resource type="Resource" path="res://fish/species/turbot/turbot.tres" id="193_turbot"] +[ext_resource type="Resource" path="res://fish/species/lobster_american/lobster_american.tres" id="194_lobster_american"] +[ext_resource type="Resource" path="res://fish/species/krill_antarctic/krill_antarctic.tres" id="195_krill_antarctic"] +[ext_resource type="Resource" path="res://fish/species/lobster_caribbean_spiny/lobster_caribbean_spiny.tres" id="196_lobster_caribbean_spiny"] +[ext_resource type="Resource" path="res://fish/species/shrimp_peacock_mantis/shrimp_peacock_mantis.tres" id="197_shrimp_peacock_mantis"] +[ext_resource type="Resource" path="res://fish/species/shrimp_skunk_cleaner/shrimp_skunk_cleaner.tres" id="198_shrimp_skunk_cleaner"] +[ext_resource type="Resource" path="res://fish/species/crab_ghost/crab_ghost.tres" id="199_crab_ghost"] +[ext_resource type="Resource" path="res://fish/species/crab_blue/crab_blue.tres" id="200_crab_blue"] +[ext_resource type="Resource" path="res://fish/species/crab_dungeness/crab_dungeness.tres" id="201_crab_dungeness"] +[ext_resource type="Resource" path="res://fish/species/crab_japanese_spider/crab_japanese_spider.tres" id="202_crab_japanese_spider"] +[ext_resource type="Resource" path="res://fish/species/crab_red_king/crab_red_king.tres" id="203_crab_red_king"] +[ext_resource type="Resource" path="res://fish/species/eel_american/eel_american.tres" id="204_eel_american"] +[ext_resource type="Resource" path="res://fish/species/eel_european/eel_european.tres" id="205_eel_european"] +[ext_resource type="Resource" path="res://fish/species/smelt_rainbow/smelt_rainbow.tres" id="206_smelt_rainbow"] +[ext_resource type="Resource" path="res://fish/species/smelt_eulachon/smelt_eulachon.tres" id="207_smelt_eulachon"] +[ext_resource type="Resource" path="res://fish/species/sturgeon_white/sturgeon_white.tres" id="208_sturgeon_white"] +[ext_resource type="Resource" path="res://fish/species/mahi_mahi/mahi_mahi.tres" id="209_mahi_mahi"] +[ext_resource type="Resource" path="res://fish/species/opah/opah.tres" id="210_opah"] +[ext_resource type="Resource" path="res://fish/species/wahoo/wahoo.tres" id="211_wahoo"] +[ext_resource type="Resource" path="res://fish/species/octopus_common/octopus_common.tres" id="212_octopus_common"] +[ext_resource type="Resource" path="res://fish/species/octopus_day/octopus_day.tres" id="213_octopus_day"] +[ext_resource type="Resource" path="res://fish/species/octopus_giant_pacific/octopus_giant_pacific.tres" id="214_octopus_giant_pacific"] +[ext_resource type="Resource" path="res://fish/species/octopus_greater_blue_ringed/octopus_greater_blue_ringed.tres" id="215_octopus_greater_blue_ringed"] +[ext_resource type="Resource" path="res://fish/species/skate_barndoor/skate_barndoor.tres" id="216_skate_barndoor"] +[ext_resource type="Resource" path="res://fish/species/manta_ray_giant/manta_ray_giant.tres" id="217_manta_ray_giant"] +[ext_resource type="Resource" path="res://fish/species/sawfish_largetooth/sawfish_largetooth.tres" id="218_sawfish_largetooth"] +[ext_resource type="Resource" path="res://fish/species/stingray_ocellate_river/stingray_ocellate_river.tres" id="219_stingray_ocellate_river"] +[ext_resource type="Resource" path="res://fish/species/stingray_southern/stingray_southern.tres" id="220_stingray_southern"] +[ext_resource type="Resource" path="res://fish/species/lionfish_red/lionfish_red.tres" id="221_lionfish_red"] +[ext_resource type="Resource" path="res://fish/species/scorpionfish_red/scorpionfish_red.tres" id="222_scorpionfish_red"] +[ext_resource type="Resource" path="res://fish/species/moray_green/moray_green.tres" id="223_moray_green"] +[ext_resource type="Resource" path="res://fish/species/flounder_peacock/flounder_peacock.tres" id="224_flounder_peacock"] +[ext_resource type="Resource" path="res://fish/species/surgeonfish_blue/surgeonfish_blue.tres" id="225_surgeonfish_blue"] +[ext_resource type="Resource" path="res://fish/species/parrotfish_rainbow/parrotfish_rainbow.tres" id="226_parrotfish_rainbow"] +[ext_resource type="Resource" path="res://fish/species/parrotfish_stoplight/parrotfish_stoplight.tres" id="227_parrotfish_stoplight"] +[ext_resource type="Resource" path="res://fish/species/tang_yellow/tang_yellow.tres" id="228_tang_yellow"] +[ext_resource type="Resource" path="res://fish/species/trumpetfish_atlantic/trumpetfish_atlantic.tres" id="229_trumpetfish_atlantic"] +[ext_resource type="Resource" path="res://fish/species/triggerfish_gray/triggerfish_gray.tres" id="230_triggerfish_gray"] +[ext_resource type="Resource" path="res://fish/species/pufferfish_guineafowl/pufferfish_guineafowl.tres" id="231_pufferfish_guineafowl"] +[ext_resource type="Resource" path="res://fish/species/cowfish_longhorn/cowfish_longhorn.tres" id="232_cowfish_longhorn"] +[ext_resource type="Resource" path="res://fish/species/triggerfish_queen/triggerfish_queen.tres" id="233_triggerfish_queen"] +[ext_resource type="Resource" path="res://fish/species/cornetfish_red/cornetfish_red.tres" id="234_cornetfish_red"] +[ext_resource type="Resource" path="res://fish/species/porcupinefish_spotted/porcupinefish_spotted.tres" id="235_porcupinefish_spotted"] +[ext_resource type="Resource" path="res://fish/species/boxfish_yellow/boxfish_yellow.tres" id="236_boxfish_yellow"] +[ext_resource type="Resource" path="res://fish/species/butterflyfish_copperband/butterflyfish_copperband.tres" id="237_butterflyfish_copperband"] +[ext_resource type="Resource" path="res://fish/species/clownfish_ocellaris/clownfish_ocellaris.tres" id="238_clownfish_ocellaris"] +[ext_resource type="Resource" path="res://fish/species/angelfish_queen/angelfish_queen.tres" id="239_angelfish_queen"] +[ext_resource type="Resource" path="res://fish/species/grouper_atlantic_goliath/grouper_atlantic_goliath.tres" id="240_grouper_atlantic_goliath"] +[ext_resource type="Resource" path="res://fish/species/trevally_bluefin/trevally_bluefin.tres" id="241_trevally_bluefin"] +[ext_resource type="Resource" path="res://fish/species/grouper_giant/grouper_giant.tres" id="242_grouper_giant"] +[ext_resource type="Resource" path="res://fish/species/barracuda_great/barracuda_great.tres" id="243_barracuda_great"] +[ext_resource type="Resource" path="res://fish/species/grouper_nassau/grouper_nassau.tres" id="244_grouper_nassau"] +[ext_resource type="Resource" path="res://fish/species/chromis_blue_green/chromis_blue_green.tres" id="245_chromis_blue_green"] +[ext_resource type="Resource" path="res://fish/species/grunt_bluestriped/grunt_bluestriped.tres" id="246_grunt_bluestriped"] +[ext_resource type="Resource" path="res://fish/species/grunt_french/grunt_french.tres" id="247_grunt_french"] +[ext_resource type="Resource" path="res://fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres" id="248_damselfish_sergeant_major"] +[ext_resource type="Resource" path="res://fish/species/wrasse_ballan/wrasse_ballan.tres" id="249_wrasse_ballan"] +[ext_resource type="Resource" path="res://fish/species/wrasse_cleaner/wrasse_cleaner.tres" id="250_wrasse_cleaner"] +[ext_resource type="Resource" path="res://fish/species/hogfish/hogfish.tres" id="251_hogfish"] +[ext_resource type="Resource" path="res://fish/species/turtle_green/turtle_green.tres" id="252_turtle_green"] +[ext_resource type="Resource" path="res://fish/species/turtle_hawksbill/turtle_hawksbill.tres" id="253_turtle_hawksbill"] +[ext_resource type="Resource" path="res://fish/species/turtle_leatherback/turtle_leatherback.tres" id="254_turtle_leatherback"] +[ext_resource type="Resource" path="res://fish/species/turtle_loggerhead/turtle_loggerhead.tres" id="255_turtle_loggerhead"] +[ext_resource type="Resource" path="res://fish/species/seadragon_leafy/seadragon_leafy.tres" id="256_seadragon_leafy"] +[ext_resource type="Resource" path="res://fish/species/seadragon_weedy/seadragon_weedy.tres" id="257_seadragon_weedy"] +[ext_resource type="Resource" path="res://fish/species/oyster_black_lip_pearl/oyster_black_lip_pearl.tres" id="258_oyster_black_lip_pearl"] +[ext_resource type="Resource" path="res://fish/species/clam_giant/clam_giant.tres" id="259_clam_giant"] +[ext_resource type="Resource" path="res://fish/species/clam_geoduck/clam_geoduck.tres" id="260_clam_geoduck"] +[ext_resource type="Resource" path="res://fish/species/conch_queen/conch_queen.tres" id="261_conch_queen"] +[ext_resource type="Resource" path="res://fish/species/abalone_red/abalone_red.tres" id="262_abalone_red"] +[ext_resource type="Resource" path="res://fish/species/squid_bigfin_reef/squid_bigfin_reef.tres" id="263_squid_bigfin_reef"] +[ext_resource type="Resource" path="res://fish/species/squid_colossal/squid_colossal.tres" id="264_squid_colossal"] +[ext_resource type="Resource" path="res://fish/species/squid_giant/squid_giant.tres" id="265_squid_giant"] +[ext_resource type="Resource" path="res://fish/species/squid_humboldt/squid_humboldt.tres" id="266_squid_humboldt"] +[ext_resource type="Resource" path="res://fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres" id="267_halfbeak_ballyhoo"] +[ext_resource type="Resource" path="res://fish/species/needlefish_hound/needlefish_hound.tres" id="268_needlefish_hound"] +[ext_resource type="Resource" path="res://fish/species/flyingfish_tropical/flyingfish_tropical.tres" id="269_flyingfish_tropical"] +[ext_resource type="Resource" path="res://fish/species/croaker_atlantic/croaker_atlantic.tres" id="270_croaker_atlantic"] +[ext_resource type="Resource" path="res://fish/species/black_drum/black_drum.tres" id="271_black_drum"] +[ext_resource type="Resource" path="res://fish/species/red_drum/red_drum.tres" id="272_red_drum"] +[ext_resource type="Resource" path="res://fish/species/mullet_striped/mullet_striped.tres" id="273_mullet_striped"] +[ext_resource type="Resource" path="res://fish/species/weakfish/weakfish.tres" id="274_weakfish"] +[ext_resource type="Resource" path="res://fish/species/white_perch/white_perch.tres" id="275_white_perch"] +[ext_resource type="Resource" path="res://fish/species/croaker_yellowfin/croaker_yellowfin.tres" id="276_croaker_yellowfin"] +[ext_resource type="Resource" path="res://fish/species/rockfish_black/rockfish_black.tres" id="277_rockfish_black"] +[ext_resource type="Resource" path="res://fish/species/sea_bass_black/sea_bass_black.tres" id="278_sea_bass_black"] +[ext_resource type="Resource" path="res://fish/species/rockfish_canary/rockfish_canary.tres" id="279_rockfish_canary"] +[ext_resource type="Resource" path="res://fish/species/porgy_scup/porgy_scup.tres" id="280_porgy_scup"] +[ext_resource type="Resource" path="res://fish/species/sheepshead/sheepshead.tres" id="281_sheepshead"] +[ext_resource type="Resource" path="res://fish/species/tautog/tautog.tres" id="282_tautog"] +[ext_resource type="Resource" path="res://fish/species/rockfish_yelloweye/rockfish_yelloweye.tres" id="283_rockfish_yelloweye"] +[ext_resource type="Resource" path="res://fish/species/lingcod/lingcod.tres" id="284_lingcod"] +[ext_resource type="Resource" path="res://fish/species/milkfish/milkfish.tres" id="285_milkfish"] +[ext_resource type="Resource" path="res://fish/species/roosterfish/roosterfish.tres" id="286_roosterfish"] +[ext_resource type="Resource" path="res://fish/species/tarpon_atlantic/tarpon_atlantic.tres" id="287_tarpon_atlantic"] +[ext_resource type="Resource" path="res://fish/species/barramundi/barramundi.tres" id="288_barramundi"] +[ext_resource type="Resource" path="res://fish/species/snook_common/snook_common.tres" id="289_snook_common"] +[ext_resource type="Resource" path="res://fish/species/bonefish/bonefish.tres" id="290_bonefish"] +[ext_resource type="Resource" path="res://fish/species/pompano_florida/pompano_florida.tres" id="291_pompano_florida"] +[ext_resource type="Resource" path="res://fish/species/ladyfish/ladyfish.tres" id="292_ladyfish"] +[ext_resource type="Resource" path="res://fish/species/permit/permit.tres" id="293_permit"] +[ext_resource type="Resource" path="res://fish/species/knifefish_clown/knifefish_clown.tres" id="294_knifefish_clown"] +[ext_resource type="Resource" path="res://fish/species/loach_clown/loach_clown.tres" id="295_loach_clown"] +[ext_resource type="Resource" path="res://fish/species/discus/discus.tres" id="296_discus"] +[ext_resource type="Resource" path="res://fish/species/angelfish_freshwater/angelfish_freshwater.tres" id="297_angelfish_freshwater"] +[ext_resource type="Resource" path="res://fish/species/gourami_giant/gourami_giant.tres" id="298_gourami_giant"] +[ext_resource type="Resource" path="res://fish/species/tilapia_nile/tilapia_nile.tres" id="299_tilapia_nile"] +[ext_resource type="Resource" path="res://fish/species/oscar/oscar.tres" id="300_oscar"] +[ext_resource type="Resource" path="res://fish/species/betta_siamese/betta_siamese.tres" id="301_betta_siamese"] +[ext_resource type="Resource" path="res://fish/species/tambaqui/tambaqui.tres" id="302_tambaqui"] +[ext_resource type="Resource" path="res://fish/species/catfish_walking/catfish_walking.tres" id="303_catfish_walking"] +[ext_resource type="Resource" path="res://fish/species/snakehead_giant/snakehead_giant.tres" id="304_snakehead_giant"] +[ext_resource type="Resource" path="res://fish/species/dorado_golden/dorado_golden.tres" id="305_dorado_golden"] +[ext_resource type="Resource" path="res://fish/species/mahseer_golden/mahseer_golden.tres" id="306_mahseer_golden"] +[ext_resource type="Resource" path="res://fish/species/peacock_bass/peacock_bass.tres" id="307_peacock_bass"] +[ext_resource type="Resource" path="res://fish/species/piranha_red_bellied/piranha_red_bellied.tres" id="308_piranha_red_bellied"] +[ext_resource type="Resource" path="res://fish/species/arowana_silver/arowana_silver.tres" id="309_arowana_silver"] +[ext_resource type="Resource" path="res://fish/species/whale_blue/whale_blue.tres" id="310_whale_blue"] +[ext_resource type="Resource" path="res://fish/species/dolphin_bottlenose/dolphin_bottlenose.tres" id="311_dolphin_bottlenose"] +[ext_resource type="Resource" path="res://fish/species/whale_humpback/whale_humpback.tres" id="312_whale_humpback"] +[ext_resource type="Resource" path="res://fish/species/whale_killer/whale_killer.tres" id="313_whale_killer"] +[ext_resource type="Resource" path="res://fish/species/whale_sperm/whale_sperm.tres" id="314_whale_sperm"] +[ext_resource type="Resource" path="res://fish/species/crab_brown/crab_brown.tres" id="315_crab_brown"] [resource] script = ExtResource("1_pool") -candidates = [ExtResource("2_bluegill"), ExtResource("3_bass"), ExtResource("4_carp"), ExtResource("5_sunfish"), ExtResource("6_catfish_blue"), ExtResource("7_catfish_channel"), ExtResource("8_catfish_flathead"), ExtResource("9_catfish_white"), ExtResource("10_tuna_albacore"), ExtResource("11_tuna_bigeye"), ExtResource("12_tuna_bluefin"), ExtResource("13_tuna_skipjack"), ExtResource("14_tuna_yellowfin"), ExtResource("15_goby_round"), ExtResource("16_salmon_atlantic"), ExtResource("17_salmon_chum"), ExtResource("18_salmon_coho"), ExtResource("19_salmon_pink"), ExtResource("20_salmon_sockeye"), ExtResource("21_anchovy_european"), ExtResource("22_anchovy_northern"), ExtResource("23_chub_european"), ExtResource("24_chub_flame"), ExtResource("25_chub_lake"), ExtResource("26_goldfish"), ExtResource("27_goldfish_bubbleeye"), ExtResource("28_grouper_gulf"), ExtResource("29_grouper_red"), ExtResource("30_mackerel_atlantic"), ExtResource("31_mackerel_cero"), ExtResource("32_mackerel_chub"), ExtResource("33_mackerel_king"), ExtResource("34_mackerel_spanish"), ExtResource("35_marlin_black"), ExtResource("36_marlin_blue"), ExtResource("37_marlin_white"), ExtResource("38_pomfret_black"), ExtResource("39_pomfret_chinese"), ExtResource("40_pomfret_golden"), ExtResource("41_pomfret_white"), ExtResource("42_sailfish"), ExtResource("43_sauger"), ExtResource("44_saugeye"), ExtResource("45_snapper_lane"), ExtResource("46_snapper_mangrove"), ExtResource("47_snapper_mutton"), ExtResource("48_snapper_red"), ExtResource("49_swordfish"), ExtResource("50_trout_cutthroat"), ExtResource("51_trout_golden"), ExtResource("52_trout_rainbow"), ExtResource("53_trout_steelhead"), ExtResource("54_walleye")] +candidates = [ExtResource("2_bluegill"), ExtResource("3_bass"), ExtResource("4_carp"), ExtResource("5_sunfish"), ExtResource("6_catfish_blue"), ExtResource("7_catfish_channel"), ExtResource("8_catfish_flathead"), ExtResource("9_catfish_white"), ExtResource("10_tuna_albacore"), ExtResource("11_tuna_bigeye"), ExtResource("12_tuna_bluefin"), ExtResource("13_tuna_skipjack"), ExtResource("14_tuna_yellowfin"), ExtResource("15_goby_round"), ExtResource("16_salmon_atlantic"), ExtResource("17_salmon_chum"), ExtResource("18_salmon_coho"), ExtResource("19_salmon_pink"), ExtResource("20_salmon_sockeye"), ExtResource("21_anchovy_european"), ExtResource("22_anchovy_northern"), ExtResource("23_chub_european"), ExtResource("24_chub_flame"), ExtResource("25_chub_lake"), ExtResource("26_goldfish"), ExtResource("27_goldfish_bubbleeye"), ExtResource("28_grouper_gulf"), ExtResource("29_grouper_red"), ExtResource("30_mackerel_atlantic"), ExtResource("31_mackerel_cero"), ExtResource("32_mackerel_chub"), ExtResource("33_mackerel_king"), ExtResource("34_mackerel_spanish"), ExtResource("35_marlin_black"), ExtResource("36_marlin_blue"), ExtResource("37_marlin_white"), ExtResource("38_pomfret_black"), ExtResource("39_pomfret_chinese"), ExtResource("40_pomfret_golden"), ExtResource("41_pomfret_white"), ExtResource("42_sailfish"), ExtResource("43_sauger"), ExtResource("44_saugeye"), ExtResource("45_snapper_lane"), ExtResource("46_snapper_mangrove"), ExtResource("47_snapper_mutton"), ExtResource("48_snapper_red"), ExtResource("49_swordfish"), ExtResource("50_trout_cutthroat"), ExtResource("51_trout_golden"), ExtResource("52_trout_rainbow"), ExtResource("53_trout_steelhead"), ExtResource("54_walleye"), ExtResource("55_bowfin"), ExtResource("56_sturgeon_lake"), ExtResource("57_gar_longnose"), ExtResource("58_paddlefish"), ExtResource("59_sturgeon_shovelnose"), ExtResource("60_gar_spotted"), ExtResource("61_lungfish_west_african"), ExtResource("62_mudskipper_atlantic"), ExtResource("63_foureyes_largescale"), ExtResource("64_mullet_red"), ExtResource("65_plaice_european"), ExtResource("66_flounder_summer"), ExtResource("67_flounder_winter"), ExtResource("68_dugong"), ExtResource("69_seal_harbor"), ExtResource("70_sea_otter"), ExtResource("71_walrus"), ExtResource("72_manatee_west_indian"), ExtResource("73_pipefish_bay"), ExtResource("74_seahorse_lined"), ExtResource("75_monkfish"), ExtResource("76_tripletail"), ExtResource("77_bluefish"), ExtResource("78_jack_crevalle"), ExtResource("79_queenfish_talang"), ExtResource("80_sand_lance_american"), ExtResource("81_menhaden_atlantic"), ExtResource("82_sardine_european"), ExtResource("83_sardine_pacific"), ExtResource("84_shark_blacktip"), ExtResource("85_shark_great_hammerhead"), ExtResource("86_shark_great_white"), ExtResource("87_shark_tiger"), ExtResource("88_shark_whale"), ExtResource("89_burbot"), ExtResource("90_cisco"), ExtResource("91_whitefish_lake"), ExtResource("92_char_arctic"), ExtResource("93_grayling_arctic"), ExtResource("94_trout_brown"), ExtResource("95_wolffish_atlantic"), ExtResource("96_cusk"), ExtResource("97_wolffish_spotted"), ExtResource("98_herring_atlantic"), ExtResource("99_capelin"), ExtResource("100_sprat_european"), ExtResource("101_herring_pacific"), ExtResource("102_pollock_alaska"), ExtResource("103_cod_atlantic"), ExtResource("104_pollock_atlantic"), ExtResource("105_sole_dover"), ExtResource("106_haddock"), ExtResource("107_cod_pacific"), ExtResource("108_hake_pacific"), ExtResource("109_hake_silver"), ExtResource("110_nautilus_chambered"), ExtResource("111_cuttlefish_common"), ExtResource("112_cuttlefish_flamboyant"), ExtResource("113_tilefish_blueline"), ExtResource("114_tilefish_golden"), ExtResource("115_ocean_perch_pacific"), ExtResource("116_sea_bass_chilean"), ExtResource("117_sablefish"), ExtResource("118_wreckfish"), ExtResource("119_oarfish_giant"), ExtResource("120_coelacanth_west_indian"), ExtResource("121_anglerfish_black_seadevil"), ExtResource("122_fangtooth_common"), ExtResource("123_gulper_eel"), ExtResource("124_barreleye_pacific"), ExtResource("125_blobfish_smooth_head"), ExtResource("126_lanternfish_spotted"), ExtResource("127_sea_star_crown_of_thorns"), ExtResource("128_sea_cucumber_giant_california"), ExtResource("129_urchin_purple"), ExtResource("130_sand_dollar"), ExtResource("131_sea_star_sunflower"), ExtResource("132_axolotl"), ExtResource("133_hellbender"), ExtResource("134_guppy"), ExtResource("135_tetra_neon"), ExtResource("136_danio_zebra"), ExtResource("137_largemouth_bass"), ExtResource("138_smallmouth_bass"), ExtResource("139_buffalo_bigmouth"), ExtResource("140_redhorse_golden"), ExtResource("141_quillback"), ExtResource("142_buffalo_smallmouth"), ExtResource("143_sucker_white"), ExtResource("144_bullhead_black"), ExtResource("145_bullhead_brown"), ExtResource("146_madtom_tadpole"), ExtResource("147_bullhead_yellow"), ExtResource("148_barbel_common"), ExtResource("149_bream_common"), ExtResource("150_roach_common"), ExtResource("151_fallfish"), ExtResource("152_ide"), ExtResource("153_rudd"), ExtResource("154_tench"), ExtResource("155_crayfish_red_swamp"), ExtResource("156_gar_alligator"), ExtResource("157_arapaima"), ExtResource("158_tigerfish_goliath"), ExtResource("159_muskellunge"), ExtResource("160_shiner_common"), ExtResource("161_chub_creek"), ExtResource("162_shiner_emerald"), ExtResource("163_minnow_fathead"), ExtResource("164_shiner_golden"), ExtResource("165_electric_eel"), ExtResource("166_freshwater_drum"), ExtResource("167_goldeye"), ExtResource("168_mooneye"), ExtResource("169_loach_weather"), ExtResource("170_koi"), ExtResource("171_crappie_black"), ExtResource("172_green_sunfish"), ExtResource("173_pumpkinseed"), ExtResource("174_redear_sunfish"), ExtResource("175_warmouth"), ExtResource("176_crappie_white"), ExtResource("177_yellow_perch"), ExtResource("178_pickerel_chain"), ExtResource("179_pickerel_grass"), ExtResource("180_pike_northern"), ExtResource("181_jelly_crystal"), ExtResource("182_jelly_lions_mane"), ExtResource("183_jelly_moon"), ExtResource("184_jelly_pacific_sea_nettle"), ExtResource("185_jelly_sea_wasp"), ExtResource("186_pompano_african"), ExtResource("187_cobia"), ExtResource("188_trevally_giant"), ExtResource("189_amberjack_greater"), ExtResource("190_amberjack_lesser"), ExtResource("191_halibut_atlantic"), ExtResource("192_halibut_pacific"), ExtResource("193_turbot"), ExtResource("194_lobster_american"), ExtResource("195_krill_antarctic"), ExtResource("196_lobster_caribbean_spiny"), ExtResource("197_shrimp_peacock_mantis"), ExtResource("198_shrimp_skunk_cleaner"), ExtResource("199_crab_ghost"), ExtResource("200_crab_blue"), ExtResource("201_crab_dungeness"), ExtResource("202_crab_japanese_spider"), ExtResource("203_crab_red_king"), ExtResource("204_eel_american"), ExtResource("205_eel_european"), ExtResource("206_smelt_rainbow"), ExtResource("207_smelt_eulachon"), ExtResource("208_sturgeon_white"), ExtResource("209_mahi_mahi"), ExtResource("210_opah"), ExtResource("211_wahoo"), ExtResource("212_octopus_common"), ExtResource("213_octopus_day"), ExtResource("214_octopus_giant_pacific"), ExtResource("215_octopus_greater_blue_ringed"), ExtResource("216_skate_barndoor"), ExtResource("217_manta_ray_giant"), ExtResource("218_sawfish_largetooth"), ExtResource("219_stingray_ocellate_river"), ExtResource("220_stingray_southern"), ExtResource("221_lionfish_red"), ExtResource("222_scorpionfish_red"), ExtResource("223_moray_green"), ExtResource("224_flounder_peacock"), ExtResource("225_surgeonfish_blue"), ExtResource("226_parrotfish_rainbow"), ExtResource("227_parrotfish_stoplight"), ExtResource("228_tang_yellow"), ExtResource("229_trumpetfish_atlantic"), ExtResource("230_triggerfish_gray"), ExtResource("231_pufferfish_guineafowl"), ExtResource("232_cowfish_longhorn"), ExtResource("233_triggerfish_queen"), ExtResource("234_cornetfish_red"), ExtResource("235_porcupinefish_spotted"), ExtResource("236_boxfish_yellow"), ExtResource("237_butterflyfish_copperband"), ExtResource("238_clownfish_ocellaris"), ExtResource("239_angelfish_queen"), ExtResource("240_grouper_atlantic_goliath"), ExtResource("241_trevally_bluefin"), ExtResource("242_grouper_giant"), ExtResource("243_barracuda_great"), ExtResource("244_grouper_nassau"), ExtResource("245_chromis_blue_green"), ExtResource("246_grunt_bluestriped"), ExtResource("247_grunt_french"), ExtResource("248_damselfish_sergeant_major"), ExtResource("249_wrasse_ballan"), ExtResource("250_wrasse_cleaner"), ExtResource("251_hogfish"), ExtResource("252_turtle_green"), ExtResource("253_turtle_hawksbill"), ExtResource("254_turtle_leatherback"), ExtResource("255_turtle_loggerhead"), ExtResource("256_seadragon_leafy"), ExtResource("257_seadragon_weedy"), ExtResource("258_oyster_black_lip_pearl"), ExtResource("259_clam_giant"), ExtResource("260_clam_geoduck"), ExtResource("261_conch_queen"), ExtResource("262_abalone_red"), ExtResource("263_squid_bigfin_reef"), ExtResource("264_squid_colossal"), ExtResource("265_squid_giant"), ExtResource("266_squid_humboldt"), ExtResource("267_halfbeak_ballyhoo"), ExtResource("268_needlefish_hound"), ExtResource("269_flyingfish_tropical"), ExtResource("270_croaker_atlantic"), ExtResource("271_black_drum"), ExtResource("272_red_drum"), ExtResource("273_mullet_striped"), ExtResource("274_weakfish"), ExtResource("275_white_perch"), ExtResource("276_croaker_yellowfin"), ExtResource("277_rockfish_black"), ExtResource("278_sea_bass_black"), ExtResource("279_rockfish_canary"), ExtResource("280_porgy_scup"), ExtResource("281_sheepshead"), ExtResource("282_tautog"), ExtResource("283_rockfish_yelloweye"), ExtResource("284_lingcod"), ExtResource("285_milkfish"), ExtResource("286_roosterfish"), ExtResource("287_tarpon_atlantic"), ExtResource("288_barramundi"), ExtResource("289_snook_common"), ExtResource("290_bonefish"), ExtResource("291_pompano_florida"), ExtResource("292_ladyfish"), ExtResource("293_permit"), ExtResource("294_knifefish_clown"), ExtResource("295_loach_clown"), ExtResource("296_discus"), ExtResource("297_angelfish_freshwater"), ExtResource("298_gourami_giant"), ExtResource("299_tilapia_nile"), ExtResource("300_oscar"), ExtResource("301_betta_siamese"), ExtResource("302_tambaqui"), ExtResource("303_catfish_walking"), ExtResource("304_snakehead_giant"), ExtResource("305_dorado_golden"), ExtResource("306_mahseer_golden"), ExtResource("307_peacock_bass"), ExtResource("308_piranha_red_bellied"), ExtResource("309_arowana_silver"), ExtResource("310_whale_blue"), ExtResource("311_dolphin_bottlenose"), ExtResource("312_whale_humpback"), ExtResource("313_whale_killer"), ExtResource("314_whale_sperm"), ExtResource("315_crab_brown")] diff --git a/fish/species/abalone_red/abalone_red.tres b/fish/species/abalone_red/abalone_red.tres new file mode 100644 index 0000000..039c7e2 --- /dev/null +++ b/fish/species/abalone_red/abalone_red.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"abalone_red" +display_name = "red abalone" +active = false +catalog_number = 6405 +collection_group = &"Shellfish" +logbook_fact = "red abalones cling to pacific rocks with broad muscular feet. rows of shell holes release water that has passed over the gills." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 12.0 +sell_value_min = 24 +sell_value_max = 56 +sell_value_curve = 0.88 diff --git a/fish/species/amberjack_greater/amberjack_greater.tres b/fish/species/amberjack_greater/amberjack_greater.tres new file mode 100644 index 0000000..53ea638 --- /dev/null +++ b/fish/species/amberjack_greater/amberjack_greater.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"amberjack_greater" +display_name = "greater amberjack" +active = false +catalog_number = 3604 +collection_group = &"Large coastal predators" +logbook_fact = "greater amberjacks circle reefs, wrecks, and offshore platforms. a dark stripe crosses the eye of their sturdy torpedo-shaped bodies." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 90.0 +sell_value_min = 15 +sell_value_max = 42 +sell_value_curve = 0.82 diff --git a/fish/species/amberjack_lesser/amberjack_lesser.tres b/fish/species/amberjack_lesser/amberjack_lesser.tres new file mode 100644 index 0000000..2fd0aec --- /dev/null +++ b/fish/species/amberjack_lesser/amberjack_lesser.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"amberjack_lesser" +display_name = "lesser amberjack" +active = false +catalog_number = 3605 +collection_group = &"Large coastal predators" +logbook_fact = "lesser amberjacks school around deep reefs and floating objects. an enlarged eye suits their preference for dimmer water." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 20.0 +sell_value_min = 7 +sell_value_max = 18 +sell_value_curve = 0.86 diff --git a/fish/species/anchovy_european/anchovy_european.tres b/fish/species/anchovy_european/anchovy_european.tres index fea09ed..722fbaf 100644 --- a/fish/species/anchovy_european/anchovy_european.tres +++ b/fish/species/anchovy_european/anchovy_european.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"anchovy_european" display_name = "european anchovy" +active = true +catalog_number = 805 +collection_group = &"Coastal schooling fish" +logbook_fact = "european anchovies form dense schools and feed by filtering tiny plankton from coastal water." allowed_water_types = 2 base_catch_weight = 0.5 catch_profile = ExtResource("2_profile") diff --git a/fish/species/anchovy_northern/anchovy_northern.tres b/fish/species/anchovy_northern/anchovy_northern.tres index ad53d6e..a1940d5 100644 --- a/fish/species/anchovy_northern/anchovy_northern.tres +++ b/fish/species/anchovy_northern/anchovy_northern.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"anchovy_northern" display_name = "northern anchovy" +active = true +catalog_number = 808 +collection_group = &"Coastal schooling fish" +logbook_fact = "northern anchovies gather in huge schools along the pacific coast and filter plankton with their fine gill rakers." allowed_water_types = 2 base_catch_weight = 0.5 catch_profile = ExtResource("2_profile") diff --git a/fish/species/angelfish_freshwater/angelfish_freshwater.tres b/fish/species/angelfish_freshwater/angelfish_freshwater.tres new file mode 100644 index 0000000..5d9bda2 --- /dev/null +++ b/fish/species/angelfish_freshwater/angelfish_freshwater.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"angelfish_freshwater" +display_name = "freshwater angelfish" +active = false +catalog_number = 7504 +collection_group = &"Tropical freshwater fish" +logbook_fact = "freshwater angelfish slip between tall plants on narrow triangular bodies. long fins and vertical bars resemble stems and shadows." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.5 +sell_value_min = 10 +sell_value_max = 22 +sell_value_curve = 0.95 diff --git a/fish/species/angelfish_queen/angelfish_queen.tres b/fish/species/angelfish_queen/angelfish_queen.tres new file mode 100644 index 0000000..d45bbea --- /dev/null +++ b/fish/species/angelfish_queen/angelfish_queen.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"angelfish_queen" +display_name = "queen angelfish" +active = false +catalog_number = 5703 +collection_group = &"Reef ornamentals" +logbook_fact = "queen angelfish graze sponges from warm atlantic reefs. a blue-ringed spot on the forehead resembles a tiny crown." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 2.0 +sell_value_min = 11 +sell_value_max = 25 +sell_value_curve = 0.94 diff --git a/fish/species/anglerfish_black_seadevil/anglerfish_black_seadevil.tres b/fish/species/anglerfish_black_seadevil/anglerfish_black_seadevil.tres new file mode 100644 index 0000000..2888120 --- /dev/null +++ b/fish/species/anglerfish_black_seadevil/anglerfish_black_seadevil.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"anglerfish_black_seadevil" +display_name = "black seadevil" +active = false +catalog_number = 1801 +collection_group = &"Deep-sea oddities" +logbook_fact = "black seadevils lure prey with a glowing organ suspended above the mouth. tiny males permanently attach to much larger females." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.05 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 2.0 +sell_value_min = 48 +sell_value_max = 105 +sell_value_curve = 0.92 diff --git a/fish/species/arapaima/arapaima.tres b/fish/species/arapaima/arapaima.tres new file mode 100644 index 0000000..2ee2c75 --- /dev/null +++ b/fish/species/arapaima/arapaima.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"arapaima" +display_name = "arapaima" +active = false +catalog_number = 2702 +collection_group = &"Freshwater giants" +logbook_fact = "arapaima must surface frequently to gulp air through a specialized swim bladder. large armored scales protect their long river-going bodies." +allowed_water_types = 1 +rarity = 4 +base_catch_weight = 0.04 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 40.0 +weight_max_lb = 400.0 +sell_value_min = 60 +sell_value_max = 140 +sell_value_curve = 0.78 diff --git a/fish/species/arowana_silver/arowana_silver.tres b/fish/species/arowana_silver/arowana_silver.tres new file mode 100644 index 0000000..413621c --- /dev/null +++ b/fish/species/arowana_silver/arowana_silver.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"arowana_silver" +display_name = "silver arowana" +active = false +catalog_number = 7606 +collection_group = &"Tropical freshwater predators" +logbook_fact = "silver arowanas patrol the surface with upturned mouths and long bodies. powerful jumps capture insects and small animals above the water." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 15.0 +sell_value_min = 25 +sell_value_max = 56 +sell_value_curve = 0.86 diff --git a/fish/species/axolotl/axolotl.tres b/fish/species/axolotl/axolotl.tres new file mode 100644 index 0000000..17ba5ad --- /dev/null +++ b/fish/species/axolotl/axolotl.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"axolotl" +display_name = "axolotl" +active = false +catalog_number = 2001 +collection_group = &"Freshwater amphibians" +logbook_fact = "axolotls remain aquatic and keep feathery external gills throughout adulthood. lost limbs and parts of several organs can regenerate." +allowed_water_types = 1 +rarity = 4 +base_catch_weight = 0.04 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 0.7 +sell_value_min = 36 +sell_value_max = 78 +sell_value_curve = 0.96 diff --git a/fish/species/barbel_common/barbel_common.tres b/fish/species/barbel_common/barbel_common.tres new file mode 100644 index 0000000..4e1b93d --- /dev/null +++ b/fish/species/barbel_common/barbel_common.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"barbel_common" +display_name = "common barbel" +active = false +catalog_number = 2501 +collection_group = &"Freshwater coarse fish" +logbook_fact = "common barbels hold close to fast river bottoms with streamlined bodies and broad fins. four mouth barbels locate food among stones." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 25.0 +sell_value_min = 13 +sell_value_max = 31 +sell_value_curve = 0.85 diff --git a/fish/species/barracuda_great/barracuda_great.tres b/fish/species/barracuda_great/barracuda_great.tres new file mode 100644 index 0000000..c2a105f --- /dev/null +++ b/fish/species/barracuda_great/barracuda_great.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"barracuda_great" +display_name = "great barracuda" +active = false +catalog_number = 5804 +collection_group = &"Reef predators" +logbook_fact = "great barracudas hover motionless before accelerating toward prey. long jaws carry two distinct rows of sharp teeth." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.12 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 80.0 +sell_value_min = 28 +sell_value_max = 65 +sell_value_curve = 0.82 diff --git a/fish/species/barramundi/barramundi.tres b/fish/species/barramundi/barramundi.tres new file mode 100644 index 0000000..afbd336 --- /dev/null +++ b/fish/species/barramundi/barramundi.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"barramundi" +display_name = "barramundi" +active = false +catalog_number = 7302 +collection_group = &"Tropical estuary" +logbook_fact = "barramundi move between rivers and coasts and can breathe in low-oxygen water. many mature first as males and later become females." +allowed_water_types = 3 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 45.0 +sell_value_min = 15 +sell_value_max = 38 +sell_value_curve = 0.84 diff --git a/fish/species/barreleye_pacific/barreleye_pacific.tres b/fish/species/barreleye_pacific/barreleye_pacific.tres new file mode 100644 index 0000000..d784c35 --- /dev/null +++ b/fish/species/barreleye_pacific/barreleye_pacific.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"barreleye_pacific" +display_name = "Pacific barreleye" +active = false +catalog_number = 1804 +collection_group = &"Deep-sea oddities" +logbook_fact = "pacific barreleyes peer upward through transparent shields covering the head. tubular eyes rotate forward when the fish turns to feed." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.045 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 2.5 +sell_value_min = 42 +sell_value_max = 88 +sell_value_curve = 0.9 diff --git a/fish/species/bass/bass.tres b/fish/species/bass/bass.tres index bc2b026..b263c3f 100644 --- a/fish/species/bass/bass.tres +++ b/fish/species/bass/bass.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"bass" display_name = "striped bass" +active = true +catalog_number = 6701 +collection_group = &"Temperate coastal" +logbook_fact = "striped bass split their lives between salt and fresh water, returning upriver to spawn. a long-lived striper may see three decades." allowed_water_types = 2 rarity = 1 base_catch_weight = 4.0 diff --git a/fish/species/betta_siamese/betta_siamese.tres b/fish/species/betta_siamese/betta_siamese.tres new file mode 100644 index 0000000..06bbac6 --- /dev/null +++ b/fish/species/betta_siamese/betta_siamese.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"betta_siamese" +display_name = "Siamese fighting fish" +active = false +catalog_number = 7508 +collection_group = &"Tropical freshwater fish" +logbook_fact = "siamese fighting fish breathe surface air with a labyrinth organ. males build bubble nests and guard the eggs beneath them." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 1.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.15 +sell_value_min = 5 +sell_value_max = 10 +sell_value_curve = 1.0 diff --git a/fish/species/black_drum/black_drum.tres b/fish/species/black_drum/black_drum.tres new file mode 100644 index 0000000..88a4ee2 --- /dev/null +++ b/fish/species/black_drum/black_drum.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"black_drum" +display_name = "black drum" +active = false +catalog_number = 6802 +collection_group = &"Temperate estuary" +logbook_fact = "black drum crush oysters and crabs with rounded throat teeth. large adults develop dark vertical bars that fade with age." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 70.0 +sell_value_min = 8 +sell_value_max = 27 +sell_value_curve = 0.82 diff --git a/fish/species/blobfish_smooth_head/blobfish_smooth_head.tres b/fish/species/blobfish_smooth_head/blobfish_smooth_head.tres new file mode 100644 index 0000000..0117988 --- /dev/null +++ b/fish/species/blobfish_smooth_head/blobfish_smooth_head.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"blobfish_smooth_head" +display_name = "smooth-head blobfish" +active = false +catalog_number = 1805 +collection_group = &"Deep-sea oddities" +logbook_fact = "smooth-head blobfish rest on deep slopes where pressure supports their soft bodies. they conserve energy by waiting for edible matter nearby." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.04 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 5.0 +sell_value_min = 42 +sell_value_max = 90 +sell_value_curve = 0.86 diff --git a/fish/species/bluefish/bluefish.tres b/fish/species/bluefish/bluefish.tres new file mode 100644 index 0000000..70288f5 --- /dev/null +++ b/fish/species/bluefish/bluefish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"bluefish" +display_name = "bluefish" +active = false +catalog_number = 702 +collection_group = &"Coastal pelagic fish" +logbook_fact = "bluefish race in coordinated schools while pursuing smaller fish. sharp teeth and powerful jaws make their feeding bursts especially fierce." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.85 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 25.0 +sell_value_min = 7 +sell_value_max = 19 +sell_value_curve = 0.85 diff --git a/fish/species/bluegill/bluegill.tres b/fish/species/bluegill/bluegill.tres index 9e98a5b..04c03d0 100644 --- a/fish/species/bluegill/bluegill.tres +++ b/fish/species/bluegill/bluegill.tres @@ -9,6 +9,10 @@ script = ExtResource("1_fish_data") id = &"bluegill" display_name = "bluegill" +active = true +catalog_number = 3102 +collection_group = &"Freshwater panfish" +logbook_fact = "bluegill fathers sweep shallow bowls into the bottom for nests, then stand guard. whole neighborhoods of nests can crowd together." allowed_water_types = 1 rarity = 0 base_catch_weight = 10.0 diff --git a/fish/species/bonefish/bonefish.tres b/fish/species/bonefish/bonefish.tres new file mode 100644 index 0000000..0c0d6dd --- /dev/null +++ b/fish/species/bonefish/bonefish.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"bonefish" +display_name = "bonefish" +active = false +catalog_number = 7401 +collection_group = &"Tropical flats fish" +logbook_fact = "bonefish sweep tropical flats in silvery schools and root for buried prey. their speed and pale bodies make them difficult to follow in shallow water." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 15.0 +sell_value_min = 13 +sell_value_max = 31 +sell_value_curve = 0.86 diff --git a/fish/species/bowfin/bowfin.tres b/fish/species/bowfin/bowfin.tres new file mode 100644 index 0000000..748197a --- /dev/null +++ b/fish/species/bowfin/bowfin.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"bowfin" +display_name = "bowfin" +active = false +catalog_number = 101 +collection_group = &"Ancient freshwater fish" +logbook_fact = "bowfin can gulp air at the surface when warm backwaters run low on oxygen. a long dorsal fin ripples as they stalk prey." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 20.0 +sell_value_min = 13 +sell_value_max = 30 +sell_value_curve = 0.85 diff --git a/fish/species/boxfish_yellow/boxfish_yellow.tres b/fish/species/boxfish_yellow/boxfish_yellow.tres new file mode 100644 index 0000000..d701ada --- /dev/null +++ b/fish/species/boxfish_yellow/boxfish_yellow.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"boxfish_yellow" +display_name = "yellow boxfish" +active = false +catalog_number = 5608 +collection_group = &"Reef oddities" +logbook_fact = "yellow boxfish juveniles are bright cubes covered in black spots. rigid body armor leaves small fins to provide careful steering." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.32 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.8 +sell_value_min = 10 +sell_value_max = 22 +sell_value_curve = 0.96 diff --git a/fish/species/bream_common/bream_common.tres b/fish/species/bream_common/bream_common.tres new file mode 100644 index 0000000..54ecc57 --- /dev/null +++ b/fish/species/bream_common/bream_common.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"bream_common" +display_name = "common bream" +active = false +catalog_number = 2502 +collection_group = &"Freshwater coarse fish" +logbook_fact = "common bream gather in slow rivers and lakes with silvery, deep-sided bodies. protruding mouths sift insect larvae from soft bottoms." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 1.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 15.0 +sell_value_min = 4 +sell_value_max = 12 +sell_value_curve = 0.88 diff --git a/fish/species/buffalo_bigmouth/buffalo_bigmouth.tres b/fish/species/buffalo_bigmouth/buffalo_bigmouth.tres new file mode 100644 index 0000000..4053eb6 --- /dev/null +++ b/fish/species/buffalo_bigmouth/buffalo_bigmouth.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"buffalo_bigmouth" +display_name = "bigmouth buffalo" +active = false +catalog_number = 2301 +collection_group = &"Freshwater bottom fish" +logbook_fact = "bigmouth buffalo filter plankton with fine gill rakers instead of rooting for prey. some individuals survive for more than a century." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.12 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 8.0 +weight_max_lb = 80.0 +sell_value_min = 28 +sell_value_max = 63 +sell_value_curve = 0.82 diff --git a/fish/species/buffalo_smallmouth/buffalo_smallmouth.tres b/fish/species/buffalo_smallmouth/buffalo_smallmouth.tres new file mode 100644 index 0000000..f0d2780 --- /dev/null +++ b/fish/species/buffalo_smallmouth/buffalo_smallmouth.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"buffalo_smallmouth" +display_name = "smallmouth buffalo" +active = false +catalog_number = 2304 +collection_group = &"Freshwater bottom fish" +logbook_fact = "smallmouth buffalo use thick lips to graze bottom-dwelling food in warm rivers. their deep, heavy bodies form a pronounced arch behind the head." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 3.0 +weight_max_lb = 45.0 +sell_value_min = 14 +sell_value_max = 34 +sell_value_curve = 0.84 diff --git a/fish/species/bullhead_black/bullhead_black.tres b/fish/species/bullhead_black/bullhead_black.tres new file mode 100644 index 0000000..87f6155 --- /dev/null +++ b/fish/species/bullhead_black/bullhead_black.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"bullhead_black" +display_name = "black bullhead" +active = false +catalog_number = 2401 +collection_group = &"Freshwater catfish" +logbook_fact = "black bullheads tolerate warm, cloudy water with little oxygen. stout spines protect the dorsal and pectoral fins from predators." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 1.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 6.0 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.92 diff --git a/fish/species/bullhead_brown/bullhead_brown.tres b/fish/species/bullhead_brown/bullhead_brown.tres new file mode 100644 index 0000000..c900d5f --- /dev/null +++ b/fish/species/bullhead_brown/bullhead_brown.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"bullhead_brown" +display_name = "brown bullhead" +active = false +catalog_number = 2403 +collection_group = &"Freshwater catfish" +logbook_fact = "brown bullheads search muddy bottoms with eight sensitive barbels. males guard nests and herd newly hatched young in a dark moving ball." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 4.0 +sell_value_min = 3 +sell_value_max = 7 +sell_value_curve = 0.95 diff --git a/fish/species/bullhead_yellow/bullhead_yellow.tres b/fish/species/bullhead_yellow/bullhead_yellow.tres new file mode 100644 index 0000000..e2b5d63 --- /dev/null +++ b/fish/species/bullhead_yellow/bullhead_yellow.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"bullhead_yellow" +display_name = "yellow bullhead" +active = false +catalog_number = 2408 +collection_group = &"Freshwater catfish" +logbook_fact = "yellow bullheads use pale chin barbels to distinguish food in murky shallows. both parents may guard the nest and schooling young." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 3.5 +sell_value_min = 3 +sell_value_max = 7 +sell_value_curve = 0.95 diff --git a/fish/species/burbot/burbot.tres b/fish/species/burbot/burbot.tres new file mode 100644 index 0000000..25e9277 --- /dev/null +++ b/fish/species/burbot/burbot.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"burbot" +display_name = "burbot" +active = false +catalog_number = 1001 +collection_group = &"Cold freshwater fish" +logbook_fact = "burbot are the only freshwater members of the cod family. they become most active in cold darkness and may spawn beneath winter ice." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 18.0 +sell_value_min = 12 +sell_value_max = 28 +sell_value_curve = 0.86 diff --git a/fish/species/butterflyfish_copperband/butterflyfish_copperband.tres b/fish/species/butterflyfish_copperband/butterflyfish_copperband.tres new file mode 100644 index 0000000..e9a3385 --- /dev/null +++ b/fish/species/butterflyfish_copperband/butterflyfish_copperband.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"butterflyfish_copperband" +display_name = "copperband butterflyfish" +active = false +catalog_number = 5701 +collection_group = &"Reef ornamentals" +logbook_fact = "copperband butterflyfish probe reef cracks with long narrow snouts. bold orange bands and a false eyespot confuse predators." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.32 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.4 +sell_value_min = 9 +sell_value_max = 21 +sell_value_curve = 0.98 diff --git a/fish/species/capelin/capelin.tres b/fish/species/capelin/capelin.tres new file mode 100644 index 0000000..0e5f25b --- /dev/null +++ b/fish/species/capelin/capelin.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"capelin" +display_name = "capelin" +active = false +catalog_number = 1302 +collection_group = &"Cold schooling fish" +logbook_fact = "capelin are small polar fish that feed whales, seabirds, and larger fish. many schools roll onto beaches or shallows to release their eggs." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 1.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.2 +sell_value_min = 4 +sell_value_max = 8 +sell_value_curve = 1.0 diff --git a/fish/species/carp/carp.tres b/fish/species/carp/carp.tres index 5673834..4bcdb6c 100644 --- a/fish/species/carp/carp.tres +++ b/fish/species/carp/carp.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.5 script = ExtResource("1_fish_data") id = &"carp" display_name = "common carp" +active = true +catalog_number = 2503 +collection_group = &"Freshwater coarse fish" +logbook_fact = "whisker-like barbels help a common carp investigate the bottom. nosing through sediment can cloud the water and loosen plants." allowed_water_types = 1 rarity = 2 base_catch_weight = 1.5 diff --git a/fish/species/catfish_blue/catfish_blue.tres b/fish/species/catfish_blue/catfish_blue.tres index 482520a..98fda16 100644 --- a/fish/species/catfish_blue/catfish_blue.tres +++ b/fish/species/catfish_blue/catfish_blue.tres @@ -16,6 +16,10 @@ preferred_bait_weight_multiplier = 1.35 script = ExtResource("1_fish_data") id = &"catfish_blue" display_name = "blue catfish" +active = true +catalog_number = 2402 +collection_group = &"Freshwater catfish" +logbook_fact = "a deep fork in the tail inspired the blue catfish's scientific name. it rests deep by daylight and becomes more active after dark." allowed_water_types = 1 rarity = 1 base_catch_weight = 1.25 diff --git a/fish/species/catfish_channel/catfish_channel.tres b/fish/species/catfish_channel/catfish_channel.tres index 265daad..69aff97 100644 --- a/fish/species/catfish_channel/catfish_channel.tres +++ b/fish/species/catfish_channel/catfish_channel.tres @@ -16,6 +16,10 @@ preferred_bait_weight_multiplier = 1.35 script = ExtResource("1_fish_data") id = &"catfish_channel" display_name = "channel catfish" +active = true +catalog_number = 2404 +collection_group = &"Freshwater catfish" +logbook_fact = "a channel catfish can taste through skin all over its body, especially near its gills and whiskers. the male watches the eggs." allowed_water_types = 1 base_catch_weight = 2.0 catch_profile = ExtResource("2_profile") diff --git a/fish/species/catfish_flathead/catfish_flathead.tres b/fish/species/catfish_flathead/catfish_flathead.tres index 651359a..84a6f01 100644 --- a/fish/species/catfish_flathead/catfish_flathead.tres +++ b/fish/species/catfish_flathead/catfish_flathead.tres @@ -16,6 +16,10 @@ preferred_bait_weight_multiplier = 1.35 script = ExtResource("1_fish_data") id = &"catfish_flathead" display_name = "flathead catfish" +active = true +catalog_number = 2405 +collection_group = &"Freshwater catfish" +logbook_fact = "flatheads favor hideouts made by logs, rocks, ledges, and other underwater structure. as they grow, fish dominate their menu." allowed_water_types = 1 rarity = 1 catch_profile = ExtResource("2_profile") diff --git a/fish/species/catfish_walking/catfish_walking.tres b/fish/species/catfish_walking/catfish_walking.tres new file mode 100644 index 0000000..7b8dbf4 --- /dev/null +++ b/fish/species/catfish_walking/catfish_walking.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"catfish_walking" +display_name = "walking catfish" +active = false +catalog_number = 7510 +collection_group = &"Tropical freshwater fish" +logbook_fact = "walking catfish wriggle across wet ground using stiff pectoral spines for support. an air-breathing organ helps them survive the journey." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 5.0 +sell_value_min = 11 +sell_value_max = 24 +sell_value_curve = 0.9 diff --git a/fish/species/catfish_white/catfish_white.tres b/fish/species/catfish_white/catfish_white.tres index 6ee72ea..e5cc545 100644 --- a/fish/species/catfish_white/catfish_white.tres +++ b/fish/species/catfish_white/catfish_white.tres @@ -16,6 +16,10 @@ preferred_bait_weight_multiplier = 1.35 script = ExtResource("1_fish_data") id = &"catfish_white" display_name = "white catfish" +active = true +catalog_number = 2407 +collection_group = &"Freshwater catfish" +logbook_fact = "white catfish began along the atlantic coast between new york and florida. people later carried them far beyond that home range." allowed_water_types = 1 base_catch_weight = 2.0 catch_profile = ExtResource("2_profile") diff --git a/fish/species/char_arctic/char_arctic.tres b/fish/species/char_arctic/char_arctic.tres new file mode 100644 index 0000000..cb47137 --- /dev/null +++ b/fish/species/char_arctic/char_arctic.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +forbid_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"char_arctic" +display_name = "arctic char" +active = false +catalog_number = 1101 +collection_group = &"Cold freshwater salmonids" +logbook_fact = "arctic char thrive farther north than any other freshwater fish. some remain in lakes while others migrate between rivers and the sea." +allowed_water_types = 3 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 25.0 +sell_value_min = 14 +sell_value_max = 32 +sell_value_curve = 0.86 diff --git a/fish/species/chromis_blue_green/chromis_blue_green.tres b/fish/species/chromis_blue_green/chromis_blue_green.tres new file mode 100644 index 0000000..e02ded5 --- /dev/null +++ b/fish/species/chromis_blue_green/chromis_blue_green.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"chromis_blue_green" +display_name = "blue-green chromis" +active = false +catalog_number = 5901 +collection_group = &"Reef schooling fish" +logbook_fact = "blue-green chromis form loose clouds above branching coral. the school dives into narrow shelter whenever a predator approaches." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.25 +sell_value_min = 2 +sell_value_max = 4 +sell_value_curve = 1.0 diff --git a/fish/species/chub_creek/chub_creek.tres b/fish/species/chub_creek/chub_creek.tres new file mode 100644 index 0000000..e2bda01 --- /dev/null +++ b/fish/species/chub_creek/chub_creek.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"chub_creek" +display_name = "creek chub" +active = false +catalog_number = 2802 +collection_group = &"Freshwater minnows" +logbook_fact = "creek chubs gather around pools and undercut banks in small streams. males build gravel ridges where several fish may spawn." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 3.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.8 +sell_value_min = 2 +sell_value_max = 4 +sell_value_curve = 1.0 diff --git a/fish/species/chub_european/chub_european.tres b/fish/species/chub_european/chub_european.tres index e282b90..aeed2b7 100644 --- a/fish/species/chub_european/chub_european.tres +++ b/fish/species/chub_european/chub_european.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"chub_european" display_name = "european chub" +active = true +catalog_number = 2505 +collection_group = &"Freshwater coarse fish" +logbook_fact = "european chub are adaptable river fish. adults often patrol shallows and feed on insects, small fish, and fruit." allowed_water_types = 1 base_catch_weight = 0.55 catch_profile = ExtResource("2_profile") diff --git a/fish/species/chub_flame/chub_flame.tres b/fish/species/chub_flame/chub_flame.tres index d631ab0..4ca30fe 100644 --- a/fish/species/chub_flame/chub_flame.tres +++ b/fish/species/chub_flame/chub_flame.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"chub_flame" display_name = "flame chub" +active = true +catalog_number = 2805 +collection_group = &"Freshwater minnows" +logbook_fact = "flame chubs prefer cool, clear streams. their bright breeding colors make a small fish easy to spot in spring water." allowed_water_types = 1 rarity = 1 base_catch_weight = 0.4 diff --git a/fish/species/chub_lake/chub_lake.tres b/fish/species/chub_lake/chub_lake.tres index c04c5b3..8b8927f 100644 --- a/fish/species/chub_lake/chub_lake.tres +++ b/fish/species/chub_lake/chub_lake.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"chub_lake" display_name = "lake chub" +active = true +catalog_number = 2807 +collection_group = &"Freshwater minnows" +logbook_fact = "lake chubs school in cold northern water and use their small mouths to pick insects and other tiny prey from the current." allowed_water_types = 1 base_catch_weight = 0.55 catch_profile = ExtResource("2_profile") diff --git a/fish/species/cisco/cisco.tres b/fish/species/cisco/cisco.tres new file mode 100644 index 0000000..694a9ec --- /dev/null +++ b/fish/species/cisco/cisco.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"cisco" +display_name = "cisco" +active = false +catalog_number = 1002 +collection_group = &"Cold freshwater fish" +logbook_fact = "cisco school in the cool open water of northern lakes. these slim relatives of salmon feed on plankton and become prey for larger fish." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 3.0 +sell_value_min = 3 +sell_value_max = 7 +sell_value_curve = 0.95 diff --git a/fish/species/clam_geoduck/clam_geoduck.tres b/fish/species/clam_geoduck/clam_geoduck.tres new file mode 100644 index 0000000..82c8eac --- /dev/null +++ b/fish/species/clam_geoduck/clam_geoduck.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"clam_geoduck" +display_name = "Pacific geoduck" +active = false +catalog_number = 6403 +collection_group = &"Shellfish" +logbook_fact = "pacific geoducks bury their shells deep while a long siphon reaches the seafloor. these clams can survive for well over a century." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 15.0 +sell_value_min = 14 +sell_value_max = 34 +sell_value_curve = 0.88 diff --git a/fish/species/clam_giant/clam_giant.tres b/fish/species/clam_giant/clam_giant.tres new file mode 100644 index 0000000..d56f410 --- /dev/null +++ b/fish/species/clam_giant/clam_giant.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"clam_giant" +display_name = "giant clam" +active = false +catalog_number = 6402 +collection_group = &"Shellfish" +logbook_fact = "giant clams host photosynthetic algae inside brightly colored mantles. heavy shells remain anchored to one reef spot throughout adulthood." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 500.0 +sell_value_min = 28 +sell_value_max = 72 +sell_value_curve = 0.8 diff --git a/fish/species/clownfish_ocellaris/clownfish_ocellaris.tres b/fish/species/clownfish_ocellaris/clownfish_ocellaris.tres new file mode 100644 index 0000000..b09dbdb --- /dev/null +++ b/fish/species/clownfish_ocellaris/clownfish_ocellaris.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"clownfish_ocellaris" +display_name = "ocellaris clownfish" +active = false +catalog_number = 5702 +collection_group = &"Reef ornamentals" +logbook_fact = "ocellaris clownfish shelter safely among stinging anemone tentacles. each group is led by a female that develops from the largest male." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 1.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.15 +sell_value_min = 5 +sell_value_max = 10 +sell_value_curve = 1.0 diff --git a/fish/species/cobia/cobia.tres b/fish/species/cobia/cobia.tres new file mode 100644 index 0000000..885f55a --- /dev/null +++ b/fish/species/cobia/cobia.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"cobia" +display_name = "cobia" +active = false +catalog_number = 3602 +collection_group = &"Large coastal predators" +logbook_fact = "cobia often follow sharks, rays, and floating structures in warm seas. a broad dark stripe runs along their long, powerful bodies." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 80.0 +sell_value_min = 15 +sell_value_max = 40 +sell_value_curve = 0.82 diff --git a/fish/species/cod_atlantic/cod_atlantic.tres b/fish/species/cod_atlantic/cod_atlantic.tres new file mode 100644 index 0000000..22ee1ca --- /dev/null +++ b/fish/species/cod_atlantic/cod_atlantic.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"cod_atlantic" +display_name = "atlantic cod" +active = false +catalog_number = 1402 +collection_group = &"Cold shelf fish" +logbook_fact = "atlantic cod carry a pale lateral line and one chin barbel. they search cold seafloors for crustaceans, mollusks, and smaller fish." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 55.0 +sell_value_min = 5 +sell_value_max = 18 +sell_value_curve = 0.85 diff --git a/fish/species/cod_pacific/cod_pacific.tres b/fish/species/cod_pacific/cod_pacific.tres new file mode 100644 index 0000000..7f0b8ce --- /dev/null +++ b/fish/species/cod_pacific/cod_pacific.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"cod_pacific" +display_name = "pacific cod" +active = false +catalog_number = 1406 +collection_group = &"Cold shelf fish" +logbook_fact = "pacific cod use a chin barbel to investigate cold shelf bottoms. seasonal schools follow prey between deeper water and coastal spawning grounds." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 45.0 +sell_value_min = 5 +sell_value_max = 17 +sell_value_curve = 0.85 diff --git a/fish/species/coelacanth_west_indian/coelacanth_west_indian.tres b/fish/species/coelacanth_west_indian/coelacanth_west_indian.tres new file mode 100644 index 0000000..fafc161 --- /dev/null +++ b/fish/species/coelacanth_west_indian/coelacanth_west_indian.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"coelacanth_west_indian" +display_name = "West Indian Ocean coelacanth" +active = false +catalog_number = 1702 +collection_group = &"Deep-sea legends" +logbook_fact = "west indian ocean coelacanths shelter in deep volcanic caves by day. fleshy lobed fins move in an alternating, limb-like rhythm." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.03 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 200.0 +sell_value_min = 65 +sell_value_max = 145 +sell_value_curve = 0.8 diff --git a/fish/species/conch_queen/conch_queen.tres b/fish/species/conch_queen/conch_queen.tres new file mode 100644 index 0000000..0868ddf --- /dev/null +++ b/fish/species/conch_queen/conch_queen.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"conch_queen" +display_name = "queen conch" +active = false +catalog_number = 6404 +collection_group = &"Shellfish" +logbook_fact = "queen conchs graze algae with a long flexible snout. adults develop a broad flared shell lip and move by vaulting on one muscular foot." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 5.0 +sell_value_min = 15 +sell_value_max = 36 +sell_value_curve = 0.91 diff --git a/fish/species/cornetfish_red/cornetfish_red.tres b/fish/species/cornetfish_red/cornetfish_red.tres new file mode 100644 index 0000000..fe4fbc9 --- /dev/null +++ b/fish/species/cornetfish_red/cornetfish_red.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"cornetfish_red" +display_name = "red cornetfish" +active = false +catalog_number = 5606 +collection_group = &"Reef oddities" +logbook_fact = "red cornetfish stalk reefs with extremely long bodies and tubular snouts. a trailing filament extends from the center of the tail." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 4.0 +sell_value_min = 24 +sell_value_max = 55 +sell_value_curve = 0.92 diff --git a/fish/species/cowfish_longhorn/cowfish_longhorn.tres b/fish/species/cowfish_longhorn/cowfish_longhorn.tres new file mode 100644 index 0000000..2f647cc --- /dev/null +++ b/fish/species/cowfish_longhorn/cowfish_longhorn.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"cowfish_longhorn" +display_name = "longhorn cowfish" +active = false +catalog_number = 5604 +collection_group = &"Reef oddities" +logbook_fact = "longhorn cowfish wear a rigid box of fused scales with horns above the eyes. small fins steer the angular body with surprising precision." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.13 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 2.0 +sell_value_min = 23 +sell_value_max = 52 +sell_value_curve = 0.94 diff --git a/fish/species/crab_blue/crab_blue.tres b/fish/species/crab_blue/crab_blue.tres new file mode 100644 index 0000000..fd2ec5c --- /dev/null +++ b/fish/species/crab_blue/crab_blue.tres @@ -0,0 +1,31 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"crab_blue" +display_name = "blue crab" +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." +collection_method = 1 +logbook_section = 1 +habitat_label = "shallow coastal water" +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.15 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.1 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.95 diff --git a/fish/species/crab_brown/crab_brown.png b/fish/species/crab_brown/crab_brown.png new file mode 100644 index 0000000..a06f039 Binary files /dev/null and b/fish/species/crab_brown/crab_brown.png differ diff --git a/items/icons/placeholder/cooler_expansion.svg.import b/fish/species/crab_brown/crab_brown.png.import similarity index 65% rename from items/icons/placeholder/cooler_expansion.svg.import rename to fish/species/crab_brown/crab_brown.png.import index fbcc85c..9faf442 100644 --- a/items/icons/placeholder/cooler_expansion.svg.import +++ b/fish/species/crab_brown/crab_brown.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://d115jiqughf40" -path="res://.godot/imported/cooler_expansion.svg-a3d63bc21ba230007f1039372b518c8f.ctex" +uid="uid://dmn3rafvxtnb1" +path="res://.godot/imported/crab_brown.png-4241f8408bf426d406e45f4cce16bbc8.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://items/icons/placeholder/cooler_expansion.svg" -dest_files=["res://.godot/imported/cooler_expansion.svg-a3d63bc21ba230007f1039372b518c8f.ctex"] +source_file="res://fish/species/crab_brown/crab_brown.png" +dest_files=["res://.godot/imported/crab_brown.png-4241f8408bf426d406e45f4cce16bbc8.ctex"] [params] @@ -38,6 +38,3 @@ process/hdr_as_srgb=false process/hdr_clamp_exposure=false process/size_limit=0 detect_3d/compress_to=1 -svg/scale=1.0 -editor/scale_with_editor_scale=false -editor/convert_colors_with_editor_theme=false diff --git a/fish/species/crab_brown/crab_brown.tres b/fish/species/crab_brown/crab_brown.tres new file mode 100644 index 0000000..14e30c8 --- /dev/null +++ b/fish/species/crab_brown/crab_brown.tres @@ -0,0 +1,27 @@ +[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="Texture2D" path="res://fish/species/crab_brown/crab_brown.png" id="3_texture"] + +[resource] +script = ExtResource("1_fish_data") +id = &"crab_brown" +display_name = "brown crab" +active = true +catalog_number = 3906 +collection_group = &"Marine crabs" +logbook_fact = "brown crabs hide among rocks and sand along sheltered coasts. broad claws help them feed on shellfish and other small shore animals." +collection_method = 1 +logbook_section = 1 +habitat_label = "dry beach sand" +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.0 +catch_profile = ExtResource("2_profile") +weight_min_lb = 0.5 +weight_max_lb = 3.0 +sell_value_min = 5 +sell_value_max = 15 +sell_value_curve = 1.0 +display_texture = ExtResource("3_texture") diff --git a/fish/species/crab_dungeness/crab_dungeness.tres b/fish/species/crab_dungeness/crab_dungeness.tres new file mode 100644 index 0000000..ada612a --- /dev/null +++ b/fish/species/crab_dungeness/crab_dungeness.tres @@ -0,0 +1,32 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"crab_dungeness" +display_name = "Dungeness crab" +active = false +catalog_number = 3903 +collection_group = &"Marine crabs" +logbook_fact = "dungeness crabs bury beneath sand with only their eyes and antennae exposed. broad toothed shells protect them along cool pacific coasts." +collection_method = 1 +logbook_section = 1 +habitat_label = "shallow coastal sand" +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.65 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 3.0 +sell_value_min = 6 +sell_value_max = 14 +sell_value_curve = 0.92 diff --git a/fish/species/crab_ghost/crab_ghost.tres b/fish/species/crab_ghost/crab_ghost.tres new file mode 100644 index 0000000..dca2eff --- /dev/null +++ b/fish/species/crab_ghost/crab_ghost.tres @@ -0,0 +1,32 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"crab_ghost" +display_name = "Atlantic ghost crab" +active = false +catalog_number = 3901 +collection_group = &"Marine crabs" +logbook_fact = "atlantic ghost crabs dash across beaches on long pale legs and retreat into deep burrows. raised eyes scan nearly every direction." +collection_method = 1 +logbook_section = 1 +habitat_label = "dry beach sand" +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.02 +weight_max_lb = 0.5 +sell_value_min = 11 +sell_value_max = 24 +sell_value_curve = 0.97 diff --git a/fish/species/crab_japanese_spider/crab_japanese_spider.tres b/fish/species/crab_japanese_spider/crab_japanese_spider.tres new file mode 100644 index 0000000..e045021 --- /dev/null +++ b/fish/species/crab_japanese_spider/crab_japanese_spider.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"crab_japanese_spider" +display_name = "Japanese spider crab" +active = false +catalog_number = 3904 +collection_group = &"Marine crabs" +logbook_fact = "japanese spider crabs carry the longest leg span of any living arthropod. adults roam deep seafloors around japan." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.045 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 44.0 +sell_value_min = 38 +sell_value_max = 88 +sell_value_curve = 0.84 diff --git a/fish/species/crab_red_king/crab_red_king.tres b/fish/species/crab_red_king/crab_red_king.tres new file mode 100644 index 0000000..d0b8a74 --- /dev/null +++ b/fish/species/crab_red_king/crab_red_king.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"crab_red_king" +display_name = "red king crab" +active = false +catalog_number = 3905 +collection_group = &"Marine crabs" +logbook_fact = "red king crabs stride over cold northern bottoms on long armored legs. despite the name, they are closer to hermit crabs than true crabs." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 24.0 +sell_value_min = 24 +sell_value_max = 58 +sell_value_curve = 0.85 diff --git a/fish/species/crappie_black/crappie_black.tres b/fish/species/crappie_black/crappie_black.tres new file mode 100644 index 0000000..ba6956e --- /dev/null +++ b/fish/species/crappie_black/crappie_black.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"crappie_black" +display_name = "black crappie" +active = false +catalog_number = 3101 +collection_group = &"Freshwater panfish" +logbook_fact = "black crappie gather around submerged wood and vegetation in clear water. irregular dark speckles scatter across their deep silver bodies." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 1.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 4.0 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.9 diff --git a/fish/species/crappie_white/crappie_white.tres b/fish/species/crappie_white/crappie_white.tres new file mode 100644 index 0000000..85c22b0 --- /dev/null +++ b/fish/species/crappie_white/crappie_white.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"crappie_white" +display_name = "white crappie" +active = false +catalog_number = 3107 +collection_group = &"Freshwater panfish" +logbook_fact = "white crappie favor warm, cloudy water and gather around structure. vertical bars cross their silver sides instead of scattered speckles." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 1.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 4.5 +sell_value_min = 4 +sell_value_max = 9 +sell_value_curve = 0.9 diff --git a/fish/species/crayfish_red_swamp/crayfish_red_swamp.tres b/fish/species/crayfish_red_swamp/crayfish_red_swamp.tres new file mode 100644 index 0000000..cd9d6a0 --- /dev/null +++ b/fish/species/crayfish_red_swamp/crayfish_red_swamp.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"crayfish_red_swamp" +display_name = "red swamp crayfish" +active = false +catalog_number = 2601 +collection_group = &"Freshwater crustaceans" +logbook_fact = "red swamp crayfish burrow into wet ground when water recedes. adaptable populations feed on plants, carrion, and small aquatic animals." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 1.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.25 +sell_value_min = 3 +sell_value_max = 7 +sell_value_curve = 0.98 diff --git a/fish/species/croaker_atlantic/croaker_atlantic.tres b/fish/species/croaker_atlantic/croaker_atlantic.tres new file mode 100644 index 0000000..2f4df70 --- /dev/null +++ b/fish/species/croaker_atlantic/croaker_atlantic.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"croaker_atlantic" +display_name = "atlantic croaker" +active = false +catalog_number = 6801 +collection_group = &"Temperate estuary" +logbook_fact = "atlantic croakers vibrate muscles against the swim bladder to make a croaking sound. chin barbels locate worms in sandy bays." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 5.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.92 diff --git a/fish/species/croaker_yellowfin/croaker_yellowfin.tres b/fish/species/croaker_yellowfin/croaker_yellowfin.tres new file mode 100644 index 0000000..ed5410d --- /dev/null +++ b/fish/species/croaker_yellowfin/croaker_yellowfin.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"croaker_yellowfin" +display_name = "yellowfin croaker" +active = false +catalog_number = 6807 +collection_group = &"Temperate estuary" +logbook_fact = "yellowfin croakers search sandy surf zones with a small chin barbel. internal muscles produce croaks and drumming calls." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 4.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.92 diff --git a/fish/species/cusk/cusk.tres b/fish/species/cusk/cusk.tres new file mode 100644 index 0000000..c43b1b7 --- /dev/null +++ b/fish/species/cusk/cusk.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"cusk" +display_name = "cusk" +active = false +catalog_number = 1202 +collection_group = &"Cold reef fish" +logbook_fact = "cusk move slowly across cold rocky bottoms, sheltering in cracks and caves. a single long fin runs around much of the rear body." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 30.0 +sell_value_min = 7 +sell_value_max = 19 +sell_value_curve = 0.85 diff --git a/fish/species/cuttlefish_common/cuttlefish_common.tres b/fish/species/cuttlefish_common/cuttlefish_common.tres new file mode 100644 index 0000000..98ec8a0 --- /dev/null +++ b/fish/species/cuttlefish_common/cuttlefish_common.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"cuttlefish_common" +display_name = "common cuttlefish" +active = false +catalog_number = 1502 +collection_group = &"Cuttlefish and nautiluses" +logbook_fact = "common cuttlefish flash changing colors and patterns through specialized skin cells. an internal cuttlebone controls buoyancy." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.55 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 12.0 +sell_value_min = 7 +sell_value_max = 17 +sell_value_curve = 0.9 diff --git a/fish/species/cuttlefish_flamboyant/cuttlefish_flamboyant.tres b/fish/species/cuttlefish_flamboyant/cuttlefish_flamboyant.tres new file mode 100644 index 0000000..4c19554 --- /dev/null +++ b/fish/species/cuttlefish_flamboyant/cuttlefish_flamboyant.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"cuttlefish_flamboyant" +display_name = "flamboyant cuttlefish" +active = false +catalog_number = 1503 +collection_group = &"Cuttlefish and nautiluses" +logbook_fact = "flamboyant cuttlefish walk along the seafloor on their arms and fins. vivid displays warn predators of potent toxins." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.4 +sell_value_min = 26 +sell_value_max = 58 +sell_value_curve = 0.96 diff --git a/fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres b/fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres new file mode 100644 index 0000000..24260cf --- /dev/null +++ b/fish/species/damselfish_sergeant_major/damselfish_sergeant_major.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"damselfish_sergeant_major" +display_name = "sergeant major" +active = false +catalog_number = 5904 +collection_group = &"Reef schooling fish" +logbook_fact = "sergeant majors guard purple egg patches on hard reef surfaces. five dark bars cross their yellow and silver bodies." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.02 +weight_max_lb = 0.5 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 0.98 diff --git a/fish/species/danio_zebra/danio_zebra.tres b/fish/species/danio_zebra/danio_zebra.tres new file mode 100644 index 0000000..72567d8 --- /dev/null +++ b/fish/species/danio_zebra/danio_zebra.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"danio_zebra" +display_name = "zebrafish" +active = false +catalog_number = 2103 +collection_group = &"Freshwater aquarium fish" +logbook_fact = "zebrafish carry horizontal blue stripes from gill to tail. hardy schools thrive in shallow streams, flooded fields, and quiet pools." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 1.35 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.12 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 0.98 diff --git a/fish/species/discus/discus.tres b/fish/species/discus/discus.tres new file mode 100644 index 0000000..4b84820 --- /dev/null +++ b/fish/species/discus/discus.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"discus" +display_name = "discus" +active = false +catalog_number = 7503 +collection_group = &"Tropical freshwater fish" +logbook_fact = "discus move through quiet amazon backwaters with round, flattened bodies. parents feed newly hatched young with nutrient-rich skin mucus." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.12 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.0 +sell_value_min = 24 +sell_value_max = 52 +sell_value_curve = 0.92 diff --git a/fish/species/dolphin_bottlenose/dolphin_bottlenose.tres b/fish/species/dolphin_bottlenose/dolphin_bottlenose.tres new file mode 100644 index 0000000..8d07e73 --- /dev/null +++ b/fish/species/dolphin_bottlenose/dolphin_bottlenose.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"dolphin_bottlenose" +display_name = "common bottlenose dolphin" +active = false +catalog_number = 7702 +collection_group = &"Whales and dolphins" +logbook_fact = "common bottlenose dolphins coordinate with whistles, clicks, and body signals. echolocation reveals prey hidden in dark or cloudy water." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.012 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 330.0 +weight_max_lb = 1400.0 +sell_value_min = 75 +sell_value_max = 180 +sell_value_curve = 0.76 diff --git a/fish/species/dorado_golden/dorado_golden.tres b/fish/species/dorado_golden/dorado_golden.tres new file mode 100644 index 0000000..c5f7abf --- /dev/null +++ b/fish/species/dorado_golden/dorado_golden.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"dorado_golden" +display_name = "golden dorado" +active = false +catalog_number = 7602 +collection_group = &"Tropical freshwater predators" +logbook_fact = "golden dorado chase fish through powerful south american rivers. muscular tails, heavy jaws, and golden scales suit life in fast current." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 55.0 +sell_value_min = 29 +sell_value_max = 66 +sell_value_curve = 0.82 diff --git a/fish/species/dugong/dugong.tres b/fish/species/dugong/dugong.tres new file mode 100644 index 0000000..d6c756d --- /dev/null +++ b/fish/species/dugong/dugong.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"dugong" +display_name = "dugong" +active = false +catalog_number = 501 +collection_group = &"Coastal marine mammals" +logbook_fact = "dugongs graze seagrass meadows with a downturned, bristled snout. they are the only fully marine members of the sirenian family." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.011 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 500.0 +weight_max_lb = 1100.0 +sell_value_min = 72 +sell_value_max = 165 +sell_value_curve = 0.77 diff --git a/fish/species/eel_american/eel_american.tres b/fish/species/eel_american/eel_american.tres new file mode 100644 index 0000000..54a671b --- /dev/null +++ b/fish/species/eel_american/eel_american.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"eel_american" +display_name = "american eel" +active = false +catalog_number = 4001 +collection_group = &"Migratory eels" +logbook_fact = "american eels hatch far out in the sargasso sea before entering freshwater rivers. adults later reverse the journey to spawn once." +allowed_water_types = 3 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 8.0 +sell_value_min = 12 +sell_value_max = 27 +sell_value_curve = 0.88 diff --git a/fish/species/eel_european/eel_european.tres b/fish/species/eel_european/eel_european.tres new file mode 100644 index 0000000..54a0c61 --- /dev/null +++ b/fish/species/eel_european/eel_european.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"eel_european" +display_name = "european eel" +active = false +catalog_number = 4002 +collection_group = &"Migratory eels" +logbook_fact = "european eels cross the atlantic as transparent larvae before entering rivers and lakes. mature silver eels return to sea to reproduce." +allowed_water_types = 3 +rarity = 2 +base_catch_weight = 0.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 10.0 +sell_value_min = 13 +sell_value_max = 30 +sell_value_curve = 0.88 diff --git a/fish/species/electric_eel/electric_eel.tres b/fish/species/electric_eel/electric_eel.tres new file mode 100644 index 0000000..cc63eea --- /dev/null +++ b/fish/species/electric_eel/electric_eel.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"electric_eel" +display_name = "electric eel" +active = false +catalog_number = 2901 +collection_group = &"Freshwater oddities" +logbook_fact = "electric eels navigate and hunt with electrical pulses produced along most of the body. they must surface regularly to breathe air." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 44.0 +sell_value_min = 25 +sell_value_max = 60 +sell_value_curve = 0.82 diff --git a/fish/species/fallfish/fallfish.tres b/fish/species/fallfish/fallfish.tres new file mode 100644 index 0000000..72e3774 --- /dev/null +++ b/fish/species/fallfish/fallfish.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"fallfish" +display_name = "fallfish" +active = false +catalog_number = 2506 +collection_group = &"Freshwater coarse fish" +logbook_fact = "fallfish are large minnows that build broad stone nests in flowing water. other stream fish often reuse the gravel mounds for spawning." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 3.5 +sell_value_min = 5 +sell_value_max = 10 +sell_value_curve = 0.92 diff --git a/fish/species/fangtooth_common/fangtooth_common.tres b/fish/species/fangtooth_common/fangtooth_common.tres new file mode 100644 index 0000000..e53e1ad --- /dev/null +++ b/fish/species/fangtooth_common/fangtooth_common.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"fangtooth_common" +display_name = "common fangtooth" +active = false +catalog_number = 1802 +collection_group = &"Deep-sea oddities" +logbook_fact = "common fangtooths carry outsized teeth that fit into pockets in the roof of the mouth. they migrate upward from deep water after dark." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.5 +sell_value_min = 25 +sell_value_max = 56 +sell_value_curve = 0.96 diff --git a/fish/species/flounder_peacock/flounder_peacock.tres b/fish/species/flounder_peacock/flounder_peacock.tres new file mode 100644 index 0000000..54737a4 --- /dev/null +++ b/fish/species/flounder_peacock/flounder_peacock.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"flounder_peacock" +display_name = "peacock flounder" +active = false +catalog_number = 5401 +collection_group = &"Reef flatfish" +logbook_fact = "peacock flounders change their spots and colors to match tropical sand. both eyes scan above while the flat body remains concealed." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 5.0 +sell_value_min = 11 +sell_value_max = 24 +sell_value_curve = 0.9 diff --git a/fish/species/flounder_summer/flounder_summer.tres b/fish/species/flounder_summer/flounder_summer.tres new file mode 100644 index 0000000..9d5892b --- /dev/null +++ b/fish/species/flounder_summer/flounder_summer.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"flounder_summer" +display_name = "summer flounder" +active = false +catalog_number = 402 +collection_group = &"Coastal flatfish" +logbook_fact = "summer flounder bury themselves in sand with only their eyes exposed. a sudden burst carries them upward to seize passing prey." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 15.0 +sell_value_min = 6 +sell_value_max = 16 +sell_value_curve = 0.88 diff --git a/fish/species/flounder_winter/flounder_winter.tres b/fish/species/flounder_winter/flounder_winter.tres new file mode 100644 index 0000000..6ad7a4b --- /dev/null +++ b/fish/species/flounder_winter/flounder_winter.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"flounder_winter" +display_name = "winter flounder" +active = false +catalog_number = 403 +collection_group = &"Coastal flatfish" +logbook_fact = "winter flounder favor cool bays and estuaries where they rest on muddy bottoms. their eyes sit on the right side of the body." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 8.0 +sell_value_min = 4 +sell_value_max = 9 +sell_value_curve = 0.9 diff --git a/fish/species/flyingfish_tropical/flyingfish_tropical.tres b/fish/species/flyingfish_tropical/flyingfish_tropical.tres new file mode 100644 index 0000000..7f94269 --- /dev/null +++ b/fish/species/flyingfish_tropical/flyingfish_tropical.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"flyingfish_tropical" +display_name = "tropical flyingfish" +active = false +catalog_number = 6603 +collection_group = &"Surface ocean fish" +logbook_fact = "tropical flyingfish accelerate underwater before spreading enlarged fins above the surface. glides help them escape pursuing predators." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.35 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 1.5 +sell_value_min = 10 +sell_value_max = 22 +sell_value_curve = 0.94 diff --git a/fish/species/foureyes_largescale/foureyes_largescale.tres b/fish/species/foureyes_largescale/foureyes_largescale.tres new file mode 100644 index 0000000..23b1b6c --- /dev/null +++ b/fish/species/foureyes_largescale/foureyes_largescale.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"foureyes_largescale" +display_name = "largescale foureyes" +active = false +catalog_number = 202 +collection_group = &"Brackish oddities" +logbook_fact = "largescale foureyes cruise with each eye divided across the waterline. one half watches above while the other scans below." +allowed_water_types = 3 +rarity = 2 +base_catch_weight = 0.32 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 1.0 +sell_value_min = 10 +sell_value_max = 23 +sell_value_curve = 0.95 diff --git a/fish/species/freshwater_drum/freshwater_drum.tres b/fish/species/freshwater_drum/freshwater_drum.tres new file mode 100644 index 0000000..bd1389b --- /dev/null +++ b/fish/species/freshwater_drum/freshwater_drum.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"freshwater_drum" +display_name = "freshwater drum" +active = false +catalog_number = 2902 +collection_group = &"Freshwater oddities" +logbook_fact = "freshwater drum crush snails and mussels with powerful throat teeth. muscles vibrating against the swim bladder produce a low drumming sound." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 35.0 +sell_value_min = 7 +sell_value_max = 20 +sell_value_curve = 0.85 diff --git a/fish/species/gar_alligator/gar_alligator.tres b/fish/species/gar_alligator/gar_alligator.tres new file mode 100644 index 0000000..48c02e2 --- /dev/null +++ b/fish/species/gar_alligator/gar_alligator.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"gar_alligator" +display_name = "alligator gar" +active = false +catalog_number = 2701 +collection_group = &"Freshwater giants" +logbook_fact = "alligator gar combine a broad tooth-filled snout with heavy armor-like scales. a vascular swim bladder lets them breathe air at the surface." +allowed_water_types = 1 +rarity = 4 +base_catch_weight = 0.055 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 300.0 +sell_value_min = 55 +sell_value_max = 125 +sell_value_curve = 0.8 diff --git a/fish/species/gar_longnose/gar_longnose.tres b/fish/species/gar_longnose/gar_longnose.tres new file mode 100644 index 0000000..29a8d68 --- /dev/null +++ b/fish/species/gar_longnose/gar_longnose.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"gar_longnose" +display_name = "longnose gar" +active = false +catalog_number = 103 +collection_group = &"Ancient freshwater fish" +logbook_fact = "longnose gar wait near the surface before snapping sideways at passing fish. their narrow jaws carry rows of fine needle-like teeth." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 35.0 +sell_value_min = 14 +sell_value_max = 34 +sell_value_curve = 0.85 diff --git a/fish/species/gar_spotted/gar_spotted.tres b/fish/species/gar_spotted/gar_spotted.tres new file mode 100644 index 0000000..abb3d16 --- /dev/null +++ b/fish/species/gar_spotted/gar_spotted.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"gar_spotted" +display_name = "spotted gar" +active = false +catalog_number = 106 +collection_group = &"Ancient freshwater fish" +logbook_fact = "spotted gar linger among vegetation where their mottled bodies break up their outline. they rise to gulp air as well as breathing through gills." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 15.0 +sell_value_min = 13 +sell_value_max = 29 +sell_value_curve = 0.88 diff --git a/fish/species/goby_round/goby_round.tres b/fish/species/goby_round/goby_round.tres index 4c8872e..20d76a4 100644 --- a/fish/species/goby_round/goby_round.tres +++ b/fish/species/goby_round/goby_round.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.35 script = ExtResource("1_fish_data") id = &"goby_round" display_name = "round goby" +active = true +catalog_number = 3401 +collection_group = &"Freshwater small fish" +logbook_fact = "round gobies use fused pelvic fins like a suction cup to hold onto rocks. they are small fish with a famously large appetite." allowed_water_types = 1 base_catch_weight = 8.0 catch_profile = ExtResource("2_profile") diff --git a/fish/species/goldeye/goldeye.tres b/fish/species/goldeye/goldeye.tres new file mode 100644 index 0000000..c623dc8 --- /dev/null +++ b/fish/species/goldeye/goldeye.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"goldeye" +display_name = "goldeye" +active = false +catalog_number = 2903 +collection_group = &"Freshwater oddities" +logbook_fact = "goldeye patrol turbid rivers with large light-sensitive eyes and a toothed tongue. they rise toward the surface to hunt insects and small fish." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 6.0 +sell_value_min = 12 +sell_value_max = 25 +sell_value_curve = 0.9 diff --git a/fish/species/goldfish/goldfish.tres b/fish/species/goldfish/goldfish.tres index 153365d..c70b2d3 100644 --- a/fish/species/goldfish/goldfish.tres +++ b/fish/species/goldfish/goldfish.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"goldfish" display_name = "goldfish" +active = true +catalog_number = 3002 +collection_group = &"Freshwater ornamental" +logbook_fact = "goldfish can tolerate a surprisingly wide range of conditions. their wild relatives often live in slow, weedy water." allowed_water_types = 1 base_catch_weight = 0.5 catch_profile = ExtResource("2_profile") diff --git a/fish/species/goldfish_bubbleeye/goldfish_bubbleeye.tres b/fish/species/goldfish_bubbleeye/goldfish_bubbleeye.tres index d6a288a..aa75c08 100644 --- a/fish/species/goldfish_bubbleeye/goldfish_bubbleeye.tres +++ b/fish/species/goldfish_bubbleeye/goldfish_bubbleeye.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"goldfish_bubbleeye" display_name = "bubble-eye goldfish" +active = true +catalog_number = 3001 +collection_group = &"Freshwater ornamental" +logbook_fact = "bubble-eye goldfish have delicate fluid-filled sacs beneath their eyes, making careful handling especially important." allowed_water_types = 1 rarity = 1 base_catch_weight = 0.35 diff --git a/fish/species/gourami_giant/gourami_giant.tres b/fish/species/gourami_giant/gourami_giant.tres new file mode 100644 index 0000000..b7a9095 --- /dev/null +++ b/fish/species/gourami_giant/gourami_giant.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"gourami_giant" +display_name = "giant gourami" +active = false +catalog_number = 7505 +collection_group = &"Tropical freshwater fish" +logbook_fact = "giant gouramis breathe surface air through a specialized labyrinth organ. adults graze plants but also investigate insects and small animals." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 20.0 +sell_value_min = 13 +sell_value_max = 31 +sell_value_curve = 0.86 diff --git a/fish/species/grayling_arctic/grayling_arctic.tres b/fish/species/grayling_arctic/grayling_arctic.tres new file mode 100644 index 0000000..fc1e09d --- /dev/null +++ b/fish/species/grayling_arctic/grayling_arctic.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"grayling_arctic" +display_name = "arctic grayling" +active = false +catalog_number = 1102 +collection_group = &"Cold freshwater salmonids" +logbook_fact = "arctic grayling carry a tall, colorful dorsal fin like a sail. they feed on drifting insects in cold, clear rivers and lakes." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 6.0 +sell_value_min = 6 +sell_value_max = 14 +sell_value_curve = 0.9 diff --git a/fish/species/green_sunfish/green_sunfish.tres b/fish/species/green_sunfish/green_sunfish.tres new file mode 100644 index 0000000..92a6838 --- /dev/null +++ b/fish/species/green_sunfish/green_sunfish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"green_sunfish" +display_name = "green sunfish" +active = false +catalog_number = 3103 +collection_group = &"Freshwater panfish" +logbook_fact = "green sunfish tolerate small, warm, cloudy pools better than many relatives. a large mouth lets them tackle insects, crayfish, and fish." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 3.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.5 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 1.0 diff --git a/fish/species/grouper_atlantic_goliath/grouper_atlantic_goliath.tres b/fish/species/grouper_atlantic_goliath/grouper_atlantic_goliath.tres new file mode 100644 index 0000000..fae0ea5 --- /dev/null +++ b/fish/species/grouper_atlantic_goliath/grouper_atlantic_goliath.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"grouper_atlantic_goliath" +display_name = "Atlantic goliath grouper" +active = false +catalog_number = 5801 +collection_group = &"Reef predators" +logbook_fact = "atlantic goliath groupers occupy reefs, wrecks, and mangrove roots. enormous mouths pull prey inward with a sudden pressure drop." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.05 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 800.0 +sell_value_min = 50 +sell_value_max = 125 +sell_value_curve = 0.78 diff --git a/fish/species/grouper_giant/grouper_giant.tres b/fish/species/grouper_giant/grouper_giant.tres new file mode 100644 index 0000000..bc4ecf1 --- /dev/null +++ b/fish/species/grouper_giant/grouper_giant.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"grouper_giant" +display_name = "giant grouper" +active = false +catalog_number = 5803 +collection_group = &"Reef predators" +logbook_fact = "giant groupers become massive ambush predators around indo-pacific reefs. juveniles carry bold black-and-yellow markings." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.045 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 880.0 +sell_value_min = 52 +sell_value_max = 128 +sell_value_curve = 0.78 diff --git a/fish/species/grouper_gulf/grouper_gulf.tres b/fish/species/grouper_gulf/grouper_gulf.tres index 1e0446e..c3aaffa 100644 --- a/fish/species/grouper_gulf/grouper_gulf.tres +++ b/fish/species/grouper_gulf/grouper_gulf.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"grouper_gulf" display_name = "gulf grouper" +active = true +catalog_number = 5805 +collection_group = &"Reef predators" +logbook_fact = "gulf grouper are ambush predators that use a sudden gulp to pull prey into their cavernous mouths." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.3 diff --git a/fish/species/grouper_nassau/grouper_nassau.tres b/fish/species/grouper_nassau/grouper_nassau.tres new file mode 100644 index 0000000..06bbcdf --- /dev/null +++ b/fish/species/grouper_nassau/grouper_nassau.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"grouper_nassau" +display_name = "Nassau grouper" +active = false +catalog_number = 5806 +collection_group = &"Reef predators" +logbook_fact = "nassau groupers shift their bars and colors with mood and surroundings. adults gather at traditional reef sites to spawn." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.12 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 55.0 +sell_value_min = 25 +sell_value_max = 62 +sell_value_curve = 0.82 diff --git a/fish/species/grouper_red/grouper_red.tres b/fish/species/grouper_red/grouper_red.tres index 5594641..b83fecb 100644 --- a/fish/species/grouper_red/grouper_red.tres +++ b/fish/species/grouper_red/grouper_red.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"grouper_red" display_name = "red grouper" +active = true +catalog_number = 5807 +collection_group = &"Reef predators" +logbook_fact = "red grouper excavate shelters in reef rubble. the hollows they make can become hiding places for many smaller animals." allowed_water_types = 2 rarity = 2 base_catch_weight = 0.2 diff --git a/fish/species/grunt_bluestriped/grunt_bluestriped.tres b/fish/species/grunt_bluestriped/grunt_bluestriped.tres new file mode 100644 index 0000000..ef78e38 --- /dev/null +++ b/fish/species/grunt_bluestriped/grunt_bluestriped.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"grunt_bluestriped" +display_name = "bluestriped grunt" +active = false +catalog_number = 5902 +collection_group = &"Reef schooling fish" +logbook_fact = "bluestriped grunts shelter on reefs by day and spread over seagrass to feed at night. grinding throat teeth produce audible grunts." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 3.0 +sell_value_min = 3 +sell_value_max = 7 +sell_value_curve = 0.94 diff --git a/fish/species/grunt_french/grunt_french.tres b/fish/species/grunt_french/grunt_french.tres new file mode 100644 index 0000000..d695ecd --- /dev/null +++ b/fish/species/grunt_french/grunt_french.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"grunt_french" +display_name = "French grunt" +active = false +catalog_number = 5903 +collection_group = &"Reef schooling fish" +logbook_fact = "french grunts gather in yellow-striped schools around reefs and wrecks. they travel into nearby sand and grass beds after dark." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 3.5 +sell_value_min = 3 +sell_value_max = 7 +sell_value_curve = 0.94 diff --git a/fish/species/gulper_eel/gulper_eel.tres b/fish/species/gulper_eel/gulper_eel.tres new file mode 100644 index 0000000..328a595 --- /dev/null +++ b/fish/species/gulper_eel/gulper_eel.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"gulper_eel" +display_name = "gulper eel" +active = false +catalog_number = 1803 +collection_group = &"Deep-sea oddities" +logbook_fact = "gulper eels unfold enormous mouths to engulf prey in the deep sea. a whip-like tail ends in a small light-producing organ." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.04 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 18.0 +sell_value_min = 44 +sell_value_max = 92 +sell_value_curve = 0.86 diff --git a/fish/species/guppy/guppy.tres b/fish/species/guppy/guppy.tres new file mode 100644 index 0000000..911447f --- /dev/null +++ b/fish/species/guppy/guppy.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"guppy" +display_name = "guppy" +active = false +catalog_number = 2101 +collection_group = &"Freshwater aquarium fish" +logbook_fact = "guppies are livebearers whose young arrive ready to swim. males display bright colors while females often favor larger, less flashy bodies." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 1.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.15 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 0.98 diff --git a/fish/species/haddock/haddock.tres b/fish/species/haddock/haddock.tres new file mode 100644 index 0000000..0f73e63 --- /dev/null +++ b/fish/species/haddock/haddock.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"haddock" +display_name = "haddock" +active = false +catalog_number = 1405 +collection_group = &"Cold shelf fish" +logbook_fact = "haddock are recognized by a dark shoulder mark and black lateral line. schools forage over cold seafloors for small bottom animals." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 20.0 +sell_value_min = 4 +sell_value_max = 11 +sell_value_curve = 0.9 diff --git a/fish/species/hake_pacific/hake_pacific.tres b/fish/species/hake_pacific/hake_pacific.tres new file mode 100644 index 0000000..85b521b --- /dev/null +++ b/fish/species/hake_pacific/hake_pacific.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"hake_pacific" +display_name = "pacific hake" +active = false +catalog_number = 1407 +collection_group = &"Cold shelf fish" +logbook_fact = "pacific hake rise from deep water at night to feed on krill and fish. large schools migrate along the west coast with changing seasons." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 8.0 +sell_value_min = 3 +sell_value_max = 9 +sell_value_curve = 0.92 diff --git a/fish/species/hake_silver/hake_silver.tres b/fish/species/hake_silver/hake_silver.tres new file mode 100644 index 0000000..79d7ea0 --- /dev/null +++ b/fish/species/hake_silver/hake_silver.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"hake_silver" +display_name = "silver hake" +active = false +catalog_number = 1408 +collection_group = &"Cold shelf fish" +logbook_fact = "silver hake hunt fish and crustaceans over sandy atlantic shelves. they move upward after dark and return toward the bottom by day." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 5.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.92 diff --git a/fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres b/fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres new file mode 100644 index 0000000..7362805 --- /dev/null +++ b/fish/species/halfbeak_ballyhoo/halfbeak_ballyhoo.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"halfbeak_ballyhoo" +display_name = "ballyhoo" +active = false +catalog_number = 6601 +collection_group = &"Surface ocean fish" +logbook_fact = "ballyhoo skim warm ocean surfaces with an extended lower jaw. tight schools scatter into quick leaps when predators strike." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 1.0 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 0.98 diff --git a/fish/species/halibut_atlantic/halibut_atlantic.tres b/fish/species/halibut_atlantic/halibut_atlantic.tres new file mode 100644 index 0000000..c8bf167 --- /dev/null +++ b/fish/species/halibut_atlantic/halibut_atlantic.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"halibut_atlantic" +display_name = "atlantic halibut" +active = false +catalog_number = 3701 +collection_group = &"Large flatfish" +logbook_fact = "atlantic halibut are immense right-eyed flatfish of cold northern seas. adults ambush fish and squid from deep rocky or sandy bottoms." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 300.0 +sell_value_min = 32 +sell_value_max = 82 +sell_value_curve = 0.8 diff --git a/fish/species/halibut_pacific/halibut_pacific.tres b/fish/species/halibut_pacific/halibut_pacific.tres new file mode 100644 index 0000000..9980aef --- /dev/null +++ b/fish/species/halibut_pacific/halibut_pacific.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"halibut_pacific" +display_name = "pacific halibut" +active = false +catalog_number = 3702 +collection_group = &"Large flatfish" +logbook_fact = "pacific halibut lie camouflaged on cold continental shelves. powerful flat bodies can travel long distances between feeding and spawning grounds." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 350.0 +sell_value_min = 34 +sell_value_max = 86 +sell_value_curve = 0.8 diff --git a/fish/species/hellbender/hellbender.tres b/fish/species/hellbender/hellbender.tres new file mode 100644 index 0000000..8a9d228 --- /dev/null +++ b/fish/species/hellbender/hellbender.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"hellbender" +display_name = "hellbender" +active = false +catalog_number = 2002 +collection_group = &"Freshwater amphibians" +logbook_fact = "hellbenders hide beneath large rocks in cool, clean rivers. folds of loose skin absorb oxygen while the flattened body grips the current." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 5.5 +sell_value_min = 24 +sell_value_max = 54 +sell_value_curve = 0.91 diff --git a/fish/species/herring_atlantic/herring_atlantic.tres b/fish/species/herring_atlantic/herring_atlantic.tres new file mode 100644 index 0000000..00f1e36 --- /dev/null +++ b/fish/species/herring_atlantic/herring_atlantic.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"herring_atlantic" +display_name = "atlantic herring" +active = false +catalog_number = 1301 +collection_group = &"Cold schooling fish" +logbook_fact = "atlantic herring gather in immense schools that move between feeding and spawning grounds. they filter plankton with fine gill rakers." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 3.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.5 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 0.98 diff --git a/fish/species/herring_pacific/herring_pacific.tres b/fish/species/herring_pacific/herring_pacific.tres new file mode 100644 index 0000000..27dcff7 --- /dev/null +++ b/fish/species/herring_pacific/herring_pacific.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"herring_pacific" +display_name = "pacific herring" +active = false +catalog_number = 1304 +collection_group = &"Cold schooling fish" +logbook_fact = "pacific herring return to coastal shallows to coat plants and rocks with eggs. their oily schools support entire marine food webs." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.5 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 0.98 diff --git a/fish/species/hogfish/hogfish.tres b/fish/species/hogfish/hogfish.tres new file mode 100644 index 0000000..8d7dd29 --- /dev/null +++ b/fish/species/hogfish/hogfish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"hogfish" +display_name = "hogfish" +active = false +catalog_number = 6103 +collection_group = &"Reef wrasses" +logbook_fact = "hogfish root through sand with long pig-like snouts in search of shellfish. adults can change from female to male as social groups shift." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 20.0 +sell_value_min = 12 +sell_value_max = 30 +sell_value_curve = 0.86 diff --git a/fish/species/ide/ide.tres b/fish/species/ide/ide.tres new file mode 100644 index 0000000..dcc9980 --- /dev/null +++ b/fish/species/ide/ide.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"ide" +display_name = "ide" +active = false +catalog_number = 2507 +collection_group = &"Freshwater coarse fish" +logbook_fact = "ide move between rivers, lakes, and brackish coastal water in loose schools. adults eat insects, crustaceans, plants, and small fish." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 9.0 +sell_value_min = 6 +sell_value_max = 14 +sell_value_curve = 0.9 diff --git a/fish/species/jack_crevalle/jack_crevalle.tres b/fish/species/jack_crevalle/jack_crevalle.tres new file mode 100644 index 0000000..e20485e --- /dev/null +++ b/fish/species/jack_crevalle/jack_crevalle.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"jack_crevalle" +display_name = "crevalle jack" +active = false +catalog_number = 705 +collection_group = &"Coastal pelagic fish" +logbook_fact = "crevalle jacks patrol warm coasts in fast-moving schools. their deep bodies and forked tails deliver long, powerful runs." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 35.0 +sell_value_min = 7 +sell_value_max = 20 +sell_value_curve = 0.84 diff --git a/fish/species/jelly_crystal/jelly_crystal.tres b/fish/species/jelly_crystal/jelly_crystal.tres new file mode 100644 index 0000000..fe2f34e --- /dev/null +++ b/fish/species/jelly_crystal/jelly_crystal.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"jelly_crystal" +display_name = "crystal jelly" +active = false +catalog_number = 3501 +collection_group = &"Jellies" +logbook_fact = "crystal jellies are nearly transparent except for glowing points around the bell. their green fluorescent protein transformed biological research." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 4.0 +sell_value_min = 15 +sell_value_max = 34 +sell_value_curve = 0.93 diff --git a/fish/species/jelly_lions_mane/jelly_lions_mane.tres b/fish/species/jelly_lions_mane/jelly_lions_mane.tres new file mode 100644 index 0000000..cedb3c8 --- /dev/null +++ b/fish/species/jelly_lions_mane/jelly_lions_mane.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"jelly_lions_mane" +display_name = "lion's mane jelly" +active = false +catalog_number = 3502 +collection_group = &"Jellies" +logbook_fact = "lion's mane jellies trail dense curtains of tentacles through cold seas. the bell darkens from yellow to deep red as the animal grows." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 200.0 +sell_value_min = 24 +sell_value_max = 58 +sell_value_curve = 0.82 diff --git a/fish/species/jelly_moon/jelly_moon.tres b/fish/species/jelly_moon/jelly_moon.tres new file mode 100644 index 0000000..ef7cbe1 --- /dev/null +++ b/fish/species/jelly_moon/jelly_moon.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"jelly_moon" +display_name = "moon jelly" +active = false +catalog_number = 3503 +collection_group = &"Jellies" +logbook_fact = "moon jellies pulse gently with four horseshoe-shaped organs visible through the bell. short tentacles sweep plankton toward the mouth." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 5.0 +sell_value_min = 6 +sell_value_max = 14 +sell_value_curve = 0.94 diff --git a/fish/species/jelly_pacific_sea_nettle/jelly_pacific_sea_nettle.tres b/fish/species/jelly_pacific_sea_nettle/jelly_pacific_sea_nettle.tres new file mode 100644 index 0000000..e3697b3 --- /dev/null +++ b/fish/species/jelly_pacific_sea_nettle/jelly_pacific_sea_nettle.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"jelly_pacific_sea_nettle" +display_name = "Pacific sea nettle" +active = false +catalog_number = 3504 +collection_group = &"Jellies" +logbook_fact = "pacific sea nettles carry rust-colored stripes across pale bells. long tentacles and frilled mouth arms capture plankton and small fish." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 12.0 +sell_value_min = 14 +sell_value_max = 32 +sell_value_curve = 0.9 diff --git a/fish/species/jelly_sea_wasp/jelly_sea_wasp.tres b/fish/species/jelly_sea_wasp/jelly_sea_wasp.tres new file mode 100644 index 0000000..64c2288 --- /dev/null +++ b/fish/species/jelly_sea_wasp/jelly_sea_wasp.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"jelly_sea_wasp" +display_name = "sea wasp" +active = false +catalog_number = 3505 +collection_group = &"Jellies" +logbook_fact = "sea wasps have box-shaped bells and clusters of eyes along the lower edge. their long tentacles deliver exceptionally potent venom." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.035 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 6.0 +sell_value_min = 42 +sell_value_max = 90 +sell_value_curve = 0.9 diff --git a/fish/species/knifefish_clown/knifefish_clown.tres b/fish/species/knifefish_clown/knifefish_clown.tres new file mode 100644 index 0000000..23781ad --- /dev/null +++ b/fish/species/knifefish_clown/knifefish_clown.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"knifefish_clown" +display_name = "clown knifefish" +active = false +catalog_number = 7501 +collection_group = &"Tropical freshwater fish" +logbook_fact = "clown knifefish undulate one long belly fin to glide forward or backward. dark eye-like spots line their silver nocturnal bodies." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 25.0 +sell_value_min = 16 +sell_value_max = 38 +sell_value_curve = 0.86 diff --git a/fish/species/koi/koi.tres b/fish/species/koi/koi.tres new file mode 100644 index 0000000..7c110cc --- /dev/null +++ b/fish/species/koi/koi.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"koi" +display_name = "koi" +active = false +catalog_number = 3003 +collection_group = &"Freshwater ornamental" +logbook_fact = "koi are colorful domesticated forms of common carp selected over many generations. each fish carries a distinctive arrangement of patches and scales." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 35.0 +sell_value_min = 14 +sell_value_max = 34 +sell_value_curve = 0.87 diff --git a/fish/species/krill_antarctic/krill_antarctic.tres b/fish/species/krill_antarctic/krill_antarctic.tres new file mode 100644 index 0000000..390487d --- /dev/null +++ b/fish/species/krill_antarctic/krill_antarctic.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"krill_antarctic" +display_name = "Antarctic krill" +active = false +catalog_number = 3802 +collection_group = &"Lobsters and shrimp" +logbook_fact = "antarctic krill form enormous swarms beneath southern seas and ice. these small crustaceans feed whales, seals, penguins, and fish." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.001 +weight_max_lb = 0.01 +sell_value_min = 10 +sell_value_max = 22 +sell_value_curve = 0.99 diff --git a/fish/species/ladyfish/ladyfish.tres b/fish/species/ladyfish/ladyfish.tres new file mode 100644 index 0000000..1cc8e7b --- /dev/null +++ b/fish/species/ladyfish/ladyfish.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"ladyfish" +display_name = "ladyfish" +active = false +catalog_number = 7403 +collection_group = &"Tropical flats fish" +logbook_fact = "ladyfish race through warm shallows after schooling prey and often leap when pursued. a deeply forked tail powers their slender bodies." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 8.0 +sell_value_min = 4 +sell_value_max = 10 +sell_value_curve = 0.9 diff --git a/fish/species/lanternfish_spotted/lanternfish_spotted.tres b/fish/species/lanternfish_spotted/lanternfish_spotted.tres new file mode 100644 index 0000000..8d47916 --- /dev/null +++ b/fish/species/lanternfish_spotted/lanternfish_spotted.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"lanternfish_spotted" +display_name = "spotted lanternfish" +active = false +catalog_number = 1806 +collection_group = &"Deep-sea oddities" +logbook_fact = "spotted lanternfish carry rows of light-producing organs along their bodies. immense schools rise toward the surface each night." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.5 +sell_value_min = 14 +sell_value_max = 30 +sell_value_curve = 0.95 diff --git a/fish/species/largemouth_bass/largemouth_bass.tres b/fish/species/largemouth_bass/largemouth_bass.tres new file mode 100644 index 0000000..e32cd20 --- /dev/null +++ b/fish/species/largemouth_bass/largemouth_bass.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"largemouth_bass" +display_name = "largemouth bass" +active = false +catalog_number = 2201 +collection_group = &"Freshwater bass" +logbook_fact = "largemouth bass wait among weeds and timber before lunging at prey. the upper jaw extends beyond the eye when the mouth is closed." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 18.0 +sell_value_min = 4 +sell_value_max = 12 +sell_value_curve = 0.9 diff --git a/fish/species/lingcod/lingcod.tres b/fish/species/lingcod/lingcod.tres new file mode 100644 index 0000000..34c8c0b --- /dev/null +++ b/fish/species/lingcod/lingcod.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"lingcod" +display_name = "lingcod" +active = false +catalog_number = 7001 +collection_group = &"Temperate reef predators" +logbook_fact = "lingcod guard rocky dens and ambush fish with a huge tooth-lined mouth. despite the name, they are greenling relatives rather than cod." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 60.0 +sell_value_min = 14 +sell_value_max = 38 +sell_value_curve = 0.83 diff --git a/fish/species/lionfish_red/lionfish_red.tres b/fish/species/lionfish_red/lionfish_red.tres new file mode 100644 index 0000000..9b46e72 --- /dev/null +++ b/fish/species/lionfish_red/lionfish_red.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"lionfish_red" +display_name = "red lionfish" +active = false +catalog_number = 5201 +collection_group = &"Reef ambush fish" +logbook_fact = "red lionfish spread ornate venomous spines when threatened. broad fins herd smaller fish into corners before a rapid gulp." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 3.0 +sell_value_min = 11 +sell_value_max = 25 +sell_value_curve = 0.92 diff --git a/fish/species/loach_clown/loach_clown.tres b/fish/species/loach_clown/loach_clown.tres new file mode 100644 index 0000000..afe5549 --- /dev/null +++ b/fish/species/loach_clown/loach_clown.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"loach_clown" +display_name = "clown loach" +active = false +catalog_number = 7502 +collection_group = &"Tropical freshwater fish" +logbook_fact = "clown loaches rest in groups and make clicking sounds while feeding. orange bodies carry three broad black bands and small eye spines." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.32 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 2.0 +sell_value_min = 10 +sell_value_max = 22 +sell_value_curve = 0.9 diff --git a/fish/species/loach_weather/loach_weather.tres b/fish/species/loach_weather/loach_weather.tres new file mode 100644 index 0000000..8b23ea1 --- /dev/null +++ b/fish/species/loach_weather/loach_weather.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"loach_weather" +display_name = "weather loach" +active = false +catalog_number = 2905 +collection_group = &"Freshwater oddities" +logbook_fact = "weather loaches gulp air and pass it through the intestine when oxygen runs low. restless activity often increases as air pressure falls." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.85 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.7 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.95 diff --git a/fish/species/lobster_american/lobster_american.tres b/fish/species/lobster_american/lobster_american.tres new file mode 100644 index 0000000..92bdb7f --- /dev/null +++ b/fish/species/lobster_american/lobster_american.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"lobster_american" +display_name = "American lobster" +active = false +catalog_number = 3801 +collection_group = &"Lobsters and shrimp" +logbook_fact = "american lobsters shelter in rocky crevices and emerge to forage along the bottom. one claw crushes while the other cuts and grips." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 44.0 +sell_value_min = 16 +sell_value_max = 42 +sell_value_curve = 0.86 diff --git a/fish/species/lobster_caribbean_spiny/lobster_caribbean_spiny.tres b/fish/species/lobster_caribbean_spiny/lobster_caribbean_spiny.tres new file mode 100644 index 0000000..94672a9 --- /dev/null +++ b/fish/species/lobster_caribbean_spiny/lobster_caribbean_spiny.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"lobster_caribbean_spiny" +display_name = "Caribbean spiny lobster" +active = false +catalog_number = 3803 +collection_group = &"Lobsters and shrimp" +logbook_fact = "caribbean spiny lobsters rely on long antennae and a thorny shell instead of large claws. groups march together across the seafloor." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 26.0 +sell_value_min = 15 +sell_value_max = 38 +sell_value_curve = 0.87 diff --git a/fish/species/lungfish_west_african/lungfish_west_african.tres b/fish/species/lungfish_west_african/lungfish_west_african.tres new file mode 100644 index 0000000..4baad67 --- /dev/null +++ b/fish/species/lungfish_west_african/lungfish_west_african.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"lungfish_west_african" +display_name = "West African lungfish" +active = false +catalog_number = 107 +collection_group = &"Ancient freshwater fish" +logbook_fact = "west african lungfish breathe air with paired lungs. when floodplains dry, they seal themselves in a mud cocoon until water returns." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 10.0 +sell_value_min = 12 +sell_value_max = 28 +sell_value_curve = 0.86 diff --git a/fish/species/mackerel_atlantic/mackerel_atlantic.tres b/fish/species/mackerel_atlantic/mackerel_atlantic.tres index 514ef07..edf6d38 100644 --- a/fish/species/mackerel_atlantic/mackerel_atlantic.tres +++ b/fish/species/mackerel_atlantic/mackerel_atlantic.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"mackerel_atlantic" display_name = "atlantic mackerel" +active = true +catalog_number = 701 +collection_group = &"Coastal pelagic fish" +logbook_fact = "atlantic mackerel travel in fast schools across the north atlantic and feed on plankton and small schooling fish." allowed_water_types = 2 base_catch_weight = 0.5 catch_profile = ExtResource("2_profile") diff --git a/fish/species/mackerel_cero/mackerel_cero.tres b/fish/species/mackerel_cero/mackerel_cero.tres index fbbb612..6dc9040 100644 --- a/fish/species/mackerel_cero/mackerel_cero.tres +++ b/fish/species/mackerel_cero/mackerel_cero.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"mackerel_cero" display_name = "cero mackerel" +active = true +catalog_number = 703 +collection_group = &"Coastal pelagic fish" +logbook_fact = "cero mackerel are streamlined coastal hunters. sharp teeth help them slash through schools of smaller fish." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.35 diff --git a/fish/species/mackerel_chub/mackerel_chub.tres b/fish/species/mackerel_chub/mackerel_chub.tres index f4ceca1..189904e 100644 --- a/fish/species/mackerel_chub/mackerel_chub.tres +++ b/fish/species/mackerel_chub/mackerel_chub.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"mackerel_chub" display_name = "chub mackerel" +active = true +catalog_number = 704 +collection_group = &"Coastal pelagic fish" +logbook_fact = "chub mackerel school near the surface and follow plankton blooms. their dark wavy bars help identify them at a glance." allowed_water_types = 2 base_catch_weight = 0.5 catch_profile = ExtResource("2_profile") diff --git a/fish/species/mackerel_king/mackerel_king.tres b/fish/species/mackerel_king/mackerel_king.tres index 2c28071..4e0a047 100644 --- a/fish/species/mackerel_king/mackerel_king.tres +++ b/fish/species/mackerel_king/mackerel_king.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"mackerel_king" display_name = "king mackerel" +active = true +catalog_number = 706 +collection_group = &"Coastal pelagic fish" +logbook_fact = "king mackerel are swift coastal predators. their pointed teeth and long body are built for sudden bursts of speed." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.3 diff --git a/fish/species/mackerel_spanish/mackerel_spanish.tres b/fish/species/mackerel_spanish/mackerel_spanish.tres index 0319917..0aee136 100644 --- a/fish/species/mackerel_spanish/mackerel_spanish.tres +++ b/fish/species/mackerel_spanish/mackerel_spanish.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"mackerel_spanish" display_name = "spanish mackerel" +active = true +catalog_number = 707 +collection_group = &"Coastal pelagic fish" +logbook_fact = "spanish mackerel hunt in warm coastal waters and often travel in loose schools while chasing baitfish." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.35 diff --git a/fish/species/madtom_tadpole/madtom_tadpole.tres b/fish/species/madtom_tadpole/madtom_tadpole.tres new file mode 100644 index 0000000..d43c1c5 --- /dev/null +++ b/fish/species/madtom_tadpole/madtom_tadpole.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"madtom_tadpole" +display_name = "tadpole madtom" +active = false +catalog_number = 2406 +collection_group = &"Freshwater catfish" +logbook_fact = "tadpole madtoms are tiny nocturnal catfish that shelter beneath vegetation and debris. venomous fin spines discourage larger predators." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.35 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.02 +weight_max_lb = 0.15 +sell_value_min = 8 +sell_value_max = 16 +sell_value_curve = 0.95 diff --git a/fish/species/mahi_mahi/mahi_mahi.tres b/fish/species/mahi_mahi/mahi_mahi.tres new file mode 100644 index 0000000..ded9958 --- /dev/null +++ b/fish/species/mahi_mahi/mahi_mahi.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"mahi_mahi" +display_name = "mahi-mahi" +active = false +catalog_number = 4701 +collection_group = &"Oceanic pelagic fish" +logbook_fact = "mahi-mahi flash brilliant blue, green, and gold while chasing prey near the surface. they grow quickly and gather around floating objects." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 50.0 +sell_value_min = 15 +sell_value_max = 38 +sell_value_curve = 0.84 diff --git a/fish/species/mahseer_golden/mahseer_golden.tres b/fish/species/mahseer_golden/mahseer_golden.tres new file mode 100644 index 0000000..4a51858 --- /dev/null +++ b/fish/species/mahseer_golden/mahseer_golden.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"mahseer_golden" +display_name = "golden mahseer" +active = false +catalog_number = 7603 +collection_group = &"Tropical freshwater predators" +logbook_fact = "golden mahseer migrate through swift mountain rivers to reach spawning grounds. large scales shine bronze beneath reddish fins." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 8.0 +weight_max_lb = 90.0 +sell_value_min = 32 +sell_value_max = 72 +sell_value_curve = 0.82 diff --git a/fish/species/manatee_west_indian/manatee_west_indian.tres b/fish/species/manatee_west_indian/manatee_west_indian.tres new file mode 100644 index 0000000..65094d0 --- /dev/null +++ b/fish/species/manatee_west_indian/manatee_west_indian.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"manatee_west_indian" +display_name = "West Indian manatee" +active = false +catalog_number = 505 +collection_group = &"Coastal marine mammals" +logbook_fact = "west indian manatees graze aquatic plants in warm coastal water and rivers. they surface regularly to breathe through nostrils atop the snout." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.012 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 440.0 +weight_max_lb = 1300.0 +sell_value_min = 70 +sell_value_max = 160 +sell_value_curve = 0.77 diff --git a/fish/species/manta_ray_giant/manta_ray_giant.tres b/fish/species/manta_ray_giant/manta_ray_giant.tres new file mode 100644 index 0000000..29eeede --- /dev/null +++ b/fish/species/manta_ray_giant/manta_ray_giant.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"manta_ray_giant" +display_name = "giant manta ray" +active = false +catalog_number = 5102 +collection_group = &"Rays and skates" +logbook_fact = "giant manta rays funnel plankton into wide mouths with two flexible head fins. each belly carries a unique pattern of dark spots." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.025 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 100.0 +weight_max_lb = 1000.0 +sell_value_min = 65 +sell_value_max = 150 +sell_value_curve = 0.76 diff --git a/fish/species/marlin_black/marlin_black.tres b/fish/species/marlin_black/marlin_black.tres index 39110c5..93f0ba2 100644 --- a/fish/species/marlin_black/marlin_black.tres +++ b/fish/species/marlin_black/marlin_black.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"marlin_black" display_name = "black marlin" +active = true +catalog_number = 4601 +collection_group = &"Oceanic billfish" +logbook_fact = "black marlin are powerful billfish that spend much of their lives in warm open ocean and can make blistering runs." allowed_water_types = 2 rarity = 3 base_catch_weight = 0.08 diff --git a/fish/species/marlin_blue/marlin_blue.tres b/fish/species/marlin_blue/marlin_blue.tres index b3328ba..51cff14 100644 --- a/fish/species/marlin_blue/marlin_blue.tres +++ b/fish/species/marlin_blue/marlin_blue.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"marlin_blue" display_name = "blue marlin" +active = true +catalog_number = 4602 +collection_group = &"Oceanic billfish" +logbook_fact = "blue marlin are highly migratory open-ocean hunters. their long bill helps them stun prey before turning back to feed." allowed_water_types = 2 rarity = 4 base_catch_weight = 0.05 diff --git a/fish/species/marlin_white/marlin_white.tres b/fish/species/marlin_white/marlin_white.tres index e90651c..bbbf34f 100644 --- a/fish/species/marlin_white/marlin_white.tres +++ b/fish/species/marlin_white/marlin_white.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"marlin_white" display_name = "white marlin" +active = true +catalog_number = 4605 +collection_group = &"Oceanic billfish" +logbook_fact = "white marlin favor warm offshore water and use their rounded dorsal fin and bill to weave through schools of prey." allowed_water_types = 2 rarity = 3 base_catch_weight = 0.1 diff --git a/fish/species/menhaden_atlantic/menhaden_atlantic.tres b/fish/species/menhaden_atlantic/menhaden_atlantic.tres new file mode 100644 index 0000000..ced8488 --- /dev/null +++ b/fish/species/menhaden_atlantic/menhaden_atlantic.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"menhaden_atlantic" +display_name = "atlantic menhaden" +active = false +catalog_number = 802 +collection_group = &"Coastal schooling fish" +logbook_fact = "atlantic menhaden strain plankton from coastal water with fine gill rakers. dense schools feed whales, seabirds, and predatory fish." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 3.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.5 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 0.98 diff --git a/fish/species/milkfish/milkfish.tres b/fish/species/milkfish/milkfish.tres new file mode 100644 index 0000000..08d5627 --- /dev/null +++ b/fish/species/milkfish/milkfish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"milkfish" +display_name = "milkfish" +active = false +catalog_number = 7101 +collection_group = &"Tropical coastal fish" +logbook_fact = "milkfish graze algae and tiny organisms in warm coastal shallows. deeply forked tails drive large schools through lagoons and estuaries." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 25.0 +sell_value_min = 4 +sell_value_max = 12 +sell_value_curve = 0.88 diff --git a/fish/species/minnow_fathead/minnow_fathead.tres b/fish/species/minnow_fathead/minnow_fathead.tres new file mode 100644 index 0000000..be28197 --- /dev/null +++ b/fish/species/minnow_fathead/minnow_fathead.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"minnow_fathead" +display_name = "fathead minnow" +active = false +catalog_number = 2804 +collection_group = &"Freshwater minnows" +logbook_fact = "fathead minnows tolerate warm, muddy ponds where many fish struggle. males guard eggs laid beneath rocks, logs, and other cover." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 4.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.02 +weight_max_lb = 0.2 +sell_value_min = 1 +sell_value_max = 2 +sell_value_curve = 1.0 diff --git a/fish/species/monkfish/monkfish.tres b/fish/species/monkfish/monkfish.tres new file mode 100644 index 0000000..a4374e1 --- /dev/null +++ b/fish/species/monkfish/monkfish.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"monkfish" +display_name = "monkfish" +active = false +catalog_number = 603 +collection_group = &"Coastal oddities" +logbook_fact = "monkfish hide against the bottom and wave a fleshy lure above a cavernous mouth. a sudden gulp draws fish and crustaceans inward." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 55.0 +sell_value_min = 14 +sell_value_max = 35 +sell_value_curve = 0.84 diff --git a/fish/species/mooneye/mooneye.tres b/fish/species/mooneye/mooneye.tres new file mode 100644 index 0000000..e663618 --- /dev/null +++ b/fish/species/mooneye/mooneye.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"mooneye" +display_name = "mooneye" +active = false +catalog_number = 2904 +collection_group = &"Freshwater oddities" +logbook_fact = "mooneyes flash silver in clear rivers and lakes while feeding near the surface. their large eyes suit evening hunts for insects and small fish." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.85 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 4.0 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.92 diff --git a/fish/species/moray_green/moray_green.tres b/fish/species/moray_green/moray_green.tres new file mode 100644 index 0000000..36aee31 --- /dev/null +++ b/fish/species/moray_green/moray_green.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"moray_green" +display_name = "green moray" +active = false +catalog_number = 5301 +collection_group = &"Reef eels" +logbook_fact = "green morays appear green because yellow mucus coats blue-gray skin. a second set of jaws pulls captured prey deeper into the throat." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 30.0 +sell_value_min = 13 +sell_value_max = 32 +sell_value_curve = 0.86 diff --git a/fish/species/mudskipper_atlantic/mudskipper_atlantic.tres b/fish/species/mudskipper_atlantic/mudskipper_atlantic.tres new file mode 100644 index 0000000..f548d93 --- /dev/null +++ b/fish/species/mudskipper_atlantic/mudskipper_atlantic.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"mudskipper_atlantic" +display_name = "atlantic mudskipper" +active = false +catalog_number = 201 +collection_group = &"Brackish oddities" +logbook_fact = "atlantic mudskippers use sturdy fins to hop and crawl across exposed mud. moist skin and mouth linings let them breathe out of water." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 1.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.5 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.95 diff --git a/fish/species/mullet_red/mullet_red.tres b/fish/species/mullet_red/mullet_red.tres new file mode 100644 index 0000000..3b2a878 --- /dev/null +++ b/fish/species/mullet_red/mullet_red.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"mullet_red" +display_name = "red mullet" +active = false +catalog_number = 301 +collection_group = &"Coastal bottom fish" +logbook_fact = "red mullet comb sandy bottoms with two chin barbels that taste hidden prey. their foraging leaves winding trails in the sediment." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 3.0 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.92 diff --git a/fish/species/mullet_striped/mullet_striped.tres b/fish/species/mullet_striped/mullet_striped.tres new file mode 100644 index 0000000..7d75d6b --- /dev/null +++ b/fish/species/mullet_striped/mullet_striped.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"mullet_striped" +display_name = "striped mullet" +active = false +catalog_number = 6804 +collection_group = &"Temperate estuary" +logbook_fact = "striped mullet leap clear of the water and travel in coastal schools. they graze algae and detritus with specialized digestive systems." +allowed_water_types = 3 +rarity = 0 +base_catch_weight = 2.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 8.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.92 diff --git a/fish/species/muskellunge/muskellunge.tres b/fish/species/muskellunge/muskellunge.tres new file mode 100644 index 0000000..84c8513 --- /dev/null +++ b/fish/species/muskellunge/muskellunge.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"muskellunge" +display_name = "muskellunge" +active = false +catalog_number = 2704 +collection_group = &"Freshwater giants" +logbook_fact = "muskellunge wait motionless among weeds before exploding toward prey. pale bodies carry bars or spots that fade into broken underwater light." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 8.0 +weight_max_lb = 60.0 +sell_value_min = 32 +sell_value_max = 72 +sell_value_curve = 0.82 diff --git a/fish/species/nautilus_chambered/nautilus_chambered.tres b/fish/species/nautilus_chambered/nautilus_chambered.tres new file mode 100644 index 0000000..fd6189c --- /dev/null +++ b/fish/species/nautilus_chambered/nautilus_chambered.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"nautilus_chambered" +display_name = "chambered nautilus" +active = false +catalog_number = 1501 +collection_group = &"Cuttlefish and nautiluses" +logbook_fact = "chambered nautiluses regulate buoyancy with gas-filled shell chambers. dozens of small tentacles feel for food in dark reef slopes." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.05 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 3.0 +sell_value_min = 34 +sell_value_max = 76 +sell_value_curve = 0.92 diff --git a/fish/species/needlefish_hound/needlefish_hound.tres b/fish/species/needlefish_hound/needlefish_hound.tres new file mode 100644 index 0000000..ecb8226 --- /dev/null +++ b/fish/species/needlefish_hound/needlefish_hound.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"needlefish_hound" +display_name = "houndfish" +active = false +catalog_number = 6602 +collection_group = &"Surface ocean fish" +logbook_fact = "houndfish hunt near the surface with long jaws filled with fine teeth. powerful leaps can carry their narrow bodies far above the water." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 10.0 +sell_value_min = 11 +sell_value_max = 25 +sell_value_curve = 0.88 diff --git a/fish/species/oarfish_giant/oarfish_giant.tres b/fish/species/oarfish_giant/oarfish_giant.tres new file mode 100644 index 0000000..f34adcc --- /dev/null +++ b/fish/species/oarfish_giant/oarfish_giant.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"oarfish_giant" +display_name = "giant oarfish" +active = false +catalog_number = 1701 +collection_group = &"Deep-sea legends" +logbook_fact = "giant oarfish drift vertically through deep open water, rowing with a crimson dorsal fin. their ribbon-like bodies can reach remarkable lengths." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.035 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 600.0 +sell_value_min = 60 +sell_value_max = 140 +sell_value_curve = 0.78 diff --git a/fish/species/ocean_perch_pacific/ocean_perch_pacific.tres b/fish/species/ocean_perch_pacific/ocean_perch_pacific.tres new file mode 100644 index 0000000..a7f9873 --- /dev/null +++ b/fish/species/ocean_perch_pacific/ocean_perch_pacific.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"ocean_perch_pacific" +display_name = "Pacific ocean perch" +active = false +catalog_number = 1603 +collection_group = &"Deep shelf fish" +logbook_fact = "pacific ocean perch hover around deep rocky reefs in the north pacific. these slow-growing fish give birth to live young." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 12.0 +sell_value_min = 7 +sell_value_max = 17 +sell_value_curve = 0.88 diff --git a/fish/species/octopus_common/octopus_common.tres b/fish/species/octopus_common/octopus_common.tres new file mode 100644 index 0000000..b86950e --- /dev/null +++ b/fish/species/octopus_common/octopus_common.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"octopus_common" +display_name = "common octopus" +active = false +catalog_number = 4901 +collection_group = &"Octopuses" +logbook_fact = "common octopuses squeeze through narrow cracks and solve problems while hunting reefs. skin cells rapidly change color and texture." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.65 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 22.0 +sell_value_min = 7 +sell_value_max = 18 +sell_value_curve = 0.87 diff --git a/fish/species/octopus_day/octopus_day.tres b/fish/species/octopus_day/octopus_day.tres new file mode 100644 index 0000000..aada4c1 --- /dev/null +++ b/fish/species/octopus_day/octopus_day.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"octopus_day" +display_name = "day octopus" +active = false +catalog_number = 4902 +collection_group = &"Octopuses" +logbook_fact = "day octopuses forage across tropical reefs while the sun is high. shifting bars and spots communicate alarm, camouflage, and threat." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 15.0 +sell_value_min = 15 +sell_value_max = 36 +sell_value_curve = 0.88 diff --git a/fish/species/octopus_giant_pacific/octopus_giant_pacific.tres b/fish/species/octopus_giant_pacific/octopus_giant_pacific.tres new file mode 100644 index 0000000..09aca6d --- /dev/null +++ b/fish/species/octopus_giant_pacific/octopus_giant_pacific.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"octopus_giant_pacific" +display_name = "giant Pacific octopus" +active = false +catalog_number = 4903 +collection_group = &"Octopuses" +logbook_fact = "giant pacific octopuses explore cold rocky dens with thousands of arm suckers. females guard their eggs without feeding until they hatch." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 200.0 +sell_value_min = 28 +sell_value_max = 70 +sell_value_curve = 0.81 diff --git a/fish/species/octopus_greater_blue_ringed/octopus_greater_blue_ringed.tres b/fish/species/octopus_greater_blue_ringed/octopus_greater_blue_ringed.tres new file mode 100644 index 0000000..a59bd06 --- /dev/null +++ b/fish/species/octopus_greater_blue_ringed/octopus_greater_blue_ringed.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"octopus_greater_blue_ringed" +display_name = "greater blue-ringed octopus" +active = false +catalog_number = 4904 +collection_group = &"Octopuses" +logbook_fact = "greater blue-ringed octopuses reveal vivid rings when threatened. a tiny beak can deliver venom strong enough to stop breathing." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.04 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.3 +sell_value_min = 38 +sell_value_max = 82 +sell_value_curve = 0.96 diff --git a/fish/species/opah/opah.tres b/fish/species/opah/opah.tres new file mode 100644 index 0000000..2bb4231 --- /dev/null +++ b/fish/species/opah/opah.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"opah" +display_name = "opah" +active = false +catalog_number = 4702 +collection_group = &"Oceanic pelagic fish" +logbook_fact = "opah circulate warm blood through much of the body, an unusual trait among fish. round silver flanks glow with red fins and pale spots." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 600.0 +sell_value_min = 36 +sell_value_max = 88 +sell_value_curve = 0.8 diff --git a/fish/species/oscar/oscar.tres b/fish/species/oscar/oscar.tres new file mode 100644 index 0000000..d3da024 --- /dev/null +++ b/fish/species/oscar/oscar.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"oscar" +display_name = "oscar" +active = false +catalog_number = 7507 +collection_group = &"Tropical freshwater fish" +logbook_fact = "oscars are large south american cichlids that recognize routines and defend territories. a tail eyespot may redirect attacks away from the head." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 4.0 +sell_value_min = 6 +sell_value_max = 14 +sell_value_curve = 0.9 diff --git a/fish/species/oyster_black_lip_pearl/oyster_black_lip_pearl.tres b/fish/species/oyster_black_lip_pearl/oyster_black_lip_pearl.tres new file mode 100644 index 0000000..cc95d74 --- /dev/null +++ b/fish/species/oyster_black_lip_pearl/oyster_black_lip_pearl.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"oyster_black_lip_pearl" +display_name = "black-lip pearl oyster" +active = false +catalog_number = 6401 +collection_group = &"Shellfish" +logbook_fact = "black-lip pearl oysters line their shells with dark-edged nacre. a trapped irritant can become coated in the same pearly material." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.22 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 5.0 +sell_value_min = 14 +sell_value_max = 40 +sell_value_curve = 0.91 diff --git a/fish/species/paddlefish/paddlefish.tres b/fish/species/paddlefish/paddlefish.tres new file mode 100644 index 0000000..c9d4925 --- /dev/null +++ b/fish/species/paddlefish/paddlefish.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"paddlefish" +display_name = "paddlefish" +active = false +catalog_number = 104 +collection_group = &"Ancient freshwater fish" +logbook_fact = "paddlefish cruise open rivers with their mouths wide, filtering tiny plankton from the water. the broad snout senses faint electrical signals." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 120.0 +sell_value_min = 34 +sell_value_max = 76 +sell_value_curve = 0.82 diff --git a/fish/species/parrotfish_rainbow/parrotfish_rainbow.tres b/fish/species/parrotfish_rainbow/parrotfish_rainbow.tres new file mode 100644 index 0000000..3320b61 --- /dev/null +++ b/fish/species/parrotfish_rainbow/parrotfish_rainbow.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"parrotfish_rainbow" +display_name = "rainbow parrotfish" +active = false +catalog_number = 5502 +collection_group = &"Reef grazers" +logbook_fact = "rainbow parrotfish scrape algae from reefs with fused beak-like teeth. crushed coral passes through them and returns to the reef as sand." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.12 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 40.0 +sell_value_min = 25 +sell_value_max = 58 +sell_value_curve = 0.84 diff --git a/fish/species/parrotfish_stoplight/parrotfish_stoplight.tres b/fish/species/parrotfish_stoplight/parrotfish_stoplight.tres new file mode 100644 index 0000000..2392e88 --- /dev/null +++ b/fish/species/parrotfish_stoplight/parrotfish_stoplight.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"parrotfish_stoplight" +display_name = "stoplight parrotfish" +active = false +catalog_number = 5503 +collection_group = &"Reef grazers" +logbook_fact = "stoplight parrotfish change color dramatically as they mature. powerful dental plates bite algae from hard reef surfaces." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 15.0 +sell_value_min = 12 +sell_value_max = 29 +sell_value_curve = 0.88 diff --git a/fish/species/peacock_bass/peacock_bass.tres b/fish/species/peacock_bass/peacock_bass.tres new file mode 100644 index 0000000..39487fe --- /dev/null +++ b/fish/species/peacock_bass/peacock_bass.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"peacock_bass" +display_name = "peacock bass" +active = false +catalog_number = 7604 +collection_group = &"Tropical freshwater predators" +logbook_fact = "peacock bass are aggressive south american cichlids rather than true bass. adults guard nests and hunt fish in warm clear water." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 25.0 +sell_value_min = 15 +sell_value_max = 36 +sell_value_curve = 0.84 diff --git a/fish/species/permit/permit.tres b/fish/species/permit/permit.tres new file mode 100644 index 0000000..bd36724 --- /dev/null +++ b/fish/species/permit/permit.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"permit" +display_name = "permit" +active = false +catalog_number = 7404 +collection_group = &"Tropical flats fish" +logbook_fact = "permit browse tropical flats for crabs and shellfish with rubbery mouths. tall silver bodies carry long sickle-shaped dorsal and anal fins." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 3.0 +weight_max_lb = 50.0 +sell_value_min = 27 +sell_value_max = 62 +sell_value_curve = 0.84 diff --git a/fish/species/pickerel_chain/pickerel_chain.tres b/fish/species/pickerel_chain/pickerel_chain.tres new file mode 100644 index 0000000..f69ab5f --- /dev/null +++ b/fish/species/pickerel_chain/pickerel_chain.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"pickerel_chain" +display_name = "chain pickerel" +active = false +catalog_number = 3301 +collection_group = &"Freshwater predators" +logbook_fact = "chain pickerel hover among vegetation before darting at fish and frogs. a chain-like pattern breaks up their green flanks." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 9.0 +sell_value_min = 6 +sell_value_max = 15 +sell_value_curve = 0.9 diff --git a/fish/species/pickerel_grass/pickerel_grass.tres b/fish/species/pickerel_grass/pickerel_grass.tres new file mode 100644 index 0000000..ef59b58 --- /dev/null +++ b/fish/species/pickerel_grass/pickerel_grass.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"pickerel_grass" +display_name = "grass pickerel" +active = false +catalog_number = 3302 +collection_group = &"Freshwater predators" +logbook_fact = "grass pickerel remain small enough to hunt in narrow, weedy creeks and marshes. dark bars camouflage their slim bodies among stems." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.5 +sell_value_min = 2 +sell_value_max = 6 +sell_value_curve = 0.95 diff --git a/fish/species/pike_northern/pike_northern.tres b/fish/species/pike_northern/pike_northern.tres new file mode 100644 index 0000000..eb4dc5d --- /dev/null +++ b/fish/species/pike_northern/pike_northern.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"pike_northern" +display_name = "northern pike" +active = false +catalog_number = 3303 +collection_group = &"Freshwater predators" +logbook_fact = "northern pike are solitary ambush hunters built for sudden acceleration. pale bean-shaped spots cover their long green bodies." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 30.0 +sell_value_min = 8 +sell_value_max = 24 +sell_value_curve = 0.85 diff --git a/fish/species/pipefish_bay/pipefish_bay.tres b/fish/species/pipefish_bay/pipefish_bay.tres new file mode 100644 index 0000000..f5582c4 --- /dev/null +++ b/fish/species/pipefish_bay/pipefish_bay.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"pipefish_bay" +display_name = "bay pipefish" +active = false +catalog_number = 601 +collection_group = &"Coastal oddities" +logbook_fact = "bay pipefish hide upright among seagrass, matching the blades with their slender bodies. males carry developing eggs in a brood pouch." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.005 +weight_max_lb = 0.04 +sell_value_min = 4 +sell_value_max = 9 +sell_value_curve = 1.0 diff --git a/fish/species/piranha_red_bellied/piranha_red_bellied.tres b/fish/species/piranha_red_bellied/piranha_red_bellied.tres new file mode 100644 index 0000000..efca0b8 --- /dev/null +++ b/fish/species/piranha_red_bellied/piranha_red_bellied.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"piranha_red_bellied" +display_name = "red-bellied piranha" +active = false +catalog_number = 7605 +collection_group = &"Tropical freshwater predators" +logbook_fact = "red-bellied piranhas communicate with barks and drum-like sounds. schools provide protection as they search for fish, insects, and carrion." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 4.0 +sell_value_min = 12 +sell_value_max = 27 +sell_value_curve = 0.9 diff --git a/fish/species/plaice_european/plaice_european.tres b/fish/species/plaice_european/plaice_european.tres new file mode 100644 index 0000000..9e9be9a --- /dev/null +++ b/fish/species/plaice_european/plaice_european.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"plaice_european" +display_name = "European plaice" +active = false +catalog_number = 401 +collection_group = &"Coastal flatfish" +logbook_fact = "european plaice lie flat on the seafloor with both eyes on the same side. orange spots help distinguish them from neighboring flatfish." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 10.0 +sell_value_min = 4 +sell_value_max = 10 +sell_value_curve = 0.9 diff --git a/fish/species/pollock_alaska/pollock_alaska.tres b/fish/species/pollock_alaska/pollock_alaska.tres new file mode 100644 index 0000000..0e4af05 --- /dev/null +++ b/fish/species/pollock_alaska/pollock_alaska.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"pollock_alaska" +display_name = "Alaska pollock" +active = false +catalog_number = 1401 +collection_group = &"Cold shelf fish" +logbook_fact = "alaska pollock form vast schools over cold northern shelves. they feed on plankton and fish while supporting many larger predators." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 8.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.92 diff --git a/fish/species/pollock_atlantic/pollock_atlantic.tres b/fish/species/pollock_atlantic/pollock_atlantic.tres new file mode 100644 index 0000000..7d50342 --- /dev/null +++ b/fish/species/pollock_atlantic/pollock_atlantic.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"pollock_atlantic" +display_name = "Atlantic pollock" +active = false +catalog_number = 1403 +collection_group = &"Cold shelf fish" +logbook_fact = "atlantic pollock are swift relatives of cod with dark backs and pale lateral lines. adults gather around rocky banks and wrecks." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 35.0 +sell_value_min = 7 +sell_value_max = 18 +sell_value_curve = 0.86 diff --git a/fish/species/pomfret_black/pomfret_black.tres b/fish/species/pomfret_black/pomfret_black.tres index 8020100..d3e73d6 100644 --- a/fish/species/pomfret_black/pomfret_black.tres +++ b/fish/species/pomfret_black/pomfret_black.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"pomfret_black" display_name = "black pomfret" +active = true +catalog_number = 803 +collection_group = &"Coastal schooling fish" +logbook_fact = "black pomfret have a deep, laterally compressed body that lets them maneuver neatly through open water." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.3 diff --git a/fish/species/pomfret_chinese/pomfret_chinese.tres b/fish/species/pomfret_chinese/pomfret_chinese.tres index 2eaf34b..76d6943 100644 --- a/fish/species/pomfret_chinese/pomfret_chinese.tres +++ b/fish/species/pomfret_chinese/pomfret_chinese.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"pomfret_chinese" display_name = "chinese pomfret" +active = true +catalog_number = 804 +collection_group = &"Coastal schooling fish" +logbook_fact = "chinese pomfret are silvery schooling fish with a tall, flattened body and long sickle-shaped fins." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.3 diff --git a/fish/species/pomfret_golden/pomfret_golden.tres b/fish/species/pomfret_golden/pomfret_golden.tres index da1f561..8637db1 100644 --- a/fish/species/pomfret_golden/pomfret_golden.tres +++ b/fish/species/pomfret_golden/pomfret_golden.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"pomfret_golden" display_name = "golden pomfret" +active = true +catalog_number = 807 +collection_group = &"Coastal schooling fish" +logbook_fact = "golden pomfret are warm-water swimmers whose golden sheen is most noticeable along the fins and flanks." allowed_water_types = 2 rarity = 2 base_catch_weight = 0.18 diff --git a/fish/species/pomfret_white/pomfret_white.tres b/fish/species/pomfret_white/pomfret_white.tres index fa317c7..7152086 100644 --- a/fish/species/pomfret_white/pomfret_white.tres +++ b/fish/species/pomfret_white/pomfret_white.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"pomfret_white" display_name = "white pomfret" +active = true +catalog_number = 810 +collection_group = &"Coastal schooling fish" +logbook_fact = "white pomfret gather over coastal grounds and use their compact, deep bodies to turn quickly while feeding." allowed_water_types = 2 base_catch_weight = 0.45 catch_profile = ExtResource("2_profile") diff --git a/fish/species/pompano_african/pompano_african.tres b/fish/species/pompano_african/pompano_african.tres new file mode 100644 index 0000000..0b73340 --- /dev/null +++ b/fish/species/pompano_african/pompano_african.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"pompano_african" +display_name = "African pompano" +active = false +catalog_number = 3601 +collection_group = &"Large coastal predators" +logbook_fact = "african pompano develop long streaming fin rays while young. adults become deep-bodied silver hunters around reefs and offshore structure." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.26 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 50.0 +sell_value_min = 14 +sell_value_max = 35 +sell_value_curve = 0.84 diff --git a/fish/species/pompano_florida/pompano_florida.tres b/fish/species/pompano_florida/pompano_florida.tres new file mode 100644 index 0000000..be7a10e --- /dev/null +++ b/fish/species/pompano_florida/pompano_florida.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"pompano_florida" +display_name = "Florida pompano" +active = false +catalog_number = 7402 +collection_group = &"Tropical flats fish" +logbook_fact = "florida pompano search warm surf for crabs, clams, and sand fleas. short blunt snouts lead into deep, silvery bodies." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.85 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 9.0 +sell_value_min = 7 +sell_value_max = 16 +sell_value_curve = 0.88 diff --git a/fish/species/porcupinefish_spotted/porcupinefish_spotted.tres b/fish/species/porcupinefish_spotted/porcupinefish_spotted.tres new file mode 100644 index 0000000..1e51582 --- /dev/null +++ b/fish/species/porcupinefish_spotted/porcupinefish_spotted.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"porcupinefish_spotted" +display_name = "spotted porcupinefish" +active = false +catalog_number = 5607 +collection_group = &"Reef oddities" +logbook_fact = "spotted porcupinefish inflate until long spines stand outward. fused teeth form a strong beak for crushing shelled prey." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.12 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 7.0 +sell_value_min = 24 +sell_value_max = 54 +sell_value_curve = 0.9 diff --git a/fish/species/porgy_scup/porgy_scup.tres b/fish/species/porgy_scup/porgy_scup.tres new file mode 100644 index 0000000..dec3c55 --- /dev/null +++ b/fish/species/porgy_scup/porgy_scup.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"porgy_scup" +display_name = "scup" +active = false +catalog_number = 6904 +collection_group = &"Temperate reef fish" +logbook_fact = "scup use small strong teeth to crush shellfish and other bottom prey. deep silver bodies gather around piers, reefs, and eelgrass." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 2.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 5.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.92 diff --git a/fish/species/pufferfish_guineafowl/pufferfish_guineafowl.tres b/fish/species/pufferfish_guineafowl/pufferfish_guineafowl.tres new file mode 100644 index 0000000..3771896 --- /dev/null +++ b/fish/species/pufferfish_guineafowl/pufferfish_guineafowl.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"pufferfish_guineafowl" +display_name = "guineafowl puffer" +active = false +catalog_number = 5603 +collection_group = &"Reef oddities" +logbook_fact = "guineafowl puffers swallow water to inflate when threatened. adults may shift from a spotted dark pattern to a vivid yellow form." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 5.0 +sell_value_min = 11 +sell_value_max = 26 +sell_value_curve = 0.9 diff --git a/fish/species/pumpkinseed/pumpkinseed.tres b/fish/species/pumpkinseed/pumpkinseed.tres new file mode 100644 index 0000000..5a24e96 --- /dev/null +++ b/fish/species/pumpkinseed/pumpkinseed.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"pumpkinseed" +display_name = "pumpkinseed" +active = false +catalog_number = 3104 +collection_group = &"Freshwater panfish" +logbook_fact = "pumpkinseeds use compact teeth to crush snails and other hard prey. blue facial lines and an orange gill spot brighten their round bodies." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 3.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 1.3 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 1.0 diff --git a/fish/species/queenfish_talang/queenfish_talang.tres b/fish/species/queenfish_talang/queenfish_talang.tres new file mode 100644 index 0000000..6b3f91c --- /dev/null +++ b/fish/species/queenfish_talang/queenfish_talang.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"queenfish_talang" +display_name = "talang queenfish" +active = false +catalog_number = 708 +collection_group = &"Coastal pelagic fish" +logbook_fact = "talang queenfish chase schooling prey near warm coasts and estuaries. hooked teeth and a deeply forked tail suit their rapid attacks." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 20.0 +sell_value_min = 7 +sell_value_max = 18 +sell_value_curve = 0.86 diff --git a/fish/species/quillback/quillback.tres b/fish/species/quillback/quillback.tres new file mode 100644 index 0000000..915fc08 --- /dev/null +++ b/fish/species/quillback/quillback.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"quillback" +display_name = "quillback" +active = false +catalog_number = 2303 +collection_group = &"Freshwater bottom fish" +logbook_fact = "quillbacks carry a long first dorsal ray that rises like a quill. small mouths gather insect larvae and other food from river and lake bottoms." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 15.0 +sell_value_min = 6 +sell_value_max = 16 +sell_value_curve = 0.88 diff --git a/fish/species/red_drum/red_drum.tres b/fish/species/red_drum/red_drum.tres new file mode 100644 index 0000000..98c6bdd --- /dev/null +++ b/fish/species/red_drum/red_drum.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"red_drum" +display_name = "red drum" +active = false +catalog_number = 6803 +collection_group = &"Temperate estuary" +logbook_fact = "red drum use powerful tails to patrol surf and estuaries. one or more black spots near the tail may confuse predators about which end is the head." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 45.0 +sell_value_min = 8 +sell_value_max = 23 +sell_value_curve = 0.84 diff --git a/fish/species/redear_sunfish/redear_sunfish.tres b/fish/species/redear_sunfish/redear_sunfish.tres new file mode 100644 index 0000000..55221d8 --- /dev/null +++ b/fish/species/redear_sunfish/redear_sunfish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"redear_sunfish" +display_name = "redear sunfish" +active = false +catalog_number = 3105 +collection_group = &"Freshwater panfish" +logbook_fact = "redear sunfish specialize in snails, crushing shells with strong throat teeth. a red or orange margin marks the dark flap behind the gill." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 1.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 3.0 +sell_value_min = 4 +sell_value_max = 9 +sell_value_curve = 0.95 diff --git a/fish/species/redhorse_golden/redhorse_golden.tres b/fish/species/redhorse_golden/redhorse_golden.tres new file mode 100644 index 0000000..15296d5 --- /dev/null +++ b/fish/species/redhorse_golden/redhorse_golden.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"redhorse_golden" +display_name = "golden redhorse" +active = false +catalog_number = 2302 +collection_group = &"Freshwater bottom fish" +logbook_fact = "golden redhorses probe flowing river bottoms with fleshy, downward-facing mouths. coppery scales and reddish fins give them a warm glow." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 8.0 +sell_value_min = 6 +sell_value_max = 14 +sell_value_curve = 0.9 diff --git a/fish/species/roach_common/roach_common.tres b/fish/species/roach_common/roach_common.tres new file mode 100644 index 0000000..a85b58f --- /dev/null +++ b/fish/species/roach_common/roach_common.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"roach_common" +display_name = "common roach" +active = false +catalog_number = 2504 +collection_group = &"Freshwater coarse fish" +logbook_fact = "common roach thrive in many lakes and rivers, often schooling near vegetation. red fins and a red-orange eye brighten their silver bodies." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 4.0 +sell_value_min = 2 +sell_value_max = 6 +sell_value_curve = 0.95 diff --git a/fish/species/rockfish_black/rockfish_black.tres b/fish/species/rockfish_black/rockfish_black.tres new file mode 100644 index 0000000..4bbcd5a --- /dev/null +++ b/fish/species/rockfish_black/rockfish_black.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"rockfish_black" +display_name = "black rockfish" +active = false +catalog_number = 6901 +collection_group = &"Temperate reef fish" +logbook_fact = "black rockfish gather above reefs and kelp along the north pacific coast. like other rockfish, they give birth to live young." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 10.0 +sell_value_min = 4 +sell_value_max = 11 +sell_value_curve = 0.9 diff --git a/fish/species/rockfish_canary/rockfish_canary.tres b/fish/species/rockfish_canary/rockfish_canary.tres new file mode 100644 index 0000000..2aeb290 --- /dev/null +++ b/fish/species/rockfish_canary/rockfish_canary.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"rockfish_canary" +display_name = "canary rockfish" +active = false +catalog_number = 6903 +collection_group = &"Temperate reef fish" +logbook_fact = "canary rockfish glow orange-yellow over deep pacific reefs. these long-lived fish mature slowly and give birth to live young." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 12.0 +sell_value_min = 7 +sell_value_max = 17 +sell_value_curve = 0.88 diff --git a/fish/species/rockfish_yelloweye/rockfish_yelloweye.tres b/fish/species/rockfish_yelloweye/rockfish_yelloweye.tres new file mode 100644 index 0000000..d06d1d3 --- /dev/null +++ b/fish/species/rockfish_yelloweye/rockfish_yelloweye.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"rockfish_yelloweye" +display_name = "yelloweye rockfish" +active = false +catalog_number = 6907 +collection_group = &"Temperate reef fish" +logbook_fact = "yelloweye rockfish can live for more than a century on deep pacific reefs. bright golden eyes stand out against orange-red bodies." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 35.0 +sell_value_min = 28 +sell_value_max = 64 +sell_value_curve = 0.84 diff --git a/fish/species/roosterfish/roosterfish.tres b/fish/species/roosterfish/roosterfish.tres new file mode 100644 index 0000000..fdb432a --- /dev/null +++ b/fish/species/roosterfish/roosterfish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"roosterfish" +display_name = "roosterfish" +active = false +catalog_number = 7201 +collection_group = &"Tropical coastal predators" +logbook_fact = "roosterfish raise seven long dorsal spines while chasing prey through tropical surf. dark bands cross their sleek silver bodies." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 100.0 +sell_value_min = 30 +sell_value_max = 70 +sell_value_curve = 0.82 diff --git a/fish/species/rudd/rudd.tres b/fish/species/rudd/rudd.tres new file mode 100644 index 0000000..317fa8c --- /dev/null +++ b/fish/species/rudd/rudd.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"rudd" +display_name = "rudd" +active = false +catalog_number = 2508 +collection_group = &"Freshwater coarse fish" +logbook_fact = "rudd feed near the surface with an upturned mouth and red fins. golden-green bodies disappear easily among warm, weedy shallows." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.4 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 4.5 +sell_value_min = 3 +sell_value_max = 7 +sell_value_curve = 0.95 diff --git a/fish/species/sablefish/sablefish.tres b/fish/species/sablefish/sablefish.tres new file mode 100644 index 0000000..d180b9b --- /dev/null +++ b/fish/species/sablefish/sablefish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sablefish" +display_name = "sablefish" +active = false +catalog_number = 1605 +collection_group = &"Deep shelf fish" +logbook_fact = "sablefish roam deep north pacific slopes and can live for many decades. soft dark bodies conceal rich, oil-filled flesh." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.22 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 40.0 +sell_value_min = 15 +sell_value_max = 36 +sell_value_curve = 0.84 diff --git a/fish/species/sailfish/sailfish.tres b/fish/species/sailfish/sailfish.tres index 7f6bea9..398f62d 100644 --- a/fish/species/sailfish/sailfish.tres +++ b/fish/species/sailfish/sailfish.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"sailfish" display_name = "sailfish" +active = true +catalog_number = 4603 +collection_group = &"Oceanic billfish" +logbook_fact = "sailfish raise their enormous dorsal fin while herding baitfish. they are among the fastest fish in the sea." allowed_water_types = 2 rarity = 3 base_catch_weight = 0.1 diff --git a/fish/species/salmon_atlantic/salmon_atlantic.tres b/fish/species/salmon_atlantic/salmon_atlantic.tres index fa3f0d6..906d86b 100644 --- a/fish/species/salmon_atlantic/salmon_atlantic.tres +++ b/fish/species/salmon_atlantic/salmon_atlantic.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"salmon_atlantic" display_name = "atlantic salmon" +active = true +catalog_number = 4401 +collection_group = &"Ocean-run salmon" +logbook_fact = "atlantic salmon can return from the ocean to the river where they hatched. unlike many salmon, some survive to make the trip again." allowed_water_types = 2 rarity = 3 base_catch_weight = 0.65 diff --git a/fish/species/salmon_chum/salmon_chum.tres b/fish/species/salmon_chum/salmon_chum.tres index 748295b..67d3684 100644 --- a/fish/species/salmon_chum/salmon_chum.tres +++ b/fish/species/salmon_chum/salmon_chum.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"salmon_chum" display_name = "chum salmon" +active = true +catalog_number = 4402 +collection_group = &"Ocean-run salmon" +logbook_fact = "chum salmon travel immense distances and develop striking bars when they return to fresh water to spawn." allowed_water_types = 2 rarity = 1 base_catch_weight = 2.5 diff --git a/fish/species/salmon_coho/salmon_coho.tres b/fish/species/salmon_coho/salmon_coho.tres index 1c1a783..ad51f7b 100644 --- a/fish/species/salmon_coho/salmon_coho.tres +++ b/fish/species/salmon_coho/salmon_coho.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"salmon_coho" display_name = "coho salmon" +active = true +catalog_number = 4403 +collection_group = &"Ocean-run salmon" +logbook_fact = "coho salmon are agile hunters known for powerful leaps. young coho may spend more than a year growing in streams." allowed_water_types = 2 rarity = 2 base_catch_weight = 1.4 diff --git a/fish/species/salmon_pink/salmon_pink.tres b/fish/species/salmon_pink/salmon_pink.tres index d390cf1..7f76c62 100644 --- a/fish/species/salmon_pink/salmon_pink.tres +++ b/fish/species/salmon_pink/salmon_pink.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"salmon_pink" display_name = "pink salmon" +active = true +catalog_number = 4404 +collection_group = &"Ocean-run salmon" +logbook_fact = "pink salmon usually follow a strict two-year life cycle. spawning males develop the hump that inspired their nickname." allowed_water_types = 2 rarity = 1 base_catch_weight = 3.0 diff --git a/fish/species/salmon_sockeye/salmon_sockeye.tres b/fish/species/salmon_sockeye/salmon_sockeye.tres index deeda39..04b75b3 100644 --- a/fish/species/salmon_sockeye/salmon_sockeye.tres +++ b/fish/species/salmon_sockeye/salmon_sockeye.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"salmon_sockeye" display_name = "sockeye salmon" +active = true +catalog_number = 4405 +collection_group = &"Ocean-run salmon" +logbook_fact = "sockeye salmon feed heavily on plankton at sea. adults turn vivid red as they return inland to spawn." allowed_water_types = 2 rarity = 2 base_catch_weight = 1.6 diff --git a/fish/species/sand_dollar/sand_dollar.tres b/fish/species/sand_dollar/sand_dollar.tres new file mode 100644 index 0000000..fd3f73c --- /dev/null +++ b/fish/species/sand_dollar/sand_dollar.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"sand_dollar" +display_name = "sand dollar" +active = false +catalog_number = 1904 +collection_group = &"Echinoderms" +logbook_fact = "sand dollars burrow just beneath sandy seafloors and filter tiny food particles. the familiar star pattern marks their five-part body plan." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.2 +sell_value_min = 4 +sell_value_max = 10 +sell_value_curve = 0.98 diff --git a/fish/species/sand_lance_american/sand_lance_american.tres b/fish/species/sand_lance_american/sand_lance_american.tres new file mode 100644 index 0000000..4f53bbb --- /dev/null +++ b/fish/species/sand_lance_american/sand_lance_american.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sand_lance_american" +display_name = "American sand lance" +active = false +catalog_number = 801 +collection_group = &"Coastal schooling fish" +logbook_fact = "american sand lance dive headfirst into loose sand when danger approaches. immense schools carry energy from plankton to larger predators." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 3.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.02 +weight_max_lb = 0.4 +sell_value_min = 1 +sell_value_max = 3 +sell_value_curve = 1.0 diff --git a/fish/species/sardine_european/sardine_european.tres b/fish/species/sardine_european/sardine_european.tres new file mode 100644 index 0000000..3052323 --- /dev/null +++ b/fish/species/sardine_european/sardine_european.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"sardine_european" +display_name = "European pilchard" +active = false +catalog_number = 806 +collection_group = &"Coastal schooling fish" +logbook_fact = "european pilchards form shimmering schools along atlantic and mediterranean coasts. they feed mainly on plankton near the surface." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 3.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.6 +sell_value_min = 2 +sell_value_max = 4 +sell_value_curve = 1.0 diff --git a/fish/species/sardine_pacific/sardine_pacific.tres b/fish/species/sardine_pacific/sardine_pacific.tres new file mode 100644 index 0000000..3e4db3d --- /dev/null +++ b/fish/species/sardine_pacific/sardine_pacific.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"sardine_pacific" +display_name = "Pacific sardine" +active = false +catalog_number = 809 +collection_group = &"Coastal schooling fish" +logbook_fact = "pacific sardines follow cool, productive water in enormous schools. their movements shift as currents and plankton supplies change." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 3.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.5 +sell_value_min = 2 +sell_value_max = 4 +sell_value_curve = 1.0 diff --git a/fish/species/sauger/sauger.tres b/fish/species/sauger/sauger.tres index 49cf5ad..0918236 100644 --- a/fish/species/sauger/sauger.tres +++ b/fish/species/sauger/sauger.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"sauger" display_name = "sauger" +active = true +catalog_number = 3304 +collection_group = &"Freshwater predators" +logbook_fact = "sauger favor turbid rivers and reservoirs. their mottled backs blend into the dim, shifting light near the bottom." allowed_water_types = 1 rarity = 1 base_catch_weight = 0.35 diff --git a/fish/species/saugeye/saugeye.tres b/fish/species/saugeye/saugeye.tres index e834005..ac82232 100644 --- a/fish/species/saugeye/saugeye.tres +++ b/fish/species/saugeye/saugeye.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"saugeye" display_name = "saugeye" +active = true +catalog_number = 3305 +collection_group = &"Freshwater predators" +logbook_fact = "saugeye are a fertile hybrid of sauger and walleye. they combine traits from both parent species and often grow quickly." allowed_water_types = 1 rarity = 2 base_catch_weight = 0.2 diff --git a/fish/species/sawfish_largetooth/sawfish_largetooth.tres b/fish/species/sawfish_largetooth/sawfish_largetooth.tres new file mode 100644 index 0000000..699c8d6 --- /dev/null +++ b/fish/species/sawfish_largetooth/sawfish_largetooth.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"sawfish_largetooth" +display_name = "largetooth sawfish" +active = false +catalog_number = 5103 +collection_group = &"Rays and skates" +logbook_fact = "largetooth sawfish detect hidden prey with a long sensory rostrum. a swift sideways sweep stuns fish and exposes animals buried in mud." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.035 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 25.0 +weight_max_lb = 1300.0 +sell_value_min = 58 +sell_value_max = 135 +sell_value_curve = 0.78 diff --git a/fish/species/scorpionfish_red/scorpionfish_red.tres b/fish/species/scorpionfish_red/scorpionfish_red.tres new file mode 100644 index 0000000..eda1241 --- /dev/null +++ b/fish/species/scorpionfish_red/scorpionfish_red.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"scorpionfish_red" +display_name = "red scorpionfish" +active = false +catalog_number = 5202 +collection_group = &"Reef ambush fish" +logbook_fact = "red scorpionfish vanish against rocky reefs through mottled color and skin flaps. venomous dorsal spines punish careless predators." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 7.0 +sell_value_min = 11 +sell_value_max = 25 +sell_value_curve = 0.9 diff --git a/fish/species/sea_bass_black/sea_bass_black.tres b/fish/species/sea_bass_black/sea_bass_black.tres new file mode 100644 index 0000000..0f4fca3 --- /dev/null +++ b/fish/species/sea_bass_black/sea_bass_black.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sea_bass_black" +display_name = "black sea bass" +active = false +catalog_number = 6902 +collection_group = &"Temperate reef fish" +logbook_fact = "black sea bass shelter around reefs, piers, and wrecks. many begin life as female and later become male." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 1.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 8.0 +sell_value_min = 4 +sell_value_max = 10 +sell_value_curve = 0.9 diff --git a/fish/species/sea_bass_chilean/sea_bass_chilean.tres b/fish/species/sea_bass_chilean/sea_bass_chilean.tres new file mode 100644 index 0000000..c52a88c --- /dev/null +++ b/fish/species/sea_bass_chilean/sea_bass_chilean.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sea_bass_chilean" +display_name = "Patagonian toothfish" +active = false +catalog_number = 1604 +collection_group = &"Deep shelf fish" +logbook_fact = "patagonian toothfish patrol cold, deep southern water with large canine teeth. natural antifreeze proteins protect their blood from freezing." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 220.0 +sell_value_min = 32 +sell_value_max = 78 +sell_value_curve = 0.8 diff --git a/fish/species/sea_cucumber_giant_california/sea_cucumber_giant_california.tres b/fish/species/sea_cucumber_giant_california/sea_cucumber_giant_california.tres new file mode 100644 index 0000000..5670862 --- /dev/null +++ b/fish/species/sea_cucumber_giant_california/sea_cucumber_giant_california.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"sea_cucumber_giant_california" +display_name = "giant California sea cucumber" +active = false +catalog_number = 1902 +collection_group = &"Echinoderms" +logbook_fact = "giant california sea cucumbers sweep organic particles from the seafloor with branching tentacles. soft tube feet anchor their long bodies." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 5.0 +sell_value_min = 13 +sell_value_max = 29 +sell_value_curve = 0.93 diff --git a/fish/species/sea_otter/sea_otter.tres b/fish/species/sea_otter/sea_otter.tres new file mode 100644 index 0000000..c7740a3 --- /dev/null +++ b/fish/species/sea_otter/sea_otter.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sea_otter" +display_name = "sea otter" +active = false +catalog_number = 503 +collection_group = &"Coastal marine mammals" +logbook_fact = "sea otters float on their backs while opening shellfish with stones. dense fur traps insulating air because they lack a thick blubber layer." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.02 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 30.0 +weight_max_lb = 100.0 +sell_value_min = 55 +sell_value_max = 125 +sell_value_curve = 0.84 diff --git a/fish/species/sea_star_crown_of_thorns/sea_star_crown_of_thorns.tres b/fish/species/sea_star_crown_of_thorns/sea_star_crown_of_thorns.tres new file mode 100644 index 0000000..bfc9493 --- /dev/null +++ b/fish/species/sea_star_crown_of_thorns/sea_star_crown_of_thorns.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"sea_star_crown_of_thorns" +display_name = "crown-of-thorns sea star" +active = false +catalog_number = 1901 +collection_group = &"Echinoderms" +logbook_fact = "crown-of-thorns sea stars crawl over reefs and digest coral tissue outside the body. venomous spines discourage many predators." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 7.0 +sell_value_min = 23 +sell_value_max = 52 +sell_value_curve = 0.91 diff --git a/fish/species/sea_star_sunflower/sea_star_sunflower.tres b/fish/species/sea_star_sunflower/sea_star_sunflower.tres new file mode 100644 index 0000000..7409a31 --- /dev/null +++ b/fish/species/sea_star_sunflower/sea_star_sunflower.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"sea_star_sunflower" +display_name = "sunflower sea star" +active = false +catalog_number = 1905 +collection_group = &"Echinoderms" +logbook_fact = "sunflower sea stars move quickly on thousands of tube feet and may carry many arms. they hunt urchins, clams, and other bottom animals." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 13.0 +sell_value_min = 14 +sell_value_max = 32 +sell_value_curve = 0.89 diff --git a/fish/species/seadragon_leafy/seadragon_leafy.tres b/fish/species/seadragon_leafy/seadragon_leafy.tres new file mode 100644 index 0000000..b137a34 --- /dev/null +++ b/fish/species/seadragon_leafy/seadragon_leafy.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"seadragon_leafy" +display_name = "leafy seadragon" +active = false +catalog_number = 6301 +collection_group = &"Seahorses and seadragons" +logbook_fact = "leafy seadragons drift among seaweed with elaborate skin lobes that resemble leaves. males carry eggs beneath the tail." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 0.5 +sell_value_min = 24 +sell_value_max = 55 +sell_value_curve = 0.94 diff --git a/fish/species/seadragon_weedy/seadragon_weedy.tres b/fish/species/seadragon_weedy/seadragon_weedy.tres new file mode 100644 index 0000000..e388f50 --- /dev/null +++ b/fish/species/seadragon_weedy/seadragon_weedy.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"seadragon_weedy" +display_name = "weedy seadragon" +active = false +catalog_number = 6302 +collection_group = &"Seahorses and seadragons" +logbook_fact = "weedy seadragons rely on slender bodies and small leaf-like lobes for camouflage. males brood rows of eggs on the underside of the tail." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.22 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 0.6 +sell_value_min = 16 +sell_value_max = 38 +sell_value_curve = 0.93 diff --git a/fish/species/seahorse_lined/seahorse_lined.tres b/fish/species/seahorse_lined/seahorse_lined.tres new file mode 100644 index 0000000..42fe3e3 --- /dev/null +++ b/fish/species/seahorse_lined/seahorse_lined.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"seahorse_lined" +display_name = "lined seahorse" +active = false +catalog_number = 602 +collection_group = &"Coastal oddities" +logbook_fact = "lined seahorses anchor to grass and coral with curling tails. the male broods eggs in a pouch and releases fully formed young." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.005 +weight_max_lb = 0.05 +sell_value_min = 9 +sell_value_max = 20 +sell_value_curve = 1.0 diff --git a/fish/species/seal_harbor/seal_harbor.tres b/fish/species/seal_harbor/seal_harbor.tres new file mode 100644 index 0000000..59bdf2b --- /dev/null +++ b/fish/species/seal_harbor/seal_harbor.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"seal_harbor" +display_name = "harbor seal" +active = false +catalog_number = 502 +collection_group = &"Coastal marine mammals" +logbook_fact = "harbor seals rest on rocky shores and sandbars between hunting trips. sensitive whiskers can trace the water trails left by moving fish." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.018 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 120.0 +weight_max_lb = 370.0 +sell_value_min = 60 +sell_value_max = 140 +sell_value_curve = 0.79 diff --git a/fish/species/shark_blacktip/shark_blacktip.tres b/fish/species/shark_blacktip/shark_blacktip.tres new file mode 100644 index 0000000..99bc169 --- /dev/null +++ b/fish/species/shark_blacktip/shark_blacktip.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"shark_blacktip" +display_name = "blacktip shark" +active = false +catalog_number = 901 +collection_group = &"Coastal sharks" +logbook_fact = "blacktip sharks are named for the dark markings on their fins. they hunt schooling fish in warm shallows and sometimes leap clear of the water." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 15.0 +weight_max_lb = 150.0 +sell_value_min = 32 +sell_value_max = 75 +sell_value_curve = 0.8 diff --git a/fish/species/shark_great_hammerhead/shark_great_hammerhead.tres b/fish/species/shark_great_hammerhead/shark_great_hammerhead.tres new file mode 100644 index 0000000..65b9d96 --- /dev/null +++ b/fish/species/shark_great_hammerhead/shark_great_hammerhead.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"shark_great_hammerhead" +display_name = "great hammerhead shark" +active = false +catalog_number = 902 +collection_group = &"Coastal sharks" +logbook_fact = "great hammerheads sweep their wide heads over the bottom while searching for rays. widely spaced senses help pinpoint hidden prey." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.035 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 50.0 +weight_max_lb = 1000.0 +sell_value_min = 62 +sell_value_max = 145 +sell_value_curve = 0.78 diff --git a/fish/species/shark_great_white/shark_great_white.tres b/fish/species/shark_great_white/shark_great_white.tres new file mode 100644 index 0000000..560b596 --- /dev/null +++ b/fish/species/shark_great_white/shark_great_white.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"shark_great_white" +display_name = "great white shark" +active = false +catalog_number = 903 +collection_group = &"Coastal sharks" +logbook_fact = "great white sharks conserve heat in their swimming muscles while crossing cool seas. rows of serrated teeth are replaced throughout life." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.025 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 100.0 +weight_max_lb = 1000.0 +sell_value_min = 70 +sell_value_max = 150 +sell_value_curve = 0.76 diff --git a/fish/species/shark_tiger/shark_tiger.tres b/fish/species/shark_tiger/shark_tiger.tres new file mode 100644 index 0000000..a3f34f7 --- /dev/null +++ b/fish/species/shark_tiger/shark_tiger.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"shark_tiger" +display_name = "tiger shark" +active = false +catalog_number = 904 +collection_group = &"Coastal sharks" +logbook_fact = "tiger sharks carry dark vertical bars that fade as they mature. broad jaws let them investigate an unusually varied range of prey." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.06 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 50.0 +weight_max_lb = 1000.0 +sell_value_min = 50 +sell_value_max = 120 +sell_value_curve = 0.8 diff --git a/fish/species/shark_whale/shark_whale.tres b/fish/species/shark_whale/shark_whale.tres new file mode 100644 index 0000000..b0bbe19 --- /dev/null +++ b/fish/species/shark_whale/shark_whale.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"shark_whale" +display_name = "whale shark" +active = false +catalog_number = 905 +collection_group = &"Coastal sharks" +logbook_fact = "whale sharks are enormous filter feeders covered in unique spot patterns. they cruise with open mouths to collect plankton and tiny fish." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.025 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 500.0 +weight_max_lb = 45000.0 +sell_value_min = 70 +sell_value_max = 160 +sell_value_curve = 0.76 diff --git a/fish/species/sheepshead/sheepshead.tres b/fish/species/sheepshead/sheepshead.tres new file mode 100644 index 0000000..df76f08 --- /dev/null +++ b/fish/species/sheepshead/sheepshead.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sheepshead" +display_name = "sheepshead" +active = false +catalog_number = 6905 +collection_group = &"Temperate reef fish" +logbook_fact = "sheepshead carry flat human-like front teeth for biting barnacles and crabs. dark bars cross their high silver bodies." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 20.0 +sell_value_min = 7 +sell_value_max = 18 +sell_value_curve = 0.86 diff --git a/fish/species/shiner_common/shiner_common.tres b/fish/species/shiner_common/shiner_common.tres new file mode 100644 index 0000000..505bb24 --- /dev/null +++ b/fish/species/shiner_common/shiner_common.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"shiner_common" +display_name = "common shiner" +active = false +catalog_number = 2801 +collection_group = &"Freshwater minnows" +logbook_fact = "common shiners flash silver in clear streams and small rivers. breeding males develop rosy fins and a blue-violet sheen." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 4.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.3 +sell_value_min = 1 +sell_value_max = 3 +sell_value_curve = 1.0 diff --git a/fish/species/shiner_emerald/shiner_emerald.tres b/fish/species/shiner_emerald/shiner_emerald.tres new file mode 100644 index 0000000..7497ae6 --- /dev/null +++ b/fish/species/shiner_emerald/shiner_emerald.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"shiner_emerald" +display_name = "emerald shiner" +active = false +catalog_number = 2803 +collection_group = &"Freshwater minnows" +logbook_fact = "emerald shiners form bright surface schools in large lakes and rivers. slender bodies feed on plankton and drifting insects." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 3.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.25 +sell_value_min = 1 +sell_value_max = 3 +sell_value_curve = 1.0 diff --git a/fish/species/shiner_golden/shiner_golden.tres b/fish/species/shiner_golden/shiner_golden.tres new file mode 100644 index 0000000..755bffb --- /dev/null +++ b/fish/species/shiner_golden/shiner_golden.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"shiner_golden" +display_name = "golden shiner" +active = false +catalog_number = 2806 +collection_group = &"Freshwater minnows" +logbook_fact = "golden shiners school around dense vegetation in warm water. an upturned mouth takes insects and plankton from near the surface." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 3.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 1.0 +sell_value_min = 2 +sell_value_max = 4 +sell_value_curve = 1.0 diff --git a/fish/species/shrimp_peacock_mantis/shrimp_peacock_mantis.tres b/fish/species/shrimp_peacock_mantis/shrimp_peacock_mantis.tres new file mode 100644 index 0000000..4a12b76 --- /dev/null +++ b/fish/species/shrimp_peacock_mantis/shrimp_peacock_mantis.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"shrimp_peacock_mantis" +display_name = "peacock mantis shrimp" +active = false +catalog_number = 3804 +collection_group = &"Lobsters and shrimp" +logbook_fact = "peacock mantis shrimp strike hard-shelled prey with club-like limbs. complex eyes move independently and detect colors beyond human vision." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.4 +sell_value_min = 22 +sell_value_max = 50 +sell_value_curve = 0.96 diff --git a/fish/species/shrimp_skunk_cleaner/shrimp_skunk_cleaner.tres b/fish/species/shrimp_skunk_cleaner/shrimp_skunk_cleaner.tres new file mode 100644 index 0000000..9ab3908 --- /dev/null +++ b/fish/species/shrimp_skunk_cleaner/shrimp_skunk_cleaner.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"shrimp_skunk_cleaner" +display_name = "skunk cleaner shrimp" +active = false +catalog_number = 3805 +collection_group = &"Lobsters and shrimp" +logbook_fact = "skunk cleaner shrimp advertise at reef cleaning stations with bright white antennae. visiting fish pause while parasites are picked away." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.15 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.98 diff --git a/fish/species/skate_barndoor/skate_barndoor.tres b/fish/species/skate_barndoor/skate_barndoor.tres new file mode 100644 index 0000000..013fc2f --- /dev/null +++ b/fish/species/skate_barndoor/skate_barndoor.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"skate_barndoor" +display_name = "barndoor skate" +active = false +catalog_number = 5101 +collection_group = &"Rays and skates" +logbook_fact = "barndoor skates glide over cold atlantic bottoms with broad wing-like fins. rows of thorny spines protect the back and tail." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 150.0 +sell_value_min = 28 +sell_value_max = 68 +sell_value_curve = 0.8 diff --git a/fish/species/smallmouth_bass/smallmouth_bass.tres b/fish/species/smallmouth_bass/smallmouth_bass.tres new file mode 100644 index 0000000..11e876f --- /dev/null +++ b/fish/species/smallmouth_bass/smallmouth_bass.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"smallmouth_bass" +display_name = "smallmouth bass" +active = false +catalog_number = 2202 +collection_group = &"Freshwater bass" +logbook_fact = "smallmouth bass favor clear water with rock and moving current. bronze flanks and dark vertical bars distinguish them from largemouth bass." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 1.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 12.0 +sell_value_min = 6 +sell_value_max = 15 +sell_value_curve = 0.9 diff --git a/fish/species/smelt_eulachon/smelt_eulachon.tres b/fish/species/smelt_eulachon/smelt_eulachon.tres new file mode 100644 index 0000000..b6d7b16 --- /dev/null +++ b/fish/species/smelt_eulachon/smelt_eulachon.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"smelt_eulachon" +display_name = "eulachon" +active = false +catalog_number = 4201 +collection_group = &"Ocean-run forage fish" +logbook_fact = "eulachon return from the pacific to spawn in coastal rivers. their oil-rich bodies once served as candles when dried and fitted with wicks." +allowed_water_types = 3 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.5 +sell_value_min = 4 +sell_value_max = 9 +sell_value_curve = 0.98 diff --git a/fish/species/smelt_rainbow/smelt_rainbow.tres b/fish/species/smelt_rainbow/smelt_rainbow.tres new file mode 100644 index 0000000..19f1b1d --- /dev/null +++ b/fish/species/smelt_rainbow/smelt_rainbow.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"smelt_rainbow" +display_name = "rainbow smelt" +active = false +catalog_number = 4101 +collection_group = &"Migratory forage fish" +logbook_fact = "rainbow smelt move between coastal water and rivers, though some remain landlocked. cucumber-like aromas cling to freshly caught fish." +allowed_water_types = 3 +rarity = 0 +base_catch_weight = 2.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.8 +sell_value_min = 2 +sell_value_max = 5 +sell_value_curve = 1.0 diff --git a/fish/species/snakehead_giant/snakehead_giant.tres b/fish/species/snakehead_giant/snakehead_giant.tres new file mode 100644 index 0000000..44b10e3 --- /dev/null +++ b/fish/species/snakehead_giant/snakehead_giant.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"snakehead_giant" +display_name = "giant snakehead" +active = false +catalog_number = 7601 +collection_group = &"Tropical freshwater predators" +logbook_fact = "giant snakeheads breathe air and guard schools of young near the surface. long bodies launch sudden attacks from flooded vegetation." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 3.0 +weight_max_lb = 45.0 +sell_value_min = 28 +sell_value_max = 64 +sell_value_curve = 0.84 diff --git a/fish/species/snapper_lane/snapper_lane.tres b/fish/species/snapper_lane/snapper_lane.tres index e9e7422..f72ab38 100644 --- a/fish/species/snapper_lane/snapper_lane.tres +++ b/fish/species/snapper_lane/snapper_lane.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"snapper_lane" display_name = "lane snapper" +active = true +catalog_number = 6001 +collection_group = &"Reef snapper" +logbook_fact = "lane snapper use reef structure for cover and hunt small crustaceans and fish when the light begins to fade." allowed_water_types = 2 base_catch_weight = 0.45 catch_profile = ExtResource("2_profile") diff --git a/fish/species/snapper_mangrove/snapper_mangrove.tres b/fish/species/snapper_mangrove/snapper_mangrove.tres index 58226ad..f400800 100644 --- a/fish/species/snapper_mangrove/snapper_mangrove.tres +++ b/fish/species/snapper_mangrove/snapper_mangrove.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"snapper_mangrove" display_name = "mangrove snapper" +active = true +catalog_number = 6002 +collection_group = &"Reef snapper" +logbook_fact = "mangrove snapper shelter among roots and docks when young, then move toward reefs as they grow." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.35 diff --git a/fish/species/snapper_mutton/snapper_mutton.tres b/fish/species/snapper_mutton/snapper_mutton.tres index 6d4441a..37b4b24 100644 --- a/fish/species/snapper_mutton/snapper_mutton.tres +++ b/fish/species/snapper_mutton/snapper_mutton.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"snapper_mutton" display_name = "mutton snapper" +active = true +catalog_number = 6003 +collection_group = &"Reef snapper" +logbook_fact = "mutton snapper have canine teeth that help them pick crabs, shrimp, and other hard-shelled prey from the reef." allowed_water_types = 2 rarity = 2 base_catch_weight = 0.2 diff --git a/fish/species/snapper_red/snapper_red.tres b/fish/species/snapper_red/snapper_red.tres index 8778c98..fabf736 100644 --- a/fish/species/snapper_red/snapper_red.tres +++ b/fish/species/snapper_red/snapper_red.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"snapper_red" display_name = "red snapper" +active = true +catalog_number = 6004 +collection_group = &"Reef snapper" +logbook_fact = "red snapper gather around reefs, wrecks, and ledges. their diet includes small fish, shrimp, and squid." allowed_water_types = 2 rarity = 1 base_catch_weight = 0.35 diff --git a/fish/species/snook_common/snook_common.tres b/fish/species/snook_common/snook_common.tres new file mode 100644 index 0000000..e810953 --- /dev/null +++ b/fish/species/snook_common/snook_common.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"snook_common" +display_name = "common snook" +active = false +catalog_number = 7303 +collection_group = &"Tropical estuary" +logbook_fact = "common snook wait around mangrove roots and current seams to ambush prey. a bold black lateral line divides their silver flanks." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 30.0 +sell_value_min = 13 +sell_value_max = 32 +sell_value_curve = 0.85 diff --git a/fish/species/sole_dover/sole_dover.tres b/fish/species/sole_dover/sole_dover.tres new file mode 100644 index 0000000..88daf9a --- /dev/null +++ b/fish/species/sole_dover/sole_dover.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sole_dover" +display_name = "Dover sole" +active = false +catalog_number = 1404 +collection_group = &"Cold shelf fish" +logbook_fact = "dover sole glide over deep muddy bottoms of the eastern pacific. both eyes migrate to the right side as the young fish becomes flat." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 8.0 +sell_value_min = 6 +sell_value_max = 14 +sell_value_curve = 0.9 diff --git a/fish/species/sprat_european/sprat_european.tres b/fish/species/sprat_european/sprat_european.tres new file mode 100644 index 0000000..20f313c --- /dev/null +++ b/fish/species/sprat_european/sprat_european.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"sprat_european" +display_name = "European sprat" +active = false +catalog_number = 1303 +collection_group = &"Cold schooling fish" +logbook_fact = "european sprat form compact schools in northern coastal seas. these tiny herring relatives feed on plankton near the surface." +allowed_water_types = 2 +rarity = 0 +base_catch_weight = 3.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.03 +weight_max_lb = 0.3 +sell_value_min = 1 +sell_value_max = 3 +sell_value_curve = 1.0 diff --git a/fish/species/squid_bigfin_reef/squid_bigfin_reef.tres b/fish/species/squid_bigfin_reef/squid_bigfin_reef.tres new file mode 100644 index 0000000..838a6ef --- /dev/null +++ b/fish/species/squid_bigfin_reef/squid_bigfin_reef.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"squid_bigfin_reef" +display_name = "bigfin reef squid" +active = false +catalog_number = 6501 +collection_group = &"Squid" +logbook_fact = "bigfin reef squid hover in coordinated groups around warm reefs. shifting colors and body patterns signal to nearby squid." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 5.0 +sell_value_min = 13 +sell_value_max = 30 +sell_value_curve = 0.91 diff --git a/fish/species/squid_colossal/squid_colossal.tres b/fish/species/squid_colossal/squid_colossal.tres new file mode 100644 index 0000000..8ade0a5 --- /dev/null +++ b/fish/species/squid_colossal/squid_colossal.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"squid_colossal" +display_name = "colossal squid" +active = false +catalog_number = 6502 +collection_group = &"Squid" +logbook_fact = "colossal squid carry swiveling hooks on their arms and tentacles. enormous eyes gather faint light in the deep southern ocean." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.015 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 400.0 +weight_max_lb = 1100.0 +sell_value_min = 82 +sell_value_max = 190 +sell_value_curve = 0.76 diff --git a/fish/species/squid_giant/squid_giant.tres b/fish/species/squid_giant/squid_giant.tres new file mode 100644 index 0000000..e67af33 --- /dev/null +++ b/fish/species/squid_giant/squid_giant.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"squid_giant" +display_name = "giant squid" +active = false +catalog_number = 6503 +collection_group = &"Squid" +logbook_fact = "giant squid hunt deep ocean prey with two long feeding tentacles. the largest eyes in the animal world watch for movement in darkness." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.02 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 100.0 +weight_max_lb = 600.0 +sell_value_min = 70 +sell_value_max = 165 +sell_value_curve = 0.77 diff --git a/fish/species/squid_humboldt/squid_humboldt.tres b/fish/species/squid_humboldt/squid_humboldt.tres new file mode 100644 index 0000000..038bcf8 --- /dev/null +++ b/fish/species/squid_humboldt/squid_humboldt.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"squid_humboldt" +display_name = "Humboldt squid" +active = false +catalog_number = 6504 +collection_group = &"Squid" +logbook_fact = "humboldt squid flash red and white while hunting in large groups. powerful beaks and grasping suckers handle fish and other squid." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 5.0 +weight_max_lb = 110.0 +sell_value_min = 26 +sell_value_max = 64 +sell_value_curve = 0.82 diff --git a/fish/species/stingray_ocellate_river/stingray_ocellate_river.tres b/fish/species/stingray_ocellate_river/stingray_ocellate_river.tres new file mode 100644 index 0000000..5ee7b41 --- /dev/null +++ b/fish/species/stingray_ocellate_river/stingray_ocellate_river.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"stingray_ocellate_river" +display_name = "ocellate river stingray" +active = false +catalog_number = 5104 +collection_group = &"Rays and skates" +logbook_fact = "ocellate river stingrays carry bright eye-like spots across a round disc. they hide beneath sand in south american rivers." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 35.0 +sell_value_min = 28 +sell_value_max = 65 +sell_value_curve = 0.82 diff --git a/fish/species/stingray_southern/stingray_southern.tres b/fish/species/stingray_southern/stingray_southern.tres new file mode 100644 index 0000000..ed24d11 --- /dev/null +++ b/fish/species/stingray_southern/stingray_southern.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"stingray_southern" +display_name = "southern stingray" +active = false +catalog_number = 5105 +collection_group = &"Rays and skates" +logbook_fact = "southern stingrays bury in warm sand with only their eyes and spiracles visible. crushing plates break open clams and crustaceans." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.23 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 80.0 +sell_value_min = 15 +sell_value_max = 40 +sell_value_curve = 0.82 diff --git a/fish/species/sturgeon_lake/sturgeon_lake.tres b/fish/species/sturgeon_lake/sturgeon_lake.tres new file mode 100644 index 0000000..b69a743 --- /dev/null +++ b/fish/species/sturgeon_lake/sturgeon_lake.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sturgeon_lake" +display_name = "lake sturgeon" +active = false +catalog_number = 102 +collection_group = &"Ancient freshwater fish" +logbook_fact = "lake sturgeon sweep the bottom with four sensitive barbels before extending a tube-like mouth to feed. they can live for decades." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 180.0 +sell_value_min = 36 +sell_value_max = 82 +sell_value_curve = 0.8 diff --git a/fish/species/sturgeon_shovelnose/sturgeon_shovelnose.tres b/fish/species/sturgeon_shovelnose/sturgeon_shovelnose.tres new file mode 100644 index 0000000..f62d908 --- /dev/null +++ b/fish/species/sturgeon_shovelnose/sturgeon_shovelnose.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sturgeon_shovelnose" +display_name = "shovelnose sturgeon" +active = false +catalog_number = 105 +collection_group = &"Ancient freshwater fish" +logbook_fact = "shovelnose sturgeon hug flowing river bottoms and probe for insect larvae. a flattened snout helps them hold position in strong current." +allowed_water_types = 1 +rarity = 2 +base_catch_weight = 0.22 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 8.0 +sell_value_min = 12 +sell_value_max = 25 +sell_value_curve = 0.9 diff --git a/fish/species/sturgeon_white/sturgeon_white.tres b/fish/species/sturgeon_white/sturgeon_white.tres new file mode 100644 index 0000000..9b4892c --- /dev/null +++ b/fish/species/sturgeon_white/sturgeon_white.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"sturgeon_white" +display_name = "white sturgeon" +active = false +catalog_number = 4301 +collection_group = &"Ocean-run giants" +logbook_fact = "white sturgeon are the largest freshwater fish in north america. armored plates line bodies built for long lives in rivers and estuaries." +allowed_water_types = 3 +rarity = 4 +base_catch_weight = 0.045 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 500.0 +sell_value_min = 58 +sell_value_max = 135 +sell_value_curve = 0.78 diff --git a/fish/species/sucker_white/sucker_white.tres b/fish/species/sucker_white/sucker_white.tres new file mode 100644 index 0000000..1e2c2b3 --- /dev/null +++ b/fish/species/sucker_white/sucker_white.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"sucker_white" +display_name = "white sucker" +active = false +catalog_number = 2305 +collection_group = &"Freshwater bottom fish" +logbook_fact = "white suckers vacuum insects and organic matter from cool freshwater bottoms. spring runs carry large groups into shallow streams." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.0 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 8.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.9 diff --git a/fish/species/sunfish/sunfish.tres b/fish/species/sunfish/sunfish.tres index 14d2ced..9b9401d 100644 --- a/fish/species/sunfish/sunfish.tres +++ b/fish/species/sunfish/sunfish.tres @@ -9,6 +9,10 @@ script = ExtResource("1_fish_data") id = &"sunfish" display_name = "sunfish" +active = true +catalog_number = 5001 +collection_group = &"Open-ocean giants" +logbook_fact = "ocean sunfish are built more like enormous swimming heads than ordinary fish. they visit the surface and turn sideways to warm up." allowed_water_types = 2 rarity = 0 base_catch_weight = 7.0 diff --git a/fish/species/surgeonfish_blue/surgeonfish_blue.tres b/fish/species/surgeonfish_blue/surgeonfish_blue.tres new file mode 100644 index 0000000..9a21da4 --- /dev/null +++ b/fish/species/surgeonfish_blue/surgeonfish_blue.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"surgeonfish_blue" +display_name = "blue tang" +active = false +catalog_number = 5501 +collection_group = &"Reef grazers" +logbook_fact = "blue tangs graze algae from coral reefs with small close-set teeth. sharp folding spines near the tail give surgeonfish their name." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 1.5 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.95 diff --git a/fish/species/swordfish/swordfish.tres b/fish/species/swordfish/swordfish.tres index 2184395..5f776ca 100644 --- a/fish/species/swordfish/swordfish.tres +++ b/fish/species/swordfish/swordfish.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"ocean"]) script = ExtResource("1_fish_data") id = &"swordfish" display_name = "swordfish" +active = true +catalog_number = 4604 +collection_group = &"Oceanic billfish" +logbook_fact = "swordfish are fast, wide-ranging predators with a flattened bill. they can cross deep water and warm surface layers in one day." allowed_water_types = 2 rarity = 4 base_catch_weight = 0.06 diff --git a/fish/species/tambaqui/tambaqui.tres b/fish/species/tambaqui/tambaqui.tres new file mode 100644 index 0000000..981d643 --- /dev/null +++ b/fish/species/tambaqui/tambaqui.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"tambaqui" +display_name = "tambaqui" +active = false +catalog_number = 7509 +collection_group = &"Tropical freshwater fish" +logbook_fact = "tambaqui follow seasonal floods into forests to feed on fruit and seeds. strong square teeth crack tough plant material." +allowed_water_types = 1 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 8.0 +weight_max_lb = 80.0 +sell_value_min = 30 +sell_value_max = 68 +sell_value_curve = 0.82 diff --git a/fish/species/tang_yellow/tang_yellow.tres b/fish/species/tang_yellow/tang_yellow.tres new file mode 100644 index 0000000..c4facd7 --- /dev/null +++ b/fish/species/tang_yellow/tang_yellow.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"tang_yellow" +display_name = "yellow tang" +active = false +catalog_number = 5504 +collection_group = &"Reef grazers" +logbook_fact = "yellow tangs browse algae across hawaiian reefs during daylight. at night their bright bodies darken and show a pale horizontal stripe." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.35 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 0.8 +sell_value_min = 9 +sell_value_max = 20 +sell_value_curve = 0.96 diff --git a/fish/species/tarpon_atlantic/tarpon_atlantic.tres b/fish/species/tarpon_atlantic/tarpon_atlantic.tres new file mode 100644 index 0000000..c2fbff0 --- /dev/null +++ b/fish/species/tarpon_atlantic/tarpon_atlantic.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"tarpon_atlantic" +display_name = "atlantic tarpon" +active = false +catalog_number = 7301 +collection_group = &"Tropical estuary" +logbook_fact = "atlantic tarpon gulp air with a lung-like swim bladder when coastal water runs low on oxygen. giant silver scales flash during leaps." +allowed_water_types = 3 +rarity = 3 +base_catch_weight = 0.09 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 180.0 +sell_value_min = 34 +sell_value_max = 82 +sell_value_curve = 0.8 diff --git a/fish/species/tautog/tautog.tres b/fish/species/tautog/tautog.tres new file mode 100644 index 0000000..cbd926f --- /dev/null +++ b/fish/species/tautog/tautog.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"tautog" +display_name = "tautog" +active = false +catalog_number = 6906 +collection_group = &"Temperate reef fish" +logbook_fact = "tautog pry mussels and crabs from rocky structure with thick lips and strong teeth. adults return to familiar shelters year after year." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 20.0 +sell_value_min = 7 +sell_value_max = 18 +sell_value_curve = 0.86 diff --git a/fish/species/tench/tench.tres b/fish/species/tench/tench.tres new file mode 100644 index 0000000..300a922 --- /dev/null +++ b/fish/species/tench/tench.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"tench" +display_name = "tench" +active = false +catalog_number = 2509 +collection_group = &"Freshwater coarse fish" +logbook_fact = "tench browse quiet, muddy bottoms with two small mouth barbels. a thick coat of slime protects their olive-green scales." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 12.0 +sell_value_min = 7 +sell_value_max = 17 +sell_value_curve = 0.88 diff --git a/fish/species/tetra_neon/tetra_neon.tres b/fish/species/tetra_neon/tetra_neon.tres new file mode 100644 index 0000000..487ef9b --- /dev/null +++ b/fish/species/tetra_neon/tetra_neon.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"tetra_neon" +display_name = "neon tetra" +active = false +catalog_number = 2102 +collection_group = &"Freshwater aquarium fish" +logbook_fact = "neon tetras school through shaded forest streams. an iridescent blue stripe remains visible in the dim, tannin-stained water." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.9 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.08 +sell_value_min = 4 +sell_value_max = 8 +sell_value_curve = 0.98 diff --git a/fish/species/tigerfish_goliath/tigerfish_goliath.tres b/fish/species/tigerfish_goliath/tigerfish_goliath.tres new file mode 100644 index 0000000..dec9d49 --- /dev/null +++ b/fish/species/tigerfish_goliath/tigerfish_goliath.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"tigerfish_goliath" +display_name = "goliath tigerfish" +active = false +catalog_number = 2703 +collection_group = &"Freshwater giants" +logbook_fact = "goliath tigerfish patrol powerful african rivers with interlocking dagger-like teeth. a muscular forked tail drives sudden attacks." +allowed_water_types = 1 +rarity = 4 +base_catch_weight = 0.035 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 20.0 +weight_max_lb = 150.0 +sell_value_min = 58 +sell_value_max = 132 +sell_value_curve = 0.8 diff --git a/fish/species/tilapia_nile/tilapia_nile.tres b/fish/species/tilapia_nile/tilapia_nile.tres new file mode 100644 index 0000000..3db4fb6 --- /dev/null +++ b/fish/species/tilapia_nile/tilapia_nile.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"tilapia_nile" +display_name = "Nile tilapia" +active = false +catalog_number = 7506 +collection_group = &"Tropical freshwater fish" +logbook_fact = "nile tilapia thrive in warm lakes and rivers on a varied diet. females protect eggs and young inside their mouths." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 8.0 +sell_value_min = 3 +sell_value_max = 9 +sell_value_curve = 0.9 diff --git a/fish/species/tilefish_blueline/tilefish_blueline.tres b/fish/species/tilefish_blueline/tilefish_blueline.tres new file mode 100644 index 0000000..53598bd --- /dev/null +++ b/fish/species/tilefish_blueline/tilefish_blueline.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"tilefish_blueline" +display_name = "blueline tilefish" +active = false +catalog_number = 1601 +collection_group = &"Deep shelf fish" +logbook_fact = "blueline tilefish shelter in burrows and rocky openings along deep shelves. a narrow blue stripe traces the edge of the dorsal fin." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.7 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 20.0 +sell_value_min = 7 +sell_value_max = 18 +sell_value_curve = 0.86 diff --git a/fish/species/tilefish_golden/tilefish_golden.tres b/fish/species/tilefish_golden/tilefish_golden.tres new file mode 100644 index 0000000..26e9543 --- /dev/null +++ b/fish/species/tilefish_golden/tilefish_golden.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"tilefish_golden" +display_name = "golden tilefish" +active = false +catalog_number = 1602 +collection_group = &"Deep shelf fish" +logbook_fact = "golden tilefish excavate burrows in soft clay along deep ocean slopes. their bright yellow spots contrast with blue-green backs." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.22 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 50.0 +sell_value_min = 15 +sell_value_max = 37 +sell_value_curve = 0.84 diff --git a/fish/species/trevally_bluefin/trevally_bluefin.tres b/fish/species/trevally_bluefin/trevally_bluefin.tres new file mode 100644 index 0000000..9c06970 --- /dev/null +++ b/fish/species/trevally_bluefin/trevally_bluefin.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"trevally_bluefin" +display_name = "bluefin trevally" +active = false +catalog_number = 5802 +collection_group = &"Reef predators" +logbook_fact = "bluefin trevally hunt reef fish in coordinated groups. electric blue fins and scattered blue spots contrast with a golden body." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 25.0 +sell_value_min = 14 +sell_value_max = 32 +sell_value_curve = 0.86 diff --git a/fish/species/trevally_giant/trevally_giant.tres b/fish/species/trevally_giant/trevally_giant.tres new file mode 100644 index 0000000..f1acce2 --- /dev/null +++ b/fish/species/trevally_giant/trevally_giant.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"trevally_giant" +display_name = "giant trevally" +active = false +catalog_number = 3603 +collection_group = &"Large coastal predators" +logbook_fact = "giant trevally hunt reef edges with bursts of speed and force. large individuals sometimes charge prey from surf and shallow lagoons." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 150.0 +sell_value_min = 32 +sell_value_max = 78 +sell_value_curve = 0.8 diff --git a/fish/species/triggerfish_gray/triggerfish_gray.tres b/fish/species/triggerfish_gray/triggerfish_gray.tres new file mode 100644 index 0000000..9eb4a35 --- /dev/null +++ b/fish/species/triggerfish_gray/triggerfish_gray.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"triggerfish_gray" +display_name = "gray triggerfish" +active = false +catalog_number = 5602 +collection_group = &"Reef oddities" +logbook_fact = "gray triggerfish lock a stout dorsal spine upright when wedged into shelter. strong teeth crack barnacles, crabs, and shellfish." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.3 +weight_max_lb = 12.0 +sell_value_min = 7 +sell_value_max = 17 +sell_value_curve = 0.88 diff --git a/fish/species/triggerfish_queen/triggerfish_queen.tres b/fish/species/triggerfish_queen/triggerfish_queen.tres new file mode 100644 index 0000000..e2de868 --- /dev/null +++ b/fish/species/triggerfish_queen/triggerfish_queen.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"triggerfish_queen" +display_name = "queen triggerfish" +active = false +catalog_number = 5605 +collection_group = &"Reef oddities" +logbook_fact = "queen triggerfish blow jets of water to uncover buried prey and flip sea urchins. vivid blue lines radiate across the face." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 15.0 +sell_value_min = 14 +sell_value_max = 32 +sell_value_curve = 0.88 diff --git a/fish/species/tripletail/tripletail.tres b/fish/species/tripletail/tripletail.tres new file mode 100644 index 0000000..e973888 --- /dev/null +++ b/fish/species/tripletail/tripletail.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"tripletail" +display_name = "tripletail" +active = false +catalog_number = 604 +collection_group = &"Coastal oddities" +logbook_fact = "tripletail often drift sideways near floating debris, resembling a loose leaf. rounded rear fins create the three-tailed silhouette." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.28 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 30.0 +sell_value_min = 13 +sell_value_max = 31 +sell_value_curve = 0.85 diff --git a/fish/species/trout_brown/trout_brown.tres b/fish/species/trout_brown/trout_brown.tres new file mode 100644 index 0000000..0e6cefd --- /dev/null +++ b/fish/species/trout_brown/trout_brown.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false + +[resource] +script = ExtResource("1_fish_data") +id = &"trout_brown" +display_name = "brown trout" +active = false +catalog_number = 1103 +collection_group = &"Cold freshwater salmonids" +logbook_fact = "brown trout often shelter by day and hunt more freely after dark. adaptable populations occupy streams, lakes, and even coastal water." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 25.0 +sell_value_min = 7 +sell_value_max = 20 +sell_value_curve = 0.86 diff --git a/fish/species/trout_cutthroat/trout_cutthroat.tres b/fish/species/trout_cutthroat/trout_cutthroat.tres index fa29c47..61dc080 100644 --- a/fish/species/trout_cutthroat/trout_cutthroat.tres +++ b/fish/species/trout_cutthroat/trout_cutthroat.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"trout_cutthroat" display_name = "cutthroat trout" +active = true +catalog_number = 1104 +collection_group = &"Cold freshwater salmonids" +logbook_fact = "cutthroat trout take their name from the red slash beneath the jaw. many populations rely on cold, well-oxygenated streams." allowed_water_types = 1 rarity = 1 base_catch_weight = 0.4 diff --git a/fish/species/trout_golden/trout_golden.tres b/fish/species/trout_golden/trout_golden.tres index 8aa3201..5c3d87e 100644 --- a/fish/species/trout_golden/trout_golden.tres +++ b/fish/species/trout_golden/trout_golden.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"trout_golden" display_name = "golden trout" +active = true +catalog_number = 1105 +collection_group = &"Cold freshwater salmonids" +logbook_fact = "golden trout evolved in clear, high-elevation california streams. their bright flanks stand out against rocky alpine water." allowed_water_types = 1 rarity = 2 base_catch_weight = 0.2 diff --git a/fish/species/trout_rainbow/trout_rainbow.tres b/fish/species/trout_rainbow/trout_rainbow.tres index f9c74f6..fbcdb42 100644 --- a/fish/species/trout_rainbow/trout_rainbow.tres +++ b/fish/species/trout_rainbow/trout_rainbow.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"trout_rainbow" display_name = "rainbow trout" +active = true +catalog_number = 1106 +collection_group = &"Cold freshwater salmonids" +logbook_fact = "rainbow trout are adaptable stream hunters. the pink lateral stripe is especially vivid when the fish is in breeding condition." allowed_water_types = 1 rarity = 1 base_catch_weight = 0.45 diff --git a/fish/species/trout_steelhead/trout_steelhead.tres b/fish/species/trout_steelhead/trout_steelhead.tres index 57887f7..92f6985 100644 --- a/fish/species/trout_steelhead/trout_steelhead.tres +++ b/fish/species/trout_steelhead/trout_steelhead.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"trout_steelhead" display_name = "steelhead trout" +active = true +catalog_number = 4501 +collection_group = &"Ocean-run salmonids" +logbook_fact = "steelhead are the ocean-going form of rainbow trout. adults can leave fresh water and later return to spawn." allowed_water_types = 1 rarity = 2 base_catch_weight = 0.2 diff --git a/fish/species/trumpetfish_atlantic/trumpetfish_atlantic.tres b/fish/species/trumpetfish_atlantic/trumpetfish_atlantic.tres new file mode 100644 index 0000000..0a99228 --- /dev/null +++ b/fish/species/trumpetfish_atlantic/trumpetfish_atlantic.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"trumpetfish_atlantic" +display_name = "atlantic trumpetfish" +active = false +catalog_number = 5601 +collection_group = &"Reef oddities" +logbook_fact = "atlantic trumpetfish hover head-down among corals and sea fans. long tubular snouts expand suddenly to draw in small fish." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.3 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.1 +weight_max_lb = 2.0 +sell_value_min = 10 +sell_value_max = 24 +sell_value_curve = 0.94 diff --git a/fish/species/tuna_albacore/tuna_albacore.tres b/fish/species/tuna_albacore/tuna_albacore.tres index 21a9155..d0b841b 100644 --- a/fish/species/tuna_albacore/tuna_albacore.tres +++ b/fish/species/tuna_albacore/tuna_albacore.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"tuna_albacore" display_name = "albacore tuna" +active = true +catalog_number = 4801 +collection_group = &"Oceanic tuna" +logbook_fact = "albacore have unusually long pectoral fins. their warm muscles help them cruise across wide stretches of open ocean." allowed_water_types = 2 rarity = 2 base_catch_weight = 1.5 diff --git a/fish/species/tuna_bigeye/tuna_bigeye.tres b/fish/species/tuna_bigeye/tuna_bigeye.tres index e5e4b7c..8c6c867 100644 --- a/fish/species/tuna_bigeye/tuna_bigeye.tres +++ b/fish/species/tuna_bigeye/tuna_bigeye.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"tuna_bigeye" display_name = "bigeye tuna" +active = true +catalog_number = 4802 +collection_group = &"Oceanic tuna" +logbook_fact = "bigeye tuna make daily trips between deep, cool water and warmer surface layers. their large eyes suit those dim depths." allowed_water_types = 2 rarity = 3 base_catch_weight = 0.55 diff --git a/fish/species/tuna_bluefin/tuna_bluefin.tres b/fish/species/tuna_bluefin/tuna_bluefin.tres index a669b5d..13e8bc2 100644 --- a/fish/species/tuna_bluefin/tuna_bluefin.tres +++ b/fish/species/tuna_bluefin/tuna_bluefin.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"tuna_bluefin" display_name = "bluefin tuna" +active = true +catalog_number = 4803 +collection_group = &"Oceanic tuna" +logbook_fact = "bluefin tuna are powerful ocean travelers. heat retained in their swimming muscles lets them thrive in surprisingly cold seas." allowed_water_types = 2 rarity = 4 base_catch_weight = 0.18 diff --git a/fish/species/tuna_skipjack/tuna_skipjack.tres b/fish/species/tuna_skipjack/tuna_skipjack.tres index 4d9e5a8..4541393 100644 --- a/fish/species/tuna_skipjack/tuna_skipjack.tres +++ b/fish/species/tuna_skipjack/tuna_skipjack.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"tuna_skipjack" display_name = "skipjack tuna" +active = true +catalog_number = 4804 +collection_group = &"Oceanic tuna" +logbook_fact = "skipjack travel in fast-moving schools and have dark stripes along their lower sides. they rarely stop swimming." allowed_water_types = 2 rarity = 1 base_catch_weight = 3.0 diff --git a/fish/species/tuna_yellowfin/tuna_yellowfin.tres b/fish/species/tuna_yellowfin/tuna_yellowfin.tres index cbd4950..5c5acc3 100644 --- a/fish/species/tuna_yellowfin/tuna_yellowfin.tres +++ b/fish/species/tuna_yellowfin/tuna_yellowfin.tres @@ -14,6 +14,10 @@ preferred_bait_weight_multiplier = 1.4 script = ExtResource("1_fish_data") id = &"tuna_yellowfin" display_name = "yellowfin tuna" +active = true +catalog_number = 4805 +collection_group = &"Oceanic tuna" +logbook_fact = "yellowfin are named for their bright yellow fins and finlets. large schools often gather with other open-ocean hunters." allowed_water_types = 2 rarity = 2 catch_profile = ExtResource("2_profile") diff --git a/fish/species/turbot/turbot.tres b/fish/species/turbot/turbot.tres new file mode 100644 index 0000000..6b46871 --- /dev/null +++ b/fish/species/turbot/turbot.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"turbot" +display_name = "turbot" +active = false +catalog_number = 3703 +collection_group = &"Large flatfish" +logbook_fact = "turbot have broad, nearly circular bodies covered with bony bumps instead of scales. they bury into sand before ambushing fish and crustaceans." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.24 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 1.0 +weight_max_lb = 35.0 +sell_value_min = 14 +sell_value_max = 34 +sell_value_curve = 0.85 diff --git a/fish/species/turtle_green/turtle_green.tres b/fish/species/turtle_green/turtle_green.tres new file mode 100644 index 0000000..91f40cd --- /dev/null +++ b/fish/species/turtle_green/turtle_green.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"turtle_green" +display_name = "green sea turtle" +active = false +catalog_number = 6201 +collection_group = &"Sea turtles" +logbook_fact = "green sea turtles graze seagrass meadows with finely serrated jaws. adults migrate long distances between feeding and nesting grounds." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.018 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 65.0 +weight_max_lb = 500.0 +sell_value_min = 62 +sell_value_max = 145 +sell_value_curve = 0.78 diff --git a/fish/species/turtle_hawksbill/turtle_hawksbill.tres b/fish/species/turtle_hawksbill/turtle_hawksbill.tres new file mode 100644 index 0000000..5e07f77 --- /dev/null +++ b/fish/species/turtle_hawksbill/turtle_hawksbill.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"turtle_hawksbill" +display_name = "hawksbill sea turtle" +active = false +catalog_number = 6202 +collection_group = &"Sea turtles" +logbook_fact = "hawksbill sea turtles reach into reef crevices with narrow hooked beaks. a diet rich in sponges helps shape coral communities." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.014 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 75.0 +weight_max_lb = 250.0 +sell_value_min = 66 +sell_value_max = 150 +sell_value_curve = 0.8 diff --git a/fish/species/turtle_leatherback/turtle_leatherback.tres b/fish/species/turtle_leatherback/turtle_leatherback.tres new file mode 100644 index 0000000..1e89bee --- /dev/null +++ b/fish/species/turtle_leatherback/turtle_leatherback.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"turtle_leatherback" +display_name = "leatherback sea turtle" +active = false +catalog_number = 6203 +collection_group = &"Sea turtles" +logbook_fact = "leatherback sea turtles cross entire oceans while hunting jellies. a flexible ridged shell and internal heat help them enter cold water." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.012 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 500.0 +weight_max_lb = 2000.0 +sell_value_min = 75 +sell_value_max = 175 +sell_value_curve = 0.76 diff --git a/fish/species/turtle_loggerhead/turtle_loggerhead.tres b/fish/species/turtle_loggerhead/turtle_loggerhead.tres new file mode 100644 index 0000000..5b64995 --- /dev/null +++ b/fish/species/turtle_loggerhead/turtle_loggerhead.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"turtle_loggerhead" +display_name = "loggerhead sea turtle" +active = false +catalog_number = 6204 +collection_group = &"Sea turtles" +logbook_fact = "loggerhead sea turtles crush crabs and mollusks with massive jaws. females often return near their birth beaches to nest." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.016 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 150.0 +weight_max_lb = 1200.0 +sell_value_min = 68 +sell_value_max = 155 +sell_value_curve = 0.77 diff --git a/fish/species/urchin_purple/urchin_purple.tres b/fish/species/urchin_purple/urchin_purple.tres new file mode 100644 index 0000000..7725d3d --- /dev/null +++ b/fish/species/urchin_purple/urchin_purple.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"urchin_purple" +display_name = "purple sea urchin" +active = false +catalog_number = 1903 +collection_group = &"Echinoderms" +logbook_fact = "purple sea urchins scrape algae from rocks with a five-toothed feeding structure. dense groups can clear kelp from entire reefs." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.75 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.05 +weight_max_lb = 1.0 +sell_value_min = 5 +sell_value_max = 12 +sell_value_curve = 0.96 diff --git a/fish/species/wahoo/wahoo.tres b/fish/species/wahoo/wahoo.tres new file mode 100644 index 0000000..4753b9d --- /dev/null +++ b/fish/species/wahoo/wahoo.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"wahoo" +display_name = "wahoo" +active = false +catalog_number = 4703 +collection_group = &"Oceanic pelagic fish" +logbook_fact = "wahoo are streamlined open-ocean hunters with rows of sharp teeth. blue vertical bars flicker across their sides during fast pursuits." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 100.0 +sell_value_min = 30 +sell_value_max = 70 +sell_value_curve = 0.82 diff --git a/fish/species/walleye/walleye.tres b/fish/species/walleye/walleye.tres index ce833c9..97dea01 100644 --- a/fish/species/walleye/walleye.tres +++ b/fish/species/walleye/walleye.tres @@ -13,6 +13,10 @@ allowed_location_tags = Array[StringName]([&"starter_pond"]) script = ExtResource("1_fish_data") id = &"walleye" display_name = "walleye" +active = true +catalog_number = 3306 +collection_group = &"Freshwater predators" +logbook_fact = "walleye have light-sensitive eyes that help them hunt in murky water and around dusk. their reflective layer gives the eyes a pale glow." allowed_water_types = 1 rarity = 1 base_catch_weight = 0.4 diff --git a/fish/species/walrus/walrus.tres b/fish/species/walrus/walrus.tres new file mode 100644 index 0000000..41682d5 --- /dev/null +++ b/fish/species/walrus/walrus.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"walrus" +display_name = "walrus" +active = false +catalog_number = 504 +collection_group = &"Coastal marine mammals" +logbook_fact = "walruses sweep the seafloor for clams and other soft-bodied prey. long tusks help them haul onto ice and settle disputes." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.012 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 800.0 +weight_max_lb = 3700.0 +sell_value_min = 74 +sell_value_max = 170 +sell_value_curve = 0.76 diff --git a/fish/species/warmouth/warmouth.tres b/fish/species/warmouth/warmouth.tres new file mode 100644 index 0000000..af0214f --- /dev/null +++ b/fish/species/warmouth/warmouth.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"warmouth" +display_name = "warmouth" +active = false +catalog_number = 3106 +collection_group = &"Freshwater panfish" +logbook_fact = "warmouth hide in weeds, roots, and murky backwaters before ambushing prey. red eyes and dark cheek stripes frame a notably large mouth." +allowed_water_types = 1 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 2.0 +sell_value_min = 5 +sell_value_max = 10 +sell_value_curve = 0.95 diff --git a/fish/species/weakfish/weakfish.tres b/fish/species/weakfish/weakfish.tres new file mode 100644 index 0000000..b0072e3 --- /dev/null +++ b/fish/species/weakfish/weakfish.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"weakfish" +display_name = "weakfish" +active = false +catalog_number = 6805 +collection_group = &"Temperate estuary" +logbook_fact = "weakfish are named for delicate mouth tissue that tears easily around a hook. iridescent spots scatter across their bronze backs." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.32 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 18.0 +sell_value_min = 12 +sell_value_max = 29 +sell_value_curve = 0.86 diff --git a/fish/species/whale_blue/whale_blue.tres b/fish/species/whale_blue/whale_blue.tres new file mode 100644 index 0000000..9fab3ed --- /dev/null +++ b/fish/species/whale_blue/whale_blue.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"whale_blue" +display_name = "blue whale" +active = false +catalog_number = 7701 +collection_group = &"Whales and dolphins" +logbook_fact = "blue whales are the largest animals known to have lived. pleated throats expand around vast mouthfuls of water that baleen filters for krill." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.005 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 100000.0 +weight_max_lb = 330000.0 +sell_value_min = 120 +sell_value_max = 300 +sell_value_curve = 0.72 diff --git a/fish/species/whale_humpback/whale_humpback.tres b/fish/species/whale_humpback/whale_humpback.tres new file mode 100644 index 0000000..e20e736 --- /dev/null +++ b/fish/species/whale_humpback/whale_humpback.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"whale_humpback" +display_name = "humpback whale" +active = false +catalog_number = 7703 +collection_group = &"Whales and dolphins" +logbook_fact = "humpback whales use long pectoral fins while turning and feeding. males produce layered songs that change across breeding seasons." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.008 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 40000.0 +weight_max_lb = 90000.0 +sell_value_min = 105 +sell_value_max = 260 +sell_value_curve = 0.73 diff --git a/fish/species/whale_killer/whale_killer.tres b/fish/species/whale_killer/whale_killer.tres new file mode 100644 index 0000000..b3ea92a --- /dev/null +++ b/fish/species/whale_killer/whale_killer.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +require_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"whale_killer" +display_name = "killer whale" +active = false +catalog_number = 7704 +collection_group = &"Whales and dolphins" +logbook_fact = "killer whales live in close family groups with distinct calls and hunting traditions. different populations specialize in different prey." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.008 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 3000.0 +weight_max_lb = 12000.0 +sell_value_min = 95 +sell_value_max = 230 +sell_value_curve = 0.74 diff --git a/fish/species/whale_sperm/whale_sperm.tres b/fish/species/whale_sperm/whale_sperm.tres new file mode 100644 index 0000000..a10b923 --- /dev/null +++ b/fish/species/whale_sperm/whale_sperm.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_day = false +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"whale_sperm" +display_name = "sperm whale" +active = false +catalog_number = 7705 +collection_group = &"Whales and dolphins" +logbook_fact = "sperm whales make deep dives to hunt squid with powerful echolocation clicks. their enormous square heads contain a waxy spermaceti organ." +allowed_water_types = 2 +rarity = 4 +base_catch_weight = 0.006 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 15000.0 +weight_max_lb = 125000.0 +sell_value_min = 115 +sell_value_max = 280 +sell_value_curve = 0.72 diff --git a/fish/species/white_perch/white_perch.tres b/fish/species/white_perch/white_perch.tres new file mode 100644 index 0000000..ee06ee4 --- /dev/null +++ b/fish/species/white_perch/white_perch.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"white_perch" +display_name = "white perch" +active = false +catalog_number = 6806 +collection_group = &"Temperate estuary" +logbook_fact = "white perch tolerate fresh and brackish water along the atlantic coast. deep silver bodies gather in schools around river mouths." +allowed_water_types = 3 +rarity = 1 +base_catch_weight = 1.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 3.5 +sell_value_min = 5 +sell_value_max = 11 +sell_value_curve = 0.9 diff --git a/fish/species/whitefish_lake/whitefish_lake.tres b/fish/species/whitefish_lake/whitefish_lake.tres new file mode 100644 index 0000000..4ad0281 --- /dev/null +++ b/fish/species/whitefish_lake/whitefish_lake.tres @@ -0,0 +1,27 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") + +[resource] +script = ExtResource("1_fish_data") +id = &"whitefish_lake" +display_name = "lake whitefish" +active = false +catalog_number = 1003 +collection_group = &"Cold freshwater fish" +logbook_fact = "lake whitefish browse deep, cold bottoms with small downward-pointing mouths. large schools move toward shallows to spawn in autumn." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 1.5 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.5 +weight_max_lb = 12.0 +sell_value_min = 4 +sell_value_max = 11 +sell_value_curve = 0.9 diff --git a/fish/species/wolffish_atlantic/wolffish_atlantic.tres b/fish/species/wolffish_atlantic/wolffish_atlantic.tres new file mode 100644 index 0000000..a305cc7 --- /dev/null +++ b/fish/species/wolffish_atlantic/wolffish_atlantic.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +forbid_rain = true + +[resource] +script = ExtResource("1_fish_data") +id = &"wolffish_atlantic" +display_name = "atlantic wolffish" +active = false +catalog_number = 1201 +collection_group = &"Cold reef fish" +logbook_fact = "atlantic wolffish use heavy jaws and blunt teeth to crush shellfish and urchins. natural antifreeze proteins help them endure icy water." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.25 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 2.0 +weight_max_lb = 40.0 +sell_value_min = 14 +sell_value_max = 34 +sell_value_curve = 0.84 diff --git a/fish/species/wolffish_spotted/wolffish_spotted.tres b/fish/species/wolffish_spotted/wolffish_spotted.tres new file mode 100644 index 0000000..52a1de3 --- /dev/null +++ b/fish/species/wolffish_spotted/wolffish_spotted.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"wolffish_spotted" +display_name = "spotted wolffish" +active = false +catalog_number = 1203 +collection_group = &"Cold reef fish" +logbook_fact = "spotted wolffish crush hard-shelled prey with broad molar-like teeth. their pale spots stand out against a dark cold-water body." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.11 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 3.0 +weight_max_lb = 50.0 +sell_value_min = 26 +sell_value_max = 60 +sell_value_curve = 0.84 diff --git a/fish/species/wrasse_ballan/wrasse_ballan.tres b/fish/species/wrasse_ballan/wrasse_ballan.tres new file mode 100644 index 0000000..63cb0b6 --- /dev/null +++ b/fish/species/wrasse_ballan/wrasse_ballan.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"wrasse_ballan" +display_name = "ballan wrasse" +active = false +catalog_number = 6101 +collection_group = &"Reef wrasses" +logbook_fact = "ballan wrasse crush shellfish and sea urchins with strong throat teeth. thick lips and mottled colors suit rocky european reefs." +allowed_water_types = 2 +rarity = 1 +base_catch_weight = 0.8 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 10.0 +sell_value_min = 6 +sell_value_max = 15 +sell_value_curve = 0.9 diff --git a/fish/species/wrasse_cleaner/wrasse_cleaner.tres b/fish/species/wrasse_cleaner/wrasse_cleaner.tres new file mode 100644 index 0000000..e22c1f0 --- /dev/null +++ b/fish/species/wrasse_cleaner/wrasse_cleaner.tres @@ -0,0 +1,29 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false +forbid_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"wrasse_cleaner" +display_name = "cleaner wrasse" +active = false +catalog_number = 6102 +collection_group = &"Reef wrasses" +logbook_fact = "cleaner wrasses advertise with a bright stripe and dance at reef stations. larger fish wait while parasites are picked from skin and gills." +allowed_water_types = 2 +rarity = 2 +base_catch_weight = 0.35 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.01 +weight_max_lb = 0.2 +sell_value_min = 9 +sell_value_max = 20 +sell_value_curve = 1.0 diff --git a/fish/species/wreckfish/wreckfish.tres b/fish/species/wreckfish/wreckfish.tres new file mode 100644 index 0000000..a2121ef --- /dev/null +++ b/fish/species/wreckfish/wreckfish.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +require_fog = true + +[resource] +script = ExtResource("1_fish_data") +id = &"wreckfish" +display_name = "wreckfish" +active = false +catalog_number = 1606 +collection_group = &"Deep shelf fish" +logbook_fact = "wreckfish gather around caves, ledges, and shipwrecks in deep water. powerful jaws let them ambush fish and large crustaceans." +allowed_water_types = 2 +rarity = 3 +base_catch_weight = 0.1 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 10.0 +weight_max_lb = 180.0 +sell_value_min = 31 +sell_value_max = 76 +sell_value_curve = 0.8 diff --git a/fish/species/yellow_perch/yellow_perch.tres b/fish/species/yellow_perch/yellow_perch.tres new file mode 100644 index 0000000..3147fdd --- /dev/null +++ b/fish/species/yellow_perch/yellow_perch.tres @@ -0,0 +1,28 @@ +[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"] + +[sub_resource type="Resource" id="Availability"] +script = ExtResource("3_availability") +allow_night = false + +[resource] +script = ExtResource("1_fish_data") +id = &"yellow_perch" +display_name = "yellow perch" +active = false +catalog_number = 3201 +collection_group = &"Freshwater perch" +logbook_fact = "yellow perch travel in schools through cool lakes and slow rivers. dark vertical bars and orange lower fins make them easy to recognize." +allowed_water_types = 1 +rarity = 0 +base_catch_weight = 2.2 +catch_profile = ExtResource("2_profile") +availability = SubResource("Availability") +weight_min_lb = 0.2 +weight_max_lb = 4.0 +sell_value_min = 3 +sell_value_max = 8 +sell_value_curve = 0.9 diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index 243a069..e42883b 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -15,6 +15,7 @@ const PlayerExperienceType = preload( ) const ItemCatalogType = preload("res://items/item_catalog.gd") const ItemDataType = preload("res://items/item_data.gd") +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") const PlayerBagType = preload("res://inventory/player_bag.gd") const PlayerHotbarType = preload("res://inventory/player_hotbar.gd") const PlayerFishingUpgradesType = preload( @@ -41,6 +42,7 @@ 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" @@ -503,6 +505,13 @@ func _unhandled_input(event: InputEvent) -> void: return if not event.is_action("fish_primary"): return + var selected_item: ItemDataType = _get_active_item() + if ( + state == FishingState.READY + and selected_item != null + and selected_item.item_id == FishingShopStockType.CRAB_NET_ID + ): + return if event.is_pressed(): match state: FishingState.READY: @@ -658,7 +667,7 @@ func has_active_fishing_rod() -> bool: var item: ItemDataType = _get_active_item() return ( item != null - and item.is_valid() + and item.is_available() and item.category == ItemDataType.Category.ROD and item.usable and item.equippable @@ -676,7 +685,11 @@ func _get_active_item() -> ItemDataType: if item_id.is_empty() or not _local_bag.owns_item(item_id): return null var item: ItemDataType = _item_catalog.get_item_by_id(item_id) - return item if item != null and item.is_valid() else null + return item if item != null and item.is_available() else null + + +func _get_active_rod() -> FishingRodDataType: + return _get_active_item() as FishingRodDataType func _is_cooler_full() -> bool: @@ -732,6 +745,36 @@ func refresh_active_item_status() -> void: status_changed.emit("") +func report_external_status(message: String) -> void: + status_changed.emit(message) + + +func present_external_catch(fish_catch: FishCatchType) -> bool: + if ( + fish_catch == null + or not fish_catch.is_valid() + or state != FishingState.READY + or _local_player == null + ): + return false + _active_player = _local_player + _pending_catch = fish_catch + _active_player.set_movement_enabled(false) + state = FishingState.SHOWING_CATCH + _showcase_ready = true + _showcase_outcome_completed = true + _put_away_press_armed = false + _active_player.begin_catch_showcase(fish_catch) + showcase_changed.emit( + fish_catch.fish.display_name, + fish_catch.fish.get_rarity_name(), + fish_catch.weight_lb, + fish_catch.quality, + true, + ) + return true + + func _update_cast_charge(delta: float) -> void: if state != FishingState.AIMING_CAST: return @@ -829,7 +872,9 @@ func _on_cast_completed() -> void: _selected_water_region = get_fishable_water_region(_cast_target) _selection_context = _build_fishing_context(_selected_water_region) + var active_rod: FishingRodDataType = _get_active_rod() _fish_selector.undiscovered_weight_multiplier = undiscovered_weight_multiplier + _fish_selector.active_rod = active_rod _fish_selector.rarity_weight_multipliers = [] for rarity: int in range(FishDataType.Rarity.size()): _fish_selector.rarity_weight_multipliers.append( @@ -856,6 +901,10 @@ func _on_cast_completed() -> void: _item_effects.get_bite_time_multiplier() if _item_effects != null else 1.0 + ) * ( + active_rod.bite_time_multiplier + if active_rod != null + else 1.0 ) _withdrawal_progress = 0.0 _withdrawal_input_held = false @@ -868,7 +917,7 @@ func _on_cast_completed() -> void: _cast_origin_position.z + _cast_direction.z * withdrawal_cancel_distance ) _presentation.set_line_mode(FishingPresentationType.LineMode.SLACK) - status_changed.emit("waiting for a bite...") + status_changed.emit("") func _consume_active_bait() -> void: @@ -1055,6 +1104,7 @@ func _activate_bite(confirmation_override: bool = false) -> void: func _get_effective_reel_speed() -> float: if _active_player == null: return 0.0 + var rod: FishingRodDataType = _get_active_rod() return _active_player.reel_speed * ( _fishing_upgrades.get_reel_speed_multiplier() if _fishing_upgrades != null @@ -1063,13 +1113,16 @@ func _get_effective_reel_speed() -> float: _item_effects.get_reel_multiplier() if _item_effects != null else 1.0 + ) * ( + rod.reel_speed_multiplier if rod != null else 1.0 ) func _get_effective_barrier_damage() -> int: if _active_player == null: return 1 - return ( + var rod: FishingRodDataType = _get_active_rod() + var damage: int = ( _fishing_upgrades.get_barrier_damage() if _fishing_upgrades != null else _active_player.click_power @@ -1078,6 +1131,13 @@ func _get_effective_barrier_damage() -> int: if _item_effects != null else 0 ) + return maxi( + roundi( + float(damage) + * (rod.barrier_power_multiplier if rod != null else 1.0) + ), + 1, + ) func _on_catch_encounter_updated( @@ -1131,10 +1191,7 @@ func _on_catch_escaped() -> void: return _stop_fight_audio() - var fish_name: String = "the fish" - if _selected_fish != null and not _selected_fish.display_name.is_empty(): - fish_name = "the %s" % _selected_fish.display_name - _cleanup_attempt("%s got away!" % fish_name, &"escape") + _cleanup_attempt("", &"escape") func _on_outcome_completed(outcome: StringName) -> void: @@ -1515,7 +1572,11 @@ func _get_active_bait_tags() -> Array[StringName]: if bait_id.is_empty() or _local_bag == null or not _local_bag.owns_item(bait_id): return [] var item: ItemDataType = _item_catalog.get_item_by_id(bait_id) - return item.bait_tags.duplicate() if item != null and item.is_bait() else [] + return ( + item.bait_tags.duplicate() + if item != null and item.is_available() and item.is_bait() + else [] + ) func _active_lure_has_effect(effect_id: StringName) -> bool: @@ -1530,7 +1591,12 @@ func _active_lure_has_effect(effect_id: StringName) -> bool: var lure: ItemDataType = _item_catalog.get_item_by_id( _local_player.active_lure_id ) - return lure != null and lure.is_lure() and effect_id in lure.lure_effects + return ( + lure != null + and lure.is_available() + and lure.is_lure() + and effect_id in lure.lure_effects + ) func _build_network_evidence() -> Dictionary: @@ -1597,7 +1663,7 @@ func _on_network_cast_accepted( _network_input_resend_elapsed = 0.0 _presentation.show_withdrawal_position(_bobber_water_position) _presentation.set_line_mode(FishingPresentationType.LineMode.SLACK) - status_changed.emit("waiting for a bite...") + status_changed.emit("") func _on_network_cast_rejected(message: String) -> void: diff --git a/gathering/catalog/crab_blue.tres b/gathering/catalog/crab_blue.tres new file mode 100644 index 0000000..9e02a04 --- /dev/null +++ b/gathering/catalog/crab_blue.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="GatherableData" load_steps=3 format=3] + +[ext_resource type="Script" path="res://gathering/gatherable_data.gd" id="1_data"] +[ext_resource type="Resource" path="res://fish/species/crab_blue/crab_blue.tres" id="2_catch"] + +[resource] +script = ExtResource("1_data") +type_id = &"crab_blue" +catch_data = ExtResource("2_catch") +required_tool_id = &"crab_net" +surface_materials = Array[StringName]([&"sand"]) +minimum_surface_y = -0.8 +population = 4 diff --git a/gathering/catalog/crab_brown.tres b/gathering/catalog/crab_brown.tres new file mode 100644 index 0000000..13ac341 --- /dev/null +++ b/gathering/catalog/crab_brown.tres @@ -0,0 +1,26 @@ +[gd_resource type="Resource" script_class="GatherableData" load_steps=3 format=3] + +[ext_resource type="Script" path="res://gathering/gatherable_data.gd" id="1_data"] +[ext_resource type="Resource" path="res://fish/species/crab_brown/crab_brown.tres" id="2_catch"] + +[resource] +script = ExtResource("1_data") +type_id = &"crab_brown" +catch_data = ExtResource("2_catch") +required_tool_id = &"crab_net" +surface_materials = Array[StringName]([&"sand"]) +minimum_surface_y = 0.08 +population = 2 +movement_speed = 0.35 +roam_radius = 3.5 +scare_radius = 2.8 +capture_radius = 0.7 +interaction_range = 2.6 +charge_duration = 2.0 +sprite_pixel_size = 0.001 +sprite_tilt_degrees = -45.0 +capture_respawn_min_seconds = 480.0 +capture_respawn_max_seconds = 720.0 +scare_respawn_min_seconds = 45.0 +scare_respawn_max_seconds = 90.0 +minimum_respawn_spacing_seconds = 180.0 diff --git a/gathering/catalog/crab_dungeness.tres b/gathering/catalog/crab_dungeness.tres new file mode 100644 index 0000000..e4af78f --- /dev/null +++ b/gathering/catalog/crab_dungeness.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="GatherableData" load_steps=3 format=3] + +[ext_resource type="Script" path="res://gathering/gatherable_data.gd" id="1_data"] +[ext_resource type="Resource" path="res://fish/species/crab_dungeness/crab_dungeness.tres" id="2_catch"] + +[resource] +script = ExtResource("1_data") +type_id = &"crab_dungeness" +catch_data = ExtResource("2_catch") +required_tool_id = &"crab_net" +surface_materials = Array[StringName]([&"sand"]) +minimum_surface_y = -0.5 +population = 4 diff --git a/gathering/catalog/crab_ghost.tres b/gathering/catalog/crab_ghost.tres new file mode 100644 index 0000000..557419e --- /dev/null +++ b/gathering/catalog/crab_ghost.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="GatherableData" load_steps=3 format=3] + +[ext_resource type="Script" path="res://gathering/gatherable_data.gd" id="1_data"] +[ext_resource type="Resource" path="res://fish/species/crab_ghost/crab_ghost.tres" id="2_catch"] + +[resource] +script = ExtResource("1_data") +type_id = &"crab_ghost" +catch_data = ExtResource("2_catch") +required_tool_id = &"crab_net" +surface_materials = Array[StringName]([&"sand"]) +minimum_surface_y = 0.08 +population = 4 diff --git a/gathering/catalog/gatherable_catalog.tres b/gathering/catalog/gatherable_catalog.tres new file mode 100644 index 0000000..90131fc --- /dev/null +++ b/gathering/catalog/gatherable_catalog.tres @@ -0,0 +1,11 @@ +[gd_resource type="Resource" script_class="GatherableCatalog" load_steps=6 format=3] + +[ext_resource type="Script" path="res://gathering/gatherable_catalog.gd" id="1_catalog"] +[ext_resource type="Resource" path="res://gathering/catalog/crab_brown.tres" id="2_brown"] +[ext_resource type="Resource" path="res://gathering/catalog/crab_ghost.tres" id="3_ghost"] +[ext_resource type="Resource" path="res://gathering/catalog/crab_blue.tres" id="4_blue"] +[ext_resource type="Resource" path="res://gathering/catalog/crab_dungeness.tres" id="5_dungeness"] + +[resource] +script = ExtResource("1_catalog") +entries = [ExtResource("2_brown"), ExtResource("3_ghost"), ExtResource("4_blue"), ExtResource("5_dungeness")] diff --git a/gathering/gatherable_catalog.gd b/gathering/gatherable_catalog.gd new file mode 100644 index 0000000..d71b65f --- /dev/null +++ b/gathering/gatherable_catalog.gd @@ -0,0 +1,23 @@ +class_name GatherableCatalog +extends Resource + +const GatherableDataType = preload("res://gathering/gatherable_data.gd") + +@export var entries: Array[GatherableDataType] = [] + + +func get_entry(type_id: StringName) -> GatherableDataType: + if type_id.is_empty(): + return null + for entry: GatherableDataType in entries: + if entry != null and entry.type_id == type_id: + return entry + return null + + +func get_available_entries() -> Array[GatherableDataType]: + var available: Array[GatherableDataType] = [] + for entry: GatherableDataType in entries: + if entry != null and entry.is_available(): + available.append(entry) + return available diff --git a/gathering/gatherable_catalog.gd.uid b/gathering/gatherable_catalog.gd.uid new file mode 100644 index 0000000..7f74b07 --- /dev/null +++ b/gathering/gatherable_catalog.gd.uid @@ -0,0 +1 @@ +uid://bpsncwh7qlfk1 diff --git a/gathering/gatherable_data.gd b/gathering/gatherable_data.gd new file mode 100644 index 0000000..f1be67c --- /dev/null +++ b/gathering/gatherable_data.gd @@ -0,0 +1,61 @@ +class_name GatherableData +extends Resource + +const FishDataType = preload("res://fish/fish_data.gd") + +@export var type_id: StringName +@export var catch_data: FishDataType +@export var required_tool_id: StringName +@export var surface_materials: Array[StringName] = [] +@export_range(-100.0, 100.0, 0.01) var minimum_surface_y: float = 0.08 +@export_range(0, 64, 1) var population: int = 0 +@export_range(0.0, 10.0, 0.05) var movement_speed: float = 0.35 +@export_range(0.1, 20.0, 0.1) var roam_radius: float = 3.5 +@export_range(0.1, 20.0, 0.1) var scare_radius: float = 2.8 +@export_range(0.1, 5.0, 0.05) var capture_radius: float = 0.7 +@export_range(0.1, 10.0, 0.05) var interaction_range: float = 2.6 +@export_range(0.1, 10.0, 0.05) var charge_duration: float = 2.0 +@export_range(0.1, 5.0, 0.01) var sprite_pixel_size: float = 0.001 +@export_range(-90.0, 90.0, 1.0) var sprite_tilt_degrees: float = -45.0 +@export_category("Respawn Budget") +@export_range(0.0, 3600.0, 1.0) var capture_respawn_min_seconds: float = 480.0 +@export_range(0.0, 3600.0, 1.0) var capture_respawn_max_seconds: float = 720.0 +@export_range(0.0, 3600.0, 1.0) var scare_respawn_min_seconds: float = 45.0 +@export_range(0.0, 3600.0, 1.0) var scare_respawn_max_seconds: float = 90.0 +@export_range(0.0, 3600.0, 1.0) var minimum_respawn_spacing_seconds: float = 180.0 + + +func is_valid() -> bool: + return ( + not type_id.is_empty() + and catch_data != null + and catch_data.is_valid_catalog_entry() + and catch_data.collection_method != FishDataType.CollectionMethod.FISHING + and not required_tool_id.is_empty() + and not surface_materials.is_empty() + and population > 0 + and movement_speed >= 0.0 + and roam_radius > 0.0 + and scare_radius > 0.0 + and capture_radius > 0.0 + and interaction_range > 0.0 + and charge_duration > 0.0 + and sprite_pixel_size > 0.0 + and capture_respawn_max_seconds >= capture_respawn_min_seconds + and scare_respawn_max_seconds >= scare_respawn_min_seconds + ) + + +func get_respawn_delay(reason: StringName, rng: RandomNumberGenerator) -> float: + var minimum_seconds: float = capture_respawn_min_seconds + var maximum_seconds: float = capture_respawn_max_seconds + if reason == &"scared": + minimum_seconds = scare_respawn_min_seconds + maximum_seconds = scare_respawn_max_seconds + if rng == null or maximum_seconds <= minimum_seconds: + return minimum_seconds + return rng.randf_range(minimum_seconds, maximum_seconds) + + +func is_available() -> bool: + return catch_data != null and catch_data.active and is_valid() diff --git a/gathering/gatherable_data.gd.uid b/gathering/gatherable_data.gd.uid new file mode 100644 index 0000000..5875fff --- /dev/null +++ b/gathering/gatherable_data.gd.uid @@ -0,0 +1 @@ +uid://doheo0308illx diff --git a/gathering/gathering_controller.gd b/gathering/gathering_controller.gd new file mode 100644 index 0000000..a0b6602 --- /dev/null +++ b/gathering/gathering_controller.gd @@ -0,0 +1,247 @@ +class_name GatheringController +extends Node3D + +const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd") +const FishingSpotType = preload("res://fishing/fishing_spot.gd") +const NetworkWorldSpawnServiceType = preload( + "res://network/network_world_spawn_service.gd" +) + +signal status_changed(message: String) + +@export_range(0.5, 5.0, 0.05) var marker_distance: float = 1.7 +@export_range(0.1, 2.0, 0.05) var marker_radius: float = 0.7 +@export_flags_3d_physics var terrain_collision_mask: int = 1 +@export_range(0.5, 20.0, 0.5) var ray_height: float = 5.0 +@export_range(0.5, 20.0, 0.5) var ray_depth: float = 8.0 + +var _player: Player +var _bag: PlayerBag +var _hotbar: PlayerHotbar +var _fishing_spot: FishingSpotType +var _service: NetworkWorldSpawnServiceType +var _marker: MeshInstance3D +var _marker_invalid_material: StandardMaterial3D +var _marker_valid_material: StandardMaterial3D +var _marker_position: Vector3 +var _marker_has_surface: bool = false +var _target_entity_id: String = "" +var _charging: bool = false +var _charge_elapsed: float = 0.0 +var _charge_duration: float = 2.0 +var _request_id: String = "" +var _gameplay_input_enabled: bool = false + + +func _ready() -> void: + _build_marker() + set_process_unhandled_input(true) + + +func setup( + player: Player, + bag: PlayerBag, + hotbar: PlayerHotbar, + fishing_spot: FishingSpotType, + service: NetworkWorldSpawnServiceType, +) -> void: + _player = player + _bag = bag + _hotbar = hotbar + _fishing_spot = fishing_spot + _service = service + _service.local_interaction_finished.connect( + _on_interaction_finished + ) + + +func set_gameplay_input_enabled(enabled: bool) -> void: + _gameplay_input_enabled = enabled + if not enabled: + _cancel_charge() + + +func is_net_selected() -> bool: + return ( + _bag != null + and _hotbar != null + and _hotbar.get_selected_item_id() + == FishingShopStockType.CRAB_NET_ID + and _bag.owns_item(FishingShopStockType.CRAB_NET_ID) + ) + + +func _process(delta: float) -> void: + var input_available: bool = ( + _gameplay_input_enabled + and is_net_selected() + and _player != null + and _player.is_local_control_enabled() + and _fishing_spot != null + and _fishing_spot.can_change_hotbar_selection() + ) + if not input_available: + if _charging: + _cancel_charge() + _set_marker_visible(false) + return + if not _charging: + _set_marker_visible(false) + return + _charge_elapsed += delta + _update_marker_target() + _update_target_entity() + var target_entry: GatherableData = _service.get_entry_for_entity( + _target_entity_id + ) + if target_entry != null: + _charge_duration = target_entry.charge_duration + var fully_charged: bool = _charge_elapsed >= _charge_duration + var valid_target: bool = ( + fully_charged + and not _target_entity_id.is_empty() + and _player.is_sneaking() + ) + _marker.material_override = ( + _marker_valid_material if valid_target else _marker_invalid_material + ) + _set_marker_visible(_marker_has_surface) + + +func _unhandled_input(event: InputEvent) -> void: + if not event.is_action("fish_primary"): + return + if event.is_pressed(): + if ( + _charging + or not _gameplay_input_enabled + or not is_net_selected() + or _fishing_spot == null + or not _fishing_spot.can_change_hotbar_selection() + ): + return + _request_id = _service.begin_local_interaction() + if _request_id.is_empty(): + return + _charging = true + _charge_elapsed = 0.0 + _charge_duration = maxf( + _service.get_charge_duration_for_tool( + FishingShopStockType.CRAB_NET_ID + ), + 0.1, + ) + _update_marker_target() + _update_target_entity() + get_viewport().set_input_as_handled() + return + if not _charging: + return + var fully_charged: bool = _charge_elapsed >= _charge_duration + if ( + fully_charged + and _marker_has_surface + and not _target_entity_id.is_empty() + and _player.is_sneaking() + ): + _service.finish_local_interaction( + _request_id, + _target_entity_id, + _marker_position, + ) + else: + _service.cancel_local_interaction(_request_id) + status_changed.emit( + "Sneak close, pull the net all the way back, and line up the marker." + ) + _reset_charge_state() + get_viewport().set_input_as_handled() + + +func _update_marker_target() -> void: + _marker_has_surface = false + if _player == null or get_world_3d() == null: + return + var facing: Vector3 = _player.get_facing_direction() + facing.y = 0.0 + if facing.is_zero_approx(): + facing = -_player.global_basis.z + facing.y = 0.0 + if facing.is_zero_approx(): + return + facing = facing.normalized() + var query_center: Vector3 = _player.global_position + facing * marker_distance + var query := PhysicsRayQueryParameters3D.create( + query_center + Vector3.UP * ray_height, + query_center - Vector3.UP * ray_depth, + terrain_collision_mask, + ) + query.collide_with_areas = false + query.collide_with_bodies = true + var hit: Dictionary = get_world_3d().direct_space_state.intersect_ray(query) + if hit.is_empty(): + return + var position: Variant = hit.get("position") + if typeof(position) != TYPE_VECTOR3: + return + _marker_position = (position as Vector3) + Vector3.UP * 0.045 + _marker.global_position = _marker_position + _marker_has_surface = true + + +func _update_target_entity() -> void: + _target_entity_id = ( + _service.find_capture_target( + _marker_position, + FishingShopStockType.CRAB_NET_ID, + ) + if _marker_has_surface and _service != null + else "" + ) + + +func _cancel_charge() -> void: + if _charging and not _request_id.is_empty() and _service != null: + _service.cancel_local_interaction(_request_id) + _reset_charge_state() + + +func _reset_charge_state() -> void: + _charging = false + _charge_elapsed = 0.0 + _request_id = "" + _target_entity_id = "" + _marker_has_surface = false + _set_marker_visible(false) + + +func _on_interaction_finished(accepted: bool, message: String) -> void: + _reset_charge_state() + if not accepted or not message.is_empty(): + status_changed.emit(message) + + +func _build_marker() -> void: + _marker_invalid_material = StandardMaterial3D.new() + _marker_invalid_material.albedo_color = Color(0.86, 0.24, 0.19, 0.9) + _marker_invalid_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + _marker_valid_material = StandardMaterial3D.new() + _marker_valid_material.albedo_color = Color(0.25, 0.9, 0.36, 0.95) + _marker_valid_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + var ring := TorusMesh.new() + ring.inner_radius = marker_radius * 0.78 + ring.outer_radius = marker_radius + ring.rings = 16 + ring.ring_segments = 24 + _marker = MeshInstance3D.new() + _marker.name = "GatheringTargetMarker" + _marker.mesh = ring + _marker.material_override = _marker_invalid_material + _marker.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + _marker.visible = false + add_child(_marker) + + +func _set_marker_visible(value: bool) -> void: + if _marker != null: + _marker.visible = value diff --git a/gathering/gathering_controller.gd.uid b/gathering/gathering_controller.gd.uid new file mode 100644 index 0000000..30e9ead --- /dev/null +++ b/gathering/gathering_controller.gd.uid @@ -0,0 +1 @@ +uid://cec7nwk815d8a diff --git a/gathering/world_gatherable.gd b/gathering/world_gatherable.gd new file mode 100644 index 0000000..1dccc7a --- /dev/null +++ b/gathering/world_gatherable.gd @@ -0,0 +1,113 @@ +class_name WorldGatherable +extends Node3D + +const GatherableDataType = preload("res://gathering/gatherable_data.gd") + +var entity_id: String = "" +var type_id: StringName +var data: GatherableDataType +var _sprite: Sprite3D +var _target_position: Vector3 +var _target_yaw: float = 0.0 +var _has_state: bool = false +var _despawning: bool = false + + +func configure( + configured_entity_id: String, + configured_data: GatherableDataType, + position: Vector3, + yaw: float, +) -> void: + entity_id = configured_entity_id + data = configured_data + type_id = data.type_id if data != null else StringName() + _ensure_visual() + if data != null and data.catch_data != null: + _sprite.texture = data.catch_data.display_texture + _sprite.pixel_size = data.sprite_pixel_size + _sprite.rotation_degrees.x = data.sprite_tilt_degrees + apply_network_state(position, yaw, true) + + +func apply_network_state( + position: Vector3, + yaw: float, + immediate: bool = false, +) -> void: + if _despawning or not position.is_finite() or not is_finite(yaw): + return + _target_position = position + _target_yaw = yaw + if immediate or not _has_state: + global_position = position + rotation.y = yaw + _has_state = true + + +func play_despawn(with_dust: bool) -> void: + if _despawning: + return + _despawning = true + if with_dust: + _emit_dust() + var tween: Tween = create_tween() + tween.set_parallel(true) + tween.set_trans(Tween.TRANS_QUAD) + tween.set_ease(Tween.EASE_IN) + if _sprite != null: + tween.tween_property(_sprite, "position:y", -0.3, 0.3) + tween.tween_property(_sprite, "scale", Vector3(0.75, 0.75, 0.75), 0.3) + tween.chain().tween_callback(queue_free) + + +func _process(delta: float) -> void: + if not _has_state or _despawning: + return + global_position = global_position.lerp( + _target_position, + 1.0 - exp(-10.0 * delta), + ) + rotation.y = lerp_angle( + rotation.y, + _target_yaw, + 1.0 - exp(-8.0 * delta), + ) + + +func _ensure_visual() -> void: + if _sprite != null: + return + _sprite = Sprite3D.new() + _sprite.name = "GatherableSprite" + _sprite.position.y = 0.22 + _sprite.shaded = false + _sprite.double_sided = true + _sprite.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST + add_child(_sprite) + + +func _emit_dust() -> void: + var particles := CPUParticles3D.new() + particles.name = "DustPoof" + particles.amount = 9 + particles.lifetime = 0.55 + particles.one_shot = true + particles.explosiveness = 1.0 + particles.direction = Vector3.UP + particles.spread = 58.0 + particles.gravity = Vector3(0.0, -2.4, 0.0) + particles.initial_velocity_min = 0.65 + particles.initial_velocity_max = 1.25 + particles.scale_amount_min = 0.7 + particles.scale_amount_max = 1.35 + var dust_material := StandardMaterial3D.new() + dust_material.albedo_color = Color(0.58, 0.46, 0.31, 0.85) + dust_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + dust_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + var dust_mesh := BoxMesh.new() + dust_mesh.size = Vector3(0.1, 0.1, 0.1) + dust_mesh.material = dust_material + particles.mesh = dust_mesh + add_child(particles) + particles.emitting = true diff --git a/gathering/world_gatherable.gd.uid b/gathering/world_gatherable.gd.uid new file mode 100644 index 0000000..f0de7f7 --- /dev/null +++ b/gathering/world_gatherable.gd.uid @@ -0,0 +1 @@ +uid://3t8ygawpkgwj diff --git a/inventory/player_bag.gd b/inventory/player_bag.gd index 4fc12b6..ffb734d 100644 --- a/inventory/player_bag.gd +++ b/inventory/player_bag.gd @@ -39,7 +39,7 @@ func add_item(item_id: StringName, quantity: int = 1) -> bool: func can_add_item(item_id: StringName, quantity: int = 1) -> bool: - var item: ItemDataType = _resolve_valid_item(item_id) + var item: ItemDataType = _resolve_available_item(item_id) if item == null or quantity <= 0: return false var existing: OwnedItemType = get_owned_item(item_id) @@ -156,3 +156,8 @@ func _resolve_valid_item(item_id: StringName) -> ItemDataType: return null var item: ItemDataType = _catalog.get_item_by_id(item_id) return item if item != null and item.is_valid() else null + + +func _resolve_available_item(item_id: StringName) -> ItemDataType: + var item: ItemDataType = _resolve_valid_item(item_id) + return item if item != null and item.active else null diff --git a/inventory/player_hotbar.gd b/inventory/player_hotbar.gd index e9dbcec..a71ab68 100644 --- a/inventory/player_hotbar.gd +++ b/inventory/player_hotbar.gd @@ -249,7 +249,7 @@ func _can_assign(item_id: StringName) -> bool: if _catalog == null: return false var item: ItemDataType = _catalog.get_item_by_id(item_id) - return item != null and item.is_valid() and item.hotbar_allowed + return item != null and item.is_available() and item.hotbar_allowed func _can_assign_fish(catch_id: StringName) -> bool: diff --git a/items/catalog/art_kit.tres b/items/catalog/art_kit.tres index ee5b28e..e59705d 100644 --- a/items/catalog/art_kit.tres +++ b/items/catalog/art_kit.tres @@ -7,7 +7,7 @@ script = ExtResource("1_item") item_id = &"art_kit" display_name = "Art Kit" -description = "Press P in the game world to paint!" +description = "Select the Art Kit in the Hotbar to paint." category = 1 icon = ExtResource("2_icon") stackable = false diff --git a/items/catalog/basic_fishing_rod.tres b/items/catalog/basic_fishing_rod.tres index f43f9ca..9849b89 100644 --- a/items/catalog/basic_fishing_rod.tres +++ b/items/catalog/basic_fishing_rod.tres @@ -1,6 +1,6 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] +[gd_resource type="Resource" script_class="FishingRodData" load_steps=3 format=3] -[ext_resource type="Script" path="res://items/item_data.gd" id="1_script"] +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] [ext_resource type="Texture2D" path="res://items/icons/equipment/64_rod_base_thick.png" id="2_icon"] [resource] @@ -8,6 +8,7 @@ script = ExtResource("1_script") item_id = &"basic_fishing_rod" display_name = "basic fishing rod" description = "a dependable starter rod. select it to cast." +active = true category = 0 icon = ExtResource("2_icon") stackable = false @@ -15,3 +16,8 @@ max_stack = 1 usable = true equippable = true hotbar_allowed = true +shop_order = 1 +shop_price = 0 +unlock_level = 1 +effect_summary = "neutral baseline with no catch-selection or handling modifiers." +tradeoff = "none." diff --git a/items/catalog/cooler_expansion.tres b/items/catalog/cooler_expansion.tres deleted file mode 100644 index 2e9ed29..0000000 --- a/items/catalog/cooler_expansion.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"] -[ext_resource type="Texture2D" path="res://items/icons/placeholder/cooler_expansion.svg" id="2"] - -[resource] -script = ExtResource("1") -item_id = &"cooler_expansion" -display_name = "cooler expansion" -description = "permanently increases the number of fish the cooler can hold." -category = 4 -icon = ExtResource("2") -stackable = false -max_stack = 1 -usable = false -equippable = false -hotbar_allowed = false diff --git a/items/catalog/crab_net.tres b/items/catalog/crab_net.tres new file mode 100644 index 0000000..104c9ff --- /dev/null +++ b/items/catalog/crab_net.tres @@ -0,0 +1,16 @@ +[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/item_data.gd" id="1_item"] + +[resource] +script = ExtResource("1_item") +item_id = &"crab_net" +display_name = "crab net" +description = "a hand net for catching crabs and other small shore animals." +active = true +category = 1 +stackable = false +max_stack = 1 +usable = false +equippable = true +hotbar_allowed = true diff --git a/items/catalog/item_catalog.tres b/items/catalog/item_catalog.tres index 43377ba..1715678 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=18 format=3] +[gd_resource type="Resource" script_class="ItemCatalog" load_steps=39 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"] @@ -6,7 +6,6 @@ [ext_resource type="Resource" path="res://items/catalog/energy_drink.tres" id="4_energy"] [ext_resource type="Resource" path="res://items/catalog/snack.tres" id="5_snack"] [ext_resource type="Resource" path="res://items/catalog/fish_finder.tres" id="6_finder"] -[ext_resource type="Resource" path="res://items/catalog/cooler_expansion.tres" id="7_cooler"] [ext_resource type="Resource" path="res://items/catalog/art_kit.tres" id="8_art_kit"] [ext_resource type="Resource" path="res://items/catalog/worms.tres" id="9_worms"] [ext_resource type="Resource" path="res://items/catalog/snails.tres" id="10_snails"] @@ -17,7 +16,29 @@ [ext_resource type="Resource" path="res://items/catalog/luminous_roe.tres" id="15_roe"] [ext_resource type="Resource" path="res://items/catalog/the_standby.tres" id="16_standby"] [ext_resource type="Resource" path="res://items/catalog/batteries.tres" id="17_batteries"] +[ext_resource type="Resource" path="res://items/catalog/rods/cardboard_classic_rod.tres" id="18_cardboard"] +[ext_resource type="Resource" path="res://items/catalog/rods/pond_skipper_rod.tres" id="19_pond"] +[ext_resource type="Resource" path="res://items/catalog/rods/river_runner_rod.tres" id="20_river"] +[ext_resource type="Resource" path="res://items/catalog/rods/lake_lunker_rod.tres" id="21_lake"] +[ext_resource type="Resource" path="res://items/catalog/rods/salt_spray_rod.tres" id="22_salt"] +[ext_resource type="Resource" path="res://items/catalog/rods/whisker_stick_rod.tres" id="23_whisker"] +[ext_resource type="Resource" path="res://items/catalog/rods/reef_raker_rod.tres" id="24_reef"] +[ext_resource type="Resource" path="res://items/catalog/rods/moonbeam_rod.tres" id="25_moonbeam"] +[ext_resource type="Resource" path="res://items/catalog/rods/sun_chaser_rod.tres" id="26_sun"] +[ext_resource type="Resource" path="res://items/catalog/rods/rainmaker_rod.tres" id="27_rain"] +[ext_resource type="Resource" path="res://items/catalog/rods/fogcatcher_rod.tres" id="28_fog"] +[ext_resource type="Resource" path="res://items/catalog/rods/field_guide_rod.tres" id="29_guide"] +[ext_resource type="Resource" path="res://items/catalog/rods/small_fry_rod.tres" id="30_small"] +[ext_resource type="Resource" path="res://items/catalog/rods/heavy_hauler_rod.tres" id="31_heavy"] +[ext_resource type="Resource" path="res://items/catalog/rods/pocket_rocket_rod.tres" id="32_rocket"] +[ext_resource type="Resource" path="res://items/catalog/rods/lucky_twig_rod.tres" id="33_lucky"] +[ext_resource type="Resource" path="res://items/catalog/rods/showboat_rod.tres" id="34_showboat"] +[ext_resource type="Resource" path="res://items/catalog/rods/deep_blue_rod.tres" id="35_deep"] +[ext_resource type="Resource" path="res://items/catalog/rods/oddity_string_rod.tres" id="36_oddity"] +[ext_resource type="Resource" path="res://items/catalog/rods/aurora_rod.tres" id="37_aurora"] +[ext_resource type="Resource" path="res://items/catalog/magnet.tres" id="38_magnet"] +[ext_resource type="Resource" path="res://items/catalog/crab_net.tres" id="39_crab_net"] [resource] script = ExtResource("1_script") -items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("7_cooler"), 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")] +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")] diff --git a/items/catalog/luminous_roe.tres b/items/catalog/luminous_roe.tres index 7814f42..677563e 100644 --- a/items/catalog/luminous_roe.tres +++ b/items/catalog/luminous_roe.tres @@ -7,7 +7,7 @@ script = ExtResource("1") item_id = &"luminous_roe" display_name = "luminous roe" -description = "rare glowing roe with the strongest rare catch odds." +description = "rare glowing roe with the strongest rarity and quality odds." icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_6"]) diff --git a/items/catalog/magnet.tres b/items/catalog/magnet.tres new file mode 100644 index 0000000..eb2e25c --- /dev/null +++ b/items/catalog/magnet.tres @@ -0,0 +1,16 @@ +[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/item_data.gd" id="1"] + +[resource] +script = ExtResource("1") +item_id = &"magnet" +display_name = "magnet" +description = "a heavy magnet ready for future magnet fishing." +active = true +category = 1 +stackable = false +max_stack = 1 +usable = false +equippable = false +hotbar_allowed = false diff --git a/items/catalog/rods/aurora_rod.tres b/items/catalog/rods/aurora_rod.tres new file mode 100644 index 0000000..303ce95 --- /dev/null +++ b/items/catalog/rods/aurora_rod.tres @@ -0,0 +1,25 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"aurora_rod" +display_name = "aurora rod" +description = "a shifting ribbon of color for anglers who have tried nearly everything else." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 21 +shop_price = 5000 +unlock_level = 15 +bite_time_multiplier = 1.35 +reel_speed_multiplier = 0.9 +rarity_weight_multipliers = Array[float]([0.85, 1.00, 1.15, 1.30, 1.45]) +quality_weight_multipliers = Array[float]([0.80, 0.95, 1.15, 1.30, 1.45]) +effect_summary = "modestly improves both species rarity and catch quality across all eligible fish." +tradeoff = "the slowest bite cycle in the set; intended as an expensive late-game sidegrade." diff --git a/items/catalog/rods/cardboard_classic_rod.tres b/items/catalog/rods/cardboard_classic_rod.tres new file mode 100644 index 0000000..97a304f --- /dev/null +++ b/items/catalog/rods/cardboard_classic_rod.tres @@ -0,0 +1,26 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"cardboard_classic_rod" +display_name = "cardboard classic" +description = "a cheerful folded rod that gets bites quickly, if not impressively." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 2 +shop_price = 40 +unlock_level = 1 +bite_time_multiplier = 0.85 +reel_speed_multiplier = 0.95 +barrier_power_multiplier = 0.85 +rarity_weight_multipliers = Array[float]([1.20, 1.00, 0.85, 0.70, 0.55]) +quality_weight_multipliers = Array[float]([1.20, 1.05, 0.85, 0.70, 0.55]) +effect_summary = "shorter bite waits and a low-cost early sidegrade." +tradeoff = "leans toward common, lower-quality fish and has weaker handling." diff --git a/items/catalog/rods/deep_blue_rod.tres b/items/catalog/rods/deep_blue_rod.tres new file mode 100644 index 0000000..2d9b03f --- /dev/null +++ b/items/catalog/rods/deep_blue_rod.tres @@ -0,0 +1,27 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"deep_blue_rod" +display_name = "deep blue" +description = "a heavy navy rod for enormous shapes moving far offshore." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 19 +shop_price = 2500 +unlock_level = 12 +preferred_collection_groups = Array[StringName]([&"Coastal sharks", &"Large coastal predators", &"Oceanic billfish", &"Oceanic pelagic fish", &"Oceanic tuna", &"Open-ocean giants"]) +affinity_weight_multiplier = 1.75 +weight_roll_bias = 0.32 +bite_time_multiplier = 1.28 +reel_speed_multiplier = 0.82 +barrier_power_multiplier = 1.25 +effect_summary = "favors large ocean predator and pelagic collection groups and biases weight upward." +tradeoff = "very long bite waits and slower reeling." diff --git a/items/catalog/rods/field_guide_rod.tres b/items/catalog/rods/field_guide_rod.tres new file mode 100644 index 0000000..2b19f30 --- /dev/null +++ b/items/catalog/rods/field_guide_rod.tres @@ -0,0 +1,23 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"field_guide_rod" +display_name = "field guide rod" +description = "a note-covered rod that keeps tugging toward something new." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 13 +shop_price = 1350 +unlock_level = 8 +undiscovered_weight_multiplier = 1.9 +bite_time_multiplier = 1.15 +effect_summary = "substantially increases the relative weight of eligible undiscovered species." +tradeoff = "longer bite waits and no bonus after a species is discovered." diff --git a/items/catalog/rods/fogcatcher_rod.tres b/items/catalog/rods/fogcatcher_rod.tres new file mode 100644 index 0000000..c0fdd25 --- /dev/null +++ b/items/catalog/rods/fogcatcher_rod.tres @@ -0,0 +1,23 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"fogcatcher_rod" +display_name = "fogcatcher" +description = "a hazy blue rod for fish that prefer not to be seen." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 12 +shop_price = 1100 +unlock_level = 7 +weather_affinity = 2 +affinity_weight_multiplier = 1.65 +effect_summary = "strongly favors eligible fog-active fish." +tradeoff = "its affinity provides no benefit outside foggy weather." diff --git a/items/catalog/rods/heavy_hauler_rod.tres b/items/catalog/rods/heavy_hauler_rod.tres new file mode 100644 index 0000000..bc68c78 --- /dev/null +++ b/items/catalog/rods/heavy_hauler_rod.tres @@ -0,0 +1,25 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"heavy_hauler_rod" +display_name = "heavy hauler" +description = "a thick rod that would rather bring home one enormous fish." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 15 +shop_price = 1500 +unlock_level = 9 +weight_roll_bias = 0.28 +bite_time_multiplier = 1.18 +reel_speed_multiplier = 0.88 +barrier_power_multiplier = 1.2 +effect_summary = "biases fish weight upward and adds barrier power." +tradeoff = "longer bite waits and slower reeling." diff --git a/items/catalog/rods/lake_lunker_rod.tres b/items/catalog/rods/lake_lunker_rod.tres new file mode 100644 index 0000000..4d30f56 --- /dev/null +++ b/items/catalog/rods/lake_lunker_rod.tres @@ -0,0 +1,25 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"lake_lunker_rod" +display_name = "lake lunker" +description = "a broad, patient rod that favors hefty catches from open lakes." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 5 +shop_price = 400 +unlock_level = 4 +preferred_location_tags = Array[StringName]([&"lake"]) +affinity_weight_multiplier = 1.45 +weight_roll_bias = 0.12 +bite_time_multiplier = 1.05 +effect_summary = "favors eligible lake fish and gently biases weight rolls upward." +tradeoff = "slightly longer bite waits." diff --git a/items/catalog/rods/lucky_twig_rod.tres b/items/catalog/rods/lucky_twig_rod.tres new file mode 100644 index 0000000..8c26b19 --- /dev/null +++ b/items/catalog/rods/lucky_twig_rod.tres @@ -0,0 +1,25 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"lucky_twig_rod" +display_name = "lucky twig" +description = "an ordinary-looking branch that insists it is extremely lucky." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 17 +shop_price = 1800 +unlock_level = 10 +bite_time_multiplier = 1.2 +reel_speed_multiplier = 0.9 +barrier_power_multiplier = 0.85 +rarity_weight_multipliers = Array[float]([0.65, 1.00, 1.35, 1.65, 2.00]) +effect_summary = "strongly shifts species rarity toward rare, epic, and legendary fish." +tradeoff = "longer bite waits, slower reeling, and weaker barrier power." diff --git a/items/catalog/rods/moonbeam_rod.tres b/items/catalog/rods/moonbeam_rod.tres new file mode 100644 index 0000000..df83634 --- /dev/null +++ b/items/catalog/rods/moonbeam_rod.tres @@ -0,0 +1,25 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"moonbeam_rod" +display_name = "moonbeam rod" +description = "a pale rod that wakes up when the island goes quiet." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 9 +shop_price = 950 +unlock_level = 6 +time_affinity = 2 +affinity_weight_multiplier = 1.5 +bite_time_multiplier = 1.08 +quality_weight_multipliers = Array[float]([0.95, 0.98, 1.00, 1.08, 1.25]) +effect_summary = "favors eligible night fish and gives shiny quality a modest lift." +tradeoff = "slightly longer bite waits." diff --git a/items/catalog/rods/oddity_string_rod.tres b/items/catalog/rods/oddity_string_rod.tres new file mode 100644 index 0000000..8c51739 --- /dev/null +++ b/items/catalog/rods/oddity_string_rod.tres @@ -0,0 +1,25 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"oddity_string_rod" +display_name = "oddity string" +description = "a crooked spool of string that attracts fish with questionable silhouettes." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 20 +shop_price = 2600 +unlock_level = 12 +preferred_collection_groups = Array[StringName]([&"Brackish oddities", &"Coastal oddities", &"Deep-sea oddities", &"Freshwater oddities", &"Reef oddities"]) +affinity_weight_multiplier = 1.8 +bite_time_multiplier = 1.18 +quality_weight_multipliers = Array[float]([1.10, 1.05, 0.95, 0.90, 0.90]) +effect_summary = "favors eligible fish from authored oddity collection groups." +tradeoff = "longer bite waits and a slight average-quality penalty." diff --git a/items/catalog/rods/pocket_rocket_rod.tres b/items/catalog/rods/pocket_rocket_rod.tres new file mode 100644 index 0000000..b93d348 --- /dev/null +++ b/items/catalog/rods/pocket_rocket_rod.tres @@ -0,0 +1,25 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"pocket_rocket_rod" +display_name = "pocket rocket" +description = "a buzzing pocket-sized rod for anglers in a hurry." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 16 +shop_price = 1400 +unlock_level = 9 +bite_time_multiplier = 0.7 +reel_speed_multiplier = 1.25 +barrier_power_multiplier = 0.8 +rarity_weight_multipliers = Array[float]([1.15, 1.00, 0.85, 0.72, 0.60]) +effect_summary = "greatly shortens bite waits and reels faster." +tradeoff = "reduces higher-rarity odds and barrier power." diff --git a/items/catalog/rods/pond_skipper_rod.tres b/items/catalog/rods/pond_skipper_rod.tres new file mode 100644 index 0000000..adb18e4 --- /dev/null +++ b/items/catalog/rods/pond_skipper_rod.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"pond_skipper_rod" +display_name = "pond skipper" +description = "a springy little rod that feels at home beside quiet ponds." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 3 +shop_price = 150 +unlock_level = 2 +preferred_location_tags = Array[StringName]([&"starter_pond", &"pond"]) +affinity_weight_multiplier = 1.5 +bite_time_multiplier = 0.95 +effect_summary = "increases the relative selection weight of eligible pond fish." +tradeoff = "none; a focused sidegrade rather than a direct upgrade." diff --git a/items/catalog/rods/rainmaker_rod.tres b/items/catalog/rods/rainmaker_rod.tres new file mode 100644 index 0000000..3a407fd --- /dev/null +++ b/items/catalog/rods/rainmaker_rod.tres @@ -0,0 +1,23 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"rainmaker_rod" +display_name = "rainmaker" +description = "a drip-shaped rod that becomes unusually lucky in the rain." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 11 +shop_price = 1100 +unlock_level = 7 +weather_affinity = 1 +affinity_weight_multiplier = 1.65 +effect_summary = "strongly favors eligible rain-active fish." +tradeoff = "its affinity provides no benefit outside rainy weather." diff --git a/items/catalog/rods/reef_raker_rod.tres b/items/catalog/rods/reef_raker_rod.tres new file mode 100644 index 0000000..2b3ff91 --- /dev/null +++ b/items/catalog/rods/reef_raker_rod.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"reef_raker_rod" +display_name = "reef raker" +description = "a bright hooked rod tuned for the crowded life around reefs." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 8 +shop_price = 900 +unlock_level = 6 +preferred_collection_groups = Array[StringName]([&"Cold reef fish", &"Reef ambush fish", &"Reef eels", &"Reef flatfish", &"Reef grazers", &"Reef oddities", &"Reef ornamentals", &"Reef predators", &"Reef schooling fish", &"Reef snapper", &"Reef wrasses", &"Temperate reef fish", &"Temperate reef predators"]) +affinity_weight_multiplier = 1.45 +quality_weight_multipliers = Array[float]([0.95, 1.00, 1.12, 1.05, 1.00]) +effect_summary = "favors eligible reef collection groups and modestly favors impressive quality." +tradeoff = "no advantage away from reef-associated fish." diff --git a/items/catalog/rods/river_runner_rod.tres b/items/catalog/rods/river_runner_rod.tres new file mode 100644 index 0000000..7f0a464 --- /dev/null +++ b/items/catalog/rods/river_runner_rod.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"river_runner_rod" +display_name = "river runner" +description = "a lively rod made to keep pace with moving river water." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 4 +shop_price = 250 +unlock_level = 3 +preferred_location_tags = Array[StringName]([&"river"]) +affinity_weight_multiplier = 1.5 +reel_speed_multiplier = 1.05 +effect_summary = "increases the relative selection weight of eligible river fish." +tradeoff = "none; a focused sidegrade rather than a direct upgrade." diff --git a/items/catalog/rods/salt_spray_rod.tres b/items/catalog/rods/salt_spray_rod.tres new file mode 100644 index 0000000..f3e6102 --- /dev/null +++ b/items/catalog/rods/salt_spray_rod.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"salt_spray_rod" +display_name = "salt spray" +description = "a sea-green rod that seems happiest with salt on the line." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 6 +shop_price = 600 +unlock_level = 5 +preferred_location_tags = Array[StringName]([&"ocean"]) +affinity_weight_multiplier = 1.5 +rarity_weight_multipliers = Array[float]([0.95, 1.00, 1.05, 1.05, 1.05]) +effect_summary = "increases the relative selection weight of eligible ocean fish." +tradeoff = "none; a focused sidegrade rather than a direct upgrade." diff --git a/items/catalog/rods/showboat_rod.tres b/items/catalog/rods/showboat_rod.tres new file mode 100644 index 0000000..99bef41 --- /dev/null +++ b/items/catalog/rods/showboat_rod.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"showboat_rod" +display_name = "showboat" +description = "a theatrical rod that refuses to land an unimpressive fish quietly." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 18 +shop_price = 2000 +unlock_level = 11 +bite_time_multiplier = 1.18 +reel_speed_multiplier = 0.9 +quality_weight_multipliers = Array[float]([0.50, 0.75, 1.35, 1.70, 2.10]) +effect_summary = "strongly shifts catch quality toward impressive, exceptional, and shiny." +tradeoff = "longer bite waits and slower reeling; species rarity is unchanged." diff --git a/items/catalog/rods/small_fry_rod.tres b/items/catalog/rods/small_fry_rod.tres new file mode 100644 index 0000000..a1631f0 --- /dev/null +++ b/items/catalog/rods/small_fry_rod.tres @@ -0,0 +1,23 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"small_fry_rod" +display_name = "small fry" +description = "a tiny rod for anglers who value character over size." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 14 +shop_price = 1200 +unlock_level = 8 +weight_roll_bias = -0.25 +quality_weight_multipliers = Array[float]([0.75, 0.90, 1.25, 1.40, 1.55]) +effect_summary = "biases fish weight downward while improving high-quality outcomes." +tradeoff = "produces smaller catches on average." diff --git a/items/catalog/rods/sun_chaser_rod.tres b/items/catalog/rods/sun_chaser_rod.tres new file mode 100644 index 0000000..22c0803 --- /dev/null +++ b/items/catalog/rods/sun_chaser_rod.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"sun_chaser_rod" +display_name = "sun chaser" +description = "a warm cream rod that likes bright mornings and busy afternoons." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 10 +shop_price = 950 +unlock_level = 6 +time_affinity = 1 +affinity_weight_multiplier = 1.4 +rarity_weight_multipliers = Array[float]([0.95, 1.00, 1.12, 1.08, 1.00]) +effect_summary = "favors eligible daytime fish and modestly improves rare-species odds." +tradeoff = "none; a focused sidegrade rather than a direct upgrade." diff --git a/items/catalog/rods/whisker_stick_rod.tres b/items/catalog/rods/whisker_stick_rod.tres new file mode 100644 index 0000000..a49ff63 --- /dev/null +++ b/items/catalog/rods/whisker_stick_rod.tres @@ -0,0 +1,24 @@ +[gd_resource type="Resource" script_class="FishingRodData" load_steps=2 format=3] + +[ext_resource type="Script" path="res://items/fishing_rod_data.gd" id="1_script"] + +[resource] +script = ExtResource("1_script") +item_id = &"whisker_stick_rod" +display_name = "whisker stick" +description = "a bendy rod with a suspicious talent for finding catfish." +active = false +category = 0 +stackable = false +max_stack = 1 +usable = true +equippable = true +hotbar_allowed = true +shop_order = 7 +shop_price = 750 +unlock_level = 5 +preferred_collection_groups = Array[StringName]([&"Freshwater catfish"]) +affinity_weight_multiplier = 1.7 +bite_time_multiplier = 1.08 +effect_summary = "strongly favors eligible fish in the Freshwater catfish collection group." +tradeoff = "slightly longer bite waits while it searches for whiskers." diff --git a/items/catalog/shrimp.tres b/items/catalog/shrimp.tres index e308163..6864265 100644 --- a/items/catalog/shrimp.tres +++ b/items/catalog/shrimp.tres @@ -7,7 +7,7 @@ script = ExtResource("1") item_id = &"shrimp" display_name = "shrimp" -description = "lively bait that noticeably improves rare catch chances." +description = "lively bait that noticeably improves rarity and quality odds." icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_2"]) diff --git a/items/catalog/snails.tres b/items/catalog/snails.tres index 12d4497..adddefb 100644 --- a/items/catalog/snails.tres +++ b/items/catalog/snails.tres @@ -7,7 +7,7 @@ script = ExtResource("1") item_id = &"snails" display_name = "snails" -description = "slow-moving bait with a better chance for rarer catches." +description = "slow-moving bait with better rarity and quality odds." icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_1"]) diff --git a/items/catalog/squid_chunks.tres b/items/catalog/squid_chunks.tres index dfe245f..bf0da93 100644 --- a/items/catalog/squid_chunks.tres +++ b/items/catalog/squid_chunks.tres @@ -7,7 +7,7 @@ script = ExtResource("1") item_id = &"squid_chunks" display_name = "squid chunks" -description = "rich cut bait that strongly favors rarer catches." +description = "rich cut bait that strongly favors rarer, higher-quality catches." icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_3"]) diff --git a/items/catalog/whole_anchovy.tres b/items/catalog/whole_anchovy.tres index cbc60b6..b52018b 100644 --- a/items/catalog/whole_anchovy.tres +++ b/items/catalog/whole_anchovy.tres @@ -7,7 +7,7 @@ script = ExtResource("1") item_id = &"whole_anchovy" display_name = "sardine" -description = "valuable oily schooling bait tuned for exceptional catches." +description = "valuable oily schooling bait tuned for exceptional rarity and quality odds." icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_5"]) diff --git a/items/catalog/whole_sardine.tres b/items/catalog/whole_sardine.tres index c5d0163..78a0e0e 100644 --- a/items/catalog/whole_sardine.tres +++ b/items/catalog/whole_sardine.tres @@ -7,7 +7,7 @@ script = ExtResource("1") item_id = &"whole_sardine" display_name = "minnow" -description = "small live bait with high rare catch potential." +description = "small live bait with strong rarity and quality potential." icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_4"]) diff --git a/items/catalog/worms.tres b/items/catalog/worms.tres index f01258e..71bd744 100644 --- a/items/catalog/worms.tres +++ b/items/catalog/worms.tres @@ -7,7 +7,7 @@ script = ExtResource("1") item_id = &"worms" display_name = "worms" -description = "basic bait that opens a small chance for rarer catches." +description = "basic bait that opens a small chance for rarer, higher-quality catches." icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"worm", &"bait_rarity_0"]) diff --git a/items/fishing_rod_data.gd b/items/fishing_rod_data.gd new file mode 100644 index 0000000..d1c8e7c --- /dev/null +++ b/items/fishing_rod_data.gd @@ -0,0 +1,151 @@ +class_name FishingRodData +extends ItemData + +const FishDataType = preload("res://fish/fish_data.gd") +const FishingContextType = preload("res://fishing/fishing_context.gd") + +enum TimeAffinity { + ANY, + DAY, + NIGHT, +} + +enum WeatherAffinity { + ANY, + RAIN, + FOG, +} + +const MULTIPLIER_COUNT: int = 5 + +@export_category("Rod Shop") +@export var shop_enabled: bool = true +@export_range(1, 999, 1) var shop_order: int = 1 +@export_range(0, 1000000, 1) var shop_price: int = 0 +@export_range(1, 100000, 1) var unlock_level: int = 1 + +@export_category("Rod Presentation") +## Optional authored visual. A null scene deliberately uses the current +## temporary rod mesh so new rod data never requires a model to function. +@export var rod_model_scene: PackedScene +@export var rod_model_transform: Transform3D = Transform3D.IDENTITY +@export var rod_tip_position := Vector3(0.0, 1.1, 0.0) + +@export_category("Rod Affinity") +@export var preferred_location_tags: Array[StringName] = [] +@export var preferred_collection_groups: Array[StringName] = [] +@export var time_affinity: TimeAffinity = TimeAffinity.ANY +@export var weather_affinity: WeatherAffinity = WeatherAffinity.ANY +@export_range(0.0, 10.0, 0.01) var affinity_weight_multiplier: float = 1.0 +@export_range(0.0, 10.0, 0.01) var undiscovered_weight_multiplier: float = 1.0 + +@export_category("Rod Catch Modifiers") +@export_range(-0.5, 0.5, 0.01) var weight_roll_bias: float = 0.0 +@export_range(0.05, 5.0, 0.01) var bite_time_multiplier: float = 1.0 +@export_range(0.05, 5.0, 0.01) var reel_speed_multiplier: float = 1.0 +@export_range(0.05, 5.0, 0.01) var barrier_power_multiplier: float = 1.0 +@export var rarity_weight_multipliers: Array[float] = [ + 1.0, 1.0, 1.0, 1.0, 1.0, +] +@export var quality_weight_multipliers: Array[float] = [ + 1.0, 1.0, 1.0, 1.0, 1.0, +] +@export_multiline var effect_summary: String +@export_multiline var tradeoff: String + + +func is_valid() -> bool: + return ( + super.is_valid() + and category == Category.ROD + and shop_order >= 1 + and shop_price >= 0 + and unlock_level >= 1 + and rarity_weight_multipliers.size() == MULTIPLIER_COUNT + and quality_weight_multipliers.size() == MULTIPLIER_COUNT + and _multipliers_are_non_negative(rarity_weight_multipliers) + and _multipliers_are_non_negative(quality_weight_multipliers) + and affinity_weight_multiplier >= 0.0 + and undiscovered_weight_multiplier >= 0.0 + and bite_time_multiplier > 0.0 + and reel_speed_multiplier > 0.0 + and barrier_power_multiplier > 0.0 + ) + + +func is_shop_available() -> bool: + return is_available() and shop_enabled and icon != null + + +func get_affinity_multiplier( + fish: FishDataType, + context: FishingContextType, +) -> float: + if fish == null or context == null or not _has_authored_affinity(): + return 1.0 + if ( + not preferred_location_tags.is_empty() + and not _has_any_match( + preferred_location_tags, + context.location_tags, + ) + ): + return 1.0 + if ( + not preferred_collection_groups.is_empty() + and fish.collection_group not in preferred_collection_groups + ): + return 1.0 + match time_affinity: + TimeAffinity.DAY: + if context.is_night or context.is_day_night_transition: + return 1.0 + TimeAffinity.NIGHT: + if not context.is_night or context.is_day_night_transition: + return 1.0 + match weather_affinity: + WeatherAffinity.RAIN: + if not context.is_raining: + return 1.0 + WeatherAffinity.FOG: + if not context.is_foggy: + return 1.0 + return maxf(affinity_weight_multiplier, 0.0) + + +func get_rarity_multiplier(rarity: int) -> float: + if rarity < 0 or rarity >= rarity_weight_multipliers.size(): + return 1.0 + return maxf(rarity_weight_multipliers[rarity], 0.0) + + +func get_quality_multiplier(quality: int) -> float: + if quality < 0 or quality >= quality_weight_multipliers.size(): + return 1.0 + return maxf(quality_weight_multipliers[quality], 0.0) + + +func _has_authored_affinity() -> bool: + return ( + not preferred_location_tags.is_empty() + or not preferred_collection_groups.is_empty() + or time_affinity != TimeAffinity.ANY + or weather_affinity != WeatherAffinity.ANY + ) + + +func _has_any_match( + expected: Array[StringName], + actual: Array[StringName], +) -> bool: + for value: StringName in expected: + if value in actual: + return true + return false + + +func _multipliers_are_non_negative(values: Array[float]) -> bool: + for value: float in values: + if not is_finite(value) or value < 0.0: + return false + return true diff --git a/items/fishing_rod_data.gd.uid b/items/fishing_rod_data.gd.uid new file mode 100644 index 0000000..324cfd7 --- /dev/null +++ b/items/fishing_rod_data.gd.uid @@ -0,0 +1 @@ +uid://car3ne3q6dhln diff --git a/items/icons/placeholder/cooler_expansion.svg b/items/icons/placeholder/cooler_expansion.svg deleted file mode 100644 index 105bff7..0000000 --- a/items/icons/placeholder/cooler_expansion.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/items/item_catalog.gd b/items/item_catalog.gd index b4245a0..511604b 100644 --- a/items/item_catalog.gd +++ b/items/item_catalog.gd @@ -28,3 +28,16 @@ func get_valid_items() -> Array[ItemDataType]: seen[item.item_id] = true result.append(item) return result + + +func get_available_item_by_id(item_id: StringName) -> ItemDataType: + var item: ItemDataType = get_item_by_id(item_id) + return item if item != null and item.is_available() else null + + +func get_available_items() -> Array[ItemDataType]: + var result: Array[ItemDataType] = [] + for item: ItemDataType in get_valid_items(): + if item.active: + result.append(item) + return result diff --git a/items/item_data.gd b/items/item_data.gd index a34cec0..72ac0d6 100644 --- a/items/item_data.gd +++ b/items/item_data.gd @@ -15,6 +15,11 @@ enum Category { @export var item_id: StringName @export var display_name: String @export_multiline var description: String +@export_category("Developer Catalog") +## Inactive items remain resolvable for existing saves, but cannot be newly +## acquired, equipped, used, or presented in shops. +@export var active: bool = true +@export_category("Item") @export var category: Category = Category.UTILITY @export var bait_tags: Array[StringName] = [] @export var lure_effects: Array[StringName] = [] @@ -35,6 +40,10 @@ func is_valid() -> bool: ) +func is_available() -> bool: + return active and is_valid() + + func get_category_name() -> String: return Category.keys()[category].to_lower() diff --git a/jobs/job_catalog.gd b/jobs/job_catalog.gd index 009a152..0b9c717 100644 --- a/jobs/job_catalog.gd +++ b/jobs/job_catalog.gd @@ -90,7 +90,7 @@ static func generate_daily_jobs( )) var species: Array[FishDataType] = [] for fish: FishDataType in candidates: - if fish != null and fish.is_selectable(): + if fish != null and fish.is_fishable(): species.append(fish) if not species.is_empty(): species.sort_custom( diff --git a/jobs/player_job_service.gd b/jobs/player_job_service.gd index 6f79e8f..684c5ea 100644 --- a/jobs/player_job_service.gd +++ b/jobs/player_job_service.gd @@ -150,6 +150,10 @@ func get_plan_id() -> String: return _active_plan_id +func has_active_board() -> bool: + return validate_board(_active_board) + + func get_time_until_refresh_text() -> String: if _active_plan_id.is_empty() or _world_time == null: return "daily jobs unavailable" @@ -450,7 +454,7 @@ func _generate_host_board(cycle: int) -> void: var candidates: Array[FishDataType] = [] if _catalog != null: for fish: FishDataType in _catalog.candidates: - if fish != null: + if fish != null and fish.is_fishable(): candidates.append(fish) var schedule_anchor_index: int = _current_weather_segment() var allow_weather_jobs: bool = ( @@ -684,7 +688,7 @@ func _registered_species_count() -> int: var count: int = 0 if _catalog != null: for fish: FishDataType in _catalog.candidates: - if fish != null and fish.is_selectable(): + if fish != null and fish.is_fishable(): count += 1 return count @@ -723,7 +727,7 @@ func _matches_canonical_board(board: Dictionary) -> bool: var candidates: Array[FishDataType] = [] if _catalog != null: for fish: FishDataType in _catalog.candidates: - if fish != null: + if fish != null and fish.is_fishable(): candidates.append(fish) var anchor_index: int = int(board.get("schedule_anchor_index", -1)) var allow_weather_jobs: bool = ( diff --git a/main/main.gd b/main/main.gd index 51929a0..d471851 100644 --- a/main/main.gd +++ b/main/main.gd @@ -24,6 +24,8 @@ const PauseMenuType = preload("res://ui/pause_menu.gd") const ItemCatalogType = preload("res://items/item_catalog.gd") const ArtShopStockType = preload("res://economy/art_shop_stock.gd") const ItemDataType = preload("res://items/item_data.gd") +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") +const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd") const FishingShopType = preload("res://ui/fishing_shop.gd") const FishingShopInteractionType = preload( "res://world/fishing_shop_interaction.gd" @@ -37,6 +39,9 @@ const PixelationResetOverlayType = preload( const WorldPixelationPostprocessType = preload( "res://main/world_pixelation_postprocess.gd" ) +const RuntimePerformanceProfileType = preload( + "res://main/runtime_performance_profile.gd" +) const NetworkSessionType = preload("res://network/network_session.gd") const DiscoveryClientType = preload("res://network/discovery_client.gd") const DedicatedServerConfigType = preload( @@ -99,6 +104,15 @@ const NetworkWorldWeatherServiceType = preload( const RainAmbienceType = preload("res://world/rain_ambience.gd") const PlayerJobServiceType = preload("res://jobs/player_job_service.gd") const NetworkJobServiceType = preload("res://network/network_job_service.gd") +const GatherableCatalogType = preload( + "res://gathering/gatherable_catalog.gd" +) +const GatheringControllerType = preload( + "res://gathering/gathering_controller.gd" +) +const NetworkWorldSpawnServiceType = preload( + "res://network/network_world_spawn_service.gd" +) const TITLE_MUSIC_SILENCE_DB: float = -80.0 const TIME_CROSSING_EPSILON_HOURS: float = 0.000001 @@ -110,6 +124,7 @@ const SHOP_PATTERN_SCALE: float = 1.75 @export var pelican_buyer_profile: FishBuyerProfileType @export var main_shop_buyer_profile: FishBuyerProfileType @export var item_catalog: ItemCatalogType +@export var gatherable_catalog: GatherableCatalogType @export_category("Title Music") @export_range(-40.0, 0.0, 0.5) var title_music_volume_db: float = -6.0 @export_range(0.0, 10.0, 0.05) var title_music_fade_out_seconds: float = 5.0 @@ -149,6 +164,12 @@ const SHOP_PATTERN_SCALE: float = 1.75 ) @onready var _player_jobs: PlayerJobServiceType = %PlayerJobService @onready var _network_jobs: NetworkJobServiceType = %NetworkJobService +@onready var _network_world_spawns: NetworkWorldSpawnServiceType = ( + %NetworkWorldSpawnService +) +@onready var _gathering_controller: GatheringControllerType = ( + %GatheringController +) @onready var _data_root: PlayerDataRoot = %PlayerDataRoot @onready var _identity_backups: IdentityBackupService = %IdentityBackupService @onready var _network_profile: NetworkProfilePreferencesType = ( @@ -190,6 +211,7 @@ const SHOP_PATTERN_SCALE: float = 1.75 ) @onready var _players_root: Node3D = $Players @onready var _surface_drawings_root: Node3D = $SurfaceDrawings +@onready var _world_gatherables_root: Node3D = $WorldGatherables @onready var _title_background: ColorRect = %TitleBackground @onready var _player_menu_backdrop: ColorRect = %PlayerMenuBackdrop @onready var _shop_backdrop: ColorRect = %ShopBackdrop @@ -216,6 +238,7 @@ var _data_folder_dialog: FileDialog var _data_folder_picker_generation: int = 0 var _restore_data_setup_after_picker: bool = false var _application_initialized := false +var _performance_profile: RuntimePerformanceProfileType var _pending_existing_root_path := "" var _local_recovery_attempt_id: String = "" var _dedicated_runtime: bool = false @@ -225,10 +248,17 @@ var _rain_ambience: RainAmbienceType func _ready() -> void: + _performance_profile = RuntimePerformanceProfileType.from_environment() _dedicated_runtime = _is_dedicated_server_runtime() if _dedicated_runtime: call_deferred("_start_dedicated_server") return + _world_pixelation.set_light_performance_profile( + _performance_profile.is_light() + ) + _test_world.set_light_performance_profile( + _performance_profile.is_light() + ) DisplayServer.window_set_title("NETfishing") _rain_ambience = RainAmbienceType.new() _rain_ambience.name = "RainAmbience" @@ -334,6 +364,7 @@ func _start_dedicated_server() -> void: if not _network_session.set_host_open(true): _fail_dedicated_server("Could not open the dedicated server.") return + _player_jobs.begin_progression_session() if config.public_listing and not _discovery.set_discoverable(true): _network_session.disconnect_session("Discovery setup failed.") _fail_dedicated_server(_discovery.get_host_status_message()) @@ -421,6 +452,7 @@ func _initialize_application(dedicated: bool) -> void: _world_weather, _player, Callable(_player, "get_active_gameplay_camera"), + _performance_profile.is_light(), ) if not _world_time.natural_time_advanced.is_connected( _on_natural_time_advanced @@ -540,6 +572,33 @@ func _initialize_application(dedicated: bool) -> void: _save_manager, _asset_reservations ) + _network_world_spawns.setup( + _network_session, + _player_spawn_service, + _test_world, + _world_gatherables_root, + gatherable_catalog, + fish_catalog, + _player.inventory, + _player.collection_log, + _player.cooler_capacity, + _player.experience, + _save_manager, + _network_item_use, + ) + _gathering_controller.setup( + _player, + _player.bag, + _player.hotbar, + _fishing_spot, + _network_world_spawns, + ) + _network_world_spawns.local_capture_received.connect( + _fishing_spot.present_external_catch + ) + _gathering_controller.status_changed.connect( + _fishing_spot.report_external_status + ) _network_fish_showcase.setup( _network_session, _player_spawn_service, @@ -560,7 +619,7 @@ func _initialize_application(dedicated: bool) -> void: _network_player_list.set_surface_drawing_service( _network_surface_drawing ) - _network_chat.setup(_network_session) + _network_chat.setup(_network_session, _world_time, _world_weather) _network_fishing.setup( _network_session, _player_spawn_service, @@ -1277,9 +1336,7 @@ func _apply_world_pixelation(pixel_size: int) -> void: var root_viewport: Viewport = get_viewport() root_viewport.scaling_3d_mode = Viewport.SCALING_3D_MODE_NEAREST root_viewport.scaling_3d_scale = ( - 0.75 - if OS.get_environment("NETFISHING_LOW_END") == "1" - else 1.0 + _performance_profile.get_world_render_scale() ) root_viewport.msaa_3d = Viewport.MSAA_DISABLED root_viewport.screen_space_aa = Viewport.SCREEN_SPACE_AA_DISABLED @@ -1324,6 +1381,7 @@ func _set_gameplay_active(active: bool) -> void: _player.set_camera_input_enabled(active) _player.set_camera_active(active) _fishing_spot.set_gameplay_input_enabled(active) + _gathering_controller.set_gameplay_input_enabled(active) _water_recovery.set_recovery_enabled(active) _game_ui.set_gameplay_ui_enabled(active) _save_manager.set_autosave_enabled(active) @@ -1555,8 +1613,13 @@ func _on_pause_join_game_requested(endpoint: String) -> void: _join_requested_from_title = false _pending_join_endpoint = endpoint _game_ui.get_pause_menu().close_for_title_transition() + var preserved_public_join: bool = ( + _discovery.preserve_public_join_for_session_switch() + ) _network_session.disconnect_session("Connecting to another game.") if not _network_session.join_direct(endpoint): + if preserved_public_join: + _discovery.cancel_pending_public_join() _handle_failed_session_switch( "Could not begin the direct connection." ) @@ -1641,14 +1704,16 @@ func _handle_failed_session_switch(message: String) -> void: _show_title_music(true) -func _on_network_server_lost() -> void: +func _on_network_server_lost(message: String) -> void: if not _gameplay_started: return + _join_requested_from_title = false + _join_requested_from_pause = false + _pending_join_endpoint = "" _set_gameplay_active(false) var title_screen: TitleScreenType = _game_ui.get_title_screen() - title_screen.reopen() - title_screen.open_join_game_page(_pending_join_endpoint) - title_screen.report_network_error("The server connection was lost.") + title_screen.reopen_to_menu() + title_screen.report_network_error(message) _show_title_music(true) @@ -1756,19 +1821,31 @@ func _on_active_hotbar_item_changed( var item = item_catalog.get_item_by_id(item_id) var active_is_rod: bool = ( item != null + and item.is_available() and item.category == ItemDataType.Category.ROD and _player.bag.owns_item(item_id) ) var active_is_art_kit: bool = ( item_id == ArtShopStockType.ART_KIT_ITEM_ID and item != null + and item.is_available() and _player.bag.owns_item(item_id) ) - _player.set_active_item_is_rod(active_is_rod, true) + var active_is_catching_net: bool = ( + item_id == FishingShopStockType.CRAB_NET_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, + ) _player.set_active_art_kit( item.icon if item != null else null, active_is_art_kit, ) + _player.set_active_catching_net(active_is_catching_net) _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) diff --git a/main/main.tscn b/main/main.tscn index 6b9f496..bdc7c78 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -57,6 +57,9 @@ [ext_resource type="Script" uid="uid://c80x8jkdnx0xy" path="res://settings/controller_mapping_manager.gd" id="56_controller_mapping"] [ext_resource type="Script" uid="uid://d0jv8n2nfqia0" path="res://network/discovery_client.gd" id="57_discovery"] [ext_resource type="AudioStream" uid="uid://c4y8puv0n6xk8" path="res://audio/music/world/craft.mp3" id="dusk_music"] +[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"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water_native"] shader = ExtResource("40_title_water") @@ -142,6 +145,7 @@ fish_catalog = ExtResource("6_pool") pelican_buyer_profile = ExtResource("7_pelicans") main_shop_buyer_profile = ExtResource("12_main_shop") item_catalog = ExtResource("11_items") +gatherable_catalog = ExtResource("58_gatherable_catalog") [node name="TitleBackgroundLayer" type="CanvasLayer" parent="." unique_id=1690754702] unique_name_in_owner = true @@ -283,6 +287,10 @@ script = ExtResource("24_network_item") unique_name_in_owner = true script = ExtResource("45_fish_showcase") +[node name="NetworkWorldSpawnService" type="Node" parent="."] +unique_name_in_owner = true +script = ExtResource("59_world_spawns") + [node name="NetworkSurfaceDrawingService" type="Node" parent="." unique_id=80122665] unique_name_in_owner = true script = ExtResource("46_surface_drawing") @@ -378,6 +386,12 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 3.3999481, 12.07081) [node name="SurfaceDrawings" type="Node3D" parent="." unique_id=234671307] +[node name="WorldGatherables" type="Node3D" parent="."] + +[node name="GatheringController" type="Node3D" parent="."] +unique_name_in_owner = true +script = ExtResource("60_gathering_controller") + [node name="FishingSpot" parent="." unique_id=1461328864 instance=ExtResource("4_spot")] unique_name_in_owner = true transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.02, -18) diff --git a/main/runtime_performance_profile.gd b/main/runtime_performance_profile.gd new file mode 100644 index 0000000..cd6395e --- /dev/null +++ b/main/runtime_performance_profile.gd @@ -0,0 +1,50 @@ +class_name RuntimePerformanceProfile +extends RefCounted + +const PROFILE_ENVIRONMENT_VARIABLE: String = ( + "NETFISHING_PERFORMANCE_PROFILE" +) +const LEGACY_LIGHT_ENVIRONMENT_VARIABLE: String = "NETFISHING_LOW_END" +const NORMAL_PROFILE: StringName = &"normal" +const LIGHT_PROFILE: StringName = &"light" +const NORMAL_WORLD_RENDER_SCALE: float = 1.0 +const LIGHT_WORLD_RENDER_SCALE: float = 0.5 + +var _profile_name: StringName = NORMAL_PROFILE + + +static func from_environment() -> RuntimePerformanceProfile: + return from_name( + StringName(OS.get_environment(PROFILE_ENVIRONMENT_VARIABLE)), + OS.get_environment(LEGACY_LIGHT_ENVIRONMENT_VARIABLE) == "1", + ) + + +static func from_name( + profile_name: StringName, + legacy_light_requested: bool = false, +) -> RuntimePerformanceProfile: + var profile := RuntimePerformanceProfile.new() + if profile_name == LIGHT_PROFILE or ( + profile_name.is_empty() and legacy_light_requested + ): + profile._profile_name = LIGHT_PROFILE + else: + profile._profile_name = NORMAL_PROFILE + return profile + + +func get_profile_name() -> StringName: + return _profile_name + + +func is_light() -> bool: + return _profile_name == LIGHT_PROFILE + + +func get_world_render_scale() -> float: + return ( + LIGHT_WORLD_RENDER_SCALE + if is_light() + else NORMAL_WORLD_RENDER_SCALE + ) diff --git a/main/runtime_performance_profile.gd.uid b/main/runtime_performance_profile.gd.uid new file mode 100644 index 0000000..3f9e338 --- /dev/null +++ b/main/runtime_performance_profile.gd.uid @@ -0,0 +1 @@ +uid://8vm104r2264h diff --git a/main/world_pixelation_postprocess.gd b/main/world_pixelation_postprocess.gd index 303fa3e..06b6e1b 100644 --- a/main/world_pixelation_postprocess.gd +++ b/main/world_pixelation_postprocess.gd @@ -5,6 +5,7 @@ extends CanvasLayer var _pixel_size: int = PlayerSettings.DEFAULT_WORLD_PIXEL_SIZE var _gameplay_active: bool = false +var _light_performance_profile: bool = false func _ready() -> void: @@ -26,6 +27,15 @@ func set_gameplay_active(active: bool) -> void: _refresh_grid() +func set_light_performance_profile(enabled: bool) -> void: + _light_performance_profile = enabled + _refresh_grid() + + +func is_light_performance_profile() -> bool: + return _light_performance_profile + + func get_grid_size() -> Vector2i: return PlayerSettings.get_world_grid_size( _pixel_size, @@ -38,6 +48,7 @@ func _refresh_grid() -> void: return var effect_enabled: bool = ( _gameplay_active + and not _light_performance_profile and _pixel_size != PlayerSettings.MIN_WORLD_PIXEL_SIZE ) _screen_grid.visible = effect_enabled diff --git a/network/discovery_client.gd b/network/discovery_client.gd index 476cff4..bf15bbf 100644 --- a/network/discovery_client.gd +++ b/network/discovery_client.gd @@ -85,6 +85,7 @@ var _join_request_in_flight: bool = false var _pending_join_endpoint: String = "" var _pending_join_token: String = "" var _pending_join_room_id: String = "" +var _preserve_pending_join_on_inactive: bool = false var _upnp_thread: Thread var _upnp_mapping_in_progress: bool = false var _upnp_operation_is_renewal: bool = false @@ -291,6 +292,17 @@ func is_public_join_preparing() -> bool: return _join_request_in_flight +func preserve_public_join_for_session_switch() -> bool: + _preserve_pending_join_on_inactive = not _pending_join_token.is_empty() + return _preserve_pending_join_on_inactive + + +func cancel_pending_public_join() -> void: + _preserve_pending_join_on_inactive = false + _set_public_join_state(PublicJoinState.IDLE) + _clear_pending_join() + + func prepare_public_join(room: Dictionary) -> bool: if _join_request_in_flight: return false @@ -913,18 +925,30 @@ func _discovery_version_mismatch_message(required_version: String) -> String: func _on_session_state_changed(state: NetworkSession.State) -> void: if state == NetworkSession.State.CONNECTING and not _pending_join_token.is_empty(): + _preserve_pending_join_on_inactive = false _set_public_join_state(PublicJoinState.CONNECTING) _join_probe_timer.start() call_deferred("_send_pending_join_probe") elif state in [ NetworkSession.State.JOINED_CLIENT, NetworkSession.State.SERVER_LOST, - NetworkSession.State.INACTIVE, ]: + _preserve_pending_join_on_inactive = false _set_public_join_state(PublicJoinState.IDLE) _join_probe_timer.stop() _clear_pending_join() + elif state == NetworkSession.State.INACTIVE: + if ( + _preserve_pending_join_on_inactive + and not _pending_join_token.is_empty() + ): + _preserve_pending_join_on_inactive = false + else: + _set_public_join_state(PublicJoinState.IDLE) + _join_probe_timer.stop() + _clear_pending_join() elif state == NetworkSession.State.CONNECTION_FAILED: + _preserve_pending_join_on_inactive = false _set_public_join_state(PublicJoinState.ERROR) _join_probe_timer.stop() _clear_pending_join() diff --git a/network/network_chat_service.gd b/network/network_chat_service.gd index 368c529..50c47d3 100644 --- a/network/network_chat_service.gd +++ b/network/network_chat_service.gd @@ -5,6 +5,7 @@ const BURST_COUNT: int = 3 const WINDOW_COUNT: int = 5 const WINDOW_SECONDS: float = 10.0 const CALL_COOLDOWN_MILLISECONDS: int = 180 +const WORLD_COMMAND_COOLDOWN_MILLISECONDS: int = 500 const CALL_PITCH_VARIANTS: Array[float] = [ 0.96, 1.03, @@ -22,6 +23,7 @@ signal message_received(message: Dictionary) signal local_message_confirmed(message: Dictionary) signal send_rejected(message: String) signal history_replaced(messages: Array[Dictionary]) +signal world_command_finished(success: bool, message: String) signal character_call_received( peer_id: int, call_id: String, @@ -35,13 +37,22 @@ var _request_ledgers: Dictionary[int, Dictionary] = {} var _rate_times: Dictionary[int, Array] = {} var _last_call_msec: Dictionary[int, int] = {} var _call_variant_indices: Dictionary[int, int] = {} +var _last_world_command_msec: Dictionary[int, int] = {} var _sequence: int = 0 var _peer_names: Dictionary[int, String] = {} var _relationships: PlayerRelationshipStore +var _world_time: WorldTimeService +var _world_weather: WorldWeatherService -func setup(session: NetworkSession) -> void: +func setup( + session: NetworkSession, + world_time: WorldTimeService, + world_weather: WorldWeatherService, +) -> void: _session = session + _world_time = world_time + _world_weather = world_weather _session.peer_authenticated.connect(_on_peer_authenticated) _session.peer_removed.connect(_on_peer_removed) _session.state_changed.connect(_on_session_state_changed) @@ -242,6 +253,188 @@ func broadcast_system_message(body: String) -> bool: return true +func request_world_time_change(phase_name: String) -> bool: + var normalized: String = phase_name.strip_edges().to_lower() + if not _valid_time_phase(normalized) or _session == null: + return false + if _session.is_host(): + return _apply_world_time_change(normalized) + if ( + not _session.is_joined_client() + or not _session.is_local_operator() + or not _session.supports_server_capability( + NetworkProtocol.WORLD_TIME_CAPABILITY + ) + ): + return false + submit_world_time_command.rpc_id(1, normalized) + return true + + +func request_world_weather_change(weather_name: String) -> bool: + var normalized: String = _normalized_weather_name(weather_name) + if normalized.is_empty() or _session == null: + return false + if _session.is_host(): + return _apply_world_weather_change(normalized) + if ( + not _session.is_joined_client() + or not _session.is_local_operator() + or not _session.supports_server_capability( + NetworkProtocol.WORLD_WEATHER_CAPABILITY + ) + ): + return false + submit_world_weather_command.rpc_id(1, normalized) + return true + + +@rpc("any_peer", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL) +func submit_world_time_command(phase_name: String) -> void: + var sender_id: int = multiplayer.get_remote_sender_id() + if not _valid_world_command_sender(sender_id): + _send_world_command_result( + sender_id, false, "Only the host or an operator can change world time." + ) + return + if not _consume_world_command_rate(sender_id): + _send_world_command_result(sender_id, false, "Slow down.") + return + var success: bool = _apply_world_time_change( + phase_name.strip_edges().to_lower() + ) + _send_world_command_result( + sender_id, + success, + "" if success else "World time could not be changed.", + ) + + +@rpc("any_peer", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL) +func submit_world_weather_command(weather_name: String) -> void: + var sender_id: int = multiplayer.get_remote_sender_id() + if not _valid_world_command_sender(sender_id): + _send_world_command_result( + sender_id, + false, + "Only the host or an operator can change world weather.", + ) + return + if not _consume_world_command_rate(sender_id): + _send_world_command_result(sender_id, false, "Slow down.") + return + var success: bool = _apply_world_weather_change( + _normalized_weather_name(weather_name) + ) + _send_world_command_result( + sender_id, + success, + "" if success else "World weather could not be changed.", + ) + + +@rpc("authority", "call_remote", "reliable", NetworkChatProtocol.RELIABLE_CHANNEL) +func receive_world_command_result(success: bool, message: String) -> void: + if _session == null or not _session.is_joined_client(): + return + world_command_finished.emit(success, message.left(120)) + + +func _valid_world_command_sender(peer_id: int) -> bool: + return ( + _session != null + and _session.is_host() + and peer_id > 1 + and _session.is_authenticated_peer(peer_id) + and _session.is_peer_operator(peer_id) + ) + + +func _apply_world_time_change(phase_name: String) -> bool: + if _world_time == null or not _valid_time_phase(phase_name): + return false + var target_hour: float = _time_phase_hour(phase_name) + if not _world_time.set_authoritative_time(target_hour): + return false + broadcast_system_message( + "World time set to %s (%s)." + % [phase_name, _world_time.get_clock_text()] + ) + return true + + +func _apply_world_weather_change(weather_name: String) -> bool: + if _world_weather == null: + return false + var normalized: String = _normalized_weather_name(weather_name) + if normalized.is_empty(): + return false + var target_weather: WorldWeatherService.Weather = ( + _weather_for_name(normalized) + ) + if not _world_weather.set_authoritative_weather(target_weather): + return false + broadcast_system_message("World weather set to %s." % normalized) + return true + + +func _send_world_command_result( + peer_id: int, + success: bool, + message: String, +) -> void: + if peer_id > 1 and _session.is_authenticated_peer(peer_id): + receive_world_command_result.rpc_id(peer_id, success, message.left(120)) + + +func _consume_world_command_rate(peer_id: int) -> bool: + var now_msec: int = Time.get_ticks_msec() + var last_msec: int = _last_world_command_msec.get( + peer_id, now_msec - WORLD_COMMAND_COOLDOWN_MILLISECONDS + ) + if now_msec - last_msec < WORLD_COMMAND_COOLDOWN_MILLISECONDS: + return false + _last_world_command_msec[peer_id] = now_msec + return true + + +static func _valid_time_phase(phase_name: String) -> bool: + return phase_name in ["dawn", "day", "dusk", "night"] + + +static func _time_phase_hour(phase_name: String) -> float: + match phase_name: + "dawn": + return WorldTimeService.DAWN_START_HOUR + "day": + return WorldTimeService.DAWN_END_HOUR + "dusk": + return WorldTimeService.DUSK_START_HOUR + "night": + return WorldTimeService.DUSK_END_HOUR + return -1.0 + + +static func _normalized_weather_name(weather_name: String) -> String: + var normalized: String = weather_name.strip_edges().to_lower() + return "clear" if normalized == "sunny" else normalized if normalized in [ + "clear", "cloudy", "rainy", "foggy" + ] else "" + + +static func _weather_for_name( + weather_name: String, +) -> WorldWeatherService.Weather: + match weather_name: + "cloudy": + return WorldWeatherService.Weather.CLOUDY + "rainy": + return WorldWeatherService.Weather.RAINY + "foggy": + return WorldWeatherService.Weather.FOGGY + return WorldWeatherService.Weather.SUNNY + + func get_history() -> Array[Dictionary]: var result: Array[Dictionary] = [] for message: Dictionary in _history: @@ -422,6 +615,7 @@ func _on_peer_removed(peer_id: int) -> void: _rate_times.erase(peer_id) _last_call_msec.erase(peer_id) _call_variant_indices.erase(peer_id) + _last_world_command_msec.erase(peer_id) if not _session.is_host(): return var display_name: String = _peer_names.get(peer_id, "Player") @@ -481,6 +675,7 @@ func _on_session_state_changed(state: NetworkSession.State) -> void: _rate_times.clear() _last_call_msec.clear() _call_variant_indices.clear() + _last_world_command_msec.clear() _peer_names.clear() _sequence = 0 history_replaced.emit([]) diff --git a/network/network_fishing_attempt.gd b/network/network_fishing_attempt.gd index 3a1b1de..84511db 100644 --- a/network/network_fishing_attempt.gd +++ b/network/network_fishing_attempt.gd @@ -21,6 +21,7 @@ var origin: Vector3 var target: Vector3 var bobber_position: Vector3 var fish_id: StringName +var rod_id: StringName var bait_tags: Array[StringName] = [] var lure_effects: Array[StringName] = [] var encounter_seed: int = 0 diff --git a/network/network_fishing_service.gd b/network/network_fishing_service.gd index a5a2154..f7e1623 100644 --- a/network/network_fishing_service.gd +++ b/network/network_fishing_service.gd @@ -5,6 +5,7 @@ const FishCatchType = preload("res://fish/fish_catch.gd") const FishDataType = preload("res://fish/fish_data.gd") const FishPoolType = preload("res://fish/fish_pool.gd") const FishSelectorType = preload("res://fish/fish_selector.gd") +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") const FishingContextType = preload("res://fishing/fishing_context.gd") const CollectionLogType = preload("res://collection/collection_log.gd") const FishExperienceType = preload("res://fish/fish_experience.gd") @@ -254,10 +255,10 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void: if not bool(data["capacity_available"]): _record_and_reject(peer_id, request_id, "Cooler is full.") return - var rod: ItemData = _item_catalog.get_item_by_id( + var rod := _item_catalog.get_item_by_id( StringName(str(data["rod_id"])) - ) - if rod == null or rod.category != ItemData.Category.ROD: + ) as FishingRodDataType + if rod == null or not rod.is_available(): _record_and_reject(peer_id, request_id, "Select a fishing rod to cast.") return var avatar: Player = _spawn_service.get_avatar(peer_id) @@ -312,7 +313,7 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void: if _item_use != null else null ) var selected_fish: FishDataType = _select_authoritative_fish( - region, data, effects + region, data, effects, rod ) if selected_fish == null or not selected_fish.is_allowed_in_water( region.water_type @@ -339,6 +340,7 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void: not lure_id.is_empty() and ( lure == null + or not lure.is_available() or not lure.is_lure() or ( owns_authoritative_bag @@ -353,6 +355,7 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void: return if not bait_id.is_empty() and ( bait == null + or not bait.is_available() or not bait.is_bait() or ( owns_authoritative_bag @@ -374,20 +377,38 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void: attempt.target = authoritative_target attempt.bobber_position = authoritative_target attempt.fish_id = selected_fish.id + attempt.rod_id = rod.item_id attempt.bait_tags = _bait_tags_for_request(data) attempt.lure_effects.clear() if lure != null: for effect_id: StringName in lure.lure_effects: attempt.lure_effects.append(effect_id) - attempt.reel_speed = float(data["reel_speed"]) * ( + var effect_reel_multiplier: float = ( effects.get_reel_multiplier() if effects != null else 1.0 ) - attempt.barrier_damage = int(data["barrier_damage"]) + ( + var effect_barrier_bonus: int = ( effects.get_barrier_bonus() if effects != null else 0 ) - attempt.bite_time_remaining = _fishing_spot.roll_bite_wait_time() * float( + var effect_bite_multiplier: float = ( effects.get_bite_time_multiplier() if effects != null else 1.0 ) + attempt.reel_speed = ( + float(data["reel_speed"]) + * effect_reel_multiplier + * rod.reel_speed_multiplier + ) + attempt.barrier_damage = maxi( + roundi( + (float(data["barrier_damage"]) + effect_barrier_bonus) + * rod.barrier_power_multiplier + ), + 1, + ) + attempt.bite_time_remaining = ( + _fishing_spot.roll_bite_wait_time() + * effect_bite_multiplier + * rod.bite_time_multiplier + ) attempt.controller = CatchController.new() add_child(attempt.controller) attempt.controller.encounter_updated.connect( @@ -472,6 +493,7 @@ func _select_authoritative_fish( region: FishableWaterRegion, data: Dictionary, effects: PlayerItemEffects, + rod: FishingRodDataType, ) -> FishDataType: var evidence_log := CollectionLogType.new() for value: Variant in data["discovered_fish_ids"]: @@ -480,6 +502,7 @@ func _select_authoritative_fish( return null evidence_log.mark_discovered(fish_id) var selector := FishSelectorType.new() + selector.active_rod = rod selector.undiscovered_weight_multiplier = ( _fishing_spot.undiscovered_weight_multiplier ) @@ -503,7 +526,11 @@ func _bait_tags_for_request(data: Dictionary) -> Array[StringName]: if bait_id.is_empty() or _item_catalog == null: return [] var bait: ItemDataType = _item_catalog.get_item_by_id(bait_id) - return bait.bait_tags.duplicate() if bait != null and bait.is_bait() else [] + return ( + bait.bait_tags.duplicate() + if bait != null and bait.is_available() and bait.is_bait() + else [] + ) func _set_bite_pending(attempt: NetworkFishingAttempt) -> void: @@ -527,13 +554,20 @@ func _start_bite(attempt: NetworkFishingAttempt) -> void: if attempt.phase != NetworkFishingAttempt.Phase.WAITING_FOR_BITE: return var fish: FishDataType = _fish_catalog.get_fish_by_id(attempt.fish_id) - if fish == null or fish.catch_profile == null: + var rod := _item_catalog.get_item_by_id(attempt.rod_id) as FishingRodDataType + if ( + fish == null + or fish.catch_profile == null + or rod == null + or not rod.is_available() + ): _cancel_attempt(attempt.owner_peer_id, "Fishing attempt ended.") return attempt.bite_confirmation_pending = false attempt.phase = NetworkFishingAttempt.Phase.FIGHTING attempt.encounter_seed = _new_seed() var selector := FishSelectorType.new() + selector.active_rod = rod selector.use_deterministic_test_seed = true selector.deterministic_test_seed = attempt.encounter_seed ^ 0x5F3759DF selector.begin_roll() @@ -953,7 +987,7 @@ func _on_attempt_escaped(peer_id: int) -> void: if attempt == null: return attempt.phase = NetworkFishingAttempt.Phase.ESCAPED - _broadcast_public_outcome(attempt, &"escape", "The fish got away!") + _broadcast_public_outcome(attempt, &"escape", "") _dispose_attempt(peer_id) diff --git a/network/network_item_use_service.gd b/network/network_item_use_service.gd index f39d198..1777c51 100644 --- a/network/network_item_use_service.gd +++ b/network/network_item_use_service.gd @@ -1,7 +1,10 @@ class_name NetworkItemUseService extends Node +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 MAX_LEDGER_ENTRIES: int = 64 @@ -128,6 +131,7 @@ func _handle_use_request(peer_id: int, data: Dictionary) -> void: ) if error.is_empty() and ( item == null + or not item.is_available() or ( item.category != ItemData.Category.CONSUMABLE and not is_fish_finder @@ -289,18 +293,28 @@ func get_effects_for_peer(peer_id: int) -> PlayerItemEffects: return avatar.item_effects if avatar != null else null +func get_equipped_item_id(peer_id: int) -> StringName: + var state: Dictionary = _equipped_states.get(peer_id, {}) + if state.is_empty() or not bool(state.get("owns_item", false)): + return StringName() + return StringName(str(state.get("item_id", ""))) + + func submit_local_equipped(item_id: StringName, owns_item: bool) -> void: if _session == null or not _session.is_gameplay_session_active(): return _local_equipped_revision += 1 var item: ItemData = _catalog.get_item_by_id(item_id) + var can_equip: bool = ( + item != null and item.is_available() and owns_item + ) var data := { "session_id": _session.get_session_id(), "owner_peer_id": _session.get_local_peer_id(), - "item_id": str(item_id) if item != null and owns_item else "", - "category": int(item.category) if item != null and owns_item else -1, + "item_id": str(item_id) if can_equip else "", + "category": int(item.category) if can_equip else -1, "revision": _local_equipped_revision, - "owns_item": owns_item and item != null, + "owns_item": can_equip, } if _session.is_host(): _handle_equipped_state(_session.get_local_peer_id(), data) @@ -331,6 +345,7 @@ func _handle_equipped_state(peer_id: int, data: Dictionary) -> void: var item: ItemData = _catalog.get_item_by_id(item_id) if not item_id.is_empty() and ( item == null + or not item.is_available() or not bool(data["owns_item"]) or int(item.category) != int(data["category"]) ): @@ -353,14 +368,27 @@ func _apply_equipped(data: Dictionary) -> void: if avatar != null: var item_id := StringName(str(data["item_id"])) var item: ItemData = _catalog.get_item_by_id(item_id) - avatar.set_active_item_is_rod( - int(data["category"]) == ItemData.Category.ROD, + avatar.set_active_fishing_rod( + ( + item as FishingRodDataType + if ( + item != null + and item.is_available() + and int(data["category"]) == ItemData.Category.ROD + and bool(data["owns_item"]) + ) + else null + ), true, ) avatar.set_active_art_kit( item.icon if item != null else null, item_id == ArtShopStockType.ART_KIT_ITEM_ID and bool(data["owns_item"]), ) + avatar.set_active_catching_net( + item_id == FishingShopStockType.CRAB_NET_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_job_service.gd b/network/network_job_service.gd index 7745903..ebf5a6e 100644 --- a/network/network_job_service.gd +++ b/network/network_job_service.gd @@ -2,11 +2,13 @@ class_name NetworkJobService extends Node const MAX_SESSION_ID_LENGTH: int = 96 +const BOARD_REQUEST_INTERVAL_SECONDS: float = 1.5 var _session: NetworkSession var _jobs: PlayerJobService var _sequence: int = 0 var _last_received_sequence: int = -1 +var _board_request_accumulator: float = 0.0 func setup(session: NetworkSession, jobs: PlayerJobService) -> void: @@ -15,13 +17,36 @@ func setup(session: NetworkSession, jobs: PlayerJobService) -> void: _session.state_changed.connect(_on_session_state_changed) _session.peer_authenticated.connect(_on_peer_authenticated) _jobs.board_changed.connect(_on_board_changed) + set_process(true) + + +func _process(delta: float) -> void: + if ( + _session == null + or _jobs == null + or not _session.is_joined_client() + or not _session.supports_server_capability( + NetworkProtocol.JOBS_CAPABILITY + ) + or _jobs.has_active_board() + ): + _board_request_accumulator = 0.0 + return + _board_request_accumulator += delta + if _board_request_accumulator < BOARD_REQUEST_INTERVAL_SECONDS: + return + _board_request_accumulator = 0.0 + _request_remote_board() func _on_session_state_changed(state: NetworkSession.State) -> void: _sequence = 0 _last_received_sequence = -1 + _board_request_accumulator = 0.0 if state == NetworkSession.State.JOINED_CLIENT: - if not _session.supports_server_capability(NetworkProtocol.JOBS_CAPABILITY): + if _session.supports_server_capability(NetworkProtocol.JOBS_CAPABILITY): + _request_remote_board() + else: _jobs.clear_remote_board() elif state in [ NetworkSession.State.INACTIVE, @@ -69,6 +94,33 @@ func _send_board(peer_id: int) -> void: }) +func _request_remote_board() -> void: + if ( + _session != null + and _session.is_joined_client() + and _session.supports_server_capability( + NetworkProtocol.JOBS_CAPABILITY + ) + ): + request_job_board.rpc_id(1) + + +@rpc("any_peer", "call_remote", "reliable", 0) +func request_job_board() -> void: + var sender_id: int = multiplayer.get_remote_sender_id() + if ( + _session == null + or not _session.is_host() + or sender_id <= 1 + or not _session.is_authenticated_peer(sender_id) + or not _session.peer_supports_capability( + sender_id, NetworkProtocol.JOBS_CAPABILITY + ) + ): + return + _send_board(sender_id) + + @rpc("authority", "call_remote", "reliable", 0) func receive_job_board(data: Dictionary) -> void: if ( @@ -86,6 +138,7 @@ func receive_job_board(data: Dictionary) -> void: var board: Dictionary = data.get("board", {}) if _jobs.apply_remote_board(board): _last_received_sequence = sequence + _board_request_accumulator = 0.0 static func validate_snapshot(value: Variant) -> bool: diff --git a/network/network_player_animation_protocol.gd b/network/network_player_animation_protocol.gd new file mode 100644 index 0000000..b27cb9d --- /dev/null +++ b/network/network_player_animation_protocol.gd @@ -0,0 +1,94 @@ +class_name NetworkPlayerAnimationProtocol +extends RefCounted + +const FORMAT_VERSION: int = 1 +const MAX_STATE_ID_LENGTH: int = 64 +const MAX_ACTION_SEQUENCE: int = 2147483647 +const MAX_ACTION_ELAPSED_SECONDS: float = 86400.0 + +const LOCOMOTION_IDLE: StringName = &"idle" +const LOCOMOTION_WALKING: StringName = &"walking" +const LOCOMOTION_RUNNING: StringName = &"running" + + +static func make_state( + locomotion_id: StringName, + grounded: bool, + action_id: StringName = &"", + action_sequence: int = 0, + action_elapsed: float = 0.0, +) -> Dictionary: + return { + "format_version": FORMAT_VERSION, + "locomotion_id": String(locomotion_id), + "grounded": grounded, + "action": make_action_state( + action_id, action_sequence, action_elapsed + ), + } + + +static func make_action_state( + action_id: StringName = &"", + action_sequence: int = 0, + action_elapsed: float = 0.0, +) -> Dictionary: + return { + "id": String(action_id), + "sequence": action_sequence, + "elapsed": action_elapsed, + } + + +static func validate_state(value: Variant) -> bool: + if typeof(value) != TYPE_DICTIONARY: + return false + var state: Dictionary = value + return ( + typeof(state.get("format_version")) == TYPE_INT + and int(state["format_version"]) == FORMAT_VERSION + and valid_state_id(state.get("locomotion_id"), false) + and typeof(state.get("grounded")) == TYPE_BOOL + and validate_action_state(state.get("action")) + ) + + +static func validate_action_state(value: Variant) -> bool: + if typeof(value) != TYPE_DICTIONARY: + return false + var action: Dictionary = value + if ( + not valid_state_id(action.get("id"), true) + or typeof(action.get("sequence")) != TYPE_INT + or typeof(action.get("elapsed")) not in [TYPE_FLOAT, TYPE_INT] + ): + return false + var sequence: int = int(action["sequence"]) + var elapsed: float = float(action["elapsed"]) + return ( + sequence >= 0 + and sequence <= MAX_ACTION_SEQUENCE + and is_finite(elapsed) + and elapsed >= 0.0 + and elapsed <= MAX_ACTION_ELAPSED_SECONDS + ) + + +static func valid_state_id(value: Variant, allow_empty: bool) -> bool: + if typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]: + return false + var state_id: String = str(value) + if state_id.is_empty(): + return allow_empty + if state_id.length() > MAX_STATE_ID_LENGTH: + return false + for index: int in state_id.length(): + var codepoint: int = state_id.unicode_at(index) + var valid_character: bool = ( + codepoint >= 97 and codepoint <= 122 + or codepoint >= 48 and codepoint <= 57 + or codepoint == 95 + ) + if not valid_character: + return false + return true diff --git a/network/network_player_animation_protocol.gd.uid b/network/network_player_animation_protocol.gd.uid new file mode 100644 index 0000000..466ad02 --- /dev/null +++ b/network/network_player_animation_protocol.gd.uid @@ -0,0 +1 @@ +uid://cmaaep4n3p2tq diff --git a/network/network_protocol.gd b/network/network_protocol.gd index 46192b6..d1f8d2c 100644 --- a/network/network_protocol.gd +++ b/network/network_protocol.gd @@ -1,7 +1,7 @@ class_name NetworkProtocol extends RefCounted -const PROTOCOL_VERSION: int = 3 +const PROTOCOL_VERSION: int = 5 const GAME_BUILD: String = "prealpha" const MAX_GAME_VERSION_LENGTH: int = 64 const MAX_DISPLAY_NAME_LENGTH: int = 24 @@ -25,6 +25,7 @@ const WORLD_TIME_CAPABILITY: String = "world_time_v1" const WORLD_WEATHER_CAPABILITY: String = "world_weather_v1" const FISH_QUALITY_CAPABILITY: String = "fish_quality_v1" const JOBS_CAPABILITY: String = "jobs_v1" +const WORLD_SPAWN_CAPABILITY: String = "world_spawn_envelope_v1" enum RejectionCode { NONE, @@ -172,6 +173,7 @@ static func make_client_hello( WORLD_TIME_CAPABILITY, WORLD_WEATHER_CAPABILITY, JOBS_CAPABILITY, + WORLD_SPAWN_CAPABILITY, ]), "cosmetic_snapshot": cosmetic_snapshot, "identity_fingerprint": identity_fingerprint, @@ -313,6 +315,7 @@ static func make_server_hello( WORLD_TIME_CAPABILITY, WORLD_WEATHER_CAPABILITY, JOBS_CAPABILITY, + WORLD_SPAWN_CAPABILITY, "chat_v1", "mail_v1", "profile_v1", diff --git a/network/network_session.gd b/network/network_session.gd index 2043072..8af4f4a 100644 --- a/network/network_session.gd +++ b/network/network_session.gd @@ -9,6 +9,9 @@ const CONNECTION_TIMEOUT_SECONDS: float = 10.0 const AUTHENTICATION_TIMEOUT_SECONDS: float = 60.0 const INPUT_INTERVAL: float = 1.0 / 30.0 const SNAPSHOT_INTERVAL: float = 1.0 / 30.0 +const MOVEMENT_SNAPSHOT_BATCH_SIZE: int = 8 +const MOVEMENT_SNAPSHOT_FIELD_COUNT: int = 12 +const MAX_MOVEMENT_INPUT_SEQUENCE: int = 2147483647 signal state_changed(state: State) signal status_message_changed(message: String) @@ -33,7 +36,7 @@ signal server_trust_required( ) signal peer_identity_observed(peer_id: int, status: String) signal operator_status_changed(peer_id: int, is_operator: bool) -signal server_lost +signal server_lost(message: String) signal remote_recovery_requested(peer_id: int, entry_position: Vector3) signal remote_recovery_presentation_changed( peer_id: int, @@ -266,6 +269,7 @@ func _register_player_host() -> void: NetworkProtocol.WORLD_TIME_CAPABILITY, NetworkProtocol.WORLD_WEATHER_CAPABILITY, NetworkProtocol.JOBS_CAPABILITY, + NetworkProtocol.WORLD_SPAWN_CAPABILITY, ]), ) _registry.update_appearance(1, _local_appearance_snapshot) @@ -554,6 +558,7 @@ func supports_server_capability(capability: StringName) -> bool: NetworkProtocol.SURFACE_DRAWING_CAPABILITY, NetworkProtocol.WORLD_TIME_CAPABILITY, NetworkProtocol.WORLD_WEATHER_CAPABILITY, + NetworkProtocol.WORLD_SPAWN_CAPABILITY, "chat_v1", "mail_v1", "profile_v1", @@ -874,7 +879,7 @@ func _on_server_disconnected() -> void: ) _moderation_disconnect_message = "" _set_state(State.SERVER_LOST, message) - server_lost.emit() + server_lost.emit(message) connection_error.emit(message) @@ -1107,7 +1112,6 @@ func submit_client_identity_proof(data: Dictionary) -> void: @rpc("authority", "call_remote", "reliable", 0) func receive_moderation_disconnect(message: String) -> void: _moderation_disconnect_message = message.left(120) - connection_error.emit(_moderation_disconnect_message) @rpc("authority", "call_remote", "reliable", 0) @@ -1741,9 +1745,10 @@ func _is_valid_movement_input(data: Dictionary) -> bool: or typeof(data.get("sprint")) != TYPE_BOOL or typeof(data.get("sneak")) != TYPE_BOOL or typeof(data.get("slow_walk")) != TYPE_BOOL - or ( - data.has("sitting") - and typeof(data.get("sitting")) != TYPE_BOOL + or typeof(data.get("sitting")) != TYPE_BOOL + or typeof(data.get("casting")) != TYPE_BOOL + or not NetworkPlayerAnimationProtocol.validate_action_state( + data.get("animation_action") ) or typeof(data.get("camera_yaw")) not in [TYPE_FLOAT, TYPE_INT] ): @@ -1751,11 +1756,18 @@ func _is_valid_movement_input(data: Dictionary) -> bool: var axis: Array = data["axis"] if axis.size() != 2: return false + if ( + typeof(axis[0]) not in [TYPE_FLOAT, TYPE_INT] + or typeof(axis[1]) not in [TYPE_FLOAT, TYPE_INT] + ): + return false var x: float = float(axis[0]) var y: float = float(axis[1]) var camera_yaw: float = float(data["camera_yaw"]) return ( - is_finite(x) + int(data["sequence"]) > 0 + and int(data["sequence"]) <= MAX_MOVEMENT_INPUT_SEQUENCE + and is_finite(x) and is_finite(y) and is_finite(camera_yaw) and absf(x) <= 1.01 @@ -1765,24 +1777,122 @@ func _is_valid_movement_input(data: Dictionary) -> bool: func _broadcast_movement_snapshots() -> void: - var snapshots: Array[Dictionary] = [] + var snapshots: Array = [] for peer_id: int in _registry.get_peer_ids(): var avatar: Player = _spawn_service.get_avatar(peer_id) if avatar == null: continue - snapshots.append(avatar.make_network_snapshot(peer_id)) - receive_movement_snapshots.rpc(snapshots) + var encoded: Array = _encode_movement_snapshot( + avatar.make_network_snapshot(peer_id) + ) + if not encoded.is_empty(): + snapshots.append(encoded) + # The compact v5 representation keeps a normal eight-player update near + # 1 KiB instead of the roughly 3.7 KiB dictionary representation. Rooms + # configured above the normal cap are divided into the same safe size. + for start_index: int in range( + 0, + snapshots.size(), + MOVEMENT_SNAPSHOT_BATCH_SIZE, + ): + receive_movement_snapshots.rpc( + snapshots.slice( + start_index, + mini( + start_index + MOVEMENT_SNAPSHOT_BATCH_SIZE, + snapshots.size(), + ), + ) + ) + + +static func _encode_movement_snapshot(snapshot: Dictionary) -> Array: + var position: Variant = snapshot.get("position") + var snapshot_velocity: Variant = snapshot.get("velocity") + var animation_value: Variant = snapshot.get("animation_state") + if ( + typeof(position) != TYPE_ARRAY + or position.size() != 3 + or typeof(snapshot_velocity) != TYPE_ARRAY + or snapshot_velocity.size() != 3 + or not NetworkPlayerAnimationProtocol.validate_state(animation_value) + ): + return [] + var animation: Dictionary = animation_value + var action: Dictionary = animation["action"] + return [ + int(snapshot.get("peer_id", 0)), + int(snapshot.get("acknowledged_input", 0)), + Vector3(float(position[0]), float(position[1]), float(position[2])), + Vector3( + float(snapshot_velocity[0]), + float(snapshot_velocity[1]), + float(snapshot_velocity[2]), + ), + float(snapshot.get("visual_yaw", 0.0)), + str(animation["locomotion_id"]), + bool(animation["grounded"]), + str(action["id"]), + int(action["sequence"]), + float(action["elapsed"]), + bool(snapshot.get("sitting", false)), + bool(snapshot.get("casting", false)), + ] + + +static func _decode_movement_snapshot(value: Variant) -> Dictionary: + if typeof(value) != TYPE_ARRAY: + return {} + var fields: Array = value + if ( + fields.size() != MOVEMENT_SNAPSHOT_FIELD_COUNT + or typeof(fields[0]) != TYPE_INT + or typeof(fields[1]) != TYPE_INT + or typeof(fields[2]) != TYPE_VECTOR3 + or typeof(fields[3]) != TYPE_VECTOR3 + or typeof(fields[4]) not in [TYPE_FLOAT, TYPE_INT] + or typeof(fields[5]) not in [TYPE_STRING, TYPE_STRING_NAME] + or typeof(fields[6]) != TYPE_BOOL + or typeof(fields[7]) not in [TYPE_STRING, TYPE_STRING_NAME] + or typeof(fields[8]) != TYPE_INT + or typeof(fields[9]) not in [TYPE_FLOAT, TYPE_INT] + or typeof(fields[10]) != TYPE_BOOL + or typeof(fields[11]) != TYPE_BOOL + ): + return {} + var position: Vector3 = fields[2] + var snapshot_velocity: Vector3 = fields[3] + return { + "peer_id": int(fields[0]), + "acknowledged_input": int(fields[1]), + "position": [position.x, position.y, position.z], + "velocity": [ + snapshot_velocity.x, + snapshot_velocity.y, + snapshot_velocity.z, + ], + "visual_yaw": float(fields[4]), + "animation_state": NetworkPlayerAnimationProtocol.make_state( + StringName(str(fields[5])), + bool(fields[6]), + StringName(str(fields[7])), + int(fields[8]), + float(fields[9]), + ), + "sitting": bool(fields[10]), + "casting": bool(fields[11]), + } @rpc("authority", "call_remote", "unreliable_ordered", 2) -func receive_movement_snapshots(snapshots: Array) -> void: +func receive_movement_snapshots(encoded_snapshots: Array) -> void: if state != State.JOINED_CLIENT: return var local_peer_id: int = multiplayer.get_unique_id() - for value: Variant in snapshots: - if typeof(value) != TYPE_DICTIONARY: + for value: Variant in encoded_snapshots: + var snapshot: Dictionary = _decode_movement_snapshot(value) + if snapshot.is_empty(): continue - var snapshot: Dictionary = value if typeof(snapshot.get("peer_id")) != TYPE_INT: continue var peer_id: int = snapshot["peer_id"] @@ -1790,7 +1900,11 @@ func receive_movement_snapshots(snapshots: Array) -> void: if avatar == null: continue if peer_id == local_peer_id: - avatar.apply_local_prediction_correction(snapshot) + avatar.apply_local_prediction_correction( + snapshot, + _input_sequence, + INPUT_INTERVAL, + ) else: avatar.push_network_snapshot(snapshot) diff --git a/network/network_shop_service.gd b/network/network_shop_service.gd index 4dd0f98..0f26553 100644 --- a/network/network_shop_service.gd +++ b/network/network_shop_service.gd @@ -5,6 +5,7 @@ const FishingShopStockType = preload( "res://economy/fishing_shop_stock.gd" ) 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") @@ -126,6 +127,15 @@ func request_supply(item_id: StringName) -> String: ) +func request_rod(item_id: StringName) -> String: + return _request_purchase( + item_id, + NetworkShopProtocol.ProductCategory.ROD, + 1, + _bag.get_quantity(item_id) if _bag != null else 0, + ) + + func request_reel_speed_upgrade() -> String: return _request_purchase( REEL_PRODUCT_ID, @@ -340,7 +350,7 @@ func _build_authoritative_result( ) if ( item == null - or not item.is_valid() + or not item.is_available() or product_id not in ( FishingShopStockType.get_stock_item_ids() ) @@ -384,7 +394,29 @@ func _build_authoritative_result( rejection = "Purchase could not be completed." resulting_state = current_state + quantity NetworkShopProtocol.ProductCategory.ROD: - rejection = "This item is not sold here." + var rod := ( + _item_catalog.get_item_by_id(product_id) + as FishingRodDataType + if _item_catalog != null else null + ) + var available_rods: Array[FishingRodDataType] = ( + FishingShopStockType.get_rod_stock(_item_catalog) + ) + if ( + rod == null + or not rod.is_shop_available() + or rod not in available_rods + or quantity != 1 + or current_state != 0 + ): + rejection = ( + "Rod already owned." + if current_state > 0 + else "Purchase could not be completed." + ) + else: + cost = rod.shop_price + resulting_state = 1 NetworkShopProtocol.ProductCategory.REEL_SPEED_UPGRADE: if ( product_id != REEL_PRODUCT_ID @@ -434,7 +466,7 @@ func _build_authoritative_result( if ( product_id != ArtShopStockType.ART_KIT_ITEM_ID or art_item == null - or not art_item.is_valid() + or not art_item.is_available() or art_item.category != ItemDataType.Category.TOOL or art_item.stackable or not art_item.equippable @@ -643,6 +675,7 @@ func _validate_local_result(data: Dictionary) -> String: var bait_unlocked: bool = _bag.is_bait_unlocked(product_id) if ( item == null + or not item.is_available() or _bag.get_quantity(product_id) != expected_state or int(data["quantity"]) != FishingShopStockType.get_purchase_quantity( product_id, expected_state, item.max_stack @@ -655,6 +688,22 @@ func _validate_local_result(data: Dictionary) -> String: return "Purchase could not be completed." if not _bag.can_add_item(product_id, data["quantity"]): return "Your Bag is full." + NetworkShopProtocol.ProductCategory.ROD: + var rod := ( + _item_catalog.get_item_by_id(product_id) + as FishingRodDataType + if _item_catalog != null else null + ) + if ( + rod == null + or not rod.is_shop_available() + or _bag.get_quantity(product_id) != expected_state + or expected_state != 0 + or int(data["quantity"]) != 1 + or cost != rod.shop_price + or not _bag.can_add_item(product_id, 1) + ): + return "Purchase could not be completed." NetworkShopProtocol.ProductCategory.REEL_SPEED_UPGRADE: if _upgrades.get_reel_speed_level() != expected_state: return "Purchase could not be completed." @@ -671,8 +720,14 @@ func _validate_local_result(data: Dictionary) -> String: if _cooler_capacity.get_next_cost() != cost: return "Purchase could not be completed." NetworkShopProtocol.ProductCategory.ART_KIT: + var art_item: ItemDataType = ( + _item_catalog.get_item_by_id(product_id) + if _item_catalog != null else null + ) if ( product_id != ArtShopStockType.ART_KIT_ITEM_ID + or art_item == null + or not art_item.is_available() or _bag.get_quantity(product_id) != expected_state or not _bag.can_add_item(product_id, 1) or cost != ArtShopStockType.ART_KIT_PRICE @@ -702,6 +757,11 @@ func _apply_local_product(data: Dictionary) -> bool: _wallet.debit(data["total_cost"]) and _bag.add_item(product_id, data["quantity"]) ) + NetworkShopProtocol.ProductCategory.ROD: + return ( + _wallet.debit(int(data["total_cost"])) + and _bag.add_item(product_id, 1) + ) NetworkShopProtocol.ProductCategory.REEL_SPEED_UPGRADE: return _upgrades.purchase_reel_speed(_wallet) NetworkShopProtocol.ProductCategory.BARRIER_POWER_UPGRADE: diff --git a/network/network_world_spawn_protocol.gd b/network/network_world_spawn_protocol.gd new file mode 100644 index 0000000..4d0a55d --- /dev/null +++ b/network/network_world_spawn_protocol.gd @@ -0,0 +1,94 @@ +class_name NetworkWorldSpawnProtocol +extends RefCounted + +const CAPABILITY: StringName = &"world_spawn_envelope_v1" +const ENVELOPE_VERSION: int = 1 +const RELIABLE_CHANNEL: int = NetworkProtocol.ITEM_RELIABLE_CHANNEL +const SNAPSHOT_CHANNEL: int = 4 +const MAX_SESSION_ID_LENGTH: int = 96 +const MAX_EVENT_ID_LENGTH: int = 48 +const MAX_ENTITY_ID_LENGTH: int = 128 +const MAX_TYPE_ID_LENGTH: int = 96 +const MAX_ENTITIES_PER_SNAPSHOT: int = 128 +## Keeps unreliable movement envelopes below ENet's practical MTU while +## allowing reliable population envelopes to carry a larger initial state. +const SNAPSHOT_ENTITIES_PER_ENVELOPE: int = 4 + + +static func make_envelope( + session_id: String, + sequence: int, + event_id: StringName, + payload: Dictionary, +) -> Dictionary: + return { + "envelope_version": ENVELOPE_VERSION, + "session_id": session_id, + "sequence": sequence, + "event_id": str(event_id), + "payload": payload.duplicate(true), + } + + +static func validate_envelope(value: Variant) -> bool: + if typeof(value) != TYPE_DICTIONARY: + return false + var data: Dictionary = value + return ( + typeof(data.get("envelope_version")) == TYPE_INT + and int(data["envelope_version"]) == ENVELOPE_VERSION + and typeof(data.get("session_id")) == TYPE_STRING + and not str(data["session_id"]).is_empty() + and str(data["session_id"]).length() <= MAX_SESSION_ID_LENGTH + and typeof(data.get("sequence")) == TYPE_INT + and int(data["sequence"]) >= 0 + and typeof(data.get("event_id")) == TYPE_STRING + and not str(data["event_id"]).is_empty() + and str(data["event_id"]).length() <= MAX_EVENT_ID_LENGTH + and typeof(data.get("payload")) == TYPE_DICTIONARY + ) + + +static func validate_entity_state(value: Variant) -> bool: + if typeof(value) != TYPE_DICTIONARY: + return false + var data: Dictionary = value + var position: Variant = data.get("position") + if typeof(position) != TYPE_ARRAY or position.size() != 3: + return false + for component: Variant in position: + if typeof(component) not in [TYPE_FLOAT, TYPE_INT]: + return false + if not is_finite(float(component)): + return false + return ( + typeof(data.get("entity_id")) == TYPE_STRING + and not str(data["entity_id"]).is_empty() + and str(data["entity_id"]).length() <= MAX_ENTITY_ID_LENGTH + and typeof(data.get("type_id")) == TYPE_STRING + and not str(data["type_id"]).is_empty() + and str(data["type_id"]).length() <= MAX_TYPE_ID_LENGTH + and typeof(data.get("yaw")) in [TYPE_FLOAT, TYPE_INT] + and is_finite(float(data["yaw"])) + and typeof(data.get("revision")) == TYPE_INT + and int(data["revision"]) >= 0 + ) + + +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.size() != 3: + return Vector3(INF, INF, INF) + for component: Variant in value: + if typeof(component) not in [TYPE_FLOAT, TYPE_INT]: + return Vector3(INF, INF, INF) + if not is_finite(float(component)): + return Vector3(INF, INF, INF) + return _array_to_vector3(value) + + +static func _array_to_vector3(value: Array) -> Vector3: + return Vector3(float(value[0]), float(value[1]), float(value[2])) diff --git a/network/network_world_spawn_protocol.gd.uid b/network/network_world_spawn_protocol.gd.uid new file mode 100644 index 0000000..d245393 --- /dev/null +++ b/network/network_world_spawn_protocol.gd.uid @@ -0,0 +1 @@ +uid://wstcwpwk41mf diff --git a/network/network_world_spawn_service.gd b/network/network_world_spawn_service.gd new file mode 100644 index 0000000..7c50731 --- /dev/null +++ b/network/network_world_spawn_service.gd @@ -0,0 +1,1126 @@ +class_name NetworkWorldSpawnService +extends Node + +const FishCatchType = preload("res://fish/fish_catch.gd") +const FishDataType = preload("res://fish/fish_data.gd") +const FishExperienceType = preload("res://fish/fish_experience.gd") +const FishPoolType = preload("res://fish/fish_pool.gd") +const FishSelectorType = preload("res://fish/fish_selector.gd") +const GatherableCatalogType = preload("res://gathering/gatherable_catalog.gd") +const GatherableDataType = preload("res://gathering/gatherable_data.gd") +const WorldGatherableType = preload("res://gathering/world_gatherable.gd") +const TestWorldType = preload("res://world/test_world.gd") + +signal local_capture_received(fish_catch: FishCatch) +signal local_interaction_finished(accepted: bool, message: String) + +const SNAPSHOT_INTERVAL_SECONDS: float = 0.2 +const CAPACITY_RESPONSE_TIMEOUT_SECONDS: float = 3.0 +const MAX_REQUEST_ID_LENGTH: int = 128 +const MAX_LEDGER_ENTRIES: int = 96 + +var _session: NetworkSession +var _spawn_service: PlayerSpawnService +var _world: TestWorldType +var _world_root: Node3D +var _catalog: GatherableCatalogType +var _fish_catalog: FishPoolType +var _local_inventory: FishInventory +var _local_collection: CollectionLog +var _local_capacity: PlayerCoolerCapacity +var _local_experience: PlayerExperience +var _save_manager: PlayerSaveManager +var _item_use: NetworkItemUseService + +var _entities: Dictionary = {} +var _presentations: Dictionary = {} +var _entity_revisions: Dictionary = {} +var _surface_triangles: Dictionary = {} +var _surface_areas: Dictionary = {} +var _surface_total_areas: Dictionary = {} +var _respawns: Array[Dictionary] = [] +var _next_respawn_by_type: Dictionary[StringName, float] = {} +var _charge_requests: Dictionary = {} +var _pending_captures: Dictionary = {} +var _received_results: Dictionary = {} +var _showcase_deadlines: Dictionary = {} +var _envelope_sequence: int = 0 +var _snapshot_elapsed: float = 0.0 +var _population_session_id: String = "" +var _rng := RandomNumberGenerator.new() + + +func setup( + session: NetworkSession, + spawn_service: PlayerSpawnService, + world: TestWorldType, + world_root: Node3D, + catalog: GatherableCatalogType, + fish_catalog: FishPoolType, + local_inventory: FishInventory, + local_collection: CollectionLog, + local_capacity: PlayerCoolerCapacity, + local_experience: PlayerExperience, + save_manager: PlayerSaveManager, + item_use: NetworkItemUseService, +) -> void: + _session = session + _spawn_service = spawn_service + _world = world + _world_root = world_root + _catalog = catalog + _fish_catalog = fish_catalog + _local_inventory = local_inventory + _local_collection = local_collection + _local_capacity = local_capacity + _local_experience = local_experience + _save_manager = save_manager + _item_use = item_use + _rng.randomize() + _session.peer_authenticated.connect(_on_peer_authenticated) + _session.peer_removed.connect(_on_peer_removed) + _session.state_changed.connect(_on_session_state_changed) + set_physics_process(true) + _begin_population_if_ready.call_deferred() + + +func begin_local_interaction() -> String: + if ( + _session == null + or not _session.is_gameplay_session_active() + or ( + not _session.is_host() + and not _session.supports_server_capability( + NetworkWorldSpawnProtocol.CAPABILITY + ) + ) + ): + local_interaction_finished.emit(false, "Catching is unavailable.") + return "" + var request_id: String = _new_id("gather") + var data: Dictionary = { + "session_id": _session.get_session_id(), + "request_id": request_id, + } + if _session.is_host(): + _handle_interaction_begin(_session.get_local_peer_id(), data) + else: + submit_interaction_begin.rpc_id(1, data) + return request_id + + +func finish_local_interaction( + request_id: String, + entity_id: String, + target_position: Vector3, +) -> void: + if request_id.is_empty() or not target_position.is_finite(): + return + var data: Dictionary = { + "session_id": _session.get_session_id(), + "request_id": request_id, + "entity_id": entity_id, + "target_position": NetworkWorldSpawnProtocol.vector3_to_array( + target_position + ), + } + if _session.is_host(): + _handle_interaction_finish(_session.get_local_peer_id(), data) + else: + submit_interaction_finish.rpc_id(1, data) + + +func cancel_local_interaction(request_id: String) -> void: + if request_id.is_empty() or _session == null: + return + if _session.is_host(): + _handle_interaction_cancel(_session.get_local_peer_id(), request_id) + else: + submit_interaction_cancel.rpc_id(1, request_id) + + +func find_entity_near( + position: Vector3, + radius: float, +) -> String: + var best_id: String = "" + var best_distance_squared: float = radius * radius + for entity_id: String in _entities: + var state: Dictionary = _entities[entity_id] + if bool(state.get("locked", false)): + continue + var entity_position: Vector3 = state.get("position", Vector3.ZERO) + var distance_squared: float = Vector2( + entity_position.x - position.x, + entity_position.z - position.z, + ).length_squared() + if distance_squared <= best_distance_squared: + best_distance_squared = distance_squared + best_id = entity_id + return best_id + + +func find_capture_target( + position: Vector3, + tool_id: StringName, +) -> String: + if tool_id.is_empty(): + return "" + var best_id: String = "" + var best_distance_squared: float = INF + for entity_id: String in _entities: + var state: Dictionary = _entities[entity_id] + if bool(state.get("locked", false)): + continue + var entry := state.get("data") as GatherableDataType + if entry == null or entry.required_tool_id != tool_id: + continue + var entity_position: Vector3 = state.get("position", Vector3.ZERO) + var distance_squared: float = Vector2( + entity_position.x - position.x, + entity_position.z - position.z, + ).length_squared() + if ( + distance_squared <= entry.capture_radius * entry.capture_radius + and distance_squared <= best_distance_squared + ): + best_distance_squared = distance_squared + best_id = entity_id + return best_id + + +func get_charge_duration_for_tool(tool_id: StringName) -> float: + var duration: float = 0.0 + if _catalog == null or tool_id.is_empty(): + return duration + for entry: GatherableDataType in _catalog.get_available_entries(): + if entry.required_tool_id == tool_id: + duration = maxf(duration, entry.charge_duration) + return duration + + +func get_entry_for_entity(entity_id: String) -> GatherableDataType: + var state: Dictionary = _entities.get(entity_id, {}) + return state.get("data") as GatherableDataType + + +func _physics_process(delta: float) -> void: + if ( + _session == null + or not _session.is_host() + or not _session.is_gameplay_session_active() + ): + return + _begin_population_if_ready() + _update_pending_capture_timeouts() + _update_respawns() + _update_showcase_deadlines() + _update_host_entities(delta) + _snapshot_elapsed += delta + if _snapshot_elapsed >= SNAPSHOT_INTERVAL_SECONDS: + _snapshot_elapsed = fmod( + _snapshot_elapsed, + SNAPSHOT_INTERVAL_SECONDS, + ) + _broadcast_entity_snapshots() + + +func _begin_population_if_ready() -> void: + if ( + _session == null + or not _session.is_host() + or not _session.is_gameplay_session_active() + or _catalog == null + or _world == null + or _world_root == null + ): + return + var session_id: String = _session.get_session_id() + if session_id.is_empty() or session_id == _population_session_id: + return + _clear_world() + _population_session_id = session_id + for entry: GatherableDataType in _catalog.get_available_entries(): + _cache_spawn_surface(entry) + for _spawn_index: int in entry.population: + _spawn_entity(entry) + + +func _cache_spawn_surface(entry: GatherableDataType) -> void: + if entry == null or _surface_triangles.has(entry.type_id): + return + var triangles: Array[PackedVector3Array] = ( + _world.get_spawn_surface_triangles( + entry.surface_materials, + entry.minimum_surface_y, + ) + ) + var areas := PackedFloat32Array() + var total_area: float = 0.0 + for triangle: PackedVector3Array in triangles: + var area: float = ( + (triangle[1] - triangle[0]).cross( + triangle[2] - triangle[0] + ).length() * 0.5 + ) + total_area += area + areas.append(total_area) + _surface_triangles[entry.type_id] = triangles + _surface_areas[entry.type_id] = areas + _surface_total_areas[entry.type_id] = total_area + if triangles.is_empty(): + push_warning( + "No valid spawn surface was found for %s." % entry.type_id + ) + + +func _spawn_entity(entry: GatherableDataType) -> void: + var position: Vector3 = _sample_surface_position(entry) + if not position.is_finite(): + return + var entity_id: String = _new_id("world") + var state: Dictionary = { + "entity_id": entity_id, + "type_id": entry.type_id, + "data": entry, + "position": position, + "target": _sample_surface_position(entry, position, entry.roam_radius), + "yaw": _rng.randf_range(-PI, PI), + "revision": 1, + "locked": false, + } + if not (state["target"] as Vector3).is_finite(): + state["target"] = position + _entities[entity_id] = state + var envelope: Dictionary = _make_envelope( + &"spawn", + {"entity": _state_to_network(state)}, + ) + _apply_envelope(envelope) + receive_world_envelope.rpc(envelope) + + +func _update_host_entities(delta: float) -> void: + for entity_id: String in _entities.keys(): + var state: Dictionary = _entities.get(entity_id, {}) + if state.is_empty() or bool(state.get("locked", false)): + continue + var entry := state.get("data") as GatherableDataType + if entry == null: + continue + var position: Vector3 = state["position"] + var target: Vector3 = state["target"] + var horizontal_delta := Vector3( + target.x - position.x, + 0.0, + target.z - position.z, + ) + if horizontal_delta.length_squared() <= 0.04: + target = _sample_surface_position( + entry, + position, + entry.roam_radius, + ) + if not target.is_finite(): + target = position + state["target"] = target + horizontal_delta = Vector3( + target.x - position.x, + 0.0, + target.z - position.z, + ) + if not horizontal_delta.is_zero_approx(): + var step: float = minf( + entry.movement_speed * delta, + horizontal_delta.length(), + ) + var direction: Vector3 = horizontal_delta.normalized() + position.x += direction.x * step + position.z += direction.z * step + position.y = lerpf( + position.y, + target.y, + minf(step / maxf(horizontal_delta.length(), 0.001), 1.0), + ) + state["yaw"] = atan2(-direction.x, -direction.z) + state["position"] = position + _entities[entity_id] = state + if _should_scare(entry, position): + _despawn_entity(entity_id, &"scared", true, true) + + +func _should_scare(entry: GatherableDataType, position: Vector3) -> bool: + var radius_squared: float = entry.scare_radius * entry.scare_radius + for peer_id: int in _spawn_service.get_peer_ids(): + var avatar: Player = _spawn_service.get_avatar(peer_id) + if ( + avatar == null + or not avatar.is_moving_horizontally() + or avatar.is_sneaking() + ): + continue + var delta := Vector2( + avatar.global_position.x - position.x, + avatar.global_position.z - position.z, + ) + if delta.length_squared() <= radius_squared: + return true + return false + + +func _sample_surface_position( + entry: GatherableDataType, + origin: Vector3 = Vector3(INF, INF, INF), + maximum_distance: float = INF, +) -> Vector3: + _cache_spawn_surface(entry) + var triangles: Array = _surface_triangles.get(entry.type_id, []) + var cumulative_areas: PackedFloat32Array = _surface_areas.get( + entry.type_id, + PackedFloat32Array(), + ) + var total_area: float = float( + _surface_total_areas.get(entry.type_id, 0.0) + ) + if triangles.is_empty() or total_area <= 0.0: + return Vector3(INF, INF, INF) + var attempts: int = 48 if is_finite(maximum_distance) else 1 + var fallback: Vector3 = Vector3(INF, INF, INF) + for _attempt: int in attempts: + var roll: float = _rng.randf() * total_area + var triangle_index: int = cumulative_areas.bsearch(roll) + triangle_index = clampi(triangle_index, 0, triangles.size() - 1) + var triangle: PackedVector3Array = triangles[triangle_index] + var root: float = sqrt(_rng.randf()) + var barycentric_b: float = root * (1.0 - _rng.randf()) + var barycentric_c: float = root - barycentric_b + var point: Vector3 = ( + triangle[0] * (1.0 - root) + + triangle[1] * barycentric_b + + triangle[2] * barycentric_c + ) + fallback = point + if ( + not is_finite(maximum_distance) + or Vector2(point.x - origin.x, point.z - origin.z).length() + <= maximum_distance + ): + return point + if origin.is_finite() and is_finite(maximum_distance): + return origin + return fallback + + +func _broadcast_entity_snapshots() -> void: + if _entities.is_empty(): + return + var snapshots: Array[Dictionary] = [] + for entity_id: String in _entities: + var state: Dictionary = _entities[entity_id] + state["revision"] = int(state["revision"]) + 1 + _entities[entity_id] = state + snapshots.append(_state_to_network(state)) + while not snapshots.is_empty(): + var chunk: Array[Dictionary] = [] + var chunk_size: int = mini( + NetworkWorldSpawnProtocol.SNAPSHOT_ENTITIES_PER_ENVELOPE, + snapshots.size(), + ) + for _index: int in chunk_size: + chunk.append(snapshots.pop_front()) + var envelope: Dictionary = _make_envelope( + &"snapshot", + {"entities": chunk}, + ) + _apply_envelope(envelope) + receive_world_snapshot_envelope.rpc(envelope) + + +func _despawn_entity( + entity_id: String, + reason: StringName, + with_dust: bool, + schedule_respawn: bool, +) -> void: + var state: Dictionary = _entities.get(entity_id, {}) + if state.is_empty(): + return + var entry := state.get("data") as GatherableDataType + _entities.erase(entity_id) + var envelope: Dictionary = _make_envelope( + &"despawn", + { + "entity_id": entity_id, + "reason": str(reason), + "with_dust": with_dust, + }, + ) + _apply_envelope(envelope) + receive_world_envelope.rpc(envelope) + if schedule_respawn and entry != null: + _respawns.append({ + "type_id": entry.type_id, + "due": _now() + entry.get_respawn_delay(reason, _rng), + }) + + +func _update_respawns() -> void: + var now: float = _now() + var remaining: Array[Dictionary] = [] + for respawn: Dictionary in _respawns: + var type_id := StringName(str(respawn.get("type_id", ""))) + var next_allowed: float = float( + _next_respawn_by_type.get(type_id, 0.0) + ) + if now < maxf(float(respawn.get("due", INF)), next_allowed): + remaining.append(respawn) + continue + var entry: GatherableDataType = _catalog.get_entry( + type_id + ) + if entry != null and entry.is_available(): + _spawn_entity(entry) + _next_respawn_by_type[type_id] = ( + now + entry.minimum_respawn_spacing_seconds + ) + _respawns = remaining + + +@rpc( + "any_peer", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func submit_interaction_begin(data: Dictionary) -> void: + var peer_id: int = multiplayer.get_remote_sender_id() + if _session.is_host() and _session.is_authenticated_peer(peer_id): + _handle_interaction_begin(peer_id, data) + + +func _handle_interaction_begin(peer_id: int, data: Dictionary) -> void: + var request_id: String = str(data.get("request_id", "")) + if ( + str(data.get("session_id", "")) != _session.get_session_id() + or request_id.is_empty() + or request_id.length() > MAX_REQUEST_ID_LENGTH + or _pending_captures.has(peer_id) + ): + _send_interaction_result(peer_id, request_id, false, "Cannot use the net now.") + return + _charge_requests[peer_id] = { + "request_id": request_id, + "started": _now(), + } + + +@rpc( + "any_peer", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func submit_interaction_finish(data: Dictionary) -> void: + var peer_id: int = multiplayer.get_remote_sender_id() + if _session.is_host() and _session.is_authenticated_peer(peer_id): + _handle_interaction_finish(peer_id, data) + + +func _handle_interaction_finish(peer_id: int, data: Dictionary) -> void: + var request_id: String = str(data.get("request_id", "")) + var entity_id: String = str(data.get("entity_id", "")) + var charge: Dictionary = _charge_requests.get(peer_id, {}) + _charge_requests.erase(peer_id) + var target_position: Vector3 = NetworkWorldSpawnProtocol.array_to_vector3( + data.get("target_position", []) + ) + var state: Dictionary = _entities.get(entity_id, {}) + var entry := state.get("data") as GatherableDataType + var avatar: Player = _spawn_service.get_avatar(peer_id) + var error: String = "" + if ( + str(data.get("session_id", "")) != _session.get_session_id() + or request_id.is_empty() + or request_id.length() > MAX_REQUEST_ID_LENGTH + or str(charge.get("request_id", "")) != request_id + or not target_position.is_finite() + ): + error = "The catch attempt was invalid." + elif state.is_empty() or entry == null or bool(state.get("locked", false)): + error = "That animal is no longer there." + elif _now() - float(charge.get("started", _now())) + 0.05 < entry.charge_duration: + error = "Pull the net all the way back first." + elif _item_use.get_equipped_item_id(peer_id) != entry.required_tool_id: + error = "Equip the correct gathering tool." + elif avatar == null or not avatar.is_sneaking(): + error = "Sneak closer before swinging the net." + else: + var entity_position: Vector3 = state["position"] + var target_distance: float = Vector2( + entity_position.x - target_position.x, + entity_position.z - target_position.z, + ).length() + var player_distance: float = Vector2( + avatar.global_position.x - entity_position.x, + avatar.global_position.z - entity_position.z, + ).length() + if target_distance > entry.capture_radius: + error = "The net missed." + elif player_distance > entry.interaction_range: + error = "Move closer before swinging the net." + if not error.is_empty(): + _send_interaction_result(peer_id, request_id, false, error) + return + var selector := FishSelectorType.new() + selector.begin_roll() + var fish_catch: FishCatch = selector.create_catch(entry.catch_data) + if fish_catch == null or not fish_catch.is_valid(): + _send_interaction_result(peer_id, request_id, false, "The catch could not be recorded.") + return + state["locked"] = true + _entities[entity_id] = state + var capacity_nonce: String = _new_id("capacity") + _pending_captures[peer_id] = { + "request_id": request_id, + "entity_id": entity_id, + "catch": fish_catch.to_network_dict(), + "result_id": _new_id("gather_result"), + "capacity_nonce": capacity_nonce, + "deadline": _now() + CAPACITY_RESPONSE_TIMEOUT_SECONDS, + } + var probe: Dictionary = { + "session_id": _session.get_session_id(), + "request_id": request_id, + "capacity_nonce": capacity_nonce, + "catch_id": str(fish_catch.catch_id), + } + if peer_id == _session.get_local_peer_id(): + _handle_local_capacity_probe(probe) + else: + receive_capacity_probe.rpc_id(peer_id, probe) + + +@rpc( + "any_peer", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func submit_interaction_cancel(request_id: String) -> void: + var peer_id: int = multiplayer.get_remote_sender_id() + if _session.is_host() and _session.is_authenticated_peer(peer_id): + _handle_interaction_cancel(peer_id, request_id) + + +func _handle_interaction_cancel(peer_id: int, request_id: String) -> void: + var charge: Dictionary = _charge_requests.get(peer_id, {}) + if str(charge.get("request_id", "")) == request_id: + _charge_requests.erase(peer_id) + + +@rpc( + "authority", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func receive_capacity_probe(data: Dictionary) -> void: + _handle_local_capacity_probe(data) + + +func _handle_local_capacity_probe(data: Dictionary) -> void: + if str(data.get("session_id", "")) != _session.get_session_id(): + return + var catch_id := StringName(str(data.get("catch_id", ""))) + var can_accept: bool = ( + _local_inventory != null + and _local_capacity != null + and ( + _local_inventory.contains_catch_id(catch_id) + or _local_inventory.get_all_catches().size() + < _local_capacity.get_capacity() + ) + ) + if _session.is_host(): + _handle_capacity_response( + _session.get_local_peer_id(), + str(data.get("request_id", "")), + str(data.get("capacity_nonce", "")), + can_accept, + ) + else: + submit_capacity_response.rpc_id( + 1, + str(data.get("request_id", "")), + str(data.get("capacity_nonce", "")), + can_accept, + ) + + +@rpc( + "any_peer", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func submit_capacity_response( + request_id: String, + capacity_nonce: String, + can_accept: bool, +) -> void: + var peer_id: int = multiplayer.get_remote_sender_id() + if _session.is_host() and _session.is_authenticated_peer(peer_id): + _handle_capacity_response( + peer_id, + request_id, + capacity_nonce, + can_accept, + ) + + +func _handle_capacity_response( + peer_id: int, + request_id: String, + capacity_nonce: String, + can_accept: bool, +) -> void: + var pending: Dictionary = _pending_captures.get(peer_id, {}) + if ( + pending.is_empty() + or str(pending.get("request_id", "")) != request_id + or str(pending.get("capacity_nonce", "")) != capacity_nonce + ): + return + if not can_accept: + _reject_pending_capture(peer_id, "Cooler is full.") + return + _pending_captures.erase(peer_id) + var entity_id: String = str(pending["entity_id"]) + _despawn_entity(entity_id, &"captured", false, true) + var outcome: Dictionary = { + "session_id": _session.get_session_id(), + "result_id": str(pending["result_id"]), + "request_id": request_id, + "target_peer_id": peer_id, + "catch": (pending["catch"] as Dictionary).duplicate(true), + } + if peer_id == _session.get_local_peer_id(): + _apply_capture_result(outcome) + else: + receive_capture_result.rpc_id(peer_id, outcome) + _broadcast_showcase(peer_id, outcome["catch"], true) + _showcase_deadlines[peer_id] = _now() + 4.5 + + +func _reject_pending_capture(peer_id: int, message: String) -> void: + var pending: Dictionary = _pending_captures.get(peer_id, {}) + _pending_captures.erase(peer_id) + if pending.is_empty(): + return + var entity_id: String = str(pending.get("entity_id", "")) + var state: Dictionary = _entities.get(entity_id, {}) + if not state.is_empty(): + state["locked"] = false + _entities[entity_id] = state + _send_interaction_result( + peer_id, + str(pending.get("request_id", "")), + false, + message, + ) + + +func _update_pending_capture_timeouts() -> void: + var now: float = _now() + for peer_id: int in _pending_captures.keys(): + var pending: Dictionary = _pending_captures[peer_id] + if now >= float(pending.get("deadline", INF)): + _reject_pending_capture(peer_id, "The catch attempt timed out.") + + +@rpc( + "authority", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func receive_capture_result(data: Dictionary) -> void: + _apply_capture_result(data) + + +func _apply_capture_result(data: Dictionary) -> void: + if ( + str(data.get("session_id", "")) != _session.get_session_id() + or int(data.get("target_peer_id", 0)) != _session.get_local_peer_id() + or typeof(data.get("catch")) != TYPE_DICTIONARY + ): + return + var result_id: String = str(data.get("result_id", "")) + var request_id: String = str(data.get("request_id", "")) + if result_id.is_empty() or request_id.is_empty(): + return + if _received_results.has(result_id): + local_interaction_finished.emit(true, "Caught.") + return + var catch_data: Dictionary = data["catch"] + var fish: FishDataType = _fish_catalog.get_fish_by_id( + StringName(str(catch_data.get("fish_id", ""))) + ) + var fish_catch: FishCatch = FishCatchType.from_network_dict( + catch_data, + fish, + ) + if fish_catch == null or not fish_catch.is_valid(): + return + var already_owned: bool = _local_inventory.contains_catch_id( + fish_catch.catch_id + ) + if ( + not already_owned + and _local_inventory.get_all_catches().size() + >= _local_capacity.get_capacity() + ): + return + if not already_owned: + var experience: int = FishExperienceType.calculate_for_collection( + fish_catch, + _local_collection, + ) + _local_inventory.add_catch(fish_catch) + _local_collection.mark_quality_discovered( + fish_catch.fish_id, + fish_catch.quality, + ) + _local_experience.award_experience(experience) + if not _save_manager.save_if_dirty(): + return + _received_results[result_id] = true + _bound(_received_results) + local_interaction_finished.emit(true, "Caught %s." % fish.display_name) + local_capture_received.emit(fish_catch) + + +func _send_interaction_result( + peer_id: int, + request_id: String, + accepted: bool, + message: String, +) -> void: + var result: Dictionary = { + "session_id": _session.get_session_id(), + "request_id": request_id if not request_id.is_empty() else "invalid", + "target_peer_id": peer_id, + "accepted": accepted, + "message": message.left(160), + } + if peer_id == _session.get_local_peer_id(): + _apply_interaction_result(result) + else: + receive_interaction_result.rpc_id(peer_id, result) + + +@rpc( + "authority", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func receive_interaction_result(data: Dictionary) -> void: + _apply_interaction_result(data) + + +func _apply_interaction_result(data: Dictionary) -> void: + if ( + str(data.get("session_id", "")) != _session.get_session_id() + or int(data.get("target_peer_id", 0)) != _session.get_local_peer_id() + or typeof(data.get("accepted")) != TYPE_BOOL + ): + return + local_interaction_finished.emit( + bool(data["accepted"]), + str(data.get("message", "")), + ) + + +func _broadcast_showcase( + peer_id: int, + catch_data: Dictionary, + visible: bool, +) -> void: + var envelope: Dictionary = _make_envelope( + &"showcase", + { + "owner_peer_id": peer_id, + "visible": visible, + "catch": catch_data.duplicate(true) if visible else {}, + }, + ) + _apply_envelope(envelope) + receive_world_envelope.rpc(envelope) + + +func _update_showcase_deadlines() -> void: + var now: float = _now() + for peer_id: int in _showcase_deadlines.keys(): + if now < float(_showcase_deadlines[peer_id]): + continue + _showcase_deadlines.erase(peer_id) + _broadcast_showcase(peer_id, {}, false) + + +func _make_envelope( + event_id: StringName, + payload: Dictionary, +) -> Dictionary: + _envelope_sequence += 1 + return NetworkWorldSpawnProtocol.make_envelope( + _session.get_session_id(), + _envelope_sequence, + event_id, + payload, + ) + + +@rpc( + "authority", + "call_remote", + "reliable", + NetworkWorldSpawnProtocol.RELIABLE_CHANNEL, +) +func receive_world_envelope(envelope: Dictionary) -> void: + _apply_envelope(envelope) + + +@rpc( + "authority", + "call_remote", + "unreliable_ordered", + NetworkWorldSpawnProtocol.SNAPSHOT_CHANNEL, +) +func receive_world_snapshot_envelope(envelope: Dictionary) -> void: + _apply_envelope(envelope) + + +func _apply_envelope(envelope: Dictionary) -> void: + if ( + not NetworkWorldSpawnProtocol.validate_envelope(envelope) + or str(envelope["session_id"]) != _session.get_session_id() + ): + return + var event_id := StringName(str(envelope["event_id"])) + var payload: Dictionary = envelope["payload"] + match event_id: + &"spawn": + _apply_entity_state(payload.get("entity", {}), true) + &"snapshot": + _apply_entity_states(payload.get("entities", []), false, false) + &"population": + _apply_entity_states(payload.get("entities", []), true, true) + &"despawn": + _apply_despawn(payload) + &"showcase": + _apply_showcase(payload) + + +func _apply_entity_states( + values: Variant, + immediate: bool, + reconcile: bool, +) -> void: + if ( + typeof(values) != TYPE_ARRAY + or values.size() > NetworkWorldSpawnProtocol.MAX_ENTITIES_PER_SNAPSHOT + ): + return + var included: Dictionary = {} + for value: Variant in values: + if not NetworkWorldSpawnProtocol.validate_entity_state(value): + continue + var state: Dictionary = value + included[str(state["entity_id"])] = true + _apply_entity_state(state, immediate) + if not reconcile: + return + for entity_id: String in _presentations.keys(): + if not included.has(entity_id): + _remove_presentation(entity_id, false) + + +func _apply_entity_state(value: Variant, immediate: bool) -> void: + if not NetworkWorldSpawnProtocol.validate_entity_state(value): + return + var state: Dictionary = value + var entity_id: String = str(state["entity_id"]) + var revision: int = int(state["revision"]) + if revision < int(_entity_revisions.get(entity_id, -1)): + return + var entry: GatherableDataType = _catalog.get_entry( + StringName(str(state["type_id"])) + ) + if entry == null: + return + var position: Vector3 = NetworkWorldSpawnProtocol.array_to_vector3( + state["position"] + ) + var presentation := _presentations.get(entity_id) as WorldGatherableType + if presentation == null or not is_instance_valid(presentation): + presentation = WorldGatherableType.new() + presentation.name = "WorldGatherable_%s" % entity_id.right(12) + _world_root.add_child(presentation) + presentation.configure( + entity_id, + entry, + position, + float(state["yaw"]), + ) + _presentations[entity_id] = presentation + else: + presentation.apply_network_state( + position, + float(state["yaw"]), + immediate, + ) + _entity_revisions[entity_id] = revision + if not _session.is_host(): + _entities[entity_id] = { + "entity_id": entity_id, + "type_id": entry.type_id, + "data": entry, + "position": position, + "yaw": float(state["yaw"]), + "revision": revision, + "locked": false, + } + + +func _apply_despawn(payload: Dictionary) -> void: + var entity_id: String = str(payload.get("entity_id", "")) + if entity_id.is_empty(): + return + if not _session.is_host(): + _entities.erase(entity_id) + _remove_presentation(entity_id, bool(payload.get("with_dust", false))) + + +func _remove_presentation(entity_id: String, with_dust: bool) -> void: + var presentation := _presentations.get(entity_id) as WorldGatherableType + _presentations.erase(entity_id) + _entity_revisions.erase(entity_id) + if presentation != null and is_instance_valid(presentation): + presentation.play_despawn(with_dust) + + +func _apply_showcase(payload: Dictionary) -> void: + if ( + typeof(payload.get("owner_peer_id")) != TYPE_INT + or typeof(payload.get("visible")) != TYPE_BOOL + ): + return + var peer_id: int = int(payload["owner_peer_id"]) + if peer_id == _session.get_local_peer_id(): + return + var avatar: Player = _spawn_service.get_avatar(peer_id) + if avatar == null: + return + if not bool(payload["visible"]): + avatar.end_catch_showcase() + return + if typeof(payload.get("catch")) != TYPE_DICTIONARY: + return + var catch_data: Dictionary = payload["catch"] + var fish: FishDataType = _fish_catalog.get_fish_by_id( + StringName(str(catch_data.get("fish_id", ""))) + ) + var fish_catch: FishCatch = FishCatchType.from_network_dict( + catch_data, + fish, + ) + if fish_catch != null: + avatar.begin_remote_catch_showcase(fish_catch) + + +func _state_to_network(state: Dictionary) -> Dictionary: + return { + "entity_id": str(state["entity_id"]), + "type_id": str(state["type_id"]), + "position": NetworkWorldSpawnProtocol.vector3_to_array( + state["position"] + ), + "yaw": float(state["yaw"]), + "revision": int(state["revision"]), + } + + +func _on_peer_authenticated(peer_id: int, _display_name: String) -> void: + if not _session.is_host(): + return + var states: Array[Dictionary] = [] + for state: Dictionary in _entities.values(): + states.append(_state_to_network(state)) + var envelope: Dictionary = _make_envelope( + &"population", + {"entities": states}, + ) + receive_world_envelope.rpc_id(peer_id, envelope) + + +func _on_peer_removed(peer_id: int) -> void: + _charge_requests.erase(peer_id) + if _pending_captures.has(peer_id): + _unlock_pending_capture(peer_id) + _showcase_deadlines.erase(peer_id) + + +func _unlock_pending_capture(peer_id: int) -> void: + var pending: Dictionary = _pending_captures.get(peer_id, {}) + _pending_captures.erase(peer_id) + if pending.is_empty(): + return + var entity_id: String = str(pending.get("entity_id", "")) + var state: Dictionary = _entities.get(entity_id, {}) + if state.is_empty(): + return + state["locked"] = false + _entities[entity_id] = state + + +func _on_session_state_changed(_state: NetworkSession.State) -> void: + if _session.is_gameplay_session_active(): + _begin_population_if_ready.call_deferred() + return + _clear_world() + _population_session_id = "" + + +func _clear_world() -> void: + for presentation: WorldGatherableType in _presentations.values(): + if presentation != null and is_instance_valid(presentation): + presentation.queue_free() + _entities.clear() + _presentations.clear() + _entity_revisions.clear() + _surface_triangles.clear() + _surface_areas.clear() + _surface_total_areas.clear() + _respawns.clear() + _next_respawn_by_type.clear() + _charge_requests.clear() + _pending_captures.clear() + _showcase_deadlines.clear() + _snapshot_elapsed = 0.0 + + +func _bound(values: Dictionary) -> void: + while values.size() > MAX_LEDGER_ENTRIES: + values.erase(values.keys().front()) + + +func _new_id(prefix: String) -> String: + return "%s:%s" % [ + prefix, + Crypto.new().generate_random_bytes(16).hex_encode(), + ] + + +func _now() -> float: + return Time.get_ticks_msec() / 1000.0 diff --git a/network/network_world_spawn_service.gd.uid b/network/network_world_spawn_service.gd.uid new file mode 100644 index 0000000..e9badc5 --- /dev/null +++ b/network/network_world_spawn_service.gd.uid @@ -0,0 +1 @@ +uid://bmuhkkl2cxsck diff --git a/player/fishing_rod_attachment.tscn b/player/fishing_rod_attachment.tscn index 46da419..f284825 100644 --- a/player/fishing_rod_attachment.tscn +++ b/player/fishing_rod_attachment.tscn @@ -14,10 +14,12 @@ bone_name = "rod_socket" [node name="FishingRod" type="Node3D" parent="."] -[node name="RodMesh" type="MeshInstance3D" parent="FishingRod"] +[node name="FallbackRodMesh" type="MeshInstance3D" parent="FishingRod"] position = Vector3(0, 0.5, 0) mesh = SubResource("FishingRodMesh") material_override = SubResource("FishingRodMaterial") +[node name="ModelMount" type="Node3D" parent="FishingRod"] + [node name="FishingRodTip" type="Marker3D" parent="FishingRod"] position = Vector3(0, 1.1, 0) diff --git a/player/net_attachment.tscn b/player/net_attachment.tscn new file mode 100644 index 0000000..7c16fca --- /dev/null +++ b/player/net_attachment.tscn @@ -0,0 +1,55 @@ +[gd_scene load_steps=7 format=3] + +[sub_resource type="StandardMaterial3D" id="HandleMaterial"] +albedo_color = Color(0.25, 0.13, 0.055, 1) +roughness = 0.9 + +[sub_resource type="CylinderMesh" id="HandleMesh"] +material = SubResource("HandleMaterial") +top_radius = 0.025 +bottom_radius = 0.035 +height = 1.25 + +[sub_resource type="StandardMaterial3D" id="HoopMaterial"] +albedo_color = Color(0.34, 0.39, 0.4, 1) +metallic = 0.25 +roughness = 0.65 + +[sub_resource type="TorusMesh" id="HoopMesh"] +material = SubResource("HoopMaterial") +inner_radius = 0.24 +outer_radius = 0.29 +rings = 12 +ring_segments = 18 + +[sub_resource type="StandardMaterial3D" id="NetMaterial"] +transparency = 1 +albedo_color = Color(0.65, 0.78, 0.75, 0.42) +shading_mode = 0 +cull_mode = 2 + +[sub_resource type="CylinderMesh" id="NetMesh"] +material = SubResource("NetMaterial") +top_radius = 0.24 +bottom_radius = 0.08 +height = 0.34 +radial_segments = 12 +rings = 1 + +[node name="NetAttachment" type="BoneAttachment3D"] +bone_name = "rod_socket" + +[node name="CatchingNet" type="Node3D" parent="."] +visible = false + +[node name="Handle" type="MeshInstance3D" parent="CatchingNet"] +position = Vector3(0, 0.55, 0) +mesh = SubResource("HandleMesh") + +[node name="Hoop" type="MeshInstance3D" parent="CatchingNet"] +position = Vector3(0, 1.17, 0) +mesh = SubResource("HoopMesh") + +[node name="Bag" type="MeshInstance3D" parent="CatchingNet"] +position = Vector3(0, 1.0, 0) +mesh = SubResource("NetMesh") diff --git a/player/player.gd b/player/player.gd index a381e11..efc288b 100644 --- a/player/player.gd +++ b/player/player.gd @@ -31,6 +31,8 @@ const ControllerMappingManagerType = preload( const FishingRodAttachmentScene = preload( "res://player/fishing_rod_attachment.tscn" ) +const NetAttachmentScene = preload("res://player/net_attachment.tscn") +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") const HeldItemAttachmentScene = preload( "res://player/held_item_attachment.tscn" ) @@ -68,6 +70,10 @@ const CHARACTER_FISHING_ANIMATION: StringName = &"fishing" const CHARACTER_FISHING_SIT_ANIMATION: StringName = &"fishing_sit" const CHARACTER_FIGHTING_ANIMATION: StringName = &"fighting" const CHARACTER_FIGHTING_SIT_ANIMATION: StringName = &"fighting_sit" +# Add future networked emote animation IDs here. The protocol accepts unknown +# 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] = [] enum FishingVisualPhase { NONE, @@ -84,6 +90,12 @@ enum PocketVisualTarget { ART_KIT, CATCH_SHOWCASE, } + +enum LocomotionState { + IDLE, + WALKING, + RUNNING, +} const FIGHTING_EYES_ID: String = "alligator_eyes" const BLINK_EYES_ID: String = "closed" const BLINK_INTERVAL_SECONDS := Vector2(2.8, 7.5) @@ -92,6 +104,11 @@ const CHARACTER_CALL_MOUTH_ID: String = "open_ah" const CHARACTER_CALL_MOUTH_DURATION_SECONDS: float = 0.16 const BASE_REEL_SPEED: float = 0.16 const LANDING_DUST_MIN_FALL_SPEED: float = 2.5 +const NETWORK_EXTRAPOLATION_LIMIT_SECONDS: float = 0.5 +const LOCAL_PREDICTION_EXTRAPOLATION_LIMIT_SECONDS: float = 0.25 +const LOCAL_PREDICTION_CORRECTION_THRESHOLD: float = 0.12 +const LOCAL_PREDICTION_SNAP_DISTANCE: float = 2.0 +const LOCAL_PREDICTION_CORRECTION_WEIGHT: float = 0.18 # The target Android handheld exposes its physical right trigger through # Godot's left-trigger axis. Keep the role named here so the platform mapping # remains isolated from camera behavior. @@ -371,6 +388,7 @@ var _remote_recovery_visual_origin: Vector3 var _remote_recovery_elapsed: float = 0.0 var _target_zoom: float = 5.0 var _showcase_rod_visibility: bool = true +var _showcase_net_visibility: bool = false var _remote_presentation_visible := true var _showcase_rod_state_stored: bool = false var _showcase_visual_rotation: Vector3 @@ -393,8 +411,18 @@ var _last_network_input_sequence: int = 0 var _network_target_position: Vector3 var _network_target_velocity: Vector3 var _network_target_visual_yaw: float = 0.0 +var _network_target_grounded: bool = false +var _network_target_locomotion_state: LocomotionState = LocomotionState.IDLE +var _network_target_animation_action_id: StringName = &"" +var _network_target_animation_action_sequence: int = 0 +var _network_target_animation_action_elapsed: float = 0.0 var _network_snapshot_ready: bool = false var _network_snapshot_age: float = 0.0 +var _animation_action_id: StringName = &"" +var _animation_action_sequence: int = 0 +var _animation_action_elapsed: float = 0.0 +var _presented_animation_action_id: StringName = &"" +var _presented_animation_action_sequence: int = -1 var _character_animation_name: StringName = &"" var _sitting: bool = false var _sit_after_landing: bool = false @@ -424,8 +452,14 @@ var _fishing_visual_phase: FishingVisualPhase = FishingVisualPhase.NONE var _fishing_after_release_pending: bool = false var _retract_animation_completed: bool = true var _fishing_rod: Node3D +var _catching_net: Node3D var _fishing_rod_tip: Marker3D +var _fishing_rod_model_mount: Node3D +var _fishing_rod_fallback_visual: GeometryInstance3D +var _custom_fishing_rod_visual: Node3D +var _active_fishing_rod_id: StringName var _active_item_is_rod: bool = false +var _active_item_is_net: bool = false var _controller_mapping_manager: ControllerMappingManagerType var _sprint_dust_landing_ready: bool = false var _sprint_dust_airborne: bool = false @@ -450,6 +484,7 @@ func _ready() -> void: bag.contents_changed.connect(_on_bag_contents_changed) _apply_presented_appearance() _initialize_fishing_rod() + _initialize_catching_net() _target_zoom = clampf(_spring_arm.spring_length, minimum_zoom, maximum_zoom) _spring_arm.spring_length = _target_zoom _camera.current = local_control_enabled @@ -475,6 +510,72 @@ func set_controller_mapping_manager( _controller_mapping_manager = mapping_manager +func begin_animation_action( + action_id: StringName, + elapsed_seconds: float = 0.0, +) -> bool: + if ( + action_id.is_empty() + or not supports_network_animation_action(action_id) + or _character_animation_player == null + or not _character_animation_player.has_animation(action_id) + ): + return false + var next_sequence: int = ( + 1 + if _animation_action_sequence + >= NetworkPlayerAnimationProtocol.MAX_ACTION_SEQUENCE + else _animation_action_sequence + 1 + ) + var action_state: Dictionary = ( + NetworkPlayerAnimationProtocol.make_action_state( + action_id, next_sequence, elapsed_seconds + ) + ) + if not NetworkPlayerAnimationProtocol.validate_action_state(action_state): + return false + _apply_animation_action_state(action_state) + return true + + +func end_animation_action() -> void: + if _animation_action_id.is_empty(): + return + var next_sequence: int = ( + 1 + if _animation_action_sequence + >= NetworkPlayerAnimationProtocol.MAX_ACTION_SEQUENCE + else _animation_action_sequence + 1 + ) + _apply_animation_action_state( + NetworkPlayerAnimationProtocol.make_action_state( + &"", next_sequence, 0.0 + ) + ) + + +func _apply_animation_action_state(action_state: Dictionary) -> void: + if not NetworkPlayerAnimationProtocol.validate_action_state(action_state): + return + var action_id := StringName(str(action_state["id"])) + if ( + not action_id.is_empty() + and not supports_network_animation_action(action_id) + ): + action_id = &"" + var action_sequence: int = int(action_state["sequence"]) + var action_changed: bool = ( + action_id != _animation_action_id + or action_sequence != _animation_action_sequence + ) + _animation_action_id = action_id + _animation_action_sequence = action_sequence + _animation_action_elapsed = float(action_state["elapsed"]) + if action_changed: + _character_animation_name = &"" + _update_character_animation() + + func _initialize_fishing_rod() -> void: var skeleton := get_node_or_null( "Visuals/CharacterRig/CharacterRig/Skeleton3D" @@ -491,6 +592,29 @@ func _initialize_fishing_rod() -> void: _fishing_rod_tip = attachment.get_node( "FishingRod/FishingRodTip" ) as Marker3D + _fishing_rod_model_mount = attachment.get_node( + "FishingRod/ModelMount" + ) as Node3D + _fishing_rod_fallback_visual = attachment.get_node( + "FishingRod/FallbackRodMesh" + ) as GeometryInstance3D + + +func _initialize_catching_net() -> void: + var skeleton := get_node_or_null( + "Visuals/CharacterRig/CharacterRig/Skeleton3D" + ) as Skeleton3D + if skeleton == null: + push_error("Player character skeleton is unavailable for the net.") + return + var attachment := NetAttachmentScene.instantiate() as BoneAttachment3D + if attachment == null: + push_error("Catching net attachment could not be instantiated.") + return + skeleton.add_child(attachment) + _catching_net = attachment.get_node("CatchingNet") as Node3D + if _catching_net != null: + _catching_net.visible = false func _initialize_held_item_attachment() -> void: @@ -613,6 +737,11 @@ func _physics_process(delta: float) -> void: func _process(delta: float) -> void: + if not _animation_action_id.is_empty(): + _animation_action_elapsed = minf( + _animation_action_elapsed + delta, + NetworkPlayerAnimationProtocol.MAX_ACTION_ELAPSED_SECONDS, + ) _update_blink(delta) _update_character_animation() _update_sprint_dust(delta) @@ -643,6 +772,13 @@ func _process(delta: float) -> void: if not local_control_enabled: return if _free_camera_active: + if _is_camera_input_enabled(): + _rotate_free_camera( + _scale_controller_camera_input( + _get_controller_camera_stick(), + delta, + ) + ) return if _camera_dragging and not Input.is_action_pressed("camera_drag"): @@ -661,15 +797,8 @@ func _process(delta: float) -> void: + vertical_zoom_input * controller_zoom_speed * delta ) else: - var adjusted_strength: float = ( - (stick.length() - controller_camera_deadzone) - / (1.0 - controller_camera_deadzone) - ) _rotate_camera( - stick.normalized() - * adjusted_strength - * controller_camera_speed - * delta + _scale_controller_camera_input(stick, delta) ) var zoom_weight: float = 1.0 - exp(-zoom_smoothing * delta) @@ -705,7 +834,7 @@ func _update_sprint_dust(delta: float) -> void: if _sprint_dust == null: return var horizontal_velocity := Vector3(velocity.x, 0.0, velocity.z) - var grounded: bool = is_on_floor() + var grounded: bool = _get_presented_grounded() if _water_recovery_active: _sprint_dust_airborne = false _sprint_dust_fall_speed = 0.0 @@ -730,18 +859,11 @@ func _update_sprint_dust(delta: float) -> void: ) _sprint_dust_airborne = false _sprint_dust_fall_speed = 0.0 - var horizontal_speed_squared: float = horizontal_velocity.length_squared() - var fastest_non_sprint_speed := maxf( - walk_speed, - maxf(sneak_speed, slow_walk_speed) - ) - var running_threshold := fastest_non_sprint_speed + 0.5 var should_emit: bool = ( grounded and not _sitting and not _water_recovery_active - and horizontal_speed_squared - > running_threshold * running_threshold + and _get_presented_locomotion_state() == LocomotionState.RUNNING ) _sprint_dust.update_trail( delta, @@ -767,6 +889,25 @@ func _get_controller_camera_stick() -> Vector2: ) +func _scale_controller_camera_input( + stick: Vector2, + delta: float, +) -> Vector2: + var strength: float = stick.length() + if strength <= controller_camera_deadzone: + return Vector2.ZERO + var adjusted_strength: float = ( + (strength - controller_camera_deadzone) + / (1.0 - controller_camera_deadzone) + ) + return ( + stick.normalized() + * adjusted_strength + * controller_camera_speed + * delta + ) + + func _get_controller_zoom_strength() -> float: if _controller_mapping_manager != null: return _controller_mapping_manager.get_role_strength( @@ -778,18 +919,17 @@ func _get_controller_zoom_strength() -> float: func _update_character_animation() -> void: if _character_animation_player == null: return - var horizontal_speed_squared: float = ( - velocity.x * velocity.x + velocity.z * velocity.z + var locomotion_state: LocomotionState = ( + _get_presented_locomotion_state() ) - var is_walking: bool = horizontal_speed_squared > 0.0025 - var fastest_non_sprint_speed := maxf( - walk_speed, - maxf(sneak_speed, slow_walk_speed) - ) - var running_threshold := fastest_non_sprint_speed + 0.5 - var is_running := ( - is_walking - and horizontal_speed_squared > running_threshold * running_threshold + var is_walking: bool = locomotion_state != LocomotionState.IDLE + var is_running: bool = locomotion_state == LocomotionState.RUNNING + var animation_action: Dictionary = _get_presented_animation_action() + var animation_action_id := StringName(str(animation_action.get("id", ""))) + var animation_action_available: bool = ( + not animation_action_id.is_empty() + and supports_network_animation_action(animation_action_id) + and _character_animation_player.has_animation(animation_action_id) ) var held_show_item_visible: bool = ( _held_fish_visible or _held_art_kit_visible @@ -878,6 +1018,8 @@ func _update_character_animation() -> void: CHARACTER_FISHING_ANIMATION, CHARACTER_IDLE_ANIMATION, ] + elif animation_action_available: + requested_animation = [animation_action_id] elif _sitting: if held_show_item_visible: requested_animation = [ @@ -934,13 +1076,45 @@ func _update_character_animation() -> void: break if next_animation.is_empty(): return - if _character_animation_name == next_animation: + var action_selected: bool = ( + animation_action_available and next_animation == animation_action_id + ) + var action_sequence: int = int(animation_action.get("sequence", 0)) + var action_changed: bool = ( + action_selected + and ( + animation_action_id != _presented_animation_action_id + or action_sequence != _presented_animation_action_sequence + ) + ) + if not action_selected: + _presented_animation_action_id = &"" + _presented_animation_action_sequence = -1 + if _character_animation_name == next_animation and not action_changed: return _character_animation_player.play(next_animation) _character_animation_name = next_animation + if action_selected: + _presented_animation_action_id = animation_action_id + _presented_animation_action_sequence = action_sequence + var elapsed: float = float(animation_action.get("elapsed", 0.0)) + var animation: Animation = _character_animation_player.get_animation( + next_animation + ) + if elapsed > 0.0 and animation != null and animation.length > 0.0: + _character_animation_player.seek( + fposmod(elapsed, animation.length), true + ) func _on_character_animation_finished(animation_name: StringName) -> void: + if ( + local_control_enabled + and not _animation_action_id.is_empty() + and animation_name == _animation_action_id + ): + end_animation_action() + return if animation_name in [ CHARACTER_POCKET_IDLE_IDLE_ANIMATION, CHARACTER_POCKET_IDLE_SHOW_ANIMATION, @@ -1278,6 +1452,11 @@ func configure_network_remote(authoritative_simulation: bool) -> void: _network_interpolation_enabled = not authoritative_simulation _network_snapshot_ready = false _network_snapshot_age = 0.0 + _network_target_grounded = false + _network_target_locomotion_state = LocomotionState.IDLE + _network_target_animation_action_id = &"" + _network_target_animation_action_sequence = 0 + _network_target_animation_action_elapsed = 0.0 _camera.current = false @@ -1301,6 +1480,7 @@ func capture_network_input(sequence: int) -> Dictionary: "casting": ( _fishing_visual_phase == FishingVisualPhase.CASTING ), + "animation_action": _make_animation_action_state(), } var axis: Vector2 = Input.get_vector( "move_left", @@ -1318,6 +1498,7 @@ func capture_network_input(sequence: int) -> Dictionary: "slow_walk": Input.is_action_pressed("slow_walk"), "sitting": _sitting, "casting": _fishing_visual_phase == FishingVisualPhase.CASTING, + "animation_action": _make_animation_action_state(), } @@ -1335,6 +1516,7 @@ func apply_authoritative_network_input(data: Dictionary) -> void: _network_sprint = bool(data.get("sprint", false)) _network_sneak = bool(data.get("sneak", false)) _network_slow_walk = bool(data.get("slow_walk", false)) + _apply_animation_action_state(data.get("animation_action", {})) _apply_network_casting(bool(data.get("casting", false))) var sitting_requested: bool = bool(data.get("sitting", false)) if sitting_requested and not is_on_floor(): @@ -1352,7 +1534,13 @@ func make_network_snapshot(peer_id: int) -> Dictionary: "position": [global_position.x, global_position.y, global_position.z], "velocity": [velocity.x, velocity.y, velocity.z], "visual_yaw": _visuals.rotation.y, - "grounded": is_on_floor(), + "animation_state": NetworkPlayerAnimationProtocol.make_state( + _get_authoritative_locomotion_id(), + is_on_floor(), + _animation_action_id, + _animation_action_sequence, + _animation_action_elapsed, + ), "sitting": _sitting, "casting": _fishing_visual_phase == FishingVisualPhase.CASTING, } @@ -1365,6 +1553,7 @@ func push_network_snapshot(snapshot: Dictionary) -> void: _network_target_position = parsed["position"] _network_target_velocity = parsed["velocity"] _network_target_visual_yaw = parsed["visual_yaw"] + _apply_network_target_animation_state(parsed["animation_state"]) _network_snapshot_age = 0.0 _apply_network_casting(bool(parsed["casting"])) _set_sitting(bool(parsed["sitting"])) @@ -1375,7 +1564,11 @@ func push_network_snapshot(snapshot: Dictionary) -> void: _network_snapshot_ready = true -func apply_local_prediction_correction(snapshot: Dictionary) -> void: +func apply_local_prediction_correction( + snapshot: Dictionary, + latest_input_sequence: int = 0, + input_interval_seconds: float = 0.0, +) -> void: var parsed: Dictionary = _parse_network_snapshot(snapshot) if parsed.is_empty(): return @@ -1391,13 +1584,32 @@ func apply_local_prediction_correction(snapshot: Dictionary) -> void: if not _sitting_intent_pending: _set_sitting(bool(parsed["sitting"])) var authoritative_position: Vector3 = parsed["position"] + if ( + acknowledged_input > 0 + and latest_input_sequence > acknowledged_input + and input_interval_seconds > 0.0 + ): + # The snapshot describes the host's position when an older input was + # acknowledged. Project it through the measured input-sequence gap so + # ordinary round-trip latency is not mistaken for prediction error. + var sequence_gap: int = latest_input_sequence - acknowledged_input + var transit_seconds: float = minf( + float(sequence_gap) * input_interval_seconds, + LOCAL_PREDICTION_EXTRAPOLATION_LIMIT_SECONDS, + ) + authoritative_position += ( + (parsed["velocity"] as Vector3) * transit_seconds + ) var error_distance: float = global_position.distance_to( authoritative_position ) - if error_distance > 2.0: + if error_distance > LOCAL_PREDICTION_SNAP_DISTANCE: global_position = authoritative_position - elif error_distance > 0.05: - global_position = global_position.lerp(authoritative_position, 0.18) + elif error_distance > LOCAL_PREDICTION_CORRECTION_THRESHOLD: + global_position = global_position.lerp( + authoritative_position, + LOCAL_PREDICTION_CORRECTION_WEIGHT, + ) func apply_network_teleport(snapshot: Dictionary) -> void: @@ -1412,6 +1624,7 @@ func apply_network_teleport(snapshot: Dictionary) -> void: _network_target_position = global_position _network_target_velocity = velocity _network_target_visual_yaw = _visuals.rotation.y + _apply_network_target_animation_state(parsed["animation_state"]) _network_snapshot_age = 0.0 _network_snapshot_ready = true @@ -1437,6 +1650,12 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary: float(network_velocity[2]) ) var visual_yaw: float = float(snapshot.get("visual_yaw", 0.0)) + var animation_state_value: Variant = snapshot.get("animation_state") + if not NetworkPlayerAnimationProtocol.validate_state(animation_state_value): + return {} + var animation_state: Dictionary = ( + animation_state_value as Dictionary + ).duplicate(true) if ( snapshot.has("acknowledged_input") and ( @@ -1464,12 +1683,119 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary: "position": parsed_position, "velocity": parsed_velocity, "visual_yaw": visual_yaw, + "animation_state": animation_state, "acknowledged_input": acknowledged_input, "sitting": sitting, "casting": casting, } +func _make_animation_action_state() -> Dictionary: + return NetworkPlayerAnimationProtocol.make_action_state( + _animation_action_id, + _animation_action_sequence, + _animation_action_elapsed, + ) + + +func _apply_network_target_animation_state(state: Dictionary) -> void: + if not NetworkPlayerAnimationProtocol.validate_state(state): + return + _network_target_grounded = bool(state["grounded"]) + _network_target_locomotion_state = _locomotion_state_from_id( + StringName(str(state["locomotion_id"])) + ) + var action: Dictionary = state["action"] + _network_target_animation_action_id = StringName(str(action["id"])) + _network_target_animation_action_sequence = int(action["sequence"]) + _network_target_animation_action_elapsed = float(action["elapsed"]) + + +func _get_authoritative_locomotion_state() -> LocomotionState: + if ( + _network_authoritative_simulation + and _is_movement_input_enabled() + and not _water_recovery_active + and not _sitting + and _network_axis.length_squared() > 0.0025 + ): + return ( + LocomotionState.RUNNING + if _network_sprint + else LocomotionState.WALKING + ) + return _locomotion_state_from_velocity() + + +func _get_authoritative_locomotion_id() -> StringName: + return _locomotion_id_from_state(_get_authoritative_locomotion_state()) + + +func _get_presented_locomotion_state() -> LocomotionState: + if _network_interpolation_enabled and _network_snapshot_ready: + return _network_target_locomotion_state + return _locomotion_state_from_velocity() + + +func _get_presented_animation_action() -> Dictionary: + if _network_interpolation_enabled and _network_snapshot_ready: + return NetworkPlayerAnimationProtocol.make_action_state( + _network_target_animation_action_id, + _network_target_animation_action_sequence, + minf( + _network_target_animation_action_elapsed + _network_snapshot_age, + NetworkPlayerAnimationProtocol.MAX_ACTION_ELAPSED_SECONDS, + ), + ) + return _make_animation_action_state() + + +func _locomotion_id_from_state(state: LocomotionState) -> StringName: + match state: + LocomotionState.WALKING: + return NetworkPlayerAnimationProtocol.LOCOMOTION_WALKING + LocomotionState.RUNNING: + return NetworkPlayerAnimationProtocol.LOCOMOTION_RUNNING + _: + return NetworkPlayerAnimationProtocol.LOCOMOTION_IDLE + + +func _locomotion_state_from_id(state_id: StringName) -> LocomotionState: + match state_id: + NetworkPlayerAnimationProtocol.LOCOMOTION_WALKING: + return LocomotionState.WALKING + NetworkPlayerAnimationProtocol.LOCOMOTION_RUNNING: + return LocomotionState.RUNNING + _: + return LocomotionState.IDLE + + +static func supports_network_animation_action(action_id: StringName) -> bool: + return action_id in NETWORK_ANIMATION_ACTION_IDS + + +func _locomotion_state_from_velocity() -> LocomotionState: + var horizontal_speed_squared: float = ( + velocity.x * velocity.x + velocity.z * velocity.z + ) + if horizontal_speed_squared <= 0.0025: + return LocomotionState.IDLE + var fastest_non_sprint_speed := maxf( + walk_speed, + maxf(sneak_speed, slow_walk_speed) + ) + var running_threshold := fastest_non_sprint_speed + 0.5 + if horizontal_speed_squared > running_threshold * running_threshold: + return LocomotionState.RUNNING + return LocomotionState.WALKING + + +func _get_presented_grounded() -> bool: + if _network_interpolation_enabled and _network_snapshot_ready: + return _network_target_grounded + return is_on_floor() + + func _apply_network_casting(casting: bool) -> void: if casting: if _fishing_visual_phase == FishingVisualPhase.NONE: @@ -1481,7 +1807,10 @@ func _apply_network_casting(casting: bool) -> void: func _update_network_interpolation(delta: float) -> void: if not _network_snapshot_ready: return - _network_snapshot_age = minf(_network_snapshot_age + delta, 0.1) + _network_snapshot_age = minf( + _network_snapshot_age + delta, + NETWORK_EXTRAPOLATION_LIMIT_SECONDS, + ) var predicted_position: Vector3 = ( _network_target_position + _network_target_velocity * _network_snapshot_age @@ -1508,6 +1837,19 @@ func is_local_control_enabled() -> bool: return local_control_enabled +func is_sneaking() -> bool: + if local_control_enabled: + return ( + _is_movement_input_enabled() + and Input.is_action_pressed("sneak") + ) + return _network_sneak + + +func is_moving_horizontally() -> bool: + return Vector2(velocity.x, velocity.z).length_squared() > 0.01 + + func set_movement_enabled(enabled: bool) -> void: _movement_enabled = enabled if not enabled: @@ -1656,6 +1998,57 @@ func get_fishing_rod() -> Node3D: return _fishing_rod +func set_active_fishing_rod( + rod: FishingRodDataType, + animate_transition: bool = false, +) -> void: + _apply_fishing_rod_model(rod) + set_active_item_is_rod(rod != null and rod.is_available(), animate_transition) + + +func set_active_catching_net(should_show: bool) -> void: + _active_item_is_net = should_show + if _catching_net == null: + return + if _showcase_rod_state_stored: + _showcase_net_visibility = should_show + return + _catching_net.visible = ( + should_show and not _has_held_show_item() and not _showcase_animation_active + ) + + +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: + return + _active_fishing_rod_id = next_id + if _custom_fishing_rod_visual != null: + _custom_fishing_rod_visual.hide() + _custom_fishing_rod_visual.queue_free() + _custom_fishing_rod_visual = null + if _fishing_rod_fallback_visual == null or _fishing_rod_tip == null: + return + _fishing_rod_fallback_visual.show() + _fishing_rod_tip.position = Vector3(0.0, 1.1, 0.0) + if rod == null: + return + _fishing_rod_tip.position = rod.rod_tip_position + if rod.rod_model_scene == null or _fishing_rod_model_mount == null: + return + var instance := rod.rod_model_scene.instantiate() as Node3D + if instance == null: + push_warning( + "Rod %s model root is not Node3D; using fallback." + % rod.item_id + ) + return + _custom_fishing_rod_visual = instance + _fishing_rod_model_mount.add_child(instance) + instance.transform = rod.rod_model_transform + _fishing_rod_fallback_visual.hide() + + func set_active_item_is_rod( active_is_rod: bool, animate_transition: bool = false, @@ -1687,6 +2080,12 @@ func _apply_active_rod_visibility() -> void: _showcase_rod_visibility = _active_item_is_rod return _fishing_rod.visible = _active_item_is_rod + if _catching_net != null: + _catching_net.visible = ( + _active_item_is_net + and not _has_held_show_item() + and not _showcase_animation_active + ) func set_active_art_kit(icon: Texture2D, should_show: bool) -> void: @@ -1715,6 +2114,8 @@ func set_active_art_kit(icon: Texture2D, should_show: bool) -> void: if not previous_show_pose: _held_art_kit_display.visible = false _fishing_rod.visible = false + if _catching_net != null: + _catching_net.visible = false if item_changed: _begin_pocket_visual( PocketVisualTarget.ART_KIT, @@ -1763,6 +2164,8 @@ func set_held_fish( if not previous_show_pose: _held_fish_display.visible = false _fishing_rod.visible = false + if _catching_net != null: + _catching_net.visible = false if item_changed: _begin_pocket_visual( PocketVisualTarget.HELD_FISH, @@ -1987,8 +2390,13 @@ func begin_catch_showcase(fish_catch: FishCatchType) -> void: _turn_showcase_toward_position(showcase_camera_position) if not _showcase_rod_state_stored: _showcase_rod_visibility = _fishing_rod.visible + _showcase_net_visibility = ( + _catching_net.visible if _catching_net != null else false + ) _showcase_rod_state_stored = true _fishing_rod.visible = false + if _catching_net != null: + _catching_net.visible = false _catch_sprite.texture = fish_catch.fish.display_texture _catch_display.scale = ( Vector3.ONE @@ -2006,8 +2414,13 @@ func begin_remote_catch_showcase(fish_catch: FishCatchType) -> void: _showcase_animation_active = true set_fishing_visual(false) _showcase_rod_visibility = _fishing_rod.visible + _showcase_net_visibility = ( + _catching_net.visible if _catching_net != null else false + ) _showcase_rod_state_stored = true _fishing_rod.visible = false + if _catching_net != null: + _catching_net.visible = false _catch_sprite.texture = fish_catch.fish.display_texture _catch_display.scale = ( Vector3.ONE @@ -2047,7 +2460,10 @@ func end_catch_showcase( _catch_sprite.texture = null if _showcase_rod_state_stored: _fishing_rod.visible = _showcase_rod_visibility + if _catching_net != null: + _catching_net.visible = _showcase_net_visibility _showcase_rod_visibility = true + _showcase_net_visibility = false _showcase_rod_state_stored = false if ( not _showcase_visual_rotation_stored diff --git a/progression/character_customization_catalog.gd b/progression/character_customization_catalog.gd index 609f593..d18fc7f 100644 --- a/progression/character_customization_catalog.gd +++ b/progression/character_customization_catalog.gd @@ -60,6 +60,7 @@ const FUR_COLOR_LABELS: Dictionary = { "fur_color_4": "detail", } const DEFAULT_FUR_STYLE: String = "solid" +const FEATURE_TEXTURE_CACHE_LIMIT: int = 24 const FEATURE_CATEGORIES: PackedStringArray = ["eyes", "nose", "mouth"] const FEATURE_ASSET_ROOTS: Dictionary = { @@ -205,7 +206,9 @@ const LEGACY_OPTION_ALIASES: Dictionary = { static var _feature_assets_ready: bool = false static var _feature_options: Dictionary = {} +static var _feature_resource_paths: Dictionary = {} static var _feature_textures: Dictionary = {} +static var _feature_texture_cache_order: Array[String] = [] static var _fur_pattern_textures: Dictionary = {} @@ -330,14 +333,56 @@ static func texture_for(category_id: String, option_id: String) -> Texture2D: return null _ensure_feature_assets() var canonical_id := canonical_option_id(category_id, option_id) - var textures: Dictionary = _feature_textures.get(category_id, {}) as Dictionary - return textures.get(canonical_id) as Texture2D + var cache_id := "%s:%s" % [category_id, canonical_id] + if _feature_textures.has(cache_id): + _touch_feature_texture(cache_id) + return _feature_textures[cache_id] as Texture2D + var resource_path := feature_texture_path(category_id, canonical_id) + if resource_path.is_empty(): + return null + var texture := ResourceLoader.load( + resource_path, + "Texture2D", + ResourceLoader.CACHE_MODE_IGNORE, + ) as Texture2D + if texture == null: + return null + _feature_textures[cache_id] = texture + _touch_feature_texture(cache_id) + _trim_feature_texture_cache() + return texture + + +static func feature_texture_path(category_id: String, option_id: String) -> String: + if category_id not in FEATURE_CATEGORIES: + return "" + _ensure_feature_assets() + var canonical_id := canonical_option_id(category_id, option_id) + var paths: Dictionary = ( + _feature_resource_paths.get(category_id, {}) as Dictionary + ) + return str(paths.get(canonical_id, "")) + + +static func _touch_feature_texture(cache_id: String) -> void: + var existing_index := _feature_texture_cache_order.find(cache_id) + if existing_index >= 0: + _feature_texture_cache_order.remove_at(existing_index) + _feature_texture_cache_order.append(cache_id) + + +static func _trim_feature_texture_cache() -> void: + while _feature_texture_cache_order.size() > FEATURE_TEXTURE_CACHE_LIMIT: + var oldest_id: String = str(_feature_texture_cache_order.pop_front()) + _feature_textures.erase(oldest_id) static func refresh_feature_assets() -> void: _feature_assets_ready = false _feature_options.clear() + _feature_resource_paths.clear() _feature_textures.clear() + _feature_texture_cache_order.clear() _ensure_feature_assets() @@ -347,7 +392,7 @@ static func _ensure_feature_assets() -> void: _feature_assets_ready = true for category_id: String in FEATURE_CATEGORIES: var options: Array = [{"id": "none", "label": "none"}] - var textures: Dictionary = {} + var resource_paths: Dictionary = {} var root_path := str(FEATURE_ASSET_ROOTS[category_id]) var directory := DirAccess.open(root_path) if directory != null: @@ -365,24 +410,23 @@ static func _ensure_feature_assets() -> void: var option_id := _feature_id_from_filename( category_id, normalized_file_name ) - if option_id.is_empty() or textures.has(option_id): + if option_id.is_empty() or resource_paths.has(option_id): continue var resource_path := root_path.path_join(normalized_file_name) - var texture := ResourceLoader.load(resource_path) as Texture2D - if texture == null: + if not ResourceLoader.exists(resource_path, "Texture2D"): push_warning( "Ignoring facial feature without a loadable texture: " + resource_path ) continue - textures[option_id] = texture + resource_paths[option_id] = resource_path options.append({ "id": option_id, "label": _feature_label(option_id), }) _feature_options[category_id] = options - _feature_textures[category_id] = textures + _feature_resource_paths[category_id] = resource_paths static func _canonical_feature_filename(file_name: String) -> String: diff --git a/project.godot b/project.godot index 6975e3b..1025c82 100644 --- a/project.godot +++ b/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="NETFISHING" -config/version="0.6.8-alpha" +config/version="0.8.1-alpha" run/main_scene="res://main/main.tscn" config/features=PackedStringArray("4.7", "GL Compatibility") config/icon="res://art/exported/system_icons/netfishing_256.png" diff --git a/scripts/build_playtest.sh b/scripts/build_playtest.sh index 980d4c4..f41f85c 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.6.8-alpha" +readonly BUILD_ROOT="${PROJECT_ROOT}/builds/v0.8.1-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/netfishing" -readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.6.8-alpha-windows-x86_64.zip" -readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.6.8-alpha-linux-x86_64.zip" +readonly WINDOWS_ZIP="${BUILD_ROOT}/NETfishing-v0.8.1-alpha-windows-x86_64.zip" +readonly LINUX_ZIP="${BUILD_ROOT}/NETfishing-v0.8.1-alpha-linux-x86_64.zip" readonly GODOT_BIN="${GODOT_BIN:-godot}" if [[ ! -f "${PROJECT_ROOT}/project.godot" ]]; then diff --git a/scripts/build_portmaster.sh b/scripts/build_portmaster.sh index 198cba5..194b8be 100755 --- a/scripts/build_portmaster.sh +++ b/scripts/build_portmaster.sh @@ -140,7 +140,10 @@ The package requires an ARM64 device, two analog sticks, GLIBC 2.28 or newer, and the \`weston_pkg_0.2\` runtime. Save data and device-local configuration remain under \`netfishing/conf/\`. -See \`netfishing/licenses/\` for bundled credits and license information. +PortMaster launches use the light performance profile by default. Put the word +\`normal\` in \`netfishing/conf/performance_profile\` to opt a capable device +into the normal rendering profile. See \`netfishing/licenses/\` for bundled +credits and license information. EOF ( @@ -162,6 +165,10 @@ grep -q '"version": 4' "${STAGE_ROOT}/port.json" grep -q '"name": "netfishing.zip"' "${STAGE_ROOT}/port.json" grep -q '^# PORTMASTER: netfishing.zip, NETfishing.sh$' \ "${STAGE_ROOT}/NETfishing.sh" +grep -Fq 'PROFILE_PATH="$CONFDIR/performance_profile"' \ + "${STAGE_ROOT}/NETfishing.sh" +grep -Fq 'NETFISHING_PERFORMANCE_PROFILE=light' \ + "${STAGE_ROOT}/NETfishing.sh" if grep -q 'GPTOKEYB' "${STAGE_ROOT}/NETfishing.sh"; then echo "GPTOKEYB must not be enabled for NETfishing." >&2 exit 1 diff --git a/scripts/normalize_texture_imports.gd b/scripts/normalize_texture_imports.gd index ae36fa0..5b78ec1 100644 --- a/scripts/normalize_texture_imports.gd +++ b/scripts/normalize_texture_imports.gd @@ -27,6 +27,10 @@ const DEFAULT_PROFILE: Dictionary = { "detect_3d/compress_to": 0, }, } +const FACIAL_FEATURE_IMPORT_PATH: String = ( + "art/exported/characters/faces/" +) +const FACIAL_FEATURE_SIZE_LIMIT: int = 512 var apply_changes: bool = false var verbose: bool = false @@ -86,11 +90,11 @@ func _parse_args() -> void: quit(1) return i += 1 - roots.append(args[i]) - i += 1 - elif arg.begins_with("--root="): - roots.append(arg.trim_prefix("--root=")) - i += 1 + roots.append(args[i]) + i += 1 + elif arg.begins_with("--root="): + roots.append(arg.trim_prefix("--root=")) + i += 1 else: printerr("Unknown argument: ", arg) should_exit = true @@ -147,6 +151,8 @@ func _handle_import_file(import_path: String) -> void: var section_data: Dictionary = DEFAULT_PROFILE[section_name] for section_key: String in section_data.keys(): var desired: Variant = section_data[section_key] + if section_key == "process/size_limit": + desired = _size_limit_for(import_path) var existing: Variant = null if cfg.has_section(section_name) and cfg.has_section_key(section_name, section_key): existing = cfg.get_value(section_name, section_key) @@ -174,6 +180,13 @@ func _handle_import_file(import_path: String) -> void: updated += 1 +func _size_limit_for(import_path: String) -> int: + var normalized_path := import_path.replace("\\", "/") + if FACIAL_FEATURE_IMPORT_PATH in normalized_path: + return FACIAL_FEATURE_SIZE_LIMIT + return int(DEFAULT_PROFILE["params"]["process/size_limit"]) + + func print_summary() -> void: if apply_changes: _print( diff --git a/scripts/portmaster/NETfishing.sh b/scripts/portmaster/NETfishing.sh index 58fbfbd..97b73ef 100755 --- a/scripts/portmaster/NETfishing.sh +++ b/scripts/portmaster/NETfishing.sh @@ -78,17 +78,36 @@ fi NETFISHING_GODOT_OPTIONS=() NETFISHING_GAME_ENVIRONMENT=() -DEVICE_COMPATIBILITY="" -if [[ -r /proc/device-tree/compatible ]]; then - DEVICE_COMPATIBILITY="$(tr '\0' ' ' &2 + NETFISHING_GAME_ENVIRONMENT+=( + "NETFISHING_PERFORMANCE_PROFILE=light" + "NETFISHING_LOW_END=1" + ) NETFISHING_GODOT_OPTIONS+=( --single-window --disable-vsync diff --git a/scripts/run_validations.sh b/scripts/run_validations.sh index f7f77f3..5992197 100755 --- a/scripts/run_validations.sh +++ b/scripts/run_validations.sh @@ -11,23 +11,27 @@ readonly RUN_ROOT="$(mktemp -d -t netfishing-validations.XXXXXX)" readonly -a QUICK_TESTS=( "tests/android_readiness_validation.gd" "tests/dedicated_server_config_validation.gd" + "tests/exported_decal_hotfix_validation.gd" "tests/fish_catalog_content_validation.gd" "tests/fish_quality_validation.gd" "tests/fishing_audio_validation.gd" "tests/fishing_surface_validation.gd" "tests/fur_pattern_validation.gd" "tests/logbook_validation.gd" + "tests/network_player_animation_protocol_validation.gd" "tests/player_experience_validation.gd" "tests/shoreline_ambience_validation.gd" "tests/surface_drawing_validation.gd" "tests/terrain_blender_material_validation.gd" "tests/texture_sampling_validation.gd" "tests/world_time_validation.gd" + "tests/world_spawn_protocol_validation.gd" "tests/world_weather_validation.gd" ) readonly -a RUNTIME_TESTS=( "tests/inventory_notepad_art_validation.gd" + "tests/light_performance_profile_validation.gd" "tests/logbook_runtime_validation.gd" "tests/player_experience_ui_validation.gd" "tests/title_credits_validation.gd" @@ -40,6 +44,7 @@ readonly -a HOST_TESTS=( "tests/economy_regression_validation.gd" "tests/fish_hotbar_showcase_validation.gd" "tests/fishing_authority_validation.gd" + "tests/gathering_showcase_validation.gd" "tests/job_system_validation.gd" "tests/surface_drawing_runtime_validation.gd" ) @@ -49,18 +54,24 @@ readonly -a NETWORK_TESTS=( "tests/fish_showcase_multiplayer_validation.gd" "tests/fishing_multiplayer_validation.gd" "tests/job_multiplayer_validation.gd" + "tests/movement_multiplayer_validation.gd" "tests/operator_multiplayer_validation.gd" "tests/surface_drawing_multiplayer_validation.gd" "tests/world_time_multiplayer_validation.gd" + "tests/world_spawn_multiplayer_validation.gd" ) +readonly SESSION_SWITCH_NETWORK_TEST="tests/session_switch_multiplayer_validation.gd" + cleanup() { rm -rf -- "${RUN_ROOT}" } trap cleanup EXIT INT TERM usage() { - printf 'Usage: %s {quick|full|host|network|all|--list}\n' "$0" + printf \ + 'Usage: %s {quick|full|host|network|session-switch|all|--list}\n' \ + "$0" } prepare_root() { @@ -163,6 +174,85 @@ run_network_test() { fi } +run_session_switch_network_test() { + local script="${SESSION_SWITCH_NETWORK_TEST}" + local name="${script#tests/}" + name="${name%.gd}" + local first_root second_root client_root + local first_pid second_pid first_status second_status client_status + local both_hosts_ready=0 + first_root="$(prepare_root "${name}-first-host")" + second_root="$(prepare_root "${name}-second-host")" + client_root="$(prepare_root "${name}-client")" + printf '\n==> %s (two hosts + client)\n' "${script}" + XDG_DATA_HOME="${first_root}/data" \ + XDG_CONFIG_HOME="${first_root}/config" \ + XDG_CACHE_HOME="${first_root}/cache" \ + timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \ + --headless --path "${PROJECT_ROOT}" --script "${script}" -- first_host \ + >"${first_root}/output.log" 2>&1 & + first_pid=$! + XDG_DATA_HOME="${second_root}/data" \ + XDG_CONFIG_HOME="${second_root}/config" \ + XDG_CACHE_HOME="${second_root}/cache" \ + timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \ + --headless --path "${PROJECT_ROOT}" --script "${script}" -- second_host \ + >"${second_root}/output.log" 2>&1 & + second_pid=$! + for _attempt in {1..100}; do + if ! kill -0 "${first_pid}" 2>/dev/null \ + || ! kill -0 "${second_pid}" 2>/dev/null; then + break + fi + if ! command -v ss >/dev/null 2>&1; then + sleep 2 + both_hosts_ready=1 + break + fi + if ss -H -lun "sport = :18142" | grep -q . \ + && ss -H -lun "sport = :18143" | grep -q .; then + both_hosts_ready=1 + break + fi + sleep 0.1 + done + if ((both_hosts_ready == 0)); then + kill "${first_pid}" "${second_pid}" 2>/dev/null || true + wait "${first_pid}" 2>/dev/null || true + wait "${second_pid}" 2>/dev/null || true + printf '%s\n' '-- first host output --' + sed -n '1,240p' "${first_root}/output.log" + printf '%s\n' '-- second host output --' + sed -n '1,240p' "${second_root}/output.log" + printf '%s\n' 'error: session-switch hosts did not become ready' >&2 + return 1 + fi + set +e + XDG_DATA_HOME="${client_root}/data" \ + XDG_CONFIG_HOME="${client_root}/config" \ + XDG_CACHE_HOME="${client_root}/cache" \ + timeout "${TEST_TIMEOUT_SECONDS}s" "${GODOT_BIN}" \ + --headless --path "${PROJECT_ROOT}" --script "${script}" -- client \ + >"${client_root}/output.log" 2>&1 + client_status=$? + wait "${first_pid}" + first_status=$? + wait "${second_pid}" + second_status=$? + set -e + printf '%s\n' '-- first host output --' + sed -n '1,240p' "${first_root}/output.log" + printf '%s\n' '-- second host output --' + sed -n '1,240p' "${second_root}/output.log" + printf '%s\n' '-- client output --' + sed -n '1,240p' "${client_root}/output.log" + if ((first_status != 0 || second_status != 0 || client_status != 0)); then + printf 'error: hosts exited %d/%d; client exited %d\n' \ + "${first_status}" "${second_status}" "${client_status}" >&2 + return 1 + fi +} + run_quick() { local test_script for test_script in "${QUICK_TESTS[@]}"; do @@ -190,6 +280,7 @@ run_network() { for test_script in "${NETWORK_TESTS[@]}"; do run_network_test "${test_script}" done + run_session_switch_network_test } list_tests() { @@ -201,6 +292,8 @@ list_tests() { printf ' %s\n' "${HOST_TESTS[@]}" printf '%s\n' 'Paired network tests:' printf ' %s\n' "${NETWORK_TESTS[@]}" + printf '%s\n' 'Two-host session-switch network test:' + printf ' %s\n' "${SESSION_SWITCH_NETWORK_TEST}" } case "${1:-}" in @@ -216,6 +309,9 @@ case "${1:-}" in network) run_network ;; + session-switch) + run_session_switch_network_test + ;; all) run_full run_host diff --git a/tests/art_tools_validation.gd b/tests/art_tools_validation.gd index 14e19e7..407711d 100644 --- a/tests/art_tools_validation.gd +++ b/tests/art_tools_validation.gd @@ -220,7 +220,27 @@ func _run() -> void: assert(QuickRadialMenu.ACTIONS.has(&"hud")) game_ui.call("_on_quick_action_selected", &"freecam") assert(player.is_free_camera_active()) - assert(player.get_active_gameplay_camera().name == &"FreeCamera") + var free_camera := player.get_active_gameplay_camera() + assert(free_camera.name == &"FreeCamera") + var free_camera_yaw_before: float = free_camera.global_rotation.y + var mapping_manager := main.get( + "_controller_mapping_manager" + ) as ControllerMappingManager + var right_stick_motion := InputEventJoypadMotion.new() + right_stick_motion.device = mapping_manager.get_active_device_id() + right_stick_motion.axis = JOY_AXIS_RIGHT_X + right_stick_motion.axis_value = 0.9 + Input.parse_input_event(right_stick_motion) + await create_timer(0.1).timeout + assert( + absf(angle_difference( + free_camera.global_rotation.y, + free_camera_yaw_before, + )) > 0.02 + ) + right_stick_motion.axis_value = 0.0 + Input.parse_input_event(right_stick_motion) + await process_frame game_ui.call("_on_quick_action_selected", &"freecam") assert(not player.is_free_camera_active()) game_ui.call("_on_quick_action_selected", &"hud") diff --git a/tests/controller_ui_navigation_validation.gd b/tests/controller_ui_navigation_validation.gd index 7cecbab..6bfe7f7 100644 --- a/tests/controller_ui_navigation_validation.gd +++ b/tests/controller_ui_navigation_validation.gd @@ -61,8 +61,12 @@ func _validate_low_end_profile_contract() -> void: var launcher: String = FileAccess.get_file_as_string( "res://scripts/portmaster/NETfishing.sh" ) - assert(launcher.contains("allwinner,h616")) - assert(launcher.contains("sun50iw9p1")) + assert(launcher.contains("NETFISHING_PERFORMANCE_PROFILE:-")) + assert(launcher.contains("$CONFDIR/performance_profile")) + assert(launcher.contains("normal)")) + assert(launcher.contains("light|\"\")")) + assert(launcher.contains("NETFISHING_PERFORMANCE_PROFILE=normal")) + assert(launcher.contains("NETFISHING_PERFORMANCE_PROFILE=light")) assert(launcher.contains("NETFISHING_LOW_END=1")) assert(launcher.contains("--max-fps 30")) assert(launcher.contains("--audio-output-latency 40")) diff --git a/tests/economy_regression_validation.gd b/tests/economy_regression_validation.gd index 51ba48d..42c56e3 100644 --- a/tests/economy_regression_validation.gd +++ b/tests/economy_regression_validation.gd @@ -48,7 +48,8 @@ func _run() -> void: as PlayerAssetReservationService ) assert(player != null) - assert(catalog != null and catalog.candidates.size() == 53) + assert(catalog != null and catalog.candidates.size() == 314) + assert(LogbookCatalog.ordered_species(catalog.candidates).size() == 54) assert(sale_service != null) assert(shop_service != null) assert(session != null and session.is_host()) @@ -72,6 +73,7 @@ func _run() -> void: assert(session.set_host_open(false)) await _test_host_shop_purchase(main, player, shop_service) + await _test_host_rod_purchase(player, shop_service) await _test_host_art_shop_purchase(main, player, shop_service) await _test_fishing_shop_sale_ui( main, player, catalog, sale_service, reservations @@ -160,6 +162,8 @@ func _run_multiplayer_client() -> void: shop_service.local_purchase_finished.connect(_on_shop_finished) var catch_ids: Array[StringName] = [] for fish: FishData in catalog.candidates: + if not fish.active: + continue var fish_catch := _make_catch(fish) player.inventory.add_catch(fish_catch) catch_ids.append(fish_catch.catch_id) @@ -260,6 +264,8 @@ func _test_each_species( sale_service: NetworkSaleService, ) -> void: for fish: FishData in catalog.candidates: + if not fish.active: + continue assert(fish != null) var fish_catch := _make_catch(fish) player.inventory.add_catch(fish_catch) @@ -372,6 +378,8 @@ func _test_host_shop_sale( var species_ids: Array[StringName] = [] var expected_payout: int = 0 for fish: FishData in catalog.candidates: + if not fish.active: + continue var fish_catch := _make_catch(fish) player.inventory.add_catch(fish_catch) species_ids.append(fish_catch.catch_id) @@ -528,6 +536,18 @@ func _test_host_art_shop_purchase( assert(not shop_service.is_local_purchase_pending()) +func _test_host_rod_purchase( + player: Player, + shop_service: NetworkShopService, +) -> void: + assert(player.bag.remove_item(&"basic_fishing_rod", 1)) + _shop_result.clear() + assert(not shop_service.request_rod(&"basic_fishing_rod").is_empty()) + assert(not _shop_result.is_empty() and bool(_shop_result[1])) + assert(player.bag.owns_item(&"basic_fishing_rod")) + assert(not shop_service.is_local_purchase_pending()) + + func _test_fishing_shop_sale_ui( main: Node, player: Player, @@ -616,6 +636,27 @@ func _test_fishing_shop_sale_ui( assert(art_supplies_tab != null and art_supplies_tab.text == "Art Supplies") var sell_mode := shop_tabs[5] as Button assert(sell_mode != null and sell_mode.text == "Sell Fish") + var equipment_tab := shop_tabs[3] as Button + assert(equipment_tab != null and equipment_tab.text == "Equipment") + await _activate_pointer_control(equipment_tab, ui_viewport) + await process_frame + var equipment_sections: Array[String] = [] + for child: Node in shop.get_node("%SuppliesList").get_children(): + if child is Label: + equipment_sections.append((child as Label).text) + assert(equipment_sections == ["rods", "equipment"]) + var supplies_list := shop.get_node("%SuppliesList") as VBoxContainer + var rod_carousel := supplies_list.get_node("RodCarousel") as HBoxContainer + assert(rod_carousel != null) + var rod_cards := rod_carousel.get_node("RodScroll/RodCards") as HBoxContainer + assert(rod_cards != null and rod_cards.get_child_count() == 1) + var basic_rod_button := rod_cards.find_child( + "Rod_basic_fishing_rod", true, false + ) as Button + assert(basic_rod_button != null) + assert(basic_rod_button.custom_minimum_size == Vector2(144.0, 144.0)) + assert(basic_rod_button.icon != null and basic_rod_button.disabled) + assert(rod_cards.find_child("Rod_aurora_rod", true, false) == null) await _activate_pointer_control(art_supplies_tab, ui_viewport) await process_frame var stock_sections: Array[String] = [] diff --git a/tests/equipment_catalog_validation.gd b/tests/equipment_catalog_validation.gd new file mode 100644 index 0000000..3e562b4 --- /dev/null +++ b/tests/equipment_catalog_validation.gd @@ -0,0 +1,49 @@ +extends SceneTree + +const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd") +const FishingShopType = preload("res://ui/fishing_shop.gd") +const ItemCatalogType = preload("res://items/item_catalog.gd") +const ItemDataType = preload("res://items/item_data.gd") +const PlayerBagType = preload("res://inventory/player_bag.gd") +const PlayerWalletType = preload("res://economy/player_wallet.gd") + +const ItemCatalogResource: ItemCatalogType = preload( + "res://items/catalog/item_catalog.tres" +) + + +func _initialize() -> void: + var shop := FishingShopType.new() + assert(shop.call("_unlock_status", false) == "locked") + assert(shop.call("_unlock_status", true) == "unlocked") + shop.free() + + var magnet: ItemDataType = ItemCatalogResource.get_available_item_by_id( + &"magnet" + ) + assert(magnet != null) + assert(magnet.category == ItemDataType.Category.TOOL) + assert(magnet.icon == null) + assert(not magnet.usable) + assert(not magnet.equippable) + assert(not magnet.hotbar_allowed) + assert(FishingShopStockType.get_price(&"magnet") == 250) + assert(FishingShopStockType.get_stock_item_ids().has(&"magnet")) + assert(FishingShopStockType.is_permanent_unlock(&"magnet", magnet)) + + var wallet := PlayerWalletType.new() + wallet.current_balance = 250 + var bag := PlayerBagType.new() + bag.setup(ItemCatalogResource) + assert(FishingShopStockType.purchase_one( + &"magnet", wallet, bag, ItemCatalogResource + )) + assert(bag.owns_item(&"magnet")) + assert(wallet.get_balance() == 0) + assert(not FishingShopStockType.purchase_one( + &"magnet", wallet, bag, ItemCatalogResource + )) + bag.free() + wallet.free() + print("Equipment catalog validation: PASS") + quit() diff --git a/tests/equipment_catalog_validation.gd.uid b/tests/equipment_catalog_validation.gd.uid new file mode 100644 index 0000000..b26406d --- /dev/null +++ b/tests/equipment_catalog_validation.gd.uid @@ -0,0 +1 @@ +uid://1f2k3yrth7qn diff --git a/tests/exported_decal_hotfix_validation.gd b/tests/exported_decal_hotfix_validation.gd index fe279b0..6560dd6 100644 --- a/tests/exported_decal_hotfix_validation.gd +++ b/tests/exported_decal_hotfix_validation.gd @@ -5,6 +5,7 @@ const FEATURE_ASSET_ROOTS: Dictionary = { "mouth": "res://art/exported/characters/faces/mouth", "nose": "res://art/exported/characters/faces/noses", } +const FACIAL_FEATURE_SIZE_LIMIT: int = 512 func _initialize() -> void: @@ -12,6 +13,8 @@ func _initialize() -> void: func _run() -> void: + CharacterCustomizationCatalog.refresh_feature_assets() + assert(CharacterCustomizationCatalog._feature_textures.is_empty()) assert( CharacterCustomizationCatalog._canonical_feature_filename( "eyes_alligator_eyes.png" @@ -33,6 +36,7 @@ func _run() -> void: ).is_empty() ) + var load_candidates: Array[Dictionary] = [] for category_id: String in FEATURE_ASSET_ROOTS: var options := CharacterCustomizationCatalog.options_for(category_id) var expected_ids: Dictionary = _feature_ids_on_disk(category_id) @@ -43,22 +47,104 @@ func _run() -> void: assert(not seen_ids.has(option_id)) seen_ids[option_id] = true if option_id != "none": - assert( - CharacterCustomizationCatalog.texture_for( + var texture_path := ( + CharacterCustomizationCatalog.feature_texture_path( category_id, option_id - ) != null + ) ) + assert(not texture_path.is_empty()) + assert(ResourceLoader.exists(texture_path, "Texture2D")) + _validate_feature_import(texture_path) + load_candidates.append({ + "category_id": category_id, + "option_id": option_id, + }) for expected_id: String in expected_ids: assert(seen_ids.has(expected_id)) + assert(CharacterCustomizationCatalog._feature_textures.is_empty()) + assert( + load_candidates.size() + > CharacterCustomizationCatalog.FEATURE_TEXTURE_CACHE_LIMIT + ) + for candidate_index: int in range( + CharacterCustomizationCatalog.FEATURE_TEXTURE_CACHE_LIMIT + 1 + ): + var candidate: Dictionary = load_candidates[candidate_index] + assert(CharacterCustomizationCatalog.texture_for( + str(candidate["category_id"]), + str(candidate["option_id"]), + ) != null) + assert( + CharacterCustomizationCatalog._feature_textures.size() + == CharacterCustomizationCatalog.FEATURE_TEXTURE_CACHE_LIMIT + ) for mouth_id: String in ["open_ah", "open_oh", "open_smile"]: assert(CharacterCustomizationCatalog.texture_for( "mouth", mouth_id ) != null) + assert( + CharacterCustomizationCatalog._feature_textures.size() + <= CharacterCustomizationCatalog.FEATURE_TEXTURE_CACHE_LIMIT + ) + + _validate_linux_arm64_texture_format() + await _validate_incremental_profile_previews() print("Exported decal hotfix validation: PASS") quit() +func _validate_feature_import(texture_path: String) -> void: + var import_config := ConfigFile.new() + assert(import_config.load(texture_path + ".import") == OK) + assert(int(import_config.get_value("params", "compress/mode", -1)) == 2) + assert(not bool(import_config.get_value( + "params", "mipmaps/generate", true + ))) + assert(int(import_config.get_value( + "params", "process/size_limit", 0 + )) == FACIAL_FEATURE_SIZE_LIMIT) + assert(int(import_config.get_value( + "params", "detect_3d/compress_to", -1 + )) == 0) + + +func _validate_linux_arm64_texture_format() -> void: + var export_config := ConfigFile.new() + assert(export_config.load("res://export_presets.cfg") == OK) + assert(not bool(export_config.get_value( + "preset.3.options", "texture_format/s3tc_bptc", true + ))) + assert(bool(export_config.get_value( + "preset.3.options", "texture_format/etc2_astc", false + ))) + + +func _validate_incremental_profile_previews() -> void: + var profile_page := ProfilePage.new() + root.add_child(profile_page) + await process_frame + for category_id: String in CharacterCustomizationCatalog.FEATURE_CATEGORIES: + var cached_before: int = ( + (profile_page.get("_feature_preview_cache") as Dictionary).size() + ) + profile_page.call("_select_category", category_id) + var initial_requests: int = ( + (profile_page.get("_feature_preview_requests") as Array).size() + ) + assert(initial_requests > 3) + for _frame: int in 4: + await process_frame + var cached_after: int = ( + (profile_page.get("_feature_preview_cache") as Dictionary).size() + ) + assert(cached_after > cached_before) + assert(cached_after - cached_before < initial_requests) + profile_page.queue_free() + for _frame: int in 8: + await process_frame + + func _feature_ids_on_disk(category_id: String) -> Dictionary: var result: Dictionary = {} var root_path: String = str(FEATURE_ASSET_ROOTS[category_id]) diff --git a/tests/fish_catalog_content_validation.gd b/tests/fish_catalog_content_validation.gd index 6746d70..a2855e5 100644 --- a/tests/fish_catalog_content_validation.gd +++ b/tests/fish_catalog_content_validation.gd @@ -89,6 +89,8 @@ func _run() -> void: func _validate_weight_based_display_scale() -> void: var previous_scale: float = 0.0 for fish: FishDataType in Catalog.candidates: + if not fish.active: + continue assert(fish.is_selectable()) assert(fish.weight_min_lb > 0.0) assert(fish.weight_max_lb <= 1000.0) @@ -119,9 +121,48 @@ func _validate_weight_based_display_scale() -> void: func _validate_catalog_and_pools() -> void: - assert(Catalog.candidates.size() == 53) + assert(Catalog.candidates.size() == 314) assert(PondPool.candidates.size() == 19) assert(OceanPool.candidates.size() == 34) + var active_count: int = 0 + var inactive_count: int = 0 + var catalog_numbers: Dictionary[int, bool] = {} + for fish: FishDataType in Catalog.candidates: + assert(fish != null and not fish.id.is_empty()) + assert(fish.catalog_number > 0) + assert(not fish.collection_group.is_empty()) + assert(not catalog_numbers.has(fish.catalog_number)) + catalog_numbers[fish.catalog_number] = true + assert(not fish.logbook_fact.strip_edges().is_empty()) + if fish.active: + active_count += 1 + assert(fish.is_selectable()) + assert(fish.display_texture != null) + else: + inactive_count += 1 + assert(not fish.is_selectable()) + assert(fish.display_texture == null) + assert(active_count == 54) + assert(inactive_count == 260) + var inactive_fish: FishDataType = Catalog.get_fish_by_id(&"bowfin") + assert(inactive_fish != null and not inactive_fish.active) + var inactive_pool := FishPoolType.new() + inactive_pool.candidates = [inactive_fish] + var inactive_context := FishingContextType.new() + inactive_context.water_type = WaterType.Type.FRESH_WATER + var inactive_collection := CollectionLogType.new() + var inactive_selector := FishSelectorType.new() + inactive_selector.use_deterministic_test_seed = true + inactive_selector.begin_roll() + assert( + inactive_selector.select_fish( + inactive_pool, + inactive_context, + inactive_collection, + ) == null + ) + assert(inactive_selector.create_catch(inactive_fish) == null) + inactive_collection.free() for fish_id: StringName in ORIGINAL_IDS: var original_fish: FishDataType = Catalog.get_fish_by_id(fish_id) assert(original_fish != null) @@ -132,14 +173,20 @@ func _validate_catalog_and_pools() -> void: assert(OceanPool.get_fish_by_id(fish_id) == null) assert(fresh_fish.is_allowed_in_water(WaterType.Type.FRESH_WATER)) assert(not fresh_fish.is_allowed_in_water(WaterType.Type.SALT_WATER)) - assert(LogbookCatalog.category_for(fresh_fish) == WaterType.Type.FRESH_WATER) + assert( + LogbookCatalog.category_for(fresh_fish) + == LogbookCatalog.Category.FRESH_WATER + ) for fish_id: StringName in SALT_WATER_IDS: var salt_fish: FishDataType = Catalog.get_fish_by_id(fish_id) assert(OceanPool.get_fish_by_id(fish_id) == salt_fish) assert(PondPool.get_fish_by_id(fish_id) == null) assert(salt_fish.is_allowed_in_water(WaterType.Type.SALT_WATER)) assert(not salt_fish.is_allowed_in_water(WaterType.Type.FRESH_WATER)) - assert(LogbookCatalog.category_for(salt_fish) == WaterType.Type.SALT_WATER) + assert( + LogbookCatalog.category_for(salt_fish) + == LogbookCatalog.Category.SALT_WATER + ) for fish_id: StringName in CATFISH_IDS: var fish: FishDataType = Catalog.get_fish_by_id(fish_id) assert(fish != null) @@ -147,13 +194,19 @@ func _validate_catalog_and_pools() -> void: assert(PondPool.get_fish_by_id(fish_id) == fish) assert(OceanPool.get_fish_by_id(fish_id) == null) assert(fish.availability.allowed_location_tags == [&"starter_pond"]) - assert(LogbookCatalog.category_for(fish) == WaterType.Type.FRESH_WATER) + assert( + LogbookCatalog.category_for(fish) + == LogbookCatalog.Category.FRESH_WATER + ) for fish_id: StringName in NEW_FRESH_WATER_IDS: var fish: FishDataType = Catalog.get_fish_by_id(fish_id) assert(fish != null and fish.is_selectable()) assert(PondPool.get_fish_by_id(fish_id) == fish) assert(OceanPool.get_fish_by_id(fish_id) == null) - assert(LogbookCatalog.category_for(fish) == WaterType.Type.FRESH_WATER) + assert( + LogbookCatalog.category_for(fish) + == LogbookCatalog.Category.FRESH_WATER + ) var expected_values: Dictionary[StringName, Array] = { &"catfish_blue": [1, 1.25, 3.0, 12.0, 6, 9], @@ -277,21 +330,33 @@ func _validate_authoritative_water_filter() -> void: stale_fresh_pool_region.location_tags = [&"coast", &"ocean"] var evidence := {"discovered_fish_ids": []} assert( - service.call("_select_authoritative_fish", pond_region, evidence, null) - != null - ) - assert( - service.call("_select_authoritative_fish", ocean_region, evidence, null) + service.call( + "_select_authoritative_fish", pond_region, evidence, null, null + ) != null ) assert( service.call( - "_select_authoritative_fish", stale_salt_pool_region, evidence, null + "_select_authoritative_fish", ocean_region, evidence, null, null + ) + != null + ) + assert( + service.call( + "_select_authoritative_fish", + stale_salt_pool_region, + evidence, + null, + null, ) == null ) assert( service.call( - "_select_authoritative_fish", stale_fresh_pool_region, evidence, null + "_select_authoritative_fish", + stale_fresh_pool_region, + evidence, + null, + null, ) == null ) service.free() @@ -347,11 +412,14 @@ func _validate_catches_and_authoritative_sale() -> void: } sale_service.set("_buyers", sale_buyers) - for index: int in Catalog.candidates.size(): - var fish: FishDataType = Catalog.candidates[index] + var catch_sequence: int = 0 + for fish: FishDataType in Catalog.candidates: + if not fish.active: + continue + catch_sequence += 1 var fish_catch: FishCatch = selector.create_catch(fish) assert(fish_catch != null) - fish_catch.catch_sequence = index + 1 + fish_catch.catch_sequence = catch_sequence assert(fish_catch.fish.display_texture == fish.display_texture) var loaded: FishCatch = FishCatchType.from_save_dict( @@ -385,7 +453,7 @@ func _validate_catches_and_authoritative_sale() -> void: var sale_result: Dictionary = sale_service.call( "_build_authoritative_result", 1, - "catalog_sale_%d" % index, + "catalog_sale_%d" % catch_sequence, [loaded.to_network_dict()], PelicanBuyer, ) diff --git a/tests/fish_hotbar_showcase_validation.gd b/tests/fish_hotbar_showcase_validation.gd index 8919221..35bb7c4 100644 --- a/tests/fish_hotbar_showcase_validation.gd +++ b/tests/fish_hotbar_showcase_validation.gd @@ -181,7 +181,7 @@ 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 == 3) + assert(NetworkProtocol.PROTOCOL_VERSION == 5) assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10) assert( NetworkProtocol.FISH_QUALITY_CAPABILITY diff --git a/tests/fish_quality_validation.gd b/tests/fish_quality_validation.gd index 72ebecc..2020575 100644 --- a/tests/fish_quality_validation.gd +++ b/tests/fish_quality_validation.gd @@ -25,7 +25,7 @@ func _run() -> void: _validate_mail_round_trip() _validate_collection_mastery() _validate_version_four_migration() - assert(NetworkProtocol.PROTOCOL_VERSION == 3) + assert(NetworkProtocol.PROTOCOL_VERSION == 5) assert(NetworkProtocol.ENET_CHANNEL_COUNT == 10) assert(NetworkProtocol.FISH_QUALITY_CAPABILITY == "fish_quality_v1") print("Fish quality validation: PASS") diff --git a/tests/fishing_authority_validation.gd b/tests/fishing_authority_validation.gd index 20f02e1..6b0429d 100644 --- a/tests/fishing_authority_validation.gd +++ b/tests/fishing_authority_validation.gd @@ -31,6 +31,9 @@ func _run() -> void: "%NetworkFishingService" ) as NetworkFishingService var fishing_spot := main.get_node("%FishingSpot") as FishingSpotType + var game_ui := main.get_node("UIPresentation/UIViewport/GameUI") + var fishing_status := game_ui.get_node("%StatusLabel") as Label + var fishing_panel := game_ui.get_node("%FishingPanel") as PanelContainer var player := main.get("_player") as Player assert(session.is_host()) assert(not session.is_open_host()) @@ -71,6 +74,9 @@ func _run() -> void: await process_frame assert(fishing_spot.state == FishingSpotType.FishingState.WAITING_FOR_BITE) assert(service.has_local_attempt()) + assert(fishing_status.text.is_empty()) + assert(not fishing_status.visible) + assert(not fishing_panel.visible) var attempts: Dictionary = service.get("_attempts") var attempt: NetworkFishingAttempt = attempts.get(session.get_local_peer_id()) assert(attempt != null) @@ -164,9 +170,12 @@ func _run() -> void: <= FishQuality.BARRIER_HEALTH_MAXIMUMS[fish_catch.quality] ) reference_controller.queue_free() - service.call("_cancel_attempt", session.get_local_peer_id(), "") + service.call("_on_attempt_escaped", session.get_local_peer_id()) await process_frame assert(not service.has_local_attempt()) + assert(fishing_status.text.is_empty()) + assert(not fishing_status.visible) + assert(not fishing_panel.visible) print("Fishing authority validation: PASS") session.disconnect_session("") diff --git a/tests/fishing_multiplayer_validation.gd b/tests/fishing_multiplayer_validation.gd index f0976f8..c40e492 100644 --- a/tests/fishing_multiplayer_validation.gd +++ b/tests/fishing_multiplayer_validation.gd @@ -147,12 +147,6 @@ func _run_host() -> void: await process_frame assert(remote_animation_player.current_animation == &"retract_sit") - var completion_deadline: int = Time.get_ticks_msec() + 12000 - while ( - Time.get_ticks_msec() < completion_deadline - and session.get_authenticated_peer_ids().size() == 2 - ): - await process_frame print("Fishing multiplayer host validation: PASS") session.disconnect_session("") main.queue_free() @@ -337,7 +331,14 @@ func _run_client() -> void: while Time.get_ticks_msec() < host_observation_deadline: await process_frame print("Fishing multiplayer client validation: PASS") - session.disconnect_session("") + var host_completion_deadline: int = Time.get_ticks_msec() + 10000 + while ( + Time.get_ticks_msec() < host_completion_deadline + and session.is_joined_client() + ): + await process_frame + if session.is_joined_client(): + session.disconnect_session("") main.queue_free() for _frame: int in 4: await process_frame diff --git a/tests/gathering_showcase_validation.gd b/tests/gathering_showcase_validation.gd new file mode 100644 index 0000000..0138bad --- /dev/null +++ b/tests/gathering_showcase_validation.gd @@ -0,0 +1,89 @@ +extends SceneTree + +const MainScene: PackedScene = preload("res://main/main.tscn") +const TEST_PORT: int = 18142 + + +func _initialize() -> void: + call_deferred("_run") + + +func _run() -> void: + 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"))) + + var session := main.get_node("%NetworkSession") as NetworkSession + 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") + for _frame: int in 8: + await process_frame + + var player := main.get("_player") as Player + var fishing_spot := main.get_node("%FishingSpot") as FishingSpot + var catalog := main.get("fish_catalog") as FishPool + assert(player != null and fishing_spot != null and catalog != null) + assert(player.bag.add_item(&"crab_net")) + assert(player.hotbar.assign_item(0, &"crab_net")) + assert(player.hotbar.select_slot(0)) + await process_frame + assert(player.hotbar.get_selected_item_id() == &"crab_net") + assert(bool(player.get("_active_item_is_net"))) + + var crab: FishData = catalog.get_fish_by_id(&"crab_brown") + assert(crab != null and crab.is_selectable()) + var crab_catch := FishCatch.new() + crab_catch.fish = crab + crab_catch.fish_id = crab.id + crab_catch.weight_lb = crab.get_minimum_weight() + crab_catch.display_scale = crab.get_display_scale_for_weight( + crab_catch.weight_lb + ) + crab_catch.sale_value = crab.get_sale_value_for_weight( + crab_catch.weight_lb + ) + crab_catch.ensure_identity() + player.inventory.add_catch(crab_catch) + player.collection_log.mark_discovered(crab.id) + + assert(fishing_spot.present_external_catch(crab_catch)) + assert(fishing_spot.state == FishingSpot.FishingState.SHOWING_CATCH) + assert(not player.is_movement_enabled()) + Input.action_release("fish_primary") + await process_frame + assert(bool(fishing_spot.get("_put_away_press_armed"))) + + var pocket_event := InputEventAction.new() + pocket_event.action = &"fish_primary" + pocket_event.pressed = true + Input.parse_input_event(pocket_event) + await process_frame + assert(fishing_spot.get("_pending_catch") == null) + + var pocket_deadline: int = Time.get_ticks_msec() + 6000 + while ( + Time.get_ticks_msec() < pocket_deadline + and fishing_spot.state != FishingSpot.FishingState.READY + ): + await process_frame + assert(fishing_spot.state == FishingSpot.FishingState.READY) + assert(player.is_movement_enabled()) + assert(player.inventory.contains_catch_id(crab_catch.catch_id)) + assert(bool(player.get("_active_item_is_net"))) + print("Gathering showcase validation: PASS") + + 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/gathering_showcase_validation.gd.uid b/tests/gathering_showcase_validation.gd.uid new file mode 100644 index 0000000..a0266d4 --- /dev/null +++ b/tests/gathering_showcase_validation.gd.uid @@ -0,0 +1 @@ +uid://cedarlvsw4bpm diff --git a/tests/job_multiplayer_validation.gd b/tests/job_multiplayer_validation.gd index b40fc2e..c31d392 100644 --- a/tests/job_multiplayer_validation.gd +++ b/tests/job_multiplayer_validation.gd @@ -23,13 +23,12 @@ func _run() -> void: 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 var jobs := main.get_node("%PlayerJobService") as PlayerJobService - assert(session.start_private_host(TEST_PORT)) - assert(save_manager.initialize_new_game()) - main.call("_enter_gameplay") + assert(session.start_dedicated_host(TEST_PORT, 8, "127.0.0.1")) + jobs.begin_progression_session() await process_frame assert(session.set_host_open(true)) + assert(session.is_dedicated_host()) var host_board: Dictionary = jobs.get_host_board_network_data() assert(PlayerJobService.validate_board(host_board)) @@ -87,6 +86,20 @@ func _run_client() -> void: assert(not jobs.get_plan_id().is_empty()) assert(jobs.get_daily_jobs().size() == JobCatalog.DAILY_JOB_COUNT) assert(jobs.get_forecast().size() == JobCatalog.WEATHER_SEGMENT_COUNT) + var preserved_plan_id: String = jobs.get_plan_id() + jobs.clear_remote_board() + assert(not jobs.has_active_board()) + assert(jobs.get_plan_id() == preserved_plan_id) + var retry_deadline: int = Time.get_ticks_msec() + 5000 + while ( + Time.get_ticks_msec() < retry_deadline + and not jobs.has_active_board() + ): + await process_frame + assert(jobs.has_active_board()) + assert(jobs.get_plan_id() == preserved_plan_id) + assert(jobs.get_daily_jobs().size() == JobCatalog.DAILY_JOB_COUNT) + assert(jobs.get_forecast().size() == JobCatalog.WEATHER_SEGMENT_COUNT) assert(weather.get_daily_plan_id().is_empty()) var game_ui := main.get_node("%GameUI") as GameUI var player_menu := game_ui.get("_player_menu") as PlayerMenu diff --git a/tests/job_system_validation.gd b/tests/job_system_validation.gd index b6036eb..32fe10e 100644 --- a/tests/job_system_validation.gd +++ b/tests/job_system_validation.gd @@ -85,6 +85,7 @@ func _run() -> void: (player_menu.get_node("%NavigationCluster") as Control).get_child_count() == 7 ) + await _validate_unavailable_fishnet_layout() var wallet_before: int = player.wallet.get_balance() var experience_before: int = player.experience.get_total_experience() @@ -162,6 +163,24 @@ func _run() -> void: var pause_menu := game_ui.get_pause_menu() var join_page := pause_menu.get_node("%JoinGamePage") as JoinGamePage assert(join_page != null) + pause_menu.show() + join_page.set_status("Connection timed out.") + join_page.show() + join_page.call( + "_on_discovery_status_changed", "2 public rooms found", false + ) + var join_status := join_page.get_node("%Status") as Label + assert(join_status.text == "Could not reach the server.") + join_page.hide() + join_page.call("_set_mode", JoinGamePage.Mode.DIRECT) + join_page.call("_set_mode", JoinGamePage.Mode.DISCOVER) + join_page.show() + join_page.call( + "_on_discovery_status_changed", "2 public rooms found", false + ) + assert(join_status.text == "2 public rooms found") + join_page.close_page() + pause_menu.hide() join_page.call("_refresh") var session_summary := ( join_page.get_node("%SessionSummary") as Label @@ -187,14 +206,63 @@ func _run() -> void: assert(int(save_data.get("save_version", -1)) == 7) assert(PlayerJobService.validate_save_data(save_data.get("jobs", {}))) - session.disconnect_session("Job system validation complete.") + _validate_pause_session_switch(main, session) main.queue_free() for _frame: int in 4: await process_frame + await create_timer(0.1).timeout print("Job system validation: PASS") quit() +func _validate_pause_session_switch( + main: Node, + session: NetworkSession, +) -> void: + var discovery := main.get_node("%DiscoveryClient") as DiscoveryClient + discovery.set("_pending_join_endpoint", "127.0.0.1:18141") + discovery.set("_pending_join_room_id", "session-switch-room") + discovery.set("_pending_join_token", "session-switch-token") + discovery.call( + "_set_public_join_state", + DiscoveryClient.PublicJoinState.CONNECTING, + ) + main.call("_on_pause_join_game_requested", "127.0.0.1:18141") + assert(session.state == NetworkSession.State.CONNECTING) + assert(discovery.get("_pending_join_token") == "session-switch-token") + var join_probe_timer := discovery.get("_join_probe_timer") as Timer + assert(join_probe_timer != null and not join_probe_timer.is_stopped()) + session.cancel_connection() + assert(session.state == NetworkSession.State.INACTIVE) + assert(str(discovery.get("_pending_join_token")).is_empty()) + + +func _validate_unavailable_fishnet_layout() -> void: + var unavailable_page := TheNetPage.new() + unavailable_page.size = Vector2(1280.0, 720.0) + root.add_child(unavailable_page) + for _frame: int in 3: + await process_frame + unavailable_page.call("_refresh_forecast", true) + await process_frame + var forecast_list := unavailable_page.get("_forecast_list") as HBoxContainer + assert(forecast_list != null) + assert(forecast_list.size.x >= 272.0) + assert(forecast_list.size.y <= 66.0) + assert(forecast_list.get_child_count() == 1) + var placeholder := forecast_list.get_child(0) as Label + assert(placeholder != null) + assert(placeholder.size.x >= 252.0) + assert(placeholder.size.y <= 66.0) + assert(placeholder.autowrap_mode == TextServer.AUTOWRAP_OFF) + var tabs := unavailable_page.get("_tabs") as HBoxContainer + assert(tabs != null) + assert(tabs.position.y + tabs.size.y < UtilityPageStyle.LAPTOP_RECT.end.y) + unavailable_page.queue_free() + for _frame: int in 2: + await process_frame + + func _validate_weather_guarantees(board: Dictionary) -> void: var jobs: Array = board.get("jobs", []) var schedule: Array = board.get("weather_schedule", []) diff --git a/tests/light_performance_profile_validation.gd b/tests/light_performance_profile_validation.gd new file mode 100644 index 0000000..2f8b9b9 --- /dev/null +++ b/tests/light_performance_profile_validation.gd @@ -0,0 +1,108 @@ +extends SceneTree + +const MainScene: PackedScene = preload("res://main/main.tscn") +const RuntimePerformanceProfileType = preload( + "res://main/runtime_performance_profile.gd" +) + + +func _initialize() -> void: + call_deferred("_run") + + +func _run() -> void: + _validate_profile_resolution() + OS.set_environment( + RuntimePerformanceProfileType.PROFILE_ENVIRONMENT_VARIABLE, + "light", + ) + root.size = Vector2i(640, 480) + 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"))) + _validate_main_profile(main) + _validate_minimal_weather(main) + main.queue_free() + for _frame: int in 4: + await process_frame + OS.unset_environment( + RuntimePerformanceProfileType.PROFILE_ENVIRONMENT_VARIABLE + ) + print("Light performance profile validation: PASS") + quit() + + +func _validate_profile_resolution() -> void: + var normal := RuntimePerformanceProfileType.from_name(&"normal") + var light := RuntimePerformanceProfileType.from_name(&"light") + var legacy := RuntimePerformanceProfileType.from_name(&"", true) + assert(not normal.is_light()) + assert(is_equal_approx(normal.get_world_render_scale(), 1.0)) + assert(light.is_light()) + assert(is_equal_approx(light.get_world_render_scale(), 0.5)) + assert(legacy.is_light()) + + +func _validate_main_profile(main: Node) -> void: + var profile: RuntimePerformanceProfile = main.get("_performance_profile") + assert(profile != null and profile.is_light()) + assert(is_equal_approx(root.scaling_3d_scale, 0.5)) + var pixelation: Node = main.get_node("%WorldPixelationPostprocess") + assert(bool(pixelation.call("is_light_performance_profile"))) + pixelation.call("set_gameplay_active", true) + var screen_grid := pixelation.get_node("ScreenGrid") as ColorRect + assert(screen_grid != null and not screen_grid.visible) + + var pond := main.get_node( + "TestWorld/Regions/StarterIslandRegion/WaterBodies/Pond/VisualWater" + ) as MeshInstance3D + var ocean := main.get_node( + "TestWorld/Regions/StarterIslandRegion/WaterBodies/Ocean/VisualWater" + ) as MeshInstance3D + assert(pond.material_override is StandardMaterial3D) + assert(ocean.material_override is StandardMaterial3D) + assert(not pond.material_override is ShaderMaterial) + assert(not ocean.material_override is ShaderMaterial) + assert(not ocean.is_processing()) + + +func _validate_minimal_weather(main: Node) -> void: + var visuals: WorldTimeVisualController = main.get_node( + "%WorldTimeVisualController" + ) + var rain := visuals.get_node("LocalRain") as GPUParticles3D + assert(rain.amount == WorldTimeVisualController.LIGHT_RAIN_PARTICLE_AMOUNT) + assert(rain.fixed_fps == WorldTimeVisualController.LIGHT_RAIN_FIXED_FPS) + var clouds := visuals.get_node("LocalStormClouds") as LocalStormCloudLayer + assert(clouds.get_patch_count() == 1) + var ceiling := clouds.get_node("CloudCeiling") as MeshInstance3D + assert(ceiling != null and ceiling.mesh is PlaneMesh) + assert(ceiling.material_override == null) + assert((ceiling.mesh as PlaneMesh).material is StandardMaterial3D) + + visuals.apply_weather_immediately(WorldWeatherService.Weather.RAINY) + assert(rain.emitting and rain.amount_ratio > 0.99) + assert(clouds.visible and clouds.get_storm_amount() > 0.87) + var cloud_material := ( + (ceiling.mesh as PlaneMesh).material as StandardMaterial3D + ) + assert(cloud_material.albedo_color.a < 0.65) + var test_world := main.get_node("TestWorld") as TestWorld + var runtime_environment := ( + test_world.get_world_environment().environment as Environment + ) + var sky_material := ( + runtime_environment.sky.sky_material as ShaderMaterial + ) + assert(is_zero_approx(float( + sky_material.get_shader_parameter("cloud_coverage") + ))) + assert(is_zero_approx(float( + sky_material.get_shader_parameter("cloud_opacity") + ))) diff --git a/tests/light_performance_profile_validation.gd.uid b/tests/light_performance_profile_validation.gd.uid new file mode 100644 index 0000000..3495021 --- /dev/null +++ b/tests/light_performance_profile_validation.gd.uid @@ -0,0 +1 @@ +uid://c6xqcslinxd1a diff --git a/tests/logbook_runtime_validation.gd b/tests/logbook_runtime_validation.gd index 88227aa..d1367de 100644 --- a/tests/logbook_runtime_validation.gd +++ b/tests/logbook_runtime_validation.gd @@ -108,9 +108,11 @@ func _validate_save_round_trip( var player := main.get("_player") as Player var catalog := main.get("fish_catalog") as FishPool assert(catalog != null) - assert(catalog.candidates.size() == 53) + assert(catalog.candidates.size() == 314) + var active_species := LogbookCatalog.ordered_species(catalog.candidates) + assert(active_species.size() == 54) for index: int in 4: - _add_test_catch(player, catalog.candidates[index]) + _add_test_catch(player, active_species[index]) assert(save_manager.save_now()) var no_catches: Array[FishCatch] = [] var no_discoveries: Array[StringName] = [] @@ -119,18 +121,18 @@ func _validate_save_round_trip( assert(save_manager.load_player_data()) assert(player.inventory.get_all_catches().size() == 4) for index: int in 4: - var original_fish: FishData = catalog.candidates[index] + var original_fish: FishData = active_species[index] assert(player.inventory.get_count(original_fish.id) == 1) assert(player.collection_log.has_discovered(original_fish.id)) - for index: int in range(4, catalog.candidates.size()): - _add_test_catch(player, catalog.candidates[index]) + for index: int in range(4, active_species.size()): + _add_test_catch(player, active_species[index]) assert(save_manager.save_now()) assert(player.inventory.replace_all_catches(no_catches, 1)) assert(player.collection_log.replace_discovered_ids(no_discoveries)) assert(save_manager.load_player_data()) - assert(player.inventory.get_all_catches().size() == 53) - for fish: FishData in catalog.candidates: + assert(player.inventory.get_all_catches().size() == 54) + for fish: FishData in active_species: assert(player.inventory.get_count(fish.id) == 1) assert(player.collection_log.has_discovered(fish.id)) for fish_id: StringName in [ diff --git a/tests/logbook_validation.gd b/tests/logbook_validation.gd index 866a34f..b8ed8be 100644 --- a/tests/logbook_validation.gd +++ b/tests/logbook_validation.gd @@ -40,121 +40,29 @@ func _run() -> void: func _validate_catalog() -> void: - var expected_ids: Array[StringName] = [ - &"bluegill", - &"bass", - &"carp", - &"sunfish", - &"catfish_blue", - &"catfish_channel", - &"catfish_flathead", - &"catfish_white", - &"tuna_albacore", - &"tuna_bigeye", - &"tuna_bluefin", - &"tuna_skipjack", - &"tuna_yellowfin", - &"goby_round", - &"salmon_atlantic", - &"salmon_chum", - &"salmon_coho", - &"salmon_pink", - &"salmon_sockeye", - &"anchovy_european", - &"anchovy_northern", - &"chub_european", - &"chub_flame", - &"chub_lake", - &"goldfish", - &"goldfish_bubbleeye", - &"grouper_gulf", - &"grouper_red", - &"mackerel_atlantic", - &"mackerel_cero", - &"mackerel_chub", - &"mackerel_king", - &"mackerel_spanish", - &"marlin_black", - &"marlin_blue", - &"marlin_white", - &"pomfret_black", - &"pomfret_chinese", - &"pomfret_golden", - &"pomfret_white", - &"sailfish", - &"sauger", - &"saugeye", - &"snapper_lane", - &"snapper_mangrove", - &"snapper_mutton", - &"snapper_red", - &"swordfish", - &"trout_cutthroat", - &"trout_golden", - &"trout_rainbow", - &"trout_steelhead", - &"walleye", - ] - assert(LogbookCatalog.CATALOG_ORDER == expected_ids) - for index: int in expected_ids.size(): - var fish := CatalogResource.get_fish_by_id(expected_ids[index]) + assert(CatalogResource.candidates.size() == 314) + var ordered := LogbookCatalog.ordered_species(CatalogResource.candidates) + assert(ordered.size() == 54) + var previous_number: int = 0 + var catalog_numbers: Dictionary[int, bool] = {} + for fish: FishDataType in CatalogResource.candidates: assert(fish != null) - assert(not LogbookCatalog.facts_for(fish.id).is_empty()) + assert(fish.catalog_number > 0) + assert(not catalog_numbers.has(fish.catalog_number)) + catalog_numbers[fish.catalog_number] = true + assert(not LogbookCatalog.facts_for(fish).is_empty()) assert( - LogbookCatalog.facts_for(fish.id) - == LogbookCatalog.facts_for(fish.id).to_lower() + LogbookCatalog.facts_for(fish) + == LogbookCatalog.facts_for(fish).to_lower() ) - if LogbookCatalog.FISH_FACTS.has(fish.id): - assert(LogbookCatalog.facts_for(fish.id) != "unknown") - else: - assert(LogbookCatalog.facts_for(fish.id) == "unknown") - var expected_category: WaterType.Type = ( - WaterType.Type.SALT_WATER - if expected_ids[index] in [ - &"bass", - &"sunfish", - &"tuna_albacore", - &"tuna_bigeye", - &"tuna_bluefin", - &"tuna_skipjack", - &"tuna_yellowfin", - &"salmon_atlantic", - &"salmon_chum", - &"salmon_coho", - &"salmon_pink", - &"salmon_sockeye", - &"anchovy_european", - &"anchovy_northern", - &"grouper_gulf", - &"grouper_red", - &"mackerel_atlantic", - &"mackerel_cero", - &"mackerel_chub", - &"mackerel_king", - &"mackerel_spanish", - &"marlin_black", - &"marlin_blue", - &"marlin_white", - &"pomfret_black", - &"pomfret_chinese", - &"pomfret_golden", - &"pomfret_white", - &"sailfish", - &"snapper_lane", - &"snapper_mangrove", - &"snapper_mutton", - &"snapper_red", - &"swordfish", - ] - else WaterType.Type.FRESH_WATER - ) - assert( - LogbookCatalog.category_for(fish) - == expected_category - ) - assert(LogbookCatalog.catalog_number(fish.id) == index + 1) + assert(LogbookCatalog.facts_for(fish) != "unknown") + for fish: FishDataType in ordered: + assert(fish.active) + assert(fish.catalog_number > previous_number) + assert(LogbookCatalog.catalog_number(fish) == fish.catalog_number) + previous_number = fish.catalog_number assert( - LogbookCatalog.empty_state(WaterType.Type.SALT_WATER) + LogbookCatalog.empty_state(LogbookCatalog.Category.SALT_WATER) == "No saltwater catches cataloged yet." ) @@ -170,7 +78,9 @@ func _validate_page() -> void: page.setup(collection, inventory, CatalogResource) page.activate() await process_frame - assert(page.get("_category") == WaterType.Type.FRESH_WATER) + assert( + page.get("_category") == LogbookCatalog.Category.FRESH_WATER + ) assert((page.get("_catalog_grid") as GridContainer).columns == 4) var initial_detail_body := page.get("_detail_body") as VBoxContainer assert(not initial_detail_body.get_parent() is ScrollContainer) @@ -181,21 +91,24 @@ func _validate_page() -> void: var shared_material: Material var silhouette_count: int = 0 - for category: WaterType.Type in [ - WaterType.Type.FRESH_WATER, - WaterType.Type.SALT_WATER, + for category: LogbookCatalog.Category in [ + LogbookCatalog.Category.FRESH_WATER, + LogbookCatalog.Category.SALT_WATER, ]: page.call("_select_category", category) await create_timer(0.25).timeout var entries: Dictionary = page.get("_entry_buttons") - assert(entries.size() == (19 if category == WaterType.Type.FRESH_WATER else 34)) + assert( + entries.size() + == (19 if category == LogbookCatalog.Category.FRESH_WATER else 34) + ) for fish: FishDataType in LogbookCatalog.ordered_species( CatalogResource.candidates ): if LogbookCatalog.category_for(fish) != category: continue var unknown_key := StringName( - "unknown_%d" % LogbookCatalog.catalog_number(fish.id) + "unknown_%d" % LogbookCatalog.catalog_number(fish) ) var entry := entries.get(unknown_key) as Button assert(entry != null) @@ -240,21 +153,30 @@ func _validate_page() -> void: ) assert(silhouette_count == 53) - page.call("_select_category", WaterType.Type.OTHER) + page.call("_select_category", LogbookCatalog.Category.SHELLFISH) + await create_timer(0.25).timeout + assert((page.get("_entry_buttons") as Dictionary).size() == 1) + assert( + (page.get("_entry_buttons") as Dictionary).has(&"unknown_3906") + ) + page.call("_select_category", LogbookCatalog.Category.OTHER) await create_timer(0.25).timeout assert((page.get("_entry_buttons") as Dictionary).is_empty()) assert( (page.get("_empty_state") as Label).text == "No entries available." ) - page.call("_select_category", WaterType.Type.FRESH_WATER) + page.call("_select_category", LogbookCatalog.Category.FRESH_WATER) await create_timer(0.25).timeout var bluegill = CatalogResource.get_fish_by_id(&"bluegill") - page.call("_select_entry", &"unknown_1", StringName()) + var bluegill_unknown := StringName( + "unknown_%d" % bluegill.catalog_number + ) + page.call("_select_entry", bluegill_unknown, StringName()) assert( (page.get("_entry_buttons") as Dictionary) - .get(&"unknown_1") != null + .get(bluegill_unknown) != null ) collection.mark_discovered(&"bluegill") await process_frame @@ -290,9 +212,13 @@ func _validate_page() -> void: assert(_detail_text(page).contains("number owned\n1")) assert(_detail_text(page).contains("number caught\nunknown")) assert(_detail_text(page).contains("body of water\nfresh water")) - assert(_detail_text(page).contains("catalog number\n#001")) + assert( + _detail_text(page).contains( + "catalog number\n#%03d" % bluegill.catalog_number + ) + ) assert(_detail_text(page).contains("fish facts\n")) - assert(_detail_text(page).contains(LogbookCatalog.facts_for(&"bluegill"))) + assert(_detail_text(page).contains(LogbookCatalog.facts_for(bluegill))) _validate_detail_field_fonts(page) _validate_handwritten_numeric_scale(page) var portrait_button := page.get("_detail_portrait_button") as Button @@ -413,7 +339,7 @@ func _validate_handwritten_numeric_scale(page: LogbookPage) -> void: var label := node as Label if label.text == "common": text_value = label - elif label.text == "#001": + elif label.text.begins_with("#"): numeric_value = label assert(text_value != null) assert(numeric_value != null) diff --git a/tests/movement_multiplayer_validation.gd b/tests/movement_multiplayer_validation.gd new file mode 100644 index 0000000..6926bf0 --- /dev/null +++ b/tests/movement_multiplayer_validation.gd @@ -0,0 +1,230 @@ +extends SceneTree + +const MainScene: PackedScene = preload("res://main/main.tscn") +const PlayerScene: PackedScene = preload("res://player/player.tscn") +const TEST_PORT: int = 18141 + + +func _initialize() -> void: + call_deferred("_run") + + +func _run() -> void: + await _validate_latency_smoothing() + var arguments: PackedStringArray = OS.get_cmdline_user_args() + if arguments.has("unit"): + print("Movement latency smoothing validation: PASS") + quit() + return + if arguments.has("host"): + await _run_host() + return + if arguments.has("client"): + await _run_client() + return + push_error("Movement multiplayer validation needs host or client mode.") + quit(1) + + +func _validate_latency_smoothing() -> void: + var avatar := PlayerScene.instantiate() as Player + root.add_child(avatar) + await process_frame + avatar.set_process(false) + avatar.set_physics_process(false) + avatar.configure_network_remote(false) + var moving_snapshot: Dictionary = _network_snapshot( + Vector3.ZERO, + Vector3(4.5, 0.0, 0.0), + 1, + ) + assert(NetworkSession.MOVEMENT_SNAPSHOT_BATCH_SIZE == 8) + var encoded_snapshots: Array = [] + for _peer: int in NetworkSession.MOVEMENT_SNAPSHOT_BATCH_SIZE: + encoded_snapshots.append( + NetworkSession._encode_movement_snapshot(moving_snapshot) + ) + assert(var_to_bytes(encoded_snapshots).size() < 1200) + assert( + NetworkSession._decode_movement_snapshot( + encoded_snapshots[0] + ) == moving_snapshot + ) + avatar.push_network_snapshot(moving_snapshot) + for _step: int in 12: + avatar.call("_update_network_interpolation", 1.0 / 30.0) + assert(avatar.global_position.x > 1.5) + + avatar.configure_network_remote(false) + avatar.global_position = Vector3(1.35, 0.0, 0.0) + avatar.apply_local_prediction_correction( + moving_snapshot, + 10, + 1.0 / 30.0, + ) + assert(avatar.global_position.x > 1.25) + avatar.queue_free() + await process_frame + + +func _network_snapshot( + position: Vector3, + velocity: Vector3, + acknowledged_input: int, +) -> Dictionary: + return { + "peer_id": 2, + "acknowledged_input": acknowledged_input, + "position": [position.x, position.y, position.z], + "velocity": [velocity.x, velocity.y, velocity.z], + "visual_yaw": 0.0, + "animation_state": NetworkPlayerAnimationProtocol.make_state( + NetworkPlayerAnimationProtocol.LOCOMOTION_RUNNING, + true, + ), + "sitting": false, + "casting": false, + } + + +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(session.start_private_host(TEST_PORT)) + assert(save_manager.initialize_new_game()) + main.call("_enter_gameplay") + for _frame: int in 4: + await physics_frame + assert(session.set_host_open(true)) + var remote_peer_id: int = await _wait_for_remote_peer(session) + assert(remote_peer_id > 1) + var player := main.get("_player") as Player + player.configure_network_remote(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(3, false, false)) + var disconnect_deadline: int = Time.get_ticks_msec() + 8000 + 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("Movement multiplayer host validation: PASS") + session.disconnect_session("") + main.queue_free() + for _frame: int in 4: + await process_frame + await create_timer(0.1).timeout + quit() + + +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 join_deadline: int = Time.get_ticks_msec() + 20000 + while Time.get_ticks_msec() < join_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 spawn_service := main.get_node( + "%PlayerSpawnService" + ) as PlayerSpawnService + var host_avatar: Player = spawn_service.get_avatar(1) + assert(host_avatar != null) + var animation_player := host_avatar.get_node( + "Visuals/CharacterRig/AnimationPlayer" + ) as AnimationPlayer + var sprint_dust := host_avatar.get_node("%SprintDust") as SprintDustTrail + var saw_running: bool = false + var saw_walking: bool = false + var saw_animation_advance: bool = false + var saw_dust: bool = false + var previous_animation: StringName = &"" + var previous_animation_position: float = -1.0 + var observation_deadline: int = Time.get_ticks_msec() + 7000 + while Time.get_ticks_msec() < observation_deadline: + await process_frame + var current_animation: StringName = animation_player.current_animation + saw_running = saw_running or current_animation.begins_with("running") + saw_walking = saw_walking or current_animation.begins_with("walking") + var current_position: float = ( + animation_player.current_animation_position + ) + if ( + current_animation == previous_animation + and previous_animation_position >= 0.0 + and absf(current_position - previous_animation_position) > 0.001 + ): + saw_animation_advance = true + previous_animation = current_animation + previous_animation_position = current_position + saw_dust = saw_dust or sprint_dust.get_active_puff_count() > 0 + if saw_running and saw_walking and saw_animation_advance and saw_dust: + break + assert(saw_running) + assert(saw_walking) + assert(saw_animation_advance) + assert(saw_dust) + print("Movement multiplayer client validation: PASS") + session.disconnect_session("") + main.queue_free() + for _frame: int in 4: + await process_frame + await create_timer(0.1).timeout + quit() + + +func _movement_input( + sequence: int, + sprinting: bool, + moving: bool = true, +) -> Dictionary: + return { + "sequence": sequence, + "axis": [0.0, -1.0] if moving else [0.0, 0.0], + "camera_yaw": 0.0, + "jump": false, + "sprint": sprinting, + "sneak": false, + "slow_walk": false, + "sitting": false, + "casting": false, + "animation_action": ( + NetworkPlayerAnimationProtocol.make_action_state() + ), + } + + +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_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 diff --git a/tests/movement_multiplayer_validation.gd.uid b/tests/movement_multiplayer_validation.gd.uid new file mode 100644 index 0000000..4d8c726 --- /dev/null +++ b/tests/movement_multiplayer_validation.gd.uid @@ -0,0 +1 @@ +uid://dlpu5lwb2pges diff --git a/tests/network_player_animation_protocol_validation.gd b/tests/network_player_animation_protocol_validation.gd new file mode 100644 index 0000000..8c3474c --- /dev/null +++ b/tests/network_player_animation_protocol_validation.gd @@ -0,0 +1,49 @@ +extends SceneTree + + +func _initialize() -> void: + var idle := NetworkPlayerAnimationProtocol.make_state( + NetworkPlayerAnimationProtocol.LOCOMOTION_IDLE, + true, + ) + assert(NetworkPlayerAnimationProtocol.validate_state(idle)) + var emote := NetworkPlayerAnimationProtocol.make_state( + NetworkPlayerAnimationProtocol.LOCOMOTION_RUNNING, + false, + &"wave_hello", + 17, + 1.25, + ) + assert(NetworkPlayerAnimationProtocol.validate_state(emote)) + var future_locomotion := NetworkPlayerAnimationProtocol.make_state( + &"swimming_fast", + false, + ) + assert(NetworkPlayerAnimationProtocol.validate_state(future_locomotion)) + var extra_future_field: Dictionary = emote.duplicate(true) + extra_future_field["future_layer"] = {"id": "umbrella"} + assert(NetworkPlayerAnimationProtocol.validate_state(extra_future_field)) + + var invalid_action: Dictionary = emote.duplicate(true) + invalid_action["action"] = { + "id": "../unsafe", + "sequence": 17, + "elapsed": 1.25, + } + assert(not NetworkPlayerAnimationProtocol.validate_state(invalid_action)) + var invalid_sequence: Dictionary = emote.duplicate(true) + invalid_sequence["action"] = { + "id": "wave_hello", + "sequence": -1, + "elapsed": 1.25, + } + assert(not NetworkPlayerAnimationProtocol.validate_state(invalid_sequence)) + var invalid_elapsed: Dictionary = emote.duplicate(true) + invalid_elapsed["action"] = { + "id": "wave_hello", + "sequence": 17, + "elapsed": INF, + } + assert(not NetworkPlayerAnimationProtocol.validate_state(invalid_elapsed)) + print("Network player animation protocol validation: PASS") + quit() diff --git a/tests/network_player_animation_protocol_validation.gd.uid b/tests/network_player_animation_protocol_validation.gd.uid new file mode 100644 index 0000000..017146d --- /dev/null +++ b/tests/network_player_animation_protocol_validation.gd.uid @@ -0,0 +1 @@ +uid://cjgelp62dkykn diff --git a/tests/operator_multiplayer_validation.gd b/tests/operator_multiplayer_validation.gd index 5847aa3..3712fd4 100644 --- a/tests/operator_multiplayer_validation.gd +++ b/tests/operator_multiplayer_validation.gd @@ -88,6 +88,11 @@ func _run_host() -> void: entry.revision, )) assert(not session.is_peer_operator(remote_peer_id)) + await create_timer(2.0).timeout + assert(session.kick_authenticated_peer( + remote_peer_id, + remote_record.identity_fingerprint, + )) var disconnect_deadline: int = Time.get_ticks_msec() + 8000 while ( @@ -173,9 +178,25 @@ func _run_client() -> void: assert(not (tabs.get_child(2) as Button).visible) assert(int(players_page.get("_current_tab")) == 0) service.request_unban.rpc_id(1, SECOND_BANNED_FINGERPRINT) - await create_timer(1.0).timeout + var disconnect_deadline: int = Time.get_ticks_msec() + 10000 + while ( + Time.get_ticks_msec() < disconnect_deadline + and session.state != NetworkSession.State.SERVER_LOST + ): + await process_frame + assert(session.state == NetworkSession.State.SERVER_LOST) + assert(not bool(main.get("_gameplay_started"))) + var game_ui := main.get_node("%GameUI") as GameUI + var title_screen := game_ui.get_title_screen() + assert(title_screen.visible) + assert(not title_screen.is_awaiting_start_input()) + assert((title_screen.get_node("%Center") as Control).visible) + assert((title_screen.get_node("%ButtonCenter") as Control).visible) + assert(not (title_screen.get_node("%JoinGamePage") as Control).visible) + var feedback := title_screen.get_node("%FeedbackLabel") as RichTextLabel + assert(feedback.visible) + assert("removed by the host" in feedback.text.to_lower()) print("Operator multiplayer client validation: PASS") - session.disconnect_session("") main.queue_free() for _frame: int in 4: await process_frame diff --git a/tests/rod_catalog_validation.gd b/tests/rod_catalog_validation.gd new file mode 100644 index 0000000..3b43b3f --- /dev/null +++ b/tests/rod_catalog_validation.gd @@ -0,0 +1,109 @@ +extends SceneTree + +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") +const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd") +const ItemCatalogType = preload("res://items/item_catalog.gd") +const ItemDataType = preload("res://items/item_data.gd") +const OwnedItemType = preload("res://items/owned_item.gd") +const PlayerBagType = preload("res://inventory/player_bag.gd") +const PlayerWalletType = preload("res://economy/player_wallet.gd") + +const ItemCatalogResource: ItemCatalogType = preload( + "res://items/catalog/item_catalog.tres" +) +const RodAttachmentScene = preload( + "res://player/fishing_rod_attachment.tscn" +) + + +func _initialize() -> void: + call_deferred("_run") + + +func _run() -> void: + _validate_catalog_activation() + _validate_shop_stock() + _validate_inactive_save_compatibility() + _validate_fallback_attachment() + print("Rod catalog validation: PASS") + quit() + + +func _validate_catalog_activation() -> void: + var rods: Array[FishingRodDataType] = [] + for item: ItemDataType in ItemCatalogResource.get_valid_items(): + var rod := item as FishingRodDataType + if rod == null: + continue + rods.append(rod) + assert(rod.is_valid()) + if rod.icon == null: + assert(not rod.active) + assert(rods.size() == 21) + var available_rods: Array[FishingRodDataType] = [] + for rod: FishingRodDataType in rods: + if rod.is_available(): + available_rods.append(rod) + assert(available_rods.size() == 1) + var basic: FishingRodDataType = available_rods.front() + assert(basic.item_id == &"basic_fishing_rod") + assert(basic.icon != null) + assert(basic.rod_model_scene == null) + assert( + ItemCatalogResource.get_available_item_by_id(&"aurora_rod") == null + ) + assert(ItemCatalogResource.get_item_by_id(&"aurora_rod") != null) + + +func _validate_shop_stock() -> void: + var stock: Array[FishingRodDataType] = ( + FishingShopStockType.get_rod_stock(ItemCatalogResource) + ) + assert(stock.size() == 1) + assert(stock.front().item_id == &"basic_fishing_rod") + var wallet := PlayerWalletType.new() + wallet.current_balance = 100 + var bag := PlayerBagType.new() + bag.setup(ItemCatalogResource) + assert( + FishingShopStockType.purchase_one( + &"basic_fishing_rod", + wallet, + bag, + ItemCatalogResource, + ) + ) + assert(bag.owns_item(&"basic_fishing_rod")) + assert(wallet.get_balance() == 100) + assert( + not FishingShopStockType.purchase_one( + &"aurora_rod", + wallet, + bag, + ItemCatalogResource, + ) + ) + bag.free() + wallet.free() + + +func _validate_inactive_save_compatibility() -> void: + var bag := PlayerBagType.new() + bag.setup(ItemCatalogResource) + assert(not bag.can_add_item(&"aurora_rod", 1)) + assert(not bag.add_item(&"aurora_rod", 1)) + var legacy_record := OwnedItemType.new() + legacy_record.item_id = &"aurora_rod" + legacy_record.quantity = 1 + var legacy_items: Array[OwnedItemType] = [legacy_record] + assert(bag.replace_all_items(legacy_items)) + assert(bag.owns_item(&"aurora_rod")) + bag.free() + + +func _validate_fallback_attachment() -> void: + var attachment := RodAttachmentScene.instantiate() + assert(attachment.get_node_or_null("FishingRod/FallbackRodMesh") != null) + assert(attachment.get_node_or_null("FishingRod/ModelMount") != null) + assert(attachment.get_node_or_null("FishingRod/FishingRodTip") != null) + attachment.free() diff --git a/tests/rod_catalog_validation.gd.uid b/tests/rod_catalog_validation.gd.uid new file mode 100644 index 0000000..ae0537e --- /dev/null +++ b/tests/rod_catalog_validation.gd.uid @@ -0,0 +1 @@ +uid://bjbgbahx5iuyy diff --git a/tests/session_switch_multiplayer_validation.gd b/tests/session_switch_multiplayer_validation.gd new file mode 100644 index 0000000..68cfcc7 --- /dev/null +++ b/tests/session_switch_multiplayer_validation.gd @@ -0,0 +1,173 @@ +extends SceneTree + +const MainScene: PackedScene = preload("res://main/main.tscn") +const FIRST_HOST_PORT: int = 18142 +const SECOND_HOST_PORT: int = 18143 +const WAIT_SECONDS: int = 20 + + +func _initialize() -> void: + call_deferred("_run") + + +func _run() -> void: + var arguments: PackedStringArray = OS.get_cmdline_user_args() + if arguments.has("first_host"): + await _run_host(FIRST_HOST_PORT, "first") + return + if arguments.has("second_host"): + await _run_host(SECOND_HOST_PORT, "second") + return + if arguments.has("client"): + await _run_client() + return + push_error( + "Session switch multiplayer validation needs first_host, " + + "second_host, or client mode." + ) + quit(1) + + +func _run_host(port: int, label: String) -> void: + var main: Node = await _create_initialized_main() + var session := main.get_node("%NetworkSession") as NetworkSession + assert(session.start_dedicated_host(port, 8, "127.0.0.1")) + assert(session.set_host_open(true)) + var remote_peer_id: int = await _wait_for_remote_peer(session) + assert(remote_peer_id > 1) + var disconnect_deadline: int = ( + Time.get_ticks_msec() + WAIT_SECONDS * 1000 + ) + 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("Session switch %s host validation: PASS" % label) + session.disconnect_session("") + main.queue_free() + for _frame: int in 4: + await process_frame + await create_timer(0.1).timeout + quit() + + +func _run_client() -> void: + var main: Node = await _create_initialized_main() + var session := main.get_node("%NetworkSession") as NetworkSession + main.call( + "_on_title_join_game_requested", + "127.0.0.1:%d" % FIRST_HOST_PORT, + ) + await _wait_for_join(main, session, FIRST_HOST_PORT) + var game_ui := main.get("_game_ui") as GameUI + var pause_menu := game_ui.get_pause_menu() + var join_page := pause_menu.get_node("%JoinGamePage") as JoinGamePage + var discovery := main.get_node("%DiscoveryClient") as DiscoveryClient + pause_menu.show() + (pause_menu.get_node("%RootPage") as Control).hide() + join_page.open_page() + join_page.set("_mode", JoinGamePage.Mode.DISCOVER) + var rooms: Array[Dictionary] = [{ + "room_id": "deferred-public-room", + "room_name": "Deferred public room", + "address": "127.0.0.1", + "port": SECOND_HOST_PORT, + "current_players": 0, + "max_players": 8, + }] + join_page.set("_discovery_rooms", rooms) + join_page.set("_selected_discovery_index", 0) + assert(bool(join_page.get("_gameplay_context"))) + assert( + str( + (join_page.call("_selected_discovery_room") as Dictionary).get( + "room_id", "" + ) + ) == "deferred-public-room" + ) + join_page.call("_request_join") + assert(not bool(discovery.get("_join_request_in_flight"))) + assert( + str( + (join_page.get("_pending_confirmation_room") as Dictionary).get( + "room_id", "" + ) + ) == "deferred-public-room" + ) + assert( + int(pause_menu.get("_confirmation_action")) + == PauseMenu.ConfirmationAction.JOIN_ANOTHER + ) + join_page.cancel_pending_join_confirmation() + (pause_menu.get_node("%ConfirmationPage") as Control).hide() + pause_menu.set("_confirmation_action", PauseMenu.ConfirmationAction.NONE) + pause_menu.set("_confirmation_returns_to_join_game", false) + join_page.open_page("127.0.0.1:%d" % SECOND_HOST_PORT) + join_page.set("_mode", JoinGamePage.Mode.DIRECT) + join_page.call("_request_join") + assert( + int(pause_menu.get("_confirmation_action")) + == PauseMenu.ConfirmationAction.JOIN_ANOTHER + ) + assert(not join_page.visible) + assert((pause_menu.get_node("%ConfirmationPage") as Control).visible) + pause_menu.call( + "_finish_confirmation_accept", + PauseMenu.ConfirmationAction.JOIN_ANOTHER, + ) + await _wait_for_join(main, session, SECOND_HOST_PORT) + assert(bool(main.get("_gameplay_started"))) + print("Session switch multiplayer client validation: PASS") + session.disconnect_session("") + main.queue_free() + for _frame: int in 4: + await process_frame + await create_timer(0.1).timeout + quit() + + +func _wait_for_join( + main: Node, + session: NetworkSession, + expected_port: int, +) -> void: + var deadline: int = Time.get_ticks_msec() + WAIT_SECONDS * 1000 + while Time.get_ticks_msec() < deadline: + await process_frame + if session.state == NetworkSession.State.VERIFYING_SERVER_IDENTITY: + main.call("_confirm_server_trust") + var endpoint: ConnectionEndpoint = session.get_current_endpoint() + if ( + session.is_joined_client() + and bool(main.get("_gameplay_started")) + and endpoint != null + and endpoint.port == expected_port + ): + return + assert(false, "Timed out joining UDP %d." % expected_port) + + +func _wait_for_remote_peer(session: NetworkSession) -> int: + var deadline: int = Time.get_ticks_msec() + WAIT_SECONDS * 1000 + while Time.get_ticks_msec() < deadline: + await process_frame + for peer_id: int in session.get_authenticated_peer_ids(): + if peer_id > 1: + return peer_id + return 0 + + +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 diff --git a/tests/session_switch_multiplayer_validation.gd.uid b/tests/session_switch_multiplayer_validation.gd.uid new file mode 100644 index 0000000..6e6f24c --- /dev/null +++ b/tests/session_switch_multiplayer_validation.gd.uid @@ -0,0 +1 @@ +uid://bkuih0ht2vny3 diff --git a/tests/world_spawn_multiplayer_validation.gd b/tests/world_spawn_multiplayer_validation.gd new file mode 100644 index 0000000..c2907f0 --- /dev/null +++ b/tests/world_spawn_multiplayer_validation.gd @@ -0,0 +1,168 @@ +extends SceneTree + +const MainScene: PackedScene = preload("res://main/main.tscn") +const TEST_PORT: int = 18141 +const EXPECTED_POPULATION: int = 2 + + +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("World spawn multiplayer 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 service: Node = main.get_node("%NetworkWorldSpawnService") + assert(session.start_dedicated_host(TEST_PORT, 8, "127.0.0.1")) + assert(session.set_host_open(true)) + await _wait_for_population(service) + _validate_population(service) + + var remote_peer_id: int = 0 + var join_deadline: int = Time.get_ticks_msec() + 20000 + while Time.get_ticks_msec() < join_deadline and remote_peer_id == 0: + await process_frame + for peer_id: int in session.get_authenticated_peer_ids(): + if peer_id != session.get_local_peer_id(): + remote_peer_id = peer_id + break + assert(remote_peer_id > 1) + assert(session.peer_supports_capability( + remote_peer_id, + NetworkProtocol.WORLD_SPAWN_CAPABILITY, + )) + + 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)) + _validate_population(service) + _validate_respawn_budget(service) + print("World spawn multiplayer host validation: PASS") + session.disconnect_session("") + main.queue_free() + for _frame: int in 4: + await process_frame + await create_timer(0.1).timeout + quit() + + +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 service: Node = main.get_node("%NetworkWorldSpawnService") + var join_deadline: int = Time.get_ticks_msec() + 20000 + while Time.get_ticks_msec() < join_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()) + assert(session.supports_server_capability( + NetworkProtocol.WORLD_SPAWN_CAPABILITY + )) + await _wait_for_population(service) + _validate_population(service) + print("World spawn multiplayer client validation: PASS") + session.disconnect_session("") + main.queue_free() + for _frame: int in 4: + await process_frame + await create_timer(0.1).timeout + quit() + + +func _wait_for_population(service: Node) -> void: + var deadline: int = Time.get_ticks_msec() + 12000 + while Time.get_ticks_msec() < deadline: + await process_frame + if (service.get("_entities") as Dictionary).size() == EXPECTED_POPULATION: + return + assert(false, "Timed out waiting for the world spawn population.") + + +func _validate_population(service: Node) -> void: + var entities: Dictionary = service.get("_entities") + var presentations: Dictionary = service.get("_presentations") + assert(entities.size() == EXPECTED_POPULATION) + assert(presentations.size() == EXPECTED_POPULATION) + for state: Dictionary in entities.values(): + assert(state.get("type_id") == &"crab_brown") + var position: Variant = state.get("position") + assert(typeof(position) == TYPE_VECTOR3) + assert((position as Vector3).is_finite()) + assert((position as Vector3).y > 0.08) + + +func _validate_respawn_budget(service: Node) -> void: + var entities: Dictionary = service.get("_entities") + var entity_ids: Array = entities.keys() + assert(entity_ids.size() == EXPECTED_POPULATION) + for entity_id: Variant in entity_ids: + service.call( + "_despawn_entity", + str(entity_id), + &"captured", + false, + true, + ) + assert((service.get("_entities") as Dictionary).is_empty()) + + var now: float = Time.get_ticks_msec() / 1000.0 + var respawns: Array = service.get("_respawns") + assert(respawns.size() == EXPECTED_POPULATION) + for index: int in respawns.size(): + var respawn: Dictionary = respawns[index] + var delay: float = float(respawn.get("due", 0.0)) - now + assert(delay >= 479.0 and delay <= 721.0) + respawn["due"] = now - 1.0 + respawns[index] = respawn + + service.call("_update_respawns") + assert((service.get("_entities") as Dictionary).size() == 1) + 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() == 1) + 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() == 2) + assert((service.get("_respawns") as Array).is_empty()) + + +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 diff --git a/tests/world_spawn_multiplayer_validation.gd.uid b/tests/world_spawn_multiplayer_validation.gd.uid new file mode 100644 index 0000000..ea4f1de --- /dev/null +++ b/tests/world_spawn_multiplayer_validation.gd.uid @@ -0,0 +1 @@ +uid://wumg5ev7jr6e diff --git a/tests/world_spawn_protocol_validation.gd b/tests/world_spawn_protocol_validation.gd new file mode 100644 index 0000000..f7d611f --- /dev/null +++ b/tests/world_spawn_protocol_validation.gd @@ -0,0 +1,102 @@ +extends SceneTree + +const Gatherables: GatherableCatalog = preload( + "res://gathering/catalog/gatherable_catalog.tres" +) +const FishCatalog: FishPool = preload("res://fish/pools/fish_catalog.tres") + + +func _initialize() -> void: + assert(NetworkProtocol.PROTOCOL_VERSION == 5) + assert( + NetworkWorldSpawnProtocol.CAPABILITY + == NetworkProtocol.WORLD_SPAWN_CAPABILITY + ) + assert(NetworkWorldSpawnProtocol.SNAPSHOT_ENTITIES_PER_ENVELOPE <= 4) + _validate_catalog_statuses() + _validate_envelopes() + print("World spawn protocol validation: PASS") + quit() + + +func _validate_catalog_statuses() -> void: + var brown: GatherableData = Gatherables.get_entry(&"crab_brown") + assert(brown != null and brown.is_available()) + assert(brown.catch_data.active) + assert(brown.catch_data.collection_method == FishData.CollectionMethod.NET) + assert(brown.catch_data.logbook_section == FishData.LogbookSection.SHELLFISH) + assert(brown.population == 2) + assert(is_equal_approx(brown.charge_duration, 2.0)) + assert(is_equal_approx(brown.capture_respawn_min_seconds, 480.0)) + assert(is_equal_approx(brown.capture_respawn_max_seconds, 720.0)) + assert(is_equal_approx(brown.scare_respawn_min_seconds, 45.0)) + assert(is_equal_approx(brown.scare_respawn_max_seconds, 90.0)) + assert(is_equal_approx(brown.minimum_respawn_spacing_seconds, 180.0)) + assert(brown.required_tool_id == &"crab_net") + assert(FishCatalog.get_fish_by_id(&"crab_brown") == brown.catch_data) + assert(not brown.catch_data.is_fishable()) + + 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()) + assert(not entry.catch_data.active) + assert(not entry.is_available()) + assert(entry.catch_data.collection_method == FishData.CollectionMethod.NET) + assert( + entry.catch_data.logbook_section + == FishData.LogbookSection.SHELLFISH + ) + + var available: Array[GatherableData] = Gatherables.get_available_entries() + assert(available.size() == 1) + assert(available.front() == brown) + var rng := RandomNumberGenerator.new() + rng.seed = 24680 + var captured_delay: float = brown.get_respawn_delay(&"captured", rng) + var scared_delay: float = brown.get_respawn_delay(&"scared", rng) + assert(captured_delay >= 480.0 and captured_delay <= 720.0) + assert(scared_delay >= 45.0 and scared_delay <= 90.0) + + +func _validate_envelopes() -> void: + var entity: Dictionary = { + "entity_id": "world:sample", + "type_id": "future_shellfish", + "position": [1.0, 2.0, 3.0], + "yaw": 0.5, + "revision": 7, + } + assert(NetworkWorldSpawnProtocol.validate_entity_state(entity)) + var future_entity: Dictionary = entity.duplicate(true) + future_entity["future_state"] = {"buried": false} + assert(NetworkWorldSpawnProtocol.validate_entity_state(future_entity)) + + var envelope: Dictionary = NetworkWorldSpawnProtocol.make_envelope( + "session-test", + 12, + &"future_spawn_type", + {"entity": entity, "future_payload": [1, 2, 3]}, + ) + assert(NetworkWorldSpawnProtocol.validate_envelope(envelope)) + + var malformed_position: Dictionary = entity.duplicate(true) + malformed_position["position"] = [1.0, "not-a-number", 3.0] + assert( + not NetworkWorldSpawnProtocol.validate_entity_state( + malformed_position + ) + ) + assert( + not NetworkWorldSpawnProtocol.array_to_vector3( + malformed_position["position"] + ).is_finite() + ) + + var invalid_envelope: Dictionary = envelope.duplicate(true) + invalid_envelope["envelope_version"] = 0 + assert(not NetworkWorldSpawnProtocol.validate_envelope(invalid_envelope)) diff --git a/tests/world_spawn_protocol_validation.gd.uid b/tests/world_spawn_protocol_validation.gd.uid new file mode 100644 index 0000000..e03c707 --- /dev/null +++ b/tests/world_spawn_protocol_validation.gd.uid @@ -0,0 +1 @@ +uid://clu8j64mh4x54 diff --git a/tests/world_time_multiplayer_validation.gd b/tests/world_time_multiplayer_validation.gd index 3d1c025..cebb0eb 100644 --- a/tests/world_time_multiplayer_validation.gd +++ b/tests/world_time_multiplayer_validation.gd @@ -3,7 +3,7 @@ extends SceneTree const MainScene = preload("res://main/main.tscn") const TEST_PORT: int = 17983 const INITIAL_HOST_TIME: float = 19.75 -const UPDATED_HOST_TIME: float = 20.75 +const UPDATED_HOST_TIME: float = WorldTimeService.DUSK_END_HOUR const TIME_TOLERANCE_HOURS: float = 0.05 @@ -61,14 +61,31 @@ func _run_host() -> void: assert(session.peer_supports_capability( remote_peer_id, NetworkProtocol.WORLD_WEATHER_CAPABILITY )) + var remote_record: PeerRegistry.PeerRecord = session.get_peer_record( + remote_peer_id + ) + assert(remote_record != null and remote_record.identity_authenticated) + assert(session.set_peer_operator( + remote_peer_id, + remote_record.identity_fingerprint, + true, + )) assert(world_time.get_phase() == WorldTimeService.Phase.DUSK) - await create_timer(1.0).timeout - world_time.synchronize_time(UPDATED_HOST_TIME) + var command_deadline: int = Time.get_ticks_msec() + 10000 + while ( + Time.get_ticks_msec() < command_deadline + and _wrapped_time_difference( + world_time.get_time_hours(), UPDATED_HOST_TIME + ) > TIME_TOLERANCE_HOURS + ): + await process_frame assert(is_equal_approx( world_time.get_persistent_time_hours(), UPDATED_HOST_TIME )) - assert(chat_ui.call("_handle_chat_command", "/weather foggy")) + var fog_deadline: int = Time.get_ticks_msec() + 10000 + while Time.get_ticks_msec() < fog_deadline and not world_weather.is_foggy(): + await process_frame assert( world_weather.get_persistent_weather() == WorldWeatherService.Weather.FOGGY @@ -153,6 +170,11 @@ func _run_client() -> void: assert(is_equal_approx(clock_panel.position.y, 10.0)) assert(is_equal_approx(weather_icon.position.y, 10.0)) assert(clock_panel.position.y + clock_panel.size.y < chat_panel.position.y) + var operator_deadline: int = Time.get_ticks_msec() + 10000 + while Time.get_ticks_msec() < operator_deadline and not session.is_local_operator(): + await process_frame + assert(session.is_local_operator()) + assert(chat_ui.call("_handle_chat_command", "/time night")) var update_deadline: int = Time.get_ticks_msec() + 10000 while ( @@ -168,6 +190,8 @@ func _run_client() -> void: assert(world_time.get_phase() == WorldTimeService.Phase.NIGHT) assert(is_equal_approx(world_time.get_persistent_time_hours(), 15.25)) assert(clock_label.text == world_time.get_clock_text()) + await create_timer(0.6).timeout + assert(chat_ui.call("_handle_chat_command", "/weather foggy")) var fog_deadline: int = Time.get_ticks_msec() + 8000 while ( Time.get_ticks_msec() < fog_deadline diff --git a/tests/world_weather_validation.gd b/tests/world_weather_validation.gd index 01ac8e1..a725117 100644 --- a/tests/world_weather_validation.gd +++ b/tests/world_weather_validation.gd @@ -456,8 +456,14 @@ func _validate_weather_presentation() -> void: assert(rain.emitting) assert(rain.amount_ratio > 0.99) assert(rain.amount == WorldTimeVisualControllerType.RAIN_PARTICLE_AMOUNT) + assert(rain.visibility_aabb == ( + WorldTimeVisualControllerType.RAIN_VISIBILITY_AABB + )) var rain_material := rain.process_material as ParticleProcessMaterial assert(rain_material != null) + assert(rain_material.emission_box_extents.is_equal_approx( + WorldTimeVisualControllerType.RAIN_EMISSION_EXTENTS + )) assert(is_equal_approx( rain_material.initial_velocity_min, WorldTimeVisualControllerType.RAIN_VELOCITY_MIN, diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index bf38a75..c080a04 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -181,6 +181,7 @@ func setup( _service.local_message_confirmed.connect(_on_local_message_confirmed) _service.history_replaced.connect(_on_history) _service.send_rejected.connect(_on_rejected) + _service.world_command_finished.connect(_on_world_command_finished) _session.peer_removed.connect(_on_peer_removed) _entry.text = _settings.current_settings.chat_draft _entry.caret_column = _entry.text.length() @@ -789,34 +790,16 @@ func _handle_time_command(parts: PackedStringArray) -> void: if parts.size() != 2: _set_status("Usage: /time [dawn, day, dusk, night]") return - if _session == null or not _session.is_host(): - _set_status("Only the host can change world time.") - return - if _world_time == null: + if _service == null or _world_time == null: _set_status("World time is unavailable.") return var phase_name := String(parts[1]).to_lower() - var target_hour: float - match phase_name: - "dawn": - target_hour = WorldTimeServiceType.DAWN_START_HOUR - "day": - target_hour = WorldTimeServiceType.DAWN_END_HOUR - "dusk": - target_hour = WorldTimeServiceType.DUSK_START_HOUR - "night": - target_hour = WorldTimeServiceType.DUSK_END_HOUR - _: - _set_status("Usage: /time [dawn, day, dusk, night]") - return - if not _world_time.set_authoritative_time(target_hour): - _set_status("World time could not be changed.") + if phase_name not in ["dawn", "day", "dusk", "night"]: + _set_status("Usage: /time [dawn, day, dusk, night]") + return + if not _service.request_world_time_change(phase_name): + _set_status("Only the host or an operator can change world time.") return - _set_status("") - _service.broadcast_system_message( - "World time set to %s (%s)." - % [phase_name, _world_time.get_clock_text()] - ) close_chat() @@ -824,37 +807,31 @@ func _handle_weather_command(parts: PackedStringArray) -> void: if parts.size() != 2: _set_status("Usage: /weather [clear, cloudy, rainy, foggy]") return - if _session == null or not _session.is_host(): - _set_status("Only the host can change world weather.") - return - if _world_weather == null: + if _service == null or _world_weather == null: _set_status("World weather is unavailable.") return var weather_name := String(parts[1]).to_lower() - var target_weather: WorldWeatherServiceType.Weather match weather_name: "clear", "sunny": - target_weather = WorldWeatherServiceType.Weather.SUNNY weather_name = "clear" - "cloudy": - target_weather = WorldWeatherServiceType.Weather.CLOUDY - "rainy": - target_weather = WorldWeatherServiceType.Weather.RAINY - "foggy": - target_weather = WorldWeatherServiceType.Weather.FOGGY + "cloudy", "rainy", "foggy": + pass _: _set_status("Usage: /weather [clear, cloudy, rainy, foggy]") return - if not _world_weather.set_authoritative_weather(target_weather): - _set_status("World weather could not be changed.") + if not _service.request_world_weather_change(weather_name): + _set_status("Only the host or an operator can change world weather.") return - _set_status("") - _service.broadcast_system_message( - "World weather set to %s." % weather_name - ) close_chat() +func _on_world_command_finished(success: bool, message: String) -> void: + if success: + _set_status("") + elif not message.is_empty(): + _set_status(message) + + func _on_local_message_confirmed(message: Dictionary) -> void: if ( not _send_pending diff --git a/ui/components/bubble_hotbar/bubble_hotbar_slot.gd b/ui/components/bubble_hotbar/bubble_hotbar_slot.gd index 4bebdfe..6c9764a 100644 --- a/ui/components/bubble_hotbar/bubble_hotbar_slot.gd +++ b/ui/components/bubble_hotbar/bubble_hotbar_slot.gd @@ -377,7 +377,7 @@ func _can_drop_data(_at_position: Vector2, data: Variant) -> bool: if _bag == null or not _bag.owns_item(item_id) or _catalog == null: return false var item: ItemDataType = _catalog.get_item_by_id(item_id) - return item != null and item.is_valid() and item.hotbar_allowed + return item != null and item.is_available() and item.hotbar_allowed if kind == "cooler_fish": var catch_id: StringName = StringName(str(payload.get("catch_id", ""))) return ( diff --git a/ui/fishing_shop.gd b/ui/fishing_shop.gd index d8678b7..ff1a44e 100644 --- a/ui/fishing_shop.gd +++ b/ui/fishing_shop.gd @@ -8,6 +8,7 @@ const PlayerWalletType = preload("res://economy/player_wallet.gd") const FishingShopStockType = preload("res://economy/fishing_shop_stock.gd") const ItemCatalogType = preload("res://items/item_catalog.gd") const ItemDataType = preload("res://items/item_data.gd") +const FishingRodDataType = preload("res://items/fishing_rod_data.gd") const PlayerBagType = preload("res://inventory/player_bag.gd") const FishingSpotType = preload("res://fishing/fishing_spot.gd") const PlayerFishingUpgradesType = preload( @@ -82,6 +83,15 @@ const SUPPLY_PRICE_HEIGHT: float = 24.0 const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0 const SUPPLY_PRICE_ICON_SIZE: float = 18.0 const SUPPLY_PRICE_ICON_GAP: float = 3.0 +const ROD_CARD_TILE_SIZE := Vector2(144.0, 144.0) +const ROD_CARD_HOST_SIZE := Vector2(152.0, 198.0) +const ROD_CAROUSEL_HEIGHT: float = 206.0 +const ROD_CAROUSEL_SEPARATION: int = 12 +const ROD_CAROUSEL_STEP: float = 328.0 +const ROD_PRICE_Y: float = 128.0 +const ROD_PRICE_HEIGHT: float = 30.0 +const ROD_PRICE_FONT_SIZE: int = 18 +const ROD_PRICE_ICON_SIZE: float = 22.0 @onready var _wallet_label: Label = %WalletLabel @onready var _shop_panel: PanelContainer = %ShopPanel @@ -650,7 +660,12 @@ func _refresh_supplies() -> void: stock_item_ids = grouped_item_ids var current_stock_group: StringName = StringName() var current_stock_grid: GridContainer - if _shop_section in [ShopSection.SNACKS, ShopSection.EQUIPMENT]: + if _shop_section == ShopSection.SNACKS: + current_stock_grid = _add_stock_icon_grid() + elif _shop_section == ShopSection.EQUIPMENT: + _add_stock_section("rods") + _add_rod_carousel() + _add_stock_section("equipment") current_stock_grid = _add_stock_icon_grid() for item_id: StringName in stock_item_ids: var item: ItemDataType = _item_catalog.get_item_by_id(item_id) @@ -671,6 +686,10 @@ func _refresh_supplies() -> void: button.alignment = HORIZONTAL_ALIGNMENT_LEFT var owned: int = _bag.get_quantity(item_id) var bait_topoff: bool = FishingShopStockType.is_bait_topoff(item_id) + var permanent_unlock: bool = ( + FishingShopStockType.is_permanent_unlock(item_id, item) + ) + var unlock_status: String = _unlock_status(owned > 0) var bait_unlocked: bool = ( not bait_topoff or _bag.is_bait_unlocked(item_id) ) @@ -683,10 +702,11 @@ func _refresh_supplies() -> void: var use_icon_tile: bool = ( current_stock_grid != null ) - button.text = "%s\nowned %d" % [ - item.display_name, - owned, - ] + button.text = ( + "%s\n%s" % [item.display_name, unlock_status] + if permanent_unlock + else "%s\nowned %d" % [item.display_name, owned] + ) var item_tooltip_text: String = item.description if bait_topoff: if use_icon_tile: @@ -724,11 +744,19 @@ func _refresh_supplies() -> void: button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN button.alignment = HORIZONTAL_ALIGNMENT_CENTER button.text = "" - item_tooltip_text = "%s\nowned %d\n%s" % [ - item.display_name, - owned, - item.description, - ] + item_tooltip_text = ( + "%s\n%s\n%s" % [ + item.display_name, + unlock_status, + item.description, + ] + if permanent_unlock + else "%s\nowned %d\n%s" % [ + item.display_name, + owned, + item.description, + ] + ) button.disabled = ( _transaction_in_progress or _closing @@ -777,6 +805,8 @@ func _refresh_supplies() -> void: func _item_belongs_in_current_section(item: ItemDataType) -> bool: + if not item.is_available(): + return false match _shop_section: ShopSection.BAIT: return item.is_bait() or item.is_lure() @@ -787,6 +817,10 @@ func _item_belongs_in_current_section(item: ItemDataType) -> bool: return false +func _unlock_status(unlocked: bool) -> String: + return "unlocked" if unlocked else "locked" + + func _add_stock_section(title: String) -> void: var label := Label.new() label.text = title @@ -811,21 +845,156 @@ func _add_stock_icon_grid() -> GridContainer: return grid +func _add_rod_carousel() -> void: + var shell := HBoxContainer.new() + shell.name = "RodCarousel" + shell.custom_minimum_size.y = ROD_CAROUSEL_HEIGHT + shell.size_flags_horizontal = Control.SIZE_EXPAND_FILL + shell.alignment = BoxContainer.ALIGNMENT_CENTER + shell.add_theme_constant_override("separation", 8) + _supplies_list.add_child(shell) + var previous := Button.new() + previous.name = "PreviousRod" + previous.text = "‹" + previous.tooltip_text = "Previous rods" + previous.custom_minimum_size = Vector2(42.0, 72.0) + previous.focus_mode = Control.FOCUS_ALL + UtilityPageStyleType.apply_ocean_button(previous) + shell.add_child(previous) + var scroll := ScrollContainer.new() + scroll.name = "RodScroll" + scroll.custom_minimum_size.y = ROD_CAROUSEL_HEIGHT + scroll.size_flags_horizontal = Control.SIZE_EXPAND_FILL + scroll.horizontal_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO + scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED + scroll.follow_focus = true + shell.add_child(scroll) + var row := HBoxContainer.new() + row.name = "RodCards" + row.size_flags_horizontal = Control.SIZE_EXPAND_FILL + row.add_theme_constant_override( + "separation", ROD_CAROUSEL_SEPARATION + ) + scroll.add_child(row) + var rods: Array[FishingRodDataType] = ( + FishingShopStockType.get_rod_stock(_item_catalog) + ) + for rod: FishingRodDataType in rods: + _add_rod_card(row, rod) + var next := Button.new() + next.name = "NextRod" + next.text = "›" + next.tooltip_text = "Next rods" + next.custom_minimum_size = Vector2(42.0, 72.0) + next.focus_mode = Control.FOCUS_ALL + UtilityPageStyleType.apply_ocean_button(next) + shell.add_child(next) + previous.disabled = rods.size() <= 1 + next.disabled = rods.size() <= 1 + previous.pressed.connect( + _scroll_rod_carousel.bind(scroll, -ROD_CAROUSEL_STEP) + ) + next.pressed.connect( + _scroll_rod_carousel.bind(scroll, ROD_CAROUSEL_STEP) + ) + + +func _add_rod_card(parent: HBoxContainer, rod: FishingRodDataType) -> void: + var owned: bool = _bag.owns_item(rod.item_id) + var level: int = _player.experience.get_level() + var level_locked: bool = level < rod.unlock_level + var tooltip := "%s\n%s" % [rod.display_name, rod.description] + if not rod.effect_summary.strip_edges().is_empty(): + tooltip += "\n%s" % rod.effect_summary + if not rod.tradeoff.strip_edges().is_empty(): + tooltip += "\ntradeoff: %s" % rod.tradeoff + tooltip += "\nlevel %d" % rod.unlock_level + tooltip += "\n%s" % _unlock_status(owned) + var button := _make_stock_button("", tooltip) + button.name = "Rod_%s" % str(rod.item_id) + button.custom_minimum_size = ROD_CARD_TILE_SIZE + button.size = ROD_CARD_TILE_SIZE + button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + button.icon = rod.icon + button.expand_icon = true + button.alignment = HORIZONTAL_ALIGNMENT_CENTER + button.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST + button.disabled = ( + owned + or level_locked + or _transaction_in_progress + or _closing + or _network_shop == null + or not _network_shop.can_request_purchase() + or not _wallet.can_afford(rod.shop_price) + or not _bag.can_add_item(rod.item_id, 1) + ) + button.pressed.connect(_purchase_rod.bind(rod.item_id)) + var host: Control = _add_supply_icon_tile( + parent, + button, + rod.shop_price, + ROD_CARD_TILE_SIZE, + ROD_CARD_HOST_SIZE, + ROD_PRICE_Y, + ROD_PRICE_HEIGHT, + ROD_PRICE_FONT_SIZE, + ROD_PRICE_ICON_SIZE, + ) + var name_label := Label.new() + name_label.name = "RodName" + name_label.position = Vector2(0.0, 162.0) + name_label.size = Vector2(ROD_CARD_HOST_SIZE.x, 30.0) + name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE + name_label.text = rod.display_name + name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + name_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + name_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS + name_label.add_theme_font_override("font", UtilityPageStyleType.TuffyFont) + name_label.add_theme_font_size_override("font_size", 17) + name_label.add_theme_color_override( + "font_color", UtilityPageStyleType.OCEAN_TEXT_PRIMARY + ) + host.add_child(name_label) + + +func _scroll_rod_carousel(scroll: ScrollContainer, amount: float) -> void: + var target: float = clampf( + float(scroll.scroll_horizontal) + amount, + 0.0, + maxf( + scroll.get_h_scroll_bar().max_value + - scroll.get_h_scroll_bar().page, + 0.0, + ), + ) + var tween := create_tween() + tween.set_trans(Tween.TRANS_QUAD) + tween.set_ease(Tween.EASE_OUT) + tween.tween_method( + func(value: float) -> void: + scroll.scroll_horizontal = roundi(value), + float(scroll.scroll_horizontal), + target, + 0.15, + ) + + func _add_art_kit_button(parent: GridContainer) -> void: var item: ItemDataType = _item_catalog.get_item_by_id( ArtShopStockType.ART_KIT_ITEM_ID ) - if item == null: + if item == null or not item.is_available(): return var owned: bool = _bag.get_quantity(ArtShopStockType.ART_KIT_ITEM_ID) > 0 var button: Button = _make_stock_button( "%s\n%s" % [ item.display_name, - "owned" if owned else "not owned", + _unlock_status(owned), ], "%s\n%s\n%s" % [ item.display_name, - "owned" if owned else "not owned", + _unlock_status(owned), item.description, ], ) @@ -942,29 +1111,35 @@ func _configure_marker_icon_tile( func _add_supply_icon_tile( - parent: GridContainer, + parent: Container, button: Button, price: int, -) -> void: + tile_size: Vector2 = SUPPLY_ICON_TILE_SIZE, + host_size: Vector2 = SUPPLY_ICON_HOST_SIZE, + price_y: float = SUPPLY_PRICE_Y, + price_height: float = SUPPLY_PRICE_HEIGHT, + price_font_size: int = SUPPLY_PRICE_FONT_SIZE, + price_icon_size: float = SUPPLY_PRICE_ICON_SIZE, +) -> Control: var tile_host := Control.new() - tile_host.custom_minimum_size = SUPPLY_ICON_HOST_SIZE + tile_host.custom_minimum_size = host_size tile_host.mouse_filter = Control.MOUSE_FILTER_IGNORE parent.add_child(tile_host) button.position = Vector2.ZERO - button.size = SUPPLY_ICON_TILE_SIZE + button.size = tile_size tile_host.add_child(button) var price_text := str(price) var price_text_width: float = UtilityPageStyleType.TuffyFont.get_string_size( price_text, HORIZONTAL_ALIGNMENT_LEFT, -1.0, - SUPPLY_PRICE_FONT_SIZE, + price_font_size, ).x var price_width: float = minf( - SUPPLY_ICON_TILE_SIZE.x, + tile_size.x, ceilf( price_text_width - + SUPPLY_PRICE_ICON_SIZE + + price_icon_size + SUPPLY_PRICE_ICON_GAP + SUPPLY_PRICE_HORIZONTAL_PADDING ), @@ -972,10 +1147,10 @@ func _add_supply_icon_tile( var price_bubble := PanelContainer.new() price_bubble.name = "PriceBubble" price_bubble.position = Vector2( - (SUPPLY_ICON_TILE_SIZE.x - price_width) * 0.5, - SUPPLY_PRICE_Y, + (tile_size.x - price_width) * 0.5, + price_y, ) - price_bubble.size = Vector2(price_width, SUPPLY_PRICE_HEIGHT) + price_bubble.size = Vector2(price_width, price_height) price_bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE price_bubble.z_index = 2 var price_style := UtilityPageStyleType.rounded_style( @@ -998,7 +1173,7 @@ func _add_supply_icon_tile( price_bubble.add_child(price_row) var price_icon := TextureRect.new() price_icon.name = "CurrencyIcon" - price_icon.custom_minimum_size = Vector2.ONE * SUPPLY_PRICE_ICON_SIZE + price_icon.custom_minimum_size = Vector2.ONE * price_icon_size price_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE price_icon.texture = CURRENCY_ICON price_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE @@ -1015,13 +1190,14 @@ func _add_supply_icon_tile( "font", UtilityPageStyleType.TuffyFont ) price_label.add_theme_font_size_override( - "font_size", SUPPLY_PRICE_FONT_SIZE + "font_size", price_font_size ) price_label.add_theme_color_override( "font_color", SUPPLY_PRICE_COLOR ) price_label.add_theme_constant_override("outline_size", 0) price_row.add_child(price_label) + return tile_host func _refresh_cooler_capacity() -> void: @@ -1073,7 +1249,7 @@ func _purchase_supply(item_id: StringName) -> void: return var owned: int = _bag.get_quantity(item_id) var item: ItemDataType = _item_catalog.get_item_by_id(item_id) - if item == null: + if item == null or not item.is_available(): _set_feedback("Purchase could not be completed.") return var quantity: int = FishingShopStockType.get_purchase_quantity( @@ -1094,6 +1270,23 @@ func _purchase_supply(item_id: StringName) -> void: _network_shop.request_supply(item_id) +func _purchase_rod(item_id: StringName) -> void: + if _network_shop == null or not _is_transaction_context_valid(): + _set_feedback("Purchase could not be completed.") + return + var rod := _item_catalog.get_item_by_id(item_id) as FishingRodDataType + if ( + rod == null + or not rod.is_shop_available() + or _bag.owns_item(item_id) + or not _wallet.can_afford(rod.shop_price) + or not _bag.can_add_item(item_id, 1) + ): + _set_feedback("Purchase could not be completed.") + return + _network_shop.request_rod(item_id) + + func _purchase_art_kit() -> void: if _network_shop == null or not _is_transaction_context_valid(): _set_feedback("Purchase could not be completed.") diff --git a/ui/logbook_catalog.gd b/ui/logbook_catalog.gd index 1ba1a1e..cf0f49b 100644 --- a/ui/logbook_catalog.gd +++ b/ui/logbook_catalog.gd @@ -3,320 +3,73 @@ extends RefCounted const FishDataType = preload("res://fish/fish_data.gd") -# Catalog numbers are a presentation contract. Add future species deliberately; -# filtering and display order must not redefine an existing number. -const CATALOG_ORDER: Array[StringName] = [ - &"bluegill", - &"bass", - &"carp", - &"sunfish", - &"catfish_blue", - &"catfish_channel", - &"catfish_flathead", - &"catfish_white", - &"tuna_albacore", - &"tuna_bigeye", - &"tuna_bluefin", - &"tuna_skipjack", - &"tuna_yellowfin", - &"goby_round", - &"salmon_atlantic", - &"salmon_chum", - &"salmon_coho", - &"salmon_pink", - &"salmon_sockeye", - &"anchovy_european", - &"anchovy_northern", - &"chub_european", - &"chub_flame", - &"chub_lake", - &"goldfish", - &"goldfish_bubbleeye", - &"grouper_gulf", - &"grouper_red", - &"mackerel_atlantic", - &"mackerel_cero", - &"mackerel_chub", - &"mackerel_king", - &"mackerel_spanish", - &"marlin_black", - &"marlin_blue", - &"marlin_white", - &"pomfret_black", - &"pomfret_chinese", - &"pomfret_golden", - &"pomfret_white", - &"sailfish", - &"sauger", - &"saugeye", - &"snapper_lane", - &"snapper_mangrove", - &"snapper_mutton", - &"snapper_red", - &"swordfish", - &"trout_cutthroat", - &"trout_golden", - &"trout_rainbow", - &"trout_steelhead", - &"walleye", -] - -# Short presentation notes are kept here with the catalog contract rather -# than mixed into gameplay balance resources. Add facts deliberately when a -# species joins the catalog. -const FISH_FACTS: Dictionary[StringName, String] = { - &"bluegill": ( - "bluegill fathers sweep shallow bowls into the bottom for nests, " - + "then stand guard. whole neighborhoods of nests can crowd together." - ), - &"bass": ( - "striped bass split their lives between salt and fresh water, " - + "returning upriver to spawn. a long-lived striper may see three decades." - ), - &"carp": ( - "whisker-like barbels help a common carp investigate the bottom. " - + "nosing through sediment can cloud the water and loosen plants." - ), - &"sunfish": ( - "ocean sunfish are built more like enormous swimming heads than " - + "ordinary fish. they visit the surface and turn sideways to warm up." - ), - &"catfish_blue": ( - "a deep fork in the tail inspired the blue catfish's scientific " - + "name. it rests deep by daylight and becomes more active after dark." - ), - &"catfish_channel": ( - "a channel catfish can taste through skin all over its body, " - + "especially near its gills and whiskers. the male watches the eggs." - ), - &"catfish_flathead": ( - "flatheads favor hideouts made by logs, rocks, ledges, and other " - + "underwater structure. as they grow, fish dominate their menu." - ), - &"catfish_white": ( - "white catfish began along the atlantic coast between new york " - + "and florida. people later carried them far beyond that home range." - ), - &"tuna_albacore": ( - "albacore have unusually long pectoral fins. their warm muscles help " - + "them cruise across wide stretches of open ocean." - ), - &"tuna_bigeye": ( - "bigeye tuna make daily trips between deep, cool water and warmer " - + "surface layers. their large eyes suit those dim depths." - ), - &"tuna_bluefin": ( - "bluefin tuna are powerful ocean travelers. heat retained in their " - + "swimming muscles lets them thrive in surprisingly cold seas." - ), - &"tuna_skipjack": ( - "skipjack travel in fast-moving schools and have dark stripes along " - + "their lower sides. they rarely stop swimming." - ), - &"tuna_yellowfin": ( - "yellowfin are named for their bright yellow fins and finlets. large " - + "schools often gather with other open-ocean hunters." - ), - &"goby_round": ( - "round gobies use fused pelvic fins like a suction cup to hold onto " - + "rocks. they are small fish with a famously large appetite." - ), - &"salmon_atlantic": ( - "atlantic salmon can return from the ocean to the river where they " - + "hatched. unlike many salmon, some survive to make the trip again." - ), - &"salmon_chum": ( - "chum salmon travel immense distances and develop striking bars when " - + "they return to fresh water to spawn." - ), - &"salmon_coho": ( - "coho salmon are agile hunters known for powerful leaps. young coho " - + "may spend more than a year growing in streams." - ), - &"salmon_pink": ( - "pink salmon usually follow a strict two-year life cycle. spawning " - + "males develop the hump that inspired their nickname." - ), - &"salmon_sockeye": ( - "sockeye salmon feed heavily on plankton at sea. adults turn vivid red " - + "as they return inland to spawn." - ), - &"anchovy_european": ( - "european anchovies form dense schools and feed by filtering tiny " - + "plankton from coastal water." - ), - &"anchovy_northern": ( - "northern anchovies gather in huge schools along the pacific coast " - + "and filter plankton with their fine gill rakers." - ), - &"chub_european": ( - "european chub are adaptable river fish. adults often patrol " - + "shallows and feed on insects, small fish, and fruit." - ), - &"chub_flame": ( - "flame chubs prefer cool, clear streams. their bright breeding colors " - + "make a small fish easy to spot in spring water." - ), - &"chub_lake": ( - "lake chubs school in cold northern water and use their small mouths " - + "to pick insects and other tiny prey from the current." - ), - &"goldfish": ( - "goldfish can tolerate a surprisingly wide range of conditions. " - + "their wild relatives often live in slow, weedy water." - ), - &"goldfish_bubbleeye": ( - "bubble-eye goldfish have delicate fluid-filled sacs beneath their " - + "eyes, making careful handling especially important." - ), - &"grouper_gulf": ( - "gulf grouper are ambush predators that use a sudden gulp to pull " - + "prey into their cavernous mouths." - ), - &"grouper_red": ( - "red grouper excavate shelters in reef rubble. the hollows they make " - + "can become hiding places for many smaller animals." - ), - &"mackerel_atlantic": ( - "atlantic mackerel travel in fast schools across the north atlantic " - + "and feed on plankton and small schooling fish." - ), - &"mackerel_cero": ( - "cero mackerel are streamlined coastal hunters. sharp teeth help " - + "them slash through schools of smaller fish." - ), - &"mackerel_chub": ( - "chub mackerel school near the surface and follow plankton blooms. " - + "their dark wavy bars help identify them at a glance." - ), - &"mackerel_king": ( - "king mackerel are swift coastal predators. their pointed teeth and " - + "long body are built for sudden bursts of speed." - ), - &"mackerel_spanish": ( - "spanish mackerel hunt in warm coastal waters and often travel in " - + "loose schools while chasing baitfish." - ), - &"marlin_black": ( - "black marlin are powerful billfish that spend much of their lives " - + "in warm open ocean and can make blistering runs." - ), - &"marlin_blue": ( - "blue marlin are highly migratory open-ocean hunters. their long bill " - + "helps them stun prey before turning back to feed." - ), - &"marlin_white": ( - "white marlin favor warm offshore water and use their rounded dorsal " - + "fin and bill to weave through schools of prey." - ), - &"pomfret_black": ( - "black pomfret have a deep, laterally compressed body that lets them " - + "maneuver neatly through open water." - ), - &"pomfret_chinese": ( - "chinese pomfret are silvery schooling fish with a tall, flattened " - + "body and long sickle-shaped fins." - ), - &"pomfret_golden": ( - "golden pomfret are warm-water swimmers whose golden sheen is most " - + "noticeable along the fins and flanks." - ), - &"pomfret_white": ( - "white pomfret gather over coastal grounds and use their compact, " - + "deep bodies to turn quickly while feeding." - ), - &"sailfish": ( - "sailfish raise their enormous dorsal fin while herding baitfish. " - + "they are among the fastest fish in the sea." - ), - &"sauger": ( - "sauger favor turbid rivers and reservoirs. their mottled backs " - + "blend into the dim, shifting light near the bottom." - ), - &"saugeye": ( - "saugeye are a fertile hybrid of sauger and walleye. they combine " - + "traits from both parent species and often grow quickly." - ), - &"snapper_lane": ( - "lane snapper use reef structure for cover and hunt small crustaceans " - + "and fish when the light begins to fade." - ), - &"snapper_mangrove": ( - "mangrove snapper shelter among roots and docks when young, then " - + "move toward reefs as they grow." - ), - &"snapper_mutton": ( - "mutton snapper have canine teeth that help them pick crabs, shrimp, " - + "and other hard-shelled prey from the reef." - ), - &"snapper_red": ( - "red snapper gather around reefs, wrecks, and ledges. their diet " - + "includes small fish, shrimp, and squid." - ), - &"swordfish": ( - "swordfish are fast, wide-ranging predators with a flattened bill. " - + "they can cross deep water and warm surface layers in one day." - ), - &"trout_cutthroat": ( - "cutthroat trout take their name from the red slash beneath the jaw. " - + "many populations rely on cold, well-oxygenated streams." - ), - &"trout_golden": ( - "golden trout evolved in clear, high-elevation california streams. " - + "their bright flanks stand out against rocky alpine water." - ), - &"trout_rainbow": ( - "rainbow trout are adaptable stream hunters. the pink lateral stripe " - + "is especially vivid when the fish is in breeding condition." - ), - &"trout_steelhead": ( - "steelhead are the ocean-going form of rainbow trout. adults can " - + "leave fresh water and later return to spawn." - ), - &"walleye": ( - "walleye have light-sensitive eyes that help them hunt in murky water " - + "and around dusk. their reflective layer gives the eyes a pale glow." - ), +enum Category { + FRESH_WATER, + SALT_WATER, + OTHER, + SHELLFISH, } -static func category_for(fish: FishDataType) -> WaterType.Type: + +static func category_for(fish: FishDataType) -> Category: if fish == null: - return WaterType.Type.OTHER - return fish.get_primary_water_type() - - -static func category_label(category: WaterType.Type) -> String: - return WaterType.label(category) - - -static func empty_state(category: WaterType.Type) -> String: - match category: + return Category.OTHER + if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH: + return Category.SHELLFISH + match fish.get_primary_water_type(): WaterType.Type.FRESH_WATER: - return "No freshwater catches cataloged yet." + return Category.FRESH_WATER WaterType.Type.SALT_WATER: + return Category.SALT_WATER + _: + return Category.OTHER + + +static func category_label(category: Category) -> String: + match category: + Category.FRESH_WATER: + return "Fresh Water" + Category.SALT_WATER: + return "Salt Water" + Category.SHELLFISH: + return "Shellfish" + _: + return "Misc" + + +static func empty_state(category: Category) -> String: + match category: + Category.FRESH_WATER: + return "No freshwater catches cataloged yet." + Category.SALT_WATER: return "No saltwater catches cataloged yet." + Category.SHELLFISH: + return "No shellfish cataloged yet." _: return "No entries available." -static func catalog_number(fish_id: StringName) -> int: - var index: int = CATALOG_ORDER.find(fish_id) - return index + 1 if index >= 0 else 0 +static func catalog_number(fish: FishDataType) -> int: + return fish.catalog_number if fish != null else 0 -static func facts_for(fish_id: StringName) -> String: - return str(FISH_FACTS.get(fish_id, "unknown")) +static func facts_for(fish: FishDataType) -> String: + if fish == null or fish.logbook_fact.strip_edges().is_empty(): + return "unknown" + return fish.logbook_fact static func ordered_species( candidates: Array[FishDataType], ) -> Array[FishDataType]: - var by_id: Dictionary[StringName, FishDataType] = {} - for fish: FishDataType in candidates: - if fish != null and not fish.id.is_empty(): - by_id[fish.id] = fish var ordered: Array[FishDataType] = [] - for fish_id: StringName in CATALOG_ORDER: - if by_id.has(fish_id): - ordered.append(by_id[fish_id]) + for fish: FishDataType in candidates: + if fish != null and fish.active and not fish.id.is_empty(): + ordered.append(fish) + ordered.sort_custom( + func(a: FishDataType, b: FishDataType) -> bool: + if a.catalog_number == b.catalog_number: + return String(a.id) < String(b.id) + return a.catalog_number < b.catalog_number + ) return ordered diff --git a/ui/logbook_page.gd b/ui/logbook_page.gd index ee63797..efa7ff4 100644 --- a/ui/logbook_page.gd +++ b/ui/logbook_page.gd @@ -50,7 +50,9 @@ const DETAIL_BOTTOM_INSET: float = 35.0 var _collection_log: CollectionLogType var _inventory: FishInventoryType var _catalog: FishPoolType -var _category: WaterType.Type = WaterType.Type.FRESH_WATER +var _category: LogbookCatalog.Category = ( + LogbookCatalog.Category.FRESH_WATER +) var _selected_id: StringName var _selected_entry_key: StringName var _active: bool = false @@ -186,13 +188,14 @@ func _build_interface() -> void: tabs.mouse_filter = Control.MOUSE_FILTER_PASS tabs.add_theme_constant_override("separation", 6) stack.add_child(tabs) - for category: WaterType.Type in [ - WaterType.Type.FRESH_WATER, - WaterType.Type.SALT_WATER, - WaterType.Type.OTHER, + for category: LogbookCatalog.Category in [ + LogbookCatalog.Category.FRESH_WATER, + LogbookCatalog.Category.SALT_WATER, + LogbookCatalog.Category.OTHER, + LogbookCatalog.Category.SHELLFISH, ]: var tab := OrganizerTabType.new() - tab.custom_minimum_size = Vector2(124, 38) + tab.custom_minimum_size = Vector2(90, 38) tab.toggle_mode = true tab.text = LogbookCatalog.category_label(category) tab.palette_index = int(category) @@ -518,7 +521,7 @@ func _add_entry_content( content.add_child(name_label) -func _select_category(category: WaterType.Type) -> void: +func _select_category(category: LogbookCatalog.Category) -> void: if category == _category: return _category = category @@ -654,7 +657,7 @@ func _refresh_details(animate: bool) -> void: func _build_known_details(fish: FishDataType) -> void: _clear_details() - var catalog_number: int = LogbookCatalog.catalog_number(fish.id) + var catalog_number: int = LogbookCatalog.catalog_number(fish) var summary_margin := MarginContainer.new() summary_margin.size_flags_horizontal = Control.SIZE_EXPAND_FILL _set_margins(summary_margin, 40, 0, 40, 0) @@ -717,9 +720,14 @@ func _build_known_details(fish: FishDataType) -> void: facts_column.size_flags_stretch_ratio = 1.0 facts_column.add_theme_constant_override("separation", 6) summary_columns.add_child(facts_column) - var facts_heading := _field_label("fish facts", 16) + var facts_heading := _field_label( + "shellfish facts" + if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH + else "fish facts", + 16, + ) facts_column.add_child(facts_heading) - var facts := _label(LogbookCatalog.facts_for(fish.id), 16) + var facts := _label(LogbookCatalog.facts_for(fish), 16) facts.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART facts.size_flags_horizontal = Control.SIZE_EXPAND_FILL facts_column.add_child(facts) @@ -745,8 +753,12 @@ func _build_known_details(fish: FishDataType) -> void: ) _add_detail_row( left_stats, - "body of water", - WaterType.label(fish.get_primary_water_type()).to_lower(), + ( + "habitat" + if fish.logbook_section == FishDataType.LogbookSection.SHELLFISH + else "body of water" + ), + fish.get_habitat_label(), ) _add_detail_row( left_stats, @@ -999,7 +1011,7 @@ func _on_inventory_changed() -> void: func _unknown_selection_key(fish: FishDataType) -> StringName: - var number: int = LogbookCatalog.catalog_number(fish.id) + var number: int = LogbookCatalog.catalog_number(fish) return StringName("unknown_%d" % number) diff --git a/ui/network/join_game_page.gd b/ui/network/join_game_page.gd index 1788ea5..d985ebd 100644 --- a/ui/network/join_game_page.gd +++ b/ui/network/join_game_page.gd @@ -2,6 +2,7 @@ class_name JoinGamePage extends Control signal join_requested(endpoint: String) +signal join_confirmation_requested(endpoint: String) signal back_requested enum Mode { @@ -62,6 +63,9 @@ var _discovery_refresh_timer: Timer var _editing_entry_id: String = "" var _name_entry_active: bool = false var _delete_armed: bool = false +var _connection_error_latched: bool = false +var _pending_confirmation_endpoint: String = "" +var _pending_confirmation_room: Dictionary = {} func _ready() -> void: @@ -181,13 +185,18 @@ func open_page(preserved_endpoint: String = "") -> void: _address.text = "127.0.0.1:7777" show() UtilityPageStyle.animate_in(self) - _set_mode(_mode) + _set_mode(_mode, false) if _mode == Mode.DIRECT: _address.grab_focus() _address.select_all() func close_page() -> void: + cancel_pending_join_confirmation() + hide_for_join_confirmation() + + +func hide_for_join_confirmation() -> void: _clear_edit_state() _discovery_refresh_timer.stop() hide() @@ -201,10 +210,35 @@ func get_endpoint_text() -> String: func set_status(message: String) -> void: + _connection_error_latched = true _set_status(_friendly_connection_message(message), true) -func _set_mode(mode: Mode) -> void: +func cancel_pending_join_confirmation() -> void: + _pending_confirmation_endpoint = "" + _pending_confirmation_room.clear() + + +func confirm_pending_join() -> bool: + if _pending_confirmation_endpoint.is_empty(): + _set_status("No server is waiting to be joined.", true) + return false + var endpoint_text: String = _pending_confirmation_endpoint + var room: Dictionary = _pending_confirmation_room.duplicate(true) + cancel_pending_join_confirmation() + if not room.is_empty(): + if _discovery == null or not _discovery.prepare_public_join(room): + _set_status("Could not prepare the public connection.", true) + return false + return true + _set_status("Connecting…") + join_requested.emit(endpoint_text) + return true + + +func _set_mode(mode: Mode, clear_connection_error: bool = true) -> void: + if clear_connection_error: + _connection_error_latched = false _mode = mode _selected_entry = null _selected_discovery_index = -1 @@ -225,12 +259,14 @@ func _set_mode(mode: Mode) -> void: func _request_join() -> void: + _connection_error_latched = false if _network_session.state in [ NetworkSession.State.CONNECTION_FAILED, NetworkSession.State.SERVER_LOST, ]: _network_session.reset_failure() var endpoint_text: String = _address.text + var discovery_room: Dictionary = {} if _mode == Mode.DISCOVER: var room: Dictionary = _selected_discovery_room() if _discovery != null and _discovery.is_own_room(room): @@ -239,15 +275,26 @@ func _request_join() -> void: if _discovery_room_is_full(room): _set_status("That room is full.", true) return - if _discovery == null or not _discovery.prepare_public_join(room): + if _discovery == null: _set_status("Could not prepare the public connection.", true) - return + return + endpoint_text = _discovery.room_endpoint(room) + discovery_room = room.duplicate(true) elif _mode != Mode.DIRECT and _selected_entry != null: endpoint_text = _selected_entry.normalized_endpoint var endpoint: ConnectionEndpoint = EndpointParser.parse(endpoint_text) if not endpoint.is_valid(): _set_status(endpoint.error_message, true) return + if _gameplay_context: + _pending_confirmation_endpoint = endpoint.normalized_display + _pending_confirmation_room = discovery_room + join_confirmation_requested.emit(endpoint.normalized_display) + return + if not discovery_room.is_empty(): + if not _discovery.prepare_public_join(discovery_room): + _set_status("Could not prepare the public connection.", true) + return _set_status("Connecting…") _address.text = endpoint.normalized_display join_requested.emit(endpoint.normalized_display) @@ -267,6 +314,10 @@ func _on_public_join_prepared(endpoint_text: String) -> void: func _on_public_join_status_changed(message: String, is_error: bool) -> void: if _mode == Mode.DISCOVER and is_visible_in_tree(): + if is_error: + _connection_error_latched = true + elif _connection_error_latched: + return _set_status(message, is_error) _refresh() @@ -708,7 +759,11 @@ func _on_discovery_rooms_updated(rooms: Array[Dictionary]) -> void: func _on_discovery_status_changed(message: String, is_error: bool) -> void: - if _mode == Mode.DISCOVER and is_visible_in_tree(): + if ( + _mode == Mode.DISCOVER + and is_visible_in_tree() + and not _connection_error_latched + ): _set_status(message, is_error) @@ -740,6 +795,7 @@ func _friendly_connection_message(message: String) -> String: return "Connection cancelled." if ( "timeout" in normalized + or "timed out" in normalized or "unavailable" in normalized or "refused" in normalized or "reach" in normalized @@ -769,6 +825,7 @@ func _clear_edit_state() -> void: func _on_cancel_pressed() -> void: + _connection_error_latched = false if _network_session != null: _network_session.cancel_connection() _refresh() @@ -795,11 +852,14 @@ func _on_state_changed(_state: NetworkSession.State) -> void: func _on_status_message_changed(message: String) -> void: + if _connection_error_latched: + return _set_status(_friendly_connection_message(message)) _refresh() func _on_connection_error(message: String) -> void: + _connection_error_latched = true _set_status(_friendly_connection_message(message), true) _refresh() diff --git a/ui/pause_menu.gd b/ui/pause_menu.gd index dccac73..f98b971 100644 --- a/ui/pause_menu.gd +++ b/ui/pause_menu.gd @@ -73,7 +73,7 @@ var _action_in_progress: bool = false var _root_transition_active: bool = false var _root_transition_generation: int = 0 var _closing_menu: bool = false -var _pending_join_endpoint: String = "" +var _confirmation_returns_to_join_game: bool = false func _ready() -> void: @@ -92,7 +92,10 @@ func _ready() -> void: _settings_panel.navigation_transition_started.connect( _emit_transition_flurry ) - _join_game_page.join_requested.connect(_on_join_game_requested) + _join_game_page.join_requested.connect(_on_confirmed_join_requested) + _join_game_page.join_confirmation_requested.connect( + _on_join_confirmation_requested + ) _join_game_page.back_requested.connect(_close_join_game) _confirmation_page.hide_page() resized.connect(_update_responsive_pause_stage) @@ -139,6 +142,7 @@ func open_menu() -> void: _join_game_page.close_page() _confirmation_page.hide_page() _confirmation_action = ConfirmationAction.NONE + _confirmation_returns_to_join_game = false _action_in_progress = false _root_page.hide_page() show() @@ -238,20 +242,29 @@ func _close_join_game() -> void: _begin_root_entry(true) -func _on_join_game_requested(endpoint: String) -> void: +func _on_join_confirmation_requested(_endpoint: String) -> void: if _action_in_progress or _root_transition_active: + _join_game_page.cancel_pending_join_confirmation() return - _pending_join_endpoint = endpoint - _open_confirmation( - ConfirmationAction.JOIN_ANOTHER, - "join another game?", + _confirmation_action = ConfirmationAction.JOIN_ANOTHER + _confirmation_returns_to_join_game = true + _confirmation_page.configure( ( - "your progression will be saved first. " - + "current players will be disconnected if you are hosting." + "join another game?\n\n" + + "your progression will be saved first. current players will be " + + "disconnected if you are hosting." ), "save and join", - false + "cancel", + BubbleConfirmationPageType.InitialFocus.CONFIRM, ) + _join_game_page.hide_for_join_confirmation() + _emit_transition_flurry() + _show_confirmation() + + +func _on_confirmed_join_requested(endpoint: String) -> void: + join_game_requested.emit(endpoint) func report_network_error(message: String) -> void: @@ -340,6 +353,7 @@ func _open_confirmation( ) -> void: if _action_in_progress or _root_transition_active: return + _confirmation_returns_to_join_game = false _confirmation_action = action _confirmation_page.configure( "%s\n\n%s" % [title, message], @@ -380,6 +394,11 @@ func _close_confirmation() -> void: func _finish_confirmation_cancel() -> void: + if _confirmation_returns_to_join_game: + _confirmation_returns_to_join_game = false + _join_game_page.cancel_pending_join_confirmation() + _join_game_page.open_page() + return _begin_root_entry(true) @@ -414,9 +433,9 @@ func _finish_confirmation_accept(action: ConfirmationAction) -> void: ConfirmationAction.QUIT_ANYWAY: quit_requested.emit() ConfirmationAction.JOIN_ANOTHER: - _join_game_page.close_page() - join_game_requested.emit(_pending_join_endpoint) - _pending_join_endpoint = "" + _confirmation_returns_to_join_game = false + _join_game_page.open_page() + _join_game_page.confirm_pending_join() _: _action_in_progress = false _begin_root_entry(false) @@ -507,6 +526,7 @@ func _finish_close(reason: CloseReason, restore_controls: bool) -> void: _root_page.hide_page() _confirmation_page.hide_page() _confirmation_action = ConfirmationAction.NONE + _confirmation_returns_to_join_game = false _action_in_progress = false _transition_flurry.clear_flurries() hide() diff --git a/ui/player_menu.gd b/ui/player_menu.gd index 7f73acd..6c5d2f2 100644 --- a/ui/player_menu.gd +++ b/ui/player_menu.gd @@ -4334,7 +4334,7 @@ func _refresh_logbook() -> void: var valid_species: Array[FishDataType] = [] if _catalog != null: for fish: FishDataType in _catalog.candidates: - if fish != null and not fish.id.is_empty(): + if fish != null and fish.active and not fish.id.is_empty(): valid_species.append(fish) _logbook_species = valid_species _logbook_empty.visible = valid_species.is_empty() diff --git a/ui/profile_page.gd b/ui/profile_page.gd index 3fc9fb4..57a30cf 100644 --- a/ui/profile_page.gd +++ b/ui/profile_page.gd @@ -6,6 +6,9 @@ const OPTION_GRID_COLUMNS: int = 6 const FUR_PALETTE_GRID_COLUMNS: int = 10 const FUR_CHANNEL_ICON_SIZE: int = 20 const FEATURE_DRAWER_ANIMATION_SECONDS: float = 0.16 +const FEATURE_PREVIEW_SCAN_MAX_SIZE: int = 256 +const FEATURE_PREVIEW_TEXTURE_MAX_SIZE: int = 128 +const FEATURE_PREVIEW_PADDING: int = 3 const ControllerMappingManagerType = preload( "res://settings/controller_mapping_manager.gd" ) @@ -50,6 +53,9 @@ var _experience_level: Label var _experience_progress: ProgressBar var _experience_value: Label var _feature_preview_cache: Dictionary = {} +var _feature_preview_requests: Array[Dictionary] = [] +var _feature_preview_generation: int = 0 +var _feature_preview_worker_active: bool = false var _fur_swatch_icon_cache: Dictionary = {} var _expanded_feature_drawers: Dictionary = {} var _feature_drawer_animation_key: String = "" @@ -69,6 +75,10 @@ func _ready() -> void: add_child(_debounce) +func _exit_tree() -> void: + _cancel_feature_preview_requests() + + func setup( service: NetworkProfileService, experience: PlayerExperience, @@ -125,6 +135,7 @@ func activate() -> void: func deactivate() -> void: visible = false _debounce.stop() + _cancel_feature_preview_requests() if _voice_preview_tween != null and _voice_preview_tween.is_valid(): _voice_preview_tween.kill() _preview.reset_view() @@ -428,6 +439,7 @@ func _select_category(category_id: String) -> void: func _refresh_options() -> void: + _cancel_feature_preview_requests() for child: Node in _option_list.get_children(): child.queue_free() var title := Label.new() @@ -801,7 +813,6 @@ func _build_feature_option_button( button.add_theme_font_size_override("font_size", 28) else: var preview := TextureRect.new() - preview.texture = _feature_preview_texture(_category_id, option_id) preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED preview.mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -815,6 +826,13 @@ func _build_feature_option_button( preview.size = Vector2(preview_width, preview_height) preview.position = Vector2((84.0 - preview_width) * 0.5, 7.0) button.add_child(preview) + var cached_preview := _cached_feature_preview_texture( + _category_id, option_id + ) + if cached_preview != null: + preview.texture = cached_preview + else: + _queue_feature_preview(preview, _category_id, option_id) if is_representative and has_variants: var indicator := Label.new() indicator.text = "<" if is_expanded else ">" @@ -858,33 +876,114 @@ func _animate_feature_drawer_button(button: Button) -> void: ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT) -func _feature_preview_texture( +func _cached_feature_preview_texture( category_id: String, option_id: String, ) -> Texture2D: var cache_key := "%s:%s" % [category_id, option_id] - if _feature_preview_cache.has(cache_key): - return _feature_preview_cache[cache_key] as Texture2D - var source := CharacterCustomizationCatalog.texture_for( + return _feature_preview_cache.get(cache_key) as Texture2D + + +func _queue_feature_preview( + target: TextureRect, + category_id: String, + option_id: String, +) -> void: + _feature_preview_requests.append({ + "generation": _feature_preview_generation, + "target": weakref(target), + "category_id": category_id, + "option_id": option_id, + }) + if _feature_preview_worker_active: + return + _feature_preview_worker_active = true + _process_feature_preview_requests.call_deferred() + + +func _cancel_feature_preview_requests() -> void: + _feature_preview_generation += 1 + _feature_preview_requests.clear() + + +func _process_feature_preview_requests() -> void: + while not _feature_preview_requests.is_empty(): + var request: Dictionary = _feature_preview_requests.pop_front() + await get_tree().process_frame + if int(request.get("generation", -1)) != _feature_preview_generation: + continue + var target_reference := request.get("target") as WeakRef + if target_reference == null: + continue + var target := target_reference.get_ref() as TextureRect + if target == null or not is_instance_valid(target): + continue + var category_id := str(request.get("category_id", "")) + var option_id := str(request.get("option_id", "")) + var preview := _cached_feature_preview_texture(category_id, option_id) + if preview == null: + preview = _create_feature_preview_texture(category_id, option_id) + if ( + preview != null + and int(request.get("generation", -1)) == _feature_preview_generation + and is_instance_valid(target) + ): + target.texture = preview + _feature_preview_worker_active = false + + +func _create_feature_preview_texture( + category_id: String, + option_id: String, +) -> Texture2D: + var cache_key := "%s:%s" % [category_id, option_id] + var resource_path := CharacterCustomizationCatalog.feature_texture_path( category_id, option_id ) + if resource_path.is_empty(): + return null + var source := ResourceLoader.load( + resource_path, + "Texture2D", + ResourceLoader.CACHE_MODE_IGNORE, + ) as Texture2D if source == null: return null var image := source.get_image() + if image == null or image.is_empty(): + return null + if image.is_compressed() and image.decompress() != OK: + return null + _resize_feature_preview_image(image, FEATURE_PREVIEW_SCAN_MAX_SIZE) var used := image.get_used_rect() if used.size.x <= 0 or used.size.y <= 0: - _feature_preview_cache[cache_key] = source - return source - var padding := 12 - used.position -= Vector2i(padding, padding) - used.size += Vector2i(padding * 2, padding * 2) + return null + used.position -= Vector2i(FEATURE_PREVIEW_PADDING, FEATURE_PREVIEW_PADDING) + used.size += Vector2i( + FEATURE_PREVIEW_PADDING * 2, + FEATURE_PREVIEW_PADDING * 2, + ) used = used.intersection(Rect2i(Vector2i.ZERO, image.get_size())) var cropped := image.get_region(used) + _resize_feature_preview_image(cropped, FEATURE_PREVIEW_TEXTURE_MAX_SIZE) var preview := ImageTexture.create_from_image(cropped) _feature_preview_cache[cache_key] = preview return preview +func _resize_feature_preview_image(image: Image, max_dimension: int) -> void: + var source_size := image.get_size() + var largest_dimension := maxi(source_size.x, source_size.y) + if largest_dimension <= max_dimension: + return + var scale := float(max_dimension) / float(largest_dimension) + image.resize( + maxi(roundi(float(source_size.x) * scale), 1), + maxi(roundi(float(source_size.y) * scale), 1), + Image.INTERPOLATE_NEAREST, + ) + + func _build_fur_color_options(options: Array) -> void: var pattern_label := Label.new() pattern_label.text = "pattern" diff --git a/ui/the_net_page.gd b/ui/the_net_page.gd index a60bc26..ff83a11 100644 --- a/ui/the_net_page.gd +++ b/ui/the_net_page.gd @@ -158,6 +158,8 @@ func _build_laptop() -> void: ) forecast_stack.add_child(forecast_heading) _forecast_list = HBoxContainer.new() + _forecast_list.custom_minimum_size = Vector2(272.0, 66.0) + _forecast_list.size_flags_horizontal = Control.SIZE_EXPAND_FILL _forecast_list.alignment = BoxContainer.ALIGNMENT_CENTER _forecast_list.add_theme_constant_override("separation", 6) forecast_stack.add_child(_forecast_list) @@ -501,7 +503,10 @@ func _daily_refresh_text() -> String: func _add_forecast_empty(message: String) -> void: var label := Label.new() label.text = message - label.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + label.custom_minimum_size = Vector2(252.0, 66.0) + label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + label.autowrap_mode = TextServer.AUTOWRAP_OFF label.add_theme_color_override( "font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY ) diff --git a/ui/title_screen.gd b/ui/title_screen.gd index 54cfcf7..9e17d3e 100644 --- a/ui/title_screen.gd +++ b/ui/title_screen.gd @@ -276,6 +276,19 @@ func reopen() -> void: _start_entry_prompt_animation() +func reopen_to_menu() -> void: + reopen() + _awaiting_start_input = false + _stop_entry_prompt_animation() + _start_prompt_center.hide() + _presentation_center.show() + _button_center.show() + var bubble_field := get_node_or_null("%BubbleField") as Control + if bubble_field != null: + bubble_field.show() + _set_title_bubbles_interactive(true) + + func open_join_game_page(endpoint: String = "") -> void: _cancel_title_entry_transition() _awaiting_start_input = false diff --git a/ui/title_screen.tscn b/ui/title_screen.tscn index c79e894..9ce8239 100644 --- a/ui/title_screen.tscn +++ b/ui/title_screen.tscn @@ -221,7 +221,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.6.8-alpha" +text = "v0.8.1-alpha" horizontal_alignment = 1 [node name="Spacer" type="Control" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"] diff --git a/world/environment/local_storm_cloud_layer.gd b/world/environment/local_storm_cloud_layer.gd index 2ec0008..1b58fb7 100644 --- a/world/environment/local_storm_cloud_layer.gd +++ b/world/environment/local_storm_cloud_layer.gd @@ -13,6 +13,9 @@ const DISTANCE_FADE_START: float = 240.0 const DISTANCE_FADE_END: float = 300.0 const WRAP_RADIUS: float = FIELD_HALF_EXTENT const MAXIMUM_OPACITY: float = 1.0 +const LIGHT_CEILING_SIZE: float = 520.0 +const LIGHT_CEILING_ALTITUDE: float = 20.0 +const LIGHT_MAXIMUM_OPACITY: float = 0.72 const BASE_VELOCITY := Vector2(0.825, -0.45) const BODY_OUTLINE: Array[Vector2] = [ Vector2(-6.9, -0.8), @@ -50,14 +53,25 @@ var _target: Node3D var _material: ShaderMaterial var _multimesh: MultiMesh var _cloud_mesh: MultiMeshInstance3D +var _light_cloud_mesh: MeshInstance3D +var _light_material: StandardMaterial3D var _drift_offset := Vector2.ZERO var _storm_amount: float = 0.0 +var _light_performance_profile: bool = false -func setup(target: Node3D) -> void: +func setup( + target: Node3D, + light_performance_profile: bool = false, +) -> void: _target = target - _build_cloud_field() - _update_cloud_transforms() + _light_performance_profile = light_performance_profile + if _light_performance_profile: + _build_light_cloud_ceiling() + _update_light_position() + else: + _build_cloud_field() + _update_cloud_transforms() set_process(true) @@ -65,8 +79,19 @@ func set_storm_amount(amount: float, color: Color) -> void: _storm_amount = clampf(amount, 0.0, 1.0) var should_be_visible: bool = _storm_amount > 0.001 if should_be_visible and not visible: - _update_cloud_transforms() + if _light_performance_profile: + _update_light_position() + else: + _update_cloud_transforms() visible = should_be_visible + if _light_performance_profile: + if _light_material != null: + var light_color: Color = color + light_color.a = ( + _storm_amount * LIGHT_MAXIMUM_OPACITY + ) + _light_material.albedo_color = light_color + return if _material == null: return _material.set_shader_parameter("weather_opacity", _storm_amount) @@ -78,12 +103,19 @@ func get_storm_amount() -> float: func get_patch_count() -> int: - return GRID_AXIS_COUNT * GRID_AXIS_COUNT + return ( + 1 + if _light_performance_profile + else GRID_AXIS_COUNT * GRID_AXIS_COUNT + ) func _process(delta: float) -> void: if not visible or _target == null or not is_instance_valid(_target): return + if _light_performance_profile: + _update_light_position() + return _drift_offset += BASE_VELOCITY * delta _drift_offset = Vector2( _wrap_axis(_drift_offset.x), @@ -128,6 +160,37 @@ func _build_cloud_field() -> void: visible = false +func _build_light_cloud_ceiling() -> void: + _light_material = StandardMaterial3D.new() + _light_material.resource_name = "light_cloud_ceiling" + _light_material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA + _light_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED + _light_material.cull_mode = BaseMaterial3D.CULL_DISABLED + _light_material.albedo_color = Color(0.22, 0.25, 0.26, 0.0) + var ceiling_mesh := PlaneMesh.new() + ceiling_mesh.size = Vector2.ONE * LIGHT_CEILING_SIZE + ceiling_mesh.material = _light_material + _light_cloud_mesh = MeshInstance3D.new() + _light_cloud_mesh.name = "CloudCeiling" + _light_cloud_mesh.position.y = LIGHT_CEILING_ALTITUDE + _light_cloud_mesh.mesh = ceiling_mesh + _light_cloud_mesh.cast_shadow = ( + GeometryInstance3D.SHADOW_CASTING_SETTING_OFF + ) + add_child(_light_cloud_mesh) + visible = false + + +func _update_light_position() -> void: + if _target == null or not is_instance_valid(_target): + return + global_position = Vector3( + _target.global_position.x, + 0.0, + _target.global_position.z, + ) + + func _build_cloud_body_mesh() -> ArrayMesh: var outline := PackedVector2Array(BODY_OUTLINE) var surface_tool := SurfaceTool.new() diff --git a/world/regions/starter_island_region.gd b/world/regions/starter_island_region.gd index 4c8880c..adb0fab 100644 --- a/world/regions/starter_island_region.gd +++ b/world/regions/starter_island_region.gd @@ -13,6 +13,14 @@ const FOLIAGE_MATERIAL_NAMES: Array[StringName] = [ &"leaf_light", &"leaf_dark", ] +const NORMAL_SALT_WATER_MATERIAL: Material = preload( + "res://world/materials/stylized_water.tres" +) +const NORMAL_FRESH_WATER_MATERIAL: Material = preload( + "res://world/materials/stylized_water_fresh.tres" +) +const LIGHT_SALT_WATER_COLOR := Color(0.11, 0.345, 0.435) +const LIGHT_FRESH_WATER_COLOR := Color(0.18, 0.46, 0.50) @export_group("Owned Nodes") @export_node_path("MeshInstance3D") @@ -60,6 +68,131 @@ func get_saltwater_shoreline_mesh() -> MeshInstance3D: return get_node_or_null(^"ShorelineRibbons/Ocean") as MeshInstance3D +func set_light_performance_profile(enabled: bool) -> void: + var pond := get_node_or_null( + ^"WaterBodies/Pond/VisualWater" + ) as MeshInstance3D + var ocean := get_node_or_null( + ^"WaterBodies/Ocean/VisualWater" + ) as MeshInstance3D + if pond != null: + pond.material_override = ( + _create_light_water_material( + LIGHT_FRESH_WATER_COLOR, + &"light_fresh_water", + ) + if enabled + else NORMAL_FRESH_WATER_MATERIAL + ) + if ocean != null: + ocean.material_override = ( + _create_light_water_material( + LIGHT_SALT_WATER_COLOR, + &"light_salt_water", + ) + if enabled + else NORMAL_SALT_WATER_MATERIAL + ) + var surface_motion := ocean as WaterSurfaceMotion + if surface_motion != null: + surface_motion.set_motion_enabled(not enabled) + + +func _create_light_water_material( + color: Color, + material_name: StringName, +) -> StandardMaterial3D: + var material := StandardMaterial3D.new() + material.resource_name = str(material_name) + material.albedo_color = color + material.roughness = 1.0 + material.shading_mode = BaseMaterial3D.SHADING_MODE_PER_VERTEX + return material + + +func get_spawn_surface_triangles( + material_names: Array[StringName], + minimum_global_y: float, + minimum_up_dot: float = 0.6, +) -> Array[PackedVector3Array]: + var triangles: Array[PackedVector3Array] = [] + if material_names.is_empty(): + return triangles + var terrain_root: Node = get_node_or_null(terrain_visual_root_path) + if terrain_root == null: + return triangles + for mesh_instance: MeshInstance3D in _collect_terrain_mesh_instances( + terrain_root + ): + var mesh: Mesh = mesh_instance.mesh + if mesh == null: + continue + for surface_index: int in mesh.get_surface_count(): + var material: Material = mesh_instance.get_active_material( + surface_index + ) + if material == null or not material_names.has( + StringName(material.resource_name) + ): + continue + var arrays: Array = mesh.surface_get_arrays(surface_index) + if arrays.size() <= Mesh.ARRAY_INDEX: + continue + var vertices := arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array + var indices := arrays[Mesh.ARRAY_INDEX] as PackedInt32Array + if vertices.is_empty(): + continue + if indices.is_empty(): + for vertex_index: int in range(0, vertices.size() - 2, 3): + _append_spawn_triangle( + triangles, + mesh_instance, + vertices[vertex_index], + vertices[vertex_index + 1], + vertices[vertex_index + 2], + minimum_global_y, + minimum_up_dot, + ) + else: + for index_offset: int in range(0, indices.size() - 2, 3): + _append_spawn_triangle( + triangles, + mesh_instance, + vertices[indices[index_offset]], + vertices[indices[index_offset + 1]], + vertices[indices[index_offset + 2]], + minimum_global_y, + minimum_up_dot, + ) + return triangles + + +func _append_spawn_triangle( + result: Array[PackedVector3Array], + mesh_instance: MeshInstance3D, + local_a: Vector3, + local_b: Vector3, + local_c: Vector3, + minimum_global_y: float, + minimum_up_dot: 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 ( + 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) + if cross.length_squared() <= 0.0000001: + return + if absf(cross.normalized().dot(Vector3.UP)) < minimum_up_dot: + return + result.append(PackedVector3Array([a, b, c])) + + func has_terrain_collision() -> bool: var collision_shape: CollisionShape3D = ( get_node_or_null(terrain_collision_shape_path) as CollisionShape3D diff --git a/world/test_world.gd b/world/test_world.gd index a3ea397..e40c3d3 100644 --- a/world/test_world.gd +++ b/world/test_world.gd @@ -50,6 +50,10 @@ func get_sun() -> DirectionalLight3D: return _sun +func set_light_performance_profile(enabled: bool) -> void: + _starter_island.set_light_performance_profile(enabled) + + func get_fishable_water_regions() -> Array[FishableWaterRegion]: var waters: Array[FishableWaterRegion] = [] for region: WorldRegion in _get_regions(): @@ -61,6 +65,16 @@ func get_saltwater_shoreline_mesh() -> MeshInstance3D: return _starter_island.get_saltwater_shoreline_mesh() +func get_spawn_surface_triangles( + material_names: Array[StringName], + minimum_global_y: float, +) -> Array[PackedVector3Array]: + return _starter_island.get_spawn_surface_triangles( + material_names, + minimum_global_y, + ) + + func _get_regions() -> Array[WorldRegion]: var regions: Array[WorldRegion] = [] for child: Node in _regions_root.get_children(): diff --git a/world/water_surface_motion.gd b/world/water_surface_motion.gd index 992f9cb..02e397e 100644 --- a/world/water_surface_motion.gd +++ b/world/water_surface_motion.gd @@ -35,6 +35,12 @@ func _process(_delta: float) -> void: ) +func set_motion_enabled(enabled: bool) -> void: + set_process(enabled and amplitude > 0.0) + if not enabled: + position.y = _base_height + + static func get_default_height_offset() -> float: return calculate_height_offset( _current_time_seconds(), diff --git a/world/world_time_visual_controller.gd b/world/world_time_visual_controller.gd index 231bbfd..e4e2166 100644 --- a/world/world_time_visual_controller.gd +++ b/world/world_time_visual_controller.gd @@ -2,10 +2,18 @@ class_name WorldTimeVisualController extends Node const UPDATE_INTERVAL_SECONDS: float = 0.1 +const LIGHT_UPDATE_INTERVAL_SECONDS: float = 0.25 const SUN_YAW_DEGREES: float = -32.0 const WEATHER_TRANSITION_SECONDS: float = 10.0 const RAIN_EMITTER_OFFSET := Vector3(0.0, 7.0, 0.0) -const RAIN_PARTICLE_AMOUNT: int = 560 +const RAIN_EMISSION_EXTENTS := Vector3(13.0, 1.0, 13.0) +const RAIN_VISIBILITY_AABB := AABB( + Vector3(-13.5, -9.0, -13.5), + Vector3(27.0, 12.0, 27.0), +) +const RAIN_PARTICLE_AMOUNT: int = 2240 +const LIGHT_RAIN_PARTICLE_AMOUNT: int = 256 +const LIGHT_RAIN_FIXED_FPS: int = 15 const RAIN_VELOCITY_MIN: float = 16.0 const RAIN_VELOCITY_MAX: float = 20.0 const RAIN_DROP_SIZE := Vector3(0.014, 0.34, 0.014) @@ -87,6 +95,7 @@ var _weather_to: WorldWeatherService.Weather = ( WorldWeatherService.Weather.SUNNY ) var _weather_transition: float = 1.0 +var _light_performance_profile: bool = false func setup( @@ -96,6 +105,7 @@ func setup( weather_service: WorldWeatherService = null, rain_target: Node3D = null, rain_camera_provider: Callable = Callable(), + light_performance_profile: bool = false, ) -> void: _time_service = time_service _weather_service = weather_service @@ -103,6 +113,7 @@ func setup( _sun = sun _rain_target = rain_target _rain_camera_provider = rain_camera_provider + _light_performance_profile = light_performance_profile if not _prepare_runtime_environment(): set_process(false) return @@ -130,7 +141,12 @@ func _process(delta: float) -> void: 1.0, ) _elapsed += delta - if _elapsed < UPDATE_INTERVAL_SECONDS: + var update_interval: float = ( + LIGHT_UPDATE_INTERVAL_SECONDS + if _light_performance_profile + else UPDATE_INTERVAL_SECONDS + ) + if _elapsed < update_interval: return _elapsed = 0.0 _apply_time(_time_service.get_time_hours()) @@ -182,19 +198,23 @@ func _prepare_runtime_environment() -> bool: func _prepare_rain() -> void: _rain = GPUParticles3D.new() _rain.name = "LocalRain" - _rain.amount = RAIN_PARTICLE_AMOUNT + _rain.amount = ( + LIGHT_RAIN_PARTICLE_AMOUNT + if _light_performance_profile + else RAIN_PARTICLE_AMOUNT + ) _rain.amount_ratio = 0.0 _rain.lifetime = 1.25 - _rain.fixed_fps = 30 - _rain.local_coords = false - _rain.visibility_aabb = AABB( - Vector3(-7.0, -9.0, -7.0), Vector3(14.0, 12.0, 14.0) + _rain.fixed_fps = ( + LIGHT_RAIN_FIXED_FPS if _light_performance_profile else 30 ) + _rain.local_coords = false + _rain.visibility_aabb = RAIN_VISIBILITY_AABB var process_material := ParticleProcessMaterial.new() process_material.emission_shape = ( ParticleProcessMaterial.EMISSION_SHAPE_BOX ) - process_material.emission_box_extents = Vector3(6.5, 1.0, 6.5) + process_material.emission_box_extents = RAIN_EMISSION_EXTENTS process_material.direction = Vector3.DOWN process_material.spread = 5.0 process_material.initial_velocity_min = RAIN_VELOCITY_MIN @@ -217,7 +237,7 @@ func _prepare_storm_clouds() -> void: _storm_clouds = LocalStormCloudLayerType.new() _storm_clouds.name = "LocalStormClouds" add_child(_storm_clouds) - _storm_clouds.setup(_rain_target) + _storm_clouds.setup(_rain_target, _light_performance_profile) func _apply_time(time_hours: float) -> void: @@ -256,6 +276,33 @@ func _apply_time(time_hours: float) -> void: var fog_color: Color = _blended_color( NIGHT_FOG, DAY_FOG, WARM_FOG, daylight, warmth ) + if _light_performance_profile: + var simple_cloud_color := _blended_color( + NIGHT_CLOUD_SHADOW, + DAY_CLOUD_SHADOW, + WARM_CLOUD_SHADOW, + daylight, + warmth, + ) + var simple_overcast: float = _weather_value( + 0.0, + 0.55, + 0.82, + 0.0, + ) + sky_top = sky_top.lerp(simple_cloud_color, simple_overcast) + sky_horizon = sky_horizon.lerp( + simple_cloud_color.lightened(0.08), + simple_overcast, + ) + ground_bottom = ground_bottom.lerp( + simple_cloud_color.darkened(0.12), + simple_overcast * 0.65, + ) + ground_horizon = ground_horizon.lerp( + simple_cloud_color, + simple_overcast * 0.75, + ) var foggy_fog_amount: float = _weather_value(0.0, 0.0, 0.0, 1.0) var rain_fog_amount: float = _weather_value(0.0, 0.0, 1.0, 0.0) var fog_daylight_amount: float = daylight * foggy_fog_amount @@ -396,6 +443,8 @@ func _apply_water_environment( foggy_horizon_occlusion: float, fog_render_color: Color, ) -> void: + if _light_performance_profile: + return var water_tint := _blended_color( NIGHT_WATER_TINT, Color.WHITE, @@ -558,6 +607,15 @@ func _apply_sky_clouds(daylight: float, warmth: float) -> void: daylight, warmth, ) + if _light_performance_profile: + _sky_material.set_shader_parameter("cloud_coverage", 0.0) + _sky_material.set_shader_parameter("cloud_opacity", 0.0) + if _storm_clouds != null: + _storm_clouds.set_storm_amount( + _weather_value(0.0, 0.42, 0.88, 0.0), + lower_cloud_color, + ) + return _sky_material.set_shader_parameter( "cloud_coverage", _weather_value(0.0, 0.58, 0.985, 0.0),