feat(packages): ludic.npc - the other people in a place: kinds, spots, acts, names and lines as open registries (a kind's routine is steps by the hour - a spot, an act, how long - with its own start and plan as function values, readable from .lres), a census on the half hour by weight (after dark only who stays out) with an extra the place asks for, walking round what is in the way and never into the water or up a scramble, no act where the place says nobody stops, facing a player within notice and back to the errand, leaving only out of every player's sight, unique names, posts that never walk (a vendor), lines by prio and chance with the story's words through a port and choices, a guest's copy of a host's people; greeted, talked and arrived as facts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
73a376ac6c
commit
029a1ebba2
23 changed files with 1664 additions and 0 deletions
120
packages/ludic.npc/census.ludic
Normal file
120
packages/ludic.npc/census.ludic
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
# census.ludic - making one (a slot reused before a new one, a look, a name, a start on its own
|
||||
# ground out of the players' sight where that can be had), a post's fixed person, and leaving
|
||||
export function npc_spawn(kind: int) -> NpcPerson {
|
||||
np_ensure()
|
||||
if np_n >= np_max or kind < 0 or kind >= NPCK_COUNT { return null }
|
||||
var p: NpcPerson = null
|
||||
for i in 0 .. len(np_walkers) {
|
||||
if not np_walkers[i].active {
|
||||
p = np_walkers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if p == null {
|
||||
if len(np_walkers) >= np_max { return null }
|
||||
p = new NpcPerson
|
||||
p.slot = len(np_walkers)
|
||||
push(np_walkers, p)
|
||||
}
|
||||
np_fresh(p, kind, npc_between(1, 99999))
|
||||
p.name = npc_pick_name(p.body)
|
||||
let k = NpcKinds[kind]
|
||||
p.leg = npc_between(0, 3)
|
||||
p.seed = npc_rnd() * 40.0
|
||||
p.out_h = k.leave_lo + npc_rnd() * (k.leave_hi - k.leave_lo)
|
||||
p.yaw = npc_rnd() * NP_TAU
|
||||
if k.start != null {
|
||||
let start = k.start
|
||||
start(p)
|
||||
} else { npc_start_near(p, NpcWorld.origin_x(), NpcWorld.origin_z(), 6.0, 30.0) }
|
||||
p.active = true
|
||||
np_n += 1
|
||||
NpcWorld.born(p)
|
||||
npc_plan(p)
|
||||
return p
|
||||
}
|
||||
|
||||
function np_fresh(p: NpcPerson, kind: int, look: int) -> void {
|
||||
p.kind = kind
|
||||
p.id = NpcWorld.next_id()
|
||||
p.look = look
|
||||
p.body = NpcWorld.body_of(look)
|
||||
p.fixed = false
|
||||
p.said = -1
|
||||
p.near = 0
|
||||
p.goal = -1
|
||||
p.subj = -1
|
||||
p.note = -1
|
||||
p.note_h = 0.0
|
||||
p.flags = 0
|
||||
p.stalk = false
|
||||
p.stall = 0.0
|
||||
p.stall_max = 0.0
|
||||
p.fails = 0
|
||||
p.phase = 0.0
|
||||
p.home_face = false
|
||||
p.heard = false
|
||||
p.act = NPCA_IDLE
|
||||
p.act_t = 0.0
|
||||
p.speed = 0.0
|
||||
p.via_on = false
|
||||
}
|
||||
|
||||
# its own ground: a spot r0 .. r1 from (cx, cz), tried a few times for one no player is within 60 m
|
||||
# of, else near the place's origin; that is home, where it stands and faces
|
||||
export function npc_start_near(p: NpcPerson, cx: float, cz: float, r0: float, r1: float) -> void {
|
||||
var x = NpcWorld.origin_x()
|
||||
var z = NpcWorld.origin_z()
|
||||
var ok = false
|
||||
for t in 0 .. 8 {
|
||||
if not npc_spot_near(cx, cz, r0, r1) { continue }
|
||||
x = np_px
|
||||
z = np_pz
|
||||
ok = true
|
||||
if npc_near(x, z) > 60.0 { break }
|
||||
}
|
||||
if not ok and npc_spot_near(x, z, 10.0, 45.0) {
|
||||
x = np_px
|
||||
z = np_pz
|
||||
}
|
||||
npc_set_at(p, x, z)
|
||||
}
|
||||
|
||||
# put it down at (x, z) as its home, stood still
|
||||
export function npc_set_at(p: NpcPerson, x: float, z: float) -> void {
|
||||
p.x = x
|
||||
p.z = z
|
||||
p.y = NpcWorld.ground(x, z)
|
||||
p.hx = x
|
||||
p.hz = z
|
||||
p.tx = x
|
||||
p.tz = z
|
||||
}
|
||||
|
||||
# somebody fixed at a post (a vendor behind the counter): never walks, never counted, never leaves,
|
||||
# turns to a player within its kind's notice and back to `yaw` when they go
|
||||
export function npc_place(kind: int, x: float, z: float, yaw: float, name: string, look: int) -> NpcPerson {
|
||||
np_ensure()
|
||||
let p = new NpcPerson
|
||||
p.slot = len(np_posts)
|
||||
np_fresh(p, kind, look)
|
||||
p.fixed = true
|
||||
p.name = name
|
||||
p.seed = float(p.slot) * 0.9
|
||||
p.stance = yaw
|
||||
p.yaw = yaw
|
||||
npc_set_at(p, x, z)
|
||||
p.active = true
|
||||
push(np_posts, p)
|
||||
NpcWorld.born(p)
|
||||
npc_do(p, NPCA_IDLE, 3600.0)
|
||||
return p
|
||||
}
|
||||
|
||||
export function npc_retire(p: NpcPerson) -> void {
|
||||
if not p.active { return }
|
||||
p.active = false
|
||||
if not p.fixed and np_n > 0 { np_n -= 1 }
|
||||
np_left(p.name)
|
||||
NpcWorld.gone(p)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue