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).
135 lines
4.4 KiB
GDScript
135 lines
4.4 KiB
GDScript
extends Control
|
|
class_name RunResults
|
|
## End-of-run results screen for the gladiator arena.
|
|
## Shown by the HUD when the local player dies. Displays survival stats and
|
|
## the death settlement (keep 10% of total value, floored at the fresh-start
|
|
## default), then returns the player to the menu/camp.
|
|
|
|
var stats: Dictionary = {}
|
|
|
|
func setup(run_stats: Dictionary):
|
|
stats = run_stats
|
|
|
|
func _ready():
|
|
# Full-screen dark backdrop
|
|
set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
|
|
|
var backdrop = ColorRect.new()
|
|
backdrop.color = Color(0.0, 0.0, 0.0, 0.75)
|
|
backdrop.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
add_child(backdrop)
|
|
|
|
# Centered panel
|
|
var center = CenterContainer.new()
|
|
center.set_anchors_preset(Control.PRESET_FULL_RECT)
|
|
add_child(center)
|
|
|
|
var panel = PanelContainer.new()
|
|
panel.custom_minimum_size = Vector2(460, 0)
|
|
center.add_child(panel)
|
|
|
|
var margin = MarginContainer.new()
|
|
margin.add_theme_constant_override("margin_left", 30)
|
|
margin.add_theme_constant_override("margin_right", 30)
|
|
margin.add_theme_constant_override("margin_top", 24)
|
|
margin.add_theme_constant_override("margin_bottom", 24)
|
|
panel.add_child(margin)
|
|
|
|
var vbox = VBoxContainer.new()
|
|
vbox.add_theme_constant_override("separation", 8)
|
|
margin.add_child(vbox)
|
|
|
|
# Title
|
|
var title = Label.new()
|
|
title.text = "YOU HAVE FALLEN"
|
|
title.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
title.add_theme_font_size_override("font_size", 36)
|
|
title.add_theme_color_override("font_color", Color(0.9, 0.2, 0.2))
|
|
vbox.add_child(title)
|
|
|
|
var subtitle = Label.new()
|
|
subtitle.text = "The arena claims another gladiator"
|
|
subtitle.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
subtitle.add_theme_font_size_override("font_size", 16)
|
|
subtitle.add_theme_color_override("font_color", Color(0.7, 0.7, 0.7))
|
|
vbox.add_child(subtitle)
|
|
|
|
_add_spacer(vbox, 12)
|
|
|
|
# Survival stats
|
|
_add_header(vbox, "=== SURVIVAL ===")
|
|
_add_stat_row(vbox, "Waves Survived", str(int(stats.get("waves", 0))))
|
|
_add_stat_row(vbox, "Time in Arena", _format_time(stats.get("time", 0.0)))
|
|
_add_stat_row(vbox, "Kills", str(int(stats.get("kills", 0))))
|
|
|
|
_add_spacer(vbox, 12)
|
|
|
|
# Settlement
|
|
_add_header(vbox, "=== SETTLEMENT ===")
|
|
_add_stat_row(vbox, "Gold Earned This Run", str(int(stats.get("gold_earned", 0))))
|
|
_add_stat_row(vbox, "Total Value at Death", str(int(stats.get("total_value", 0))))
|
|
|
|
var kept = int(stats.get("kept", 0))
|
|
var kept_row = Label.new()
|
|
kept_row.text = "The arena takes its cut... you keep " + str(kept) + " gold"
|
|
kept_row.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
kept_row.add_theme_font_size_override("font_size", 18)
|
|
kept_row.add_theme_color_override("font_color", Color(1.0, 0.8, 0.0))
|
|
vbox.add_child(kept_row)
|
|
|
|
_add_spacer(vbox, 16)
|
|
|
|
# Return button
|
|
var return_button = Button.new()
|
|
return_button.text = "Return to Camp"
|
|
return_button.custom_minimum_size = Vector2(0, 44)
|
|
return_button.pressed.connect(_on_return_pressed)
|
|
vbox.add_child(return_button)
|
|
|
|
# Make sure the mouse is usable on the overlay
|
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
|
|
|
func _add_header(parent: Control, text: String):
|
|
var label = Label.new()
|
|
label.text = text
|
|
label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER
|
|
label.add_theme_font_size_override("font_size", 16)
|
|
label.add_theme_color_override("font_color", Color(0.8, 0.8, 0.8))
|
|
parent.add_child(label)
|
|
|
|
func _add_stat_row(parent: Control, label_text: String, value_text: String):
|
|
var row = HBoxContainer.new()
|
|
var label = Label.new()
|
|
label.text = label_text
|
|
label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
label.add_theme_font_size_override("font_size", 16)
|
|
row.add_child(label)
|
|
|
|
var value = Label.new()
|
|
value.text = value_text
|
|
value.add_theme_font_size_override("font_size", 16)
|
|
value.add_theme_color_override("font_color", Color(1.0, 0.8, 0.0))
|
|
row.add_child(value)
|
|
parent.add_child(row)
|
|
|
|
func _add_spacer(parent: Control, height: int):
|
|
var spacer = Control.new()
|
|
spacer.custom_minimum_size = Vector2(0, height)
|
|
parent.add_child(spacer)
|
|
|
|
func _format_time(seconds: float) -> String:
|
|
var total = int(seconds)
|
|
return "%dm %02ds" % [total / 60, total % 60]
|
|
|
|
func _on_return_pressed():
|
|
# Tear down the multiplayer session and go back to the menu.
|
|
# (Host quitting ends the session for connected clients - acceptable for now.)
|
|
multiplayer.multiplayer_peer = null
|
|
Network.players.clear()
|
|
|
|
if has_node("/root/HUD"):
|
|
get_node("/root/HUD").reset()
|
|
|
|
get_tree().reload_current_scene()
|