feat: add persistent inventory storage slots

This commit is contained in:
Alexander Sellite 2026-08-16 16:39:35 -04:00
parent 6db6fbba7f
commit a88e76848b
13 changed files with 875 additions and 65 deletions

View file

@ -0,0 +1,72 @@
class_name BagStorageSlot
extends Button
signal bag_item_dropped(item_id: StringName, slot_index: int)
var storage_slot_index: int = -1
var _preview: TextureRect
func _ready() -> void:
focus_mode = Control.FOCUS_NONE
toggle_mode = false
mouse_default_cursor_shape = Control.CURSOR_POINTING_HAND
_apply_style()
_preview = TextureRect.new()
_preview.name = "PlacementPreview"
_preview.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
_preview.offset_left = 8.0
_preview.offset_top = 7.0
_preview.offset_right = -8.0
_preview.offset_bottom = -7.0
_preview.expand_mode = TextureRect.EXPAND_IGNORE_SIZE
_preview.stretch_mode = TextureRect.STRETCH_KEEP_ASPECT_CENTERED
_preview.mouse_filter = Control.MOUSE_FILTER_IGNORE
_preview.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST
_preview.visible = false
add_child(_preview)
func configure(slot_index: int) -> void:
storage_slot_index = slot_index
name = "BagStorageSlot%d" % slot_index
accessibility_name = "storage slot %d" % (slot_index + 1)
func set_placement_preview(texture: Texture2D, active: bool) -> void:
if _preview == null:
return
_preview.texture = texture
_preview.visible = active and texture != null
z_index = 2 if _preview.visible else 0
func _can_drop_data(_at_position: Vector2, data: Variant) -> bool:
return (
typeof(data) == TYPE_DICTIONARY
and str((data as Dictionary).get("kind", "")) == "bag_item"
and not str((data as Dictionary).get("item_id", "")).is_empty()
)
func _drop_data(_at_position: Vector2, data: Variant) -> void:
if not _can_drop_data(Vector2.ZERO, data):
return
bag_item_dropped.emit(
StringName(str((data as Dictionary)["item_id"])),
storage_slot_index,
)
func _apply_style() -> void:
var normal := UtilityPageStyle.rounded_style(
Color(UtilityPageStyle.OCEAN_FIELD, 0.72),
14,
)
var hover := UtilityPageStyle.rounded_style(
Color(UtilityPageStyle.OCEAN_SELECTED, 0.68),
14,
)
for state: StringName in [&"normal", &"pressed", &"focus", &"disabled"]:
add_theme_stylebox_override(state, normal)
add_theme_stylebox_override(&"hover", hover)

View file

@ -0,0 +1 @@
uid://ofac40nwf1oa

View file

