112 lines
4.2 KiB
Python
112 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
|
|
@dataclass
|
|
class LayerPickerMenu:
|
|
"""Pure interaction state for choosing which of a tile's occupied layers a tool targets.
|
|
|
|
No rendering here - this project has no text-rendering system yet, so drawing an actual
|
|
on-screen popup box is a follow-up UI-layer task. This is the game-logic side of it: which
|
|
option is highlighted, and what layer name comes back once the player confirms.
|
|
"""
|
|
|
|
map_id: str
|
|
x: int
|
|
y: int
|
|
z: int
|
|
options: list[str] = field(default_factory=list)
|
|
selected_index: int = 0
|
|
|
|
def cycle(self, delta: int = 1) -> None:
|
|
if self.options:
|
|
self.selected_index = (self.selected_index + delta) % len(self.options)
|
|
|
|
def confirm(self) -> str | None:
|
|
if not self.options:
|
|
return None
|
|
return self.options[self.selected_index]
|
|
|
|
|
|
@dataclass
|
|
class AbilityBarSlot:
|
|
"""One assigned slot on the ability bar: where to find the ability-granting thing (a
|
|
wielded item in a hand, or an installed implant) when this slot gets triggered - see
|
|
resources/logic/actions.py::activate_ability_bar_slot for the dispatch on `kind`.
|
|
"""
|
|
|
|
kind: str # "hand" or "implant"
|
|
source_id: str # hand_slot name (e.g. "right_hand") or implant item_def_id
|
|
ability_id: str # the ability granted, for display/bookkeeping
|
|
|
|
|
|
@dataclass
|
|
class AbilityBar:
|
|
"""Pure interaction state for the bottom-left ability bar: up to 10 assignable slots
|
|
(numkeys 1-9 and 0), selectable by mouse click or hotkey - both just call select().
|
|
|
|
No rendering here - same boundary as LayerPickerMenu above: this project has no
|
|
text/icon-rendering system yet, so drawing the actual bar and highlighting the
|
|
selected slot on-screen is a follow-up UI-layer task.
|
|
"""
|
|
|
|
slots: list[AbilityBarSlot | None] = field(default_factory=lambda: [None] * 10)
|
|
selected_index: int | None = None
|
|
|
|
def assign(self, index: int, slot: AbilityBarSlot | None) -> None:
|
|
self.slots[index] = slot
|
|
|
|
def select(self, index: int) -> None:
|
|
if 0 <= index < len(self.slots):
|
|
self.selected_index = index
|
|
|
|
def selected_slot(self) -> AbilityBarSlot | None:
|
|
if self.selected_index is None:
|
|
return None
|
|
return self.slots[self.selected_index]
|
|
|
|
|
|
@dataclass
|
|
class CharacterMenu:
|
|
"""Pure interaction state for the bottom-left tabbed character menu: a "bio" tab (skills/
|
|
mental stats/health - all already on the Character model) and an "equipment" tab (worn
|
|
clothing + held items). Right-clicking an equipped slot that grants an inventory (e.g. a
|
|
worn backpack) opens a popup submenu above the menu showing that slot's contents.
|
|
|
|
No rendering here - same boundary as LayerPickerMenu/AbilityBar above: the actual bio and
|
|
equipment data already lives on the Character/DefRegistry models this engine built over the
|
|
whole session, so this class only tracks which tab is active and which backpack popup (if
|
|
any) is open - drawing the tabs, labels, and popup box is a follow-up UI-layer task.
|
|
"""
|
|
|
|
TABS = ("bio", "equipment")
|
|
|
|
active_tab: str = "bio"
|
|
open_backpack_slot: str | None = None # clothing slot whose granted-inventory popup is open
|
|
|
|
def select_tab(self, tab: str) -> None:
|
|
if tab in self.TABS:
|
|
self.active_tab = tab
|
|
self.open_backpack_slot = None # leaving equipment closes any open popup
|
|
|
|
def right_click_equipment_slot(self, slot: str, character, registry) -> bool:
|
|
"""Toggles the backpack popup for a worn item that grants an inventory (e.g. a
|
|
backpack). No-op (returns False, nothing opens) for an empty slot or a worn item with
|
|
no granted inventory - e.g. a plain jacket has nothing to pop open.
|
|
"""
|
|
piece = character.get_clothing(slot)
|
|
if piece is None:
|
|
return False
|
|
item_def = registry.get_item_def(piece.item_def_id)
|
|
if item_def.grants_inventory_size is None:
|
|
return False
|
|
if self.open_backpack_slot == slot:
|
|
self.open_backpack_slot = None
|
|
return False
|
|
self.open_backpack_slot = slot
|
|
return True
|
|
|
|
def close_backpack_popup(self) -> None:
|
|
self.open_backpack_slot = None
|