Gladiator arena: gold economy, outfitting shop, permadeath runs
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).
This commit is contained in:
@@ -11,15 +11,40 @@ class_name CharacterSheet
|
||||
# Player reference
|
||||
var player: Character = null
|
||||
|
||||
# Preview mode: renders from a loadout dictionary instead of a live player.
|
||||
# Used by the outfitting screen at character creation.
|
||||
# preview_data keys: "nick" (String), "main" (WeaponData or null), "offhand" (WeaponData or null)
|
||||
var preview_mode: bool = false
|
||||
var preview_data: Dictionary = {}
|
||||
|
||||
# Character defaults shown in preview (match player.gd export defaults)
|
||||
const PREVIEW_MAX_HEALTH := 100.0
|
||||
const PREVIEW_ATTACK_DAMAGE := 10.0
|
||||
const PREVIEW_ATTACK_RANGE := 3.0
|
||||
const PREVIEW_ATTACK_COOLDOWN := 0.5
|
||||
const PREVIEW_DASH_COOLDOWN := 4.0
|
||||
const PREVIEW_DASH_DURATION := 0.25
|
||||
const PREVIEW_DASH_MULTIPLIER := 2.0
|
||||
|
||||
# Visibility
|
||||
var is_visible: bool = false
|
||||
|
||||
func _ready():
|
||||
# Start hidden
|
||||
hide()
|
||||
is_visible = false
|
||||
if preview_mode:
|
||||
_configure_preview_layout()
|
||||
show()
|
||||
is_visible = true
|
||||
refresh_all()
|
||||
else:
|
||||
# Start hidden (in-game Tab toggle behavior)
|
||||
hide()
|
||||
is_visible = false
|
||||
|
||||
func _input(event):
|
||||
# Embedded preview never toggles - it's part of the outfitting screen
|
||||
if preview_mode:
|
||||
return
|
||||
|
||||
# Close on Escape press if open
|
||||
if event.is_action_pressed("quit") and is_visible:
|
||||
toggle_sheet()
|
||||
@@ -37,6 +62,35 @@ func set_player(p: Character):
|
||||
if player:
|
||||
refresh_all()
|
||||
|
||||
## Feed the sheet a loadout to preview (outfitting screen).
|
||||
## Safe to call before or after the sheet enters the tree.
|
||||
func set_preview(data: Dictionary):
|
||||
preview_mode = true
|
||||
preview_data = data
|
||||
if is_inside_tree():
|
||||
refresh_all()
|
||||
|
||||
## Re-shape the fullscreen overlay into an embeddable panel:
|
||||
## no dark backdrop, panel fills the space its parent gives it.
|
||||
func _configure_preview_layout():
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
var backdrop = get_node_or_null("DarkBackground")
|
||||
if backdrop:
|
||||
backdrop.hide()
|
||||
var panel = get_node_or_null("Panel")
|
||||
if panel:
|
||||
panel.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
panel.offset_left = 0
|
||||
panel.offset_top = 0
|
||||
panel.offset_right = 0
|
||||
panel.offset_bottom = 0
|
||||
var title = get_node_or_null("Panel/MarginContainer/VBoxContainer/TitleLabel")
|
||||
if title:
|
||||
title.text = "YOUR GLADIATOR"
|
||||
var close_hint = get_node_or_null("Panel/MarginContainer/VBoxContainer/CloseHint")
|
||||
if close_hint:
|
||||
close_hint.hide()
|
||||
|
||||
## Toggle character sheet visibility
|
||||
func toggle_sheet():
|
||||
is_visible = !is_visible
|
||||
@@ -53,7 +107,7 @@ func toggle_sheet():
|
||||
|
||||
## Refresh all data in the sheet
|
||||
func refresh_all():
|
||||
if not player:
|
||||
if not player and not preview_mode:
|
||||
return
|
||||
|
||||
_refresh_stats()
|
||||
@@ -81,11 +135,40 @@ func _refresh_stats():
|
||||
spacer1.custom_minimum_size = Vector2(0, 10)
|
||||
stats_container.add_child(spacer1)
|
||||
|
||||
# Get player name
|
||||
var player_id = player.name.to_int()
|
||||
var player_name = "Player"
|
||||
if Network.players.has(player_id):
|
||||
player_name = Network.players[player_id]["nick"]
|
||||
# Resolve stats from either the live player or the preview loadout
|
||||
var player_name: String
|
||||
var current_hp: int
|
||||
var max_hp: int
|
||||
var atk_damage: float
|
||||
var atk_range: float
|
||||
var atk_cooldown: float
|
||||
|
||||
if preview_mode:
|
||||
player_name = preview_data.get("nick", "Gladiator")
|
||||
if player_name.strip_edges() == "":
|
||||
player_name = "Gladiator"
|
||||
current_hp = int(PREVIEW_MAX_HEALTH)
|
||||
max_hp = int(PREVIEW_MAX_HEALTH)
|
||||
# Combat stats come from the selected main weapon, or bare fists
|
||||
var main_weapon = preview_data.get("main")
|
||||
if main_weapon is WeaponData:
|
||||
atk_damage = main_weapon.damage
|
||||
atk_range = main_weapon.attack_range
|
||||
atk_cooldown = main_weapon.attack_cooldown
|
||||
else:
|
||||
atk_damage = PREVIEW_ATTACK_DAMAGE
|
||||
atk_range = PREVIEW_ATTACK_RANGE
|
||||
atk_cooldown = PREVIEW_ATTACK_COOLDOWN
|
||||
else:
|
||||
var player_id = player.name.to_int()
|
||||
player_name = "Player"
|
||||
if Network.players.has(player_id):
|
||||
player_name = Network.players[player_id]["nick"]
|
||||
current_hp = int(player.current_health)
|
||||
max_hp = int(player.max_health)
|
||||
atk_damage = player.attack_damage
|
||||
atk_range = player.attack_range
|
||||
atk_cooldown = player.attack_cooldown
|
||||
|
||||
_add_stat_label("Name: " + player_name)
|
||||
_add_stat_label("Level: 1") # Hardcoded for now
|
||||
@@ -93,23 +176,35 @@ func _refresh_stats():
|
||||
|
||||
# Health stats
|
||||
_add_stat_label("=== HEALTH ===", Color(0.8, 0.8, 0.8))
|
||||
_add_stat_label("Current HP: " + str(int(player.current_health)))
|
||||
_add_stat_label("Max HP: " + str(int(player.max_health)))
|
||||
_add_stat_label("Health: " + str(int(player.get_health_percent() * 100)) + "%")
|
||||
_add_stat_label("Current HP: " + str(current_hp))
|
||||
_add_stat_label("Max HP: " + str(max_hp))
|
||||
var hp_percent = int(float(current_hp) / max_hp * 100) if max_hp > 0 else 0
|
||||
_add_stat_label("Health: " + str(hp_percent) + "%")
|
||||
_add_stat_label("")
|
||||
|
||||
# Movement stats
|
||||
# Movement stats (class constants - same for live and preview)
|
||||
_add_stat_label("=== MOVEMENT ===", Color(0.8, 0.8, 0.8))
|
||||
_add_stat_label("Walk Speed: " + str(player.NORMAL_SPEED))
|
||||
_add_stat_label("Sprint Speed: " + str(player.SPRINT_SPEED))
|
||||
_add_stat_label("Jump Power: " + str(player.JUMP_VELOCITY))
|
||||
_add_stat_label("Walk Speed: " + str(Character.NORMAL_SPEED))
|
||||
_add_stat_label("Sprint Speed: " + str(Character.SPRINT_SPEED))
|
||||
_add_stat_label("Jump Power: " + str(Character.JUMP_VELOCITY))
|
||||
_add_stat_label("")
|
||||
|
||||
# Combat stats
|
||||
_add_stat_label("=== COMBAT ===", Color(0.8, 0.8, 0.8))
|
||||
_add_stat_label("Base Damage: " + str(player.attack_damage))
|
||||
_add_stat_label("Attack Range: " + str(player.attack_range))
|
||||
_add_stat_label("Attack Cooldown: " + str(player.attack_cooldown) + "s")
|
||||
if preview_mode and preview_data.get("main") is WeaponData:
|
||||
_add_stat_label("Weapon Damage: " + str(atk_damage))
|
||||
else:
|
||||
_add_stat_label("Base Damage: " + str(atk_damage))
|
||||
_add_stat_label("Attack Range: " + str(atk_range))
|
||||
_add_stat_label("Attack Cooldown: " + str(atk_cooldown) + "s")
|
||||
|
||||
# Off-hand block bonus (preview only - handy when choosing a shield)
|
||||
if preview_mode:
|
||||
var offhand = preview_data.get("offhand")
|
||||
if offhand is WeaponData and offhand.can_block:
|
||||
_add_stat_label("")
|
||||
_add_stat_label("=== DEFENSE ===", Color(0.8, 0.8, 0.8))
|
||||
_add_stat_label("Block Reduction: " + str(int(offhand.block_reduction * 100)) + "%")
|
||||
|
||||
## Refresh equipped weapons
|
||||
func _refresh_weapons():
|
||||
@@ -143,12 +238,26 @@ func _refresh_weapons():
|
||||
push_error("[CharacterSheet] Failed to load weapon_slot.tscn")
|
||||
return
|
||||
|
||||
# Resolve weapon data from preview loadout or live player
|
||||
var main_data: WeaponData = null
|
||||
var offhand_data: WeaponData = null
|
||||
if preview_mode:
|
||||
if preview_data.get("main") is WeaponData:
|
||||
main_data = preview_data["main"]
|
||||
if preview_data.get("offhand") is WeaponData:
|
||||
offhand_data = preview_data["offhand"]
|
||||
else:
|
||||
if player.equipped_weapon and player.equipped_weapon.weapon_data:
|
||||
main_data = player.equipped_weapon.weapon_data
|
||||
if player.equipped_offhand and player.equipped_offhand.weapon_data:
|
||||
offhand_data = player.equipped_offhand.weapon_data
|
||||
|
||||
# Create main hand slot
|
||||
var main_hand_slot = weapon_slot_scene.instantiate()
|
||||
slots_container.add_child(main_hand_slot)
|
||||
|
||||
if player.equipped_weapon and player.equipped_weapon.weapon_data:
|
||||
main_hand_slot.set_weapon(player.equipped_weapon.weapon_data, "Main Hand")
|
||||
if main_data:
|
||||
main_hand_slot.set_weapon(main_data, "Main Hand")
|
||||
else:
|
||||
main_hand_slot.clear_weapon()
|
||||
|
||||
@@ -156,8 +265,8 @@ func _refresh_weapons():
|
||||
var off_hand_slot = weapon_slot_scene.instantiate()
|
||||
slots_container.add_child(off_hand_slot)
|
||||
|
||||
if player.equipped_offhand and player.equipped_offhand.weapon_data:
|
||||
off_hand_slot.set_weapon(player.equipped_offhand.weapon_data, "Off-Hand")
|
||||
if offhand_data:
|
||||
off_hand_slot.set_weapon(offhand_data, "Off-Hand")
|
||||
else:
|
||||
off_hand_slot.clear_weapon()
|
||||
|
||||
@@ -182,17 +291,24 @@ func _refresh_abilities():
|
||||
spacer.custom_minimum_size = Vector2(0, 10)
|
||||
abilities_container.add_child(spacer)
|
||||
|
||||
# Dash ability
|
||||
# Dash ability (defaults in preview, live values in game)
|
||||
var dash_cd = PREVIEW_DASH_COOLDOWN if preview_mode else player.dash_cooldown
|
||||
var dash_dur = PREVIEW_DASH_DURATION if preview_mode else player.dash_duration
|
||||
var dash_mult = PREVIEW_DASH_MULTIPLIER if preview_mode else player.dash_speed_multiplier
|
||||
|
||||
_add_stat_label("--- DASH (F) ---", Color(0.8, 0.8, 0.8))
|
||||
_add_stat_label("Cooldown: " + str(player.dash_cooldown) + "s")
|
||||
_add_stat_label("Duration: " + str(player.dash_duration) + "s")
|
||||
_add_stat_label("Speed: " + str(player.dash_speed_multiplier) + "x")
|
||||
_add_stat_label("Cooldown: " + str(dash_cd) + "s")
|
||||
_add_stat_label("Duration: " + str(dash_dur) + "s")
|
||||
_add_stat_label("Speed: " + str(dash_mult) + "x")
|
||||
_add_stat_label("Description: Dash in movement direction")
|
||||
var dash_remaining = player._dash_cooldown_timer
|
||||
if dash_remaining > 0:
|
||||
_add_stat_label("Ready in: " + str(ceil(dash_remaining)) + "s", Color(1.0, 0.5, 0.5))
|
||||
else:
|
||||
if preview_mode:
|
||||
_add_stat_label("Status: READY", Color(0.0, 1.0, 0.0))
|
||||
else:
|
||||
var dash_remaining = player._dash_cooldown_timer
|
||||
if dash_remaining > 0:
|
||||
_add_stat_label("Ready in: " + str(ceil(dash_remaining)) + "s", Color(1.0, 0.5, 0.5))
|
||||
else:
|
||||
_add_stat_label("Status: READY", Color(0.0, 1.0, 0.0))
|
||||
|
||||
# Spacer
|
||||
var spacer2 = Control.new()
|
||||
@@ -201,7 +317,7 @@ func _refresh_abilities():
|
||||
|
||||
# Jump ability
|
||||
_add_stat_label("--- JUMP (Space) ---", Color(0.8, 0.8, 0.8))
|
||||
_add_stat_label("Power: " + str(player.JUMP_VELOCITY))
|
||||
_add_stat_label("Power: " + str(Character.JUMP_VELOCITY))
|
||||
_add_stat_label("Description: Jump into the air")
|
||||
_add_stat_label("Status: ALWAYS READY", Color(0.0, 1.0, 0.0))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user