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:
@@ -42,6 +42,9 @@ func _ready():
|
||||
# Add quick-fill preset buttons
|
||||
_create_preset_buttons()
|
||||
|
||||
# Reshape the menu into the gladiator outfitting screen
|
||||
_setup_outfitting_menu()
|
||||
|
||||
# Create or find weapons container
|
||||
if has_node("WeaponsContainer"):
|
||||
weapons_container = get_node("WeaponsContainer")
|
||||
@@ -89,6 +92,9 @@ func initialize_multiplayer():
|
||||
|
||||
_multiplayer_initialized = true
|
||||
|
||||
# Begin this player's arena run (starts the survival clock + gold tracking)
|
||||
GameState.start_run()
|
||||
|
||||
if multiplayer.is_server():
|
||||
print("[Level] Running server initialization")
|
||||
Network.connect("player_connected", Callable(self, "_on_player_connected"))
|
||||
@@ -377,6 +383,13 @@ func _spawn_player_local(id: int, spawn_pos: Vector3):
|
||||
else:
|
||||
push_warning("[Level] HUD autoload not found! Make sure to restart Godot to register the new autoload.")
|
||||
|
||||
# Equip the loadout chosen in the outfitting screen (broadcasts to all peers)
|
||||
var loadout = GameState.get_selected_loadout()
|
||||
if loadout.get("main", "") != "":
|
||||
player.rpc("equip_weapon_from_world", loadout["main"])
|
||||
if loadout.get("offhand", "") != "":
|
||||
player.rpc("equip_weapon_from_world", loadout["offhand"])
|
||||
|
||||
var skin_enum = player_info["skin"]
|
||||
player.set_player_skin(skin_enum)
|
||||
# rpc("sync_player_skin", id, skin_enum)
|
||||
@@ -470,6 +483,40 @@ func _on_send_pressed() -> void:
|
||||
func msg_rpc(nick, msg):
|
||||
chat.text += str(nick, " : ", msg, "\n")
|
||||
|
||||
# ---------- OUTFITTING MENU ----------
|
||||
## Turn the plain host/join menu into the gladiator camp:
|
||||
## menu options on the left, armory shop + character sheet preview on the right.
|
||||
func _setup_outfitting_menu():
|
||||
# Retitle and shrink the header so it fits the narrowed left column
|
||||
var title = menu.get_node_or_null("MainContainer/Label")
|
||||
if title:
|
||||
title.text = "SURVIVAL OF\nTHE SNIPPEST"
|
||||
title.add_theme_font_size_override("font_size", 42)
|
||||
|
||||
# Narrow the existing menu column to the left third of the screen
|
||||
var main_container = menu.get_node_or_null("MainContainer")
|
||||
if main_container:
|
||||
main_container.anchor_right = 0.34
|
||||
|
||||
# Rename buttons to fit the arena fiction
|
||||
var host_button = menu.get_node_or_null("MainContainer/MainMenu/Buttons/Host")
|
||||
if host_button:
|
||||
host_button.text = "ENTER ARENA"
|
||||
var join_button = menu.get_node_or_null("MainContainer/MainMenu/Buttons/Join")
|
||||
if join_button:
|
||||
join_button.text = "JOIN FRIEND"
|
||||
|
||||
# Armory + preview fill the rest of the screen
|
||||
var outfitting_script = load("res://level/ui/scripts/outfitting_screen.gd")
|
||||
var outfitting = outfitting_script.new()
|
||||
outfitting.name = "OutfittingScreen"
|
||||
outfitting.anchor_left = 0.35
|
||||
outfitting.anchor_top = 0.04
|
||||
outfitting.anchor_right = 0.985
|
||||
outfitting.anchor_bottom = 0.96
|
||||
menu.add_child(outfitting)
|
||||
outfitting.set_nick_input(nick_input)
|
||||
|
||||
# ---------- PRESET BUTTONS ----------
|
||||
func _create_preset_buttons():
|
||||
# Create a container for preset buttons
|
||||
@@ -795,6 +842,18 @@ func _spawn_armed_enemy_local(enemy_name: String, spawn_pos: Vector3, main_weapo
|
||||
enemies_container.add_child(enemy, true)
|
||||
print("[Peer ", multiplayer.get_unique_id(), "] Armed enemy ", enemy_name, " spawned successfully")
|
||||
|
||||
# Server credits kill gold for armed enemies spawned outside the wave spawner
|
||||
if multiplayer.is_server():
|
||||
enemy.died.connect(_on_level_enemy_died.bind(enemy))
|
||||
|
||||
## Kill-gold payout for enemies spawned directly by the level (not the wave spawner).
|
||||
## Practice dummies are deliberately NOT connected - they respawn and would be farmable.
|
||||
func _on_level_enemy_died(killer_id: int, enemy: Node):
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
if killer_id > 0 and Network.players.has(killer_id) and enemy is BaseEnemy:
|
||||
GameState.server_credit_kill(killer_id, enemy.gold_reward)
|
||||
|
||||
## Spawn initial armed enemies when server starts
|
||||
func _spawn_armed_enemies():
|
||||
if not multiplayer.is_server():
|
||||
|
||||
Reference in New Issue
Block a user