diff --git a/fish/fish_catch.gd b/fish/fish_catch.gd index f79b3df..a4ec224 100644 --- a/fish/fish_catch.gd +++ b/fish/fish_catch.gd @@ -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() diff --git a/fish/fish_selector.gd b/fish/fish_selector.gd index 837c422..25966e5 100644 --- a/fish/fish_selector.gd +++ b/fish/fish_selector.gd @@ -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() diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index b63cbd7..db70441 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -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() diff --git a/inventory/fish_inventory.gd b/inventory/fish_inventory.gd index 0c1b964..ed4db36 100644 --- a/inventory/fish_inventory.gd +++ b/inventory/fish_inventory.gd @@ -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: diff --git a/main/main.gd b/main/main.gd index 53b0755..2aafa6b 100644 --- a/main/main.gd +++ b/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 + ) diff --git a/main/main.tscn b/main/main.tscn index 067c9a5..191c9de 100644 --- a/main/main.tscn +++ b/main/main.tscn @@ -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")] diff --git a/player/player.gd b/player/player.gd index effd739..3610ebd 100644 --- a/player/player.gd +++ b/player/player.gd @@ -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 diff --git a/project.godot b/project.godot index ac06154..ee3e77f 100644 --- a/project.godot +++ b/project.godot @@ -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={ diff --git a/ui/game_ui.gd b/ui/game_ui.gd index d658061..318c86b 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -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 diff --git a/ui/game_ui.tscn b/ui/game_ui.tscn index 9c6db47..b0b60a6 100644 --- a/ui/game_ui.tscn +++ b/ui/game_ui.tscn @@ -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 diff --git a/ui/player_menu.gd b/ui/player_menu.gd new file mode 100644 index 0000000..f6a82fa --- /dev/null +++ b/ui/player_menu.gd @@ -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() diff --git a/ui/player_menu.tscn b/ui/player_menu.tscn new file mode 100644 index 0000000..a3d71d9 --- /dev/null +++ b/ui/player_menu.tscn @@ -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