- Air: direct steering bends existing velocity toward input without changing its magnitude (air_steer_rate), on top of Quake accelerate with raised gains (air_strafe_accel 55->90, per-tick cap 1.2->2.5). No-input air drag nearly removed so held momentum carries. Steering skips opposing input to avoid flipping through zero. - Dash: charge system on the machine — dash_charges (2) spend instantly, each recharges in dash_cooldown (2 s). get_dash_cooldown_remaining() now reports time to the NEXT charge (0 while one is banked) so existing HUDs keep working; get_dash_charges() added for pips. - Tests: dash tests updated to the charge API + new coverage for spend-2-then-recharge; 11/11 pass, spawn smoke clean. Co-Authored-By: Claude Fable 5 <[email protected]>
161 lines
5.8 KiB
GDScript
161 lines
5.8 KiB
GDScript
extends Node
|
|
class_name StateSlide
|
|
|
|
## Momentum slide. Friction is a flat deceleration on level ground but gravity
|
|
## projected along the floor accelerates you downhill, so slopes are the fast
|
|
## route. The capsule stays low via the machine's crouch handling (slide counts
|
|
## as crouched); no capsule fiddling here.
|
|
|
|
var machine: MovementStateMachine
|
|
var params: MovementParams:
|
|
get: return machine.params
|
|
|
|
var elapsed: 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()
|
|
|
|
# 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()
|
|
|
|
# Entry speed: keep momentum, add a flat boost when actually moving fast on
|
|
# the ground (rewards slide-cancelling sprints without making crouch a brake)
|
|
var entry_speed := hvel.length()
|
|
if machine.player.is_on_floor() and entry_speed > params.walk_speed * 0.8:
|
|
entry_speed += params.slide_boost
|
|
entry_speed = maxf(entry_speed, params.slide_speed * 0.75)
|
|
machine.player.velocity.x = _slide_direction.x * machine.get_effective_speed(entry_speed)
|
|
machine.player.velocity.z = _slide_direction.z * machine.get_effective_speed(entry_speed)
|
|
|
|
|
|
func exit() -> void:
|
|
machine.slide_cooldown_timer = params.slide_cooldown
|
|
if machine.player.slide_player:
|
|
machine.player.slide_player.stop()
|
|
|
|
|
|
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
|
|
var hvel := Vector3(vel.x, 0.0, vel.z)
|
|
var hspeed := hvel.length()
|
|
|
|
# ── Slope physics: gravity projected along the floor plane ────────────
|
|
var on_slope := false
|
|
if player.is_on_floor():
|
|
var floor_n := player.get_floor_normal()
|
|
if floor_n.y < 0.999:
|
|
# Downhill direction on this slope
|
|
var downhill := (Vector3.DOWN - floor_n * Vector3.DOWN.dot(floor_n))
|
|
if downhill.length_squared() > 0.0001:
|
|
downhill = downhill.normalized()
|
|
var steepness := 1.0 - floor_n.y # 0 flat .. ~0.3 steep
|
|
var slope_pull := downhill * params.slide_slope_accel * steepness * 8.0
|
|
hvel += Vector3(slope_pull.x, 0.0, slope_pull.z) * delta
|
|
on_slope = downhill.dot(_slide_direction) > 0.1
|
|
|
|
# ── Friction: flat decel on level ground, nearly free downhill ────────
|
|
if hspeed > 0.01:
|
|
var friction := params.slide_friction_flat
|
|
if on_slope:
|
|
friction *= 0.15
|
|
var new_speed := maxf(hvel.length() - friction * delta, 0.0)
|
|
if hvel.length() > 0.001:
|
|
hvel = hvel.normalized() * new_speed
|
|
|
|
# ── Steering: bend velocity toward input without changing speed ───────
|
|
var wish_dir := machine.wish_dir_world
|
|
if wish_dir.length_squared() > 0.01 and hvel.length_squared() > 0.01:
|
|
wish_dir = wish_dir.normalized()
|
|
var speed_now := hvel.length()
|
|
var steered := hvel.lerp(wish_dir * speed_now, params.slide_steer_rate * delta)
|
|
if steered.length_squared() > 0.01:
|
|
hvel = steered.normalized() * speed_now
|
|
_slide_direction = hvel.normalized()
|
|
|
|
vel.x = hvel.x
|
|
vel.z = hvel.z
|
|
|
|
# ── Gravity (for slopes / edges) ──────────────────────────────────────
|
|
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 out_speed := Vector3(player.velocity.x, 0.0, player.velocity.z).length()
|
|
|
|
# Jump out of slide: momentum-preserving hop
|
|
if machine.input_jump_just_pressed and machine.jump_cooldown_timer <= 0.0:
|
|
var hv := Vector3(player.velocity.x, 0.0, player.velocity.z)
|
|
if hv.length_squared() > 0.01:
|
|
var dir := hv.normalized()
|
|
var jump_speed := maxf(out_speed, minf(out_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
|
|
player.velocity.y = params.jump_velocity
|
|
machine.current_jump_count = 1
|
|
machine.jump_cooldown_timer = params.jump_cooldown
|
|
machine.on_ground = false
|
|
machine.register_chain_mechanic("slide_jump")
|
|
machine.movement_event.emit("jump", {})
|
|
if player.jump_player:
|
|
player.jump_player.play()
|
|
machine.switch_to("air")
|
|
return
|
|
|
|
# Slide straight onto a wall → wall run (keeps the flow going)
|
|
if not player.is_on_floor() and machine.wall_cooldown_timer <= 0.0 and out_speed > params.slide_min_speed:
|
|
var wall_n := machine.detect_wall_horizontal()
|
|
if wall_n != Vector3.ZERO and abs(machine.wish_dir_world.dot(wall_n)) < 0.85:
|
|
machine.switch_to("wall_run")
|
|
return
|
|
|
|
# Dash out of slide
|
|
if machine.input_dash and machine.can_dash():
|
|
machine.switch_to("dash")
|
|
return
|
|
|
|
# Slide ended: too slow
|
|
if out_speed < 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
|