Add expanded fishing gear and presentation
This commit is contained in:
parent
551db3ccc6
commit
72d3a303f5
29 changed files with 783 additions and 86 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -6,6 +6,8 @@ const ItemDataType = preload("res://items/item_data.gd")
|
|||
const PlayerBagType = preload("res://inventory/player_bag.gd")
|
||||
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
||||
const WORM_MAX_STACK: int = 10
|
||||
const FISH_FINDER_ID: StringName = &"fish_finder"
|
||||
const BATTERIES_ID: StringName = &"batteries"
|
||||
|
||||
const ITEM_PRICES: Dictionary[StringName, int] = {
|
||||
&"worms": 1,
|
||||
|
|
@ -15,10 +17,12 @@ const ITEM_PRICES: Dictionary[StringName, int] = {
|
|||
&"whole_anchovy": 30,
|
||||
&"whole_sardine": 60,
|
||||
&"luminous_roe": 125,
|
||||
&"the_standby": 750,
|
||||
&"coffee": 20,
|
||||
&"energy_drink": 35,
|
||||
&"snack": 30,
|
||||
&"fish_finder": 60,
|
||||
FISH_FINDER_ID: 500,
|
||||
BATTERIES_ID: 15,
|
||||
}
|
||||
const BAIT_UNLOCK_PRICES: Dictionary[StringName, int] = {
|
||||
&"snails": 400,
|
||||
|
|
@ -36,10 +40,12 @@ const ITEM_ORDER: Array[StringName] = [
|
|||
&"whole_anchovy",
|
||||
&"whole_sardine",
|
||||
&"luminous_roe",
|
||||
&"the_standby",
|
||||
FISH_FINDER_ID,
|
||||
&"coffee",
|
||||
&"energy_drink",
|
||||
&"snack",
|
||||
&"fish_finder",
|
||||
BATTERIES_ID,
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -69,6 +75,20 @@ static func is_bait_topoff(item_id: StringName) -> bool:
|
|||
return item_id == &"worms" or BAIT_UNLOCK_PRICES.has(item_id)
|
||||
|
||||
|
||||
static func is_permanent_unlock(
|
||||
item_id: StringName,
|
||||
item: ItemDataType,
|
||||
) -> bool:
|
||||
return (
|
||||
item != null
|
||||
and (item.is_lure() or item_id == FISH_FINDER_ID)
|
||||
)
|
||||
|
||||
|
||||
static func is_passive_supply(item_id: StringName) -> bool:
|
||||
return item_id == BATTERIES_ID
|
||||
|
||||
|
||||
static func get_purchase_cost(
|
||||
item_id: StringName,
|
||||
quantity: int,
|
||||
|
|
@ -92,6 +112,7 @@ static func purchase_one(
|
|||
return false
|
||||
var item: ItemDataType = catalog.get_item_by_id(item_id)
|
||||
var bait_topoff: bool = is_bait_topoff(item_id)
|
||||
var permanent_unlock: bool = is_permanent_unlock(item_id, item)
|
||||
var quantity: int = get_purchase_quantity(
|
||||
item_id,
|
||||
bag.get_quantity(item_id),
|
||||
|
|
@ -106,9 +127,18 @@ static func purchase_one(
|
|||
price < 0
|
||||
or item == null
|
||||
or not item.is_valid()
|
||||
or (item.category != ItemDataType.Category.CONSUMABLE and not bait_topoff)
|
||||
or not item.stackable
|
||||
or (not item.usable and not bait_topoff)
|
||||
or (
|
||||
item.category != ItemDataType.Category.CONSUMABLE
|
||||
and not bait_topoff
|
||||
and not permanent_unlock
|
||||
)
|
||||
or (not item.stackable and not permanent_unlock)
|
||||
or (
|
||||
not item.usable
|
||||
and not bait_topoff
|
||||
and not permanent_unlock
|
||||
and not is_passive_supply(item_id)
|
||||
)
|
||||
or not bag.can_add_item(item_id, quantity)
|
||||
or not wallet.can_afford(price)
|
||||
):
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ const WORM_ROLL_WEIGHTS: Array[float] = [80.0, 10.0, 5.0, 4.0, 1.0]
|
|||
const SNAIL_ROLL_WEIGHTS: Array[float] = [68.0, 16.0, 8.0, 6.0, 2.0]
|
||||
const SHRIMP_ROLL_WEIGHTS: Array[float] = [56.0, 21.0, 12.0, 8.0, 3.0]
|
||||
const SQUID_ROLL_WEIGHTS: Array[float] = [44.0, 25.0, 16.0, 11.0, 4.0]
|
||||
const ANCHOVY_ROLL_WEIGHTS: Array[float] = [34.0, 27.0, 20.0, 13.0, 6.0]
|
||||
const SARDINE_ROLL_WEIGHTS: Array[float] = [25.0, 28.0, 23.0, 16.0, 8.0]
|
||||
const SARDINE_ROLL_WEIGHTS: Array[float] = [34.0, 27.0, 20.0, 13.0, 6.0]
|
||||
const MINNOW_ROLL_WEIGHTS: Array[float] = [25.0, 28.0, 23.0, 16.0, 8.0]
|
||||
const LUMINOUS_ROE_ROLL_WEIGHTS: Array[float] = [16.0, 26.0, 26.0, 20.0, 12.0]
|
||||
const SALE_MULTIPLIERS: Array[float] = [1.0, 1.1, 1.25, 1.5, 2.0]
|
||||
# Legacy profile multiplier retained for serialized/profile compatibility.
|
||||
|
|
@ -158,9 +158,9 @@ static func rarity_weights_for_bait(
|
|||
elif active_bait_tags.has(&"bait_rarity_6"):
|
||||
weights.assign(LUMINOUS_ROE_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_5"):
|
||||
weights.assign(SARDINE_ROLL_WEIGHTS)
|
||||
weights.assign(MINNOW_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_4"):
|
||||
weights.assign(ANCHOVY_ROLL_WEIGHTS)
|
||||
weights.assign(SARDINE_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_3"):
|
||||
weights.assign(SQUID_ROLL_WEIGHTS)
|
||||
elif active_bait_tags.has(&"bait_rarity_2"):
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const WorldWeatherServiceType = preload(
|
|||
)
|
||||
|
||||
signal status_changed(status: String)
|
||||
signal local_speech_requested(message: String)
|
||||
signal catch_display_changed(
|
||||
progress: float,
|
||||
chase_progress: float,
|
||||
|
|
@ -64,6 +65,7 @@ signal showcase_changed(
|
|||
visible: bool,
|
||||
)
|
||||
signal bite_activated
|
||||
signal bite_prompt_changed(is_visible: bool)
|
||||
signal ready_for_equipment_refresh
|
||||
signal fish_showcase_toggle_requested
|
||||
signal art_ui_toggle_requested
|
||||
|
|
@ -175,6 +177,8 @@ var _network_auto_click_accumulator: float = 0.0
|
|||
var _network_active_barrier_index: int = -1
|
||||
var _bite_rng: RandomNumberGenerator = RandomNumberGenerator.new()
|
||||
var _pending_cleanup_message: String = ""
|
||||
var _bite_confirmation_pending: bool = false
|
||||
var _bite_confirmation_requested: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -251,6 +255,9 @@ func setup(
|
|||
_network_fishing.local_bite_started.connect(
|
||||
_on_network_bite_started
|
||||
)
|
||||
_network_fishing.local_bite_pending.connect(
|
||||
_on_network_bite_pending
|
||||
)
|
||||
_network_fishing.local_snapshot_received.connect(
|
||||
_on_network_fishing_snapshot
|
||||
)
|
||||
|
|
@ -514,14 +521,25 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
return
|
||||
if (
|
||||
active_item != null
|
||||
and active_item.category == ItemDataType.Category.CONSUMABLE
|
||||
and (
|
||||
active_item.category == ItemDataType.Category.CONSUMABLE
|
||||
or (
|
||||
active_item.item_id == PlayerItemEffectsType.FISH_FINDER_ID
|
||||
)
|
||||
)
|
||||
):
|
||||
if _network_item_use != null:
|
||||
_network_item_use.request_use(active_item.item_id)
|
||||
elif _item_effects.use_consumable(active_item, _local_bag):
|
||||
elif _item_effects.use_item(active_item, _local_bag):
|
||||
status_changed.emit(_item_effects.get_feedback(
|
||||
active_item.item_id
|
||||
))
|
||||
elif (
|
||||
active_item.item_id == PlayerItemEffectsType.FISH_FINDER_ID
|
||||
):
|
||||
local_speech_requested.emit(
|
||||
PlayerItemEffectsType.FISH_FINDER_DEAD_MESSAGE
|
||||
)
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
if not has_active_fishing_rod():
|
||||
|
|
@ -552,6 +570,10 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
else:
|
||||
return
|
||||
FishingState.WAITING_FOR_BITE:
|
||||
if _bite_confirmation_pending:
|
||||
confirm_pending_bite()
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
_withdrawal_input_held = true
|
||||
_start_reeling_audio()
|
||||
if _network_fishing != null:
|
||||
|
|
@ -568,6 +590,9 @@ func _unhandled_input(event: InputEvent) -> void:
|
|||
_confirm_cast()
|
||||
get_viewport().set_input_as_handled()
|
||||
elif state == FishingState.WAITING_FOR_BITE:
|
||||
if _bite_confirmation_pending:
|
||||
get_viewport().set_input_as_handled()
|
||||
return
|
||||
_withdrawal_input_held = false
|
||||
_stop_reeling_audio()
|
||||
if _network_fishing != null:
|
||||
|
|
@ -682,6 +707,28 @@ func is_fighting() -> bool:
|
|||
return state == FishingState.FIGHTING
|
||||
|
||||
|
||||
func confirm_pending_bite() -> void:
|
||||
if state != FishingState.WAITING_FOR_BITE or not _bite_confirmation_pending:
|
||||
return
|
||||
_bite_confirmation_requested = true
|
||||
if _network_fishing != null:
|
||||
_network_primary_input_held = false
|
||||
_network_input_resend_elapsed = 0.0
|
||||
_network_fishing.submit_local_input(false, true)
|
||||
else:
|
||||
_activate_bite(true)
|
||||
|
||||
|
||||
func _set_bite_confirmation_pending(is_pending: bool) -> void:
|
||||
if _bite_confirmation_pending == is_pending:
|
||||
if not is_pending:
|
||||
_bite_confirmation_requested = false
|
||||
return
|
||||
_bite_confirmation_pending = is_pending
|
||||
_bite_confirmation_requested = false
|
||||
bite_prompt_changed.emit(is_pending)
|
||||
|
||||
|
||||
func is_returning() -> bool:
|
||||
return state == FishingState.RETURNING
|
||||
|
||||
|
|
@ -791,6 +838,7 @@ func _on_cast_completed() -> void:
|
|||
_consume_active_bait()
|
||||
|
||||
state = FishingState.WAITING_FOR_BITE
|
||||
_active_player.set_fishing_visual(true)
|
||||
_state_time_remaining = roll_bite_wait_time() * (
|
||||
_item_effects.get_bite_time_multiplier()
|
||||
if _item_effects != null
|
||||
|
|
@ -845,6 +893,8 @@ func roll_bite_wait_time() -> float:
|
|||
|
||||
|
||||
func _update_waiting_for_bite(delta: float) -> void:
|
||||
if _bite_confirmation_pending:
|
||||
return
|
||||
if (
|
||||
_withdrawal_input_held
|
||||
and not Input.is_action_pressed("fish_primary")
|
||||
|
|
@ -939,7 +989,7 @@ func _get_withdrawable_distance() -> float:
|
|||
return landing_offset.length() - withdrawal_cancel_distance
|
||||
|
||||
|
||||
func _activate_bite() -> void:
|
||||
func _activate_bite(confirmation_override: bool = false) -> void:
|
||||
if state != FishingState.WAITING_FOR_BITE:
|
||||
return
|
||||
|
||||
|
|
@ -951,7 +1001,19 @@ func _activate_bite() -> void:
|
|||
):
|
||||
_cancel_attempt()
|
||||
return
|
||||
if (
|
||||
not confirmation_override
|
||||
and _active_lure_has_effect(&"deferred_fight")
|
||||
):
|
||||
_state_time_remaining = 0.0
|
||||
_withdrawal_input_held = false
|
||||
_set_bite_confirmation_pending(true)
|
||||
status_changed.emit("")
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
_presentation.show_bite()
|
||||
return
|
||||
|
||||
_set_bite_confirmation_pending(false)
|
||||
state = FishingState.FIGHTING
|
||||
_active_player.set_fighting_visual(true)
|
||||
_state_time_remaining = 0.0
|
||||
|
|
@ -1043,6 +1105,7 @@ func _on_catch_completed() -> void:
|
|||
return
|
||||
_stop_fight_audio()
|
||||
_active_player.set_fighting_visual(false)
|
||||
_active_player.set_fishing_visual(false)
|
||||
state = FishingState.SHOWING_CATCH
|
||||
_showcase_ready = false
|
||||
_showcase_outcome_completed = false
|
||||
|
|
@ -1163,10 +1226,12 @@ func _cleanup_attempt(
|
|||
cooldown_message: String = "",
|
||||
visual_outcome: StringName = &"",
|
||||
) -> void:
|
||||
_set_bite_confirmation_pending(false)
|
||||
_stop_fight_audio()
|
||||
_stop_reeling_audio()
|
||||
if _active_player != null:
|
||||
_active_player.set_fighting_visual(false)
|
||||
_active_player.set_fishing_visual(false)
|
||||
if not visual_outcome.is_empty():
|
||||
if state == FishingState.RETURNING:
|
||||
return
|
||||
|
|
@ -1196,6 +1261,7 @@ func _finalize_attempt_cleanup(cooldown_message: String) -> void:
|
|||
_showcase_restore_generation += 1
|
||||
if _active_player != null:
|
||||
_active_player.set_fighting_visual(false)
|
||||
_active_player.set_fishing_visual(false)
|
||||
_active_player.end_catch_showcase()
|
||||
_active_player.set_movement_enabled(true)
|
||||
_active_player = null
|
||||
|
|
@ -1410,6 +1476,21 @@ func _get_active_bait_tags() -> Array[StringName]:
|
|||
return item.bait_tags.duplicate() if item != null and item.is_bait() else []
|
||||
|
||||
|
||||
func _active_lure_has_effect(effect_id: StringName) -> bool:
|
||||
if (
|
||||
_local_player == null
|
||||
or _item_catalog == null
|
||||
or _local_bag == null
|
||||
or _local_player.active_lure_id.is_empty()
|
||||
or not _local_bag.owns_item(_local_player.active_lure_id)
|
||||
):
|
||||
return false
|
||||
var lure: ItemDataType = _item_catalog.get_item_by_id(
|
||||
_local_player.active_lure_id
|
||||
)
|
||||
return lure != null and lure.is_lure() and effect_id in lure.lure_effects
|
||||
|
||||
|
||||
func _build_network_evidence() -> Dictionary:
|
||||
var rarity_multipliers: Array[float] = []
|
||||
for rarity: int in range(FishDataType.Rarity.size()):
|
||||
|
|
@ -1421,6 +1502,7 @@ func _build_network_evidence() -> Dictionary:
|
|||
var item: ItemDataType = _get_active_item()
|
||||
return {
|
||||
"bait_id": str(_active_player.active_bait_id),
|
||||
"lure_id": str(_active_player.active_lure_id),
|
||||
"rod_id": str(item.item_id) if item != null else "",
|
||||
"reel_speed": _active_player.reel_speed * (
|
||||
_fishing_upgrades.get_reel_speed_multiplier()
|
||||
|
|
@ -1441,6 +1523,13 @@ func _on_network_item_use_finished(
|
|||
accepted: bool,
|
||||
message: String,
|
||||
) -> void:
|
||||
if (
|
||||
not accepted
|
||||
and message == PlayerItemEffectsType.FISH_FINDER_DEAD_MESSAGE
|
||||
):
|
||||
status_changed.emit("")
|
||||
local_speech_requested.emit(message)
|
||||
return
|
||||
status_changed.emit(message)
|
||||
if accepted:
|
||||
refresh_active_item_status()
|
||||
|
|
@ -1455,6 +1544,8 @@ func _on_network_cast_accepted(
|
|||
_cast_target = target
|
||||
_bobber_water_position = target
|
||||
state = FishingState.WAITING_FOR_BITE
|
||||
if _active_player != null:
|
||||
_active_player.set_fishing_visual(true)
|
||||
_network_primary_input_held = false
|
||||
_network_input_resend_elapsed = 0.0
|
||||
_presentation.show_withdrawal_position(_bobber_water_position)
|
||||
|
|
@ -1478,10 +1569,24 @@ func _on_network_cast_rejected(message: String) -> void:
|
|||
_cleanup_attempt(visible_message, &"invalid")
|
||||
|
||||
|
||||
func _on_network_bite_pending(_attempt_id: String) -> void:
|
||||
if state != FishingState.WAITING_FOR_BITE:
|
||||
return
|
||||
_stop_reeling_audio()
|
||||
_withdrawal_input_held = false
|
||||
_network_primary_input_held = false
|
||||
_network_input_resend_elapsed = 0.0
|
||||
_set_bite_confirmation_pending(true)
|
||||
status_changed.emit("")
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
_presentation.show_bite()
|
||||
|
||||
|
||||
func _on_network_bite_started(_attempt_id: String) -> void:
|
||||
if state != FishingState.WAITING_FOR_BITE:
|
||||
return
|
||||
_stop_reeling_audio()
|
||||
_set_bite_confirmation_pending(false)
|
||||
state = FishingState.FIGHTING
|
||||
if _active_player != null:
|
||||
_active_player.set_fighting_visual(true)
|
||||
|
|
@ -1512,7 +1617,10 @@ func _resend_network_input_state(delta: float) -> void:
|
|||
_network_input_resend_elapsed,
|
||||
NETWORK_INPUT_RESEND_INTERVAL_SECONDS,
|
||||
)
|
||||
_network_fishing.submit_local_input(_network_primary_input_held, false)
|
||||
_network_fishing.submit_local_input(
|
||||
false if _bite_confirmation_pending else _network_primary_input_held,
|
||||
_bite_confirmation_pending and _bite_confirmation_requested,
|
||||
)
|
||||
|
||||
|
||||
func _on_network_fishing_snapshot(snapshot: Dictionary) -> void:
|
||||
|
|
@ -1577,6 +1685,7 @@ func _on_network_catch_received(fish_catch: FishCatchType) -> void:
|
|||
_pending_catch = fish_catch
|
||||
if _active_player != null:
|
||||
_active_player.set_fighting_visual(false)
|
||||
_active_player.set_fishing_visual(false)
|
||||
state = FishingState.SHOWING_CATCH
|
||||
_showcase_ready = false
|
||||
_showcase_outcome_completed = false
|
||||
|
|
|
|||
15
items/catalog/batteries.tres
Normal file
15
items/catalog/batteries.tres
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1_item"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_item")
|
||||
item_id = &"batteries"
|
||||
display_name = "batteries"
|
||||
description = "replacement batteries for the fish finder."
|
||||
category = 3
|
||||
stackable = true
|
||||
max_stack = 99
|
||||
usable = false
|
||||
equippable = false
|
||||
hotbar_allowed = false
|
||||
|
|
@ -7,10 +7,11 @@
|
|||
script = ExtResource("1")
|
||||
item_id = &"fish_finder"
|
||||
display_name = "fish finder"
|
||||
description = "temporarily shortens bite waits and improves rarity weights."
|
||||
category = 3
|
||||
description = "uses one battery to temporarily shorten bite waits and improve rarity weights."
|
||||
category = 1
|
||||
icon = ExtResource("2")
|
||||
stackable = true
|
||||
max_stack = 99
|
||||
stackable = false
|
||||
max_stack = 1
|
||||
usable = true
|
||||
equippable = true
|
||||
hotbar_allowed = true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="Resource" script_class="ItemCatalog" load_steps=16 format=3]
|
||||
[gd_resource type="Resource" script_class="ItemCatalog" load_steps=18 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_catalog.gd" id="1_script"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/basic_fishing_rod.tres" id="2_rod"]
|
||||
|
|
@ -15,7 +15,9 @@
|
|||
[ext_resource type="Resource" path="res://items/catalog/whole_anchovy.tres" id="13_anchovy"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/whole_sardine.tres" id="14_sardine"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/luminous_roe.tres" id="15_roe"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/the_standby.tres" id="16_standby"]
|
||||
[ext_resource type="Resource" path="res://items/catalog/batteries.tres" id="17_batteries"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_script")
|
||||
items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("7_cooler"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("13_anchovy"), ExtResource("14_sardine"), ExtResource("15_roe")]
|
||||
items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("7_cooler"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("13_anchovy"), ExtResource("14_sardine"), ExtResource("15_roe"), ExtResource("16_standby"), ExtResource("17_batteries")]
|
||||
|
|
|
|||
16
items/catalog/the_standby.tres
Normal file
16
items/catalog/the_standby.tres
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://items/item_data.gd" id="1_item"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1_item")
|
||||
item_id = &"the_standby"
|
||||
display_name = "The Standby"
|
||||
description = "holds a bite until you're ready to start the fight."
|
||||
category = 7
|
||||
lure_effects = Array[StringName]([&"deferred_fight"])
|
||||
stackable = false
|
||||
max_stack = 1
|
||||
usable = false
|
||||
equippable = true
|
||||
hotbar_allowed = false
|
||||
|
|
@ -5,8 +5,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"whole_anchovy"
|
||||
display_name = "whole anchovy"
|
||||
description = "premium whole bait with high rare catch potential."
|
||||
display_name = "sardine"
|
||||
description = "oily schooling bait with high rare catch potential."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_4"])
|
||||
stackable = true
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
[resource]
|
||||
script = ExtResource("1")
|
||||
item_id = &"whole_sardine"
|
||||
display_name = "whole sardine"
|
||||
description = "valuable whole bait tuned for exceptional catches."
|
||||
display_name = "minnow"
|
||||
description = "valuable live bait tuned for exceptional catches."
|
||||
category = 2
|
||||
bait_tags = Array[StringName]([&"bait_rarity_5"])
|
||||
stackable = true
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ enum Category {
|
|||
@export_multiline var description: String
|
||||
@export var category: Category = Category.UTILITY
|
||||
@export var bait_tags: Array[StringName] = []
|
||||
@export var lure_effects: Array[StringName] = []
|
||||
@export var icon: Texture2D
|
||||
@export var stackable: bool = false
|
||||
@export_range(1, 999, 1) var max_stack: int = 1
|
||||
|
|
@ -40,3 +41,7 @@ func get_category_name() -> String:
|
|||
|
||||
func is_bait() -> bool:
|
||||
return category == Category.BAIT and not bait_tags.is_empty()
|
||||
|
||||
|
||||
func is_lure() -> bool:
|
||||
return category == Category.LURE and equippable
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@ var target: Vector3
|
|||
var bobber_position: Vector3
|
||||
var fish_id: StringName
|
||||
var bait_tags: Array[StringName] = []
|
||||
var lure_effects: Array[StringName] = []
|
||||
var encounter_seed: int = 0
|
||||
var bite_time_remaining: float = 0.0
|
||||
var bite_confirmation_pending: bool = false
|
||||
var withdrawal_progress: float = 0.0
|
||||
var reel_speed: float = 0.0
|
||||
var barrier_damage: int = 1
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@ static func validate_cast_request(data: Variant) -> String:
|
|||
return "Malformed fishing request."
|
||||
if payload.has("bait_id") and typeof(payload["bait_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]:
|
||||
return "Malformed fishing request."
|
||||
if payload.has("lure_id") and typeof(payload["lure_id"]) not in [TYPE_STRING, TYPE_STRING_NAME]:
|
||||
return "Malformed fishing request."
|
||||
var request_id: String = payload["request_id"]
|
||||
var session_id: String = payload["session_id"]
|
||||
var origin: Array = payload["origin"]
|
||||
|
|
@ -89,6 +91,7 @@ static func validate_cast_request(data: Variant) -> String:
|
|||
or discovered.size() > MAX_EVIDENCE_FISH_IDS
|
||||
or str(payload["rod_id"]).is_empty()
|
||||
or str(payload["rod_id"]).length() > 96
|
||||
or str(payload.get("lure_id", "")).length() > 96
|
||||
):
|
||||
return "Fishing request values are outside allowed limits."
|
||||
for value: Variant in rarity:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ const MIN_CAST_INTERVAL: float = 0.25
|
|||
signal local_cast_accepted(attempt_id: String, target: Vector3)
|
||||
signal local_cast_rejected(message: String)
|
||||
signal local_bite_started(attempt_id: String)
|
||||
signal local_bite_pending(attempt_id: String)
|
||||
signal local_snapshot_received(snapshot: Dictionary)
|
||||
signal local_catch_received(fish_catch: FishCatch)
|
||||
signal local_attempt_ended(outcome: StringName, message: String)
|
||||
|
|
@ -111,6 +112,7 @@ func request_local_cast(
|
|||
"discovered_fish_ids": evidence.get("discovered_fish_ids", []),
|
||||
"capacity_available": bool(evidence.get("capacity_available", false)),
|
||||
"bait_id": str(evidence.get("bait_id", "")),
|
||||
"lure_id": str(evidence.get("lure_id", "")),
|
||||
}
|
||||
if _session.is_host():
|
||||
_handle_cast_request(_session.get_local_peer_id(), data)
|
||||
|
|
@ -176,11 +178,16 @@ func _process(delta: float) -> void:
|
|||
continue
|
||||
match attempt.phase:
|
||||
NetworkFishingAttempt.Phase.WAITING_FOR_BITE:
|
||||
if attempt.bite_confirmation_pending:
|
||||
continue
|
||||
_update_waiting_attempt(attempt, delta)
|
||||
if not _attempts.has(peer_id):
|
||||
continue
|
||||
attempt.bite_time_remaining -= delta
|
||||
if attempt.bite_time_remaining <= 0.0:
|
||||
if &"deferred_fight" in attempt.lure_effects:
|
||||
_set_bite_pending(attempt)
|
||||
else:
|
||||
_start_bite(attempt)
|
||||
NetworkFishingAttempt.Phase.PENDING_CAPACITY:
|
||||
if now >= attempt.capacity_deadline:
|
||||
|
|
@ -296,6 +303,23 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void:
|
|||
return
|
||||
var bait_id: StringName = StringName(str(data.get("bait_id", "")))
|
||||
var avatar_bag: PlayerBag = avatar.bag
|
||||
var lure_id: StringName = StringName(str(data.get("lure_id", "")))
|
||||
var lure: ItemDataType = (
|
||||
_item_catalog.get_item_by_id(lure_id)
|
||||
if not lure_id.is_empty() and _item_catalog != null
|
||||
else null
|
||||
)
|
||||
if (
|
||||
not lure_id.is_empty()
|
||||
and (
|
||||
lure == null
|
||||
or not lure.is_lure()
|
||||
or avatar_bag == null
|
||||
or not avatar_bag.owns_item(lure_id)
|
||||
)
|
||||
):
|
||||
_record_and_reject(peer_id, request_id, "Lure is unavailable.")
|
||||
return
|
||||
if not bait_id.is_empty() and (avatar_bag == null or not avatar_bag.remove_item(bait_id, 1)):
|
||||
_record_and_reject(peer_id, request_id, "No bait available.")
|
||||
return
|
||||
|
|
@ -310,6 +334,10 @@ func _handle_cast_request(peer_id: int, data: Dictionary) -> void:
|
|||
attempt.bobber_position = authoritative_target
|
||||
attempt.fish_id = selected_fish.id
|
||||
attempt.bait_tags = _bait_tags_for_request(data)
|
||||
attempt.lure_effects.clear()
|
||||
if lure != null:
|
||||
for effect_id: StringName in lure.lure_effects:
|
||||
attempt.lure_effects.append(effect_id)
|
||||
attempt.reel_speed = float(data["reel_speed"]) * (
|
||||
effects.get_reel_multiplier() if effects != null else 1.0
|
||||
)
|
||||
|
|
@ -437,6 +465,23 @@ func _bait_tags_for_request(data: Dictionary) -> Array[StringName]:
|
|||
return bait.bait_tags.duplicate() if bait != null and bait.is_bait() else []
|
||||
|
||||
|
||||
func _set_bite_pending(attempt: NetworkFishingAttempt) -> void:
|
||||
if (
|
||||
attempt.phase != NetworkFishingAttempt.Phase.WAITING_FOR_BITE
|
||||
or attempt.bite_confirmation_pending
|
||||
):
|
||||
return
|
||||
attempt.bite_confirmation_pending = true
|
||||
attempt.bite_time_remaining = 0.0
|
||||
attempt.input_held = false
|
||||
var data: Dictionary = {
|
||||
"attempt_id": attempt.attempt_id,
|
||||
"owner_peer_id": attempt.owner_peer_id,
|
||||
}
|
||||
_apply_bite_pending(data)
|
||||
receive_bite_pending.rpc(data)
|
||||
|
||||
|
||||
func _start_bite(attempt: NetworkFishingAttempt) -> void:
|
||||
if attempt.phase != NetworkFishingAttempt.Phase.WAITING_FOR_BITE:
|
||||
return
|
||||
|
|
@ -444,6 +489,7 @@ func _start_bite(attempt: NetworkFishingAttempt) -> void:
|
|||
if fish == null or fish.catch_profile == null:
|
||||
_cancel_attempt(attempt.owner_peer_id, "Fishing attempt ended.")
|
||||
return
|
||||
attempt.bite_confirmation_pending = false
|
||||
attempt.phase = NetworkFishingAttempt.Phase.FIGHTING
|
||||
attempt.encounter_seed = _new_seed()
|
||||
var selector := FishSelectorType.new()
|
||||
|
|
@ -496,6 +542,14 @@ func _handle_fishing_input(peer_id: int, data: Dictionary) -> void:
|
|||
return
|
||||
attempt.last_input_sequence = int(data["sequence"])
|
||||
attempt.input_held = bool(data["held"])
|
||||
if (
|
||||
attempt.phase == NetworkFishingAttempt.Phase.WAITING_FOR_BITE
|
||||
and attempt.bite_confirmation_pending
|
||||
):
|
||||
if bool(data["pressed"]):
|
||||
attempt.input_held = false
|
||||
_start_bite(attempt)
|
||||
return
|
||||
if attempt.phase != NetworkFishingAttempt.Phase.FIGHTING:
|
||||
return
|
||||
attempt.controller.set_reel_input(attempt.input_held)
|
||||
|
|
@ -978,6 +1032,21 @@ func _apply_cast_accepted(data: Dictionary) -> void:
|
|||
presentation.show_cast(origin, target)
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_bite_pending(data: Dictionary) -> void:
|
||||
_apply_bite_pending(data)
|
||||
|
||||
|
||||
func _apply_bite_pending(data: Dictionary) -> void:
|
||||
if (
|
||||
typeof(data.get("attempt_id")) != TYPE_STRING
|
||||
or typeof(data.get("owner_peer_id")) != TYPE_INT
|
||||
):
|
||||
return
|
||||
if int(data["owner_peer_id"]) == _session.get_local_peer_id():
|
||||
local_bite_pending.emit(str(data["attempt_id"]))
|
||||
|
||||
|
||||
@rpc("authority", "call_remote", "reliable", 0)
|
||||
func receive_bite_started(data: Dictionary) -> void:
|
||||
_apply_bite_started(data)
|
||||
|
|
|
|||
|
|
@ -50,9 +50,19 @@ func request_use(item_id: StringName) -> String:
|
|||
if not _pending_local.is_empty():
|
||||
local_item_use_finished.emit(false, "An item use is already pending.")
|
||||
return ""
|
||||
var consumed_item_id: StringName = _consumed_item_id(item_id)
|
||||
if item_id == PlayerItemEffects.FISH_FINDER_ID and (
|
||||
_local_bag == null
|
||||
or not _local_bag.owns_item(item_id)
|
||||
or _local_bag.get_quantity(consumed_item_id) < 1
|
||||
):
|
||||
local_item_use_finished.emit(
|
||||
false, PlayerItemEffects.FISH_FINDER_DEAD_MESSAGE
|
||||
)
|
||||
return ""
|
||||
if (
|
||||
_reservations != null
|
||||
and _reservations.get_available_item_quantity(item_id) < 1
|
||||
and _reservations.get_available_item_quantity(consumed_item_id) < 1
|
||||
):
|
||||
local_item_use_finished.emit(false, "Reserved in a letter.")
|
||||
return ""
|
||||
|
|
@ -73,7 +83,7 @@ func request_use(item_id: StringName) -> String:
|
|||
"request_id": request_id,
|
||||
"session_id": _session.get_session_id(),
|
||||
"item_id": str(item_id),
|
||||
"quantity": _local_bag.get_quantity(item_id),
|
||||
"quantity": _local_bag.get_quantity(consumed_item_id),
|
||||
}
|
||||
_pending_local = data.duplicate(true)
|
||||
local_item_use_pending.emit(request_id)
|
||||
|
|
@ -109,14 +119,28 @@ func _handle_use_request(peer_id: int, data: Dictionary) -> void:
|
|||
var item: ItemData = _catalog.get_item_by_id(item_id)
|
||||
var avatar: Player = _spawn_service.get_avatar(peer_id)
|
||||
var duration: float = 0.0
|
||||
var is_fish_finder: bool = (
|
||||
item_id == PlayerItemEffects.FISH_FINDER_ID
|
||||
and item != null
|
||||
and item.category == ItemData.Category.TOOL
|
||||
and item.usable
|
||||
and item.equippable
|
||||
)
|
||||
if error.is_empty() and (
|
||||
item == null
|
||||
or item.category != ItemData.Category.CONSUMABLE
|
||||
or (
|
||||
item.category != ItemData.Category.CONSUMABLE
|
||||
and not is_fish_finder
|
||||
)
|
||||
or not item.usable
|
||||
):
|
||||
error = "That item cannot be used now."
|
||||
if error.is_empty() and int(data["quantity"]) < 1:
|
||||
error = "You do not have that item."
|
||||
error = (
|
||||
PlayerItemEffects.FISH_FINDER_DEAD_MESSAGE
|
||||
if is_fish_finder
|
||||
else "You do not have that item."
|
||||
)
|
||||
if error.is_empty() and (
|
||||
avatar == null
|
||||
or avatar.is_water_recovery_active()
|
||||
|
|
@ -180,9 +204,10 @@ func _apply_result(data: Dictionary) -> void:
|
|||
_acknowledge(data, false, str(data["message"]))
|
||||
return
|
||||
var item_id := StringName(str(data["item_id"]))
|
||||
var consumed_item_id: StringName = _consumed_item_id(item_id)
|
||||
if (
|
||||
_reservations != null
|
||||
and _reservations.get_available_item_quantity(item_id) < 1
|
||||
and _reservations.get_available_item_quantity(consumed_item_id) < 1
|
||||
):
|
||||
_pending_local.clear()
|
||||
local_item_use_finished.emit(false, "Reserved in a letter.")
|
||||
|
|
@ -190,8 +215,8 @@ func _apply_result(data: Dictionary) -> void:
|
|||
return
|
||||
var bag_snapshot := _local_bag.get_all_items()
|
||||
if (
|
||||
_local_bag.get_quantity(item_id) < 1
|
||||
or not _local_bag.remove_item(item_id, 1)
|
||||
_local_bag.get_quantity(consumed_item_id) < 1
|
||||
or not _local_bag.remove_item(consumed_item_id, 1)
|
||||
or not _save_manager.save_if_dirty()
|
||||
):
|
||||
_local_bag.replace_all_items(bag_snapshot)
|
||||
|
|
@ -409,3 +434,11 @@ func _new_id(prefix: String) -> String:
|
|||
return "%s:%s" % [
|
||||
prefix, Crypto.new().generate_random_bytes(16).hex_encode(),
|
||||
]
|
||||
|
||||
|
||||
func _consumed_item_id(item_id: StringName) -> StringName:
|
||||
return (
|
||||
PlayerItemEffects.BATTERIES_ID
|
||||
if item_id == PlayerItemEffects.FISH_FINDER_ID
|
||||
else item_id
|
||||
)
|
||||
|
|
|
|||
|
|
@ -335,15 +335,29 @@ func _build_authoritative_result(
|
|||
var bait_topoff: bool = (
|
||||
FishingShopStockType.is_bait_topoff(product_id)
|
||||
)
|
||||
var permanent_unlock: bool = (
|
||||
FishingShopStockType.is_permanent_unlock(product_id, item)
|
||||
)
|
||||
if (
|
||||
item == null
|
||||
or not item.is_valid()
|
||||
or product_id not in (
|
||||
FishingShopStockType.get_stock_item_ids()
|
||||
)
|
||||
or (item.category != ItemDataType.Category.CONSUMABLE and not bait_topoff)
|
||||
or not item.stackable
|
||||
or (not item.usable and not bait_topoff)
|
||||
or (
|
||||
item.category != ItemDataType.Category.CONSUMABLE
|
||||
and not bait_topoff
|
||||
and not permanent_unlock
|
||||
)
|
||||
or (not item.stackable and not permanent_unlock)
|
||||
or (
|
||||
not item.usable
|
||||
and not bait_topoff
|
||||
and not permanent_unlock
|
||||
and not FishingShopStockType.is_passive_supply(
|
||||
product_id
|
||||
)
|
||||
)
|
||||
or current_state >= item.max_stack
|
||||
or current_state + quantity > item.max_stack
|
||||
):
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@ roughness = 0.8
|
|||
bone_name = "hand.R"
|
||||
|
||||
[node name="FishingRod" type="Node3D" parent="."]
|
||||
rotation_degrees = Vector3(322.6, -31.2, 248.9)
|
||||
rotation_degrees = Vector3(65, 0, 0)
|
||||
|
||||
[node name="RodMesh" type="MeshInstance3D" parent="FishingRod"]
|
||||
position = Vector3(0, 0.42, 0)
|
||||
position = Vector3(0, 0.5, 0)
|
||||
mesh = SubResource("FishingRodMesh")
|
||||
material_override = SubResource("FishingRodMaterial")
|
||||
|
||||
[node name="FishingRodTip" type="Marker3D" parent="FishingRod"]
|
||||
position = Vector3(0, 0.95, 0)
|
||||
position = Vector3(0, 1.1, 0)
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ const CHARACTER_IDLE_SIT_ANIMATION: StringName = &"idle_sit"
|
|||
const CHARACTER_IDLE_SIT_SHOW_ANIMATION: StringName = &"idle_sit_show"
|
||||
const CHARACTER_WALKING_ANIMATION: StringName = &"walking"
|
||||
const CHARACTER_WALKING_SHOW_ANIMATION: StringName = &"walking_show"
|
||||
const CHARACTER_FISHING_ANIMATION: StringName = &"fishing"
|
||||
const CHARACTER_FISHING_SIT_ANIMATION: StringName = &"fishing_sit"
|
||||
const CHARACTER_FIGHTING_ANIMATION: StringName = &"fighting"
|
||||
const CHARACTER_FIGHTING_SIT_ANIMATION: StringName = &"fighting_sit"
|
||||
const FIGHTING_EYES_ID: String = "alligator_eyes"
|
||||
const BASE_REEL_SPEED: float = 0.16
|
||||
# The target Android handheld exposes its physical right trigger through
|
||||
|
|
@ -53,7 +57,9 @@ var appearance_snapshot: Dictionary = (
|
|||
)
|
||||
var animalese_voice_id: String = "natural"
|
||||
var active_bait_id: StringName = StringName()
|
||||
var active_lure_id: StringName = StringName()
|
||||
signal active_bait_changed(item_id: StringName)
|
||||
signal active_lure_changed(item_id: StringName)
|
||||
|
||||
|
||||
func equip_bait(item: ItemDataType) -> bool:
|
||||
|
|
@ -73,6 +79,23 @@ func unequip_bait() -> void:
|
|||
active_bait_changed.emit(active_bait_id)
|
||||
|
||||
|
||||
func equip_lure(item: ItemDataType) -> bool:
|
||||
if item == null or not item.is_lure() or bag == null:
|
||||
return false
|
||||
if not bag.owns_item(item.item_id):
|
||||
return false
|
||||
active_lure_id = item.item_id
|
||||
active_lure_changed.emit(active_lure_id)
|
||||
return true
|
||||
|
||||
|
||||
func unequip_lure() -> void:
|
||||
if active_lure_id.is_empty():
|
||||
return
|
||||
active_lure_id = StringName()
|
||||
active_lure_changed.emit(active_lure_id)
|
||||
|
||||
|
||||
func apply_appearance_snapshot(snapshot: Dictionary) -> void:
|
||||
if CharacterCustomizationCatalog.validate_snapshot(snapshot):
|
||||
appearance_snapshot = snapshot.duplicate(true)
|
||||
|
|
@ -93,6 +116,16 @@ func set_fighting_visual(active: bool) -> void:
|
|||
return
|
||||
_fighting_visual_active = active
|
||||
_apply_presented_appearance()
|
||||
_character_animation_name = &""
|
||||
_update_character_animation()
|
||||
|
||||
|
||||
func set_fishing_visual(active: bool) -> void:
|
||||
if _fishing_visual_active == active:
|
||||
return
|
||||
_fishing_visual_active = active
|
||||
_character_animation_name = &""
|
||||
_update_character_animation()
|
||||
|
||||
|
||||
func _apply_presented_appearance() -> void:
|
||||
|
|
@ -246,6 +279,7 @@ var _sitting_intent_sequence: int = -1
|
|||
var _held_fish_visible: bool = false
|
||||
var _showcase_animation_active: bool = false
|
||||
var _fighting_visual_active: bool = false
|
||||
var _fishing_visual_active: bool = false
|
||||
var _fishing_rod: Node3D
|
||||
var _fishing_rod_tip: Marker3D
|
||||
var _controller_mapping_manager: ControllerMappingManagerType
|
||||
|
|
@ -268,6 +302,11 @@ func _on_bag_contents_changed() -> void:
|
|||
and bag.get_quantity(active_bait_id) <= 0
|
||||
):
|
||||
unequip_bait()
|
||||
if (
|
||||
not active_lure_id.is_empty()
|
||||
and not bag.owns_item(active_lure_id)
|
||||
):
|
||||
unequip_lure()
|
||||
|
||||
|
||||
func set_controller_mapping_manager(
|
||||
|
|
@ -527,6 +566,30 @@ func _update_character_animation() -> void:
|
|||
&"show",
|
||||
&"show_loop",
|
||||
]
|
||||
elif _fighting_visual_active:
|
||||
if _sitting:
|
||||
requested_animation = [
|
||||
CHARACTER_FIGHTING_SIT_ANIMATION,
|
||||
CHARACTER_IDLE_SIT_ANIMATION,
|
||||
&"idle_sit_loop",
|
||||
]
|
||||
else:
|
||||
requested_animation = [
|
||||
CHARACTER_FIGHTING_ANIMATION,
|
||||
CHARACTER_IDLE_ANIMATION,
|
||||
]
|
||||
elif _fishing_visual_active:
|
||||
if _sitting:
|
||||
requested_animation = [
|
||||
CHARACTER_FISHING_SIT_ANIMATION,
|
||||
CHARACTER_IDLE_SIT_ANIMATION,
|
||||
&"idle_sit_loop",
|
||||
]
|
||||
else:
|
||||
requested_animation = [
|
||||
CHARACTER_FISHING_ANIMATION,
|
||||
CHARACTER_IDLE_ANIMATION,
|
||||
]
|
||||
elif _sitting:
|
||||
if _held_fish_visible:
|
||||
requested_animation = [
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func _physics_process(delta: float) -> void:
|
|||
_visual.transparency = 1.0 - _opacity
|
||||
|
||||
|
||||
func _place_on_surface(position: Vector3, normal: Vector3) -> void:
|
||||
func _place_on_surface(surface_position: Vector3, normal: Vector3) -> void:
|
||||
var tangent := Vector3.RIGHT - normal * Vector3.RIGHT.dot(normal)
|
||||
if tangent.length_squared() < 0.001:
|
||||
tangent = Vector3.FORWARD - normal * Vector3.FORWARD.dot(normal)
|
||||
|
|
@ -62,5 +62,5 @@ func _place_on_surface(position: Vector3, normal: Vector3) -> void:
|
|||
var bitangent := tangent.cross(normal).normalized()
|
||||
_placement.global_transform = Transform3D(
|
||||
Basis(tangent, normal, bitangent),
|
||||
position + normal * surface_offset,
|
||||
surface_position + normal * surface_offset,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ const COFFEE_ID: StringName = &"coffee"
|
|||
const ENERGY_DRINK_ID: StringName = &"energy_drink"
|
||||
const SNACK_ID: StringName = &"snack"
|
||||
const FISH_FINDER_ID: StringName = &"fish_finder"
|
||||
const BATTERIES_ID: StringName = &"batteries"
|
||||
const FISH_FINDER_DEAD_MESSAGE: String = "hmm... batteries are dead..."
|
||||
const COFFEE_DURATION: float = 90.0
|
||||
const ENERGY_DRINK_DURATION: float = 90.0
|
||||
const SNACK_DURATION: float = 60.0
|
||||
|
|
@ -45,16 +47,35 @@ func use_consumable(
|
|||
item: ItemDataType,
|
||||
bag: PlayerBagType,
|
||||
) -> bool:
|
||||
return use_item(item, bag)
|
||||
|
||||
|
||||
func use_item(
|
||||
item: ItemDataType,
|
||||
bag: PlayerBagType,
|
||||
) -> bool:
|
||||
var is_fish_finder: bool = (
|
||||
item != null
|
||||
and item.item_id == FISH_FINDER_ID
|
||||
and item.category == ItemDataType.Category.TOOL
|
||||
and item.equippable
|
||||
)
|
||||
if (
|
||||
item == null
|
||||
or bag == null
|
||||
or item.category != ItemDataType.Category.CONSUMABLE
|
||||
or (
|
||||
item.category != ItemDataType.Category.CONSUMABLE
|
||||
and not is_fish_finder
|
||||
)
|
||||
or not item.usable
|
||||
or not _remaining.has(item.item_id)
|
||||
or not bag.owns_item(item.item_id)
|
||||
):
|
||||
return false
|
||||
if not bag.remove_item(item.item_id, 1):
|
||||
var consumed_item_id: StringName = (
|
||||
BATTERIES_ID if is_fish_finder else item.item_id
|
||||
)
|
||||
if not bag.remove_item(consumed_item_id, 1):
|
||||
return false
|
||||
_remaining[item.item_id] = _get_duration(item.item_id)
|
||||
effects_changed.emit()
|
||||
|
|
|
|||
|
|
@ -34,6 +34,10 @@ const CLOCK_SIZE := Vector2(116.0, 34.0)
|
|||
const CLOCK_EDGE_MARGIN: float = 10.0
|
||||
const WEATHER_ICON_SIZE := Vector2(34.0, 34.0)
|
||||
const WEATHER_ICON_GAP: float = 6.0
|
||||
const FISH_FINDER_EFFECT_ICON_SIZE := Vector2(18.0, 18.0)
|
||||
const FISH_FINDER_EFFECT_ICON: Texture2D = preload(
|
||||
"res://items/icons/consumables/64_consumable_fishfinder.png"
|
||||
)
|
||||
const CHAT_SHOW_ICON: Texture2D = preload(
|
||||
"res://ui/icons/pictograms/arrow_light_right_more.png"
|
||||
)
|
||||
|
|
@ -86,6 +90,7 @@ var _animalese_voice: AnimaleseVoiceType
|
|||
var _clock_panel: PanelContainer
|
||||
var _clock_label: Label
|
||||
var _weather_icon: WeatherIconType
|
||||
var _fish_finder_effect_icon: TextureRect
|
||||
var _speech: Dictionary[int, Dictionary] = {}
|
||||
var _draft_save_timer: Timer
|
||||
var _opacity_tween: Tween
|
||||
|
|
@ -110,6 +115,7 @@ var _dock_right: bool = false
|
|||
var _mobile_mode: bool = false
|
||||
var _world_time: WorldTimeServiceType
|
||||
var _world_weather: WorldWeatherServiceType
|
||||
var _item_effects: PlayerItemEffects
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -129,6 +135,7 @@ func setup(
|
|||
settings: PlayerSettingsManager,
|
||||
world_time: WorldTimeServiceType,
|
||||
world_weather: WorldWeatherServiceType,
|
||||
item_effects: PlayerItemEffects,
|
||||
) -> void:
|
||||
_service = service
|
||||
_session = session
|
||||
|
|
@ -138,6 +145,14 @@ func setup(
|
|||
_settings = settings
|
||||
_world_time = world_time
|
||||
_world_weather = world_weather
|
||||
_item_effects = item_effects
|
||||
if (
|
||||
_fishing_spot != null
|
||||
and not _fishing_spot.local_speech_requested.is_connected(
|
||||
show_local_speech
|
||||
)
|
||||
):
|
||||
_fishing_spot.local_speech_requested.connect(show_local_speech)
|
||||
if (
|
||||
_world_time != null
|
||||
and not _world_time.time_changed.is_connected(
|
||||
|
|
@ -361,6 +376,15 @@ func _build_ui() -> void:
|
|||
"font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY
|
||||
)
|
||||
_clock_panel.add_child(_clock_label)
|
||||
_fish_finder_effect_icon = TextureRect.new()
|
||||
_fish_finder_effect_icon.name = "FishFinderEffectIcon"
|
||||
_fish_finder_effect_icon.texture = FISH_FINDER_EFFECT_ICON
|
||||
_fish_finder_effect_icon.size = FISH_FINDER_EFFECT_ICON_SIZE
|
||||
_fish_finder_effect_icon.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
||||
_fish_finder_effect_icon.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
||||
_fish_finder_effect_icon.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
|
||||
_fish_finder_effect_icon.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
add_child(_fish_finder_effect_icon)
|
||||
_weather_icon = WeatherIconType.new()
|
||||
_weather_icon.name = "WorldWeatherIcon"
|
||||
_weather_icon.size = WEATHER_ICON_SIZE
|
||||
|
|
@ -800,6 +824,24 @@ func _on_message(message: Dictionary) -> void:
|
|||
if int(message["kind"]) != NetworkChatProtocol.Kind.PLAYER:
|
||||
return
|
||||
var peer_id: int = message["sender_peer_id"]
|
||||
_show_speech_bubble(
|
||||
peer_id,
|
||||
str(message["body"]),
|
||||
str(message.get("sender_fingerprint", "")),
|
||||
)
|
||||
|
||||
|
||||
func show_local_speech(body: String) -> void:
|
||||
if body.strip_edges().is_empty() or _session == null:
|
||||
return
|
||||
_show_speech_bubble(_session.get_local_peer_id(), body, "")
|
||||
|
||||
|
||||
func _show_speech_bubble(
|
||||
peer_id: int,
|
||||
body: String,
|
||||
fingerprint: String,
|
||||
) -> void:
|
||||
_on_peer_removed(peer_id)
|
||||
var bubble := PanelContainer.new()
|
||||
bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
|
|
@ -808,7 +850,7 @@ func _on_message(message: Dictionary) -> void:
|
|||
var pointer := _create_speech_pointer()
|
||||
bubble.add_child(pointer)
|
||||
var label := Label.new()
|
||||
label.text = str(message["body"])
|
||||
label.text = body
|
||||
label.custom_minimum_size = Vector2(
|
||||
SPEECH_BUBBLE_WIDTH - 20.0,
|
||||
0.0,
|
||||
|
|
@ -844,7 +886,7 @@ func _on_message(message: Dictionary) -> void:
|
|||
+ reveal_seconds
|
||||
+ SPEECH_SECONDS
|
||||
),
|
||||
"fingerprint": str(message.get("sender_fingerprint", "")),
|
||||
"fingerprint": fingerprint,
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -891,6 +933,7 @@ func _refresh_visibility() -> void:
|
|||
_panel.hide()
|
||||
_clock_panel.hide()
|
||||
_weather_icon.hide()
|
||||
_fish_finder_effect_icon.hide()
|
||||
_collapse_button.hide()
|
||||
_height_button.hide()
|
||||
_unread_indicator.hide()
|
||||
|
|
@ -898,6 +941,10 @@ func _refresh_visibility() -> void:
|
|||
return
|
||||
_clock_panel.show()
|
||||
_weather_icon.show()
|
||||
_fish_finder_effect_icon.visible = (
|
||||
_item_effects != null
|
||||
and _item_effects.is_active(PlayerItemEffects.FISH_FINDER_ID)
|
||||
)
|
||||
_collapse_button.show()
|
||||
var collapsed := _presentation_state == PresentationState.COLLAPSED
|
||||
_panel.show()
|
||||
|
|
@ -1232,6 +1279,11 @@ func _layout_presentation(animate: bool) -> void:
|
|||
clock_x,
|
||||
CLOCK_EDGE_MARGIN,
|
||||
)
|
||||
var fish_finder_effect_position := Vector2(
|
||||
clock_position.x
|
||||
+ (CLOCK_SIZE.x - FISH_FINDER_EFFECT_ICON_SIZE.x) * 0.5,
|
||||
clock_position.y + CLOCK_SIZE.y + 2.0,
|
||||
)
|
||||
var weather_icon_x: float = (
|
||||
clock_position.x - WEATHER_ICON_GAP - WEATHER_ICON_SIZE.x
|
||||
if _dock_right
|
||||
|
|
@ -1255,6 +1307,8 @@ func _layout_presentation(animate: bool) -> void:
|
|||
_panel.size = target_size
|
||||
_clock_panel.position = clock_position
|
||||
_clock_panel.size = CLOCK_SIZE
|
||||
_fish_finder_effect_icon.position = fish_finder_effect_position
|
||||
_fish_finder_effect_icon.size = FISH_FINDER_EFFECT_ICON_SIZE
|
||||
_weather_icon.position = weather_icon_position
|
||||
_weather_icon.size = WEATHER_ICON_SIZE
|
||||
_collapse_button.position = collapse_position
|
||||
|
|
@ -1275,6 +1329,12 @@ func _layout_presentation(animate: bool) -> void:
|
|||
clock_position,
|
||||
UIMotion.CHAT_RESIZE_DURATION,
|
||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
_height_tween.tween_property(
|
||||
_fish_finder_effect_icon,
|
||||
"position",
|
||||
fish_finder_effect_position,
|
||||
UIMotion.CHAT_RESIZE_DURATION,
|
||||
).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_OUT)
|
||||
_height_tween.tween_property(
|
||||
_weather_icon,
|
||||
"position",
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const ShopInteractionType = preload(
|
|||
"res://world/fishing_shop_interaction.gd"
|
||||
)
|
||||
const UtilityPageStyleType = preload("res://ui/utility_page_style.gd")
|
||||
const OrganizerTabType = preload("res://ui/components/organizer_tab.gd")
|
||||
|
||||
signal menu_visibility_changed(is_open: bool)
|
||||
signal sell_fish_requested
|
||||
|
|
@ -39,6 +40,22 @@ enum CloseReason {
|
|||
TEARDOWN,
|
||||
}
|
||||
|
||||
enum ShopSection {
|
||||
UPGRADES,
|
||||
BAIT,
|
||||
SNACKS,
|
||||
EQUIPMENT,
|
||||
ART_SUPPLIES,
|
||||
}
|
||||
|
||||
const SHOP_SECTION_LABELS: Array[String] = [
|
||||
"Upgrades",
|
||||
"Bait",
|
||||
"Snacks",
|
||||
"Equipment",
|
||||
"Art Supplies",
|
||||
]
|
||||
|
||||
@onready var _wallet_label: Label = %WalletLabel
|
||||
@onready var _shop_panel: PanelContainer = %ShopPanel
|
||||
@onready var _shop_cooler_page: Control = %ShopCoolerPage
|
||||
|
|
@ -47,6 +64,10 @@ enum CloseReason {
|
|||
@onready var _buy_mode_button: Button = %BuyModeButton
|
||||
@onready var _sell_mode_button: Button = %SellModeButton
|
||||
@onready var _feedback: Label = %Feedback
|
||||
@onready var _shop_tab_bar: HBoxContainer = %ShopTabBar
|
||||
@onready var _upgrades_content: VBoxContainer = %Upgrades
|
||||
@onready var _supplies_content: VBoxContainer = %Supplies
|
||||
@onready var _stock_title: Label = %StockTitle
|
||||
@onready var _reel_level: Label = %ReelLevel
|
||||
@onready var _reel_effect: Label = %ReelEffect
|
||||
@onready var _reel_cost: Label = %ReelCost
|
||||
|
|
@ -81,11 +102,14 @@ var _transaction_in_progress: bool = false
|
|||
var _closing: bool = false
|
||||
var _cooler_page_active: bool = false
|
||||
var _cooler_modal_open: bool = false
|
||||
var _shop_section: ShopSection = ShopSection.UPGRADES
|
||||
var _shop_tabs: Array[OrganizerTab] = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
UtilityPageStyleType.apply_page(self)
|
||||
_apply_shop_styles()
|
||||
_build_shop_tabs()
|
||||
%CloseButton.pressed.connect(close_shop)
|
||||
_buy_mode_button.pressed.connect(_focus_buy_page)
|
||||
_sell_mode_button.pressed.connect(_request_shop_cooler)
|
||||
|
|
@ -107,12 +131,39 @@ func _request_shop_cooler() -> void:
|
|||
|
||||
func _focus_buy_page() -> void:
|
||||
_feedback.text = ""
|
||||
if _shop_section == ShopSection.UPGRADES:
|
||||
_reel_purchase.grab_focus()
|
||||
return
|
||||
for child: Node in _supplies_list.get_children():
|
||||
var stock_button := child as Button
|
||||
if stock_button != null and not stock_button.disabled:
|
||||
stock_button.grab_focus()
|
||||
return
|
||||
_reel_purchase.grab_focus()
|
||||
_buy_mode_button.grab_focus()
|
||||
|
||||
|
||||
func _build_shop_tabs() -> void:
|
||||
for section_index: int in range(ShopSection.size()):
|
||||
var tab: OrganizerTab = OrganizerTabType.new()
|
||||
tab.text = SHOP_SECTION_LABELS[section_index]
|
||||
tab.palette_index = section_index
|
||||
tab.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
tab.pressed.connect(_select_shop_section.bind(section_index, true))
|
||||
_shop_tab_bar.add_child(tab)
|
||||
_shop_tabs.append(tab)
|
||||
_select_shop_section(ShopSection.UPGRADES, false)
|
||||
|
||||
|
||||
func _select_shop_section(section_index: int, focus_content: bool) -> void:
|
||||
_shop_section = section_index as ShopSection
|
||||
var showing_upgrades: bool = _shop_section == ShopSection.UPGRADES
|
||||
_upgrades_content.visible = showing_upgrades
|
||||
_supplies_content.visible = not showing_upgrades
|
||||
for tab_index: int in range(_shop_tabs.size()):
|
||||
_shop_tabs[tab_index].set_selected(tab_index == section_index, true)
|
||||
_refresh_supplies()
|
||||
if focus_content and visible:
|
||||
_focus_buy_page()
|
||||
|
||||
|
||||
func _apply_shop_styles() -> void:
|
||||
|
|
@ -236,6 +287,7 @@ func open_shop() -> bool:
|
|||
_feedback.text = ""
|
||||
deactivate_shop_cooler_page()
|
||||
show()
|
||||
_select_shop_section(ShopSection.UPGRADES, false)
|
||||
_refresh_all()
|
||||
_buy_mode_button.grab_focus()
|
||||
menu_visibility_changed.emit(true)
|
||||
|
|
@ -413,10 +465,12 @@ func _refresh_supplies() -> void:
|
|||
for child: Node in _supplies_list.get_children():
|
||||
_supplies_list.remove_child(child)
|
||||
child.queue_free()
|
||||
_add_stock_section("supplies")
|
||||
if _shop_section == ShopSection.UPGRADES:
|
||||
return
|
||||
_stock_title.text = SHOP_SECTION_LABELS[int(_shop_section)]
|
||||
for item_id: StringName in FishingShopStockType.get_stock_item_ids():
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(item_id)
|
||||
if item == null:
|
||||
if item == null or not _item_belongs_in_current_section(item):
|
||||
continue
|
||||
var button := Button.new()
|
||||
button.custom_minimum_size = Vector2(195, 54)
|
||||
|
|
@ -492,6 +546,7 @@ func _refresh_supplies() -> void:
|
|||
UtilityPageStyleType.apply_ocean_button(button)
|
||||
button.pressed.connect(_purchase_supply.bind(item_id))
|
||||
_supplies_list.add_child(button)
|
||||
if _shop_section == ShopSection.ART_SUPPLIES:
|
||||
_add_stock_section("art kit")
|
||||
_add_art_kit_button()
|
||||
_add_stock_section("markers")
|
||||
|
|
@ -505,6 +560,20 @@ func _refresh_supplies() -> void:
|
|||
_add_art_upgrade_button(product_id)
|
||||
|
||||
|
||||
func _item_belongs_in_current_section(item: ItemDataType) -> bool:
|
||||
match _shop_section:
|
||||
ShopSection.BAIT:
|
||||
return item.is_bait()
|
||||
ShopSection.SNACKS:
|
||||
return item.category == ItemDataType.Category.CONSUMABLE
|
||||
ShopSection.EQUIPMENT:
|
||||
return item.category in [
|
||||
ItemDataType.Category.TOOL,
|
||||
ItemDataType.Category.LURE,
|
||||
]
|
||||
return false
|
||||
|
||||
|
||||
func _add_stock_section(title: String) -> void:
|
||||
var label := Label.new()
|
||||
label.text = title
|
||||
|
|
|
|||
|
|
@ -143,12 +143,19 @@ text = ""
|
|||
horizontal_alignment = 1
|
||||
theme_override_colors/font_color = Color(1, 0.82, 0.4, 1)
|
||||
|
||||
[node name="ShopTabBar" type="HBoxContainer" parent="ShopPanel/Margin/Layout"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
alignment = 1
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="Body" type="HBoxContainer" parent="ShopPanel/Margin/Layout"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
theme_override_constants/separation = 12
|
||||
theme_override_constants/separation = 0
|
||||
|
||||
[node name="Upgrades" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(270, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
|
@ -160,12 +167,14 @@ text = "fishing upgrades"
|
|||
theme_override_font_sizes/font_size = 23
|
||||
|
||||
[node name="Supplies" type="VBoxContainer" parent="ShopPanel/Margin/Layout/Body"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(210, 0)
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 6
|
||||
|
||||
[node name="Title" type="Label" parent="ShopPanel/Margin/Layout/Body/Supplies"]
|
||||
[node name="StockTitle" type="Label" parent="ShopPanel/Margin/Layout/Body/Supplies"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "shop stock"
|
||||
theme_override_font_sizes/font_size = 23
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ const FISHING_PANEL_SHOWCASE_TOP_OFFSET: float = -174.0
|
|||
const FISHING_PANEL_SHOWCASE_BOTTOM_OFFSET: float = -104.0
|
||||
|
||||
@onready var _status_label: Label = %StatusLabel
|
||||
@onready var _bite_prompt_button: Button = %BitePromptButton
|
||||
@onready var _gameplay_transient_hud: Control = %GameplayTransientHUD
|
||||
@onready var _active_bait_button: Button = %ActiveBaitButton
|
||||
@onready var _active_bait_quantity_badge: Panel = %ActiveBaitQuantityBadge
|
||||
|
|
@ -190,6 +191,7 @@ var _settings_manager: PlayerSettingsManagerType
|
|||
|
||||
|
||||
func _ready() -> void:
|
||||
_bite_prompt_button.pressed.connect(_on_bite_prompt_pressed)
|
||||
_apply_active_bait_indicator_style()
|
||||
_refresh_active_bait_indicator()
|
||||
# Reward feedback must remain above full-screen canonical menus. Keeping the
|
||||
|
|
@ -307,7 +309,7 @@ func setup(
|
|||
_experience.experience_awarded.connect(_on_experience_awarded)
|
||||
_chat_ui.setup(
|
||||
network_chat_service, network_session, spawn_service, player,
|
||||
fishing_spot, settings_manager, world_time, world_weather
|
||||
fishing_spot, settings_manager, world_time, world_weather, item_effects
|
||||
)
|
||||
_title_settings_panel.setup_network_profile(
|
||||
network_profile, network_session
|
||||
|
|
@ -316,6 +318,7 @@ func setup(
|
|||
network_profile, network_session
|
||||
)
|
||||
fishing_spot.status_changed.connect(_on_fishing_status_changed)
|
||||
fishing_spot.bite_prompt_changed.connect(_on_bite_prompt_changed)
|
||||
fishing_spot.catch_display_changed.connect(_on_catch_display_changed)
|
||||
fishing_spot.showcase_changed.connect(_on_showcase_changed)
|
||||
fishing_spot.art_ui_toggle_requested.connect(_toggle_surface_drawing)
|
||||
|
|
@ -1590,6 +1593,7 @@ func _refresh_fishing_panel_visibility() -> void:
|
|||
var has_content: bool = (
|
||||
not _status_label.text.strip_edges().is_empty()
|
||||
or not _showcase_details.text.strip_edges().is_empty()
|
||||
or _bite_prompt_button.visible
|
||||
)
|
||||
_fishing_panel.visible = (
|
||||
_gameplay_ui_enabled
|
||||
|
|
@ -1597,6 +1601,16 @@ func _refresh_fishing_panel_visibility() -> void:
|
|||
)
|
||||
|
||||
|
||||
func _on_bite_prompt_changed(is_visible: bool) -> void:
|
||||
_bite_prompt_button.visible = is_visible
|
||||
_refresh_fishing_panel_visibility()
|
||||
|
||||
|
||||
func _on_bite_prompt_pressed() -> void:
|
||||
if _fishing_spot != null:
|
||||
_fishing_spot.confirm_pending_bite()
|
||||
|
||||
|
||||
func _on_catch_display_changed(
|
||||
progress: float,
|
||||
chase_progress: float,
|
||||
|
|
|
|||
|
|
@ -279,6 +279,16 @@ text = ""
|
|||
horizontal_alignment = 1
|
||||
theme_override_font_sizes/font_size = 16
|
||||
|
||||
[node name="BitePromptButton" type="Button" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
custom_minimum_size = Vector2(0, 42)
|
||||
layout_mode = 2
|
||||
focus_mode = 0
|
||||
mouse_default_cursor_shape = 2
|
||||
text = "you got a bite!"
|
||||
theme_override_font_sizes/font_size = 22
|
||||
|
||||
[node name="ShowcaseDetails" type="Label" parent="UIRoot/CanonicalStage/GameplayTransientHUD/FishingPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
|
|
|
|||
|
|
@ -189,6 +189,7 @@ const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
|
|||
@onready var _cooler_combined_offer_value: Label = %CoolerCombinedOfferValue
|
||||
@onready var _favorite_bubble: NotepadInkActionType = %FavoriteBubble
|
||||
@onready var _sell_bubble: NotepadInkActionType = %SellBubble
|
||||
@onready var _sell_all_bubble: NotepadInkActionType = %SellAllBubble
|
||||
@onready var _bag_page: Control = %BagPage
|
||||
@onready var _tackle_box_page: Control = %TackleBoxPage
|
||||
@onready var _inventory_sub_tabs: HBoxContainer = %InventorySubTabs
|
||||
|
|
@ -392,7 +393,7 @@ func _ready() -> void:
|
|||
)
|
||||
_bait_filter.pressed.connect(_set_tackle_view.bind(TackleView.BAIT))
|
||||
_lures_filter.pressed.connect(_set_tackle_view.bind(TackleView.LURES))
|
||||
_tackle_equip_button.pressed.connect(_toggle_active_bait)
|
||||
_tackle_equip_button.pressed.connect(_toggle_active_tackle)
|
||||
_logbook_tab.pressed.connect(
|
||||
_show_section.bind(Section.LOGBOOK)
|
||||
)
|
||||
|
|
@ -409,6 +410,7 @@ func _ready() -> void:
|
|||
_sell_button.pressed.connect(_on_sell_pressed)
|
||||
_favorite_bubble.pressed.connect(_on_favorite_pressed)
|
||||
_sell_bubble.pressed.connect(_on_sell_pressed)
|
||||
_sell_all_bubble.pressed.connect(_on_sell_all_pressed)
|
||||
_confirm_sale_button.pressed.connect(_on_confirm_sale_pressed)
|
||||
_cancel_sale_button.pressed.connect(_close_sale_confirmation)
|
||||
_configure_sale_confirmation_focus()
|
||||
|
|
@ -593,6 +595,8 @@ func setup(
|
|||
_bag.contents_changed.connect(_on_bag_changed)
|
||||
if not _player.active_bait_changed.is_connected(_on_active_bait_changed):
|
||||
_player.active_bait_changed.connect(_on_active_bait_changed)
|
||||
if not _player.active_lure_changed.is_connected(_on_active_lure_changed):
|
||||
_player.active_lure_changed.connect(_on_active_lure_changed)
|
||||
if not _hotbar.slots_changed.is_connected(_on_hotbar_changed):
|
||||
_hotbar.slots_changed.connect(_on_hotbar_changed)
|
||||
if not _cooler_capacity.capacity_changed.is_connected(
|
||||
|
|
@ -754,6 +758,8 @@ func _try_enter_notepad_controller_ownership() -> bool:
|
|||
actions.append(_favorite_bubble)
|
||||
if not _sell_bubble.disabled:
|
||||
actions.append(_sell_bubble)
|
||||
if not _sell_all_bubble.disabled:
|
||||
actions.append(_sell_all_bubble)
|
||||
elif _current_section == Section.TACKLE_BOX:
|
||||
if not focus_owner.has_meta(&"controller_tackle_item_id"):
|
||||
return false
|
||||
|
|
@ -1943,20 +1949,29 @@ func _refresh_tackle_box() -> void:
|
|||
var item: ItemDataType = _item_catalog.get_item_by_id(owned.item_id)
|
||||
var row := Button.new()
|
||||
row.icon = item.icon
|
||||
row.tooltip_text = "%s ×%d" % [item.display_name, owned.quantity]
|
||||
if item.is_bait() and item.icon != null:
|
||||
row.tooltip_text = (
|
||||
"%s ×%d" % [item.display_name, owned.quantity]
|
||||
if item.is_bait()
|
||||
else item.display_name
|
||||
)
|
||||
var compact_tackle_item: bool = item.is_bait() or item.is_lure()
|
||||
if compact_tackle_item:
|
||||
row.custom_minimum_size = Vector2(72, 72)
|
||||
row.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN
|
||||
row.expand_icon = true
|
||||
row.expand_icon = item.icon != null
|
||||
row.alignment = HORIZONTAL_ALIGNMENT_CENTER
|
||||
row.text = "" if item.icon != null else item.display_name
|
||||
if item.icon == null:
|
||||
row.add_theme_font_size_override("font_size", 12)
|
||||
else:
|
||||
row.text = "%s ×%d" % [item.display_name, owned.quantity]
|
||||
row.alignment = HORIZONTAL_ALIGNMENT_LEFT
|
||||
row.toggle_mode = true
|
||||
row.set_meta(&"controller_tackle_item_id", owned.item_id)
|
||||
row.button_pressed = owned.item_id == _selected_tackle_item_id
|
||||
if item.is_bait() and item.icon != null:
|
||||
if compact_tackle_item:
|
||||
_apply_tackle_bait_button_style(row)
|
||||
if item.is_bait():
|
||||
_add_tackle_quantity_badge(row, owned.quantity)
|
||||
else:
|
||||
UtilityPageStyle.apply_ocean_button(row)
|
||||
|
|
@ -2053,7 +2068,7 @@ func _update_tackle_detail() -> void:
|
|||
else "Select a lure for details."
|
||||
)
|
||||
return
|
||||
_tackle_equip_button.visible = item.is_bait()
|
||||
_tackle_equip_button.visible = item.is_bait() or item.is_lure()
|
||||
var quantity: int = _bag.get_quantity(item.item_id) if _bag != null else 0
|
||||
var assigned_slot: int = -1
|
||||
if _hotbar != null:
|
||||
|
|
@ -2061,19 +2076,22 @@ func _update_tackle_detail() -> void:
|
|||
if _hotbar.get_item_id(slot_index) == item.item_id:
|
||||
assigned_slot = slot_index
|
||||
break
|
||||
var detail_lines: Array[String] = [
|
||||
item.display_name,
|
||||
"",
|
||||
"quantity: %d" % quantity,
|
||||
]
|
||||
var detail_lines: Array[String] = [item.display_name, ""]
|
||||
if item.is_bait():
|
||||
detail_lines.append("quantity: %d" % quantity)
|
||||
if assigned_slot >= 0:
|
||||
detail_lines.append("hotbar slot %d" % (assigned_slot + 1))
|
||||
detail_lines.append("")
|
||||
detail_lines.append(item.description)
|
||||
_tackle_detail_text.text = "\n".join(detail_lines)
|
||||
if item.is_bait():
|
||||
if item.is_bait() or item.is_lure():
|
||||
var is_equipped: bool = (
|
||||
_player != null and _player.active_bait_id == item.item_id
|
||||
_player != null
|
||||
and (
|
||||
_player.active_bait_id == item.item_id
|
||||
if item.is_bait()
|
||||
else _player.active_lure_id == item.item_id
|
||||
)
|
||||
)
|
||||
_tackle_equip_button.text = (
|
||||
"dequip %s" % item.display_name
|
||||
|
|
@ -2085,16 +2103,24 @@ func _update_tackle_detail() -> void:
|
|||
_tackle_equip_button.refresh_ink_state()
|
||||
|
||||
|
||||
func _toggle_active_bait() -> void:
|
||||
func _toggle_active_tackle() -> void:
|
||||
if _player == null or _item_catalog == null:
|
||||
return
|
||||
var item: ItemDataType = _item_catalog.get_item_by_id(_selected_tackle_item_id)
|
||||
if item == null or not item.is_bait():
|
||||
if item == null:
|
||||
return
|
||||
if item.is_bait():
|
||||
if _player.active_bait_id == item.item_id:
|
||||
_player.unequip_bait()
|
||||
else:
|
||||
_player.equip_bait(item)
|
||||
elif item.is_lure():
|
||||
if _player.active_lure_id == item.item_id:
|
||||
_player.unequip_lure()
|
||||
else:
|
||||
_player.equip_lure(item)
|
||||
else:
|
||||
return
|
||||
_update_tackle_detail()
|
||||
|
||||
|
||||
|
|
@ -2102,6 +2128,10 @@ func _on_active_bait_changed(_item_id: StringName) -> void:
|
|||
_update_tackle_detail()
|
||||
|
||||
|
||||
func _on_active_lure_changed(_item_id: StringName) -> void:
|
||||
_update_tackle_detail()
|
||||
|
||||
|
||||
func _apply_cooler_control_styles() -> void:
|
||||
var profile: BubbleMenuProfile = _inventory_tab.profile
|
||||
if profile == null:
|
||||
|
|
@ -2426,6 +2456,9 @@ func _update_shell_layout() -> void:
|
|||
_sell_bubble.custom_minimum_size = (
|
||||
Vector2(74.0, 42.0) if compact else Vector2(118.0, 50.0)
|
||||
)
|
||||
_sell_all_bubble.custom_minimum_size = (
|
||||
Vector2(96.0, 38.0) if compact else Vector2(122.0, 44.0)
|
||||
)
|
||||
_favorite_bubble.position = (
|
||||
Vector2(144.0, 148.0) if compact else Vector2(26.0, 378.0)
|
||||
)
|
||||
|
|
@ -2438,12 +2471,21 @@ func _update_shell_layout() -> void:
|
|||
_sell_bubble.size = (
|
||||
Vector2(74.0, 42.0) if compact else Vector2(118.0, 50.0)
|
||||
)
|
||||
_sell_all_bubble.position = (
|
||||
Vector2(102.0, 190.0) if compact else Vector2(78.0, 426.0)
|
||||
)
|
||||
_sell_all_bubble.size = (
|
||||
Vector2(96.0, 38.0) if compact else Vector2(122.0, 44.0)
|
||||
)
|
||||
_favorite_bubble.add_theme_font_size_override(
|
||||
"font_size", 11 if compact else 17,
|
||||
)
|
||||
_sell_bubble.add_theme_font_size_override(
|
||||
"font_size", 11 if compact else 17,
|
||||
)
|
||||
_sell_all_bubble.add_theme_font_size_override(
|
||||
"font_size", 11 if compact else 15,
|
||||
)
|
||||
_update_sort_direction_text()
|
||||
_bag_page.size = reference_size
|
||||
_bag_page.position = Vector2.ZERO
|
||||
|
|
@ -2946,7 +2988,11 @@ func _set_content_interactive(interactive: bool) -> void:
|
|||
var detail_interactive: bool = (
|
||||
cooler_interactive and _detail_constellation.visible
|
||||
)
|
||||
for action: NotepadInkActionType in [_favorite_bubble, _sell_bubble]:
|
||||
for action: NotepadInkActionType in [
|
||||
_favorite_bubble,
|
||||
_sell_bubble,
|
||||
_sell_all_bubble,
|
||||
]:
|
||||
var action_interactive: bool = detail_interactive and not action.disabled
|
||||
action.focus_mode = (
|
||||
Control.FOCUS_ALL
|
||||
|
|
@ -3595,6 +3641,11 @@ func _configure_cooler_fish_focus() -> void:
|
|||
not _favorite_bubble.disabled
|
||||
and index + columns >= controls.size()
|
||||
)
|
||||
else control.get_path_to(_sell_all_bubble)
|
||||
if (
|
||||
not _sell_all_bubble.disabled
|
||||
and index + columns >= controls.size()
|
||||
)
|
||||
else control.get_path_to(
|
||||
controls[mini(index + columns, controls.size() - 1)]
|
||||
)
|
||||
|
|
@ -3620,6 +3671,18 @@ func _configure_cooler_fish_focus() -> void:
|
|||
_sell_bubble.get_path_to(_close_button)
|
||||
)
|
||||
_sell_bubble.focus_neighbor_top = _sell_bubble.get_path_to(focused_node)
|
||||
_sell_bubble.focus_neighbor_bottom = (
|
||||
_sell_bubble.get_path_to(_sell_all_bubble)
|
||||
)
|
||||
_sell_all_bubble.focus_neighbor_left = (
|
||||
_sell_all_bubble.get_path_to(_favorite_bubble)
|
||||
)
|
||||
_sell_all_bubble.focus_neighbor_right = (
|
||||
_sell_all_bubble.get_path_to(_close_button)
|
||||
)
|
||||
_sell_all_bubble.focus_neighbor_top = (
|
||||
_sell_all_bubble.get_path_to(_sell_bubble)
|
||||
)
|
||||
|
||||
|
||||
func _compare_catches(left: FishCatchType, right: FishCatchType) -> bool:
|
||||
|
|
@ -3684,6 +3747,7 @@ func _close_detail_constellation() -> void:
|
|||
_sell_bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_favorite_bubble.refresh_ink_state()
|
||||
_sell_bubble.refresh_ink_state()
|
||||
_refresh_cooler_notepad_action_interactivity()
|
||||
_configure_cooler_fish_focus()
|
||||
|
||||
|
||||
|
|
@ -3750,6 +3814,7 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
_sell_bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
_favorite_bubble.refresh_ink_state()
|
||||
_sell_bubble.refresh_ink_state()
|
||||
_refresh_cooler_notepad_action_interactivity()
|
||||
_configure_cooler_fish_focus()
|
||||
return
|
||||
_cooler_detail_texture.texture = fish_catch.fish.display_texture
|
||||
|
|
@ -3797,7 +3862,24 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
and not _transitioning
|
||||
and not _page_transitioning
|
||||
)
|
||||
for action: NotepadInkActionType in [_favorite_bubble, _sell_bubble]:
|
||||
_refresh_cooler_notepad_action_interactivity()
|
||||
_configure_cooler_fish_focus()
|
||||
|
||||
|
||||
func _refresh_cooler_notepad_action_interactivity() -> void:
|
||||
var detail_interactive: bool = (
|
||||
_detail_constellation.visible
|
||||
and _current_section == Section.COOLER
|
||||
and (visible or _shop_cooler_context_active)
|
||||
and not _transitioning
|
||||
and not _page_transitioning
|
||||
and not _sale_confirmation.visible
|
||||
)
|
||||
for action: NotepadInkActionType in [
|
||||
_favorite_bubble,
|
||||
_sell_bubble,
|
||||
_sell_all_bubble,
|
||||
]:
|
||||
var action_interactive: bool = detail_interactive and not action.disabled
|
||||
action.focus_mode = (
|
||||
Control.FOCUS_ALL
|
||||
|
|
@ -3810,7 +3892,6 @@ func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|||
else Control.MOUSE_FILTER_IGNORE
|
||||
)
|
||||
action.refresh_ink_state()
|
||||
_configure_cooler_fish_focus()
|
||||
|
||||
|
||||
func _update_sale_summary() -> void:
|
||||
|
|
@ -3818,6 +3899,7 @@ func _update_sale_summary() -> void:
|
|||
var buyer_id: StringName = (
|
||||
active_buyer.id if active_buyer != null else StringName()
|
||||
)
|
||||
_update_sell_all_action(active_buyer, buyer_id)
|
||||
var offer_label: String = _get_offer_label(active_buyer)
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
var selected_count: int = selected_ids.size()
|
||||
|
|
@ -3927,6 +4009,55 @@ func _update_sale_summary() -> void:
|
|||
_sale_unavailable.visible = not _sale_unavailable.text.is_empty()
|
||||
|
||||
|
||||
func _update_sell_all_action(
|
||||
active_buyer: FishBuyerProfileType,
|
||||
buyer_id: StringName,
|
||||
) -> void:
|
||||
var sell_all_ids: Array[StringName] = _get_sell_all_catch_ids()
|
||||
var sale_available: bool = (
|
||||
not _sale_in_progress
|
||||
and (
|
||||
_network_sale_service == null
|
||||
or not _network_sale_service.is_local_sale_pending()
|
||||
)
|
||||
and _network_sale_service != null
|
||||
and not buyer_id.is_empty()
|
||||
and _network_sale_service.can_request_sale(buyer_id)
|
||||
and _sale_service != null
|
||||
and active_buyer != null
|
||||
and not sell_all_ids.is_empty()
|
||||
)
|
||||
if sale_available:
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
sell_all_ids,
|
||||
active_buyer,
|
||||
)
|
||||
sale_available = preview.is_success()
|
||||
_sell_all_bubble.text = "sell all fish"
|
||||
_sell_all_bubble.disabled = not sale_available
|
||||
_sell_all_bubble.persistent_mark = false
|
||||
_sell_all_bubble.refresh_ink_state()
|
||||
|
||||
|
||||
func _get_sell_all_catch_ids() -> Array[StringName]:
|
||||
var catch_ids: Array[StringName] = []
|
||||
if _inventory == null:
|
||||
return catch_ids
|
||||
for fish_catch: FishCatchType in _inventory.get_all_catches():
|
||||
if (
|
||||
fish_catch == null
|
||||
or not fish_catch.is_valid()
|
||||
or fish_catch.is_favorited
|
||||
or (
|
||||
_reservations != null
|
||||
and _reservations.is_fish_reserved(fish_catch.catch_id)
|
||||
)
|
||||
):
|
||||
continue
|
||||
catch_ids.append(fish_catch.catch_id)
|
||||
return catch_ids
|
||||
|
||||
|
||||
func _on_favorite_pressed() -> void:
|
||||
var focused_id: StringName = _fish_selection.get_focused_id()
|
||||
if _inventory == null or focused_id.is_empty():
|
||||
|
|
@ -3951,6 +4082,18 @@ func _on_favorite_pressed() -> void:
|
|||
|
||||
|
||||
func _on_sell_pressed() -> void:
|
||||
_begin_sale_confirmation(_fish_selection.get_selected_ids())
|
||||
|
||||
|
||||
func _on_sell_all_pressed() -> void:
|
||||
var catch_ids: Array[StringName] = _get_sell_all_catch_ids()
|
||||
if catch_ids.is_empty():
|
||||
_transaction_feedback.text = "No sellable fish in the cooler."
|
||||
return
|
||||
_begin_sale_confirmation(catch_ids)
|
||||
|
||||
|
||||
func _begin_sale_confirmation(catch_ids: Array[StringName]) -> void:
|
||||
var active_buyer: FishBuyerProfileType = _get_active_sale_buyer()
|
||||
var buyer_id: StringName = (
|
||||
active_buyer.id if active_buyer != null else StringName()
|
||||
|
|
@ -3973,16 +4116,15 @@ func _on_sell_pressed() -> void:
|
|||
"Selling is not supported by this server."
|
||||
)
|
||||
return
|
||||
var selected_ids: Array[StringName] = _fish_selection.get_selected_ids()
|
||||
if (
|
||||
_inventory == null
|
||||
or _sale_service == null
|
||||
or active_buyer == null
|
||||
or selected_ids.is_empty()
|
||||
or catch_ids.is_empty()
|
||||
):
|
||||
return
|
||||
var preview: FishSaleResultType = _sale_service.preview_batch(
|
||||
selected_ids,
|
||||
catch_ids,
|
||||
active_buyer
|
||||
)
|
||||
if not preview.is_success():
|
||||
|
|
@ -3994,7 +4136,7 @@ func _on_sell_pressed() -> void:
|
|||
)
|
||||
_refresh_inventory()
|
||||
return
|
||||
_confirmation_catch_ids = selected_ids.duplicate()
|
||||
_confirmation_catch_ids = catch_ids.duplicate()
|
||||
_confirmation_buyer = active_buyer
|
||||
_confirmation_buyer_id = active_buyer.id
|
||||
_confirmation_generation = _menu_generation
|
||||
|
|
|
|||
|
|
@ -470,6 +470,16 @@ offset_right = 256.0
|
|||
offset_bottom = 464.0
|
||||
text = "sell fish"
|
||||
|
||||
[node name="SellAllBubble" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot/CoolerPage/DetailConstellation" instance=ExtResource("12_ink_action")]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(122, 44)
|
||||
layout_mode = 0
|
||||
offset_left = 78.0
|
||||
offset_top = 426.0
|
||||
offset_right = 200.0
|
||||
offset_bottom = 470.0
|
||||
text = "sell all fish"
|
||||
|
||||
[node name="BagPage" type="Control" parent="ResponsivePlayerMenuStage/PlayerMenuPresentationScaleRoot"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue