ludic/packages/ludic.things/README.md

103 lines
6.1 KiB
Markdown

# ludic.things
The things placed in a world - a tent, a fire ring, a branch on the ground, a snare - and the
kinds they are. The game names the kinds and draws them; the package keeps every Thing, answers
where they are, asks a kind what it does, reports what happened, and saves the ones worth
keeping. Uses [`ludic.base`](../ludic.base/README.md) and nothing else.
```ludic
import "ludic.things"
```
## A Thing
```ludic
property Thing {
uid, kind, id # unique for the run; its place in ThingKinds; what the kind says it means
look # how the game draws it ("" is not drawn)
x, y, z, yaw
owner # whoever put it down, -1 the world's own
active # false: taken, hidden, used up for now
reach # how close it is used from (0: its kind's)
timer, used, n # state the kind decides the meaning of
sx, sz, syaw # a heading or a point the kind decides the meaning of
seat, seats, taken # its seats: the first of the game's seat rows, how many, a bit per one sat in
dawn # the last day a restock reached it (saved), for the catch-up
shared # the game copies it to others (a party)
}
```
## The kinds
`ThingKinds` is an `open registry`, filled by the game - usually one `def` per kind in one file,
so `TH_<KEY>` is the order they are written in. A kind's behaviour is function values; a null one
is a question the kind ignores.
```ludic
def ThingKinds fire { name: "The campfire", prompt: fn fire_prompt, use: fn fire_use }
def ThingKinds lure { name_of: fn bait_name, tick: fn bait_wear, put_down: true, saved: true }
```
| field | |
| --- | --- |
| `name`, `name_of: fn(Thing) -> string` | what it is called (`name_of` answering "" falls back to `name`) |
| `prompt: fn(Thing) -> string`, `use: fn(Thing)` | what using it would do, and doing it |
| `tick: fn(Thing, float)` | hours of the world's clock, for every active one |
| `reach`, `reach_of: fn(Thing) -> float` | how close it is used from |
| `personal` | one belongs to whoever put it down |
| `saved` | the save section keeps a placed one (the port has the last word) |
| `put_down` | set down by someone rather than grown by the world: a restock leaves it (the port has the last word) |
| `keeps` | keeps its `used` through a restock |
**A removed Thing's record is placed again.** Ludic frees nothing, and a world places and takes
away Things all day, so `thing_remove` keeps the record and `thing_put` / `thing_spawn` make the
oldest one again - set back as new, with a new `uid` - once 32 are waiting. Hold a Thing across a
removal only by its `uid` (`thing_by_uid`), never by the record.
A fact's record (`things_facts()`) is kept too: it comes back two drains after it was handed out, so
read a drained list before the drain after next, and copy what must last longer.
## The port
Every member has a default, so any may be left out of the `bind`:
```ludic
export port ThingsWorld {
ground: fn(float, float) -> float # the height a Thing stands at (unbound: 0)
placed: fn(Thing) # it exists now: draw it
removed: fn(Thing) # gone for good: stop drawing it
shown: fn(Thing) # its `active` changed
moved: fn(Thing) # it stands somewhere else
keeps: fn(Thing) -> bool # the save carries it (unbound: its kind's `saved`)
put_down: fn(Thing) -> bool # a restock leaves it be (unbound: its kind's `put_down`)
restored: fn(Thing) # a load put it back, after `placed`
}
```
`placed`, `removed`, `shown` and `moved` are called at once, inside the verb, so the game can make
the actor the moment the Thing exists (a caller often scales or tints it on the next line).
## API
| | |
| --- | --- |
| `thing_spawn(kind, look, x, y, z, yaw, id) -> Thing`, `thing_put(kind, look, x, z, yaw, sink, reach, id) -> Thing` | a new Thing, at a height or on the ground set into it by `sink` |
| `thing_remove(t)`, `things_drop(gone: fn(Thing) -> bool)`, `things_clear()` | gone for good (told to the port); a new world, quietly |
| `things_reserve(n)`, `things_spare_count()` | room in the grid for `n` Things, made once at the start; how many removed records wait to be placed again |
| `thing_move(t, x, z, yaw)`, `thing_move_to(t, x, y, z, yaw)` | on the ground there, or at a height |
| `thing_set_seats(t, first, n)`, `thing_seat_take(t, i) -> bool`, `thing_seat_leave(t, i)`, `thing_seat_taken(t, i)`, `thing_seat_free(t) -> int` | its seats: a run of the game's seat rows (`seat`, `seats`, saved) and a bit per seat someone is in (`taken`, never saved; up to `THING_SEATS_MAX`) |
| `thing_hide(t)`, `thing_show(t)`, `thing_set_active(t, on)` | `active`, told to the port |
| `things_all()`, `things_count()`, `things_ready()`, `thing_by_uid(uid)` | every Thing, in the order placed; whether a world was ever set out |
| `thing_find(kind, id)`, `thing_nearest(kind, x, z)`, `things_within(x, z, r)`, `things_each(kind)`, `things_each_all(kind)`, `thing_dist2(t, x, z)` | where they are: only active ones answer except the `_all`; a kind below 0 is any; `within` is nearest first |
| `thing_name(t)`, `thing_prompt(t)`, `thing_use(t)`, `thing_can_use(t)`, `thing_reach(t)`, `thing_kind(t)`, `thing_is_personal(t)`, `thing_is_put_down(t)` | the dispatcher |
| `things_tick(hours)`, `things_restock(day)` | every active kind that ticks; every Thing the world grew back and unused where a morning reaches (`ThingsWorld.restocks`), its `dawn` set to the day |
| `things_catch_up(chunk, day, out)` | a chunk's Things that missed a dawn (`dawn < day`) and a morning now reaches, restocked as that morning would have (no dice), into the caller's list |
| `things_facts() -> Queue<ThingFact>` | `{ what, uid, kind, id, owner, x, z }`: `THING_PLACED`, `THING_REMOVED`, `THING_USED` |
| `thing_kind_ok(k)`, `thing_kind_by_key(key)` | the kinds |
| `things_system() -> System` | `"things"`, `PH_COMMIT`: its save section - every active Thing the port keeps, its kind by KEY, so the game may reorder its kinds - and a reset that removes the kept ones for the load to put back |
## Tests
```bash
ludic test packages/ludic.things
```