Add inventory and logbook player menu
This commit is contained in:
parent
bd2768b676
commit
ad89c22cca
12 changed files with 719 additions and 50 deletions
|
|
@ -5,14 +5,26 @@ const FishDataType = preload("res://fish/fish_data.gd")
|
|||
|
||||
@export var fish: FishDataType
|
||||
@export var fish_id: StringName
|
||||
@export var catch_id: StringName
|
||||
@export var catch_sequence: int = 0
|
||||
@export var weight_lb: float = 0.0
|
||||
@export var display_scale: float = 1.0
|
||||
|
||||
|
||||
func ensure_identity() -> void:
|
||||
if not catch_id.is_empty():
|
||||
return
|
||||
catch_id = StringName(
|
||||
"%s:%d:%d"
|
||||
% [fish_id, Time.get_ticks_usec(), get_instance_id()]
|
||||
)
|
||||
|
||||
|
||||
func is_valid() -> bool:
|
||||
return (
|
||||
fish != null
|
||||
and not fish_id.is_empty()
|
||||
and not catch_id.is_empty()
|
||||
and fish.id == fish_id
|
||||
and weight_lb >= fish.get_minimum_weight()
|
||||
and weight_lb <= fish.get_maximum_weight()
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ func create_catch(fish: FishDataType) -> FishCatchType:
|
|||
var caught_fish := FishCatchType.new()
|
||||
caught_fish.fish = fish
|
||||
caught_fish.fish_id = fish.id
|
||||
caught_fish.ensure_identity()
|
||||
caught_fish.weight_lb = _rng.randf_range(
|
||||
fish.get_minimum_weight(),
|
||||
fish.get_maximum_weight()
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ signal showcase_changed(
|
|||
weight_lb: float,
|
||||
visible: bool,
|
||||
)
|
||||
signal bite_activated
|
||||
|
||||
enum FishingState {
|
||||
READY,
|
||||
|
|
@ -125,6 +126,13 @@ func setup(
|
|||
_local_collection_log = local_collection_log
|
||||
|
||||
|
||||
func can_open_player_menu() -> bool:
|
||||
return state in [
|
||||
FishingState.READY,
|
||||
FishingState.WAITING_FOR_BITE,
|
||||
]
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
_showcase_restore_generation += 1
|
||||
if (
|
||||
|
|
@ -437,6 +445,7 @@ func _activate_bite() -> void:
|
|||
state = FishingState.BITE_ACTIVE
|
||||
_state_time_remaining = bite_window_duration
|
||||
_withdrawal_input_held = false
|
||||
bite_activated.emit()
|
||||
status_changed.emit("Bite! Left click to hook")
|
||||
_presentation.set_line_mode(FishingPresentationType.LineMode.TAUT)
|
||||
_presentation.show_bite()
|
||||
|
|
|
|||
|
|
@ -4,18 +4,29 @@ extends Node
|
|||
const FishCatchType = preload("res://fish/fish_catch.gd")
|
||||
|
||||
signal contents_changed(fish_id: StringName, count: int)
|
||||
signal catches_changed
|
||||
|
||||
var _catches: Array[FishCatchType] = []
|
||||
var _next_catch_sequence: int = 1
|
||||
|
||||
|
||||
func add_catch(fish_catch: FishCatchType) -> void:
|
||||
if fish_catch == null or not fish_catch.is_valid():
|
||||
return
|
||||
if get_catch(fish_catch.catch_id) != null:
|
||||
return
|
||||
if fish_catch.catch_sequence <= 0:
|
||||
fish_catch.catch_sequence = _next_catch_sequence
|
||||
_next_catch_sequence = maxi(
|
||||
_next_catch_sequence,
|
||||
fish_catch.catch_sequence + 1
|
||||
)
|
||||
_catches.append(fish_catch)
|
||||
contents_changed.emit(
|
||||
fish_catch.fish_id,
|
||||
get_count(fish_catch.fish_id)
|
||||
)
|
||||
catches_changed.emit()
|
||||
|
||||
|
||||
func get_count(fish_id: StringName) -> int:
|
||||
|
|
@ -26,6 +37,15 @@ func get_all_catches() -> Array[FishCatchType]:
|
|||
return _catches.duplicate()
|
||||
|
||||
|
||||
func get_catch(catch_id: StringName) -> FishCatchType:
|
||||
if catch_id.is_empty():
|
||||
return null
|
||||
for fish_catch: FishCatchType in _catches:
|
||||
if fish_catch != null and fish_catch.catch_id == catch_id:
|
||||
return fish_catch
|
||||
return null
|
||||
|
||||
|
||||
func get_catches_by_fish_id(fish_id: StringName) -> Array[FishCatchType]:
|
||||
var matching: Array[FishCatchType] = []
|
||||
for fish_catch: FishCatchType in _catches:
|
||||
|
|
|
|||
11
main/main.gd
11
main/main.gd
|
|
@ -1,9 +1,12 @@
|
|||
extends Node3D
|
||||
|
||||
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||
const GameUIType = preload("res://ui/game_ui.gd")
|
||||
const PlayerType = preload("res://player/player.gd")
|
||||
|
||||
@export var fish_catalog: FishPoolType
|
||||
|
||||
@onready var _player: PlayerType = %Player
|
||||
@onready var _fishing_spot: FishingSpotType = %FishingSpot
|
||||
@onready var _game_ui: GameUIType = %GameUI
|
||||
|
|
@ -15,4 +18,10 @@ func _ready() -> void:
|
|||
_player.inventory,
|
||||
_player.collection_log
|
||||
)
|
||||
_game_ui.setup(_player.inventory, _fishing_spot)
|
||||
_game_ui.setup(
|
||||
_player,
|
||||
_player.inventory,
|
||||
_player.collection_log,
|
||||
fish_catalog,
|
||||
_fishing_spot
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
[gd_scene load_steps=6 format=3]
|
||||
[gd_scene load_steps=7 format=3]
|
||||
|
||||
[ext_resource type="PackedScene" path="res://world/test_world.tscn" id="1_world"]
|
||||
[ext_resource type="PackedScene" path="res://player/player.tscn" id="2_player"]
|
||||
[ext_resource type="Script" path="res://main/main.gd" id="3_main"]
|
||||
[ext_resource type="PackedScene" path="res://fishing/fishing_spot.tscn" id="4_spot"]
|
||||
[ext_resource type="PackedScene" path="res://ui/game_ui.tscn" id="5_ui"]
|
||||
[ext_resource type="Resource" path="res://fish/pools/test_water_pool.tres" id="6_pool"]
|
||||
|
||||
[node name="Main" type="Node3D"]
|
||||
script = ExtResource("3_main")
|
||||
fish_catalog = ExtResource("6_pool")
|
||||
|
||||
[node name="TestWorld" parent="." instance=ExtResource("1_world")]
|
||||
|
||||
|
|
|
|||
|
|
@ -230,6 +230,20 @@ func set_movement_enabled(enabled: bool) -> void:
|
|||
velocity.z = 0.0
|
||||
|
||||
|
||||
func is_movement_enabled() -> bool:
|
||||
return _movement_enabled
|
||||
|
||||
|
||||
func set_camera_input_enabled(enabled: bool) -> void:
|
||||
_camera_input_enabled = enabled
|
||||
if not enabled:
|
||||
_camera_dragging = false
|
||||
|
||||
|
||||
func is_camera_input_enabled() -> bool:
|
||||
return _camera_input_enabled
|
||||
|
||||
|
||||
func get_fishing_rod_tip() -> Marker3D:
|
||||
return _fishing_rod_tip
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ camera_zoom_out={
|
|||
}
|
||||
open_backpack={
|
||||
"deadzone": 0.2,
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194308,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":16,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194306,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
|
||||
]
|
||||
}
|
||||
open_emotes={
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
class_name GameUI
|
||||
extends CanvasLayer
|
||||
|
||||
const CollectionLogType = preload("res://collection/collection_log.gd")
|
||||
const FishPoolType = preload("res://fish/fish_pool.gd")
|
||||
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
|
||||
const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
||||
const PlayerMenuType = preload("res://ui/player_menu.gd")
|
||||
const PlayerType = preload("res://player/player.gd")
|
||||
|
||||
@onready var _status_label: Label = %StatusLabel
|
||||
@onready var _bluegill_count_label: Label = %BluegillCountLabel
|
||||
@onready var _bass_count_label: Label = %BassCountLabel
|
||||
@onready var _carp_count_label: Label = %CarpCountLabel
|
||||
@onready var _catch_track: Control = %CatchTrack
|
||||
@onready var _green_catch_progress: ProgressBar = %GreenCatchProgress
|
||||
@onready var _red_chase_progress: ProgressBar = %RedChaseProgress
|
||||
|
|
@ -15,28 +16,32 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd")
|
|||
@onready var _barrier_summary: Label = %BarrierSummary
|
||||
@onready var _barrier_health: Label = %BarrierHealth
|
||||
@onready var _showcase_details: Label = %ShowcaseDetails
|
||||
@onready var _fishing_panel: PanelContainer = %FishingPanel
|
||||
@onready var _player_menu: PlayerMenuType = %PlayerMenu
|
||||
|
||||
var _showcase_active: bool = false
|
||||
|
||||
|
||||
func setup(inventory: FishInventoryType, fishing_spot: FishingSpotType) -> void:
|
||||
inventory.contents_changed.connect(_on_inventory_contents_changed)
|
||||
func setup(
|
||||
player: PlayerType,
|
||||
inventory: FishInventoryType,
|
||||
collection_log: CollectionLogType,
|
||||
catalog: FishPoolType,
|
||||
fishing_spot: FishingSpotType,
|
||||
) -> void:
|
||||
fishing_spot.status_changed.connect(_on_fishing_status_changed)
|
||||
fishing_spot.catch_display_changed.connect(_on_catch_display_changed)
|
||||
fishing_spot.showcase_changed.connect(_on_showcase_changed)
|
||||
_update_bluegill_count(inventory.get_count(&"bluegill"))
|
||||
_bass_count_label.text = "Bass: %d" % inventory.get_count(&"bass")
|
||||
_carp_count_label.text = "Carp: %d" % inventory.get_count(&"carp")
|
||||
|
||||
|
||||
func _on_inventory_contents_changed(fish_id: StringName, count: int) -> void:
|
||||
match fish_id:
|
||||
&"bluegill":
|
||||
_update_bluegill_count(count)
|
||||
&"bass":
|
||||
_bass_count_label.text = "Bass: %d" % count
|
||||
&"carp":
|
||||
_carp_count_label.text = "Carp: %d" % count
|
||||
_player_menu.menu_visibility_changed.connect(
|
||||
_on_player_menu_visibility_changed
|
||||
)
|
||||
_player_menu.setup(
|
||||
player,
|
||||
inventory,
|
||||
collection_log,
|
||||
catalog,
|
||||
fishing_spot
|
||||
)
|
||||
|
||||
|
||||
func _on_fishing_status_changed(status: String) -> void:
|
||||
|
|
@ -173,5 +178,5 @@ func _on_showcase_changed(
|
|||
_showcase_details.visible = true
|
||||
|
||||
|
||||
func _update_bluegill_count(count: int) -> void:
|
||||
_bluegill_count_label.text = "Bluegill: %d" % count
|
||||
func _on_player_menu_visibility_changed(is_open: bool) -> void:
|
||||
_fishing_panel.visible = not is_open
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
[gd_scene load_steps=6 format=3]
|
||||
[gd_scene load_steps=7 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/game_ui.gd" id="1_ui"]
|
||||
[ext_resource type="PackedScene" path="res://ui/player_menu.tscn" id="2_menu"]
|
||||
|
||||
[sub_resource type="StyleBoxFlat" id="StyleBox_chase_background"]
|
||||
bg_color = Color(0.08, 0.08, 0.1, 0.9)
|
||||
|
|
@ -17,33 +18,8 @@ bg_color = Color(0, 0, 0, 0)
|
|||
[node name="GameUI" type="CanvasLayer"]
|
||||
script = ExtResource("1_ui")
|
||||
|
||||
[node name="InventoryPanel" type="PanelContainer" parent="."]
|
||||
offset_left = 16.0
|
||||
offset_top = 16.0
|
||||
offset_right = 176.0
|
||||
offset_bottom = 104.0
|
||||
|
||||
[node name="MarginContainer" type="MarginContainer" parent="InventoryPanel"]
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="InventoryPanel/MarginContainer"]
|
||||
|
||||
[node name="BluegillCountLabel" type="Label" parent="InventoryPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
text = "Bluegill: 0"
|
||||
|
||||
[node name="BassCountLabel" type="Label" parent="InventoryPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
text = "Bass: 0"
|
||||
|
||||
[node name="CarpCountLabel" type="Label" parent="InventoryPanel/MarginContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
text = "Carp: 0"
|
||||
|
||||
[node name="FishingPanel" type="PanelContainer" parent="."]
|
||||
unique_name_in_owner = true
|
||||
anchors_preset = 7
|
||||
anchor_left = 0.5
|
||||
anchor_top = 1.0
|
||||
|
|
@ -128,3 +104,6 @@ horizontal_alignment = 1
|
|||
unique_name_in_owner = true
|
||||
visible = false
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="PlayerMenu" parent="." instance=ExtResource("2_menu")]
|
||||
unique_name_in_owner = true
|
||||
|
|
|
|||
405
ui/player_menu.gd
Normal file
405
ui/player_menu.gd
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
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 = "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:
|
||||
_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()
|
||||
213
ui/player_menu.tscn
Normal file
213
ui/player_menu.tscn
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://ui/player_menu.gd" id="1_menu"]
|
||||
|
||||
[node name="PlayerMenu" type="Control"]
|
||||
process_mode = 3
|
||||
visible = false
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 0
|
||||
script = ExtResource("1_menu")
|
||||
|
||||
[node name="Dimmer" type="ColorRect" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
mouse_filter = 2
|
||||
color = Color(0.015, 0.02, 0.03, 0.72)
|
||||
|
||||
[node name="MenuPanel" type="PanelContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 8
|
||||
anchor_left = 0.5
|
||||
anchor_top = 0.5
|
||||
anchor_right = 0.5
|
||||
anchor_bottom = 0.5
|
||||
offset_left = -480.0
|
||||
offset_top = -310.0
|
||||
offset_right = 480.0
|
||||
offset_bottom = 310.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="MenuPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 18
|
||||
theme_override_constants/margin_top = 14
|
||||
theme_override_constants/margin_right = 18
|
||||
theme_override_constants/margin_bottom = 16
|
||||
|
||||
[node name="Layout" type="VBoxContainer" parent="MenuPanel/Margin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="Header" type="HBoxContainer" parent="MenuPanel/Margin/Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Title" type="Label" parent="MenuPanel/Margin/Layout/Header"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_font_sizes/font_size = 24
|
||||
text = "Player Menu"
|
||||
|
||||
[node name="InventoryTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Inventory"
|
||||
|
||||
[node name="LogbookTab" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
toggle_mode = true
|
||||
text = "Logbook"
|
||||
|
||||
[node name="CloseButton" type="Button" parent="MenuPanel/Margin/Layout/Header"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Close"
|
||||
|
||||
[node name="Separator" type="HSeparator" parent="MenuPanel/Margin/Layout"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Content" type="Control" parent="MenuPanel/Margin/Layout"]
|
||||
custom_minimum_size = Vector2(0, 530)
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="InventorySection" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="SortBar" type="HBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="SortLabel" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
layout_mode = 2
|
||||
text = "Sort:"
|
||||
|
||||
[node name="SortOption" type="OptionButton" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(160, 0)
|
||||
|
||||
[node name="SortDirection" type="Button" parent="MenuPanel/Margin/Layout/Content/InventorySection/SortBar"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
custom_minimum_size = Vector2(130, 0)
|
||||
text = "Newest first"
|
||||
|
||||
[node name="InventoryBody" type="HSplitContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
split_offset = 620
|
||||
|
||||
[node name="InventoryList" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"]
|
||||
custom_minimum_size = Vector2(610, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InventoryListMargin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 8
|
||||
theme_override_constants/margin_top = 8
|
||||
theme_override_constants/margin_right = 8
|
||||
theme_override_constants/margin_bottom = 8
|
||||
|
||||
[node name="InventoryStack" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="InventoryEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "No fish in your inventory."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="InventoryScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="InventoryGrid" type="GridContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/InventoryList/InventoryListMargin/InventoryStack/InventoryScroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 8
|
||||
theme_override_constants/v_separation = 8
|
||||
columns = 3
|
||||
|
||||
[node name="DetailPanel" type="PanelContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody"]
|
||||
custom_minimum_size = Vector2(270, 0)
|
||||
layout_mode = 2
|
||||
|
||||
[node name="DetailMargin" type="MarginContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 12
|
||||
theme_override_constants/margin_top = 12
|
||||
theme_override_constants/margin_right = 12
|
||||
theme_override_constants/margin_bottom = 12
|
||||
|
||||
[node name="DetailStack" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin"]
|
||||
layout_mode = 2
|
||||
theme_override_constants/separation = 10
|
||||
|
||||
[node name="DetailTexture" type="TextureRect" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(240, 180)
|
||||
layout_mode = 2
|
||||
expand_mode = 1
|
||||
stretch_mode = 5
|
||||
texture_filter = 1
|
||||
|
||||
[node name="DetailName" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
theme_override_font_sizes/font_size = 22
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="DetailData" type="Label" parent="MenuPanel/Margin/Layout/Content/InventorySection/InventoryBody/DetailPanel/DetailMargin/DetailStack"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
autowrap_mode = 2
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LogbookSection" type="VBoxContainer" parent="MenuPanel/Margin/Layout/Content"]
|
||||
unique_name_in_owner = true
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="LogbookEmpty" type="Label" parent="MenuPanel/Margin/Layout/Content/LogbookSection"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "No species discovered yet."
|
||||
horizontal_alignment = 1
|
||||
|
||||
[node name="LogbookScroll" type="ScrollContainer" parent="MenuPanel/Margin/Layout/Content/LogbookSection"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
horizontal_scroll_mode = 0
|
||||
|
||||
[node name="LogbookGrid" type="GridContainer" parent="MenuPanel/Margin/Layout/Content/LogbookSection/LogbookScroll"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/h_separation = 12
|
||||
theme_override_constants/v_separation = 12
|
||||
columns = 4
|
||||
Loading…
Add table
Add a link
Reference in a new issue