Compare commits
4 commits
2f30691808
...
c85f709851
| Author | SHA1 | Date | |
|---|---|---|---|
| c85f709851 | |||
| 352350157b | |||
| 0d50c324e9 | |||
| 2a5e0a8f26 |
|
|
@ -37,6 +37,8 @@ written with or without assistance.
|
|||
- “Gentle Ocean Waves Loop” by kkenny101, used for the saltwater shoreline
|
||||
ambience. Freesound sound 852826, Creative Commons Zero (CC0):
|
||||
https://freesound.org/s/852826/
|
||||
- Rain Loop Ontario by Ayton -- https://freesound.org/s/212799/ -- License:
|
||||
Attribution 3.0
|
||||
- “Quick Water Droplet” by qubodup, used for the bobber water-impact sound.
|
||||
Freesound sound 792931, Creative Commons Zero (CC0):
|
||||
https://freesound.org/s/792931/
|
||||
|
|
|
|||
26
README.md
|
|
@ -38,32 +38,6 @@ scripts/run_validations.sh quick
|
|||
Additional suites and individual commands are documented in
|
||||
[`docs/TESTING.md`](docs/TESTING.md).
|
||||
|
||||
## Texture import normalization
|
||||
|
||||
Texture import settings are cached in `.import` sidecars. To keep new textures
|
||||
consistent by default, normalize imported textures before committing:
|
||||
|
||||
```sh
|
||||
./scripts/normalize_texture_profiles.sh
|
||||
```
|
||||
|
||||
To normalize only a specific directory (for example environment textures):
|
||||
|
||||
```sh
|
||||
./scripts/normalize_texture_profiles.sh res://art/exported/environment/textures
|
||||
```
|
||||
|
||||
The script enforces a single baseline texture profile by default and sets
|
||||
`import_profile = "managed_default"` in `[params]`. If any texture needs
|
||||
custom settings, set:
|
||||
|
||||
```ini
|
||||
[params]
|
||||
import_profile="custom"
|
||||
```
|
||||
|
||||
in its `.import` file to opt it out.
|
||||
|
||||
## Building
|
||||
|
||||
Export presets are maintained for Linux, Windows, and Android. Repository
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cd7qwsbeg1jxx"
|
||||
path="res://.godot/imported/cliff_wall.png-7ce756d49c24c7b8fdda307d68086d87.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/cliff_wall.png"
|
||||
dest_files=["res://.godot/imported/cliff_wall.png-7ce756d49c24c7b8fdda307d68086d87.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dtv2k327higrb"
|
||||
path="res://.godot/imported/dirt_light_wall.png-40336902aac8516f68e99f8e8988d86f.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/dirt_light_wall.png"
|
||||
dest_files=["res://.godot/imported/dirt_light_wall.png-40336902aac8516f68e99f8e8988d86f.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dusa52a3eghr7"
|
||||
path="res://.godot/imported/grass_heavy.png-ba7f679d1bc5402f8b0a3465ac6a44b8.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/grass_heavy.png"
|
||||
dest_files=["res://.godot/imported/grass_heavy.png-ba7f679d1bc5402f8b0a3465ac6a44b8.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://crci2on30ctoc"
|
||||
path.s3tc="res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/grass_lite.png"
|
||||
dest_files=["res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.s3tc.ctex", "res://.godot/imported/grass_lite.png-f89cf6413257cd31582bd4f9f875ae85.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=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://btxhq3rdlum2"
|
||||
path.s3tc="res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/grass_olive_tile.png"
|
||||
dest_files=["res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.s3tc.ctex", "res://.godot/imported/grass_olive_tile.png-72072a382d0948818df97773afa5493f.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=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bnje415gq85nn"
|
||||
path.s3tc="res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/sand.png"
|
||||
dest_files=["res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.s3tc.ctex", "res://.godot/imported/sand.png-41fc96c392119deeba43c2d4e49d88a5.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=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://cyukfgkghrigk"
|
||||
path="res://.godot/imported/sand_dark.png-ab4ec28097312f566fe7b882978a5858.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/sand_dark.png"
|
||||
dest_files=["res://.godot/imported/sand_dark.png-ab4ec28097312f566fe7b882978a5858.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ctll4fbskvat8"
|
||||
path.s3tc="res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.s3tc.ctex"
|
||||
path.etc2="res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.etc2.ctex"
|
||||
metadata={
|
||||
"imported_formats": ["s3tc_bptc", "etc2_astc"],
|
||||
"vram_texture": true
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/sandstone_heavy.png"
|
||||
dest_files=["res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.s3tc.ctex", "res://.godot/imported/sandstone_heavy.png-c324c68e8c28417ecd3506dbd5f24ef5.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=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bgbaiuts1c4sb"
|
||||
path="res://.godot/imported/sandstone_lite.png-8d5c0c5c4e3a227e0e487db42a418aba.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/sandstone_lite.png"
|
||||
dest_files=["res://.godot/imported/sandstone_lite.png-8d5c0c5c4e3a227e0e487db42a418aba.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
compress/rdo_quality_loss=0.0
|
||||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
process/channel_remap/red=0
|
||||
process/channel_remap/green=1
|
||||
process/channel_remap/blue=2
|
||||
process/channel_remap/alpha=3
|
||||
process/fix_alpha_border=true
|
||||
process/premult_alpha=false
|
||||
process/normal_map_invert_y=false
|
||||
process/hdr_as_srgb=false
|
||||
process/hdr_clamp_exposure=false
|
||||
process/size_limit=0
|
||||
detect_3d/compress_to=0
|
||||
import_profile="managed_default"
|
||||
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
BIN
audio/ambience/rain_loop_ontario.ogg
Normal file
19
audio/ambience/rain_loop_ontario.ogg.import
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[remap]
|
||||
|
||||
importer="oggvorbisstr"
|
||||
type="AudioStreamOggVorbis"
|
||||
uid="uid://cfqskba5j2iig"
|
||||
path="res://.godot/imported/rain_loop_ontario.ogg-c206e8a7ee5398c9fdc6d59462050964.oggvorbisstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/ambience/rain_loop_ontario.ogg"
|
||||
dest_files=["res://.godot/imported/rain_loop_ontario.ogg-c206e8a7ee5398c9fdc6d59462050964.oggvorbisstr"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=false
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
|
|
@ -9,13 +9,33 @@ const WORM_MAX_STACK: int = 10
|
|||
|
||||
const ITEM_PRICES: Dictionary[StringName, int] = {
|
||||
&"worms": 1,
|
||||
&"snails": 3,
|
||||
&"shrimp": 7,
|
||||
&"squid_chunks": 15,
|
||||
&"whole_anchovy": 30,
|
||||
&"whole_sardine": 60,
|
||||
&"luminous_roe": 125,
|
||||
&"coffee": 20,
|
||||
&"energy_drink": 35,
|
||||
&"snack": 30,
|
||||
&"fish_finder": 60,
|
||||
}
|
||||
const BAIT_UNLOCK_PRICES: Dictionary[StringName, int] = {
|
||||
&"snails": 400,
|
||||
&"shrimp": 1200,
|
||||
&"squid_chunks": 3500,
|
||||
&"whole_anchovy": 9000,
|
||||
&"whole_sardine": 22000,
|
||||
&"luminous_roe": 50000,
|
||||
}
|
||||
const ITEM_ORDER: Array[StringName] = [
|
||||
&"worms",
|
||||
&"snails",
|
||||
&"shrimp",
|
||||
&"squid_chunks",
|
||||
&"whole_anchovy",
|
||||
&"whole_sardine",
|
||||
&"luminous_roe",
|
||||
&"coffee",
|
||||
&"energy_drink",
|
||||
&"snack",
|
||||
|
|
@ -31,14 +51,35 @@ static func get_stock_item_ids() -> Array[StringName]:
|
|||
return ITEM_ORDER.duplicate()
|
||||
|
||||
|
||||
static func get_purchase_quantity(item_id: StringName, owned: int) -> int:
|
||||
if item_id == &"worms":
|
||||
return maxi(WORM_MAX_STACK - owned, 0)
|
||||
static func get_unlock_price(item_id: StringName) -> int:
|
||||
return BAIT_UNLOCK_PRICES.get(item_id, -1)
|
||||
|
||||
|
||||
static func get_purchase_quantity(
|
||||
item_id: StringName,
|
||||
owned: int,
|
||||
maximum_stack: int = WORM_MAX_STACK,
|
||||
) -> int:
|
||||
if is_bait_topoff(item_id):
|
||||
return maxi(maximum_stack - owned, 0)
|
||||
return 1
|
||||
|
||||
|
||||
static func is_bait_topoff(item_id: StringName) -> bool:
|
||||
return item_id == &"worms"
|
||||
return item_id == &"worms" or BAIT_UNLOCK_PRICES.has(item_id)
|
||||
|
||||
|
||||
static func get_purchase_cost(
|
||||
item_id: StringName,
|
||||
quantity: int,
|
||||
bait_unlocked: bool,
|
||||
) -> int:
|
||||
if quantity <= 0:
|
||||
return -1
|
||||
if is_bait_topoff(item_id) and item_id != &"worms" and not bait_unlocked:
|
||||
return get_unlock_price(item_id)
|
||||
var unit_price: int = get_price(item_id)
|
||||
return unit_price * quantity if unit_price >= 0 else -1
|
||||
|
||||
|
||||
static func purchase_one(
|
||||
|
|
@ -49,22 +90,32 @@ static func purchase_one(
|
|||
) -> bool:
|
||||
if wallet == null or bag == null or catalog == null:
|
||||
return false
|
||||
var price: int = get_price(item_id)
|
||||
var item: ItemDataType = catalog.get_item_by_id(item_id)
|
||||
var bait_topoff: bool = is_bait_topoff(item_id)
|
||||
var quantity: int = get_purchase_quantity(
|
||||
item_id,
|
||||
bag.get_quantity(item_id),
|
||||
item.max_stack if item != null else WORM_MAX_STACK,
|
||||
)
|
||||
var price: int = get_purchase_cost(
|
||||
item_id,
|
||||
quantity,
|
||||
bag.is_bait_unlocked(item_id),
|
||||
)
|
||||
if (
|
||||
price < 0
|
||||
or item == null
|
||||
or not item.is_valid()
|
||||
or (item.category != ItemDataType.Category.CONSUMABLE and not is_bait_topoff(item_id))
|
||||
or (item.category != ItemDataType.Category.CONSUMABLE and not bait_topoff)
|
||||
or not item.stackable
|
||||
or (not item.usable and not is_bait_topoff(item_id))
|
||||
or not bag.can_add_item(item_id, 1)
|
||||
or (not item.usable and not bait_topoff)
|
||||
or not bag.can_add_item(item_id, quantity)
|
||||
or not wallet.can_afford(price)
|
||||
):
|
||||
return false
|
||||
if not wallet.debit(price):
|
||||
return false
|
||||
if bag.add_item(item_id, 1):
|
||||
if bag.add_item(item_id, quantity):
|
||||
return true
|
||||
if not wallet.credit(price):
|
||||
push_error("Fishing Shop failed to roll back an item purchase.")
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ const ALL_TIERS_MASK: int = (1 << TIER_COUNT) - 1
|
|||
# multipliers later without changing catch serialization or tier identity.
|
||||
const BASE_ROLL_WEIGHTS: Array[float] = [40.0, 32.0, 18.0, 8.0, 2.0]
|
||||
const WORM_ROLL_WEIGHTS: Array[float] = [80.0, 10.0, 5.0, 4.0, 1.0]
|
||||
const SNAIL_ROLL_WEIGHTS: Array[float] = [68.0, 16.0, 8.0, 6.0, 2.0]
|
||||
const SHRIMP_ROLL_WEIGHTS: Array[float] = [56.0, 21.0, 12.0, 8.0, 3.0]
|
||||
const SQUID_ROLL_WEIGHTS: Array[float] = [44.0, 25.0, 16.0, 11.0, 4.0]
|
||||
const ANCHOVY_ROLL_WEIGHTS: Array[float] = [34.0, 27.0, 20.0, 13.0, 6.0]
|
||||
const SARDINE_ROLL_WEIGHTS: Array[float] = [25.0, 28.0, 23.0, 16.0, 8.0]
|
||||
const LUMINOUS_ROE_ROLL_WEIGHTS: Array[float] = [16.0, 26.0, 26.0, 20.0, 12.0]
|
||||
const SALE_MULTIPLIERS: Array[float] = [1.0, 1.1, 1.25, 1.5, 2.0]
|
||||
# Legacy profile multiplier retained for serialized/profile compatibility.
|
||||
# New encounters use the weighted quality/rarity/weight bands below.
|
||||
|
|
@ -136,17 +142,34 @@ static func roll(
|
|||
|
||||
|
||||
static func roll_weights_for_bait(active_bait_tags: Array[StringName]) -> Array[float]:
|
||||
var bait_weights: Array[float] = rarity_weights_for_bait(active_bait_tags)
|
||||
var weights: Array[float] = []
|
||||
for quality: int in TIER_COUNT:
|
||||
weights.append(bait_weights[quality] / BASE_ROLL_WEIGHTS[quality])
|
||||
return weights
|
||||
|
||||
|
||||
static func rarity_weights_for_bait(
|
||||
active_bait_tags: Array[StringName],
|
||||
) -> Array[float]:
|
||||
var weights: Array[float] = []
|
||||
if active_bait_tags.is_empty():
|
||||
weights.assign([2.5, 0.0, 0.0, 0.0, 0.0])
|
||||
weights.assign([100.0, 0.0, 0.0, 0.0, 0.0])
|
||||
elif active_bait_tags.has(&"bait_rarity_6"):
|
||||
weights.assign(LUMINOUS_ROE_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_5"):
|
||||
weights.assign(SARDINE_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_4"):
|
||||
weights.assign(ANCHOVY_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_3"):
|
||||
weights.assign(SQUID_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_2"):
|
||||
weights.assign(SHRIMP_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_1"):
|
||||
weights.assign(SNAIL_ROLL_WEIGHTS)
|
||||
else:
|
||||
weights.assign([
|
||||
WORM_ROLL_WEIGHTS[0] / BASE_ROLL_WEIGHTS[0],
|
||||
WORM_ROLL_WEIGHTS[1] / BASE_ROLL_WEIGHTS[1],
|
||||
WORM_ROLL_WEIGHTS[2] / BASE_ROLL_WEIGHTS[2],
|
||||
WORM_ROLL_WEIGHTS[3] / BASE_ROLL_WEIGHTS[3],
|
||||
WORM_ROLL_WEIGHTS[4] / BASE_ROLL_WEIGHTS[4],
|
||||
])
|
||||
# Unknown and legacy bait tags retain the original worms behavior.
|
||||
weights.assign(WORM_ROLL_WEIGHTS)
|
||||
return weights
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -81,9 +81,9 @@ func select_fish(
|
|||
|
||||
|
||||
func _roll_rarity(context: FishingContextType) -> int:
|
||||
var weights: Array[float] = [100.0, 0.0, 0.0, 0.0, 0.0]
|
||||
if not context.active_bait_tags.is_empty():
|
||||
weights = [80.0, 10.0, 5.0, 4.0, 1.0]
|
||||
var weights: Array[float] = FishQualityType.rarity_weights_for_bait(
|
||||
context.active_bait_tags
|
||||
)
|
||||
for index: int in range(weights.size()):
|
||||
if index < rarity_weight_multipliers.size():
|
||||
weights[index] *= maxf(rarity_weight_multipliers[index], 0.0)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
class_name FishingPresentation
|
||||
extends Node3D
|
||||
|
||||
const WaterSurfaceMotionType = preload(
|
||||
"res://world/water_surface_motion.gd"
|
||||
)
|
||||
|
||||
signal cast_completed
|
||||
signal outcome_completed(outcome: StringName)
|
||||
signal presentation_interrupted
|
||||
|
|
@ -52,6 +56,7 @@ var _rod: Node3D
|
|||
var _rod_tip: Marker3D
|
||||
var _rod_neutral_rotation: Vector3
|
||||
var _cast_arrival_position: Vector3
|
||||
var _cast_tracks_water_surface: bool = false
|
||||
var _bobber_surface_position: Vector3
|
||||
var _bobber_idle_elapsed: float = 0.0
|
||||
var _active_tween: Tween
|
||||
|
|
@ -68,6 +73,9 @@ func _process(delta: float) -> void:
|
|||
if _mode == VisualMode.FISHING and _bobber.visible:
|
||||
_bobber_idle_elapsed += delta
|
||||
_apply_bobber_idle_motion()
|
||||
var bobber_position := _bobber.global_position
|
||||
bobber_position.y += WaterSurfaceMotionType.get_default_height_offset()
|
||||
_bobber.global_position = bobber_position
|
||||
if _line_mode != LineMode.HIDDEN:
|
||||
_update_line(delta)
|
||||
|
||||
|
|
@ -154,6 +162,7 @@ func begin_cast(
|
|||
_cast_arrival_position = (
|
||||
blocked_landing_position if cast_is_blocked else target
|
||||
)
|
||||
_cast_tracks_water_surface = not cast_is_blocked
|
||||
|
||||
var cast_start: Vector3 = _rod_tip.global_position
|
||||
_bobber.global_position = cast_start
|
||||
|
|
@ -289,6 +298,7 @@ func cleanup() -> void:
|
|||
_bobber.scale = Vector3.ONE
|
||||
_bobber.rotation = Vector3.ZERO
|
||||
_cast_arrival_position = Vector3.ZERO
|
||||
_cast_tracks_water_surface = false
|
||||
_bobber_surface_position = Vector3.ZERO
|
||||
_bobber_idle_elapsed = 0.0
|
||||
set_line_mode(LineMode.HIDDEN)
|
||||
|
|
@ -306,6 +316,11 @@ func _set_cast_sample(
|
|||
) -> void:
|
||||
var sample: Vector3 = start.lerp(target, progress)
|
||||
sample.y += sin(progress * PI) * cast_arc_height
|
||||
if _cast_tracks_water_surface:
|
||||
sample.y += (
|
||||
WaterSurfaceMotionType.get_default_height_offset()
|
||||
* smoothstep(0.72, 1.0, progress)
|
||||
)
|
||||
_bobber.global_position = sample
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -100,14 +100,14 @@ enum FishingState {
|
|||
@export_range(0.0, 1.0, 0.01) var withdrawal_surface_clearance: float = 0.4
|
||||
|
||||
@export_category("Timing")
|
||||
const BITE_QUICK_MIN_SECONDS: float = 8.0
|
||||
const BITE_QUICK_MAX_SECONDS: float = 24.0
|
||||
const BITE_TYPICAL_MAX_SECONDS: float = 72.0
|
||||
const BITE_LONG_MAX_SECONDS: float = 144.0
|
||||
const BITE_MAX_SECONDS: float = 192.0
|
||||
const BITE_QUICK_PROBABILITY: float = 0.25
|
||||
const BITE_QUICK_MIN_SECONDS: float = 6.0
|
||||
const BITE_QUICK_MAX_SECONDS: float = 18.0
|
||||
const BITE_TYPICAL_MAX_SECONDS: float = 48.0
|
||||
const BITE_LONG_MAX_SECONDS: float = 72.0
|
||||
const BITE_MAX_SECONDS: float = 90.0
|
||||
const BITE_QUICK_PROBABILITY: float = 0.30
|
||||
const BITE_TYPICAL_PROBABILITY: float = 0.65
|
||||
const BITE_LONG_PROBABILITY: float = 0.08
|
||||
const BITE_LONG_PROBABILITY: float = 0.04
|
||||
const NETWORK_INPUT_RESEND_INTERVAL_SECONDS: float = 0.1
|
||||
@export_range(0.1, 10.0, 0.1) var cooldown_duration: float = 1.0
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ extends RefCounted
|
|||
const FishableWaterRegionType = preload(
|
||||
"res://world/fishable_water_region.gd"
|
||||
)
|
||||
const WaterSurfaceMotionType = preload(
|
||||
"res://world/water_surface_motion.gd"
|
||||
)
|
||||
|
||||
var has_surface: bool = false
|
||||
var position: Vector3 = Vector3.ZERO
|
||||
|
|
@ -19,7 +22,12 @@ func is_fishable() -> bool:
|
|||
func get_marker_position(vertical_offset: float) -> Vector3:
|
||||
if not has_surface:
|
||||
return position
|
||||
return position + normal.normalized() * vertical_offset
|
||||
var presentation_offset := vertical_offset
|
||||
if is_water_surface:
|
||||
presentation_offset += (
|
||||
WaterSurfaceMotionType.get_default_height_offset()
|
||||
)
|
||||
return position + normal.normalized() * presentation_offset
|
||||
|
||||
|
||||
func get_bobber_position(solid_clearance: float) -> Vector3:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
class_name RemoteFishingPresentation
|
||||
extends Node3D
|
||||
|
||||
const WaterSurfaceMotionType = preload(
|
||||
"res://world/water_surface_motion.gd"
|
||||
)
|
||||
|
||||
signal return_completed
|
||||
|
||||
var _owner: Player
|
||||
|
|
@ -140,6 +144,10 @@ func _set_cast_sample(
|
|||
) -> void:
|
||||
_target = start.lerp(target, progress)
|
||||
_target.y += sin(progress * PI) * 2.0
|
||||
_target.y += (
|
||||
WaterSurfaceMotionType.get_default_height_offset()
|
||||
* smoothstep(0.72, 1.0, progress)
|
||||
)
|
||||
_bobber.global_position = _target
|
||||
|
||||
|
||||
|
|
@ -152,7 +160,10 @@ func _on_cast_finished() -> void:
|
|||
|
||||
func _apply_bobber_idle_motion() -> void:
|
||||
var phase: float = _bobber_idle_elapsed * 0.7 * TAU
|
||||
_target = _pending_target + Vector3.UP * sin(phase) * 0.035
|
||||
_target = _pending_target + Vector3.UP * (
|
||||
WaterSurfaceMotionType.get_default_height_offset()
|
||||
+ sin(phase) * 0.035
|
||||
)
|
||||
_bobber.global_position = _target
|
||||
_bobber.rotation.z = deg_to_rad(4.0) * sin(phase * 0.73)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,10 +5,15 @@ const ItemCatalogType = preload("res://items/item_catalog.gd")
|
|||
const ItemDataType = preload("res://items/item_data.gd")
|
||||
const OwnedItemType = preload("res://items/owned_item.gd")
|
||||
|
||||
const DEFAULT_UNLOCKED_BAIT_IDS: Array[StringName] = [&"worms"]
|
||||
|
||||
signal contents_changed
|
||||
|
||||
var _catalog: ItemCatalogType
|
||||
var _items: Array[OwnedItemType] = []
|
||||
var _unlocked_bait_ids: Array[StringName] = (
|
||||
DEFAULT_UNLOCKED_BAIT_IDS.duplicate()
|
||||
)
|
||||
|
||||
|
||||
func setup(catalog: ItemCatalogType) -> void:
|
||||
|
|
@ -27,6 +32,8 @@ func add_item(item_id: StringName, quantity: int = 1) -> bool:
|
|||
owned.item_id = item_id
|
||||
owned.quantity = quantity
|
||||
_items.append(owned)
|
||||
if item.is_bait() and not _unlocked_bait_ids.has(item_id):
|
||||
_unlocked_bait_ids.append(item_id)
|
||||
contents_changed.emit()
|
||||
return true
|
||||
|
||||
|
|
@ -85,6 +92,46 @@ func get_all_items() -> Array[OwnedItemType]:
|
|||
return result
|
||||
|
||||
|
||||
func get_unlocked_bait_ids() -> Array[StringName]:
|
||||
return _unlocked_bait_ids.duplicate()
|
||||
|
||||
|
||||
func is_bait_unlocked(item_id: StringName) -> bool:
|
||||
return _unlocked_bait_ids.has(item_id)
|
||||
|
||||
|
||||
func get_unlocked_bait_items() -> Array[OwnedItemType]:
|
||||
var result: Array[OwnedItemType] = []
|
||||
for item_id: StringName in _unlocked_bait_ids:
|
||||
var owned: OwnedItemType = get_owned_item(item_id)
|
||||
if owned != null:
|
||||
result.append(owned.duplicate_record())
|
||||
continue
|
||||
var empty_record := OwnedItemType.new()
|
||||
empty_record.item_id = item_id
|
||||
empty_record.quantity = 0
|
||||
result.append(empty_record)
|
||||
return result
|
||||
|
||||
|
||||
func replace_unlocked_bait_ids(item_ids: Array[StringName]) -> bool:
|
||||
var validated: Array[StringName] = DEFAULT_UNLOCKED_BAIT_IDS.duplicate()
|
||||
var seen: Dictionary[StringName, bool] = {}
|
||||
for item_id: StringName in item_ids:
|
||||
if item_id.is_empty() or seen.has(item_id):
|
||||
return false
|
||||
seen[item_id] = true
|
||||
if validated.has(item_id):
|
||||
continue
|
||||
var item: ItemDataType = _resolve_valid_item(item_id)
|
||||
if item == null or not item.is_bait():
|
||||
return false
|
||||
validated.append(item_id)
|
||||
_unlocked_bait_ids = validated
|
||||
contents_changed.emit()
|
||||
return true
|
||||
|
||||
|
||||
func replace_all_items(items: Array[OwnedItemType]) -> bool:
|
||||
var validated: Array[OwnedItemType] = []
|
||||
var seen: Dictionary[StringName, bool] = {}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="ItemCatalog" load_steps=10 format=3]
|
||||
[gd_resource type="Resource" script_class="ItemCatalog" load_steps=16 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_catalog.gd" id="1_script"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/basic_fishing_rod.tres" id="2_rod"]
|
||||
|
|
@ -9,7 +9,13 @@
|
|||
[ext_resource type="Resource" path="res://items/catalog/cooler_expansion.tres" id="7_cooler"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/art_kit.tres" id="8_art_kit"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/worms.tres" id="9_worms"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/snails.tres" id="10_snails"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/shrimp.tres" id="11_shrimp"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/squid_chunks.tres" id="12_squid"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/whole_anchovy.tres" id="13_anchovy"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/whole_sardine.tres" id="14_sardine"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/luminous_roe.tres" id="15_roe"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("7_cooler"), ExtResource("8_art_kit"), ExtResource("9_worms")]
|
||||
items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("7_cooler"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("13_anchovy"), ExtResource("14_sardine"), ExtResource("15_roe")]
|
||||
|
|
|
|||
14
items/catalog/luminous_roe.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"luminous_roe"
|
||||
display_name = "luminous roe"
|
||||
description = "rare glowing roe with the strongest rare catch odds."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_6"])
|
||||
stackable = true
|
||||
max_stack = 10
|
||||
hotbar_allowed = false
|
||||
14
items/catalog/shrimp.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"shrimp"
|
||||
display_name = "shrimp"
|
||||
description = "lively bait that noticeably improves rare catch chances."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_2"])
|
||||
stackable = true
|
||||
max_stack = 10
|
||||
hotbar_allowed = false
|
||||
14
items/catalog/snails.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"snails"
|
||||
display_name = "snails"
|
||||
description = "slow-moving bait with a better chance for rarer catches."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_1"])
|
||||
stackable = true
|
||||
max_stack = 10
|
||||
hotbar_allowed = false
|
||||
14
items/catalog/squid_chunks.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"squid_chunks"
|
||||
display_name = "squid chunks"
|
||||
description = "rich cut bait that strongly favors rarer catches."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_3"])
|
||||
stackable = true
|
||||
max_stack = 10
|
||||
hotbar_allowed = false
|
||||
14
items/catalog/whole_anchovy.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"whole_anchovy"
|
||||
display_name = "whole anchovy"
|
||||
description = "premium whole bait with high rare catch potential."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_4"])
|
||||
stackable = true
|
||||
max_stack = 10
|
||||
hotbar_allowed = false
|
||||
14
items/catalog/whole_sardine.tres
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"whole_sardine"
|
||||
display_name = "whole sardine"
|
||||
description = "valuable whole bait tuned for exceptional catches."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_5"])
|
||||
stackable = true
|
||||
max_stack = 10
|
||||
hotbar_allowed = false
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1"]
|
||||
[ext_resource type="Texture2D" path="res://items/icons/bait/64_bait_worms.png" id="2_icon"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"worms"
|
||||
display_name = "worms"
|
||||
description = "common bait. fills your tacklebox at the fishing shop."
|
||||
description = "basic bait that opens a small chance for rarer catches."
|
||||
icon = ExtResource("2_icon")
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"worm"])
|
||||
bait_tags = Array[StringName]([&"worm", &"bait_rarity_0"])
|
||||
stackable = true
|
||||
max_stack = 10
|
||||
hotbar_allowed = false
|
||||
|
|
|
|||
BIN
items/icons/bait/64_bait_worms.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -2,20 +2,20 @@
|
|||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b2ce1ikmnbktx"
|
||||
path="res://.godot/imported/dirt.png-ca5bf3f3fbf583edaa719ac8c686549d.ctex"
|
||||
uid="uid://bn6815x0rrspw"
|
||||
path="res://.godot/imported/64_bait_worms.png-c9f31d77527e73888fc56963d4e78e30.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/dirt.png"
|
||||
dest_files=["res://.godot/imported/dirt.png-ca5bf3f3fbf583edaa719ac8c686549d.ctex"]
|
||||
source_file="res://items/icons/bait/64_bait_worms.png"
|
||||
dest_files=["res://.godot/imported/64_bait_worms.png-c9f31d77527e73888fc56963d4e78e30.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
@ -37,5 +37,4 @@ 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
|
||||
import_profile="managed_default"
|
||||
detect_3d/compress_to=1
|
||||
BIN
items/icons/equipment/64_cooler_plus.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
|
|
@ -2,20 +2,20 @@
|
|||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b4n8bf4cgua4s"
|
||||
path="res://.godot/imported/grass.png-4d1108b11a421d9c16bd72fb0bc993d3.ctex"
|
||||
uid="uid://baht2hredfky8"
|
||||
path="res://.godot/imported/64_cooler_plus.png-d0daa39246f92fd9774ec1721970efad.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://art/exported/environment/textures/grass.png"
|
||||
dest_files=["res://.godot/imported/grass.png-4d1108b11a421d9c16bd72fb0bc993d3.ctex"]
|
||||
source_file="res://items/icons/equipment/64_cooler_plus.png"
|
||||
dest_files=["res://.godot/imported/64_cooler_plus.png-d0daa39246f92fd9774ec1721970efad.ctex"]
|
||||
|
||||
[params]
|
||||
|
||||
compress/mode=2
|
||||
compress/mode=0
|
||||
compress/high_quality=false
|
||||
compress/lossy_quality=0.7
|
||||
compress/uastc_level=0
|
||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
|||
compress/hdr_compression=1
|
||||
compress/normal_map=0
|
||||
compress/channel_pack=0
|
||||
mipmaps/generate=true
|
||||
mipmaps/generate=false
|
||||
mipmaps/limit=-1
|
||||
roughness/mode=0
|
||||
roughness/src_normal=""
|
||||
|
|
@ -37,5 +37,4 @@ 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
|
||||
import_profile="managed_default"
|
||||
detect_3d/compress_to=1
|
||||
|
|
@ -36,7 +36,7 @@ var _pending_rewards: Array[Dictionary] = []
|
|||
var _lifetime_claimed: Array[String] = []
|
||||
var _total_catches: int = 0
|
||||
var _total_sold: int = 0
|
||||
var _previous_hour: float = WorldTimeService.DEFAULT_START_HOUR
|
||||
var _daily_clock_hour: float = WorldTimeService.DEFAULT_START_HOUR
|
||||
|
||||
|
||||
func setup(
|
||||
|
|
@ -55,8 +55,8 @@ func setup(
|
|||
_world_time = world_time
|
||||
_world_weather = world_weather
|
||||
_session = session
|
||||
_previous_hour = _world_time.get_time_hours()
|
||||
_world_time.time_changed.connect(_on_time_changed)
|
||||
_daily_clock_hour = _world_time.get_time_hours()
|
||||
_world_time.natural_time_advanced.connect(_on_natural_time_advanced)
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_collection.collection_changed.connect(_on_current_state_changed)
|
||||
_experience.experience_changed.connect(_on_experience_changed)
|
||||
|
|
@ -86,7 +86,6 @@ func set_save_manager(save_manager: PlayerSaveManager) -> void:
|
|||
|
||||
func begin_progression_session() -> void:
|
||||
_progression_ready = true
|
||||
_previous_hour = _world_time.get_time_hours()
|
||||
if _session.is_host():
|
||||
_activate_host_board()
|
||||
changed.emit()
|
||||
|
|
@ -155,7 +154,7 @@ func get_time_until_refresh_text() -> String:
|
|||
if _active_plan_id.is_empty() or _world_time == null:
|
||||
return "daily jobs unavailable"
|
||||
var elapsed: float = fposmod(
|
||||
_world_time.get_time_hours() - DAILY_REFRESH_HOUR,
|
||||
_daily_clock_hour - DAILY_REFRESH_HOUR,
|
||||
WorldTimeService.HOURS_PER_DAY,
|
||||
)
|
||||
var hours_remaining: float = WorldTimeService.HOURS_PER_DAY - elapsed
|
||||
|
|
@ -185,7 +184,10 @@ func claim(claim_id: String) -> bool:
|
|||
|
||||
|
||||
func get_host_board_network_data() -> Dictionary:
|
||||
return _host_board.duplicate(true)
|
||||
var board := _host_board.duplicate(true)
|
||||
if not board.is_empty():
|
||||
board["daily_clock_hour"] = _daily_clock_hour
|
||||
return board
|
||||
|
||||
|
||||
func apply_remote_board(board: Dictionary) -> bool:
|
||||
|
|
@ -198,6 +200,8 @@ func apply_remote_board(board: Dictionary) -> bool:
|
|||
_daily_progress.clear()
|
||||
_daily_completions.clear()
|
||||
_active_board = board.duplicate(true)
|
||||
if board.has("daily_clock_hour"):
|
||||
_daily_clock_hour = float(board["daily_clock_hour"])
|
||||
changed.emit()
|
||||
return true
|
||||
|
||||
|
|
@ -223,6 +227,7 @@ func to_save_data() -> Dictionary:
|
|||
"active_plan_id": _active_plan_id,
|
||||
"daily_progress": progress,
|
||||
"daily_completions": completions,
|
||||
"daily_clock_hour": _daily_clock_hour,
|
||||
"pending_rewards": _pending_rewards.duplicate(true),
|
||||
"lifetime_claimed": _lifetime_claimed.duplicate(),
|
||||
"statistics": {
|
||||
|
|
@ -246,6 +251,12 @@ func restore_from_save_data(data: Dictionary) -> bool:
|
|||
var completions: Dictionary = data.get("daily_completions", {})
|
||||
for key: Variant in completions:
|
||||
_daily_completions[str(key)] = mini(int(completions[key]), 1)
|
||||
_daily_clock_hour = float(data.get(
|
||||
"daily_clock_hour",
|
||||
_world_time.get_time_hours()
|
||||
if _world_time != null
|
||||
else WorldTimeService.DEFAULT_START_HOUR,
|
||||
))
|
||||
_pending_rewards.clear()
|
||||
var rewards: Array = data.get("pending_rewards", [])
|
||||
var restored_daily_rewards: Dictionary[String, bool] = {}
|
||||
|
|
@ -290,6 +301,7 @@ func reset_to_defaults() -> void:
|
|||
_lifetime_claimed.clear()
|
||||
_total_catches = 0
|
||||
_total_sold = 0
|
||||
_daily_clock_hour = WorldTimeService.DEFAULT_START_HOUR
|
||||
changed.emit()
|
||||
|
||||
|
||||
|
|
@ -300,6 +312,7 @@ static func default_save_data() -> Dictionary:
|
|||
"active_plan_id": "",
|
||||
"daily_progress": {},
|
||||
"daily_completions": {},
|
||||
"daily_clock_hour": WorldTimeService.DEFAULT_START_HOUR,
|
||||
"pending_rewards": [],
|
||||
"lifetime_claimed": [],
|
||||
"statistics": {"fish_caught": 0, "fish_sold": 0},
|
||||
|
|
@ -363,6 +376,16 @@ static func validate_save_data(value: Variant) -> bool:
|
|||
var host_board: Dictionary = data.get("host_board", {})
|
||||
if not host_board.is_empty() and not validate_board(host_board):
|
||||
return false
|
||||
if (
|
||||
data.has("daily_clock_hour")
|
||||
and (
|
||||
typeof(data["daily_clock_hour"]) not in [TYPE_FLOAT, TYPE_INT]
|
||||
or not is_finite(float(data["daily_clock_hour"]))
|
||||
or float(data["daily_clock_hour"]) < 0.0
|
||||
or float(data["daily_clock_hour"]) >= WorldTimeService.HOURS_PER_DAY
|
||||
)
|
||||
):
|
||||
return false
|
||||
var progress: Dictionary = data.get("daily_progress", {})
|
||||
if progress.size() > 8:
|
||||
return false
|
||||
|
|
@ -475,19 +498,22 @@ func _apply_host_weather_plan() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _on_time_changed(
|
||||
time_hours: float,
|
||||
_phase: WorldTimeService.Phase,
|
||||
) -> void:
|
||||
func _on_natural_time_advanced(hours: float) -> void:
|
||||
if not is_finite(hours) or hours <= 0.0:
|
||||
return
|
||||
var previous_hour := _daily_clock_hour
|
||||
_daily_clock_hour = fposmod(
|
||||
_daily_clock_hour + hours,
|
||||
WorldTimeService.HOURS_PER_DAY,
|
||||
)
|
||||
if (
|
||||
_progression_ready
|
||||
and _session != null
|
||||
and _session.is_host()
|
||||
and _crossed_daily_refresh(_previous_hour, time_hours)
|
||||
and _crossed_daily_refresh(previous_hour, _daily_clock_hour)
|
||||
):
|
||||
var cycle: int = int(_host_board.get("cycle", -1)) + 1
|
||||
_generate_host_board(maxi(cycle, 0))
|
||||
_previous_hour = time_hours
|
||||
|
||||
|
||||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
|
|
|
|||
18
main/main.gd
|
|
@ -91,6 +91,7 @@ const WorldWeatherServiceType = preload("res://world/world_weather_service.gd")
|
|||
const NetworkWorldWeatherServiceType = preload(
|
||||
"res://network/network_world_weather_service.gd"
|
||||
)
|
||||
const RainAmbienceType = preload("res://world/rain_ambience.gd")
|
||||
const PlayerJobServiceType = preload("res://jobs/player_job_service.gd")
|
||||
const NetworkJobServiceType = preload("res://network/network_job_service.gd")
|
||||
|
||||
|
|
@ -210,10 +211,15 @@ var _pending_existing_root_path := ""
|
|||
var _local_recovery_attempt_id: String = ""
|
||||
|
||||
@onready var _shoreline_ambience: ShorelineAmbience = %ShorelineAmbience
|
||||
var _rain_ambience: RainAmbienceType
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
DisplayServer.window_set_title("NETfishing")
|
||||
_rain_ambience = RainAmbienceType.new()
|
||||
_rain_ambience.name = "RainAmbience"
|
||||
add_child(_rain_ambience)
|
||||
_rain_ambience.configure(_world_weather)
|
||||
_shoreline_ambience.configure(
|
||||
_player,
|
||||
_test_world.get_saltwater_shoreline_mesh(),
|
||||
|
|
@ -1096,7 +1102,16 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_game_ui.set_shop_prompt_visible(_can_show_shop_prompt())
|
||||
var show_shop_prompt := _can_show_shop_prompt()
|
||||
var shop_prompt_anchor := (
|
||||
_shop_interaction.get_prompt_anchor_position()
|
||||
if _shop_interaction != null
|
||||
else Vector3.ZERO
|
||||
)
|
||||
_game_ui.set_shop_prompt_visible(
|
||||
show_shop_prompt,
|
||||
shop_prompt_anchor,
|
||||
)
|
||||
|
||||
|
||||
func _apply_runtime_settings(settings: PlayerSettingsType) -> void:
|
||||
|
|
@ -1151,6 +1166,7 @@ func _reset_pixelation() -> void:
|
|||
func _set_gameplay_active(active: bool) -> void:
|
||||
_gameplay_started = active
|
||||
_shoreline_ambience.set_active(active)
|
||||
_rain_ambience.set_active(active)
|
||||
_title_background.visible = not active
|
||||
_world_pixelation.set_gameplay_active(active)
|
||||
_ui_pixelation.set_gameplay_active(active)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,21 @@ func send_local_message(body: String) -> bool:
|
|||
return true
|
||||
|
||||
|
||||
func broadcast_system_message(body: String) -> bool:
|
||||
if _session == null or not _session.is_host():
|
||||
return false
|
||||
var clean := NetworkChatProtocol.sanitize_body(body)
|
||||
if clean.is_empty():
|
||||
return false
|
||||
_broadcast(_make_message(
|
||||
NetworkChatProtocol.Kind.SYSTEM,
|
||||
0,
|
||||
"",
|
||||
clean,
|
||||
))
|
||||
return true
|
||||
|
||||
|
||||
func get_history() -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
for message: Dictionary in _history:
|
||||
|
|
|
|||
|
|
@ -992,6 +992,7 @@ func _capture_assets() -> Dictionary:
|
|||
return {
|
||||
"wallet": _wallet.get_balance(),
|
||||
"bag": _bag.get_all_items(),
|
||||
"unlocked_bait_ids": _bag.get_unlocked_bait_ids(),
|
||||
"catches": _inventory.get_all_catches(),
|
||||
"next_sequence": _inventory.get_next_catch_sequence(),
|
||||
"discovered": _collection_log.get_discovered_ids(),
|
||||
|
|
@ -1002,6 +1003,7 @@ func _capture_assets() -> Dictionary:
|
|||
func _restore_assets(snapshot: Dictionary) -> void:
|
||||
_wallet.restore_balance(int(snapshot["wallet"]))
|
||||
_bag.replace_all_items(snapshot["bag"])
|
||||
_bag.replace_unlocked_bait_ids(snapshot["unlocked_bait_ids"])
|
||||
_inventory.replace_all_catches(
|
||||
snapshot["catches"], int(snapshot["next_sequence"])
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,13 @@ class_name NetworkProfilePreferences
|
|||
extends Node
|
||||
|
||||
const FORMAT_VERSION: int = 1
|
||||
const VoiceProfilesType = preload(
|
||||
"res://player/animalese_voice_profiles.gd"
|
||||
)
|
||||
var profile_id: String = ""
|
||||
var display_name: String = "Player"
|
||||
var voice_id: String = VoiceProfilesType.DEFAULT_ID
|
||||
var speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
var created_at_unix: int = 0
|
||||
var _profile_path := ""
|
||||
var _expected_hash := ""
|
||||
|
|
@ -48,19 +53,39 @@ func load_or_create() -> bool:
|
|||
Time.get_ticks_usec(),
|
||||
]
|
||||
display_name = "Player"
|
||||
voice_id = VoiceProfilesType.DEFAULT_ID
|
||||
speech_speed_id = VoiceProfilesType.DEFAULT_SPEED_ID
|
||||
created_at_unix = int(Time.get_unix_time_from_system())
|
||||
return _save_atomic()
|
||||
|
||||
|
||||
func set_display_name(value: String) -> bool:
|
||||
return set_profile_identity(value, voice_id, speech_speed_id)
|
||||
|
||||
|
||||
func set_profile_identity(
|
||||
value: String,
|
||||
selected_voice_id: String,
|
||||
selected_speech_speed_id: String,
|
||||
) -> bool:
|
||||
var clean_name: String = value.strip_edges()
|
||||
if not is_valid_display_name(clean_name):
|
||||
if (
|
||||
not is_valid_display_name(clean_name)
|
||||
or not VoiceProfilesType.is_valid(selected_voice_id)
|
||||
or not VoiceProfilesType.is_valid_speed(selected_speech_speed_id)
|
||||
):
|
||||
return false
|
||||
var previous: String = display_name
|
||||
var previous_name: String = display_name
|
||||
var previous_voice_id: String = voice_id
|
||||
var previous_speech_speed_id: String = speech_speed_id
|
||||
display_name = clean_name
|
||||
voice_id = selected_voice_id
|
||||
speech_speed_id = selected_speech_speed_id
|
||||
if _save_atomic():
|
||||
return true
|
||||
display_name = previous
|
||||
display_name = previous_name
|
||||
voice_id = previous_voice_id
|
||||
speech_speed_id = previous_speech_speed_id
|
||||
return false
|
||||
|
||||
|
||||
|
|
@ -109,6 +134,12 @@ func _load_existing() -> bool:
|
|||
return false
|
||||
profile_id = loaded_id
|
||||
display_name = loaded_name
|
||||
voice_id = VoiceProfilesType.sanitized_id(
|
||||
str(data.get("voice_id", VoiceProfilesType.DEFAULT_ID))
|
||||
)
|
||||
speech_speed_id = VoiceProfilesType.sanitized_speed_id(
|
||||
str(data.get("speech_speed_id", VoiceProfilesType.DEFAULT_SPEED_ID))
|
||||
)
|
||||
created_at_unix = int(data["created_at_unix"])
|
||||
return true
|
||||
|
||||
|
|
@ -118,6 +149,8 @@ func _save_atomic() -> bool:
|
|||
"format_version": FORMAT_VERSION,
|
||||
"profile_id": profile_id,
|
||||
"display_name": display_name,
|
||||
"voice_id": voice_id,
|
||||
"speech_speed_id": speech_speed_id,
|
||||
"created_at_unix": created_at_unix,
|
||||
}
|
||||
var result := PortableFileGuard.write_guarded(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
class_name NetworkProfileService
|
||||
extends Node
|
||||
|
||||
const VoiceProfilesType = preload(
|
||||
"res://player/animalese_voice_profiles.gd"
|
||||
)
|
||||
|
||||
signal conflict_result(
|
||||
request_id: String,
|
||||
has_conflict: bool,
|
||||
|
|
@ -18,6 +22,8 @@ var _preferences: NetworkProfilePreferences
|
|||
var _appearance_store: PlayerAppearanceStore
|
||||
var _spawn_service: PlayerSpawnService
|
||||
var _pending_apply: Dictionary[String, Dictionary] = {}
|
||||
var _pending_voice_ids: Dictionary[String, String] = {}
|
||||
var _pending_speech_speed_ids: Dictionary[String, String] = {}
|
||||
var _host_pending_apply: Dictionary[String, Dictionary] = {}
|
||||
var _latest_check_id: String = ""
|
||||
var _latest_check_name: String = ""
|
||||
|
|
@ -40,6 +46,7 @@ func setup(
|
|||
_session.join_authenticated.connect(_on_join_authenticated)
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_apply_to_avatar(1, _appearance_store.get_snapshot())
|
||||
_apply_local_voice_to_avatar()
|
||||
|
||||
|
||||
func get_persisted_name() -> String:
|
||||
|
|
@ -50,6 +57,14 @@ func get_persisted_appearance() -> Dictionary:
|
|||
return _appearance_store.get_snapshot()
|
||||
|
||||
|
||||
func get_persisted_voice_id() -> String:
|
||||
return _preferences.voice_id
|
||||
|
||||
|
||||
func get_persisted_speech_speed_id() -> String:
|
||||
return _preferences.speech_speed_id
|
||||
|
||||
|
||||
func get_identity_fingerprint() -> String:
|
||||
return (
|
||||
_session.get_local_identity_fingerprint()
|
||||
|
|
@ -87,11 +102,15 @@ func apply_profile(
|
|||
display_name: String,
|
||||
appearance: Dictionary,
|
||||
use_anyway: bool,
|
||||
voice_id: String = VoiceProfilesType.DEFAULT_ID,
|
||||
speech_speed_id: String = VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
) -> bool:
|
||||
var clean_name := display_name.strip_edges()
|
||||
if (
|
||||
not NetworkProfilePreferences.is_valid_display_name(clean_name)
|
||||
or not CharacterCustomizationCatalog.validate_snapshot(appearance)
|
||||
or not VoiceProfilesType.is_valid(voice_id)
|
||||
or not VoiceProfilesType.is_valid_speed(speech_speed_id)
|
||||
):
|
||||
apply_finished.emit(false, "Check the player name and appearance choices.")
|
||||
return false
|
||||
|
|
@ -108,6 +127,8 @@ func apply_profile(
|
|||
"profile_update", NetworkProfileProtocol.signature_fields(request)
|
||||
)
|
||||
_pending_apply[request_id] = request
|
||||
_pending_voice_ids[request_id] = voice_id
|
||||
_pending_speech_speed_ids[request_id] = speech_speed_id
|
||||
if _session == null or not _session.is_gameplay_session_active():
|
||||
_apply_local_result(request_id, true, "", false, PackedStringArray())
|
||||
elif _session.is_host():
|
||||
|
|
@ -264,20 +285,46 @@ func _apply_local_result(
|
|||
return
|
||||
if not accepted:
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
conflict_result.emit(request_id, conflict, suggestions)
|
||||
apply_finished.emit(false, message)
|
||||
return
|
||||
var previous_name := _preferences.display_name
|
||||
if not _preferences.set_display_name(str(request["display_name"])):
|
||||
var previous_voice_id := _preferences.voice_id
|
||||
var previous_speech_speed_id := _preferences.speech_speed_id
|
||||
var requested_voice_id: String = _pending_voice_ids.get(
|
||||
request_id,
|
||||
VoiceProfilesType.DEFAULT_ID,
|
||||
)
|
||||
var requested_speech_speed_id: String = _pending_speech_speed_ids.get(
|
||||
request_id,
|
||||
VoiceProfilesType.DEFAULT_SPEED_ID,
|
||||
)
|
||||
if not _preferences.set_profile_identity(
|
||||
str(request["display_name"]),
|
||||
requested_voice_id,
|
||||
requested_speech_speed_id,
|
||||
):
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
apply_finished.emit(false, "Profile could not be saved.")
|
||||
return
|
||||
if not _appearance_store.save_snapshot(request["appearance"]):
|
||||
_preferences.set_display_name(previous_name)
|
||||
_preferences.set_profile_identity(
|
||||
previous_name,
|
||||
previous_voice_id,
|
||||
previous_speech_speed_id,
|
||||
)
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
apply_finished.emit(false, "Profile could not be saved.")
|
||||
return
|
||||
_pending_apply.erase(request_id)
|
||||
_pending_voice_ids.erase(request_id)
|
||||
_pending_speech_speed_ids.erase(request_id)
|
||||
if _session != null and _session.is_gameplay_session_active():
|
||||
if _session.is_host():
|
||||
_session.apply_canonical_profile(
|
||||
|
|
@ -304,6 +351,7 @@ func _apply_local_result(
|
|||
_session.get_local_peer_id() if _session != null else 1,
|
||||
_appearance_store.get_snapshot(),
|
||||
)
|
||||
_apply_local_voice_to_avatar()
|
||||
apply_finished.emit(true, "Profile saved.")
|
||||
|
||||
|
||||
|
|
@ -361,6 +409,7 @@ func _on_join_authenticated() -> void:
|
|||
_apply_to_avatar(
|
||||
_session.get_local_peer_id(), _appearance_store.get_snapshot()
|
||||
)
|
||||
_apply_local_voice_to_avatar()
|
||||
|
||||
|
||||
func _on_peer_removed(_peer_id: int) -> void:
|
||||
|
|
@ -400,6 +449,8 @@ func _refresh_latest_check() -> void:
|
|||
func _on_session_state_changed(state: NetworkSession.State) -> void:
|
||||
if state == NetworkSession.State.INACTIVE:
|
||||
_pending_apply.clear()
|
||||
_pending_voice_ids.clear()
|
||||
_pending_speech_speed_ids.clear()
|
||||
_host_pending_apply.clear()
|
||||
_latest_check_id = ""
|
||||
_latest_check_name = ""
|
||||
|
|
@ -465,5 +516,16 @@ func _apply_to_avatar(peer_id: int, appearance: Dictionary) -> void:
|
|||
avatar.apply_appearance_snapshot(appearance)
|
||||
|
||||
|
||||
func _apply_local_voice_to_avatar() -> void:
|
||||
if _spawn_service == null:
|
||||
return
|
||||
var local_peer_id := (
|
||||
_session.get_local_peer_id() if _session != null else 1
|
||||
)
|
||||
var avatar := _spawn_service.get_avatar(local_peer_id)
|
||||
if avatar != null:
|
||||
avatar.apply_animalese_voice_id(_preferences.voice_id)
|
||||
|
||||
|
||||
func _new_id() -> String:
|
||||
return Crypto.new().generate_random_bytes(16).hex_encode()
|
||||
|
|
|
|||
|
|
@ -56,6 +56,11 @@ static func validate_request(data: Variant) -> String:
|
|||
var quantity: int = payload["quantity"]
|
||||
var wallet_balance: int = payload["wallet_balance"]
|
||||
var current_state: int = payload["current_state"]
|
||||
if (
|
||||
payload.has("bait_unlocked")
|
||||
and typeof(payload["bait_unlocked"]) != TYPE_BOOL
|
||||
):
|
||||
return "Purchase could not be completed."
|
||||
if (
|
||||
request_id.is_empty()
|
||||
or request_id.length() > MAX_ID_LENGTH
|
||||
|
|
|
|||
|
|
@ -109,7 +109,15 @@ func is_local_purchase_pending() -> bool:
|
|||
|
||||
func request_supply(item_id: StringName) -> String:
|
||||
var owned: int = _bag.get_quantity(item_id) if _bag != null else 0
|
||||
var quantity: int = FishingShopStockType.get_purchase_quantity(item_id, owned)
|
||||
var item: ItemDataType = (
|
||||
_item_catalog.get_item_by_id(item_id)
|
||||
if _item_catalog != null else null
|
||||
)
|
||||
var quantity: int = FishingShopStockType.get_purchase_quantity(
|
||||
item_id,
|
||||
owned,
|
||||
item.max_stack if item != null else FishingShopStockType.WORM_MAX_STACK,
|
||||
)
|
||||
return _request_purchase(
|
||||
item_id,
|
||||
NetworkShopProtocol.ProductCategory.SUPPLY,
|
||||
|
|
@ -223,6 +231,13 @@ func _request_purchase(
|
|||
"wallet_balance": _wallet.get_balance() if _wallet != null else 0,
|
||||
"current_state": current_state,
|
||||
}
|
||||
if (
|
||||
category == NetworkShopProtocol.ProductCategory.SUPPLY
|
||||
and FishingShopStockType.is_bait_topoff(product_id)
|
||||
):
|
||||
request["bait_unlocked"] = (
|
||||
_bag != null and _bag.is_bait_unlocked(product_id)
|
||||
)
|
||||
_pending_local_request = request.duplicate(true)
|
||||
local_purchase_pending.emit(request_id)
|
||||
if _session.is_host():
|
||||
|
|
@ -317,16 +332,18 @@ func _build_authoritative_result(
|
|||
var item: ItemDataType = _item_catalog.get_item_by_id(
|
||||
product_id
|
||||
) if _item_catalog != null else null
|
||||
cost = FishingShopStockType.get_price(product_id)
|
||||
var bait_topoff: bool = (
|
||||
FishingShopStockType.is_bait_topoff(product_id)
|
||||
)
|
||||
if (
|
||||
item == null
|
||||
or not item.is_valid()
|
||||
or product_id not in (
|
||||
FishingShopStockType.get_stock_item_ids()
|
||||
)
|
||||
or (item.category != ItemDataType.Category.CONSUMABLE and not FishingShopStockType.is_bait_topoff(product_id))
|
||||
or (item.category != ItemDataType.Category.CONSUMABLE and not bait_topoff)
|
||||
or not item.stackable
|
||||
or (not item.usable and not FishingShopStockType.is_bait_topoff(product_id))
|
||||
or (not item.usable and not bait_topoff)
|
||||
or current_state >= item.max_stack
|
||||
or current_state + quantity > item.max_stack
|
||||
):
|
||||
|
|
@ -336,10 +353,21 @@ func _build_authoritative_result(
|
|||
else "Purchase could not be completed."
|
||||
)
|
||||
else:
|
||||
if not FishingShopStockType.is_bait_topoff(product_id) and quantity != 1:
|
||||
var expected_quantity: int = (
|
||||
FishingShopStockType.get_purchase_quantity(
|
||||
product_id, current_state, item.max_stack
|
||||
)
|
||||
)
|
||||
if quantity != expected_quantity:
|
||||
rejection = "Purchase could not be completed."
|
||||
else:
|
||||
cost *= quantity
|
||||
cost = FishingShopStockType.get_purchase_cost(
|
||||
product_id,
|
||||
quantity,
|
||||
bool(request.get("bait_unlocked", true)),
|
||||
)
|
||||
if cost < 0:
|
||||
rejection = "Purchase could not be completed."
|
||||
resulting_state = current_state + quantity
|
||||
NetworkShopProtocol.ProductCategory.ROD:
|
||||
rejection = "This item is not sold here."
|
||||
|
|
@ -532,6 +560,9 @@ func _apply_purchase_result(data: Dictionary) -> void:
|
|||
return
|
||||
var wallet_snapshot: int = _wallet.get_balance()
|
||||
var bag_snapshot: Array[OwnedItemType] = _bag.get_all_items()
|
||||
var bait_unlock_snapshot: Array[StringName] = (
|
||||
_bag.get_unlocked_bait_ids()
|
||||
)
|
||||
var reel_snapshot: int = _upgrades.get_reel_speed_level()
|
||||
var barrier_snapshot: int = _upgrades.get_barrier_power_level()
|
||||
var cooler_snapshot: int = _cooler_capacity.get_level()
|
||||
|
|
@ -539,6 +570,7 @@ func _apply_purchase_result(data: Dictionary) -> void:
|
|||
var applied: bool = _apply_local_product(data)
|
||||
if not applied or not _save_manager.save_if_dirty():
|
||||
_bag.replace_all_items(bag_snapshot)
|
||||
_bag.replace_unlocked_bait_ids(bait_unlock_snapshot)
|
||||
_upgrades.restore_levels(reel_snapshot, barrier_snapshot)
|
||||
_cooler_capacity.restore_level(cooler_snapshot)
|
||||
_art_unlocks.restore_mask(art_snapshot)
|
||||
|
|
@ -590,9 +622,22 @@ func _validate_local_result(data: Dictionary) -> String:
|
|||
return "Not enough fish coin."
|
||||
match category:
|
||||
NetworkShopProtocol.ProductCategory.SUPPLY:
|
||||
if _bag.get_quantity(product_id) != expected_state:
|
||||
var item: ItemDataType = (
|
||||
_item_catalog.get_item_by_id(product_id)
|
||||
if _item_catalog != null else null
|
||||
)
|
||||
var bait_unlocked: bool = _bag.is_bait_unlocked(product_id)
|
||||
if (
|
||||
item == null
|
||||
or _bag.get_quantity(product_id) != expected_state
|
||||
or int(data["quantity"]) != FishingShopStockType.get_purchase_quantity(
|
||||
product_id, expected_state, item.max_stack
|
||||
)
|
||||
):
|
||||
return "Purchase could not be completed."
|
||||
if cost != FishingShopStockType.get_price(product_id) * int(data["quantity"]):
|
||||
if cost != FishingShopStockType.get_purchase_cost(
|
||||
product_id, int(data["quantity"]), bait_unlocked
|
||||
):
|
||||
return "Purchase could not be completed."
|
||||
if not _bag.can_add_item(product_id, data["quantity"]):
|
||||
return "Your Bag is full."
|
||||
|
|
|
|||
|
|
@ -17,6 +17,12 @@ func setup(session: NetworkSession, world_time: WorldTimeService) -> void:
|
|||
_world_time = world_time
|
||||
_session.state_changed.connect(_on_session_state_changed)
|
||||
_session.peer_authenticated.connect(_on_peer_authenticated)
|
||||
if not _world_time.authoritative_time_set.is_connected(
|
||||
_on_authoritative_time_set
|
||||
):
|
||||
_world_time.authoritative_time_set.connect(
|
||||
_on_authoritative_time_set
|
||||
)
|
||||
set_process(true)
|
||||
|
||||
|
||||
|
|
@ -82,6 +88,13 @@ func _on_peer_authenticated(peer_id: int, _display_name: String) -> void:
|
|||
_send_snapshot(peer_id)
|
||||
|
||||
|
||||
func _on_authoritative_time_set(_time_hours: float) -> void:
|
||||
if _session == null or not _session.is_host():
|
||||
return
|
||||
_sync_elapsed = 0.0
|
||||
_broadcast_snapshot()
|
||||
|
||||
|
||||
func _broadcast_snapshot() -> void:
|
||||
if _session == null or not _session.is_host():
|
||||
return
|
||||
|
|
|
|||
57
player/animalese_voice_profiles.gd
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
class_name AnimaleseVoiceProfiles
|
||||
extends RefCounted
|
||||
|
||||
const DEFAULT_ID: String = "natural"
|
||||
const DEFAULT_SPEED_ID: String = "normal"
|
||||
const OPTIONS: Array[Dictionary] = [
|
||||
{"id": "tiny", "label": "tiny", "pitch": 1.24},
|
||||
{"id": "bright", "label": "bright", "pitch": 1.10},
|
||||
{"id": "natural", "label": "natural", "pitch": 1.0},
|
||||
{"id": "mellow", "label": "mellow", "pitch": 0.89},
|
||||
{"id": "deep", "label": "deep", "pitch": 0.77},
|
||||
]
|
||||
const SPEED_OPTIONS: Array[Dictionary] = [
|
||||
{"id": "slow", "label": "slow", "characters_per_second": 20.0},
|
||||
{"id": "relaxed", "label": "relaxed", "characters_per_second": 24.0},
|
||||
{"id": "normal", "label": "normal", "characters_per_second": 28.0},
|
||||
{"id": "quick", "label": "quick", "characters_per_second": 34.0},
|
||||
{"id": "rapid", "label": "rapid", "characters_per_second": 40.0},
|
||||
]
|
||||
|
||||
|
||||
static func is_valid(voice_id: String) -> bool:
|
||||
for option: Dictionary in OPTIONS:
|
||||
if str(option.get("id", "")) == voice_id:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
static func sanitized_id(voice_id: String) -> String:
|
||||
return voice_id if is_valid(voice_id) else DEFAULT_ID
|
||||
|
||||
|
||||
static func pitch_for(voice_id: String) -> float:
|
||||
var resolved_id := sanitized_id(voice_id)
|
||||
for option: Dictionary in OPTIONS:
|
||||
if str(option.get("id", "")) == resolved_id:
|
||||
return float(option.get("pitch", 1.0))
|
||||
return 1.0
|
||||
|
||||
|
||||
static func is_valid_speed(speed_id: String) -> bool:
|
||||
for option: Dictionary in SPEED_OPTIONS:
|
||||
if str(option.get("id", "")) == speed_id:
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
static func sanitized_speed_id(speed_id: String) -> String:
|
||||
return speed_id if is_valid_speed(speed_id) else DEFAULT_SPEED_ID
|
||||
|
||||
|
||||
static func speed_for(speed_id: String) -> float:
|
||||
var resolved_id := sanitized_speed_id(speed_id)
|
||||
for option: Dictionary in SPEED_OPTIONS:
|
||||
if str(option.get("id", "")) == resolved_id:
|
||||
return float(option.get("characters_per_second", 28.0))
|
||||
return 28.0
|
||||
1
player/animalese_voice_profiles.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://dlut32tq2v66j
|
||||
185
player/player.gd
|
|
@ -50,6 +50,7 @@ const CONTROLLER_ZOOM_TRIGGER_AXIS: JoyAxis = JOY_AXIS_TRIGGER_LEFT
|
|||
var appearance_snapshot: Dictionary = (
|
||||
CharacterCustomizationCatalog.default_snapshot()
|
||||
)
|
||||
var animalese_voice_id: String = "natural"
|
||||
var active_bait_id: StringName = StringName()
|
||||
signal active_bait_changed(item_id: StringName)
|
||||
|
||||
|
|
@ -76,6 +77,14 @@ func apply_appearance_snapshot(snapshot: Dictionary) -> void:
|
|||
appearance_snapshot = snapshot.duplicate(true)
|
||||
PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot)
|
||||
|
||||
|
||||
func apply_animalese_voice_id(voice_id: String) -> void:
|
||||
animalese_voice_id = voice_id
|
||||
|
||||
|
||||
func get_animalese_voice_id() -> String:
|
||||
return animalese_voice_id
|
||||
|
||||
class ShowcaseCameraSnapshot:
|
||||
extends RefCounted
|
||||
|
||||
|
|
@ -129,6 +138,8 @@ class ShowcaseCameraSnapshot:
|
|||
@export var zoom_smoothing: float = 12.0
|
||||
@export_range(0.1, 12.0, 0.1) var controller_zoom_speed: float = 4.0
|
||||
@export_range(0.0, 1.0, 0.01) var controller_trigger_threshold: float = 0.55
|
||||
@export_range(1.0, 40.0, 0.5) var free_camera_speed: float = 10.0
|
||||
@export_range(1.0, 10.0, 0.5) var free_camera_sprint_multiplier: float = 3.0
|
||||
|
||||
@onready var _visuals: Node3D = %Visuals
|
||||
@onready var _character_rig: Node3D = get_node_or_null(
|
||||
|
|
@ -141,6 +152,7 @@ class ShowcaseCameraSnapshot:
|
|||
@onready var _camera_pitch: Node3D = %CameraPitch
|
||||
@onready var _spring_arm: SpringArm3D = %SpringArm3D
|
||||
@onready var _camera: Camera3D = %Camera3D
|
||||
@onready var _player_collision_shape: CollisionShape3D = $CollisionShape3D
|
||||
@onready var inventory: FishInventoryType = %Inventory
|
||||
@onready var collection_log: CollectionLogType = %CollectionLog
|
||||
@onready var wallet: PlayerWalletType = %Wallet
|
||||
|
|
@ -165,6 +177,11 @@ var _catch_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04)
|
|||
var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity"))
|
||||
var _camera_dragging: bool = false
|
||||
var _camera_input_enabled: bool = true
|
||||
var _free_camera_active: bool = false
|
||||
var _free_camera_body: CharacterBody3D
|
||||
var _free_camera: Camera3D
|
||||
var _free_camera_yaw: float = 0.0
|
||||
var _free_camera_pitch: float = 0.0
|
||||
var _movement_enabled: bool = true
|
||||
var _water_recovery_active: bool = false
|
||||
var _remote_recovery_presentation_active: bool = false
|
||||
|
|
@ -197,6 +214,8 @@ var _network_target_visual_yaw: float = 0.0
|
|||
var _network_snapshot_ready: bool = false
|
||||
var _character_animation_name: StringName = &""
|
||||
var _sitting: bool = false
|
||||
var _sitting_intent_pending: bool = false
|
||||
var _sitting_intent_sequence: int = -1
|
||||
var _held_fish_visible: bool = false
|
||||
var _showcase_animation_active: bool = false
|
||||
var _fishing_rod: Node3D
|
||||
|
|
@ -205,6 +224,8 @@ var _controller_mapping_manager: ControllerMappingManagerType
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
if not bag.contents_changed.is_connected(_on_bag_contents_changed):
|
||||
bag.contents_changed.connect(_on_bag_contents_changed)
|
||||
PlayerVisualPresenter.apply_appearance(_visuals, appearance_snapshot)
|
||||
_initialize_fishing_rod()
|
||||
_target_zoom = clampf(_spring_arm.spring_length, minimum_zoom, maximum_zoom)
|
||||
|
|
@ -213,6 +234,14 @@ func _ready() -> void:
|
|||
_initialize_held_item_attachment()
|
||||
|
||||
|
||||
func _on_bag_contents_changed() -> void:
|
||||
if (
|
||||
not active_bait_id.is_empty()
|
||||
and bag.get_quantity(active_bait_id) <= 0
|
||||
):
|
||||
unequip_bait()
|
||||
|
||||
|
||||
func set_controller_mapping_manager(
|
||||
mapping_manager: ControllerMappingManagerType,
|
||||
) -> void:
|
||||
|
|
@ -267,8 +296,18 @@ func _physics_process(delta: float) -> void:
|
|||
if _network_interpolation_enabled:
|
||||
_update_network_interpolation(delta)
|
||||
return
|
||||
if local_control_enabled and _free_camera_active:
|
||||
_update_free_camera_physics()
|
||||
velocity.x = 0.0
|
||||
velocity.z = 0.0
|
||||
if not is_on_floor():
|
||||
velocity.y -= _gravity * fall_gravity_multiplier * delta
|
||||
move_and_slide()
|
||||
_network_jump_pending = false
|
||||
return
|
||||
var jump_requested: bool = (
|
||||
_movement_enabled
|
||||
and not _free_camera_active
|
||||
and (
|
||||
(local_control_enabled and Input.is_action_just_pressed("jump"))
|
||||
or (_network_authoritative_simulation and _network_jump_pending)
|
||||
|
|
@ -276,7 +315,7 @@ func _physics_process(delta: float) -> void:
|
|||
)
|
||||
if _sitting:
|
||||
if jump_requested:
|
||||
_set_sitting(false)
|
||||
_set_sitting(false, local_control_enabled)
|
||||
else:
|
||||
velocity = Vector3.ZERO
|
||||
_network_jump_pending = false
|
||||
|
|
@ -370,6 +409,8 @@ func _process(delta: float) -> void:
|
|||
)
|
||||
if not local_control_enabled:
|
||||
return
|
||||
if _free_camera_active:
|
||||
return
|
||||
|
||||
if (
|
||||
_camera_input_enabled
|
||||
|
|
@ -519,7 +560,7 @@ func toggle_sitting() -> void:
|
|||
])
|
||||
):
|
||||
return
|
||||
_set_sitting(should_sit)
|
||||
_set_sitting(should_sit, local_control_enabled)
|
||||
|
||||
|
||||
func _has_any_character_animation(candidates: Array[StringName]) -> bool:
|
||||
|
|
@ -531,10 +572,16 @@ func _has_any_character_animation(candidates: Array[StringName]) -> bool:
|
|||
return false
|
||||
|
||||
|
||||
func _set_sitting(should_sit: bool) -> void:
|
||||
func _set_sitting(
|
||||
should_sit: bool,
|
||||
is_local_intent: bool = false,
|
||||
) -> void:
|
||||
if _sitting == should_sit:
|
||||
return
|
||||
_sitting = should_sit
|
||||
if is_local_intent:
|
||||
_sitting_intent_pending = true
|
||||
_sitting_intent_sequence = -1
|
||||
if _sitting:
|
||||
velocity = Vector3.ZERO
|
||||
_character_animation_name = &""
|
||||
|
|
@ -548,6 +595,10 @@ func is_sitting() -> bool:
|
|||
func _unhandled_input(event: InputEvent) -> void:
|
||||
if not local_control_enabled or not _camera_input_enabled:
|
||||
return
|
||||
if event.is_action_pressed("toggle_free_camera"):
|
||||
_set_free_camera_active(not _free_camera_active)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
||||
if event.is_action("camera_drag"):
|
||||
_camera_dragging = event.is_pressed()
|
||||
|
|
@ -555,9 +606,14 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
return
|
||||
|
||||
if event is InputEventMouseMotion and _camera_dragging:
|
||||
if _free_camera_active:
|
||||
_rotate_free_camera(event.relative * mouse_sensitivity)
|
||||
else:
|
||||
_rotate_camera(event.relative * mouse_sensitivity)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if _free_camera_active:
|
||||
return
|
||||
|
||||
var mouse_zoom_in: bool = (
|
||||
event is InputEventMouseButton
|
||||
|
|
@ -617,6 +673,98 @@ func _rotate_camera(delta_rotation: Vector2) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _set_free_camera_active(active: bool) -> void:
|
||||
if _free_camera_active == active:
|
||||
return
|
||||
if active:
|
||||
var camera_transform: Transform3D = _camera.global_transform
|
||||
var duplicated_camera := _camera.duplicate() as Camera3D
|
||||
if duplicated_camera == null:
|
||||
return
|
||||
var camera_body := CharacterBody3D.new()
|
||||
camera_body.name = "FreeCameraBody"
|
||||
camera_body.collision_layer = 0
|
||||
camera_body.collision_mask = collision_mask
|
||||
camera_body.motion_mode = CharacterBody3D.MOTION_MODE_FLOATING
|
||||
add_child(camera_body)
|
||||
camera_body.top_level = true
|
||||
camera_body.global_position = camera_transform.origin
|
||||
var camera_collision := CollisionShape3D.new()
|
||||
camera_collision.name = "FreeCameraCollision"
|
||||
camera_collision.shape = _player_collision_shape.shape
|
||||
camera_body.add_child(camera_collision)
|
||||
duplicated_camera.name = "FreeCamera"
|
||||
duplicated_camera.unique_name_in_owner = false
|
||||
camera_body.add_child(duplicated_camera)
|
||||
duplicated_camera.transform = Transform3D(
|
||||
camera_transform.basis,
|
||||
Vector3.ZERO,
|
||||
)
|
||||
_free_camera_body = camera_body
|
||||
_free_camera = duplicated_camera
|
||||
var camera_rotation: Vector3 = _free_camera.global_rotation
|
||||
_free_camera_yaw = camera_rotation.y
|
||||
_free_camera_pitch = camera_rotation.x
|
||||
_camera.current = false
|
||||
_free_camera.current = true
|
||||
_free_camera_active = true
|
||||
return
|
||||
_free_camera_active = false
|
||||
_camera_dragging = false
|
||||
if _free_camera != null:
|
||||
_free_camera.current = false
|
||||
_free_camera = null
|
||||
if _free_camera_body != null:
|
||||
_free_camera_body.queue_free()
|
||||
_free_camera_body = null
|
||||
_camera.current = local_control_enabled
|
||||
|
||||
|
||||
func _update_free_camera_physics() -> void:
|
||||
if _free_camera == null or _free_camera_body == null:
|
||||
_set_free_camera_active(false)
|
||||
return
|
||||
var input_vector: Vector2 = Input.get_vector(
|
||||
"move_left",
|
||||
"move_right",
|
||||
"move_forward",
|
||||
"move_backward",
|
||||
)
|
||||
var vertical_input: float = (
|
||||
Input.get_action_strength("jump")
|
||||
- Input.get_action_strength("sneak")
|
||||
)
|
||||
var movement: Vector3 = (
|
||||
_free_camera.global_basis.x * input_vector.x
|
||||
+ _free_camera.global_basis.z * input_vector.y
|
||||
+ Vector3.UP * vertical_input
|
||||
)
|
||||
if movement.length_squared() > 1.0:
|
||||
movement = movement.normalized()
|
||||
var movement_speed: float = free_camera_speed
|
||||
if Input.is_action_pressed("sprint"):
|
||||
movement_speed *= free_camera_sprint_multiplier
|
||||
_free_camera_body.velocity = movement * movement_speed
|
||||
_free_camera_body.move_and_slide()
|
||||
|
||||
|
||||
func _rotate_free_camera(delta_rotation: Vector2) -> void:
|
||||
if _free_camera == null:
|
||||
return
|
||||
_free_camera_yaw -= delta_rotation.x
|
||||
var vertical_direction: float = -1.0 if invert_camera_y else 1.0
|
||||
_free_camera_pitch = clampf(
|
||||
_free_camera_pitch - delta_rotation.y * vertical_direction,
|
||||
deg_to_rad(minimum_pitch_degrees),
|
||||
deg_to_rad(maximum_pitch_degrees),
|
||||
)
|
||||
_free_camera.global_rotation = Vector3(
|
||||
_free_camera_pitch,
|
||||
_free_camera_yaw,
|
||||
0.0,
|
||||
)
|
||||
|
||||
|
||||
func _set_target_zoom(value: float) -> void:
|
||||
_target_zoom = clampf(value, minimum_zoom, maximum_zoom)
|
||||
|
||||
|
|
@ -633,6 +781,8 @@ func _apply_axis_deadzone(value: float) -> float:
|
|||
|
||||
|
||||
func set_local_control(enabled: bool) -> void:
|
||||
if not enabled and _free_camera_active:
|
||||
_set_free_camera_active(false)
|
||||
local_control_enabled = enabled
|
||||
if is_node_ready():
|
||||
_camera.current = enabled
|
||||
|
|
@ -657,7 +807,9 @@ func configure_network_remote(authoritative_simulation: bool) -> void:
|
|||
|
||||
|
||||
func capture_network_input(sequence: int) -> Dictionary:
|
||||
if not _movement_enabled or _water_recovery_active:
|
||||
if _sitting_intent_pending and _sitting_intent_sequence < 0:
|
||||
_sitting_intent_sequence = sequence
|
||||
if not _movement_enabled or _water_recovery_active or _free_camera_active:
|
||||
return {
|
||||
"sequence": sequence,
|
||||
"axis": [0.0, 0.0],
|
||||
|
|
@ -734,6 +886,16 @@ func apply_local_prediction_correction(snapshot: Dictionary) -> void:
|
|||
var parsed: Dictionary = _parse_network_snapshot(snapshot)
|
||||
if parsed.is_empty():
|
||||
return
|
||||
var acknowledged_input: int = parsed["acknowledged_input"]
|
||||
var sitting_intent_acknowledged: bool = (
|
||||
_sitting_intent_pending
|
||||
and _sitting_intent_sequence >= 0
|
||||
and acknowledged_input >= _sitting_intent_sequence
|
||||
)
|
||||
if sitting_intent_acknowledged:
|
||||
_sitting_intent_pending = false
|
||||
_sitting_intent_sequence = -1
|
||||
if not _sitting_intent_pending:
|
||||
_set_sitting(bool(parsed["sitting"]))
|
||||
var authoritative_position: Vector3 = parsed["position"]
|
||||
var error_distance: float = global_position.distance_to(
|
||||
|
|
@ -780,6 +942,17 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary:
|
|||
float(network_velocity[2])
|
||||
)
|
||||
var visual_yaw: float = float(snapshot.get("visual_yaw", 0.0))
|
||||
if (
|
||||
snapshot.has("acknowledged_input")
|
||||
and (
|
||||
typeof(snapshot.get("acknowledged_input")) != TYPE_INT
|
||||
or int(snapshot.get("acknowledged_input")) < 0
|
||||
)
|
||||
):
|
||||
return {}
|
||||
var acknowledged_input: int = int(
|
||||
snapshot.get("acknowledged_input", 0)
|
||||
)
|
||||
if snapshot.has("sitting") and typeof(snapshot.get("sitting")) != TYPE_BOOL:
|
||||
return {}
|
||||
var sitting: bool = bool(snapshot.get("sitting", false))
|
||||
|
|
@ -793,6 +966,7 @@ func _parse_network_snapshot(snapshot: Dictionary) -> Dictionary:
|
|||
"position": parsed_position,
|
||||
"velocity": parsed_velocity,
|
||||
"visual_yaw": visual_yaw,
|
||||
"acknowledged_input": acknowledged_input,
|
||||
"sitting": sitting,
|
||||
}
|
||||
|
||||
|
|
@ -835,6 +1009,9 @@ func set_camera_input_enabled(enabled: bool) -> void:
|
|||
|
||||
|
||||
func set_camera_active(active: bool) -> void:
|
||||
if _free_camera_active and _free_camera != null:
|
||||
_free_camera.current = active
|
||||
else:
|
||||
_camera.current = active
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ class LoadSnapshot:
|
|||
var wallet_balance: int = 0
|
||||
var next_catch_sequence: int = 1
|
||||
var bag_items: Array[OwnedItemType] = []
|
||||
var unlocked_bait_ids: Array[StringName] = []
|
||||
var hotbar_slots: Array[StringName] = []
|
||||
var fish_hotbar_slots: Array[StringName] = []
|
||||
var selected_hotbar_slot: int = 0
|
||||
|
|
@ -258,7 +259,10 @@ func load_player_data() -> bool:
|
|||
var wallet_restored: bool = _wallet.restore_balance(
|
||||
snapshot.wallet_balance
|
||||
)
|
||||
var bag_restored: bool = _bag.replace_all_items(snapshot.bag_items)
|
||||
var bag_restored: bool = (
|
||||
_bag.replace_all_items(snapshot.bag_items)
|
||||
and _bag.replace_unlocked_bait_ids(snapshot.unlocked_bait_ids)
|
||||
)
|
||||
var hotbar_restored: bool = _hotbar.replace_state(
|
||||
snapshot.hotbar_slots,
|
||||
snapshot.selected_hotbar_slot,
|
||||
|
|
@ -492,6 +496,12 @@ func _build_save_dictionary() -> Dictionary:
|
|||
if owned == null or not owned.is_valid():
|
||||
return {}
|
||||
serialized_items.append(owned.to_save_dict())
|
||||
var serialized_unlocked_baits: Array[String] = []
|
||||
for item_id: StringName in _bag.get_unlocked_bait_ids():
|
||||
var item_data = _item_catalog.get_item_by_id(item_id)
|
||||
if item_data == null or not item_data.is_valid() or not item_data.is_bait():
|
||||
return {}
|
||||
serialized_unlocked_baits.append(String(item_id))
|
||||
var serialized_slots: Array[String] = []
|
||||
for item_id: StringName in _hotbar.get_slots():
|
||||
serialized_slots.append(String(item_id))
|
||||
|
|
@ -524,6 +534,7 @@ func _build_save_dictionary() -> Dictionary:
|
|||
},
|
||||
"bag": {
|
||||
"items": serialized_items,
|
||||
"unlocked_bait_ids": serialized_unlocked_baits,
|
||||
},
|
||||
"hotbar": {
|
||||
"selected_slot": _hotbar.get_selected_slot(),
|
||||
|
|
@ -589,6 +600,10 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
|
|||
or typeof(inventory_data.get("catches")) != TYPE_ARRAY
|
||||
or not inventory_data.has("next_catch_sequence")
|
||||
or typeof(bag_data.get("items")) != TYPE_ARRAY
|
||||
or (
|
||||
bag_data.has("unlocked_bait_ids")
|
||||
and typeof(bag_data.get("unlocked_bait_ids")) != TYPE_ARRAY
|
||||
)
|
||||
or typeof(hotbar_data.get("slots")) != TYPE_ARRAY
|
||||
or not hotbar_data.has("selected_slot")
|
||||
or not experience_data.has("total_experience")
|
||||
|
|
@ -781,6 +796,34 @@ func _build_load_snapshot(save_data: Dictionary) -> LoadSnapshot:
|
|||
seen_items[item_id] = true
|
||||
snapshot.bag_items.append(owned)
|
||||
|
||||
var seen_unlocked_baits: Dictionary[StringName, bool] = {}
|
||||
if bag_data.has("unlocked_bait_ids"):
|
||||
var unlocked_bait_values: Array = bag_data["unlocked_bait_ids"]
|
||||
for value: Variant in unlocked_bait_values:
|
||||
if typeof(value) not in [TYPE_STRING, TYPE_STRING_NAME]:
|
||||
continue
|
||||
var bait_id: StringName = StringName(str(value))
|
||||
var bait_data = _item_catalog.get_item_by_id(bait_id)
|
||||
if (
|
||||
bait_id.is_empty()
|
||||
or seen_unlocked_baits.has(bait_id)
|
||||
or bait_data == null
|
||||
or not bait_data.is_valid()
|
||||
or not bait_data.is_bait()
|
||||
):
|
||||
continue
|
||||
seen_unlocked_baits[bait_id] = true
|
||||
snapshot.unlocked_bait_ids.append(bait_id)
|
||||
for owned: OwnedItemType in snapshot.bag_items:
|
||||
var owned_item_data = _item_catalog.get_item_by_id(owned.item_id)
|
||||
if (
|
||||
owned_item_data != null
|
||||
and owned_item_data.is_bait()
|
||||
and not seen_unlocked_baits.has(owned.item_id)
|
||||
):
|
||||
seen_unlocked_baits[owned.item_id] = true
|
||||
snapshot.unlocked_bait_ids.append(owned.item_id)
|
||||
|
||||
var slot_values: Array = hotbar_data["slots"]
|
||||
snapshot.hotbar_slots.resize(PlayerHotbarType.SLOT_COUNT)
|
||||
snapshot.hotbar_slots.fill(StringName())
|
||||
|
|
@ -1098,6 +1141,8 @@ func _restore_defaults() -> void:
|
|||
)
|
||||
_wallet.restore_balance(0)
|
||||
_bag.replace_all_items(default_items)
|
||||
var default_unlocked_baits: Array[StringName] = []
|
||||
_bag.replace_unlocked_bait_ids(default_unlocked_baits)
|
||||
_hotbar.replace_state(default_slots, 0)
|
||||
_fishing_upgrades.reset_to_defaults()
|
||||
_cooler_capacity.reset_to_defaults()
|
||||
|
|
|
|||
71
scripts/generate_placeholder_animalese.gd
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
extends SceneTree
|
||||
|
||||
const OUTPUT_DIRECTORY := "res://sound/dialogue/animalese/placeholder"
|
||||
const SAMPLE_RATE: int = 22050
|
||||
const SAMPLE_SECONDS: float = 0.055
|
||||
const CHARACTERS := "abcdefghijklmnopqrstuvwxyz"
|
||||
const VOWELS := "aeiou"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var absolute_directory := ProjectSettings.globalize_path(OUTPUT_DIRECTORY)
|
||||
var directory_error: Error = DirAccess.make_dir_recursive_absolute(
|
||||
absolute_directory
|
||||
)
|
||||
if directory_error != OK:
|
||||
push_error("Could not create animalese sample directory.")
|
||||
quit(1)
|
||||
return
|
||||
for character_index: int in range(CHARACTERS.length()):
|
||||
var character := CHARACTERS.substr(character_index, 1)
|
||||
_save_sample(character, character_index)
|
||||
_save_sample("fallback", CHARACTERS.length())
|
||||
quit()
|
||||
|
||||
|
||||
func _save_sample(sample_name: String, sample_index: int) -> void:
|
||||
var stream := _build_sample(sample_name, sample_index)
|
||||
var output_path := "%s/%s" % [OUTPUT_DIRECTORY, sample_name]
|
||||
var save_error: Error = stream.save_to_wav(output_path)
|
||||
if save_error != OK:
|
||||
push_error("Could not save animalese sample: %s" % sample_name)
|
||||
|
||||
|
||||
func _build_sample(sample_name: String, sample_index: int) -> AudioStreamWAV:
|
||||
var frame_count := roundi(SAMPLE_RATE * SAMPLE_SECONDS)
|
||||
var pcm := PackedByteArray()
|
||||
pcm.resize(frame_count * 2)
|
||||
var base_frequency := 245.0 + float(sample_index % 7) * 18.0
|
||||
var formant_one := 620.0 + float(sample_index % 5) * 85.0
|
||||
var formant_two := 1320.0 + float(sample_index % 6) * 115.0
|
||||
var is_vowel := VOWELS.contains(sample_name)
|
||||
var noise_seed := sample_index * 7919 + 104729
|
||||
for frame_index: int in range(frame_count):
|
||||
var time := float(frame_index) / SAMPLE_RATE
|
||||
var attack := minf(time / 0.004, 1.0)
|
||||
var release := minf((SAMPLE_SECONDS - time) / 0.014, 1.0)
|
||||
var envelope := maxf(minf(attack, release), 0.0)
|
||||
noise_seed = int(
|
||||
(noise_seed * 1103515245 + 12345) & 0x7fffffff
|
||||
)
|
||||
var noise := float(noise_seed % 65536) / 32767.5 - 1.0
|
||||
var voiced := (
|
||||
sin(TAU * base_frequency * time) * 0.42
|
||||
+ sin(TAU * base_frequency * 2.0 * time) * 0.16
|
||||
)
|
||||
var formants := (
|
||||
sin(TAU * formant_one * time) * 0.18
|
||||
+ sin(TAU * formant_two * time) * 0.10
|
||||
)
|
||||
var consonant_noise := noise * (0.08 if is_vowel else 0.24)
|
||||
var value := envelope * (voiced + formants + consonant_noise) * 0.72
|
||||
var signed_sample := clampi(roundi(value * 32767.0), -32768, 32767)
|
||||
var encoded_sample := signed_sample if signed_sample >= 0 else signed_sample + 65536
|
||||
pcm[frame_index * 2] = encoded_sample & 0xff
|
||||
pcm[frame_index * 2 + 1] = (encoded_sample >> 8) & 0xff
|
||||
var stream := AudioStreamWAV.new()
|
||||
stream.set("format", AudioStreamWAV.FORMAT_16_BITS)
|
||||
stream.mix_rate = SAMPLE_RATE
|
||||
stream.stereo = false
|
||||
stream.data = pcm
|
||||
return stream
|
||||
1
scripts/generate_placeholder_animalese.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://cwqm75icgcr4u
|
||||
|
|
@ -25,30 +25,27 @@ const DEFAULT_PROFILE: Dictionary = {
|
|||
"process/hdr_clamp_exposure": false,
|
||||
"process/size_limit": 0,
|
||||
"detect_3d/compress_to": 0,
|
||||
"import_profile": "managed_default",
|
||||
},
|
||||
}
|
||||
|
||||
const CUSTOM_PROFILE_KEY := "custom"
|
||||
|
||||
var apply_changes := false
|
||||
var verbose := false
|
||||
var apply_changes: bool = false
|
||||
var verbose: bool = false
|
||||
var roots: PackedStringArray = PackedStringArray()
|
||||
var user_root_overrides := false
|
||||
var should_exit := false
|
||||
var should_exit: bool = false
|
||||
|
||||
var skipped_custom := 0
|
||||
var skipped_non_texture := 0
|
||||
var skipped_unreadable := 0
|
||||
var updated := 0
|
||||
var unchanged := 0
|
||||
var errors: int = 0
|
||||
var skipped_non_texture: int = 0
|
||||
var updated: int = 0
|
||||
var unchanged: int = 0
|
||||
|
||||
func _initialize() -> void:
|
||||
_parse_args()
|
||||
if should_exit:
|
||||
return
|
||||
if roots.is_empty() and not user_root_overrides:
|
||||
roots.append("res://")
|
||||
if roots.is_empty():
|
||||
printerr("At least one --root path is required.")
|
||||
quit(2)
|
||||
return
|
||||
|
||||
for root: String in roots:
|
||||
var normalized_root := root
|
||||
|
|
@ -63,7 +60,7 @@ func _initialize() -> void:
|
|||
_scan_directory(normalized_root)
|
||||
|
||||
print_summary()
|
||||
quit(0)
|
||||
quit(1 if errors > 0 else 0)
|
||||
|
||||
|
||||
func _parse_args() -> void:
|
||||
|
|
@ -91,23 +88,21 @@ func _parse_args() -> void:
|
|||
i += 1
|
||||
roots.append(args[i])
|
||||
i += 1
|
||||
user_root_overrides = true
|
||||
elif arg.begins_with("--root="):
|
||||
roots.append(arg.trim_prefix("--root="))
|
||||
i += 1
|
||||
user_root_overrides = true
|
||||
else:
|
||||
if arg.begins_with("-"):
|
||||
_print("Ignoring unknown option: %s" % arg)
|
||||
i += 1
|
||||
continue
|
||||
printerr("Unknown argument: ", arg)
|
||||
should_exit = true
|
||||
quit(2)
|
||||
return
|
||||
|
||||
|
||||
func _scan_directory(path: String) -> void:
|
||||
var dir := DirAccess.open(path)
|
||||
if dir == null:
|
||||
printerr("Failed to open root: ", path)
|
||||
skipped_non_texture += 1
|
||||
errors += 1
|
||||
return
|
||||
|
||||
dir.list_dir_begin()
|
||||
|
|
@ -136,7 +131,7 @@ func _handle_import_file(import_path: String) -> void:
|
|||
var err := cfg.load(import_path)
|
||||
if err != OK:
|
||||
printerr("Could not load import settings: ", import_path, " (", err, ")")
|
||||
skipped_unreadable += 1
|
||||
errors += 1
|
||||
return
|
||||
|
||||
if not cfg.has_section("remap") or not cfg.has_section_key("remap", "importer"):
|
||||
|
|
@ -147,15 +142,6 @@ func _handle_import_file(import_path: String) -> void:
|
|||
skipped_non_texture += 1
|
||||
return
|
||||
|
||||
var current_profile := ""
|
||||
if cfg.has_section("params") and cfg.has_section_key("params", "import_profile"):
|
||||
current_profile = str(cfg.get_value("params", "import_profile"))
|
||||
if current_profile == CUSTOM_PROFILE_KEY:
|
||||
skipped_custom += 1
|
||||
if verbose:
|
||||
_print("Skipping custom profile: %s" % import_path)
|
||||
return
|
||||
|
||||
var changed := false
|
||||
for section_name: String in DEFAULT_PROFILE.keys():
|
||||
var section_data: Dictionary = DEFAULT_PROFILE[section_name]
|
||||
|
|
@ -178,7 +164,7 @@ func _handle_import_file(import_path: String) -> void:
|
|||
err = cfg.save(import_path)
|
||||
if err != OK:
|
||||
printerr("Could not save import settings: ", import_path, " (", err, ")")
|
||||
skipped_unreadable += 1
|
||||
errors += 1
|
||||
return
|
||||
if verbose:
|
||||
_print("Updated: %s" % import_path)
|
||||
|
|
@ -190,11 +176,18 @@ func _handle_import_file(import_path: String) -> void:
|
|||
|
||||
func print_summary() -> void:
|
||||
if apply_changes:
|
||||
_print("Texture import normalization complete: updated=%d unchanged=%d skipped_custom=%d skipped_non_texture=%d skipped_unreadable=%d" % [updated, unchanged, skipped_custom, skipped_non_texture, skipped_unreadable])
|
||||
_print(
|
||||
"Texture import normalization complete: "
|
||||
+ "updated=%d unchanged=%d skipped_non_texture=%d errors=%d"
|
||||
% [updated, unchanged, skipped_non_texture, errors]
|
||||
)
|
||||
else:
|
||||
_print("Dry-run complete: would-update=%d unchanged=%d skipped_custom=%d skipped_non_texture=%d skipped_unreadable=%d" % [updated, unchanged, skipped_custom, skipped_non_texture, skipped_unreadable])
|
||||
_print(
|
||||
"Dry-run complete: "
|
||||
+ "would-update=%d unchanged=%d skipped_non_texture=%d errors=%d"
|
||||
% [updated, unchanged, skipped_non_texture, errors]
|
||||
)
|
||||
_print("Pass --apply to write normalized settings into .import files.")
|
||||
_print("Set [params] import_profile = \"custom\" to opt a texture out of default normalization.")
|
||||
|
||||
|
||||
func _print(msg: String) -> void:
|
||||
|
|
@ -207,6 +200,5 @@ func _print_usage() -> void:
|
|||
print()
|
||||
print("Defaults:")
|
||||
print("- Dry-run only unless --apply is provided")
|
||||
print("- Scans all .import files under each root")
|
||||
print("- Requires at least one explicit --root path")
|
||||
print("- Skips .godot")
|
||||
print("- Skips textures marked with [params] import_profile = \"custom\"")
|
||||
|
|
|
|||
|
|
@ -5,15 +5,26 @@ set -euo pipefail
|
|||
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||
readonly PROJECT_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
|
||||
readonly GODOT_BIN="${GODOT_BIN:-godot}"
|
||||
readonly ROOT_PATH="${1:-res://}"
|
||||
|
||||
if [[ $# -ne 1 ]]; then
|
||||
echo "usage: scripts/normalize_texture_profiles.sh res://path/to/textures" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
readonly ROOT_PATH="$1"
|
||||
|
||||
readonly WORK_ROOT="$(mktemp -d -t netfishing-godot-data.XXXXXX)"
|
||||
trap 'rm -rf -- "${WORK_ROOT}"' EXIT
|
||||
|
||||
cd "${PROJECT_ROOT}"
|
||||
|
||||
XDG_DATA_HOME="${WORK_ROOT}/data" \
|
||||
XDG_CONFIG_HOME="${WORK_ROOT}/config" \
|
||||
XDG_CACHE_HOME="${WORK_ROOT}/cache" \
|
||||
${GODOT_BIN} --headless --path . \
|
||||
export XDG_DATA_HOME="${WORK_ROOT}/data"
|
||||
export XDG_CONFIG_HOME="${WORK_ROOT}/config"
|
||||
export XDG_CACHE_HOME="${WORK_ROOT}/cache"
|
||||
|
||||
"${GODOT_BIN}" --headless --path . \
|
||||
--script scripts/normalize_texture_imports.gd -- --apply --root "${ROOT_PATH}"
|
||||
|
||||
# Synchronize Godot-generated remap and destination metadata with the updated
|
||||
# import parameters before the caller reviews or commits the sidecars.
|
||||
"${GODOT_BIN}" --headless --path . --import
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ readonly -a QUICK_TESTS=(
|
|||
"tests/player_experience_validation.gd"
|
||||
"tests/shoreline_ambience_validation.gd"
|
||||
"tests/surface_drawing_validation.gd"
|
||||
"tests/terrain_projection_validation.gd"
|
||||
"tests/terrain_blender_material_validation.gd"
|
||||
"tests/world_time_validation.gd"
|
||||
"tests/world_weather_validation.gd"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ANDROID_HOME="${ANDROID_HOME:-/home/sol6_vi/Android/Sdk}"
|
||||
ANDROID_HOME="${ANDROID_HOME:-${HOME}/Android/Sdk}"
|
||||
JAVA_HOME="${JAVA_HOME:-}"
|
||||
INSTALL=0
|
||||
AUTO=0
|
||||
|
|
@ -55,6 +55,9 @@ done
|
|||
|
||||
PROJECT_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
GODOT="${GODOT_BIN:-godot}"
|
||||
PROJECT_VERSION="$(
|
||||
sed -n 's/^config\/version="\([^"]*\)"/\1/p' "${PROJECT_ROOT}/project.godot"
|
||||
)"
|
||||
MISSING=0
|
||||
|
||||
print_status() { printf '%s %s\n' "[$1]" "$2"; }
|
||||
|
|
@ -71,7 +74,7 @@ check_dir() {
|
|||
|
||||
check_file() {
|
||||
local path="$1" label="$2"
|
||||
if [[ -x "$path" || -f "$path" ]]; then
|
||||
if [[ -x "$path" ]]; then
|
||||
print_status OK "${label}: ${path}"
|
||||
else
|
||||
print_status ERR "Missing ${label}: ${path}"
|
||||
|
|
@ -113,6 +116,8 @@ resolve_java_home() {
|
|||
check_java() {
|
||||
if resolve_java_home && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
print_status OK "Java binary: $JAVA_HOME/bin/java"
|
||||
check_file "$JAVA_HOME/bin/javac" "javac binary"
|
||||
check_file "$JAVA_HOME/bin/keytool" "keytool binary"
|
||||
"$JAVA_HOME/bin/java" -version 2>&1 | sed -n '1,2p'
|
||||
local java_ver
|
||||
java_ver=$("$JAVA_HOME/bin/java" -version 2>&1 | sed -n '1p')
|
||||
|
|
@ -161,18 +166,13 @@ check_sdk_paths() {
|
|||
check_dir "$ANDROID_HOME/platform-tools" "platform-tools"
|
||||
check_file "$ANDROID_HOME/platform-tools/adb" "adb binary"
|
||||
|
||||
if [[ -d "$ANDROID_HOME/build-tools" ]]; then
|
||||
local bt
|
||||
bt="$(ls -1v "$ANDROID_HOME/build-tools" 2>/dev/null | tail -n 1 || true)"
|
||||
if [[ -n "$bt" ]]; then
|
||||
print_status OK "build-tools versions: $(ls -1 "$ANDROID_HOME/build-tools" | tr '\n' ' ')"
|
||||
check_file "$ANDROID_HOME/build-tools/$bt/apksigner" "apksigner"
|
||||
if [[ -d "$ANDROID_HOME/build-tools/$BUILDTOOLS_VERSION" ]]; then
|
||||
print_status OK "build-tools ${BUILDTOOLS_VERSION}: present"
|
||||
check_file \
|
||||
"$ANDROID_HOME/build-tools/$BUILDTOOLS_VERSION/apksigner" \
|
||||
"apksigner"
|
||||
else
|
||||
print_status ERR "build-tools directory exists but is empty"
|
||||
MISSING=$((MISSING+1))
|
||||
fi
|
||||
else
|
||||
print_status ERR "Missing build-tools root: $ANDROID_HOME/build-tools"
|
||||
print_status ERR "Missing build-tools version: ${BUILDTOOLS_VERSION}"
|
||||
MISSING=$((MISSING+1))
|
||||
fi
|
||||
|
||||
|
|
@ -198,6 +198,7 @@ check_sdk_paths() {
|
|||
|
||||
install_jdk_hint() {
|
||||
local pm=0
|
||||
local -a elevate=()
|
||||
echo "Attempting automatic JDK install..."
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
pm="apt-get"
|
||||
|
|
@ -212,19 +213,18 @@ install_jdk_hint() {
|
|||
fi
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
local sudo_cmd=(sudo)
|
||||
if ! command -v sudo >/dev/null 2>&1; then
|
||||
echo "sudo not found; please run with root privileges for package install."
|
||||
return 1
|
||||
fi
|
||||
elevate=(sudo)
|
||||
fi
|
||||
|
||||
if [[ "$pm" == "apt-get" ]]; then
|
||||
echo "apt-get install -y openjdk-17-jdk"
|
||||
if [[ $AUTO -eq 1 ]]; then
|
||||
if command -v sudo >/dev/null 2>&1; then
|
||||
sudo apt-get update && sudo apt-get install -y openjdk-17-jdk
|
||||
fi
|
||||
"${elevate[@]}" apt-get update
|
||||
"${elevate[@]}" apt-get install -y openjdk-17-jdk
|
||||
else
|
||||
echo "Run as manual step first: sudo apt-get update && sudo apt-get install -y openjdk-17-jdk"
|
||||
return 1
|
||||
|
|
@ -232,14 +232,14 @@ install_jdk_hint() {
|
|||
elif [[ "$pm" == "dnf" ]]; then
|
||||
echo "dnf install -y java-17-openjdk-devel"
|
||||
if [[ $AUTO -eq 1 ]]; then
|
||||
sudo dnf install -y java-17-openjdk-devel
|
||||
"${elevate[@]}" dnf install -y java-17-openjdk-devel
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo "pacman -S --noconfirm jdk17-openjdk"
|
||||
if [[ $AUTO -eq 1 ]]; then
|
||||
sudo pacman -S --noconfirm jdk17-openjdk
|
||||
"${elevate[@]}" pacman -S --noconfirm jdk17-openjdk
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
|
|
@ -305,7 +305,7 @@ install_sdk_components() {
|
|||
echo "Cannot install SDK packages without sdkmanager."
|
||||
return 1
|
||||
fi
|
||||
"$sdkmanager_path" --sdk_root="${ANDROID_HOME}" --list | head -n 40 | sed -n '1,40p'
|
||||
"$sdkmanager_path" --sdk_root="${ANDROID_HOME}" --list | sed -n '1,40p'
|
||||
echo "Installing required SDK components: platform-tools, platform-android-${SDK_VERSION}, build-tools-${BUILDTOOLS_VERSION}"
|
||||
if [[ $AUTO -eq 1 ]]; then
|
||||
yes | "$sdkmanager_path" --sdk_root="${ANDROID_HOME}" --install "platform-tools" "platforms;android-${SDK_VERSION}" "build-tools;${BUILDTOOLS_VERSION}"
|
||||
|
|
@ -336,7 +336,7 @@ check_editor_settings() {
|
|||
run_export_hint() {
|
||||
echo
|
||||
echo "Example export commands:"
|
||||
echo " ${GODOT} --headless --path \"$PROJECT_ROOT\" --export-release \"Android\" builds/v0.5.1-prealpha/android/NETFISHING.apk"
|
||||
echo " ${GODOT} --headless --path \"$PROJECT_ROOT\" --export-release \"Android\" builds/${PROJECT_VERSION}/android/NETfishing.apk"
|
||||
echo " ${GODOT} --headless --path \"$PROJECT_ROOT\" --export-debug \"Android\" builds/android/NETfishing-debug.apk"
|
||||
}
|
||||
|
||||
|
|
@ -354,7 +354,12 @@ fi
|
|||
if [[ $INSTALL -eq 1 ]]; then
|
||||
echo
|
||||
print_status INFO "Attempting dependency install path..."
|
||||
if [[ -z "$JAVA_HOME" || ! -x "$JAVA_HOME/bin/java" ]]; then
|
||||
if [[ \
|
||||
-z "$JAVA_HOME" \
|
||||
|| ! -x "$JAVA_HOME/bin/java" \
|
||||
|| ! -x "$JAVA_HOME/bin/javac" \
|
||||
|| ! -x "$JAVA_HOME/bin/keytool" \
|
||||
]]; then
|
||||
install_jdk_hint || true
|
||||
fi
|
||||
|
||||
|
|
@ -377,11 +382,11 @@ if [[ $INSTALL -eq 1 ]]; then
|
|||
fi
|
||||
|
||||
print_status ERR "Dependency check failed with ${MISSING} issue(s)."
|
||||
cat <<'TIPS'
|
||||
cat <<TIPS
|
||||
Suggested manual recovery:
|
||||
1) Install OpenJDK 17 JDK.
|
||||
2) Install Android SDK command line tools and run:
|
||||
sdkmanager --sdk_root="${ANDROID_HOME}" --install "platform-tools" "platforms;android-33" "build-tools;34.0.0"
|
||||
sdkmanager --sdk_root="${ANDROID_HOME}" --install "platform-tools" "platforms;android-${SDK_VERSION}" "build-tools;${BUILDTOOLS_VERSION}"
|
||||
3) Set JAVA_HOME and ANDROID_HOME in your shell and Godot Exporter settings.
|
||||
4) Re-run this script without --install (verification), then re-run Godot Android export.
|
||||
TIPS
|
||||
|
|
|
|||
BIN
sound/dialogue/animalese/placeholder/a.wav
Normal file
24
sound/dialogue/animalese/placeholder/a.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dvajjy4bwadio"
|
||||
path="res://.godot/imported/a.wav-9ab4b1a9a555e6fcf07951f399769b30.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/a.wav"
|
||||
dest_files=["res://.godot/imported/a.wav-9ab4b1a9a555e6fcf07951f399769b30.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/b.wav
Normal file
24
sound/dialogue/animalese/placeholder/b.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://cs30tdyuo6wtc"
|
||||
path="res://.godot/imported/b.wav-eed860af2033bda258bd172899061b45.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/b.wav"
|
||||
dest_files=["res://.godot/imported/b.wav-eed860af2033bda258bd172899061b45.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/c.wav
Normal file
24
sound/dialogue/animalese/placeholder/c.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://2inox7x31xam"
|
||||
path="res://.godot/imported/c.wav-61dc49533634805d70c71d509b63e37b.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/c.wav"
|
||||
dest_files=["res://.godot/imported/c.wav-61dc49533634805d70c71d509b63e37b.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/d.wav
Normal file
24
sound/dialogue/animalese/placeholder/d.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://yvha8eukw6l7"
|
||||
path="res://.godot/imported/d.wav-910bc02bc42bdb1b37561959a317c28a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/d.wav"
|
||||
dest_files=["res://.godot/imported/d.wav-910bc02bc42bdb1b37561959a317c28a.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/e.wav
Normal file
24
sound/dialogue/animalese/placeholder/e.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://8ujopf8aqs83"
|
||||
path="res://.godot/imported/e.wav-7da5f8fcd017bf931de2bd49dc00f652.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/e.wav"
|
||||
dest_files=["res://.godot/imported/e.wav-7da5f8fcd017bf931de2bd49dc00f652.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/f.wav
Normal file
24
sound/dialogue/animalese/placeholder/f.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://c0gv4cphnfcxf"
|
||||
path="res://.godot/imported/f.wav-8eb31f2de6240647a0d6d2520aadcd6a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/f.wav"
|
||||
dest_files=["res://.godot/imported/f.wav-8eb31f2de6240647a0d6d2520aadcd6a.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/fallback.wav
Normal file
24
sound/dialogue/animalese/placeholder/fallback.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://bw0pod4tmexm0"
|
||||
path="res://.godot/imported/fallback.wav-7a40516b188f10028ac39bdc24fb52e5.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/fallback.wav"
|
||||
dest_files=["res://.godot/imported/fallback.wav-7a40516b188f10028ac39bdc24fb52e5.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/g.wav
Normal file
24
sound/dialogue/animalese/placeholder/g.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://c7706p0fc8y7c"
|
||||
path="res://.godot/imported/g.wav-45b2cbd9653a31477e3f00c9432d560c.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/g.wav"
|
||||
dest_files=["res://.godot/imported/g.wav-45b2cbd9653a31477e3f00c9432d560c.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/h.wav
Normal file
24
sound/dialogue/animalese/placeholder/h.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://cl66wka105xmo"
|
||||
path="res://.godot/imported/h.wav-2da53a6638eebb75f0867eaf75dc24ba.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/h.wav"
|
||||
dest_files=["res://.godot/imported/h.wav-2da53a6638eebb75f0867eaf75dc24ba.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/i.wav
Normal file
24
sound/dialogue/animalese/placeholder/i.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://jicgdbnyt6bm"
|
||||
path="res://.godot/imported/i.wav-1e8d7f13980a6f2f61d73f65a2efc91c.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/i.wav"
|
||||
dest_files=["res://.godot/imported/i.wav-1e8d7f13980a6f2f61d73f65a2efc91c.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/j.wav
Normal file
24
sound/dialogue/animalese/placeholder/j.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://cji2evr0u3m6c"
|
||||
path="res://.godot/imported/j.wav-2f355374ffcea77f8dc630ee45879444.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/j.wav"
|
||||
dest_files=["res://.godot/imported/j.wav-2f355374ffcea77f8dc630ee45879444.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/k.wav
Normal file
24
sound/dialogue/animalese/placeholder/k.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://eypaqf1iaq5"
|
||||
path="res://.godot/imported/k.wav-b85d045dc022b9055c25a650b2e3af2a.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/k.wav"
|
||||
dest_files=["res://.godot/imported/k.wav-b85d045dc022b9055c25a650b2e3af2a.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/l.wav
Normal file
24
sound/dialogue/animalese/placeholder/l.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://bixgnaq4idkhj"
|
||||
path="res://.godot/imported/l.wav-b21006c3b048e0c797d6baf19ab0f663.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/l.wav"
|
||||
dest_files=["res://.godot/imported/l.wav-b21006c3b048e0c797d6baf19ab0f663.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/m.wav
Normal file
24
sound/dialogue/animalese/placeholder/m.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dan4o17oreugv"
|
||||
path="res://.godot/imported/m.wav-aa394c4b1d5309d859ce73a368754264.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/m.wav"
|
||||
dest_files=["res://.godot/imported/m.wav-aa394c4b1d5309d859ce73a368754264.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||
BIN
sound/dialogue/animalese/placeholder/n.wav
Normal file
24
sound/dialogue/animalese/placeholder/n.wav.import
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[remap]
|
||||
|
||||
importer="wav"
|
||||
type="AudioStreamWAV"
|
||||
uid="uid://dxup6pw80km61"
|
||||
path="res://.godot/imported/n.wav-84e9a6fa6664222868d647187a09f374.sample"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://sound/dialogue/animalese/placeholder/n.wav"
|
||||
dest_files=["res://.godot/imported/n.wav-84e9a6fa6664222868d647187a09f374.sample"]
|
||||
|
||||
[params]
|
||||
|
||||
force/8_bit=false
|
||||
force/mono=false
|
||||
force/max_rate=false
|
||||
force/max_rate_hz=44100
|
||||
edit/trim=false
|
||||
edit/normalize=false
|
||||
edit/loop_mode=0
|
||||
edit/loop_begin=0
|
||||
edit/loop_end=-1
|
||||
compress/mode=2
|
||||