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:
2026-07-02 17:45:03 -04:00
parent e7299b17e9
commit 926446e5cf
7 changed files with 341 additions and 309 deletions
+2 -2
View File
@@ -233,7 +233,7 @@ func broadcast_despawn_player(peer_id: int) -> void:
## Client → Server: send raw input for the given local tick.
## Called by ClientPrediction.on_after_tick() each physics tick.
## Uses ENet channel 0 (unreliable-ordered) for lowest-latency input delivery.
@rpc("unreliable", "any_peer", "call_remote", Chan.INPUT)
@rpc("unreliable", "any_peer", "call_remote", 0)
func send_client_input(tick: int, input_dict: Dictionary) -> void:
if not multiplayer.is_server():
return
@@ -245,7 +245,7 @@ func send_client_input(tick: int, input_dict: Dictionary) -> void:
## Called by server-side code (e.g. GameServer after each tick).
## entity_id identifies which simulation entity this state belongs to.
## Uses ENet channel 1 (reliable-ordered) so state corrections are not dropped.
@rpc("unreliable", "authority", "call_remote", Chan.EVENTS)
@rpc("unreliable", "authority", "call_remote", 1)
func send_server_state(entity_id: int, snapshot_dict: Dictionary) -> void:
if multiplayer.is_server():
return