t_p2_buy: add buy menu and economy system

Creates the full buy/economy subsystem for a tactical FPS:

- EconomyManager (server/scripts/economy/economy_manager.gd): per-player
  money tracking with starting 00, kill/round/objective rewards, spending
  validation, escalating loss bonuses, and money_changed signal.

- BuyMenuHandler (server/scripts/economy/buy_menu_handler.gd): server-side
  RPC validation of purchase requests — checks weapon validity, buy-zone
  membership (scans BuyZone nodes in scene tree), affordability, then
  deducts money and grants weapon via WeaponServer.give_weapon().

- BuyMenu (client/characters/weapon/buy_menu.gd): client-side Control UI
  with styled panel, money display, weapon grid (name/cost/stats), numeric
  key (1-4) and mouse-click purchase, server-driven affordability, and
  auto-close after purchase.

- GameServer integration: creates EconomyManager + BuyMenuHandler in
  _ready(), registers players for economy on spawn, awards kill rewards
  via DamageProcessor.player_killed signal, and unregisters on despawn.

Economy rules: win=+,250, loss=,900 (+00/streak, cap ,900),
kill=+00, bomb_plant/defuse=+00. Weapon costs: pistol=00,
smg=,200, rifle=,700, shotgun=,900.
This commit is contained in:
2026-07-01 20:31:25 -04:00
parent f20d532add
commit 6a08487c4c
4 changed files with 928 additions and 0 deletions
+38
View File
@@ -55,6 +55,12 @@ var team_manager: TeamManager = null
## RoundManager — match lifecycle (warmup → prep → live → post).
var round_manager: RoundManager = null
## EconomyManager — per-player money tracking and earnings.
var economy_manager: EconomyManager = null
## BuyMenuHandler — server-side buy request validation and processing.
var buy_menu_handler: BuyMenuHandler = null
## Current server tick counter, incremented each time tick() is called.
var _current_tick: int = 0
@@ -122,6 +128,18 @@ func _ready() -> void:
round_manager.team_manager = team_manager
round_manager.damage_processor = damage_processor
# --- Economy system ---
economy_manager = EconomyManager.new()
add_child(economy_manager)
buy_menu_handler = BuyMenuHandler.new()
add_child(buy_menu_handler)
buy_menu_handler.initialise(economy_manager, weapon_server, team_manager)
# Wire kill rewards: when DamageProcessor detects a kill, award money to the shooter
# Note: shooter_id is an entity_id; we map it to peer_id for economy lookup.
damage_processor.player_killed.connect(_on_kill_for_economy)
# --- RCON command handling ---
var rcon_handler = get_node_or_null("/root/RconServer/RconCommandHandler")
if rcon_handler and rcon_handler.has_signal("rcon_command"):
@@ -241,6 +259,11 @@ func register_player_node(entity_id: int, node: Node3D, max_health: float = 100.
lag_compensation.register_player_node(entity_id, node)
if damage_processor:
damage_processor.register_player(entity_id, max_health)
# Register peer ID for economy tracking
var peer_id: int = entity_to_peer.get(entity_id, -1)
if peer_id >= 0 and economy_manager:
economy_manager.register_player(peer_id)
print("[GameServer] Registered player peer=%d with economy manager" % peer_id)
print("[GameServer] Registered player node entity=%d with lag compensation + damage processor" % entity_id)
## Unregister a player node from LagCompensation and DamageProcessor.
@@ -249,6 +272,10 @@ func unregister_player_node(entity_id: int) -> void:
lag_compensation.unregister_player_node(entity_id)
if damage_processor:
damage_processor.unregister_player(entity_id)
# Unregister from economy tracking
var peer_id: int = entity_to_peer.get(entity_id, -1)
if peer_id >= 0 and economy_manager:
economy_manager.unregister_player(peer_id)
print("[GameServer] Unregistered player node entity=%d" % entity_id)
# ---------------------------------------------------------------------------
@@ -260,6 +287,17 @@ func _on_player_killed(victim_id: int, shooter_id: int) -> void:
player_damaged.emit(victim_id, shooter_id, 0.0, true)
print("[GameServer] Kill: victim_entity=%d, shooter_entity=%d (peer=%d)" % [victim_id, shooter_id, shooter_peer])
## Economy kill reward — award money to the shooter when they get a kill.
## Connected from DamageProcessor.player_killed in _ready().
func _on_kill_for_economy(victim_id: int, shooter_id: int) -> void:
if economy_manager == null:
return
# Map entity_id → peer_id for economy lookup
var shooter_peer: int = entity_to_peer.get(shooter_id, -1)
if shooter_peer < 0:
return
economy_manager.award_kill_reward(shooter_peer)
## Round-level kill handler — tracks kills for round stats and checks elimination.
## Connected from DamageProcessor.player_killed.
func _on_kill_for_round(victim_id: int, shooter_id: int) -> void: