Compare commits

...

31 commits

Author SHA1 Message Date
eeef99793d Prepare private v0.20.4-alpha test build 2026-09-01 19:07:24 -04:00
41b4f86ddf restore full world effects outside portmaster 2026-09-01 19:00:13 -04:00
4fda6e97f6 Integrate per-save identity and interaction UI 2026-09-01 17:30:50 -04:00
ac82a28f3b Harden home exits and interior presentation 2026-09-01 17:30:34 -04:00
76b4f19e4d Let shopkeepers roam safely around their shops 2026-09-01 17:30:27 -04:00
134ece4a10 Reduce fog and suppress weather indoors 2026-09-01 17:30:18 -04:00
6029eb29b8 Keep generated stream casts in freshwater 2026-09-01 17:30:10 -04:00
cd00f3c55e Restore remote locomotion after fishing returns 2026-09-01 17:30:04 -04:00
4196cda1d5 Make decor occupy individual inventory slots 2026-09-01 17:29:57 -04:00
f2239d248a Optimize finalized surface artwork 2026-09-01 13:05:05 -04:00
82938d3d57 fix supporter release packaging isolation 2026-09-01 11:37:32 -04:00
4d31fd1517 feat(network): add selectable privacy-preserving discovery 2026-09-01 10:21:26 -04:00
9cf0c8fc9c test: align storage validation with home upgrades 2026-09-01 10:21:21 -04:00
ff78710303 fix: harden recovery flow and clean diagnostics 2026-09-01 07:44:01 -04:00
0158b0215f Scale coastal crab spawning by habitat 2026-08-31 16:04:34 -04:00
d8bf59bca0 Add starter RV progression and fishing feedback 2026-08-31 16:04:20 -04:00
eed361681a Import completed creature artwork 2026-08-31 16:03:21 -04:00
014c064fae Prepare private v0.20.2-alpha test build 2026-08-31 06:06:09 -04:00
3be79fa3fc Normalize cursor handoff across gameplay menus 2026-08-31 05:48:47 -04:00
6578d3e870 Add transactional RV decor synchronization 2026-08-31 05:48:36 -04:00
ee992d9f0c Restore public discovery default after patron build 2026-08-31 00:52:31 -04:00
9b96a3bf53 Replace starter RV placeholder with donated motorcoach 2026-08-31 00:31:02 -04:00
826f62c352 Prepare private v0.20.1-alpha patron release 2026-08-30 23:53:07 -04:00
b48cb73f3a Harden multiplayer validation for portable homes 2026-08-30 23:52:25 -04:00
d2dce7f900 Stabilize runtime validation under dynamic UI load 2026-08-30 22:32:27 -04:00
983e4d0ec3 Update validation for portable home protocol 2026-08-30 22:00:04 -04:00
fe099f47dd Add portable RV homes and decor prototype 2026-08-30 21:37:26 -04:00
81decf2b71 Add logbook catalog index filters 2026-08-29 20:19:47 -04:00
4575830857 Improve nearby water recovery and camera prompts
Recover players on generated worlds to the nearest safe grass or sand surface. Use the active gameplay camera when positioning shop and storage prompts.
2026-08-29 17:43:11 -04:00
ae802493a0 Fix clam spurt origin artifact (#118) 2026-08-29 17:41:10 -04:00
f910e6bdbb Update Forgejo links for Straywild rename 2026-08-26 00:05:51 -04:00
313 changed files with 21118 additions and 1936 deletions

View file

@ -22,9 +22,9 @@ Discord: https://discord.gg/5gP22447kc
Matrix: https://matrix.to/#/#straywild:matrix.makearmy.io Matrix: https://matrix.to/#/#straywild:matrix.makearmy.io
Repo: https://forge.makearmy.io/woofmeow/netfishing Repo: https://forge.makearmy.io/woofmeow/straywild
Releases: https://forge.makearmy.io/woofmeow/netfishing/releases Releases: https://forge.makearmy.io/woofmeow/straywild/releases
## Installing release builds ## Installing release builds

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://busj0feq2dncu"
path.s3tc="res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.s3tc.ctex"
path.etc2="res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2502.png"
dest_files=["res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.s3tc.ctex", "res://.godot/imported/2502.png-3922e306611b91541a86520cf32cbdbd.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://4a7c1aat4777"
path.s3tc="res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.s3tc.ctex"
path.etc2="res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2504.png"
dest_files=["res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.s3tc.ctex", "res://.godot/imported/2504.png-c9f47ab152b933d8158b8c058ec5fcc2.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b8u3lw242yr3c"
path.s3tc="res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.s3tc.ctex"
path.etc2="res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2505.png"
dest_files=["res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.s3tc.ctex", "res://.godot/imported/2505.png-fe14d70a6372dbe0cc4a8275d0816cad.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c2k6ghkng6081"
path.s3tc="res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.s3tc.ctex"
path.etc2="res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2508.png"
dest_files=["res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.s3tc.ctex", "res://.godot/imported/2508.png-0478df4a4cb2af8e08ada8086efda0c3.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d14jvhurcfryb"
path.s3tc="res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.s3tc.ctex"
path.etc2="res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2601.png"
dest_files=["res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.s3tc.ctex", "res://.godot/imported/2601.png-912942ea4c6b2c66982e089ee46fde12.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cc7el34sfad78"
path.s3tc="res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.s3tc.ctex"
path.etc2="res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2801.png"
dest_files=["res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.s3tc.ctex", "res://.godot/imported/2801.png-d1c5df9faa2d1eb1cc3453c8e18d4049.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bawecr4tv52p1"
path.s3tc="res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.s3tc.ctex"
path.etc2="res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2802.png"
dest_files=["res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.s3tc.ctex", "res://.godot/imported/2802.png-6319ce264fbd950e232fd0b428319265.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjwm07240ehr2"
path.s3tc="res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.s3tc.ctex"
path.etc2="res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2803.png"
dest_files=["res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.s3tc.ctex", "res://.godot/imported/2803.png-c2e31a8adbab3bf9899bc74b3feb4abb.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dgcrfxo1k4juj"
path.s3tc="res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.s3tc.ctex"
path.etc2="res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2804.png"
dest_files=["res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.s3tc.ctex", "res://.godot/imported/2804.png-f86c52a01f04302b057cf526a498e87a.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c5be8db5ppkq4"
path.s3tc="res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.s3tc.ctex"
path.etc2="res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2806.png"
dest_files=["res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.s3tc.ctex", "res://.godot/imported/2806.png-ed0484fec72605166ddb723c1a1bc7d0.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bdus4qo0ebs3e"
path.s3tc="res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.s3tc.ctex"
path.etc2="res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/2807.png"
dest_files=["res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.s3tc.ctex", "res://.godot/imported/2807.png-aceaf0f586f975d29e318a9513fc3125.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b68vlgtclvkfg"
path.s3tc="res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.s3tc.ctex"
path.etc2="res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3002.png"
dest_files=["res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.s3tc.ctex", "res://.godot/imported/3002.png-a87d71158798b43b6bf702a6e1664b4d.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c4d3f28rf86bb"
path.s3tc="res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.s3tc.ctex"
path.etc2="res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3102.png"
dest_files=["res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.s3tc.ctex", "res://.godot/imported/3102.png-951ca90331737e3057749e1bdbd88c44.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjtncgnb0hcsj"
path.s3tc="res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.s3tc.ctex"
path.etc2="res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3103.png"
dest_files=["res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.s3tc.ctex", "res://.godot/imported/3103.png-f3b4b5bb43b7611f7c448dbdfff36375.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dlme86lrrhyg8"
path.s3tc="res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.s3tc.ctex"
path.etc2="res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3104.png"
dest_files=["res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.s3tc.ctex", "res://.godot/imported/3104.png-fa31dd69bf22b484cf3ac0d99197b886.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://csv1dsgvifh4l"
path.s3tc="res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.s3tc.ctex"
path.etc2="res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3107.png"
dest_files=["res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.s3tc.ctex", "res://.godot/imported/3107.png-5b981d56f7094a29a92b2f5035a47b11.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cd8u0ow8cqsbh"
path.s3tc="res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.s3tc.ctex"
path.etc2="res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3201.png"
dest_files=["res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.s3tc.ctex", "res://.godot/imported/3201.png-846cb0fbb88950bb70d5b03c50a88457.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://begsj5tj035e1"
path.s3tc="res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.s3tc.ctex"
path.etc2="res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3401.png"
dest_files=["res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.s3tc.ctex", "res://.godot/imported/3401.png-cd8c7ec154f8248110ec396b3f8a76c9.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ddc556gcby50v"
path.s3tc="res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.s3tc.ctex"
path.etc2="res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/3902.png"
dest_files=["res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.s3tc.ctex", "res://.godot/imported/3902.png-485bc4f9f1a505225bdc037111e32499.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bahd6nks2g7ik"
path.s3tc="res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.s3tc.ctex"
path.etc2="res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/4101.png"
dest_files=["res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.s3tc.ctex", "res://.godot/imported/4101.png-163a8880f3559279d2acc129509430bc.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://byil5mr6w3j1q"
path.s3tc="res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.s3tc.ctex"
path.etc2="res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/5001.png"
dest_files=["res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.s3tc.ctex", "res://.godot/imported/5001.png-ebe37fb8bd4263bb83e36bb5890b5641.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://0k4qnt7wusam"
path.s3tc="res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.s3tc.ctex"
path.etc2="res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/5902.png"
dest_files=["res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.s3tc.ctex", "res://.godot/imported/5902.png-e3a13a1e827488fa86f34c2aad883127.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bjb8gqbta2oxw"
path.s3tc="res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.s3tc.ctex"
path.etc2="res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/5903.png"
dest_files=["res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.s3tc.ctex", "res://.godot/imported/5903.png-44743c2e322f48eefaeedcd3defec27d.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dtapwjgdbcgr7"
path.s3tc="res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.s3tc.ctex"
path.etc2="res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/5904.png"
dest_files=["res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.s3tc.ctex", "res://.godot/imported/5904.png-e43ba3245eadac7081a6a23e64ccc940.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c0164i40101lq"
path.s3tc="res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.s3tc.ctex"
path.etc2="res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6001.png"
dest_files=["res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.s3tc.ctex", "res://.godot/imported/6001.png-6fd8c3ef0885c466baece687cd24d38d.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://gnlehenln3kp"
path.s3tc="res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.s3tc.ctex"
path.etc2="res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6406.png"
dest_files=["res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.s3tc.ctex", "res://.godot/imported/6406.png-67ebebdd8d13cd88e0a209b3ed00024c.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://jauudro8rgay"
path.s3tc="res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.s3tc.ctex"
path.etc2="res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6601.png"
dest_files=["res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.s3tc.ctex", "res://.godot/imported/6601.png-7d5f9459e449f3aa7ae72a9e069e6a21.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cuik8y4tqqetk"
path.s3tc="res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.s3tc.ctex"
path.etc2="res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6801.png"
dest_files=["res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.s3tc.ctex", "res://.godot/imported/6801.png-f771f612f1db1d1b99cb446dfebc4992.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cu5s2rxrc0dh5"
path.s3tc="res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.s3tc.ctex"
path.etc2="res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6807.png"
dest_files=["res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.s3tc.ctex", "res://.godot/imported/6807.png-35d499aa8b3f10cc974cc7df8b70f89e.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://umjy47x208xb"
path.s3tc="res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.s3tc.ctex"
path.etc2="res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6901.png"
dest_files=["res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.s3tc.ctex", "res://.godot/imported/6901.png-f9ff2d1fc8e00eabd73822b5031f91d8.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://csdl6u3uvft45"
path.s3tc="res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.s3tc.ctex"
path.etc2="res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6902.png"
dest_files=["res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.s3tc.ctex", "res://.godot/imported/6902.png-f8899611759244b42c6c786ff118dcba.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -0,0 +1,42 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bqh7d4v8jrbpr"
path.s3tc="res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.s3tc.ctex"
path.etc2="res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.etc2.ctex"
metadata={
"imported_formats": ["s3tc_bptc", "etc2_astc"],
"vram_texture": true
}
[deps]
source_file="res://art/exported/creatures/6904.png"
dest_files=["res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.s3tc.ctex", "res://.godot/imported/6904.png-38739e4e48fbb83e3f65074a93d681bb.etc2.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View file

@ -142,8 +142,10 @@ environment and UI artwork, item artwork, finished fish and creature artwork,
and character customization pattern and channel-map artwork. Voyager's and character customization pattern and channel-map artwork. Voyager's
incorporated original work includes 3D assets, the provisional fish incorporated original work includes 3D assets, the provisional fish
placeholder artwork, the title and dusk music identified above, and the robot placeholder artwork, the title and dusk music identified above, and the robot
animalese tones. Specific contributor exceptions are recorded below and in the animalese tones. The current starter motorcoach exterior was supplied by a
in-game credits. contributing artist who released its copyright to the project; the original
and cleaned source files are retained in the owners' source-work archive.
Specific contributor exceptions are recorded below and in the in-game credits.
These credits intentionally describe work by creator and contribution family These credits intentionally describe work by creator and contribution family
instead of maintaining a second file-by-file asset catalog. Runtime resources instead of maintaining a second file-by-file asset catalog. Runtime resources

View file

@ -38,11 +38,15 @@ node names, connect persistent and networked state to that data.
network services validate and replicate bounded domains such as fishing, network services validate and replicate bounded domains such as fishing,
sales, shops, item use, profiles, jobs, mail, chat, drawings, time, and weather. sales, shops, item use, profiles, jobs, mail, chat, drawings, time, and weather.
`DiscoveryClient` is an optional directory layer beside `NetworkSession`. An `DiscoveryClient` is an optional directory layer beside `NetworkSession`. A
open host may publish a short-lived room lease, and the shared Join Game page player host cycles its discovery control through `private`, `direct`, and
may browse compatible leases before handing the selected address back to the `relay`. Private leaves the room unlisted, direct discovery discloses the host
existing direct ENet connection flow. The directory does not carry gameplay route only after an explicit Join, and relay discovery allocates a UDP privacy
traffic or become a gameplay authority. Its base URL comes from route without making discovery a gameplay authority. Relay joins fail closed
and never silently downgrade to direct. Dedicated servers are always direct:
the game forces direct advertisements and discovery independently rejects a
dedicated relay request. Public room browsing never receives a host address.
The base URL comes from
`network/discovery/base_url`, with `straywild_DISCOVERY_URL` available as a `network/discovery/base_url`, with `straywild_DISCOVERY_URL` available as a
development/deployment override. development/deployment override.
@ -53,8 +57,9 @@ friendship; unblocking does not recreate it. Discovery may publish opt-in,
short-lived friend presence and deliver invitations only while both games are short-lived friend presence and deliver invitations only while both games are
online. It stores hashed capability identifiers rather than identity online. It stores hashed capability identifiers rather than identity
fingerprints or a complete friend graph, keeps no durable social records, and fingerprints or a complete friend graph, keeps no durable social records, and
provides no offline delivery. Direct joins still use the existing verified provides no offline delivery. Public joins still use the existing verified
public-room and ENet connection path. server-identity and ENet connection path. Discovery endpoints remain transient:
they are not shown, added to recent servers, or persisted in the trust store.
The host is authoritative. Clients submit requests or evidence; the host The host is authoritative. Clients submit requests or evidence; the host
derives trusted context from registered peers, authoritative regions, and derives trusted context from registered peers, authoritative regions, and
@ -279,6 +284,35 @@ godot --headless --path . --script scripts/normalize_texture_imports.gd \
godot --headless --path . --script tests/texture_sampling_validation.gd godot --headless --path . --script tests/texture_sampling_validation.gd
``` ```
### Surface artwork runtime
Art Kit cells are authoritative palette and author planes, not per-cell nodes
or dictionaries. Keep `SurfaceDrawingCellBuffer` as the live representation;
materialize the legacy cell-array view only for inspection and tests. Finished
artwork uses a full-resolution nearest-neighbor texture on a coarse,
surface-conforming mesh. It discards guide and brush transforms, receives a
distance visibility range, and unloads its render node when far from the local
player while retaining compact session state.
`surface_drawing_v3` snapshots carry packed palette and participant-index
planes. The host sends late-join snapshots over multiple frames, prioritizing
active and nearby canvases, so adding finished work must not restore a
single-frame snapshot burst. Active editing and finalized artwork have
separate bounded budgets; finalizing a canvas frees active-grid capacity.
The finalized budget holds 512 maximum-size (128 x 128) canvases. At the
current 0.075-meter cell size, 441 canvases can tile the full 200 x 200-meter
generated-world footprint. If the generated world grows, update the finalized
canvas and cell budgets together and rerun the artwork performance validation.
Run both focused validations after changing this path:
```sh
godot --headless --path . --script tests/surface_drawing_validation.gd
godot --headless --path . \
--script tests/surface_drawing_performance_validation.gd
```
### Importing animalese voice sets ### Importing animalese voice sets
Animalese clips use one deterministic runtime level and format: -18 dBFS RMS Animalese clips use one deterministic runtime level and format: -18 dBFS RMS

View file

@ -67,15 +67,20 @@ are pinned to the same clean `main` commit:
bash scripts/build_portmaster.sh bash scripts/build_portmaster.sh
``` ```
The script deletes and regenerates the Linux ARM64 export, stages the package, The script deletes and regenerates a PortMaster-specific ARM64 export in
validates its structure, and writes: `builds/v<project-version>/portmaster-arm64/`, stages the package, validates its
structure, and writes:
```text ```text
builds/v<project-version>/straywild.zip builds/v<project-version>/straywild.zip
``` ```
`--package-only` is reserved for repackaging an already validated ARM64 export. `--package-only` is reserved for repackaging an already validated
It does not rebuild game content. PortMaster-specific ARM64 export. It does not rebuild game content, and verifies
the tagged-export marker written by a prior full build so a generic Linux ARM64
binary cannot accidentally be repackaged as PortMaster. The standard Linux
ARM64 release staging directory remains separate so building PortMaster cannot
replace the normal Linux ARM64 payload with reduced assets.
For a packaging-only correction to an already published game release, commit For a packaging-only correction to an already published game release, commit
only PortMaster launcher, packaging-script, or PortMaster documentation changes only PortMaster launcher, packaging-script, or PortMaster documentation changes
@ -214,7 +219,11 @@ The light profile passes these Godot options:
- `--audio-output-latency 40` - `--audio-output-latency 40`
It also passes `straywild_PERFORMANCE_PROFILE=light` and It also passes `straywild_PERFORMANCE_PROFILE=light` and
`straywild_LOW_END=1`. The game renders the 3D world at 37.5% linear `straywild_LOW_END=1`. Those selectors are honored only by the dedicated
PortMaster export, which carries the immutable `straywild_portmaster` Godot
feature tag; desktop, Android, macOS, generic ARM64, and dedicated-server
builds always use the full presentation even if those environment variables
are present. The game renders the 3D world at 37.5% linear
resolution with nearest-neighbor scaling, reducing 3D pixel work by about 86% resolution with nearest-neighbor scaling, reducing 3D pixel work by about 86%
while the separately rendered UI retains its canonical resolution. The light while the separately rendered UI retains its canonical resolution. The light
profile also: profile also:
@ -260,10 +269,11 @@ On a typical muOS installation, the full path is:
/mnt/mmc/ports/straywild/conf/performance_profile /mnt/mmc/ports/straywild/conf/performance_profile
``` ```
Set the file to `light`, or remove it, to restore the default. An externally Set the file to `light`, or remove it, to restore the default. Within the
provided `straywild_PERFORMANCE_PROFILE=normal` or `light` environment value PortMaster package, an externally provided
takes precedence over the persistent file. Invalid values safely fall back to `straywild_PERFORMANCE_PROFILE=normal` or `light` environment value takes
the light profile and are reported in `straywild/log.txt`. precedence over the persistent file. Invalid values safely fall back to the
light profile and are reported in `straywild/log.txt`.
Do not use Godot's low processor mode for the light profile. It reduces idle 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 CPU usage by sleeping between updates and is not a game-performance

View file

@ -1,13 +1,18 @@
straywild straywild
v0.19.0-alpha v0.20.4-alpha
Alpha 0.19.0 Alpha 0.20.4
Thank you for trying this early private playtest. Thank you for supporting this private testing release.
straywild is an early multiplayer fishing game. This alpha includes local straywild is an early multiplayer fishing game. This alpha includes local
progression, player-hosted and dedicated multiplayer sessions, public room progression, player-hosted and dedicated multiplayer sessions, public room
discovery, and direct UDP connections. Expect rough edges and please keep a discovery, player-selected direct or privacy-relayed hosting, and optional
backup of any save or identity data you care about. direct UDP connections. Player hosts can cycle discovery through private,
direct, and relay. Dedicated servers are always direct and cannot use the
shared relay. The relay hides peers' addresses from one another but not from
the relay operator. Direct connections expose public IP addresses to the
remote peer and are clearly labeled in game. Expect rough edges and please
keep a backup of any save or identity data you care about.
INSTALLATION INSTALLATION
@ -83,6 +88,8 @@ FEATURES TO TRY
- Fishing, net gathering, and beach digging - Fishing, net gathering, and beach digging
- Equipment and consumable use from the hotbar - Equipment and consumable use from the hotbar
- Fishing Shop sell tray, wallet updates, and persistent upgrades - Fishing Shop sell tray, wallet updates, and persistent upgrades
- Portable RV homes, multiplayer visits, RV storage, and the early decor
purchasing and placement prototype
- Seeded generated worlds and the authored starter-island layout - Seeded generated worlds and the authored starter-island layout
- Generated beaches, ponds, lakes, complete rivers, denser forests, varied - Generated beaches, ponds, lakes, complete rivers, denser forests, varied
elevation, and closed coastal cliffs elevation, and closed coastal cliffs
@ -113,9 +120,11 @@ Please include:
KNOWN ALPHA LIMITATIONS KNOWN ALPHA LIMITATIONS
- Public sessions remain direct connections; restrictive NAT or carrier-grade - Direct public sessions may fail behind restrictive NAT or carrier-grade
networks may prevent some players from joining. networks. Player-hosted relay sessions add an extra network hop and may have
more latency. Dedicated servers are direct-only.
- Multiplayer, dedicated-server deployment, UI, and balance are still alpha. - Multiplayer, dedicated-server deployment, UI, and balance are still alpha.
- Some fish and character artwork remains temporary. - Some fish and character artwork remains temporary.
- RV exteriors and decor use temporary prototype geometry and presentation.
- There is no installer or automatic updater. - There is no installer or automatic updater.
- Windows builds are unsigned. - Windows builds are unsigned.

View file

@ -17,10 +17,10 @@ for local README files.
## Related repositories ## Related repositories
- [Discovery service API](https://forge.makearmy.io/woofmeow/netfishing-discovery-server/src/branch/main/docs/API.md) - [Discovery service API](https://forge.makearmy.io/woofmeow/straywild-discovery-server/src/branch/main/docs/API.md)
stays with the service implementation and defines its versioned HTTP stays with the service implementation and defines its versioned HTTP
contract. contract.
- [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/netfishing-dedicated-server/src/branch/main/README.md) - [Dedicated-server packaging](https://forge.makearmy.io/woofmeow/straywild-dedicated-server/src/branch/main/README.md)
stays with its installer and update scripts. It consumes this repository's stays with its installer and update scripts. It consumes this repository's
attribution and legal notices from the exact pinned game commit instead of attribution and legal notices from the exact pinned game commit instead of
maintaining copies. maintaining copies.

View file

@ -5,6 +5,9 @@ const GUIDED_PIXEL_FILL: float = 0.88
const SURFACE_OFFSET: float = 0.012 const SURFACE_OFFSET: float = 0.012
const LAYER_OFFSET_STEP: float = 0.0002 const LAYER_OFFSET_STEP: float = 0.0002
const SURFACE_SAMPLE_DEPTH: float = 0.45 const SURFACE_SAMPLE_DEPTH: float = 0.45
const FINALIZED_VISIBILITY_RANGE: float = 96.0
const FINALIZED_VISIBILITY_MARGIN: float = 12.0
const FINALIZED_MAX_SUBDIVISIONS: int = 32
var canvas_id: String = "" var canvas_id: String = ""
var grid_width: int = 0 var grid_width: int = 0
@ -18,7 +21,7 @@ var _surface_origin: Vector3
var _surface_normal: Vector3 var _surface_normal: Vector3
var _surface_tangent: Vector3 var _surface_tangent: Vector3
var _surface_bitangent: Vector3 var _surface_bitangent: Vector3
var _cells: Dictionary[int, Dictionary] = {} var _cell_buffer: SurfaceDrawingCellBuffer
var _cell_surface_transforms: Array[Transform3D] = [] var _cell_surface_transforms: Array[Transform3D] = []
var _pixel_image: Image var _pixel_image: Image
var _pixel_texture: ImageTexture var _pixel_texture: ImageTexture
@ -36,8 +39,15 @@ func setup(
data: Dictionary, data: Dictionary,
relationships: PlayerRelationshipStore, relationships: PlayerRelationshipStore,
solid_surface_mask: int = 1, solid_surface_mask: int = 1,
cell_buffer: SurfaceDrawingCellBuffer = null,
) -> bool: ) -> bool:
if not SurfaceDrawingProtocol.validate_canvas_state(data): if (
(cell_buffer == null and not SurfaceDrawingProtocol.validate_canvas_state(data))
or (
cell_buffer != null
and not SurfaceDrawingProtocol.validate_canvas_metadata(data)
)
):
return false return false
canvas_id = str(data["canvas_id"]) canvas_id = str(data["canvas_id"])
grid_width = int(data["width"]) grid_width = int(data["width"])
@ -45,7 +55,6 @@ func setup(
cell_size = float(data["cell_size"]) cell_size = float(data["cell_size"])
revision = int(data["revision"]) revision = int(data["revision"])
creator_fingerprint = str(data["creator_fingerprint"]) creator_fingerprint = str(data["creator_fingerprint"])
participant_fingerprints = _participants_from_state(data)
_finalized = bool(data.get("finalized", false)) _finalized = bool(data.get("finalized", false))
_guide_visible = bool(data.get("guide_visible", true)) and not _finalized _guide_visible = bool(data.get("guide_visible", true)) and not _finalized
_layer = int(data.get("layer", 0)) _layer = int(data.get("layer", 0))
@ -64,12 +73,20 @@ func setup(
_surface_bitangent = _surface_normal.cross(_surface_tangent).normalized() _surface_bitangent = _surface_normal.cross(_surface_tangent).normalized()
_relationships = relationships _relationships = relationships
_solid_surface_mask = solid_surface_mask _solid_surface_mask = solid_surface_mask
_cells.clear() _cell_buffer = cell_buffer
for cell_value: Variant in data["cells"]: if _cell_buffer == null:
var cell: Dictionary = cell_value _cell_buffer = SurfaceDrawingCellBuffer.new()
_cells[_cell_key(int(cell["x"]), int(cell["y"]))] = cell.duplicate(true) if not _cell_buffer.configure_from_state(data, true):
_build_grid() return false
_build_pixel_renderer() elif _cell_buffer.width != grid_width or _cell_buffer.height != grid_height:
return false
participant_fingerprints = _cell_buffer.get_participants()
if _finalized:
_build_finalized_pixel_renderer()
else:
var samples: Dictionary = _sample_grid_vertices()
_build_grid(samples)
_build_pixel_renderer(samples)
return true return true
@ -80,16 +97,25 @@ func apply_update(data: Dictionary) -> bool:
or int(data["revision"]) <= revision or int(data["revision"]) <= revision
): ):
return false return false
for edit_value: Variant in data["edits"]: if _cell_buffer == null or not _cell_buffer.apply_edits(data["edits"]):
var edit: Dictionary = edit_value return false
var key: int = _cell_key(int(edit["x"]), int(edit["y"])) return _finish_buffered_update(data)
if str(edit["color_id"]).is_empty():
_cells.erase(key)
else: func apply_buffered_update(data: Dictionary) -> bool:
_cells[key] = edit.duplicate(true) if (
participant_fingerprints = _participants_from_update( not SurfaceDrawingProtocol.validate_canvas_update(data)
data, participant_fingerprints or str(data["canvas_id"]) != canvas_id
) or int(data["revision"]) <= revision
or _cell_buffer == null
):
return false
return _finish_buffered_update(data)
func _finish_buffered_update(data: Dictionary) -> bool:
_cell_buffer.ensure_participants(data.get("participant_fingerprints", []))
participant_fingerprints = _cell_buffer.get_participants()
revision = int(data["revision"]) revision = int(data["revision"])
if is_hidden_by_relationship(): if is_hidden_by_relationship():
_rebuild_pixel_image() _rebuild_pixel_image()
@ -110,12 +136,18 @@ func apply_guide_update(data: Dictionary) -> bool:
participant_fingerprints = _participants_from_update( participant_fingerprints = _participants_from_update(
data, participant_fingerprints data, participant_fingerprints
) )
if _cell_buffer != null:
_cell_buffer.ensure_participants(participant_fingerprints)
participant_fingerprints = _cell_buffer.get_participants()
_finalized = bool(data["finalized"]) _finalized = bool(data["finalized"])
_guide_visible = bool(data["guide_visible"]) and not _finalized _guide_visible = bool(data["guide_visible"]) and not _finalized
if _finalized: if _finalized:
_destroy_grid() _destroy_grid()
_cell_surface_transforms.clear()
_build_finalized_pixel_renderer()
else: else:
_refresh_grid_visibility() _refresh_grid_visibility()
_configure_render_distance()
return true return true
@ -155,11 +187,7 @@ func get_rendered_pixel_size() -> float:
func get_rendered_pixel_count() -> int: func get_rendered_pixel_count() -> int:
if is_hidden_by_relationship(): if is_hidden_by_relationship():
return 0 return 0
var count: int = 0 return _cell_buffer.get_painted_count() if _cell_buffer != null else 0
for cell: Dictionary in _cells.values():
if not _visible_cell_color(cell).is_equal_approx(Color.TRANSPARENT):
count += 1
return count
func get_export_image() -> Image: func get_export_image() -> Image:
@ -255,18 +283,10 @@ func get_cell_surface_transform(
func get_authoritative_cells() -> Array[Dictionary]: func get_authoritative_cells() -> Array[Dictionary]:
var result: Array[Dictionary] = [] return _cell_buffer.to_cells() if _cell_buffer != null else []
for value: Dictionary in _cells.values():
result.append(value.duplicate(true))
result.sort_custom(func(a: Dictionary, b: Dictionary) -> bool:
var a_key: int = _cell_key(int(a["x"]), int(a["y"]))
var b_key: int = _cell_key(int(b["x"]), int(b["y"]))
return a_key < b_key
)
return result
func _build_grid() -> void: func _build_grid(samples: Dictionary) -> void:
if _grid_instance != null: if _grid_instance != null:
_grid_instance.queue_free() _grid_instance.queue_free()
_grid_instance = null _grid_instance = null
@ -279,50 +299,14 @@ func _build_grid() -> void:
material.albedo_color = Color(0.75, 0.94, 0.96, 0.38) material.albedo_color = Color(0.75, 0.94, 0.96, 0.38)
material.no_depth_test = false material.no_depth_test = false
immediate.surface_begin(Mesh.PRIMITIVE_LINES, material) immediate.surface_begin(Mesh.PRIMITIVE_LINES, material)
var half_width: float = float(grid_width) * cell_size * 0.5
var half_height: float = float(grid_height) * cell_size * 0.5
var sampled_vertices: Dictionary[Vector2i, Dictionary] = {}
for x: int in range(grid_width + 1): for x: int in range(grid_width + 1):
var horizontal: float = -half_width + float(x) * cell_size
for y_segment: int in range(grid_height): for y_segment: int in range(grid_height):
var start_vertical: float = ( _add_grid_vertex(immediate, x, y_segment, samples)
-half_height + float(y_segment) * cell_size _add_grid_vertex(immediate, x, y_segment + 1, samples)
)
_add_grid_vertex(
immediate,
_surface_origin + _surface_tangent * horizontal
+ _surface_bitangent * start_vertical,
Vector2i(x, y_segment),
sampled_vertices,
)
_add_grid_vertex(
immediate,
_surface_origin + _surface_tangent * horizontal
+ _surface_bitangent * (start_vertical + cell_size),
Vector2i(x, y_segment + 1),
sampled_vertices,
)
for y: int in range(grid_height + 1): for y: int in range(grid_height + 1):
var vertical: float = -half_height + float(y) * cell_size
for x_segment: int in range(grid_width): for x_segment: int in range(grid_width):
var start_horizontal: float = ( _add_grid_vertex(immediate, x_segment, y, samples)
-half_width + float(x_segment) * cell_size _add_grid_vertex(immediate, x_segment + 1, y, samples)
)
_add_grid_vertex(
immediate,
_surface_origin + _surface_tangent * start_horizontal
+ _surface_bitangent * vertical,
Vector2i(x_segment, y),
sampled_vertices,
)
_add_grid_vertex(
immediate,
_surface_origin
+ _surface_tangent * (start_horizontal + cell_size)
+ _surface_bitangent * vertical,
Vector2i(x_segment + 1, y),
sampled_vertices,
)
immediate.surface_end() immediate.surface_end()
_grid_instance = MeshInstance3D.new() _grid_instance = MeshInstance3D.new()
_grid_instance.name = "PixelGrid" _grid_instance.name = "PixelGrid"
@ -334,63 +318,56 @@ func _build_grid() -> void:
func _add_grid_vertex( func _add_grid_vertex(
immediate: ImmediateMesh, immediate: ImmediateMesh,
world_point: Vector3, x: int,
grid_point: Vector2i, y: int,
sampled_vertices: Dictionary[Vector2i, Dictionary], samples: Dictionary,
) -> void: ) -> void:
var sampled: Dictionary = sampled_vertices.get(grid_point, {})
if sampled.is_empty():
sampled = _sample_surface(world_point)
sampled_vertices[grid_point] = sampled
var point: Vector3 = sampled["position"]
var normal: Vector3 = sampled["normal"]
immediate.surface_add_vertex( immediate.surface_add_vertex(
to_local(point + normal * (_surface_offset() * 1.5)) _sample_position(samples, x, y, _surface_offset() * 1.5)
) )
func _build_pixel_renderer() -> void: func _build_pixel_renderer(samples: Dictionary) -> void:
if _pixel_instance != null: if _pixel_instance != null:
_pixel_instance.queue_free() _pixel_instance.queue_free()
_pixel_instance = null _pixel_instance = null
var vertex_width: int = grid_width + 1
var vertex_count: int = vertex_width * (grid_height + 1)
var cell_count: int = grid_width * grid_height var cell_count: int = grid_width * grid_height
var vertices := PackedVector3Array() var vertices := PackedVector3Array()
var texture_coordinates := PackedVector2Array() var texture_coordinates := PackedVector2Array()
var indices := PackedInt32Array() var indices := PackedInt32Array()
vertices.resize(cell_count * 4) vertices.resize(vertex_count)
texture_coordinates.resize(cell_count * 4) texture_coordinates.resize(vertex_count)
indices.resize(cell_count * 6) indices.resize(cell_count * 6)
_cell_surface_transforms.clear() _cell_surface_transforms.clear()
if not _finalized:
_cell_surface_transforms.resize(cell_count) _cell_surface_transforms.resize(cell_count)
for y: int in range(grid_height + 1):
for x: int in range(grid_width + 1):
var vertex_index: int = y * vertex_width + x
vertices[vertex_index] = _sample_position(
samples, x, y, _surface_offset()
)
texture_coordinates[vertex_index] = Vector2(
float(x) / float(grid_width),
1.0 - float(y) / float(grid_height),
)
for y: int in range(grid_height): for y: int in range(grid_height):
for x: int in range(grid_width): for x: int in range(grid_width):
var cell_index: int = _cell_key(x, y) var cell_index: int = _cell_key(x, y)
var surface_transform := _sample_cell_transform(x, y, 1.0) if not _finalized:
_cell_surface_transforms[cell_index] = surface_transform _cell_surface_transforms[cell_index] = _transform_from_samples(
var center: Vector3 = surface_transform.origin samples, x, y
var horizontal: Vector3 = surface_transform.basis.x * 0.5 )
var vertical: Vector3 = surface_transform.basis.y * 0.5 var vertex_offset: int = y * vertex_width + x
var vertex_offset: int = cell_index * 4
vertices[vertex_offset] = center - horizontal - vertical
vertices[vertex_offset + 1] = center + horizontal - vertical
vertices[vertex_offset + 2] = center + horizontal + vertical
vertices[vertex_offset + 3] = center - horizontal + vertical
var image_row: int = _image_y(y)
var u_min: float = float(x) / float(grid_width)
var u_max: float = float(x + 1) / float(grid_width)
var v_min: float = float(image_row) / float(grid_height)
var v_max: float = float(image_row + 1) / float(grid_height)
texture_coordinates[vertex_offset] = Vector2(u_min, v_max)
texture_coordinates[vertex_offset + 1] = Vector2(u_max, v_max)
texture_coordinates[vertex_offset + 2] = Vector2(u_max, v_min)
texture_coordinates[vertex_offset + 3] = Vector2(u_min, v_min)
var index_offset: int = cell_index * 6 var index_offset: int = cell_index * 6
indices[index_offset] = vertex_offset indices[index_offset] = vertex_offset
indices[index_offset + 1] = vertex_offset + 1 indices[index_offset + 1] = vertex_offset + 1
indices[index_offset + 2] = vertex_offset + 2 indices[index_offset + 2] = vertex_offset + vertex_width + 1
indices[index_offset + 3] = vertex_offset indices[index_offset + 3] = vertex_offset
indices[index_offset + 4] = vertex_offset + 2 indices[index_offset + 4] = vertex_offset + vertex_width + 1
indices[index_offset + 5] = vertex_offset + 3 indices[index_offset + 5] = vertex_offset + vertex_width
_rebuild_pixel_image() _rebuild_pixel_image()
var arrays: Array = [] var arrays: Array = []
arrays.resize(Mesh.ARRAY_MAX) arrays.resize(Mesh.ARRAY_MAX)
@ -414,21 +391,103 @@ func _build_pixel_renderer() -> void:
_pixel_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF _pixel_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
add_child(_pixel_instance) add_child(_pixel_instance)
_pixel_instance.visible = not is_hidden_by_relationship() _pixel_instance.visible = not is_hidden_by_relationship()
_configure_render_distance()
func _build_finalized_pixel_renderer() -> void:
if _pixel_instance != null:
_pixel_instance.queue_free()
_pixel_instance = null
var step: int = maxi(
1,
ceili(float(grid_width) / float(FINALIZED_MAX_SUBDIVISIONS)),
)
var coordinates := PackedInt32Array()
var coordinate: int = 0
while coordinate < grid_width:
coordinates.append(coordinate)
coordinate += step
if coordinates.is_empty() or coordinates[-1] != grid_width:
coordinates.append(grid_width)
var vertex_width: int = coordinates.size()
var vertices := PackedVector3Array()
var texture_coordinates := PackedVector2Array()
var indices := PackedInt32Array()
vertices.resize(vertex_width * vertex_width)
texture_coordinates.resize(vertex_width * vertex_width)
indices.resize((vertex_width - 1) * (vertex_width - 1) * 6)
var half_width: float = float(grid_width) * cell_size * 0.5
var half_height: float = float(grid_height) * cell_size * 0.5
for lattice_y: int in vertex_width:
var cell_y: int = coordinates[lattice_y]
var vertical: float = -half_height + float(cell_y) * cell_size
for lattice_x: int in vertex_width:
var cell_x: int = coordinates[lattice_x]
var horizontal: float = -half_width + float(cell_x) * cell_size
var expected: Vector3 = (
_surface_origin
+ _surface_tangent * horizontal
+ _surface_bitangent * vertical
)
var sampled: Dictionary = _sample_surface(expected)
var vertex_index: int = lattice_y * vertex_width + lattice_x
var point: Vector3 = sampled["position"]
var normal: Vector3 = sampled["normal"]
vertices[vertex_index] = to_local(
point + normal * _surface_offset()
)
texture_coordinates[vertex_index] = Vector2(
float(cell_x) / float(grid_width),
1.0 - float(cell_y) / float(grid_height),
)
for lattice_y: int in range(vertex_width - 1):
for lattice_x: int in range(vertex_width - 1):
var cell_index: int = lattice_y * (vertex_width - 1) + lattice_x
var vertex_offset: int = lattice_y * vertex_width + lattice_x
var index_offset: int = cell_index * 6
indices[index_offset] = vertex_offset
indices[index_offset + 1] = vertex_offset + 1
indices[index_offset + 2] = vertex_offset + vertex_width + 1
indices[index_offset + 3] = vertex_offset
indices[index_offset + 4] = vertex_offset + vertex_width + 1
indices[index_offset + 5] = vertex_offset + vertex_width
if _pixel_image == null or _pixel_texture == null:
_rebuild_pixel_image()
var arrays: Array = []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = vertices
arrays[Mesh.ARRAY_TEX_UV] = texture_coordinates
arrays[Mesh.ARRAY_INDEX] = indices
var mesh := ArrayMesh.new()
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
var material := StandardMaterial3D.new()
material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
material.albedo_color = Color.WHITE
material.albedo_texture = _pixel_texture
material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
material.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
material.alpha_scissor_threshold = 0.5
material.cull_mode = BaseMaterial3D.CULL_DISABLED
mesh.surface_set_material(0, material)
_pixel_instance = MeshInstance3D.new()
_pixel_instance.name = "ArtworkTexture"
_pixel_instance.mesh = mesh
_pixel_instance.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
add_child(_pixel_instance)
_pixel_instance.visible = not is_hidden_by_relationship()
_configure_render_distance()
func _rebuild_pixel_image() -> void: func _rebuild_pixel_image() -> void:
if ( if _cell_buffer == null:
_pixel_image == null return
or _pixel_image.get_width() != grid_width _pixel_image = Image.create_from_data(
or _pixel_image.get_height() != grid_height grid_width,
): grid_height,
_pixel_image = Image.create( false,
grid_width, grid_height, false, Image.FORMAT_RGBA8 Image.FORMAT_RGBA8,
_cell_buffer.to_rgba8_data(is_hidden_by_relationship()),
) )
_pixel_image.fill(Color.TRANSPARENT)
if not is_hidden_by_relationship():
for cell: Dictionary in _cells.values():
_set_image_cell(int(cell["x"]), int(cell["y"]), cell)
if _pixel_texture == null: if _pixel_texture == null:
_pixel_texture = ImageTexture.create_from_image(_pixel_image) _pixel_texture = ImageTexture.create_from_image(_pixel_image)
else: else:
@ -443,7 +502,7 @@ func _apply_pixel_edits(edits: Array) -> void:
var edit: Dictionary = edit_value var edit: Dictionary = edit_value
var x: int = int(edit["x"]) var x: int = int(edit["x"])
var y: int = int(edit["y"]) var y: int = int(edit["y"])
var cell: Dictionary = _cells.get(_cell_key(x, y), {}) var cell: Dictionary = _cell_buffer.get_cell(x, y)
_set_image_cell(x, y, cell) _set_image_cell(x, y, cell)
_pixel_texture.update(_pixel_image) _pixel_texture.update(_pixel_image)
@ -475,6 +534,78 @@ func _image_y(cell_y: int) -> int:
return grid_height - 1 - cell_y return grid_height - 1 - cell_y
func _sample_grid_vertices() -> Dictionary:
var vertex_width: int = grid_width + 1
var vertex_count: int = vertex_width * (grid_height + 1)
var positions := PackedVector3Array()
var normals := PackedVector3Array()
positions.resize(vertex_count)
normals.resize(vertex_count)
var half_width: float = float(grid_width) * cell_size * 0.5
var half_height: float = float(grid_height) * cell_size * 0.5
var inverse_basis: Basis = global_basis.inverse()
for y: int in range(grid_height + 1):
var vertical: float = -half_height + float(y) * cell_size
for x: int in range(grid_width + 1):
var horizontal: float = -half_width + float(x) * cell_size
var expected: Vector3 = (
_surface_origin
+ _surface_tangent * horizontal
+ _surface_bitangent * vertical
)
var sampled: Dictionary = _sample_surface(expected)
var index: int = y * vertex_width + x
positions[index] = to_local(sampled["position"])
normals[index] = (
inverse_basis * (sampled["normal"] as Vector3)
).normalized()
return {"positions": positions, "normals": normals}
func _sample_position(
samples: Dictionary,
x: int,
y: int,
offset: float,
) -> Vector3:
var index: int = y * (grid_width + 1) + x
var positions: PackedVector3Array = samples["positions"]
var normals: PackedVector3Array = samples["normals"]
return positions[index] + normals[index] * offset
func _transform_from_samples(
samples: Dictionary,
x: int,
y: int,
) -> Transform3D:
var bottom_left: Vector3 = _sample_position(
samples, x, y, _surface_offset()
)
var bottom_right: Vector3 = _sample_position(
samples, x + 1, y, _surface_offset()
)
var top_left: Vector3 = _sample_position(
samples, x, y + 1, _surface_offset()
)
var top_right: Vector3 = _sample_position(
samples, x + 1, y + 1, _surface_offset()
)
var horizontal: Vector3 = (
(bottom_right - bottom_left) + (top_right - top_left)
) * 0.5
var vertical: Vector3 = (
(top_left - bottom_left) + (top_right - bottom_right)
) * 0.5
var normal: Vector3 = horizontal.cross(vertical).normalized()
if horizontal.is_zero_approx() or vertical.is_zero_approx() or normal.is_zero_approx():
return _sample_cell_transform(x, y, 1.0)
var center: Vector3 = (
bottom_left + bottom_right + top_left + top_right
) * 0.25
return Transform3D(Basis(horizontal, vertical, normal), center)
func _sample_cell_transform( func _sample_cell_transform(
x: int, x: int,
y: int, y: int,
@ -549,6 +680,17 @@ func _refresh_grid_visibility() -> void:
) )
func _configure_render_distance() -> void:
if _pixel_instance == null:
return
_pixel_instance.visibility_range_end = (
FINALIZED_VISIBILITY_RANGE if _finalized else 0.0
)
_pixel_instance.visibility_range_end_margin = (
FINALIZED_VISIBILITY_MARGIN if _finalized else 0.0
)
func _participants_from_state(data: Dictionary) -> Array[String]: func _participants_from_state(data: Dictionary) -> Array[String]:
var result: Array[String] = [] var result: Array[String] = []
_append_participant(result, str(data.get("creator_fingerprint", ""))) _append_participant(result, str(data.get("creator_fingerprint", "")))

View file

@ -0,0 +1,284 @@
# SPDX-License-Identifier: GPL-3.0-or-later
class_name SurfaceDrawingCellBuffer
extends RefCounted
const ENCODING := "palette_author_planes_v1"
var width: int = 0
var height: int = 0
var _color_indices := PackedByteArray()
var _author_indices := PackedInt32Array()
var _participants: Array[String] = []
var _participant_slots: Dictionary[String, int] = {}
var _author_masks: Dictionary[int, PackedByteArray] = {}
var _painted_count: int = 0
func configure_from_state(
data: Dictionary,
already_validated: bool = false,
) -> bool:
if not already_validated and not SurfaceDrawingProtocol.validate_canvas_state(data):
return false
width = int(data["width"])
height = int(data["height"])
_reset_planes()
for value: Variant in data.get("participant_fingerprints", []):
_add_participant(str(value))
_add_participant(str(data.get("creator_fingerprint", "")))
if str(data.get("cell_encoding", "")) == ENCODING:
return _configure_packed(
data.get("cell_colors", PackedByteArray()),
data.get("cell_authors", PackedInt32Array()),
)
for value: Variant in data.get("cells", []):
if typeof(value) != TYPE_DICTIONARY or not _apply_edit(value):
return false
return true
func apply_edits(edits: Array) -> bool:
for value: Variant in edits:
if typeof(value) != TYPE_DICTIONARY or not _apply_edit(value):
return false
return true
func ensure_participants(values: Array) -> bool:
for value: Variant in values:
if _add_participant(str(value)) <= 0:
return false
return true
func get_cell(x: int, y: int) -> Dictionary:
var key: int = _key(x, y)
if key < 0 or _color_indices[key] == 0:
return {}
var author_slot: int = _author_indices[key]
return {
"x": x,
"y": y,
"color_id": str(
SurfaceDrawingPalette.id_for_palette_index(_color_indices[key])
),
"author_fingerprint": (
_participants[author_slot - 1]
if author_slot > 0 and author_slot <= _participants.size()
else ""
),
}
func get_cell_value(x: int, y: int) -> Dictionary:
var cell: Dictionary = get_cell(x, y)
if cell.is_empty():
return {}
return {
"color_id": cell["color_id"],
"author_fingerprint": cell["author_fingerprint"],
}
func get_painted_count() -> int:
return _painted_count
func get_participants() -> Array[String]:
return _participants.duplicate()
func get_author_cell_keys(fingerprint: String) -> PackedInt32Array:
var slot: int = int(_participant_slots.get(fingerprint, 0))
var mask: PackedByteArray = _author_masks.get(slot, PackedByteArray())
var result := PackedInt32Array()
if slot <= 0 or mask.is_empty():
return result
for key: int in width * height:
if _mask_has(mask, key):
result.append(key)
return result
func to_cells() -> Array[Dictionary]:
var result: Array[Dictionary] = []
for key: int in width * height:
if _color_indices[key] == 0:
continue
result.append(get_cell(key % width, floori(float(key) / float(width))))
return result
func to_compact_fields() -> Dictionary:
return {
"cell_encoding": ENCODING,
"cell_colors": _color_indices.duplicate(),
"cell_authors": _author_indices.duplicate(),
"participant_fingerprints": get_participants(),
}
func to_rgba8_data(hidden: bool = false) -> PackedByteArray:
var result := PackedByteArray()
result.resize(width * height * 4)
# Match Image.fill(Color.TRANSPARENT): transparent texels retain white RGB,
# preventing dark fringes if filtering is ever changed by a platform driver.
for pixel_index: int in width * height:
var offset: int = pixel_index * 4
result[offset] = 255
result[offset + 1] = 255
result[offset + 2] = 255
if hidden:
return result
for key: int in width * height:
var palette_index: int = _color_indices[key]
if palette_index == 0:
continue
var x: int = key % width
var y: int = floori(float(key) / float(width))
var image_key: int = (height - 1 - y) * width + x
var offset: int = image_key * 4
var color: Color = SurfaceDrawingPalette.color_for_palette_index(
palette_index
)
result[offset] = roundi(color.r * 255.0)
result[offset + 1] = roundi(color.g * 255.0)
result[offset + 2] = roundi(color.b * 255.0)
result[offset + 3] = roundi(color.a * 255.0)
return result
func estimated_storage_bytes() -> int:
var masks_size: int = 0
for mask: PackedByteArray in _author_masks.values():
masks_size += mask.size()
return _color_indices.size() + _author_indices.size() * 4 + masks_size
func _configure_packed(colors: Variant, authors: Variant) -> bool:
if (
typeof(colors) != TYPE_PACKED_BYTE_ARRAY
or typeof(authors) != TYPE_PACKED_INT32_ARRAY
):
return false
var packed_colors: PackedByteArray = colors
var packed_authors: PackedInt32Array = authors
if (
packed_colors.size() != width * height
or packed_authors.size() != width * height
):
return false
_color_indices = packed_colors.duplicate()
_author_indices = packed_authors.duplicate()
_painted_count = 0
for key: int in width * height:
var color_index: int = _color_indices[key]
var author_slot: int = _author_indices[key]
if color_index == 0:
if author_slot != 0:
return false
continue
if (
color_index > SurfaceDrawingPalette.COLORS.size()
or author_slot <= 0
or author_slot > _participants.size()
):
return false
_painted_count += 1
_set_author_mask_bit(author_slot, key, true)
return true
func _apply_edit(value: Dictionary) -> bool:
var x: int = int(value.get("x", -1))
var y: int = int(value.get("y", -1))
var key: int = _key(x, y)
if key < 0:
return false
var color_id := StringName(str(value.get("color_id", "")))
var next_color_index: int = (
SurfaceDrawingPalette.palette_index_for_id(color_id)
if not color_id.is_empty()
else 0
)
if not color_id.is_empty() and next_color_index <= 0:
return false
var next_author_slot: int = 0
if next_color_index > 0:
next_author_slot = _add_participant(
str(value.get("author_fingerprint", ""))
)
if next_author_slot <= 0:
return false
var previous_color_index: int = _color_indices[key]
var previous_author_slot: int = _author_indices[key]
if previous_color_index == next_color_index and previous_author_slot == next_author_slot:
return true
if previous_author_slot > 0:
_set_author_mask_bit(previous_author_slot, key, false)
if previous_color_index == 0 and next_color_index > 0:
_painted_count += 1
elif previous_color_index > 0 and next_color_index == 0:
_painted_count -= 1
_color_indices[key] = next_color_index
_author_indices[key] = next_author_slot
if next_author_slot > 0:
_set_author_mask_bit(next_author_slot, key, true)
return true
func _reset_planes() -> void:
_color_indices.resize(width * height)
_color_indices.fill(0)
_author_indices.resize(width * height)
_author_indices.fill(0)
_participants.clear()
_participant_slots.clear()
_author_masks.clear()
_painted_count = 0
func _add_participant(fingerprint: String) -> int:
var existing: int = int(_participant_slots.get(fingerprint, 0))
if existing > 0:
return existing
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
return 0
if _participants.size() >= SurfaceDrawingProtocol.MAX_PARTICIPANTS:
return 0
_participants.append(fingerprint)
var slot: int = _participants.size()
_participant_slots[fingerprint] = slot
return slot
func _set_author_mask_bit(slot: int, key: int, enabled: bool) -> void:
if slot <= 0 or key < 0:
return
var mask: PackedByteArray = _author_masks.get(slot, PackedByteArray())
var required_size: int = ceili(float(width * height) / 8.0)
if mask.size() != required_size:
mask.resize(required_size)
var byte_index: int = floori(float(key) / 8.0)
var bit: int = 1 << (key % 8)
if enabled:
mask[byte_index] = mask[byte_index] | bit
else:
mask[byte_index] = mask[byte_index] & (~bit & 0xff)
_author_masks[slot] = mask
func _mask_has(mask: PackedByteArray, key: int) -> bool:
var byte_index: int = floori(float(key) / 8.0)
return (
byte_index >= 0
and byte_index < mask.size()
and (mask[byte_index] & (1 << (key % 8))) != 0
)
func _key(x: int, y: int) -> int:
if x < 0 or x >= width or y < 0 or y >= height:
return -1
return y * width + x

View file

@ -0,0 +1 @@
uid://b4clw478edbnk

View file

@ -45,6 +45,31 @@ static func get_color_ids() -> Array[StringName]:
return result return result
static func palette_index_for_id(color_id: StringName) -> int:
for index: int in COLORS.size():
if StringName(COLORS[index].get("id", &"")) == color_id:
return index + 1
return 0
static func id_for_palette_index(palette_index: int) -> StringName:
var index: int = palette_index - 1
return (
StringName(COLORS[index].get("id", &""))
if index >= 0 and index < COLORS.size()
else StringName()
)
static func color_for_palette_index(palette_index: int) -> Color:
var index: int = palette_index - 1
return (
COLORS[index].get("color", Color.TRANSPARENT)
if index >= 0 and index < COLORS.size()
else Color.TRANSPARENT
)
static func filter_unlocked_ids( static func filter_unlocked_ids(
unlocked_ids: Array[StringName], unlocked_ids: Array[StringName],
) -> Array[StringName]: ) -> Array[StringName]:

View file

@ -25,7 +25,7 @@ static func resolve(
} }
var nearest_distance: float = SNAP_DISTANCE var nearest_distance: float = SNAP_DISTANCE
for state: Dictionary in canvas_states: for state: Dictionary in canvas_states:
if not SurfaceDrawingProtocol.validate_canvas_state(state): if not SurfaceDrawingProtocol.validate_canvas_metadata(state):
continue continue
var anchor_normal: Vector3 = SurfaceDrawingProtocol.array_to_vector( var anchor_normal: Vector3 = SurfaceDrawingProtocol.array_to_vector(
state["normal"] state["normal"]

View file

@ -1,7 +1,7 @@
class_name SurfaceDrawingProtocol class_name SurfaceDrawingProtocol
extends RefCounted extends RefCounted
const CAPABILITY: StringName = &"surface_drawing_v2" const CAPABILITY: StringName = &"surface_drawing_v3"
const RELIABLE_CHANNEL: int = NetworkProtocol.DRAWING_RELIABLE_CHANNEL const RELIABLE_CHANNEL: int = NetworkProtocol.DRAWING_RELIABLE_CHANNEL
const GRID_SIZES: Array[int] = [16, 32, 64, 128] const GRID_SIZES: Array[int] = [16, 32, 64, 128]
const DEFAULT_GRID_SIZE: int = 16 const DEFAULT_GRID_SIZE: int = 16
@ -11,8 +11,12 @@ const GRID_WIDTH: int = DEFAULT_GRID_SIZE
const GRID_HEIGHT: int = DEFAULT_GRID_SIZE const GRID_HEIGHT: int = DEFAULT_GRID_SIZE
const CELL_SIZE: float = 0.075 const CELL_SIZE: float = 0.075
const MAX_ACTIVE_CANVASES: int = 24 const MAX_ACTIVE_CANVASES: int = 24
const MAX_CANVASES: int = 48 const MAX_FINALIZED_CANVASES: int = 512
const MAX_SESSION_GRID_CELLS: int = 49152 const MAX_CANVASES: int = MAX_ACTIVE_CANVASES + MAX_FINALIZED_CANVASES
const MAX_ACTIVE_GRID_CELLS: int = 49152
const MAX_FINALIZED_GRID_CELLS: int = 8 * 1024 * 1024
# Compatibility alias for code concerned only with live editing capacity.
const MAX_SESSION_GRID_CELLS: int = MAX_ACTIVE_GRID_CELLS
const MAX_EDITS_PER_REQUEST: int = 16 const MAX_EDITS_PER_REQUEST: int = 16
const MAX_CANVAS_ID_LENGTH: int = 64 const MAX_CANVAS_ID_LENGTH: int = 64
const MAX_REQUEST_ID_LENGTH: int = 64 const MAX_REQUEST_ID_LENGTH: int = 64
@ -22,6 +26,7 @@ const MAX_SESSION_ID_LENGTH: int = 96
# for a complete room turnover while never accepting unattributed edits. # for a complete room turnover while never accepting unattributed edits.
const MAX_PARTICIPANTS: int = 256 const MAX_PARTICIPANTS: int = 256
const MAX_COORDINATE: float = 10000.0 const MAX_COORDINATE: float = 10000.0
const CELL_ENCODING := "palette_author_planes_v1"
static func validate_canvas_request(data: Variant) -> bool: static func validate_canvas_request(data: Variant) -> bool:
@ -122,37 +127,12 @@ static func validate_canvas_state(data: Variant) -> bool:
if typeof(data) != TYPE_DICTIONARY: if typeof(data) != TYPE_DICTIONARY:
return false return false
var value: Dictionary = data var value: Dictionary = data
if ( if not validate_canvas_metadata(value):
typeof(value.get("session_id")) != TYPE_STRING
or str(value["session_id"]).is_empty()
or str(value["session_id"]).length() > MAX_SESSION_ID_LENGTH
or typeof(value.get("canvas_id")) != TYPE_STRING
or str(value["canvas_id"]).is_empty()
or str(value["canvas_id"]).length() > MAX_CANVAS_ID_LENGTH
or not _valid_vector(value.get("origin"))
or not _valid_vector(value.get("normal"))
or not _valid_vector(value.get("tangent"))
or typeof(value.get("width")) != TYPE_INT
or int(value["width"]) not in GRID_SIZES
or typeof(value.get("height")) != TYPE_INT
or int(value["height"]) != int(value["width"])
or typeof(value.get("cell_size")) not in [TYPE_FLOAT, TYPE_INT]
or not is_equal_approx(float(value["cell_size"]), CELL_SIZE)
or typeof(value.get("revision")) != TYPE_INT
or int(value["revision"]) < 0
or typeof(value.get("guide_visible", true)) != TYPE_BOOL
or typeof(value.get("finalized", false)) != TYPE_BOOL
or typeof(value.get("layer", 0)) != TYPE_INT
or int(value.get("layer", 0)) < 0
or not NetworkIdentityCrypto.valid_fingerprint(
value.get("creator_fingerprint")
)
or not _valid_participant_fingerprints(
value.get("participant_fingerprints", [])
)
or typeof(value.get("cells")) != TYPE_ARRAY
):
return false return false
var participants: Array = value.get("participant_fingerprints", [])
if not participants.is_empty() and str(value["creator_fingerprint"]) not in participants:
return false
if typeof(value.get("cells")) == TYPE_ARRAY:
var cells: Array = value["cells"] var cells: Array = value["cells"]
var grid_width: int = int(value["width"]) var grid_width: int = int(value["width"])
var grid_height: int = int(value["height"]) var grid_height: int = int(value["height"])
@ -165,14 +145,73 @@ static func validate_canvas_state(data: Variant) -> bool:
or int((cell_value as Dictionary)["y"]) >= grid_height or int((cell_value as Dictionary)["y"]) >= grid_height
): ):
return false return false
var participants: Array = value.get("participant_fingerprints", []) if (
if not participants.is_empty(): not participants.is_empty()
if str(value["creator_fingerprint"]) not in participants: and str((cell_value as Dictionary)["author_fingerprint"])
return false not in participants
for cell_value: Variant in cells: ):
if str((cell_value as Dictionary)["author_fingerprint"]) not in participants:
return false return false
return true return true
if (
str(value.get("cell_encoding", "")) != CELL_ENCODING
or typeof(value.get("cell_colors")) != TYPE_PACKED_BYTE_ARRAY
or typeof(value.get("cell_authors")) != TYPE_PACKED_INT32_ARRAY
or participants.is_empty()
):
return false
var colors: PackedByteArray = value["cell_colors"]
var authors: PackedInt32Array = value["cell_authors"]
var expected_size: int = int(value["width"]) * int(value["height"])
if colors.size() != expected_size or authors.size() != expected_size:
return false
for index: int in expected_size:
var color_index: int = colors[index]
var author_slot: int = authors[index]
if color_index == 0:
if author_slot != 0:
return false
elif (
color_index > SurfaceDrawingPalette.COLORS.size()
or author_slot <= 0
or author_slot > participants.size()
):
return false
return true
static func validate_canvas_metadata(data: Variant) -> bool:
if typeof(data) != TYPE_DICTIONARY:
return false
var value: Dictionary = data
return (
typeof(value.get("session_id")) == TYPE_STRING
and not str(value["session_id"]).is_empty()
and str(value["session_id"]).length() <= MAX_SESSION_ID_LENGTH
and typeof(value.get("canvas_id")) == TYPE_STRING
and not str(value["canvas_id"]).is_empty()
and str(value["canvas_id"]).length() <= MAX_CANVAS_ID_LENGTH
and _valid_vector(value.get("origin"))
and _valid_vector(value.get("normal"))
and _valid_vector(value.get("tangent"))
and typeof(value.get("width")) == TYPE_INT
and int(value["width"]) in GRID_SIZES
and typeof(value.get("height")) == TYPE_INT
and int(value["height"]) == int(value["width"])
and typeof(value.get("cell_size")) in [TYPE_FLOAT, TYPE_INT]
and is_equal_approx(float(value["cell_size"]), CELL_SIZE)
and typeof(value.get("revision")) == TYPE_INT
and int(value["revision"]) >= 0
and typeof(value.get("guide_visible", true)) == TYPE_BOOL
and typeof(value.get("finalized", false)) == TYPE_BOOL
and typeof(value.get("layer", 0)) == TYPE_INT
and int(value.get("layer", 0)) >= 0
and NetworkIdentityCrypto.valid_fingerprint(
value.get("creator_fingerprint")
)
and _valid_participant_fingerprints(
value.get("participant_fingerprints", [])
)
)
static func validate_guide_update(data: Variant) -> bool: static func validate_guide_update(data: Variant) -> bool:

View file

@ -18,4 +18,4 @@ static func get_unit_value(item: ItemDataType) -> int:
if not is_sellable(item): if not is_sellable(item):
return -1 return -1
var purchase_price := FishingShopStockType.get_price(item.item_id) var purchase_price := FishingShopStockType.get_price(item.item_id)
return purchase_price / 2 if purchase_price >= 0 else -1 return int(float(purchase_price) / 2.0) if purchase_price >= 0 else -1

View file

@ -9,7 +9,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.19.0-alpha/windows-x86_64/straywild.exe" export_path="builds/v0.20.4-alpha/windows-x86_64/straywild.exe"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -35,11 +35,11 @@ application/modify_resources=true
application/icon="res://art/exported/system_icons/straywild.ico" application/icon="res://art/exported/system_icons/straywild.ico"
application/console_wrapper_icon="" application/console_wrapper_icon=""
application/icon_interpolation=0 application/icon_interpolation=0
application/file_version="0.19.0.0" application/file_version="0.20.4.0"
application/product_version="0.19.0.0" application/product_version="0.20.4.0"
application/company_name="Woofmeow" application/company_name="Woofmeow"
application/product_name="straywild" application/product_name="straywild"
application/file_description="straywild v0.19.0-alpha" application/file_description="straywild v0.20.4-alpha"
application/copyright="Copyright © 2026 Woofmeow" application/copyright="Copyright © 2026 Woofmeow"
application/trademarks="straywild and Woofmeow branding is reserved; see TRADEMARKS.md" application/trademarks="straywild and Woofmeow branding is reserved; see TRADEMARKS.md"
application/export_angle=0 application/export_angle=0
@ -62,7 +62,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.19.0-alpha/linux-arm64/straywild.arm64" export_path="builds/v0.20.4-alpha/linux-arm64/straywild.arm64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -92,7 +92,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.19.0-alpha/macos/straywild.zip" export_path="builds/v0.20.4-alpha/macos/straywild.zip"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -108,8 +108,8 @@ custom_template/release=""
application/bundle_identifier="io.woofmeow.netfishing" application/bundle_identifier="io.woofmeow.netfishing"
application/icon="res://art/exported/system_icons/straywild_1024.png" application/icon="res://art/exported/system_icons/straywild_1024.png"
application/icon_interpolation=0 application/icon_interpolation=0
application/short_version="0.19.0" application/short_version="0.20.4"
application/version="0.19.0" application/version="0.20.4"
application/architecture="universal" application/architecture="universal"
codesign/enable=false codesign/enable=false
notarization/enable=false notarization/enable=false
@ -125,7 +125,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*,tests/*" exclude_filter="builds/*,scripts/*,tests/*"
export_path="builds/v0.19.0-alpha/server-linux-x86_64/straywildServer.x86_64" export_path="builds/v0.20.4-alpha/server-linux-x86_64/straywildServer.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -155,7 +155,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.19.0-alpha/android/straywild.apk" export_path="builds/v0.20.4-alpha/android/straywild.apk"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -176,8 +176,8 @@ architectures/armeabi-v7a=false
architectures/arm64-v8a=true architectures/arm64-v8a=true
architectures/x86=false architectures/x86=false
architectures/x86_64=false architectures/x86_64=false
version/code=190000 version/code=200400
version/name="v0.19.0-alpha" version/name="v0.20.4-alpha"
package/unique_name="io.woofmeow.netfishing" package/unique_name="io.woofmeow.netfishing"
package/name="straywild" package/name="straywild"
package/signed=true package/signed=true
@ -214,7 +214,7 @@ custom_features=""
export_filter="all_resources" export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt" include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*" exclude_filter="builds/*,scripts/*"
export_path="builds/v0.19.0-alpha/linux-x86_64/straywild.x86_64" export_path="builds/v0.20.4-alpha/linux-x86_64/straywild.x86_64"
patches=PackedStringArray() patches=PackedStringArray()
encryption_include_filters="" encryption_include_filters=""
encryption_exclude_filters="" encryption_exclude_filters=""
@ -239,3 +239,33 @@ ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp="" ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="#!/usr/bin/env bash\nexport DISPLAY=:0\nunzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"\nchmod +x \"{temp_dir}/{exe_name}\"\n\"{temp_dir}/{exe_name}\" {cmd_args}" ssh_remote_deploy/run_script="#!/usr/bin/env bash\nexport DISPLAY=:0\nunzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"\nchmod +x \"{temp_dir}/{exe_name}\"\n\"{temp_dir}/{exe_name}\" {cmd_args}"
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash\nkill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")\nrm -rf \"{temp_dir}\"" ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash\nkill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\")\nrm -rf \"{temp_dir}\""
[preset.6]
name="PortMaster ARM64"
platform="Linux"
runnable=false
advanced_options=false
dedicated_server=false
custom_features="straywild_portmaster"
export_filter="all_resources"
include_filter="LICENSE,ASSET-LICENSE.md,TRADEMARKS.md,docs/ATTRIBUTION.md,ui/fonts/*LICENSE.txt"
exclude_filter="builds/*,scripts/*"
export_path="builds/v0.20.4-alpha/portmaster-arm64/straywild.arm64"
patches=PackedStringArray()
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.6.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=0
binary_format/embed_pck=false
binary_format/architecture="arm64"
texture_format/s3tc_bptc=false
texture_format/etc2_astc=true

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Resource" path="res://fish/day_availability.tres" id="3_availability"] [ext_resource type="Resource" path="res://fish/day_availability.tres" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [ext_resource type="Texture2D" path="res://art/exported/creatures/3102.png" id="4_texture"]
[resource] [resource]
script = ExtResource("1_fish_data") script = ExtResource("1_fish_data")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/2502.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/2802.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [ext_resource type="Texture2D" path="res://art/exported/creatures/2505.png" id="4_texture"]
[sub_resource type="Resource" id="ChubEuropeanAvailability"] [sub_resource type="Resource" id="ChubEuropeanAvailability"]
script = ExtResource("3_availability_script") script = ExtResource("3_availability_script")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [ext_resource type="Texture2D" path="res://art/exported/creatures/2807.png" id="4_texture"]
[sub_resource type="Resource" id="ChubLakeAvailability"] [sub_resource type="Resource" id="ChubLakeAvailability"]
script = ExtResource("3_availability_script") script = ExtResource("3_availability_script")

View file

@ -2,7 +2,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Texture2D" path="res://fish/species/clam_manila/clam_manila.png" id="3_texture"] [ext_resource type="Texture2D" path="res://art/exported/creatures/6406.png" id="3_texture"]
[resource] [resource]
script = ExtResource("1_fish_data") script = ExtResource("1_fish_data")

View file

@ -1,9 +1,11 @@
[gd_resource type="Resource" script_class="FishData" load_steps=4 format=3] [gd_resource type="Resource" script_class="FishData" load_steps=5 format=3]
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/3902.png" id="99_creature_texture"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")
allow_night = false allow_night = false
@ -12,7 +14,7 @@ allow_night = false
script = ExtResource("1_fish_data") script = ExtResource("1_fish_data")
id = &"crab_blue" id = &"crab_blue"
display_name = "blue crab" display_name = "blue crab"
active = false active = true
catalog_number = 3902 catalog_number = 3902
collection_group = &"Marine crabs" 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." logbook_fact = "blue crabs swim with flattened rear legs shaped like paddles. bright blue claws and an olive shell conceal a quick, defensive temperament."
@ -30,3 +32,5 @@ weight_max_lb = 1.1
sell_value_min = 3 sell_value_min = 3
sell_value_max = 8 sell_value_max = 8
sell_value_curve = 0.95 sell_value_curve = 0.95
display_texture = ExtResource("99_creature_texture")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/3107.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/2601.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/6801.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/6807.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/5904.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [ext_resource type="Texture2D" path="res://art/exported/creatures/3401.png" id="4_texture"]
[sub_resource type="Resource" id="RoundGobyAvailability"] [sub_resource type="Resource" id="RoundGobyAvailability"]
script = ExtResource("3_availability_script") script = ExtResource("3_availability_script")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" uid="uid://h7hg45b7wmst" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"] [ext_resource type="Script" uid="uid://bs7cqi88csolc" path="res://fish/fish_availability.gd" id="3_availability_script"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="4_texture"] [ext_resource type="Texture2D" path="res://art/exported/creatures/3002.png" id="4_texture"]
[sub_resource type="Resource" id="GoldfishAvailability"] [sub_resource type="Resource" id="GoldfishAvailability"]
script = ExtResource("3_availability_script") script = ExtResource("3_availability_script")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/3103.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/5902.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/5903.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/6601.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/2804.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/6904.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/3104.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

View file

@ -3,7 +3,7 @@
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"] [ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"] [ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"] [ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability"]
[ext_resource type="Texture2D" path="res://art/exported/creatures/fish_placeholder.png" id="99_fish_placeholder"] [ext_resource type="Texture2D" path="res://art/exported/creatures/2504.png" id="99_fish_placeholder"]
[sub_resource type="Resource" id="Availability"] [sub_resource type="Resource" id="Availability"]
script = ExtResource("3_availability") script = ExtResource("3_availability")

Some files were not shown because too many files have changed in this diff Show more