- Quake-style air-strafe accelerate (real speed gain from strafing; external speed from dash/rockets never clamped) - Wall run: keeps entry momentum (incl. upward carry), accelerates along the wall instead of hard-setting velocity, gravity fades in over the run - Slide: slope physics (gravity projected along floor accelerates downhill), flat-ground decel instead of multiplicative friction, entry boost, direct slide->wallrun and slide->dash transitions - Dash: cooldown 10s -> 2s, per-player (was a static shared across instances), momentum fully kept on exit, FOV punch event - Grapple: taut pendulum with active rope control (W reels in, S pays out), release keeps swing energy - Wall climb: carries upward momentum in, jump-off kick, shared vault helper - Crouch capsule: single owner in the machine, smoothly lerped, ceiling check; camera eye height follows via crouch factor (no more head snapping) - Landing: machine emits 'land' events scaled by impact; camera dip + pitched down thud; footsteps get pitch variation - Camera rig: event-driven (land dip, dash FOV kick, vault pitch impulse), slide roll tilt - Model: Jump vs Fall split by vertical velocity, Land one-shot on heavy landings, wall-run body lean (synced to remotes via synced_wall_side) Co-Authored-By: Claude Fable 5 <[email protected]>
111 lines
4.0 KiB
GDScript
111 lines
4.0 KiB
GDScript
extends Node
|
|
class_name StateGrapple
|
|
|
|
## Swing grapple with active rope control:
|
|
## - the rope acts as a hard pendulum constraint at its current length
|
|
## - holding forward reels in (rope shortens, converts to speed)
|
|
## - holding back pays rope out (up to the original latch length)
|
|
## - strafe input steers tangentially around the swing sphere
|
|
## - jump detaches with a boost along your current motion
|
|
|
|
var machine: MovementStateMachine
|
|
var params: MovementParams:
|
|
get: return machine.params
|
|
|
|
var _rope_length: float = 0.0
|
|
|
|
|
|
func enter(_data: Dictionary = {}) -> void:
|
|
machine.on_ground = false
|
|
machine.wall_normal = Vector3.ZERO
|
|
machine.wall_side = 0.0
|
|
machine.register_chain_mechanic("grapple")
|
|
_rope_length = machine.grapple_length
|
|
|
|
|
|
func exit() -> void:
|
|
pass
|
|
|
|
|
|
func update(delta: float) -> void:
|
|
var player := machine.player
|
|
var vel: Vector3 = player.velocity
|
|
|
|
# If player releases the button, disconnect
|
|
if not machine.input_grapple:
|
|
machine.switch_to("air")
|
|
return
|
|
|
|
var grapple_pos: Vector3 = machine.grapple_point
|
|
var to_point: Vector3 = grapple_pos - player.global_position
|
|
var current_dist: float = to_point.length()
|
|
if current_dist < 0.01:
|
|
machine.switch_to("air")
|
|
return
|
|
var dir_to_point: Vector3 = to_point / current_dist
|
|
|
|
# Jump to detach and get a boost
|
|
if machine.input_jump_just_pressed and machine.jump_cooldown_timer <= 0.0:
|
|
vel.y = maxf(vel.y, params.jump_velocity)
|
|
# Boost along current motion so the release keeps the swing's energy
|
|
var h_vel := Vector3(vel.x, 0.0, vel.z)
|
|
if h_vel.length_squared() > 0.1:
|
|
var boost := h_vel.normalized() * params.grapple_jump_boost * 0.5
|
|
vel.x += boost.x
|
|
vel.z += boost.z
|
|
# Plus a steer boost toward held direction
|
|
var h_look = machine.wish_dir_world
|
|
if h_look.length_squared() > 0.1:
|
|
var h_boost = h_look.normalized() * params.grapple_jump_boost * 0.5
|
|
vel.x += h_boost.x
|
|
vel.z += h_boost.z
|
|
|
|
player.velocity = vel
|
|
machine.jump_cooldown_timer = params.jump_cooldown
|
|
machine.register_chain_mechanic("grapple_jump")
|
|
machine.switch_to("air")
|
|
if player.jump_player:
|
|
player.jump_player.play()
|
|
return
|
|
|
|
# Apply gravity
|
|
vel.y -= params.gravity * delta
|
|
|
|
# Base reel: constant gentle pull toward the hook
|
|
vel += dir_to_point * params.grapple_pull_force * delta
|
|
|
|
# ── Active rope control ───────────────────────────────────────────────
|
|
var fwd_input := -machine.input_dir.y # +1 holding forward, -1 back
|
|
if fwd_input > 0.1:
|
|
# Reel in: shorten rope and pull hard — converts to swing speed
|
|
vel += dir_to_point * params.grapple_reel_force * fwd_input * delta
|
|
_rope_length = maxf(_rope_length - 8.0 * fwd_input * delta, 2.0)
|
|
elif fwd_input < -0.1:
|
|
# Pay out rope back toward the original latch length
|
|
_rope_length = minf(_rope_length + 8.0 * -fwd_input * delta, machine.grapple_length)
|
|
|
|
# Keep the working length taut to where we actually are (swinging inside
|
|
# the sphere shortens the constraint, giving crisp Tarzan arcs)
|
|
_rope_length = minf(_rope_length, maxf(current_dist, 2.0))
|
|
|
|
# ── Pendulum constraint at the current rope length ────────────────────
|
|
if current_dist > _rope_length:
|
|
# Kill outward radial velocity (the rope is taut and inextensible)
|
|
var radial_vel = vel.project(dir_to_point)
|
|
if radial_vel.dot(dir_to_point) < 0:
|
|
vel -= radial_vel
|
|
# Spring correction toward the sphere surface
|
|
var diff = current_dist - _rope_length
|
|
vel += dir_to_point * diff * params.grapple_spring_strength * delta
|
|
|
|
# ── Tangential steering (strafe around the swing sphere) ──────────────
|
|
var strafe_input := machine.input_dir.x
|
|
if absf(strafe_input) > 0.1:
|
|
var right := player.global_transform.basis.x
|
|
var tangent_steer := right - right.project(dir_to_point)
|
|
if tangent_steer.length_squared() > 0.01:
|
|
vel += tangent_steer.normalized() * params.grapple_air_control * strafe_input * delta
|
|
|
|
player.velocity = vel
|
|
player.move_and_slide()
|