189 lines
6.1 KiB
GDScript
189 lines
6.1 KiB
GDScript
extends CanvasLayer
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Layout constants (screen space: 1920 x 1080)
|
|
# ---------------------------------------------------------------------------
|
|
const SCREEN_W := 1920.0
|
|
const SCREEN_H := 1080.0
|
|
const PANEL_W := 620.0
|
|
const PANEL_H := 500.0
|
|
const LINE_H := 36.0
|
|
const LIST_PAD_X := 24.0
|
|
const LIST_PAD_Y := 60.0
|
|
|
|
const BG_COLOR := Color(0.06, 0.07, 0.12, 0.94)
|
|
const CURSOR_COLOR := Color(0.9, 0.85, 0.3, 1)
|
|
const NORMAL_COLOR := Color(0.75, 0.75, 0.85, 1)
|
|
const EQUIPPED_COLOR := Color(0.4, 0.9, 0.6, 1)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# State
|
|
# ---------------------------------------------------------------------------
|
|
var _items: Array = [] # Array[BodyPartData] — sorted by slot
|
|
var _cursor: int = 0
|
|
var _item_labels: Array = [] # Array[Label]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# UI nodes (built once in _ready)
|
|
# ---------------------------------------------------------------------------
|
|
var _bg: Polygon2D
|
|
var _title_lbl: Label
|
|
var _hint_lbl: Label
|
|
var _list_root: Node2D # parent for item labels (positioned in screen space)
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Boot
|
|
# ---------------------------------------------------------------------------
|
|
func _ready() -> void:
|
|
_build_static_ui()
|
|
hide()
|
|
|
|
|
|
func _build_static_ui() -> void:
|
|
var cx := SCREEN_W / 2.0
|
|
var cy := SCREEN_H / 2.0
|
|
var hw := PANEL_W / 2.0
|
|
var hh := PANEL_H / 2.0
|
|
|
|
# Background panel
|
|
_bg = Polygon2D.new()
|
|
_bg.polygon = PackedVector2Array([
|
|
Vector2(-hw, -hh), Vector2(hw, -hh),
|
|
Vector2( hw, hh), Vector2(-hw, hh),
|
|
])
|
|
_bg.color = BG_COLOR
|
|
_bg.position = Vector2(cx, cy)
|
|
add_child(_bg)
|
|
|
|
# Title
|
|
_title_lbl = Label.new()
|
|
_title_lbl.text = "— ARMORY —"
|
|
_title_lbl.position = Vector2(cx - 60, cy - hh + 14)
|
|
add_child(_title_lbl)
|
|
|
|
# Hint bar
|
|
_hint_lbl = Label.new()
|
|
_hint_lbl.text = "[↑↓] Navigate [Enter] Equip [Esc] Close"
|
|
_hint_lbl.position = Vector2(cx - hw + LIST_PAD_X, cy + hh - 30)
|
|
add_child(_hint_lbl)
|
|
|
|
# Container node for item labels
|
|
_list_root = Node2D.new()
|
|
_list_root.position = Vector2(cx - hw + LIST_PAD_X, cy - hh + LIST_PAD_Y)
|
|
add_child(_list_root)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Input
|
|
# ---------------------------------------------------------------------------
|
|
func _input(event: InputEvent) -> void:
|
|
if not visible:
|
|
return
|
|
|
|
if event.is_action_pressed("ui_up"):
|
|
_cursor = wrapi(_cursor - 1, 0, maxi(1, _items.size()))
|
|
_refresh_labels()
|
|
get_viewport().set_input_as_handled()
|
|
elif event.is_action_pressed("ui_down"):
|
|
_cursor = wrapi(_cursor + 1, 0, maxi(1, _items.size()))
|
|
_refresh_labels()
|
|
get_viewport().set_input_as_handled()
|
|
elif event.is_action_pressed("ui_accept"):
|
|
_equip_selected()
|
|
get_viewport().set_input_as_handled()
|
|
elif event.is_action_pressed("ui_cancel"):
|
|
hide()
|
|
get_viewport().set_input_as_handled()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Open — call from hub.gd
|
|
# ---------------------------------------------------------------------------
|
|
func open() -> void:
|
|
_rebuild_items()
|
|
_cursor = 0
|
|
_refresh_labels()
|
|
show()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Default body parts (always available)
|
|
# ---------------------------------------------------------------------------
|
|
const DEFAULT_PARTS: Array[String] = [
|
|
"res://data/body_parts/head_default.tres",
|
|
"res://data/body_parts/torso_default.tres",
|
|
"res://data/body_parts/left_arm_default.tres",
|
|
"res://data/body_parts/right_arm_default.tres",
|
|
"res://data/body_parts/legs_default.tres",
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Rebuild item list from UpgradeManager
|
|
# ---------------------------------------------------------------------------
|
|
func _rebuild_items() -> void:
|
|
_items.clear()
|
|
|
|
# Add default parts first (always available)
|
|
for path: String in DEFAULT_PARTS:
|
|
var part := load(path) as BodyPartData
|
|
if part:
|
|
_items.append(part)
|
|
|
|
# Add acquired parts
|
|
for path: String in UpgradeManager.acquired_part_paths:
|
|
if path == "":
|
|
continue
|
|
var part := load(path) as BodyPartData
|
|
if part:
|
|
_items.append(part)
|
|
|
|
# Sort by slot order so the list groups naturally
|
|
var slot_order := UpgradeManager.SLOTS
|
|
_items.sort_custom(func(a, b): return slot_order.find(a.slot) < slot_order.find(b.slot))
|
|
|
|
# Destroy old labels
|
|
for lbl: Label in _item_labels:
|
|
lbl.queue_free()
|
|
_item_labels.clear()
|
|
|
|
# Create one label per item
|
|
for i in _items.size():
|
|
var lbl := Label.new()
|
|
lbl.position = Vector2(0, i * LINE_H)
|
|
_list_root.add_child(lbl)
|
|
_item_labels.append(lbl)
|
|
|
|
|
|
func _refresh_labels() -> void:
|
|
for i in _item_labels.size():
|
|
var part: BodyPartData = _items[i]
|
|
var equipped_path: String = UpgradeManager.equipped_parts.get(part.slot, "")
|
|
# Check if this part is equipped (empty string means default is equipped)
|
|
var is_default_part := part.resource_path in DEFAULT_PARTS
|
|
var is_equipped := equipped_path == part.resource_path or (equipped_path == "" and is_default_part)
|
|
var is_cursor := i == _cursor
|
|
|
|
var slot_tag := "[%s]" % part.slot.to_upper()
|
|
var equip_tag := " ✓" if is_equipped else ""
|
|
var lbl: Label = _item_labels[i]
|
|
lbl.text = ("%s %s%s" % [slot_tag, part.display_name, equip_tag]).lstrip("")
|
|
|
|
if is_cursor:
|
|
lbl.add_theme_color_override("font_color", CURSOR_COLOR)
|
|
elif is_equipped:
|
|
lbl.add_theme_color_override("font_color", EQUIPPED_COLOR)
|
|
else:
|
|
lbl.add_theme_color_override("font_color", NORMAL_COLOR)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Equip the part under the cursor
|
|
# ---------------------------------------------------------------------------
|
|
func _equip_selected() -> void:
|
|
if _items.is_empty():
|
|
return
|
|
var part: BodyPartData = _items[_cursor]
|
|
UpgradeManager.equip_part(part)
|
|
SaveManager.save()
|
|
_refresh_labels()
|