Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
148 lines
3.7 KiB
GDScript
148 lines
3.7 KiB
GDScript
extends Control
|
|
|
|
## D-065: Inventory grid — 3x3 slots, bottom-right, 40x40px icons.
|
|
## No empty slots displayed — icons appear only when items are carried.
|
|
## 1-9 hotkeys for direct slot access. Reads from GameState.player_inventory.
|
|
## Lives on UILayer (z-layer 7).
|
|
|
|
signal item_selected(slot: int, item_id: int)
|
|
|
|
const SLOT_SIZE := 40
|
|
const SLOT_GAP := 4
|
|
const GRID_COLS := 3
|
|
const GRID_ROWS := 3
|
|
const MAX_SLOTS := 9
|
|
|
|
const SLOT_BG := Color(0.1, 0.1, 0.14, 0.6)
|
|
const SLOT_BORDER := Color(0.3, 0.3, 0.38, 0.8)
|
|
const SLOT_ACTIVE := Color("#c8d0e0")
|
|
const SLOT_TEXT := Color("#c8d0e0")
|
|
const SLOT_TEXT_DIM := Color("#8b8ba0")
|
|
const HOTKEY_SIZE := 10
|
|
|
|
var _slots: Array = [] # [{item_id, name, slot}] from GameState
|
|
var _slot_nodes: Array[Control] = []
|
|
var _selected_slot: int = -1
|
|
|
|
|
|
func _ready() -> void:
|
|
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
|
_build_grid()
|
|
visible = false
|
|
|
|
|
|
func _build_grid() -> void:
|
|
for node in _slot_nodes:
|
|
if is_instance_valid(node):
|
|
node.queue_free()
|
|
_slot_nodes.clear()
|
|
|
|
var grid_w: float = GRID_COLS * SLOT_SIZE + (GRID_COLS - 1) * SLOT_GAP
|
|
var grid_h: float = GRID_ROWS * SLOT_SIZE + (GRID_ROWS - 1) * SLOT_GAP
|
|
|
|
# Position grid at bottom-right with margin
|
|
custom_minimum_size = Vector2(grid_w, grid_h)
|
|
size = Vector2(grid_w, grid_h)
|
|
|
|
|
|
func update_from_state() -> void:
|
|
_slots = GameState.player_inventory.duplicate()
|
|
|
|
if _slots.is_empty():
|
|
visible = false
|
|
return
|
|
|
|
visible = true
|
|
queue_redraw()
|
|
|
|
|
|
func _draw() -> void:
|
|
if _slots.is_empty():
|
|
return
|
|
|
|
for item in _slots:
|
|
var slot_idx: int = item.get("slot", 0)
|
|
if slot_idx < 0 or slot_idx >= MAX_SLOTS:
|
|
continue
|
|
|
|
var col: int = slot_idx % GRID_COLS
|
|
var row: int = slot_idx / GRID_COLS
|
|
var pos := Vector2(col * (SLOT_SIZE + SLOT_GAP), row * (SLOT_SIZE + SLOT_GAP))
|
|
var rect := Rect2(pos, Vector2(SLOT_SIZE, SLOT_SIZE))
|
|
|
|
# Slot background
|
|
draw_rect(rect, SLOT_BG)
|
|
# Border
|
|
draw_rect(rect, SLOT_BORDER, false, 1.0)
|
|
|
|
# Selected highlight
|
|
if slot_idx == _selected_slot:
|
|
draw_rect(rect, SLOT_ACTIVE, false, 2.0)
|
|
|
|
# Item name (truncated, centered)
|
|
var item_name: String = item.get("name", "?")
|
|
if item_name.length() > 5:
|
|
item_name = item_name.substr(0, 4) + "."
|
|
var font := ThemeDB.fallback_font
|
|
var font_size := 11
|
|
var text_size := font.get_string_size(item_name, HORIZONTAL_ALIGNMENT_CENTER, -1, font_size)
|
|
var text_pos := (
|
|
pos + Vector2((SLOT_SIZE - text_size.x) / 2.0, SLOT_SIZE / 2.0 + text_size.y / 4.0)
|
|
)
|
|
draw_string(font, text_pos, item_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, SLOT_TEXT)
|
|
|
|
# Hotkey number (top-left corner)
|
|
var hotkey := str(slot_idx + 1)
|
|
draw_string(
|
|
font,
|
|
pos + Vector2(3, HOTKEY_SIZE + 2),
|
|
hotkey,
|
|
HORIZONTAL_ALIGNMENT_LEFT,
|
|
-1,
|
|
HOTKEY_SIZE,
|
|
SLOT_TEXT_DIM
|
|
)
|
|
|
|
|
|
func _unhandled_input(event: InputEvent) -> void:
|
|
if not visible:
|
|
return
|
|
if event is InputEventKey and event.pressed and not event.echo:
|
|
var key: int = event.physical_keycode
|
|
# 1-9 hotkeys (Key_1 = 49, Key_9 = 57)
|
|
if key >= KEY_1 and key <= KEY_9:
|
|
var slot_idx: int = key - KEY_1
|
|
_select_slot(slot_idx)
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
func _select_slot(slot_idx: int) -> void:
|
|
# Find item in this slot
|
|
for item in _slots:
|
|
if item.get("slot", -1) == slot_idx:
|
|
_selected_slot = slot_idx
|
|
item_selected.emit(slot_idx, item.get("item_id", -1))
|
|
queue_redraw()
|
|
return
|
|
# No item in slot — deselect
|
|
_selected_slot = -1
|
|
queue_redraw()
|
|
|
|
|
|
# -- Public API ---------------------------------------------------------------
|
|
|
|
|
|
func get_slot_count() -> int:
|
|
return _slots.size()
|
|
|
|
|
|
func get_selected_slot() -> int:
|
|
return _selected_slot
|
|
|
|
|
|
func get_item_at_slot(slot_idx: int) -> Dictionary:
|
|
for item in _slots:
|
|
if item.get("slot", -1) == slot_idx:
|
|
return item
|
|
return {}
|