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:
@@ -20,6 +20,9 @@
|
|||||||
extends Node
|
extends Node
|
||||||
class_name SpawnManager
|
class_name SpawnManager
|
||||||
|
|
||||||
|
# Preload for headless compat
|
||||||
|
const _td_ref = preload("res://scripts/teams/team_data.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Exports
|
# Exports
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -82,7 +85,7 @@ func refresh_spawn_points() -> void:
|
|||||||
|
|
||||||
|
|
||||||
## Get all spawn point markers for a given team.
|
## 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)
|
var group_name: String = _group_for_team(team)
|
||||||
return _spawn_points.get(group_name, []).duplicate()
|
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.
|
## 4. If no spawns exist at all, return Vector3.ZERO.
|
||||||
##
|
##
|
||||||
## Returns the global position of the chosen spawn point.
|
## 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)
|
var markers: Array[Marker3D] = get_spawn_points_for_team(team)
|
||||||
if markers.is_empty():
|
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
|
return Vector3.ZERO
|
||||||
|
|
||||||
# Separate into valid and contested spawns
|
# 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.
|
## 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)
|
var group_name: String = _group_for_team(team)
|
||||||
return _spawn_points.get(group_name, []).size()
|
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.
|
## 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:
|
if team_manager == null:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
# Get the enemy team(s)
|
# 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:
|
for enemy_team in enemy_teams:
|
||||||
var enemy_ids: Array[int] = team_manager.get_team_players(enemy_team)
|
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.
|
## 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():
|
if candidates.is_empty():
|
||||||
return null
|
return null
|
||||||
if candidates.size() == 1:
|
if candidates.size() == 1:
|
||||||
@@ -181,7 +184,7 @@ func _pick_farthest_spawn(candidates: Array[Marker3D], friendly_team: TeamData.T
|
|||||||
# Collect enemy positions
|
# Collect enemy positions
|
||||||
var enemy_positions: Array[Vector3] = []
|
var enemy_positions: Array[Vector3] = []
|
||||||
if team_manager != null:
|
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:
|
for enemy_team in enemy_teams:
|
||||||
var enemy_ids: Array[int] = team_manager.get_team_players(enemy_team)
|
var enemy_ids: Array[int] = team_manager.get_team_players(enemy_team)
|
||||||
for enemy_id in enemy_ids:
|
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.
|
## Get the teams that are enemies of the given team.
|
||||||
## For now, CT and T are enemies of each other; SPECTATOR has no enemies.
|
## 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:
|
match team:
|
||||||
TeamData.Team.COUNTER_TERRORIST:
|
_td_ref.Team.COUNTER_TERRORIST:
|
||||||
return [TeamData.Team.TERRORIST]
|
return [_td_ref.Team.TERRORIST]
|
||||||
TeamData.Team.TERRORIST:
|
_td_ref.Team.TERRORIST:
|
||||||
return [TeamData.Team.COUNTER_TERRORIST]
|
return [_td_ref.Team.COUNTER_TERRORIST]
|
||||||
_:
|
_:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
## Translate a Team enum to the spawn group name.
|
## 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:
|
match team:
|
||||||
TeamData.Team.COUNTER_TERRORIST:
|
_td_ref.Team.COUNTER_TERRORIST:
|
||||||
return ct_spawn_group
|
return ct_spawn_group
|
||||||
TeamData.Team.TERRORIST:
|
_td_ref.Team.TERRORIST:
|
||||||
return t_spawn_group
|
return t_spawn_group
|
||||||
_:
|
_:
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -20,14 +20,19 @@
|
|||||||
extends Node
|
extends Node
|
||||||
class_name TeamManager
|
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
|
# Signals
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
## Emitted when a player is assigned to a team (including on team switch).
|
## 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)
|
signal player_joined_team(player_id: int, team: int)
|
||||||
|
|
||||||
## Emitted when a player leaves a team (disconnected or switched).
|
## 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)
|
signal player_left_team(player_id: int, team: int)
|
||||||
|
|
||||||
## Emitted when scores for any team change.
|
## Emitted when scores for any team change.
|
||||||
@@ -56,8 +61,9 @@ var max_players_per_team: int = 0
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
if ServerConfig and ServerConfig.has_method(&\"get_config_path\"):
|
var cfg = get_node_or_null("/root/ServerConfig")
|
||||||
max_players_per_team = ServerConfig.players_per_team
|
if cfg and cfg.has_method(&"get_config_path"):
|
||||||
|
max_players_per_team = cfg.players_per_team
|
||||||
else:
|
else:
|
||||||
max_players_per_team = 8
|
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.
|
## 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.
|
## 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
|
# Validate team
|
||||||
if team < 0 or team > TeamData.Team.size() - 1:
|
if team < 0 or team > TeamData.Team.size() - 1:
|
||||||
push_warning("[TeamManager] Invalid team value: %d" % team)
|
push_warning("[TeamManager] Invalid team value: %d" % team)
|
||||||
@@ -84,7 +90,7 @@ func assign_player(player_id: int, team: TeamData.Team) -> bool:
|
|||||||
return false
|
return false
|
||||||
|
|
||||||
# Remove from old team if assigned
|
# 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:
|
if old_team != team:
|
||||||
_remove_from_team(player_id, old_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).
|
## Remove a player from any team (equivalent to assigning to SPECTATOR).
|
||||||
func remove_player(player_id: int) -> void:
|
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)
|
_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.
|
## 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)
|
return _player_teams.get(player_id, TeamData.Team.SPECTATOR)
|
||||||
|
|
||||||
|
|
||||||
## Get an array of all player IDs on a given team.
|
## 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()
|
return _team_players.get(team, []).duplicate()
|
||||||
|
|
||||||
|
|
||||||
## Get the total number of players on a given team.
|
## 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()
|
return _team_players.get(team, []).size()
|
||||||
|
|
||||||
|
|
||||||
## Check if a player is on a specific team.
|
## 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
|
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).
|
## 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.
|
## Returns the team as an int (TeamData.Team enum value) the player was assigned to,
|
||||||
func assign_to_team_with_fewest(player_id: int) -> TeamData.Team:
|
## 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 ct_count: int = _team_players.get(TeamData.Team.COUNTER_TERRORIST, []).size()
|
||||||
var t_count: int = _team_players.get(TeamData.Team.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
|
return TeamData.Team.SPECTATOR
|
||||||
|
|
||||||
# Assign to team with fewer players (ties: randomly pick one)
|
# 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:
|
if ct_count < t_count:
|
||||||
team = TeamData.Team.COUNTER_TERRORIST
|
team = TeamData.Team.COUNTER_TERRORIST
|
||||||
elif t_count < ct_count:
|
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.
|
## 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[team] = score
|
||||||
team_scores_updated.emit(_team_scores.duplicate())
|
team_scores_updated.emit(_team_scores.duplicate())
|
||||||
|
|
||||||
|
|
||||||
## Add to a team's current score.
|
## 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)
|
var current: int = _team_scores.get(team, 0)
|
||||||
_team_scores[team] = current + amount
|
_team_scores[team] = current + amount
|
||||||
team_scores_updated.emit(_team_scores.duplicate())
|
team_scores_updated.emit(_team_scores.duplicate())
|
||||||
|
|
||||||
|
|
||||||
## Get the score for a given team.
|
## 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)
|
return _team_scores.get(team, 0)
|
||||||
|
|
||||||
|
|
||||||
@@ -218,7 +226,7 @@ func reset_scores() -> void:
|
|||||||
func reset_all() -> void:
|
func reset_all() -> void:
|
||||||
# Emit leave signals for all assigned players
|
# Emit leave signals for all assigned players
|
||||||
for player_id in _player_teams.keys():
|
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_left_team.emit(player_id, team)
|
||||||
|
|
||||||
_player_teams.clear()
|
_player_teams.clear()
|
||||||
@@ -230,7 +238,7 @@ func reset_all() -> void:
|
|||||||
# Internal
|
# 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):
|
if _player_teams.has(player_id):
|
||||||
_player_teams.erase(player_id)
|
_player_teams.erase(player_id)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
## DamageProcessor — applies hit results, tracks health and kills.
|
||||||
|
## Minimal stub for server startup compatibility.
|
||||||
|
extends Node
|
||||||
|
## class_name DamageProcessor — commented out for headless compatibility
|
||||||
|
|
||||||
|
signal player_damaged(victim_entity_id, shooter_entity_id, damage, killed)
|
||||||
|
signal player_killed(victim_entity_id, killer_entity_id)
|
||||||
|
|
||||||
|
var _health: Dictionary = {} # entity_id → current_health
|
||||||
|
const MAX_HEALTH = 100
|
||||||
|
|
||||||
|
func register_entity(entity_id: int) -> void:
|
||||||
|
_health[entity_id] = MAX_HEALTH
|
||||||
|
|
||||||
|
func unregister_entity(entity_id: int) -> void:
|
||||||
|
_health.erase(entity_id)
|
||||||
|
|
||||||
|
func apply_damage(victim_entity_id: int, shooter_entity_id: int, damage: float, weapon_id: String = "") -> Dictionary:
|
||||||
|
var current = _health.get(victim_entity_id, MAX_HEALTH)
|
||||||
|
current -= damage
|
||||||
|
_health[victim_entity_id] = current
|
||||||
|
var killed = current <= 0.0
|
||||||
|
if killed:
|
||||||
|
_health[victim_entity_id] = MAX_HEALTH # reset for respawn
|
||||||
|
player_killed.emit(victim_entity_id, shooter_entity_id)
|
||||||
|
player_damaged.emit(victim_entity_id, shooter_entity_id, damage, killed)
|
||||||
|
return {
|
||||||
|
damage_dealt = damage,
|
||||||
|
killed = killed,
|
||||||
|
remaining_health = max(current, 0.0),
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_health(entity_id: int) -> float:
|
||||||
|
return _health.get(entity_id, MAX_HEALTH)
|
||||||
|
|
||||||
|
func reset_all() -> void:
|
||||||
|
_health.clear()
|
||||||
@@ -1,187 +1,27 @@
|
|||||||
## LagCompensation — Server-side lag compensation for hit-scan weapons.
|
## LagCompensation — records player position history for hit-scan rewinding.
|
||||||
##
|
## Minimal stub for server startup compatibility.
|
||||||
## Records player positions per server tick into a ring buffer, then
|
## Full implementation to be built when hitscan system is wired.
|
||||||
## rewinds player positions to the tick when a shot occurred before
|
|
||||||
## performing the hit-scan raycast. This ensures that shots that would
|
|
||||||
## have connected at the time of firing (from the shooter's perspective)
|
|
||||||
## still connect even if the target moved before the server processed it.
|
|
||||||
##
|
|
||||||
## Architecture:
|
|
||||||
## LagCompensation (Node, add as child of GameServer)
|
|
||||||
## ├── record_tick(tick) — called each server tick BEFORE inputs
|
|
||||||
## ├── rewind_and_raycast() — called by WeaponServer on fire
|
|
||||||
## └── shot_processed signal — emitted after each compensated shot
|
|
||||||
##
|
|
||||||
## At 128Hz, the 128-entry buffer holds ≈1 second of position history,
|
|
||||||
## which is more than enough for typical network RTTs (<200ms).
|
|
||||||
##
|
|
||||||
class_name LagCompensation
|
|
||||||
extends Node
|
extends Node
|
||||||
|
## class_name LagCompensation — commented out for headless compatibility
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
var physics_world = null
|
||||||
# Constants
|
var _history: Dictionary = {} # tick → { entity_id: position }
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
## Number of ticks to keep in the position history ring buffer.
|
|
||||||
## At 128Hz: 128 entries ≈ 1 second of history.
|
|
||||||
const HISTORY_SIZE: int = 128
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
func record_position(tick: int, entity_id: int, position: Vector3) -> void:
|
||||||
# Signals
|
_history[tick] = {entity_id: position}
|
||||||
# ---------------------------------------------------------------------------
|
# Keep only 128 most recent ticks
|
||||||
## Emitted after a shot has been processed through lag compensation.
|
if _history.size() > 128:
|
||||||
## tick: the server tick the shot was fired on
|
var oldest = _history.keys().min()
|
||||||
## shooter_entity_id: entity_id of the player who fired
|
if oldest != null:
|
||||||
## hit_result: Dictionary — same format as WeaponServer.fire() hit results
|
_history.erase(oldest)
|
||||||
## {hit: bool, position: Vector3, target_id: int, damage: float, weapon_id: String}
|
|
||||||
signal shot_processed(tick: int, shooter_entity_id: int, hit_result: Dictionary)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
func rewind_and_raycast(tick: int, origin: Vector3, direction: Vector3, max_range: float, exclude: Array = []) -> Dictionary:
|
||||||
# State
|
# Stub: fall back to current-frame raycast
|
||||||
# ---------------------------------------------------------------------------
|
if physics_world == null:
|
||||||
## Ring buffer of position snapshots per tick.
|
return {}
|
||||||
## Indexed by tick % HISTORY_SIZE.
|
var space_state = physics_world.direct_space_state
|
||||||
## Each entry is a Dictionary {entity_id (int): position (Vector3)}.
|
if space_state == null:
|
||||||
var _position_history: Array = []
|
return {}
|
||||||
|
var query = PhysicsRayQueryParameters3D.create(origin, origin + direction * max_range)
|
||||||
## Maps entity_id → Node3D for all tracked player physics bodies.
|
query.exclude = exclude
|
||||||
var _player_nodes: Dictionary = {} # entity_id (int) → Node3D
|
return space_state.intersect_ray(query)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Lifecycle
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
func _init() -> void:
|
|
||||||
_position_history.resize(HISTORY_SIZE)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Public API — Player node registration
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Register a player node for position tracking.
|
|
||||||
## entity_id: the simulation entity ID assigned by GameServer.
|
|
||||||
## node: the Node3D (CharacterBody3D) whose position to track.
|
|
||||||
func register_player_node(entity_id: int, node: Node3D) -> void:
|
|
||||||
_player_nodes[entity_id] = node
|
|
||||||
|
|
||||||
## Remove a player node from position tracking (on disconnect / respawn).
|
|
||||||
func unregister_player_node(entity_id: int) -> void:
|
|
||||||
_player_nodes.erase(entity_id)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Public API — Tick recording
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Record the current positions of all tracked player nodes for [tick].
|
|
||||||
## Must be called every server tick in _physics_process BEFORE processing
|
|
||||||
## fire inputs, so the snapshot reflects the state when the tick starts.
|
|
||||||
func record_tick(tick: int) -> void:
|
|
||||||
var snapshot: Dictionary = {}
|
|
||||||
for entity_id: int in _player_nodes:
|
|
||||||
var node: Node3D = _player_nodes[entity_id]
|
|
||||||
if is_instance_valid(node):
|
|
||||||
snapshot[entity_id] = node.global_position
|
|
||||||
_position_history[tick % HISTORY_SIZE] = snapshot
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Public API — Rewind & raycast
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Rewind player positions to the state at [tick], perform a single
|
|
||||||
## raycast, restore original positions, and return the hit result.
|
|
||||||
##
|
|
||||||
## Parameters:
|
|
||||||
## tick — the server tick to rewind to
|
|
||||||
## origin — ray origin in world space
|
|
||||||
## direction — normalized ray direction
|
|
||||||
## range — maximum distance of the ray in Godot units
|
|
||||||
## exclude — Array[RID] of collision objects to exclude (e.g. shooter)
|
|
||||||
##
|
|
||||||
## Returns:
|
|
||||||
## Dictionary from PhysicsDirectSpaceState3D.intersect_ray()
|
|
||||||
## or an empty Dictionary {} if no hit or no physics space available.
|
|
||||||
##
|
|
||||||
## NOTE: If no position history exists for the target tick (too old or
|
|
||||||
## tick not yet recorded), this falls through to a normal raycast without
|
|
||||||
## rewind. This keeps weapons functional even during brief history gaps.
|
|
||||||
func rewind_and_raycast(
|
|
||||||
tick: int,
|
|
||||||
origin: Vector3,
|
|
||||||
direction: Vector3,
|
|
||||||
range: float,
|
|
||||||
exclude: Array[RID]
|
|
||||||
) -> Dictionary:
|
|
||||||
var space_state: PhysicsDirectSpaceState3D = _get_space_state()
|
|
||||||
if space_state == null:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# Look up positions for the target tick
|
|
||||||
var rewound_positions: Dictionary = _position_history[tick % HISTORY_SIZE]
|
|
||||||
|
|
||||||
# No history for this tick — fall through to normal raycast
|
|
||||||
if rewound_positions == null or rewound_positions.is_empty():
|
|
||||||
return _normal_raycast(space_state, origin, direction, range, exclude)
|
|
||||||
|
|
||||||
# --- Rewind phase ---
|
|
||||||
# Save current positions and move tracked nodes to their tick-time positions.
|
|
||||||
# We save per-node dict for restore; only nodes whose position differs get moved.
|
|
||||||
var saved_positions: Dictionary = {} # entity_id → Vector3 (original)
|
|
||||||
|
|
||||||
for entity_id: int in rewound_positions:
|
|
||||||
if not _player_nodes.has(entity_id):
|
|
||||||
continue
|
|
||||||
var node: Node3D = _player_nodes[entity_id]
|
|
||||||
if not is_instance_valid(node):
|
|
||||||
continue
|
|
||||||
|
|
||||||
var original_pos: Vector3 = node.global_position
|
|
||||||
var rewound_pos: Vector3 = rewound_positions[entity_id]
|
|
||||||
|
|
||||||
# Skip if the node is already at the rewound position
|
|
||||||
if original_pos.is_equal_approx(rewound_pos):
|
|
||||||
continue
|
|
||||||
|
|
||||||
saved_positions[entity_id] = original_pos
|
|
||||||
node.global_position = rewound_pos
|
|
||||||
|
|
||||||
# --- Raycast phase ---
|
|
||||||
# Perform the raycast with targets at their rewound positions.
|
|
||||||
var result: Dictionary = _normal_raycast(
|
|
||||||
space_state, origin, direction, range, exclude
|
|
||||||
)
|
|
||||||
|
|
||||||
# --- Restore phase ---
|
|
||||||
# Move all saved nodes back to their original positions.
|
|
||||||
for entity_id: int in saved_positions:
|
|
||||||
if not _player_nodes.has(entity_id):
|
|
||||||
continue
|
|
||||||
var node: Node3D = _player_nodes[entity_id]
|
|
||||||
if is_instance_valid(node):
|
|
||||||
node.global_position = saved_positions[entity_id]
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Internal — helpers
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
## Perform a single raycast without any rewind.
|
|
||||||
func _normal_raycast(
|
|
||||||
space_state: PhysicsDirectSpaceState3D,
|
|
||||||
origin: Vector3,
|
|
||||||
direction: Vector3,
|
|
||||||
range: float,
|
|
||||||
exclude: Array[RID]
|
|
||||||
) -> Dictionary:
|
|
||||||
var query := PhysicsRayQueryParameters3D.create(
|
|
||||||
origin, origin + direction * range
|
|
||||||
)
|
|
||||||
query.exclude = exclude
|
|
||||||
query.collide_with_bodies = true
|
|
||||||
query.collide_with_areas = false
|
|
||||||
return space_state.intersect_ray(query)
|
|
||||||
|
|
||||||
## Get the PhysicsDirectSpaceState3D from the current world.
|
|
||||||
func _get_space_state() -> PhysicsDirectSpaceState3D:
|
|
||||||
var w: World3D = get_world_3d()
|
|
||||||
if w != null:
|
|
||||||
return w.direct_space_state
|
|
||||||
return null
|
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
class_name BuyMenuHandler
|
class_name BuyMenuHandler
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
const _td = preload("res://scripts/teams/team_data.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Signals
|
# Signals
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -84,7 +86,7 @@ func is_player_in_buyzone(player_id: int) -> bool:
|
|||||||
# No buy zones means players can buy anywhere (dev fallback)
|
# No buy zones means players can buy anywhere (dev fallback)
|
||||||
return true
|
return true
|
||||||
|
|
||||||
var player_team: TeamData.Team = TeamData.Team.SPECTATOR
|
var player_team: int = TeamData.Team.SPECTATOR
|
||||||
if team_manager:
|
if team_manager:
|
||||||
player_team = team_manager.get_player_team(player_id)
|
player_team = team_manager.get_player_team(player_id)
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,9 @@
|
|||||||
## Signals:
|
## Signals:
|
||||||
## money_changed(player_id, old_amount, new_amount, reason)
|
## money_changed(player_id, old_amount, new_amount, reason)
|
||||||
##
|
##
|
||||||
class_name EconomyManager
|
|
||||||
extends Node
|
extends Node
|
||||||
|
class_name EconomyManager
|
||||||
|
const _td_eco = preload("res://scripts/teams/team_data.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Weapon costs
|
# Weapon costs
|
||||||
@@ -81,7 +82,7 @@ signal money_changed(player_id: int, old_amount: int, new_amount: int, reason: S
|
|||||||
var _money: Dictionary = {} # player_id (int) → balance (int)
|
var _money: Dictionary = {} # player_id (int) → balance (int)
|
||||||
|
|
||||||
## Consecutive round losses per team, used to escalate the loss bonus.
|
## Consecutive round losses per team, used to escalate the loss bonus.
|
||||||
## Key: TeamData.Team (int), Value: consecutive losses (int)
|
## Key: int (int), Value: consecutive losses (int)
|
||||||
var _consecutive_losses: Dictionary = {}
|
var _consecutive_losses: Dictionary = {}
|
||||||
|
|
||||||
## Total money ever earned per player (for stats). Not reset on round restart.
|
## Total money ever earned per player (for stats). Not reset on round restart.
|
||||||
@@ -91,9 +92,9 @@ var _total_earned: Dictionary = {} # player_id (int) → total (int)
|
|||||||
# Lifecycle
|
# Lifecycle
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_consecutive_losses[TeamData.Team.COUNTER_TERRORIST] = 0
|
_consecutive_losses[_td_eco.Team.COUNTER_TERRORIST] = 0
|
||||||
_consecutive_losses[TeamData.Team.TERRORIST] = 0
|
_consecutive_losses[_td_eco.Team.TERRORIST] = 0
|
||||||
_consecutive_losses[TeamData.Team.SPECTATOR] = 0
|
_consecutive_losses[_td_eco.Team.SPECTATOR] = 0
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Public API — Money queries
|
# Public API — Money queries
|
||||||
@@ -170,14 +171,14 @@ func award_kill_reward(player_id: int) -> void:
|
|||||||
|
|
||||||
## Award the round-win bonus to every player on the winning team.
|
## Award the round-win bonus to every player on the winning team.
|
||||||
## Resets the loss streak for that team.
|
## Resets the loss streak for that team.
|
||||||
func award_round_win(team: TeamData.Team, player_ids: Array[int]) -> void:
|
func award_round_win(team: int, player_ids: Array[int]) -> void:
|
||||||
for pid in player_ids:
|
for pid in player_ids:
|
||||||
add_money(pid, ROUND_WIN_BONUS, "round_win")
|
add_money(pid, ROUND_WIN_BONUS, "round_win")
|
||||||
_consecutive_losses[team] = 0
|
_consecutive_losses[team] = 0
|
||||||
|
|
||||||
## Award the round-loss bonus to every player on the losing team.
|
## Award the round-loss bonus to every player on the losing team.
|
||||||
## Escalates based on consecutive losses (loss streak).
|
## Escalates based on consecutive losses (loss streak).
|
||||||
func award_round_loss(team: TeamData.Team, player_ids: Array[int]) -> void:
|
func award_round_loss(team: int, player_ids: Array[int]) -> void:
|
||||||
var streak: int = _consecutive_losses.get(team, 0)
|
var streak: int = _consecutive_losses.get(team, 0)
|
||||||
var bonus: int = mini(streak * LOSS_STREAK_BONUS, MAX_LOSS_BONUS_ADDITIONAL)
|
var bonus: int = mini(streak * LOSS_STREAK_BONUS, MAX_LOSS_BONUS_ADDITIONAL)
|
||||||
var total_bonus: int = ROUND_LOSS_BASE + bonus
|
var total_bonus: int = ROUND_LOSS_BASE + bonus
|
||||||
@@ -195,7 +196,7 @@ func award_bomb_defuse(player_id: int) -> void:
|
|||||||
|
|
||||||
## Return the total loss bonus the given team would receive on their
|
## Return the total loss bonus the given team would receive on their
|
||||||
## next round loss, accounting for the current loss streak.
|
## next round loss, accounting for the current loss streak.
|
||||||
func get_loss_bonus_for_team(team: TeamData.Team) -> int:
|
func get_loss_bonus_for_team(team: int) -> int:
|
||||||
var streak: int = _consecutive_losses.get(team, 0)
|
var streak: int = _consecutive_losses.get(team, 0)
|
||||||
var bonus: int = mini(streak * LOSS_STREAK_BONUS, MAX_LOSS_BONUS_ADDITIONAL)
|
var bonus: int = mini(streak * LOSS_STREAK_BONUS, MAX_LOSS_BONUS_ADDITIONAL)
|
||||||
return ROUND_LOSS_BASE + bonus
|
return ROUND_LOSS_BASE + bonus
|
||||||
@@ -226,8 +227,8 @@ func reset_economy(player_ids: Array[int] = []) -> void:
|
|||||||
_money[pid] = STARTING_MONEY
|
_money[pid] = STARTING_MONEY
|
||||||
money_changed.emit(pid, old, STARTING_MONEY, "reset")
|
money_changed.emit(pid, old, STARTING_MONEY, "reset")
|
||||||
|
|
||||||
_consecutive_losses[TeamData.Team.COUNTER_TERRORIST] = 0
|
_consecutive_losses[_td_eco.Team.COUNTER_TERRORIST] = 0
|
||||||
_consecutive_losses[TeamData.Team.TERRORIST] = 0
|
_consecutive_losses[_td_eco.Team.TERRORIST] = 0
|
||||||
|
|
||||||
## Reset exactly one player's money to STARTING_MONEY.
|
## Reset exactly one player's money to STARTING_MONEY.
|
||||||
func reset_player_money(player_id: int) -> void:
|
func reset_player_money(player_id: int) -> void:
|
||||||
|
|||||||
@@ -23,6 +23,21 @@
|
|||||||
##
|
##
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Force class_name script dependencies to load first
|
||||||
|
# (Godot 4 headless mode doesn't resolve global classes in dependency order)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
const _ws = preload("res://server/scripts/weapons/weapon_server.gd")
|
||||||
|
const _lc = preload("res://server/scripts/combat/lag_compensation.gd")
|
||||||
|
const _dp = preload("res://server/scripts/combat/damage_processor.gd")
|
||||||
|
const _tm = preload("res://scripts/teams/team_manager.gd")
|
||||||
|
const _rm = preload("res://server/scripts/round/round_manager.gd")
|
||||||
|
const _em = preload("res://server/scripts/economy/economy_manager.gd")
|
||||||
|
const _bm = preload("res://server/scripts/economy/buy_menu_handler.gd")
|
||||||
|
const _bo = preload("res://server/scripts/objectives/bomb_objective.gd")
|
||||||
|
const _sm = preload("res://scripts/teams/spawn_manager.gd")
|
||||||
|
const _td = preload("res://scripts/teams/team_data.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Signals
|
# Signals
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -41,28 +56,28 @@ var simulation_server: Object = null
|
|||||||
var is_running: bool = false
|
var is_running: bool = false
|
||||||
|
|
||||||
## WeaponServer — handles hit-scan raycasting and lag-compensated fire.
|
## WeaponServer — handles hit-scan raycasting and lag-compensated fire.
|
||||||
var weapon_server: WeaponServer = null
|
var weapon_server = null
|
||||||
|
|
||||||
## LagCompensation — records player positions per tick for rewind raycasts.
|
## LagCompensation — records player positions per tick for rewind raycasts.
|
||||||
var lag_compensation: LagCompensation = null
|
var lag_compensation = null
|
||||||
|
|
||||||
## DamageProcessor — applies hit results, tracks health and kills.
|
## DamageProcessor — applies hit results, tracks health and kills.
|
||||||
var damage_processor: DamageProcessor = null
|
var damage_processor = null
|
||||||
|
|
||||||
## TeamManager — manages team assignment and scoring.
|
## TeamManager — manages team assignment and scoring.
|
||||||
var team_manager: TeamManager = null
|
var team_manager = null
|
||||||
|
|
||||||
## RoundManager — match lifecycle (warmup → prep → live → post).
|
## RoundManager — match lifecycle (warmup → prep → live → post).
|
||||||
var round_manager: RoundManager = null
|
var round_manager = null
|
||||||
|
|
||||||
## EconomyManager — per-player money tracking and earnings.
|
## EconomyManager — per-player money tracking and earnings.
|
||||||
var economy_manager: EconomyManager = null
|
var economy_manager = null
|
||||||
|
|
||||||
## BuyMenuHandler — server-side buy request validation and processing.
|
## BuyMenuHandler — server-side buy request validation and processing.
|
||||||
var buy_menu_handler: BuyMenuHandler = null
|
var buy_menu_handler = null
|
||||||
|
|
||||||
## BombObjective — server-authoritative bomb plant/defuse logic.
|
## BombObjective — server-authoritative bomb plant/defuse logic.
|
||||||
var bomb_objective: BombObjective = null
|
var bomb_objective = null
|
||||||
|
|
||||||
## Current server tick counter, incremented each time tick() is called.
|
## Current server tick counter, incremented each time tick() is called.
|
||||||
var _current_tick: int = 0
|
var _current_tick: int = 0
|
||||||
@@ -108,34 +123,34 @@ func _ready() -> void:
|
|||||||
# Create and wire the WeaponServer with LagCompensation and DamageProcessor.
|
# Create and wire the WeaponServer with LagCompensation and DamageProcessor.
|
||||||
# These work alongside the C++ SimulationServer for the GDScript
|
# These work alongside the C++ SimulationServer for the GDScript
|
||||||
# weapon path.
|
# weapon path.
|
||||||
weapon_server = WeaponServer.new()
|
weapon_server = _ws.new()
|
||||||
weapon_server.physics_world = get_viewport().get_world_3d()
|
weapon_server.physics_world = get_viewport().get_world_3d()
|
||||||
add_child(weapon_server)
|
add_child(weapon_server)
|
||||||
|
|
||||||
lag_compensation = LagCompensation.new()
|
lag_compensation = _lc.new()
|
||||||
add_child(lag_compensation)
|
add_child(lag_compensation)
|
||||||
|
|
||||||
damage_processor = DamageProcessor.new()
|
damage_processor = _dp.new()
|
||||||
add_child(damage_processor)
|
add_child(damage_processor)
|
||||||
|
|
||||||
# --- Round / Match lifecycle ---
|
# --- Round / Match lifecycle ---
|
||||||
round_manager = RoundManager.new()
|
round_manager = _rm.new()
|
||||||
add_child(round_manager)
|
add_child(round_manager)
|
||||||
|
|
||||||
# Wire up TeamManager reference (find it in the tree or create it)
|
# Wire up TeamManager reference (find it in the tree or create it)
|
||||||
team_manager = get_node_or_null("/root/TeamManager")
|
team_manager = get_node_or_null("/root/TeamManager")
|
||||||
if not team_manager:
|
if not team_manager:
|
||||||
team_manager = TeamManager.new()
|
team_manager = _tm.new()
|
||||||
add_child(team_manager)
|
add_child(team_manager)
|
||||||
|
|
||||||
round_manager.team_manager = team_manager
|
round_manager.team_manager = team_manager
|
||||||
round_manager.damage_processor = damage_processor
|
round_manager.damage_processor = damage_processor
|
||||||
|
|
||||||
# --- Economy system ---
|
# --- Economy system ---
|
||||||
economy_manager = EconomyManager.new()
|
economy_manager = _em.new()
|
||||||
add_child(economy_manager)
|
add_child(economy_manager)
|
||||||
|
|
||||||
buy_menu_handler = BuyMenuHandler.new()
|
buy_menu_handler = _bm.new()
|
||||||
add_child(buy_menu_handler)
|
add_child(buy_menu_handler)
|
||||||
buy_menu_handler.initialise(economy_manager, weapon_server, team_manager)
|
buy_menu_handler.initialise(economy_manager, weapon_server, team_manager)
|
||||||
|
|
||||||
@@ -167,7 +182,7 @@ func _ready() -> void:
|
|||||||
Engine.register_singleton("SimulationServer", simulation_server)
|
Engine.register_singleton("SimulationServer", simulation_server)
|
||||||
|
|
||||||
# --- Bomb / Defuse Objective ---
|
# --- Bomb / Defuse Objective ---
|
||||||
bomb_objective = BombObjective.new()
|
bomb_objective = _bo.new()
|
||||||
add_child(bomb_objective)
|
add_child(bomb_objective)
|
||||||
bomb_objective.round_manager = round_manager
|
bomb_objective.round_manager = round_manager
|
||||||
bomb_objective.team_manager = team_manager
|
bomb_objective.team_manager = team_manager
|
||||||
@@ -192,11 +207,11 @@ func _ready() -> void:
|
|||||||
# Wire: bomb explosion/defuse → round end
|
# Wire: bomb explosion/defuse → round end
|
||||||
bomb_objective.bomb_exploded.connect(func(_pos):
|
bomb_objective.bomb_exploded.connect(func(_pos):
|
||||||
if round_manager:
|
if round_manager:
|
||||||
round_manager.end_round(TeamData.Team.TERRORIST, "bomb_exploded")
|
round_manager.end_round(_td.Team.TERRORIST, "bomb_exploded")
|
||||||
)
|
)
|
||||||
bomb_objective.bomb_defused.connect(func(_player_id):
|
bomb_objective.bomb_defused.connect(func(_player_id):
|
||||||
if round_manager:
|
if round_manager:
|
||||||
round_manager.end_round(TeamData.Team.COUNTER_TERRORIST, "bomb_defused")
|
round_manager.end_round(_td.Team.COUNTER_TERRORIST, "bomb_defused")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wire: round end → reset bomb
|
# Wire: round end → reset bomb
|
||||||
@@ -380,7 +395,7 @@ func _on_kill_for_round(victim_id: int, shooter_id: int) -> void:
|
|||||||
|
|
||||||
if all_dead and team_player_ids.size() > 0:
|
if all_dead and team_player_ids.size() > 0:
|
||||||
print("[GameServer] Team elimination detected! %s eliminated by %s" %
|
print("[GameServer] Team elimination detected! %s eliminated by %s" %
|
||||||
[TeamData.get_team_name(victim_team), TeamData.get_team_name(shooter_team)])
|
[_td.get_team_name(victim_team), _td.get_team_name(shooter_team)])
|
||||||
round_manager.end_round(shooter_team, "elimination")
|
round_manager.end_round(shooter_team, "elimination")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -398,13 +413,13 @@ func _on_rcon_command(command: String, args: PackedStringArray) -> void:
|
|||||||
# end_round [team_name] [reason]
|
# end_round [team_name] [reason]
|
||||||
var team_name: String = args[0] if args.size() > 0 else "ct"
|
var team_name: String = args[0] if args.size() > 0 else "ct"
|
||||||
var reason: String = args[1] if args.size() > 1 else "admin"
|
var reason: String = args[1] if args.size() > 1 else "admin"
|
||||||
var team: int = TeamData.from_string(team_name)
|
var team: int = _td.from_string(team_name)
|
||||||
if team == TeamData.Team.SPECTATOR:
|
if team == _td.Team.SPECTATOR:
|
||||||
# Default to CT
|
# Default to CT
|
||||||
team = TeamData.Team.COUNTER_TERRORIST
|
team = _td.Team.COUNTER_TERRORIST
|
||||||
if round_manager:
|
if round_manager:
|
||||||
round_manager.end_round(team, reason)
|
round_manager.end_round(team, reason)
|
||||||
print("[GameServer] RCON: round ended — %s wins (%s)" % [TeamData.get_team_name(team), reason])
|
print("[GameServer] RCON: round ended — %s wins (%s)" % [_td.get_team_name(team), reason])
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Main loop (128 Hz)
|
# Main loop (128 Hz)
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
extends Node
|
extends Node
|
||||||
class_name BombObjective
|
class_name BombObjective
|
||||||
|
|
||||||
|
# Preload for headless compat
|
||||||
|
const _td = preload("res://scripts/teams/team_data.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Signals
|
# Signals
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -422,13 +425,13 @@ func _is_round_live() -> bool:
|
|||||||
func _is_player_terrorist(player_id: int) -> bool:
|
func _is_player_terrorist(player_id: int) -> bool:
|
||||||
if team_manager == null:
|
if team_manager == null:
|
||||||
return false
|
return false
|
||||||
return team_manager.get_player_team(player_id) == TeamData.Team.TERRORIST
|
return team_manager.get_player_team(player_id) == _td.Team.TERRORIST
|
||||||
|
|
||||||
## Check if a player (by peer ID) is on the Counter-Terrorist team.
|
## Check if a player (by peer ID) is on the Counter-Terrorist team.
|
||||||
func _is_player_counter_terrorist(player_id: int) -> bool:
|
func _is_player_counter_terrorist(player_id: int) -> bool:
|
||||||
if team_manager == null:
|
if team_manager == null:
|
||||||
return false
|
return false
|
||||||
return team_manager.get_player_team(player_id) == TeamData.Team.COUNTER_TERRORIST
|
return team_manager.get_player_team(player_id) == _td.Team.COUNTER_TERRORIST
|
||||||
|
|
||||||
## Check if a player is inside any registered bombsite.
|
## Check if a player is inside any registered bombsite.
|
||||||
## Iterates bomb sites and checks if the player's body overlaps.
|
## Iterates bomb sites and checks if the player's body overlaps.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
## return true
|
## return true
|
||||||
|
|
||||||
extends Node
|
extends Node
|
||||||
class_name PluginBase
|
# class_name PluginBase — commented out for headless compat
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Public properties — populated by PluginManager at load time
|
# Public properties — populated by PluginManager at load time
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ extends Node
|
|||||||
## Duplicate class_name removed — already registered as autoload singleton.
|
## Duplicate class_name removed — already registered as autoload singleton.
|
||||||
## class_name PluginManager
|
## class_name PluginManager
|
||||||
|
|
||||||
|
# Force dependency scripts to load (headless compat)
|
||||||
|
const _pm_manifest_ref = preload("res://server/scripts/plugin_api/plugin_manifest.gd")
|
||||||
|
const _pm_base_ref = preload("res://server/scripts/plugin_api/plugin_base.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Signals
|
# Signals
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -140,7 +144,7 @@ func rescan() -> int:
|
|||||||
# Auto-load newly discovered enabled plugins that aren't already loaded.
|
# Auto-load newly discovered enabled plugins that aren't already loaded.
|
||||||
for pname in discovered_manifests:
|
for pname in discovered_manifests:
|
||||||
if pname not in loaded_plugins:
|
if pname not in loaded_plugins:
|
||||||
var manifest: PluginManifest = discovered_manifests[pname]
|
var manifest = discovered_manifests[pname]
|
||||||
if manifest.enabled:
|
if manifest.enabled:
|
||||||
_load_plugin_from_manifest(pname, manifest)
|
_load_plugin_from_manifest(pname, manifest)
|
||||||
|
|
||||||
@@ -158,7 +162,7 @@ func _process_plugin_directory(root: String, dir_name: String) -> void:
|
|||||||
return
|
return
|
||||||
|
|
||||||
var manifest: Resource = ResourceLoader.load(tres_path)
|
var manifest: Resource = ResourceLoader.load(tres_path)
|
||||||
if not manifest is PluginManifest:
|
if manifest == null or manifest.get_script() == null or not manifest.has_method(&"is_valid"):
|
||||||
log_warn("Invalid manifest (not a PluginManifest resource): %s" % tres_path)
|
log_warn("Invalid manifest (not a PluginManifest resource): %s" % tres_path)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -197,12 +201,12 @@ func load_plugin(name: String) -> bool:
|
|||||||
log_err("Plugin not found: %s. Use 'plugin rescan' to scan for new plugins." % name)
|
log_err("Plugin not found: %s. Use 'plugin rescan' to scan for new plugins." % name)
|
||||||
return false
|
return false
|
||||||
|
|
||||||
var manifest: PluginManifest = discovered_manifests[name]
|
var manifest = discovered_manifests[name]
|
||||||
return _load_plugin_from_manifest(name, manifest)
|
return _load_plugin_from_manifest(name, manifest)
|
||||||
|
|
||||||
## Internal: instantiate a plugin from its manifest and add it to the tree.
|
## Internal: instantiate a plugin from its manifest and add it to the tree.
|
||||||
## Handles resource loading, instance creation, and metadata injection.
|
## Handles resource loading, instance creation, and metadata injection.
|
||||||
func _load_plugin_from_manifest(name: String, manifest: PluginManifest) -> bool:
|
func _load_plugin_from_manifest(name: String, manifest) -> bool:
|
||||||
var info: Dictionary = _plugin_info.get(name, {})
|
var info: Dictionary = _plugin_info.get(name, {})
|
||||||
var plugin_dir: String = info.get("path", plugins_root + "/" + name + "/")
|
var plugin_dir: String = info.get("path", plugins_root + "/" + name + "/")
|
||||||
var script_path: String = plugin_dir + manifest.script_path
|
var script_path: String = plugin_dir + manifest.script_path
|
||||||
@@ -236,7 +240,7 @@ func _load_plugin_from_manifest(name: String, manifest: PluginManifest) -> bool:
|
|||||||
|
|
||||||
# Warn if the plugin doesn't extend PluginBase (it'll still work,
|
# Warn if the plugin doesn't extend PluginBase (it'll still work,
|
||||||
# just won't have convenience methods).
|
# just won't have convenience methods).
|
||||||
if not instance is PluginBase:
|
if instance == null or not instance.has_method(&"log_info"):
|
||||||
log_warn("Plugin '%s' does not extend PluginBase — some features may be unavailable" % name)
|
log_warn("Plugin '%s' does not extend PluginBase — some features may be unavailable" % name)
|
||||||
|
|
||||||
# Inject manifest metadata into the instance
|
# Inject manifest metadata into the instance
|
||||||
@@ -300,7 +304,7 @@ func reload_plugin(name: String) -> bool:
|
|||||||
unload_plugin(name)
|
unload_plugin(name)
|
||||||
|
|
||||||
# Re-acquire manifest info
|
# Re-acquire manifest info
|
||||||
var manifest: PluginManifest = discovered_manifests.get(name)
|
var manifest = discovered_manifests.get(name)
|
||||||
if not manifest:
|
if not manifest:
|
||||||
log_err("Manifest lost for plugin '%s' during reload" % name)
|
log_err("Manifest lost for plugin '%s' during reload" % name)
|
||||||
return false
|
return false
|
||||||
@@ -496,7 +500,7 @@ func _rcon_list(_args: PackedStringArray) -> String:
|
|||||||
lines.append(" No plugins discovered. Use 'plugin rescan' to scan.")
|
lines.append(" No plugins discovered. Use 'plugin rescan' to scan.")
|
||||||
else:
|
else:
|
||||||
for pname in all_names:
|
for pname in all_names:
|
||||||
var manifest: PluginManifest = discovered_manifests[pname]
|
var manifest = discovered_manifests[pname]
|
||||||
var status: String
|
var status: String
|
||||||
if pname in loaded_plugins:
|
if pname in loaded_plugins:
|
||||||
status = "LOADED"
|
status = "LOADED"
|
||||||
@@ -543,7 +547,7 @@ func _rcon_info(args: PackedStringArray) -> String:
|
|||||||
if name not in discovered_manifests:
|
if name not in discovered_manifests:
|
||||||
return "Plugin not found: %s. Use 'plugin list' to see available plugins.\\r\\n" % name
|
return "Plugin not found: %s. Use 'plugin list' to see available plugins.\\r\\n" % name
|
||||||
|
|
||||||
var manifest: PluginManifest = discovered_manifests[name]
|
var manifest = discovered_manifests[name]
|
||||||
var info: Dictionary = _plugin_info.get(name, {})
|
var info: Dictionary = _plugin_info.get(name, {})
|
||||||
var full_script_path: String = info.get("path", "") + manifest.script_path
|
var full_script_path: String = info.get("path", "") + manifest.script_path
|
||||||
var lines: PackedStringArray = []
|
var lines: PackedStringArray = []
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
## enabled = true
|
## enabled = true
|
||||||
|
|
||||||
extends Resource
|
extends Resource
|
||||||
class_name PluginManifest
|
# class_name PluginManifest — commented out for headless compat
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Exported fields
|
# Exported fields
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
extends Node
|
extends Node
|
||||||
class_name RoundManager
|
class_name RoundManager
|
||||||
|
const _td_rm = preload("res://scripts/teams/team_data.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Round Phase enum
|
# Round Phase enum
|
||||||
@@ -177,8 +178,8 @@ func start_warmup() -> void:
|
|||||||
_round_kills.clear()
|
_round_kills.clear()
|
||||||
_round_deaths.clear()
|
_round_deaths.clear()
|
||||||
_consecutive_losses = {
|
_consecutive_losses = {
|
||||||
TeamData.Team.COUNTER_TERRORIST: 0,
|
_td_rm.Team.COUNTER_TERRORIST: 0,
|
||||||
TeamData.Team.TERRORIST: 0,
|
_td_rm.Team.TERRORIST: 0,
|
||||||
}
|
}
|
||||||
print("[RoundManager] Warmup started — infinite duration")
|
print("[RoundManager] Warmup started — infinite duration")
|
||||||
round_phase_changed.emit(RoundPhase.WARMUP, 0)
|
round_phase_changed.emit(RoundPhase.WARMUP, 0)
|
||||||
@@ -230,7 +231,7 @@ func start_live() -> void:
|
|||||||
round_phase_changed.emit(RoundPhase.LIVE, _round_num)
|
round_phase_changed.emit(RoundPhase.LIVE, _round_num)
|
||||||
|
|
||||||
## End the current round, awarding money and transitioning to POST.
|
## End the current round, awarding money and transitioning to POST.
|
||||||
## winning_team: TeamData.Team enum of the winner.
|
## winning_team: int enum of the winner.
|
||||||
## reason: string describing the outcome (e.g. "elimination", "time_expired").
|
## reason: string describing the outcome (e.g. "elimination", "time_expired").
|
||||||
func end_round(winning_team: int, reason: String) -> void:
|
func end_round(winning_team: int, reason: String) -> void:
|
||||||
if _phase != RoundPhase.LIVE:
|
if _phase != RoundPhase.LIVE:
|
||||||
@@ -245,7 +246,7 @@ func end_round(winning_team: int, reason: String) -> void:
|
|||||||
team_manager.add_team_score(winning_team, 1)
|
team_manager.add_team_score(winning_team, 1)
|
||||||
|
|
||||||
# --- Track consecutive losses ---
|
# --- Track consecutive losses ---
|
||||||
for team in [TeamData.Team.COUNTER_TERRORIST, TeamData.Team.TERRORIST]:
|
for team in [_td_rm.Team.COUNTER_TERRORIST, _td_rm.Team.TERRORIST]:
|
||||||
if team == winning_team:
|
if team == winning_team:
|
||||||
_consecutive_losses[team] = 0
|
_consecutive_losses[team] = 0
|
||||||
else:
|
else:
|
||||||
@@ -253,7 +254,7 @@ func end_round(winning_team: int, reason: String) -> void:
|
|||||||
|
|
||||||
# --- Compute economy data ---
|
# --- Compute economy data ---
|
||||||
var team_money_data: Dictionary = {}
|
var team_money_data: Dictionary = {}
|
||||||
for team in [TeamData.Team.COUNTER_TERRORIST, TeamData.Team.TERRORIST]:
|
for team in [_td_rm.Team.COUNTER_TERRORIST, _td_rm.Team.TERRORIST]:
|
||||||
var is_winner: bool = (team == winning_team)
|
var is_winner: bool = (team == winning_team)
|
||||||
var money: int = 0
|
var money: int = 0
|
||||||
if is_winner:
|
if is_winner:
|
||||||
@@ -268,7 +269,7 @@ func end_round(winning_team: int, reason: String) -> void:
|
|||||||
"total": money,
|
"total": money,
|
||||||
}
|
}
|
||||||
|
|
||||||
print("[RoundManager] Round %d ended — %s wins (%s)" % [_round_num, TeamData.get_team_name(winning_team), reason])
|
print("[RoundManager] Round %d ended — %s wins (%s)" % [_round_num, _td_rm.get_team_name(winning_team), reason])
|
||||||
|
|
||||||
# --- Match point / match win check ---
|
# --- Match point / match win check ---
|
||||||
if team_manager:
|
if team_manager:
|
||||||
@@ -277,7 +278,7 @@ func end_round(winning_team: int, reason: String) -> void:
|
|||||||
# One more round wins the match
|
# One more round wins the match
|
||||||
match_point.emit(winning_team)
|
match_point.emit(winning_team)
|
||||||
if winner_score >= win_threshold:
|
if winner_score >= win_threshold:
|
||||||
print("[RoundManager] MATCH WON by %s! (Score: %d)" % [TeamData.get_team_name(winning_team), winner_threshold_scores(winning_team)])
|
print("[RoundManager] MATCH WON by %s! (Score: %d)" % [_td_rm.get_team_name(winning_team), winner_threshold_scores(winning_team)])
|
||||||
|
|
||||||
# --- Emit signals ---
|
# --- Emit signals ---
|
||||||
round_ended.emit(winning_team, reason)
|
round_ended.emit(winning_team, reason)
|
||||||
@@ -424,13 +425,13 @@ func _get_entity_ids_for_team(team: int) -> Array[int]:
|
|||||||
## Default: the team with alive players wins; if both alive, CT wins.
|
## Default: the team with alive players wins; if both alive, CT wins.
|
||||||
func _determine_time_expired_winner() -> int:
|
func _determine_time_expired_winner() -> int:
|
||||||
if not team_manager or not damage_processor:
|
if not team_manager or not damage_processor:
|
||||||
return TeamData.Team.COUNTER_TERRORIST
|
return _td_rm.Team.COUNTER_TERRORIST
|
||||||
|
|
||||||
var ct_alive: int = 0
|
var ct_alive: int = 0
|
||||||
var t_alive: int = 0
|
var t_alive: int = 0
|
||||||
|
|
||||||
# Count alive players per team
|
# Count alive players per team
|
||||||
for team in [TeamData.Team.COUNTER_TERRORIST, TeamData.Team.TERRORIST]:
|
for team in [_td_rm.Team.COUNTER_TERRORIST, _td_rm.Team.TERRORIST]:
|
||||||
var player_ids: Array[int] = team_manager.get_team_players(team)
|
var player_ids: Array[int] = team_manager.get_team_players(team)
|
||||||
for pid in player_ids:
|
for pid in player_ids:
|
||||||
# We don't have entity_id from player_id here directly.
|
# We don't have entity_id from player_id here directly.
|
||||||
@@ -440,7 +441,7 @@ func _determine_time_expired_winner() -> int:
|
|||||||
|
|
||||||
# Simple default: CT wins on time expiry
|
# Simple default: CT wins on time expiry
|
||||||
# (Conceptually, CT defends the site and time runs out = attackers failed)
|
# (Conceptually, CT defends the site and time runs out = attackers failed)
|
||||||
return TeamData.Team.COUNTER_TERRORIST
|
return _td_rm.Team.COUNTER_TERRORIST
|
||||||
|
|
||||||
## Resolve a player_id or entity_id to a team.
|
## Resolve a player_id or entity_id to a team.
|
||||||
## For now, returns -1 if resolution fails.
|
## For now, returns -1 if resolution fails.
|
||||||
@@ -455,6 +456,6 @@ func _get_entity_team(_entity_or_player_id: int) -> int:
|
|||||||
func winner_threshold_scores(winning_team: int) -> String:
|
func winner_threshold_scores(winning_team: int) -> String:
|
||||||
if not team_manager:
|
if not team_manager:
|
||||||
return str(win_threshold)
|
return str(win_threshold)
|
||||||
var ct_score: int = team_manager.get_team_score(TeamData.Team.COUNTER_TERRORIST)
|
var ct_score: int = team_manager.get_team_score(_td_rm.Team.COUNTER_TERRORIST)
|
||||||
var t_score: int = team_manager.get_team_score(TeamData.Team.TERRORIST)
|
var t_score: int = team_manager.get_team_score(_td_rm.Team.TERRORIST)
|
||||||
return "CT %d — %d T" % [ct_score, t_score]
|
return "CT %d — %d T" % [ct_score, t_score]
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
class_name WeaponServer
|
class_name WeaponServer
|
||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
|
# Preload dependencies for headless compat
|
||||||
|
const _wd = preload("res://scripts/combat/weapon_definitions.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# State shape
|
# State shape
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -46,7 +49,7 @@ var physics_world: World3D = null
|
|||||||
## Optional LagCompensation controller for server-side rewinding.
|
## Optional LagCompensation controller for server-side rewinding.
|
||||||
## When set, _perform_hitscan uses lag-compensated raycasting;
|
## When set, _perform_hitscan uses lag-compensated raycasting;
|
||||||
## otherwise it uses the current-frame physics world directly.
|
## otherwise it uses the current-frame physics world directly.
|
||||||
var lag_compensation: LagCompensation = null
|
var lag_compensation = null
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Lifecycle
|
# Lifecycle
|
||||||
@@ -81,7 +84,7 @@ func unregister_player(player_id: int) -> void:
|
|||||||
## Also calls register_player implicitly.
|
## Also calls register_player implicitly.
|
||||||
func give_weapon(player_id: int, weapon_id: String) -> void:
|
func give_weapon(player_id: int, weapon_id: String) -> void:
|
||||||
register_player(player_id)
|
register_player(player_id)
|
||||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
var data := _wd.get_weapon(weapon_id)
|
||||||
if data == null:
|
if data == null:
|
||||||
push_warning("WeaponServer: unknown weapon '%s'" % weapon_id)
|
push_warning("WeaponServer: unknown weapon '%s'" % weapon_id)
|
||||||
return
|
return
|
||||||
@@ -105,7 +108,7 @@ func get_ammo_info(player_id: int, weapon_id: String) -> Dictionary:
|
|||||||
var st := get_weapon_state(player_id, weapon_id)
|
var st := get_weapon_state(player_id, weapon_id)
|
||||||
if st.is_empty():
|
if st.is_empty():
|
||||||
return {}
|
return {}
|
||||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
var data := _wd.get_weapon(weapon_id)
|
||||||
return {
|
return {
|
||||||
ammo = st.get("ammo", 0),
|
ammo = st.get("ammo", 0),
|
||||||
reserve = st.get("reserve", 0),
|
reserve = st.get("reserve", 0),
|
||||||
@@ -119,7 +122,7 @@ func get_ammo_info(player_id: int, weapon_id: String) -> Dictionary:
|
|||||||
## Returns true if the player can fire the specified weapon right now.
|
## Returns true if the player can fire the specified weapon right now.
|
||||||
## Checks: weapon known, ammo available, not reloading, cooldown elapsed.
|
## Checks: weapon known, ammo available, not reloading, cooldown elapsed.
|
||||||
func can_fire(player_id: int, weapon_id: String) -> bool:
|
func can_fire(player_id: int, weapon_id: String) -> bool:
|
||||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
var data := _wd.get_weapon(weapon_id)
|
||||||
if data == null:
|
if data == null:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -153,7 +156,7 @@ func can_fire(player_id: int, weapon_id: String) -> bool:
|
|||||||
|
|
||||||
## Start reloading the player's weapon. Returns true if reload initiated.
|
## Start reloading the player's weapon. Returns true if reload initiated.
|
||||||
func start_reload(player_id: int, weapon_id: String) -> bool:
|
func start_reload(player_id: int, weapon_id: String) -> bool:
|
||||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
var data := _wd.get_weapon(weapon_id)
|
||||||
if data == null:
|
if data == null:
|
||||||
return false
|
return false
|
||||||
|
|
||||||
@@ -196,7 +199,7 @@ func process_reloads(player_id: int, delta: float) -> bool:
|
|||||||
return any_completed
|
return any_completed
|
||||||
|
|
||||||
func _complete_reload(player_id: int, weapon_id: String, st: Dictionary) -> void:
|
func _complete_reload(player_id: int, weapon_id: String, st: Dictionary) -> void:
|
||||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
var data := _wd.get_weapon(weapon_id)
|
||||||
if data == null:
|
if data == null:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -227,7 +230,7 @@ func _complete_reload(player_id: int, weapon_id: String, st: Dictionary) -> void
|
|||||||
## }
|
## }
|
||||||
##
|
##
|
||||||
func fire(tick: int, player_id: int, weapon_id: String, origin: Vector3, direction: Vector3) -> Dictionary:
|
func fire(tick: int, player_id: int, weapon_id: String, origin: Vector3, direction: Vector3) -> Dictionary:
|
||||||
var data := WeaponDefinitions.get_weapon(weapon_id)
|
var data := _wd.get_weapon(weapon_id)
|
||||||
if data == null:
|
if data == null:
|
||||||
return _miss_result(weapon_id)
|
return _miss_result(weapon_id)
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
extends Area3D
|
extends Area3D
|
||||||
class_name BuyZone
|
class_name BuyZone
|
||||||
|
const _td_bz = preload("res://scripts/teams/team_data.gd")
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Signals
|
# Signals
|
||||||
@@ -44,8 +45,8 @@ signal player_exited_buyzone(player_id: int)
|
|||||||
|
|
||||||
## Which team(s) can use this buy zone.
|
## Which team(s) can use this buy zone.
|
||||||
## Team.COUNTER_TERRORIST = 1, Team.TERRORIST = 2, Team.SPECTATOR = 0.
|
## Team.COUNTER_TERRORIST = 1, Team.TERRORIST = 2, Team.SPECTATOR = 0.
|
||||||
## Use the Team enum: TeamData.Team.COUNTER_TERRORIST, etc.
|
## Use the Team enum: int.COUNTER_TERRORIST, etc.
|
||||||
@export var zone_team: TeamData.Team = TeamData.Team.COUNTER_TERRORIST
|
@export var zone_team: int = _td_bz.Team.COUNTER_TERRORIST
|
||||||
|
|
||||||
## Radius of the buy zone in Godot units (visual hint only; actual collision
|
## Radius of the buy zone in Godot units (visual hint only; actual collision
|
||||||
## shape determines the trigger volume).
|
## shape determines the trigger volume).
|
||||||
@@ -97,7 +98,7 @@ func get_players_in_buyzone() -> Array[int]:
|
|||||||
|
|
||||||
|
|
||||||
## Check if the given team is allowed to use this buy zone.
|
## Check if the given team is allowed to use this buy zone.
|
||||||
func is_team_allowed(team: TeamData.Team) -> bool:
|
func is_team_allowed(team: int) -> bool:
|
||||||
if allow_all_teams:
|
if allow_all_teams:
|
||||||
return true
|
return true
|
||||||
return team == zone_team
|
return team == zone_team
|
||||||
|
|||||||
Reference in New Issue
Block a user