Save workspace artifacts: character-controller, gdextension scaffold, network scripts, map-pipeline, server config
Includes code from task workspaces that was never pushed: - client/characters/ — fps_character_controller.gd (400 lines), fps_camera.gd, input_handler.gd - gdextension/simulation/ — C++ GDExtension scaffold: entity, movement, hit detection, simulation server, state serializer, bitstream (14 files) - scripts/network/ — ENet-based network_manager.gd, server/client_main.gd, player.gd - scripts/map_packaging/ — PCK pipeline: pack_map.gd, map_downloader.gd, map_registry_server.py, README - scripts/config/ — server_config.gd (480 lines) - scenes/ — client_main, server_main, player, test_range - project.godot, export_presets.cfg
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
## Player — Replicated player state.
|
||||
##
|
||||
## Server-authoritative movement model (Phase 0 placeholder).
|
||||
## The player who owns this node (multiplayer authority) sends input,
|
||||
## server validates and broadcasts position to all peers.
|
||||
##
|
||||
## Phase 0: simple position RPC replication.
|
||||
## Phase 1+: full input → movement → state broadcast loop.
|
||||
|
||||
extends Node3D
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Exports
|
||||
# ---------------------------------------------------------------------------
|
||||
@export var movement_speed: float = 5.0
|
||||
@export var sync_rate: float = 10.0 # Hz — how often to broadcast state
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# State
|
||||
# ---------------------------------------------------------------------------
|
||||
var is_local: bool = false # True for the local player instance
|
||||
var is_client_controlled: bool = false
|
||||
|
||||
var _sync_timer: float = 0.0
|
||||
|
||||
# Interpolation state for remote players
|
||||
var _remote_position: Vector3 = Vector3.ZERO
|
||||
var _remote_velocity: Vector3 = Vector3.ZERO
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Lifecycle
|
||||
# ---------------------------------------------------------------------------
|
||||
func _ready() -> void:
|
||||
is_local = multiplayer.get_unique_id() == multiplayer.get_multiplayer_authority()
|
||||
|
||||
if is_local:
|
||||
# This is our own player — we'll send input to server
|
||||
is_client_controlled = not multiplayer.is_server()
|
||||
if is_client_controlled:
|
||||
print("[Player] Local player (client-controlled): %d" % multiplayer.get_unique_id())
|
||||
else:
|
||||
# Remote player — disable direct control
|
||||
set_process(false)
|
||||
set_physics_process(false)
|
||||
print("[Player] Remote player: %d (authority: %d)" % [multiplayer.get_unique_id(), multiplayer.get_multiplayer_authority()])
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Client-side: send position to server
|
||||
# ---------------------------------------------------------------------------
|
||||
func _process(delta: float) -> void:
|
||||
if not is_client_controlled:
|
||||
return
|
||||
|
||||
# Simple movement for Phase 0 testing
|
||||
var input_dir := Vector3.ZERO
|
||||
if Input.is_action_pressed("ui_right"):
|
||||
input_dir.x += 1.0
|
||||
if Input.is_action_pressed("ui_left"):
|
||||
input_dir.x -= 1.0
|
||||
if Input.is_action_pressed("ui_up"):
|
||||
input_dir.z -= 1.0
|
||||
if Input.is_action_pressed("ui_down"):
|
||||
input_dir.z += 1.0
|
||||
|
||||
if input_dir != Vector3.ZERO:
|
||||
input_dir = input_dir.normalized()
|
||||
position += input_dir * movement_speed * delta
|
||||
|
||||
# Rate-limited position update to server
|
||||
_sync_timer += delta
|
||||
if _sync_timer >= (1.0 / sync_rate):
|
||||
_sync_timer = 0.0
|
||||
_send_position.rpc_id(1, position)
|
||||
|
||||
@rpc("unreliable", "any_peer")
|
||||
func _send_position(pos: Vector3) -> void:
|
||||
# Server receives position from client
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
|
||||
var peer_id: int = multiplayer.get_remote_sender_id()
|
||||
if peer_id != multiplayer.get_multiplayer_authority():
|
||||
# Peer is not authorized for this player — reject
|
||||
push_warning("[Player] Unauthorized position update from peer %d" % peer_id)
|
||||
return
|
||||
|
||||
# Server validates and broadcasts to all other peers
|
||||
position = pos
|
||||
_broadcast_position.rpc(pos)
|
||||
|
||||
@rpc("unreliable", "authority", "call_local")
|
||||
func _broadcast_position(pos: Vector3) -> void:
|
||||
# All peers (including server) update this player's position
|
||||
if not is_local:
|
||||
# Remote player — store for interpolation
|
||||
_remote_position = pos
|
||||
position = pos
|
||||
else:
|
||||
# Local player — authoritative position already set via input
|
||||
pass
|
||||
Reference in New Issue
Block a user