Compare commits
5 commits
a91f2b9825
...
633bbd9a88
| Author | SHA1 | Date | |
|---|---|---|---|
| 633bbd9a88 | |||
| 7ab3c86e93 | |||
| 1c801509af | |||
| e560cc0b3e | |||
| 9277326164 |
BIN
art/source/ui/title/bubbles/bubble1.xcf
Normal file
BIN
art/source/ui/title/bubbles/bubble2.xcf
Normal file
BIN
art/source/ui/title/bubbles/bubble3.xcf
Normal file
BIN
audio/music/title/as_in_four_wolves.ogg
Normal file
19
audio/music/title/as_in_four_wolves.ogg.import
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[remap]
|
||||
|
||||
importer="oggvorbisstr"
|
||||
type="AudioStreamOggVorbis"
|
||||
uid="uid://bgkfluumuen4m"
|
||||
path="res://.godot/imported/as_in_four_wolves.ogg-3d5f167a45d25a52b56a9dd816196d44.oggvorbisstr"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://audio/music/title/as_in_four_wolves.ogg"
|
||||
dest_files=["res://.godot/imported/as_in_four_wolves.ogg-3d5f167a45d25a52b56a9dd816196d44.oggvorbisstr"]
|
||||
|
||||
[params]
|
||||
|
||||
loop=true
|
||||
loop_offset=0
|
||||
bpm=0
|
||||
beat_count=0
|
||||
bar_beats=4
|
||||
15
default_bus_layout.tres
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[gd_resource type="AudioBusLayout" format=3]
|
||||
|
||||
[resource]
|
||||
bus/0/name = &"Master"
|
||||
bus/0/solo = false
|
||||
bus/0/mute = false
|
||||
bus/0/bypass_fx = false
|
||||
bus/0/volume_db = 0.0
|
||||
bus/0/send = &""
|
||||
bus/1/name = &"Music"
|
||||
bus/1/solo = false
|
||||
bus/1/mute = false
|
||||
bus/1/bypass_fx = false
|
||||
bus/1/volume_db = 0.0
|
||||
bus/1/send = &"Master"
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
[resource]
|
||||
script = ExtResource("1_profile")
|
||||
id = &"main_fishing_shop"
|
||||
display_name = "Main Fishing Shop"
|
||||
display_name = "main fishing shop"
|
||||
animal_name_singular = "shopkeeper"
|
||||
animal_name_plural = "shopkeepers"
|
||||
payout_multiplier = 1.0
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
[resource]
|
||||
script = ExtResource("1_profile")
|
||||
id = &"pelicans"
|
||||
display_name = "Pelicans"
|
||||
display_name = "pelicans"
|
||||
animal_name_singular = "pelican"
|
||||
animal_name_plural = "pelicans"
|
||||
payout_multiplier = 0.25
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
[resource]
|
||||
script = ExtResource("1_profile")
|
||||
id = &"trading_post"
|
||||
display_name = "Trading Post"
|
||||
display_name = "trading post"
|
||||
animal_name_singular = "otter"
|
||||
animal_name_plural = "otters"
|
||||
payout_multiplier = 0.85
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func get_sale_message(
|
|||
var buyer_name: String = animal_name_plural
|
||||
if buyer_name.is_empty():
|
||||
buyer_name = display_name
|
||||
return "You sold your %s to the %s for $%d." % [
|
||||
return "you sold your %s to the %s for $%d." % [
|
||||
fish_name,
|
||||
buyer_name,
|
||||
payout,
|
||||
|
|
|
|||
|
|
@ -8,12 +8,15 @@ enum Status {
|
|||
INVALID_VALUE,
|
||||
INVALID_BUYER,
|
||||
INVALID_OFFER,
|
||||
INVALID_SELECTION,
|
||||
TRANSACTION_FAILED,
|
||||
}
|
||||
|
||||
var success: bool = false
|
||||
var status: Status = Status.TRANSACTION_FAILED
|
||||
var catch_id: StringName
|
||||
var catch_ids: Array[StringName] = []
|
||||
var fish_count: int = 0
|
||||
var fish_name: String = ""
|
||||
var buyer_id: StringName
|
||||
var buyer_display_name: String = ""
|
||||
|
|
@ -32,14 +35,16 @@ func get_message() -> String:
|
|||
Status.SUCCESS:
|
||||
return sale_message
|
||||
Status.NOT_FOUND:
|
||||
return "Fish no longer exists."
|
||||
return "fish no longer exists."
|
||||
Status.FAVORITED:
|
||||
return "Favorited fish cannot be sold."
|
||||
return "favorited fish cannot be sold."
|
||||
Status.INVALID_VALUE:
|
||||
return "Invalid sale value."
|
||||
return "invalid sale value."
|
||||
Status.INVALID_BUYER:
|
||||
return "Buyer is unavailable."
|
||||
return "buyer is unavailable."
|
||||
Status.INVALID_OFFER:
|
||||
return "Invalid buyer offer."
|
||||
return "invalid buyer offer."
|
||||
Status.INVALID_SELECTION:
|
||||
return "the fish selection is invalid."
|
||||
_:
|
||||
return "Transaction failed."
|
||||
return "transaction failed."
|
||||
|
|
|
|||
|
|
@ -25,24 +25,53 @@ func can_sell(
|
|||
catch_id: StringName,
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> bool:
|
||||
return _validate_sale(catch_id, buyer).is_success()
|
||||
return preview_batch([catch_id], buyer).is_success()
|
||||
|
||||
|
||||
func can_sell_batch(
|
||||
catch_ids: Array[StringName],
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> bool:
|
||||
return preview_batch(catch_ids, buyer).is_success()
|
||||
|
||||
|
||||
func preview_batch(
|
||||
catch_ids: Array[StringName],
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> FishSaleResultType:
|
||||
return _validate_batch(catch_ids, buyer)
|
||||
|
||||
|
||||
func sell(
|
||||
catch_id: StringName,
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> FishSaleResultType:
|
||||
var result: FishSaleResultType = _validate_sale(catch_id, buyer)
|
||||
return sell_batch([catch_id], buyer)
|
||||
|
||||
|
||||
func sell_batch(
|
||||
catch_ids: Array[StringName],
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> FishSaleResultType:
|
||||
var result: FishSaleResultType = _validate_batch(catch_ids, buyer)
|
||||
if not result.is_success():
|
||||
return result
|
||||
|
||||
var removed_catch: FishCatchType = _inventory.remove_catch_by_id(catch_id)
|
||||
if removed_catch == null:
|
||||
var inventory_snapshot: Array[FishCatchType] = _inventory.get_all_catches()
|
||||
var next_sequence_snapshot: int = _inventory.get_next_catch_sequence()
|
||||
var removed_catches: Array[FishCatchType] = (
|
||||
_inventory.remove_catches_by_ids(result.catch_ids)
|
||||
)
|
||||
if removed_catches.size() != result.catch_ids.size():
|
||||
result.success = false
|
||||
result.status = FishSaleResultType.Status.NOT_FOUND
|
||||
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
||||
return result
|
||||
if not _wallet.credit(result.payout):
|
||||
_inventory.add_catch(removed_catch)
|
||||
if not _inventory.replace_all_catches(
|
||||
inventory_snapshot,
|
||||
next_sequence_snapshot
|
||||
):
|
||||
push_error("Unable to roll back a failed fish batch sale.")
|
||||
result.success = false
|
||||
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
||||
return result
|
||||
|
|
@ -50,27 +79,22 @@ func sell(
|
|||
return result
|
||||
|
||||
|
||||
func _validate_sale(
|
||||
catch_id: StringName,
|
||||
func _validate_batch(
|
||||
catch_ids: Array[StringName],
|
||||
buyer: FishBuyerProfileType,
|
||||
) -> FishSaleResultType:
|
||||
var result := FishSaleResultType.new()
|
||||
result.catch_id = catch_id
|
||||
if (
|
||||
catch_id.is_empty()
|
||||
or _inventory == null
|
||||
or _wallet == null
|
||||
):
|
||||
result.status = FishSaleResultType.Status.NOT_FOUND
|
||||
result.catch_ids = catch_ids.duplicate()
|
||||
result.fish_count = catch_ids.size()
|
||||
if _inventory == null or _wallet == null:
|
||||
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
||||
return result
|
||||
if catch_ids.is_empty():
|
||||
result.status = FishSaleResultType.Status.INVALID_SELECTION
|
||||
return result
|
||||
if buyer == null or not buyer.is_valid():
|
||||
result.status = FishSaleResultType.Status.INVALID_BUYER
|
||||
return result
|
||||
var fish_catch: FishCatchType = _inventory.get_catch_by_id(catch_id)
|
||||
if fish_catch == null:
|
||||
result.status = FishSaleResultType.Status.NOT_FOUND
|
||||
return result
|
||||
result.fish_name = fish_catch.fish.display_name
|
||||
result.buyer_id = buyer.id
|
||||
result.buyer_display_name = buyer.display_name
|
||||
result.buyer_animal_name = (
|
||||
|
|
@ -78,21 +102,55 @@ func _validate_sale(
|
|||
if not buyer.animal_name_plural.is_empty()
|
||||
else buyer.display_name
|
||||
)
|
||||
result.base_value = fish_catch.sale_value
|
||||
result.payout = buyer.get_offer(result.base_value)
|
||||
var seen_ids: Dictionary[StringName, bool] = {}
|
||||
var contains_favorite: bool = false
|
||||
const MAX_SAFE_TOTAL: int = 9223372036854775807
|
||||
for catch_id: StringName in catch_ids:
|
||||
if catch_id.is_empty() or seen_ids.has(catch_id):
|
||||
result.status = FishSaleResultType.Status.INVALID_SELECTION
|
||||
return result
|
||||
seen_ids[catch_id] = true
|
||||
var fish_catch: FishCatchType = _inventory.get_catch_by_id(catch_id)
|
||||
if fish_catch == null:
|
||||
result.status = FishSaleResultType.Status.NOT_FOUND
|
||||
return result
|
||||
if fish_catch.is_favorited:
|
||||
result.status = FishSaleResultType.Status.FAVORITED
|
||||
elif fish_catch.sale_value < 0:
|
||||
contains_favorite = true
|
||||
if fish_catch.sale_value < 0:
|
||||
result.status = FishSaleResultType.Status.INVALID_VALUE
|
||||
elif result.payout < 0:
|
||||
return result
|
||||
var offer: int = buyer.get_offer(fish_catch.sale_value)
|
||||
if offer < 0:
|
||||
result.status = FishSaleResultType.Status.INVALID_OFFER
|
||||
elif not _wallet.can_credit(result.payout):
|
||||
return result
|
||||
if (
|
||||
result.base_value > MAX_SAFE_TOTAL - fish_catch.sale_value
|
||||
or result.payout > MAX_SAFE_TOTAL - offer
|
||||
):
|
||||
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
||||
else:
|
||||
return result
|
||||
result.base_value += fish_catch.sale_value
|
||||
result.payout += offer
|
||||
if result.fish_count == 1:
|
||||
result.catch_id = fish_catch.catch_id
|
||||
result.fish_name = fish_catch.fish.display_name
|
||||
if contains_favorite:
|
||||
result.status = FishSaleResultType.Status.FAVORITED
|
||||
return result
|
||||
if not _wallet.can_credit(result.payout):
|
||||
result.status = FishSaleResultType.Status.TRANSACTION_FAILED
|
||||
return result
|
||||
result.status = FishSaleResultType.Status.SUCCESS
|
||||
result.success = true
|
||||
if result.fish_count == 1:
|
||||
result.sale_message = buyer.get_sale_message(
|
||||
result.fish_name,
|
||||
result.payout
|
||||
)
|
||||
else:
|
||||
result.sale_message = "you sold %d fish to the %s for $%d." % [
|
||||
result.fish_count,
|
||||
result.buyer_animal_name,
|
||||
result.payout,
|
||||
]
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -98,4 +98,4 @@ func get_sale_value_for_weight(weight_lb: float) -> int:
|
|||
|
||||
|
||||
func get_rarity_name() -> String:
|
||||
return Rarity.keys()[rarity].capitalize()
|
||||
return Rarity.keys()[rarity].to_lower()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
[gd_resource type="Resource" load_steps=5 format=3]
|
||||
[gd_resource type="Resource" load_steps=6 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_pool.gd" id="1_pool"]
|
||||
[ext_resource type="Resource" path="res://fish/species/bluegill/bluegill.tres" id="2_bluegill"]
|
||||
[ext_resource type="Resource" path="res://fish/species/bass/bass.tres" id="3_bass"]
|
||||
[ext_resource type="Resource" path="res://fish/species/carp/carp.tres" id="4_carp"]
|
||||
[ext_resource type="Resource" path="res://fish/species/sunfish/sunfish.tres" id="5_sunfish"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_pool")
|
||||
candidates = [ExtResource("2_bluegill"), ExtResource("3_bass"), ExtResource("4_carp")]
|
||||
candidates = [ExtResource("2_bluegill"), ExtResource("3_bass"), ExtResource("4_carp"), ExtResource("5_sunfish")]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/bass_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/bass/bass.svg" id="4_texture"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/bass/fish_bass_striped.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="BassAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
|
|
@ -15,7 +15,7 @@ preferred_bait_weight_multiplier = 1.4
|
|||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"bass"
|
||||
display_name = "Bass"
|
||||
display_name = "striped bass"
|
||||
rarity = 1
|
||||
base_catch_weight = 4.0
|
||||
catch_profile = ExtResource("2_profile")
|
||||
|
|
|
|||
BIN
fish/species/bass/fish_bass_striped.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
40
fish/species/bass/fish_bass_striped.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dvh6s86asl4ix"
|
||||
path="res://.godot/imported/fish_bass_striped.png-b8c0def688ff4dd2d32a6dd21fa2339c.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/bass/fish_bass_striped.png"
|
||||
dest_files=["res://.godot/imported/fish_bass_striped.png-b8c0def688ff4dd2d32a6dd21fa2339c.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
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Resource" path="res://fish/default_availability.tres" id="3_availability"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/bluegill/bluegill.svg" id="4_texture"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/bluegill/fish_bluegill.png" id="4_texture"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"bluegill"
|
||||
display_name = "Bluegill"
|
||||
display_name = "bluegill"
|
||||
rarity = 0
|
||||
base_catch_weight = 10.0
|
||||
catch_profile = ExtResource("2_profile")
|
||||
|
|
|
|||
BIN
fish/species/bluegill/fish_bluegill.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
40
fish/species/bluegill/fish_bluegill.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://bwsl8862rf2vs"
|
||||
path="res://.godot/imported/fish_bluegill.png-2307d25b88382e057d2259e28f2f89aa.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/bluegill/fish_bluegill.png"
|
||||
dest_files=["res://.godot/imported/fish_bluegill.png-2307d25b88382e057d2259e28f2f89aa.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
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/carp_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Script" path="res://fish/fish_availability.gd" id="3_availability_script"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/carp/carp.svg" id="4_texture"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/carp/fish_carp_common.png" id="4_texture"]
|
||||
|
||||
[sub_resource type="Resource" id="CarpAvailability"]
|
||||
script = ExtResource("3_availability_script")
|
||||
|
|
@ -15,7 +15,7 @@ preferred_bait_weight_multiplier = 1.5
|
|||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"carp"
|
||||
display_name = "Carp"
|
||||
display_name = "common carp"
|
||||
rarity = 2
|
||||
base_catch_weight = 1.5
|
||||
catch_profile = ExtResource("2_profile")
|
||||
|
|
|
|||
BIN
fish/species/carp/fish_carp_common.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
40
fish/species/carp/fish_carp_common.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://c6pa1gqwv1a5s"
|
||||
path="res://.godot/imported/fish_carp_common.png-4111bce53aeff44874d8e561aa1600a2.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/carp/fish_carp_common.png"
|
||||
dest_files=["res://.godot/imported/fish_carp_common.png-4111bce53aeff44874d8e561aa1600a2.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
|
||||
BIN
fish/species/sunfish/fish_sunfish.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
40
fish/species/sunfish/fish_sunfish.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://x0exmgq6g7yy"
|
||||
path="res://.godot/imported/fish_sunfish.png-a59a80b2543272e8c6e8ab17d8d3670e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://fish/species/sunfish/fish_sunfish.png"
|
||||
dest_files=["res://.godot/imported/fish_sunfish.png-a59a80b2543272e8c6e8ab17d8d3670e.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
|
||||
24
fish/species/sunfish/sunfish.tres
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[gd_resource type="Resource" load_steps=5 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://fish/fish_data.gd" id="1_fish_data"]
|
||||
[ext_resource type="Resource" path="res://fishing/common_catch_profile.tres" id="2_profile"]
|
||||
[ext_resource type="Resource" path="res://fish/default_availability.tres" id="3_availability"]
|
||||
[ext_resource type="Texture2D" path="res://fish/species/sunfish/fish_sunfish.png" id="4_texture"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_fish_data")
|
||||
id = &"sunfish"
|
||||
display_name = "sunfish"
|
||||
rarity = 0
|
||||
base_catch_weight = 7.0
|
||||
catch_profile = ExtResource("2_profile")
|
||||
availability = ExtResource("3_availability")
|
||||
weight_min_lb = 0.4
|
||||
weight_max_lb = 2.5
|
||||
display_scale_min = 0.7
|
||||
display_scale_max = 1.1
|
||||
display_scale_curve = 1.0
|
||||
sell_value_min = 3
|
||||
sell_value_max = 3
|
||||
sell_value_curve = 1.0
|
||||
display_texture = ExtResource("4_texture")
|
||||
|
|
@ -385,14 +385,14 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if not has_active_fishing_rod():
|
||||
status_changed.emit("Select a fishing rod to cast.")
|
||||
status_changed.emit("select a fishing rod to cast.")
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if (
|
||||
_is_cooler_full()
|
||||
):
|
||||
status_changed.emit(
|
||||
"Cooler full. Sell fish before casting again."
|
||||
"cooler full. sell fish before casting again."
|
||||
)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
|
|
@ -438,7 +438,7 @@ func _begin_aiming(player: PlayerType) -> void:
|
|||
_cast_charge = 0.0
|
||||
_cast_target = _calculate_cast_target(minimum_cast_distance)
|
||||
state = FishingState.AIMING_CAST
|
||||
status_changed.emit("Hold left click to aim • Release to cast")
|
||||
status_changed.emit("hold left click to aim • release to cast")
|
||||
var target_is_fishable: bool = is_target_fishable(_cast_target)
|
||||
var preview_position: Vector3 = _resolve_preview_surface_position(_cast_target)
|
||||
_presentation.begin_aim(
|
||||
|
|
@ -530,7 +530,7 @@ func _confirm_cast() -> void:
|
|||
return
|
||||
|
||||
state = FishingState.CASTING
|
||||
status_changed.emit("Casting...")
|
||||
status_changed.emit("casting...")
|
||||
_presentation.begin_cast(_cast_target)
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
|
||||
|
|
@ -542,7 +542,7 @@ func _on_cast_completed() -> void:
|
|||
_selected_water_region = get_fishable_water_region(_cast_target)
|
||||
_cast_landing_is_fishable = _selected_water_region != null
|
||||
if not _cast_landing_is_fishable:
|
||||
_cleanup_attempt("Can't fish there.", &"invalid")
|
||||
_cleanup_attempt("can't fish there.", &"invalid")
|
||||
return
|
||||
_selection_context = _build_fishing_context(_selected_water_region)
|
||||
_fish_selector.undiscovered_weight_multiplier = undiscovered_weight_multiplier
|
||||
|
|
@ -562,7 +562,7 @@ func _on_cast_completed() -> void:
|
|||
_local_collection_log
|
||||
)
|
||||
if _selected_fish == null:
|
||||
_cleanup_attempt("Nothing is biting here.", &"invalid")
|
||||
_cleanup_attempt("nothing is biting here.", &"invalid")
|
||||
return
|
||||
|
||||
state = FishingState.WAITING_FOR_BITE
|
||||
|
|
@ -580,7 +580,7 @@ func _on_cast_completed() -> void:
|
|||
_cast_origin_position.z + _cast_direction.z * withdrawal_cancel_distance
|
||||
)
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.SLACK)
|
||||
status_changed.emit("Waiting for a bite...")
|
||||
status_changed.emit("waiting for a bite...")
|
||||
|
||||
|
||||
func _update_waiting_for_bite(delta: float) -> void:
|
||||
|
|
@ -698,7 +698,7 @@ func _activate_bite() -> void:
|
|||
_withdrawal_input_held = false
|
||||
_fight_start_position = _bobber_water_position
|
||||
bite_activated.emit()
|
||||
status_changed.emit("Fish on!")
|
||||
status_changed.emit("fish on!")
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
_presentation.begin_reeling()
|
||||
_catch_controller.start_encounter(
|
||||
|
|
@ -800,9 +800,9 @@ func _on_catch_escaped() -> void:
|
|||
if state != FishingState.FIGHTING:
|
||||
return
|
||||
|
||||
var fish_name: String = "The fish"
|
||||
var fish_name: String = "the fish"
|
||||
if _selected_fish != null and not _selected_fish.display_name.is_empty():
|
||||
fish_name = "The %s" % _selected_fish.display_name
|
||||
fish_name = "the %s" % _selected_fish.display_name
|
||||
_cleanup_attempt("%s got away!" % fish_name, &"escape")
|
||||
|
||||
|
||||
|
|
@ -822,7 +822,7 @@ func _on_outcome_completed(outcome: StringName) -> void:
|
|||
_pending_catch.weight_lb,
|
||||
true
|
||||
)
|
||||
status_changed.emit("Left click or Escape to put away")
|
||||
status_changed.emit("left click or escape to put away")
|
||||
|
||||
|
||||
func _put_away_catch() -> void:
|
||||
|
|
@ -846,7 +846,7 @@ func _put_away_catch() -> void:
|
|||
_active_player.end_catch_showcase(
|
||||
_finish_showcase_put_away.bind(
|
||||
restore_generation,
|
||||
"Caught %s!" % caught_name
|
||||
"caught %s!" % caught_name
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -926,7 +926,7 @@ func _return_to_ready() -> void:
|
|||
|
||||
|
||||
func _cancel_attempt() -> void:
|
||||
_cleanup_attempt("Fishing cancelled.", &"cancel")
|
||||
_cleanup_attempt("fishing cancelled.", &"cancel")
|
||||
|
||||
|
||||
func _cancel_from_withdrawal() -> void:
|
||||
|
|
|
|||
|
|
@ -95,18 +95,40 @@ func contains_catch_id(catch_id: StringName) -> bool:
|
|||
func remove_catch_by_id(catch_id: StringName) -> FishCatchType:
|
||||
if catch_id.is_empty():
|
||||
return null
|
||||
for index: int in range(_catches.size()):
|
||||
var fish_catch: FishCatchType = _catches[index]
|
||||
if fish_catch == null or fish_catch.catch_id != catch_id:
|
||||
continue
|
||||
_catches.remove_at(index)
|
||||
contents_changed.emit(
|
||||
fish_catch.fish_id,
|
||||
get_count(fish_catch.fish_id)
|
||||
)
|
||||
var removed: Array[FishCatchType] = remove_catches_by_ids([catch_id])
|
||||
return removed.front() if removed.size() == 1 else null
|
||||
|
||||
|
||||
func remove_catches_by_ids(
|
||||
catch_ids: Array[StringName],
|
||||
) -> Array[FishCatchType]:
|
||||
var removed: Array[FishCatchType] = []
|
||||
if catch_ids.is_empty():
|
||||
return removed
|
||||
var requested_ids: Dictionary[StringName, bool] = {}
|
||||
for catch_id: StringName in catch_ids:
|
||||
if catch_id.is_empty() or requested_ids.has(catch_id):
|
||||
return removed
|
||||
requested_ids[catch_id] = true
|
||||
for catch_id: StringName in catch_ids:
|
||||
var fish_catch: FishCatchType = get_catch_by_id(catch_id)
|
||||
if fish_catch == null:
|
||||
removed.clear()
|
||||
return removed
|
||||
removed.append(fish_catch)
|
||||
|
||||
var remaining: Array[FishCatchType] = []
|
||||
var affected_species: Dictionary[StringName, bool] = {}
|
||||
for fish_catch: FishCatchType in _catches:
|
||||
if requested_ids.has(fish_catch.catch_id):
|
||||
affected_species[fish_catch.fish_id] = true
|
||||
else:
|
||||
remaining.append(fish_catch)
|
||||
_catches = remaining
|
||||
for fish_id: StringName in affected_species:
|
||||
contents_changed.emit(fish_id, get_count(fish_id))
|
||||
catches_changed.emit()
|
||||
return fish_catch
|
||||
return null
|
||||
return removed
|
||||
|
||||
|
||||
func set_catch_favorited(
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
item_id = &"basic_fishing_rod"
|
||||
display_name = "Basic Fishing Rod"
|
||||
description = "A dependable starter rod. Select it to cast."
|
||||
display_name = "basic fishing rod"
|
||||
description = "a dependable starter rod. select it to cast."
|
||||
category = 0
|
||||
icon = ExtResource("2_icon")
|
||||
stackable = false
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"coffee"
|
||||
display_name = "Coffee"
|
||||
description = "Temporarily increases ground movement speed."
|
||||
display_name = "coffee"
|
||||
description = "temporarily increases ground movement speed."
|
||||
category = 3
|
||||
icon = ExtResource("2")
|
||||
stackable = true
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"cooler_expansion"
|
||||
display_name = "Cooler Expansion"
|
||||
description = "Permanently increases the number of fish the Cooler can hold."
|
||||
display_name = "cooler expansion"
|
||||
description = "permanently increases the number of fish the cooler can hold."
|
||||
category = 4
|
||||
icon = ExtResource("2")
|
||||
stackable = false
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"energy_drink"
|
||||
display_name = "Energy Drink"
|
||||
description = "Temporarily increases reeling progress."
|
||||
display_name = "energy drink"
|
||||
description = "temporarily increases reeling progress."
|
||||
category = 3
|
||||
icon = ExtResource("2")
|
||||
stackable = true
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"fish_finder"
|
||||
display_name = "Fish Finder"
|
||||
description = "Temporarily shortens bite waits and improves rarity weights."
|
||||
display_name = "fish finder"
|
||||
description = "temporarily shortens bite waits and improves rarity weights."
|
||||
category = 3
|
||||
icon = ExtResource("2")
|
||||
stackable = true
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"snack"
|
||||
display_name = "Snack"
|
||||
description = "Temporarily adds one barrier damage."
|
||||
display_name = "snack"
|
||||
description = "temporarily adds one barrier damage."
|
||||
category = 3
|
||||
icon = ExtResource("2")
|
||||
stackable = true
|
||||
|
|
|
|||
|
|
@ -33,4 +33,4 @@ func is_valid() -> bool:
|
|||
|
||||
|
||||
func get_category_name() -> String:
|
||||
return Category.keys()[category].capitalize()
|
||||
return Category.keys()[category].to_lower()
|
||||
|
|
|
|||
125
main/main.gd
|
|
@ -25,10 +25,16 @@ const FishingShopInteractionType = preload(
|
|||
"res://world/fishing_shop_interaction.gd"
|
||||
)
|
||||
|
||||
const TITLE_MUSIC_SILENCE_DB: float = -80.0
|
||||
|
||||
@export var fish_catalog: FishPoolType
|
||||
@export var pelican_buyer_profile: FishBuyerProfileType
|
||||
@export var main_shop_buyer_profile: FishBuyerProfileType
|
||||
@export var item_catalog: ItemCatalogType
|
||||
@export_category("Title Music")
|
||||
@export_range(-40.0, 0.0, 0.5) var title_music_volume_db: float = -14.0
|
||||
@export_range(0.0, 5.0, 0.05) var title_music_fade_in_seconds: float = 1.0
|
||||
@export_range(0.0, 5.0, 0.05) var title_music_fade_out_seconds: float = 0.75
|
||||
|
||||
@onready var _test_world: TestWorldType = $TestWorld
|
||||
@onready var _player: PlayerType = %Player
|
||||
|
|
@ -37,9 +43,14 @@ const FishingShopInteractionType = preload(
|
|||
@onready var _water_recovery: WaterRecoveryControllerType = %WaterRecovery
|
||||
@onready var _save_manager: PlayerSaveManagerType = %PlayerSaveManager
|
||||
@onready var _settings_manager: PlayerSettingsManagerType = %PlayerSettingsManager
|
||||
@onready var _title_music: AudioStreamPlayer = %TitleMusic
|
||||
|
||||
var _gameplay_started: bool = false
|
||||
var _shop_interaction: FishingShopInteractionType
|
||||
var _title_music_tween: Tween
|
||||
var _title_music_transition_generation: int = 0
|
||||
var _title_music_requested: bool = false
|
||||
var _quit_in_progress: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -144,6 +155,7 @@ func _ready() -> void:
|
|||
_player.hotbar.get_selected_slot(),
|
||||
_player.hotbar.get_selected_item_id()
|
||||
)
|
||||
_show_title_music()
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
|
|
@ -226,20 +238,26 @@ func _set_gameplay_active(active: bool) -> void:
|
|||
|
||||
|
||||
func _on_gameplay_requested() -> void:
|
||||
if _gameplay_started:
|
||||
if _gameplay_started or _quit_in_progress:
|
||||
return
|
||||
_fade_out_title_music()
|
||||
_game_ui.get_title_screen().hide()
|
||||
_set_gameplay_active(true)
|
||||
|
||||
|
||||
func _on_return_to_title_requested() -> void:
|
||||
if _quit_in_progress:
|
||||
return
|
||||
var pause_menu: PauseMenuType = _game_ui.get_pause_menu()
|
||||
pause_menu.close_for_title_transition()
|
||||
_set_gameplay_active(false)
|
||||
_game_ui.get_title_screen().reopen()
|
||||
_show_title_music()
|
||||
|
||||
|
||||
func _on_reset_progress_requested() -> void:
|
||||
if _quit_in_progress:
|
||||
return
|
||||
var pause_menu: PauseMenuType = _game_ui.get_pause_menu()
|
||||
if not _save_manager.delete_progression_save():
|
||||
pause_menu.report_reset_failure()
|
||||
|
|
@ -250,6 +268,7 @@ func _on_reset_progress_requested() -> void:
|
|||
pause_menu.close_for_title_transition()
|
||||
_set_gameplay_active(false)
|
||||
_game_ui.get_title_screen().reopen()
|
||||
_show_title_music()
|
||||
|
||||
|
||||
func _on_water_recovery_starting() -> void:
|
||||
|
|
@ -282,12 +301,116 @@ func _refresh_active_hotbar_item() -> void:
|
|||
|
||||
|
||||
func _on_quit_requested() -> void:
|
||||
if _quit_in_progress:
|
||||
return
|
||||
_quit_in_progress = true
|
||||
_settings_manager.save_if_dirty()
|
||||
if _gameplay_started:
|
||||
_save_manager.save_if_dirty()
|
||||
_fade_out_title_music(_finish_quit)
|
||||
|
||||
|
||||
func _show_title_music() -> void:
|
||||
_title_music_requested = true
|
||||
var generation: int = _replace_title_music_transition()
|
||||
if not _title_music.playing:
|
||||
_title_music.volume_db = TITLE_MUSIC_SILENCE_DB
|
||||
_title_music.play()
|
||||
if is_equal_approx(_title_music.volume_db, title_music_volume_db):
|
||||
return
|
||||
if title_music_fade_in_seconds <= 0.0:
|
||||
_title_music.volume_db = title_music_volume_db
|
||||
return
|
||||
_title_music_tween = create_tween()
|
||||
_title_music_tween.set_trans(Tween.TRANS_CUBIC)
|
||||
_title_music_tween.set_ease(Tween.EASE_OUT)
|
||||
_title_music_tween.tween_property(
|
||||
_title_music,
|
||||
"volume_db",
|
||||
title_music_volume_db,
|
||||
title_music_fade_in_seconds
|
||||
)
|
||||
_title_music_tween.finished.connect(
|
||||
_on_title_music_faded_in.bind(generation),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
|
||||
|
||||
func _fade_out_title_music(on_complete: Callable = Callable()) -> void:
|
||||
_title_music_requested = false
|
||||
var generation: int = _replace_title_music_transition()
|
||||
if not _title_music.playing:
|
||||
_title_music.volume_db = title_music_volume_db
|
||||
if on_complete.is_valid():
|
||||
on_complete.call()
|
||||
return
|
||||
if title_music_fade_out_seconds <= 0.0:
|
||||
_complete_title_music_fade_out(generation, on_complete)
|
||||
return
|
||||
_title_music_tween = create_tween()
|
||||
_title_music_tween.set_trans(Tween.TRANS_CUBIC)
|
||||
_title_music_tween.set_ease(Tween.EASE_IN)
|
||||
_title_music_tween.tween_property(
|
||||
_title_music,
|
||||
"volume_db",
|
||||
TITLE_MUSIC_SILENCE_DB,
|
||||
title_music_fade_out_seconds
|
||||
)
|
||||
_title_music_tween.finished.connect(
|
||||
_complete_title_music_fade_out.bind(generation, on_complete),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
|
||||
|
||||
func _replace_title_music_transition() -> int:
|
||||
_title_music_transition_generation += 1
|
||||
if _title_music_tween != null:
|
||||
_title_music_tween.kill()
|
||||
_title_music_tween = null
|
||||
return _title_music_transition_generation
|
||||
|
||||
|
||||
func _on_title_music_faded_in(generation: int) -> void:
|
||||
if (
|
||||
generation != _title_music_transition_generation
|
||||
or not _title_music_requested
|
||||
):
|
||||
return
|
||||
_title_music_tween = null
|
||||
_title_music.volume_db = title_music_volume_db
|
||||
|
||||
|
||||
func _complete_title_music_fade_out(
|
||||
generation: int,
|
||||
on_complete: Callable,
|
||||
) -> void:
|
||||
if (
|
||||
generation != _title_music_transition_generation
|
||||
or _title_music_requested
|
||||
):
|
||||
return
|
||||
_title_music_tween = null
|
||||
_title_music.stop()
|
||||
_title_music.volume_db = title_music_volume_db
|
||||
if on_complete.is_valid():
|
||||
on_complete.call()
|
||||
|
||||
|
||||
func _finish_quit() -> void:
|
||||
_title_music.queue_free()
|
||||
await get_tree().process_frame
|
||||
get_tree().quit()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if _title_music_tween != null:
|
||||
_title_music_tween.kill()
|
||||
_title_music_tween = null
|
||||
if is_instance_valid(_title_music):
|
||||
_title_music.stop()
|
||||
_title_music.stream = null
|
||||
|
||||
|
||||
func _on_shop_range_changed(in_range: bool) -> void:
|
||||
if not in_range:
|
||||
_game_ui.get_fishing_shop().close_for_range_exit()
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=13 format=3]
|
||||
[gd_scene load_steps=14 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://world/test_world.tscn" id="1_world"]
|
||||
[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"]
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
[ext_resource type="Script" path="res://settings/player_settings_manager.gd" id="10_settings"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/item_catalog.tres" id="11_items"]
|
||||
[ext_resource type="Resource" path="res://economy/buyers/main_fishing_shop.tres" id="12_main_shop"]
|
||||
[ext_resource type="AudioStream" path="res://audio/music/title/as_in_four_wolves.ogg" id="13_title_music"]
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
script = ExtResource("3_main")
|
||||
|
|
@ -44,5 +45,11 @@ script = ExtResource("9_save")
|
|||
unique_name_in_owner = true
|
||||
script = ExtResource("10_settings")
|
||||
|
||||
[node name="TitleMusic" type="AudioStreamPlayer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
stream = ExtResource("13_title_music")
|
||||
volume_db = -80.0
|
||||
bus = &"Music"
|
||||
|
||||
[node name="GameUI" parent="." instance=ExtResource("5_ui")]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class ShowcaseCameraSnapshot:
|
|||
@export_range(-80.0, 80.0, 1.0) var showcase_camera_pitch: float = -8.0
|
||||
@export_range(1.0, 10.0, 0.1) var showcase_camera_zoom_distance: float = 3.6
|
||||
@export_range(0.5, 3.0, 0.05) var showcase_camera_target_height: float = 1.45
|
||||
@export_range(0.01, 1.0, 0.01) var catch_presentation_base_scale: float = 0.10
|
||||
|
||||
@export_category("Camera")
|
||||
@export var mouse_sensitivity: float = 0.005
|
||||
|
|
@ -369,7 +370,11 @@ func begin_catch_showcase(fish_catch: FishCatchType) -> void:
|
|||
_showcase_rod_state_stored = true
|
||||
_fishing_rod.visible = false
|
||||
_catch_sprite.texture = fish_catch.fish.display_texture
|
||||
_catch_display.scale = Vector3.ONE * fish_catch.display_scale
|
||||
_catch_display.scale = (
|
||||
Vector3.ONE
|
||||
* fish_catch.display_scale
|
||||
* catch_presentation_base_scale
|
||||
)
|
||||
_catch_display.visible = _catch_sprite.texture != null
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -117,13 +117,13 @@ func get_rarity_weight_multiplier(rarity: int) -> float:
|
|||
func get_feedback(item_id: StringName) -> String:
|
||||
match item_id:
|
||||
COFFEE_ID:
|
||||
return "Coffee active: movement speed increased."
|
||||
return "coffee active: movement speed increased."
|
||||
ENERGY_DRINK_ID:
|
||||
return "Energy Drink active: reeling speed increased."
|
||||
return "energy drink active: reeling speed increased."
|
||||
SNACK_ID:
|
||||
return "Snack active: barrier damage increased."
|
||||
return "snack active: barrier damage increased."
|
||||
FISH_FINDER_ID:
|
||||
return "Fish Finder active: bites and uncommon catches improved."
|
||||
return "fish finder active: bites and uncommon catches improved."
|
||||
_:
|
||||
return ""
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ config/icon="res://icon.svg"
|
|||
window/stretch/mode="canvas_items"
|
||||
window/stretch/aspect="expand"
|
||||
|
||||
[gui]
|
||||
|
||||
theme/custom="res://ui/game_theme.tres"
|
||||
|
||||
[input]
|
||||
|
||||
move_forward={
|
||||
|
|
|
|||
|
|
@ -227,46 +227,46 @@ func inspect_save() -> SaveInspectionType:
|
|||
result.has_primary_file = FileAccess.file_exists(SAVE_PATH)
|
||||
if not result.has_primary_file:
|
||||
result.status = SaveInspectionType.Status.MISSING
|
||||
result.message = "No save found."
|
||||
result.message = "no save found."
|
||||
return result
|
||||
var save_file := FileAccess.open(SAVE_PATH, FileAccess.READ)
|
||||
if save_file == null:
|
||||
result.status = SaveInspectionType.Status.IO_ERROR
|
||||
result.message = "The save could not be read."
|
||||
result.message = "the save could not be read."
|
||||
return result
|
||||
var json := JSON.new()
|
||||
var parse_error: Error = json.parse(save_file.get_as_text())
|
||||
save_file.close()
|
||||
if parse_error != OK or typeof(json.data) != TYPE_DICTIONARY:
|
||||
result.status = SaveInspectionType.Status.MALFORMED
|
||||
result.message = "The save is corrupt and was preserved."
|
||||
result.message = "the save is corrupt and was preserved."
|
||||
return result
|
||||
var save_data: Dictionary = json.data
|
||||
result.detected_version = _read_integer(save_data.get("save_version"), -1)
|
||||
if result.detected_version > SAVE_VERSION:
|
||||
result.status = SaveInspectionType.Status.UNSUPPORTED_VERSION
|
||||
result.message = "This save belongs to a newer game version."
|
||||
result.message = "this save belongs to a newer game version."
|
||||
return result
|
||||
if result.detected_version < 1:
|
||||
result.status = SaveInspectionType.Status.MALFORMED
|
||||
result.message = "The save version is unsupported."
|
||||
result.message = "the save version is unsupported."
|
||||
return result
|
||||
if result.detected_version != SAVE_VERSION:
|
||||
save_data = _migrate_save(save_data, result.detected_version)
|
||||
if save_data.is_empty():
|
||||
result.status = SaveInspectionType.Status.MALFORMED
|
||||
result.message = "The save version is unsupported."
|
||||
result.message = "the save version is unsupported."
|
||||
return result
|
||||
var snapshot: LoadSnapshot = _build_load_snapshot(save_data)
|
||||
if snapshot == null:
|
||||
result.status = SaveInspectionType.Status.MALFORMED
|
||||
result.message = "The save is structurally invalid and was preserved."
|
||||
result.message = "the save is structurally invalid and was preserved."
|
||||
return result
|
||||
result.status = SaveInspectionType.Status.VALID_SUPPORTED
|
||||
result.catch_count = snapshot.catches.size()
|
||||
result.wallet_balance = snapshot.wallet_balance
|
||||
result.discovered_species_count = snapshot.discovered_ids.size()
|
||||
result.message = "Save ready."
|
||||
result.message = "save ready."
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
BIN
ui/assets/title/bubbles/bubble1.png
Normal file
|
After Width: | Height: | Size: 372 B |
40
ui/assets/title/bubbles/bubble1.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dkvilbotou8bp"
|
||||
path="res://.godot/imported/bubble1.png-c853cfc8f5bc5b89bb69a71c23de669d.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui/assets/title/bubbles/bubble1.png"
|
||||
dest_files=["res://.godot/imported/bubble1.png-c853cfc8f5bc5b89bb69a71c23de669d.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
|
||||
BIN
ui/assets/title/bubbles/bubble2.png
Normal file
|
After Width: | Height: | Size: 357 B |
40
ui/assets/title/bubbles/bubble2.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://dh11c1271egs7"
|
||||
path="res://.godot/imported/bubble2.png-3fe11ab91543c1b1505cfc1100bfd0c7.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui/assets/title/bubbles/bubble2.png"
|
||||
dest_files=["res://.godot/imported/bubble2.png-3fe11ab91543c1b1505cfc1100bfd0c7.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
|
||||
BIN
ui/assets/title/bubbles/bubble3.png
Normal file
|
After Width: | Height: | Size: 360 B |
40
ui/assets/title/bubbles/bubble3.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://b5praw1x4vukl"
|
||||
path="res://.godot/imported/bubble3.png-7e3e34f881bccf21cec803168829b93e.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui/assets/title/bubbles/bubble3.png"
|
||||
dest_files=["res://.godot/imported/bubble3.png-7e3e34f881bccf21cec803168829b93e.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
|
||||
BIN
ui/assets/title/netfishing_logo.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
40
ui/assets/title/netfishing_logo.png.import
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[remap]
|
||||
|
||||
importer="texture"
|
||||
type="CompressedTexture2D"
|
||||
uid="uid://ckqayis1ckb2v"
|
||||
path="res://.godot/imported/netfishing_logo.png-82ab9fce5851dcc5239eca178e8cde99.ctex"
|
||||
metadata={
|
||||
"vram_texture": false
|
||||
}
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui/assets/title/netfishing_logo.png"
|
||||
dest_files=["res://.godot/imported/netfishing_logo.png-82ab9fce5851dcc5239eca178e8cde99.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
|
||||
106
ui/fish_batch_selection.gd
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
class_name FishBatchSelection
|
||||
extends RefCounted
|
||||
|
||||
var _selected_ids: Dictionary[StringName, bool] = {}
|
||||
var _visible_ids: Array[StringName] = []
|
||||
var _focused_id: StringName
|
||||
var _anchor_id: StringName
|
||||
|
||||
|
||||
func set_visible_order(visible_ids: Array[StringName]) -> void:
|
||||
_visible_ids = visible_ids.duplicate()
|
||||
var visible_set: Dictionary[StringName, bool] = {}
|
||||
for catch_id: StringName in _visible_ids:
|
||||
if not catch_id.is_empty():
|
||||
visible_set[catch_id] = true
|
||||
for catch_id: StringName in _selected_ids.keys():
|
||||
if not visible_set.has(catch_id):
|
||||
_selected_ids.erase(catch_id)
|
||||
if not visible_set.has(_focused_id):
|
||||
_focused_id = StringName()
|
||||
if not visible_set.has(_anchor_id):
|
||||
_anchor_id = StringName()
|
||||
|
||||
|
||||
func apply_click(
|
||||
catch_id: StringName,
|
||||
ctrl_pressed: bool,
|
||||
shift_pressed: bool,
|
||||
) -> void:
|
||||
if catch_id.is_empty() or not _visible_ids.has(catch_id):
|
||||
return
|
||||
if not shift_pressed or _anchor_id.is_empty():
|
||||
if ctrl_pressed:
|
||||
if _selected_ids.has(catch_id):
|
||||
_selected_ids.erase(catch_id)
|
||||
else:
|
||||
_selected_ids[catch_id] = true
|
||||
else:
|
||||
_selected_ids.clear()
|
||||
_selected_ids[catch_id] = true
|
||||
_focused_id = catch_id
|
||||
_anchor_id = catch_id
|
||||
return
|
||||
|
||||
var anchor_index: int = _visible_ids.find(_anchor_id)
|
||||
var target_index: int = _visible_ids.find(catch_id)
|
||||
if anchor_index < 0 or target_index < 0:
|
||||
select_only(catch_id)
|
||||
return
|
||||
if not ctrl_pressed:
|
||||
_selected_ids.clear()
|
||||
var first_index: int = mini(anchor_index, target_index)
|
||||
var last_index: int = maxi(anchor_index, target_index)
|
||||
for index: int in range(first_index, last_index + 1):
|
||||
_selected_ids[_visible_ids[index]] = true
|
||||
_focused_id = catch_id
|
||||
|
||||
|
||||
func select_only(catch_id: StringName) -> void:
|
||||
_selected_ids.clear()
|
||||
if catch_id.is_empty() or not _visible_ids.has(catch_id):
|
||||
_focused_id = StringName()
|
||||
_anchor_id = StringName()
|
||||
return
|
||||
_selected_ids[catch_id] = true
|
||||
_focused_id = catch_id
|
||||
_anchor_id = catch_id
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
_selected_ids.clear()
|
||||
_focused_id = StringName()
|
||||
_anchor_id = StringName()
|
||||
|
||||
|
||||
func remove_ids(catch_ids: Array[StringName]) -> void:
|
||||
for catch_id: StringName in catch_ids:
|
||||
_selected_ids.erase(catch_id)
|
||||
if _focused_id == catch_id:
|
||||
_focused_id = StringName()
|
||||
if _anchor_id == catch_id:
|
||||
_anchor_id = StringName()
|
||||
|
||||
|
||||
func get_selected_ids() -> Array[StringName]:
|
||||
var selected_in_order: Array[StringName] = []
|
||||
for catch_id: StringName in _visible_ids:
|
||||
if _selected_ids.has(catch_id):
|
||||
selected_in_order.append(catch_id)
|
||||
return selected_in_order
|
||||
|
||||
|
||||
func is_selected(catch_id: StringName) -> bool:
|
||||
return _selected_ids.has(catch_id)
|
||||
|
||||
|
||||
func get_selected_count() -> int:
|
||||
return _selected_ids.size()
|
||||
|
||||
|
||||
func get_focused_id() -> StringName:
|
||||
return _focused_id
|
||||
|
||||
|
||||
func get_anchor_id() -> StringName:
|
||||
return _anchor_id
|
||||
1
ui/fish_batch_selection.gd.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://ct2140qw4pdug
|
||||
|
|
@ -24,6 +24,9 @@ const PlayerCoolerCapacityType = preload(
|
|||
const ShopInteractionType = preload(
|
||||
"res://world/fishing_shop_interaction.gd"
|
||||
)
|
||||
const FishBatchSelectionType = preload(
|
||||
"res://ui/fish_batch_selection.gd"
|
||||
)
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
|
||||
|
|
@ -42,6 +45,7 @@ enum CloseReason {
|
|||
@onready var _fish_texture: TextureRect = %FishTexture
|
||||
@onready var _fish_name: Label = %FishName
|
||||
@onready var _fish_details: Label = %FishDetails
|
||||
@onready var _selection_summary: Label = %SelectionSummary
|
||||
@onready var _sell_button: Button = %SellButton
|
||||
@onready var _feedback: Label = %Feedback
|
||||
@onready var _reel_level: Label = %ReelLevel
|
||||
|
|
@ -73,14 +77,16 @@ var _interaction: ShopInteractionType
|
|||
var _bag: PlayerBagType
|
||||
var _item_catalog: ItemCatalogType
|
||||
var _cooler_capacity: PlayerCoolerCapacityType
|
||||
var _selected_catch_id: StringName
|
||||
var _confirmation_catch_id: StringName
|
||||
var _fish_selection := FishBatchSelectionType.new()
|
||||
var _confirmation_catch_ids: Array[StringName] = []
|
||||
var _confirmation_generation: int = -1
|
||||
var _prior_movement_enabled: bool = true
|
||||
var _prior_camera_enabled: bool = true
|
||||
var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
||||
var _snapshot_stored: bool = false
|
||||
var _mouse_snapshot_stored: bool = false
|
||||
var _generation: int = 0
|
||||
var _selection_input_generation: int = 0
|
||||
var _transaction_in_progress: bool = false
|
||||
var _closing: bool = false
|
||||
|
||||
|
|
@ -88,6 +94,7 @@ var _closing: bool = false
|
|||
func _ready() -> void:
|
||||
%CloseButton.pressed.connect(close_shop)
|
||||
_fish_list.item_selected.connect(_on_fish_selected)
|
||||
_fish_list.item_clicked.connect(_on_fish_clicked)
|
||||
_sell_button.pressed.connect(_open_sale_confirmation)
|
||||
_confirm_sale.pressed.connect(_on_confirm_sale)
|
||||
_cancel_sale.pressed.connect(_close_sale_confirmation)
|
||||
|
|
@ -120,6 +127,7 @@ func setup(
|
|||
_bag = bag
|
||||
_item_catalog = item_catalog
|
||||
_cooler_capacity = cooler_capacity
|
||||
_fish_selection.clear()
|
||||
if not _inventory.catches_changed.is_connected(_on_inventory_changed):
|
||||
_inventory.catches_changed.connect(_on_inventory_changed)
|
||||
if not _wallet.balance_changed.is_connected(_on_wallet_changed):
|
||||
|
|
@ -189,7 +197,7 @@ func close_shop(
|
|||
_generation += 1
|
||||
_transaction_in_progress = false
|
||||
_close_sale_confirmation()
|
||||
_selected_catch_id = StringName()
|
||||
_fish_selection.clear()
|
||||
hide()
|
||||
get_viewport().gui_release_focus()
|
||||
if _fishing_spot != null and is_instance_valid(_fishing_spot):
|
||||
|
|
@ -242,9 +250,9 @@ func _refresh_all() -> void:
|
|||
|
||||
func _refresh_wallet() -> void:
|
||||
_wallet_label.text = (
|
||||
"Wallet: $%d" % _wallet.get_balance()
|
||||
"wallet: $%d" % _wallet.get_balance()
|
||||
if _wallet != null
|
||||
else "Wallet: $0"
|
||||
else "wallet: $0"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -253,7 +261,7 @@ func _refresh_fish_list() -> void:
|
|||
var catches: Array[FishCatchType] = (
|
||||
_inventory.get_all_catches() if _inventory != null else []
|
||||
)
|
||||
_sales_title.text = "Cooler %d / %d • Full base value" % [
|
||||
_sales_title.text = "cooler %d / %d • full base value" % [
|
||||
catches.size(),
|
||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
||||
]
|
||||
|
|
@ -261,11 +269,21 @@ func _refresh_fish_list() -> void:
|
|||
func(a: FishCatchType, b: FishCatchType) -> bool:
|
||||
return a.catch_sequence > b.catch_sequence
|
||||
)
|
||||
var selected_index: int = -1
|
||||
var visible_ids: Array[StringName] = []
|
||||
for fish_catch: FishCatchType in catches:
|
||||
if fish_catch != null and fish_catch.is_valid():
|
||||
visible_ids.append(fish_catch.catch_id)
|
||||
_fish_selection.set_visible_order(visible_ids)
|
||||
for fish_catch: FishCatchType in catches:
|
||||
if fish_catch == null or not fish_catch.is_valid():
|
||||
continue
|
||||
var marker: String = "★ " if fish_catch.is_favorited else ""
|
||||
var marker: String = ""
|
||||
if _fish_selection.is_selected(fish_catch.catch_id):
|
||||
marker += "✓ "
|
||||
if fish_catch.catch_id == _fish_selection.get_focused_id():
|
||||
marker += "◆ "
|
||||
if fish_catch.is_favorited:
|
||||
marker += "★ "
|
||||
var index: int = _fish_list.add_item(
|
||||
"%s%s — %.2f lb"
|
||||
% [marker, fish_catch.fish.display_name, fish_catch.weight_lb],
|
||||
|
|
@ -277,16 +295,15 @@ func _refresh_fish_list() -> void:
|
|||
"%s • %s"
|
||||
% [
|
||||
fish_catch.fish.get_rarity_name(),
|
||||
"Favorited" if fish_catch.is_favorited else "Available",
|
||||
"favorited" if fish_catch.is_favorited else "available",
|
||||
]
|
||||
)
|
||||
if fish_catch.catch_id == _selected_catch_id:
|
||||
selected_index = index
|
||||
_fish_empty.visible = _fish_list.item_count == 0
|
||||
if selected_index >= 0:
|
||||
_fish_list.select(selected_index)
|
||||
elif not _selected_catch_id.is_empty():
|
||||
_selected_catch_id = StringName()
|
||||
_fish_list.deselect_all()
|
||||
for index: int in range(_fish_list.item_count):
|
||||
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
|
||||
if _fish_selection.is_selected(catch_id):
|
||||
_fish_list.select(index, false)
|
||||
|
||||
|
||||
func _refresh_selected_fish() -> void:
|
||||
|
|
@ -295,27 +312,72 @@ func _refresh_selected_fish() -> void:
|
|||
fish_catch.fish.display_texture if fish_catch != null else null
|
||||
)
|
||||
_fish_name.text = (
|
||||
fish_catch.fish.display_name if fish_catch != null else "Select a fish"
|
||||
fish_catch.fish.display_name if fish_catch != null else "select a fish"
|
||||
)
|
||||
if fish_catch == null:
|
||||
_fish_details.text = "Choose one individual fish from your Cooler."
|
||||
_sell_button.disabled = true
|
||||
_fish_details.text = "choose one individual fish from your cooler."
|
||||
_update_sale_summary()
|
||||
return
|
||||
var offer: int = _buyer.get_offer(fish_catch.sale_value)
|
||||
var individual_preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch([fish_catch.catch_id], _buyer)
|
||||
if _sale_service != null
|
||||
else null
|
||||
)
|
||||
var offer: int = (
|
||||
individual_preview.payout if individual_preview != null else -1
|
||||
)
|
||||
_fish_details.text = (
|
||||
"%.2f lb • %s\nBase value: $%d\nMain-shop offer: $%d%s"
|
||||
"%.2f lb • %s\nbase value: $%d\nmain-shop offer: $%d%s"
|
||||
% [
|
||||
fish_catch.weight_lb,
|
||||
fish_catch.fish.get_rarity_name(),
|
||||
fish_catch.sale_value,
|
||||
offer,
|
||||
"\nFavorited fish cannot be sold."
|
||||
"\nfavorited fish cannot be sold."
|
||||
if fish_catch.is_favorited
|
||||
else "",
|
||||
]
|
||||
)
|
||||
_update_sale_summary()
|
||||
|
||||
|
||||
func _update_sale_summary() -> void:
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
var selected_count: int = selected_ids.size()
|
||||
_sell_button.text = (
|
||||
"sell fish"
|
||||
if selected_count <= 1
|
||||
else "sell %d fish" % selected_count
|
||||
)
|
||||
if selected_count == 0:
|
||||
_selection_summary.text = "no fish selected"
|
||||
_sell_button.disabled = true
|
||||
return
|
||||
var preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch(selected_ids, _buyer)
|
||||
if _sale_service != null
|
||||
else null
|
||||
)
|
||||
_selection_summary.text = (
|
||||
"1 fish selected"
|
||||
if selected_count == 1
|
||||
else "%d fish selected" % selected_count
|
||||
)
|
||||
if (
|
||||
preview != null
|
||||
and (
|
||||
preview.is_success()
|
||||
or preview.status == FishSaleResultType.Status.FAVORITED
|
||||
)
|
||||
):
|
||||
_selection_summary.text += "\ntotal offer: $%d" % preview.payout
|
||||
if preview != null and preview.status == FishSaleResultType.Status.FAVORITED:
|
||||
_selection_summary.text += (
|
||||
"\nfavorited fish must be removed from the selection."
|
||||
)
|
||||
_sell_button.disabled = (
|
||||
fish_catch.is_favorited
|
||||
preview == null
|
||||
or not preview.is_success()
|
||||
or _transaction_in_progress
|
||||
or _closing
|
||||
)
|
||||
|
|
@ -326,7 +388,7 @@ func _refresh_upgrades() -> void:
|
|||
return
|
||||
var reel_level: int = _upgrades.get_reel_speed_level()
|
||||
var reel_cost: int = _upgrades.get_next_reel_speed_cost()
|
||||
_reel_level.text = "Level %d" % reel_level
|
||||
_reel_level.text = "level %d" % reel_level
|
||||
_reel_purchase.disabled = (
|
||||
reel_cost < 0
|
||||
or _transaction_in_progress
|
||||
|
|
@ -335,7 +397,7 @@ func _refresh_upgrades() -> void:
|
|||
)
|
||||
if reel_cost < 0:
|
||||
_reel_effect.text = "%.2f×" % _upgrades.get_reel_speed_multiplier()
|
||||
_reel_cost.text = "MAX"
|
||||
_reel_cost.text = "max"
|
||||
else:
|
||||
_reel_effect.text = (
|
||||
"%.2f× → %.2f×"
|
||||
|
|
@ -345,10 +407,10 @@ func _refresh_upgrades() -> void:
|
|||
]
|
||||
)
|
||||
_reel_cost.text = "$%d" % reel_cost
|
||||
_reel_purchase.text = "MAX" if reel_cost < 0 else "Purchase"
|
||||
_reel_purchase.text = "max" if reel_cost < 0 else "purchase"
|
||||
var barrier_level: int = _upgrades.get_barrier_power_level()
|
||||
var barrier_cost: int = _upgrades.get_next_barrier_power_cost()
|
||||
_barrier_level.text = "Level %d" % barrier_level
|
||||
_barrier_level.text = "level %d" % barrier_level
|
||||
_barrier_purchase.disabled = (
|
||||
barrier_cost < 0
|
||||
or _transaction_in_progress
|
||||
|
|
@ -357,7 +419,7 @@ func _refresh_upgrades() -> void:
|
|||
)
|
||||
if barrier_cost < 0:
|
||||
_barrier_effect.text = "%d damage" % _upgrades.get_barrier_damage()
|
||||
_barrier_cost.text = "MAX"
|
||||
_barrier_cost.text = "max"
|
||||
else:
|
||||
_barrier_effect.text = (
|
||||
"%d damage → %d damage"
|
||||
|
|
@ -367,7 +429,7 @@ func _refresh_upgrades() -> void:
|
|||
]
|
||||
)
|
||||
_barrier_cost.text = "$%d" % barrier_cost
|
||||
_barrier_purchase.text = "MAX" if barrier_cost < 0 else "Purchase"
|
||||
_barrier_purchase.text = "max" if barrier_cost < 0 else "purchase"
|
||||
|
||||
|
||||
func _refresh_supplies() -> void:
|
||||
|
|
@ -385,7 +447,7 @@ func _refresh_supplies() -> void:
|
|||
button.icon = item.icon
|
||||
button.expand_icon = true
|
||||
button.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
button.text = "%s\n$%d • Owned %d" % [
|
||||
button.text = "%s\n$%d • owned %d" % [
|
||||
item.display_name,
|
||||
FishingShopStockType.get_price(item_id),
|
||||
_bag.get_quantity(item_id),
|
||||
|
|
@ -409,7 +471,7 @@ func _refresh_cooler_capacity() -> void:
|
|||
var level: int = _cooler_capacity.get_level()
|
||||
var cost: int = _cooler_capacity.get_next_cost()
|
||||
var next_capacity: int = _cooler_capacity.get_next_capacity()
|
||||
_cooler_level.text = "Level %d" % level
|
||||
_cooler_level.text = "level %d" % level
|
||||
_cooler_purchase.disabled = (
|
||||
cost < 0
|
||||
or _transaction_in_progress
|
||||
|
|
@ -418,46 +480,99 @@ func _refresh_cooler_capacity() -> void:
|
|||
)
|
||||
if cost < 0:
|
||||
_cooler_effect.text = "%d fish" % _cooler_capacity.get_capacity()
|
||||
_cooler_cost.text = "MAX"
|
||||
_cooler_purchase.text = "MAX"
|
||||
_cooler_cost.text = "max"
|
||||
_cooler_purchase.text = "max"
|
||||
else:
|
||||
_cooler_effect.text = "%d → %d fish" % [
|
||||
_cooler_capacity.get_capacity(),
|
||||
next_capacity,
|
||||
]
|
||||
_cooler_cost.text = "$%d" % cost
|
||||
_cooler_purchase.text = "Purchase"
|
||||
_cooler_purchase.text = "purchase"
|
||||
|
||||
|
||||
func _on_fish_selected(index: int) -> void:
|
||||
if index < 0 or index >= _fish_list.item_count:
|
||||
return
|
||||
_selected_catch_id = StringName(str(_fish_list.get_item_metadata(index)))
|
||||
var input_generation: int = _selection_input_generation
|
||||
call_deferred(
|
||||
"_apply_keyboard_fish_selection",
|
||||
index,
|
||||
input_generation
|
||||
)
|
||||
|
||||
|
||||
func _apply_keyboard_fish_selection(
|
||||
index: int,
|
||||
input_generation: int,
|
||||
) -> void:
|
||||
if (
|
||||
input_generation != _selection_input_generation
|
||||
or index < 0
|
||||
or index >= _fish_list.item_count
|
||||
):
|
||||
return
|
||||
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
|
||||
_fish_selection.select_only(catch_id)
|
||||
_feedback.text = ""
|
||||
_refresh_fish_list()
|
||||
_refresh_selected_fish()
|
||||
|
||||
|
||||
func _on_fish_clicked(
|
||||
index: int,
|
||||
_position: Vector2,
|
||||
mouse_button_index: int,
|
||||
) -> void:
|
||||
if (
|
||||
mouse_button_index != MOUSE_BUTTON_LEFT
|
||||
or index < 0
|
||||
or index >= _fish_list.item_count
|
||||
):
|
||||
return
|
||||
_selection_input_generation += 1
|
||||
var catch_id := StringName(str(_fish_list.get_item_metadata(index)))
|
||||
_fish_selection.apply_click(
|
||||
catch_id,
|
||||
Input.is_key_pressed(KEY_CTRL),
|
||||
Input.is_key_pressed(KEY_SHIFT)
|
||||
)
|
||||
_feedback.text = ""
|
||||
_refresh_fish_list()
|
||||
_refresh_selected_fish()
|
||||
|
||||
|
||||
func _open_sale_confirmation() -> void:
|
||||
var fish_catch: FishCatchType = _get_selected_catch()
|
||||
if not _is_transaction_context_valid() or fish_catch == null:
|
||||
_feedback.text = "This fish is no longer available."
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
if not _is_transaction_context_valid() or selected_ids.is_empty():
|
||||
_feedback.text = "the fish selection is no longer available."
|
||||
_refresh_all()
|
||||
return
|
||||
if fish_catch.is_favorited:
|
||||
_feedback.text = "Favorited fish cannot be sold."
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
selected_ids,
|
||||
_buyer
|
||||
)
|
||||
if not preview.is_success():
|
||||
_feedback.text = (
|
||||
"favorited fish cannot be sold. "
|
||||
+ "remove them from the selection first."
|
||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||
else preview.get_message()
|
||||
)
|
||||
return
|
||||
_confirmation_catch_id = fish_catch.catch_id
|
||||
var offer: int = _buyer.get_offer(fish_catch.sale_value)
|
||||
_confirmation_catch_ids = selected_ids.duplicate()
|
||||
_confirmation_generation = _generation
|
||||
_confirmation_text.text = (
|
||||
"Sell this %.2f lb %s to the Fishing Shop for $%d?"
|
||||
% [fish_catch.weight_lb, fish_catch.fish.display_name, offer]
|
||||
"sell %d fish to the fishing shop for $%d?"
|
||||
% [preview.fish_count, preview.payout]
|
||||
)
|
||||
_confirmation.show()
|
||||
_cancel_sale.grab_focus()
|
||||
|
||||
|
||||
func _close_sale_confirmation() -> void:
|
||||
_confirmation_catch_id = StringName()
|
||||
_confirmation_catch_ids.clear()
|
||||
_confirmation_generation = -1
|
||||
_confirmation.hide()
|
||||
|
||||
|
||||
|
|
@ -465,18 +580,23 @@ func _on_confirm_sale() -> void:
|
|||
if _transaction_in_progress:
|
||||
return
|
||||
var transaction_generation: int = _generation
|
||||
var catch_id: StringName = _confirmation_catch_id
|
||||
var catch_ids: Array[StringName] = _confirmation_catch_ids.duplicate()
|
||||
var confirmation_generation: int = _confirmation_generation
|
||||
_close_sale_confirmation()
|
||||
if (
|
||||
catch_id.is_empty()
|
||||
catch_ids.is_empty()
|
||||
or not _is_transaction_context_valid()
|
||||
or transaction_generation != _generation
|
||||
or confirmation_generation != transaction_generation
|
||||
or _buyer.id != MAIN_SHOP_BUYER_ID
|
||||
):
|
||||
_feedback.text = "Unable to complete sale."
|
||||
_feedback.text = "unable to complete sale."
|
||||
return
|
||||
_transaction_in_progress = true
|
||||
var result: FishSaleResultType = _sale_service.sell(catch_id, _buyer)
|
||||
var result: FishSaleResultType = _sale_service.sell_batch(
|
||||
catch_ids,
|
||||
_buyer
|
||||
)
|
||||
_transaction_in_progress = false
|
||||
if (
|
||||
transaction_generation != _generation
|
||||
|
|
@ -484,8 +604,15 @@ func _on_confirm_sale() -> void:
|
|||
):
|
||||
return
|
||||
if result.is_success():
|
||||
_feedback.text = "Fish sold for $%d." % result.payout
|
||||
_selected_catch_id = StringName()
|
||||
_feedback.text = (
|
||||
"fish sold for $%d."
|
||||
if result.fish_count == 1
|
||||
else "%d fish sold for $%d." % [
|
||||
result.fish_count,
|
||||
result.payout,
|
||||
]
|
||||
)
|
||||
_fish_selection.remove_ids(catch_ids)
|
||||
else:
|
||||
_feedback.text = result.get_message()
|
||||
_refresh_all()
|
||||
|
|
@ -501,14 +628,14 @@ func _purchase_barrier_power() -> void:
|
|||
|
||||
func _purchase_supply(item_id: StringName) -> void:
|
||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||
_feedback.text = "Unable to complete purchase."
|
||||
_feedback.text = "unable to complete purchase."
|
||||
return
|
||||
var price: int = FishingShopStockType.get_price(item_id)
|
||||
if not _bag.can_add_item(item_id, 1):
|
||||
_feedback.text = "Bag cannot accept this item."
|
||||
_feedback.text = "bag cannot accept this item."
|
||||
return
|
||||
if not _wallet.can_afford(price):
|
||||
_feedback.text = "Not enough money."
|
||||
_feedback.text = "not enough money."
|
||||
return
|
||||
var transaction_generation: int = _generation
|
||||
_transaction_in_progress = true
|
||||
|
|
@ -522,21 +649,21 @@ func _purchase_supply(item_id: StringName) -> void:
|
|||
if transaction_generation != _generation or not visible:
|
||||
return
|
||||
_feedback.text = (
|
||||
"Item purchased." if purchased else "Unable to complete purchase."
|
||||
"item purchased." if purchased else "unable to complete purchase."
|
||||
)
|
||||
_refresh_all()
|
||||
|
||||
|
||||
func _purchase_cooler_capacity() -> void:
|
||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||
_feedback.text = "Unable to complete purchase."
|
||||
_feedback.text = "unable to complete purchase."
|
||||
return
|
||||
var cost: int = _cooler_capacity.get_next_cost()
|
||||
if cost < 0:
|
||||
_feedback.text = "Maximum level reached."
|
||||
_feedback.text = "maximum level reached."
|
||||
return
|
||||
if not _wallet.can_afford(cost):
|
||||
_feedback.text = "Not enough money."
|
||||
_feedback.text = "not enough money."
|
||||
return
|
||||
var transaction_generation: int = _generation
|
||||
_transaction_in_progress = true
|
||||
|
|
@ -545,16 +672,16 @@ func _purchase_cooler_capacity() -> void:
|
|||
if transaction_generation != _generation or not visible:
|
||||
return
|
||||
_feedback.text = (
|
||||
"Upgrade purchased."
|
||||
"upgrade purchased."
|
||||
if purchased
|
||||
else "Unable to complete purchase."
|
||||
else "unable to complete purchase."
|
||||
)
|
||||
_refresh_all()
|
||||
|
||||
|
||||
func _purchase_upgrade(is_reel_speed: bool) -> void:
|
||||
if _transaction_in_progress or not _is_transaction_context_valid():
|
||||
_feedback.text = "Unable to complete purchase."
|
||||
_feedback.text = "unable to complete purchase."
|
||||
return
|
||||
var cost: int = (
|
||||
_upgrades.get_next_reel_speed_cost()
|
||||
|
|
@ -562,10 +689,10 @@ func _purchase_upgrade(is_reel_speed: bool) -> void:
|
|||
else _upgrades.get_next_barrier_power_cost()
|
||||
)
|
||||
if cost < 0:
|
||||
_feedback.text = "Maximum level reached."
|
||||
_feedback.text = "maximum level reached."
|
||||
return
|
||||
if not _wallet.can_afford(cost):
|
||||
_feedback.text = "Not enough money."
|
||||
_feedback.text = "not enough money."
|
||||
return
|
||||
var transaction_generation: int = _generation
|
||||
_transaction_in_progress = true
|
||||
|
|
@ -578,17 +705,18 @@ func _purchase_upgrade(is_reel_speed: bool) -> void:
|
|||
if transaction_generation != _generation or not visible:
|
||||
return
|
||||
_feedback.text = (
|
||||
"Upgrade purchased."
|
||||
"upgrade purchased."
|
||||
if purchased
|
||||
else "Unable to complete purchase."
|
||||
else "unable to complete purchase."
|
||||
)
|
||||
_refresh_all()
|
||||
|
||||
|
||||
func _get_selected_catch() -> FishCatchType:
|
||||
if _inventory == null or _selected_catch_id.is_empty():
|
||||
var focused_id: StringName = _fish_selection.get_focused_id()
|
||||
if _inventory == null or focused_id.is_empty():
|
||||
return null
|
||||
return _inventory.get_catch_by_id(_selected_catch_id)
|
||||
return _inventory.get_catch_by_id(focused_id)
|
||||
|
||||
|
||||
func _is_transaction_context_valid() -> bool:
|
||||
|
|
@ -607,12 +735,22 @@ func _is_transaction_context_valid() -> bool:
|
|||
|
||||
func _on_inventory_changed() -> void:
|
||||
_refresh_fish_list()
|
||||
if not _confirmation_catch_ids.is_empty():
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
_confirmation_catch_ids,
|
||||
_buyer
|
||||
)
|
||||
if (
|
||||
not _confirmation_catch_id.is_empty()
|
||||
and _inventory.get_catch_by_id(_confirmation_catch_id) == null
|
||||
_confirmation_generation != _generation
|
||||
or not preview.is_success()
|
||||
):
|
||||
_close_sale_confirmation()
|
||||
_feedback.text = "This fish is no longer available."
|
||||
_feedback.text = (
|
||||
"favorited fish cannot be sold. "
|
||||
+ "remove them from the selection first."
|
||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||
else preview.get_message()
|
||||
)
|
||||
_refresh_selected_fish()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ theme_override_constants/separation = 8
|
|||
[node name="Title" type="Label" parent="ShopPanel/Margin/Layout/Header"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
text = "Fishing Shop"
|
||||
text = "fishing shop"
|
||||
theme_override_font_sizes/font_size = 24
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
|
||||
|
|
@ -78,12 +78,12 @@ texture_filter = 1
|
|||
[node name="WalletLabel" type="Label" parent="ShopPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Wallet: $0"
|
||||
text = "wallet: $0"
|
||||
|
||||
[node name="CloseButton" type="Button" parent="ShopPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
text = "close"
|
||||
custom_minimum_size = Vector2(72, 30)
|
||||
|
||||
[node name="Feedback" type="Label" parent="ShopPanel/Margin/Layout"]
|
||||
|
|
@ -107,7 +107,7 @@ theme_override_constants/separation = 6
|
|||
[node name="SalesTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Sell individual Cooler fish • Full base value"
|
||||
text = "sell individual cooler fish • full base value"
|
||||
theme_override_font_sizes/font_size = 17
|
||||
|
||||
[node name="SalesBody" type="HSplitContainer" parent="ShopPanel/Margin/Layout/Body/FishSales"]
|
||||
|
|
@ -133,7 +133,7 @@ layout_mode = 2
|
|||
[node name="FishEmpty" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin/ListStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Your Cooler is empty."
|
||||
text = "your cooler is empty."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="FishList" type="ItemList" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/ListPanel/ListMargin/ListStack"]
|
||||
|
|
@ -142,6 +142,7 @@ layout_mode = 2
|
|||
size_flags_vertical = 3
|
||||
fixed_icon_size = Vector2i(48, 36)
|
||||
icon_mode = 1
|
||||
select_mode = 1
|
||||
same_column_width = true
|
||||
|
||||
[node name="FishDetail" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody"]
|
||||
|
|
@ -170,22 +171,30 @@ texture_filter = 1
|
|||
[node name="FishName" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Select a fish"
|
||||
text = "select a fish"
|
||||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 19
|
||||
|
||||
[node name="FishDetails" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Choose one individual fish from your Cooler."
|
||||
text = "choose one individual fish from your cooler."
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
[node name="SelectionSummary" type="Label" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "no fish selected"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
|
||||
[node name="SellButton" type="Button" parent="ShopPanel/Margin/Layout/Body/FishSales/SalesBody/FishDetail/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Sell Selected"
|
||||
text = "sell selected"
|
||||
|
||||
[node name="Upgrades" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
|
||||
custom_minimum_size = Vector2(270, 0)
|
||||
|
|
@ -194,7 +203,7 @@ theme_override_constants/separation = 8
|
|||
|
||||
[node name="UpgradeTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
||||
layout_mode = 2
|
||||
text = "Fishing Upgrades"
|
||||
text = "fishing upgrades"
|
||||
theme_override_font_sizes/font_size = 17
|
||||
|
||||
[node name="Supplies" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
|
||||
|
|
@ -204,7 +213,7 @@ theme_override_constants/separation = 6
|
|||
|
||||
[node name="Title" type="Label" parent="ShopPanel/Margin/Layout/Body/Supplies"]
|
||||
layout_mode = 2
|
||||
text = "Supplies"
|
||||
text = "supplies"
|
||||
theme_override_font_sizes/font_size = 17
|
||||
|
||||
[node name="Scroll" type="ScrollContainer" parent="ShopPanel/Margin/Layout/Body/Supplies"]
|
||||
|
|
@ -246,13 +255,13 @@ size_flags_horizontal = 3
|
|||
|
||||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
||||
layout_mode = 2
|
||||
text = "Reel Speed"
|
||||
text = "reel speed"
|
||||
theme_override_font_sizes/font_size = 17
|
||||
|
||||
[node name="ReelLevel" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Level 0"
|
||||
text = "level 0"
|
||||
|
||||
[node name="ReelEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -267,7 +276,7 @@ text = "$50"
|
|||
[node name="ReelPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/ReelCard/Margin/Row"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Purchase"
|
||||
text = "purchase"
|
||||
|
||||
[node name="BarrierCard" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
||||
layout_mode = 2
|
||||
|
|
@ -297,13 +306,13 @@ size_flags_horizontal = 3
|
|||
|
||||
[node name="Name" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
||||
layout_mode = 2
|
||||
text = "Barrier Power"
|
||||
text = "barrier power"
|
||||
theme_override_font_sizes/font_size = 17
|
||||
|
||||
[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"
|
||||
text = "level 0"
|
||||
|
||||
[node name="BarrierEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -318,7 +327,7 @@ text = "$100"
|
|||
[node name="BarrierPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/BarrierCard/Margin/Row"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Purchase"
|
||||
text = "purchase"
|
||||
|
||||
[node name="CoolerCard" type="PanelContainer" parent="ShopPanel/Margin/Layout/Body/Upgrades"]
|
||||
layout_mode = 2
|
||||
|
|
@ -348,13 +357,13 @@ 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"
|
||||
text = "cooler capacity"
|
||||
theme_override_font_sizes/font_size = 17
|
||||
|
||||
[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"
|
||||
text = "level 0"
|
||||
|
||||
[node name="CoolerEffect" type="Label" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row/Data"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -369,7 +378,7 @@ text = "$75"
|
|||
[node name="CoolerPurchase" type="Button" parent="ShopPanel/Margin/Layout/Body/Upgrades/CoolerCard/Margin/Row"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Purchase"
|
||||
text = "purchase"
|
||||
|
||||
[node name="SaleConfirmation" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -413,10 +422,10 @@ theme_override_constants/separation = 8
|
|||
[node name="ConfirmSale" type="Button" parent="SaleConfirmation/Margin/Stack/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Sell Fish"
|
||||
text = "sell fish"
|
||||
theme_type_variation = &"DangerButton"
|
||||
|
||||
[node name="CancelSale" type="Button" parent="SaleConfirmation/Margin/Stack/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Cancel"
|
||||
text = "cancel"
|
||||
|
|
|
|||
BIN
ui/fonts/seattle_avenue.otf
Normal file
36
ui/fonts/seattle_avenue.otf.import
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
[remap]
|
||||
|
||||
importer="font_data_dynamic"
|
||||
type="FontFile"
|
||||
uid="uid://ddbejmxpa3kw6"
|
||||
path="res://.godot/imported/seattle_avenue.otf-6438e7427b67d392fd88ff9a0c47fc59.fontdata"
|
||||
|
||||
[deps]
|
||||
|
||||
source_file="res://ui/fonts/seattle_avenue.otf"
|
||||
dest_files=["res://.godot/imported/seattle_avenue.otf-6438e7427b67d392fd88ff9a0c47fc59.fontdata"]
|
||||
|
||||
[params]
|
||||
|
||||
Rendering=null
|
||||
antialiasing=1
|
||||
generate_mipmaps=false
|
||||
disable_embedded_bitmaps=true
|
||||
multichannel_signed_distance_field=false
|
||||
msdf_pixel_range=8
|
||||
msdf_size=48
|
||||
allow_system_fallback=true
|
||||
force_autohinter=false
|
||||
modulate_color_glyphs=false
|
||||
hinting=3
|
||||
subpixel_positioning=4
|
||||
keep_rounding_remainders=true
|
||||
oversampling=0.0
|
||||
Fallbacks=null
|
||||
fallbacks=[]
|
||||
Compress=null
|
||||
compress=true
|
||||
preload=[]
|
||||
language_support={}
|
||||
script_support={}
|
||||
opentype_features={}
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
[gd_resource type="Theme" load_steps=12 format=3]
|
||||
[gd_resource type="Theme" load_steps=13 format=3]
|
||||
|
||||
[ext_resource type="FontFile" uid="uid://ddbejmxpa3kw6" path="res://ui/fonts/seattle_avenue.otf" id="1_font"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="Panel"]
|
||||
bg_color = Color(0.075, 0.145, 0.17, 0.96)
|
||||
|
|
@ -143,6 +145,7 @@ corner_radius_bottom_right = 6
|
|||
corner_radius_bottom_left = 6
|
||||
|
||||
[resource]
|
||||
default_font = ExtResource("1_font")
|
||||
default_font_size = 15
|
||||
Label/colors/font_color = Color(0.956, 0.972, 0.976, 1)
|
||||
Label/colors/font_shadow_color = Color(0, 0, 0, 0)
|
||||
|
|
|
|||
|
|
@ -129,10 +129,10 @@ func _process(_delta: float) -> void:
|
|||
if remaining <= 0.0:
|
||||
continue
|
||||
var label: String = {
|
||||
PlayerItemEffectsType.COFFEE_ID: "Coffee",
|
||||
PlayerItemEffectsType.ENERGY_DRINK_ID: "Energy",
|
||||
PlayerItemEffectsType.SNACK_ID: "Snack",
|
||||
PlayerItemEffectsType.FISH_FINDER_ID: "Finder",
|
||||
PlayerItemEffectsType.COFFEE_ID: "coffee",
|
||||
PlayerItemEffectsType.ENERGY_DRINK_ID: "energy",
|
||||
PlayerItemEffectsType.SNACK_ID: "snack",
|
||||
PlayerItemEffectsType.FISH_FINDER_ID: "finder",
|
||||
}.get(item_id, "")
|
||||
parts.append(
|
||||
"%s %d:%02d"
|
||||
|
|
@ -296,7 +296,7 @@ func _on_catch_display_changed(
|
|||
"%d%% %s"
|
||||
% [roundi(barrier_positions[barrier_index] * 100.0), marker]
|
||||
)
|
||||
_barrier_summary.text = "Barriers: %s" % " ".join(barrier_labels)
|
||||
_barrier_summary.text = "barriers: %s" % " ".join(barrier_labels)
|
||||
|
||||
if (
|
||||
active_barrier_index >= 0
|
||||
|
|
@ -304,14 +304,14 @@ func _on_catch_display_changed(
|
|||
and active_barrier_index < barrier_max_health.size()
|
||||
):
|
||||
_barrier_health.text = (
|
||||
"Barrier: %d / %d"
|
||||
"barrier: %d / %d"
|
||||
% [
|
||||
barrier_health[active_barrier_index],
|
||||
barrier_max_health[active_barrier_index],
|
||||
]
|
||||
)
|
||||
else:
|
||||
_barrier_health.text = "Hold left click to reel"
|
||||
_barrier_health.text = "hold left click to reel"
|
||||
|
||||
var chase_gap: float = progress - chase_progress
|
||||
if chase_gap <= 0.05:
|
||||
|
|
@ -319,7 +319,7 @@ func _on_catch_display_changed(
|
|||
"%s%s"
|
||||
% [
|
||||
_barrier_health.text,
|
||||
"\nRed is closing in!",
|
||||
"\nred is closing in!",
|
||||
]
|
||||
).strip_edges()
|
||||
_refresh_fishing_panel_visibility()
|
||||
|
|
@ -377,11 +377,11 @@ func _on_showcase_changed(
|
|||
_barrier_health.visible = false
|
||||
_clear_barrier_markers()
|
||||
_showcase_details.text = (
|
||||
"%.1f lb • %s\nLeft click or Escape to put away"
|
||||
"%.1f lb • %s\nleft click or escape to put away"
|
||||
% [weight_lb, rarity_name]
|
||||
)
|
||||
_showcase_details.visible = true
|
||||
_set_fishing_status("You caught a %s!" % fish_name)
|
||||
_set_fishing_status("you caught a %s!" % fish_name)
|
||||
|
||||
|
||||
func _on_player_menu_visibility_changed(is_open: bool) -> void:
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ theme_override_constants/margin_bottom = 7
|
|||
|
||||
[node name="Label" type="Label" parent="ShopPrompt/Margin"]
|
||||
layout_mode = 2
|
||||
text = "Press E to open Fishing Shop"
|
||||
text = "press e to open fishing shop"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="FishingShop" parent="." instance=ExtResource("8_shop")]
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func refresh() -> void:
|
|||
_quantity_label.text = quantity_text
|
||||
_quantity_label.visible = not quantity_text.is_empty()
|
||||
tooltip_text = (
|
||||
item.display_name if item != null else "Empty hotbar slot"
|
||||
item.display_name if item != null else "empty hotbar slot"
|
||||
)
|
||||
button_pressed = slot_index == _hotbar.get_selected_slot()
|
||||
|
||||
|
|
|
|||
|
|
@ -161,9 +161,9 @@ func _save_now() -> void:
|
|||
_action_in_progress = true
|
||||
_save_button.disabled = true
|
||||
if _save_manager.save_now():
|
||||
_feedback.text = "Game saved."
|
||||
_feedback.text = "game saved."
|
||||
else:
|
||||
_feedback.text = "Save failed. Previous save was preserved."
|
||||
_feedback.text = "save failed. previous save was preserved."
|
||||
_action_in_progress = false
|
||||
call_deferred("_reenable_save_button")
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ func _open_settings() -> void:
|
|||
|
||||
func _on_settings_applied() -> void:
|
||||
_root_panel.show()
|
||||
_feedback.text = "Settings saved."
|
||||
_feedback.text = "settings saved."
|
||||
%SettingsButton.grab_focus()
|
||||
|
||||
|
||||
|
|
@ -193,9 +193,9 @@ func _on_settings_closed() -> void:
|
|||
func _confirm_return_to_title() -> void:
|
||||
_open_confirmation(
|
||||
ConfirmationAction.RETURN_TO_TITLE,
|
||||
"Return to Title",
|
||||
"Unsaved progress will be saved first.",
|
||||
"Save and Return",
|
||||
"return to title",
|
||||
"unsaved progress will be saved first.",
|
||||
"save and return",
|
||||
false
|
||||
)
|
||||
|
||||
|
|
@ -203,13 +203,13 @@ func _confirm_return_to_title() -> void:
|
|||
func _confirm_reset_progress() -> void:
|
||||
_open_confirmation(
|
||||
ConfirmationAction.RESET_PROGRESS,
|
||||
"Reset all progression?",
|
||||
"reset all progression?",
|
||||
(
|
||||
"This permanently deletes your fish, discoveries, "
|
||||
"this permanently deletes your fish, discoveries, "
|
||||
+ "favorites, and wallet balance.\n\n"
|
||||
+ "Your settings will be preserved."
|
||||
+ "your settings will be preserved."
|
||||
),
|
||||
"DELETE ALL PROGRESS",
|
||||
"delete all progress",
|
||||
true
|
||||
)
|
||||
|
||||
|
|
@ -226,9 +226,9 @@ func _request_quit() -> void:
|
|||
return
|
||||
_open_confirmation(
|
||||
ConfirmationAction.QUIT_ANYWAY,
|
||||
"Save failed",
|
||||
"Some progress or settings could not be saved. Quit anyway?",
|
||||
"Quit Anyway",
|
||||
"save failed",
|
||||
"some progress or settings could not be saved. quit anyway?",
|
||||
"quit anyway",
|
||||
true
|
||||
)
|
||||
|
||||
|
|
@ -276,7 +276,7 @@ func _accept_confirmation() -> void:
|
|||
if _save_manager.save_now():
|
||||
return_to_title_requested.emit()
|
||||
else:
|
||||
_feedback.text = "Save failed. Previous save was preserved."
|
||||
_feedback.text = "save failed. previous save was preserved."
|
||||
_root_panel.show()
|
||||
ConfirmationAction.RESET_PROGRESS:
|
||||
reset_progress_requested.emit()
|
||||
|
|
@ -291,7 +291,7 @@ func report_reset_failure() -> void:
|
|||
_action_in_progress = false
|
||||
_root_panel.show()
|
||||
_confirmation_panel.hide()
|
||||
_feedback.text = "Reset failed. Your progression was preserved."
|
||||
_feedback.text = "reset failed. your progression was preserved."
|
||||
%ResumeButton.grab_focus()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,45 +55,45 @@ theme_override_constants/separation = 11
|
|||
[node name="Title" type="Label" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 30
|
||||
text = "Game Menu"
|
||||
text = "game menu"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ResumeButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 43)
|
||||
layout_mode = 2
|
||||
text = "Return to Game"
|
||||
text = "return to game"
|
||||
|
||||
[node name="SaveButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 43)
|
||||
layout_mode = 2
|
||||
text = "Save Now"
|
||||
text = "save now"
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 43)
|
||||
layout_mode = 2
|
||||
text = "Settings"
|
||||
text = "settings"
|
||||
|
||||
[node name="ReturnToTitleButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 43)
|
||||
layout_mode = 2
|
||||
text = "Return to Title"
|
||||
text = "return to title"
|
||||
|
||||
[node name="ResetProgressButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 43)
|
||||
layout_mode = 2
|
||||
theme_type_variation = &"DangerButton"
|
||||
text = "Reset Progress"
|
||||
text = "reset progress"
|
||||
|
||||
[node name="QuitButton" type="Button" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 43)
|
||||
layout_mode = 2
|
||||
text = "Quit"
|
||||
text = "quit"
|
||||
|
||||
[node name="FeedbackLabel" type="Label" parent="RootCenter/RootPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -147,13 +147,13 @@ unique_name_in_owner = true
|
|||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.937, 0.357, 0.384, 1)
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Confirm"
|
||||
text = "confirm"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="ConfirmationText" type="Label" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Are you sure?"
|
||||
text = "are you sure?"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
|
|
@ -166,10 +166,10 @@ alignment = 1
|
|||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(190, 43)
|
||||
layout_mode = 2
|
||||
text = "Confirm"
|
||||
text = "confirm"
|
||||
|
||||
[node name="CancelConfirmButton" type="Button" parent="ConfirmationCenter/ConfirmationPanel/Margin/Content/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(130, 43)
|
||||
layout_mode = 2
|
||||
text = "Cancel"
|
||||
text = "cancel"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ const ItemDragSourceType = preload("res://ui/item_drag_source.gd")
|
|||
const PlayerCoolerCapacityType = preload(
|
||||
"res://progression/player_cooler_capacity.gd"
|
||||
)
|
||||
const FishBatchSelectionType = preload(
|
||||
"res://ui/fish_batch_selection.gd"
|
||||
)
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
|
||||
|
|
@ -68,6 +71,7 @@ enum CloseReason {
|
|||
@onready var _detail_texture: TextureRect = %DetailTexture
|
||||
@onready var _detail_name: Label = %DetailName
|
||||
@onready var _detail_data: Label = %DetailData
|
||||
@onready var _selection_summary: Label = %SelectionSummary
|
||||
@onready var _favorite_button: Button = %FavoriteButton
|
||||
@onready var _sell_button: Button = %SellButton
|
||||
@onready var _sale_unavailable: Label = %SaleUnavailable
|
||||
|
|
@ -94,7 +98,7 @@ var _cooler_capacity: PlayerCoolerCapacityType
|
|||
var _current_section: Section = Section.COOLER
|
||||
var _sort_mode: SortMode = SortMode.CATCH_ORDER
|
||||
var _sort_descending: bool = true
|
||||
var _selected_catch_id: StringName
|
||||
var _fish_selection := FishBatchSelectionType.new()
|
||||
var _selected_bag_item_id: StringName
|
||||
var _prior_movement_enabled: bool = true
|
||||
var _prior_camera_input_enabled: bool = true
|
||||
|
|
@ -102,9 +106,10 @@ var _prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
|
|||
var _control_snapshot_stored: bool = false
|
||||
var _mouse_snapshot_stored: bool = false
|
||||
var _menu_generation: int = 0
|
||||
var _confirmation_catch_id: StringName
|
||||
var _confirmation_catch_ids: Array[StringName] = []
|
||||
var _confirmation_buyer: FishBuyerProfileType
|
||||
var _confirmation_buyer_id: StringName
|
||||
var _confirmation_generation: int = -1
|
||||
var _sale_in_progress: bool = false
|
||||
|
||||
|
||||
|
|
@ -123,9 +128,9 @@ func _ready() -> void:
|
|||
_sell_button.pressed.connect(_on_sell_pressed)
|
||||
_confirm_sale_button.pressed.connect(_on_confirm_sale_pressed)
|
||||
_cancel_sale_button.pressed.connect(_close_sale_confirmation)
|
||||
_sort_option.add_item("Catch order", SortMode.CATCH_ORDER)
|
||||
_sort_option.add_item("Name", SortMode.NAME)
|
||||
_sort_option.add_item("Rarity", SortMode.RARITY)
|
||||
_sort_option.add_item("catch order", SortMode.CATCH_ORDER)
|
||||
_sort_option.add_item("name", SortMode.NAME)
|
||||
_sort_option.add_item("rarity", SortMode.RARITY)
|
||||
_sort_option.select(SortMode.CATCH_ORDER)
|
||||
_update_sort_direction_text()
|
||||
_show_section(_current_section)
|
||||
|
|
@ -157,6 +162,7 @@ func setup(
|
|||
_hotbar = hotbar
|
||||
_item_catalog = item_catalog
|
||||
_cooler_capacity = cooler_capacity
|
||||
_fish_selection.clear()
|
||||
if not _inventory.catches_changed.is_connected(_on_inventory_changed):
|
||||
_inventory.catches_changed.connect(_on_inventory_changed)
|
||||
if not _collection_log.fish_discovered.is_connected(_on_fish_discovered):
|
||||
|
|
@ -234,6 +240,13 @@ func close_menu(
|
|||
return
|
||||
get_viewport().gui_cancel_drag()
|
||||
_close_sale_confirmation()
|
||||
if reason in [
|
||||
CloseReason.BITE_STARTED,
|
||||
CloseReason.WATER_RECOVERY,
|
||||
CloseReason.SESSION_END,
|
||||
CloseReason.TEARDOWN,
|
||||
]:
|
||||
_fish_selection.clear()
|
||||
var closing_generation: int = _menu_generation
|
||||
visible = false
|
||||
get_viewport().gui_release_focus()
|
||||
|
|
@ -249,6 +262,7 @@ func close_menu(
|
|||
|
||||
|
||||
func close_for_water_recovery() -> void:
|
||||
_fish_selection.clear()
|
||||
close_menu(CloseReason.WATER_RECOVERY, false)
|
||||
|
||||
|
||||
|
|
@ -257,13 +271,14 @@ func close_for_game_menu() -> void:
|
|||
|
||||
|
||||
func close_for_session_end() -> void:
|
||||
_fish_selection.clear()
|
||||
close_menu(CloseReason.SESSION_END, false)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if visible:
|
||||
close_menu(CloseReason.TEARDOWN, false)
|
||||
_selected_catch_id = StringName()
|
||||
_fish_selection.clear()
|
||||
_menu_generation += 1
|
||||
|
||||
|
||||
|
|
@ -340,11 +355,11 @@ func _on_sort_direction_pressed() -> void:
|
|||
func _update_sort_direction_text() -> void:
|
||||
match _sort_mode:
|
||||
SortMode.CATCH_ORDER:
|
||||
_sort_direction.text = "Newest first" if _sort_descending else "Oldest first"
|
||||
_sort_direction.text = "newest first" if _sort_descending else "oldest first"
|
||||
SortMode.NAME:
|
||||
_sort_direction.text = "Z–A" if _sort_descending else "A–Z"
|
||||
_sort_direction.text = "z–a" if _sort_descending else "a–z"
|
||||
SortMode.RARITY:
|
||||
_sort_direction.text = "High to low" if _sort_descending else "Low to high"
|
||||
_sort_direction.text = "high to low" if _sort_descending else "low to high"
|
||||
|
||||
|
||||
func _refresh_all() -> void:
|
||||
|
|
@ -386,9 +401,9 @@ func _refresh_bag() -> void:
|
|||
item.get_category_name(),
|
||||
]
|
||||
card.tooltip_text = (
|
||||
"Drag to a hotbar slot."
|
||||
"drag to a hotbar slot."
|
||||
if item.hotbar_allowed
|
||||
else "This item cannot be assigned to the hotbar."
|
||||
else "this item cannot be assigned to the hotbar."
|
||||
)
|
||||
card.setup(item.item_id, item.display_name, item.icon)
|
||||
card.pressed.connect(_select_bag_item.bind(item.item_id))
|
||||
|
|
@ -427,19 +442,19 @@ func _update_bag_detail() -> void:
|
|||
_bag_detail_texture.texture = item.icon if item != null else null
|
||||
_bag_detail_name.text = item.display_name if item != null else ""
|
||||
_bag_detail_data.text = (
|
||||
"%s\nQuantity: %d\n%s\n%s"
|
||||
"%s\nquantity: %d\n%s\n%s"
|
||||
% [
|
||||
item.get_category_name(),
|
||||
quantity,
|
||||
item.description,
|
||||
(
|
||||
"Can be assigned to the hotbar."
|
||||
"can be assigned to the hotbar."
|
||||
if item.hotbar_allowed
|
||||
else "Cannot be assigned to the hotbar."
|
||||
else "cannot be assigned to the hotbar."
|
||||
),
|
||||
]
|
||||
if item != null
|
||||
else "Select a Bag item for details."
|
||||
else "select a bag item for details."
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -470,8 +485,8 @@ func _refresh_economy_summary() -> void:
|
|||
if _inventory != null
|
||||
else 0
|
||||
)
|
||||
_wallet_balance.text = "Wallet: $%d" % balance
|
||||
_held_value.text = "Held fish base value: $%d" % held_total
|
||||
_wallet_balance.text = "wallet: $%d" % balance
|
||||
_held_value.text = "held fish base value: $%d" % held_total
|
||||
_cooler_count.text = "%d / %d" % [
|
||||
_inventory.get_all_catches().size() if _inventory != null else 0,
|
||||
_cooler_capacity.get_capacity() if _cooler_capacity != null else 0,
|
||||
|
|
@ -493,16 +508,18 @@ func _refresh_inventory() -> void:
|
|||
catches.append(fish_catch)
|
||||
catches.sort_custom(_compare_catches)
|
||||
_inventory_empty.visible = catches.is_empty()
|
||||
var visible_ids: Array[StringName] = []
|
||||
for fish_catch: FishCatchType in catches:
|
||||
visible_ids.append(fish_catch.catch_id)
|
||||
_fish_selection.set_visible_order(visible_ids)
|
||||
|
||||
var selected: FishCatchType
|
||||
if _inventory != null:
|
||||
selected = _inventory.get_catch(_selected_catch_id)
|
||||
if selected == null and not catches.is_empty():
|
||||
selected = catches.front()
|
||||
_selected_catch_id = selected.catch_id
|
||||
selected = _inventory.get_catch(_fish_selection.get_focused_id())
|
||||
for fish_catch: FishCatchType in catches:
|
||||
_inventory_grid.add_child(_create_inventory_card(fish_catch))
|
||||
_update_inventory_detail(selected)
|
||||
_update_sale_summary()
|
||||
_update_sort_direction_text()
|
||||
|
||||
|
||||
|
|
@ -527,11 +544,17 @@ func _create_inventory_card(fish_catch: FishCatchType) -> Button:
|
|||
card.theme_type_variation = &"CardButton"
|
||||
card.custom_minimum_size = Vector2(132.0, 118.0)
|
||||
card.toggle_mode = true
|
||||
card.button_pressed = fish_catch.catch_id == _selected_catch_id
|
||||
card.pressed.connect(_select_catch.bind(fish_catch.catch_id))
|
||||
var is_selected: bool = _fish_selection.is_selected(fish_catch.catch_id)
|
||||
var is_focused: bool = (
|
||||
fish_catch.catch_id == _fish_selection.get_focused_id()
|
||||
)
|
||||
card.button_pressed = is_selected
|
||||
card.pressed.connect(_on_catch_card_pressed.bind(fish_catch.catch_id))
|
||||
_apply_inventory_card_styles(
|
||||
card,
|
||||
UIPalette.get_rarity_color(fish_catch.fish.rarity)
|
||||
UIPalette.get_rarity_color(fish_catch.fish.rarity),
|
||||
is_selected,
|
||||
is_focused
|
||||
)
|
||||
|
||||
var content := VBoxContainer.new()
|
||||
|
|
@ -573,34 +596,83 @@ func _create_inventory_card(fish_catch: FishCatchType) -> Button:
|
|||
favorite_marker.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
favorite_marker.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
card.add_child(favorite_marker)
|
||||
if is_selected:
|
||||
var selected_marker := Label.new()
|
||||
selected_marker.text = "✓"
|
||||
selected_marker.add_theme_color_override(
|
||||
"font_color",
|
||||
UIPalette.SUCCESS
|
||||
)
|
||||
selected_marker.add_theme_font_size_override("font_size", 18)
|
||||
selected_marker.set_anchors_preset(Control.PRESET_TOP_LEFT)
|
||||
selected_marker.offset_left = 8.0
|
||||
selected_marker.offset_top = 5.0
|
||||
selected_marker.offset_right = 30.0
|
||||
selected_marker.offset_bottom = 29.0
|
||||
selected_marker.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
card.add_child(selected_marker)
|
||||
if is_focused:
|
||||
var focus_marker := Label.new()
|
||||
focus_marker.text = "◆"
|
||||
focus_marker.add_theme_color_override(
|
||||
"font_color",
|
||||
UIPalette.PRIMARY
|
||||
)
|
||||
focus_marker.set_anchors_preset(Control.PRESET_BOTTOM_RIGHT)
|
||||
focus_marker.offset_left = -26.0
|
||||
focus_marker.offset_top = -25.0
|
||||
focus_marker.offset_right = -8.0
|
||||
focus_marker.offset_bottom = -7.0
|
||||
focus_marker.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
focus_marker.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
card.add_child(focus_marker)
|
||||
return card
|
||||
|
||||
|
||||
func _apply_inventory_card_styles(
|
||||
card: Button,
|
||||
rarity_color: Color,
|
||||
is_selected: bool,
|
||||
is_focused: bool,
|
||||
) -> void:
|
||||
card.add_theme_stylebox_override(
|
||||
"normal",
|
||||
_create_inventory_card_style(rarity_color, 0)
|
||||
_create_inventory_card_style(
|
||||
rarity_color,
|
||||
0,
|
||||
is_selected,
|
||||
is_focused
|
||||
)
|
||||
)
|
||||
card.add_theme_stylebox_override(
|
||||
"hover",
|
||||
_create_inventory_card_style(rarity_color, 1)
|
||||
_create_inventory_card_style(
|
||||
rarity_color,
|
||||
1,
|
||||
is_selected,
|
||||
is_focused
|
||||
)
|
||||
)
|
||||
card.add_theme_stylebox_override(
|
||||
"focus",
|
||||
_create_inventory_card_style(rarity_color, 1)
|
||||
_create_inventory_card_style(
|
||||
rarity_color,
|
||||
1,
|
||||
is_selected,
|
||||
true
|
||||
)
|
||||
)
|
||||
card.add_theme_stylebox_override(
|
||||
"pressed",
|
||||
_create_inventory_card_style(rarity_color, 2)
|
||||
_create_inventory_card_style(rarity_color, 2, true, is_focused)
|
||||
)
|
||||
|
||||
|
||||
func _create_inventory_card_style(
|
||||
rarity_color: Color,
|
||||
state_index: int,
|
||||
is_selected: bool,
|
||||
is_focused: bool,
|
||||
) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
match state_index:
|
||||
|
|
@ -609,9 +681,13 @@ func _create_inventory_card_style(
|
|||
2:
|
||||
style.bg_color = UIPalette.PRIMARY.darkened(0.48)
|
||||
_:
|
||||
style.bg_color = UIPalette.ELEVATED_PANEL
|
||||
style.bg_color = (
|
||||
UIPalette.PRIMARY.darkened(0.55)
|
||||
if is_selected
|
||||
else UIPalette.ELEVATED_PANEL
|
||||
)
|
||||
style.border_color = rarity_color
|
||||
var border_width: int = 4 if state_index == 2 else 2
|
||||
var border_width: int = 4 if state_index == 2 or is_focused else 2
|
||||
style.set_border_width_all(border_width)
|
||||
style.set_corner_radius_all(8)
|
||||
style.content_margin_left = 7.0
|
||||
|
|
@ -621,13 +697,18 @@ func _create_inventory_card_style(
|
|||
return style
|
||||
|
||||
|
||||
func _select_catch(catch_id: StringName) -> void:
|
||||
func _on_catch_card_pressed(catch_id: StringName) -> void:
|
||||
if _inventory == null:
|
||||
return
|
||||
var selected: FishCatchType = _inventory.get_catch(catch_id)
|
||||
if selected == null:
|
||||
return
|
||||
_selected_catch_id = catch_id
|
||||
_fish_selection.apply_click(
|
||||
catch_id,
|
||||
Input.is_key_pressed(KEY_CTRL),
|
||||
Input.is_key_pressed(KEY_SHIFT)
|
||||
)
|
||||
_transaction_feedback.text = ""
|
||||
_refresh_inventory()
|
||||
|
||||
|
||||
|
|
@ -638,26 +719,28 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
_detail_name.text = ""
|
||||
_detail_data.text = ""
|
||||
_favorite_button.disabled = true
|
||||
_favorite_button.text = "Favorite"
|
||||
_sell_button.disabled = true
|
||||
_sale_unavailable.text = ""
|
||||
_sale_unavailable.visible = false
|
||||
_favorite_button.text = "favorite"
|
||||
return
|
||||
_detail_texture.texture = fish_catch.fish.display_texture
|
||||
_detail_texture.visible = fish_catch.fish.display_texture != null
|
||||
_detail_name.text = fish_catch.fish.display_name
|
||||
var individual_preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch([fish_catch.catch_id], _default_buyer)
|
||||
if _sale_service != null
|
||||
else null
|
||||
)
|
||||
var buyer_offer: int = (
|
||||
_default_buyer.get_offer(fish_catch.sale_value)
|
||||
if _default_buyer != null
|
||||
individual_preview.payout
|
||||
if individual_preview != null
|
||||
else -1
|
||||
)
|
||||
var buyer_offer_text: String = "Buyer unavailable"
|
||||
if buyer_offer >= 0:
|
||||
var buyer_offer_text: String = "buyer unavailable"
|
||||
if buyer_offer >= 0 and _default_buyer != null:
|
||||
buyer_offer_text = "%s offer: $%d" % [
|
||||
_default_buyer.display_name,
|
||||
buyer_offer,
|
||||
]
|
||||
_detail_data.text = "%.2f lb\n%s\nBase value: $%d\n%s" % [
|
||||
_detail_data.text = "%.2f lb\n%s\nbase value: $%d\n%s" % [
|
||||
fish_catch.weight_lb,
|
||||
fish_catch.fish.get_rarity_name(),
|
||||
fish_catch.sale_value,
|
||||
|
|
@ -665,35 +748,68 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
]
|
||||
_favorite_button.disabled = false
|
||||
_favorite_button.text = (
|
||||
"Unfavorite" if fish_catch.is_favorited else "Favorite"
|
||||
"unfavorite" if fish_catch.is_favorited else "favorite"
|
||||
)
|
||||
var valid_sale_value: bool = fish_catch.sale_value >= 0
|
||||
var valid_buyer: bool = (
|
||||
_default_buyer != null
|
||||
and _default_buyer.is_valid()
|
||||
and buyer_offer >= 0
|
||||
|
||||
|
||||
func _update_sale_summary() -> void:
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
var selected_count: int = selected_ids.size()
|
||||
_sell_button.text = (
|
||||
"sell fish"
|
||||
if selected_count == 1
|
||||
else "sell %d fish" % selected_count
|
||||
)
|
||||
_sell_button.disabled = (
|
||||
fish_catch.is_favorited
|
||||
or not valid_sale_value
|
||||
or not valid_buyer
|
||||
if selected_count == 0:
|
||||
_selection_summary.text = "no fish selected"
|
||||
_sell_button.text = "sell fish"
|
||||
_sell_button.disabled = true
|
||||
_sale_unavailable.text = ""
|
||||
_sale_unavailable.visible = false
|
||||
return
|
||||
var preview: FishSaleResultType = (
|
||||
_sale_service.preview_batch(selected_ids, _default_buyer)
|
||||
if _sale_service != null
|
||||
else null
|
||||
)
|
||||
if fish_catch.is_favorited:
|
||||
_sale_unavailable.text = "Favorited fish cannot be sold."
|
||||
elif not valid_sale_value:
|
||||
_sale_unavailable.text = "Invalid sale value."
|
||||
elif not valid_buyer:
|
||||
_sale_unavailable.text = "Buyer is unavailable."
|
||||
var count_text: String = (
|
||||
"1 fish selected"
|
||||
if selected_count == 1
|
||||
else "%d fish selected" % selected_count
|
||||
)
|
||||
_selection_summary.text = count_text
|
||||
if (
|
||||
preview != null
|
||||
and preview.payout >= 0
|
||||
and _default_buyer != null
|
||||
and (
|
||||
preview.is_success()
|
||||
or preview.status == FishSaleResultType.Status.FAVORITED
|
||||
)
|
||||
):
|
||||
_selection_summary.text += "\n%s total offer: $%d" % [
|
||||
_default_buyer.display_name,
|
||||
preview.payout,
|
||||
]
|
||||
_sell_button.disabled = preview == null or not preview.is_success()
|
||||
if preview != null and preview.status == FishSaleResultType.Status.FAVORITED:
|
||||
_sale_unavailable.text = (
|
||||
"favorited fish cannot be sold. "
|
||||
+ "remove them from the selection first."
|
||||
)
|
||||
elif preview != null and not preview.is_success():
|
||||
_sale_unavailable.text = preview.get_message()
|
||||
else:
|
||||
_sale_unavailable.text = ""
|
||||
_sale_unavailable.visible = not _sale_unavailable.text.is_empty()
|
||||
|
||||
|
||||
func _on_favorite_pressed() -> void:
|
||||
if _inventory == null or _selected_catch_id.is_empty():
|
||||
var focused_id: StringName = _fish_selection.get_focused_id()
|
||||
if _inventory == null or focused_id.is_empty():
|
||||
return
|
||||
var fish_catch: FishCatchType = _inventory.get_catch_by_id(
|
||||
_selected_catch_id
|
||||
focused_id
|
||||
)
|
||||
if fish_catch == null:
|
||||
_refresh_inventory()
|
||||
|
|
@ -712,46 +828,40 @@ func _on_favorite_pressed() -> void:
|
|||
|
||||
|
||||
func _on_sell_pressed() -> void:
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
if (
|
||||
_inventory == null
|
||||
or _sale_service == null
|
||||
or _default_buyer == null
|
||||
or _selected_catch_id.is_empty()
|
||||
or selected_ids.is_empty()
|
||||
):
|
||||
return
|
||||
var fish_catch: FishCatchType = _inventory.get_catch_by_id(
|
||||
_selected_catch_id
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
selected_ids,
|
||||
_default_buyer
|
||||
)
|
||||
if not preview.is_success():
|
||||
_transaction_feedback.text = (
|
||||
"favorited fish cannot be sold. "
|
||||
+ "remove them from the selection first."
|
||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||
else preview.get_message()
|
||||
)
|
||||
if fish_catch == null:
|
||||
_transaction_feedback.text = "Fish no longer exists."
|
||||
_refresh_inventory()
|
||||
return
|
||||
if fish_catch.is_favorited:
|
||||
_transaction_feedback.text = "Favorited fish cannot be sold."
|
||||
return
|
||||
if fish_catch.sale_value < 0:
|
||||
_transaction_feedback.text = "Invalid sale value."
|
||||
return
|
||||
if not _default_buyer.is_valid():
|
||||
_transaction_feedback.text = "Buyer is unavailable."
|
||||
return
|
||||
var buyer_offer: int = _default_buyer.get_offer(
|
||||
fish_catch.sale_value
|
||||
)
|
||||
if buyer_offer < 0:
|
||||
_transaction_feedback.text = "Invalid buyer offer."
|
||||
return
|
||||
_confirmation_catch_id = fish_catch.catch_id
|
||||
_confirmation_catch_ids = selected_ids.duplicate()
|
||||
_confirmation_buyer = _default_buyer
|
||||
_confirmation_buyer_id = _default_buyer.id
|
||||
_confirmation_generation = _menu_generation
|
||||
var fish_label: String = "fish"
|
||||
_confirmation_message.text = (
|
||||
"Sell this %.2f lb %s to the %s for $%d?\nBase value: $%d"
|
||||
"sell %d %s to the %s for $%d?\ncombined base value: $%d"
|
||||
% [
|
||||
fish_catch.weight_lb,
|
||||
fish_catch.fish.display_name,
|
||||
preview.fish_count,
|
||||
fish_label,
|
||||
_get_buyer_display_group(_default_buyer),
|
||||
buyer_offer,
|
||||
fish_catch.sale_value,
|
||||
preview.payout,
|
||||
preview.base_value,
|
||||
]
|
||||
)
|
||||
_sale_confirmation.visible = true
|
||||
|
|
@ -763,31 +873,39 @@ func _on_confirm_sale_pressed() -> void:
|
|||
if (
|
||||
_sale_in_progress
|
||||
or _sale_service == null
|
||||
or _confirmation_catch_id.is_empty()
|
||||
or _confirmation_catch_ids.is_empty()
|
||||
or _confirmation_buyer == null
|
||||
or _confirmation_buyer.id != _confirmation_buyer_id
|
||||
or _confirmation_generation != _menu_generation
|
||||
):
|
||||
return
|
||||
_sale_in_progress = true
|
||||
var requested_catch_id: StringName = _confirmation_catch_id
|
||||
var requested_catch_ids: Array[StringName] = (
|
||||
_confirmation_catch_ids.duplicate()
|
||||
)
|
||||
var transaction_generation: int = _menu_generation
|
||||
var transaction_buyer: FishBuyerProfileType = _confirmation_buyer
|
||||
_confirm_sale_button.disabled = true
|
||||
var result: FishSaleResultType = _sale_service.sell(
|
||||
requested_catch_id,
|
||||
_confirmation_buyer
|
||||
var result: FishSaleResultType = _sale_service.sell_batch(
|
||||
requested_catch_ids,
|
||||
transaction_buyer
|
||||
)
|
||||
_close_sale_confirmation()
|
||||
_transaction_feedback.text = result.get_message()
|
||||
_sale_in_progress = false
|
||||
if result.is_success() and _selected_catch_id == requested_catch_id:
|
||||
_selected_catch_id = StringName()
|
||||
if transaction_generation != _menu_generation or not visible:
|
||||
return
|
||||
_transaction_feedback.text = result.get_message()
|
||||
if result.is_success():
|
||||
_fish_selection.remove_ids(requested_catch_ids)
|
||||
_refresh_all()
|
||||
_inventory_tab.grab_focus()
|
||||
|
||||
|
||||
func _close_sale_confirmation() -> void:
|
||||
_confirmation_catch_id = StringName()
|
||||
_confirmation_catch_ids.clear()
|
||||
_confirmation_buyer = null
|
||||
_confirmation_buyer_id = StringName()
|
||||
_confirmation_generation = -1
|
||||
_sale_confirmation.visible = false
|
||||
_confirmation_message.text = ""
|
||||
_confirm_sale_button.disabled = false
|
||||
|
|
@ -796,25 +914,35 @@ func _close_sale_confirmation() -> void:
|
|||
func _revalidate_confirmation() -> void:
|
||||
if not _sale_confirmation.visible:
|
||||
return
|
||||
var fish_catch: FishCatchType
|
||||
if _inventory != null:
|
||||
fish_catch = _inventory.get_catch_by_id(_confirmation_catch_id)
|
||||
if fish_catch == null:
|
||||
if (
|
||||
_confirmation_generation != _menu_generation
|
||||
or _confirmation_catch_ids.is_empty()
|
||||
or _sale_service == null
|
||||
or _confirmation_buyer == null
|
||||
or _confirmation_buyer.id != _confirmation_buyer_id
|
||||
):
|
||||
_close_sale_confirmation()
|
||||
_transaction_feedback.text = "Fish no longer exists."
|
||||
elif fish_catch.is_favorited:
|
||||
_close_sale_confirmation()
|
||||
_transaction_feedback.text = "Favorited fish cannot be sold."
|
||||
elif fish_catch.sale_value < 0:
|
||||
_close_sale_confirmation()
|
||||
_transaction_feedback.text = "Invalid sale value."
|
||||
elif (
|
||||
_transaction_feedback.text = "sale selection is no longer available."
|
||||
return
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
_confirmation_catch_ids,
|
||||
_confirmation_buyer
|
||||
)
|
||||
if (
|
||||
not preview.is_success()
|
||||
or (
|
||||
_confirmation_buyer == null
|
||||
or _confirmation_buyer.id != _confirmation_buyer_id
|
||||
or not _confirmation_buyer.is_valid()
|
||||
)
|
||||
):
|
||||
_close_sale_confirmation()
|
||||
_transaction_feedback.text = "Buyer is unavailable."
|
||||
_transaction_feedback.text = (
|
||||
"favorited fish cannot be sold. "
|
||||
+ "remove them from the selection first."
|
||||
if preview.status == FishSaleResultType.Status.FAVORITED
|
||||
else preview.get_message()
|
||||
)
|
||||
|
||||
|
||||
func _get_buyer_display_group(
|
||||
|
|
@ -840,9 +968,9 @@ func _refresh_logbook() -> void:
|
|||
valid_species.append(fish)
|
||||
_logbook_empty.visible = valid_species.is_empty()
|
||||
_logbook_empty.text = (
|
||||
"No fish catalog configured."
|
||||
"no fish catalog configured."
|
||||
if valid_species.is_empty()
|
||||
else "No species discovered yet."
|
||||
else "no species discovered yet."
|
||||
)
|
||||
if not valid_species.is_empty() and _collection_log != null:
|
||||
var any_discovered: bool = false
|
||||
|
|
@ -881,12 +1009,12 @@ func _create_logbook_card(fish: FishDataType) -> Control:
|
|||
var owned_count: int = (
|
||||
_inventory.get_count(fish.id) if _inventory != null else 0
|
||||
)
|
||||
details.text = "%s\nOwned: %d" % [
|
||||
details.text = "%s\nowned: %d" % [
|
||||
fish.get_rarity_name(),
|
||||
owned_count,
|
||||
]
|
||||
else:
|
||||
details.text = "Undiscovered"
|
||||
details.text = "undiscovered"
|
||||
details.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
content.add_child(details)
|
||||
return card
|
||||
|
|
@ -904,7 +1032,7 @@ func _create_texture_frame(
|
|||
texture_frame.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
texture_frame.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
if texture == null:
|
||||
texture_frame.tooltip_text = "Display texture unavailable"
|
||||
texture_frame.tooltip_text = "display texture unavailable"
|
||||
return texture_frame
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -56,39 +56,39 @@ layout_mode = 2
|
|||
size_flags_horizontal = 3
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
theme_override_font_sizes/font_size = 22
|
||||
text = "Player Menu"
|
||||
text = "player menu"
|
||||
|
||||
[node name="WalletBalance" type="Label" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Wallet: $0"
|
||||
text = "wallet: $0"
|
||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
||||
|
||||
[node name="InventoryTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Cooler"
|
||||
text = "cooler"
|
||||
custom_minimum_size = Vector2(88, 30)
|
||||
|
||||
[node name="BagTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Bag"
|
||||
text = "bag"
|
||||
custom_minimum_size = Vector2(76, 30)
|
||||
|
||||
[node name="LogbookTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Logbook"
|
||||
text = "logbook"
|
||||
custom_minimum_size = Vector2(88, 30)
|
||||
|
||||
[node name="CloseButton" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
text = "close"
|
||||
custom_minimum_size = Vector2(68, 30)
|
||||
|
||||
[node name="Separator" type="HSeparator" parent="MenuPanel/Margin/Layout"]
|
||||
|
|
@ -123,7 +123,7 @@ theme_override_constants/separation = 6
|
|||
|
||||
[node name="SortLabel" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
layout_mode = 2
|
||||
text = "Sort:"
|
||||
text = "sort:"
|
||||
|
||||
[node name="SortOption" type="OptionButton" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -134,7 +134,7 @@ custom_minimum_size = Vector2(130, 30)
|
|||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(112, 30)
|
||||
text = "Newest first"
|
||||
text = "newest first"
|
||||
|
||||
[node name="SortSpacer" type="Control" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
layout_mode = 2
|
||||
|
|
@ -149,7 +149,7 @@ theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
|||
[node name="HeldValue" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Held fish base value: $0"
|
||||
text = "held fish base value: $0"
|
||||
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
||||
|
||||
[node name="InventoryBody" type="HSplitContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"]
|
||||
|
|
@ -176,7 +176,7 @@ theme_override_constants/separation = 6
|
|||
[node name="InventoryEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Your Cooler is empty."
|
||||
text = "your cooler is empty."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InventoryScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
||||
|
|
@ -228,17 +228,25 @@ layout_mode = 2
|
|||
autowrap_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="SelectionSummary" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "no fish selected"
|
||||
autowrap_mode = 2
|
||||
horizontal_alignment = 1
|
||||
theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1)
|
||||
|
||||
[node name="FavoriteButton" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Favorite"
|
||||
text = "favorite"
|
||||
|
||||
[node name="SellButton" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
disabled = true
|
||||
text = "Sell"
|
||||
text = "sell"
|
||||
theme_type_variation = &"DangerButton"
|
||||
|
||||
[node name="SaleUnavailable" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
|
|
@ -262,7 +270,7 @@ theme_override_constants/separation = 7
|
|||
|
||||
[node name="BagHint" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection"]
|
||||
layout_mode = 2
|
||||
text = "Drag hotbar-compatible items onto a slot below. Right-click a slot to clear it."
|
||||
text = "drag hotbar-compatible items onto a slot below. right-click a slot to clear it."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BagBody" type="HSplitContainer" parent="MenuPanel/Margin/Layout/Content/BagSection"]
|
||||
|
|
@ -289,7 +297,7 @@ theme_override_constants/separation = 6
|
|||
[node name="BagEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Your Bag is empty."
|
||||
text = "your bag is empty."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="BagScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagList/Margin/Stack"]
|
||||
|
|
@ -337,7 +345,7 @@ horizontal_alignment = 1
|
|||
[node name="BagDetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/BagSection/BagBody/BagDetail/Margin/Stack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Select a Bag item for details."
|
||||
text = "select a bag item for details."
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
|
|
@ -355,7 +363,7 @@ theme_override_constants/separation = 7
|
|||
[node name="LogbookEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/LogbookSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "No species discovered yet."
|
||||
text = "no species discovered yet."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LogbookScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/LogbookSection"]
|
||||
|
|
@ -412,12 +420,12 @@ alignment = 1
|
|||
[node name="ConfirmSaleButton" type="Button" parent="MenuPanel/Margin/Layout/Content/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Confirm"
|
||||
text = "confirm"
|
||||
theme_type_variation = &"DangerButton"
|
||||
custom_minimum_size = Vector2(120, 38)
|
||||
|
||||
[node name="CancelSaleButton" type="Button" parent="MenuPanel/Margin/Layout/Content/SaleConfirmation/ConfirmationMargin/ConfirmationLayout/ConfirmationButtons"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Cancel"
|
||||
text = "cancel"
|
||||
custom_minimum_size = Vector2(120, 38)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func close_panel() -> void:
|
|||
|
||||
func _apply_settings() -> void:
|
||||
if _settings_manager == null:
|
||||
_feedback.text = "Settings are unavailable."
|
||||
_feedback.text = "settings are unavailable."
|
||||
return
|
||||
var edited := PlayerSettings.new()
|
||||
edited.auto_click_enabled = _auto_click_toggle.button_pressed
|
||||
|
|
@ -59,7 +59,7 @@ func _apply_settings() -> void:
|
|||
hide()
|
||||
applied.emit()
|
||||
else:
|
||||
_feedback.text = "Failed to save settings."
|
||||
_feedback.text = "failed to save settings."
|
||||
|
||||
|
||||
func _load_controls() -> void:
|
||||
|
|
|
|||
|
|
@ -23,17 +23,17 @@ theme_override_constants/separation = 12
|
|||
[node name="Heading" type="Label" parent="Margin/Content"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "Settings"
|
||||
text = "settings"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="AutoClickToggle" type="CheckButton" parent="Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Accessibility auto-click"
|
||||
text = "accessibility auto-click"
|
||||
|
||||
[node name="AutoClickHelp" type="Label" parent="Margin/Content"]
|
||||
layout_mode = 2
|
||||
text = "Lower intervals click barriers faster while held."
|
||||
text = "lower intervals click barriers faster while held."
|
||||
|
||||
[node name="AutoClickRow" type="HBoxContainer" parent="Margin/Content"]
|
||||
layout_mode = 2
|
||||
|
|
@ -42,7 +42,7 @@ theme_override_constants/separation = 12
|
|||
[node name="Label" type="Label" parent="Margin/Content/AutoClickRow"]
|
||||
custom_minimum_size = Vector2(190, 0)
|
||||
layout_mode = 2
|
||||
text = "Auto-click interval"
|
||||
text = "auto-click interval"
|
||||
|
||||
[node name="AutoClickInterval" type="HSlider" parent="Margin/Content/AutoClickRow"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -66,7 +66,7 @@ theme_override_constants/separation = 12
|
|||
[node name="Label" type="Label" parent="Margin/Content/MouseRow"]
|
||||
custom_minimum_size = Vector2(190, 0)
|
||||
layout_mode = 2
|
||||
text = "Mouse camera sensitivity"
|
||||
text = "mouse camera sensitivity"
|
||||
|
||||
[node name="MouseSensitivity" type="HSlider" parent="Margin/Content/MouseRow"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -90,7 +90,7 @@ theme_override_constants/separation = 12
|
|||
[node name="Label" type="Label" parent="Margin/Content/ControllerRow"]
|
||||
custom_minimum_size = Vector2(190, 0)
|
||||
layout_mode = 2
|
||||
text = "Controller camera sensitivity"
|
||||
text = "controller camera sensitivity"
|
||||
|
||||
[node name="ControllerSensitivity" type="HSlider" parent="Margin/Content/ControllerRow"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -110,7 +110,7 @@ text = "2.5"
|
|||
[node name="InvertYToggle" type="CheckButton" parent="Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Invert vertical camera"
|
||||
text = "invert vertical camera"
|
||||
|
||||
[node name="SettingsFeedback" type="Label" parent="Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -127,10 +127,10 @@ alignment = 2
|
|||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(120, 42)
|
||||
layout_mode = 2
|
||||
text = "Apply"
|
||||
text = "apply"
|
||||
|
||||
[node name="CancelSettingsButton" type="Button" parent="Margin/Content/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(120, 42)
|
||||
layout_mode = 2
|
||||
text = "Back"
|
||||
text = "back"
|
||||
|
|
|
|||
48
ui/title_logo_underwater.gdshader
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
shader_type canvas_item;
|
||||
|
||||
uniform float horizontal_displacement_pixels : hint_range(0.0, 2.0, 0.25) = 1.5;
|
||||
uniform float wave_speed : hint_range(0.0, 2.0, 0.05) = 0.55;
|
||||
uniform float wave_scale : hint_range(0.0, 0.20, 0.005) = 0.055;
|
||||
uniform vec3 underwater_tint : source_color = vec3(0.78, 0.96, 1.08);
|
||||
uniform float tint_strength : hint_range(0.0, 0.35, 0.01) = 0.18;
|
||||
uniform float highlight_strength : hint_range(0.0, 0.15, 0.01) = 0.06;
|
||||
uniform float bob_amount_pixels : hint_range(0.0, 4.0, 1.0) = 4.0;
|
||||
uniform float bob_speed : hint_range(0.0, 2.0, 0.05) = 0.65;
|
||||
|
||||
void vertex() {
|
||||
VERTEX.y += round(
|
||||
sin(TIME * bob_speed) * bob_amount_pixels
|
||||
);
|
||||
}
|
||||
|
||||
void fragment() {
|
||||
float stepped_time = floor(TIME * 12.0) / 12.0;
|
||||
float source_row = floor(UV.y / max(TEXTURE_PIXEL_SIZE.y, 0.000001));
|
||||
float wave = sin(
|
||||
source_row * wave_scale + stepped_time * wave_speed
|
||||
);
|
||||
float pixel_offset = clamp(
|
||||
round(wave * horizontal_displacement_pixels),
|
||||
-2.0,
|
||||
2.0
|
||||
);
|
||||
vec2 sample_uv = UV + vec2(pixel_offset * TEXTURE_PIXEL_SIZE.x, 0.0);
|
||||
vec4 source = texture(TEXTURE, sample_uv);
|
||||
|
||||
vec3 treated_color = mix(
|
||||
source.rgb,
|
||||
source.rgb * underwater_tint,
|
||||
tint_strength
|
||||
);
|
||||
float highlight_wave = sin(
|
||||
UV.y * 26.0 - stepped_time * wave_speed * 0.8
|
||||
) * 0.5 + 0.5;
|
||||
float highlight_level = floor(highlight_wave * 3.0) / 3.0;
|
||||
treated_color += (
|
||||
vec3(0.12, 0.30, 0.34)
|
||||
* highlight_level
|
||||
* highlight_strength
|
||||
* source.a
|
||||
);
|
||||
COLOR = vec4(treated_color, source.a);
|
||||
}
|
||||
1
ui/title_logo_underwater.gdshader.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://bu14y18kegufh
|
||||
|
|
@ -7,6 +7,52 @@ const SettingsManagerType = preload(
|
|||
"res://settings/player_settings_manager.gd"
|
||||
)
|
||||
const SettingsPanelType = preload("res://ui/settings_panel.gd")
|
||||
const DECORATIVE_FISH_TEXTURES: Array[Texture2D] = [
|
||||
preload("res://fish/species/bass/fish_bass_striped.png"),
|
||||
preload("res://fish/species/bluegill/fish_bluegill.png"),
|
||||
preload("res://fish/species/carp/fish_carp_common.png"),
|
||||
preload("res://fish/species/sunfish/fish_sunfish.png"),
|
||||
]
|
||||
const DECORATIVE_BUBBLE_TEXTURES: Array[Texture2D] = [
|
||||
preload("res://ui/assets/title/bubbles/bubble1.png"),
|
||||
preload("res://ui/assets/title/bubbles/bubble2.png"),
|
||||
preload("res://ui/assets/title/bubbles/bubble3.png"),
|
||||
]
|
||||
|
||||
const FIRST_FISH_DELAY_MIN: float = 2.0
|
||||
const FIRST_FISH_DELAY_MAX: float = 5.0
|
||||
const NEXT_FISH_DELAY_MIN: float = 5.0
|
||||
const NEXT_FISH_DELAY_MAX: float = 12.0
|
||||
const CROSSING_DURATION_MIN: float = 8.0
|
||||
const CROSSING_DURATION_MAX: float = 16.0
|
||||
const FISH_LONGEST_SIDE_MIN: float = 120.0
|
||||
const FISH_LONGEST_SIDE_MAX: float = 210.0
|
||||
const FISH_EDGE_MARGIN: float = 24.0
|
||||
const BUBBLE_EVENT_DELAY_MIN: float = 4.0
|
||||
const BUBBLE_EVENT_DELAY_MAX: float = 10.0
|
||||
const BUBBLE_CLUSTER_DELAY_MIN: float = 0.25
|
||||
const BUBBLE_CLUSTER_DELAY_MAX: float = 0.65
|
||||
const BUBBLE_TRAVEL_DURATION_MIN: float = 7.0
|
||||
const BUBBLE_TRAVEL_DURATION_MAX: float = 14.0
|
||||
const BUBBLE_SCALE_MIN: float = 0.65
|
||||
const BUBBLE_SCALE_MAX: float = 1.15
|
||||
const BUBBLE_OPACITY_MIN: float = 0.55
|
||||
const BUBBLE_OPACITY_MAX: float = 0.90
|
||||
const BUBBLE_DRIFT_MIN: float = 10.0
|
||||
const BUBBLE_DRIFT_MAX: float = 45.0
|
||||
const BUBBLE_WOBBLE_MIN: float = 3.0
|
||||
const BUBBLE_WOBBLE_MAX: float = 9.0
|
||||
const BUBBLE_EDGE_MARGIN: float = 16.0
|
||||
const MAX_DECORATIVE_BUBBLES: int = 6
|
||||
const LOGO_ASPECT_RATIO: float = 2560.0 / 760.0
|
||||
const LOGO_WIDTH_FACTOR: float = 0.52
|
||||
const LOGO_MIN_WIDTH: float = 320.0
|
||||
const LOGO_MAX_WIDTH: float = 720.0
|
||||
const BUTTON_COLUMN_WIDTH: float = 360.0
|
||||
const TITLE_HORIZONTAL_MARGIN: float = 48.0
|
||||
const START_PROMPT_MIN_SCALE: float = 0.985
|
||||
const START_PROMPT_MAX_SCALE: float = 1.015
|
||||
const START_PROMPT_CYCLE_SECONDS: float = 3.0
|
||||
|
||||
signal gameplay_requested
|
||||
signal quit_requested
|
||||
|
|
@ -21,17 +67,41 @@ enum ConfirmationAction {
|
|||
@onready var _new_game_button: Button = %NewGameButton
|
||||
@onready var _settings_button: Button = %SettingsButton
|
||||
@onready var _delete_button: Button = %DeleteSaveButton
|
||||
@onready var _quit_button: Button = %QuitButton
|
||||
@onready var _feedback_label: Label = %FeedbackLabel
|
||||
@onready var _confirmation_panel: PanelContainer = %ConfirmationPanel
|
||||
@onready var _confirmation_text: Label = %ConfirmationText
|
||||
@onready var _confirm_button: Button = %ConfirmButton
|
||||
@onready var _settings_panel: SettingsPanelType = %SettingsPanel
|
||||
@onready var _decorative_fish_layer: Control = %DecorativeFishLayer
|
||||
@onready var _decorative_fish_timer: Timer = %DecorativeFishTimer
|
||||
@onready var _decorative_bubble_layer: Control = %DecorativeBubbleLayer
|
||||
@onready var _decorative_bubble_event_timer: Timer = %DecorativeBubbleEventTimer
|
||||
@onready var _decorative_bubble_cluster_timer: Timer = %DecorativeBubbleClusterTimer
|
||||
@onready var _title_logo: TextureRect = %TitleLogo
|
||||
@onready var _button_center: CenterContainer = %ButtonCenter
|
||||
@onready var _button_stack: VBoxContainer = %ButtonStack
|
||||
@onready var _start_prompt_center: CenterContainer = %StartPromptCenter
|
||||
@onready var _start_prompt_label: Label = %StartPromptLabel
|
||||
|
||||
var _save_manager: SaveManagerType
|
||||
var _settings_manager: SettingsManagerType
|
||||
var _inspection: SaveInspectionType
|
||||
var _confirmation_action: ConfirmationAction = ConfirmationAction.NONE
|
||||
var _action_in_progress: bool = false
|
||||
var _decorative_rng := RandomNumberGenerator.new()
|
||||
var _decorative_fish: TextureRect
|
||||
var _decorative_fish_tween: Tween
|
||||
var _decorative_presentation_ready: bool = false
|
||||
var _decorative_presentation_active: bool = false
|
||||
var _decorative_generation: int = 0
|
||||
var _decorative_bubbles: Array[TextureRect] = []
|
||||
var _decorative_bubble_tweens: Dictionary[int, Tween] = {}
|
||||
var _pending_cluster_bubbles: int = 0
|
||||
var _awaiting_start_input: bool = false
|
||||
var _start_prompt_elapsed: float = 0.0
|
||||
var _navigation_focus_active: bool = false
|
||||
var _modal_restore_navigation_focus: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -44,6 +114,20 @@ func _ready() -> void:
|
|||
%CancelConfirmButton.pressed.connect(_close_confirmation)
|
||||
_settings_panel.applied.connect(_on_settings_applied)
|
||||
_settings_panel.closed.connect(_on_settings_closed)
|
||||
_decorative_fish_timer.timeout.connect(_on_decorative_fish_timer_timeout)
|
||||
_decorative_bubble_event_timer.timeout.connect(
|
||||
_on_decorative_bubble_event_timer_timeout
|
||||
)
|
||||
_decorative_bubble_cluster_timer.timeout.connect(
|
||||
_on_decorative_bubble_cluster_timer_timeout
|
||||
)
|
||||
visibility_changed.connect(_on_title_visibility_changed)
|
||||
resized.connect(_update_title_layout)
|
||||
_start_prompt_label.resized.connect(_update_start_prompt_pivot)
|
||||
_decorative_rng.randomize()
|
||||
set_process(false)
|
||||
call_deferred("_update_title_layout")
|
||||
call_deferred("_update_start_prompt_pivot")
|
||||
|
||||
|
||||
func setup(
|
||||
|
|
@ -55,20 +139,104 @@ func setup(
|
|||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_refresh_save_inspection()
|
||||
show()
|
||||
call_deferred("_focus_initial_button")
|
||||
_decorative_presentation_ready = true
|
||||
_start_decorative_presentation()
|
||||
_begin_title_entry()
|
||||
|
||||
|
||||
func reopen() -> void:
|
||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||
_prepare_awaiting_start_input()
|
||||
_close_confirmation()
|
||||
_settings_panel.hide()
|
||||
_refresh_save_inspection()
|
||||
show()
|
||||
call_deferred("_focus_initial_button")
|
||||
_start_decorative_presentation()
|
||||
_start_entry_prompt_animation()
|
||||
|
||||
|
||||
func is_awaiting_start_input() -> bool:
|
||||
return _awaiting_start_input
|
||||
|
||||
|
||||
func is_decorative_presentation_active() -> bool:
|
||||
return _decorative_presentation_active
|
||||
|
||||
|
||||
func get_active_decorative_fish_count() -> int:
|
||||
return 1 if is_instance_valid(_decorative_fish) else 0
|
||||
|
||||
|
||||
func get_active_decorative_bubble_count() -> int:
|
||||
return _decorative_bubbles.size()
|
||||
|
||||
|
||||
func is_decorative_bubble_scheduler_active() -> bool:
|
||||
return (
|
||||
_decorative_presentation_active
|
||||
and (
|
||||
not _decorative_bubble_event_timer.is_stopped()
|
||||
or not _decorative_bubble_cluster_timer.is_stopped()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _update_title_layout() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
var available_width: float = maxf(1.0, size.x - TITLE_HORIZONTAL_MARGIN)
|
||||
var logo_width: float = minf(
|
||||
clampf(
|
||||
size.x * LOGO_WIDTH_FACTOR,
|
||||
LOGO_MIN_WIDTH,
|
||||
LOGO_MAX_WIDTH
|
||||
),
|
||||
available_width
|
||||
)
|
||||
_title_logo.custom_minimum_size = Vector2(
|
||||
logo_width,
|
||||
logo_width / LOGO_ASPECT_RATIO
|
||||
)
|
||||
_button_stack.custom_minimum_size = Vector2(
|
||||
minf(BUTTON_COLUMN_WIDTH, available_width),
|
||||
0.0
|
||||
)
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if (
|
||||
not _awaiting_start_input
|
||||
or not visible
|
||||
or not _start_prompt_center.visible
|
||||
):
|
||||
return
|
||||
_start_prompt_elapsed = fmod(
|
||||
_start_prompt_elapsed + delta,
|
||||
START_PROMPT_CYCLE_SECONDS
|
||||
)
|
||||
var phase: float = _start_prompt_elapsed / START_PROMPT_CYCLE_SECONDS
|
||||
var pulse_weight: float = (sin(phase * TAU - PI * 0.5) + 1.0) * 0.5
|
||||
var prompt_scale: float = lerpf(
|
||||
START_PROMPT_MIN_SCALE,
|
||||
START_PROMPT_MAX_SCALE,
|
||||
pulse_weight
|
||||
)
|
||||
_start_prompt_label.scale = Vector2.ONE * prompt_scale
|
||||
|
||||
|
||||
func _input(event: InputEvent) -> void:
|
||||
if not visible or not event.is_action_pressed("ui_cancel"):
|
||||
if not visible:
|
||||
return
|
||||
if _awaiting_start_input:
|
||||
if not _is_start_prompt_reveal_event(event):
|
||||
return
|
||||
_reveal_primary_menu()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if _handle_primary_menu_focus_input(event):
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if not event.is_action_pressed("ui_cancel"):
|
||||
return
|
||||
if _confirmation_panel.visible:
|
||||
_close_confirmation()
|
||||
|
|
@ -79,6 +247,119 @@ func _input(event: InputEvent) -> void:
|
|||
get_viewport().set_input_as_handled()
|
||||
|
||||
|
||||
func _is_start_prompt_reveal_event(event: InputEvent) -> bool:
|
||||
if event is InputEventKey:
|
||||
var key_event := event as InputEventKey
|
||||
return key_event.pressed and not key_event.echo
|
||||
if event is InputEventJoypadButton:
|
||||
return (event as InputEventJoypadButton).pressed
|
||||
if event is InputEventMouseButton:
|
||||
return (event as InputEventMouseButton).pressed
|
||||
return false
|
||||
|
||||
|
||||
func _handle_primary_menu_focus_input(event: InputEvent) -> bool:
|
||||
if (
|
||||
not _button_center.visible
|
||||
or _confirmation_panel.visible
|
||||
or _settings_panel.visible
|
||||
):
|
||||
return false
|
||||
if event is InputEventMouseMotion:
|
||||
_navigation_focus_active = false
|
||||
_release_primary_menu_focus()
|
||||
return false
|
||||
if event is InputEventKey and (event as InputEventKey).echo:
|
||||
return false
|
||||
var moves_forward: bool = (
|
||||
event.is_action_pressed("ui_down")
|
||||
or event.is_action_pressed("ui_right")
|
||||
)
|
||||
var moves_backward: bool = (
|
||||
event.is_action_pressed("ui_up")
|
||||
or event.is_action_pressed("ui_left")
|
||||
)
|
||||
if not moves_forward and not moves_backward:
|
||||
return false
|
||||
_navigation_focus_active = true
|
||||
if _primary_menu_has_focus():
|
||||
return false
|
||||
if moves_forward:
|
||||
_get_first_available_menu_button().grab_focus()
|
||||
else:
|
||||
_quit_button.grab_focus()
|
||||
return true
|
||||
|
||||
|
||||
func _get_first_available_menu_button() -> Button:
|
||||
return _continue_button if not _continue_button.disabled else _new_game_button
|
||||
|
||||
|
||||
func _primary_menu_has_focus() -> bool:
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
return focus_owner != null and _button_center.is_ancestor_of(focus_owner)
|
||||
|
||||
|
||||
func _release_primary_menu_focus() -> void:
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if focus_owner != null and _button_center.is_ancestor_of(focus_owner):
|
||||
focus_owner.release_focus()
|
||||
|
||||
|
||||
func _release_title_focus() -> void:
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if focus_owner != null and is_ancestor_of(focus_owner):
|
||||
focus_owner.release_focus()
|
||||
|
||||
|
||||
func _begin_title_entry() -> void:
|
||||
_prepare_awaiting_start_input()
|
||||
_start_entry_prompt_animation()
|
||||
|
||||
|
||||
func _prepare_awaiting_start_input() -> void:
|
||||
_awaiting_start_input = true
|
||||
_navigation_focus_active = false
|
||||
_modal_restore_navigation_focus = false
|
||||
_button_center.hide()
|
||||
_feedback_label.hide()
|
||||
_start_prompt_center.show()
|
||||
var focus_owner: Control = get_viewport().gui_get_focus_owner()
|
||||
if focus_owner != null and is_ancestor_of(focus_owner):
|
||||
focus_owner.release_focus()
|
||||
|
||||
|
||||
func _start_entry_prompt_animation() -> void:
|
||||
_start_prompt_elapsed = 0.0
|
||||
_start_prompt_label.scale = Vector2.ONE * START_PROMPT_MIN_SCALE
|
||||
_update_start_prompt_pivot()
|
||||
set_process(true)
|
||||
|
||||
|
||||
func _stop_entry_prompt_animation() -> void:
|
||||
set_process(false)
|
||||
_start_prompt_elapsed = 0.0
|
||||
_start_prompt_label.scale = Vector2.ONE
|
||||
|
||||
|
||||
func _update_start_prompt_pivot() -> void:
|
||||
if not is_node_ready():
|
||||
return
|
||||
_start_prompt_label.pivot_offset = _start_prompt_label.size * 0.5
|
||||
|
||||
|
||||
func _reveal_primary_menu() -> void:
|
||||
if not _awaiting_start_input:
|
||||
return
|
||||
_awaiting_start_input = false
|
||||
_stop_entry_prompt_animation()
|
||||
_start_prompt_center.hide()
|
||||
_button_center.show()
|
||||
_feedback_label.show()
|
||||
_navigation_focus_active = false
|
||||
_release_title_focus()
|
||||
|
||||
|
||||
func _on_continue_pressed() -> void:
|
||||
if (
|
||||
_action_in_progress
|
||||
|
|
@ -90,11 +371,11 @@ func _on_continue_pressed() -> void:
|
|||
return
|
||||
_action_in_progress = true
|
||||
if _save_manager.load_player_data():
|
||||
_feedback_label.text = "Save loaded."
|
||||
_feedback_label.text = "save loaded."
|
||||
gameplay_requested.emit()
|
||||
else:
|
||||
_refresh_save_inspection()
|
||||
_feedback_label.text = "Failed to load save. The original was preserved."
|
||||
_feedback_label.text = "failed to load save. the original was preserved."
|
||||
_action_in_progress = false
|
||||
|
||||
|
||||
|
|
@ -112,17 +393,17 @@ func _on_new_game_pressed() -> void:
|
|||
return
|
||||
if _inspection.status == SaveInspectionType.Status.UNSUPPORTED_VERSION:
|
||||
_feedback_label.text = (
|
||||
"This save is from a newer game version. "
|
||||
+ "Use Delete Save before starting over."
|
||||
"this save is from a newer game version. "
|
||||
+ "use delete save before starting over."
|
||||
)
|
||||
return
|
||||
if _inspection.status == SaveInspectionType.Status.IO_ERROR:
|
||||
_feedback_label.text = "The existing save cannot be accessed safely."
|
||||
_feedback_label.text = "the existing save cannot be accessed safely."
|
||||
return
|
||||
_open_confirmation(
|
||||
ConfirmationAction.NEW_GAME,
|
||||
"Start a new game? Existing progression will be deleted.",
|
||||
"Start New Game"
|
||||
"start a new game? existing progression will be deleted.",
|
||||
"start new game"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -137,8 +418,8 @@ func _on_delete_pressed() -> void:
|
|||
return
|
||||
_open_confirmation(
|
||||
ConfirmationAction.DELETE_SAVE,
|
||||
"Delete your saved progression? This cannot be undone.",
|
||||
"Delete"
|
||||
"delete your saved progression? this cannot be undone.",
|
||||
"delete"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -149,7 +430,7 @@ func _on_confirmation_accepted() -> void:
|
|||
_close_confirmation()
|
||||
_action_in_progress = true
|
||||
if not _save_manager.delete_progression_save():
|
||||
_feedback_label.text = "Failed to delete saved progression."
|
||||
_feedback_label.text = "failed to delete saved progression."
|
||||
_action_in_progress = false
|
||||
_refresh_save_inspection()
|
||||
return
|
||||
|
|
@ -158,7 +439,7 @@ func _on_confirmation_accepted() -> void:
|
|||
if action == ConfirmationAction.NEW_GAME:
|
||||
gameplay_requested.emit()
|
||||
else:
|
||||
_feedback_label.text = "Saved progression deleted."
|
||||
_feedback_label.text = "saved progression deleted."
|
||||
_action_in_progress = false
|
||||
|
||||
|
||||
|
|
@ -167,6 +448,7 @@ func _open_confirmation(
|
|||
message: String,
|
||||
confirm_text: String,
|
||||
) -> void:
|
||||
_modal_restore_navigation_focus = _navigation_focus_active
|
||||
_confirmation_action = action
|
||||
_confirmation_text.text = message
|
||||
_confirm_button.text = confirm_text
|
||||
|
|
@ -175,14 +457,23 @@ func _open_confirmation(
|
|||
|
||||
|
||||
func _close_confirmation() -> void:
|
||||
var restore_navigation_focus: bool = _modal_restore_navigation_focus
|
||||
_modal_restore_navigation_focus = false
|
||||
_confirmation_action = ConfirmationAction.NONE
|
||||
_confirmation_panel.visible = false
|
||||
if _awaiting_start_input:
|
||||
return
|
||||
if restore_navigation_focus:
|
||||
_focus_initial_button()
|
||||
else:
|
||||
_navigation_focus_active = false
|
||||
_release_title_focus()
|
||||
|
||||
|
||||
func _open_settings() -> void:
|
||||
if _confirmation_panel.visible or _action_in_progress:
|
||||
return
|
||||
_modal_restore_navigation_focus = _navigation_focus_active
|
||||
_feedback_label.text = ""
|
||||
_settings_panel.open_panel(_settings_manager)
|
||||
|
||||
|
|
@ -192,12 +483,23 @@ func _close_settings() -> void:
|
|||
|
||||
|
||||
func _on_settings_applied() -> void:
|
||||
_feedback_label.text = "Settings saved."
|
||||
_settings_button.grab_focus()
|
||||
_feedback_label.text = "settings saved."
|
||||
_restore_settings_focus()
|
||||
|
||||
|
||||
func _on_settings_closed() -> void:
|
||||
_restore_settings_focus()
|
||||
|
||||
|
||||
func _restore_settings_focus() -> void:
|
||||
var restore_navigation_focus: bool = _modal_restore_navigation_focus
|
||||
_modal_restore_navigation_focus = false
|
||||
if restore_navigation_focus:
|
||||
_navigation_focus_active = true
|
||||
_settings_button.grab_focus()
|
||||
else:
|
||||
_navigation_focus_active = false
|
||||
_release_title_focus()
|
||||
|
||||
|
||||
func _refresh_save_inspection() -> void:
|
||||
|
|
@ -217,12 +519,18 @@ func _refresh_save_inspection() -> void:
|
|||
|
||||
|
||||
func _focus_initial_button() -> void:
|
||||
if not is_node_ready() or not visible:
|
||||
if (
|
||||
not is_node_ready()
|
||||
or not visible
|
||||
or _awaiting_start_input
|
||||
or not _button_center.visible
|
||||
):
|
||||
return
|
||||
if not _continue_button.disabled:
|
||||
_continue_button.grab_focus()
|
||||
else:
|
||||
_new_game_button.grab_focus()
|
||||
_navigation_focus_active = true
|
||||
|
||||
|
||||
func _on_quit_pressed() -> void:
|
||||
|
|
@ -232,3 +540,430 @@ func _on_quit_pressed() -> void:
|
|||
and not _settings_panel.visible
|
||||
):
|
||||
quit_requested.emit()
|
||||
|
||||
|
||||
func _on_title_visibility_changed() -> void:
|
||||
if not _decorative_presentation_ready:
|
||||
return
|
||||
if visible:
|
||||
_start_decorative_presentation()
|
||||
else:
|
||||
_stop_entry_prompt_animation()
|
||||
_navigation_focus_active = false
|
||||
_modal_restore_navigation_focus = false
|
||||
_stop_decorative_presentation()
|
||||
|
||||
|
||||
func _start_decorative_presentation() -> void:
|
||||
if (
|
||||
not _decorative_presentation_ready
|
||||
or not visible
|
||||
or _decorative_presentation_active
|
||||
):
|
||||
return
|
||||
_decorative_generation += 1
|
||||
_decorative_presentation_active = true
|
||||
_schedule_next_decorative_fish(
|
||||
FIRST_FISH_DELAY_MIN,
|
||||
FIRST_FISH_DELAY_MAX
|
||||
)
|
||||
_schedule_next_decorative_bubble_event(
|
||||
BUBBLE_EVENT_DELAY_MIN,
|
||||
BUBBLE_EVENT_DELAY_MAX
|
||||
)
|
||||
|
||||
|
||||
func _stop_decorative_presentation() -> void:
|
||||
_decorative_generation += 1
|
||||
_decorative_presentation_active = false
|
||||
_decorative_fish_timer.stop()
|
||||
if _decorative_fish_tween != null:
|
||||
_decorative_fish_tween.kill()
|
||||
_decorative_fish_tween = null
|
||||
if is_instance_valid(_decorative_fish):
|
||||
_decorative_fish.queue_free()
|
||||
_decorative_fish = null
|
||||
_decorative_bubble_event_timer.stop()
|
||||
_decorative_bubble_cluster_timer.stop()
|
||||
_pending_cluster_bubbles = 0
|
||||
for bubble_tween: Tween in _decorative_bubble_tweens.values():
|
||||
if bubble_tween != null:
|
||||
bubble_tween.kill()
|
||||
_decorative_bubble_tweens.clear()
|
||||
for bubble: TextureRect in _decorative_bubbles:
|
||||
if is_instance_valid(bubble):
|
||||
bubble.queue_free()
|
||||
_decorative_bubbles.clear()
|
||||
|
||||
|
||||
func _schedule_next_decorative_fish(
|
||||
minimum_delay: float,
|
||||
maximum_delay: float,
|
||||
) -> void:
|
||||
if not _decorative_presentation_active or not visible:
|
||||
return
|
||||
_decorative_fish_timer.start(
|
||||
_decorative_rng.randf_range(minimum_delay, maximum_delay)
|
||||
)
|
||||
|
||||
|
||||
func _on_decorative_fish_timer_timeout() -> void:
|
||||
if (
|
||||
not _decorative_presentation_active
|
||||
or not visible
|
||||
or is_instance_valid(_decorative_fish)
|
||||
):
|
||||
return
|
||||
_spawn_decorative_fish(_decorative_generation)
|
||||
|
||||
|
||||
func _spawn_decorative_fish(generation: int) -> void:
|
||||
if (
|
||||
generation != _decorative_generation
|
||||
or not _decorative_presentation_active
|
||||
or not visible
|
||||
or DECORATIVE_FISH_TEXTURES.is_empty()
|
||||
):
|
||||
return
|
||||
var layer_size: Vector2 = _decorative_fish_layer.size
|
||||
if layer_size.x <= 1.0 or layer_size.y <= 1.0:
|
||||
_schedule_next_decorative_fish(0.5, 1.0)
|
||||
return
|
||||
var texture: Texture2D = DECORATIVE_FISH_TEXTURES[
|
||||
_decorative_rng.randi_range(0, DECORATIVE_FISH_TEXTURES.size() - 1)
|
||||
]
|
||||
var source_size: Vector2 = texture.get_size()
|
||||
if source_size.x <= 0.0 or source_size.y <= 0.0:
|
||||
_schedule_next_decorative_fish(
|
||||
NEXT_FISH_DELAY_MIN,
|
||||
NEXT_FISH_DELAY_MAX
|
||||
)
|
||||
return
|
||||
var longest_side: float = _decorative_rng.randf_range(
|
||||
FISH_LONGEST_SIDE_MIN,
|
||||
FISH_LONGEST_SIDE_MAX
|
||||
)
|
||||
var presentation_scale: float = longest_side / maxf(
|
||||
source_size.x,
|
||||
source_size.y
|
||||
)
|
||||
var presentation_size: Vector2 = source_size * presentation_scale
|
||||
var fish_control := TextureRect.new()
|
||||
fish_control.name = "DecorativeFish"
|
||||
fish_control.texture = texture
|
||||
fish_control.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
fish_control.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
fish_control.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
fish_control.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
fish_control.custom_minimum_size = presentation_size
|
||||
fish_control.size = presentation_size
|
||||
fish_control.modulate.a = 0.46
|
||||
|
||||
var direction: float = (
|
||||
1.0
|
||||
if _decorative_rng.randi_range(0, 1) == 1
|
||||
else -1.0
|
||||
)
|
||||
fish_control.flip_h = direction > 0.0
|
||||
var minimum_y: float = maxf(24.0, layer_size.y * 0.08)
|
||||
var maximum_y: float = maxf(
|
||||
minimum_y,
|
||||
layer_size.y - presentation_size.y - maxf(24.0, layer_size.y * 0.08)
|
||||
)
|
||||
var base_y: float = _decorative_rng.randf_range(minimum_y, maximum_y)
|
||||
var bob_amplitude: float = _decorative_rng.randf_range(3.0, 8.0)
|
||||
var bob_speed: float = _decorative_rng.randf_range(0.65, 1.15)
|
||||
var crossing_duration: float = _decorative_rng.randf_range(
|
||||
CROSSING_DURATION_MIN,
|
||||
CROSSING_DURATION_MAX
|
||||
)
|
||||
fish_control.position = Vector2(
|
||||
-presentation_size.x - FISH_EDGE_MARGIN
|
||||
if direction > 0.0
|
||||
else layer_size.x + FISH_EDGE_MARGIN,
|
||||
base_y
|
||||
)
|
||||
_decorative_fish_layer.add_child(fish_control)
|
||||
_decorative_fish = fish_control
|
||||
_decorative_fish_tween = create_tween()
|
||||
_decorative_fish_tween.tween_method(
|
||||
_update_decorative_fish.bind(
|
||||
fish_control,
|
||||
direction,
|
||||
base_y,
|
||||
bob_amplitude,
|
||||
bob_speed,
|
||||
crossing_duration,
|
||||
generation
|
||||
),
|
||||
0.0,
|
||||
1.0,
|
||||
crossing_duration
|
||||
)
|
||||
_decorative_fish_tween.finished.connect(
|
||||
_on_decorative_fish_finished.bind(fish_control, generation),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
|
||||
|
||||
func _update_decorative_fish(
|
||||
progress: float,
|
||||
fish_control: TextureRect,
|
||||
direction: float,
|
||||
base_y: float,
|
||||
bob_amplitude: float,
|
||||
bob_speed: float,
|
||||
crossing_duration: float,
|
||||
generation: int,
|
||||
) -> void:
|
||||
if (
|
||||
generation != _decorative_generation
|
||||
or not _decorative_presentation_active
|
||||
or not is_instance_valid(fish_control)
|
||||
):
|
||||
return
|
||||
var start_x: float = (
|
||||
-fish_control.size.x - FISH_EDGE_MARGIN
|
||||
if direction > 0.0
|
||||
else _decorative_fish_layer.size.x + FISH_EDGE_MARGIN
|
||||
)
|
||||
var end_x: float = (
|
||||
_decorative_fish_layer.size.x + FISH_EDGE_MARGIN
|
||||
if direction > 0.0
|
||||
else -fish_control.size.x - FISH_EDGE_MARGIN
|
||||
)
|
||||
fish_control.position.x = lerpf(start_x, end_x, progress)
|
||||
var bobbed_y: float = (
|
||||
base_y
|
||||
+ sin(progress * crossing_duration * bob_speed) * bob_amplitude
|
||||
)
|
||||
fish_control.position.y = clampf(
|
||||
bobbed_y,
|
||||
8.0,
|
||||
maxf(8.0, _decorative_fish_layer.size.y - fish_control.size.y - 8.0)
|
||||
)
|
||||
|
||||
|
||||
func _on_decorative_fish_finished(
|
||||
fish_control: TextureRect,
|
||||
generation: int,
|
||||
) -> void:
|
||||
if generation != _decorative_generation:
|
||||
return
|
||||
_decorative_fish_tween = null
|
||||
if is_instance_valid(fish_control):
|
||||
fish_control.queue_free()
|
||||
if _decorative_fish == fish_control:
|
||||
_decorative_fish = null
|
||||
_schedule_next_decorative_fish(
|
||||
NEXT_FISH_DELAY_MIN,
|
||||
NEXT_FISH_DELAY_MAX
|
||||
)
|
||||
|
||||
|
||||
func _schedule_next_decorative_bubble_event(
|
||||
minimum_delay: float,
|
||||
maximum_delay: float,
|
||||
) -> void:
|
||||
if not _decorative_presentation_active or not visible:
|
||||
return
|
||||
_decorative_bubble_event_timer.start(
|
||||
_decorative_rng.randf_range(minimum_delay, maximum_delay)
|
||||
)
|
||||
|
||||
|
||||
func _on_decorative_bubble_event_timer_timeout() -> void:
|
||||
if not _decorative_presentation_active or not visible:
|
||||
return
|
||||
var available_slots: int = (
|
||||
MAX_DECORATIVE_BUBBLES - _decorative_bubbles.size()
|
||||
)
|
||||
if available_slots <= 0:
|
||||
_schedule_next_decorative_bubble_event(
|
||||
BUBBLE_EVENT_DELAY_MIN,
|
||||
BUBBLE_EVENT_DELAY_MAX
|
||||
)
|
||||
return
|
||||
var event_size: int = _choose_decorative_bubble_event_size()
|
||||
event_size = mini(event_size, available_slots)
|
||||
if not _spawn_decorative_bubble(_decorative_generation):
|
||||
_schedule_next_decorative_bubble_event(0.5, 1.0)
|
||||
return
|
||||
_pending_cluster_bubbles = event_size - 1
|
||||
if _pending_cluster_bubbles > 0:
|
||||
_start_decorative_bubble_cluster_timer()
|
||||
else:
|
||||
_schedule_next_decorative_bubble_event(
|
||||
BUBBLE_EVENT_DELAY_MIN,
|
||||
BUBBLE_EVENT_DELAY_MAX
|
||||
)
|
||||
|
||||
|
||||
func _choose_decorative_bubble_event_size() -> int:
|
||||
var roll: float = _decorative_rng.randf()
|
||||
if roll < 0.75:
|
||||
return 1
|
||||
if roll < 0.95:
|
||||
return 2
|
||||
return 3
|
||||
|
||||
|
||||
func _start_decorative_bubble_cluster_timer() -> void:
|
||||
_decorative_bubble_cluster_timer.start(
|
||||
_decorative_rng.randf_range(
|
||||
BUBBLE_CLUSTER_DELAY_MIN,
|
||||
BUBBLE_CLUSTER_DELAY_MAX
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
func _on_decorative_bubble_cluster_timer_timeout() -> void:
|
||||
if (
|
||||
not _decorative_presentation_active
|
||||
or not visible
|
||||
or _pending_cluster_bubbles <= 0
|
||||
):
|
||||
_pending_cluster_bubbles = 0
|
||||
return
|
||||
if _decorative_bubbles.size() < MAX_DECORATIVE_BUBBLES:
|
||||
_spawn_decorative_bubble(_decorative_generation)
|
||||
_pending_cluster_bubbles -= 1
|
||||
if _pending_cluster_bubbles > 0:
|
||||
_start_decorative_bubble_cluster_timer()
|
||||
else:
|
||||
_schedule_next_decorative_bubble_event(
|
||||
BUBBLE_EVENT_DELAY_MIN,
|
||||
BUBBLE_EVENT_DELAY_MAX
|
||||
)
|
||||
|
||||
|
||||
func _spawn_decorative_bubble(generation: int) -> bool:
|
||||
if (
|
||||
generation != _decorative_generation
|
||||
or not _decorative_presentation_active
|
||||
or not visible
|
||||
or _decorative_bubbles.size() >= MAX_DECORATIVE_BUBBLES
|
||||
or DECORATIVE_BUBBLE_TEXTURES.is_empty()
|
||||
):
|
||||
return false
|
||||
var layer_size: Vector2 = _decorative_bubble_layer.size
|
||||
if layer_size.x <= 1.0 or layer_size.y <= 1.0:
|
||||
return false
|
||||
var texture: Texture2D = DECORATIVE_BUBBLE_TEXTURES[
|
||||
_decorative_rng.randi_range(
|
||||
0,
|
||||
DECORATIVE_BUBBLE_TEXTURES.size() - 1
|
||||
)
|
||||
]
|
||||
var presentation_scale: float = _decorative_rng.randf_range(
|
||||
BUBBLE_SCALE_MIN,
|
||||
BUBBLE_SCALE_MAX
|
||||
)
|
||||
var presentation_size: Vector2 = texture.get_size() * presentation_scale
|
||||
var bubble := TextureRect.new()
|
||||
bubble.name = "DecorativeBubble"
|
||||
bubble.texture = texture
|
||||
bubble.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
bubble.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
bubble.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
bubble.size = presentation_size
|
||||
bubble.modulate.a = _decorative_rng.randf_range(
|
||||
BUBBLE_OPACITY_MIN,
|
||||
BUBBLE_OPACITY_MAX
|
||||
)
|
||||
_decorative_bubble_layer.add_child(bubble)
|
||||
_decorative_bubbles.append(bubble)
|
||||
|
||||
var normalized_x: float = _decorative_rng.randf_range(0.08, 0.92)
|
||||
var drift_direction: float = (
|
||||
1.0 if _decorative_rng.randi_range(0, 1) == 1 else -1.0
|
||||
)
|
||||
var horizontal_drift: float = (
|
||||
_decorative_rng.randf_range(
|
||||
BUBBLE_DRIFT_MIN,
|
||||
BUBBLE_DRIFT_MAX
|
||||
)
|
||||
* drift_direction
|
||||
)
|
||||
var wobble_amplitude: float = _decorative_rng.randf_range(
|
||||
BUBBLE_WOBBLE_MIN,
|
||||
BUBBLE_WOBBLE_MAX
|
||||
)
|
||||
var wobble_cycles: float = _decorative_rng.randf_range(0.8, 1.6)
|
||||
var wobble_phase: float = _decorative_rng.randf_range(0.0, TAU)
|
||||
var travel_duration: float = _decorative_rng.randf_range(
|
||||
BUBBLE_TRAVEL_DURATION_MIN,
|
||||
BUBBLE_TRAVEL_DURATION_MAX
|
||||
)
|
||||
var bubble_tween: Tween = create_tween()
|
||||
_decorative_bubble_tweens[bubble.get_instance_id()] = bubble_tween
|
||||
bubble_tween.tween_method(
|
||||
_update_decorative_bubble.bind(
|
||||
bubble,
|
||||
normalized_x,
|
||||
horizontal_drift,
|
||||
wobble_amplitude,
|
||||
wobble_cycles,
|
||||
wobble_phase,
|
||||
generation
|
||||
),
|
||||
0.0,
|
||||
1.0,
|
||||
travel_duration
|
||||
)
|
||||
bubble_tween.finished.connect(
|
||||
_on_decorative_bubble_finished.bind(bubble, generation),
|
||||
CONNECT_ONE_SHOT
|
||||
)
|
||||
return true
|
||||
|
||||
|
||||
func _update_decorative_bubble(
|
||||
progress: float,
|
||||
bubble: TextureRect,
|
||||
normalized_x: float,
|
||||
horizontal_drift: float,
|
||||
wobble_amplitude: float,
|
||||
wobble_cycles: float,
|
||||
wobble_phase: float,
|
||||
generation: int,
|
||||
) -> void:
|
||||
if (
|
||||
generation != _decorative_generation
|
||||
or not _decorative_presentation_active
|
||||
or not is_instance_valid(bubble)
|
||||
):
|
||||
return
|
||||
var layer_size: Vector2 = _decorative_bubble_layer.size
|
||||
var start_y: float = (
|
||||
layer_size.y + bubble.size.y + BUBBLE_EDGE_MARGIN
|
||||
)
|
||||
var end_y: float = -bubble.size.y - BUBBLE_EDGE_MARGIN
|
||||
var base_x: float = normalized_x * layer_size.x
|
||||
var wobble: float = sin(
|
||||
wobble_phase + progress * TAU * wobble_cycles
|
||||
) * wobble_amplitude
|
||||
bubble.position = Vector2(
|
||||
base_x
|
||||
+ horizontal_drift * progress
|
||||
+ wobble
|
||||
- bubble.size.x * 0.5,
|
||||
lerpf(start_y, end_y, progress)
|
||||
)
|
||||
|
||||
|
||||
func _on_decorative_bubble_finished(
|
||||
bubble: TextureRect,
|
||||
generation: int,
|
||||
) -> void:
|
||||
if generation != _decorative_generation:
|
||||
return
|
||||
if is_instance_valid(bubble):
|
||||
_decorative_bubble_tweens.erase(bubble.get_instance_id())
|
||||
_decorative_bubbles.erase(bubble)
|
||||
bubble.queue_free()
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_stop_decorative_presentation()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
[gd_scene load_steps=4 format=3]
|
||||
[gd_scene load_steps=9 format=3]
|
||||
|
||||
[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="PackedScene" path="res://ui/settings_panel.tscn" id="3_settings"]
|
||||
[ext_resource type="Shader" path="res://ui/title_water_background.gdshader" id="4_water_shader"]
|
||||
[ext_resource type="Texture2D" path="res://ui/assets/title/netfishing_logo.png" id="5_logo"]
|
||||
[ext_resource type="Shader" path="res://ui/title_logo_underwater.gdshader" id="6_logo_shader"]
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_water"]
|
||||
shader = ExtResource("4_water_shader")
|
||||
|
||||
[sub_resource type="ShaderMaterial" id="ShaderMaterial_title_logo"]
|
||||
shader = ExtResource("6_logo_shader")
|
||||
|
||||
[node name="TitleScreen" type="Control"]
|
||||
unique_name_in_owner = true
|
||||
|
|
@ -18,15 +27,78 @@ theme = ExtResource("2_theme")
|
|||
script = ExtResource("1_script")
|
||||
|
||||
[node name="Background" type="ColorRect" parent="."]
|
||||
material = SubResource("ShaderMaterial_title_water")
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
color = Color(0.047, 0.102, 0.145, 1)
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="DecorativeFishLayer" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
z_index = 1
|
||||
clip_contents = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="DecorativeFishTimer" type="Timer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
one_shot = true
|
||||
|
||||
[node name="DecorativeBubbleLayer" type="Control" parent="."]
|
||||
unique_name_in_owner = true
|
||||
z_index = 2
|
||||
clip_contents = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="DecorativeBubbleEventTimer" type="Timer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
one_shot = true
|
||||
|
||||
[node name="DecorativeBubbleClusterTimer" type="Timer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
one_shot = true
|
||||
|
||||
[node name="StartPromptCenter" type="CenterContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
z_index = 12
|
||||
layout_mode = 1
|
||||
anchor_top = 1.0
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
offset_top = -104.0
|
||||
offset_bottom = -48.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 0
|
||||
mouse_filter = 2
|
||||
|
||||
[node name="StartPromptLabel" type="Label" parent="StartPromptCenter"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
mouse_filter = 2
|
||||
theme_override_colors/font_color = Color(1, 1, 1, 1)
|
||||
theme_override_colors/font_outline_color = Color(0.02, 0.075, 0.11, 0.9)
|
||||
theme_override_constants/outline_size = 3
|
||||
theme_override_font_sizes/font_size = 26
|
||||
text = "press any key to start"
|
||||
horizontal_alignment = 1
|
||||
vertical_alignment = 1
|
||||
|
||||
[node name="Center" type="CenterContainer" parent="."]
|
||||
z_index = 10
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
|
|
@ -34,75 +106,77 @@ anchor_bottom = 1.0
|
|||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="MainPanel" type="PanelContainer" parent="Center"]
|
||||
custom_minimum_size = Vector2(440, 0)
|
||||
[node name="MainContent" type="VBoxContainer" parent="Center"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
alignment = 1
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="Center/MainPanel"]
|
||||
[node name="TitleLogo" type="TextureRect" parent="Center/MainContent"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(640, 190)
|
||||
material = SubResource("ShaderMaterial_title_logo")
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 36
|
||||
theme_override_constants/margin_top = 30
|
||||
theme_override_constants/margin_right = 36
|
||||
theme_override_constants/margin_bottom = 30
|
||||
mouse_filter = 2
|
||||
texture_filter = 1
|
||||
texture = ExtResource("5_logo")
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
|
||||
[node name="Content" type="VBoxContainer" parent="Center/MainPanel/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="Title" type="Label" parent="Center/MainPanel/Margin/Content"]
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 36
|
||||
text = "NETFISHING"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Subtitle" type="Label" parent="Center/MainPanel/Margin/Content"]
|
||||
layout_mode = 2
|
||||
text = "A cozy social fishing game"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="PlaytestLabel" type="Label" parent="Center/MainPanel/Margin/Content"]
|
||||
[node name="PlaytestLabel" type="Label" parent="Center/MainContent"]
|
||||
layout_mode = 2
|
||||
theme_override_colors/font_color = Color(0.682, 0.733, 0.761, 1)
|
||||
theme_override_font_sizes/font_size = 13
|
||||
text = "Pre-Alpha Playtest 0.1"
|
||||
text = "pre-alpha playtest 0.1"
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="Spacer" type="Control" parent="Center/MainPanel/Margin/Content"]
|
||||
custom_minimum_size = Vector2(0, 12)
|
||||
[node name="Spacer" type="Control" parent="Center/MainContent"]
|
||||
custom_minimum_size = Vector2(0, 4)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ContinueButton" type="Button" parent="Center/MainPanel/Margin/Content"]
|
||||
[node name="ButtonCenter" type="CenterContainer" parent="Center/MainContent"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ButtonStack" type="VBoxContainer" parent="Center/MainContent/ButtonCenter"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(360, 0)
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 8
|
||||
|
||||
[node name="ContinueButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
text = "Continue"
|
||||
text = "continue"
|
||||
|
||||
[node name="NewGameButton" type="Button" parent="Center/MainPanel/Margin/Content"]
|
||||
[node name="NewGameButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
text = "New Game"
|
||||
text = "new game"
|
||||
|
||||
[node name="SettingsButton" type="Button" parent="Center/MainPanel/Margin/Content"]
|
||||
[node name="SettingsButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
text = "Settings"
|
||||
text = "settings"
|
||||
|
||||
[node name="DeleteSaveButton" type="Button" parent="Center/MainPanel/Margin/Content"]
|
||||
[node name="DeleteSaveButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
text = "Delete Save"
|
||||
text = "delete save"
|
||||
|
||||
[node name="QuitButton" type="Button" parent="Center/MainPanel/Margin/Content"]
|
||||
[node name="QuitButton" type="Button" parent="Center/MainContent/ButtonCenter/ButtonStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
layout_mode = 2
|
||||
text = "Quit"
|
||||
text = "quit"
|
||||
|
||||
[node name="FeedbackLabel" type="Label" parent="Center/MainPanel/Margin/Content"]
|
||||
[node name="FeedbackLabel" type="Label" parent="Center/MainContent"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(0, 42)
|
||||
layout_mode = 2
|
||||
text = ""
|
||||
|
|
@ -141,7 +215,7 @@ theme_override_constants/separation = 14
|
|||
[node name="ConfirmationText" type="Label" parent="ConfirmationPanel/Margin/Content"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Confirm?"
|
||||
text = "confirm?"
|
||||
horizontal_alignment = 1
|
||||
autowrap_mode = 2
|
||||
|
||||
|
|
@ -154,13 +228,13 @@ alignment = 1
|
|||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(150, 42)
|
||||
layout_mode = 2
|
||||
text = "Confirm"
|
||||
text = "confirm"
|
||||
|
||||
[node name="CancelConfirmButton" type="Button" parent="ConfirmationPanel/Margin/Content/Buttons"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(120, 42)
|
||||
layout_mode = 2
|
||||
text = "Cancel"
|
||||
text = "cancel"
|
||||
|
||||
[node name="SettingsPanel" parent="." instance=ExtResource("3_settings")]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
95
ui/title_water_background.gdshader
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
shader_type canvas_item;
|
||||
|
||||
uniform vec4 surface_color : source_color = vec4(0.10, 0.45, 0.62, 1.0);
|
||||
uniform vec4 middle_color : source_color = vec4(0.055, 0.285, 0.40, 1.0);
|
||||
uniform vec4 depth_color : source_color = vec4(0.02, 0.16, 0.27, 1.0);
|
||||
uniform vec2 virtual_pixel_density = vec2(320.0, 180.0);
|
||||
uniform float color_step_count : hint_range(2.0, 16.0, 1.0) = 14.0;
|
||||
uniform float continuous_depth_blend : hint_range(0.0, 0.25, 0.01) = 0.12;
|
||||
uniform float wave_speed : hint_range(0.0, 0.5, 0.01) = 0.10;
|
||||
uniform float wave_scale : hint_range(0.5, 8.0, 0.1) = 2.4;
|
||||
uniform float wave_strength : hint_range(0.0, 0.05, 0.001) = 0.012;
|
||||
uniform float caustic_strength : hint_range(0.0, 0.08, 0.001) = 0.018;
|
||||
|
||||
void fragment() {
|
||||
vec2 grid_size = max(virtual_pixel_density, vec2(1.0));
|
||||
vec2 pixel_uv = (floor(UV * grid_size) + vec2(0.5)) / grid_size;
|
||||
float motion = TIME * wave_speed;
|
||||
|
||||
float broad_wave = sin(
|
||||
pixel_uv.x * wave_scale + motion
|
||||
);
|
||||
float secondary_wave = sin(
|
||||
pixel_uv.x * 1.1 + pixel_uv.y * 1.6 - motion * 0.65
|
||||
);
|
||||
float movement_falloff = 1.0 - pixel_uv.y * 0.75;
|
||||
float depth_displacement = (
|
||||
(broad_wave * 0.65 + secondary_wave * 0.35)
|
||||
* wave_strength
|
||||
* movement_falloff
|
||||
);
|
||||
float continuous_depth = clamp(
|
||||
pixel_uv.y
|
||||
+ depth_displacement,
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
float depth_steps = max(2.0, floor(color_step_count));
|
||||
float band_index = floor(
|
||||
continuous_depth * (depth_steps - 1.0) + 0.5
|
||||
);
|
||||
float band_depth = band_index / (depth_steps - 1.0);
|
||||
float display_depth = mix(
|
||||
band_depth,
|
||||
continuous_depth,
|
||||
clamp(continuous_depth_blend, 0.0, 0.25)
|
||||
);
|
||||
|
||||
vec3 water_color;
|
||||
if (display_depth < 0.5) {
|
||||
water_color = mix(
|
||||
surface_color.rgb,
|
||||
middle_color.rgb,
|
||||
display_depth * 2.0
|
||||
);
|
||||
} else {
|
||||
water_color = mix(
|
||||
middle_color.rgb,
|
||||
depth_color.rgb,
|
||||
(display_depth - 0.5) * 2.0
|
||||
);
|
||||
}
|
||||
|
||||
float caustic_wave = clamp(
|
||||
(
|
||||
sin(
|
||||
pixel_uv.x * 4.0
|
||||
+ pixel_uv.y * 1.2
|
||||
+ motion * 0.7
|
||||
)
|
||||
* sin(
|
||||
pixel_uv.x * 1.7
|
||||
- pixel_uv.y * 2.2
|
||||
- motion * 0.55
|
||||
)
|
||||
- 0.25
|
||||
) / 0.75,
|
||||
0.0,
|
||||
1.0
|
||||
);
|
||||
float caustic_level = floor(
|
||||
caustic_wave * 3.0
|
||||
) / 3.0;
|
||||
float upper_water_mask = 1.0 - smoothstep(
|
||||
0.18,
|
||||
0.34,
|
||||
pixel_uv.y
|
||||
);
|
||||
water_color += (
|
||||
vec3(0.05, 0.12, 0.14)
|
||||
* caustic_level
|
||||
* upper_water_mask
|
||||
* caustic_strength
|
||||
);
|
||||
COLOR = vec4(clamp(water_color, vec3(0.0), vec3(1.0)), 1.0);
|
||||
}
|
||||
1
ui/title_water_background.gdshader.uid
Normal file
|
|
@ -0,0 +1 @@
|
|||
uid://c8s1g7tn771du
|
||||
|
|
@ -60,7 +60,7 @@ material_override = SubResource("PrimaryMaterial")
|
|||
|
||||
[node name="ShopName" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(0, 3.7, 0)
|
||||
text = "Fishing Shop"
|
||||
text = "fishing shop"
|
||||
font_size = 42
|
||||
outline_size = 9
|
||||
billboard = 1
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ material_override = SubResource("WoodMaterial")
|
|||
|
||||
[node name="Sign" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(0, 2.55, 0)
|
||||
text = "Sell to Pelicans\nfrom the Cooler"
|
||||
text = "sell to pelicans\nfrom the cooler"
|
||||
font_size = 30
|
||||
outline_size = 8
|
||||
billboard = 1
|
||||
|
|
@ -43,7 +43,7 @@ no_depth_test = true
|
|||
|
||||
[node name="ConvenienceLabel" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(0, 1.65, -1.15)
|
||||
text = "Available anywhere • 0.25x"
|
||||
text = "available anywhere • 0.25x"
|
||||
font_size = 20
|
||||
outline_size = 6
|
||||
billboard = 1
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ mesh = SubResource("CraneArmMesh")
|
|||
material_override = SubResource("BeaconMaterial")
|
||||
[node name="CoastLabel" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(25, 3.2, 23)
|
||||
text = "COAST"
|
||||
text = "coast"
|
||||
font_size = 38
|
||||
outline_size = 8
|
||||
billboard = 1
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ mesh = SubResource("DockRampMesh")
|
|||
material_override = SubResource("DockMaterial")
|
||||
[node name="LakeLabel" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(-25, 3, 20)
|
||||
text = "LAKE"
|
||||
text = "lake"
|
||||
font_size = 38
|
||||
outline_size = 8
|
||||
billboard = 1
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ mesh = SubResource("ReedMesh")
|
|||
material_override = SubResource("ReedMaterial")
|
||||
[node name="MarshLabel" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(21, 3, 17)
|
||||
text = "MARSH"
|
||||
text = "marsh"
|
||||
font_size = 36
|
||||
outline_size = 8
|
||||
billboard = 1
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ mesh = SubResource("RailMesh")
|
|||
material_override = SubResource("RailMaterial")
|
||||
[node name="RiverLabel" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(-16, 3, 20)
|
||||
text = "RIVER"
|
||||
text = "river"
|
||||
font_size = 34
|
||||
outline_size = 7
|
||||
billboard = 1
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ mesh = SubResource("WaterMesh")
|
|||
material_override = SubResource("WaterMaterial")
|
||||
[node name="PondLabel" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(-14, 2.4, 11)
|
||||
text = "POND"
|
||||
text = "pond"
|
||||
font_size = 34
|
||||
outline_size = 7
|
||||
billboard = 1
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ material_override = SubResource("RoofMaterial")
|
|||
|
||||
[node name="VillageLabel" type="Label3D" parent="Visuals"]
|
||||
position = Vector3(0, 3.2, 7)
|
||||
text = "VILLAGE"
|
||||
text = "village"
|
||||
font_size = 42
|
||||
outline_size = 8
|
||||
billboard = 1
|
||||
|
|
|
|||