wl_fact made a new WildFact per fact, about one a frame, and Ludic never gives one back. ludic.base's q_unheld(q, pool, out) lists the records a queue no longer holds: neither waiting nor handed out by its last drain, which is good until the next drain. ludic.wildlife and ludic.npc now keep a pool, hand out a free record, and make a new one only when every record is held. Taking a record writes the state, so wl_fact / np_fact and the few callers holding their state read-only take it mut. Tests: q_unheld's rules (an empty drain holds on, the safe side); 3000 prints drained frame by frame use at most 4 records; 500 arrivals drained turn by turn use at most 4. base 37, wildlife 27, npc 17; all packages 420. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
2.7 KiB
Text
81 lines
2.7 KiB
Text
# react.ludic - alert (it stops and looks: the window the camera wants), wary (it puts ground between
|
|
# you without running) and fleeing; true when it is doing one of them, which is all it does this tick
|
|
function wl_react(wildlife_st: mut WildlifeState, a: WildAnimal, np: int, dt: float) -> bool {
|
|
let s = wl_sp(wildlife_st, a.sp)
|
|
if a.state == WILD_ALERT {
|
|
a.speed = 0.0
|
|
wl_face(a, a.sus_x, a.sus_z, 2.2, dt)
|
|
if a.timer < 0.0 { wl_wander(wildlife_st, a, 3.0 + wl_rnd(wildlife_st) * 4.0) }
|
|
return true
|
|
}
|
|
if a.state == WILD_WARY {
|
|
wl_away(a, a.sus_x, a.sus_z, 20.0)
|
|
wl_head(a, a.tx, a.tz, 2.6, dt)
|
|
a.speed = s.walk * 0.8
|
|
wl_step(wildlife_st, a, dt)
|
|
if a.timer < 0.0 { wl_wander(wildlife_st, a, 3.0 + wl_rnd(wildlife_st) * 4.0) }
|
|
return true
|
|
}
|
|
if a.state == WILD_FLEE {
|
|
wl_away(a, WildlifeWorld.player_x(np), WildlifeWorld.player_z(np), 40.0)
|
|
wl_head(a, a.tx, a.tz, 5.0, dt)
|
|
a.speed = s.run
|
|
wl_step(wildlife_st, a, dt)
|
|
if a.timer < 0.0 {
|
|
wl_rehome(wildlife_st, a)
|
|
wl_wander(wildlife_st, a, 5.0)
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
# walking to (or reached) something set out; true while it is
|
|
function wl_approach(wildlife_st: mut WildlifeState, a: WildAnimal, dt: float) -> bool {
|
|
if (a.state == WILD_IDLE or a.state == WILD_WANDER) and wl_attraction(wildlife_st, a) { wl_take_lure(wildlife_st, a) }
|
|
if a.state != WILD_APPROACH { return false }
|
|
if a.lure == 0 or not WildlifeWorld.present(a.lure) {
|
|
a.lure = 0
|
|
a.state = WILD_IDLE
|
|
a.timer = 2.0
|
|
return true
|
|
}
|
|
wl_face(a, a.lure_x, a.lure_z, 3.0, dt)
|
|
a.speed = wl_sp(wildlife_st, a.sp).walk
|
|
let dx = a.lure_x - a.x
|
|
let dz = a.lure_z - a.z
|
|
if Math.sqrt(dx * dx + dz * dz) < 1.3 {
|
|
a.speed = 0.0
|
|
wl_arrive(wildlife_st, a)
|
|
return true
|
|
}
|
|
wl_step(wildlife_st, a, dt)
|
|
return true
|
|
}
|
|
|
|
# at a snare it is held; at food it eats for 14 s; at a lure it stands curious for 9
|
|
function wl_arrive(wildlife_st: mut WildlifeState, a: WildAnimal) -> void {
|
|
a.state = WILD_EAT
|
|
a.timer = 9.0
|
|
if a.lure_kind == WILD_FEED { a.timer = 14.0 }
|
|
if a.lure_kind != WILD_SNARE { return }
|
|
a.state = WILD_TRAPPED
|
|
a.x = a.lure_x
|
|
a.z = a.lure_z
|
|
a.y = WildlifeWorld.ground(a.x, a.z)
|
|
let f = wl_fact(wildlife_st, WILD_CAUGHT, a)
|
|
f.lure = a.lure
|
|
f.owner = a.lure_owner
|
|
}
|
|
|
|
# eating what it came to; when it is done, food is credited and it wanders off
|
|
function wl_eat(wildlife_st: mut WildlifeState, a: WildAnimal) -> bool {
|
|
if a.state != WILD_EAT { return false }
|
|
a.speed = 0.0
|
|
if a.timer < 0.0 {
|
|
if a.lure != 0 and a.lure_kind == WILD_FEED and WildlifeWorld.present(a.lure) { wl_ate(wildlife_st, a, a.lure, a.lure_owner, wl_sp(wildlife_st, a.sp).diet) }
|
|
a.lure = 0
|
|
wl_wander(wildlife_st, a, 4.0)
|
|
}
|
|
return true
|
|
}
|