fix: a lot of networking bugs
This commit is contained in:
@@ -54,7 +54,13 @@ var grapple_rope: MeshInstance3D
|
||||
var synced_movement_state: String = "idle"
|
||||
var synced_movement_speed: float = 0.0
|
||||
var synced_is_crouching: bool = false
|
||||
var synced_weapon_path: String = ""
|
||||
@export var synced_weapon_path: String = ""
|
||||
@export var synced_grapple_point: Vector3 = Vector3.ZERO
|
||||
@export var synced_is_grapple_shooting: bool = false
|
||||
|
||||
# For homing rocket
|
||||
@export var synced_is_targeting: bool = false
|
||||
@export var synced_homing_target_pos: Vector3 = Vector3.ZERO
|
||||
|
||||
var synced_loadout_p1: String = ""
|
||||
var synced_loadout_p2: String = ""
|
||||
@@ -315,7 +321,17 @@ func _spawn_remote_tracer(origin: Vector3, target: Vector3) -> void:
|
||||
tracer.look_at(target, up_vec)
|
||||
|
||||
func _spawn_remote_projectile(origin: Vector3, fire_dir: Vector3, weapon_name: String) -> void:
|
||||
# Spawn a visual-only projectile (no damage, just the mesh flying)
|
||||
# Try to find the actual weapon to spawn its custom projectile so it looks identical!
|
||||
if camera:
|
||||
var wman = camera.get_node_or_null("WeaponManager")
|
||||
if wman:
|
||||
for w in wman.weapons.values():
|
||||
if "weapon_name" in w and w.weapon_name == weapon_name:
|
||||
if w.has_method("_spawn_custom_projectile"):
|
||||
w._spawn_custom_projectile(origin, fire_dir)
|
||||
return
|
||||
|
||||
# Fallback if weapon not found (e.g. joined late and weapons didn't build yet)
|
||||
var proj = Node3D.new()
|
||||
proj.name = "RemoteProjectile"
|
||||
|
||||
@@ -339,7 +355,7 @@ func _spawn_remote_projectile(origin: Vector3, fire_dir: Vector3, weapon_name: S
|
||||
light.omni_range = 4.0
|
||||
proj.add_child(light)
|
||||
else:
|
||||
# Rocket-shaped mesh (cone + cylinder)
|
||||
# Generic rocket fallback
|
||||
var mesh_inst = MeshInstance3D.new()
|
||||
var p_mesh = CylinderMesh.new()
|
||||
p_mesh.top_radius = 0.0
|
||||
@@ -354,7 +370,6 @@ func _spawn_remote_projectile(origin: Vector3, fire_dir: Vector3, weapon_name: S
|
||||
mesh_inst.rotation.x = deg_to_rad(-90)
|
||||
proj.add_child(mesh_inst)
|
||||
|
||||
# Smoke trail
|
||||
var smoke = GPUParticles3D.new()
|
||||
var smoke_mat = ParticleProcessMaterial.new()
|
||||
smoke_mat.direction = Vector3(0, 0, 1)
|
||||
@@ -468,7 +483,7 @@ func rpc_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_n
|
||||
health = 0.0
|
||||
die(impulse)
|
||||
|
||||
if is_multiplayer_authority():
|
||||
if multiplayer.is_server():
|
||||
if get_node_or_null("/root/NetworkManager") and multiplayer.has_multiplayer_peer():
|
||||
var victim_id = multiplayer.get_unique_id()
|
||||
var killer_id = attacker_id
|
||||
@@ -512,12 +527,56 @@ func _ensure_machine() -> MovementStateMachine:
|
||||
_machine = null
|
||||
return null
|
||||
|
||||
var _client_input_dir: Vector2 = Vector2.ZERO
|
||||
var _client_wish_dir_world: Vector3 = Vector3.ZERO
|
||||
var _client_jump: bool = false
|
||||
var _client_jump_just_pressed: bool = false
|
||||
var _client_crouch: bool = false
|
||||
var _client_dash: bool = false
|
||||
var _client_grapple: bool = false
|
||||
var _client_grapple_just_pressed: bool = false
|
||||
|
||||
@rpc("any_peer", "call_local", "unreliable")
|
||||
func server_receive_inputs(input_dir: Vector2, wish_dir: Vector3, jump: bool, jump_just: bool, crouch: bool, dash: bool, grapple: bool, grapple_just: bool) -> void:
|
||||
if not multiplayer.is_server(): return
|
||||
var sender = multiplayer.get_remote_sender_id()
|
||||
if sender != str(name).to_int() and sender != 1: return # Accept from owner or host self
|
||||
|
||||
_client_input_dir = input_dir
|
||||
_client_wish_dir_world = wish_dir
|
||||
_client_jump = jump
|
||||
_client_jump_just_pressed = jump_just
|
||||
_client_crouch = crouch
|
||||
_client_dash = dash
|
||||
_client_grapple = grapple
|
||||
_client_grapple_just_pressed = grapple_just
|
||||
|
||||
func _physics_process(_delta: float) -> void:
|
||||
if not is_multiplayer_authority(): return
|
||||
# Local client captures input and sends it
|
||||
if is_multiplayer_authority():
|
||||
var raw_input := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||
var forward := -global_transform.basis.z
|
||||
forward.y = 0.0
|
||||
forward = forward.normalized()
|
||||
var right := global_transform.basis.x
|
||||
right.y = 0.0
|
||||
right = right.normalized()
|
||||
|
||||
var world_dir := (forward * (-raw_input.y) + right * raw_input.x)
|
||||
if world_dir.length_squared() > 1.0:
|
||||
world_dir = world_dir.normalized()
|
||||
|
||||
var input_jump = Input.is_action_pressed("jump")
|
||||
var input_jump_just = Input.is_action_just_pressed("jump")
|
||||
var input_crouch = Input.is_action_pressed("crouch")
|
||||
var input_dash = Input.is_action_just_pressed("dash")
|
||||
var input_grapple = Input.is_action_pressed("grapple")
|
||||
var input_grapple_just = Input.is_action_just_pressed("grapple")
|
||||
|
||||
server_receive_inputs.rpc_id(1, raw_input, world_dir, input_jump, input_jump_just, input_crouch, input_dash, input_grapple, input_grapple_just)
|
||||
|
||||
var speed = velocity.length()
|
||||
var wind_factor = clampf((speed - 10.0) / 25.0, 0.0, 1.0)
|
||||
# Decibel scale is non-linear, so lerping from -80 means it stays inaudible for a long time.
|
||||
# -40 db is practically silent for a background loop, -15 db is our max quiet wind.
|
||||
wind_player.volume_db = lerpf(-40.0, -15.0, wind_factor)
|
||||
if not wind_player.playing and wind_factor > 0.0:
|
||||
wind_player.play()
|
||||
@@ -526,67 +585,51 @@ func _physics_process(_delta: float) -> void:
|
||||
if not sm:
|
||||
return
|
||||
|
||||
# Poll input in _physics_process so it's synchronized with the state machine tick
|
||||
sm.input_jump_just_pressed = Input.is_action_just_pressed("jump")
|
||||
sm.input_jump_pressed = Input.is_action_pressed("jump")
|
||||
sm.input_crouch = Input.is_action_pressed("crouch")
|
||||
sm.input_dash = Input.is_action_just_pressed("dash")
|
||||
|
||||
sm.input_grapple = Input.is_action_pressed("grapple")
|
||||
sm.input_grapple_just_pressed = Input.is_action_just_pressed("grapple")
|
||||
# Only the Server sets input values to the StateMachine for evaluation
|
||||
if multiplayer.is_server():
|
||||
sm.input_dir = _client_input_dir
|
||||
sm.wish_dir_world = _client_wish_dir_world
|
||||
sm.input_jump_pressed = _client_jump
|
||||
sm.input_jump_just_pressed = _client_jump_just_pressed
|
||||
sm.input_crouch = _client_crouch
|
||||
sm.input_dash = _client_dash
|
||||
sm.input_grapple = _client_grapple
|
||||
sm.input_grapple_just_pressed = _client_grapple_just_pressed
|
||||
|
||||
# Reset one-frame actions
|
||||
_client_jump_just_pressed = false
|
||||
_client_dash = false
|
||||
_client_grapple_just_pressed = false
|
||||
|
||||
# Update grapple rope visuals
|
||||
if (sm.current_state == "grapple" or sm.is_grapple_shooting) and camera:
|
||||
# Update synced properties for the grapple
|
||||
if sm.current_state == "grapple" or sm.is_grapple_shooting:
|
||||
synced_grapple_point = sm.grapple_point
|
||||
synced_is_grapple_shooting = sm.is_grapple_shooting
|
||||
|
||||
# Update grapple rope visuals (runs on all clients for smooth interpolation)
|
||||
if (synced_movement_state == "grapple" or synced_is_grapple_shooting) and camera:
|
||||
grapple_rope.visible = true
|
||||
|
||||
# For shoot, start at right side of camera slightly below and closer
|
||||
var start_pos = camera.global_position + camera.global_transform.basis * Vector3(0.3, -0.3, -0.15)
|
||||
var end_pos = sm.grapple_point
|
||||
|
||||
if sm.is_grapple_shooting and sm.grapple_travel_time > 0.0:
|
||||
# Interpolate end_pos
|
||||
var t = clampf(sm.grapple_shoot_time / sm.grapple_travel_time, 0.0, 1.0)
|
||||
end_pos = start_pos.lerp(sm.grapple_point, t)
|
||||
|
||||
var end_pos = synced_grapple_point
|
||||
var dist = start_pos.distance_to(end_pos)
|
||||
if dist > 0.01:
|
||||
grapple_rope.global_position = (start_pos + end_pos) / 2.0
|
||||
# Look at end point
|
||||
var up_vec = Vector3.UP
|
||||
var dir = (end_pos - start_pos).normalized()
|
||||
if abs(dir.dot(up_vec)) > 0.99:
|
||||
up_vec = Vector3.RIGHT
|
||||
# Set rotation first, which resets scale
|
||||
grapple_rope.transform.basis = Basis.looking_at(dir, up_vec) * Basis.from_euler(Vector3(PI/2.0, 0, 0))
|
||||
# Then apply the scale
|
||||
grapple_rope.scale = Vector3(1.0, dist, 1.0)
|
||||
else:
|
||||
if grapple_rope:
|
||||
grapple_rope.visible = false
|
||||
|
||||
# Swing sound
|
||||
if sm.current_state == "grapple" and sm.player.velocity.length() > 5.0:
|
||||
if synced_movement_state == "grapple" and velocity.length() > 5.0:
|
||||
grapple_swing_player.volume_db = lerpf(grapple_swing_player.volume_db, -15.0, _delta * 10.0)
|
||||
else:
|
||||
grapple_swing_player.volume_db = lerpf(grapple_swing_player.volume_db, -80.0, _delta * 15.0)
|
||||
|
||||
# Raw 2D input
|
||||
var raw_input := Input.get_vector("move_left", "move_right", "move_forward", "move_back")
|
||||
|
||||
# Transform input direction by player yaw so movement is camera-relative
|
||||
var forward := -global_transform.basis.z
|
||||
forward.y = 0.0
|
||||
forward = forward.normalized()
|
||||
var right := global_transform.basis.x
|
||||
right.y = 0.0
|
||||
right = right.normalized()
|
||||
|
||||
var world_dir := (forward * (-raw_input.y) + right * raw_input.x)
|
||||
if world_dir.length_squared() > 1.0:
|
||||
world_dir = world_dir.normalized()
|
||||
|
||||
sm.input_dir = raw_input
|
||||
sm.wish_dir_world = world_dir
|
||||
# Update humanoid model animation state (runs everywhere)
|
||||
|
||||
# Update humanoid model animation state
|
||||
var humanoid = get_node_or_null("HumanoidModel")
|
||||
@@ -873,44 +916,53 @@ func _on_respawn_pressed() -> void:
|
||||
|
||||
@rpc("any_peer", "call_local", "reliable")
|
||||
func rpc_respawn(spawn_pos: Vector3) -> void:
|
||||
is_dead = false
|
||||
health = max_health
|
||||
shield = 0.0
|
||||
|
||||
if is_instance_valid(ragdoll_instance):
|
||||
if ragdoll_instance.has_meta("spring_arm"):
|
||||
var sa = ragdoll_instance.get_meta("spring_arm")
|
||||
if is_instance_valid(sa):
|
||||
sa.queue_free()
|
||||
ragdoll_instance.queue_free()
|
||||
if is_dead:
|
||||
is_dead = false
|
||||
health = max_health
|
||||
shield = max_shield
|
||||
time_since_last_damage = 0.0
|
||||
|
||||
# Restore camera parent
|
||||
if is_instance_valid(camera) and is_instance_valid(head_pivot):
|
||||
var p = camera.get_parent()
|
||||
if p != head_pivot:
|
||||
p.remove_child(camera)
|
||||
head_pivot.add_child(camera)
|
||||
camera.position = Vector3.ZERO
|
||||
camera.rotation = Vector3.ZERO
|
||||
if is_instance_valid(ragdoll_instance):
|
||||
if ragdoll_instance.has_meta("spring_arm"):
|
||||
var sa = ragdoll_instance.get_meta("spring_arm")
|
||||
if is_instance_valid(sa):
|
||||
sa.queue_free()
|
||||
ragdoll_instance.queue_free()
|
||||
|
||||
visible = true
|
||||
process_mode = Node.PROCESS_MODE_INHERIT
|
||||
|
||||
# Restore camera parent
|
||||
if is_instance_valid(camera) and is_instance_valid(head_pivot):
|
||||
var p = camera.get_parent()
|
||||
if p != head_pivot:
|
||||
p.remove_child(camera)
|
||||
head_pivot.add_child(camera)
|
||||
camera.position = Vector3.ZERO
|
||||
camera.rotation = Vector3.ZERO
|
||||
|
||||
# Re-enable movement state machine
|
||||
if _machine:
|
||||
_machine.process_mode = Node.PROCESS_MODE_INHERIT
|
||||
|
||||
# Re-enable movement state machine
|
||||
if _machine:
|
||||
_machine.process_mode = Node.PROCESS_MODE_INHERIT
|
||||
|
||||
# Re-enable collision
|
||||
var col = get_node_or_null("CollisionShape3D")
|
||||
if col:
|
||||
col.set_deferred("disabled", false)
|
||||
|
||||
var visual_model = get_node_or_null("HumanoidModel")
|
||||
if visual_model:
|
||||
visual_model.visible = true
|
||||
# Re-enable collision
|
||||
var col = get_node_or_null("CollisionShape3D")
|
||||
if col:
|
||||
col.set_deferred("disabled", false)
|
||||
|
||||
var visual_model = get_node_or_null("HumanoidModel")
|
||||
if visual_model:
|
||||
visual_model.visible = true
|
||||
|
||||
# Server sets the actual position to sync to everyone
|
||||
if multiplayer.is_server() or not multiplayer.has_multiplayer_peer() or multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||
position = spawn_pos
|
||||
velocity = Vector3.ZERO
|
||||
|
||||
# Local client resets UI and rebuilds weapons for their view
|
||||
if is_multiplayer_authority():
|
||||
if death_screen:
|
||||
death_screen.visible = false
|
||||
position = spawn_pos
|
||||
velocity = Vector3.ZERO
|
||||
|
||||
# Optional: reset weapons
|
||||
var wman = camera.get_node_or_null("WeaponManager") if camera else null
|
||||
|
||||
Reference in New Issue
Block a user