chroma_solstice/docs/adding-npcs.md
Your Name c348b81a93 Add named Marks and cross-room NPC pathfinding
- Mark: a debug non-collidable named destination (pixel-art target icon),
  editor tool + name modal, globally-unique names, programmatic Room:addMark
- NPCs walk to Marks: each RGB copy is a per-room resident (like the player),
  running its own A* on its own colour layer, transferring rooms as it crosses
  seams; rendered and collided by the room it currently stands in
- Player copies are obstacles in NPC pathfinding
- A* is footprint-aware for multi-cell NPCs
- Editor: click an NPC to open a filterable destination picker
- Ink: Dialogue.open binds walk_to; Jorge can be sent to "end"/"begin"
- Fix: a blocked NPC now replans each tick and resumes instead of freezing
- [npc] move diagnostics under DEBUG (start/goal/route or block reason)
- Docs (adding-npcs) + CLAUDE.md LÖVE 11.x/float-colour corrections
- Fix test harness loadSprite stub; movement tests green (28)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-21 15:56:53 -04:00

129 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Adding a talkable NPC
An NPC/sign is just an immovable `Entity` that mixes in `Dialogable`. Bumping it
(a rejected move) opens the dialogue box. Talking is the same everywhere — you
only supply **art** + **a story**.
## 1. Write the story
Create `stories/<name>.ink` — a line, a few choices, an ending:
```ink
yo wassup
* kick the sign
it doesnt budge.
* [say nothing] you have nothing to say to a sign.
- -> END
```
## 2. Compile it
The game can't parse `.ink` (LÖVE has no lpeg), so compile to a `.lua` book:
```sh
lua tools/compile_ink.lua <name>
```
Re-run this **every time you edit the `.ink`** — the game loads the `.lua`, not
the `.ink`. (Needs `lua` + `lua-lpeg` installed.)
## 3. Load the book
In `main.lua`, next to `startSignBook`:
```lua
<name>Book = require "stories.<name>"
```
## 4. Make the entity
In `npc.lua`, copy `Sign` — pick your art and name your book. Include `Walker`
too (it's what lets the NPC path to a Mark; see "Making an NPC walk" below), and
call `self:initWalker()` after `Entity.initialize`:
```lua
Oracle = class("Oracle", Entity)
Oracle:include(Dialogable)
Oracle:include(Walker)
function Oracle:initialize(x, y)
self.book = oracleBook -- from step 3
Entity.initialize(self, {
x = x, y = y, color = "red",
collision = "unmoveable",
sprites = {
red = loadSprite("art/npc/oracle_r.png"),
green = loadSprite("art/npc/oracle_g.png"),
blue = loadSprite("art/npc/oracle_b.png"),
},
})
self:initWalker()
end
```
Per-channel art lives in `art/npc/` as `<name>_r/_g/_b.png` (16px cells).
## 5. Place it in a room
Map a save class to your entity in `Room:createEntity` (`room.lua`), alongside
the `npc` → `Sign` branch:
```lua
if class == "oracle" then
local entity = Oracle:new(x, y)
self:registerNpc(entity)
return entity
end
```
Then add it to a room's `rooms/<room>.sav` `objects` list:
```lua
{ class = "oracle", x = 4, y = 8 }
```
That's it — walk into it and it talks. NPCs are stamped into every colour's
collision matrix, so they block movement and obey the world's colour rules (a
red-only player reads them in red).
# Marks — named destinations
A **Mark** is a debug non-collidable: an editor-only annotation the game reads
back. It carries a globally-unique name, and it's the only thing an NPC can be
sent to. It is *not* a Note — it just shares the debug overlay toggle. In the
editor, click the **target** tool at the bottom of the panel, then click a cell
to drop one and type its name (colliding names auto-suffix `_2`, `_3`, …).
Left-click an existing mark to rename it; right-click to delete. Marks show only
under `DEBUG` while the overlay is on (`Tab`).
You can also create marks from code so they exist without living in a `.sav`:
```lua
room:addMark("gate", 5, 9) -- registers + renders in debug; skipped by serialize
```
# Making an NPC walk
Any NPC that includes `Walker` (see step 4) can be sent to a Mark. From Ink, call
the bound external function — e.g. to leave once the conversation ends:
```ink
Oracle: The way is open now. Go.
~ walk_to("gate")
-> END
```
`walk_to(name)` is wired for every Dialogable (`dialogable.lua`); it resolves the
Mark by name and calls `npc:sendTo`. Under the hood the NPC becomes the player's
RGB model — up to three colour copies, each running its **own** A* on its own
collision layer (`pathfinding.lua`), stepping in real time and pathing across
room seams over the world grid. Copies split apart and additively re-merge to
white on arrival. Pathfinding is footprint-aware, so a 2×2 NPC routes its whole
body around obstacles rather than threading a 1-wide gap.
## Testing without a story
You don't need an Ink book to try it: in the editor (`DEBUG`), **left-click an
NPC** to open the destination picker — a filterable list of every Mark. Type to
filter, click a row (or Enter for the top match) to send it; Esc cancels.