Entity renderer: add framerate-independent position lerping so entities slide between tiles instead of snapping. Tuned for Sprint snappiness and Walk/Careful fluidity. Fog shader: set ColorRect to transparent fallback so a shader load failure doesn't paint solid white over the world. Track camera position (not player position) so fog stays synced during smooth camera pan. Restructure GLSL to avoid early return (some GPU drivers miscompile it). Minor type fixes: typed Array[Vector2] in cursor_renderer tick drawing, untyped Array in inventory_grid to avoid Godot typed-array cast issues. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
140 lines
3.7 KiB
GDScript
140 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 {}
|