@ -9,6 +9,9 @@ const FishQualityType = preload("res://fish/fish_quality.gd")
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")
const FishingShopStockType = preload(
"res://economy/fishing_shop_stock.gd"
)
const NetworkSessionType = preload("res://network/network_session.gd")
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
const InventoryNotepadType = preload(
@ -72,6 +75,9 @@ const BagItemSpriteType = preload(
const BagItemSpriteScene = preload(
"res://ui/components/bubble_menu/bag_item_sprite.tscn"
)
const BagStorageSlotType = preload(
"res://ui/components/bubble_menu/bag_storage_slot.gd"
)
const LogbookEntryType = preload(
"res://ui/components/bubble_menu/logbook_entry.gd"
)
@ -140,6 +146,7 @@ enum ControllerOwnership {
SORT_FILTER,
HOTBAR_MANAGEMENT,
HOTBAR_PLACEMENT,
STORAGE_PLACEMENT,
PAGE_CONTENT,
}
@ -154,6 +161,8 @@ const INVENTORY_NOTEPAD_SIZE := Vector2(278.0, 484.0)
const INVENTORY_MAIN_CORNER_RADIUS: int = 58
const INVENTORY_INNER_CORNER_RADIUS: int = 45
const TACKLE_GRID_COLUMNS: int = 3
const BAG_STORAGE_COLUMNS: int = 5
const BAG_STORAGE_MINIMUM_SLOTS: int = 15
const SALE_CONFIRMATION_SIZE := Vector2(520.0, 190.0)
const CONTROLLER_PICKUP_HOLD_SECONDS: float = 0.42
@ -325,7 +334,9 @@ var _controller_hotbar_assignment_kind: PlayerHotbarType.AssignmentKind = (
PlayerHotbarType.AssignmentKind.EMPTY
)
var _controller_hotbar_identity: StringName
var _controller_storage_identity: StringName
var _controller_previous_hotbar_slot: int = 0
var _controller_previous_storage_slot: int = 0
var _controller_accept_held: bool = false
var _controller_accept_hold_elapsed: float = 0.0
var _content_interactive_enabled: bool = false
@ -367,7 +378,7 @@ var _fish_nodes: Dictionary[StringName, CoolerFishSpriteType] = {}
var _cooler_slot_nodes: Array[Panel] = []
var _sorted_catches: Array[FishCatchType] = []
var _bag_item_nodes: Dictionary[StringName, BagItemSpriteType] = {}
var _bag_slot_nodes: Array[Panel] = []
var _bag_slot_nodes: Array[BagStorageSlotType] = []
var _sorted_bag_items: Array[OwnedItemType] = []
var _bag_drag_active: bool = false
var _motion_elapsed: float = 0.0
@ -693,16 +704,45 @@ func _handle_controller_ownership_input(event: InputEvent) -> bool:
return true
return false
if _controller_ownership == ControllerOwnership.HOTBAR_MANAGEMENT:
if event.is_action_pressed("ui_up"):
_release_controller_ownership(true, false)
return true
if accept_pressed:
if _hotbar != null:
_hotbar.clear_slot(_hotbar.get_selected_slot())
return true
return false
if _controller_ownership == ControllerOwnership.HOTBAR_PLACEMENT:
if event.is_action_pressed("ui_up"):
if _return_controller_hotbar_placement_to_storage():
return true
_release_controller_ownership(true, true)
return true
if accept_pressed:
_confirm_controller_hotbar_placement()
return true
return false
if _controller_ownership == ControllerOwnership.STORAGE_PLACEMENT:
if accept_pressed:
_confirm_controller_storage_placement()
return true
if accept_event:
return true
if (
event.is_action_pressed("ui_down")
and _controller_storage_focus_is_on_last_row()
):
var focused_slot := (
get_viewport().gui_get_focus_owner() as BagStorageSlotType
)
if focused_slot != null:
_controller_previous_storage_slot = (
focused_slot.storage_slot_index
)
return _begin_controller_hotbar_placement_for_item(
_controller_storage_identity
)
return false
if _controller_ownership == ControllerOwnership.NOTEPAD_ACTIONS:
return false
if _controller_ownership == ControllerOwnership.SORT_FILTER:
@ -886,13 +926,26 @@ func _controller_focus_is_on_last_inventory_row() -> bool:
var item_node := focus_owner as BagItemSpriteType
if item_node == null:
return false
var item_index: int = _sorted_bag_items.find_custom(
func(owned: OwnedItemType) -> bool:
return owned.item_id == item_node.item_id
if _bag == null:
var legacy_index: int = _sorted_bag_items.find_custom(
func(owned: OwnedItemType) -> bool:
return owned.item_id == item_node.item_id
)
return (
legacy_index >= 0
and legacy_index + BAG_STORAGE_COLUMNS
>= _sorted_bag_items.size()
)
var item_slot: int = (
_bag.get_storage_slot(item_node.item_id)
)
var maximum_slot: int = -1
for owned: OwnedItemType in _sorted_bag_items:
maximum_slot = maxi(maximum_slot, owned.storage_slot)
return (
item_index >= 0
and item_index + 3 >= _sorted_bag_items.size()
item_slot >= 0
and item_slot / BAG_STORAGE_COLUMNS
>= maximum_slot / BAG_STORAGE_COLUMNS
)
return false
@ -956,6 +1009,28 @@ func _try_begin_controller_hotbar_placement() -> bool:
identity = item_node.item_id
else:
return false
return _begin_controller_hotbar_placement(assignment_kind, identity)
func _begin_controller_hotbar_placement_for_item(item_id: StringName) -> bool:
var item: ItemDataType = (
_item_catalog.get_item_by_id(item_id)
if _item_catalog != null else null
)
if item == null or not item.hotbar_allowed:
return true
return _begin_controller_hotbar_placement(
PlayerHotbarType.AssignmentKind.ITEM,
item_id,
)
func _begin_controller_hotbar_placement(
assignment_kind: PlayerHotbarType.AssignmentKind,
identity: StringName,
) -> bool:
if _hotbar == null or identity.is_empty():
return false
_controller_source_section = _current_section
_controller_source_identity = identity
_controller_hotbar_assignment_kind = assignment_kind
@ -977,6 +1052,53 @@ func _try_begin_controller_hotbar_placement() -> bool:
return true
func _try_begin_controller_storage_placement() -> bool:
if _current_section != Section.BAG or _bag == null:
return false
var item_node := get_viewport().gui_get_focus_owner() as BagItemSpriteType
if item_node == null or item_node.item_id.is_empty():
return false
var source_slot: int = _bag.get_storage_slot(item_node.item_id)
if source_slot < 0 or source_slot >= _bag_slot_nodes.size():
return false
_controller_source_section = Section.BAG
_controller_source_identity = item_node.item_id
_controller_storage_identity = item_node.item_id
_controller_ownership = ControllerOwnership.STORAGE_PLACEMENT
_apply_inventory_controller_zone_focus_modes()
_configure_bag_storage_slot_focus()
_bag_slot_nodes[source_slot].call_deferred("grab_focus")
return true
func _confirm_controller_storage_placement() -> void:
if (
_controller_ownership != ControllerOwnership.STORAGE_PLACEMENT
or _bag == null
or _controller_storage_identity.is_empty()
):
return
var slot := get_viewport().gui_get_focus_owner() as BagStorageSlotType
if slot == null or slot.storage_slot_index < 0:
return
var moved: bool = _bag.move_item_to_storage_slot(
_controller_storage_identity,
slot.storage_slot_index,
)
if moved:
_release_controller_ownership(true, false)
func _controller_storage_focus_is_on_last_row() -> bool:
var slot := get_viewport().gui_get_focus_owner() as BagStorageSlotType
if slot == null:
return false
return (
slot.storage_slot_index / BAG_STORAGE_COLUMNS
>= (_bag_slot_nodes.size() - 1) / BAG_STORAGE_COLUMNS
)
func _find_controller_hotbar_assignment(
assignment_kind: PlayerHotbarType.AssignmentKind,
identity: StringName,
@ -1023,6 +1145,31 @@ func _confirm_controller_hotbar_placement() -> void:
_release_controller_ownership(true, false)
func _return_controller_hotbar_placement_to_storage() -> bool:
if (
_controller_ownership != ControllerOwnership.HOTBAR_PLACEMENT
or _controller_source_section != Section.BAG
or _controller_storage_identity.is_empty()
or _bag_slot_nodes.is_empty()
):
return false
if _hotbar != null:
_hotbar.select_slot(_controller_previous_hotbar_slot)
controller_hotbar_placement_ended.emit()
_controller_ownership = ControllerOwnership.STORAGE_PLACEMENT
_controller_hotbar_assignment_kind = PlayerHotbarType.AssignmentKind.EMPTY
_controller_hotbar_identity = StringName()
_apply_inventory_controller_zone_focus_modes()
_configure_bag_storage_slot_focus()
var target_index: int = clampi(
_controller_previous_storage_slot,
0,
_bag_slot_nodes.size() - 1,
)
_bag_slot_nodes[target_index].call_deferred("grab_focus")
return true
func _release_controller_ownership(
restore_source_focus: bool,
restore_previous_hotbar_slot: bool,
@ -1037,6 +1184,7 @@ func _release_controller_ownership(
_controller_source_identity = StringName()
_controller_hotbar_assignment_kind = PlayerHotbarType.AssignmentKind.EMPTY
_controller_hotbar_identity = StringName()
_controller_storage_identity = StringName()
_apply_inventory_controller_zone_focus_modes()
if prior_ownership == ControllerOwnership.HOTBAR_PLACEMENT:
if restore_previous_hotbar_slot and _hotbar != null:
@ -1320,6 +1468,8 @@ func _consume_player_menu_back() -> bool:
_release_controller_ownership(true, false)
ControllerOwnership.HOTBAR_PLACEMENT:
_release_controller_ownership(true, true)
ControllerOwnership.STORAGE_PLACEMENT:
_release_controller_ownership(true, false)
ControllerOwnership.ITEM_LIST:
_enter_inventory_tabs_zone()
ControllerOwnership.INVENTORY_TABS:
@ -1773,7 +1923,10 @@ func _process(delta: float) -> void:
>= CONTROLLER_PICKUP_HOLD_SECONDS
):
_cancel_controller_accept_hold()
_try_begin_controller_hotbar_placement()
if _current_section == Section.BAG:
_try_begin_controller_storage_placement()
else:
_try_begin_controller_hotbar_placement()
_motion_elapsed += delta
if visible:
_navigation_cluster.advance_motion(delta)
@ -1907,6 +2060,11 @@ func _apply_inventory_controller_zone_focus_modes() -> void:
regular_active
and _controller_ownership == ControllerOwnership.SORT_FILTER
)
var storage_active: bool = (
regular_active
and _current_section == Section.BAG
and _controller_ownership == ControllerOwnership.STORAGE_PLACEMENT
)
for sort_control: Control in [
_cooler_sort_option,
_cooler_sort_direction,
@ -1952,6 +2110,27 @@ func _apply_inventory_controller_zone_focus_modes() -> void:
)
else Control.FOCUS_NONE
)
item_node.modulate.a = (
0.24
if (
storage_active
and item_node.item_id == _controller_storage_identity
)
else 1.0
)
for slot: BagStorageSlotType in _bag_slot_nodes:
if not is_instance_valid(slot):
continue
slot.focus_mode = (
Control.FOCUS_ALL if storage_active else Control.FOCUS_NONE
)
var preview_active: bool = (
storage_active and slot.has_focus()
)
slot.set_placement_preview(
_controller_storage_texture(),
preview_active,
)
for button: Button in _tackle_item_buttons.values():
button.focus_mode = (
Control.FOCUS_ALL
@ -2124,8 +2303,8 @@ func _refresh_tackle_box() -> void:
)
if item != null and item.category == ItemDataType.Category.LURE:
lure_items.append(owned)
bait_items.sort_custom(_sort_bag_items)
lure_items.sort_custom(_sort_bag_items)
bait_items.sort_custom(_sort_tackle_items)
lure_items.sort_custom(_sort_tackle_items)
_populate_tackle_column(bait_items, _bait_item_list)
_populate_tackle_column(lure_items, _lure_item_list)
if (
@ -3411,10 +3590,10 @@ func _refresh_bag() -> void:
):
filtered_items.append(owned)
owned_items = filtered_items
owned_items.sort_custom(_sort_bag_items)
owned_items.sort_custom(_sort_bag_storage_items)
_sorted_bag_items = owned_items
_bag_empty.visible = owned_items.is_empty()
_bag_empty_state.visible = owned_items.is_empty()
_bag_empty.visible = false
_bag_empty_state.visible = false
_bag_empty_state.text = (
"No equipment in your Bag."
if _bag_view == BagView.EQUIPMENT
@ -3462,9 +3641,9 @@ func _sync_bag_item_nodes(owned_items: Array[OwnedItemType]) -> void:
0.97 + float(identity_hash % 7) * 0.01,
)
item_node.tooltip_text = (
"drag to a hotbar slot."
"drag to another storage slot or the hotbar."
if item.hotbar_allowed
else "this item cannot be assigned to the hotbar."
else "drag to another storage slot."
)
item_node.disabled = false
item_node.set_selected(item.item_id == _selected_bag_item_id)
@ -3482,63 +3661,104 @@ func _sync_bag_item_nodes(owned_items: Array[OwnedItemType]) -> void:
func _layout_bag_items() -> void:
if not is_node_ready():
return
_bag_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_DISABLED
var columns: int = 3
_bag_scroll.vertical_scroll_mode = ScrollContainer.SCROLL_MODE_AUTO
var columns: int = BAG_STORAGE_COLUMNS
var cell_size := (
Vector2(166.0, 72.0)
Vector2(102.0, 48.0)
if _compact_layout
else Vector2(246.0, 154.0)
else Vector2(154.0, 110.0)
)
var slot_size := (
Vector2(90.0, 40.0)
if _compact_layout
else Vector2(140.0, 96.0)
)
var item_size := (
Vector2(86.0, 62.0)
Vector2(78.0, 36.0)
if _compact_layout
else Vector2(138.0, 102.0)
else Vector2(122.0, 82.0)
)
var origin := (
Vector2(16.0, 4.0)
Vector2(5.0, 4.0)
if _compact_layout
else Vector2(54.0, 44.0)
else Vector2(10.0, 9.0)
)
_bag_item_field.custom_minimum_size = (
Vector2(520.0, 140.0)
if _compact_layout
else Vector2(820.0, 240.0)
var highest_slot: int = -1
for owned: OwnedItemType in _sorted_bag_items:
highest_slot = maxi(highest_slot, owned.storage_slot)
var slot_count: int = maxi(
BAG_STORAGE_MINIMUM_SLOTS,
highest_slot + 1,
)
_sync_inventory_slot_visuals(
_bag_item_field,
_bag_slot_nodes,
maxi(9, _sorted_bag_items.size()),
slot_count = ceili(float(slot_count) / float(columns)) * columns
var required_rows: int = ceili(float(slot_count) / float(columns))
var content_size := Vector2(
520.0 if _compact_layout else 788.0,
maxf(
152.0 if _compact_layout else 358.0,
origin.y * 2.0 + float(required_rows) * cell_size.y,
),
)
_bag_host.custom_minimum_size = content_size
_bag_item_field.custom_minimum_size = content_size
_sync_bag_storage_slots(
slot_count,
columns,
cell_size,
item_size,
slot_size,
origin,
18.0,
)
for index: int in _sorted_bag_items.size():
var owned: OwnedItemType = _sorted_bag_items[index]
for owned: OwnedItemType in _sorted_bag_items:
var item_node := _bag_item_nodes.get(
owned.item_id
) as BagItemSpriteType
if item_node == null:
if item_node == null or owned.storage_slot < 0:
continue
var identity_hash: int = absi(String(owned.item_id).hash())
var column: int = index % columns
var row: int = floori(float(index) / float(columns))
var stable_offset := Vector2(
float(identity_hash % 13) - 6.0,
float(floori(float(identity_hash) / 19.0) % 11) - 5.0,
var column: int = owned.storage_slot % columns
var row: int = floori(float(owned.storage_slot) / float(columns))
var slot_position := origin + Vector2(
float(column) * cell_size.x,
float(row) * cell_size.y,
)
var lane_offset: float = 18.0 if row % 2 == 1 else 0.0
item_node.custom_minimum_size = item_size
item_node.size = item_size
item_node.position = (
origin
+ Vector2(
float(column) * cell_size.x + lane_offset,
float(row) * cell_size.y,
)
+ stable_offset
slot_position + (slot_size - item_size) * 0.5
)
item_node.z_index = 1
func _sync_bag_storage_slots(
slot_count: int,
columns: int,
cell_size: Vector2,
slot_size: Vector2,
origin: Vector2,
) -> void:
for slot: BagStorageSlotType in _bag_slot_nodes:
if is_instance_valid(slot):
slot.queue_free()
_bag_slot_nodes.clear()
for slot_index: int in slot_count:
var row: int = floori(float(slot_index) / float(columns))
var column: int = slot_index % columns
var slot := BagStorageSlotType.new()
slot.configure(slot_index)
slot.size = slot_size
slot.custom_minimum_size = slot_size
slot.position = origin + Vector2(
float(column) * cell_size.x,
float(row) * cell_size.y,
)
slot.bag_item_dropped.connect(_on_bag_item_dropped)
slot.pressed.connect(_on_bag_storage_slot_pressed.bind(slot))
slot.focus_entered.connect(
_on_bag_storage_slot_focus_entered.bind(slot)
)
_bag_item_field.add_child(slot)
_bag_item_field.move_child(slot, 0)
_bag_slot_nodes.append(slot)
_configure_bag_storage_slot_focus()
func _sync_inventory_slot_visuals(
@ -3596,30 +3816,44 @@ func _configure_bag_item_focus() -> void:
active_tab.focus_neighbor_bottom = active_tab.get_path_to(
controls.front()
)
for index: int in controls.size():
var control: BagItemSpriteType = controls[index]
var column: int = index % 3
ControllerFocusNavigationType.configure_spatial_neighbors(controls)
func _configure_bag_storage_slot_focus() -> void:
for index: int in _bag_slot_nodes.size():
var slot: BagStorageSlotType = _bag_slot_nodes[index]
if not is_instance_valid(slot):
continue
var column: int = index % BAG_STORAGE_COLUMNS
var left_index: int = index - 1 if column > 0 else index
var right_index: int = (
index + 1
if column < 2 and index + 1 < controls.size()
if (
column < BAG_STORAGE_COLUMNS - 1
and index + 1 < _bag_slot_nodes.size()
)
else index
)
control.focus_neighbor_left = control.get_path_to(
controls[left_index]
var top_index: int = (
index - BAG_STORAGE_COLUMNS
if index >= BAG_STORAGE_COLUMNS else index
)
control.focus_neighbor_right = control.get_path_to(
controls[right_index]
var bottom_index: int = (
index + BAG_STORAGE_COLUMNS
if index + BAG_STORAGE_COLUMNS < _bag_slot_nodes.size()
else index
)
control.focus_neighbor_top = (
control.get_path_to(control)
if index < 3
else control.get_path_to(controls[index - 3])
slot.focus_neighbor_left = slot.get_path_to(
_bag_slot_nodes[left_index]
)
control.focus_neighbor_bottom = control.get_path_to(
controls[index + 3]
if index + 3 < controls.size()
else control
slot.focus_neighbor_right = slot.get_path_to(
_bag_slot_nodes[right_index]
)
slot.focus_neighbor_top = slot.get_path_to(
_bag_slot_nodes[top_index]
)
slot.focus_neighbor_bottom = slot.get_path_to(
_bag_slot_nodes[bottom_index]
)
@ -3643,6 +3877,28 @@ func _sort_bag_items(a: OwnedItemType, b: OwnedItemType) -> bool:
return item_a.display_name.naturalnocasecmp_to(item_b.display_name) < 0
func _sort_bag_storage_items(a: OwnedItemType, b: OwnedItemType) -> bool:
if a.storage_slot >= 0 and b.storage_slot >= 0:
return a.storage_slot < b.storage_slot
if a.storage_slot >= 0:
return true
if b.storage_slot >= 0:
return false
return _sort_bag_items(a, b)
func _sort_tackle_items(a: OwnedItemType, b: OwnedItemType) -> bool:
var order_a := FishingShopStockType.get_stock_order_index(a.item_id)
var order_b := FishingShopStockType.get_stock_order_index(b.item_id)
if order_a >= 0 and order_b >= 0 and order_a != order_b:
return order_a < order_b
if order_a >= 0 and order_b < 0:
return true
if order_b >= 0 and order_a < 0:
return false
return _sort_bag_items(a, b)
func _select_bag_item(item_id: StringName) -> void:
if _bag_drag_active or get_viewport().gui_is_dragging():
return
@ -3743,6 +3999,42 @@ func _on_bag_drag_started() -> void:
_set_content_interactive(false)
func _on_bag_item_dropped(item_id: StringName, slot_index: int) -> void:
if _bag == null or item_id.is_empty() or slot_index < 0:
return
if _bag.move_item_to_storage_slot(item_id, slot_index):
_select_bag_item(item_id)
func _on_bag_storage_slot_pressed(slot: BagStorageSlotType) -> void:
if (
_controller_ownership == ControllerOwnership.STORAGE_PLACEMENT
and slot != null
):
_confirm_controller_storage_placement()
func _on_bag_storage_slot_focus_entered(slot: BagStorageSlotType) -> void:
if _controller_ownership != ControllerOwnership.STORAGE_PLACEMENT:
return
var texture := _controller_storage_texture()
for candidate: BagStorageSlotType in _bag_slot_nodes:
if is_instance_valid(candidate):
candidate.set_placement_preview(texture, candidate == slot)
func _controller_storage_texture() -> Texture2D:
var item: ItemDataType = (
_item_catalog.get_item_by_id(_controller_storage_identity)
if (
_item_catalog != null
and not _controller_storage_identity.is_empty()
)
else null
)
return item.icon if item != null else null
func _on_bag_drag_finished() -> void:
_bag_drag_active = false
if visible and _current_section == Section.BAG:
@ -3839,6 +4131,9 @@ func _sync_cooler_fish_nodes(catches: Array[FishCatchType]) -> void:
fish_node.pressed.connect(
_on_catch_card_pressed.bind(fish_catch.catch_id)
)
fish_node.focus_entered.connect(
_on_catch_card_focused.bind(fish_catch.catch_id)
)
var identity_hash: int = absi(String(fish_catch.catch_id).hash())
fish_node.configure(
fish_catch.catch_id,
@ -4067,6 +4362,24 @@ func _on_catch_card_pressed(catch_id: StringName) -> void:
_refresh_inventory()
func _on_catch_card_focused(catch_id: StringName) -> void:
if _inventory == null or _inventory.get_catch(catch_id) == null:
return
_fish_selection.focus_only(catch_id)
for visible_catch: FishCatchType in _sorted_catches:
var fish_node := _fish_nodes.get(
visible_catch.catch_id
) as CoolerFishSpriteType
if fish_node == null:
continue
fish_node.set_item_state(
_fish_selection.is_selected(visible_catch.catch_id),
visible_catch.catch_id == catch_id,
visible_catch.is_favorited,
)
_update_inventory_detail(_inventory.get_catch(catch_id))
func _on_fish_field_gui_input(event: InputEvent) -> void:
if (
event is InputEventMouseButton