2026-07-25 16:02:34 -04:00
|
|
|
|
class_name PlayerMenu
|
|
|
|
|
|
extends Control
|
|
|
|
|
|
|
|
|
|
|
|
const CollectionLogType = preload("res://collection/collection_log.gd")
|
|
|
|
|
|
const FishCatchType = preload("res://fish/fish_catch.gd")
|
|
|
|
|
|
const FishDataType = preload("res://fish/fish_data.gd")
|
2026-07-25 16:27:07 -04:00
|
|
|
|
const FishBuyerProfileType = preload("res://economy/fish_buyer_profile.gd")
|
|
|
|
|
|
const FishSaleResultType = preload("res://economy/fish_sale_result.gd")
|
|
|
|
|
|
const FishSaleServiceType = preload("res://economy/fish_sale_service.gd")
|
2026-07-25 16:02:34 -04:00
|
|
|
|
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
|
|
|
|
|
const FishPoolType = preload("res://fish/fish_pool.gd")
|
|
|
|
|
|
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
|
|
|
|
|
const PlayerType = preload("res://player/player.gd")
|
2026-07-25 16:27:07 -04:00
|
|
|
|
const PlayerWalletType = preload("res://economy/player_wallet.gd")
|
2026-07-25 16:02:34 -04:00
|
|
|
|
|
|
|
|
|
|
signal menu_visibility_changed(is_open: bool)
|
|
|
|
|
|
|
|
|
|
|
|
enum Section {
|
|
|
|
|
|
INVENTORY,
|
|
|
|
|
|
LOGBOOK,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
enum SortMode {
|
|
|
|
|
|
CATCH_ORDER,
|
|
|
|
|
|
NAME,
|
|
|
|
|
|
RARITY,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@onready var _inventory_tab: Button = %InventoryTab
|
|
|
|
|
|
@onready var _logbook_tab: Button = %LogbookTab
|
|
|
|
|
|
@onready var _close_button: Button = %CloseButton
|
2026-07-25 16:27:07 -04:00
|
|
|
|
@onready var _wallet_balance: Label = %WalletBalance
|
2026-07-25 16:02:34 -04:00
|
|
|
|
@onready var _inventory_section: Control = %InventorySection
|
|
|
|
|
|
@onready var _logbook_section: Control = %LogbookSection
|
|
|
|
|
|
@onready var _sort_option: OptionButton = %SortOption
|
|
|
|
|
|
@onready var _sort_direction: Button = %SortDirection
|
2026-07-25 16:27:07 -04:00
|
|
|
|
@onready var _held_value: Label = %HeldValue
|
2026-07-25 16:02:34 -04:00
|
|
|
|
@onready var _inventory_empty: Label = %InventoryEmpty
|
|
|
|
|
|
@onready var _inventory_grid: GridContainer = %InventoryGrid
|
|
|
|
|
|
@onready var _detail_texture: TextureRect = %DetailTexture
|
|
|
|
|
|
@onready var _detail_name: Label = %DetailName
|
|
|
|
|
|
@onready var _detail_data: Label = %DetailData
|
2026-07-25 16:27:07 -04:00
|
|
|
|
@onready var _favorite_button: Button = %FavoriteButton
|
|
|
|
|
|
@onready var _sell_button: Button = %SellButton
|
|
|
|
|
|
@onready var _sale_unavailable: Label = %SaleUnavailable
|
|
|
|
|
|
@onready var _transaction_feedback: Label = %TransactionFeedback
|
|
|
|
|
|
@onready var _sale_confirmation: PanelContainer = %SaleConfirmation
|
|
|
|
|
|
@onready var _confirmation_message: Label = %ConfirmationMessage
|
|
|
|
|
|
@onready var _confirm_sale_button: Button = %ConfirmSaleButton
|
|
|
|
|
|
@onready var _cancel_sale_button: Button = %CancelSaleButton
|
2026-07-25 16:02:34 -04:00
|
|
|
|
@onready var _logbook_empty: Label = %LogbookEmpty
|
|
|
|
|
|
@onready var _logbook_grid: GridContainer = %LogbookGrid
|
|
|
|
|
|
|
|
|
|
|
|
var _player: PlayerType
|
|
|
|
|
|
var _inventory: FishInventoryType
|
|
|
|
|
|
var _collection_log: CollectionLogType
|
2026-07-25 16:27:07 -04:00
|
|
|
|
var _wallet: PlayerWalletType
|
|
|
|
|
|
var _sale_service: FishSaleServiceType
|
|
|
|
|
|
var _default_buyer: FishBuyerProfileType
|
2026-07-25 16:02:34 -04:00
|
|
|
|
var _catalog: FishPoolType
|
|
|
|
|
|
var _fishing_spot: FishingSpotType
|
|
|
|
|
|
var _current_section: Section = Section.INVENTORY
|
|
|
|
|
|
var _sort_mode: SortMode = SortMode.CATCH_ORDER
|
|
|
|
|
|
var _sort_descending: bool = true
|
|
|
|
|
|
var _selected_catch_id: StringName
|
|
|
|
|
|
var _prior_movement_enabled: bool = true
|
|
|
|
|
|
var _prior_camera_input_enabled: bool = true
|
|
|
|
|
|
var _control_snapshot_stored: bool = false
|
|
|
|
|
|
var _menu_generation: int = 0
|
2026-07-25 16:27:07 -04:00
|
|
|
|
var _confirmation_catch_id: StringName
|
|
|
|
|
|
var _confirmation_buyer: FishBuyerProfileType
|
|
|
|
|
|
var _confirmation_buyer_id: StringName
|
|
|
|
|
|
var _sale_in_progress: bool = false
|
2026-07-25 16:02:34 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _ready() -> void:
|
|
|
|
|
|
_inventory_tab.pressed.connect(
|
|
|
|
|
|
_show_section.bind(Section.INVENTORY)
|
|
|
|
|
|
)
|
|
|
|
|
|
_logbook_tab.pressed.connect(
|
|
|
|
|
|
_show_section.bind(Section.LOGBOOK)
|
|
|
|
|
|
)
|
|
|
|
|
|
_close_button.pressed.connect(close_menu)
|
|
|
|
|
|
_sort_option.item_selected.connect(_on_sort_selected)
|
|
|
|
|
|
_sort_direction.pressed.connect(_on_sort_direction_pressed)
|
2026-07-25 16:27:07 -04:00
|
|
|
|
_favorite_button.pressed.connect(_on_favorite_pressed)
|
|
|
|
|
|
_sell_button.pressed.connect(_on_sell_pressed)
|
|
|
|
|
|
_confirm_sale_button.pressed.connect(_on_confirm_sale_pressed)
|
|
|
|
|
|
_cancel_sale_button.pressed.connect(_close_sale_confirmation)
|
2026-07-25 16:02:34 -04:00
|
|
|
|
_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)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func setup(
|
|
|
|
|
|
player: PlayerType,
|
|
|
|
|
|
inventory: FishInventoryType,
|
|
|
|
|
|
collection_log: CollectionLogType,
|
2026-07-25 16:27:07 -04:00
|
|
|
|
wallet: PlayerWalletType,
|
|
|
|
|
|
sale_service: FishSaleServiceType,
|
|
|
|
|
|
default_buyer: FishBuyerProfileType,
|
2026-07-25 16:02:34 -04:00
|
|
|
|
catalog: FishPoolType,
|
|
|
|
|
|
fishing_spot: FishingSpotType,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
_player = player
|
|
|
|
|
|
_inventory = inventory
|
|
|
|
|
|
_collection_log = collection_log
|
2026-07-25 16:27:07 -04:00
|
|
|
|
_wallet = wallet
|
|
|
|
|
|
_sale_service = sale_service
|
|
|
|
|
|
_default_buyer = default_buyer
|
2026-07-25 16:02:34 -04:00
|
|
|
|
_catalog = catalog
|
|
|
|
|
|
_fishing_spot = fishing_spot
|
|
|
|
|
|
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):
|
|
|
|
|
|
_collection_log.fish_discovered.connect(_on_fish_discovered)
|
2026-07-25 16:27:07 -04:00
|
|
|
|
if not _wallet.balance_changed.is_connected(_on_wallet_balance_changed):
|
|
|
|
|
|
_wallet.balance_changed.connect(_on_wallet_balance_changed)
|
2026-07-25 16:02:34 -04:00
|
|
|
|
if not _fishing_spot.bite_activated.is_connected(_on_bite_activated):
|
|
|
|
|
|
_fishing_spot.bite_activated.connect(_on_bite_activated)
|
|
|
|
|
|
_refresh_all()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _input(event: InputEvent) -> void:
|
|
|
|
|
|
if event is InputEventKey and event.echo:
|
|
|
|
|
|
return
|
|
|
|
|
|
if visible:
|
2026-07-25 16:27:07 -04:00
|
|
|
|
if (
|
|
|
|
|
|
event.is_action_pressed("ui_cancel")
|
|
|
|
|
|
and _sale_confirmation.visible
|
|
|
|
|
|
):
|
|
|
|
|
|
_close_sale_confirmation()
|
|
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
return
|
2026-07-25 16:02:34 -04:00
|
|
|
|
if (
|
|
|
|
|
|
event.is_action_pressed("open_backpack")
|
|
|
|
|
|
or event.is_action_pressed("ui_cancel")
|
|
|
|
|
|
):
|
|
|
|
|
|
close_menu()
|
|
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
return
|
|
|
|
|
|
if (
|
|
|
|
|
|
event.is_action_pressed("open_backpack")
|
|
|
|
|
|
and _fishing_spot != null
|
|
|
|
|
|
and _fishing_spot.can_open_player_menu()
|
|
|
|
|
|
):
|
|
|
|
|
|
open_menu()
|
|
|
|
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func open_menu() -> void:
|
|
|
|
|
|
if (
|
|
|
|
|
|
visible
|
|
|
|
|
|
or _player == null
|
|
|
|
|
|
or _fishing_spot == null
|
|
|
|
|
|
or not _fishing_spot.can_open_player_menu()
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
_menu_generation += 1
|
|
|
|
|
|
_prior_movement_enabled = _player.is_movement_enabled()
|
|
|
|
|
|
_prior_camera_input_enabled = _player.is_camera_input_enabled()
|
|
|
|
|
|
_control_snapshot_stored = true
|
|
|
|
|
|
_player.set_movement_enabled(false)
|
|
|
|
|
|
_player.set_camera_input_enabled(false)
|
|
|
|
|
|
visible = true
|
|
|
|
|
|
_refresh_all()
|
|
|
|
|
|
_show_section(_current_section)
|
|
|
|
|
|
_focus_current_section()
|
|
|
|
|
|
menu_visibility_changed.emit(true)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func close_menu() -> void:
|
|
|
|
|
|
if not visible:
|
|
|
|
|
|
return
|
2026-07-25 16:27:07 -04:00
|
|
|
|
_close_sale_confirmation()
|
2026-07-25 16:02:34 -04:00
|
|
|
|
var closing_generation: int = _menu_generation
|
|
|
|
|
|
visible = false
|
|
|
|
|
|
get_viewport().gui_release_focus()
|
|
|
|
|
|
_restore_player_controls(closing_generation)
|
|
|
|
|
|
_menu_generation += 1
|
|
|
|
|
|
menu_visibility_changed.emit(false)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _exit_tree() -> void:
|
|
|
|
|
|
if visible:
|
|
|
|
|
|
visible = false
|
|
|
|
|
|
_restore_player_controls(_menu_generation)
|
|
|
|
|
|
_selected_catch_id = StringName()
|
|
|
|
|
|
_menu_generation += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _restore_player_controls(generation: int) -> void:
|
|
|
|
|
|
if (
|
|
|
|
|
|
generation != _menu_generation
|
|
|
|
|
|
or not _control_snapshot_stored
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
if _player != null and is_instance_valid(_player):
|
|
|
|
|
|
_player.set_movement_enabled(_prior_movement_enabled)
|
|
|
|
|
|
_player.set_camera_input_enabled(_prior_camera_input_enabled)
|
|
|
|
|
|
_control_snapshot_stored = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_bite_activated() -> void:
|
|
|
|
|
|
if visible:
|
|
|
|
|
|
close_menu()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _show_section(section: Section) -> void:
|
|
|
|
|
|
_current_section = section
|
|
|
|
|
|
if not is_node_ready():
|
|
|
|
|
|
return
|
|
|
|
|
|
_inventory_section.visible = section == Section.INVENTORY
|
|
|
|
|
|
_logbook_section.visible = section == Section.LOGBOOK
|
|
|
|
|
|
_inventory_tab.button_pressed = section == Section.INVENTORY
|
|
|
|
|
|
_logbook_tab.button_pressed = section == Section.LOGBOOK
|
|
|
|
|
|
if visible:
|
|
|
|
|
|
_focus_current_section()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _focus_current_section() -> void:
|
|
|
|
|
|
if _current_section == Section.INVENTORY:
|
|
|
|
|
|
_inventory_tab.grab_focus()
|
|
|
|
|
|
else:
|
|
|
|
|
|
_logbook_tab.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_sort_selected(index: int) -> void:
|
|
|
|
|
|
_sort_mode = _sort_option.get_item_id(index)
|
|
|
|
|
|
_refresh_inventory()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_sort_direction_pressed() -> void:
|
|
|
|
|
|
_sort_descending = not _sort_descending
|
|
|
|
|
|
_update_sort_direction_text()
|
|
|
|
|
|
_refresh_inventory()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_sort_direction_text() -> void:
|
|
|
|
|
|
match _sort_mode:
|
|
|
|
|
|
SortMode.CATCH_ORDER:
|
|
|
|
|
|
_sort_direction.text = "Newest first" if _sort_descending else "Oldest first"
|
|
|
|
|
|
SortMode.NAME:
|
|
|
|
|
|
_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"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_all() -> void:
|
2026-07-25 16:27:07 -04:00
|
|
|
|
_refresh_economy_summary()
|
2026-07-25 16:02:34 -04:00
|
|
|
|
_refresh_inventory()
|
|
|
|
|
|
_refresh_logbook()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_inventory_changed() -> void:
|
2026-07-25 16:27:07 -04:00
|
|
|
|
_refresh_economy_summary()
|
|
|
|
|
|
_revalidate_confirmation()
|
2026-07-25 16:02:34 -04:00
|
|
|
|
_refresh_inventory()
|
|
|
|
|
|
_refresh_logbook()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_fish_discovered(_fish_id: StringName) -> void:
|
|
|
|
|
|
_refresh_logbook()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-25 16:27:07 -04:00
|
|
|
|
func _on_wallet_balance_changed(
|
|
|
|
|
|
_new_balance: int,
|
|
|
|
|
|
_delta: int,
|
|
|
|
|
|
) -> void:
|
|
|
|
|
|
_refresh_economy_summary()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_economy_summary() -> void:
|
|
|
|
|
|
if not is_node_ready():
|
|
|
|
|
|
return
|
|
|
|
|
|
var balance: int = _wallet.get_balance() if _wallet != null else 0
|
|
|
|
|
|
var held_total: int = (
|
|
|
|
|
|
_inventory.get_total_sale_value()
|
|
|
|
|
|
if _inventory != null
|
|
|
|
|
|
else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
_wallet_balance.text = "Wallet: $%d" % balance
|
|
|
|
|
|
_held_value.text = "Held fish base value: $%d" % held_total
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-07-25 16:02:34 -04:00
|
|
|
|
func _refresh_inventory() -> void:
|
|
|
|
|
|
if not is_node_ready():
|
|
|
|
|
|
return
|
|
|
|
|
|
_clear_container(_inventory_grid)
|
|
|
|
|
|
var catches: Array[FishCatchType] = []
|
|
|
|
|
|
if _inventory != null:
|
|
|
|
|
|
for fish_catch: FishCatchType in _inventory.get_all_catches():
|
|
|
|
|
|
if fish_catch != null and fish_catch.is_valid():
|
|
|
|
|
|
catches.append(fish_catch)
|
|
|
|
|
|
catches.sort_custom(_compare_catches)
|
|
|
|
|
|
_inventory_empty.visible = catches.is_empty()
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
for fish_catch: FishCatchType in catches:
|
|
|
|
|
|
_inventory_grid.add_child(_create_inventory_card(fish_catch))
|
|
|
|
|
|
_update_inventory_detail(selected)
|
|
|
|
|
|
_update_sort_direction_text()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _compare_catches(left: FishCatchType, right: FishCatchType) -> bool:
|
|
|
|
|
|
var comparison: int = 0
|
|
|
|
|
|
match _sort_mode:
|
|
|
|
|
|
SortMode.CATCH_ORDER:
|
|
|
|
|
|
comparison = left.catch_sequence - right.catch_sequence
|
|
|
|
|
|
SortMode.NAME:
|
|
|
|
|
|
comparison = left.fish.display_name.naturalnocasecmp_to(
|
|
|
|
|
|
right.fish.display_name
|
|
|
|
|
|
)
|
|
|
|
|
|
SortMode.RARITY:
|
|
|
|
|
|
comparison = left.fish.rarity - right.fish.rarity
|
|
|
|
|
|
if comparison == 0:
|
|
|
|
|
|
comparison = left.catch_sequence - right.catch_sequence
|
|
|
|
|
|
return comparison > 0 if _sort_descending else comparison < 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _create_inventory_card(fish_catch: FishCatchType) -> Button:
|
|
|
|
|
|
var card := Button.new()
|
|
|
|
|
|
card.custom_minimum_size = Vector2(170.0, 160.0)
|
|
|
|
|
|
card.toggle_mode = true
|
|
|
|
|
|
card.button_pressed = fish_catch.catch_id == _selected_catch_id
|
|
|
|
|
|
card.tooltip_text = String(fish_catch.catch_id)
|
|
|
|
|
|
card.pressed.connect(_select_catch.bind(fish_catch.catch_id))
|
|
|
|
|
|
|
|
|
|
|
|
var content := VBoxContainer.new()
|
|
|
|
|
|
content.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT, Control.PRESET_MODE_MINSIZE, 8)
|
|
|
|
|
|
content.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
card.add_child(content)
|
|
|
|
|
|
content.add_child(_create_texture_frame(fish_catch.fish.display_texture, Vector2(138.0, 82.0)))
|
|
|
|
|
|
var name_label := Label.new()
|
|
|
|
|
|
name_label.text = fish_catch.fish.display_name
|
|
|
|
|
|
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
name_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
content.add_child(name_label)
|
|
|
|
|
|
var data_label := Label.new()
|
|
|
|
|
|
data_label.text = "%.2f lb • %s\nCatch #%d" % [
|
|
|
|
|
|
fish_catch.weight_lb,
|
|
|
|
|
|
fish_catch.fish.get_rarity_name(),
|
|
|
|
|
|
fish_catch.catch_sequence,
|
|
|
|
|
|
]
|
2026-07-25 16:27:07 -04:00
|
|
|
|
data_label.text += "\nBase value: $%d" % fish_catch.sale_value
|
|
|
|
|
|
if fish_catch.is_favorited:
|
|
|
|
|
|
data_label.text += " • ★"
|
2026-07-25 16:02:34 -04:00
|
|
|
|
data_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
data_label.mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
|
|
|
|
content.add_child(data_label)
|
|
|
|
|
|
return card
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _select_catch(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
|
|
|
|
|
|
_refresh_inventory()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_inventory_detail(fish_catch: FishCatchType) -> void:
|
|
|
|
|
|
if fish_catch == null:
|
|
|
|
|
|
_detail_texture.texture = null
|
|
|
|
|
|
_detail_texture.visible = false
|
|
|
|
|
|
_detail_name.text = ""
|
|
|
|
|
|
_detail_data.text = ""
|
2026-07-25 16:27:07 -04:00
|
|
|
|
_favorite_button.disabled = true
|
|
|
|
|
|
_favorite_button.text = "Favorite"
|
|
|
|
|
|
_sell_button.disabled = true
|
|
|
|
|
|
_sale_unavailable.text = ""
|
|
|
|
|
|
_sale_unavailable.visible = false
|
2026-07-25 16:02:34 -04:00
|
|
|
|
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
|
2026-07-25 16:27:07 -04:00
|
|
|
|
var buyer_offer: int = (
|
|
|
|
|
|
_default_buyer.get_offer(fish_catch.sale_value)
|
|
|
|
|
|
if _default_buyer != null
|
|
|
|
|
|
else -1
|
|
|
|
|
|
)
|
|
|
|
|
|
var buyer_offer_text: String = "Buyer unavailable"
|
|
|
|
|
|
if buyer_offer >= 0:
|
|
|
|
|
|
buyer_offer_text = "%s offer: $%d" % [
|
|
|
|
|
|
_default_buyer.display_name,
|
|
|
|
|
|
buyer_offer,
|
|
|
|
|
|
]
|
|
|
|
|
|
_detail_data.text = "%.2f lb\n%s\nBase value: $%d\n%s\nCatch #%d\n%s" % [
|
2026-07-25 16:02:34 -04:00
|
|
|
|
fish_catch.weight_lb,
|
|
|
|
|
|
fish_catch.fish.get_rarity_name(),
|
2026-07-25 16:27:07 -04:00
|
|
|
|
fish_catch.sale_value,
|
|
|
|
|
|
buyer_offer_text,
|
2026-07-25 16:02:34 -04:00
|
|
|
|
fish_catch.catch_sequence,
|
|
|
|
|
|
String(fish_catch.catch_id),
|
|
|
|
|
|
]
|
2026-07-25 16:27:07 -04:00
|
|
|
|
_favorite_button.disabled = false
|
|
|
|
|
|
_favorite_button.text = (
|
|
|
|
|
|
"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
|
|
|
|
|
|
)
|
|
|
|
|
|
_sell_button.disabled = (
|
|
|
|
|
|
fish_catch.is_favorited
|
|
|
|
|
|
or not valid_sale_value
|
|
|
|
|
|
or not valid_buyer
|
|
|
|
|
|
)
|
|
|
|
|
|
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."
|
|
|
|
|
|
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():
|
|
|
|
|
|
return
|
|
|
|
|
|
var fish_catch: FishCatchType = _inventory.get_catch_by_id(
|
|
|
|
|
|
_selected_catch_id
|
|
|
|
|
|
)
|
|
|
|
|
|
if fish_catch == null:
|
|
|
|
|
|
_refresh_inventory()
|
|
|
|
|
|
return
|
|
|
|
|
|
if _inventory.set_catch_favorited(
|
|
|
|
|
|
fish_catch.catch_id,
|
|
|
|
|
|
not fish_catch.is_favorited
|
|
|
|
|
|
):
|
|
|
|
|
|
_transaction_feedback.text = (
|
|
|
|
|
|
"%s %s."
|
|
|
|
|
|
% [
|
|
|
|
|
|
fish_catch.fish.display_name,
|
|
|
|
|
|
"favorited" if fish_catch.is_favorited else "unfavorited",
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_sell_pressed() -> void:
|
|
|
|
|
|
if (
|
|
|
|
|
|
_inventory == null
|
|
|
|
|
|
or _sale_service == null
|
|
|
|
|
|
or _default_buyer == null
|
|
|
|
|
|
or _selected_catch_id.is_empty()
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
var fish_catch: FishCatchType = _inventory.get_catch_by_id(
|
|
|
|
|
|
_selected_catch_id
|
|
|
|
|
|
)
|
|
|
|
|
|
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_buyer = _default_buyer
|
|
|
|
|
|
_confirmation_buyer_id = _default_buyer.id
|
|
|
|
|
|
_confirmation_message.text = (
|
|
|
|
|
|
"Sell this %.2f lb %s to the %s for $%d?\nBase value: $%d"
|
|
|
|
|
|
% [
|
|
|
|
|
|
fish_catch.weight_lb,
|
|
|
|
|
|
fish_catch.fish.display_name,
|
|
|
|
|
|
_get_buyer_display_group(_default_buyer),
|
|
|
|
|
|
buyer_offer,
|
|
|
|
|
|
fish_catch.sale_value,
|
|
|
|
|
|
]
|
|
|
|
|
|
)
|
|
|
|
|
|
_sale_confirmation.visible = true
|
|
|
|
|
|
_confirm_sale_button.disabled = false
|
|
|
|
|
|
_confirm_sale_button.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_confirm_sale_pressed() -> void:
|
|
|
|
|
|
if (
|
|
|
|
|
|
_sale_in_progress
|
|
|
|
|
|
or _sale_service == null
|
|
|
|
|
|
or _confirmation_catch_id.is_empty()
|
|
|
|
|
|
or _confirmation_buyer == null
|
|
|
|
|
|
or _confirmation_buyer.id != _confirmation_buyer_id
|
|
|
|
|
|
):
|
|
|
|
|
|
return
|
|
|
|
|
|
_sale_in_progress = true
|
|
|
|
|
|
var requested_catch_id: StringName = _confirmation_catch_id
|
|
|
|
|
|
_confirm_sale_button.disabled = true
|
|
|
|
|
|
var result: FishSaleResultType = _sale_service.sell(
|
|
|
|
|
|
requested_catch_id,
|
|
|
|
|
|
_confirmation_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()
|
|
|
|
|
|
_refresh_all()
|
|
|
|
|
|
_inventory_tab.grab_focus()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _close_sale_confirmation() -> void:
|
|
|
|
|
|
_confirmation_catch_id = StringName()
|
|
|
|
|
|
_confirmation_buyer = null
|
|
|
|
|
|
_confirmation_buyer_id = StringName()
|
|
|
|
|
|
_sale_confirmation.visible = false
|
|
|
|
|
|
_confirmation_message.text = ""
|
|
|
|
|
|
_confirm_sale_button.disabled = false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
|
_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 (
|
|
|
|
|
|
_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."
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _get_buyer_display_group(
|
|
|
|
|
|
buyer: FishBuyerProfileType,
|
|
|
|
|
|
) -> String:
|
|
|
|
|
|
if buyer == null:
|
|
|
|
|
|
return "buyer"
|
|
|
|
|
|
if not buyer.animal_name_plural.is_empty():
|
|
|
|
|
|
return buyer.animal_name_plural
|
|
|
|
|
|
if not buyer.display_name.is_empty():
|
|
|
|
|
|
return buyer.display_name
|
|
|
|
|
|
return "buyer"
|
2026-07-25 16:02:34 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _refresh_logbook() -> void:
|
|
|
|
|
|
if not is_node_ready():
|
|
|
|
|
|
return
|
|
|
|
|
|
_clear_container(_logbook_grid)
|
|
|
|
|
|
var valid_species: Array[FishDataType] = []
|
|
|
|
|
|
if _catalog != null:
|
|
|
|
|
|
for fish: FishDataType in _catalog.candidates:
|
|
|
|
|
|
if fish != null and not fish.id.is_empty():
|
|
|
|
|
|
valid_species.append(fish)
|
|
|
|
|
|
_logbook_empty.visible = valid_species.is_empty()
|
|
|
|
|
|
_logbook_empty.text = (
|
|
|
|
|
|
"No fish catalog configured."
|
|
|
|
|
|
if valid_species.is_empty()
|
|
|
|
|
|
else "No species discovered yet."
|
|
|
|
|
|
)
|
|
|
|
|
|
if not valid_species.is_empty() and _collection_log != null:
|
|
|
|
|
|
var any_discovered: bool = false
|
|
|
|
|
|
for fish: FishDataType in valid_species:
|
|
|
|
|
|
if _collection_log.has_discovered(fish.id):
|
|
|
|
|
|
any_discovered = true
|
|
|
|
|
|
break
|
|
|
|
|
|
_logbook_empty.visible = not any_discovered
|
|
|
|
|
|
for fish: FishDataType in valid_species:
|
|
|
|
|
|
_logbook_grid.add_child(_create_logbook_card(fish))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _create_logbook_card(fish: FishDataType) -> Control:
|
|
|
|
|
|
var discovered: bool = (
|
|
|
|
|
|
_collection_log != null
|
|
|
|
|
|
and _collection_log.has_discovered(fish.id)
|
|
|
|
|
|
)
|
|
|
|
|
|
var card := PanelContainer.new()
|
|
|
|
|
|
card.custom_minimum_size = Vector2(190.0, 170.0)
|
|
|
|
|
|
var content := VBoxContainer.new()
|
|
|
|
|
|
content.add_theme_constant_override("separation", 4)
|
|
|
|
|
|
card.add_child(content)
|
|
|
|
|
|
var texture_frame: TextureRect = _create_texture_frame(
|
|
|
|
|
|
fish.display_texture,
|
|
|
|
|
|
Vector2(170.0, 92.0)
|
|
|
|
|
|
)
|
|
|
|
|
|
if not discovered:
|
|
|
|
|
|
texture_frame.modulate = Color(0.08, 0.1, 0.12, 1.0)
|
|
|
|
|
|
content.add_child(texture_frame)
|
|
|
|
|
|
var name_label := Label.new()
|
|
|
|
|
|
name_label.text = fish.display_name if discovered else "???"
|
|
|
|
|
|
name_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
content.add_child(name_label)
|
|
|
|
|
|
var details := Label.new()
|
|
|
|
|
|
if discovered:
|
|
|
|
|
|
var owned_count: int = (
|
|
|
|
|
|
_inventory.get_count(fish.id) if _inventory != null else 0
|
|
|
|
|
|
)
|
|
|
|
|
|
details.text = "%s\nOwned: %d" % [
|
|
|
|
|
|
fish.get_rarity_name(),
|
|
|
|
|
|
owned_count,
|
|
|
|
|
|
]
|
|
|
|
|
|
else:
|
|
|
|
|
|
details.text = "Undiscovered"
|
|
|
|
|
|
details.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
|
|
|
|
content.add_child(details)
|
|
|
|
|
|
return card
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _create_texture_frame(
|
|
|
|
|
|
texture: Texture2D,
|
|
|
|
|
|
minimum_size: Vector2,
|
|
|
|
|
|
) -> TextureRect:
|
|
|
|
|
|
var texture_frame := TextureRect.new()
|
|
|
|
|
|
texture_frame.custom_minimum_size = minimum_size
|
|
|
|
|
|
texture_frame.texture = texture
|
|
|
|
|
|
texture_frame.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
|
|
|
|
|
|
texture_frame.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
|
|
|
|
|
|
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"
|
|
|
|
|
|
return texture_frame
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _clear_container(container: Container) -> void:
|
|
|
|
|
|
for child: Node in container.get_children():
|
|
|
|
|
|
container.remove_child(child)
|
|
|
|
|
|
child.queue_free()
|