feat(character): 16.9 - char_knock, a knock-back the walker carries (never into a trunk); a landing keeps only the air's speed along the body's facing

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 02:07:33 +03:00
parent 3fae64f975
commit 5c50755652
5 changed files with 39 additions and 3 deletions

View file

@ -71,6 +71,7 @@ still stick.
| `char_hold(reason)`, `char_release(reason)` (`CHAR_HOLD_SCREEN`, `CHAR_HOLD_STAGE`) | holds by reason: two holders cannot release each other |
| `char_board(x, z, yaw)`, `char_ride_at(x, y, z, yaw, speed)`, `char_step_off(x, z)` | carried by something (`CharacterBody.riding`/`ride`) |
| `char_safe_spot(x, z, from_y, max_depth) -> bool`, `body_safe_x()`, `body_safe_z()` | somewhere to put a body down |
| `char_knock(vx, vz, up)` | knocked off its feet (a shove): the walker carries it and it lands where the world lets it |
| `char_stand_at(x, z, from_y)` | the ground with a collider's top when it is a step, for a put-down |
| `char_camera(dt)`, `char_camera_third(dt)`, `char_look(yaw, pitch)`, `char_look_pitch`, `char_orbit(dist)`, `char_lock_orbit`, `char_set_fps`, `char_fps_toggle() -> CHAR_EYES_*` | the camera; read it back with `body_eye_x/y/z/yaw/pitch()` and `body_eyes_now()` |
| `body_x()`, `body_y()`, `body_z()`, `body_yaw()`, `body_speed()`, `body_moving()`, `body_run()`, `body_phase()`, `body_time()`, `body_water()`, `body_airborne()`, `body_vy()`, `body_landing()`, `body_steep()`, `body_step()`, `body_sitting()`, `body_sit_mode()`, `body_sit_t()`, `body_swimming()`, `body_diving()`, `body_depth()`, `body_breath()`, `body_breath_max()`, `body_floating()`, `body_swim_phase()`, `body_can_run()`, `body_held()`, `body_fps()`, `body_cam_yaw()`, `body_cam_pitch()`, `body_cam_dist()` | the queries |

View file

@ -67,6 +67,7 @@ export state CharacterState {
chr_air: bool = false
chr_air_vx: float = 0.0
chr_air_vz: float = 0.0
chr_knock: float = 0.0 # a knock-back's speed up, handed to the walker on the next step
chr_land: float = 0.0 # seconds of the landing's crouch left
chr_steep: float = 0.0 # seconds of "that was too steep" left to say
chr_can_run: bool = true

View file

@ -240,6 +240,20 @@ program CharacterTest {
expect(not body_airborne(character_st))
}
test "a knock-back carries the body off its feet, and a post stops it" (character_st: mut CharacterState) {
fresh(character_st, 0.0, -2.0, 0.0)
char_knock(character_st, 0.0, 5.0, 3.0)
char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0)
expect(body_airborne(character_st))
for i in 0 .. 120 { char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(not body_airborne(character_st))
expect(body_z(character_st) > 0.5)
fresh(character_st, -3.5, 0.0, 0.0)
char_knock(character_st, -8.0, 0.0, 2.0)
for i in 0 .. 120 { char_update(character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(body_x(character_st) > -5.2)
}
test "the safe spot clears a collider, keeps out of deep water, and allows a wade when asked" (character_st: mut CharacterState) {
expect(char_safe_spot(character_st, -6.0, 0.0, 0.0, 0.0))
let dx = body_safe_x(character_st) + 6.0

View file

@ -123,3 +123,14 @@ export function char_step_off(character_st: mut CharacterState, x: float, z: flo
character_st.chr_z = z
character_st.chr_y = CharacterGround.height(x, z)
}
# knocked off its feet at (vx, vz) across and `up` upward (a bear's shove): the walker carries it,
# so it lands wherever the world lets it - never inside a trunk. Not while swimming or riding.
export function char_knock(character_st: mut CharacterState, vx: float, vz: float, up: float) -> void {
if character_st.chr_swim or CharacterBody.riding() { return }
character_st.chr_sit = false
character_st.chr_air = true
character_st.chr_air_vx = vx
character_st.chr_air_vz = vz
character_st.chr_knock = up
}

View file

@ -14,7 +14,12 @@ function chr_walk(character_st: mut CharacterState, fx: float, fz: float, dx: fl
var vx = fx * character_st.chr_speed
var vz = fz * character_st.chr_speed
var up = 0.0
if character_st.chr_air {
if character_st.chr_knock > 0.0 {
up = character_st.chr_knock
character_st.chr_knock = 0.0
vx = character_st.chr_air_vx
vz = character_st.chr_air_vz
} else if character_st.chr_air {
character_st.chr_air_vx = character_st.chr_air_vx + dx * (4.0 * dt)
character_st.chr_air_vz = character_st.chr_air_vz + dz * (4.0 * dt)
vx = character_st.chr_air_vx
@ -60,6 +65,10 @@ function chr_walked(character_st: mut CharacterState, g: int, jumped: bool, dt:
function chr_landed(character_st: mut CharacterState) -> void {
character_st.chr_land = 0.22
character_st.chr_speed = Math.sqrt(character_st.chr_air_vx * character_st.chr_air_vx + character_st.chr_air_vz * character_st.chr_air_vz)
chr_say(character_st, CHAR_LANDED, character_st.chr_speed)
let carried = Math.sqrt(character_st.chr_air_vx * character_st.chr_air_vx + character_st.chr_air_vz * character_st.chr_air_vz)
# the walk goes on only as fast as the air was carrying it the way it faces (a knock from the
# front does not walk it back into what knocked it)
let along = -Math.sin(character_st.chr_yaw) * character_st.chr_air_vx - Math.cos(character_st.chr_yaw) * character_st.chr_air_vz
character_st.chr_speed = Math.max(along, 0.0)
chr_say(character_st, CHAR_LANDED, carried)
}