fix: a lot of networking bugs
This commit is contained in:
+34
-19
@@ -293,25 +293,40 @@ func _spawn_player(pid: int) -> CharacterBody3D:
|
|||||||
# Use a slightly random spawn position to avoid exact overlapping
|
# Use a slightly random spawn position to avoid exact overlapping
|
||||||
player.position = Vector3(randf_range(-2, 2), 2.0, randf_range(-2, 2))
|
player.position = Vector3(randf_range(-2, 2), 2.0, randf_range(-2, 2))
|
||||||
|
|
||||||
# Network Synchronizer
|
# Server Synchronizer (Host is the ground truth)
|
||||||
var sync = MultiplayerSynchronizer.new()
|
var server_sync = MultiplayerSynchronizer.new()
|
||||||
sync.name = "MultiplayerSynchronizer"
|
server_sync.name = "ServerSynchronizer"
|
||||||
sync.set_multiplayer_authority(pid)
|
server_sync.set_multiplayer_authority(1) # Host always controls these
|
||||||
var rep_config = SceneReplicationConfig.new()
|
var server_rep_config = SceneReplicationConfig.new()
|
||||||
rep_config.add_property(":position")
|
server_rep_config.add_property(":position")
|
||||||
rep_config.add_property(":rotation")
|
server_rep_config.add_property(":synced_movement_state")
|
||||||
rep_config.add_property("HeadPivot:rotation")
|
server_rep_config.add_property(":synced_movement_speed")
|
||||||
rep_config.add_property(":synced_movement_state")
|
server_rep_config.add_property(":synced_is_crouching")
|
||||||
rep_config.add_property(":synced_movement_speed")
|
server_rep_config.add_property(":health")
|
||||||
rep_config.add_property(":synced_is_crouching")
|
server_rep_config.add_property(":shield")
|
||||||
rep_config.add_property(":synced_weapon_path")
|
server_rep_config.add_property(":is_dead")
|
||||||
rep_config.add_property(":synced_loadout_p1")
|
server_rep_config.add_property(":synced_grapple_point")
|
||||||
rep_config.add_property(":synced_loadout_p2")
|
server_rep_config.add_property(":synced_is_grapple_shooting")
|
||||||
rep_config.add_property(":synced_loadout_sp")
|
server_sync.replication_config = server_rep_config
|
||||||
rep_config.add_property(":synced_loadout_melee")
|
player.add_child(server_sync)
|
||||||
rep_config.add_property(":synced_loadout_ready")
|
|
||||||
sync.replication_config = rep_config
|
# Client Synchronizer (Client dictates their aim and loadout setup)
|
||||||
player.add_child(sync)
|
var client_sync = MultiplayerSynchronizer.new()
|
||||||
|
client_sync.name = "MultiplayerSynchronizer" # Keep original name for compatibility if needed elsewhere
|
||||||
|
client_sync.set_multiplayer_authority(pid)
|
||||||
|
var client_rep_config = SceneReplicationConfig.new()
|
||||||
|
client_rep_config.add_property(":rotation")
|
||||||
|
client_rep_config.add_property("HeadPivot:rotation")
|
||||||
|
client_rep_config.add_property(":synced_weapon_path")
|
||||||
|
client_rep_config.add_property(":synced_loadout_p1")
|
||||||
|
client_rep_config.add_property(":synced_loadout_p2")
|
||||||
|
client_rep_config.add_property(":synced_loadout_sp")
|
||||||
|
client_rep_config.add_property(":synced_loadout_melee")
|
||||||
|
client_rep_config.add_property(":synced_loadout_ready")
|
||||||
|
client_rep_config.add_property(":synced_is_targeting")
|
||||||
|
client_rep_config.add_property(":synced_homing_target_pos")
|
||||||
|
client_sync.replication_config = client_rep_config
|
||||||
|
player.add_child(client_sync)
|
||||||
|
|
||||||
# Collision shape
|
# Collision shape
|
||||||
var col_shape := CollisionShape3D.new()
|
var col_shape := CollisionShape3D.new()
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ func _ready() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _physics_process(delta: float) -> void:
|
func _physics_process(delta: float) -> void:
|
||||||
|
if player and not player.multiplayer.is_server(): return
|
||||||
|
|
||||||
# Update coyote time
|
# Update coyote time
|
||||||
if on_ground:
|
if on_ground:
|
||||||
coyote_timer = params.coyote_time
|
coyote_timer = params.coyote_time
|
||||||
|
|||||||
@@ -54,7 +54,13 @@ var grapple_rope: MeshInstance3D
|
|||||||
var synced_movement_state: String = "idle"
|
var synced_movement_state: String = "idle"
|
||||||
var synced_movement_speed: float = 0.0
|
var synced_movement_speed: float = 0.0
|
||||||
var synced_is_crouching: bool = false
|
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_p1: String = ""
|
||||||
var synced_loadout_p2: 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)
|
tracer.look_at(target, up_vec)
|
||||||
|
|
||||||
func _spawn_remote_projectile(origin: Vector3, fire_dir: Vector3, weapon_name: String) -> void:
|
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()
|
var proj = Node3D.new()
|
||||||
proj.name = "RemoteProjectile"
|
proj.name = "RemoteProjectile"
|
||||||
|
|
||||||
@@ -339,7 +355,7 @@ func _spawn_remote_projectile(origin: Vector3, fire_dir: Vector3, weapon_name: S
|
|||||||
light.omni_range = 4.0
|
light.omni_range = 4.0
|
||||||
proj.add_child(light)
|
proj.add_child(light)
|
||||||
else:
|
else:
|
||||||
# Rocket-shaped mesh (cone + cylinder)
|
# Generic rocket fallback
|
||||||
var mesh_inst = MeshInstance3D.new()
|
var mesh_inst = MeshInstance3D.new()
|
||||||
var p_mesh = CylinderMesh.new()
|
var p_mesh = CylinderMesh.new()
|
||||||
p_mesh.top_radius = 0.0
|
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)
|
mesh_inst.rotation.x = deg_to_rad(-90)
|
||||||
proj.add_child(mesh_inst)
|
proj.add_child(mesh_inst)
|
||||||
|
|
||||||
# Smoke trail
|
|
||||||
var smoke = GPUParticles3D.new()
|
var smoke = GPUParticles3D.new()
|
||||||
var smoke_mat = ParticleProcessMaterial.new()
|
var smoke_mat = ParticleProcessMaterial.new()
|
||||||
smoke_mat.direction = Vector3(0, 0, 1)
|
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
|
health = 0.0
|
||||||
die(impulse)
|
die(impulse)
|
||||||
|
|
||||||
if is_multiplayer_authority():
|
if multiplayer.is_server():
|
||||||
if get_node_or_null("/root/NetworkManager") and multiplayer.has_multiplayer_peer():
|
if get_node_or_null("/root/NetworkManager") and multiplayer.has_multiplayer_peer():
|
||||||
var victim_id = multiplayer.get_unique_id()
|
var victim_id = multiplayer.get_unique_id()
|
||||||
var killer_id = attacker_id
|
var killer_id = attacker_id
|
||||||
@@ -512,12 +527,56 @@ func _ensure_machine() -> MovementStateMachine:
|
|||||||
_machine = null
|
_machine = null
|
||||||
return 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:
|
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 speed = velocity.length()
|
||||||
var wind_factor = clampf((speed - 10.0) / 25.0, 0.0, 1.0)
|
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)
|
wind_player.volume_db = lerpf(-40.0, -15.0, wind_factor)
|
||||||
if not wind_player.playing and wind_factor > 0.0:
|
if not wind_player.playing and wind_factor > 0.0:
|
||||||
wind_player.play()
|
wind_player.play()
|
||||||
@@ -526,67 +585,51 @@ func _physics_process(_delta: float) -> void:
|
|||||||
if not sm:
|
if not sm:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Poll input in _physics_process so it's synchronized with the state machine tick
|
# Only the Server sets input values to the StateMachine for evaluation
|
||||||
sm.input_jump_just_pressed = Input.is_action_just_pressed("jump")
|
if multiplayer.is_server():
|
||||||
sm.input_jump_pressed = Input.is_action_pressed("jump")
|
sm.input_dir = _client_input_dir
|
||||||
sm.input_crouch = Input.is_action_pressed("crouch")
|
sm.wish_dir_world = _client_wish_dir_world
|
||||||
sm.input_dash = Input.is_action_just_pressed("dash")
|
sm.input_jump_pressed = _client_jump
|
||||||
|
sm.input_jump_just_pressed = _client_jump_just_pressed
|
||||||
sm.input_grapple = Input.is_action_pressed("grapple")
|
sm.input_crouch = _client_crouch
|
||||||
sm.input_grapple_just_pressed = Input.is_action_just_pressed("grapple")
|
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
|
# Update synced properties for the grapple
|
||||||
if (sm.current_state == "grapple" or sm.is_grapple_shooting) and camera:
|
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
|
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 start_pos = camera.global_position + camera.global_transform.basis * Vector3(0.3, -0.3, -0.15)
|
||||||
var end_pos = sm.grapple_point
|
var end_pos = synced_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 dist = start_pos.distance_to(end_pos)
|
var dist = start_pos.distance_to(end_pos)
|
||||||
if dist > 0.01:
|
if dist > 0.01:
|
||||||
grapple_rope.global_position = (start_pos + end_pos) / 2.0
|
grapple_rope.global_position = (start_pos + end_pos) / 2.0
|
||||||
# Look at end point
|
|
||||||
var up_vec = Vector3.UP
|
var up_vec = Vector3.UP
|
||||||
var dir = (end_pos - start_pos).normalized()
|
var dir = (end_pos - start_pos).normalized()
|
||||||
if abs(dir.dot(up_vec)) > 0.99:
|
if abs(dir.dot(up_vec)) > 0.99:
|
||||||
up_vec = Vector3.RIGHT
|
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))
|
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)
|
grapple_rope.scale = Vector3(1.0, dist, 1.0)
|
||||||
else:
|
else:
|
||||||
if grapple_rope:
|
if grapple_rope:
|
||||||
grapple_rope.visible = false
|
grapple_rope.visible = false
|
||||||
|
|
||||||
# Swing sound
|
if synced_movement_state == "grapple" and velocity.length() > 5.0:
|
||||||
if sm.current_state == "grapple" and sm.player.velocity.length() > 5.0:
|
|
||||||
grapple_swing_player.volume_db = lerpf(grapple_swing_player.volume_db, -15.0, _delta * 10.0)
|
grapple_swing_player.volume_db = lerpf(grapple_swing_player.volume_db, -15.0, _delta * 10.0)
|
||||||
else:
|
else:
|
||||||
grapple_swing_player.volume_db = lerpf(grapple_swing_player.volume_db, -80.0, _delta * 15.0)
|
grapple_swing_player.volume_db = lerpf(grapple_swing_player.volume_db, -80.0, _delta * 15.0)
|
||||||
|
|
||||||
# Raw 2D input
|
# Update humanoid model animation state (runs everywhere)
|
||||||
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
|
# Update humanoid model animation state
|
||||||
var humanoid = get_node_or_null("HumanoidModel")
|
var humanoid = get_node_or_null("HumanoidModel")
|
||||||
@@ -873,44 +916,53 @@ func _on_respawn_pressed() -> void:
|
|||||||
|
|
||||||
@rpc("any_peer", "call_local", "reliable")
|
@rpc("any_peer", "call_local", "reliable")
|
||||||
func rpc_respawn(spawn_pos: Vector3) -> void:
|
func rpc_respawn(spawn_pos: Vector3) -> void:
|
||||||
is_dead = false
|
if is_dead:
|
||||||
health = max_health
|
is_dead = false
|
||||||
shield = 0.0
|
health = max_health
|
||||||
|
shield = max_shield
|
||||||
if is_instance_valid(ragdoll_instance):
|
time_since_last_damage = 0.0
|
||||||
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()
|
|
||||||
|
|
||||||
# Restore camera parent
|
if is_instance_valid(ragdoll_instance):
|
||||||
if is_instance_valid(camera) and is_instance_valid(head_pivot):
|
if ragdoll_instance.has_meta("spring_arm"):
|
||||||
var p = camera.get_parent()
|
var sa = ragdoll_instance.get_meta("spring_arm")
|
||||||
if p != head_pivot:
|
if is_instance_valid(sa):
|
||||||
p.remove_child(camera)
|
sa.queue_free()
|
||||||
head_pivot.add_child(camera)
|
ragdoll_instance.queue_free()
|
||||||
camera.position = Vector3.ZERO
|
|
||||||
camera.rotation = Vector3.ZERO
|
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
|
# Re-enable collision
|
||||||
if _machine:
|
var col = get_node_or_null("CollisionShape3D")
|
||||||
_machine.process_mode = Node.PROCESS_MODE_INHERIT
|
if col:
|
||||||
|
col.set_deferred("disabled", false)
|
||||||
# Re-enable collision
|
|
||||||
var col = get_node_or_null("CollisionShape3D")
|
var visual_model = get_node_or_null("HumanoidModel")
|
||||||
if col:
|
if visual_model:
|
||||||
col.set_deferred("disabled", false)
|
visual_model.visible = true
|
||||||
|
|
||||||
var visual_model = get_node_or_null("HumanoidModel")
|
# Server sets the actual position to sync to everyone
|
||||||
if visual_model:
|
if multiplayer.is_server() or not multiplayer.has_multiplayer_peer() or multiplayer.multiplayer_peer is OfflineMultiplayerPeer:
|
||||||
visual_model.visible = true
|
position = spawn_pos
|
||||||
|
velocity = Vector3.ZERO
|
||||||
|
|
||||||
|
# Local client resets UI and rebuilds weapons for their view
|
||||||
if is_multiplayer_authority():
|
if is_multiplayer_authority():
|
||||||
if death_screen:
|
if death_screen:
|
||||||
death_screen.visible = false
|
death_screen.visible = false
|
||||||
position = spawn_pos
|
|
||||||
velocity = Vector3.ZERO
|
|
||||||
|
|
||||||
# Optional: reset weapons
|
# Optional: reset weapons
|
||||||
var wman = camera.get_node_or_null("WeaponManager") if camera else null
|
var wman = camera.get_node_or_null("WeaponManager") if camera else null
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func _on_hit(result: Dictionary) -> void:
|
|||||||
if not result.collider.has_method("take_damage"):
|
if not result.collider.has_method("take_damage"):
|
||||||
if result.collider is StaticBody3D or result.collider is CSGShape3D:
|
if result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||||
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5)
|
ImpactSpawner.spawn(get_tree(), "crater", result.position, result.normal, explosion_radius * 1.5)
|
||||||
elif not owner_player or owner_player.is_multiplayer_authority():
|
elif owner_player and owner_player.multiplayer.is_server():
|
||||||
result.collider.take_damage(damage, result.position, owner_player)
|
result.collider.take_damage(damage, result.position, owner_player)
|
||||||
_explode(result.position)
|
_explode(result.position)
|
||||||
destroy()
|
destroy()
|
||||||
@@ -200,11 +200,11 @@ func _explode(pos: Vector3) -> void:
|
|||||||
if col == owner_player and not can_self_damage:
|
if col == owner_player and not can_self_damage:
|
||||||
# Skip self damage, but we still applied the knockback!
|
# Skip self damage, but we still applied the knockback!
|
||||||
pass
|
pass
|
||||||
elif not owner_player or owner_player.is_multiplayer_authority():
|
elif owner_player and owner_player.multiplayer.is_server():
|
||||||
col.take_damage(final_damage, target_pos, owner_player, dir * final_knockback)
|
col.take_damage(final_damage, target_pos, owner_player, dir * final_knockback)
|
||||||
|
|
||||||
# Broadcast Visual Explosion Effect to other peers
|
# Broadcast Visual Explosion Effect to other peers
|
||||||
if owner_player and owner_player.has_method("server_play_explosion") and owner_player.multiplayer.has_multiplayer_peer() and owner_player.multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
if owner_player and owner_player.is_multiplayer_authority() and owner_player.has_method("server_play_explosion") and owner_player.multiplayer.has_multiplayer_peer() and owner_player.multiplayer.multiplayer_peer is not OfflineMultiplayerPeer:
|
||||||
owner_player.server_play_explosion.rpc_id(1, pos, explosion_radius)
|
owner_player.server_play_explosion.rpc_id(1, pos, explosion_radius)
|
||||||
|
|
||||||
# Local Visual Effect
|
# Local Visual Effect
|
||||||
|
|||||||
@@ -102,12 +102,19 @@ func _physics_process(delta: float) -> void:
|
|||||||
|
|
||||||
if ignited and is_instance_valid(weapon_source):
|
if ignited and is_instance_valid(weapon_source):
|
||||||
var target_dir: Vector3 = initial_aim_dir
|
var target_dir: Vector3 = initial_aim_dir
|
||||||
var is_painting = weapon_source.get("is_targeting")
|
var is_painting = false
|
||||||
|
var target: Vector3 = Vector3.ZERO
|
||||||
|
|
||||||
if is_painting != null and is_painting:
|
if weapon_source.get("player") and weapon_source.player:
|
||||||
var target: Vector3 = weapon_source.get("homing_target_pos")
|
is_painting = weapon_source.player.synced_is_targeting
|
||||||
if target != null and target != Vector3.ZERO:
|
target = weapon_source.player.synced_homing_target_pos
|
||||||
target_dir = (target - global_position).normalized()
|
else:
|
||||||
|
is_painting = weapon_source.get("is_targeting")
|
||||||
|
if is_painting != null and is_painting:
|
||||||
|
target = weapon_source.get("homing_target_pos")
|
||||||
|
|
||||||
|
if is_painting and target != Vector3.ZERO:
|
||||||
|
target_dir = (target - global_position).normalized()
|
||||||
|
|
||||||
if target_dir != Vector3.ZERO:
|
if target_dir != Vector3.ZERO:
|
||||||
var current_dir = velocity.normalized()
|
var current_dir = velocity.normalized()
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func _on_hit(result: Dictionary) -> void:
|
|||||||
final_damage = lerpf(damage, min_damage, falloff_factor)
|
final_damage = lerpf(damage, min_damage, falloff_factor)
|
||||||
|
|
||||||
if result.collider.has_method("take_damage"):
|
if result.collider.has_method("take_damage"):
|
||||||
if not owner_player or owner_player.is_multiplayer_authority():
|
if owner_player and owner_player.multiplayer.is_server():
|
||||||
result.collider.take_damage(final_damage, result.position, owner_player)
|
result.collider.take_damage(final_damage, result.position, owner_player)
|
||||||
elif result.collider is StaticBody3D or result.collider is CSGShape3D:
|
elif result.collider is StaticBody3D or result.collider is CSGShape3D:
|
||||||
var size = 0.4 if impact_type == "plasma" else 0.15
|
var size = 0.4 if impact_type == "plasma" else 0.15
|
||||||
|
|||||||
@@ -90,10 +90,19 @@ func _process(delta: float) -> void:
|
|||||||
homing_target_pos = target
|
homing_target_pos = target
|
||||||
(targeting_laser.mesh as CylinderMesh).height = 300.0
|
(targeting_laser.mesh as CylinderMesh).height = 300.0
|
||||||
targeting_laser.position.z = -150.0
|
targeting_laser.position.z = -150.0
|
||||||
|
|
||||||
|
if player and player.is_multiplayer_authority():
|
||||||
|
player.synced_is_targeting = true
|
||||||
|
player.synced_homing_target_pos = homing_target_pos
|
||||||
|
else:
|
||||||
|
if player and player.is_multiplayer_authority():
|
||||||
|
player.synced_is_targeting = false
|
||||||
|
|
||||||
func unequip() -> void:
|
func unequip() -> void:
|
||||||
is_targeting = false
|
is_targeting = false
|
||||||
targeting_laser.visible = false
|
targeting_laser.visible = false
|
||||||
|
if player and player.is_multiplayer_authority():
|
||||||
|
player.synced_is_targeting = false
|
||||||
|
|
||||||
# Override fire to shoot 8 rockets at once
|
# Override fire to shoot 8 rockets at once
|
||||||
func _fire() -> void:
|
func _fire() -> void:
|
||||||
|
|||||||
Reference in New Issue
Block a user