ludic/packages/ludic.wildlife/lures.ludic
Orkuncakilkaya fe2d2bacd7 ludic.base, ludic.wildlife, ludic.npc: fact records reused, not made per fact
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>
2026-09-28 01:58:19 +03:00

96 lines
3.3 KiB
Text

# lures.ludic - what an animal would walk to: food of its diet, a lure of its diet, a snare it is
# small enough for (half the time: a curiosity), within 70 m - 175 m for food while feeding is taught
function wl_lure_scratch() -> void {
}
function wl_copy_lure(from: WildLure, to: WildLure) -> void {
to.uid = from.uid
to.kind = from.kind
to.diet = from.diet
to.size = from.size
to.x = from.x
to.z = from.z
to.owner = from.owner
}
function wl_draws(wildlife_st: WildlifeState, a: WildAnimal, l: WildLure) -> bool {
let s = wl_sp(wildlife_st, a.sp)
if l.kind == WILD_SNARE { return s.size <= l.size and wl_rnd(wildlife_st) > 0.5 }
return l.diet == s.diet and l.diet != WILD_SEED
}
# the nearest thing set out that draws it, into `wl_best`; false when none does
function wl_attraction(wildlife_st: WildlifeState, a: WildAnimal) -> bool {
wl_lure_scratch()
var bd = 70.0
var got = false
let taught = WildlifeWorld.taught()
for i in 0 .. WildlifeWorld.lures() {
if not WildlifeWorld.lure(i, wildlife_st.wl_ask) or not wl_draws(wildlife_st, a, wildlife_st.wl_ask) { continue }
let dx = wildlife_st.wl_ask.x - a.x
let dz = wildlife_st.wl_ask.z - a.z
var d = Math.sqrt(dx * dx + dz * dz)
if taught and wildlife_st.wl_ask.kind == WILD_FEED { d = d * 0.4 }
if d < bd {
bd = d
got = true
wl_copy_lure(wildlife_st.wl_ask, wildlife_st.wl_best)
}
}
return got
}
function wl_take_lure(wildlife_st: WildlifeState, a: WildAnimal) -> void {
a.lure = wildlife_st.wl_best.uid
a.lure_kind = wildlife_st.wl_best.kind
a.lure_x = wildlife_st.wl_best.x
a.lure_z = wildlife_st.wl_best.z
a.lure_owner = wildlife_st.wl_best.owner
a.state = WILD_APPROACH
}
# Is there ANY seed down? Asked once a second for the whole flock rather than per bird per tick.
function wl_seed_scan(wildlife_st: mut WildlifeState, dt: float) -> void {
wildlife_st.wl_seed_t = wildlife_st.wl_seed_t - dt
if wildlife_st.wl_seed_t > 0.0 { return }
wildlife_st.wl_seed_t = 1.0
wildlife_st.wl_seed_any = false
wl_lure_scratch()
for i in 0 .. WildlifeWorld.lures() {
if WildlifeWorld.lure(i, wildlife_st.wl_ask) and wildlife_st.wl_ask.kind == WILD_FEED and wildlife_st.wl_ask.diet == WILD_SEED {
wildlife_st.wl_seed_any = true
return
}
}
}
# the nearest seed within 120 m, into `wl_best`: the one thing that brings an animal TO you
function wl_bird_seed(wildlife_st: WildlifeState, a: WildAnimal) -> bool {
if not wildlife_st.wl_seed_any { return false }
wl_lure_scratch()
var bd = 120.0
var got = false
for i in 0 .. WildlifeWorld.lures() {
if not WildlifeWorld.lure(i, wildlife_st.wl_ask) or wildlife_st.wl_ask.kind != WILD_FEED or wildlife_st.wl_ask.diet != WILD_SEED { continue }
let dx = wildlife_st.wl_ask.x - a.x
let dz = wildlife_st.wl_ask.z - a.z
let d = Math.sqrt(dx * dx + dz * dz)
if d < bd {
bd = d
got = true
wl_copy_lure(wildlife_st.wl_ask, wildlife_st.wl_best)
}
}
return got
}
# it ate what was set out: calm for three game hours, and the game credits whoever left it
function wl_ate(wildlife_st: mut WildlifeState, a: WildAnimal, uid: int, owner: int, diet: int) -> void {
a.calm = 180.0
a.fed += 1
let f = wl_fact(wildlife_st, WILD_FED, a)
f.lure = uid
f.owner = owner
f.diet = diet
}