|
|
|
@@ -39,6 +39,7 @@ var footstep_player: AudioStreamPlayer
|
|
|
|
|
var footstep_streams: Array = []
|
|
|
|
|
var land_player: AudioStreamPlayer
|
|
|
|
|
var _remote_footstep_timer: float = 0.0
|
|
|
|
|
var _slide_surface_event: String = ""
|
|
|
|
|
|
|
|
|
|
# Networked one-shot animation actions (reload/throw): the owner bumps the
|
|
|
|
|
# sequence; remote peers play the named action when it changes.
|
|
|
|
@@ -50,6 +51,7 @@ var _was_reloading: bool = false
|
|
|
|
|
## own third-person model (remote shooters go through server_play_fire_effects).
|
|
|
|
|
var _last_local_ammo: int = -1
|
|
|
|
|
var dash_player: AudioStreamPlayer
|
|
|
|
|
var _jet_vfx: PlayerJetVFX = null
|
|
|
|
|
var recent_attackers: Dictionary = {} # attacker_id: timestamp
|
|
|
|
|
var hit_player: AudioStreamPlayer
|
|
|
|
|
var slide_player: AudioStreamPlayer
|
|
|
|
@@ -146,6 +148,7 @@ func _ready() -> void:
|
|
|
|
|
camera.add_child(flashlight)
|
|
|
|
|
|
|
|
|
|
_setup_audio()
|
|
|
|
|
_setup_jet_vfx()
|
|
|
|
|
_setup_grapple()
|
|
|
|
|
_setup_grenade_trajectory()
|
|
|
|
|
|
|
|
|
@@ -215,7 +218,13 @@ func set_third_person(on: bool) -> void:
|
|
|
|
|
return
|
|
|
|
|
third_person = on
|
|
|
|
|
if is_instance_valid(_tp_camera):
|
|
|
|
|
_tp_camera.fov = SettingsManager.world_fov # stay in sync with settings
|
|
|
|
|
# Tool-script regression runners compile this controller before autoload
|
|
|
|
|
# singletons register, so a direct `SettingsManager` identifier makes the
|
|
|
|
|
# whole movement state machine fail to compile. Resolve the same singleton
|
|
|
|
|
# at runtime; level scenes and `-s` tests now share one safe path.
|
|
|
|
|
var settings = get_tree().root.get_node_or_null("SettingsManager")
|
|
|
|
|
if settings:
|
|
|
|
|
_tp_camera.fov = settings.world_fov
|
|
|
|
|
_tp_camera.current = on
|
|
|
|
|
if is_instance_valid(camera):
|
|
|
|
|
camera.current = not on
|
|
|
|
@@ -223,6 +232,8 @@ func set_third_person(on: bool) -> void:
|
|
|
|
|
var visual = get_visual_model()
|
|
|
|
|
if visual and visual.has_method("set_owner_visible"):
|
|
|
|
|
visual.set_owner_visible(on)
|
|
|
|
|
if is_instance_valid(_jet_vfx):
|
|
|
|
|
_jet_vfx.set_render_enabled(on)
|
|
|
|
|
# Hide the first-person weapon viewmodel in third person (it would float in
|
|
|
|
|
# the middle of the screen); the third-person weapon on the model shows.
|
|
|
|
|
if is_instance_valid(camera):
|
|
|
|
@@ -260,6 +271,21 @@ func get_visual_model() -> Node3D:
|
|
|
|
|
return skinned_model
|
|
|
|
|
return get_node_or_null("HumanoidModel")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _setup_jet_vfx() -> void:
|
|
|
|
|
_jet_vfx = PlayerJetVFX.new()
|
|
|
|
|
_jet_vfx.name = "JetVFX"
|
|
|
|
|
add_child(_jet_vfx)
|
|
|
|
|
_jet_vfx.set_render_enabled(not is_multiplayer_authority() or third_person)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _update_jet_vfx(state: String, move_dir: Vector2) -> void:
|
|
|
|
|
if not is_instance_valid(_jet_vfx):
|
|
|
|
|
return
|
|
|
|
|
_jet_vfx.set_visual_model(get_visual_model())
|
|
|
|
|
_jet_vfx.set_render_enabled(not is_multiplayer_authority() or third_person)
|
|
|
|
|
_jet_vfx.set_dash(state == "dash", move_dir)
|
|
|
|
|
|
|
|
|
|
## Swap the visual model to match a skin id. GLB skins replace the procedural
|
|
|
|
|
## model entirely; color skins tint the procedural model.
|
|
|
|
|
func _apply_skin_model(skin_id: String) -> void:
|
|
|
|
@@ -306,88 +332,150 @@ func _hide_remote_weapons() -> void:
|
|
|
|
|
|
|
|
|
|
func _setup_audio() -> void:
|
|
|
|
|
footstep_player = AudioStreamPlayer.new()
|
|
|
|
|
footstep_player.bus = "SFX"
|
|
|
|
|
for path in ["res://assets/sounds/footstep.wav", "res://assets/sounds/footstep_01.wav",
|
|
|
|
|
"res://assets/sounds/footstep_02.wav", "res://assets/sounds/footstep_03.wav",
|
|
|
|
|
"res://assets/sounds/footstep_04.wav"]:
|
|
|
|
|
if ResourceLoader.exists(path):
|
|
|
|
|
footstep_streams.append(load(path))
|
|
|
|
|
footstep_player.stream = footstep_streams[0] if not footstep_streams.is_empty() else null
|
|
|
|
|
_configure_audio_player(footstep_player, "footstep_concrete")
|
|
|
|
|
add_child(footstep_player)
|
|
|
|
|
|
|
|
|
|
land_player = AudioStreamPlayer.new()
|
|
|
|
|
land_player.bus = "SFX"
|
|
|
|
|
land_player.stream = load("res://assets/sounds/land.wav")
|
|
|
|
|
_configure_audio_player(land_player, "land")
|
|
|
|
|
add_child(land_player)
|
|
|
|
|
|
|
|
|
|
jump_player = AudioStreamPlayer.new()
|
|
|
|
|
jump_player.bus = "SFX"
|
|
|
|
|
jump_player.stream = load("res://assets/sounds/jump.wav")
|
|
|
|
|
_configure_audio_player(jump_player, "jump")
|
|
|
|
|
add_child(jump_player)
|
|
|
|
|
|
|
|
|
|
double_jump_player = AudioStreamPlayer.new()
|
|
|
|
|
double_jump_player.bus = "SFX"
|
|
|
|
|
jump_player.bus = "SFX"
|
|
|
|
|
double_jump_player.stream = load("res://assets/sounds/double_jump.wav")
|
|
|
|
|
_configure_audio_player(double_jump_player, "double_jump")
|
|
|
|
|
add_child(double_jump_player)
|
|
|
|
|
|
|
|
|
|
vault_player = AudioStreamPlayer.new()
|
|
|
|
|
vault_player.bus = "SFX"
|
|
|
|
|
vault_player.stream = load("res://assets/sounds/vault.wav")
|
|
|
|
|
_configure_audio_player(vault_player, "vault")
|
|
|
|
|
add_child(vault_player)
|
|
|
|
|
|
|
|
|
|
dash_player = AudioStreamPlayer.new()
|
|
|
|
|
dash_player.bus = "SFX"
|
|
|
|
|
dash_player.stream = load("res://assets/sounds/dash.wav")
|
|
|
|
|
dash_player.volume_db = -5.0
|
|
|
|
|
_configure_audio_player(dash_player, "dash")
|
|
|
|
|
add_child(dash_player)
|
|
|
|
|
|
|
|
|
|
hit_player = AudioStreamPlayer.new()
|
|
|
|
|
hit_player.bus = "SFX"
|
|
|
|
|
hit_player.stream = load("res://assets/sounds/hit_confirm.wav")
|
|
|
|
|
_configure_audio_player(hit_player, "hit_confirm")
|
|
|
|
|
hit_player.max_polyphony = 8
|
|
|
|
|
add_child(hit_player)
|
|
|
|
|
|
|
|
|
|
slide_player = AudioStreamPlayer.new()
|
|
|
|
|
slide_player.bus = "SFX"
|
|
|
|
|
slide_player.stream = load("res://assets/sounds/slide.wav")
|
|
|
|
|
slide_player.volume_db = -10.0
|
|
|
|
|
_configure_audio_player(slide_player, "movement_slide_concrete")
|
|
|
|
|
add_child(slide_player)
|
|
|
|
|
|
|
|
|
|
wallrun_player = AudioStreamPlayer.new()
|
|
|
|
|
wallrun_player.bus = "SFX"
|
|
|
|
|
wallrun_player.stream = load("res://assets/sounds/wallrun.wav")
|
|
|
|
|
wallrun_player.volume_db = -10.0
|
|
|
|
|
_configure_audio_player(wallrun_player, "wallrun")
|
|
|
|
|
add_child(wallrun_player)
|
|
|
|
|
|
|
|
|
|
wind_player = AudioStreamPlayer.new()
|
|
|
|
|
wind_player.bus = "Wind"
|
|
|
|
|
wind_player.stream = load("res://assets/sounds/wind.wav")
|
|
|
|
|
_configure_audio_player(wind_player, "wind")
|
|
|
|
|
wind_player.volume_db = -80.0 # Start silent
|
|
|
|
|
add_child(wind_player)
|
|
|
|
|
wind_player.play()
|
|
|
|
|
|
|
|
|
|
grapple_shoot_player = AudioStreamPlayer.new()
|
|
|
|
|
grapple_shoot_player.bus = "SFX"
|
|
|
|
|
grapple_shoot_player.stream = load("res://assets/sounds/dash.wav")
|
|
|
|
|
grapple_shoot_player.volume_db = -5.0
|
|
|
|
|
grapple_shoot_player.pitch_scale = 1.5
|
|
|
|
|
_configure_audio_player(grapple_shoot_player, "grapple_launch")
|
|
|
|
|
add_child(grapple_shoot_player)
|
|
|
|
|
|
|
|
|
|
grapple_latch_player = AudioStreamPlayer.new()
|
|
|
|
|
grapple_latch_player.bus = "SFX"
|
|
|
|
|
grapple_latch_player.stream = load("res://assets/sounds/hit_confirm.wav")
|
|
|
|
|
grapple_latch_player.pitch_scale = 0.8
|
|
|
|
|
_configure_audio_player(grapple_latch_player, "grapple_latch")
|
|
|
|
|
add_child(grapple_latch_player)
|
|
|
|
|
|
|
|
|
|
grapple_swing_player = AudioStreamPlayer.new()
|
|
|
|
|
grapple_swing_player.bus = "SFX"
|
|
|
|
|
grapple_swing_player.stream = load("res://assets/sounds/wind.wav")
|
|
|
|
|
grapple_swing_player.pitch_scale = 1.5
|
|
|
|
|
_configure_audio_player(grapple_swing_player, "grapple_reel")
|
|
|
|
|
grapple_swing_player.volume_db = -80.0
|
|
|
|
|
add_child(grapple_swing_player)
|
|
|
|
|
grapple_swing_player.play()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _configure_audio_player(player: Node, event: String,
|
|
|
|
|
fallback_path: String = "", volume_db_offset: float = 0.0) -> void:
|
|
|
|
|
# See set_third_person(): direct autoload identifiers are unavailable while
|
|
|
|
|
# `-s` regression scripts compile this controller's dependency graph.
|
|
|
|
|
var audio_manager = get_tree().root.get_node_or_null("AudioManager")
|
|
|
|
|
if audio_manager:
|
|
|
|
|
audio_manager.configure_player(
|
|
|
|
|
player, event, fallback_path, volume_db_offset)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Material under/alongside the controller, using the same collider metadata as
|
|
|
|
|
## gunshot occlusion. Builders already identify wood/metal/glass/brick, so
|
|
|
|
|
## movement audio stays correct without map-specific node lists.
|
|
|
|
|
func acoustic_surface(direction: Vector3 = Vector3.DOWN,
|
|
|
|
|
distance: float = 1.8) -> String:
|
|
|
|
|
if not is_inside_tree() or direction.length_squared() < 0.001:
|
|
|
|
|
return "concrete"
|
|
|
|
|
var start := global_position + Vector3.UP * 0.12
|
|
|
|
|
var query := PhysicsRayQueryParameters3D.create(
|
|
|
|
|
start, start + direction.normalized() * distance, 1)
|
|
|
|
|
query.exclude = [get_rid()]
|
|
|
|
|
var hit := get_world_3d().direct_space_state.intersect_ray(query)
|
|
|
|
|
if hit.is_empty():
|
|
|
|
|
return "concrete"
|
|
|
|
|
return Acoustics.classify(hit.collider)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _surface_family(material: String) -> String:
|
|
|
|
|
match material:
|
|
|
|
|
"metal":
|
|
|
|
|
return "metal"
|
|
|
|
|
"wood":
|
|
|
|
|
return "wood"
|
|
|
|
|
"glass":
|
|
|
|
|
return "glass"
|
|
|
|
|
_:
|
|
|
|
|
# Brick, stone, sand and untagged structural surfaces all use the
|
|
|
|
|
# concrete family; each still varies sample and pitch.
|
|
|
|
|
return "concrete"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func surface_event(prefix: String, direction: Vector3 = Vector3.DOWN,
|
|
|
|
|
distance: float = 1.8) -> String:
|
|
|
|
|
return "%s_%s" % [prefix, _surface_family(
|
|
|
|
|
acoustic_surface(direction, distance))]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func play_footstep(speed: float) -> void:
|
|
|
|
|
if footstep_player == null:
|
|
|
|
|
return
|
|
|
|
|
var event := surface_event("footstep")
|
|
|
|
|
var speed_mix := clampf((speed - 1.0) / 10.0, 0.0, 1.0)
|
|
|
|
|
_configure_audio_player(footstep_player, event, "",
|
|
|
|
|
lerpf(-3.0, 0.5, speed_mix))
|
|
|
|
|
footstep_player.pitch_scale = lerpf(0.96, 1.045, speed_mix) \
|
|
|
|
|
* randf_range(0.985, 1.015)
|
|
|
|
|
footstep_player.play()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func update_slide_audio(speed: float) -> void:
|
|
|
|
|
if slide_player == null:
|
|
|
|
|
return
|
|
|
|
|
var event := surface_event("movement_slide")
|
|
|
|
|
if event != _slide_surface_event:
|
|
|
|
|
var was_playing := slide_player.playing
|
|
|
|
|
_configure_audio_player(slide_player, event)
|
|
|
|
|
_slide_surface_event = event
|
|
|
|
|
if was_playing:
|
|
|
|
|
slide_player.play()
|
|
|
|
|
var intensity := clampf((speed - 4.0) / 16.0, 0.0, 1.0)
|
|
|
|
|
slide_player.volume_db = lerpf(-24.0, -13.0, intensity)
|
|
|
|
|
slide_player.pitch_scale = lerpf(0.94, 1.02, intensity)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func update_wallrun_audio(speed: float, wall_normal: Vector3) -> void:
|
|
|
|
|
if wallrun_player == null:
|
|
|
|
|
return
|
|
|
|
|
var intensity := clampf((speed - 4.0) / 16.0, 0.0, 1.0)
|
|
|
|
|
# The same warm friction bed works on every wall; material changes its
|
|
|
|
|
# resonance subtly without ever turning metal walls into a piercing whine.
|
|
|
|
|
var material := acoustic_surface(-wall_normal, 1.3)
|
|
|
|
|
var material_pitch := 1.035 if material == "metal" else (
|
|
|
|
|
0.94 if material == "wood" else 1.0)
|
|
|
|
|
wallrun_player.volume_db = lerpf(-24.0, -13.0, intensity)
|
|
|
|
|
wallrun_player.pitch_scale = lerpf(0.95, 1.02, intensity) * material_pitch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _setup_speedlines() -> void:
|
|
|
|
|
_speedlines = ColorRect.new()
|
|
|
|
|
_speedlines.name = "SpeedLines"
|
|
|
|
@@ -646,7 +734,8 @@ func server_play_explosion(pos: Vector3, radius: float) -> void:
|
|
|
|
|
func rpc_play_explosion(pos: Vector3, radius: float) -> void:
|
|
|
|
|
if is_multiplayer_authority(): return
|
|
|
|
|
|
|
|
|
|
ExplosionVFX.spawn(get_tree(), pos, radius)
|
|
|
|
|
var pitch := clampf(1.06 - (radius - 2.5) * 0.035, 0.76, 1.12)
|
|
|
|
|
ExplosionVFX.spawn(get_tree(), pos, radius, pitch)
|
|
|
|
|
|
|
|
|
|
@rpc("any_peer", "call_local", "reliable")
|
|
|
|
|
func server_take_damage(amount: float, hit_pos: Vector3, attacker_id: int, weapon_name: String, impulse: Vector3) -> void:
|
|
|
|
@@ -900,8 +989,12 @@ func _physics_process(_delta: float) -> void:
|
|
|
|
|
machine.input_grapple_just_pressed = input_grapple_just
|
|
|
|
|
|
|
|
|
|
var speed = velocity.length()
|
|
|
|
|
var wind_factor = clampf((speed - 10.0) / 25.0, 0.0, 1.0)
|
|
|
|
|
wind_player.volume_db = lerpf(-40.0, -15.0, wind_factor)
|
|
|
|
|
var wind_factor = clampf((speed - 12.0) / 24.0, 0.0, 1.0)
|
|
|
|
|
var wind_target := -70.0 if wind_factor <= 0.001 \
|
|
|
|
|
else lerpf(-42.0, -15.0, pow(wind_factor, 0.82))
|
|
|
|
|
wind_player.volume_db = lerpf(wind_player.volume_db, wind_target,
|
|
|
|
|
1.0 - exp(-4.5 * _delta))
|
|
|
|
|
wind_player.pitch_scale = lerpf(1.0, 1.24, wind_factor)
|
|
|
|
|
if not wind_player.playing and wind_factor > 0.0:
|
|
|
|
|
wind_player.play()
|
|
|
|
|
|
|
|
|
@@ -927,7 +1020,13 @@ func _physics_process(_delta: float) -> void:
|
|
|
|
|
# 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
|
|
|
|
|
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 grapple_visual := get_visual_model()
|
|
|
|
|
if synced_movement_state == "grapple" and grapple_visual \
|
|
|
|
|
and grapple_visual.has_method(
|
|
|
|
|
"get_grapple_hand_world_position"):
|
|
|
|
|
start_pos = grapple_visual.get_grapple_hand_world_position()
|
|
|
|
|
var end_pos = synced_grapple_point
|
|
|
|
|
var dist = start_pos.distance_to(end_pos)
|
|
|
|
|
if dist > 0.01:
|
|
|
|
@@ -943,9 +1042,13 @@ func _physics_process(_delta: float) -> void:
|
|
|
|
|
grapple_rope.visible = false
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
var grapple_factor := clampf((velocity.length() - 5.0) / 18.0, 0.0, 1.0)
|
|
|
|
|
grapple_swing_player.volume_db = lerpf(grapple_swing_player.volume_db,
|
|
|
|
|
lerpf(-28.0, -15.0, grapple_factor), 1.0 - exp(-8.0 * _delta))
|
|
|
|
|
grapple_swing_player.pitch_scale = lerpf(0.92, 1.08, grapple_factor)
|
|
|
|
|
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, 1.0 - exp(-12.0 * _delta))
|
|
|
|
|
|
|
|
|
|
# Local player's aim-down-sights state (drives the model's weapon raise).
|
|
|
|
|
synced_is_ads = _read_ads()
|
|
|
|
@@ -954,16 +1057,21 @@ func _physics_process(_delta: float) -> void:
|
|
|
|
|
var visual = get_visual_model()
|
|
|
|
|
if visual:
|
|
|
|
|
var h_speed = Vector2(velocity.x, velocity.z).length()
|
|
|
|
|
visual.update_state(sm.current_state, h_speed, sm.input_crouch)
|
|
|
|
|
if visual.has_method("set_locomotion"):
|
|
|
|
|
var d := _local_move_dir()
|
|
|
|
|
visual.set_locomotion(d.x, d.y, 1.0 if synced_is_ads else 0.0)
|
|
|
|
|
# Direction must be present before update_state drives the BlendSpace;
|
|
|
|
|
# otherwise the first movement frame still samples last frame's Idle.
|
|
|
|
|
if visual.has_method("set_wall_side"):
|
|
|
|
|
visual.set_wall_side(sm.wall_side)
|
|
|
|
|
if visual.has_method("set_wall_run_motion"):
|
|
|
|
|
visual.set_wall_run_motion(velocity)
|
|
|
|
|
visual.update_state(sm.current_state, h_speed, sm.input_crouch)
|
|
|
|
|
if visual.has_method("set_dancing"):
|
|
|
|
|
visual.set_dancing(synced_is_dancing, synced_dance_index)
|
|
|
|
|
if visual.has_method("set_grapple_target") and sm.current_state == "grapple":
|
|
|
|
|
visual.set_grapple_target(synced_grapple_point)
|
|
|
|
|
visual.set_grapple_target(synced_grapple_point, velocity)
|
|
|
|
|
_update_jet_vfx(sm.current_state, _local_move_dir())
|
|
|
|
|
|
|
|
|
|
# Publish state for remote peers
|
|
|
|
|
synced_movement_state = sm.current_state
|
|
|
|
@@ -1005,6 +1113,10 @@ func _on_movement_event(ev: String, data: Dictionary) -> void:
|
|
|
|
|
grapple_latch_player.play()
|
|
|
|
|
elif ev == "dash":
|
|
|
|
|
_speedline_burst = 1.0
|
|
|
|
|
elif ev == "double_jump":
|
|
|
|
|
_trigger_action("double_jump")
|
|
|
|
|
if is_instance_valid(_jet_vfx):
|
|
|
|
|
_jet_vfx.burst_double_jump()
|
|
|
|
|
elif ev == "land":
|
|
|
|
|
if land_player:
|
|
|
|
|
var heavy: bool = data.get("heavy", false)
|
|
|
|
@@ -1039,16 +1151,24 @@ func _process(delta: float) -> void:
|
|
|
|
|
|
|
|
|
|
var visual = get_visual_model()
|
|
|
|
|
if visual:
|
|
|
|
|
visual.update_state(synced_movement_state, synced_movement_speed, synced_is_crouching)
|
|
|
|
|
if visual.has_method("set_locomotion"):
|
|
|
|
|
var d := _local_move_dir()
|
|
|
|
|
visual.set_locomotion(d.x, d.y, 1.0 if synced_is_ads else 0.0)
|
|
|
|
|
if visual.has_method("set_wall_side"):
|
|
|
|
|
visual.set_wall_side(synced_wall_side)
|
|
|
|
|
if visual.has_method("set_wall_run_motion"):
|
|
|
|
|
visual.set_wall_run_motion(synced_velocity)
|
|
|
|
|
visual.update_state(
|
|
|
|
|
synced_movement_state,
|
|
|
|
|
synced_movement_speed,
|
|
|
|
|
synced_is_crouching,
|
|
|
|
|
)
|
|
|
|
|
_update_jet_vfx(synced_movement_state, _local_move_dir())
|
|
|
|
|
if visual.has_method("set_dancing"):
|
|
|
|
|
visual.set_dancing(synced_is_dancing, synced_dance_index)
|
|
|
|
|
if visual.has_method("set_grapple_target") and synced_movement_state == "grapple":
|
|
|
|
|
visual.set_grapple_target(synced_grapple_point)
|
|
|
|
|
visual.set_grapple_target(
|
|
|
|
|
synced_grapple_point, synced_velocity)
|
|
|
|
|
# Upper body follows the owner's synced camera pitch
|
|
|
|
|
if visual.has_method("set_aim_pitch") and head_pivot:
|
|
|
|
|
visual.set_aim_pitch(head_pivot.rotation.x)
|
|
|
|
@@ -1056,6 +1176,8 @@ func _process(delta: float) -> void:
|
|
|
|
|
if visual.has_method("play_action") and synced_action_seq != _last_action_seq:
|
|
|
|
|
_last_action_seq = synced_action_seq
|
|
|
|
|
visual.play_action(synced_action)
|
|
|
|
|
if synced_action == "double_jump" and is_instance_valid(_jet_vfx):
|
|
|
|
|
_jet_vfx.burst_double_jump()
|
|
|
|
|
# Check for weapon changes
|
|
|
|
|
if synced_weapon_path != "" and synced_weapon_path != visual.get_meta("current_weapon_path", ""):
|
|
|
|
|
visual.set_weapon(synced_weapon_path)
|
|
|
|
@@ -1068,8 +1190,11 @@ func _process(delta: float) -> void:
|
|
|
|
|
_remote_footstep_timer -= delta
|
|
|
|
|
if _remote_footstep_timer <= 0.0:
|
|
|
|
|
var am = get_tree().root.get_node_or_null("AudioManager")
|
|
|
|
|
if am and am.has_sound("footstep"):
|
|
|
|
|
am.play_3d("footstep", global_position + Vector3(0, -0.8, 0), -8.0)
|
|
|
|
|
var event := surface_event("footstep")
|
|
|
|
|
if am and am.has_sound(event):
|
|
|
|
|
am.play_3d(event, global_position + Vector3(0, -0.8, 0),
|
|
|
|
|
-4.0, lerpf(0.96, 1.04,
|
|
|
|
|
clampf(synced_movement_speed / 14.0, 0.0, 1.0)))
|
|
|
|
|
_remote_footstep_timer = maxf(0.2, 3.0 / synced_movement_speed)
|
|
|
|
|
else:
|
|
|
|
|
_remote_footstep_timer = 0.0
|
|
|
|
|