25221bf3cb
- Fixed double-emission bug in network_manager.gd: ENet signal connections (peer_connected/peer_disconnected) are now only connected when netfox NetworkEvents is NOT available, preventing duplicate player_connected/ player_disconnected signal emissions. - Fixed stop() to only disconnect ENet signals when they were actually connected (mirrors the conditional connection in start_server()). - Fixed headless parse error in player_net_input.gd: replaced direct NetworkTime.before_tick_loop reference with Engine.get_singleton() call, avoiding unresolved type identifier in headless mode. - netfox_bootstrap.gd added as minimal autoload placeholder. - Dual-path architecture: netfox NetworkEvents when available (editor), ENet fallback when netfox unavailable (headless server). - Broadcast RPCs (spawn, round state, scores) kept as-is since they work identically through Godot's MultiplayerAPI in both paths.
134 lines
5.2 KiB
GDScript
134 lines
5.2 KiB
GDScript
## PlayerNetInput — netfox-compatible input gathering for tactical-shooter.
|
|
##
|
|
## Extends Node directly (avoids headless class_name issues with BaseNetInput)
|
|
## but follows the same pattern: connects to NetworkTime.before_tick_loop to
|
|
## call _gather() on each tick.
|
|
##
|
|
## RollbackSynchronizer references the exported properties as input_properties.
|
|
##
|
|
## Usage:
|
|
## Place as a child of the Player (Node3D). The parent node's
|
|
## RollbackSynchronizer will pick up the input_properties paths.
|
|
##
|
|
## Set hitscan_manager_ref to the RollbackHitscanManager child so that
|
|
## _gather() can call record_fire() for RewindableAction-based fire tracking.
|
|
|
|
extends Node
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Input state — read by RollbackSynchronizer as input_properties
|
|
# ---------------------------------------------------------------------------
|
|
|
|
## Movement direction in local space (normalized, or zero).
|
|
@export var move_direction: Vector3 = Vector3.ZERO
|
|
|
|
## Mouse look: yaw (horizontal, radians).
|
|
@export var look_yaw: float = 0.0
|
|
|
|
## Mouse look: pitch (vertical, radians).
|
|
@export var look_pitch: float = 0.0
|
|
|
|
## Jump pressed this tick.
|
|
@export var jump: bool = false
|
|
|
|
## Sprint toggle active.
|
|
@export var sprint: bool = false
|
|
|
|
## Crouch toggle active.
|
|
@export var crouch: bool = false
|
|
|
|
## Shoot pressed this tick.
|
|
@export var shoot: bool = false
|
|
|
|
## Aim-down-sights pressed.
|
|
@export var aim: bool = false
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# RollbackHitscanManager reference (set by player.gd)
|
|
# ---------------------------------------------------------------------------
|
|
## Reference to the RollbackHitscanManager for RewindableAction fire tracking.
|
|
var hitscan_mgr_ref: Node = null
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Lifecycle
|
|
# ---------------------------------------------------------------------------
|
|
|
|
func _ready() -> void:
|
|
# Same pattern as netfox's BaseNetInput:
|
|
# connect to NetworkTime.before_tick_loop to gather input each tick
|
|
# Use Engine.get_singleton instead of direct NetworkTime reference to
|
|
# avoid headless parse errors (NetworkTime is an editor-plugin type).
|
|
var nt = Engine.get_singleton(&"NetworkTime") if Engine.has_singleton(&"NetworkTime") else null
|
|
if nt:
|
|
nt.before_tick_loop.connect(_on_before_tick_loop)
|
|
else:
|
|
push_warning("[PlayerNetInput] netfox NetworkTime not available — " +
|
|
"input gathering disabled")
|
|
|
|
func _on_before_tick_loop() -> void:
|
|
if is_multiplayer_authority():
|
|
_gather()
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Input gathering (called before each tick on the authority peer)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
func _gather() -> void:
|
|
# Movement direction
|
|
var dir := Vector3.ZERO
|
|
if Input.is_action_pressed(&"move_forward") or Input.is_action_pressed(&"move_up"):
|
|
dir.z -= 1.0
|
|
if Input.is_action_pressed(&"move_backward") or Input.is_action_pressed(&"move_down"):
|
|
dir.z += 1.0
|
|
if Input.is_action_pressed(&"move_left"):
|
|
dir.x -= 1.0
|
|
if Input.is_action_pressed(&"move_right"):
|
|
dir.x += 1.0
|
|
if dir.length_squared() > 0.0:
|
|
dir = dir.normalized()
|
|
move_direction = dir
|
|
|
|
# Read look direction from parent's FPSCharacterController if available
|
|
var parent := get_parent()
|
|
if parent and parent.has_method(&"get_current_yaw"):
|
|
look_yaw = parent.get_current_yaw()
|
|
if parent and parent.has_method(&"get_current_pitch"):
|
|
look_pitch = parent.get_current_pitch()
|
|
|
|
# Action inputs
|
|
jump = Input.is_action_just_pressed(&"jump")
|
|
sprint = Input.is_action_pressed(&"sprint")
|
|
crouch = Input.is_action_pressed(&"crouch")
|
|
shoot = Input.is_action_pressed(&"shoot")
|
|
aim = Input.is_action_pressed(&"aim")
|
|
|
|
# -----------------------------------------------------------------------
|
|
# Fire recording for RewindableAction rollback sync
|
|
# -----------------------------------------------------------------------
|
|
# When the client fires, record the shot in the RollbackHitscanManager's
|
|
# RewindableAction. This propagates to the server via netfox sync so
|
|
# the server can process the lag-compensated raycast at the correct tick.
|
|
#
|
|
# We compute the eye position and look direction at gather time for
|
|
# the RewindableAction context. The server also reconstructs these from
|
|
# the synced state (position + yaw/pitch) for authoritative hit detection.
|
|
if shoot and hitscan_mgr_ref != null and hitscan_mgr_ref.has_method(&"record_fire"):
|
|
var current_tick: int =_get_current_tick()
|
|
var eye_pos: Vector3 = parent.global_position + Vector3(0, 1.7, 0) if parent else Vector3.ZERO
|
|
var look_dir: Vector3 = _yaw_pitch_to_dir(look_yaw, look_pitch)
|
|
hitscan_mgr_ref.record_fire(current_tick, eye_pos, look_dir)
|
|
|
|
## Get the current network tick, or 0 if NetworkTime is not available.
|
|
func _get_current_tick() -> int:
|
|
var nt = Engine.get_singleton(&"NetworkTime") if Engine.has_singleton(&"NetworkTime") else null
|
|
if nt != null and nt.has_method(&"get_tick"):
|
|
return nt.get_tick()
|
|
if nt != null:
|
|
return nt.tick if nt.get("tick") != null else 0
|
|
return 0
|
|
|
|
## Convert yaw/pitch to a forward direction vector.
|
|
static func _yaw_pitch_to_dir(yaw: float, pitch: float) -> Vector3:
|
|
var cp := cos(pitch)
|
|
return Vector3(cp * sin(yaw), sin(pitch), cp * cos(yaw))
|