wip(0.S3): the packages migrated by ludic migrate state packages - every package test green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-25 14:02:21 +03:00
parent 1e8b5b0523
commit 07505e7ef2
294 changed files with 14996 additions and 14675 deletions

View file

@ -1,70 +1,70 @@
# camera.ludic - the orbit: a pivot at the chest easing after the body, the camera on its orbit
# behind and a share of the distance to the right (so the body sits left of the crosshair), lifted
# out of the ground and aimed back at the pivot. Or the eyes, with the gait's bob on them.
function chr_eyes_allowed() -> bool { return not CharacterBody.riding() and not chr_sit and CharacterBody.eyes_ok() }
function chr_eyes_allowed(character_st: CharacterState) -> bool { return not CharacterBody.riding() and not character_st.chr_sit and CharacterBody.eyes_ok() }
function chr_shoulder() -> float { return Math.clamp(chr_cam_dist * 0.30, 0.6, 1.9) }
function chr_shoulder(character_st: CharacterState) -> float { return Math.clamp(character_st.chr_cam_dist * 0.30, 0.6, 1.9) }
export function char_camera(dt: float) -> void {
if chr_fps and chr_eyes_allowed() {
chr_camera_eyes(dt)
export function char_camera(character_st: mut CharacterState, dt: float) -> void {
if character_st.chr_fps and chr_eyes_allowed(character_st) {
chr_camera_eyes(character_st, dt)
return
}
char_camera_third(dt)
char_camera_third(character_st, dt)
}
# over the shoulder, whatever the player plays in (a figure looked at from inside its head is not)
export function char_camera_third(dt: float) -> void {
chr_eyes_now = false
export function char_camera_third(character_st: mut CharacterState, dt: float) -> void {
character_st.chr_eyes_now = false
let k = Math.min(1.0, 10.0 * dt)
var ty = chr_y + 1.45
if chr_swim { ty = chr_y + 0.9 }
chr_piv_x = Math.lerp(chr_piv_x, chr_x, k)
chr_piv_y = Math.lerp(chr_piv_y, ty, k)
chr_piv_z = Math.lerp(chr_piv_z, chr_z, k)
let cyw = Math.cos(chr_cam_yaw)
let syw = Math.sin(chr_cam_yaw)
let cpt = Math.cos(chr_cam_pitch)
var ty = character_st.chr_y + 1.45
if character_st.chr_swim { ty = character_st.chr_y + 0.9 }
character_st.chr_piv_x = Math.lerp(character_st.chr_piv_x, character_st.chr_x, k)
character_st.chr_piv_y = Math.lerp(character_st.chr_piv_y, ty, k)
character_st.chr_piv_z = Math.lerp(character_st.chr_piv_z, character_st.chr_z, k)
let cyw = Math.cos(character_st.chr_cam_yaw)
let syw = Math.sin(character_st.chr_cam_yaw)
let cpt = Math.cos(character_st.chr_cam_pitch)
let fx = -(syw * cpt)
let fy = Math.sin(chr_cam_pitch)
let fy = Math.sin(character_st.chr_cam_pitch)
let fz = -(cyw * cpt)
let ax = chr_piv_x + cyw * chr_shoulder()
let az = chr_piv_z - syw * chr_shoulder()
let cx = ax - fx * chr_cam_dist
var cyy = chr_piv_y - fy * chr_cam_dist
let cz = az - fz * chr_cam_dist
let ax = character_st.chr_piv_x + cyw * chr_shoulder(character_st)
let az = character_st.chr_piv_z - syw * chr_shoulder(character_st)
let cx = ax - fx * character_st.chr_cam_dist
var cyy = character_st.chr_piv_y - fy * character_st.chr_cam_dist
let cz = az - fz * character_st.chr_cam_dist
cyy = Math.max(cyy, CharacterGround.bed(cx, cz) + 0.55)
# at the top of the water the camera is held clear of it; under it, it follows the dive
if chr_swim and not chr_dive and cyy < chr_water + 0.35 { cyy = chr_water + 0.35 }
if character_st.chr_swim and not character_st.chr_dive and cyy < character_st.chr_water + 0.35 { cyy = character_st.chr_water + 0.35 }
let mid_g = CharacterGround.bed((cx + ax) * 0.5, (cz + az) * 0.5) + 0.4
let mid_y = (cyy + chr_piv_y) * 0.5
let mid_y = (cyy + character_st.chr_piv_y) * 0.5
if mid_y < mid_g { cyy = cyy + (mid_g - mid_y) * 2.0 }
chr_eye_x = cx
chr_eye_y = cyy
chr_eye_z = cz
character_st.chr_eye_x = cx
character_st.chr_eye_y = cyy
character_st.chr_eye_z = cz
let ddx = ax - cx
let ddz = az - cz
chr_eye_yaw = Math.atan2(-ddx, -ddz)
chr_eye_pitch = Math.atan2(chr_piv_y - cyy, Math.sqrt(ddx * ddx + ddz * ddz))
character_st.chr_eye_yaw = Math.atan2(-ddx, -ddz)
character_st.chr_eye_pitch = Math.atan2(character_st.chr_piv_y - cyy, Math.sqrt(ddx * ddx + ddz * ddz))
}
# the eyes: the head's height, the walk's bob, and the look the player is already holding
function chr_camera_eyes(dt: float) -> void {
chr_eyes_now = true
function chr_camera_eyes(character_st: mut CharacterState, dt: float) -> void {
character_st.chr_eyes_now = true
var eye = 1.62
if chr_swim { eye = 0.42 }
if chr_dive { eye = 0.30 }
let moving = Math.clamp(chr_speed / 5.0, 0.0, 1.0)
let want = 0.035 * moving * (1.0 - Math.cos(chr_phase * 2.0))
chr_fps_bob = Math.lerp(chr_fps_bob, want, Math.min(1.0, 12.0 * dt))
if character_st.chr_swim { eye = 0.42 }
if character_st.chr_dive { eye = 0.30 }
let moving = Math.clamp(character_st.chr_speed / 5.0, 0.0, 1.0)
let want = 0.035 * moving * (1.0 - Math.cos(character_st.chr_phase * 2.0))
character_st.chr_fps_bob = Math.lerp(character_st.chr_fps_bob, want, Math.min(1.0, 12.0 * dt))
# never inside the ground on a steep step
let ey = Math.max(chr_y + eye + chr_fps_bob, CharacterGround.bed(chr_x, chr_z) + 0.30)
chr_piv_x = chr_x
chr_piv_y = ey
chr_piv_z = chr_z
chr_eye_x = chr_x
chr_eye_y = ey
chr_eye_z = chr_z
chr_eye_yaw = chr_cam_yaw
chr_eye_pitch = chr_cam_pitch
let ey = Math.max(character_st.chr_y + eye + character_st.chr_fps_bob, CharacterGround.bed(character_st.chr_x, character_st.chr_z) + 0.30)
character_st.chr_piv_x = character_st.chr_x
character_st.chr_piv_y = ey
character_st.chr_piv_z = character_st.chr_z
character_st.chr_eye_x = character_st.chr_x
character_st.chr_eye_y = ey
character_st.chr_eye_z = character_st.chr_z
character_st.chr_eye_yaw = character_st.chr_cam_yaw
character_st.chr_eye_pitch = character_st.chr_cam_pitch
}

View file

@ -10,25 +10,23 @@ export function char_stand_at(x: float, z: float, from_y: float) -> float {
# A step up and a slope up are different refusals. Within CHAR_STEP of the feet is a step, taken
# whatever the gradient into it (a 45 cm deck arrives in one frame); above that the boots decide,
# measured over CHAR_SLOPE_PROBE, so the frame rate never changes the answer.
export function char_slope_blocks(nh: float, fx: float, fz: float) -> bool {
if nh - chr_y > CHAR_STEP { return true }
let sx = chr_x + fx * CHAR_SLOPE_PROBE
let sz = chr_z + fz * CHAR_SLOPE_PROBE
return CharacterGround.height(sx, sz) - chr_y > CHAR_SLOPE_PROBE * CharacterGround.slope_limit()
export function char_slope_blocks(character_st: CharacterState, nh: float, fx: float, fz: float) -> bool {
if nh - character_st.chr_y > CHAR_STEP { return true }
let sx = character_st.chr_x + fx * CHAR_SLOPE_PROBE
let sz = character_st.chr_z + fz * CHAR_SLOPE_PROBE
return CharacterGround.height(sx, sz) - character_st.chr_y > CHAR_SLOPE_PROBE * CharacterGround.slope_limit()
}
# a body of radius r at (x, z) from y0 to y1, out of every collider: the resolved point is chr_out_*
var chr_out_x: float = 0.0
var chr_out_z: float = 0.0
function chr_push(x: float, z: float, r: float, y0: float, y1: float) -> bool {
chr_ask_x = x
chr_ask_z = z
function chr_push(character_st: mut CharacterState, x: float, z: float, r: float, y0: float, y1: float) -> bool {
character_st.chr_ask_x = x
character_st.chr_ask_z = z
let hit = CharacterGround.push(x, z, r, y0, y1)
chr_out_x = x
chr_out_z = z
character_st.chr_out_x = x
character_st.chr_out_z = z
if hit {
chr_out_x = CharacterGround.pushed_x()
chr_out_z = CharacterGround.pushed_z()
character_st.chr_out_x = CharacterGround.pushed_x()
character_st.chr_out_z = CharacterGround.pushed_z()
}
return hit
}

View file

@ -39,14 +39,70 @@ export port CharacterInput {
look_always: fn() -> bool = fn chr__no # the look is held: never settle behind
}
var chr_ask_x: float = 0.0
var chr_ask_z: float = 0.0
export state CharacterState {
chr_out_x: float = 0.0
chr_out_z: float = 0.0
chr_ask_x: float = 0.0
chr_ask_z: float = 0.0
chr_x: float = 0.0
chr_y: float = 0.0
chr_z: float = 0.0
chr_yaw: float = 0.0 # facing, radians; 0 faces -z
chr_speed: float = 0.0 # m/s along the facing
chr_phase: float = 0.0 # the gait: one cycle is two steps, advanced by ground covered
chr_move: float = 0.0 # 0 standing .. 1 moving
chr_run: float = 0.0 # 0 walking .. 1 running
chr_time: float = 0.0
chr_water: float = -10000.0 # the water line under the body, every update
chr_walk_speed: float = 3.2
chr_run_speed: float = 6.8
chr_vy: float = 0.0
chr_air: bool = false
chr_air_vx: float = 0.0
chr_air_vz: float = 0.0
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
chr_step: float = 0.0 # metres covered this frame
chr_sit: bool = false
chr_sit_mode: int = 0
chr_sit_t: float = 0.0 # 0 standing .. 1 sat, eased over a quarter second
chr_sit_x: float = 0.0 # the seat's point, walked to over the blend
chr_sit_z: float = 0.0
chr_sit_yaw: float = 0.0
chr_swim: bool = false
chr_dive: bool = false
chr_depth: float = 0.0 # metres below the surface
chr_breath: float = 0.0
chr_float: float = 0.0 # 0 swimming .. 1 floating
chr_swim_phase: float = 0.0
chr_edge_said: float = 0.0
chr_holds: int = CHAR_HOLD_SCREEN
chr_cam_yaw: float = 0.0
chr_cam_pitch: float = -0.157
chr_cam_dist: float = 4.2
chr_lock: bool = false # the orbit never settles behind on its own
chr_fps: bool = false
chr_fps_bob: float = 0.0
chr_piv_x: float = 0.0
chr_piv_y: float = 0.0
chr_piv_z: float = 0.0
chr_eye_x: float = 0.0 # where the camera is and where it looks, once computed
chr_eye_y: float = 0.0
chr_eye_z: float = 0.0
chr_eye_yaw: float = 0.0
chr_eye_pitch: float = 0.0
chr_eyes_now: bool = false # the last camera was the eyes: the figure is hidden
chr_safe_x: float = 0.0
chr_safe_z: float = 0.0
chr_fact_q: Queue<CharacterFact> = null
}
function chr__top_plain(x: float, z: float, r: float, from_y: float, step: float, base: float) -> float { return base }
function chr__bed_plain(x: float, z: float) -> float { return CharacterGround.height(x, z) }
function chr__dry(x: float, z: float) -> float { return -10000.0 }
function chr__no_push(x: float, z: float, r: float, y0: float, y1: float) -> bool { return false }
function chr__asked_x() -> float { return chr_ask_x }
function chr__asked_z() -> float { return chr_ask_z }
function chr__asked_x(character_st: CharacterState) -> float { return character_st.chr_ask_x }
function chr__asked_z(character_st: CharacterState) -> float { return character_st.chr_ask_z }
function chr__slope_plain(x: float, z: float) -> float {
let e = 2.0
let dx = CharacterGround.height(x + e, z) - CharacterGround.height(x - e, z)

View file

@ -1,48 +1,48 @@
# queries.ludic - what the rest of a game may ASK about the body: the only way out of its state
export function body_x() -> float { return chr_x }
export function body_y() -> float { return chr_y }
export function body_z() -> float { return chr_z }
export function body_yaw() -> float { return chr_yaw }
export function body_speed() -> float { return chr_speed }
export function body_moving() -> float { return chr_move }
export function body_run() -> float { return chr_run }
export function body_phase() -> float { return chr_phase }
export function body_time() -> float { return chr_time }
export function body_water() -> float { return chr_water }
export function body_airborne() -> bool { return chr_air }
export function body_vy() -> float { return chr_vy }
export function body_landing() -> float { return chr_land }
export function body_steep() -> float { return chr_steep }
export function body_step() -> float { return chr_step }
export function body_sitting() -> bool { return chr_sit }
export function body_sit_mode() -> int { return chr_sit_mode }
export function body_sit_t() -> float { return chr_sit_t }
export function body_swimming() -> bool { return chr_swim }
export function body_diving() -> bool { return chr_dive }
export function body_depth() -> float { return chr_depth }
export function body_breath() -> float { return chr_breath }
export function body_floating() -> float { return chr_float }
export function body_swim_phase() -> float { return chr_swim_phase }
export function body_can_run() -> bool { return chr_can_run }
export function body_walk_speed() -> float { return chr_walk_speed }
export function body_run_speed() -> float { return chr_run_speed }
export function body_held() -> bool { return chr_holds != 0 }
export function body_fps() -> bool { return chr_fps }
export function body_cam_yaw() -> float { return chr_cam_yaw }
export function body_cam_pitch() -> float { return chr_cam_pitch }
export function body_cam_dist() -> float { return chr_cam_dist }
export function body_x(character_st: CharacterState) -> float { return character_st.chr_x }
export function body_y(character_st: CharacterState) -> float { return character_st.chr_y }
export function body_z(character_st: CharacterState) -> float { return character_st.chr_z }
export function body_yaw(character_st: CharacterState) -> float { return character_st.chr_yaw }
export function body_speed(character_st: CharacterState) -> float { return character_st.chr_speed }
export function body_moving(character_st: CharacterState) -> float { return character_st.chr_move }
export function body_run(character_st: CharacterState) -> float { return character_st.chr_run }
export function body_phase(character_st: CharacterState) -> float { return character_st.chr_phase }
export function body_time(character_st: CharacterState) -> float { return character_st.chr_time }
export function body_water(character_st: CharacterState) -> float { return character_st.chr_water }
export function body_airborne(character_st: CharacterState) -> bool { return character_st.chr_air }
export function body_vy(character_st: CharacterState) -> float { return character_st.chr_vy }
export function body_landing(character_st: CharacterState) -> float { return character_st.chr_land }
export function body_steep(character_st: CharacterState) -> float { return character_st.chr_steep }
export function body_step(character_st: CharacterState) -> float { return character_st.chr_step }
export function body_sitting(character_st: CharacterState) -> bool { return character_st.chr_sit }
export function body_sit_mode(character_st: CharacterState) -> int { return character_st.chr_sit_mode }
export function body_sit_t(character_st: CharacterState) -> float { return character_st.chr_sit_t }
export function body_swimming(character_st: CharacterState) -> bool { return character_st.chr_swim }
export function body_diving(character_st: CharacterState) -> bool { return character_st.chr_dive }
export function body_depth(character_st: CharacterState) -> float { return character_st.chr_depth }
export function body_breath(character_st: CharacterState) -> float { return character_st.chr_breath }
export function body_floating(character_st: CharacterState) -> float { return character_st.chr_float }
export function body_swim_phase(character_st: CharacterState) -> float { return character_st.chr_swim_phase }
export function body_can_run(character_st: CharacterState) -> bool { return character_st.chr_can_run }
export function body_walk_speed(character_st: CharacterState) -> float { return character_st.chr_walk_speed }
export function body_run_speed(character_st: CharacterState) -> float { return character_st.chr_run_speed }
export function body_held(character_st: CharacterState) -> bool { return character_st.chr_holds != 0 }
export function body_fps(character_st: CharacterState) -> bool { return character_st.chr_fps }
export function body_cam_yaw(character_st: CharacterState) -> float { return character_st.chr_cam_yaw }
export function body_cam_pitch(character_st: CharacterState) -> float { return character_st.chr_cam_pitch }
export function body_cam_dist(character_st: CharacterState) -> float { return character_st.chr_cam_dist }
# the camera the last char_camera worked out, for the game to hand its renderer
export function body_eye_x() -> float { return chr_eye_x }
export function body_eye_y() -> float { return chr_eye_y }
export function body_eye_z() -> float { return chr_eye_z }
export function body_eye_yaw() -> float { return chr_eye_yaw }
export function body_eye_pitch() -> float { return chr_eye_pitch }
export function body_eyes_now() -> bool { return chr_eyes_now }
export function body_eye_x(character_st: CharacterState) -> float { return character_st.chr_eye_x }
export function body_eye_y(character_st: CharacterState) -> float { return character_st.chr_eye_y }
export function body_eye_z(character_st: CharacterState) -> float { return character_st.chr_eye_z }
export function body_eye_yaw(character_st: CharacterState) -> float { return character_st.chr_eye_yaw }
export function body_eye_pitch(character_st: CharacterState) -> float { return character_st.chr_eye_pitch }
export function body_eyes_now(character_st: CharacterState) -> bool { return character_st.chr_eyes_now }
# where char_safe_spot found room, after it answered true
export function body_safe_x() -> float { return chr_safe_x }
export function body_safe_z() -> float { return chr_safe_z }
export function body_safe_x(character_st: CharacterState) -> float { return character_st.chr_safe_x }
export function body_safe_z(character_st: CharacterState) -> float { return character_st.chr_safe_z }
# the air a full breath holds, with the lungs the game gives
export function body_breath_max() -> float { return CHAR_BREATH_MAX * CharacterBody.breath() }

View file

@ -2,11 +2,11 @@
# every landing asks. The wanted spot if it will do, else the nearest ring out that is clear of every
# collider, no deeper than max_depth, within CHAR_DROP of the height being left, and on ground
# gentle enough to walk off - a spot you can stand on but cannot leave is not a safe spot.
function chr_safe_ok(x: float, z: float, from_y: float, max_depth: float) -> bool {
function chr_safe_ok(character_st: mut CharacterState, x: float, z: float, from_y: float, max_depth: float) -> bool {
let g = CharacterGround.height(x, z)
if Math.abs(g - from_y) > CHAR_DROP { return false }
if CharacterGround.water(x, z) - g > max_depth { return false }
if chr_push(x, z, 0.4, g, g + CHAR_BODY) { return false }
if chr_push(character_st, x, z, 0.4, g, g + CHAR_BODY) { return false }
var lo = g
var hi = g
for k in 0 .. 4 {
@ -18,19 +18,19 @@ function chr_safe_ok(x: float, z: float, from_y: float, max_depth: float) -> boo
return not (hi - lo > CHAR_SLOPE_PROBE * 2.0)
}
export function char_safe_spot(x: float, z: float, from_y: float, max_depth: float) -> bool {
chr_safe_x = x
chr_safe_z = z
if chr_safe_ok(x, z, from_y, max_depth) { return true }
export function char_safe_spot(character_st: mut CharacterState, x: float, z: float, from_y: float, max_depth: float) -> bool {
character_st.chr_safe_x = x
character_st.chr_safe_z = z
if chr_safe_ok(character_st, x, z, from_y, max_depth) { return true }
for r in 0 .. 9 {
let rad = 0.8 + float(r) * 1.1
for k in 0 .. 16 {
let a = float(k) * (chr_pi / 8.0)
let px = x + Math.sin(a) * rad
let pz = z + Math.cos(a) * rad
if chr_safe_ok(px, pz, from_y, max_depth) {
chr_safe_x = px
chr_safe_z = pz
if chr_safe_ok(character_st, px, pz, from_y, max_depth) {
character_st.chr_safe_x = px
character_st.chr_safe_z = pz
return true
}
}

View file

@ -1,54 +1,54 @@
# sit.ludic - sitting: on a seat's point, or on the ground where the body stands. The pose either
# side is a still one, so the sit's own weight (body_sit_t) eases 0 -> 1 over a quarter second and
# the body walks the last step to the seat over the same time rather than jumping there.
export function char_sit(x: float, z: float, yaw: float, mode: int) -> void {
chr_sit = true
chr_sit_mode = mode
chr_speed = 0.0
chr_move = 0.0
chr_run = 0.0
chr_sit_x = x
chr_sit_z = z
chr_sit_yaw = yaw
chr_water = CharacterGround.water(chr_x, chr_z)
export function char_sit(character_st: mut CharacterState, x: float, z: float, yaw: float, mode: int) -> void {
character_st.chr_sit = true
character_st.chr_sit_mode = mode
character_st.chr_speed = 0.0
character_st.chr_move = 0.0
character_st.chr_run = 0.0
character_st.chr_sit_x = x
character_st.chr_sit_z = z
character_st.chr_sit_yaw = yaw
character_st.chr_water = CharacterGround.water(character_st.chr_x, character_st.chr_z)
}
function chr_sit_tick(dt: float) -> void {
function chr_sit_tick(character_st: mut CharacterState, dt: float) -> void {
var want = 0.0
if chr_sit { want = 1.0 }
chr_sit_t = char_toward(chr_sit_t, want, 4.0 * dt)
if not chr_sit { return }
if character_st.chr_sit { want = 1.0 }
character_st.chr_sit_t = char_toward(character_st.chr_sit_t, want, 4.0 * dt)
if not character_st.chr_sit { return }
let k = Math.min(1.0, 8.0 * dt)
chr_x = Math.lerp(chr_x, chr_sit_x, k)
chr_z = Math.lerp(chr_z, chr_sit_z, k)
chr_yaw = chr_yaw + char_wrap(chr_sit_yaw - chr_yaw) * k
chr_y = CharacterGround.height(chr_x, chr_z)
chr_water = CharacterGround.water(chr_x, chr_z)
character_st.chr_x = Math.lerp(character_st.chr_x, character_st.chr_sit_x, k)
character_st.chr_z = Math.lerp(character_st.chr_z, character_st.chr_sit_z, k)
character_st.chr_yaw = character_st.chr_yaw + char_wrap(character_st.chr_sit_yaw - character_st.chr_yaw) * k
character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z)
character_st.chr_water = CharacterGround.water(character_st.chr_x, character_st.chr_z)
}
# Sitting down where the body stands: not astride something, not in the air, not on a slope,
# not in the water. Asked again while sitting, it stands up. The answer is a CHAR_* for the game
# to word.
export function char_sit_here() -> int {
if chr_sit {
char_stand()
export function char_sit_here(character_st: mut CharacterState) -> int {
if character_st.chr_sit {
char_stand(character_st)
return CHAR_STOOD
}
if CharacterBody.riding() { return CHAR_NOT_RIDING }
if chr_air { return CHAR_NOT_AIRBORNE }
if CharacterGround.slope(chr_x, chr_z) > 0.35 { return CHAR_NOT_STEEP }
if chr_y < chr_water + 0.9 { return CHAR_NOT_WET }
char_sit(chr_x, chr_z, chr_yaw, CHAR_SIT_GROUND)
if character_st.chr_air { return CHAR_NOT_AIRBORNE }
if CharacterGround.slope(character_st.chr_x, character_st.chr_z) > 0.35 { return CHAR_NOT_STEEP }
if character_st.chr_y < character_st.chr_water + 0.9 { return CHAR_NOT_WET }
char_sit(character_st, character_st.chr_x, character_st.chr_z, character_st.chr_yaw, CHAR_SIT_GROUND)
return CHAR_SAT
}
# standing up out of a seat put down against a boulder or a bank must not leave the body inside it
export function char_stand() -> void {
chr_sit = false
if char_safe_spot(chr_x, chr_z, chr_y, 0.0) {
chr_x = chr_safe_x
chr_z = chr_safe_z
chr_y = CharacterGround.height(chr_x, chr_z)
export function char_stand(character_st: mut CharacterState) -> void {
character_st.chr_sit = false
if char_safe_spot(character_st, character_st.chr_x, character_st.chr_z, character_st.chr_y, 0.0) {
character_st.chr_x = character_st.chr_safe_x
character_st.chr_z = character_st.chr_safe_z
character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z)
}
}

View file

@ -1,77 +1,25 @@
# state.ludic - the body, its camera, and the queue of what happened to it
const chr_pi: float = 3.14159265
var chr_x: float = 0.0
var chr_y: float = 0.0
var chr_z: float = 0.0
var chr_yaw: float = 0.0 # facing, radians; 0 faces -z
var chr_speed: float = 0.0 # m/s along the facing
var chr_phase: float = 0.0 # the gait: one cycle is two steps, advanced by ground covered
var chr_move: float = 0.0 # 0 standing .. 1 moving
var chr_run: float = 0.0 # 0 walking .. 1 running
var chr_time: float = 0.0
var chr_water: float = -10000.0 # the water line under the body, every update
var chr_walk_speed: float = 3.2
var chr_run_speed: float = 6.8
var chr_vy: float = 0.0
var chr_air: bool = false
var chr_air_vx: float = 0.0
var chr_air_vz: float = 0.0
var chr_land: float = 0.0 # seconds of the landing's crouch left
var chr_steep: float = 0.0 # seconds of "that was too steep" left to say
var chr_can_run: bool = true
var chr_step: float = 0.0 # metres covered this frame
var chr_sit: bool = false
var chr_sit_mode: int = 0
var chr_sit_t: float = 0.0 # 0 standing .. 1 sat, eased over a quarter second
var chr_sit_x: float = 0.0 # the seat's point, walked to over the blend
var chr_sit_z: float = 0.0
var chr_sit_yaw: float = 0.0
var chr_swim: bool = false
var chr_dive: bool = false
var chr_depth: float = 0.0 # metres below the surface
var chr_breath: float = 0.0
var chr_float: float = 0.0 # 0 swimming .. 1 floating
var chr_swim_phase: float = 0.0
var chr_edge_said: float = 0.0
var chr_holds: int = CHAR_HOLD_SCREEN
# the camera: an orbit round a pivot at the chest, or the eyes
var chr_cam_yaw: float = 0.0
var chr_cam_pitch: float = -0.157
var chr_cam_dist: float = 4.2
var chr_lock: bool = false # the orbit never settles behind on its own
var chr_fps: bool = false
var chr_fps_bob: float = 0.0
var chr_piv_x: float = 0.0
var chr_piv_y: float = 0.0
var chr_piv_z: float = 0.0
var chr_eye_x: float = 0.0 # where the camera is and where it looks, once computed
var chr_eye_y: float = 0.0
var chr_eye_z: float = 0.0
var chr_eye_yaw: float = 0.0
var chr_eye_pitch: float = 0.0
var chr_eyes_now: bool = false # the last camera was the eyes: the figure is hidden
var chr_safe_x: float = 0.0
var chr_safe_z: float = 0.0
var chr_fact_q: Queue<CharacterFact> = null
export function char_facts() -> Queue<CharacterFact> {
if chr_fact_q == null { chr_fact_q = queue_new("character.facts") }
return chr_fact_q
export function char_facts(base_st: mut BaseState, character_st: mut CharacterState) -> Queue<CharacterFact> {
if character_st.chr_fact_q == null { character_st.chr_fact_q = queue_new(base_st, "character.facts") }
return character_st.chr_fact_q
}
function chr_say(what: int, v: float) -> void {
function chr_say(base_st: mut BaseState, character_st: mut CharacterState, what: int, v: float) -> void {
let f = new CharacterFact
f.what = what
f.x = chr_x
f.y = chr_y
f.z = chr_z
f.x = character_st.chr_x
f.y = character_st.chr_y
f.z = character_st.chr_z
f.v = v
q_push(char_facts(), f)
q_push(base_st, char_facts(base_st, character_st), f)
}

View file

@ -1,61 +1,61 @@
# steer.ludic - turning to the stick and easing to the speed it asks; then the gait and the orbit
function chr_turn_to(dx: float, dz: float, rate: float) -> float {
let diff = char_wrap(Math.atan2(-dx, -dz) - chr_yaw)
chr_yaw = chr_yaw + Math.clamp(diff, -rate, rate)
function chr_turn_to(character_st: mut CharacterState, dx: float, dz: float, rate: float) -> float {
let diff = char_wrap(Math.atan2(-dx, -dz) - character_st.chr_yaw)
character_st.chr_yaw = character_st.chr_yaw + Math.clamp(diff, -rate, rate)
return diff
}
function chr_steer(dx: float, dz: float, mag: float, run: bool, dt: float) -> void {
function chr_steer(character_st: mut CharacterState, dx: float, dz: float, mag: float, run: bool, dt: float) -> void {
var target = 0.0
if mag > 0.05 and not chr_air {
let diff = chr_turn_to(dx, dz, Math.lerp(10.0, 5.0, chr_run) * dt)
if mag > 0.05 and not character_st.chr_air {
let diff = chr_turn_to(character_st, dx, dz, Math.lerp(10.0, 5.0, character_st.chr_run) * dt)
let lined = Math.clamp((Math.cos(diff) + 0.3) / 1.3, 0.0, 1.0)
var top = chr_walk_speed * CharacterBody.pace()
if run { top = chr_run_speed * CharacterBody.pace() }
var top = character_st.chr_walk_speed * CharacterBody.pace()
if run { top = character_st.chr_run_speed * CharacterBody.pace() }
if CharacterBody.sneaking() { top = 1.3 }
target = mag * top * lined
} else if CharacterBody.holding() and not chr_air {
} else if CharacterBody.holding() and not character_st.chr_air {
# a hold turns the body to the thing being worked, so the work is seen being done
chr_turn_to(CharacterBody.hold_x() - chr_x, CharacterBody.hold_z() - chr_z, 6.0 * dt)
chr_turn_to(character_st, CharacterBody.hold_x() - character_st.chr_x, CharacterBody.hold_z() - character_st.chr_z, 6.0 * dt)
}
# in the water the top speed and the inertia are the water's: a swim is half a walk
if chr_swim {
if character_st.chr_swim {
target = mag * 1.5
if run { target = mag * 2.4 }
if mag > 0.05 { chr_turn_to(dx, dz, 4.0 * dt) }
if mag > 0.05 { chr_turn_to(character_st, dx, dz, 4.0 * dt) }
}
if not chr_air {
if not character_st.chr_air {
var acc = 12.0
if target > chr_speed { acc = 9.0 }
if chr_swim { acc = 4.5 }
chr_speed = char_toward(chr_speed, target, acc * dt)
if target > character_st.chr_speed { acc = 9.0 }
if character_st.chr_swim { acc = 4.5 }
character_st.chr_speed = char_toward(character_st.chr_speed, target, acc * dt)
}
}
# The gait's phase is distance over the stride, so the feet never slide; a boot comes down as the
# phase crosses 0 and again as it crosses pi, and that is when a footfall is said.
function chr_gait(dist: float, dyaw: float, dpitch: float, dt: float) -> void {
if chr_land > 0.0 { chr_land = chr_land - dt }
if chr_steep > 0.0 { chr_steep = chr_steep - dt }
if chr_swim { chr_swim_phase = (chr_swim_phase + dt * (1.6 + chr_speed * 0.9)) % (2.0 * chr_pi) }
let stride = Math.lerp(1.5, 2.7, chr_run)
let phase0 = chr_phase
chr_phase = (chr_phase + dist / stride * (2.0 * chr_pi)) % (2.0 * chr_pi)
if not chr_air and not chr_swim and not chr_sit and chr_speed > 0.4 {
if chr_cross(phase0, chr_phase, 0.0) or chr_cross(phase0, chr_phase, chr_pi) { chr_say(CHAR_FOOTFALL, chr_speed) }
function chr_gait(base_st: mut BaseState, character_st: mut CharacterState, dist: float, dyaw: float, dpitch: float, dt: float) -> void {
if character_st.chr_land > 0.0 { character_st.chr_land = character_st.chr_land - dt }
if character_st.chr_steep > 0.0 { character_st.chr_steep = character_st.chr_steep - dt }
if character_st.chr_swim { character_st.chr_swim_phase = (character_st.chr_swim_phase + dt * (1.6 + character_st.chr_speed * 0.9)) % (2.0 * chr_pi) }
let stride = Math.lerp(1.5, 2.7, character_st.chr_run)
let phase0 = character_st.chr_phase
character_st.chr_phase = (character_st.chr_phase + dist / stride * (2.0 * chr_pi)) % (2.0 * chr_pi)
if not character_st.chr_air and not character_st.chr_swim and not character_st.chr_sit and character_st.chr_speed > 0.4 {
if chr_cross(phase0, character_st.chr_phase, 0.0) or chr_cross(phase0, character_st.chr_phase, chr_pi) { chr_say(base_st, character_st, CHAR_FOOTFALL, character_st.chr_speed) }
}
var mv = 0.0
if chr_speed > 0.12 { mv = 1.0 }
chr_move = char_toward(chr_move, mv, 5.0 * dt)
chr_run = char_toward(chr_run, Math.clamp((chr_speed - 3.6) / 2.8, 0.0, 1.0), 3.0 * dt)
chr_look(dyaw, dpitch)
if character_st.chr_speed > 0.12 { mv = 1.0 }
character_st.chr_move = char_toward(character_st.chr_move, mv, 5.0 * dt)
character_st.chr_run = char_toward(character_st.chr_run, Math.clamp((character_st.chr_speed - 3.6) / 2.8, 0.0, 1.0), 3.0 * dt)
chr_look(character_st, dyaw, dpitch)
# the orbit settles slowly behind a walking body unless the player is holding the look
if dyaw == 0.0 and chr_speed > 0.4 and not chr_lock and not CharacterInput.look_always() {
chr_cam_yaw = chr_cam_yaw + char_wrap(chr_yaw - chr_cam_yaw) * Math.min(1.0, 1.1 * dt)
if dyaw == 0.0 and character_st.chr_speed > 0.4 and not character_st.chr_lock and not CharacterInput.look_always() {
character_st.chr_cam_yaw = character_st.chr_cam_yaw + char_wrap(character_st.chr_yaw - character_st.chr_cam_yaw) * Math.min(1.0, 1.1 * dt)
}
}
function chr_look(dyaw: float, dpitch: float) -> void {
chr_cam_yaw = chr_cam_yaw + dyaw
chr_cam_pitch = Math.clamp(chr_cam_pitch + dpitch, -0.96, 0.61)
function chr_look(character_st: mut CharacterState, dyaw: float, dpitch: float) -> void {
character_st.chr_cam_yaw = character_st.chr_cam_yaw + dyaw
character_st.chr_cam_pitch = Math.clamp(character_st.chr_cam_pitch + dpitch, -0.96, 0.61)
}

View file

@ -1,22 +1,22 @@
# swim.ludic - afloat: buoyancy, the descent, the breath, and the shore stopping the body
function chr_swim_tick(dx: float, dz: float, jump: bool, dt: float) -> void {
function chr_swim_tick(base_st: mut BaseState, character_st: mut CharacterState, dx: float, dz: float, jump: bool, dt: float) -> void {
# standing still out here is floating, not swimming
if Math.abs(dx) + Math.abs(dz) > 0.05 { chr_float = 0.0 } else { chr_float = Math.min(1.0, chr_float + dt / 1.5) }
chr_dive_tick(jump, dt)
chr_breath_tick(dt)
chr_current_tick(dt)
let step = chr_speed * dt
if Math.abs(dx) + Math.abs(dz) > 0.05 { character_st.chr_float = 0.0 } else { character_st.chr_float = Math.min(1.0, character_st.chr_float + dt / 1.5) }
chr_dive_tick(character_st, jump, dt)
chr_breath_tick(base_st, character_st, dt)
chr_current_tick(base_st, character_st, dt)
let step = character_st.chr_speed * dt
if step > 0.0 {
chr_push(chr_x - Math.sin(chr_yaw) * step, chr_z - Math.cos(chr_yaw) * step, 0.35, chr_y, chr_y + CHAR_BODY)
let mx = chr_out_x - chr_x
let mz = chr_out_z - chr_z
chr_step = Math.sqrt(mx * mx + mz * mz)
chr_x = chr_out_x
chr_z = chr_out_z
chr_push(character_st, character_st.chr_x - Math.sin(character_st.chr_yaw) * step, character_st.chr_z - Math.cos(character_st.chr_yaw) * step, 0.35, character_st.chr_y, character_st.chr_y + CHAR_BODY)
let mx = character_st.chr_out_x - character_st.chr_x
let mz = character_st.chr_out_z - character_st.chr_z
character_st.chr_step = Math.sqrt(mx * mx + mz * mz)
character_st.chr_x = character_st.chr_out_x
character_st.chr_z = character_st.chr_out_z
}
# the surface, with a slow bob so a floating body is not a statue on glass
let bob = 0.04 * Math.sin(chr_time * 3.8)
chr_y = chr_water - 0.30 + bob - chr_depth
let bob = 0.04 * Math.sin(character_st.chr_time * 3.8)
character_st.chr_y = character_st.chr_water - 0.30 + bob - character_st.chr_depth
# shallow enough to stand up in, and at the top rather than under it
if chr_bed_depth(chr_x, chr_z) < CHAR_STAND_UP and chr_depth < 0.2 { chr_leave_water() }
if chr_bed_depth(character_st.chr_x, character_st.chr_z) < CHAR_STAND_UP and character_st.chr_depth < 0.2 { chr_leave_water(base_st, character_st) }
}

View file

@ -1,20 +1,20 @@
# system.ludic - the body as a system: reset, a tick from the input port, and nothing saved (where a
# body stands is the game's to write, with the rest of the player)
export function char_reset() -> void {
char_teleport(0.0, 0.0, 0.0)
chr_time = 0.0
chr_phase = 0.0
chr_breath = CHAR_BREATH_MAX
chr_can_run = true
chr_sit_t = 0.0
chr_steep = 0.0
chr_land = 0.0
chr_edge_said = 0.0
chr_fps = false
q_clear(char_facts())
export function char_reset(base_st: mut BaseState, character_st: mut CharacterState) -> void {
char_teleport(character_st, 0.0, 0.0, 0.0)
character_st.chr_time = 0.0
character_st.chr_phase = 0.0
character_st.chr_breath = CHAR_BREATH_MAX
character_st.chr_can_run = true
character_st.chr_sit_t = 0.0
character_st.chr_steep = 0.0
character_st.chr_land = 0.0
character_st.chr_edge_said = 0.0
character_st.chr_fps = false
q_clear(base_st, char_facts(base_st, character_st))
}
function chr_sys_tick(t: Tick) -> void { char_tick(t.dt) }
function chr_sys_tick(base_st: mut BaseState, character_st: mut CharacterState, t: Tick) -> void { char_tick(base_st, character_st, t.dt) }
export function char_system() -> System {
let s = system_new("character", PH_INPUT)

View file

@ -20,24 +20,32 @@ program CharacterTest {
return -10000.0
}
# a boulder at (0, 6), a metre round and 40 cm high, and a pillar at (-6, 0)
var px: float = 0.0
var pz: float = 0.0
function fk_ring(x: float, z: float, cx: float, cz: float, r: float) -> bool {
state CharactertestState {
px: float = 0.0
pz: float = 0.0
riding: bool = false
rides: int = 0
sneaking: bool = false
pace: float = 1.0
eyes: bool = true
mz: float = 0.0
}
function fk_ring(charactertest_st: mut CharactertestState, x: float, z: float, cx: float, cz: float, r: float) -> bool {
let dx = x - cx
let dz = z - cz
let d = Math.sqrt(dx * dx + dz * dz)
if d >= r { return false }
let k = r / Math.max(d, 0.001)
px = cx + dx * k
pz = cz + dz * k
charactertest_st.px = cx + dx * k
charactertest_st.pz = cz + dz * k
return true
}
function fk_push(x: float, z: float, r: float, y0: float, y1: float) -> bool {
if y0 < 0.4 and fk_ring(x, z, 0.0, 6.0, 1.0 + r) { return true }
return fk_ring(x, z, -6.0, 0.0, 0.5 + r)
function fk_push(charactertest_st: mut CharactertestState, x: float, z: float, r: float, y0: float, y1: float) -> bool {
if y0 < 0.4 and fk_ring(charactertest_st, x, z, 0.0, 6.0, 1.0 + r) { return true }
return fk_ring(charactertest_st, x, z, -6.0, 0.0, 0.5 + r)
}
function fk_px() -> float { return px }
function fk_pz() -> float { return pz }
function fk_px(charactertest_st: CharactertestState) -> float { return charactertest_st.px }
function fk_pz(charactertest_st: CharactertestState) -> float { return charactertest_st.pz }
function fk_top(x: float, z: float, r: float, from_y: float, step: float, base: float) -> float {
let dx = x
let dz = z - 6.0
@ -45,228 +53,222 @@ program CharacterTest {
return base
}
var riding: bool = false
var rides: int = 0
var sneaking: bool = false
var pace: float = 1.0
var eyes: bool = true
function fk_riding() -> bool { return riding }
function fk_ride(ix: float, iz: float, run: bool, dt: float) -> void { rides += 1 }
function fk_sneak() -> bool { return sneaking }
function fk_pace() -> float { return pace }
function fk_eyes() -> bool { return eyes }
function fk_riding(charactertest_st: CharactertestState) -> bool { return charactertest_st.riding }
function fk_ride(charactertest_st: mut CharactertestState, ix: float, iz: float, run: bool, dt: float) -> void { charactertest_st.rides += 1 }
function fk_sneak(charactertest_st: CharactertestState) -> bool { return charactertest_st.sneaking }
function fk_pace(charactertest_st: CharactertestState) -> float { return charactertest_st.pace }
function fk_eyes(charactertest_st: CharactertestState) -> bool { return charactertest_st.eyes }
var mz: float = 0.0
function fk_move_z() -> float { return mz }
function fk_move_z(charactertest_st: CharactertestState) -> float { return charactertest_st.mz }
bind CharacterGround { height: fn fk_height, water: fn fk_water, push: fn fk_push, pushed_x: fn fk_px, pushed_z: fn fk_pz, top_at: fn fk_top }
bind CharacterBody { riding: fn fk_riding, ride: fn fk_ride, sneaking: fn fk_sneak, pace: fn fk_pace, eyes_ok: fn fk_eyes }
bind CharacterInput { move_z: fn fk_move_z }
function fresh(x: float, z: float, yaw: float) -> void {
char_reset()
char_release(CHAR_HOLD_SCREEN)
char_teleport(x, z, yaw)
function fresh(base_st: mut BaseState, character_st: mut CharacterState, x: float, z: float, yaw: float) -> void {
char_reset(base_st, character_st)
char_release(character_st, CHAR_HOLD_SCREEN)
char_teleport(character_st, x, z, yaw)
}
function facts() -> []CharacterFact { return q_drain(char_facts()) }
function facts(base_st: mut BaseState, character_st: mut CharacterState) -> []CharacterFact { return q_drain(base_st, char_facts(base_st, character_st)) }
function count(fs: []CharacterFact, what: int) -> int {
var n = 0
for i in 0 .. len(fs) { if fs[i].what == what { n += 1 } }
return n
}
# walk the stick straight ahead (the camera looks where the body faces) for `secs`
function walk(secs: float, hz: float, run: bool) -> void {
function walk(base_st: mut BaseState, character_st: mut CharacterState, secs: float, hz: float, run: bool) -> void {
let n = int(secs * hz)
for i in 0 .. n { char_update(0.0, 1.0, run, false, 0.0, 0.0, 1.0 / hz) }
for i in 0 .. n { char_update(base_st, character_st, 0.0, 1.0, run, false, 0.0, 0.0, 1.0 / hz) }
}
# yaw 0 faces -z; the heading of a walk toward +x is -pi/2
const EAST: float = -1.5707963
const SOUTH: float = 3.14159265
test "a 45 cm deck is a step at 60 Hz and at 240" {
fresh(1.0, 0.0, EAST)
walk(2.0, 60.0, false)
expect(body_x() > 5.0)
expect_near(body_y(), 0.45, 0.01)
fresh(1.0, 0.0, EAST)
walk(2.0, 240.0, false)
expect(body_x() > 5.0)
expect_near(body_y(), 0.45, 0.01)
expect_eq(count(facts(), CHAR_TOO_STEEP), 0)
test "a 45 cm deck is a step at 60 Hz and at 240" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 1.0, 0.0, EAST)
walk(base_st, character_st, 2.0, 60.0, false)
expect(body_x(character_st) > 5.0)
expect_near(body_y(character_st), 0.45, 0.01)
fresh(base_st, character_st, 1.0, 0.0, EAST)
walk(base_st, character_st, 2.0, 240.0, false)
expect(body_x(character_st) > 5.0)
expect_near(body_y(character_st), 0.45, 0.01)
expect_eq(count(facts(base_st, character_st), CHAR_TOO_STEEP), 0)
}
test "a cliff is refused at the same place whatever the frame rate, and said once" {
fresh(0.0, -8.0, 0.0)
walk(3.0, 30.0, false)
let at30 = body_z()
expect(body_steep() > 0.0)
expect_eq(count(facts(), CHAR_TOO_STEEP), 1)
fresh(0.0, -8.0, 0.0)
walk(3.0, 240.0, false)
expect_near(body_z(), at30, 0.25)
expect(body_z() > -10.0)
expect(body_y() < 0.3)
test "a cliff is refused at the same place whatever the frame rate, and said once" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, -8.0, 0.0)
walk(base_st, character_st, 3.0, 30.0, false)
let at30 = body_z(character_st)
expect(body_steep(character_st) > 0.0)
expect_eq(count(facts(base_st, character_st), CHAR_TOO_STEEP), 1)
fresh(base_st, character_st, 0.0, -8.0, 0.0)
walk(base_st, character_st, 3.0, 240.0, false)
expect_near(body_z(character_st), at30, 0.25)
expect(body_z(character_st) > -10.0)
expect(body_y(character_st) < 0.3)
}
test "a gentle rise is walked up" {
fresh(0.0, 8.0, SOUTH)
walk(3.0, 60.0, false)
expect(body_z() > 14.0)
expect(body_y() > 1.0)
test "a gentle rise is walked up" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 8.0, SOUTH)
walk(base_st, character_st, 3.0, 60.0, false)
expect(body_z(character_st) > 14.0)
expect(body_y(character_st) > 1.0)
}
test "the boots set the limit: a rise they cannot take is refused" {
fresh(0.0, 8.0, SOUTH)
walk(0.2, 60.0, false)
expect(not char_slope_blocks(body_y() + 0.1, 0.0, 1.0))
test "the boots set the limit: a rise they cannot take is refused" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 8.0, SOUTH)
walk(base_st, character_st, 0.2, 60.0, false)
expect(not char_slope_blocks(character_st, body_y(character_st) + 0.1, 0.0, 1.0))
}
test "the walk reaches the walk speed, the run the run speed, a sneak its own" {
fresh(0.0, 0.0, SOUTH)
walk(2.0, 60.0, false)
expect_near(body_speed(), 3.2, 0.05)
fresh(0.0, 0.0, SOUTH)
walk(2.0, 60.0, true)
expect_near(body_speed(), 6.8, 0.05)
pace = 0.5
fresh(0.0, 0.0, SOUTH)
walk(2.0, 60.0, false)
expect_near(body_speed(), 1.6, 0.05)
pace = 1.0
sneaking = true
fresh(0.0, 0.0, SOUTH)
walk(2.0, 60.0, true)
expect_near(body_speed(), 1.3, 0.05)
test "the walk reaches the walk speed, the run the run speed, a sneak its own" (base_st: mut BaseState, character_st: mut CharacterState, charactertest_st: mut CharactertestState) {
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false)
expect_near(body_speed(character_st), 3.2, 0.05)
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, true)
expect_near(body_speed(character_st), 6.8, 0.05)
charactertest_st.pace = 0.5
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false)
expect_near(body_speed(character_st), 1.6, 0.05)
charactertest_st.pace = 1.0
charactertest_st.sneaking = true
fresh(base_st, character_st, 0.0, 0.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, true)
expect_near(body_speed(character_st), 1.3, 0.05)
}
test "a boulder is a wall to a body below its step and a step onto its top" {
fresh(0.0, 3.0, SOUTH)
walk(2.0, 60.0, false)
expect(body_z() < 5.1)
test "a boulder is a wall to a body below its step and a step onto its top" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 3.0, SOUTH)
walk(base_st, character_st, 2.0, 60.0, false)
expect(body_z(character_st) < 5.1)
expect_near(char_stand_at(0.0, 6.0, 0.0), 0.4, 0.001)
expect_near(char_stand_at(3.0, 6.0, 0.0), 0.0, 0.001)
# and the world's own ground never includes it
expect_near(fk_height(0.0, 6.0), 0.0, 0.001)
}
test "wading shelves into a swim, and the shallows give the ground back" {
fresh(18.0, 0.0, EAST)
walk(3.0, 60.0, false)
let fs = facts()
expect(body_swimming())
test "wading shelves into a swim, and the shallows give the ground back" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 18.0, 0.0, EAST)
walk(base_st, character_st, 3.0, 60.0, false)
let fs = facts(base_st, character_st)
expect(body_swimming(character_st))
expect_eq(count(fs, CHAR_STARTED_SWIMMING), 1)
expect_near(body_y(), -0.30, 0.06)
expect(body_breath() > 40.0)
char_face(-EAST)
char_look(-EAST, 0.0)
walk(8.0, 60.0, false)
expect(not body_swimming())
expect_eq(count(facts(), CHAR_LEFT_WATER), 1)
expect_near(body_y(character_st), -0.30, 0.06)
expect(body_breath(character_st) > 40.0)
char_face(character_st, -EAST)
char_look(character_st, -EAST, 0.0)
walk(base_st, character_st, 8.0, 60.0, false)
expect(not body_swimming(character_st))
expect_eq(count(facts(base_st, character_st), CHAR_LEFT_WATER), 1)
}
test "the jump held dives, the air runs out, and the body surfaces" {
fresh(30.0, 0.0, 0.0)
char_resume_swim(false, 0.0, 2.0)
for i in 0 .. 600 { char_update(0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0) }
let fs = facts()
test "the jump held dives, the air runs out, and the body surfaces" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 30.0, 0.0, 0.0)
char_resume_swim(character_st, false, 0.0, 2.0)
for i in 0 .. 600 { char_update(base_st, character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0) }
let fs = facts(base_st, character_st)
expect(count(fs, CHAR_OUT_OF_AIR) > 0)
for i in 0 .. 300 { char_update(0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(body_depth() < 0.05)
expect(body_breath() > 10.0)
for i in 0 .. 300 { char_update(base_st, character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(body_depth(character_st) < 0.05)
expect(body_breath(character_st) > 10.0)
}
test "a jump leaves the ground, falls, lands, and says both" {
fresh(0.0, 0.0, 0.0)
char_update(0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
expect(body_airborne())
test "a jump leaves the ground, falls, lands, and says both" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
char_update(base_st, character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
expect(body_airborne(character_st))
var guard = 0
while body_airborne() and guard < 200 {
char_update(0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0)
while body_airborne(character_st) and guard < 200 {
char_update(base_st, character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0)
guard += 1
}
let fs = facts()
expect(not body_airborne())
let fs = facts(base_st, character_st)
expect(not body_airborne(character_st))
expect_eq(count(fs, CHAR_JUMPED), 1)
expect_eq(count(fs, CHAR_LANDED), 1)
expect(guard > 30)
char_allow_run(false)
char_update(0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
expect(not body_airborne())
char_allow_run(character_st, false)
char_update(base_st, character_st, 0.0, 0.0, false, true, 0.0, 0.0, 1.0 / 60.0)
expect(not body_airborne(character_st))
}
test "the safe spot clears a collider, keeps out of deep water, and allows a wade when asked" {
expect(char_safe_spot(-6.0, 0.0, 0.0, 0.0))
let dx = body_safe_x() + 6.0
let dz = body_safe_z()
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
let dz = body_safe_z(character_st)
expect(dx * dx + dz * dz > 0.81)
expect(not char_safe_spot(40.0, 0.0, 0.0, 0.0))
expect(char_safe_spot(20.5, 0.0, 0.0, 1.25))
expect(char_safe_spot(15.0, 0.0, 0.0, 0.0))
expect_near(body_safe_x(), 15.0, 0.001)
expect(not char_safe_spot(character_st, 40.0, 0.0, 0.0, 0.0))
expect(char_safe_spot(character_st, 20.5, 0.0, 0.0, 1.25))
expect(char_safe_spot(character_st, 15.0, 0.0, 0.0, 0.0))
expect_near(body_safe_x(character_st), 15.0, 0.001)
}
test "sitting where you stand, and the refusals" {
fresh(0.0, 0.0, 0.0)
expect_eq(char_sit_here(), CHAR_SAT)
for i in 0 .. 30 { char_update(0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(body_sitting())
expect_near(body_sit_t(), 1.0, 0.01)
expect_eq(char_sit_here(), CHAR_STOOD)
expect(not body_sitting())
fresh(0.0, -14.0, 0.0)
expect_eq(char_sit_here(), CHAR_NOT_STEEP)
fresh(24.0, 0.0, 0.0)
char_resume_swim(false, 0.0, 10.0)
expect_eq(char_sit_here(), CHAR_NOT_WET)
riding = true
fresh(0.0, 0.0, 0.0)
expect_eq(char_sit_here(), CHAR_NOT_RIDING)
riding = false
test "sitting where you stand, and the refusals" (base_st: mut BaseState, character_st: mut CharacterState, charactertest_st: mut CharactertestState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
expect_eq(char_sit_here(character_st), CHAR_SAT)
for i in 0 .. 30 { char_update(base_st, character_st, 0.0, 0.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect(body_sitting(character_st))
expect_near(body_sit_t(character_st), 1.0, 0.01)
expect_eq(char_sit_here(character_st), CHAR_STOOD)
expect(not body_sitting(character_st))
fresh(base_st, character_st, 0.0, -14.0, 0.0)
expect_eq(char_sit_here(character_st), CHAR_NOT_STEEP)
fresh(base_st, character_st, 24.0, 0.0, 0.0)
char_resume_swim(character_st, false, 0.0, 10.0)
expect_eq(char_sit_here(character_st), CHAR_NOT_WET)
charactertest_st.riding = true
fresh(base_st, character_st, 0.0, 0.0, 0.0)
expect_eq(char_sit_here(character_st), CHAR_NOT_RIDING)
charactertest_st.riding = false
}
test "a hold stops the stick, and only its own reason lets it go" {
fresh(0.0, 0.0, 0.0)
mz = 1.0
char_hold(CHAR_HOLD_STAGE)
char_hold(CHAR_HOLD_SCREEN)
for i in 0 .. 60 { char_tick(1.0 / 60.0) }
expect_near(body_z(), 0.0, 0.001)
char_release(CHAR_HOLD_SCREEN)
char_release(CHAR_HOLD_SCREEN)
expect(body_held())
char_release(CHAR_HOLD_STAGE)
for i in 0 .. 60 { char_tick(1.0 / 60.0) }
expect(body_z() < -1.0)
mz = 0.0
test "a hold stops the stick, and only its own reason lets it go" (base_st: mut BaseState, character_st: mut CharacterState, charactertest_st: mut CharactertestState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
charactertest_st.mz = 1.0
char_hold(character_st, CHAR_HOLD_STAGE)
char_hold(character_st, CHAR_HOLD_SCREEN)
for i in 0 .. 60 { char_tick(base_st, character_st, 1.0 / 60.0) }
expect_near(body_z(character_st), 0.0, 0.001)
char_release(character_st, CHAR_HOLD_SCREEN)
char_release(character_st, CHAR_HOLD_SCREEN)
expect(body_held(character_st))
char_release(character_st, CHAR_HOLD_STAGE)
for i in 0 .. 60 { char_tick(base_st, character_st, 1.0 / 60.0) }
expect(body_z(character_st) < -1.0)
charactertest_st.mz = 0.0
}
test "riding hands the stick over and leaves the body where the ride puts it" {
fresh(0.0, 0.0, 0.0)
riding = true
rides = 0
char_board(3.0, 3.0, 0.0)
for i in 0 .. 10 { char_update(0.0, 1.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect_eq(rides, 10)
char_ride_at(4.0, 1.2, 3.0, 0.0, 2.0)
expect_near(body_x(), 4.0, 0.001)
expect_near(body_y(), 1.2, 0.001)
riding = false
char_step_off(5.0, 0.0)
expect_near(body_y(), 0.45, 0.001)
test "riding hands the stick over and leaves the body where the ride puts it" (base_st: mut BaseState, character_st: mut CharacterState, charactertest_st: mut CharactertestState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
charactertest_st.riding = true
charactertest_st.rides = 0
char_board(character_st, 3.0, 3.0, 0.0)
for i in 0 .. 10 { char_update(base_st, character_st, 0.0, 1.0, false, false, 0.0, 0.0, 1.0 / 60.0) }
expect_eq(charactertest_st.rides, 10)
char_ride_at(character_st, 4.0, 1.2, 3.0, 0.0, 2.0)
expect_near(body_x(character_st), 4.0, 0.001)
expect_near(body_y(character_st), 1.2, 0.001)
charactertest_st.riding = false
char_step_off(character_st, 5.0, 0.0)
expect_near(body_y(character_st), 0.45, 0.001)
}
test "the orbit sits behind and above the ground; the eyes at head height, refused when sat" {
fresh(0.0, 0.0, 0.0)
char_camera(1.0)
expect(body_eye_z() > 1.0)
expect(body_eye_y() > 0.55)
expect(not body_eyes_now())
expect_eq(char_fps_toggle(), CHAR_EYES_ON)
char_camera(1.0)
expect(body_eyes_now())
expect_near(body_eye_y(), 1.62, 0.05)
expect_eq(char_fps_toggle(), CHAR_EYES_OFF)
char_sit(0.0, 0.0, 0.0, CHAR_SIT_SEAT)
expect_eq(char_fps_toggle(), CHAR_EYES_SITTING)
test "the orbit sits behind and above the ground; the eyes at head height, refused when sat" (base_st: mut BaseState, character_st: mut CharacterState) {
fresh(base_st, character_st, 0.0, 0.0, 0.0)
char_camera(character_st, 1.0)
expect(body_eye_z(character_st) > 1.0)
expect(body_eye_y(character_st) > 0.55)
expect(not body_eyes_now(character_st))
expect_eq(char_fps_toggle(character_st), CHAR_EYES_ON)
char_camera(character_st, 1.0)
expect(body_eyes_now(character_st))
expect_near(body_eye_y(character_st), 1.62, 0.05)
expect_eq(char_fps_toggle(character_st), CHAR_EYES_OFF)
char_sit(character_st, 0.0, 0.0, 0.0, CHAR_SIT_SEAT)
expect_eq(char_fps_toggle(character_st), CHAR_EYES_SITTING)
}
}

View file

@ -1,28 +1,28 @@
# update.ludic - a frame of the body: the stick in the camera's frame, the step, the gait, the camera
export function char_update(ix0: float, iz0: float, run0: bool, jump0: bool, dyaw: float, dpitch: float, dt: float) -> void {
var run = run0 and chr_can_run
export function char_update(base_st: mut BaseState, character_st: mut CharacterState, ix0: float, iz0: float, run0: bool, jump0: bool, dyaw: float, dpitch: float, dt: float) -> void {
var run = run0 and character_st.chr_can_run
var ix = ix0
var iz = iz0
var jump = jump0
chr_step = 0.0
chr_time = chr_time + dt
chr_water = CharacterGround.water(chr_x, chr_z)
chr_sit_tick(dt)
character_st.chr_step = 0.0
character_st.chr_time = character_st.chr_time + dt
character_st.chr_water = CharacterGround.water(character_st.chr_x, character_st.chr_z)
chr_sit_tick(character_st, dt)
if CharacterBody.riding() {
chr_ride_update(ix, iz, run, dyaw, dpitch, dt)
chr_ride_update(character_st, ix, iz, run, dyaw, dpitch, dt)
return
}
if CharacterBody.sneaking() { run = false }
# sitting: any step stands up, and nothing else moves
if chr_sit and (Math.abs(ix) > 0.3 or Math.abs(iz) > 0.3 or jump) { chr_sit = false }
if chr_sit {
if character_st.chr_sit and (Math.abs(ix) > 0.3 or Math.abs(iz) > 0.3 or jump) { character_st.chr_sit = false }
if character_st.chr_sit {
ix = 0.0
iz = 0.0
jump = false
}
# the stick in the camera's frame: forward on the ground is (-sin yaw, -cos yaw)
let cy = Math.cos(chr_cam_yaw)
let sy = Math.sin(chr_cam_yaw)
let cy = Math.cos(character_st.chr_cam_yaw)
let sy = Math.sin(character_st.chr_cam_yaw)
var dx = -sy * iz + cy * ix
var dz = -cy * iz - sy * ix
var mag = Math.sqrt(dx * dx + dz * dz)
@ -31,30 +31,30 @@ export function char_update(ix0: float, iz0: float, run0: bool, jump0: bool, dya
dz = dz / mag
mag = 1.0
}
chr_steer(dx, dz, mag, run, dt)
let dist = chr_travel(dx, dz, -Math.sin(chr_yaw), -Math.cos(chr_yaw), jump, dt)
chr_gait(dist, dyaw, dpitch, dt)
char_camera(dt)
chr_steer(character_st, dx, dz, mag, run, dt)
let dist = chr_travel(base_st, character_st, dx, dz, -Math.sin(character_st.chr_yaw), -Math.cos(character_st.chr_yaw), jump, dt)
chr_gait(base_st, character_st, dist, dyaw, dpitch, dt)
char_camera(character_st, dt)
}
# something else carries the body (CharacterBody.ride moves it with char_ride_at); the orbit
# drifts behind whatever it is on
function chr_ride_update(ix: float, iz: float, run: bool, dyaw: float, dpitch: float, dt: float) -> void {
function chr_ride_update(character_st: mut CharacterState, ix: float, iz: float, run: bool, dyaw: float, dpitch: float, dt: float) -> void {
CharacterBody.ride(ix, iz, run, dt)
chr_look(dyaw, dpitch)
if dyaw == 0.0 and not CharacterInput.look_always() { chr_cam_yaw = chr_cam_yaw + char_wrap(chr_yaw - chr_cam_yaw) * Math.min(1.0, 1.5 * dt) }
char_camera(dt)
chr_look(character_st, dyaw, dpitch)
if dyaw == 0.0 and not CharacterInput.look_always() { character_st.chr_cam_yaw = character_st.chr_cam_yaw + char_wrap(character_st.chr_yaw - character_st.chr_cam_yaw) * Math.min(1.0, 1.5 * dt) }
char_camera(character_st, dt)
}
# a frame from the input port: the game's devices, read once, stepping the body
export function char_tick(dt: float) -> void {
export function char_tick(base_st: mut BaseState, character_st: mut CharacterState, dt: float) -> void {
var ix = 0.0
var iz = 0.0
var run = false
var jump = false
var dyaw = 0.0
var dpitch = 0.0
if chr_holds == 0 {
if character_st.chr_holds == 0 {
ix = CharacterInput.move_x()
iz = CharacterInput.move_z()
run = CharacterInput.run()
@ -62,7 +62,7 @@ export function char_tick(dt: float) -> void {
dyaw = CharacterInput.look_yaw()
dpitch = CharacterInput.look_pitch()
let wheel = CharacterInput.zoom()
if wheel != 0.0 { chr_cam_dist = Math.clamp(chr_cam_dist - wheel * 0.35, 1.8, 9.0) }
if wheel != 0.0 { character_st.chr_cam_dist = Math.clamp(character_st.chr_cam_dist - wheel * 0.35, 1.8, 9.0) }
}
char_update(ix, iz, run, jump, dyaw, dpitch, dt)
char_update(base_st, character_st, ix, iz, run, jump, dyaw, dpitch, dt)
}

View file

@ -1,125 +1,125 @@
# verbs.ludic - every change the rest of a game may make to the body
export function char_config(walk: float, run: float) -> void {
chr_walk_speed = walk
chr_run_speed = run
export function char_config(character_st: mut CharacterState, walk: float, run: float) -> void {
character_st.chr_walk_speed = walk
character_st.chr_run_speed = run
}
# put the body somewhere, standing, the camera behind it
export function char_teleport(x: float, z: float, yaw: float) -> void {
chr_x = x
chr_z = z
chr_yaw = yaw
chr_y = CharacterGround.height(x, z)
char_halt()
chr_sit = false
chr_swim = false
chr_dive = false
chr_depth = 0.0
chr_float = 0.0
export function char_teleport(character_st: mut CharacterState, x: float, z: float, yaw: float) -> void {
character_st.chr_x = x
character_st.chr_z = z
character_st.chr_yaw = yaw
character_st.chr_y = CharacterGround.height(x, z)
char_halt(character_st)
character_st.chr_sit = false
character_st.chr_swim = false
character_st.chr_dive = false
character_st.chr_depth = 0.0
character_st.chr_float = 0.0
# the water where the body lands, not where it was: a trip reloaded mid-swim reads it next
chr_water = CharacterGround.water(x, z)
chr_cam_yaw = yaw
chr_piv_x = chr_x
chr_piv_y = chr_y + 1.45
chr_piv_z = chr_z
char_camera(1.0)
character_st.chr_water = CharacterGround.water(x, z)
character_st.chr_cam_yaw = yaw
character_st.chr_piv_x = character_st.chr_x
character_st.chr_piv_y = character_st.chr_y + 1.45
character_st.chr_piv_z = character_st.chr_z
char_camera(character_st, 1.0)
}
# a screen holds the body still by a reason, so two holders cannot release each other's hold
export function char_hold(reason: int) -> void { chr_holds = chr_holds | reason }
export function char_release(reason: int) -> void { if (chr_holds & reason) != 0 { chr_holds = chr_holds - reason } }
export function char_hold(character_st: mut CharacterState, reason: int) -> void { character_st.chr_holds = character_st.chr_holds | reason }
export function char_release(character_st: mut CharacterState, reason: int) -> void { if (character_st.chr_holds & reason) != 0 { character_st.chr_holds = character_st.chr_holds - reason } }
# everything that carries the body stops: no fall, no stride, no run
export function char_halt() -> void {
chr_air = false
chr_vy = 0.0
chr_speed = 0.0
chr_move = 0.0
chr_run = 0.0
export function char_halt(character_st: mut CharacterState) -> void {
character_st.chr_air = false
character_st.chr_vy = 0.0
character_st.chr_speed = 0.0
character_st.chr_move = 0.0
character_st.chr_run = 0.0
}
# out of strength in the water: the stroke stops and the body floats
export function char_go_limp() -> void {
chr_speed = 0.0
chr_float = 1.0
export function char_go_limp(character_st: mut CharacterState) -> void {
character_st.chr_speed = 0.0
character_st.chr_float = 1.0
}
# set down somewhere without the teleport's reset (a swimmer turned back)
export function char_push_to(x: float, z: float) -> void {
chr_x = x
chr_z = z
chr_speed = 0.0
export function char_push_to(character_st: mut CharacterState, x: float, z: float) -> void {
character_st.chr_x = x
character_st.chr_z = z
character_st.chr_speed = 0.0
}
# stood on the ground at a point, facing a way
export function char_stand_on(x: float, z: float, yaw: float) -> void {
chr_x = x
chr_z = z
chr_yaw = yaw
chr_y = CharacterGround.height(x, z)
export function char_stand_on(character_st: mut CharacterState, x: float, z: float, yaw: float) -> void {
character_st.chr_x = x
character_st.chr_z = z
character_st.chr_yaw = yaw
character_st.chr_y = CharacterGround.height(x, z)
}
# a trip saved in the middle of the lake reloads in the middle of the lake, not on the bed
export function char_resume_swim(dive: bool, depth: float, breath: float) -> void {
chr_water = CharacterGround.water(chr_x, chr_z)
chr_swim = true
chr_dive = dive
chr_depth = depth
chr_breath = breath
chr_y = chr_water - 0.30 - chr_depth
export function char_resume_swim(character_st: mut CharacterState, dive: bool, depth: float, breath: float) -> void {
character_st.chr_water = CharacterGround.water(character_st.chr_x, character_st.chr_z)
character_st.chr_swim = true
character_st.chr_dive = dive
character_st.chr_depth = depth
character_st.chr_breath = breath
character_st.chr_y = character_st.chr_water - 0.30 - character_st.chr_depth
}
# the map changed under the body: nothing about the old water may survive
export function char_map_changed() -> void {
chr_swim = false
chr_dive = false
chr_depth = 0.0
chr_water = CharacterGround.water(chr_x, chr_z)
export function char_map_changed(character_st: mut CharacterState) -> void {
character_st.chr_swim = false
character_st.chr_dive = false
character_st.chr_depth = 0.0
character_st.chr_water = CharacterGround.water(character_st.chr_x, character_st.chr_z)
}
export function char_face(yaw: float) -> void { chr_yaw = yaw }
export function char_allow_run(on: bool) -> void { chr_can_run = on }
export function char_look(yaw: float, pitch: float) -> void {
chr_cam_yaw = yaw
chr_cam_pitch = pitch
export function char_face(character_st: mut CharacterState, yaw: float) -> void { character_st.chr_yaw = yaw }
export function char_allow_run(character_st: mut CharacterState, on: bool) -> void { character_st.chr_can_run = on }
export function char_look(character_st: mut CharacterState, yaw: float, pitch: float) -> void {
character_st.chr_cam_yaw = yaw
character_st.chr_cam_pitch = pitch
}
export function char_look_pitch(pitch: float) -> void { chr_cam_pitch = pitch }
export function char_orbit(dist: float) -> void { chr_cam_dist = Math.clamp(dist, 1.8, 9.0) }
export function char_lock_orbit(on: bool) -> void { chr_lock = on }
export function char_set_fps(on: bool) -> void { chr_fps = on }
export function char_look_pitch(character_st: mut CharacterState, pitch: float) -> void { character_st.chr_cam_pitch = pitch }
export function char_orbit(character_st: mut CharacterState, dist: float) -> void { character_st.chr_cam_dist = Math.clamp(dist, 1.8, 9.0) }
export function char_lock_orbit(character_st: mut CharacterState, on: bool) -> void { character_st.chr_lock = on }
export function char_set_fps(character_st: mut CharacterState, on: bool) -> void { character_st.chr_fps = on }
# the eyes or the shoulder; refused while sitting or carried, and the answer says which
export function char_fps_toggle() -> int {
if not chr_fps and not chr_eyes_allowed() {
if chr_sit { return CHAR_EYES_SITTING }
export function char_fps_toggle(character_st: mut CharacterState) -> int {
if not character_st.chr_fps and not chr_eyes_allowed(character_st) {
if character_st.chr_sit { return CHAR_EYES_SITTING }
return CHAR_EYES_RIDING
}
chr_fps = not chr_fps
if chr_fps { return CHAR_EYES_ON }
character_st.chr_fps = not character_st.chr_fps
if character_st.chr_fps { return CHAR_EYES_ON }
return CHAR_EYES_OFF
}
# something to ride: getting on, being carried, and stepping off onto a spot char_safe_spot found
export function char_board(x: float, z: float, yaw: float) -> void {
chr_sit = false
chr_x = x
chr_z = z
chr_yaw = yaw
chr_speed = 0.0
chr_move = 0.0
chr_run = 0.0
export function char_board(character_st: mut CharacterState, x: float, z: float, yaw: float) -> void {
character_st.chr_sit = false
character_st.chr_x = x
character_st.chr_z = z
character_st.chr_yaw = yaw
character_st.chr_speed = 0.0
character_st.chr_move = 0.0
character_st.chr_run = 0.0
}
export function char_ride_at(x: float, y: float, z: float, yaw: float, speed: float) -> void {
chr_x = x
chr_y = y
chr_z = z
chr_yaw = yaw
chr_speed = speed
export function char_ride_at(character_st: mut CharacterState, x: float, y: float, z: float, yaw: float, speed: float) -> void {
character_st.chr_x = x
character_st.chr_y = y
character_st.chr_z = z
character_st.chr_yaw = yaw
character_st.chr_speed = speed
}
export function char_step_off(x: float, z: float) -> void {
chr_x = x
chr_z = z
chr_y = CharacterGround.height(x, z)
export function char_step_off(character_st: mut CharacterState, x: float, z: float) -> void {
character_st.chr_x = x
character_st.chr_z = z
character_st.chr_y = CharacterGround.height(x, z)
}

View file

@ -1,69 +1,69 @@
# walk.ludic - the step itself: swimming, in the air, or on the ground; the distance, for the gait
function chr_travel(dx: float, dz: float, fx: float, fz: float, jump: bool, dt: float) -> float {
if chr_swim {
chr_swim_tick(dx, dz, jump, dt)
return chr_speed * dt
function chr_travel(base_st: mut BaseState, character_st: mut CharacterState, dx: float, dz: float, fx: float, fz: float, jump: bool, dt: float) -> float {
if character_st.chr_swim {
chr_swim_tick(base_st, character_st, dx, dz, jump, dt)
return character_st.chr_speed * dt
}
if chr_air { return chr_fly(dx, dz, dt) }
return chr_walk(fx, fz, jump, dt)
if character_st.chr_air { return chr_fly(base_st, character_st, dx, dz, dt) }
return chr_walk(base_st, character_st, fx, fz, jump, dt)
}
# ballistic: gravity, the takeoff's velocity carried, a little steering
function chr_fly(dx: float, dz: float, dt: float) -> float {
chr_vy = chr_vy - 14.0 * dt
chr_air_vx = chr_air_vx + dx * (4.0 * dt)
chr_air_vz = chr_air_vz + dz * (4.0 * dt)
chr_push(chr_x + chr_air_vx * dt, chr_z + chr_air_vz * dt, 0.35, chr_y, chr_y + CHAR_BODY)
chr_y = chr_y + chr_vy * dt
let ground = char_stand_at(chr_out_x, chr_out_z, chr_y)
chr_x = chr_out_x
chr_z = chr_out_z
if chr_y < ground and chr_vy < 0.0 {
chr_y = ground
chr_air = false
chr_land = 0.22
chr_speed = Math.sqrt(chr_air_vx * chr_air_vx + chr_air_vz * chr_air_vz)
chr_say(CHAR_LANDED, chr_speed)
function chr_fly(base_st: mut BaseState, character_st: mut CharacterState, dx: float, dz: float, dt: float) -> float {
character_st.chr_vy = character_st.chr_vy - 14.0 * dt
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)
chr_push(character_st, character_st.chr_x + character_st.chr_air_vx * dt, character_st.chr_z + character_st.chr_air_vz * dt, 0.35, character_st.chr_y, character_st.chr_y + CHAR_BODY)
character_st.chr_y = character_st.chr_y + character_st.chr_vy * dt
let ground = char_stand_at(character_st.chr_out_x, character_st.chr_out_z, character_st.chr_y)
character_st.chr_x = character_st.chr_out_x
character_st.chr_z = character_st.chr_out_z
if character_st.chr_y < ground and character_st.chr_vy < 0.0 {
character_st.chr_y = ground
character_st.chr_air = false
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(base_st, character_st, CHAR_LANDED, character_st.chr_speed)
}
return chr_speed * (dt * 0.4)
return character_st.chr_speed * (dt * 0.4)
}
# on the ground: a step, unless a slope refuses it or the water takes the body; then a jump
function chr_walk(fx: float, fz: float, jump: bool, dt: float) -> float {
var dist = chr_speed * dt
if dist > 0.0 { dist = chr_walk_step(fx, fz, dist) } else { chr_y = CharacterGround.height(chr_x, chr_z) }
if jump and chr_can_run and chr_land < 0.05 {
chr_air = true
chr_vy = 5.0
chr_air_vx = fx * chr_speed
chr_air_vz = fz * chr_speed
chr_say(CHAR_JUMPED, 0.0)
function chr_walk(base_st: mut BaseState, character_st: mut CharacterState, fx: float, fz: float, jump: bool, dt: float) -> float {
var dist = character_st.chr_speed * dt
if dist > 0.0 { dist = chr_walk_step(base_st, character_st, fx, fz, dist) } else { character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z) }
if jump and character_st.chr_can_run and character_st.chr_land < 0.05 {
character_st.chr_air = true
character_st.chr_vy = 5.0
character_st.chr_air_vx = fx * character_st.chr_speed
character_st.chr_air_vz = fz * character_st.chr_speed
chr_say(base_st, character_st, CHAR_JUMPED, 0.0)
}
return dist
}
function chr_walk_step(fx: float, fz: float, dist: float) -> float {
chr_push(chr_x + fx * dist, chr_z + fz * dist, 0.35, chr_y, chr_y + CHAR_BODY)
let nx = chr_out_x
let nz = chr_out_z
let nh = char_stand_at(nx, nz, chr_y)
if char_slope_blocks(nh, fx, fz) {
if not (chr_steep > 0.0) { chr_say(CHAR_TOO_STEEP, nh - chr_y) }
chr_steep = 1.2
chr_speed = 0.0
function chr_walk_step(base_st: mut BaseState, character_st: mut CharacterState, fx: float, fz: float, dist: float) -> float {
chr_push(character_st, character_st.chr_x + fx * dist, character_st.chr_z + fz * dist, 0.35, character_st.chr_y, character_st.chr_y + CHAR_BODY)
let nx = character_st.chr_out_x
let nz = character_st.chr_out_z
let nh = char_stand_at(nx, nz, character_st.chr_y)
if char_slope_blocks(character_st, nh, fx, fz) {
if not (character_st.chr_steep > 0.0) { chr_say(base_st, character_st, CHAR_TOO_STEEP, nh - character_st.chr_y) }
character_st.chr_steep = 1.2
character_st.chr_speed = 0.0
return 0.0
}
# the lake shelves: shin-deep is a wade, and past CHAR_WADE_MAX it is a swim. Entering owns
# the position and the height this frame, or the body sits on the bed for one frame.
if nh < CharacterGround.water(nx, nz) - CHAR_WADE_MAX {
chr_enter_water(nx, nz)
chr_enter_water(base_st, character_st, nx, nz)
return dist
}
let mx = nx - chr_x
let mz = nz - chr_z
chr_step = Math.sqrt(mx * mx + mz * mz)
chr_x = nx
chr_z = nz
chr_y = nh
let mx = nx - character_st.chr_x
let mz = nz - character_st.chr_z
character_st.chr_step = Math.sqrt(mx * mx + mz * mz)
character_st.chr_x = nx
character_st.chr_z = nz
character_st.chr_y = nh
return dist
}

View file

@ -1,73 +1,73 @@
# water.ludic - one axis decides it: how far the bed is below the surface where the body stands.
# Under CHAR_WADE_MAX the walk wades; past it the feet leave the bottom. Coming out wants a
# shallower bed (CHAR_STAND_UP) than going in did, so a shelving shore does not flicker.
function chr_enter_water(x: float, z: float) -> void {
if chr_swim { return }
chr_swim = true
chr_dive = false
chr_depth = 0.0
chr_breath = body_breath_max()
chr_float = 0.0
chr_air = false
chr_vy = 0.0
chr_sit = false
chr_x = x
chr_z = z
chr_water = CharacterGround.water(x, z)
chr_y = chr_water - 0.30
chr_say(CHAR_STARTED_SWIMMING, 0.0)
function chr_enter_water(base_st: mut BaseState, character_st: mut CharacterState, x: float, z: float) -> void {
if character_st.chr_swim { return }
character_st.chr_swim = true
character_st.chr_dive = false
character_st.chr_depth = 0.0
character_st.chr_breath = body_breath_max()
character_st.chr_float = 0.0
character_st.chr_air = false
character_st.chr_vy = 0.0
character_st.chr_sit = false
character_st.chr_x = x
character_st.chr_z = z
character_st.chr_water = CharacterGround.water(x, z)
character_st.chr_y = character_st.chr_water - 0.30
chr_say(base_st, character_st, CHAR_STARTED_SWIMMING, 0.0)
}
function chr_leave_water() -> void {
if not chr_swim { return }
chr_swim = false
chr_dive = false
chr_depth = 0.0
chr_y = CharacterGround.height(chr_x, chr_z)
chr_speed = Math.min(chr_speed, 1.0)
chr_say(CHAR_LEFT_WATER, 0.0)
function chr_leave_water(base_st: mut BaseState, character_st: mut CharacterState) -> void {
if not character_st.chr_swim { return }
character_st.chr_swim = false
character_st.chr_dive = false
character_st.chr_depth = 0.0
character_st.chr_y = CharacterGround.height(character_st.chr_x, character_st.chr_z)
character_st.chr_speed = Math.min(character_st.chr_speed, 1.0)
chr_say(base_st, character_st, CHAR_LEFT_WATER, 0.0)
}
# the descent: the jump held pushes the body under, letting go floats it back up
function chr_dive_tick(jump: bool, dt: float) -> void {
let bed = Math.max(chr_bed_depth(chr_x, chr_z), 0.0)
function chr_dive_tick(character_st: mut CharacterState, jump: bool, dt: float) -> void {
let bed = Math.max(chr_bed_depth(character_st.chr_x, character_st.chr_z), 0.0)
let max_d = Math.min(6.0, Math.max(bed - 0.4, 0.0))
if jump and max_d > 0.2 {
chr_dive = true
chr_depth = Math.min(chr_depth + 1.2 * dt, max_d)
character_st.chr_dive = true
character_st.chr_depth = Math.min(character_st.chr_depth + 1.2 * dt, max_d)
} else {
chr_depth = Math.max(chr_depth - 1.6 * dt, 0.0)
if chr_depth < 0.05 { chr_dive = false }
character_st.chr_depth = Math.max(character_st.chr_depth - 1.6 * dt, 0.0)
if character_st.chr_depth < 0.05 { character_st.chr_dive = false }
}
}
# air is spent only under the surface, and comes back fast at the top
function chr_breath_tick(dt: float) -> void {
if not (chr_depth > 0.55) {
chr_breath = Math.min(chr_breath + dt * 12.0, body_breath_max())
function chr_breath_tick(base_st: mut BaseState, character_st: mut CharacterState, dt: float) -> void {
if not (character_st.chr_depth > 0.55) {
character_st.chr_breath = Math.min(character_st.chr_breath + dt * 12.0, body_breath_max())
return
}
chr_breath = chr_breath - dt
if chr_breath < 0.0 {
chr_breath = 0.0
chr_dive = false
chr_depth = Math.max(chr_depth - 3.0 * dt, 0.0)
chr_say(CHAR_OUT_OF_AIR, dt)
character_st.chr_breath = character_st.chr_breath - dt
if character_st.chr_breath < 0.0 {
character_st.chr_breath = 0.0
character_st.chr_dive = false
character_st.chr_depth = Math.max(character_st.chr_depth - 3.0 * dt, 0.0)
chr_say(base_st, character_st, CHAR_OUT_OF_AIR, dt)
}
}
# The edge of the world is a current, not a wall: it takes the outward part of a stroke, so
# turning round always works. Said once every twelve seconds while it is strong.
function chr_current_tick(dt: float) -> void {
let fx = -Math.sin(chr_yaw)
let fz = -Math.cos(chr_yaw)
let take = Math.clamp(CharacterGround.current(chr_x, chr_z, fx, fz), 0.0, 1.0)
function chr_current_tick(base_st: mut BaseState, character_st: mut CharacterState, dt: float) -> void {
let fx = -Math.sin(character_st.chr_yaw)
let fz = -Math.cos(character_st.chr_yaw)
let take = Math.clamp(CharacterGround.current(character_st.chr_x, character_st.chr_z, fx, fz), 0.0, 1.0)
if take > 0.0 {
chr_speed = chr_speed * (1.0 - take)
if take > 0.55 and chr_edge_said < 0.0 {
chr_edge_said = 12.0
chr_say(CHAR_CURRENT, take)
character_st.chr_speed = character_st.chr_speed * (1.0 - take)
if take > 0.55 and character_st.chr_edge_said < 0.0 {
character_st.chr_edge_said = 12.0
chr_say(base_st, character_st, CHAR_CURRENT, take)
}
}
chr_edge_said = chr_edge_said - dt
character_st.chr_edge_said = character_st.chr_edge_said - dt
}