# ludic.npc The other people in a place: each with a routine by the hour rather than an intelligence - where they start, where they go and what they do there, when they walk out - walking between spots and round whatever is in the way, never into the water, turning to face a player who comes near, and saying one line at a time from a table of lines. Uses [`ludic.base`](../ludic.base/README.md) and nothing else. ```ludic import "ludic.npc" ``` The package owns the people's state, the census, the walk, the acts, the lines, its own dice and a guest's copy of a host's people. It never draws: the port says a person was `born` (make an actor, key it by the person's `slot`, or `id` across machines) and `gone`, and the game reads `x`, `y`, `z`, `yaw`, `act`, `stalk` and `phase` (2.6 a metre walked) every frame to pose it. Clips are the game's too (`NpcAct.clip` is a hint: `"walk"`, `"idle"`, or `""` for a pose of its own). ## Four tables the game fills - **`NpcKinds`** - a kind of person: `weight` in the census draw (`night`: the only kinds drawn after dark), whether it `stays` the night, `leave_lo` .. `leave_hi` (the hour it walks out), `notice` (m it turns to face a player), `start` (where a new one goes: call `npc_start_near(p, x, z, r0, r1)`), `plan` (the first word on what next; true: decided) and `steps`, its routine. - **`NpcStep`** (inside a kind) - in the hours `from` .. `to` (round midnight when `from > to`), `weight` of the draw, go to `spot` (`near` .. `far` m, `n` the spot's own number) and do `act` for `lo` .. `hi` seconds. A step whose spot cannot be found gives way to the next one in the table. - **`NpcSpots`** - where a step goes; `find(p, step)` sends the person (`npc_go` / `npc_do`, or `npc_step_go`) or says it cannot. The package's own: `here`, `home`, `near_home`, `near_here`, `shore_home`, `shore_here` (facing the water) and `wander`; a game adds its own (a sign, an animal). - **`NpcActs`** - what somebody does; the package's `walk` (`NPCA_WALK`) and `idle` come first, from `acts.lres`. `turns` (false: keeps its eyes on its work), `clip`, and `tick(p, dt)` (the game's while it lasts; true: decided). - **`NpcNames`** (`name`, `body`) and **`NpcLines`** - see below. A kind's `.lres` may name functions (`start: fn my_start`), and a step list is `[{ ... }, ...]`. ## The rules it keeps 1. **A routine, not a mind.** A person plans on arriving and when its act runs out: the kind's `plan`, then a weighted draw among its steps for this hour, then standing a while. 2. **Nobody walks into the lake or up a scramble**: a step into water (0.4 m) or up more than 1.4x the step is refused; eight steps round are tried, and a target that gets no closer for twenty seconds is given up (three times: a few steps anywhere walkable). 3. **Nobody stops where the place says not to** (`NpcWorld.allowed`: a player's camp). They may walk through; an act begun there walks out first. 4. **They face whoever came over to talk**, within the kind's `notice` - unless creeping or at an act that does not `turns` - and go back to their errand when the player walks off. A player coming within notice is a `Greeted` fact, once, until they have gone half as far again. 5. **The census is counted, not tallied**, on the half hour: up to `NpcWorld.want()` (and never past `npc_config`'s cap), plus `NpcWorld.extra()` whatever the crowd. 6. **They leave only out of every player's sight**: after their hour at 120 m, at night (by 22:00, except those who `stay`) at 70 m, and a crowd over what is wanted at 150 m - never while out for something (`goal >= 0`). 7. **No name twice**: not another person's, not one the place keeps (`NpcWorld.taken`), not one that left lately. 8. **Posts** (`npc_place`: a vendor) never walk, are never counted and never leave; they turn to a player within notice and back to their stance. ## Lines as data ``` hush { prio: 5, act: NPCA_PHOTO, fill: true } # the story's words (NpcTalk.say) sighting { prio: 4, chance: 30, fill: true } # considered three times in ten water { prio: 3, act: NPCA_WATER, text: "Boil it, even up here." } hello { prio: 1, text: "Morning.", choices: [{ text: "Any fish?", line: "fish", code: 7 }] } ``` The highest `prio` with anything to say wins, each line of it in table order given its `chance`; a `fill` line's words are `NpcTalk.say(p, line)`, and "" means it has nothing true to say now. `npc_talk` picks one (a `Talked` fact); `npc_choose` answers a choice with the line it names (a `Talked` fact carrying the choice's `code`). A vendor's greeting is `NpcTalk.greeting(p)`. ## The ports ```ludic export port NpcWorld { # every member has a default: a flat dry field at noon, one player, nobody wanted ground, water, slope: fn(float, float) -> float allowed: fn(float, float) -> bool # may a person stop here push: fn(x, z, r, feet, head) -> bool, pushed_x, pushed_z # a walker slid clear of still things (r 0.35) clear: fn(ax, ay, az, bx, by, bz) -> bool # no still thing between: npc_line_ok asks it at 1 m hour: fn() -> float, day: fn() -> int # the clock players: fn() -> int, player_on: fn(int) -> bool, player_x / player_z: fn(int) -> float want: fn() -> int, extra: fn() -> int # the census: how many, and a kind brought out regardless (-1) origin_x / origin_z: fn() -> float # where anyone goes when nowhere else will do next_id: fn() -> int, body_of: fn(int) -> int # an id across machines; a look seed's body taken: fn(string) -> bool # a name the place keeps born / gone: fn(NpcPerson) -> void # draw it, stop drawing it way: fn(NpcPerson, x, z) -> bool, way_x, way_z # the next corner of its way toward (x, z) } export port NpcTalk { say: fn(NpcPerson, int) -> string, greeting: fn(NpcPerson) -> string } ``` **A person walks by the world's way where the world has one.** `way` is asked for the next corner toward where it is going - every half second, on reaching the corner, and at once for a new target - and the person walks corner to corner, but begins its errand only at the target itself. With no answer (the default) it walks straight and steps round what is in the way, as before; the step round and the twenty seconds' give-up stay as the fallback. Maroon Lake binds `way` to its navmesh by a person's taste. ## API | | | | --- | --- | | `NpcPerson`, `npc_walkers()`, `npc_posts()`, `npc_at(slot)`, `npc_post(i)`, `npc_by_id(id)`, `npc_count()`, `npc_max()` | the people (`goal`, `subj`, `note`, `note_h`, `flags` are the game's) | | `npc_config(max)`, `npc_seed(n)`, `npc_rnd()`, `npc_between(lo, hi)`, `npc_secs(lo, hi)` | the cap and the package's own dice | | `npc_tick(dt)`, `npc_posts_tick(dt)`, `npc_census_now()`, `npc_hold(on)`, `npc_reset()`, `npc_clear()` | a step where the place is run (or of the posts alone), the census at once, held still, a new trip, a new place | | `npc_spawn(kind)`, `npc_place(kind, x, z, yaw, name, look)`, `npc_retire(p)`, `npc_start_near(p, x, z, r0, r1)`, `npc_set_at(p, x, z)` | making and leaving | | `npc_go(p, x, z, act, secs)`, `npc_do(p, act, secs)`, `npc_face(p, x, z)`, `npc_face_ahead(p)`, `npc_set_home_face(p, x, z)`, `npc_plan(p)`, `npc_steps(p, steps)`, `npc_step_go(p, step, x, z)`, `npc_wander(p)` | the routine | | `npc_walk(p, dt)`, `npc_act(p, dt)`, `npc_notice(p)` | a person's step | | `npc_walkable(x, z)`, `npc_line_ok(ax, az, bx, bz)`, `npc_spot_near(x, z, d0, d1)`, `npc_toward(ax, az, bx, bz, d)`, `npc_shore(x, z, r)`, `npc_px()`, `npc_pz()`, `npc_wx()`, `npc_wz()` | the ground | | `npc_near(x, z)`, `npc_near_i()`, `npc_d(...)`, `npc_wrap(a)` | the nearest player, distances, angles | | `npc_pick_name(body)`, `npc_name_used(n)`, `npc_name_clashes()` | names | | `npc_talk(p, player)`, `npc_line_for(p)`, `npc_words(p, line)`, `npc_said()`, `npc_choose(p, line, c, player)`, `npc_talked_today(p)`, `npc_greeting(p)` | talk | | `npc_mirror(on)`, `npc_mirroring()`, `npc_put(slot, id, kind, look, seed, name, hx, hz)`, `npc_heard(slot, x, y, z, yaw, speed, act, stalk)`, `npc_heard_lately(p)`, `npc_mirror_tick(dt)` | a guest's copy of the host's people | | `npc_facts() -> Queue` | `NPC_F_GREETED` (`player`), `NPC_F_TALKED` (`player`, `line`, `choice`), `NPC_F_ARRIVED` (`act`), `NPC_F_STUCK` (`how` - `NPC_STUCK_DETOUR`, `_RETRY`, `_GAVE_UP` - at `x`, `z`: the way failed it and it fell back) | | `npc_system() -> System` | `"npc"`, `PH_SIMULATE`, a reset and no tick; nothing saved | ## Tests ```bash ludic test packages/ludic.npc ```