ludic/packages/ludic.wildlife/ground.ludic
Orkuncakilkaya 81b6782c9d ludic.wildlife (18.2): a walker a crowd moves
wildlife_set_crowd(a, true) hands a walker to a crowd. Its step no longer moves it, and its want is two fields on it: goal_x / goal_z (the point it heads for or faces) and goal_speed (0 when it stands). A route reads those and hands back where the crowd put it through wildlife_moved(a, x, z, vx, vz). That sets its place, its ground, its heading from its velocity and its prints, and updates its row's columns so the spatial index finds it there. Nothing allocates per animal per frame: the want is the animal's own fields, and moved writes in place. The prints and the turn counters moved to walked.ludic (steer.ludic was at the 100-line limit). crowd_test holds it; with crowd off every old test is unchanged. wildlife 26/26.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 00:27:12 +03:00

90 lines
3.4 KiB
Text

# ground.ludic - a walker's tick: its needs, the alert, then the first of charging, reacting, going
# to its spot, walking to something set out, eating, idling and wandering that has it this tick
export function wildlife_tick_ground(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float, gh: float) -> void {
a.goal_speed = 0.0
wl_needs(wildlife_st, a, gh)
let dh = wl_near(wildlife_st, a.x, a.z)
let np = wildlife_st.wl_near_i
a.timer = a.timer - dt
if a.calm > 0.0 { a.calm = a.calm - gh * 60.0 }
if a.spook > 0.0 { a.spook = a.spook - gh * 60.0 }
wl_alert(wildlife_st, a, np, dh, dt)
if a.state == WILD_TRAPPED {
a.speed = 0.0
return
}
if wl_charge(wildlife_st, a, np, dh, dt) { return }
wl_posture(wildlife_st, a, np, dt)
if wl_react(wildlife_st, a, np, dt) { return }
if wl_seek(wildlife_st, a, dt) { return }
if wl_drink(wildlife_st, a, dt, gh) { return }
if wl_approach(wildlife_st, a, dt) { return }
if wl_eat(wildlife_st, a) { return }
let s = wl_sp(wildlife_st, a.sp)
if a.state == WILD_IDLE {
a.speed = 0.0
if a.timer < 0.0 {
if a.timer > -6.0 and s.tracks and wl_rnd(wildlife_st) < 0.06 { wl_fact(wildlife_st, WILD_BED, a) } # a long sit leaves crushed grass
a.state = WILD_WANDER
a.timer = 6.0 + wl_rnd(wildlife_st) * 10.0
wl_clamp(wildlife_st, a)
wl_pick_target(wildlife_st, a, s.home)
}
return
}
wl_head(a, a.tx, a.tz, 2.0, dt)
a.speed = s.walk
wl_step(wildlife_st, a, dt)
if wl_at_target(a, 1.5) or a.timer < 0.0 {
a.state = WILD_IDLE
a.timer = 3.0 + wl_rnd(wildlife_st) * 8.0
}
}
# a charger close enough comes at the nearest player; true while it is charging
function wl_charge(wildlife_st: mut WildlifeState, a: WildAnimal, np: int, dh: float, dt: float) -> bool {
let s = wl_sp(wildlife_st, a.sp)
if s.charges and a.state != WILD_CHARGE and dh < WildlifeWorld.charge_reach() and not (a.calm > 0.0) {
a.state = WILD_CHARGE
a.timer = 2.5
wl_fact(wildlife_st, WILD_CHARGED, a).player = np
}
if a.state != WILD_CHARGE { return false }
wl_face(a, WildlifeWorld.player_x(np), WildlifeWorld.player_z(np), 4.0, dt)
a.speed = s.run
wl_step(wildlife_st, a, dt)
if dh < 2.6 {
wl_fact(wildlife_st, WILD_SHOVED, a).player = np
wl_wander(wildlife_st, a, 8.0)
a.calm = 30.0
}
if a.timer < 0.0 { wl_wander(wildlife_st, a, 6.0) }
return true
}
# Four postures read off the alert. Staring is a REACTION, not a condition: each times out and
# hands the animal back to its business, and only a fresh rise above `seen` looks up again.
function wl_posture(wildlife_st: WildlifeState, a: WildAnimal, np: int, dt: float) -> void {
if wl_sp(wildlife_st, a.sp).tame or a.state == WILD_EAT { return }
if a.alert > 95.0 {
if a.state != WILD_FLEE {
a.state = WILD_FLEE
a.timer = 3.0 + wl_rnd(wildlife_st) * 3.0
a.spook = 120.0 + wl_rnd(wildlife_st) * 180.0
a.alert = 70.0
wl_fact(wildlife_st, WILD_FLED, a).player = np
}
} else if a.alert > 60.0 {
if a.state != WILD_WARY and a.state != WILD_FLEE and a.alert > a.seen {
a.state = WILD_WARY
a.timer = 2.0 + wl_rnd(wildlife_st) * 2.0
}
} else if a.alert > 25.0 {
if a.state != WILD_ALERT and a.state != WILD_WARY and a.alert > a.seen {
a.state = WILD_ALERT
a.timer = 1.5 + wl_rnd(wildlife_st) * 2.5
}
}
if a.alert > a.seen { a.seen = a.alert }
a.seen = Math.max(a.seen - 4.0 * dt, 0.0)
}