Polish world, shop, and artwork presentation
7
addons/netfishing_shoreline_baker/plugin.cfg
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[plugin]
|
||||||
|
|
||||||
|
name="NETfishing Shoreline Auto-Baker"
|
||||||
|
description="Rebuilds configured static shoreline ribbons after authored terrain reimports."
|
||||||
|
author="woofmeow"
|
||||||
|
version="1.0.0"
|
||||||
|
script="shoreline_auto_baker.gd"
|
||||||
66
addons/netfishing_shoreline_baker/shoreline_auto_baker.gd
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
@tool
|
||||||
|
extends EditorPlugin
|
||||||
|
|
||||||
|
const TERRAIN_RESOURCE := (
|
||||||
|
"res://art/exported/environment/terrain/starter_island.glb"
|
||||||
|
)
|
||||||
|
const REGION_SCENE := "res://world/regions/starter_island_region.tscn"
|
||||||
|
|
||||||
|
var _bake_pending := false
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
var filesystem := get_editor_interface().get_resource_filesystem()
|
||||||
|
if not filesystem.resources_reimported.is_connected(_on_resources_reimported):
|
||||||
|
filesystem.resources_reimported.connect(_on_resources_reimported)
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
var filesystem := get_editor_interface().get_resource_filesystem()
|
||||||
|
if filesystem.resources_reimported.is_connected(_on_resources_reimported):
|
||||||
|
filesystem.resources_reimported.disconnect(_on_resources_reimported)
|
||||||
|
|
||||||
|
|
||||||
|
func _on_resources_reimported(resources: PackedStringArray) -> void:
|
||||||
|
if not resources.has(TERRAIN_RESOURCE) or _bake_pending:
|
||||||
|
return
|
||||||
|
_bake_pending = true
|
||||||
|
call_deferred("_bake_configured_shorelines")
|
||||||
|
|
||||||
|
|
||||||
|
func _bake_configured_shorelines() -> void:
|
||||||
|
_bake_pending = false
|
||||||
|
var packed_region := ResourceLoader.load(
|
||||||
|
REGION_SCENE,
|
||||||
|
"PackedScene",
|
||||||
|
ResourceLoader.CACHE_MODE_REPLACE,
|
||||||
|
) as PackedScene
|
||||||
|
if packed_region == null:
|
||||||
|
push_error("Shoreline auto-bake could not load %s." % REGION_SCENE)
|
||||||
|
return
|
||||||
|
var region := packed_region.instantiate() as Node3D
|
||||||
|
add_child(region)
|
||||||
|
var baker := region.get_node_or_null(
|
||||||
|
"ShorelineRibbonBaker"
|
||||||
|
) as ShorelineRibbonBaker
|
||||||
|
if baker == null:
|
||||||
|
push_error("Shoreline auto-bake found no configured baker.")
|
||||||
|
region.queue_free()
|
||||||
|
return
|
||||||
|
var results := baker.rebuild_all()
|
||||||
|
for result: Dictionary in results:
|
||||||
|
if result.get("skipped", false):
|
||||||
|
continue
|
||||||
|
var output_path: String = result.get("output_path", "")
|
||||||
|
if not output_path.is_empty():
|
||||||
|
ResourceLoader.load(
|
||||||
|
output_path,
|
||||||
|
"ArrayMesh",
|
||||||
|
ResourceLoader.CACHE_MODE_REPLACE,
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"Shoreline auto-bake: %s, %d loops, %d triangles"
|
||||||
|
% [output_path, result.loop_count, result.triangle_count]
|
||||||
|
)
|
||||||
|
region.queue_free()
|
||||||
|
get_editor_interface().get_resource_filesystem().scan()
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
uid://cbk4xpalei5v3
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ compress/rdo_quality_loss=0.0
|
||||||
compress/hdr_compression=1
|
compress/hdr_compression=1
|
||||||
compress/normal_map=0
|
compress/normal_map=0
|
||||||
compress/channel_pack=0
|
compress/channel_pack=0
|
||||||
mipmaps/generate=true
|
mipmaps/generate=false
|
||||||
mipmaps/limit=-1
|
mipmaps/limit=-1
|
||||||
roughness/mode=0
|
roughness/mode=0
|
||||||
roughness/src_normal=""
|
roughness/src_normal=""
|
||||||
|
|
|
||||||
|
|
@ -232,17 +232,20 @@ smoothing overrides, and generated `.tres` path. Props, players, bobbers, and
|
||||||
gameplay areas are not scanned unless selected explicitly. Only `SALT_WATER`
|
gameplay areas are not scanned unless selected explicitly. Only `SALT_WATER`
|
||||||
bodies generate ribbons.
|
bodies generate ribbons.
|
||||||
|
|
||||||
In the editor, select the baker and press **Rebuild Shoreline Ribbons**. The
|
The editor automatically rebuilds configured shoreline resources when an
|
||||||
equivalent validation command is:
|
authored terrain GLB is reimported. Select the baker and press **Rebuild
|
||||||
|
Shoreline Ribbons** only when intentionally forcing a rebuild without a terrain
|
||||||
|
reimport. The equivalent headless fallback and validation command is:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
godot --headless --path . --script scripts/bake_shoreline_ribbons.gd
|
godot --headless --path . --script scripts/bake_shoreline_ribbons.gd
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `debug_path_stage` only while comparing extraction stages and leave it Off
|
Use `debug_path_stage` only while comparing extraction stages and leave it Off
|
||||||
in committed scenes. Rebuild when saltwater terrain at the waterline, water
|
in committed scenes. A rebuild is required when saltwater terrain at the
|
||||||
height, or generation bounds change. Normal gameplay loads committed generated
|
waterline, water height, or generation bounds change; terrain reimports handle
|
||||||
meshes and never runs extraction.
|
the usual case automatically. Normal gameplay loads committed generated meshes
|
||||||
|
and never runs extraction.
|
||||||
|
|
||||||
### Facial-feature textures
|
### Facial-feature textures
|
||||||
|
|
||||||
|
|
@ -262,6 +265,24 @@ snapshots, so do not casually rename them. Preserve the established RGBA
|
||||||
transparent canvas. Restart a development build after adding an image; rebuild
|
transparent canvas. Restart a development build after adding an image; rebuild
|
||||||
exports to package new `res://` files.
|
exports to package new `res://` files.
|
||||||
|
|
||||||
|
### Texture sampling
|
||||||
|
|
||||||
|
NETfishing artwork always uses nearest-neighbor sampling. Do not enable linear,
|
||||||
|
bilinear, trilinear, or anisotropic texture filtering, and do not generate
|
||||||
|
mipmaps. Shader samplers must declare `filter_nearest`. The
|
||||||
|
`TextureSamplingPolicy` autoload applies nearest sampling to 2D canvas items,
|
||||||
|
Sprite3D presentations, imported 3D materials, and dynamically constructed
|
||||||
|
mesh materials at runtime.
|
||||||
|
|
||||||
|
After importing new artwork, normalize its tracked import metadata where
|
||||||
|
needed and run the focused policy check:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
godot --headless --path . --script scripts/normalize_texture_imports.gd \
|
||||||
|
-- --apply --root res://path/to/new/artwork
|
||||||
|
godot --headless --path . --script tests/texture_sampling_validation.gd
|
||||||
|
```
|
||||||
|
|
||||||
### Bubble menus
|
### Bubble menus
|
||||||
|
|
||||||
Instance `ui/components/bubble_menu/bubble_button.tscn` for each action, or
|
Instance `ui/components/bubble_menu/bubble_button.tscn` for each action, or
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,4 @@ display_name = "pelicans"
|
||||||
animal_name_singular = "pelican"
|
animal_name_singular = "pelican"
|
||||||
animal_name_plural = "pelicans"
|
animal_name_plural = "pelicans"
|
||||||
payout_multiplier = 0.25
|
payout_multiplier = 0.25
|
||||||
sale_message_template = "You sold your {fish} to the pelicans for ${payout}."
|
sale_message_template = "You sold your {fish} to the pelicans."
|
||||||
|
|
|
||||||
|
|
@ -53,8 +53,7 @@ func get_sale_message(
|
||||||
var buyer_name: String = animal_name_plural
|
var buyer_name: String = animal_name_plural
|
||||||
if buyer_name.is_empty():
|
if buyer_name.is_empty():
|
||||||
buyer_name = display_name
|
buyer_name = display_name
|
||||||
return "you sold your %s to the %s for $%d." % [
|
return "you sold your %s to the %s." % [
|
||||||
fish_name,
|
fish_name,
|
||||||
buyer_name,
|
buyer_name,
|
||||||
payout,
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -165,9 +165,10 @@ func _validate_batch(
|
||||||
result.payout
|
result.payout
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
result.sale_message = "you sold %d fish to the %s for $%d." % [
|
result.sale_message = (
|
||||||
|
"you sold %d fish to the %s." % [
|
||||||
result.fish_count,
|
result.fish_count,
|
||||||
result.buyer_animal_name,
|
result.buyer_animal_name,
|
||||||
result.payout,
|
]
|
||||||
]
|
)
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[gd_resource type="Resource" script_class="ItemData" load_steps=3 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_item"]
|
[ext_resource type="Script" path="res://items/item_data.gd" id="1_item"]
|
||||||
[ext_resource type="Texture2D" path="res://items/icons/placeholder/art_kit.svg" id="2_icon"]
|
[ext_resource type="Texture2D" path="res://items/icons/art/art_kit.png" id="2_icon"]
|
||||||
|
|
||||||
[resource]
|
[resource]
|
||||||
script = ExtResource("1_item")
|
script = ExtResource("1_item")
|
||||||
|
|
|
||||||
BIN
items/icons/art/art_kit.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
|
|
@ -2,16 +2,16 @@
|
||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://bm0kkevv62mu0"
|
uid="uid://datu7j7tq2k3e"
|
||||||
path="res://.godot/imported/art_kit.svg-d4bd09e1da2e2339f7d505ee23d771f8.ctex"
|
path="res://.godot/imported/art_kit.png-b54a66a446203c844f97c37241065423.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://items/icons/placeholder/art_kit.svg"
|
source_file="res://items/icons/art/art_kit.png"
|
||||||
dest_files=["res://.godot/imported/art_kit.svg-d4bd09e1da2e2339f7d505ee23d771f8.ctex"]
|
dest_files=["res://.godot/imported/art_kit.png-b54a66a446203c844f97c37241065423.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
|
@ -38,6 +38,3 @@ process/hdr_as_srgb=false
|
||||||
process/hdr_clamp_exposure=false
|
process/hdr_clamp_exposure=false
|
||||||
process/size_limit=0
|
process/size_limit=0
|
||||||
detect_3d/compress_to=1
|
detect_3d/compress_to=1
|
||||||
svg/scale=1.0
|
|
||||||
editor/scale_with_editor_scale=false
|
|
||||||
editor/convert_colors_with_editor_theme=false
|
|
||||||
BIN
items/icons/art/art_kit_eraser.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -2,16 +2,16 @@
|
||||||
|
|
||||||
importer="texture"
|
importer="texture"
|
||||||
type="CompressedTexture2D"
|
type="CompressedTexture2D"
|
||||||
uid="uid://trqvs4dgd4rt"
|
uid="uid://cdbba3bvhuear"
|
||||||
path="res://.godot/imported/fish_coin.svg-9d00b5918d94a555938a84ee65e06386.ctex"
|
path="res://.godot/imported/art_kit_eraser.png-4df8445ba3139b8236508fc61a024158.ctex"
|
||||||
metadata={
|
metadata={
|
||||||
"vram_texture": false
|
"vram_texture": false
|
||||||
}
|
}
|
||||||
|
|
||||||
[deps]
|
[deps]
|
||||||
|
|
||||||
source_file="res://items/icons/placeholder/fish_coin.svg"
|
source_file="res://items/icons/art/art_kit_eraser.png"
|
||||||
dest_files=["res://.godot/imported/fish_coin.svg-9d00b5918d94a555938a84ee65e06386.ctex"]
|
dest_files=["res://.godot/imported/art_kit_eraser.png-4df8445ba3139b8236508fc61a024158.ctex"]
|
||||||
|
|
||||||
[params]
|
[params]
|
||||||
|
|
||||||
|
|
@ -38,6 +38,3 @@ process/hdr_as_srgb=false
|
||||||
process/hdr_clamp_exposure=false
|
process/hdr_clamp_exposure=false
|
||||||
process/size_limit=0
|
process/size_limit=0
|
||||||
detect_3d/compress_to=1
|
detect_3d/compress_to=1
|
||||||
svg/scale=1.0
|
|
||||||
editor/scale_with_editor_scale=false
|
|
||||||
editor/convert_colors_with_editor_theme=false
|
|
||||||
BIN
items/icons/art/art_kit_marker.png
Normal file
|
After Width: | Height: | Size: 945 B |
40
items/icons/art/art_kit_marker.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://b20kab6kxh7ap"
|
||||||
|
path="res://.godot/imported/art_kit_marker.png-77e7b82481305350436ed5cca046df56.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://items/icons/art/art_kit_marker.png"
|
||||||
|
dest_files=["res://.godot/imported/art_kit_marker.png-77e7b82481305350436ed5cca046df56.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.2 KiB |
|
|
@ -1,7 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
|
||||||
<rect x="7" y="10" width="50" height="44" rx="12" fill="#35b9c7"/>
|
|
||||||
<rect x="12" y="15" width="40" height="34" rx="9" fill="#0d2c3a"/>
|
|
||||||
<circle cx="23" cy="27" r="6" fill="#f5eed9"/>
|
|
||||||
<circle cx="39" cy="27" r="6" fill="#ef5b62"/>
|
|
||||||
<path d="M18 42L30 33L35 38L46 30L50 42Z" fill="#ffd166"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 388 B |
|
|
@ -1,6 +0,0 @@
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
||||||
<circle cx="32" cy="32" r="26" fill="#ffd25a" stroke="#17243a" stroke-width="5"/>
|
|
||||||
<ellipse cx="31" cy="32" rx="14" ry="9" fill="#54c7c0" stroke="#17243a" stroke-width="4"/>
|
|
||||||
<path d="M44 32 l10-9 v18 z" fill="#54c7c0" stroke="#17243a" stroke-width="4" stroke-linejoin="round"/>
|
|
||||||
<circle cx="25" cy="29" r="2.5" fill="#17243a"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 402 B |
BIN
items/icons/shop/32_currency.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
40
items/icons/shop/32_currency.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://bf3jlmqhmmuge"
|
||||||
|
path="res://.godot/imported/32_currency.png-c07c9577bae5b0fbbae9513440339b6f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://items/icons/shop/32_currency.png"
|
||||||
|
dest_files=["res://.godot/imported/32_currency.png-c07c9577bae5b0fbbae9513440339b6f.ctex"]
|
||||||
|
|
||||||
|
[params]
|
||||||
|
|
||||||
|
compress/mode=0
|
||||||
|
compress/high_quality=false
|
||||||
|
compress/lossy_quality=0.7
|
||||||
|
compress/uastc_level=0
|
||||||
|
compress/rdo_quality_loss=0.0
|
||||||
|
compress/hdr_compression=1
|
||||||
|
compress/normal_map=0
|
||||||
|
compress/channel_pack=0
|
||||||
|
mipmaps/generate=false
|
||||||
|
mipmaps/limit=-1
|
||||||
|
roughness/mode=0
|
||||||
|
roughness/src_normal=""
|
||||||
|
process/channel_remap/red=0
|
||||||
|
process/channel_remap/green=1
|
||||||
|
process/channel_remap/blue=2
|
||||||
|
process/channel_remap/alpha=3
|
||||||
|
process/fix_alpha_border=true
|
||||||
|
process/premult_alpha=false
|
||||||
|
process/normal_map_invert_y=false
|
||||||
|
process/hdr_as_srgb=false
|
||||||
|
process/hdr_clamp_exposure=false
|
||||||
|
process/size_limit=0
|
||||||
|
detect_3d/compress_to=1
|
||||||
|
|
@ -383,7 +383,7 @@ static func _lifetime_title(chain_id: String, target: int) -> String:
|
||||||
"catch":
|
"catch":
|
||||||
return "seasoned angler"
|
return "seasoned angler"
|
||||||
"sell":
|
"sell":
|
||||||
return "fish coin regular"
|
return "market regular"
|
||||||
"level":
|
"level":
|
||||||
return "reach level %d" % target
|
return "reach level %d" % target
|
||||||
"discover":
|
"discover":
|
||||||
|
|
|
||||||
|
|
@ -656,9 +656,7 @@ func _reward_claimed(reward: Dictionary) -> void:
|
||||||
var fish_coin: int = int(reward.get("fish_coin", 0))
|
var fish_coin: int = int(reward.get("fish_coin", 0))
|
||||||
var experience: int = int(reward.get("experience", 0))
|
var experience: int = int(reward.get("experience", 0))
|
||||||
reward_claimed.emit(title, fish_coin, experience)
|
reward_claimed.emit(title, fish_coin, experience)
|
||||||
status_changed.emit("payment received — $%d and %d xp" % [
|
status_changed.emit("payment received — %d xp" % experience)
|
||||||
fish_coin, experience,
|
|
||||||
])
|
|
||||||
changed.emit()
|
changed.emit()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
74
main/texture_sampling_policy.gd
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
extends Node
|
||||||
|
|
||||||
|
## Enforces NETfishing's nearest-neighbor-only artwork policy at runtime.
|
||||||
|
##
|
||||||
|
## Godot uses separate texture-filter enums for CanvasItem and 3D materials.
|
||||||
|
## Applying both here prevents imported models, dynamically created controls,
|
||||||
|
## and Sprite3D presentations from silently falling back to linear sampling.
|
||||||
|
|
||||||
|
|
||||||
|
func _enter_tree() -> void:
|
||||||
|
get_tree().node_added.connect(_on_node_added)
|
||||||
|
call_deferred("_enforce_existing_tree")
|
||||||
|
|
||||||
|
|
||||||
|
func _on_node_added(node: Node) -> void:
|
||||||
|
enforce_node(node)
|
||||||
|
# Some runtime presenters assign their mesh or material immediately after
|
||||||
|
# add_child(). Recheck once deferred so those resources are covered too.
|
||||||
|
call_deferred("_enforce_deferred_node", weakref(node))
|
||||||
|
|
||||||
|
|
||||||
|
func _enforce_existing_tree() -> void:
|
||||||
|
enforce_subtree(get_tree().root)
|
||||||
|
|
||||||
|
|
||||||
|
func _enforce_deferred_node(node_ref: WeakRef) -> void:
|
||||||
|
var node := node_ref.get_ref() as Node
|
||||||
|
if node != null:
|
||||||
|
enforce_node(node)
|
||||||
|
|
||||||
|
|
||||||
|
static func enforce_subtree(node: Node) -> void:
|
||||||
|
enforce_node(node)
|
||||||
|
for child: Node in node.get_children():
|
||||||
|
enforce_subtree(child)
|
||||||
|
|
||||||
|
|
||||||
|
static func enforce_node(node: Node) -> void:
|
||||||
|
if node is CanvasItem:
|
||||||
|
(node as CanvasItem).texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||||
|
if node is SpriteBase3D:
|
||||||
|
(node as SpriteBase3D).texture_filter = (
|
||||||
|
BaseMaterial3D.TEXTURE_FILTER_NEAREST
|
||||||
|
)
|
||||||
|
if node is GeometryInstance3D:
|
||||||
|
var geometry := node as GeometryInstance3D
|
||||||
|
enforce_material(geometry.material_override)
|
||||||
|
enforce_material(geometry.material_overlay)
|
||||||
|
if node is MeshInstance3D:
|
||||||
|
enforce_mesh((node as MeshInstance3D).mesh)
|
||||||
|
if node is MultiMeshInstance3D:
|
||||||
|
var multimesh := (node as MultiMeshInstance3D).multimesh
|
||||||
|
if multimesh != null:
|
||||||
|
enforce_mesh(multimesh.mesh)
|
||||||
|
if node is GPUParticles3D:
|
||||||
|
var particles := node as GPUParticles3D
|
||||||
|
for pass_index: int in particles.draw_passes:
|
||||||
|
enforce_mesh(particles.get_draw_pass_mesh(pass_index))
|
||||||
|
if node is CPUParticles3D:
|
||||||
|
enforce_mesh((node as CPUParticles3D).mesh)
|
||||||
|
|
||||||
|
|
||||||
|
static func enforce_mesh(mesh: Mesh) -> void:
|
||||||
|
if mesh == null:
|
||||||
|
return
|
||||||
|
for surface_index: int in mesh.get_surface_count():
|
||||||
|
enforce_material(mesh.surface_get_material(surface_index))
|
||||||
|
|
||||||
|
|
||||||
|
static func enforce_material(material: Material) -> void:
|
||||||
|
if material is BaseMaterial3D:
|
||||||
|
(material as BaseMaterial3D).texture_filter = (
|
||||||
|
BaseMaterial3D.TEXTURE_FILTER_NEAREST
|
||||||
|
)
|
||||||
1
main/texture_sampling_policy.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dwd4wfn8bwk5i
|
||||||
|
|
@ -464,7 +464,7 @@ func _build_authoritative_result(
|
||||||
current_state, product_id
|
current_state, product_id
|
||||||
)
|
)
|
||||||
if rejection.is_empty() and (cost < 0 or wallet_balance < cost):
|
if rejection.is_empty() and (cost < 0 or wallet_balance < cost):
|
||||||
rejection = "Not enough fish coin."
|
rejection = "Insufficient funds."
|
||||||
if not rejection.is_empty():
|
if not rejection.is_empty():
|
||||||
return _rejected_result(request, rejection)
|
return _rejected_result(request, rejection)
|
||||||
return {
|
return {
|
||||||
|
|
@ -633,7 +633,7 @@ func _validate_local_result(data: Dictionary) -> String:
|
||||||
):
|
):
|
||||||
return "Purchase could not be completed."
|
return "Purchase could not be completed."
|
||||||
if not _wallet.can_afford(cost):
|
if not _wallet.can_afford(cost):
|
||||||
return "Not enough fish coin."
|
return "Insufficient funds."
|
||||||
match category:
|
match category:
|
||||||
NetworkShopProtocol.ProductCategory.SUPPLY:
|
NetworkShopProtocol.ProductCategory.SUPPLY:
|
||||||
var item: ItemDataType = (
|
var item: ItemDataType = (
|
||||||
|
|
|
||||||
|
|
@ -344,7 +344,7 @@ func arm_guide_action(action: int) -> bool:
|
||||||
_selected_canvas_id = ""
|
_selected_canvas_id = ""
|
||||||
_update_aim()
|
_update_aim()
|
||||||
_refresh_stencil_visibility()
|
_refresh_stencil_visibility()
|
||||||
_emit_hud_state("click a grid to %s it" % _guide_action_verb(action))
|
_emit_hud_state(_guide_action_status(action))
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1874,8 +1874,10 @@ func _execute_armed_guide_action() -> void:
|
||||||
_restore_selected_guide()
|
_restore_selected_guide()
|
||||||
GuideAction.FINALIZE:
|
GuideAction.FINALIZE:
|
||||||
_finalize_selected_guide()
|
_finalize_selected_guide()
|
||||||
_clear_armed_guide_action(true)
|
# Guide actions are persistent modes. Keep the selected action armed so
|
||||||
_emit_hud_state("")
|
# multiple grids can be handled without returning to the toolbar after
|
||||||
|
# every click; selecting the active toggle again explicitly exits it.
|
||||||
|
_emit_hud_state(_guide_action_status(action))
|
||||||
|
|
||||||
|
|
||||||
func _cancel_armed_guide_action() -> void:
|
func _cancel_armed_guide_action() -> void:
|
||||||
|
|
@ -1904,12 +1906,18 @@ func _guide_action_verb(action: int) -> String:
|
||||||
GuideAction.HIDE:
|
GuideAction.HIDE:
|
||||||
return "hide"
|
return "hide"
|
||||||
GuideAction.RESTORE:
|
GuideAction.RESTORE:
|
||||||
return "restore"
|
return "show"
|
||||||
GuideAction.FINALIZE:
|
GuideAction.FINALIZE:
|
||||||
return "finish"
|
return "finish"
|
||||||
return "use"
|
return "use"
|
||||||
|
|
||||||
|
|
||||||
|
func _guide_action_status(action: int) -> String:
|
||||||
|
return "%s grid mode • click grids • toggle again to exit" % (
|
||||||
|
_guide_action_verb(action).capitalize()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _remove_selected_guide() -> void:
|
func _remove_selected_guide() -> void:
|
||||||
if _selected_canvas_id.is_empty():
|
if _selected_canvas_id.is_empty():
|
||||||
_emit_hud_state("aim at a placed grid before removing its guide")
|
_emit_hud_state("aim at a placed grid before removing its guide")
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ render_mode unshaded, cull_back;
|
||||||
// base color and the red, green, and blue channels select the three authored
|
// base color and the red, green, and blue channels select the three authored
|
||||||
// accent colors. Alpha is intentionally ignored so artists may leave unused
|
// accent colors. Alpha is intentionally ignored so artists may leave unused
|
||||||
// UV space transparent.
|
// UV space transparent.
|
||||||
uniform sampler2D pattern_texture : filter_linear_mipmap_anisotropic, repeat_disable;
|
uniform sampler2D pattern_texture : filter_nearest, repeat_disable;
|
||||||
uniform vec4 base_color : source_color = vec4(1.0);
|
uniform vec4 base_color : source_color = vec4(1.0);
|
||||||
uniform vec4 red_zone_color : source_color = vec4(0.9, 0.8, 0.6, 1.0);
|
uniform vec4 red_zone_color : source_color = vec4(0.9, 0.8, 0.6, 1.0);
|
||||||
uniform vec4 green_zone_color : source_color = vec4(0.4, 0.2, 0.1, 1.0);
|
uniform vec4 green_zone_color : source_color = vec4(0.4, 0.2, 0.1, 1.0);
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,12 @@ visible = false
|
||||||
|
|
||||||
[node name="HeldArtKitSprite" type="Sprite3D" parent="HeldArtKitDisplay"]
|
[node name="HeldArtKitSprite" type="Sprite3D" parent="HeldArtKitDisplay"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
rotation = Vector3(0, 0, 4.71239)
|
||||||
pixel_size = 0.008
|
pixel_size = 0.008
|
||||||
|
flip_h = true
|
||||||
shaded = false
|
shaded = false
|
||||||
double_sided = true
|
double_sided = true
|
||||||
texture_filter = 1
|
texture_filter = 0
|
||||||
|
|
||||||
[node name="CatchDisplay" type="Node3D" parent="."]
|
[node name="CatchDisplay" type="Node3D" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,7 @@ var _held_item_attachment: BoneAttachment3D
|
||||||
var _held_fish_display: Node3D
|
var _held_fish_display: Node3D
|
||||||
var _held_fish_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04)
|
var _held_fish_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04)
|
||||||
var _held_fish_sprite: Sprite3D
|
var _held_fish_sprite: Sprite3D
|
||||||
|
var _held_art_kit_display: Node3D
|
||||||
var _held_art_kit_sprite: Sprite3D
|
var _held_art_kit_sprite: Sprite3D
|
||||||
var _catch_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04)
|
var _catch_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04)
|
||||||
|
|
||||||
|
|
@ -510,6 +511,7 @@ func _initialize_held_item_attachment() -> void:
|
||||||
_held_fish_sprite = attachment.get_node(
|
_held_fish_sprite = attachment.get_node(
|
||||||
"HeldFishDisplay/HeldFishSprite"
|
"HeldFishDisplay/HeldFishSprite"
|
||||||
) as Sprite3D
|
) as Sprite3D
|
||||||
|
_held_art_kit_display = attachment.get_node("HeldArtKitDisplay") as Node3D
|
||||||
_held_art_kit_sprite = attachment.get_node(
|
_held_art_kit_sprite = attachment.get_node(
|
||||||
"HeldArtKitDisplay/HeldArtKitSprite"
|
"HeldArtKitDisplay/HeldArtKitSprite"
|
||||||
) as Sprite3D
|
) as Sprite3D
|
||||||
|
|
@ -1710,6 +1712,8 @@ func set_active_art_kit(icon: Texture2D, should_show: bool) -> void:
|
||||||
_held_art_kit_visible = true
|
_held_art_kit_visible = true
|
||||||
_held_art_kit_sprite.texture = icon
|
_held_art_kit_sprite.texture = icon
|
||||||
_held_art_kit_sprite.visible = true
|
_held_art_kit_sprite.visible = true
|
||||||
|
if not previous_show_pose:
|
||||||
|
_held_art_kit_display.visible = false
|
||||||
_fishing_rod.visible = false
|
_fishing_rod.visible = false
|
||||||
if item_changed:
|
if item_changed:
|
||||||
_begin_pocket_visual(
|
_begin_pocket_visual(
|
||||||
|
|
@ -1717,8 +1721,11 @@ func set_active_art_kit(icon: Texture2D, should_show: bool) -> void:
|
||||||
previous_show_pose,
|
previous_show_pose,
|
||||||
true,
|
true,
|
||||||
Callable(),
|
Callable(),
|
||||||
|
false,
|
||||||
|
_apply_active_art_kit_visual,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
_apply_active_art_kit_visual()
|
||||||
_update_character_animation()
|
_update_character_animation()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1798,6 +1805,7 @@ func _apply_pending_held_fish() -> void:
|
||||||
func _clear_active_art_kit() -> void:
|
func _clear_active_art_kit() -> void:
|
||||||
var was_visible: bool = _held_art_kit_visible
|
var was_visible: bool = _held_art_kit_visible
|
||||||
_held_art_kit_visible = false
|
_held_art_kit_visible = false
|
||||||
|
_held_art_kit_display.visible = false
|
||||||
_held_art_kit_sprite.texture = null
|
_held_art_kit_sprite.texture = null
|
||||||
_held_art_kit_sprite.visible = false
|
_held_art_kit_sprite.visible = false
|
||||||
if was_visible:
|
if was_visible:
|
||||||
|
|
@ -1807,6 +1815,12 @@ func _clear_active_art_kit() -> void:
|
||||||
_update_character_animation()
|
_update_character_animation()
|
||||||
|
|
||||||
|
|
||||||
|
func _apply_active_art_kit_visual() -> void:
|
||||||
|
if not _held_art_kit_visible or _held_art_kit_sprite.texture == null:
|
||||||
|
return
|
||||||
|
_held_art_kit_display.visible = true
|
||||||
|
|
||||||
|
|
||||||
func _begin_pocket_visual(
|
func _begin_pocket_visual(
|
||||||
target: PocketVisualTarget,
|
target: PocketVisualTarget,
|
||||||
starts_in_show_pose: bool,
|
starts_in_show_pose: bool,
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,7 @@ static func _runtime_fur_pattern_material(
|
||||||
static func _configure_matte_material(material: StandardMaterial3D) -> void:
|
static func _configure_matte_material(material: StandardMaterial3D) -> void:
|
||||||
if material == null:
|
if material == null:
|
||||||
return
|
return
|
||||||
|
material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_NEAREST
|
||||||
# Character artwork is deliberately graphic and flat. Unshaded rendering
|
# Character artwork is deliberately graphic and flat. Unshaded rendering
|
||||||
# keeps the authored fur and facial colors invariant across world weather,
|
# keeps the authored fur and facial colors invariant across world weather,
|
||||||
# time of day, and the isolated Profile preview.
|
# time of day, and the isolated Profile preview.
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,18 @@ run/main_scene="res://main/main.tscn"
|
||||||
config/features=PackedStringArray("4.7", "GL Compatibility")
|
config/features=PackedStringArray("4.7", "GL Compatibility")
|
||||||
config/icon="res://art/exported/system_icons/netfishing_256.png"
|
config/icon="res://art/exported/system_icons/netfishing_256.png"
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
TextureSamplingPolicy="*res://main/texture_sampling_policy.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/stretch/aspect="expand"
|
window/stretch/aspect="expand"
|
||||||
|
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/netfishing_shoreline_baker/plugin.cfg")
|
||||||
|
|
||||||
[gui]
|
[gui]
|
||||||
|
|
||||||
theme/custom="res://ui/game_theme.tres"
|
theme/custom="res://ui/game_theme.tres"
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const DEFAULT_PROFILE: Dictionary = {
|
||||||
"compress/hdr_compression": 1,
|
"compress/hdr_compression": 1,
|
||||||
"compress/normal_map": 0,
|
"compress/normal_map": 0,
|
||||||
"compress/channel_pack": 0,
|
"compress/channel_pack": 0,
|
||||||
"mipmaps/generate": true,
|
"mipmaps/generate": false,
|
||||||
"mipmaps/limit": -1,
|
"mipmaps/limit": -1,
|
||||||
"roughness/mode": 0,
|
"roughness/mode": 0,
|
||||||
"roughness/src_normal": "",
|
"roughness/src_normal": "",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ readonly -a QUICK_TESTS=(
|
||||||
"tests/shoreline_ambience_validation.gd"
|
"tests/shoreline_ambience_validation.gd"
|
||||||
"tests/surface_drawing_validation.gd"
|
"tests/surface_drawing_validation.gd"
|
||||||
"tests/terrain_blender_material_validation.gd"
|
"tests/terrain_blender_material_validation.gd"
|
||||||
|
"tests/texture_sampling_validation.gd"
|
||||||
"tests/world_time_validation.gd"
|
"tests/world_time_validation.gd"
|
||||||
"tests/world_weather_validation.gd"
|
"tests/world_weather_validation.gd"
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -325,6 +325,25 @@ func _run() -> void:
|
||||||
and finalize_button != null
|
and finalize_button != null
|
||||||
and close_button != null
|
and close_button != null
|
||||||
)
|
)
|
||||||
|
assert(eraser_button.text.is_empty())
|
||||||
|
assert(eraser_button.icon != null)
|
||||||
|
assert(
|
||||||
|
eraser_button.icon.resource_path.ends_with(
|
||||||
|
"/art/art_kit_eraser.png"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for icon_button: Button in [eraser_button, undo_button]:
|
||||||
|
assert(icon_button.custom_minimum_size == Vector2(48, 44))
|
||||||
|
assert(icon_button.get_theme_constant("icon_max_width") == 40)
|
||||||
|
var icon_style := (
|
||||||
|
icon_button.get_theme_stylebox("normal") as StyleBoxFlat
|
||||||
|
)
|
||||||
|
assert(is_equal_approx(icon_style.content_margin_left, 4.0))
|
||||||
|
assert(is_equal_approx(icon_style.content_margin_top, 2.0))
|
||||||
|
assert(hide_button.button_group != null)
|
||||||
|
assert(hide_button.button_group == restore_button.button_group)
|
||||||
|
assert(hide_button.button_group == finalize_button.button_group)
|
||||||
|
assert(hide_button.button_group.allow_unpress)
|
||||||
assert(brush_option.item_count == 4)
|
assert(brush_option.item_count == 4)
|
||||||
assert(grid_option.item_count == 4)
|
assert(grid_option.item_count == 4)
|
||||||
assert(not brush_option.get_popup().is_item_disabled(0))
|
assert(not brush_option.get_popup().is_item_disabled(0))
|
||||||
|
|
@ -367,30 +386,42 @@ func _run() -> void:
|
||||||
world_click.button_index = MOUSE_BUTTON_LEFT
|
world_click.button_index = MOUSE_BUTTON_LEFT
|
||||||
world_click.pressed = true
|
world_click.pressed = true
|
||||||
assert(service.handle_input(world_click, true))
|
assert(service.handle_input(world_click, true))
|
||||||
|
assert(
|
||||||
|
service.get_armed_guide_action()
|
||||||
|
== NetworkSurfaceDrawingService.GuideAction.HIDE
|
||||||
|
)
|
||||||
|
assert(service.is_placement_mode())
|
||||||
|
assert(not service.is_eraser_mode())
|
||||||
|
assert(hide_button.button_pressed)
|
||||||
|
hide_button.pressed.emit()
|
||||||
assert(
|
assert(
|
||||||
service.get_armed_guide_action()
|
service.get_armed_guide_action()
|
||||||
== NetworkSurfaceDrawingService.GuideAction.NONE
|
== NetworkSurfaceDrawingService.GuideAction.NONE
|
||||||
)
|
)
|
||||||
assert(not service.is_placement_mode())
|
assert(not service.is_placement_mode() and service.is_eraser_mode())
|
||||||
assert(service.is_eraser_mode())
|
|
||||||
|
|
||||||
restore_button.pressed.emit()
|
restore_button.pressed.emit()
|
||||||
assert(
|
assert(
|
||||||
service.get_armed_guide_action()
|
service.get_armed_guide_action()
|
||||||
== NetworkSurfaceDrawingService.GuideAction.RESTORE
|
== NetworkSurfaceDrawingService.GuideAction.RESTORE
|
||||||
)
|
)
|
||||||
restore_button.pressed.emit()
|
|
||||||
assert(
|
|
||||||
service.get_armed_guide_action()
|
|
||||||
== NetworkSurfaceDrawingService.GuideAction.NONE
|
|
||||||
)
|
|
||||||
assert(not service.is_placement_mode() and service.is_eraser_mode())
|
|
||||||
finalize_button.pressed.emit()
|
finalize_button.pressed.emit()
|
||||||
assert(
|
assert(
|
||||||
service.get_armed_guide_action()
|
service.get_armed_guide_action()
|
||||||
== NetworkSurfaceDrawingService.GuideAction.FINALIZE
|
== NetworkSurfaceDrawingService.GuideAction.FINALIZE
|
||||||
)
|
)
|
||||||
|
assert(not restore_button.button_pressed)
|
||||||
|
assert(finalize_button.button_pressed)
|
||||||
assert(service.handle_input(world_click, true))
|
assert(service.handle_input(world_click, true))
|
||||||
|
assert(
|
||||||
|
service.get_armed_guide_action()
|
||||||
|
== NetworkSurfaceDrawingService.GuideAction.FINALIZE
|
||||||
|
)
|
||||||
|
finalize_button.pressed.emit()
|
||||||
|
assert(
|
||||||
|
service.get_armed_guide_action()
|
||||||
|
== NetworkSurfaceDrawingService.GuideAction.NONE
|
||||||
|
)
|
||||||
assert(not service.is_placement_mode() and service.is_eraser_mode())
|
assert(not service.is_placement_mode() and service.is_eraser_mode())
|
||||||
(color_buttons[&"ocean_teal"] as Button).pressed.emit()
|
(color_buttons[&"ocean_teal"] as Button).pressed.emit()
|
||||||
assert(not service.is_eraser_mode())
|
assert(not service.is_eraser_mode())
|
||||||
|
|
@ -410,6 +441,32 @@ func _run() -> void:
|
||||||
player.hotbar.select_slot(0)
|
player.hotbar.select_slot(0)
|
||||||
var fishing_spot := main.get_node("%FishingSpot") as FishingSpot
|
var fishing_spot := main.get_node("%FishingSpot") as FishingSpot
|
||||||
await process_frame
|
await process_frame
|
||||||
|
var held_art_kit_display := player.get("_held_art_kit_display") as Node3D
|
||||||
|
var held_art_kit_sprite := player.get("_held_art_kit_sprite") as Sprite3D
|
||||||
|
var pocket_animation_player := player.get(
|
||||||
|
"_character_animation_player"
|
||||||
|
) as AnimationPlayer
|
||||||
|
var pocket_animation_name: StringName = player.get(
|
||||||
|
"_pocket_visual_animation"
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
not held_art_kit_display.visible
|
||||||
|
and pocket_animation_player != null
|
||||||
|
and not pocket_animation_name.is_empty()
|
||||||
|
and pocket_animation_player.has_animation(pocket_animation_name)
|
||||||
|
):
|
||||||
|
var pocket_animation: Animation = (
|
||||||
|
pocket_animation_player.get_animation(pocket_animation_name)
|
||||||
|
)
|
||||||
|
await create_timer(pocket_animation.length * 0.55).timeout
|
||||||
|
assert(held_art_kit_display.visible)
|
||||||
|
assert(held_art_kit_sprite.visible and held_art_kit_sprite.texture != null)
|
||||||
|
assert(held_art_kit_sprite.texture.resource_path.ends_with("/art/art_kit.png"))
|
||||||
|
assert(is_zero_approx(angle_difference(
|
||||||
|
held_art_kit_sprite.rotation.z,
|
||||||
|
-PI * 0.5,
|
||||||
|
)))
|
||||||
|
assert(held_art_kit_sprite.flip_h)
|
||||||
assert(not fishing_spot.has_signal(&"art_ui_toggle_requested"))
|
assert(not fishing_spot.has_signal(&"art_ui_toggle_requested"))
|
||||||
assert(service.is_active() and toolbar.visible)
|
assert(service.is_active() and toolbar.visible)
|
||||||
assert(service.get_grid_size() == 128)
|
assert(service.get_grid_size() == 128)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,10 @@ func _run() -> void:
|
||||||
assert(body != null)
|
assert(body != null)
|
||||||
var body_material := body.material_override as StandardMaterial3D
|
var body_material := body.material_override as StandardMaterial3D
|
||||||
assert(body_material != null)
|
assert(body_material != null)
|
||||||
|
assert(
|
||||||
|
body_material.texture_filter
|
||||||
|
== BaseMaterial3D.TEXTURE_FILTER_NEAREST
|
||||||
|
)
|
||||||
assert(body_material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED)
|
assert(body_material.shading_mode == BaseMaterial3D.SHADING_MODE_UNSHADED)
|
||||||
assert(is_zero_approx(body_material.metallic))
|
assert(is_zero_approx(body_material.metallic))
|
||||||
assert(is_zero_approx(body_material.metallic_specular))
|
assert(is_zero_approx(body_material.metallic_specular))
|
||||||
|
|
|
||||||
|
|
@ -511,6 +511,8 @@ func _test_host_art_shop_purchase(
|
||||||
ArtShopStock.ART_KIT_ITEM_ID
|
ArtShopStock.ART_KIT_ITEM_ID
|
||||||
)
|
)
|
||||||
assert(art_item != null and art_item.hotbar_allowed and art_item.equippable)
|
assert(art_item != null and art_item.hotbar_allowed and art_item.equippable)
|
||||||
|
assert(art_item.icon != null)
|
||||||
|
assert(art_item.icon.resource_path.ends_with("/art/art_kit.png"))
|
||||||
assert(player.hotbar.assign_item(0, ArtShopStock.ART_KIT_ITEM_ID))
|
assert(player.hotbar.assign_item(0, ArtShopStock.ART_KIT_ITEM_ID))
|
||||||
for product_id: StringName in [
|
for product_id: StringName in [
|
||||||
&"marker_ocean_teal", &"brush_2x", &"grid_32x",
|
&"marker_ocean_teal", &"brush_2x", &"grid_32x",
|
||||||
|
|
@ -568,6 +570,41 @@ func _test_fishing_shop_sale_ui(
|
||||||
var shop_panel_style := shop_panel.get_theme_stylebox("panel") as StyleBoxFlat
|
var shop_panel_style := shop_panel.get_theme_stylebox("panel") as StyleBoxFlat
|
||||||
assert(shop_panel_style != null)
|
assert(shop_panel_style != null)
|
||||||
assert(shop_panel_style.corner_radius_top_left == 24)
|
assert(shop_panel_style.corner_radius_top_left == 24)
|
||||||
|
var upgrade_grid := shop.get_node("%UpgradeGrid") as GridContainer
|
||||||
|
assert(upgrade_grid != null and upgrade_grid.columns == 3)
|
||||||
|
assert((shop.get_node("%Upgrades") as Control).visible)
|
||||||
|
assert(not (shop.get_node("%Supplies") as Control).visible)
|
||||||
|
for upgrade_name: String in [
|
||||||
|
"ReelPurchase", "BarrierPurchase", "CoolerPurchase"
|
||||||
|
]:
|
||||||
|
var upgrade_button := shop.get_node("%%%s" % upgrade_name) as Button
|
||||||
|
assert(upgrade_button != null)
|
||||||
|
assert(upgrade_button.size == Vector2(144.0, 144.0))
|
||||||
|
assert(upgrade_button.text.is_empty())
|
||||||
|
assert(upgrade_button.icon != null)
|
||||||
|
assert(upgrade_button.tooltip_text.contains("level"))
|
||||||
|
assert(not shop.has_node("%ReelLevel"))
|
||||||
|
assert(not shop.has_node("%BarrierEffect"))
|
||||||
|
assert(not shop.has_node("%CoolerLevel"))
|
||||||
|
var balance_coin := shop.get_node(
|
||||||
|
"ShopPanel/Margin/Layout/Header/BalanceDisplay/CoinIcon"
|
||||||
|
) as TextureRect
|
||||||
|
assert(balance_coin != null and balance_coin.texture != null)
|
||||||
|
assert(
|
||||||
|
balance_coin.texture.resource_path.ends_with(
|
||||||
|
"/shop/32_currency.png"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for cost_name: String in ["ReelCost", "BarrierCost", "CoolerCost"]:
|
||||||
|
var cost_display := shop.get_node("%%%s" % cost_name) as CurrencyAmount
|
||||||
|
assert(cost_display != null)
|
||||||
|
var cost_icon := cost_display.get_node("Icon") as TextureRect
|
||||||
|
assert(cost_icon != null and cost_icon.texture != null)
|
||||||
|
assert(
|
||||||
|
cost_icon.texture.resource_path.ends_with(
|
||||||
|
"/shop/32_currency.png"
|
||||||
|
)
|
||||||
|
)
|
||||||
assert(not shop.has_node("ShopPanel/Margin/Layout/ModeTabs"))
|
assert(not shop.has_node("ShopPanel/Margin/Layout/ModeTabs"))
|
||||||
var shop_tabs: Array = shop.get("_shop_tabs") as Array
|
var shop_tabs: Array = shop.get("_shop_tabs") as Array
|
||||||
assert(shop_tabs.size() == 6)
|
assert(shop_tabs.size() == 6)
|
||||||
|
|
@ -582,6 +619,37 @@ func _test_fishing_shop_sale_ui(
|
||||||
if child is Label:
|
if child is Label:
|
||||||
stock_sections.append((child as Label).text)
|
stock_sections.append((child as Label).text)
|
||||||
assert(stock_sections == ["art kit", "markers", "brushes", "grids"])
|
assert(stock_sections == ["art kit", "markers", "brushes", "grids"])
|
||||||
|
var marker_icons := shop.find_children(
|
||||||
|
"MarkerIcon", "TextureRect", true, false
|
||||||
|
)
|
||||||
|
assert(marker_icons.size() == ArtShopStock.MARKER_PRODUCTS.size())
|
||||||
|
for index: int in marker_icons.size():
|
||||||
|
var marker_icon := marker_icons[index] as TextureRect
|
||||||
|
var marker_material := marker_icon.material as ShaderMaterial
|
||||||
|
var product_id: StringName = ArtShopStock.MARKER_PRODUCTS[index]
|
||||||
|
var color_id: StringName = PlayerArtUnlocks.color_id_for_product(
|
||||||
|
product_id
|
||||||
|
)
|
||||||
|
assert(marker_icon.texture.resource_path.ends_with("art_kit_marker.png"))
|
||||||
|
assert(marker_material != null)
|
||||||
|
assert(
|
||||||
|
marker_material.get_shader_parameter("marker_color")
|
||||||
|
== SurfaceDrawingPalette.get_color(color_id)
|
||||||
|
)
|
||||||
|
var price_bubbles := shop.find_children(
|
||||||
|
"PriceBubble", "PanelContainer", true, false
|
||||||
|
)
|
||||||
|
assert(not price_bubbles.is_empty())
|
||||||
|
for price_bubble: Node in price_bubbles:
|
||||||
|
var currency_icon := price_bubble.find_child(
|
||||||
|
"CurrencyIcon", true, false
|
||||||
|
) as TextureRect
|
||||||
|
assert(currency_icon != null and currency_icon.texture != null)
|
||||||
|
assert(
|
||||||
|
currency_icon.texture.resource_path.ends_with(
|
||||||
|
"/shop/32_currency.png"
|
||||||
|
)
|
||||||
|
)
|
||||||
await _activate_pointer_control(sell_mode, ui_viewport)
|
await _activate_pointer_control(sell_mode, ui_viewport)
|
||||||
await process_frame
|
await process_frame
|
||||||
assert(shop.visible and not player_menu.visible)
|
assert(shop.visible and not player_menu.visible)
|
||||||
|
|
|
||||||
|
|
@ -189,6 +189,27 @@ func _validate_starter_region_surfaces() -> void:
|
||||||
assert(not covered_ocean.is_fishable())
|
assert(not covered_ocean.is_fishable())
|
||||||
assert(covered_ocean.position.y > -0.45)
|
assert(covered_ocean.position.y > -0.45)
|
||||||
|
|
||||||
|
# The ocean fishing layer covers the island's complete editable footprint.
|
||||||
|
# Terrain at or above the waterline remains authoritative, while shoreline
|
||||||
|
# terrain lowered below the surface becomes fishable without reshaping a
|
||||||
|
# manually authored exclusion ring.
|
||||||
|
var editable_shallows_found := 0
|
||||||
|
for x_position: int in range(-24, 24, 2):
|
||||||
|
for z_position: int in range(-21, 22, 2):
|
||||||
|
var candidate: FishingSurfaceSampleType = (
|
||||||
|
fishing_spot.resolve_fishing_surface(
|
||||||
|
Vector3(x_position, -0.45, z_position),
|
||||||
|
4.0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
candidate.is_fishable()
|
||||||
|
and candidate.water_region.water_type
|
||||||
|
== WaterType.Type.SALT_WATER
|
||||||
|
):
|
||||||
|
editable_shallows_found += 1
|
||||||
|
assert(editable_shallows_found > 0)
|
||||||
|
|
||||||
fishing_spot.queue_free()
|
fishing_spot.queue_free()
|
||||||
region.queue_free()
|
region.queue_free()
|
||||||
await process_frame
|
await process_frame
|
||||||
|
|
|
||||||
97
tests/shoreline_ribbon_validation.gd
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const ShorelineMesh: ArrayMesh = preload(
|
||||||
|
"res://world/generated/shorelines/starter_ocean_shoreline.tres"
|
||||||
|
)
|
||||||
|
const GEOMETRY_EPSILON := 0.0001
|
||||||
|
const CROSSING_NEIGHBORHOOD := 32
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
assert(ShorelineMesh.get_surface_count() == 1)
|
||||||
|
var arrays := ShorelineMesh.surface_get_arrays(0)
|
||||||
|
var vertices := arrays[Mesh.ARRAY_VERTEX] as PackedVector3Array
|
||||||
|
var indices := arrays[Mesh.ARRAY_INDEX] as PackedInt32Array
|
||||||
|
assert(not vertices.is_empty())
|
||||||
|
assert(vertices.size() % 2 == 0)
|
||||||
|
assert(indices.size() == vertices.size() * 3)
|
||||||
|
var point_count := vertices.size() / 2
|
||||||
|
var land_edge := PackedVector2Array()
|
||||||
|
var water_edge := PackedVector2Array()
|
||||||
|
for index: int in point_count:
|
||||||
|
var land := vertices[index * 2]
|
||||||
|
var water := vertices[index * 2 + 1]
|
||||||
|
land_edge.append(Vector2(land.x, land.z))
|
||||||
|
water_edge.append(Vector2(water.x, water.z))
|
||||||
|
var ribbon_width := land.distance_to(water)
|
||||||
|
assert(ribbon_width >= 0.34)
|
||||||
|
assert(ribbon_width <= 1.5)
|
||||||
|
var crossing_count := (
|
||||||
|
_count_local_crossings(water_edge)
|
||||||
|
+ _count_local_crossings(land_edge)
|
||||||
|
)
|
||||||
|
if crossing_count > 0:
|
||||||
|
push_error(
|
||||||
|
"Shoreline ribbon contains %d local edge crossings."
|
||||||
|
% crossing_count
|
||||||
|
)
|
||||||
|
quit(1)
|
||||||
|
return
|
||||||
|
print("Shoreline ribbon validation: PASS")
|
||||||
|
quit()
|
||||||
|
|
||||||
|
|
||||||
|
func _count_local_crossings(points: PackedVector2Array) -> int:
|
||||||
|
var crossing_count := 0
|
||||||
|
for first: int in points.size():
|
||||||
|
var first_next := (first + 1) % points.size()
|
||||||
|
var checked_neighbors := mini(
|
||||||
|
CROSSING_NEIGHBORHOOD,
|
||||||
|
points.size() - 2,
|
||||||
|
)
|
||||||
|
for offset: int in range(2, checked_neighbors + 1):
|
||||||
|
var second := (first + offset) % points.size()
|
||||||
|
var second_next := (second + 1) % points.size()
|
||||||
|
if second_next == first:
|
||||||
|
continue
|
||||||
|
if _segments_cross(
|
||||||
|
points[first],
|
||||||
|
points[first_next],
|
||||||
|
points[second],
|
||||||
|
points[second_next],
|
||||||
|
):
|
||||||
|
crossing_count += 1
|
||||||
|
return crossing_count
|
||||||
|
|
||||||
|
|
||||||
|
func _segments_cross(
|
||||||
|
a: Vector2,
|
||||||
|
b: Vector2,
|
||||||
|
c: Vector2,
|
||||||
|
d: Vector2,
|
||||||
|
) -> bool:
|
||||||
|
if (
|
||||||
|
maxf(a.x, b.x) < minf(c.x, d.x) - GEOMETRY_EPSILON
|
||||||
|
or maxf(c.x, d.x) < minf(a.x, b.x) - GEOMETRY_EPSILON
|
||||||
|
or maxf(a.y, b.y) < minf(c.y, d.y) - GEOMETRY_EPSILON
|
||||||
|
or maxf(c.y, d.y) < minf(a.y, b.y) - GEOMETRY_EPSILON
|
||||||
|
):
|
||||||
|
return false
|
||||||
|
var first_direction := b - a
|
||||||
|
var second_direction := d - c
|
||||||
|
var denominator := first_direction.cross(second_direction)
|
||||||
|
if absf(denominator) <= GEOMETRY_EPSILON:
|
||||||
|
return false
|
||||||
|
var offset := c - a
|
||||||
|
var first_amount := offset.cross(second_direction) / denominator
|
||||||
|
var second_amount := offset.cross(first_direction) / denominator
|
||||||
|
return (
|
||||||
|
first_amount > GEOMETRY_EPSILON
|
||||||
|
and first_amount < 1.0 - GEOMETRY_EPSILON
|
||||||
|
and second_amount > GEOMETRY_EPSILON
|
||||||
|
and second_amount < 1.0 - GEOMETRY_EPSILON
|
||||||
|
)
|
||||||
1
tests/shoreline_ribbon_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://c73frygpfhred
|
||||||
148
tests/texture_sampling_validation.gd
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const POLICY := preload("res://main/texture_sampling_policy.gd")
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
_validate_runtime_policy()
|
||||||
|
_validate_repository_files("res://")
|
||||||
|
if failures.is_empty():
|
||||||
|
print("Texture sampling validation passed.")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
for failure: String in failures:
|
||||||
|
printerr("Texture sampling validation: ", failure)
|
||||||
|
quit(1)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_runtime_policy() -> void:
|
||||||
|
var texture_rect := TextureRect.new()
|
||||||
|
texture_rect.texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||||
|
POLICY.enforce_node(texture_rect)
|
||||||
|
_check(
|
||||||
|
texture_rect.texture_filter == CanvasItem.TEXTURE_FILTER_NEAREST,
|
||||||
|
"CanvasItem policy must replace linear sampling with nearest sampling.",
|
||||||
|
)
|
||||||
|
texture_rect.free()
|
||||||
|
|
||||||
|
var sprite := Sprite3D.new()
|
||||||
|
sprite.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR
|
||||||
|
POLICY.enforce_node(sprite)
|
||||||
|
_check(
|
||||||
|
sprite.texture_filter == BaseMaterial3D.TEXTURE_FILTER_NEAREST,
|
||||||
|
"Sprite3D policy must replace linear sampling with nearest sampling.",
|
||||||
|
)
|
||||||
|
sprite.free()
|
||||||
|
|
||||||
|
var material := StandardMaterial3D.new()
|
||||||
|
material.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR
|
||||||
|
var quad := QuadMesh.new()
|
||||||
|
quad.material = material
|
||||||
|
var mesh_instance := MeshInstance3D.new()
|
||||||
|
mesh_instance.mesh = quad
|
||||||
|
POLICY.enforce_node(mesh_instance)
|
||||||
|
_check(
|
||||||
|
material.texture_filter == BaseMaterial3D.TEXTURE_FILTER_NEAREST,
|
||||||
|
"3D material policy must replace linear sampling with nearest sampling.",
|
||||||
|
)
|
||||||
|
mesh_instance.free()
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_repository_files(path: String) -> void:
|
||||||
|
var directory := DirAccess.open(path)
|
||||||
|
if directory == null:
|
||||||
|
failures.append("Could not scan %s." % path)
|
||||||
|
return
|
||||||
|
directory.list_dir_begin()
|
||||||
|
while true:
|
||||||
|
var name := directory.get_next()
|
||||||
|
if name.is_empty():
|
||||||
|
break
|
||||||
|
if name == ".godot" or name == ".git":
|
||||||
|
continue
|
||||||
|
var child_path := path.path_join(name)
|
||||||
|
if directory.current_is_dir():
|
||||||
|
_validate_repository_files(child_path)
|
||||||
|
continue
|
||||||
|
if name.ends_with(".import"):
|
||||||
|
_validate_import_file(child_path)
|
||||||
|
elif name.ends_with(".gdshader"):
|
||||||
|
_validate_shader_file(child_path)
|
||||||
|
elif name.ends_with(".tscn") or name.ends_with(".tres"):
|
||||||
|
_validate_serialized_resource(child_path)
|
||||||
|
elif name.ends_with(".gd") and child_path != get_script().resource_path:
|
||||||
|
_validate_script_file(child_path)
|
||||||
|
directory.list_dir_end()
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_import_file(path: String) -> void:
|
||||||
|
var source := FileAccess.get_file_as_string(path)
|
||||||
|
_check(
|
||||||
|
"mipmaps/generate=true" not in source,
|
||||||
|
"Texture import generates mipmaps: %s" % path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_shader_file(path: String) -> void:
|
||||||
|
var source := FileAccess.get_file_as_string(path)
|
||||||
|
var declaration_start := source.find("uniform sampler")
|
||||||
|
while declaration_start >= 0:
|
||||||
|
var declaration_end := source.find(";", declaration_start)
|
||||||
|
if declaration_end < 0:
|
||||||
|
_check(false, "Incomplete sampler declaration in %s." % path)
|
||||||
|
return
|
||||||
|
var declaration := source.substr(
|
||||||
|
declaration_start,
|
||||||
|
declaration_end - declaration_start,
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
"filter_nearest" in declaration,
|
||||||
|
"Shader sampler must declare filter_nearest: %s" % path,
|
||||||
|
)
|
||||||
|
declaration_start = source.find("uniform sampler", declaration_end + 1)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_serialized_resource(path: String) -> void:
|
||||||
|
var source := FileAccess.get_file_as_string(path)
|
||||||
|
var uses_3d_filter_enum := false
|
||||||
|
for line: String in source.split("\n"):
|
||||||
|
if line.begins_with("["):
|
||||||
|
uses_3d_filter_enum = (
|
||||||
|
("type=\"Sprite3D\"" in line)
|
||||||
|
or ("type=\"AnimatedSprite3D\"" in line)
|
||||||
|
or ("type=\"StandardMaterial3D\"" in line)
|
||||||
|
or ("type=\"ORMMaterial3D\"" in line)
|
||||||
|
)
|
||||||
|
if not line.begins_with("texture_filter = "):
|
||||||
|
continue
|
||||||
|
var value := int(line.trim_prefix("texture_filter = "))
|
||||||
|
if uses_3d_filter_enum:
|
||||||
|
_check(
|
||||||
|
value == BaseMaterial3D.TEXTURE_FILTER_NEAREST,
|
||||||
|
"3D resource declares non-nearest filtering: %s" % path,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
_check(
|
||||||
|
value == CanvasItem.TEXTURE_FILTER_PARENT_NODE
|
||||||
|
or value == CanvasItem.TEXTURE_FILTER_NEAREST,
|
||||||
|
"Canvas resource declares non-nearest filtering: %s" % path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _validate_script_file(path: String) -> void:
|
||||||
|
var source := FileAccess.get_file_as_string(path)
|
||||||
|
_check(
|
||||||
|
"TEXTURE_FILTER_LINEAR" not in source,
|
||||||
|
"Script explicitly requests linear texture filtering: %s" % path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
1
tests/texture_sampling_validation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://dqm0j1xnkeox1
|
||||||
15
ui/art_kit_marker_icon.gdshader
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
shader_type canvas_item;
|
||||||
|
|
||||||
|
uniform vec4 marker_color : source_color = vec4(1.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
|
||||||
|
void fragment() {
|
||||||
|
vec4 source = texture(TEXTURE, UV) * COLOR;
|
||||||
|
float red_dominance = source.r - max(source.g, source.b);
|
||||||
|
float marker_mask = smoothstep(0.04, 0.18, red_dominance);
|
||||||
|
vec3 colored_marker = marker_color.rgb * source.r;
|
||||||
|
COLOR = vec4(
|
||||||
|
mix(source.rgb, colored_marker, marker_mask),
|
||||||
|
source.a * marker_color.a
|
||||||
|
);
|
||||||
|
}
|
||||||
1
ui/art_kit_marker_icon.gdshader.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bsg3cwrnjq6ev
|
||||||
|
|
@ -5,6 +5,7 @@ extends PanelContainer
|
||||||
|
|
||||||
@onready var _heading: Label = %Heading
|
@onready var _heading: Label = %Heading
|
||||||
@onready var _value: Label = %Value
|
@onready var _value: Label = %Value
|
||||||
|
@onready var _currency_icon: TextureRect = %CurrencyIcon
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -16,3 +17,10 @@ func _ready() -> void:
|
||||||
func set_content(heading: String, value: String) -> void:
|
func set_content(heading: String, value: String) -> void:
|
||||||
_heading.text = heading
|
_heading.text = heading
|
||||||
_value.text = value
|
_value.text = value
|
||||||
|
_currency_icon.visible = false
|
||||||
|
|
||||||
|
|
||||||
|
func set_currency_amount(heading: String, amount: int) -> void:
|
||||||
|
_heading.text = heading
|
||||||
|
_value.text = str(amount)
|
||||||
|
_currency_icon.visible = true
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
[gd_scene load_steps=3 format=3]
|
[gd_scene load_steps=4 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_status_bubble.gd" id="1_status"]
|
[ext_resource type="Script" path="res://ui/components/bubble_menu/bubble_status_bubble.gd" id="1_status"]
|
||||||
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="2_profile"]
|
[ext_resource type="Resource" path="res://ui/components/bubble_menu/bubble_menu_profile.tres" id="2_profile"]
|
||||||
|
[ext_resource type="Texture2D" path="res://items/icons/shop/32_currency.png" id="3_currency"]
|
||||||
|
|
||||||
[node name="BubbleStatusBubble" type="PanelContainer"]
|
[node name="BubbleStatusBubble" type="PanelContainer"]
|
||||||
custom_minimum_size = Vector2(112, 62)
|
custom_minimum_size = Vector2(112, 62)
|
||||||
|
|
@ -23,7 +24,25 @@ theme_override_font_sizes/font_size = 13
|
||||||
text = "status"
|
text = "status"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="Value" type="Label" parent="Labels"]
|
[node name="ValueRow" type="HBoxContainer" parent="Labels"]
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 3
|
||||||
|
alignment = 1
|
||||||
|
|
||||||
|
[node name="CurrencyIcon" type="TextureRect" parent="Labels/ValueRow"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
visible = false
|
||||||
|
custom_minimum_size = Vector2(18, 18)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
texture = ExtResource("3_currency")
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
texture_filter = 1
|
||||||
|
|
||||||
|
[node name="Value" type="Label" parent="Labels/ValueRow"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
|
|
|
||||||
48
ui/components/currency_amount.gd
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
class_name CurrencyAmount
|
||||||
|
extends HBoxContainer
|
||||||
|
|
||||||
|
@export var amount: int = 0:
|
||||||
|
set(value):
|
||||||
|
amount = value
|
||||||
|
_display_text = str(value)
|
||||||
|
_refresh_amount()
|
||||||
|
@export_range(8.0, 64.0, 1.0) var icon_size: float = 18.0:
|
||||||
|
set(value):
|
||||||
|
icon_size = value
|
||||||
|
_refresh_icon_size()
|
||||||
|
|
||||||
|
@onready var _icon: TextureRect = %Icon
|
||||||
|
@onready var _amount_label: Label = %Amount
|
||||||
|
|
||||||
|
var _display_text: String = "0"
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
_refresh_icon_size()
|
||||||
|
_refresh_amount()
|
||||||
|
|
||||||
|
|
||||||
|
func set_amount(value: int) -> void:
|
||||||
|
amount = value
|
||||||
|
|
||||||
|
|
||||||
|
func set_amount_text(value: String) -> void:
|
||||||
|
_display_text = value
|
||||||
|
_refresh_amount()
|
||||||
|
|
||||||
|
|
||||||
|
func get_amount_label() -> Label:
|
||||||
|
if _amount_label != null:
|
||||||
|
return _amount_label
|
||||||
|
return get_node_or_null("Amount") as Label
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_amount() -> void:
|
||||||
|
if _amount_label != null:
|
||||||
|
_amount_label.text = _display_text
|
||||||
|
|
||||||
|
|
||||||
|
func _refresh_icon_size() -> void:
|
||||||
|
if _icon != null:
|
||||||
|
_icon.custom_minimum_size = Vector2.ONE * icon_size
|
||||||
1
ui/components/currency_amount.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://b7jx040ujcol
|
||||||
29
ui/components/currency_amount.tscn
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
[gd_scene load_steps=3 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://ui/components/currency_amount.gd" id="1_script"]
|
||||||
|
[ext_resource type="Texture2D" path="res://items/icons/shop/32_currency.png" id="2_coin"]
|
||||||
|
|
||||||
|
[node name="CurrencyAmount" type="HBoxContainer"]
|
||||||
|
mouse_filter = 2
|
||||||
|
theme_override_constants/separation = 3
|
||||||
|
alignment = 1
|
||||||
|
script = ExtResource("1_script")
|
||||||
|
|
||||||
|
[node name="Icon" type="TextureRect" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(18, 18)
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
texture = ExtResource("2_coin")
|
||||||
|
expand_mode = 1
|
||||||
|
stretch_mode = 5
|
||||||
|
texture_filter = 1
|
||||||
|
|
||||||
|
[node name="Amount" type="Label" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 4
|
||||||
|
mouse_filter = 2
|
||||||
|
text = "0"
|
||||||
|
vertical_alignment = 1
|
||||||
28
ui/currency_presentation.gd
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
class_name CurrencyPresentation
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const ICON_PATH: String = (
|
||||||
|
"res://items/icons/shop/32_currency.png"
|
||||||
|
)
|
||||||
|
const AMOUNT_SCENE: PackedScene = preload(
|
||||||
|
"res://ui/components/currency_amount.tscn"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
static func instantiate_amount(
|
||||||
|
value: int,
|
||||||
|
icon_size: float = 18.0,
|
||||||
|
) -> CurrencyAmount:
|
||||||
|
var display := AMOUNT_SCENE.instantiate() as CurrencyAmount
|
||||||
|
display.icon_size = icon_size
|
||||||
|
display.amount = value
|
||||||
|
return display
|
||||||
|
|
||||||
|
|
||||||
|
static func bbcode_amount(value: int, icon_size: int = 18) -> String:
|
||||||
|
return "[img=%dx%d]%s[/img] %d" % [
|
||||||
|
icon_size,
|
||||||
|
icon_size,
|
||||||
|
ICON_PATH,
|
||||||
|
value,
|
||||||
|
]
|
||||||
1
ui/currency_presentation.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
uid://bcvy1l2jqqbmt
|
||||||
|
|
@ -30,6 +30,15 @@ const UIMotionType = preload("res://ui/ui_motion.gd")
|
||||||
const FALLBACK_SUPPLY_ICON: Texture2D = preload(
|
const FALLBACK_SUPPLY_ICON: Texture2D = preload(
|
||||||
"res://ui/icons/pictograms/x_light.png"
|
"res://ui/icons/pictograms/x_light.png"
|
||||||
)
|
)
|
||||||
|
const ART_KIT_MARKER_ICON: Texture2D = preload(
|
||||||
|
"res://items/icons/art/art_kit_marker.png"
|
||||||
|
)
|
||||||
|
const ART_KIT_MARKER_SHADER: Shader = preload(
|
||||||
|
"res://ui/art_kit_marker_icon.gdshader"
|
||||||
|
)
|
||||||
|
const CURRENCY_ICON: Texture2D = preload(
|
||||||
|
"res://items/icons/shop/32_currency.png"
|
||||||
|
)
|
||||||
|
|
||||||
signal menu_visibility_changed(is_open: bool)
|
signal menu_visibility_changed(is_open: bool)
|
||||||
signal menu_exit_started
|
signal menu_exit_started
|
||||||
|
|
@ -71,6 +80,8 @@ const SUPPLY_PRICE_FONT_SIZE: int = 15
|
||||||
const SUPPLY_PRICE_Y: float = 60.0
|
const SUPPLY_PRICE_Y: float = 60.0
|
||||||
const SUPPLY_PRICE_HEIGHT: float = 24.0
|
const SUPPLY_PRICE_HEIGHT: float = 24.0
|
||||||
const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0
|
const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0
|
||||||
|
const SUPPLY_PRICE_ICON_SIZE: float = 18.0
|
||||||
|
const SUPPLY_PRICE_ICON_GAP: float = 3.0
|
||||||
|
|
||||||
@onready var _wallet_label: Label = %WalletLabel
|
@onready var _wallet_label: Label = %WalletLabel
|
||||||
@onready var _shop_panel: PanelContainer = %ShopPanel
|
@onready var _shop_panel: PanelContainer = %ShopPanel
|
||||||
|
|
@ -79,23 +90,20 @@ const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0
|
||||||
@onready var _shop_cooler_page: Control = %ShopCoolerPage
|
@onready var _shop_cooler_page: Control = %ShopCoolerPage
|
||||||
@onready var _shop_cooler_mount: Control = %ShopCoolerMount
|
@onready var _shop_cooler_mount: Control = %ShopCoolerMount
|
||||||
@onready var _shop_body: HBoxContainer = $ShopPanel/Margin/Layout/Body
|
@onready var _shop_body: HBoxContainer = $ShopPanel/Margin/Layout/Body
|
||||||
@onready var _feedback: Label = %Feedback
|
@onready var _feedback: RichTextLabel = %Feedback
|
||||||
@onready var _shop_tab_bar: HBoxContainer = %ShopTabBar
|
@onready var _shop_tab_bar: HBoxContainer = %ShopTabBar
|
||||||
@onready var _upgrades_content: VBoxContainer = %Upgrades
|
@onready var _upgrades_content: VBoxContainer = %Upgrades
|
||||||
@onready var _supplies_content: VBoxContainer = %Supplies
|
@onready var _supplies_content: VBoxContainer = %Supplies
|
||||||
@onready var _stock_title: Label = %StockTitle
|
@onready var _stock_title: Label = %StockTitle
|
||||||
@onready var _reel_level: Label = %ReelLevel
|
@onready var _reel_cost: CurrencyAmount = %ReelCost
|
||||||
@onready var _reel_effect: Label = %ReelEffect
|
@onready var _reel_price_bubble: PanelContainer = %ReelPriceBubble
|
||||||
@onready var _reel_cost: Label = %ReelCost
|
|
||||||
@onready var _reel_purchase: Button = %ReelPurchase
|
@onready var _reel_purchase: Button = %ReelPurchase
|
||||||
@onready var _barrier_level: Label = %BarrierLevel
|
@onready var _barrier_cost: CurrencyAmount = %BarrierCost
|
||||||
@onready var _barrier_effect: Label = %BarrierEffect
|
@onready var _barrier_price_bubble: PanelContainer = %BarrierPriceBubble
|
||||||
@onready var _barrier_cost: Label = %BarrierCost
|
|
||||||
@onready var _barrier_purchase: Button = %BarrierPurchase
|
@onready var _barrier_purchase: Button = %BarrierPurchase
|
||||||
@onready var _supplies_list: VBoxContainer = %SuppliesList
|
@onready var _supplies_list: VBoxContainer = %SuppliesList
|
||||||
@onready var _cooler_level: Label = %CoolerLevel
|
@onready var _cooler_cost: CurrencyAmount = %CoolerCost
|
||||||
@onready var _cooler_effect: Label = %CoolerEffect
|
@onready var _cooler_price_bubble: PanelContainer = %CoolerPriceBubble
|
||||||
@onready var _cooler_cost: Label = %CoolerCost
|
|
||||||
@onready var _cooler_purchase: Button = %CoolerPurchase
|
@onready var _cooler_purchase: Button = %CoolerPurchase
|
||||||
|
|
||||||
var _player: PlayerType
|
var _player: PlayerType
|
||||||
|
|
@ -136,17 +144,17 @@ func _ready() -> void:
|
||||||
|
|
||||||
func _request_shop_cooler() -> bool:
|
func _request_shop_cooler() -> bool:
|
||||||
if _transaction_in_progress:
|
if _transaction_in_progress:
|
||||||
_feedback.text = "Finish the current transaction first."
|
_set_feedback("Finish the current transaction first.")
|
||||||
return false
|
return false
|
||||||
if not _is_transaction_context_valid():
|
if not _is_transaction_context_valid():
|
||||||
_feedback.text = "The fishing shop is no longer available."
|
_set_feedback("The fishing shop is no longer available.")
|
||||||
return false
|
return false
|
||||||
sell_fish_requested.emit()
|
sell_fish_requested.emit()
|
||||||
return _cooler_page_active
|
return _cooler_page_active
|
||||||
|
|
||||||
|
|
||||||
func _focus_shop_section() -> void:
|
func _focus_shop_section() -> void:
|
||||||
_feedback.text = ""
|
_set_feedback("")
|
||||||
if _shop_section == ShopSection.UPGRADES:
|
if _shop_section == ShopSection.UPGRADES:
|
||||||
_reel_purchase.grab_focus()
|
_reel_purchase.grab_focus()
|
||||||
return
|
return
|
||||||
|
|
@ -193,6 +201,10 @@ func _select_shop_section(section_index: int, focus_content: bool) -> void:
|
||||||
if _closing:
|
if _closing:
|
||||||
return
|
return
|
||||||
if section_index == int(_shop_section):
|
if section_index == int(_shop_section):
|
||||||
|
if section_index != ShopSection.SELL_FISH:
|
||||||
|
var showing_upgrades := section_index == ShopSection.UPGRADES
|
||||||
|
_upgrades_content.visible = showing_upgrades
|
||||||
|
_supplies_content.visible = not showing_upgrades
|
||||||
_update_shop_tab_selection()
|
_update_shop_tab_selection()
|
||||||
if focus_content:
|
if focus_content:
|
||||||
_focus_shop_section()
|
_focus_shop_section()
|
||||||
|
|
@ -236,13 +248,31 @@ func _apply_shop_styles() -> void:
|
||||||
"panel", shop_panel_style
|
"panel", shop_panel_style
|
||||||
)
|
)
|
||||||
for panel: PanelContainer in [
|
for panel: PanelContainer in [
|
||||||
$ShopPanel/Margin/Layout/Body/Upgrades/ReelCard,
|
_reel_price_bubble,
|
||||||
$ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard,
|
_barrier_price_bubble,
|
||||||
$ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard,
|
_cooler_price_bubble,
|
||||||
]:
|
]:
|
||||||
panel.add_theme_stylebox_override(
|
var price_style := UtilityPageStyleType.rounded_style(
|
||||||
"panel", UtilityPageStyleType.row_style()
|
UtilityPageStyleType.OCEAN_FIELD,
|
||||||
|
18,
|
||||||
)
|
)
|
||||||
|
price_style.content_margin_left = 8.0
|
||||||
|
price_style.content_margin_right = 8.0
|
||||||
|
price_style.content_margin_top = 2.0
|
||||||
|
price_style.content_margin_bottom = 2.0
|
||||||
|
panel.add_theme_stylebox_override(
|
||||||
|
"panel", price_style
|
||||||
|
)
|
||||||
|
for price: CurrencyAmount in [
|
||||||
|
_reel_cost,
|
||||||
|
_barrier_cost,
|
||||||
|
_cooler_cost,
|
||||||
|
]:
|
||||||
|
var amount_label := price.get_amount_label()
|
||||||
|
amount_label.add_theme_color_override(
|
||||||
|
"font_color", SUPPLY_PRICE_COLOR
|
||||||
|
)
|
||||||
|
amount_label.add_theme_font_size_override("font_size", 18)
|
||||||
for button: BaseButton in [
|
for button: BaseButton in [
|
||||||
%CloseButton,
|
%CloseButton,
|
||||||
_reel_purchase,
|
_reel_purchase,
|
||||||
|
|
@ -336,7 +366,7 @@ func open_shop() -> bool:
|
||||||
_player.set_camera_input_enabled(false)
|
_player.set_camera_input_enabled(false)
|
||||||
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
|
_fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, true)
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
_feedback.text = ""
|
_set_feedback("")
|
||||||
deactivate_shop_cooler_page()
|
deactivate_shop_cooler_page()
|
||||||
show()
|
show()
|
||||||
_shop_tab_bar.show()
|
_shop_tab_bar.show()
|
||||||
|
|
@ -520,7 +550,6 @@ func _refresh_upgrades() -> void:
|
||||||
return
|
return
|
||||||
var reel_level: int = _upgrades.get_reel_speed_level()
|
var reel_level: int = _upgrades.get_reel_speed_level()
|
||||||
var reel_cost: int = _upgrades.get_next_reel_speed_cost()
|
var reel_cost: int = _upgrades.get_next_reel_speed_cost()
|
||||||
_reel_level.text = "level %d" % reel_level
|
|
||||||
_reel_purchase.disabled = (
|
_reel_purchase.disabled = (
|
||||||
reel_cost < 0
|
reel_cost < 0
|
||||||
or _transaction_in_progress
|
or _transaction_in_progress
|
||||||
|
|
@ -529,27 +558,30 @@ func _refresh_upgrades() -> void:
|
||||||
or not _network_shop.can_request_purchase()
|
or not _network_shop.can_request_purchase()
|
||||||
or not _upgrades.can_purchase_reel_speed(_wallet)
|
or not _upgrades.can_purchase_reel_speed(_wallet)
|
||||||
)
|
)
|
||||||
_reel_purchase.tooltip_text = (
|
var reel_effect: String
|
||||||
"Purchases are unavailable in this session."
|
|
||||||
if _network_shop == null or not _network_shop.can_request_purchase()
|
|
||||||
else ""
|
|
||||||
)
|
|
||||||
if reel_cost < 0:
|
if reel_cost < 0:
|
||||||
_reel_effect.text = "%.2f×" % _upgrades.get_reel_speed_multiplier()
|
reel_effect = "%.2f× reel speed · maximum level" % (
|
||||||
_reel_cost.text = "max"
|
_upgrades.get_reel_speed_multiplier()
|
||||||
|
)
|
||||||
|
_reel_price_bubble.hide()
|
||||||
else:
|
else:
|
||||||
_reel_effect.text = (
|
_reel_price_bubble.show()
|
||||||
|
reel_effect = (
|
||||||
"%.2f× → %.2f×"
|
"%.2f× → %.2f×"
|
||||||
% [
|
% [
|
||||||
_upgrades.get_reel_speed_multiplier(),
|
_upgrades.get_reel_speed_multiplier(),
|
||||||
1.0 + float(reel_level + 1) * 0.10,
|
1.0 + float(reel_level + 1) * 0.10,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
_reel_cost.text = "$%d" % reel_cost
|
_reel_cost.set_amount(reel_cost)
|
||||||
_reel_purchase.text = "max" if reel_cost < 0 else "purchase"
|
_reel_purchase.tooltip_text = _upgrade_tooltip(
|
||||||
|
"reel speed",
|
||||||
|
reel_level,
|
||||||
|
reel_effect,
|
||||||
|
)
|
||||||
|
_reel_purchase.accessibility_name = _reel_purchase.tooltip_text
|
||||||
var barrier_level: int = _upgrades.get_barrier_power_level()
|
var barrier_level: int = _upgrades.get_barrier_power_level()
|
||||||
var barrier_cost: int = _upgrades.get_next_barrier_power_cost()
|
var barrier_cost: int = _upgrades.get_next_barrier_power_cost()
|
||||||
_barrier_level.text = "level %d" % barrier_level
|
|
||||||
_barrier_purchase.disabled = (
|
_barrier_purchase.disabled = (
|
||||||
barrier_cost < 0
|
barrier_cost < 0
|
||||||
or _transaction_in_progress
|
or _transaction_in_progress
|
||||||
|
|
@ -558,20 +590,39 @@ func _refresh_upgrades() -> void:
|
||||||
or not _network_shop.can_request_purchase()
|
or not _network_shop.can_request_purchase()
|
||||||
or not _upgrades.can_purchase_barrier_power(_wallet)
|
or not _upgrades.can_purchase_barrier_power(_wallet)
|
||||||
)
|
)
|
||||||
_barrier_purchase.tooltip_text = _reel_purchase.tooltip_text
|
var barrier_effect: String
|
||||||
if barrier_cost < 0:
|
if barrier_cost < 0:
|
||||||
_barrier_effect.text = "%d damage" % _upgrades.get_barrier_damage()
|
barrier_effect = "%d damage · maximum level" % (
|
||||||
_barrier_cost.text = "max"
|
_upgrades.get_barrier_damage()
|
||||||
|
)
|
||||||
|
_barrier_price_bubble.hide()
|
||||||
else:
|
else:
|
||||||
_barrier_effect.text = (
|
_barrier_price_bubble.show()
|
||||||
|
barrier_effect = (
|
||||||
"%d damage → %d damage"
|
"%d damage → %d damage"
|
||||||
% [
|
% [
|
||||||
_upgrades.get_barrier_damage(),
|
_upgrades.get_barrier_damage(),
|
||||||
barrier_level + 2,
|
barrier_level + 2,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
_barrier_cost.text = "$%d" % barrier_cost
|
_barrier_cost.set_amount(barrier_cost)
|
||||||
_barrier_purchase.text = "max" if barrier_cost < 0 else "purchase"
|
_barrier_purchase.tooltip_text = _upgrade_tooltip(
|
||||||
|
"rod power",
|
||||||
|
barrier_level,
|
||||||
|
barrier_effect,
|
||||||
|
)
|
||||||
|
_barrier_purchase.accessibility_name = _barrier_purchase.tooltip_text
|
||||||
|
|
||||||
|
|
||||||
|
func _upgrade_tooltip(
|
||||||
|
upgrade_name: String,
|
||||||
|
level: int,
|
||||||
|
effect: String,
|
||||||
|
) -> String:
|
||||||
|
var tooltip := "%s\nlevel %d\n%s" % [upgrade_name, level, effect]
|
||||||
|
if _network_shop == null or not _network_shop.can_request_purchase():
|
||||||
|
tooltip += "\nPurchases are unavailable in this session."
|
||||||
|
return tooltip
|
||||||
|
|
||||||
|
|
||||||
func _refresh_supplies() -> void:
|
func _refresh_supplies() -> void:
|
||||||
|
|
@ -632,9 +683,8 @@ func _refresh_supplies() -> void:
|
||||||
var use_icon_tile: bool = (
|
var use_icon_tile: bool = (
|
||||||
current_stock_grid != null
|
current_stock_grid != null
|
||||||
)
|
)
|
||||||
button.text = "%s\n$%d • owned %d" % [
|
button.text = "%s\nowned %d" % [
|
||||||
item.display_name,
|
item.display_name,
|
||||||
FishingShopStockType.get_price(item_id),
|
|
||||||
owned,
|
owned,
|
||||||
]
|
]
|
||||||
var item_tooltip_text: String = item.description
|
var item_tooltip_text: String = item.description
|
||||||
|
|
@ -646,27 +696,24 @@ func _refresh_supplies() -> void:
|
||||||
button.text = ""
|
button.text = ""
|
||||||
else:
|
else:
|
||||||
button.text = (
|
button.text = (
|
||||||
"%s\nunlock $%d" % [item.display_name, total_cost]
|
"%s\nunlock and fill" % item.display_name
|
||||||
if not bait_unlocked
|
if not bait_unlocked
|
||||||
else "%s\n$%d each • %d/%d" % [
|
else "%s\n%d/%d owned" % [
|
||||||
item.display_name,
|
item.display_name,
|
||||||
FishingShopStockType.get_price(item_id),
|
|
||||||
owned,
|
owned,
|
||||||
item.max_stack,
|
item.max_stack,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
item_tooltip_text = (
|
item_tooltip_text = (
|
||||||
"%s\nunlock $%d • fills to %d/%d\n%s" % [
|
"%s\nunlock and fill to %d/%d\n%s" % [
|
||||||
item.display_name,
|
item.display_name,
|
||||||
total_cost,
|
|
||||||
item.max_stack,
|
item.max_stack,
|
||||||
item.max_stack,
|
item.max_stack,
|
||||||
item.description,
|
item.description,
|
||||||
]
|
]
|
||||||
if not bait_unlocked
|
if not bait_unlocked
|
||||||
else "%s\n$%d each • %d/%d\n%s" % [
|
else "%s\n%d/%d owned\n%s" % [
|
||||||
item.display_name,
|
item.display_name,
|
||||||
FishingShopStockType.get_price(item_id),
|
|
||||||
owned,
|
owned,
|
||||||
item.max_stack,
|
item.max_stack,
|
||||||
item.description,
|
item.description,
|
||||||
|
|
@ -677,9 +724,8 @@ func _refresh_supplies() -> void:
|
||||||
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||||
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
button.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
button.text = ""
|
button.text = ""
|
||||||
item_tooltip_text = "%s\n$%d • owned %d\n%s" % [
|
item_tooltip_text = "%s\nowned %d\n%s" % [
|
||||||
item.display_name,
|
item.display_name,
|
||||||
FishingShopStockType.get_price(item_id),
|
|
||||||
owned,
|
owned,
|
||||||
item.description,
|
item.description,
|
||||||
]
|
]
|
||||||
|
|
@ -704,7 +750,11 @@ func _refresh_supplies() -> void:
|
||||||
_add_supply_icon_tile(
|
_add_supply_icon_tile(
|
||||||
current_stock_grid,
|
current_stock_grid,
|
||||||
button,
|
button,
|
||||||
FishingShopStockType.get_price(item_id),
|
(
|
||||||
|
total_cost
|
||||||
|
if bait_topoff and not bait_unlocked
|
||||||
|
else FishingShopStockType.get_price(item_id)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
_supplies_list.add_child(button)
|
_supplies_list.add_child(button)
|
||||||
|
|
@ -769,14 +819,12 @@ func _add_art_kit_button(parent: GridContainer) -> void:
|
||||||
return
|
return
|
||||||
var owned: bool = _bag.get_quantity(ArtShopStockType.ART_KIT_ITEM_ID) > 0
|
var owned: bool = _bag.get_quantity(ArtShopStockType.ART_KIT_ITEM_ID) > 0
|
||||||
var button: Button = _make_stock_button(
|
var button: Button = _make_stock_button(
|
||||||
"%s\n$%d • %s" % [
|
"%s\n%s" % [
|
||||||
item.display_name,
|
item.display_name,
|
||||||
ArtShopStockType.ART_KIT_PRICE,
|
|
||||||
"owned" if owned else "not owned",
|
"owned" if owned else "not owned",
|
||||||
],
|
],
|
||||||
"%s\n$%d • %s\n%s" % [
|
"%s\n%s\n%s" % [
|
||||||
item.display_name,
|
item.display_name,
|
||||||
ArtShopStockType.ART_KIT_PRICE,
|
|
||||||
"owned" if owned else "not owned",
|
"owned" if owned else "not owned",
|
||||||
item.description,
|
item.description,
|
||||||
],
|
],
|
||||||
|
|
@ -808,19 +856,26 @@ func _add_art_upgrade_button(
|
||||||
)
|
)
|
||||||
var unlocked: bool = _art_unlocks.owns_product(product_id)
|
var unlocked: bool = _art_unlocks.owns_product(product_id)
|
||||||
var button: Button = _make_stock_button(
|
var button: Button = _make_stock_button(
|
||||||
"%s\n$%d • %s" % [
|
"%s\n%s" % [
|
||||||
ArtShopStockType.get_display_name(product_id),
|
ArtShopStockType.get_display_name(product_id),
|
||||||
ArtShopStockType.UPGRADE_PRICE,
|
|
||||||
"unlocked" if unlocked else "locked",
|
"unlocked" if unlocked else "locked",
|
||||||
],
|
],
|
||||||
"%s\n$%d • %s\n%s" % [
|
"%s\n%s\n%s" % [
|
||||||
ArtShopStockType.get_display_name(product_id),
|
ArtShopStockType.get_display_name(product_id),
|
||||||
ArtShopStockType.UPGRADE_PRICE,
|
|
||||||
"unlocked" if unlocked else "locked",
|
"unlocked" if unlocked else "locked",
|
||||||
ArtShopStockType.get_description(product_id),
|
ArtShopStockType.get_description(product_id),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
_configure_supply_icon_tile(button, FALLBACK_SUPPLY_ICON)
|
var marker_color_id: StringName = (
|
||||||
|
PlayerArtUnlocksType.color_id_for_product(product_id)
|
||||||
|
)
|
||||||
|
if marker_color_id.is_empty():
|
||||||
|
_configure_supply_icon_tile(button, FALLBACK_SUPPLY_ICON)
|
||||||
|
else:
|
||||||
|
_configure_marker_icon_tile(
|
||||||
|
button,
|
||||||
|
SurfaceDrawingPalette.get_color(marker_color_id),
|
||||||
|
)
|
||||||
_add_supply_icon_tile(
|
_add_supply_icon_tile(
|
||||||
parent,
|
parent,
|
||||||
button,
|
button,
|
||||||
|
|
@ -863,6 +918,29 @@ func _configure_supply_icon_tile(
|
||||||
button.text = ""
|
button.text = ""
|
||||||
|
|
||||||
|
|
||||||
|
func _configure_marker_icon_tile(
|
||||||
|
button: Button,
|
||||||
|
marker_color: Color,
|
||||||
|
) -> void:
|
||||||
|
button.custom_minimum_size = SUPPLY_ICON_TILE_SIZE
|
||||||
|
button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||||
|
button.icon = null
|
||||||
|
button.text = ""
|
||||||
|
var icon := TextureRect.new()
|
||||||
|
icon.name = "MarkerIcon"
|
||||||
|
icon.position = Vector2(4.0, 4.0)
|
||||||
|
icon.size = Vector2(64.0, 64.0)
|
||||||
|
icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
|
icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
|
icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
var marker_material := ShaderMaterial.new()
|
||||||
|
marker_material.shader = ART_KIT_MARKER_SHADER
|
||||||
|
marker_material.set_shader_parameter("marker_color", marker_color)
|
||||||
|
icon.material = marker_material
|
||||||
|
icon.texture = ART_KIT_MARKER_ICON
|
||||||
|
button.add_child(icon)
|
||||||
|
|
||||||
|
|
||||||
func _add_supply_icon_tile(
|
func _add_supply_icon_tile(
|
||||||
parent: GridContainer,
|
parent: GridContainer,
|
||||||
button: Button,
|
button: Button,
|
||||||
|
|
@ -875,7 +953,7 @@ func _add_supply_icon_tile(
|
||||||
button.position = Vector2.ZERO
|
button.position = Vector2.ZERO
|
||||||
button.size = SUPPLY_ICON_TILE_SIZE
|
button.size = SUPPLY_ICON_TILE_SIZE
|
||||||
tile_host.add_child(button)
|
tile_host.add_child(button)
|
||||||
var price_text := "$%d" % price
|
var price_text := str(price)
|
||||||
var price_text_width: float = UtilityPageStyleType.TuffyFont.get_string_size(
|
var price_text_width: float = UtilityPageStyleType.TuffyFont.get_string_size(
|
||||||
price_text,
|
price_text,
|
||||||
HORIZONTAL_ALIGNMENT_LEFT,
|
HORIZONTAL_ALIGNMENT_LEFT,
|
||||||
|
|
@ -884,7 +962,12 @@ func _add_supply_icon_tile(
|
||||||
).x
|
).x
|
||||||
var price_width: float = minf(
|
var price_width: float = minf(
|
||||||
SUPPLY_ICON_TILE_SIZE.x,
|
SUPPLY_ICON_TILE_SIZE.x,
|
||||||
ceilf(price_text_width + SUPPLY_PRICE_HORIZONTAL_PADDING),
|
ceilf(
|
||||||
|
price_text_width
|
||||||
|
+ SUPPLY_PRICE_ICON_SIZE
|
||||||
|
+ SUPPLY_PRICE_ICON_GAP
|
||||||
|
+ SUPPLY_PRICE_HORIZONTAL_PADDING
|
||||||
|
),
|
||||||
)
|
)
|
||||||
var price_bubble := PanelContainer.new()
|
var price_bubble := PanelContainer.new()
|
||||||
price_bubble.name = "PriceBubble"
|
price_bubble.name = "PriceBubble"
|
||||||
|
|
@ -905,6 +988,23 @@ func _add_supply_icon_tile(
|
||||||
price_style.content_margin_bottom = 0.0
|
price_style.content_margin_bottom = 0.0
|
||||||
price_bubble.add_theme_stylebox_override("panel", price_style)
|
price_bubble.add_theme_stylebox_override("panel", price_style)
|
||||||
tile_host.add_child(price_bubble)
|
tile_host.add_child(price_bubble)
|
||||||
|
var price_row := HBoxContainer.new()
|
||||||
|
price_row.name = "CurrencyAmount"
|
||||||
|
price_row.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
price_row.alignment = BoxContainer.ALIGNMENT_CENTER
|
||||||
|
price_row.add_theme_constant_override(
|
||||||
|
"separation", int(SUPPLY_PRICE_ICON_GAP)
|
||||||
|
)
|
||||||
|
price_bubble.add_child(price_row)
|
||||||
|
var price_icon := TextureRect.new()
|
||||||
|
price_icon.name = "CurrencyIcon"
|
||||||
|
price_icon.custom_minimum_size = Vector2.ONE * SUPPLY_PRICE_ICON_SIZE
|
||||||
|
price_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
price_icon.texture = CURRENCY_ICON
|
||||||
|
price_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
|
price_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
|
price_icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||||
|
price_row.add_child(price_icon)
|
||||||
var price_label := Label.new()
|
var price_label := Label.new()
|
||||||
price_label.name = "Price"
|
price_label.name = "Price"
|
||||||
price_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
price_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
|
@ -921,7 +1021,7 @@ func _add_supply_icon_tile(
|
||||||
"font_color", SUPPLY_PRICE_COLOR
|
"font_color", SUPPLY_PRICE_COLOR
|
||||||
)
|
)
|
||||||
price_label.add_theme_constant_override("outline_size", 0)
|
price_label.add_theme_constant_override("outline_size", 0)
|
||||||
price_bubble.add_child(price_label)
|
price_row.add_child(price_label)
|
||||||
|
|
||||||
|
|
||||||
func _refresh_cooler_capacity() -> void:
|
func _refresh_cooler_capacity() -> void:
|
||||||
|
|
@ -930,7 +1030,6 @@ func _refresh_cooler_capacity() -> void:
|
||||||
var level: int = _cooler_capacity.get_level()
|
var level: int = _cooler_capacity.get_level()
|
||||||
var cost: int = _cooler_capacity.get_next_cost()
|
var cost: int = _cooler_capacity.get_next_cost()
|
||||||
var next_capacity: int = _cooler_capacity.get_next_capacity()
|
var next_capacity: int = _cooler_capacity.get_next_capacity()
|
||||||
_cooler_level.text = "level %d" % level
|
|
||||||
_cooler_purchase.disabled = (
|
_cooler_purchase.disabled = (
|
||||||
cost < 0
|
cost < 0
|
||||||
or _transaction_in_progress
|
or _transaction_in_progress
|
||||||
|
|
@ -939,18 +1038,25 @@ func _refresh_cooler_capacity() -> void:
|
||||||
or not _network_shop.can_request_purchase()
|
or not _network_shop.can_request_purchase()
|
||||||
or not _cooler_capacity.can_purchase(_wallet)
|
or not _cooler_capacity.can_purchase(_wallet)
|
||||||
)
|
)
|
||||||
_cooler_purchase.tooltip_text = _reel_purchase.tooltip_text
|
var cooler_effect: String
|
||||||
if cost < 0:
|
if cost < 0:
|
||||||
_cooler_effect.text = "%d fish" % _cooler_capacity.get_capacity()
|
cooler_effect = "%d fish · maximum level" % (
|
||||||
_cooler_cost.text = "max"
|
_cooler_capacity.get_capacity()
|
||||||
_cooler_purchase.text = "max"
|
)
|
||||||
|
_cooler_price_bubble.hide()
|
||||||
else:
|
else:
|
||||||
_cooler_effect.text = "%d → %d fish" % [
|
_cooler_price_bubble.show()
|
||||||
|
cooler_effect = "%d → %d fish" % [
|
||||||
_cooler_capacity.get_capacity(),
|
_cooler_capacity.get_capacity(),
|
||||||
next_capacity,
|
next_capacity,
|
||||||
]
|
]
|
||||||
_cooler_cost.text = "$%d" % cost
|
_cooler_cost.set_amount(cost)
|
||||||
_cooler_purchase.text = "purchase"
|
_cooler_purchase.tooltip_text = _upgrade_tooltip(
|
||||||
|
"cooler capacity",
|
||||||
|
level,
|
||||||
|
cooler_effect,
|
||||||
|
)
|
||||||
|
_cooler_purchase.accessibility_name = _cooler_purchase.tooltip_text
|
||||||
|
|
||||||
|
|
||||||
func _purchase_reel_speed() -> void:
|
func _purchase_reel_speed() -> void:
|
||||||
|
|
@ -963,65 +1069,65 @@ func _purchase_barrier_power() -> void:
|
||||||
|
|
||||||
func _purchase_supply(item_id: StringName) -> void:
|
func _purchase_supply(item_id: StringName) -> void:
|
||||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||||
_feedback.text = "unable to complete purchase."
|
_set_feedback("unable to complete purchase.")
|
||||||
return
|
return
|
||||||
var owned: int = _bag.get_quantity(item_id)
|
var owned: int = _bag.get_quantity(item_id)
|
||||||
var item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
var item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
||||||
if item == null:
|
if item == null:
|
||||||
_feedback.text = "Purchase could not be completed."
|
_set_feedback("Purchase could not be completed.")
|
||||||
return
|
return
|
||||||
var quantity: int = FishingShopStockType.get_purchase_quantity(
|
var quantity: int = FishingShopStockType.get_purchase_quantity(
|
||||||
item_id, owned, item.max_stack
|
item_id, owned, item.max_stack
|
||||||
)
|
)
|
||||||
if quantity <= 0 or not _bag.can_add_item(item_id, quantity):
|
if quantity <= 0 or not _bag.can_add_item(item_id, quantity):
|
||||||
_feedback.text = "Your Bag is full."
|
_set_feedback("Your Bag is full.")
|
||||||
return
|
return
|
||||||
var total_cost: int = FishingShopStockType.get_purchase_cost(
|
var total_cost: int = FishingShopStockType.get_purchase_cost(
|
||||||
item_id, quantity, _bag.is_bait_unlocked(item_id)
|
item_id, quantity, _bag.is_bait_unlocked(item_id)
|
||||||
)
|
)
|
||||||
if total_cost < 0 or not _wallet.can_afford(total_cost):
|
if total_cost < 0 or not _wallet.can_afford(total_cost):
|
||||||
_feedback.text = "Not enough fish coin."
|
_set_feedback("Insufficient funds.")
|
||||||
return
|
return
|
||||||
if _network_shop == null:
|
if _network_shop == null:
|
||||||
_feedback.text = "Purchase could not be completed."
|
_set_feedback("Purchase could not be completed.")
|
||||||
return
|
return
|
||||||
_network_shop.request_supply(item_id)
|
_network_shop.request_supply(item_id)
|
||||||
|
|
||||||
|
|
||||||
func _purchase_art_kit() -> void:
|
func _purchase_art_kit() -> void:
|
||||||
if _network_shop == null or not _is_transaction_context_valid():
|
if _network_shop == null or not _is_transaction_context_valid():
|
||||||
_feedback.text = "Purchase could not be completed."
|
_set_feedback("Purchase could not be completed.")
|
||||||
return
|
return
|
||||||
_network_shop.request_art_kit()
|
_network_shop.request_art_kit()
|
||||||
|
|
||||||
|
|
||||||
func _purchase_art_upgrade(product_id: StringName) -> void:
|
func _purchase_art_upgrade(product_id: StringName) -> void:
|
||||||
if _network_shop == null or not _is_transaction_context_valid():
|
if _network_shop == null or not _is_transaction_context_valid():
|
||||||
_feedback.text = "Purchase could not be completed."
|
_set_feedback("Purchase could not be completed.")
|
||||||
return
|
return
|
||||||
_network_shop.request_art_upgrade(product_id)
|
_network_shop.request_art_upgrade(product_id)
|
||||||
|
|
||||||
|
|
||||||
func _purchase_cooler_capacity() -> void:
|
func _purchase_cooler_capacity() -> void:
|
||||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||||
_feedback.text = "unable to complete purchase."
|
_set_feedback("unable to complete purchase.")
|
||||||
return
|
return
|
||||||
var cost: int = _cooler_capacity.get_next_cost()
|
var cost: int = _cooler_capacity.get_next_cost()
|
||||||
if cost < 0:
|
if cost < 0:
|
||||||
_feedback.text = "Upgrade is already at maximum."
|
_set_feedback("Upgrade is already at maximum.")
|
||||||
return
|
return
|
||||||
if not _wallet.can_afford(cost):
|
if not _wallet.can_afford(cost):
|
||||||
_feedback.text = "Not enough fish coin."
|
_set_feedback("Insufficient funds.")
|
||||||
return
|
return
|
||||||
if _network_shop == null:
|
if _network_shop == null:
|
||||||
_feedback.text = "Purchase could not be completed."
|
_set_feedback("Purchase could not be completed.")
|
||||||
return
|
return
|
||||||
_network_shop.request_cooler_capacity_upgrade()
|
_network_shop.request_cooler_capacity_upgrade()
|
||||||
|
|
||||||
|
|
||||||
func _purchase_upgrade(is_reel_speed: bool) -> void:
|
func _purchase_upgrade(is_reel_speed: bool) -> void:
|
||||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||||
_feedback.text = "unable to complete purchase."
|
_set_feedback("unable to complete purchase.")
|
||||||
return
|
return
|
||||||
var cost: int = (
|
var cost: int = (
|
||||||
_upgrades.get_next_reel_speed_cost()
|
_upgrades.get_next_reel_speed_cost()
|
||||||
|
|
@ -1029,13 +1135,13 @@ func _purchase_upgrade(is_reel_speed: bool) -> void:
|
||||||
else _upgrades.get_next_barrier_power_cost()
|
else _upgrades.get_next_barrier_power_cost()
|
||||||
)
|
)
|
||||||
if cost < 0:
|
if cost < 0:
|
||||||
_feedback.text = "Upgrade is already at maximum."
|
_set_feedback("Upgrade is already at maximum.")
|
||||||
return
|
return
|
||||||
if not _wallet.can_afford(cost):
|
if not _wallet.can_afford(cost):
|
||||||
_feedback.text = "Not enough fish coin."
|
_set_feedback("Insufficient funds.")
|
||||||
return
|
return
|
||||||
if _network_shop == null:
|
if _network_shop == null:
|
||||||
_feedback.text = "Purchase could not be completed."
|
_set_feedback("Purchase could not be completed.")
|
||||||
return
|
return
|
||||||
if is_reel_speed:
|
if is_reel_speed:
|
||||||
_network_shop.request_reel_speed_upgrade()
|
_network_shop.request_reel_speed_upgrade()
|
||||||
|
|
@ -1046,7 +1152,7 @@ func _purchase_upgrade(is_reel_speed: bool) -> void:
|
||||||
func _on_network_purchase_pending(_request_id: String) -> void:
|
func _on_network_purchase_pending(_request_id: String) -> void:
|
||||||
_transaction_in_progress = true
|
_transaction_in_progress = true
|
||||||
if visible:
|
if visible:
|
||||||
_feedback.text = "Purchasing…"
|
_set_feedback("Purchasing…")
|
||||||
_refresh_all()
|
_refresh_all()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1062,14 +1168,21 @@ func _on_network_purchase_finished(
|
||||||
_transaction_in_progress = false
|
_transaction_in_progress = false
|
||||||
if not visible:
|
if not visible:
|
||||||
return
|
return
|
||||||
_feedback.text = (
|
_set_feedback(
|
||||||
"Purchase complete. $%d spent." % total_cost
|
(
|
||||||
|
"Purchase complete • %s spent."
|
||||||
|
% CurrencyPresentation.bbcode_amount(total_cost, 18)
|
||||||
|
)
|
||||||
if accepted
|
if accepted
|
||||||
else message
|
else message
|
||||||
)
|
)
|
||||||
_refresh_all()
|
_refresh_all()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_feedback(message: String) -> void:
|
||||||
|
_feedback.text = "[center]%s[/center]" % message
|
||||||
|
|
||||||
|
|
||||||
func _is_transaction_context_valid() -> bool:
|
func _is_transaction_context_valid() -> bool:
|
||||||
return (
|
return (
|
||||||
visible
|
visible
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
[gd_scene load_steps=8 format=3]
|
[gd_scene load_steps=9 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://ui/fishing_shop.gd" id="1_script"]
|
[ext_resource type="Script" path="res://ui/fishing_shop.gd" id="1_script"]
|
||||||
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
||||||
[ext_resource type="Texture2D" path="res://items/icons/shop/64_speed_plus.png" id="3_reel"]
|
[ext_resource type="Texture2D" path="res://items/icons/shop/64_speed_plus.png" id="3_reel"]
|
||||||
[ext_resource type="Texture2D" path="res://items/icons/shop/64_power_plus.png" id="4_barrier"]
|
[ext_resource type="Texture2D" path="res://items/icons/shop/64_power_plus.png" id="4_barrier"]
|
||||||
[ext_resource type="Texture2D" path="res://items/icons/placeholder/fish_coin.svg" id="5_coin"]
|
[ext_resource type="Texture2D" path="res://items/icons/shop/32_currency.png" id="5_coin"]
|
||||||
[ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"]
|
[ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"]
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="7_close"]
|
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="7_close"]
|
||||||
|
[ext_resource type="PackedScene" path="res://ui/components/currency_amount.tscn" id="8_currency"]
|
||||||
|
|
||||||
[node name="FishingShop" type="Control"]
|
[node name="FishingShop" type="Control"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -126,12 +127,16 @@ icon = ExtResource("7_close")
|
||||||
expand_icon = true
|
expand_icon = true
|
||||||
icon_max_width = 40
|
icon_max_width = 40
|
||||||
|
|
||||||
[node name="Feedback" type="Label" parent="ShopPanel/Margin/Layout"]
|
[node name="Feedback" type="RichTextLabel" parent="ShopPanel/Margin/Layout"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 28)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = ""
|
text = ""
|
||||||
horizontal_alignment = 1
|
bbcode_enabled = true
|
||||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
fit_content = true
|
||||||
|
scroll_active = false
|
||||||
|
theme_override_colors/default_color = Color(1, 0.82, 0.4, 1)
|
||||||
|
theme_override_font_sizes/normal_font_size = 20
|
||||||
|
|
||||||
[node name="ShopTabBar" type="HBoxContainer" parent="."]
|
[node name="ShopTabBar" type="HBoxContainer" parent="."]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -184,164 +189,102 @@ layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
theme_override_constants/separation = 5
|
theme_override_constants/separation = 5
|
||||||
|
|
||||||
[node name="ReelCard" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
[node name="UpgradeScroll" type="ScrollContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
size_flags_vertical = 3
|
||||||
|
horizontal_scroll_mode = 0
|
||||||
|
|
||||||
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard"]
|
[node name="UpgradeGrid" type="GridContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll"]
|
||||||
layout_mode = 2
|
unique_name_in_owner = true
|
||||||
theme_override_constants/margin_left = 9
|
|
||||||
theme_override_constants/margin_top = 7
|
|
||||||
theme_override_constants/margin_right = 9
|
|
||||||
theme_override_constants/margin_bottom = 7
|
|
||||||
|
|
||||||
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 9
|
|
||||||
|
|
||||||
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
|
|
||||||
custom_minimum_size = Vector2(88, 88)
|
|
||||||
layout_mode = 2
|
|
||||||
texture = ExtResource("3_reel")
|
|
||||||
expand_mode = 1
|
|
||||||
stretch_mode = 5
|
|
||||||
texture_filter = 1
|
|
||||||
|
|
||||||
[node name="Data" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
|
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
|
theme_override_constants/h_separation = 20
|
||||||
|
theme_override_constants/v_separation = 20
|
||||||
|
columns = 3
|
||||||
|
|
||||||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
[node name="ReelTile" type="Control" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid"]
|
||||||
|
custom_minimum_size = Vector2(144, 168)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "reel speed"
|
mouse_filter = 2
|
||||||
theme_override_font_sizes/font_size = 22
|
|
||||||
|
|
||||||
[node name="ReelLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
[node name="ReelPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/ReelTile"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
offset_right = 144.0
|
||||||
|
offset_bottom = 144.0
|
||||||
|
tooltip_text = "reel speed"
|
||||||
|
icon = ExtResource("3_reel")
|
||||||
|
expand_icon = true
|
||||||
|
icon_max_width = 128
|
||||||
|
|
||||||
|
[node name="ReelPriceBubble" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/ReelTile"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
z_index = 2
|
||||||
|
offset_left = 22.0
|
||||||
|
offset_top = 128.0
|
||||||
|
offset_right = 122.0
|
||||||
|
offset_bottom = 160.0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="ReelCost" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/ReelTile/ReelPriceBubble" instance=ExtResource("8_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "level 0"
|
amount = 50
|
||||||
theme_override_font_sizes/font_size = 18
|
icon_size = 24.0
|
||||||
|
|
||||||
[node name="ReelEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
[node name="BarrierTile" type="Control" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid"]
|
||||||
|
custom_minimum_size = Vector2(144, 168)
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="BarrierPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/BarrierTile"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
offset_right = 144.0
|
||||||
|
offset_bottom = 144.0
|
||||||
|
tooltip_text = "rod power"
|
||||||
|
icon = ExtResource("4_barrier")
|
||||||
|
expand_icon = true
|
||||||
|
icon_max_width = 128
|
||||||
|
|
||||||
|
[node name="BarrierPriceBubble" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/BarrierTile"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
z_index = 2
|
||||||
|
offset_left = 22.0
|
||||||
|
offset_top = 128.0
|
||||||
|
offset_right = 122.0
|
||||||
|
offset_bottom = 160.0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="BarrierCost" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/BarrierTile/BarrierPriceBubble" instance=ExtResource("8_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "1.00× → 1.10×"
|
amount = 100
|
||||||
theme_override_font_sizes/font_size = 18
|
icon_size = 24.0
|
||||||
|
|
||||||
[node name="ReelCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
[node name="CoolerTile" type="Control" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid"]
|
||||||
|
custom_minimum_size = Vector2(144, 168)
|
||||||
|
layout_mode = 2
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="CoolerPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/CoolerTile"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
offset_right = 144.0
|
||||||
|
offset_bottom = 144.0
|
||||||
|
tooltip_text = "cooler capacity"
|
||||||
|
icon = ExtResource("6_cooler")
|
||||||
|
expand_icon = true
|
||||||
|
icon_max_width = 128
|
||||||
|
|
||||||
|
[node name="CoolerPriceBubble" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/CoolerTile"]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
z_index = 2
|
||||||
|
offset_left = 22.0
|
||||||
|
offset_top = 128.0
|
||||||
|
offset_right = 122.0
|
||||||
|
offset_bottom = 160.0
|
||||||
|
mouse_filter = 2
|
||||||
|
|
||||||
|
[node name="CoolerCost" parent="ShopPanel/Margin/Layout/Body/Upgrades/UpgradeScroll/UpgradeGrid/CoolerTile/CoolerPriceBubble" instance=ExtResource("8_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "$50"
|
amount = 75
|
||||||
theme_override_font_sizes/font_size = 18
|
icon_size = 24.0
|
||||||
|
|
||||||
[node name="ReelPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "purchase"
|
|
||||||
|
|
||||||
[node name="BarrierCard" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 9
|
|
||||||
theme_override_constants/margin_top = 7
|
|
||||||
theme_override_constants/margin_right = 9
|
|
||||||
theme_override_constants/margin_bottom = 7
|
|
||||||
|
|
||||||
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 9
|
|
||||||
|
|
||||||
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
|
|
||||||
custom_minimum_size = Vector2(88, 88)
|
|
||||||
layout_mode = 2
|
|
||||||
texture = ExtResource("4_barrier")
|
|
||||||
expand_mode = 1
|
|
||||||
stretch_mode = 5
|
|
||||||
texture_filter = 1
|
|
||||||
|
|
||||||
[node name="Data" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "rod power"
|
|
||||||
theme_override_font_sizes/font_size = 22
|
|
||||||
|
|
||||||
[node name="BarrierLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "level 0"
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
|
|
||||||
[node name="BarrierEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "1 damage → 2 damage"
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
|
|
||||||
[node name="BarrierCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "$100"
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
|
|
||||||
[node name="BarrierPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "purchase"
|
|
||||||
|
|
||||||
[node name="CoolerCard" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
|
||||||
layout_mode = 2
|
|
||||||
|
|
||||||
[node name="Margin" type="MarginContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/margin_left = 9
|
|
||||||
theme_override_constants/margin_top = 7
|
|
||||||
theme_override_constants/margin_right = 9
|
|
||||||
theme_override_constants/margin_bottom = 7
|
|
||||||
|
|
||||||
[node name="Row" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin"]
|
|
||||||
layout_mode = 2
|
|
||||||
theme_override_constants/separation = 9
|
|
||||||
|
|
||||||
[node name="Icon" type="TextureRect" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
|
|
||||||
custom_minimum_size = Vector2(88, 88)
|
|
||||||
layout_mode = 2
|
|
||||||
texture = ExtResource("6_cooler")
|
|
||||||
expand_mode = 1
|
|
||||||
stretch_mode = 5
|
|
||||||
texture_filter = 1
|
|
||||||
|
|
||||||
[node name="Data" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
|
|
||||||
layout_mode = 2
|
|
||||||
size_flags_horizontal = 3
|
|
||||||
|
|
||||||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
|
||||||
layout_mode = 2
|
|
||||||
text = "cooler capacity"
|
|
||||||
theme_override_font_sizes/font_size = 22
|
|
||||||
|
|
||||||
[node name="CoolerLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "level 0"
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
|
|
||||||
[node name="CoolerEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "12 → 18 fish"
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
|
|
||||||
[node name="CoolerCost" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "$75"
|
|
||||||
theme_override_font_sizes/font_size = 18
|
|
||||||
|
|
||||||
[node name="CoolerPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
|
|
||||||
unique_name_in_owner = true
|
|
||||||
layout_mode = 2
|
|
||||||
text = "purchase"
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ const InventoryNotepadType = preload(
|
||||||
const LogbookPortraitType = preload(
|
const LogbookPortraitType = preload(
|
||||||
"res://ui/components/logbook_portrait.gd"
|
"res://ui/components/logbook_portrait.gd"
|
||||||
)
|
)
|
||||||
|
const CurrencyPresentationType = preload(
|
||||||
|
"res://ui/currency_presentation.gd"
|
||||||
|
)
|
||||||
|
|
||||||
const DETAIL_FADE_DURATION: float = 0.12
|
const DETAIL_FADE_DURATION: float = 0.12
|
||||||
const CATEGORY_FADE_DURATION: float = UIMotion.UTILITY_EXIT_DURATION
|
const CATEGORY_FADE_DURATION: float = UIMotion.UTILITY_EXIT_DURATION
|
||||||
|
|
@ -756,19 +759,17 @@ func _build_known_details(fish: FishDataType) -> void:
|
||||||
_add_detail_row(left_stats, "number caught", "unknown")
|
_add_detail_row(left_stats, "number caught", "unknown")
|
||||||
_add_detail_row(right_stats, "rarity", fish.get_rarity_name().to_lower())
|
_add_detail_row(right_stats, "rarity", fish.get_rarity_name().to_lower())
|
||||||
_add_detail_row(right_stats, "time of day", _availability_text(fish))
|
_add_detail_row(right_stats, "time of day", _availability_text(fish))
|
||||||
_add_detail_row(
|
_add_currency_detail_row(
|
||||||
right_stats,
|
right_stats,
|
||||||
"value range",
|
"value range",
|
||||||
"%d–%d fish coin" % [
|
FishQualityType.apply_sale_value(
|
||||||
FishQualityType.apply_sale_value(
|
fish.sell_value_min,
|
||||||
fish.sell_value_min,
|
FishQualityType.Tier.BORING,
|
||||||
FishQualityType.Tier.BORING,
|
),
|
||||||
),
|
FishQualityType.apply_sale_value(
|
||||||
FishQualityType.apply_sale_value(
|
fish.sell_value_max,
|
||||||
fish.sell_value_max,
|
FishQualityType.Tier.SHINY,
|
||||||
FishQualityType.Tier.SHINY,
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
_add_detail_row(
|
_add_detail_row(
|
||||||
right_stats,
|
right_stats,
|
||||||
|
|
@ -878,6 +879,31 @@ func _add_detail_row(
|
||||||
parent.add_child(row)
|
parent.add_child(row)
|
||||||
|
|
||||||
|
|
||||||
|
func _add_currency_detail_row(
|
||||||
|
parent: VBoxContainer,
|
||||||
|
row_name: String,
|
||||||
|
minimum_value: int,
|
||||||
|
maximum_value: int,
|
||||||
|
) -> void:
|
||||||
|
var row := VBoxContainer.new()
|
||||||
|
row.add_theme_constant_override("separation", 2)
|
||||||
|
var heading := _field_label(row_name, 14)
|
||||||
|
heading.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||||
|
heading.add_theme_color_override("font_color", MUTED_INK)
|
||||||
|
row.add_child(heading)
|
||||||
|
var value: CurrencyAmount = (
|
||||||
|
CurrencyPresentationType.instantiate_amount(minimum_value, 16.0)
|
||||||
|
)
|
||||||
|
value.set_amount_text("%d–%d" % [minimum_value, maximum_value])
|
||||||
|
value.get_amount_label().add_theme_font_override(
|
||||||
|
"font", InventoryNotepadType.HANDWRITTEN_FONT
|
||||||
|
)
|
||||||
|
value.get_amount_label().add_theme_font_size_override("font_size", 16)
|
||||||
|
value.get_amount_label().add_theme_color_override("font_color", INK)
|
||||||
|
row.add_child(value)
|
||||||
|
parent.add_child(row)
|
||||||
|
|
||||||
|
|
||||||
func _make_stats_column() -> VBoxContainer:
|
func _make_stats_column() -> VBoxContainer:
|
||||||
var column := VBoxContainer.new()
|
var column := VBoxContainer.new()
|
||||||
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
column.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ const SALUTATION_LABELS := {
|
||||||
"good_luck_have_fun": "Good Luck Have Fun",
|
"good_luck_have_fun": "Good Luck Have Fun",
|
||||||
}
|
}
|
||||||
const FishQualityType = preload("res://fish/fish_quality.gd")
|
const FishQualityType = preload("res://fish/fish_quality.gd")
|
||||||
|
const CurrencyPresentationType = preload(
|
||||||
|
"res://ui/currency_presentation.gd"
|
||||||
|
)
|
||||||
|
|
||||||
var _service: NetworkMailService
|
var _service: NetworkMailService
|
||||||
var _reservations: PlayerAssetReservationService
|
var _reservations: PlayerAssetReservationService
|
||||||
|
|
@ -36,14 +39,16 @@ var _signature: Label
|
||||||
var _attachment_kind: OptionButton
|
var _attachment_kind: OptionButton
|
||||||
var _attachment_choice: OptionButton
|
var _attachment_choice: OptionButton
|
||||||
var _attachment_amount: LineEdit
|
var _attachment_amount: LineEdit
|
||||||
var _coin_available: Label
|
var _coin_available_heading: Label
|
||||||
|
var _coin_available: CurrencyAmount
|
||||||
|
var _attachment_amount_currency_icon: TextureRect
|
||||||
var _amount_minus: Button
|
var _amount_minus: Button
|
||||||
var _amount_plus: Button
|
var _amount_plus: Button
|
||||||
var _attachment_summary: Label
|
var _attachment_summary: RichTextLabel
|
||||||
var _send_button: Button
|
var _send_button: Button
|
||||||
var _status: Label
|
var _status: Label
|
||||||
var _letter_text: Label
|
var _letter_text: Label
|
||||||
var _letter_gift: Label
|
var _letter_gift: RichTextLabel
|
||||||
var _accept: Button
|
var _accept: Button
|
||||||
var _decline: Button
|
var _decline: Button
|
||||||
var _archive: Button
|
var _archive: Button
|
||||||
|
|
@ -212,7 +217,7 @@ func _build_compose() -> Control:
|
||||||
_attachment_kind = OptionButton.new()
|
_attachment_kind = OptionButton.new()
|
||||||
_attachment_kind.position = Vector2(688, 48)
|
_attachment_kind.position = Vector2(688, 48)
|
||||||
_attachment_kind.size = Vector2(280, 46)
|
_attachment_kind.size = Vector2(280, 46)
|
||||||
for label: String in ["No gift", "Fish coin", "Fish", "Consumable"]:
|
for label: String in ["No gift", "Currency", "Fish", "Consumable"]:
|
||||||
_attachment_kind.add_item(label)
|
_attachment_kind.add_item(label)
|
||||||
_attachment_kind.item_selected.connect(_refresh_attachment_choices)
|
_attachment_kind.item_selected.connect(_refresh_attachment_choices)
|
||||||
page.add_child(_attachment_kind)
|
page.add_child(_attachment_kind)
|
||||||
|
|
@ -225,13 +230,34 @@ func _build_compose() -> Control:
|
||||||
_update_attachment_summary()
|
_update_attachment_summary()
|
||||||
)
|
)
|
||||||
page.add_child(_attachment_choice)
|
page.add_child(_attachment_choice)
|
||||||
_coin_available = Label.new()
|
_coin_available_heading = Label.new()
|
||||||
_coin_available.position = Vector2(688, 158)
|
_coin_available_heading.text = "Available"
|
||||||
_coin_available.size = Vector2(280, 28)
|
_coin_available_heading.position = Vector2(688, 158)
|
||||||
|
_coin_available_heading.size = Vector2(80, 28)
|
||||||
|
page.add_child(_coin_available_heading)
|
||||||
|
_coin_available = CurrencyPresentationType.instantiate_amount(0, 18.0)
|
||||||
|
_coin_available.position = Vector2(770, 158)
|
||||||
|
_coin_available.size = Vector2(198, 28)
|
||||||
|
_coin_available.alignment = BoxContainer.ALIGNMENT_BEGIN
|
||||||
page.add_child(_coin_available)
|
page.add_child(_coin_available)
|
||||||
|
_attachment_amount_currency_icon = TextureRect.new()
|
||||||
|
_attachment_amount_currency_icon.position = Vector2(746, 204)
|
||||||
|
_attachment_amount_currency_icon.size = Vector2(18, 18)
|
||||||
|
_attachment_amount_currency_icon.texture = preload(
|
||||||
|
"res://items/icons/shop/32_currency.png"
|
||||||
|
)
|
||||||
|
_attachment_amount_currency_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||||
|
_attachment_amount_currency_icon.stretch_mode = (
|
||||||
|
TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||||
|
)
|
||||||
|
_attachment_amount_currency_icon.texture_filter = (
|
||||||
|
CanvasItem.TEXTURE_FILTER_NEAREST
|
||||||
|
)
|
||||||
|
_attachment_amount_currency_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
page.add_child(_attachment_amount_currency_icon)
|
||||||
_attachment_amount = LineEdit.new()
|
_attachment_amount = LineEdit.new()
|
||||||
_attachment_amount.position = Vector2(746, 190)
|
_attachment_amount.position = Vector2(770, 190)
|
||||||
_attachment_amount.size = Vector2(164, 46)
|
_attachment_amount.size = Vector2(140, 46)
|
||||||
_attachment_amount.placeholder_text = "0"
|
_attachment_amount.placeholder_text = "0"
|
||||||
_attachment_amount.text = "0"
|
_attachment_amount.text = "0"
|
||||||
_attachment_amount.text_changed.connect(_on_attachment_amount_changed)
|
_attachment_amount.text_changed.connect(_on_attachment_amount_changed)
|
||||||
|
|
@ -248,9 +274,12 @@ func _build_compose() -> Control:
|
||||||
_amount_plus.size = Vector2(48, 46)
|
_amount_plus.size = Vector2(48, 46)
|
||||||
_amount_plus.pressed.connect(_step_attachment_amount.bind(1))
|
_amount_plus.pressed.connect(_step_attachment_amount.bind(1))
|
||||||
page.add_child(_amount_plus)
|
page.add_child(_amount_plus)
|
||||||
_attachment_summary = Label.new()
|
_attachment_summary = RichTextLabel.new()
|
||||||
_attachment_summary.position = Vector2(688, 246)
|
_attachment_summary.position = Vector2(688, 246)
|
||||||
_attachment_summary.size = Vector2(280, 78)
|
_attachment_summary.size = Vector2(280, 78)
|
||||||
|
_attachment_summary.bbcode_enabled = true
|
||||||
|
_attachment_summary.fit_content = true
|
||||||
|
_attachment_summary.scroll_active = false
|
||||||
_attachment_summary.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
_attachment_summary.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
page.add_child(_attachment_summary)
|
page.add_child(_attachment_summary)
|
||||||
var cancel := Button.new()
|
var cancel := Button.new()
|
||||||
|
|
@ -281,9 +310,12 @@ func _build_letter() -> Control:
|
||||||
_letter_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
_letter_text.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
_letter_text.vertical_alignment = VERTICAL_ALIGNMENT_TOP
|
_letter_text.vertical_alignment = VERTICAL_ALIGNMENT_TOP
|
||||||
page.add_child(_letter_text)
|
page.add_child(_letter_text)
|
||||||
_letter_gift = Label.new()
|
_letter_gift = RichTextLabel.new()
|
||||||
_letter_gift.position = Vector2(730, 46)
|
_letter_gift.position = Vector2(730, 46)
|
||||||
_letter_gift.size = Vector2(230, 180)
|
_letter_gift.size = Vector2(230, 180)
|
||||||
|
_letter_gift.bbcode_enabled = true
|
||||||
|
_letter_gift.fit_content = true
|
||||||
|
_letter_gift.scroll_active = false
|
||||||
_letter_gift.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
_letter_gift.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART
|
||||||
page.add_child(_letter_gift)
|
page.add_child(_letter_gift)
|
||||||
_accept = Button.new()
|
_accept = Button.new()
|
||||||
|
|
@ -479,13 +511,18 @@ func _refresh_attachment_choices(_index: int) -> void:
|
||||||
_attachment_amount.visible = amount_visible
|
_attachment_amount.visible = amount_visible
|
||||||
_amount_minus.visible = amount_visible
|
_amount_minus.visible = amount_visible
|
||||||
_amount_plus.visible = amount_visible
|
_amount_plus.visible = amount_visible
|
||||||
_coin_available.visible = _attachment_kind.selected == 1
|
var currency_selected: bool = _attachment_kind.selected == 1
|
||||||
|
_coin_available_heading.visible = currency_selected
|
||||||
|
_coin_available.visible = currency_selected
|
||||||
|
_attachment_amount_currency_icon.visible = currency_selected
|
||||||
|
_attachment_amount.position.x = 770.0 if currency_selected else 746.0
|
||||||
|
_attachment_amount.size.x = 140.0 if currency_selected else 164.0
|
||||||
match _attachment_kind.selected:
|
match _attachment_kind.selected:
|
||||||
0:
|
0:
|
||||||
_attachment_choice.add_item("No attachment")
|
_attachment_choice.add_item("No attachment")
|
||||||
1:
|
1:
|
||||||
_attachment_choice.add_item("Fish coin")
|
_attachment_choice.add_item("Currency")
|
||||||
_coin_available.text = "Available: %d fish coin" % (
|
_coin_available.set_amount(
|
||||||
_reservations.get_available_fish_coin()
|
_reservations.get_available_fish_coin()
|
||||||
)
|
)
|
||||||
2:
|
2:
|
||||||
|
|
@ -705,7 +742,9 @@ func _attachment_text(attachment: Dictionary) -> String:
|
||||||
PlayerAssetReservationService.AttachmentType.NONE:
|
PlayerAssetReservationService.AttachmentType.NONE:
|
||||||
return "No gift attached."
|
return "No gift attached."
|
||||||
PlayerAssetReservationService.AttachmentType.FISH_COIN:
|
PlayerAssetReservationService.AttachmentType.FISH_COIN:
|
||||||
return "%d fish coin" % int(attachment.get("amount", 0))
|
return CurrencyPresentationType.bbcode_amount(
|
||||||
|
int(attachment.get("amount", 0)), 18
|
||||||
|
)
|
||||||
PlayerAssetReservationService.AttachmentType.FISH:
|
PlayerAssetReservationService.AttachmentType.FISH:
|
||||||
var fish_catch := _inventory.get_catch_by_id(
|
var fish_catch := _inventory.get_catch_by_id(
|
||||||
StringName(str(attachment.get("catch_id", "")))
|
StringName(str(attachment.get("catch_id", "")))
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||||
const InventoryNotepadType = preload(
|
const InventoryNotepadType = preload(
|
||||||
"res://ui/components/inventory_notepad.gd"
|
"res://ui/components/inventory_notepad.gd"
|
||||||
)
|
)
|
||||||
|
const CurrencyPresentationType = preload(
|
||||||
|
"res://ui/currency_presentation.gd"
|
||||||
|
)
|
||||||
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
|
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
|
||||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||||
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
||||||
|
|
@ -168,7 +171,7 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
||||||
@onready var _notepad_binding: Label = %BindingDecoration
|
@onready var _notepad_binding: Label = %BindingDecoration
|
||||||
@onready var _notepad_title: Label = %NotepadTitle
|
@onready var _notepad_title: Label = %NotepadTitle
|
||||||
@onready var _notepad_rule: ColorRect = %NotepadRule
|
@onready var _notepad_rule: ColorRect = %NotepadRule
|
||||||
@onready var _notepad_wallet_value: Label = %NotepadWalletValue
|
@onready var _notepad_wallet_value: CurrencyAmount = %NotepadWalletValue
|
||||||
@onready var _notepad_capacity_value: Label = %NotepadCapacityValue
|
@onready var _notepad_capacity_value: Label = %NotepadCapacityValue
|
||||||
@onready var _cooler_detail_texture: TextureRect = %CoolerDetailTexture
|
@onready var _cooler_detail_texture: TextureRect = %CoolerDetailTexture
|
||||||
@onready var _cooler_detail_name: Label = %CoolerDetailName
|
@onready var _cooler_detail_name: Label = %CoolerDetailName
|
||||||
|
|
@ -178,7 +181,7 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
||||||
@onready var _cooler_weight_unit: Label = %CoolerWeightUnit
|
@onready var _cooler_weight_unit: Label = %CoolerWeightUnit
|
||||||
@onready var _cooler_offer_row: HBoxContainer = %CoolerOfferRow
|
@onready var _cooler_offer_row: HBoxContainer = %CoolerOfferRow
|
||||||
@onready var _cooler_offer_label: Label = %CoolerOfferLabel
|
@onready var _cooler_offer_label: Label = %CoolerOfferLabel
|
||||||
@onready var _cooler_offer_value: Label = %CoolerOfferValue
|
@onready var _cooler_offer_value: CurrencyAmount = %CoolerOfferValue
|
||||||
@onready var _cooler_selection_summary: Control = %CoolerSelectionSummary
|
@onready var _cooler_selection_summary: Control = %CoolerSelectionSummary
|
||||||
@onready var _cooler_selection_empty: Label = %CoolerSelectionEmpty
|
@onready var _cooler_selection_empty: Label = %CoolerSelectionEmpty
|
||||||
@onready var _cooler_selected_count_row: HBoxContainer = %CoolerSelectedCountRow
|
@onready var _cooler_selected_count_row: HBoxContainer = %CoolerSelectedCountRow
|
||||||
|
|
@ -186,7 +189,7 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
||||||
@onready var _cooler_selected_count_label: Label = %CoolerSelectedCountLabel
|
@onready var _cooler_selected_count_label: Label = %CoolerSelectedCountLabel
|
||||||
@onready var _cooler_combined_offer_row: HBoxContainer = %CoolerCombinedOfferRow
|
@onready var _cooler_combined_offer_row: HBoxContainer = %CoolerCombinedOfferRow
|
||||||
@onready var _cooler_combined_offer_label: Label = %CoolerCombinedOfferLabel
|
@onready var _cooler_combined_offer_label: Label = %CoolerCombinedOfferLabel
|
||||||
@onready var _cooler_combined_offer_value: Label = %CoolerCombinedOfferValue
|
@onready var _cooler_combined_offer_value: CurrencyAmount = %CoolerCombinedOfferValue
|
||||||
@onready var _favorite_bubble: NotepadInkActionType = %FavoriteBubble
|
@onready var _favorite_bubble: NotepadInkActionType = %FavoriteBubble
|
||||||
@onready var _sell_bubble: NotepadInkActionType = %SellBubble
|
@onready var _sell_bubble: NotepadInkActionType = %SellBubble
|
||||||
@onready var _sell_all_bubble: NotepadInkActionType = %SellAllBubble
|
@onready var _sell_all_bubble: NotepadInkActionType = %SellAllBubble
|
||||||
|
|
@ -247,7 +250,7 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
||||||
@onready var _content_stage: Control = %Content
|
@onready var _content_stage: Control = %Content
|
||||||
@onready var _cooler_scroll: ScrollContainer = %CoolerScroll
|
@onready var _cooler_scroll: ScrollContainer = %CoolerScroll
|
||||||
@onready var _cooler_host: Control = %CoolerHost
|
@onready var _cooler_host: Control = %CoolerHost
|
||||||
@onready var _wallet_balance: Label = %WalletBalance
|
@onready var _wallet_balance: CurrencyAmount = %WalletBalance
|
||||||
@onready var _header: Control = %Header
|
@onready var _header: Control = %Header
|
||||||
@onready var _separator: Control = %Separator
|
@onready var _separator: Control = %Separator
|
||||||
@onready var _wallet_status: BubbleStatusBubbleType = %WalletStatus
|
@onready var _wallet_status: BubbleStatusBubbleType = %WalletStatus
|
||||||
|
|
@ -266,16 +269,16 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
||||||
@onready var _bag_detail_data: Label = %BagDetailData
|
@onready var _bag_detail_data: Label = %BagDetailData
|
||||||
@onready var _sort_option: OptionButton = %SortOption
|
@onready var _sort_option: OptionButton = %SortOption
|
||||||
@onready var _sort_direction: Button = %SortDirection
|
@onready var _sort_direction: Button = %SortDirection
|
||||||
@onready var _held_value: Label = %HeldValue
|
@onready var _held_value: CurrencyAmount = %HeldValue
|
||||||
@onready var _cooler_count: Label = %CoolerCount
|
@onready var _cooler_count: Label = %CoolerCount
|
||||||
@onready var _inventory_empty: Label = %InventoryEmpty
|
@onready var _inventory_empty: Label = %InventoryEmpty
|
||||||
@onready var _selection_summary: Label = %SelectionSummary
|
@onready var _selection_summary: Label = %SelectionSummary
|
||||||
@onready var _favorite_button: Button = %FavoriteButton
|
@onready var _favorite_button: Button = %FavoriteButton
|
||||||
@onready var _sell_button: Button = %SellButton
|
@onready var _sell_button: Button = %SellButton
|
||||||
@onready var _sale_unavailable: Label = %SaleUnavailable
|
@onready var _sale_unavailable: Label = %SaleUnavailable
|
||||||
@onready var _transaction_feedback: Label = %TransactionFeedback
|
@onready var _transaction_feedback: RichTextLabel = %TransactionFeedback
|
||||||
@onready var _sale_confirmation: PanelContainer = %SaleConfirmation
|
@onready var _sale_confirmation: PanelContainer = %SaleConfirmation
|
||||||
@onready var _confirmation_message: Label = %ConfirmationMessage
|
@onready var _confirmation_message: RichTextLabel = %ConfirmationMessage
|
||||||
@onready var _confirm_sale_button: Button = %ConfirmSaleButton
|
@onready var _confirm_sale_button: Button = %ConfirmSaleButton
|
||||||
@onready var _cancel_sale_button: Button = %CancelSaleButton
|
@onready var _cancel_sale_button: Button = %CancelSaleButton
|
||||||
@onready var _logbook_empty: Label = %LogbookEmpty
|
@onready var _logbook_empty: Label = %LogbookEmpty
|
||||||
|
|
@ -1881,11 +1884,11 @@ func _apply_inventory_styles() -> void:
|
||||||
UtilityPageStyle.panel_style(),
|
UtilityPageStyle.panel_style(),
|
||||||
)
|
)
|
||||||
_confirmation_message.add_theme_font_override(
|
_confirmation_message.add_theme_font_override(
|
||||||
"font",
|
"normal_font",
|
||||||
UtilityPageStyle.TuffyFont,
|
UtilityPageStyle.TuffyFont,
|
||||||
)
|
)
|
||||||
_confirmation_message.add_theme_color_override(
|
_confirmation_message.add_theme_color_override(
|
||||||
"font_color",
|
"default_color",
|
||||||
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
|
UtilityPageStyle.OCEAN_TEXT_PRIMARY,
|
||||||
)
|
)
|
||||||
UtilityPageStyle.apply_ocean_button(_confirm_sale_button)
|
UtilityPageStyle.apply_ocean_button(_confirm_sale_button)
|
||||||
|
|
@ -2405,14 +2408,13 @@ func _update_shell_layout() -> void:
|
||||||
_notepad_capacity_value.size = (
|
_notepad_capacity_value.size = (
|
||||||
Vector2(142.0, 17.0) if compact else Vector2(124.0, 23.0)
|
Vector2(142.0, 17.0) if compact else Vector2(124.0, 23.0)
|
||||||
)
|
)
|
||||||
for status_value: Label in [
|
_notepad_wallet_value.icon_size = 10.0 if compact else 14.0
|
||||||
_notepad_wallet_value,
|
_notepad_wallet_value.get_amount_label().add_theme_font_size_override(
|
||||||
_notepad_capacity_value,
|
"font_size", 10 if compact else 14
|
||||||
]:
|
)
|
||||||
status_value.add_theme_font_size_override(
|
_notepad_capacity_value.add_theme_font_size_override(
|
||||||
"font_size",
|
"font_size", 10 if compact else 14
|
||||||
10 if compact else 14,
|
)
|
||||||
)
|
|
||||||
_cooler_detail_texture.position = (
|
_cooler_detail_texture.position = (
|
||||||
Vector2(5.0, 84.0) if compact else Vector2(80.0, 137.0)
|
Vector2(5.0, 84.0) if compact else Vector2(80.0, 137.0)
|
||||||
)
|
)
|
||||||
|
|
@ -2594,22 +2596,21 @@ func _layout_cooler_detail_text(compact: bool) -> void:
|
||||||
"font_size",
|
"font_size",
|
||||||
13 if compact else 18,
|
13 if compact else 18,
|
||||||
)
|
)
|
||||||
_cooler_offer_value.add_theme_font_size_override(
|
_cooler_offer_value.icon_size = 10.0 if compact else 15.0
|
||||||
"font_size",
|
_cooler_offer_value.get_amount_label().add_theme_font_size_override(
|
||||||
10 if compact else 15,
|
"font_size", 10 if compact else 15
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
func _layout_cooler_selection_text(compact: bool) -> void:
|
func _layout_cooler_selection_text(compact: bool) -> void:
|
||||||
var row_height: float = 17.5 if compact else 20.0
|
var row_height: float = 17.5 if compact else 20.0
|
||||||
for numeric_label: Label in [
|
_cooler_selected_count_value.add_theme_font_size_override(
|
||||||
_cooler_selected_count_value,
|
"font_size", 10 if compact else 14
|
||||||
_cooler_combined_offer_value,
|
)
|
||||||
]:
|
_cooler_combined_offer_value.icon_size = 10.0 if compact else 14.0
|
||||||
numeric_label.add_theme_font_size_override(
|
_cooler_combined_offer_value.get_amount_label().add_theme_font_size_override(
|
||||||
"font_size",
|
"font_size", 10 if compact else 14
|
||||||
10 if compact else 14,
|
)
|
||||||
)
|
|
||||||
for word_label: Label in [
|
for word_label: Label in [
|
||||||
_cooler_selection_empty,
|
_cooler_selection_empty,
|
||||||
_cooler_selected_count_label,
|
_cooler_selected_count_label,
|
||||||
|
|
@ -3438,9 +3439,9 @@ func _refresh_economy_summary() -> void:
|
||||||
if _inventory != null
|
if _inventory != null
|
||||||
else 0
|
else 0
|
||||||
)
|
)
|
||||||
_wallet_balance.text = "wallet: $%d" % balance
|
_wallet_balance.set_amount(balance)
|
||||||
_wallet_status.set_content("wallet", "$%d" % balance)
|
_wallet_status.set_currency_amount("wallet", balance)
|
||||||
_notepad_wallet_value.text = "$%d" % balance
|
_notepad_wallet_value.set_amount(balance)
|
||||||
_capacity_status.set_content("cooler", "%d / %d" % [
|
_capacity_status.set_content("cooler", "%d / %d" % [
|
||||||
_inventory.get_all_catches().size() if _inventory != null else 0,
|
_inventory.get_all_catches().size() if _inventory != null else 0,
|
||||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
||||||
|
|
@ -3449,8 +3450,8 @@ func _refresh_economy_summary() -> void:
|
||||||
_inventory.get_all_catches().size() if _inventory != null else 0,
|
_inventory.get_all_catches().size() if _inventory != null else 0,
|
||||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
||||||
]
|
]
|
||||||
_held_value_status.set_content("held value", "$%d" % held_total)
|
_held_value_status.set_currency_amount("held value", held_total)
|
||||||
_held_value.text = "held fish base value: $%d" % held_total
|
_held_value.set_amount(held_total)
|
||||||
_cooler_count.text = "%d / %d" % [
|
_cooler_count.text = "%d / %d" % [
|
||||||
_inventory.get_all_catches().size() if _inventory != null else 0,
|
_inventory.get_all_catches().size() if _inventory != null else 0,
|
||||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
||||||
|
|
@ -3707,7 +3708,7 @@ func _on_catch_card_pressed(catch_id: StringName) -> void:
|
||||||
Input.is_key_pressed(KEY_CTRL),
|
Input.is_key_pressed(KEY_CTRL),
|
||||||
Input.is_key_pressed(KEY_SHIFT)
|
Input.is_key_pressed(KEY_SHIFT)
|
||||||
)
|
)
|
||||||
_transaction_feedback.text = ""
|
_set_transaction_feedback("")
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3719,7 +3720,7 @@ func _on_fish_field_gui_input(event: InputEvent) -> void:
|
||||||
and not get_viewport().gui_is_dragging()
|
and not get_viewport().gui_is_dragging()
|
||||||
):
|
):
|
||||||
_fish_selection.clear()
|
_fish_selection.clear()
|
||||||
_transaction_feedback.text = ""
|
_set_transaction_feedback("")
|
||||||
_refresh_inventory()
|
_refresh_inventory()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3751,9 +3752,9 @@ func _clear_cooler_detail_stats() -> void:
|
||||||
_cooler_weight_value,
|
_cooler_weight_value,
|
||||||
_cooler_weight_unit,
|
_cooler_weight_unit,
|
||||||
_cooler_offer_label,
|
_cooler_offer_label,
|
||||||
_cooler_offer_value,
|
|
||||||
]:
|
]:
|
||||||
label.text = ""
|
label.text = ""
|
||||||
|
_cooler_offer_value.visible = false
|
||||||
|
|
||||||
|
|
||||||
func _set_cooler_selection_summary_empty() -> void:
|
func _set_cooler_selection_summary_empty() -> void:
|
||||||
|
|
@ -3764,10 +3765,10 @@ func _set_cooler_selection_summary_empty() -> void:
|
||||||
_cooler_selected_count_value,
|
_cooler_selected_count_value,
|
||||||
_cooler_selected_count_label,
|
_cooler_selected_count_label,
|
||||||
_cooler_combined_offer_label,
|
_cooler_combined_offer_label,
|
||||||
_cooler_combined_offer_value,
|
|
||||||
]:
|
]:
|
||||||
label.visible = false
|
label.visible = false
|
||||||
label.text = ""
|
label.text = ""
|
||||||
|
_cooler_combined_offer_value.visible = false
|
||||||
|
|
||||||
|
|
||||||
func _set_cooler_selection_summary(
|
func _set_cooler_selection_summary(
|
||||||
|
|
@ -3785,10 +3786,9 @@ func _set_cooler_selection_summary(
|
||||||
_cooler_selected_count_label.text = "fish selected"
|
_cooler_selected_count_label.text = "fish selected"
|
||||||
if offer_value >= 0:
|
if offer_value >= 0:
|
||||||
_cooler_combined_offer_label.text = "combined offer"
|
_cooler_combined_offer_label.text = "combined offer"
|
||||||
_cooler_combined_offer_value.text = "$%d" % offer_value
|
_cooler_combined_offer_value.set_amount(offer_value)
|
||||||
else:
|
else:
|
||||||
_cooler_combined_offer_label.text = "offer unavailable"
|
_cooler_combined_offer_label.text = "offer unavailable"
|
||||||
_cooler_combined_offer_value.text = ""
|
|
||||||
|
|
||||||
|
|
||||||
func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
||||||
|
|
@ -3838,10 +3838,11 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
||||||
]
|
]
|
||||||
if buyer_offer >= 0 and active_buyer != null:
|
if buyer_offer >= 0 and active_buyer != null:
|
||||||
_cooler_offer_label.text = _get_offer_label(active_buyer)
|
_cooler_offer_label.text = _get_offer_label(active_buyer)
|
||||||
_cooler_offer_value.text = "$%d" % buyer_offer
|
_cooler_offer_value.visible = true
|
||||||
|
_cooler_offer_value.set_amount(buyer_offer)
|
||||||
else:
|
else:
|
||||||
_cooler_offer_label.text = "buyer unavailable"
|
_cooler_offer_label.text = "buyer unavailable"
|
||||||
_cooler_offer_value.text = ""
|
_cooler_offer_value.visible = false
|
||||||
_favorite_button.disabled = false
|
_favorite_button.disabled = false
|
||||||
_favorite_button.text = (
|
_favorite_button.text = (
|
||||||
"unfavorite" if fish_catch.is_favorited else "favorite"
|
"unfavorite" if fish_catch.is_favorited else "favorite"
|
||||||
|
|
@ -3970,12 +3971,8 @@ func _update_sale_summary() -> void:
|
||||||
or preview.status == FishSaleResultType.Status.FAVORITED
|
or preview.status == FishSaleResultType.Status.FAVORITED
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_selection_summary.text += "\n%s total offer: $%d" % [
|
|
||||||
active_buyer.display_name,
|
|
||||||
preview.payout,
|
|
||||||
]
|
|
||||||
_set_cooler_selection_summary(selected_count, preview.payout)
|
_set_cooler_selection_summary(selected_count, preview.payout)
|
||||||
_offer_status.set_content(offer_label, "$%d" % preview.payout)
|
_offer_status.set_currency_amount(offer_label, preview.payout)
|
||||||
else:
|
else:
|
||||||
_set_cooler_selection_summary(selected_count, -1)
|
_set_cooler_selection_summary(selected_count, -1)
|
||||||
_offer_status.set_content(offer_label, "unavailable")
|
_offer_status.set_content(offer_label, "unavailable")
|
||||||
|
|
@ -4058,7 +4055,7 @@ func _on_favorite_pressed() -> void:
|
||||||
fish_catch.catch_id,
|
fish_catch.catch_id,
|
||||||
not fish_catch.is_favorited
|
not fish_catch.is_favorited
|
||||||
):
|
):
|
||||||
_transaction_feedback.text = (
|
_set_transaction_feedback(
|
||||||
"%s %s."
|
"%s %s."
|
||||||
% [
|
% [
|
||||||
fish_catch.fish.display_name,
|
fish_catch.fish.display_name,
|
||||||
|
|
@ -4074,7 +4071,7 @@ func _on_sell_pressed() -> void:
|
||||||
func _on_sell_all_pressed() -> void:
|
func _on_sell_all_pressed() -> void:
|
||||||
var catch_ids: Array[StringName] = _get_sell_all_catch_ids()
|
var catch_ids: Array[StringName] = _get_sell_all_catch_ids()
|
||||||
if catch_ids.is_empty():
|
if catch_ids.is_empty():
|
||||||
_transaction_feedback.text = "No sellable fish in the cooler."
|
_set_transaction_feedback("No sellable fish in the cooler.")
|
||||||
return
|
return
|
||||||
_begin_sale_confirmation(catch_ids)
|
_begin_sale_confirmation(catch_ids)
|
||||||
|
|
||||||
|
|
@ -4091,14 +4088,14 @@ func _begin_sale_confirmation(catch_ids: Array[StringName]) -> void:
|
||||||
and _network_sale_service.is_local_sale_pending()
|
and _network_sale_service.is_local_sale_pending()
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_transaction_feedback.text = "Selling…"
|
_set_transaction_feedback("Selling…")
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
_network_sale_service == null
|
_network_sale_service == null
|
||||||
or buyer_id.is_empty()
|
or buyer_id.is_empty()
|
||||||
or not _network_sale_service.can_request_sale(buyer_id)
|
or not _network_sale_service.can_request_sale(buyer_id)
|
||||||
):
|
):
|
||||||
_transaction_feedback.text = (
|
_set_transaction_feedback(
|
||||||
"Selling is not supported by this server."
|
"Selling is not supported by this server."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
@ -4114,7 +4111,7 @@ func _begin_sale_confirmation(catch_ids: Array[StringName]) -> void:
|
||||||
active_buyer
|
active_buyer
|
||||||
)
|
)
|
||||||
if not preview.is_success():
|
if not preview.is_success():
|
||||||
_transaction_feedback.text = (
|
_set_transaction_feedback(
|
||||||
"favorited fish cannot be sold. "
|
"favorited fish cannot be sold. "
|
||||||
+ "remove them from the selection first."
|
+ "remove them from the selection first."
|
||||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||||
|
|
@ -4128,15 +4125,15 @@ func _begin_sale_confirmation(catch_ids: Array[StringName]) -> void:
|
||||||
_confirmation_generation = _menu_generation
|
_confirmation_generation = _menu_generation
|
||||||
var fish_label: String = "fish"
|
var fish_label: String = "fish"
|
||||||
_confirmation_message.text = (
|
_confirmation_message.text = (
|
||||||
"sell %d %s to the %s for $%d?\ncombined base value: $%d"
|
"[center]sell %d %s to the %s for %s?\n"
|
||||||
% [
|
+ "combined base value: %s[/center]"
|
||||||
preview.fish_count,
|
) % [
|
||||||
fish_label,
|
preview.fish_count,
|
||||||
_get_buyer_display_group(active_buyer),
|
fish_label,
|
||||||
preview.payout,
|
_get_buyer_display_group(active_buyer),
|
||||||
preview.base_value,
|
CurrencyPresentationType.bbcode_amount(preview.payout, 22),
|
||||||
]
|
CurrencyPresentationType.bbcode_amount(preview.base_value, 22),
|
||||||
)
|
]
|
||||||
_sale_confirmation.visible = true
|
_sale_confirmation.visible = true
|
||||||
if _shop_cooler_context_active:
|
if _shop_cooler_context_active:
|
||||||
shop_cooler_modal_changed.emit(true)
|
shop_cooler_modal_changed.emit(true)
|
||||||
|
|
@ -4182,7 +4179,7 @@ func _on_confirm_sale_pressed() -> void:
|
||||||
and transaction_generation == _menu_generation
|
and transaction_generation == _menu_generation
|
||||||
and (visible or _shop_cooler_context_active)
|
and (visible or _shop_cooler_context_active)
|
||||||
):
|
):
|
||||||
_transaction_feedback.text = "Selling…"
|
_set_transaction_feedback("Selling…")
|
||||||
|
|
||||||
|
|
||||||
func _can_use_shared_world_actions() -> bool:
|
func _can_use_shared_world_actions() -> bool:
|
||||||
|
|
@ -4195,7 +4192,7 @@ func _can_use_shared_world_actions() -> bool:
|
||||||
func _on_network_sale_pending(_request_id: String) -> void:
|
func _on_network_sale_pending(_request_id: String) -> void:
|
||||||
_sale_in_progress = true
|
_sale_in_progress = true
|
||||||
if visible or _shop_cooler_context_active:
|
if visible or _shop_cooler_context_active:
|
||||||
_transaction_feedback.text = "Selling…"
|
_set_transaction_feedback("Selling…")
|
||||||
_update_sale_summary()
|
_update_sale_summary()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4213,11 +4210,12 @@ func _on_network_sale_finished(
|
||||||
return
|
return
|
||||||
_refresh_all()
|
_refresh_all()
|
||||||
var feedback_message: String = (
|
var feedback_message: String = (
|
||||||
"Sale complete. $%d received." % payout
|
"Sale complete • %s received."
|
||||||
|
% CurrencyPresentationType.bbcode_amount(payout, 20)
|
||||||
if accepted
|
if accepted
|
||||||
else message
|
else message
|
||||||
)
|
)
|
||||||
_transaction_feedback.text = feedback_message
|
_set_transaction_feedback(feedback_message)
|
||||||
if not accepted:
|
if not accepted:
|
||||||
_sale_unavailable.text = feedback_message
|
_sale_unavailable.text = feedback_message
|
||||||
_sale_unavailable.visible = true
|
_sale_unavailable.visible = true
|
||||||
|
|
@ -4273,7 +4271,7 @@ func _revalidate_confirmation() -> void:
|
||||||
or _confirmation_buyer.id != _confirmation_buyer_id
|
or _confirmation_buyer.id != _confirmation_buyer_id
|
||||||
):
|
):
|
||||||
_close_sale_confirmation()
|
_close_sale_confirmation()
|
||||||
_transaction_feedback.text = "sale selection is no longer available."
|
_set_transaction_feedback("sale selection is no longer available.")
|
||||||
return
|
return
|
||||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||||
_confirmation_catch_ids,
|
_confirmation_catch_ids,
|
||||||
|
|
@ -4288,7 +4286,7 @@ func _revalidate_confirmation() -> void:
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
_close_sale_confirmation()
|
_close_sale_confirmation()
|
||||||
_transaction_feedback.text = (
|
_set_transaction_feedback(
|
||||||
"favorited fish cannot be sold. "
|
"favorited fish cannot be sold. "
|
||||||
+ "remove them from the selection first."
|
+ "remove them from the selection first."
|
||||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||||
|
|
@ -4308,6 +4306,10 @@ func _get_buyer_display_group(
|
||||||
return "buyer"
|
return "buyer"
|
||||||
|
|
||||||
|
|
||||||
|
func _set_transaction_feedback(message: String) -> void:
|
||||||
|
_transaction_feedback.text = "[center]%s[/center]" % message
|
||||||
|
|
||||||
|
|
||||||
func _get_active_sale_buyer() -> FishBuyerProfileType:
|
func _get_active_sale_buyer() -> FishBuyerProfileType:
|
||||||
if _sale_buyer_override != null and _sale_buyer_override.is_valid():
|
if _sale_buyer_override != null and _sale_buyer_override.is_valid():
|
||||||
return _sale_buyer_override
|
return _sale_buyer_override
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=33 format=3]
|
[gd_scene load_steps=34 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"]
|
[ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"]
|
||||||
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/profile_simple.png" id="29_profile_simple"]
|
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/profile_simple.png" id="29_profile_simple"]
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/online_simple.png" id="30_online_simple"]
|
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/online_simple.png" id="30_online_simple"]
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/close.png" id="31_close"]
|
[ext_resource type="Texture2D" path="res://ui/icons/tab_menu/close.png" id="31_close"]
|
||||||
|
[ext_resource type="PackedScene" path="res://ui/components/currency_amount.tscn" id="32_currency"]
|
||||||
|
|
||||||
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_collection"]
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_collection"]
|
||||||
|
|
||||||
|
|
@ -224,7 +225,7 @@ theme_override_font_sizes/font_size = 19
|
||||||
text = "cooler notes"
|
text = "cooler notes"
|
||||||
horizontal_alignment = 1
|
horizontal_alignment = 1
|
||||||
|
|
||||||
[node name="NotepadWalletValue" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation"]
|
[node name="NotepadWalletValue" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation" instance=ExtResource("32_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 0
|
layout_mode = 0
|
||||||
offset_left = 18.0
|
offset_left = 18.0
|
||||||
|
|
@ -232,9 +233,7 @@ offset_top = 61.0
|
||||||
offset_right = 138.0
|
offset_right = 138.0
|
||||||
offset_bottom = 84.0
|
offset_bottom = 84.0
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
theme_override_colors/font_color = Color(0.16, 0.16, 0.14, 1)
|
icon_size = 14.0
|
||||||
theme_override_font_sizes/font_size = 14
|
|
||||||
text = "$0"
|
|
||||||
|
|
||||||
[node name="NotepadCapacityValue" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation"]
|
[node name="NotepadCapacityValue" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -367,12 +366,11 @@ mouse_filter = 2
|
||||||
theme_override_colors/font_color = Color(0.16, 0.16, 0.14, 1)
|
theme_override_colors/font_color = Color(0.16, 0.16, 0.14, 1)
|
||||||
theme_override_font_sizes/font_size = 18
|
theme_override_font_sizes/font_size = 18
|
||||||
|
|
||||||
[node name="CoolerOfferValue" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation/CoolerDetailStats/CoolerOfferRow"]
|
[node name="CoolerOfferValue" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation/CoolerDetailStats/CoolerOfferRow" instance=ExtResource("32_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
theme_override_colors/font_color = Color(0.16, 0.16, 0.14, 1)
|
icon_size = 15.0
|
||||||
theme_override_font_sizes/font_size = 15
|
|
||||||
|
|
||||||
[node name="CoolerSelectionSummary" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation"]
|
[node name="CoolerSelectionSummary" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -442,12 +440,11 @@ theme_override_colors/font_color = Color(0.2, 0.18, 0.15, 0.86)
|
||||||
theme_override_font_sizes/font_size = 18
|
theme_override_font_sizes/font_size = 18
|
||||||
text = "combined offer"
|
text = "combined offer"
|
||||||
|
|
||||||
[node name="CoolerCombinedOfferValue" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation/CoolerSelectionSummary/CoolerCombinedOfferRow"]
|
[node name="CoolerCombinedOfferValue" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation/CoolerSelectionSummary/CoolerCombinedOfferRow" instance=ExtResource("32_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
mouse_filter = 2
|
mouse_filter = 2
|
||||||
theme_override_colors/font_color = Color(0.2, 0.18, 0.15, 0.86)
|
icon_size = 14.0
|
||||||
theme_override_font_sizes/font_size = 14
|
|
||||||
|
|
||||||
[node name="FavoriteBubble" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation" instance=ExtResource("12_ink_action")]
|
[node name="FavoriteBubble" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation" instance=ExtResource("12_ink_action")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -965,24 +962,31 @@ unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_constants/separation = 8
|
theme_override_constants/separation = 8
|
||||||
|
|
||||||
[node name="WalletBalance" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Header"]
|
[node name="WalletHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Header"]
|
||||||
|
layout_mode = 2
|
||||||
|
text = "wallet"
|
||||||
|
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
||||||
|
|
||||||
|
[node name="WalletBalance" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Header" instance=ExtResource("32_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
size_flags_horizontal = 3
|
size_flags_horizontal = 3
|
||||||
text = "wallet: $0"
|
alignment = 0
|
||||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
|
||||||
|
|
||||||
[node name="Separator" type="HSeparator" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
[node name="Separator" type="HSeparator" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
||||||
[node name="TransactionFeedback" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
[node name="TransactionFeedback" type="RichTextLabel" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 30)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = ""
|
text = ""
|
||||||
horizontal_alignment = 1
|
bbcode_enabled = true
|
||||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
fit_content = true
|
||||||
theme_override_font_sizes/font_size = 21
|
scroll_active = false
|
||||||
|
theme_override_colors/default_color = Color(1, 0.82, 0.4, 1)
|
||||||
|
theme_override_font_sizes/normal_font_size = 21
|
||||||
|
|
||||||
[node name="Content" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
[node name="Content" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -1057,12 +1061,16 @@ layout_mode = 2
|
||||||
text = "0 / 12"
|
text = "0 / 12"
|
||||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||||
|
|
||||||
[node name="HeldValue" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
[node name="HeldValueHeading" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||||
|
visible = false
|
||||||
|
layout_mode = 2
|
||||||
|
text = "held fish base value"
|
||||||
|
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
||||||
|
|
||||||
|
[node name="HeldValue" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection/SortBar" instance=ExtResource("32_currency")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
text = "held fish base value: $0"
|
|
||||||
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
|
||||||
|
|
||||||
[node name="InventoryBody" type="BoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection"]
|
[node name="InventoryBody" type="BoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/MenuPanel/Margin/Layout/Content/InventorySection"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -1366,12 +1374,15 @@ layout_mode = 2
|
||||||
theme_override_constants/separation = 12
|
theme_override_constants/separation = 12
|
||||||
alignment = 1
|
alignment = 1
|
||||||
|
|
||||||
[node name="ConfirmationMessage" type="Label" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
|
[node name="ConfirmationMessage" type="RichTextLabel" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
custom_minimum_size = Vector2(0, 64)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_font_sizes/font_size = 22
|
bbcode_enabled = true
|
||||||
|
fit_content = true
|
||||||
|
scroll_active = false
|
||||||
|
theme_override_font_sizes/normal_font_size = 22
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
horizontal_alignment = 1
|
|
||||||
|
|
||||||
[node name="ConfirmationButtons" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
|
[node name="ConfirmationButtons" type="HBoxContainer" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/SaleConfirmation/ConfirmationMargin/ConfirmationLayout"]
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ func _ready() -> void:
|
||||||
UtilityPageStyleType.apply_ocean_button(button)
|
UtilityPageStyleType.apply_ocean_button(button)
|
||||||
button.custom_minimum_size = Vector2(48, 44)
|
button.custom_minimum_size = Vector2(48, 44)
|
||||||
_make_button_round(button, 22)
|
_make_button_round(button, 22)
|
||||||
|
_enlarge_action_icon(_eraser_button)
|
||||||
|
_enlarge_action_icon(_undo_button)
|
||||||
_mode_button.custom_minimum_size = Vector2(48, 48)
|
_mode_button.custom_minimum_size = Vector2(48, 48)
|
||||||
_make_button_round(_mode_button, 24)
|
_make_button_round(_mode_button, 24)
|
||||||
_mode_button.pressed.connect(_toggle_mode)
|
_mode_button.pressed.connect(_toggle_mode)
|
||||||
|
|
@ -91,6 +93,25 @@ func _make_button_round(button: Button, radius: int) -> void:
|
||||||
button.add_theme_stylebox_override(style_name, style)
|
button.add_theme_stylebox_override(style_name, style)
|
||||||
|
|
||||||
|
|
||||||
|
func _enlarge_action_icon(button: Button) -> void:
|
||||||
|
# The shared ocean button uses generous text padding, leaving only a
|
||||||
|
# 20-pixel icon area in these compact bubbles. Keep the 48x44 control and
|
||||||
|
# hitbox intact while giving icon-only actions a full 40x40 presentation.
|
||||||
|
button.add_theme_constant_override("icon_max_width", 40)
|
||||||
|
for style_name: StringName in [
|
||||||
|
&"normal", &"hover", &"pressed", &"focus", &"disabled",
|
||||||
|
]:
|
||||||
|
var existing: StyleBox = button.get_theme_stylebox(style_name)
|
||||||
|
var style := existing.duplicate() as StyleBoxFlat
|
||||||
|
if style == null:
|
||||||
|
continue
|
||||||
|
style.content_margin_left = 4.0
|
||||||
|
style.content_margin_right = 4.0
|
||||||
|
style.content_margin_top = 2.0
|
||||||
|
style.content_margin_bottom = 2.0
|
||||||
|
button.add_theme_stylebox_override(style_name, style)
|
||||||
|
|
||||||
|
|
||||||
func setup(
|
func setup(
|
||||||
service: NetworkSurfaceDrawingService,
|
service: NetworkSurfaceDrawingService,
|
||||||
unlocks: PlayerArtUnlocks,
|
unlocks: PlayerArtUnlocks,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
[gd_scene load_steps=6 format=3]
|
[gd_scene load_steps=8 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://ui/surface_drawing_toolbar.gd" id="1_script"]
|
[ext_resource type="Script" path="res://ui/surface_drawing_toolbar.gd" id="1_script"]
|
||||||
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/undo_light.png" id="3_undo_light"]
|
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/undo_light.png" id="3_undo_light"]
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/check_mark_light.png" id="4_check_mark_light"]
|
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/check_mark_light.png" id="4_check_mark_light"]
|
||||||
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="5_x_light"]
|
[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="5_x_light"]
|
||||||
|
[ext_resource type="Texture2D" path="res://items/icons/art/art_kit_eraser.png" id="6_art_kit_eraser"]
|
||||||
|
|
||||||
|
[sub_resource type="ButtonGroup" id="GuideActionGroup"]
|
||||||
|
allow_unpress = true
|
||||||
|
|
||||||
[node name="SurfaceDrawingToolbar" type="Control"]
|
[node name="SurfaceDrawingToolbar" type="Control"]
|
||||||
visible = false
|
visible = false
|
||||||
|
|
@ -51,9 +55,12 @@ tooltip_text = "grid size"
|
||||||
[node name="EraserButton" type="Button" parent="TopPanel/Top"]
|
[node name="EraserButton" type="Button" parent="TopPanel/Top"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
texture_filter = 1
|
||||||
tooltip_text = "toggle eraser"
|
tooltip_text = "toggle eraser"
|
||||||
toggle_mode = true
|
toggle_mode = true
|
||||||
text = "⌫"
|
accessibility_name = "toggle eraser"
|
||||||
|
icon = ExtResource("6_art_kit_eraser")
|
||||||
|
expand_icon = true
|
||||||
|
|
||||||
[node name="UndoButton" type="Button" parent="TopPanel/Top"]
|
[node name="UndoButton" type="Button" parent="TopPanel/Top"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
@ -67,24 +74,27 @@ expand_icon = true
|
||||||
[node name="HideGuideButton" type="Button" parent="TopPanel/Top"]
|
[node name="HideGuideButton" type="Button" parent="TopPanel/Top"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
tooltip_text = "hide grid on next click"
|
tooltip_text = "toggle hide grid mode"
|
||||||
toggle_mode = true
|
toggle_mode = true
|
||||||
|
button_group = SubResource("GuideActionGroup")
|
||||||
text = "◌"
|
text = "◌"
|
||||||
|
|
||||||
[node name="RestoreGuideButton" type="Button" parent="TopPanel/Top"]
|
[node name="RestoreGuideButton" type="Button" parent="TopPanel/Top"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
tooltip_text = "restore hidden grid on next click"
|
tooltip_text = "toggle show grid mode"
|
||||||
toggle_mode = true
|
toggle_mode = true
|
||||||
|
button_group = SubResource("GuideActionGroup")
|
||||||
text = "▤"
|
text = "▤"
|
||||||
|
|
||||||
[node name="FinalizeGuideButton" type="Button" parent="TopPanel/Top"]
|
[node name="FinalizeGuideButton" type="Button" parent="TopPanel/Top"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
texture_filter = 1
|
texture_filter = 1
|
||||||
tooltip_text = "finish grid on next click"
|
tooltip_text = "toggle finalize grid mode"
|
||||||
toggle_mode = true
|
toggle_mode = true
|
||||||
accessibility_name = "finish grid on next click"
|
button_group = SubResource("GuideActionGroup")
|
||||||
|
accessibility_name = "toggle finalize grid mode"
|
||||||
icon = ExtResource("4_check_mark_light")
|
icon = ExtResource("4_check_mark_light")
|
||||||
expand_icon = true
|
expand_icon = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
class_name TheNetPage
|
class_name TheNetPage
|
||||||
extends Control
|
extends Control
|
||||||
|
|
||||||
|
const CurrencyPresentationType = preload(
|
||||||
|
"res://ui/currency_presentation.gd"
|
||||||
|
)
|
||||||
|
|
||||||
enum View {
|
enum View {
|
||||||
DAILY,
|
DAILY,
|
||||||
LIFETIME,
|
LIFETIME,
|
||||||
|
|
@ -278,15 +282,11 @@ func _build_job_rows(jobs: Array[Dictionary], empty_text: String) -> void:
|
||||||
)
|
)
|
||||||
content.add_child(count)
|
content.add_child(count)
|
||||||
|
|
||||||
var reward := Label.new()
|
var reward := _make_reward_display(
|
||||||
reward.custom_minimum_size.x = 98.0
|
int(job.get("fish_coin", 0)),
|
||||||
reward.text = "$%d • %d xp" % [
|
int(job.get("experience", 0)),
|
||||||
int(job.get("fish_coin", 0)), int(job.get("experience", 0)),
|
|
||||||
]
|
|
||||||
reward.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
||||||
reward.add_theme_color_override(
|
|
||||||
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
|
||||||
)
|
)
|
||||||
|
reward.custom_minimum_size.x = 128.0
|
||||||
content.add_child(reward)
|
content.add_child(reward)
|
||||||
|
|
||||||
var claim := Button.new()
|
var claim := Button.new()
|
||||||
|
|
@ -326,17 +326,19 @@ func _build_payment_rows() -> void:
|
||||||
var content := HBoxContainer.new()
|
var content := HBoxContainer.new()
|
||||||
content.add_theme_constant_override("separation", 14)
|
content.add_theme_constant_override("separation", 14)
|
||||||
row.add_child(content)
|
row.add_child(content)
|
||||||
|
var reward_details := VBoxContainer.new()
|
||||||
|
reward_details.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||||
var label := Label.new()
|
var label := Label.new()
|
||||||
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
label.text = str(reward.get("title", "completed job"))
|
||||||
label.text = "%s\n$%d • %d xp" % [
|
|
||||||
str(reward.get("title", "completed job")),
|
|
||||||
int(reward.get("fish_coin", 0)),
|
|
||||||
int(reward.get("experience", 0)),
|
|
||||||
]
|
|
||||||
label.add_theme_color_override(
|
label.add_theme_color_override(
|
||||||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||||
)
|
)
|
||||||
content.add_child(label)
|
reward_details.add_child(label)
|
||||||
|
reward_details.add_child(_make_reward_display(
|
||||||
|
int(reward.get("fish_coin", 0)),
|
||||||
|
int(reward.get("experience", 0)),
|
||||||
|
))
|
||||||
|
content.add_child(reward_details)
|
||||||
var claim := Button.new()
|
var claim := Button.new()
|
||||||
claim.text = "claim"
|
claim.text = "claim"
|
||||||
claim.custom_minimum_size = Vector2(110.0, 42.0)
|
claim.custom_minimum_size = Vector2(110.0, 42.0)
|
||||||
|
|
@ -363,6 +365,27 @@ func _claim(claim_id: String) -> void:
|
||||||
_refresh()
|
_refresh()
|
||||||
|
|
||||||
|
|
||||||
|
func _make_reward_display(fish_coin: int, experience: int) -> HBoxContainer:
|
||||||
|
var reward := HBoxContainer.new()
|
||||||
|
reward.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||||
|
reward.add_theme_constant_override("separation", 7)
|
||||||
|
var currency: CurrencyAmount = (
|
||||||
|
CurrencyPresentationType.instantiate_amount(fish_coin, 18.0)
|
||||||
|
)
|
||||||
|
currency.get_amount_label().add_theme_color_override(
|
||||||
|
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||||
|
)
|
||||||
|
reward.add_child(currency)
|
||||||
|
var experience_label := Label.new()
|
||||||
|
experience_label.text = "• %d xp" % experience
|
||||||
|
experience_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
||||||
|
experience_label.add_theme_color_override(
|
||||||
|
"font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY
|
||||||
|
)
|
||||||
|
reward.add_child(experience_label)
|
||||||
|
return reward
|
||||||
|
|
||||||
|
|
||||||
func _add_empty(message: String) -> void:
|
func _add_empty(message: String) -> void:
|
||||||
var empty := Label.new()
|
var empty := Label.new()
|
||||||
empty.text = message
|
empty.text = message
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,9 @@ const TitleCreditsPageType = preload("res://ui/title_credits_page.gd")
|
||||||
const NetworkSessionType = preload("res://network/network_session.gd")
|
const NetworkSessionType = preload("res://network/network_session.gd")
|
||||||
const SavedServerStoreType = preload("res://network/saved_server_store.gd")
|
const SavedServerStoreType = preload("res://network/saved_server_store.gd")
|
||||||
const JoinGamePageType = preload("res://ui/network/join_game_page.gd")
|
const JoinGamePageType = preload("res://ui/network/join_game_page.gd")
|
||||||
|
const CurrencyPresentationType = preload(
|
||||||
|
"res://ui/currency_presentation.gd"
|
||||||
|
)
|
||||||
const DECORATIVE_FISH_TEXTURES: Array[Texture2D] = [
|
const DECORATIVE_FISH_TEXTURES: Array[Texture2D] = [
|
||||||
preload("res://fish/species/bass/fish_bass_striped.png"),
|
preload("res://fish/species/bass/fish_bass_striped.png"),
|
||||||
preload("res://fish/species/bluegill/fish_bluegill.png"),
|
preload("res://fish/species/bluegill/fish_bluegill.png"),
|
||||||
|
|
@ -106,7 +109,7 @@ enum ConfirmationAction {
|
||||||
@onready var _join_game_button: BubbleButtonType = %JoinGameButton
|
@onready var _join_game_button: BubbleButtonType = %JoinGameButton
|
||||||
@onready var _new_game_label: Label = %NewGameLabel
|
@onready var _new_game_label: Label = %NewGameLabel
|
||||||
@onready var _delete_save_label: Label = %DeleteSaveLabel
|
@onready var _delete_save_label: Label = %DeleteSaveLabel
|
||||||
@onready var _feedback_label: Label = %FeedbackLabel
|
@onready var _feedback_label: RichTextLabel = %FeedbackLabel
|
||||||
@onready var _confirmation_page: TitleConfirmationBubblePageType = (
|
@onready var _confirmation_page: TitleConfirmationBubblePageType = (
|
||||||
%ConfirmationPage
|
%ConfirmationPage
|
||||||
)
|
)
|
||||||
|
|
@ -287,7 +290,7 @@ func open_join_game_page(endpoint: String = "") -> void:
|
||||||
func report_network_error(message: String) -> void:
|
func report_network_error(message: String) -> void:
|
||||||
_join_game_page.set_status(message)
|
_join_game_page.set_status(message)
|
||||||
if not _join_game_page.visible:
|
if not _join_game_page.visible:
|
||||||
_feedback_label.text = message
|
_feedback_label.text = _center_feedback_text(message)
|
||||||
_feedback_label.show()
|
_feedback_label.show()
|
||||||
_feedback_label.modulate.a = 1.0
|
_feedback_label.modulate.a = 1.0
|
||||||
|
|
||||||
|
|
@ -953,16 +956,23 @@ func _get_continue_stats_text() -> String:
|
||||||
or _inspection.status != SaveInspectionType.Status.VALID_SUPPORTED
|
or _inspection.status != SaveInspectionType.Status.VALID_SUPPORTED
|
||||||
):
|
):
|
||||||
return ""
|
return ""
|
||||||
return (
|
return _center_feedback_text(
|
||||||
"%d fish • $%d • %d discovered"
|
"%d fish • %s • %d discovered" % [
|
||||||
% [
|
|
||||||
_inspection.catch_count,
|
_inspection.catch_count,
|
||||||
_inspection.wallet_balance,
|
CurrencyPresentationType.bbcode_amount(
|
||||||
|
_inspection.wallet_balance, 18
|
||||||
|
),
|
||||||
_inspection.discovered_species_count,
|
_inspection.discovered_species_count,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
func _center_feedback_text(message: String) -> String:
|
||||||
|
if message.begins_with("[center]"):
|
||||||
|
return message
|
||||||
|
return "[center]%s[/center]" % message
|
||||||
|
|
||||||
|
|
||||||
func _on_continue_pressed() -> void:
|
func _on_continue_pressed() -> void:
|
||||||
if (
|
if (
|
||||||
_action_in_progress
|
_action_in_progress
|
||||||
|
|
@ -991,13 +1001,15 @@ func _on_new_game_pressed() -> void:
|
||||||
new_game_requested.emit()
|
new_game_requested.emit()
|
||||||
return
|
return
|
||||||
if _inspection.status == SaveInspectionType.Status.UNSUPPORTED_VERSION:
|
if _inspection.status == SaveInspectionType.Status.UNSUPPORTED_VERSION:
|
||||||
_feedback_label.text = (
|
_feedback_label.text = _center_feedback_text(
|
||||||
"this save is from a newer game version. "
|
"this save is from a newer game version. "
|
||||||
+ "use delete save before starting over."
|
+ "use delete save before starting over."
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if _inspection.status == SaveInspectionType.Status.IO_ERROR:
|
if _inspection.status == SaveInspectionType.Status.IO_ERROR:
|
||||||
_feedback_label.text = "the existing save cannot be accessed safely."
|
_feedback_label.text = _center_feedback_text(
|
||||||
|
"the existing save cannot be accessed safely."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
_open_confirmation(
|
_open_confirmation(
|
||||||
ConfirmationAction.NEW_GAME,
|
ConfirmationAction.NEW_GAME,
|
||||||
|
|
@ -1043,14 +1055,16 @@ func _on_confirmation_accepted() -> void:
|
||||||
_action_in_progress = false
|
_action_in_progress = false
|
||||||
return
|
return
|
||||||
if not _save_manager.delete_progression_save():
|
if not _save_manager.delete_progression_save():
|
||||||
_feedback_label.text = "failed to delete saved progression."
|
_feedback_label.text = _center_feedback_text(
|
||||||
|
"failed to delete saved progression."
|
||||||
|
)
|
||||||
_action_in_progress = false
|
_action_in_progress = false
|
||||||
_refresh_save_inspection()
|
_refresh_save_inspection()
|
||||||
_begin_confirmation_return()
|
_begin_confirmation_return()
|
||||||
return
|
return
|
||||||
_save_manager.initialize_new_game()
|
_save_manager.initialize_new_game()
|
||||||
_refresh_save_inspection()
|
_refresh_save_inspection()
|
||||||
_feedback_label.text = "saved progression deleted."
|
_feedback_label.text = _center_feedback_text("saved progression deleted.")
|
||||||
_begin_confirmation_return()
|
_begin_confirmation_return()
|
||||||
_action_in_progress = false
|
_action_in_progress = false
|
||||||
|
|
||||||
|
|
@ -1325,8 +1339,9 @@ func _begin_title_cluster_return(feedback_text: String) -> void:
|
||||||
_title_settings_transition_generation += 1
|
_title_settings_transition_generation += 1
|
||||||
_cancel_title_settings_tween()
|
_cancel_title_settings_tween()
|
||||||
_title_settings_transition_active = true
|
_title_settings_transition_active = true
|
||||||
if _feedback_label.text != feedback_text:
|
var centered_feedback: String = _center_feedback_text(feedback_text)
|
||||||
_feedback_label.text = feedback_text
|
if _feedback_label.text != centered_feedback:
|
||||||
|
_feedback_label.text = centered_feedback
|
||||||
_feedback_label.visible = _feedback_visible_before_settings
|
_feedback_label.visible = _feedback_visible_before_settings
|
||||||
_hide_continue_stats_context()
|
_hide_continue_stats_context()
|
||||||
_set_title_bubbles_interactive(false)
|
_set_title_bubbles_interactive(false)
|
||||||
|
|
@ -1434,7 +1449,7 @@ func _refresh_save_inspection() -> void:
|
||||||
_delete_save_label.modulate.a = (
|
_delete_save_label.modulate.a = (
|
||||||
DISABLED_BUBBLE_LABEL_ALPHA if _delete_button.disabled else 1.0
|
DISABLED_BUBBLE_LABEL_ALPHA if _delete_button.disabled else 1.0
|
||||||
)
|
)
|
||||||
_feedback_label.text = _inspection.message
|
_feedback_label.text = _center_feedback_text(_inspection.message)
|
||||||
if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED:
|
if _inspection.status == SaveInspectionType.Status.VALID_SUPPORTED:
|
||||||
_feedback_label.text = _get_continue_stats_text()
|
_feedback_label.text = _get_continue_stats_text()
|
||||||
if _continue_button.disabled:
|
if _continue_button.disabled:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[gd_scene load_steps=18 format=3]
|
[gd_scene load_steps=19 format=3]
|
||||||
|
|
||||||
[ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"]
|
[ext_resource type="Script" path="res://ui/title_screen.gd" id="1_script"]
|
||||||
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
[ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"]
|
||||||
|
|
@ -22,6 +22,17 @@ shader = ExtResource("4_water_shader")
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_logo"]
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_logo"]
|
||||||
shader = ExtResource("6_logo_shader")
|
shader = ExtResource("6_logo_shader")
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_continue_stats"]
|
||||||
|
content_margin_left = 14.0
|
||||||
|
content_margin_top = 6.0
|
||||||
|
content_margin_right = 14.0
|
||||||
|
content_margin_bottom = 6.0
|
||||||
|
bg_color = Color(0.031, 0.122, 0.169, 1)
|
||||||
|
corner_radius_top_left = 14
|
||||||
|
corner_radius_top_right = 14
|
||||||
|
corner_radius_bottom_right = 14
|
||||||
|
corner_radius_bottom_left = 14
|
||||||
|
|
||||||
[node name="TitleScreen" type="Control"]
|
[node name="TitleScreen" type="Control"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
z_index = 200
|
z_index = 200
|
||||||
|
|
@ -433,15 +444,18 @@ motion_phase = 4.8
|
||||||
deformation_amplitude = 0.02
|
deformation_amplitude = 0.02
|
||||||
deformation_period = 4.8
|
deformation_period = 4.8
|
||||||
|
|
||||||
[node name="FeedbackLabel" type="Label" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]
|
[node name="FeedbackLabel" type="RichTextLabel" parent="ResponsiveTitleStage/TitlePresentationScaleRoot/Center/MainContent"]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
visible = false
|
visible = false
|
||||||
custom_minimum_size = Vector2(0, 44)
|
custom_minimum_size = Vector2(430, 44)
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
|
size_flags_horizontal = 4
|
||||||
text = ""
|
text = ""
|
||||||
horizontal_alignment = 1
|
bbcode_enabled = true
|
||||||
vertical_alignment = 1
|
scroll_active = false
|
||||||
autowrap_mode = 2
|
autowrap_mode = 2
|
||||||
|
vertical_alignment = 1
|
||||||
|
theme_override_styles/normal = SubResource("StyleBoxFlat_continue_stats")
|
||||||
|
|
||||||
[node name="ConfirmationPage" parent="ResponsiveTitleStage/TitlePresentationScaleRoot" instance=ExtResource("10_confirmation_page")]
|
[node name="ConfirmationPage" parent="ResponsiveTitleStage/TitlePresentationScaleRoot" instance=ExtResource("10_confirmation_page")]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,12 @@
|
||||||
|
|
||||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_netfishing"]
|
[sub_resource type="ShaderMaterial" id="ShaderMaterial_netfishing"]
|
||||||
shader = ExtResource("1_sky_shader")
|
shader = ExtResource("1_sky_shader")
|
||||||
|
shader_parameter/cloud_coverage = 0.0
|
||||||
|
shader_parameter/cloud_opacity = 0.0
|
||||||
|
shader_parameter/cloud_light_color = Color(0.7, 0.76, 0.77, 1)
|
||||||
|
shader_parameter/cloud_shadow_color = Color(0.31, 0.38, 0.4, 1)
|
||||||
|
shader_parameter/cloud_wind = Vector2(0.01, -0.006)
|
||||||
|
shader_parameter/cloud_scale = 0.72
|
||||||
shader_parameter/sky_top_color = Color(0.204, 0.498, 0.643, 1)
|
shader_parameter/sky_top_color = Color(0.204, 0.498, 0.643, 1)
|
||||||
shader_parameter/sky_horizon_color = Color(0.663, 0.843, 0.847, 1)
|
shader_parameter/sky_horizon_color = Color(0.663, 0.843, 0.847, 1)
|
||||||
shader_parameter/ground_bottom_color = Color(0.157, 0.361, 0.439, 1)
|
shader_parameter/ground_bottom_color = Color(0.157, 0.361, 0.439, 1)
|
||||||
|
|
|
||||||
|
|
@ -49,9 +49,6 @@ uniform float shoreline_base_fill : hint_range(0.0, 1.0, 0.01) = 0.32;
|
||||||
uniform float shoreline_opacity_boost : hint_range(0.0, 0.5, 0.01) = 0.26;
|
uniform float shoreline_opacity_boost : hint_range(0.0, 0.5, 0.01) = 0.26;
|
||||||
uniform float surface_highlight_night_floor : hint_range(0.0, 1.0, 0.01) = 0.72;
|
uniform float surface_highlight_night_floor : hint_range(0.0, 1.0, 0.01) = 0.72;
|
||||||
uniform float shoreline_minimum_reach : hint_range(0.1, 1.0, 0.01) = 0.48;
|
uniform float shoreline_minimum_reach : hint_range(0.1, 1.0, 0.01) = 0.48;
|
||||||
uniform float shoreline_crest_width : hint_range(0.02, 0.5, 0.01) = 0.13;
|
|
||||||
uniform float shoreline_trail_spacing : hint_range(0.05, 0.8, 0.01) = 0.28;
|
|
||||||
uniform float shoreline_trail_strength : hint_range(0.0, 1.0, 0.01) = 0.55;
|
|
||||||
|
|
||||||
uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
|
uniform float distant_alpha_start : hint_range(10.0, 2000.0, 1.0) = 260.0;
|
||||||
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
|
uniform float distant_alpha_end : hint_range(20.0, 5000.0, 1.0) = 1400.0;
|
||||||
|
|
@ -486,25 +483,6 @@ void fragment() {
|
||||||
water_depth
|
water_depth
|
||||||
);
|
);
|
||||||
shoreline_mask *= shoreline_depth_visibility;
|
shoreline_mask *= shoreline_depth_visibility;
|
||||||
float shoreline_lead_depth = animated_shoreline_depth * 0.82;
|
|
||||||
float shoreline_lead_crest = 1.0 - smoothstep(
|
|
||||||
shoreline_crest_width * 0.25,
|
|
||||||
shoreline_crest_width,
|
|
||||||
abs(contour_depth - shoreline_lead_depth)
|
|
||||||
);
|
|
||||||
float shoreline_trail_depth = max(
|
|
||||||
shoreline_lead_depth - shoreline_trail_spacing,
|
|
||||||
shoreline_crest_width
|
|
||||||
);
|
|
||||||
float shoreline_trail_crest = 1.0 - smoothstep(
|
|
||||||
shoreline_crest_width * 0.40,
|
|
||||||
shoreline_crest_width * 1.45,
|
|
||||||
abs(contour_depth - shoreline_trail_depth)
|
|
||||||
);
|
|
||||||
shoreline_lead_crest *= shoreline_depth_visibility;
|
|
||||||
shoreline_trail_crest *= (
|
|
||||||
shoreline_depth_visibility * shoreline_trail_strength
|
|
||||||
);
|
|
||||||
float shoreline_marks = max(
|
float shoreline_marks = max(
|
||||||
blocky_mark_layer(
|
blocky_mark_layer(
|
||||||
world_position.xz * shoreline_mark_density
|
world_position.xz * shoreline_mark_density
|
||||||
|
|
@ -524,17 +502,14 @@ void fragment() {
|
||||||
1.0,
|
1.0,
|
||||||
shoreline_marks
|
shoreline_marks
|
||||||
);
|
);
|
||||||
float moving_crests = max(
|
|
||||||
shoreline_lead_crest,
|
|
||||||
shoreline_trail_crest
|
|
||||||
) * mix(0.68, 1.0, shoreline_marks);
|
|
||||||
shoreline_foam = max(shoreline_foam, moving_crests);
|
|
||||||
shoreline_foam *= shoreline_proximity;
|
shoreline_foam *= shoreline_proximity;
|
||||||
water_alpha = min(
|
water_alpha = min(
|
||||||
water_alpha + shoreline_foam * shoreline_opacity_boost,
|
water_alpha + shoreline_foam * shoreline_opacity_boost,
|
||||||
0.98
|
0.98
|
||||||
);
|
);
|
||||||
water_alpha *= contact_stability;
|
// Preserve most of the water layer at exact terrain contact. Fully fading
|
||||||
|
// it exposed the sand texture as a broken tan seam along the water plane.
|
||||||
|
water_alpha *= mix(0.82, 1.0, contact_stability);
|
||||||
|
|
||||||
float surface_mottle = value_noise(
|
float surface_mottle = value_noise(
|
||||||
world_position.xz * surface_mottle_scale
|
world_position.xz * surface_mottle_scale
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,6 @@ shader_parameter/shoreline_base_fill = 0.58
|
||||||
shader_parameter/shoreline_opacity_boost = 0.38
|
shader_parameter/shoreline_opacity_boost = 0.38
|
||||||
shader_parameter/surface_highlight_night_floor = 0.72
|
shader_parameter/surface_highlight_night_floor = 0.72
|
||||||
shader_parameter/shoreline_minimum_reach = 0.22
|
shader_parameter/shoreline_minimum_reach = 0.22
|
||||||
shader_parameter/shoreline_crest_width = 0.08
|
|
||||||
shader_parameter/shoreline_trail_spacing = 0.13
|
|
||||||
shader_parameter/shoreline_trail_strength = 0.55
|
|
||||||
shader_parameter/distant_alpha_start = 260.0
|
shader_parameter/distant_alpha_start = 260.0
|
||||||
shader_parameter/distant_alpha_end = 1400.0
|
shader_parameter/distant_alpha_end = 1400.0
|
||||||
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,6 @@ shader_parameter/shoreline_base_fill = 0.58
|
||||||
shader_parameter/shoreline_opacity_boost = 0.38
|
shader_parameter/shoreline_opacity_boost = 0.38
|
||||||
shader_parameter/surface_highlight_night_floor = 0.72
|
shader_parameter/surface_highlight_night_floor = 0.72
|
||||||
shader_parameter/shoreline_minimum_reach = 0.22
|
shader_parameter/shoreline_minimum_reach = 0.22
|
||||||
shader_parameter/shoreline_crest_width = 0.08
|
|
||||||
shader_parameter/shoreline_trail_spacing = 0.13
|
|
||||||
shader_parameter/shoreline_trail_strength = 0.55
|
|
||||||
shader_parameter/distant_alpha_start = 260.0
|
shader_parameter/distant_alpha_start = 260.0
|
||||||
shader_parameter/distant_alpha_end = 1400.0
|
shader_parameter/distant_alpha_end = 1400.0
|
||||||
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
shader_parameter/environment_tint = Color(1, 1, 1, 1)
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@
|
||||||
[ext_resource type="Environment" path="res://world/environment/netfishing_environment.tres" id="4_environment"]
|
[ext_resource type="Environment" path="res://world/environment/netfishing_environment.tres" id="4_environment"]
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="NorthSouthBoundsShape"]
|
[sub_resource type="BoxShape3D" id="NorthSouthBoundsShape"]
|
||||||
size = Vector3(120, 14, 2)
|
size = Vector3(168.8772, 14, 2)
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="EastWestBoundsShape"]
|
[sub_resource type="BoxShape3D" id="EastWestBoundsShape"]
|
||||||
size = Vector3(2, 14, 90)
|
size = Vector3(2, 14, 126.45079)
|
||||||
|
|
||||||
[sub_resource type="BoxShape3D" id="FailsafeShape"]
|
[sub_resource type="BoxShape3D" id="FailsafeShape"]
|
||||||
size = Vector3(118, 4, 88)
|
size = Vector3(166.8772, 4, 124.45079)
|
||||||
|
|
||||||
[node name="TestWorld" type="Node3D"]
|
[node name="TestWorld" type="Node3D"]
|
||||||
script = ExtResource("1_world")
|
script = ExtResource("1_world")
|
||||||
|
|
@ -49,25 +49,25 @@ shape = SubResource("FailsafeShape")
|
||||||
[node name="WorldBounds" type="Node3D" parent="."]
|
[node name="WorldBounds" type="Node3D" parent="."]
|
||||||
|
|
||||||
[node name="North" type="StaticBody3D" parent="WorldBounds"]
|
[node name="North" type="StaticBody3D" parent="WorldBounds"]
|
||||||
position = Vector3(12, 5, 45)
|
position = Vector3(12, 5, 63.225395)
|
||||||
|
|
||||||
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/North"]
|
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/North"]
|
||||||
shape = SubResource("NorthSouthBoundsShape")
|
shape = SubResource("NorthSouthBoundsShape")
|
||||||
|
|
||||||
[node name="South" type="StaticBody3D" parent="WorldBounds"]
|
[node name="South" type="StaticBody3D" parent="WorldBounds"]
|
||||||
position = Vector3(12, 5, -45)
|
position = Vector3(12, 5, -63.225395)
|
||||||
|
|
||||||
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/South"]
|
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/South"]
|
||||||
shape = SubResource("NorthSouthBoundsShape")
|
shape = SubResource("NorthSouthBoundsShape")
|
||||||
|
|
||||||
[node name="West" type="StaticBody3D" parent="WorldBounds"]
|
[node name="West" type="StaticBody3D" parent="WorldBounds"]
|
||||||
position = Vector3(-48, 5, 0)
|
position = Vector3(-72.4386, 5, 0)
|
||||||
|
|
||||||
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/West"]
|
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/West"]
|
||||||
shape = SubResource("EastWestBoundsShape")
|
shape = SubResource("EastWestBoundsShape")
|
||||||
|
|
||||||
[node name="East" type="StaticBody3D" parent="WorldBounds"]
|
[node name="East" type="StaticBody3D" parent="WorldBounds"]
|
||||||
position = Vector3(72, 5, 0)
|
position = Vector3(96.4386, 5, 0)
|
||||||
|
|
||||||
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/East"]
|
[node name="Shape" type="CollisionShape3D" parent="WorldBounds/East"]
|
||||||
shape = SubResource("EastWestBoundsShape")
|
shape = SubResource("EastWestBoundsShape")
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,8 @@ func _rebuild(configuration: ShorelineRibbonConfig) -> Dictionary:
|
||||||
configuration.water_is_inside,
|
configuration.water_is_inside,
|
||||||
configuration.simplification_tolerance,
|
configuration.simplification_tolerance,
|
||||||
configuration.smoothing_iterations,
|
configuration.smoothing_iterations,
|
||||||
configuration.resample_spacing
|
configuration.resample_spacing,
|
||||||
|
configuration.corner_rounding_distance,
|
||||||
)
|
)
|
||||||
var output_directory := configuration.output_resource_path.get_base_dir()
|
var output_directory := configuration.output_resource_path.get_base_dir()
|
||||||
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_directory))
|
DirAccess.make_dir_recursive_absolute(ProjectSettings.globalize_path(output_directory))
|
||||||
|
|
|
||||||
|
|
@ -12,3 +12,4 @@ extends Resource
|
||||||
@export var simplification_tolerance := -1.0
|
@export var simplification_tolerance := -1.0
|
||||||
@export_range(-1, 4, 1) var smoothing_iterations := -1
|
@export_range(-1, 4, 1) var smoothing_iterations := -1
|
||||||
@export var resample_spacing := -1.0
|
@export var resample_spacing := -1.0
|
||||||
|
@export var corner_rounding_distance := -1.0
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,14 @@ const WATER_PLANE_EPSILON := 0.001
|
||||||
const ENDPOINT_MERGE_TOLERANCE := 0.03
|
const ENDPOINT_MERGE_TOLERANCE := 0.03
|
||||||
const LOOP_CLOSURE_TOLERANCE := 0.06
|
const LOOP_CLOSURE_TOLERANCE := 0.06
|
||||||
const MINIMUM_FRAGMENT_LENGTH := 2.0
|
const MINIMUM_FRAGMENT_LENGTH := 2.0
|
||||||
const SIMPLIFICATION_TOLERANCE := 0.025
|
const SIMPLIFICATION_TOLERANCE := 0.55
|
||||||
const SMOOTHING_ITERATIONS := 0
|
const SMOOTHING_ITERATIONS := 3
|
||||||
const RESAMPLE_SPACING := 0.10
|
const RESAMPLE_SPACING := 0.16
|
||||||
|
const CORNER_ROUNDING_DISTANCE := 2.4
|
||||||
|
const CORNER_ROUNDING_START_DEGREES := 24.0
|
||||||
|
const CORNER_ROUNDING_FULL_DEGREES := 78.0
|
||||||
|
const MAXIMUM_JOIN_SCALE := 1.0
|
||||||
|
const RIBBON_REACH_GROWTH_PER_METER := 0.52
|
||||||
const RIBBON_WIDTH := 1.35
|
const RIBBON_WIDTH := 1.35
|
||||||
const LAND_INSET := 0.12
|
const LAND_INSET := 0.12
|
||||||
const SURFACE_OFFSET := 0.018
|
const SURFACE_OFFSET := 0.018
|
||||||
|
|
@ -21,7 +26,8 @@ static func generate(
|
||||||
water_is_inside: bool,
|
water_is_inside: bool,
|
||||||
simplification_override := -1.0,
|
simplification_override := -1.0,
|
||||||
smoothing_iterations_override := -1,
|
smoothing_iterations_override := -1,
|
||||||
resample_spacing_override := -1.0
|
resample_spacing_override := -1.0,
|
||||||
|
corner_rounding_override := -1.0
|
||||||
) -> Dictionary:
|
) -> Dictionary:
|
||||||
var simplification := (
|
var simplification := (
|
||||||
SIMPLIFICATION_TOLERANCE
|
SIMPLIFICATION_TOLERANCE
|
||||||
|
|
@ -38,6 +44,11 @@ static func generate(
|
||||||
if resample_spacing_override < 0.0
|
if resample_spacing_override < 0.0
|
||||||
else resample_spacing_override
|
else resample_spacing_override
|
||||||
)
|
)
|
||||||
|
var corner_rounding := (
|
||||||
|
CORNER_ROUNDING_DISTANCE
|
||||||
|
if corner_rounding_override < 0.0
|
||||||
|
else corner_rounding_override
|
||||||
|
)
|
||||||
var segments := _extract_segments(faces, water_height, bounds)
|
var segments := _extract_segments(faces, water_height, bounds)
|
||||||
var raw_paths := _stitch_segments(segments)
|
var raw_paths := _stitch_segments(segments)
|
||||||
var processed_paths: Array[Dictionary] = []
|
var processed_paths: Array[Dictionary] = []
|
||||||
|
|
@ -52,7 +63,12 @@ static func generate(
|
||||||
continue
|
continue
|
||||||
var simplified := _simplify(raw_points, closed, simplification)
|
var simplified := _simplify(raw_points, closed, simplification)
|
||||||
simplified_paths.append({"points": simplified, "closed": closed})
|
simplified_paths.append({"points": simplified, "closed": closed})
|
||||||
var smoothed := _chaikin(simplified, closed, smoothing_iterations)
|
var rounded := _round_sharp_corners(
|
||||||
|
simplified,
|
||||||
|
closed,
|
||||||
|
corner_rounding,
|
||||||
|
)
|
||||||
|
var smoothed := _chaikin(rounded, closed, smoothing_iterations)
|
||||||
var resampled := _resample(smoothed, closed, resample_spacing)
|
var resampled := _resample(smoothed, closed, resample_spacing)
|
||||||
if resampled.size() < (3 if closed else 2):
|
if resampled.size() < (3 if closed else 2):
|
||||||
continue
|
continue
|
||||||
|
|
@ -296,6 +312,65 @@ static func _chaikin(
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
static func _round_sharp_corners(
|
||||||
|
points: PackedVector2Array,
|
||||||
|
closed: bool,
|
||||||
|
rounding_distance: float,
|
||||||
|
) -> PackedVector2Array:
|
||||||
|
if rounding_distance <= WATER_PLANE_EPSILON or points.size() < 3:
|
||||||
|
return points
|
||||||
|
var result := PackedVector2Array()
|
||||||
|
for index: int in points.size():
|
||||||
|
if not closed and (index == 0 or index == points.size() - 1):
|
||||||
|
result.append(points[index])
|
||||||
|
continue
|
||||||
|
var previous := points[(index - 1 + points.size()) % points.size()]
|
||||||
|
var point := points[index]
|
||||||
|
var following := points[(index + 1) % points.size()]
|
||||||
|
var incoming_vector := point - previous
|
||||||
|
var outgoing_vector := following - point
|
||||||
|
var incoming_length := incoming_vector.length()
|
||||||
|
var outgoing_length := outgoing_vector.length()
|
||||||
|
if (
|
||||||
|
incoming_length <= WATER_PLANE_EPSILON
|
||||||
|
or outgoing_length <= WATER_PLANE_EPSILON
|
||||||
|
):
|
||||||
|
result.append(point)
|
||||||
|
continue
|
||||||
|
var incoming := incoming_vector / incoming_length
|
||||||
|
var outgoing := outgoing_vector / outgoing_length
|
||||||
|
var turn_degrees := rad_to_deg(
|
||||||
|
acos(clampf(incoming.dot(outgoing), -1.0, 1.0))
|
||||||
|
)
|
||||||
|
var corner_weight := smoothstep(
|
||||||
|
CORNER_ROUNDING_START_DEGREES,
|
||||||
|
CORNER_ROUNDING_FULL_DEGREES,
|
||||||
|
turn_degrees,
|
||||||
|
)
|
||||||
|
var cut_distance := minf(
|
||||||
|
rounding_distance * corner_weight,
|
||||||
|
minf(incoming_length, outgoing_length) * 0.44,
|
||||||
|
)
|
||||||
|
if cut_distance <= WATER_PLANE_EPSILON:
|
||||||
|
result.append(point)
|
||||||
|
continue
|
||||||
|
var entry := point - incoming * cut_distance
|
||||||
|
var exit := point + outgoing * cut_distance
|
||||||
|
var curve_steps := maxi(3, ceili(cut_distance / 0.22))
|
||||||
|
for step: int in curve_steps + 1:
|
||||||
|
var amount := float(step) / float(curve_steps)
|
||||||
|
var first := entry.lerp(point, amount)
|
||||||
|
var second := point.lerp(exit, amount)
|
||||||
|
var rounded_point := first.lerp(second, amount)
|
||||||
|
if (
|
||||||
|
result.is_empty()
|
||||||
|
or result[result.size() - 1].distance_to(rounded_point)
|
||||||
|
> WATER_PLANE_EPSILON
|
||||||
|
):
|
||||||
|
result.append(rounded_point)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
static func _resample(
|
static func _resample(
|
||||||
points: PackedVector2Array,
|
points: PackedVector2Array,
|
||||||
closed: bool,
|
closed: bool,
|
||||||
|
|
@ -348,10 +423,10 @@ static func _build_mesh(
|
||||||
else 0.0
|
else 0.0
|
||||||
)
|
)
|
||||||
var base_index := vertices.size()
|
var base_index := vertices.size()
|
||||||
var path_distance := 0.0
|
var water_normals := PackedVector2Array()
|
||||||
|
var join_scales := PackedFloat32Array()
|
||||||
|
var water_reaches := PackedFloat32Array()
|
||||||
for index: int in points.size():
|
for index: int in points.size():
|
||||||
if index > 0:
|
|
||||||
path_distance += points[index - 1].distance_to(points[index])
|
|
||||||
var previous := points[(index - 1 + points.size()) % points.size()]
|
var previous := points[(index - 1 + points.size()) % points.size()]
|
||||||
var following := points[(index + 1) % points.size()]
|
var following := points[(index + 1) % points.size()]
|
||||||
if not closed:
|
if not closed:
|
||||||
|
|
@ -381,14 +456,37 @@ static func _build_mesh(
|
||||||
water_normal = -water_normal
|
water_normal = -water_normal
|
||||||
var join_scale := minf(
|
var join_scale := minf(
|
||||||
1.0 / maxf(absf(water_normal.dot(outgoing_normal)), 0.55),
|
1.0 / maxf(absf(water_normal.dot(outgoing_normal)), 0.55),
|
||||||
1.65,
|
MAXIMUM_JOIN_SCALE,
|
||||||
)
|
)
|
||||||
|
var water_reach := _safe_water_reach(
|
||||||
|
incoming,
|
||||||
|
outgoing,
|
||||||
|
water_normal,
|
||||||
|
previous.distance_to(point),
|
||||||
|
point.distance_to(following),
|
||||||
|
(RIBBON_WIDTH - LAND_INSET) * join_scale,
|
||||||
|
)
|
||||||
|
water_normals.append(water_normal)
|
||||||
|
join_scales.append(join_scale)
|
||||||
|
water_reaches.append(water_reach)
|
||||||
|
water_reaches = _smooth_water_reaches(
|
||||||
|
points,
|
||||||
|
water_reaches,
|
||||||
|
closed,
|
||||||
|
)
|
||||||
|
var path_distance := 0.0
|
||||||
|
for index: int in points.size():
|
||||||
|
if index > 0:
|
||||||
|
path_distance += points[index - 1].distance_to(points[index])
|
||||||
|
var point := points[index]
|
||||||
|
var water_normal := water_normals[index]
|
||||||
|
var join_scale := join_scales[index]
|
||||||
var land_point := (
|
var land_point := (
|
||||||
point - water_normal * LAND_INSET * join_scale
|
point - water_normal * LAND_INSET * join_scale
|
||||||
)
|
)
|
||||||
var water_point := (
|
var water_point := (
|
||||||
point
|
point
|
||||||
+ water_normal * (RIBBON_WIDTH - LAND_INSET) * join_scale
|
+ water_normal * water_reaches[index]
|
||||||
)
|
)
|
||||||
vertices.append(Vector3(land_point.x, water_height + SURFACE_OFFSET, land_point.y))
|
vertices.append(Vector3(land_point.x, water_height + SURFACE_OFFSET, land_point.y))
|
||||||
vertices.append(Vector3(water_point.x, water_height + SURFACE_OFFSET, water_point.y))
|
vertices.append(Vector3(water_point.x, water_height + SURFACE_OFFSET, water_point.y))
|
||||||
|
|
@ -416,6 +514,58 @@ static func _build_mesh(
|
||||||
return mesh
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
|
static func _safe_water_reach(
|
||||||
|
incoming: Vector2,
|
||||||
|
outgoing: Vector2,
|
||||||
|
water_normal: Vector2,
|
||||||
|
incoming_length: float,
|
||||||
|
outgoing_length: float,
|
||||||
|
desired_reach: float,
|
||||||
|
) -> float:
|
||||||
|
var turn_cross := incoming.cross(outgoing)
|
||||||
|
var water_side := incoming.cross(water_normal)
|
||||||
|
if turn_cross * water_side <= 0.0:
|
||||||
|
return desired_reach
|
||||||
|
var turn_angle := acos(clampf(incoming.dot(outgoing), -1.0, 1.0))
|
||||||
|
if turn_angle <= WATER_PLANE_EPSILON:
|
||||||
|
return desired_reach
|
||||||
|
var radius := minf(incoming_length, outgoing_length) / maxf(
|
||||||
|
2.0 * sin(turn_angle * 0.5),
|
||||||
|
WATER_PLANE_EPSILON,
|
||||||
|
)
|
||||||
|
return minf(desired_reach, maxf(radius * 0.58, 0.24))
|
||||||
|
|
||||||
|
|
||||||
|
static func _smooth_water_reaches(
|
||||||
|
points: PackedVector2Array,
|
||||||
|
reaches: PackedFloat32Array,
|
||||||
|
closed: bool,
|
||||||
|
) -> PackedFloat32Array:
|
||||||
|
var result := reaches.duplicate()
|
||||||
|
if result.size() < 2:
|
||||||
|
return result
|
||||||
|
for _pass: int in 3:
|
||||||
|
var forward_start := 0 if closed else 1
|
||||||
|
for index: int in range(forward_start, result.size()):
|
||||||
|
var previous := (index - 1 + result.size()) % result.size()
|
||||||
|
var allowed := (
|
||||||
|
result[previous]
|
||||||
|
+ points[previous].distance_to(points[index])
|
||||||
|
* RIBBON_REACH_GROWTH_PER_METER
|
||||||
|
)
|
||||||
|
result[index] = minf(result[index], allowed)
|
||||||
|
var backward_start := result.size() - 1 if closed else result.size() - 2
|
||||||
|
for index: int in range(backward_start, -1, -1):
|
||||||
|
var following := (index + 1) % result.size()
|
||||||
|
var allowed := (
|
||||||
|
result[following]
|
||||||
|
+ points[index].distance_to(points[following])
|
||||||
|
* RIBBON_REACH_GROWTH_PER_METER
|
||||||
|
)
|
||||||
|
result[index] = minf(result[index], allowed)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
static func _closed_path_water_side(
|
static func _closed_path_water_side(
|
||||||
points: PackedVector2Array,
|
points: PackedVector2Array,
|
||||||
water_is_inside: bool,
|
water_is_inside: bool,
|
||||||
|
|
|
||||||