Turns the sandbox into a survival roguelike loop: outfit your gladiator with banked gold, enter the arena, earn gold from kills and wave clears, die, keep 10% of your total value (floored at the 500 fresh-start). Economy (GameState autoload): - banked_gold, owned items, and loadout persist to user://save.cfg - Kill gold (basic 10g, armed 25g) credited server-side to the killer - Wave-clear bonus (25 + 5/wave) for all players; wave synced to peers - Death settlement liquidates gear, guarded against double-fire Character creation / shop: - Menu reframed: armory shop + live Character Sheet loadout preview - Character Sheet gains preview mode (renders from loadout, no player) - Buy/Equip with hand rules; two-handers and off-hands displace each other symmetrically (shop and in-game pickups) - Chosen loadout auto-equips on spawn via existing RPC path Combat feel: - Attacks snap to camera facing and lock direction for the swing - Dash commits to its direction for the full duration - Weapon slots show name text when no icon is set - Hitbox/hurtbox debug meshes hidden by default (H toggles) Death is final: players no longer respawn; a results screen shows waves, time, kills, and the settlement, then returns to camp. Armed enemies no longer respawn (were an infinite gold farm and respawned invisible).
222 lines
6.8 KiB
GDScript
222 lines
6.8 KiB
GDScript
extends CanvasLayer
|
|
## HUD Manager - Main UI controller for the WoW-style interface
|
|
## This autoload manages all UI components and connects to the local player
|
|
|
|
# UI Component references
|
|
var action_bar: Control = null
|
|
var unit_frame: Control = null
|
|
var target_frame: Control = null
|
|
var character_sheet: Control = null
|
|
var tab_hint: Control = null
|
|
var keybind_hint: Control = null
|
|
var escape_menu: Control = null
|
|
var run_results: Control = null
|
|
|
|
# Player reference
|
|
var local_player: Character = null
|
|
|
|
func _ready():
|
|
# Create main HUD container
|
|
name = "HUD"
|
|
layer = 100 # Ensure UI is above game world
|
|
|
|
print("[HUD] HUD Manager initialized")
|
|
|
|
## Set the local player and connect signals
|
|
func set_local_player(player: Character):
|
|
if local_player:
|
|
_disconnect_player_signals()
|
|
|
|
local_player = player
|
|
|
|
if not local_player:
|
|
return
|
|
|
|
print("[HUD] Local player set: ", local_player.name)
|
|
_connect_player_signals()
|
|
_create_ui_components()
|
|
|
|
## Connect to player signals
|
|
func _connect_player_signals():
|
|
if not local_player:
|
|
return
|
|
|
|
# Health signals (from BaseUnit)
|
|
local_player.health_changed.connect(_on_player_health_changed)
|
|
local_player.died.connect(_on_player_died)
|
|
local_player.respawned.connect(_on_player_respawned)
|
|
|
|
# Ability/weapon signals
|
|
local_player.dash_cooldown_updated.connect(_on_dash_cooldown_updated)
|
|
local_player.attack_cooldown_updated.connect(_on_attack_cooldown_updated)
|
|
local_player.weapon_equipped_changed.connect(_on_weapon_changed)
|
|
|
|
print("[HUD] Connected to player signals")
|
|
|
|
## Disconnect from player signals
|
|
func _disconnect_player_signals():
|
|
if not local_player:
|
|
return
|
|
|
|
if local_player.health_changed.is_connected(_on_player_health_changed):
|
|
local_player.health_changed.disconnect(_on_player_health_changed)
|
|
if local_player.died.is_connected(_on_player_died):
|
|
local_player.died.disconnect(_on_player_died)
|
|
if local_player.respawned.is_connected(_on_player_respawned):
|
|
local_player.respawned.disconnect(_on_player_respawned)
|
|
if local_player.dash_cooldown_updated.is_connected(_on_dash_cooldown_updated):
|
|
local_player.dash_cooldown_updated.disconnect(_on_dash_cooldown_updated)
|
|
if local_player.attack_cooldown_updated.is_connected(_on_attack_cooldown_updated):
|
|
local_player.attack_cooldown_updated.disconnect(_on_attack_cooldown_updated)
|
|
if local_player.weapon_equipped_changed.is_connected(_on_weapon_changed):
|
|
local_player.weapon_equipped_changed.disconnect(_on_weapon_changed)
|
|
|
|
## Create all UI components
|
|
func _create_ui_components():
|
|
print("[HUD] Creating UI components...")
|
|
|
|
_create_action_bar()
|
|
_create_unit_frame()
|
|
_create_character_sheet()
|
|
_create_tab_hint()
|
|
_create_keybind_hint()
|
|
_create_escape_menu()
|
|
|
|
## Create action bar at bottom of screen
|
|
func _create_action_bar():
|
|
var action_bar_scene = load("res://level/ui/scenes/action_bar.tscn")
|
|
if action_bar_scene:
|
|
action_bar = action_bar_scene.instantiate()
|
|
add_child(action_bar)
|
|
if action_bar and local_player:
|
|
action_bar.set_player(local_player)
|
|
print("[HUD] Action bar created")
|
|
else:
|
|
push_error("[HUD] Failed to load action_bar.tscn")
|
|
|
|
## Create unit frame (character portrait)
|
|
func _create_unit_frame():
|
|
var unit_frame_scene = load("res://level/ui/scenes/unit_frame.tscn")
|
|
if unit_frame_scene:
|
|
unit_frame = unit_frame_scene.instantiate()
|
|
add_child(unit_frame)
|
|
if unit_frame and local_player:
|
|
unit_frame.set_player(local_player)
|
|
print("[HUD] Unit frame created")
|
|
else:
|
|
push_error("[HUD] Failed to load unit_frame.tscn")
|
|
|
|
## Create character sheet (toggle with Tab)
|
|
func _create_character_sheet():
|
|
var character_sheet_scene = load("res://level/ui/scenes/character_sheet.tscn")
|
|
if character_sheet_scene:
|
|
character_sheet = character_sheet_scene.instantiate()
|
|
add_child(character_sheet)
|
|
if character_sheet and local_player:
|
|
character_sheet.set_player(local_player)
|
|
print("[HUD] Character sheet created")
|
|
else:
|
|
push_error("[HUD] Failed to load character_sheet.tscn")
|
|
|
|
## Create Tab hint (always visible on left side)
|
|
func _create_tab_hint():
|
|
var tab_hint_scene = load("res://level/ui/scenes/tab_hint.tscn")
|
|
if tab_hint_scene:
|
|
tab_hint = tab_hint_scene.instantiate()
|
|
add_child(tab_hint)
|
|
print("[HUD] Tab hint created")
|
|
else:
|
|
push_error("[HUD] Failed to load tab_hint.tscn")
|
|
|
|
## Create keybind hint (always visible, top-right corner)
|
|
func _create_keybind_hint():
|
|
var keybind_hint_scene = load("res://level/ui/scenes/keybind_hint.tscn")
|
|
if keybind_hint_scene:
|
|
keybind_hint = keybind_hint_scene.instantiate()
|
|
add_child(keybind_hint)
|
|
print("[HUD] Keybind hint created")
|
|
else:
|
|
push_error("[HUD] Failed to load keybind_hint.tscn")
|
|
|
|
## Create escape menu (toggle with Escape)
|
|
func _create_escape_menu():
|
|
var escape_menu_scene = load("res://level/ui/scenes/escape_menu.tscn")
|
|
if escape_menu_scene:
|
|
escape_menu = escape_menu_scene.instantiate()
|
|
add_child(escape_menu)
|
|
if escape_menu and local_player:
|
|
escape_menu.set_player(local_player)
|
|
print("[HUD] Escape menu created")
|
|
else:
|
|
push_error("[HUD] Failed to load escape_menu.tscn")
|
|
|
|
## Show all UI components
|
|
func show_hud():
|
|
show()
|
|
|
|
## Hide all UI components
|
|
func hide_hud():
|
|
hide()
|
|
|
|
## Tear down all UI components (used when returning to the menu after a run).
|
|
## The HUD is an autoload, so its children survive scene reloads unless freed.
|
|
func reset():
|
|
if local_player and is_instance_valid(local_player):
|
|
_disconnect_player_signals()
|
|
local_player = null
|
|
|
|
for child in get_children():
|
|
child.queue_free()
|
|
|
|
action_bar = null
|
|
unit_frame = null
|
|
target_frame = null
|
|
character_sheet = null
|
|
tab_hint = null
|
|
keybind_hint = null
|
|
escape_menu = null
|
|
run_results = null
|
|
print("[HUD] Reset - all UI components removed")
|
|
|
|
## Player signal callbacks
|
|
func _on_player_health_changed(old_health: float, new_health: float):
|
|
# Update unit frame
|
|
if unit_frame:
|
|
unit_frame.update_health(new_health, local_player.max_health)
|
|
|
|
func _on_player_died(killer_id: int):
|
|
print("[HUD] Player died - ending run")
|
|
_show_run_results()
|
|
|
|
## End the local player's run and show the results screen.
|
|
## GameState.end_run() is guarded against double-calls (died can fire twice on host).
|
|
func _show_run_results():
|
|
if run_results and is_instance_valid(run_results):
|
|
return # Already showing
|
|
|
|
var stats = GameState.end_run()
|
|
|
|
var results_script = load("res://level/ui/scripts/run_results.gd")
|
|
run_results = results_script.new()
|
|
run_results.setup(stats)
|
|
add_child(run_results)
|
|
|
|
func _on_player_respawned():
|
|
print("[HUD] Player respawned")
|
|
# Refresh all UI elements
|
|
if local_player:
|
|
_on_player_health_changed(0, local_player.current_health)
|
|
|
|
## Cooldown signal handlers
|
|
func _on_dash_cooldown_updated(remaining: float, total: float):
|
|
if action_bar:
|
|
action_bar.update_dash_cooldown(remaining, total)
|
|
|
|
func _on_attack_cooldown_updated(remaining: float, total: float):
|
|
if action_bar:
|
|
action_bar.update_attack_cooldown(remaining, total)
|
|
|
|
func _on_weapon_changed():
|
|
if action_bar:
|
|
action_bar.on_weapon_changed()
|