t_p1_round: Add round/match lifecycle system (RoundManager)
- New server/scripts/round/round_manager.gd with: - RoundPhase enum (WARMUP, PREP, LIVE, POST) - Round life cycle: warmup -> prep (15s) -> live (105s) -> post (8s) - Team elimination detection using entity->peer->team mappings - Time-expired round ending (default: CT wins) - Economy hooks: win money (250), loss money (900 escalating to 900), kill bonus (00) - All requested signals: round_phase_changed, round_started, round_ended, match_point, warmup_ended, round_economy_data - Updated GameServer: - Creates and wires RoundManager in _ready() - Connects DamageProcessor.player_killed -> round tracking + elimination detection - Added peer_to_entity mapping for backwards entity lookup - Added RCON command handler (_on_rcon_command) for start_match/end_round - Updated RCON command handler: - Added start_match and end_round to dispatch list and help text
This commit is contained in:
@@ -49,12 +49,21 @@ var lag_compensation: LagCompensation = null
|
||||
## DamageProcessor — applies hit results, tracks health and kills.
|
||||
var damage_processor: DamageProcessor = null
|
||||
|
||||
## TeamManager — manages team assignment and scoring.
|
||||
var team_manager: TeamManager = null
|
||||
|
||||
## RoundManager — match lifecycle (warmup → prep → live → post).
|
||||
var round_manager: RoundManager = null
|
||||
|
||||
## Current server tick counter, incremented each time tick() is called.
|
||||
var _current_tick: int = 0
|
||||
|
||||
# Map entity_id → peer_id for broadcasting damage events
|
||||
var entity_to_peer: Dictionary = {} # entity_id (int) → peer_id (int)
|
||||
|
||||
# Map peer_id → entity_id for backwards lookup
|
||||
var peer_to_entity: Dictionary = {} # peer_id (int) → entity_id (int)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -100,12 +109,36 @@ func _ready() -> void:
|
||||
damage_processor = DamageProcessor.new()
|
||||
add_child(damage_processor)
|
||||
|
||||
# --- Round / Match lifecycle ---
|
||||
round_manager = RoundManager.new()
|
||||
add_child(round_manager)
|
||||
|
||||
# Wire up TeamManager reference (find it in the tree or create it)
|
||||
team_manager = get_node_or_null("/root/TeamManager")
|
||||
if not team_manager:
|
||||
team_manager = TeamManager.new()
|
||||
add_child(team_manager)
|
||||
|
||||
round_manager.team_manager = team_manager
|
||||
round_manager.damage_processor = damage_processor
|
||||
|
||||
# --- RCON command handling ---
|
||||
var rcon_handler = get_node_or_null("/root/RconServer/RconCommandHandler")
|
||||
if rcon_handler and rcon_handler.has_signal("rcon_command"):
|
||||
rcon_handler.rcon_command.connect(_on_rcon_command)
|
||||
print("[GameServer] Connected to RCON command handler")
|
||||
else:
|
||||
print("[GameServer] RCON handler not available — commands won't be routed")
|
||||
|
||||
# Wire lag compensation into WeaponServer
|
||||
weapon_server.lag_compensation = lag_compensation
|
||||
|
||||
# Connect damage processor signals to GameServer signals for upstream relay
|
||||
damage_processor.player_killed.connect(_on_player_killed)
|
||||
|
||||
# Connect damage processor kills to RoundManager for round tracking and elimination
|
||||
damage_processor.player_killed.connect(_on_kill_for_round)
|
||||
|
||||
_current_tick = 0
|
||||
print("[GameServer] Combat subsystems ready: WeaponServer + LagCompensation + DamageProcessor")
|
||||
|
||||
@@ -159,6 +192,7 @@ func spawn_player_entity(peer_id: int, spawn_pos: Vector3) -> int:
|
||||
return -1
|
||||
|
||||
entity_to_peer[entity_id] = peer_id
|
||||
peer_to_entity[peer_id] = entity_id
|
||||
print("[GameServer] Spawned entity %d for peer %d at (%.1f, %.1f, %.1f)" % [entity_id, peer_id, spawn_pos.x, spawn_pos.y, spawn_pos.z])
|
||||
return entity_id
|
||||
|
||||
@@ -170,8 +204,10 @@ func despawn_player_entity(entity_id: int) -> void:
|
||||
if entity_id >= 0:
|
||||
simulation_server.despawn_entity(entity_id)
|
||||
|
||||
# Remove from mapping
|
||||
# Remove from mappings
|
||||
if entity_id in entity_to_peer:
|
||||
var pid: int = entity_to_peer[entity_id]
|
||||
peer_to_entity.erase(pid)
|
||||
entity_to_peer.erase(entity_id)
|
||||
|
||||
## Get the peer ID for a given entity ID.
|
||||
@@ -224,6 +260,71 @@ 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])
|
||||
|
||||
## 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:
|
||||
# Forward to RoundManager for kill tracking
|
||||
if round_manager:
|
||||
round_manager.on_player_killed(victim_id, shooter_id)
|
||||
|
||||
# Check for team elimination using our entity→peer→team mapping
|
||||
if not round_manager or not round_manager.team_manager:
|
||||
return
|
||||
|
||||
# Get teams for victim and shooter
|
||||
var victim_peer: int = entity_to_peer.get(victim_id, -1)
|
||||
var shooter_peer: int = entity_to_peer.get(shooter_id, -1)
|
||||
|
||||
if victim_peer < 0 or shooter_peer < 0:
|
||||
return
|
||||
|
||||
var victim_team: int = round_manager.team_manager.get_player_team(victim_peer)
|
||||
var shooter_team: int = round_manager.team_manager.get_player_team(shooter_peer)
|
||||
|
||||
if victim_team < 0 or shooter_team < 0 or victim_team == shooter_team:
|
||||
return
|
||||
|
||||
# Check if all players on victim's team are dead
|
||||
var all_dead: bool = true
|
||||
var team_player_ids: Array[int] = round_manager.team_manager.get_team_players(victim_team)
|
||||
|
||||
for pid in team_player_ids:
|
||||
var eid: int = peer_to_entity.get(pid, -1)
|
||||
if eid < 0:
|
||||
continue
|
||||
var health: float = damage_processor.get_health(eid) if damage_processor else -1.0
|
||||
if health > 0.0:
|
||||
all_dead = false
|
||||
break
|
||||
|
||||
if all_dead and team_player_ids.size() > 0:
|
||||
print("[GameServer] Team elimination detected! %s eliminated by %s" %
|
||||
[TeamData.get_team_name(victim_team), TeamData.get_team_name(shooter_team)])
|
||||
round_manager.end_round(shooter_team, "elimination")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# RCON command dispatch
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
## Handle RCON commands dispatched via rcon_command_handler.
|
||||
func _on_rcon_command(command: String, args: PackedStringArray) -> void:
|
||||
match command:
|
||||
"start_match":
|
||||
if round_manager:
|
||||
round_manager.start_match()
|
||||
print("[GameServer] RCON: match started")
|
||||
"end_round":
|
||||
# end_round [team_name] [reason]
|
||||
var team_name: String = args[0] if args.size() > 0 else "ct"
|
||||
var reason: String = args[1] if args.size() > 1 else "admin"
|
||||
var team: int = TeamData.from_string(team_name)
|
||||
if team == TeamData.Team.SPECTATOR:
|
||||
# Default to CT
|
||||
team = TeamData.Team.COUNTER_TERRORIST
|
||||
if round_manager:
|
||||
round_manager.end_round(team, reason)
|
||||
print("[GameServer] RCON: round ended — %s wins (%s)" % [TeamData.get_team_name(team), reason])
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main loop (128 Hz)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user