aboutsummaryrefslogtreecommitdiff
path: root/AGENTS.md
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-08 19:59:14 -0400
committerhistoria <[not public]>2026-07-08 19:59:14 -0400
commit09d325dcf5e779eab5550d3fd3377bde50101428 (patch)
treea433be903aabbf1d2eadce2aacdac12a9eb8dde0 /AGENTS.md
parent9184377301c2604e003f36426788c032fb0ca524 (diff)
downloadthehouseoficarus-09d325dcf5e779eab5550d3fd3377bde50101428.tar.gz
feat: unify on use, on look, and on kill. all support same conditions/actions now.
Diffstat (limited to 'AGENTS.md')
-rw-r--r--AGENTS.md30
1 files changed, 25 insertions, 5 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 893ba59..9016e73 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -19,7 +19,7 @@ File prefixes in `internal/game/`: `game.go/cmd_registry.go/tick.go` (core), `co
## Key Conventions
-**YAML-driven.** Rooms, items, objects, mobs, drops, modules, courses under `data/`. Read from disk on every access — edit and it takes effect immediately. **The filename is the ID** for every data type: lookup by filename stem. **Do not put a top-level `id:` field** (ignored). A nested `- id:` under a room's `objects:`/`mobs:` is a *reference* and is still required. Subdirectories are organizational only. **Local objects:** a room `objects:` entry that carries content fields (`name`/`description`/`aliases`/`inroom_description`/`color`/`hidden`/`on_look`) is a fully local, room-scoped object def instead of a reference — passive subset only (no gather/talk/use/safespot/steal/guard_mob/use_interactions). Local identity derives from the normalized `name` (lowercased, whitespace-collapsed, spaces kept); `id:` is reserved for references and ignored+warned on local objects. Each object in a room must have a unique name (startup error on collision, incl. local-name vs referenced file id); names may repeat across different rooms.
+**YAML-driven.** Rooms, items, objects, mobs, drops, modules, courses under `data/`. Read from disk on every access — edit and it takes effect immediately. **The filename is the ID** for every data type: lookup by filename stem. **Do not put a top-level `id:` field** (ignored). A nested `- id:` under a room's `objects:`/`mobs:` is a *reference* and is still required. Subdirectories are organizational only. **Local objects:** a room `objects:` entry that carries content fields (`name`/`description`/`aliases`/`inroom_description`/`color`/`hidden`/`on_look`) is a fully local, room-scoped object def instead of a reference — passive subset only (no gather/talk/use/safespot/steal/guard_mob/on_use). Local identity derives from the normalized `name` (lowercased, whitespace-collapsed, spaces kept); `id:` is reserved for references and ignored+warned on local objects. Each object in a room must have a unique name (startup error on collision, incl. local-name vs referenced file id); names may repeat across different rooms.
**Live state.** Player data persisted to YAML on every change. Ground items, mob instances, object states are in-memory only.
@@ -38,9 +38,9 @@ File prefixes in `internal/game/`: `game.go/cmd_registry.go/tick.go` (core), `co
## Room Scripting
-- **`on_enter` steps**: message + optional condition. A step may have `delay` and/or `set_global_flags`/`set_player_flags`. Timed steps run as scheduled enter sequences; otherwise messages print synchronously. Conditions snapshotted once at entry. Resumes on reconnect if interrupted.
+- **`on_enter` steps** (`[]behavior.StepAction`): message + optional `condition` + `delay` + any of the shared `StepAction` effects (`set_global_flags`/`set_player_flags`, `give_item`/`take_item`, `teleport`, `heal`, `credits`, `aps_node`, `broadcast`, `broadcast_global`, `spawn_mob`, `despawn_mob`). Timed steps (any non-zero field triggers the timed path) run as scheduled enter sequences; otherwise messages print synchronously. Conditions snapshotted once at entry. Resumes on reconnect if interrupted.
- **Conditional descriptions** (`description: [{text, condition}]`): first passing variant wins. For objects, if no variant matches the object is absent for that player. Accepts plain string or list.
-- **Exits** can `set_global_flags`/`set_player_flags` on successful traverse.
+- **Exits** carry an `on_traverse:` list of `behavior.Interaction` (same shape as on_use) — first passing entry fires AFTER `p.RoomID` is updated to the destination, so `aps_node`/`teleport` resolve against the new room. `ExitDef.Condition` still gates whether the exit is passable at all; each `on_traverse` entry has its own additional `Condition`.
## Data Files
@@ -77,9 +77,29 @@ Actions route through `startAction()` in `act.go`: normalize verb → resolve ta
**Behavior types (YAML-driven):** `gather` (mine/chop/cut/fish), `use` (crafting stations), `talk` (NPC dialog). **Hardcoded actions:** burn/stoke, search, production (cook/smelt/smith/craft/mix/fletch/clean), steal, agility, farm, identify, finishing blow. Gather behaviors use SuccessChance = Base + (level-required)*PerLevel, clamped `[0, Cap]`. Action types and all YAML config fields are self-documenting in the source (`act_state.go`, `behavior.go`, `item/item.go`).
+### Interactions (unified conditional-action model)
+
+`behavior.Interaction` (struct in `behavior/behavior.go`) is the shared per-entry shape for every "when X happens, do Y with optional condition" feature in the game:
+
+- **On Use** (`on_use:` on objects) — fires on `use <obj>` (bare, no `item_id`) or `use <item> on <obj>` (item-keyed). Triggered in `cmd_use.go`; queued as a one-tick action; tick executor in `act_use_interaction.go`.
+- **On Look** (`on_look:` on objects) — fires after the object's description is shown on `look <obj>`. Triggered in `look_target.go`; runs the first passing entry inline (no queue). An entry with an `item_id` only fires if the player currently carries that item in their inventory.
+- **On Kill** (`on_kill:` on mobs) — fires (additively over standard loot) when a combat kill of the mob completes OR when a `kind: task` mob's work drains its HP to zero (the "completion" of the task). Triggered from `endCombat` in `combat_mob.go`. An entry with an `item_id` only fires if the player is wielding that item in a weapon-hand slot (main_hand or off_hand) at the moment of the kill.
+- **Exit On Traverse** (`on_traverse:` on exits) — fires AFTER `p.RoomID` is set to the destination, so `aps_node`/`teleport` resolve against the new room. Stashed in `Player.MovePendingInteraction` at move-classify time, applied in `completeMove` (`cmd_move.go`).
+- **On Enter steps** (`on_enter:` on rooms) — list of `behavior.StepAction`; each step has its own `Condition` + `Message`/`Delay` + full effect set. Reconnect-resume. Driven by `enterSeq`/`EnterSeqTick` in `act_room.go`.
+- **Trigger steps** (`steps:` on room/global `TriggerDef`) — same `StepAction` per step, newly supports per-step `Condition`. Trigger watcher + async playback in `sys_triggers.go` / `trigger_store.go`. The event-listener / cascade / re-entry-guard machinery is separate from interactions' inline dispatch.
+- **Talk** — `TalkNode.Action` and `TalkOption.Action` are `*NodeAction` (inline subset). Wrap via `handleNodeAction` → `applyNodeAction`.
+
+Each list of Interactions is walked top-to-bottom; the **first** entry whose item filter and `Condition` pass wins and fires (one entry per triggering event). `applyInteraction` (`act_effects.go`) is the convenience wrapper for that walk + fire; individual callers can loop directly. The item filter is interaction-kind-specific: on_use matches the held item, on_look requires `p.HasItem`, on_kill requires `p.IsWielding`; nil disables the filter (exit traversal, on_traverse).
+
+**Effect superset — `behavior.StepAction`.** The universal effect container embedded by all interaction `action` fields. Holds the inline-only `NodeAction` fields (`set_global_flags`, `set_player_flags`, `give_item`, `take_item`, `teleport`, `heal`, `credits`, `aps_node`) PLUS the sequence-only fields (`message`, `broadcast`, `broadcast_global`, `spawn_mob`, `despawn_mob`, `delay`) and a per-step `Condition`. Inline callers (use/look/kill/talk) ignore the sequence-only fields; on_enter / trigger steps use them.
+
+**One executor — `applyStepAction(sess, p, step, roomID, scope, flagValue)` in `act_effects.go`.** Scopes: `scopePlayer` (inline: flags + items + heal + credits + teleport + aps_node + broadcast), `scopePlayerSeq` (on_enter / trigger player seq: above + broadcast_global + spawn_mob + despawn_mob), `scopeGlobal` (trigger global seq: broadcasts + global flags + world-owned spawn/despawn only). `applyNodeAction` (`act_talk.go`) is a thin wrapper passing a NodeAction-wrapped StepAction at scopePlayer; `fireEnterStep` / `executeTriggerStep` / `executeGlobalTriggerStep` are thin wrappers at scopePlayerSeq / scopeGlobal. The old duplicate executors (`applyNodeAction` body, `fireEnterStep` body, `executeTriggerStep`, `executeGlobalTriggerStep`) collapsed into one in `act_effects.go`.
+
+`aps_node` retains its dedicated bool field — it stamps `aps_node_<currentRoomID>: true` on the player, which the `aps` command (`cmd_aps.go`) reads to build the datapad. (The room-id is dynamic, so it can't be expressed as a static `set_player_flags` entry without template substitution.) `reputation_cost` was removed (assassin reputation shop is not yet wired up).
+
### Condition System
-Used by exits, talk options, on-enter, use_interactions: `global_flag` (global), `player_flag` (per-char), `value` (exact match), `has_item`, `min_credits`, `all_of`, `any_of`, `not`. Bare global_flag/player_flag passes on truthy.
+Used by exits (exit gate + `on_traverse`), talk options/nodes, on-enter steps, on_use / on_look (objects), on_kill (mobs), trigger steps: `global_flag` (global), `player_flag` (per-char), `value` (exact match), `has_item`, `min_credits`, `all_of`, `any_of`, `not`. Bare global_flag/player_flag passes on truthy.
## Two Depletion Mechanics
@@ -145,7 +165,7 @@ Full field reference in `item/item.go`. Key fields: equip_slot, weapon_type, att
- `startAction` searches objects first, then mobs. Mobs need `behavior:` on YAML def to be interactable.
- Exits: old `north: 2` still works via custom `UnmarshalYAML`. Same for `RoomMob`.
-- `checkCondition` is the single condition evaluator — shared across exits, talk, on-enter, use_interactions.
+- `checkCondition` is the single condition evaluator — shared across exits, talk, on-enter, on_use/on_look (objects), on_kill (mobs), trigger steps. Every interaction type and every timed step shares one condition language.
- Object `hidden: true` = not in room listings but still interactable.
- Alias expansion happens before command dispatch and can shadow built-ins.
- `say` preserves case from raw input.