114 lines
7.5 KiB
Markdown
114 lines
7.5 KiB
Markdown
# 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.
|
|
`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
|
|
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
|
|
}
|
|
export port NpcTalk { say: fn(NpcPerson, int) -> string, greeting: fn(NpcPerson) -> string }
|
|
```
|
|
|
|
## 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<NpcFact>` | `NPC_F_GREETED` (`player`), `NPC_F_TALKED` (`player`, `line`, `choice`), `NPC_F_ARRIVED` (`act`) |
|
|
| `npc_system() -> System` | `"npc"`, `PH_SIMULATE`, a reset and no tick; nothing saved |
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
ludic test packages/ludic.npc
|
|
```
|