extends Node class_name StateGround var machine: MovementStateMachine var params: MovementParams: get: return machine.params var _footstep_timer: float = 0.0 func enter(_data: Dictionary = {}) -> void: machine.on_ground = true machine.current_jump_count = 0 # Clear wall tilt on landing var rig = _get_camera_rig() if rig: rig.clear_wall_tilt() # Ensure capsule is correct when entering ground state _update_capsule_height() func exit() -> void: pass func update(delta: float) -> void: var player := machine.player var vel: Vector3 = player.velocity # ── Jump (coyote time + jump buffer) ────────────────────────────────── if machine.input_jump_just_pressed and machine.coyote_timer > 0.0 and machine.jump_cooldown_timer <= 0.0: player.velocity.y = params.jump_velocity machine.current_jump_count = 1 machine.on_ground = false machine.coyote_timer = 0.0 machine.register_chain_mechanic("jump") machine.jump_cooldown_timer = params.jump_cooldown if player.jump_player: player.jump_player.play() machine.switch_to("air") return # Jump buffer: player pressed jump just before landing if machine.on_ground and machine.jump_buffer_time > 0.0 and machine.jump_cooldown_timer <= 0.0: player.velocity.y = params.jump_velocity machine.current_jump_count = 1 machine.on_ground = false machine.jump_buffer_time = 0.0 machine.register_chain_mechanic("jump") machine.jump_cooldown_timer = params.jump_cooldown if player.jump_player: player.jump_player.play() machine.switch_to("air") return # ── Slide (crouch while moving fast enough) ─────────────────────────── if machine.input_crouch and machine.slide_cooldown_timer <= 0.0: var hspeed := Vector3(player.velocity.x, 0.0, player.velocity.z).length() if hspeed > params.slide_min_speed: machine.switch_to("slide") return # ── Determine target speed ──────────────────────────────────────────── var wish_dir: Vector3 = machine.wish_dir_world var speed := params.walk_speed if machine.input_crouch: speed = params.crouch_speed var effective_speed := machine.get_effective_speed(speed) # ── Horizontal movement (exponential interpolation for smoothness) ─── var hvel := Vector3(vel.x, 0.0, vel.z) var target_vel := wish_dir * effective_speed if wish_dir.length_squared() > 0.0: hvel = hvel.lerp(target_vel, 1.0 - exp(-params.ground_acceleration * delta)) else: hvel = hvel.lerp(Vector3.ZERO, 1.0 - exp(-params.ground_deceleration * delta)) vel.x = hvel.x vel.z = hvel.z # ── Gravity (correct direction: downward) ───────────────────────────── if not player.is_on_floor(): vel.y -= params.gravity * delta else: # Snap to floor: small downward velocity keeps is_on_floor() stable # ONLY if we aren't being launched upward by an impulse! if vel.y <= 0.0: vel.y = -0.5 player.velocity = vel player.move_and_slide() _update_capsule_height() var current_hspeed = Vector2(player.velocity.x, player.velocity.z).length() if current_hspeed > 1.0: _footstep_timer -= delta if _footstep_timer <= 0.0: if player.footstep_player: player.footstep_player.play() _footstep_timer = max(0.2, 3.0 / current_hspeed) else: _footstep_timer = 0.0 # ── Update ground status ────────────────────────────────────────────── if player.is_on_floor(): machine.on_ground = true machine.current_jump_count = 0 else: machine.on_ground = false # ── Launch or Falling off ledge → air state ─────────────────────────── if not machine.on_ground: if player.velocity.y > 0.0 or machine.coyote_timer <= 0.0: machine.switch_to("air") return # ── Wall run check ──────────────────────────────────────────────────── var wall_n := machine.detect_wall_horizontal() if wall_n != Vector3.ZERO and machine.input_dir.length() > 0.1: if not player.is_on_floor(): machine.switch_to("wall_run") return # ── Dash ────────────────────────────────────────────────────────────── if machine.input_dash: var hspeed := Vector3(player.velocity.x, 0.0, player.velocity.z).length() if hspeed > 0.1 or machine.input_dir.length() > 0.1: machine.switch_to("dash") return func _get_camera_rig(): if machine.player and machine.player.has_node("HeadPivot"): return machine.player.get_node("HeadPivot") return null func _update_capsule_height() -> void: var shape = _get_capsule() if shape: if machine.input_crouch: shape.height = machine.original_capsule_height * 0.5 else: shape.height = machine.original_capsule_height 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