extends Node class_name StateSlide var machine: MovementStateMachine var params: MovementParams: get: return machine.params var elapsed: float = 0.0 var _saved_capsule_height: float = 0.0 var _slide_direction: Vector3 = Vector3.ZERO func enter(_data: Dictionary = {}) -> void: elapsed = 0.0 machine.register_chain_mechanic("slide") if machine.player.slide_player and not machine.player.slide_player.playing: machine.player.slide_player.play() # Save original capsule height and halve it var shape: CapsuleShape3D = _get_capsule() if shape: _saved_capsule_height = shape.height shape.height = _saved_capsule_height * 0.5 # Slide in the direction of current velocity (momentum-based) var hvel := Vector3(machine.player.velocity.x, 0.0, machine.player.velocity.z) if hvel.length_squared() > 0.01: _slide_direction = hvel.normalized() else: # Fallback: player forward _slide_direction = -machine.player.global_transform.basis.z _slide_direction.y = 0.0 _slide_direction = _slide_direction.normalized() # Set initial slide velocity var slide_speed := maxf(hvel.length(), params.slide_speed) machine.player.velocity.x = _slide_direction.x * machine.get_effective_speed(slide_speed) machine.player.velocity.z = _slide_direction.z * machine.get_effective_speed(slide_speed) func exit() -> void: machine.slide_cooldown_timer = params.slide_cooldown if machine.player.slide_player: machine.player.slide_player.stop() # Restore original capsule height var shape: CapsuleShape3D = _get_capsule() if shape and _saved_capsule_height > 0.0: shape.height = _saved_capsule_height func update(delta: float) -> void: elapsed += delta var player := machine.player if player.slide_player and not player.slide_player.playing: player.slide_player.play() var vel: Vector3 = player.velocity # ── Apply friction to horizontal velocity ───────────────────────────── vel.x *= pow(params.slide_friction, delta * 10.0) vel.z *= pow(params.slide_friction, delta * 10.0) # ── Allow slight steering ───────────────────────────────────────────── var wish_dir := machine.wish_dir_world if wish_dir.length_squared() > 0.01: wish_dir = wish_dir.normalized() var hvel := Vector3(vel.x, 0.0, vel.z) var current_speed := hvel.length() # Slowly bend velocity towards wish_dir var steer_speed = 3.0 * delta var new_hvel = hvel.lerp(wish_dir * current_speed, steer_speed) vel.x = new_hvel.x vel.z = new_hvel.z if new_hvel.length_squared() > 0.01: _slide_direction = new_hvel.normalized() # ── Gravity (for slopes) ────────────────────────────────────────────── if not player.is_on_floor(): vel.y -= params.gravity * delta else: vel.y = -2.0 # Strong floor snap player.velocity = vel player.move_and_slide() # ── Update ground status ────────────────────────────────────────────── if player.is_on_floor(): machine.on_ground = true machine.current_jump_count = 0 else: machine.on_ground = false # ── End conditions ──────────────────────────────────────────────────── var hspeed := Vector3(player.velocity.x, 0.0, player.velocity.z).length() # Slide ended: too slow if hspeed < params.slide_min_speed: machine.switch_to("ground") return # Player released crouch if not machine.input_crouch: machine.switch_to("ground") return # Fell off edge if not machine.on_ground and machine.coyote_timer <= 0.0: machine.switch_to("air") return # Jump out of slide if machine.input_jump_just_pressed and machine.jump_cooldown_timer <= 0.0: player.velocity.y = params.jump_velocity var hvel := Vector3(player.velocity.x, 0.0, player.velocity.z) if hvel.length_squared() > 0.01: var dir := hvel.normalized() var current_speed := hvel.length() var jump_speed := maxf(current_speed, minf(current_speed + params.slide_jump_speed_boost, params.bunny_hop_speed_cap)) player.velocity.x = dir.x * jump_speed player.velocity.z = dir.z * jump_speed machine.current_jump_count = 1 machine.jump_cooldown_timer = params.jump_cooldown machine.on_ground = false machine.register_chain_mechanic("slide_jump") machine.switch_to("air") return func _get_capsule() -> CapsuleShape3D: for child in machine.player.get_children(): if child is CollisionShape3D and child.shape is CapsuleShape3D: return child.shape as CapsuleShape3D return null