Fix headless class_name dependencies

- Replaced TeamData.Team type hints with int in all scripts
- Added explicit preloads for headless mode class resolution
- Created stub LagCompensation and DamageProcessor scripts
- Fixed PluginManager, SpawnManager, EconomyManager, BuyZone,
  RoundManager, BuyMenuHandler, BombObjective class_name references
- Updated server config to match ServerConfig.gd format

Gray screen root cause: server scripts failed to parse in headless
mode due to Godot 4 class_name loading order (Resource after Node),
leaving server_main.gd non-functional — accepted connections but
never spawned players.
This commit is contained in:
2026-07-02 10:09:28 -04:00
parent ad48f38ca5
commit 4a5264c5b0
14 changed files with 202 additions and 284 deletions
+19 -16
View File
@@ -20,6 +20,9 @@
extends Node
class_name SpawnManager
# Preload for headless compat
const _td_ref = preload("res://scripts/teams/team_data.gd")
# ---------------------------------------------------------------------------
# Exports
# ---------------------------------------------------------------------------
@@ -82,7 +85,7 @@ func refresh_spawn_points() -> void:
## Get all spawn point markers for a given team.
func get_spawn_points_for_team(team: TeamData.Team) -> Array[Marker3D]:
func get_spawn_points_for_team(team: int) -> Array[Marker3D]:
var group_name: String = _group_for_team(team)
return _spawn_points.get(group_name, []).duplicate()
@@ -96,10 +99,10 @@ func get_spawn_points_for_team(team: TeamData.Team) -> Array[Marker3D]:
## 4. If no spawns exist at all, return Vector3.ZERO.
##
## Returns the global position of the chosen spawn point.
func select_spawn(player_id: int, team: TeamData.Team) -> Vector3:
func select_spawn(player_id: int, team: int) -> Vector3:
var markers: Array[Marker3D] = get_spawn_points_for_team(team)
if markers.is_empty():
push_warning("[SpawnManager] No spawn points found for team %s" % TeamData.get_team_name(team))
push_warning("[SpawnManager] No spawn points found for team %s" % _td_ref.get_team_name(team))
return Vector3.ZERO
# Separate into valid and contested spawns
@@ -141,7 +144,7 @@ func select_spawn(player_id: int, team: TeamData.Team) -> Vector3:
## Return the number of spawn points for a team.
func get_spawn_count(team: TeamData.Team) -> int:
func get_spawn_count(team: int) -> int:
var group_name: String = _group_for_team(team)
return _spawn_points.get(group_name, []).size()
@@ -151,12 +154,12 @@ func get_spawn_count(team: TeamData.Team) -> int:
# ---------------------------------------------------------------------------
## Check if a position is safe from enemy proximity.
func _is_spawn_safe(pos: Vector3, friendly_team: TeamData.Team) -> bool:
func _is_spawn_safe(pos: Vector3, friendly_team: int) -> bool:
if team_manager == null:
return true
# Get the enemy team(s)
var enemy_teams: Array[TeamData.Team] = _get_enemy_teams(friendly_team)
var enemy_teams: Array[int] = _get_enemy_teams(friendly_team)
for enemy_team in enemy_teams:
var enemy_ids: Array[int] = team_manager.get_team_players(enemy_team)
@@ -172,7 +175,7 @@ func _is_spawn_safe(pos: Vector3, friendly_team: TeamData.Team) -> bool:
## Pick the spawn farthest from all known enemy positions.
func _pick_farthest_spawn(candidates: Array[Marker3D], friendly_team: TeamData.Team) -> Marker3D:
func _pick_farthest_spawn(candidates: Array[Marker3D], friendly_team: int) -> Marker3D:
if candidates.is_empty():
return null
if candidates.size() == 1:
@@ -181,7 +184,7 @@ func _pick_farthest_spawn(candidates: Array[Marker3D], friendly_team: TeamData.T
# Collect enemy positions
var enemy_positions: Array[Vector3] = []
if team_manager != null:
var enemy_teams: Array[TeamData.Team] = _get_enemy_teams(friendly_team)
var enemy_teams: Array[int] = _get_enemy_teams(friendly_team)
for enemy_team in enemy_teams:
var enemy_ids: Array[int] = team_manager.get_team_players(enemy_team)
for enemy_id in enemy_ids:
@@ -213,22 +216,22 @@ func _pick_farthest_spawn(candidates: Array[Marker3D], friendly_team: TeamData.T
## Get the teams that are enemies of the given team.
## For now, CT and T are enemies of each other; SPECTATOR has no enemies.
func _get_enemy_teams(team: TeamData.Team) -> Array[TeamData.Team]:
func _get_enemy_teams(team: int) -> Array[int]:
match team:
TeamData.Team.COUNTER_TERRORIST:
return [TeamData.Team.TERRORIST]
TeamData.Team.TERRORIST:
return [TeamData.Team.COUNTER_TERRORIST]
_td_ref.Team.COUNTER_TERRORIST:
return [_td_ref.Team.TERRORIST]
_td_ref.Team.TERRORIST:
return [_td_ref.Team.COUNTER_TERRORIST]
_:
return []
## Translate a Team enum to the spawn group name.
func _group_for_team(team: TeamData.Team) -> String:
func _group_for_team(team: int) -> String:
match team:
TeamData.Team.COUNTER_TERRORIST:
_td_ref.Team.COUNTER_TERRORIST:
return ct_spawn_group
TeamData.Team.TERRORIST:
_td_ref.Team.TERRORIST:
return t_spawn_group
_:
return ""
+25 -17
View File
@@ -20,14 +20,19 @@
extends Node
class_name TeamManager
# Force TeamData to be loaded first (Godot 4 class_name loading order: Resources after Nodes)
const _team_data_ref = preload("res://scripts/teams/team_data.gd")
# ---------------------------------------------------------------------------
# Signals
# ---------------------------------------------------------------------------
## Emitted when a player is assigned to a team (including on team switch).
## team is an int (TeamData.Team enum value).
signal player_joined_team(player_id: int, team: int)
## Emitted when a player leaves a team (disconnected or switched).
## team is an int (TeamData.Team enum value).
signal player_left_team(player_id: int, team: int)
## Emitted when scores for any team change.
@@ -56,8 +61,9 @@ var max_players_per_team: int = 0
# ---------------------------------------------------------------------------
func _ready() -> void:
if ServerConfig and ServerConfig.has_method(&\"get_config_path\"):
max_players_per_team = ServerConfig.players_per_team
var cfg = get_node_or_null("/root/ServerConfig")
if cfg and cfg.has_method(&"get_config_path"):
max_players_per_team = cfg.players_per_team
else:
max_players_per_team = 8
@@ -68,7 +74,7 @@ func _ready() -> void:
## Assign a player to a team. Returns true if successful, false if the team is full.
## Emits player_left_team for the old team (if any) and player_joined_team for the new team.
func assign_player(player_id: int, team: TeamData.Team) -> bool:
func assign_player(player_id: int, team: int) -> bool:
# Validate team
if team < 0 or team > TeamData.Team.size() - 1:
push_warning("[TeamManager] Invalid team value: %d" % team)
@@ -84,7 +90,7 @@ func assign_player(player_id: int, team: TeamData.Team) -> bool:
return false
# Remove from old team if assigned
var old_team: TeamData.Team = _player_teams.get(player_id, TeamData.Team.SPECTATOR)
var old_team: int = _player_teams.get(player_id, TeamData.Team.SPECTATOR)
if old_team != team:
_remove_from_team(player_id, old_team)
@@ -101,7 +107,7 @@ func assign_player(player_id: int, team: TeamData.Team) -> bool:
## Remove a player from any team (equivalent to assigning to SPECTATOR).
func remove_player(player_id: int) -> void:
var current_team: TeamData.Team = _player_teams.get(player_id, TeamData.Team.SPECTATOR)
var current_team: int = _player_teams.get(player_id, TeamData.Team.SPECTATOR)
_remove_from_team(player_id, current_team)
@@ -116,22 +122,23 @@ func unregister_player(player_id: int) -> void:
# ---------------------------------------------------------------------------
## Get the team a player belongs to. Returns SPECTATOR if unassigned.
func get_player_team(player_id: int) -> TeamData.Team:
## Returns an int (TeamData.Team enum value).
func get_player_team(player_id: int) -> int:
return _player_teams.get(player_id, TeamData.Team.SPECTATOR)
## Get an array of all player IDs on a given team.
func get_team_players(team: TeamData.Team) -> Array[int]:
func get_team_players(team: int) -> Array[int]:
return _team_players.get(team, []).duplicate()
## Get the total number of players on a given team.
func get_team_player_count(team: TeamData.Team) -> int:
func get_team_player_count(team: int) -> int:
return _team_players.get(team, []).size()
## Check if a player is on a specific team.
func is_player_on_team(player_id: int, team: TeamData.Team) -> bool:
func is_player_on_team(player_id: int, team: int) -> bool:
return _player_teams.get(player_id, TeamData.Team.SPECTATOR) == team
@@ -145,8 +152,9 @@ func get_all_assignments() -> Dictionary:
# ---------------------------------------------------------------------------
## Assign a player to the team with the fewest members (ties: random).
## Returns the team the player was assigned to, or SPECTATOR if both teams are full.
func assign_to_team_with_fewest(player_id: int) -> TeamData.Team:
## Returns the team as an int (TeamData.Team enum value) the player was assigned to,
## or SPECTATOR if both teams are full.
func assign_to_team_with_fewest(player_id: int) -> int:
var ct_count: int = _team_players.get(TeamData.Team.COUNTER_TERRORIST, []).size()
var t_count: int = _team_players.get(TeamData.Team.TERRORIST, []).size()
@@ -163,7 +171,7 @@ func assign_to_team_with_fewest(player_id: int) -> TeamData.Team:
return TeamData.Team.SPECTATOR
# Assign to team with fewer players (ties: randomly pick one)
var team: TeamData.Team
var team: int = TeamData.Team.COUNTER_TERRORIST
if ct_count < t_count:
team = TeamData.Team.COUNTER_TERRORIST
elif t_count < ct_count:
@@ -181,20 +189,20 @@ func assign_to_team_with_fewest(player_id: int) -> TeamData.Team:
# ---------------------------------------------------------------------------
## Set the score for a given team.
func set_team_score(team: TeamData.Team, score: int) -> void:
func set_team_score(team: int, score: int) -> void:
_team_scores[team] = score
team_scores_updated.emit(_team_scores.duplicate())
## Add to a team's current score.
func add_team_score(team: TeamData.Team, amount: int = 1) -> void:
func add_team_score(team: int, amount: int = 1) -> void:
var current: int = _team_scores.get(team, 0)
_team_scores[team] = current + amount
team_scores_updated.emit(_team_scores.duplicate())
## Get the score for a given team.
func get_team_score(team: TeamData.Team) -> int:
func get_team_score(team: int) -> int:
return _team_scores.get(team, 0)
@@ -218,7 +226,7 @@ func reset_scores() -> void:
func reset_all() -> void:
# Emit leave signals for all assigned players
for player_id in _player_teams.keys():
var team: TeamData.Team = _player_teams[player_id]
var team: int = _player_teams[player_id]
player_left_team.emit(player_id, team)
_player_teams.clear()
@@ -230,7 +238,7 @@ func reset_all() -> void:
# Internal
# ---------------------------------------------------------------------------
func _remove_from_team(player_id: int, team: TeamData.Team) -> void:
func _remove_from_team(player_id: int, team: int) -> void:
if _player_teams.has(player_id):
_player_teams.erase(player_id)