netfishing/inventory/player_hotbar.gd

301 lines
7.9 KiB
GDScript3
Raw Normal View History

class_name PlayerHotbar
extends Node
const SLOT_COUNT: int = 9
const ItemCatalogType = preload("res://items/item_catalog.gd")
const ItemDataType = preload("res://items/item_data.gd")
const PlayerBagType = preload("res://inventory/player_bag.gd")
2026-08-01 17:12:29 -04:00
const FishInventoryType = preload("res://inventory/fish_inventory.gd")
enum AssignmentKind {
EMPTY,
ITEM,
FISH,
}
signal slots_changed
signal selected_slot_changed(slot_index: int, item_id: StringName)
2026-08-01 17:12:29 -04:00
signal selected_assignment_changed(
slot_index: int,
kind: AssignmentKind,
identity: StringName,
)
var _bag: PlayerBagType
var _catalog: ItemCatalogType
2026-08-01 17:12:29 -04:00
var _fish_inventory: FishInventoryType
var _slots: Array[StringName] = []
2026-08-01 17:12:29 -04:00
var _fish_slots: Array[StringName] = []
var _selected_slot: int = 0
func _init() -> void:
_slots.resize(SLOT_COUNT)
_slots.fill(StringName())
2026-08-01 17:12:29 -04:00
_fish_slots.resize(SLOT_COUNT)
_fish_slots.fill(StringName())
2026-08-01 17:12:29 -04:00
func setup(
bag: PlayerBagType,
catalog: ItemCatalogType,
fish_inventory: FishInventoryType = null,
) -> void:
_bag = bag
_catalog = catalog
2026-08-01 17:12:29 -04:00
_fish_inventory = fish_inventory
if _bag != null and not _bag.contents_changed.is_connected(
_on_bag_contents_changed
):
_bag.contents_changed.connect(_on_bag_contents_changed)
2026-08-01 17:12:29 -04:00
if (
_fish_inventory != null
and not _fish_inventory.catches_changed.is_connected(
_on_fish_inventory_changed
)
):
_fish_inventory.catches_changed.connect(_on_fish_inventory_changed)
_validate_assignments()
func assign_item(slot_index: int, item_id: StringName) -> bool:
if not _is_slot_valid(slot_index) or not _can_assign(item_id):
return false
if _slots[slot_index] == item_id:
return true
2026-08-01 17:12:29 -04:00
var selected_slot_was_affected: bool = slot_index == _selected_slot
for index: int in range(SLOT_COUNT):
if index != slot_index and _slots[index] == item_id:
_slots[index] = StringName()
2026-08-01 17:12:29 -04:00
selected_slot_was_affected = (
selected_slot_was_affected or index == _selected_slot
)
_slots[slot_index] = item_id
2026-08-01 17:12:29 -04:00
_fish_slots[slot_index] = StringName()
slots_changed.emit()
2026-08-01 17:12:29 -04:00
if selected_slot_was_affected:
_emit_selected_assignment()
return true
func assign_fish(slot_index: int, catch_id: StringName) -> bool:
if not _is_slot_valid(slot_index) or not _can_assign_fish(catch_id):
return false
if _fish_slots[slot_index] == catch_id:
return true
var selected_slot_was_affected: bool = slot_index == _selected_slot
for index: int in range(SLOT_COUNT):
if index != slot_index and _fish_slots[index] == catch_id:
_fish_slots[index] = StringName()
selected_slot_was_affected = (
selected_slot_was_affected or index == _selected_slot
)
_slots[slot_index] = StringName()
_fish_slots[slot_index] = catch_id
slots_changed.emit()
if selected_slot_was_affected:
_emit_selected_assignment()
return true
func clear_slot(slot_index: int) -> bool:
2026-08-01 17:12:29 -04:00
if (
not _is_slot_valid(slot_index)
or (
_slots[slot_index].is_empty()
and _fish_slots[slot_index].is_empty()
)
):
return false
_slots[slot_index] = StringName()
2026-08-01 17:12:29 -04:00
_fish_slots[slot_index] = StringName()
slots_changed.emit()
if slot_index == _selected_slot:
2026-08-01 17:12:29 -04:00
_emit_selected_assignment()
return true
func swap_slots(from_index: int, to_index: int) -> bool:
if (
not _is_slot_valid(from_index)
or not _is_slot_valid(to_index)
or from_index == to_index
):
return false
var temporary: StringName = _slots[from_index]
_slots[from_index] = _slots[to_index]
_slots[to_index] = temporary
2026-08-01 17:12:29 -04:00
temporary = _fish_slots[from_index]
_fish_slots[from_index] = _fish_slots[to_index]
_fish_slots[to_index] = temporary
slots_changed.emit()
if from_index == _selected_slot or to_index == _selected_slot:
2026-08-01 17:12:29 -04:00
_emit_selected_assignment()
return true
func select_slot(slot_index: int) -> bool:
if not _is_slot_valid(slot_index):
return false
if _selected_slot == slot_index:
return true
_selected_slot = slot_index
2026-08-01 17:12:29 -04:00
_emit_selected_assignment()
return true
func cycle_selection(direction: int) -> bool:
if direction == 0:
return false
return select_slot(
posmod(_selected_slot + signi(direction), SLOT_COUNT)
)
func get_selected_slot() -> int:
return _selected_slot
func get_selected_item_id() -> StringName:
return _slots[_selected_slot]
2026-08-01 17:12:29 -04:00
func get_selected_fish_catch_id() -> StringName:
return _fish_slots[_selected_slot]
func get_selected_assignment_kind() -> AssignmentKind:
return get_assignment_kind(_selected_slot)
func get_item_id(slot_index: int) -> StringName:
return _slots[slot_index] if _is_slot_valid(slot_index) else StringName()
2026-08-01 17:12:29 -04:00
func get_fish_catch_id(slot_index: int) -> StringName:
return (
_fish_slots[slot_index]
if _is_slot_valid(slot_index)
else StringName()
)
func get_assignment_kind(slot_index: int) -> AssignmentKind:
if not _is_slot_valid(slot_index):
return AssignmentKind.EMPTY
if not _fish_slots[slot_index].is_empty():
return AssignmentKind.FISH
if not _slots[slot_index].is_empty():
return AssignmentKind.ITEM
return AssignmentKind.EMPTY
func get_slots() -> Array[StringName]:
return _slots.duplicate()
2026-08-01 17:12:29 -04:00
func get_fish_slots() -> Array[StringName]:
return _fish_slots.duplicate()
func replace_state(
slots: Array[StringName],
selected_slot: int,
2026-08-01 17:12:29 -04:00
fish_slots: Array[StringName] = [],
) -> bool:
var normalized: Array[StringName] = []
normalized.resize(SLOT_COUNT)
normalized.fill(StringName())
2026-08-01 17:12:29 -04:00
var normalized_fish: Array[StringName] = []
normalized_fish.resize(SLOT_COUNT)
normalized_fish.fill(StringName())
var seen_assignments: Dictionary[StringName, bool] = {}
for index: int in range(mini(slots.size(), SLOT_COUNT)):
var item_id: StringName = slots[index]
if item_id.is_empty():
continue
if _can_assign(item_id) and not seen_assignments.has(item_id):
normalized[index] = item_id
seen_assignments[item_id] = true
2026-08-01 17:12:29 -04:00
var seen_fish: Dictionary[StringName, bool] = {}
for index: int in range(mini(fish_slots.size(), SLOT_COUNT)):
if not normalized[index].is_empty():
continue
var catch_id: StringName = fish_slots[index]
if _can_assign_fish(catch_id) and not seen_fish.has(catch_id):
normalized_fish[index] = catch_id
seen_fish[catch_id] = true
_slots = normalized
2026-08-01 17:12:29 -04:00
_fish_slots = normalized_fish
_selected_slot = clampi(selected_slot, 0, SLOT_COUNT - 1)
slots_changed.emit()
2026-08-01 17:12:29 -04:00
_emit_selected_assignment()
return true
func has_any_assignment() -> bool:
2026-08-01 17:12:29 -04:00
for index: int in range(SLOT_COUNT):
if (
not _slots[index].is_empty()
or not _fish_slots[index].is_empty()
):
return true
return false
func _can_assign(item_id: StringName) -> bool:
if item_id.is_empty() or _bag == null or not _bag.owns_item(item_id):
return false
if _catalog == null:
return false
var item: ItemDataType = _catalog.get_item_by_id(item_id)
return item != null and item.is_valid() and item.hotbar_allowed
2026-08-01 17:12:29 -04:00
func _can_assign_fish(catch_id: StringName) -> bool:
return (
not catch_id.is_empty()
and _fish_inventory != null
and _fish_inventory.contains_catch_id(catch_id)
)
func _validate_assignments() -> void:
var changed: bool = false
for index: int in range(SLOT_COUNT):
if not _slots[index].is_empty() and not _can_assign(_slots[index]):
_slots[index] = StringName()
changed = true
2026-08-01 17:12:29 -04:00
if (
not _fish_slots[index].is_empty()
and not _can_assign_fish(_fish_slots[index])
):
_fish_slots[index] = StringName()
changed = true
if changed:
slots_changed.emit()
2026-08-01 17:12:29 -04:00
_emit_selected_assignment()
func _on_bag_contents_changed() -> void:
_validate_assignments()
2026-08-01 17:12:29 -04:00
func _on_fish_inventory_changed() -> void:
_validate_assignments()
func _emit_selected_assignment() -> void:
var kind: AssignmentKind = get_selected_assignment_kind()
var identity: StringName = StringName()
if kind == AssignmentKind.ITEM:
identity = _slots[_selected_slot]
elif kind == AssignmentKind.FISH:
identity = _fish_slots[_selected_slot]
selected_slot_changed.emit(_selected_slot, _slots[_selected_slot])
selected_assignment_changed.emit(_selected_slot, kind, identity)
func _is_slot_valid(slot_index: int) -> bool:
return slot_index >= 0 and slot_index < SLOT_COUNT