netfishing/ui/player_menu.gd

405 lines
12 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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")
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")
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
@onready var _inventory_section: Control = %InventorySection
@onready var _logbook_section: Control = %LogbookSection
@onready var _sort_option: OptionButton = %SortOption
@onready var _sort_direction: Button = %SortDirection
@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
@onready var _logbook_empty: Label = %LogbookEmpty
@onready var _logbook_grid: GridContainer = %LogbookGrid
var _player: PlayerType
var _inventory: FishInventoryType
var _collection_log: CollectionLogType
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
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)
_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,
catalog: FishPoolType,
fishing_spot: FishingSpotType,
) -> void:
_player = player
_inventory = inventory
_collection_log = collection_log
_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)
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:
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
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 = "ZA" if _sort_descending else "AZ"
SortMode.RARITY:
_sort_direction.text = "High to low" if _sort_descending else "Low to high"
func _refresh_all() -> void:
_refresh_inventory()
_refresh_logbook()
func _on_inventory_changed() -> void:
_refresh_inventory()
_refresh_logbook()
func _on_fish_discovered(_fish_id: StringName) -> void:
_refresh_logbook()
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,
]
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 = ""
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
_detail_data.text = "%.2f lb\n%s\nCatch #%d\n%s" % [
fish_catch.weight_lb,
fish_catch.fish.get_rarity_name(),
fish_catch.catch_sequence,
String(fish_catch.catch_id),
]
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()