P7.8: Port FPS controller to netfox BaseNetInput pattern
- FPSCharacterController: removed SimulationServer dependency, added _rollback_tick() with acceleration-based CharacterBody3D movement, gravity, jump, crouch/sprint speed. Kept mouse look, crouch lerp, sprint/crouch toggle, _move_local() standalone fallback. - Player: extends FPSCharacterController (CharacterBody3D). Added authority-guarded _rollback_tick that delegates to super for server and local client prediction. TickInterpolator created dynamically (avoids headless class_name parse errors). PlayerNetInput child wired via existing RollbackSynchronizer input_properties. - player.tscn: CharacterBody3D root with FpsCamera, CollisionShape3D, PlayerNetInput children. TickInterpolator created in code. - client_main.gd: _spawn_local_player() creates FPS player node with first-person camera for own peer. _spawn_remote_player() removes FpsCamera for remote players. - fps_camera.gd: duck-typed _controller (Node) instead of class_name cast for headless safety. Fixed type inference warnings. Also includes parent task P7.5-P7.7 changes: - project.godot: main_scene -> server_main.tscn - network_manager.gd: Chan enum -> raw channel ints - server_main.gd: deferred ServerConfig load, GameServer load() - game_server.gd: commented out dev deps for headless compilation
This commit is contained in:
@@ -26,17 +26,21 @@ extends Node
|
||||
# ---------------------------------------------------------------------------
|
||||
# Force class_name script dependencies to load first
|
||||
# (Godot 4 headless mode doesn't resolve global classes in dependency order)
|
||||
#
|
||||
# NOTE: these are commented out for P7.5 build — they reference development
|
||||
# systems (weapons, economy, objectives, teams) that aren't part of this
|
||||
# phase. When those systems are ready, uncomment the relevant preloads.
|
||||
# ---------------------------------------------------------------------------
|
||||
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")
|
||||
#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
|
||||
@@ -79,6 +83,9 @@ var buy_menu_handler = null
|
||||
## BombObjective — server-authoritative bomb plant/defuse logic.
|
||||
var bomb_objective = null
|
||||
|
||||
## TeamData reference — cached with load() to avoid class_name resolution in headless.
|
||||
var _td = null
|
||||
|
||||
## Current server tick counter, incremented each time tick() is called.
|
||||
var _current_tick: int = 0
|
||||
|
||||
@@ -207,11 +214,11 @@ func _ready() -> void:
|
||||
# Wire: bomb explosion/defuse → round end
|
||||
bomb_objective.bomb_exploded.connect(func(_pos):
|
||||
if round_manager:
|
||||
round_manager.end_round(_td.Team.TERRORIST, "bomb_exploded")
|
||||
round_manager.end_round(2, "bomb_exploded") # TeamData.Team.TERRORIST
|
||||
)
|
||||
bomb_objective.bomb_defused.connect(func(_player_id):
|
||||
if round_manager:
|
||||
round_manager.end_round(_td.Team.COUNTER_TERRORIST, "bomb_defused")
|
||||
round_manager.end_round(1, "bomb_defused") # TeamData.Team.COUNTER_TERRORIST
|
||||
)
|
||||
|
||||
# Wire: round end → reset bomb
|
||||
@@ -394,8 +401,13 @@ func _on_kill_for_round(victim_id: int, shooter_id: int) -> void:
|
||||
break
|
||||
|
||||
if all_dead and team_player_ids.size() > 0:
|
||||
print("[GameServer] Team elimination detected! %s eliminated by %s" %
|
||||
[_td.get_team_name(victim_team), _td.get_team_name(shooter_team)])
|
||||
var victim_name: String = "Spectator"
|
||||
var shooter_name: String = "Spectator"
|
||||
if victim_team == 1: victim_name = "CT"
|
||||
elif victim_team == 2: victim_name = "T"
|
||||
if shooter_team == 1: shooter_name = "CT"
|
||||
elif shooter_team == 2: shooter_name = "T"
|
||||
print("[GameServer] Team elimination detected! %s eliminated by %s" % [victim_name, shooter_name])
|
||||
round_manager.end_round(shooter_team, "elimination")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -413,13 +425,27 @@ func _on_rcon_command(command: String, args: PackedStringArray) -> void:
|
||||
# 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 = _td.from_string(team_name)
|
||||
if team == _td.Team.SPECTATOR:
|
||||
var team: int = _parse_team_name(team_name)
|
||||
if team == 0: # TeamData.Team.SPECTATOR
|
||||
# Default to CT
|
||||
team = _td.Team.COUNTER_TERRORIST
|
||||
team = 1 # TeamData.Team.COUNTER_TERRORIST
|
||||
if round_manager:
|
||||
round_manager.end_round(team, reason)
|
||||
print("[GameServer] RCON: round ended — %s wins (%s)" % [_td.get_team_name(team), reason])
|
||||
team_name = "Spectator"
|
||||
if team == 1: team_name = "CT"
|
||||
elif team == 2: team_name = "T"
|
||||
print("[GameServer] RCON: round ended — %s wins (%s)" % [team_name, reason])
|
||||
|
||||
## Parse a team name string to a team ID (replaces TeamData.from_string for headless compat).
|
||||
## 0 = Spectator, 1 = CT, 2 = T. Defaults to 0 for unknown names.
|
||||
func _parse_team_name(name_str: String) -> int:
|
||||
match name_str.to_lower().strip_edges():
|
||||
"ct", "counter_terrorist", "counter-terrorist", "team_a", "1":
|
||||
return 1
|
||||
"t", "terrorist", "team_b", "2":
|
||||
return 2
|
||||
_:
|
||||
return 0 # Spectator
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main loop (128 Hz)
|
||||
|
||||
Reference in New Issue
Block a user