diff options
| author | historia <[not public]> | 2026-06-16 01:59:27 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-16 01:59:27 -0400 |
| commit | 43f21aabae8924f35c12c8485e690aeefe0089fb (patch) | |
| tree | b5fbadb89df3cf4ffec86503bb8e2d7e52b27454 /AGENTS.md | |
| parent | 4cc1ddcd185509080b4b3e15d1646567edd51c9d (diff) | |
| download | thehouseoficarus-43f21aabae8924f35c12c8485e690aeefe0089fb.tar.gz | |
feat: overhauled ansi color, added prompt options
Diffstat (limited to 'AGENTS.md')
| -rw-r--r-- | AGENTS.md | 500 |
1 files changed, 389 insertions, 111 deletions
@@ -2,62 +2,70 @@ Third Collapse — a Runescape-like sci-fi MUD written in Go. Data-driven via YAML files. No database, no ORM. -See `TODO.md` for the full project overview, implemented features, and roadmap. +Set on a terraformed asteroid where technology has regressed. "Science" replaces Prayer, "Technology" replaces Magic, Scavenging replaces Runecrafting. ## Build & Run ```bash make build # go build -o tc ./cmd/mud make run # build + run with DATA_DIR=./data -make test # go test ./... +make test # go test ./... -timeout 60s make vet # go vet ./... ``` Binary accepts `--config <path>` (defaults to `config.yaml`). If no config found, starts telnet on :4000. -Config options: +Dependencies: `gopkg.in/yaml.v3`, `github.com/gorilla/websocket`. + +Config: ```yaml game: tick_length: 600 # ms per game tick (default 600, min 50) ``` -Dependencies: `gopkg.in/yaml.v3`, `github.com/gorilla/websocket`. - ## Package Map ``` cmd/mud/main.go Entry point — config loading, multi-listener, tick engine -internal/config/ YAML config (telnet, http, https listeners) + doc.go -internal/net/ Conn interface (tcp + websocket), session state, hub, IAC echo + doc.go -internal/game/ Login flow, command dispatch, input sanitization, all game logic + doc.go -internal/action/ Behavior structs (GatherConfig, TalkConfig, etc.), Action, drop tables + doc.go -internal/player/ Player struct, skills, inventory, XP, name/password validation + doc.go -internal/world/ Room, MobDef, MobInstance, ObjState, ground items, wandering + doc.go -internal/combat/ Combat state tracking, OSRS-style combat formulas + doc.go -internal/object/ ItemDef, ObjectDef, item/object YAML loaders + doc.go -internal/engine/ Tick scheduler — 600ms interval, subscriber pattern + doc.go +internal/ + config/ YAML config (telnet, http, https listeners) + net/ Conn interface (tcp + websocket), session states, hub, IAC echo + game/ Login flow, command dispatch, input sanitization, all game logic + action/ Behavior structs (GatherConfig, TalkConfig, etc.), Action, drop tables, SuccessChance + player/ Player struct, skills, inventory, XP (RSC table), name/password validation, accounts + world/ Room, MobDef, MobInstance, ObjState, ground items, wandering, drop tables + combat/ Combat state tracking, combat lock, OSRS-style combat formulas + object/ ItemDef, ObjectDef, item/object YAML loaders, EquipSlot, WeaponType, ItemStats + engine/ Tick scheduler — configurable ms interval, subscriber pattern, ToTicks() + color/ ANSI 16-color and xterm256-color escape code tags (<red>text</red>, <bold>, etc.) ``` `internal/game/` is the largest package. Key files: | File | Purpose | |---|---| -| `game.go` | Game struct, HandleSession state machine, command dispatch | +| `game.go` | Game struct, HandleSession state machine, command dispatch, ProcessQueuedCommands | | `login_account.go` | Account auth, creation, password handling, main menu, account rename/purge | | `login_char.go` | Character creation, connection, rename, delete | -| `cmd_*.go` | Command handlers (see Adding a New Command below) | -| `action.go` | StartAction, CancelAction, AdvanceActions, checkCondition | -| `action_*.go` | Action lifecycle per behavior type (gather, talk, use, toggle, burn) | -| `action_state.go` | ActionState type, ActionType consts, Description for look display | +| `cmd_*.go` | Command handlers for each command (see Commands section) | +| `action.go` | StartAction, CancelAction, AdvanceActions, checkCondition, verbAliases, verbSkill | +| `action_*.go` | Action lifecycle per type (gather, talk, use, toggle) | +| `action_burn.go` | Burn/stoke firemaking (standalone, not behavior-YAML-driven) | +| `action_search.go` | Search bird's nests for loot | +| `action_room.go` | RunEnterSteps (on_enter scripts), BroadcastRespawns | +| `action_state.go` | ActionType consts, ActionState struct, Description() for look display | +| `action_default_target.go` | Smart auto-selection for gather/attack | | `tick.go` | DisconnectTick, RegenTick, WanderTick, SharedDepletionTick | | `map.go` | BFS graph builder, tiny map, full map, display utilities (strip, trim) | -| `types.go` | Shared types (EquipSlots, itemMatch, xpGain, deathDrop) | +| `types.go` | EquipSlots, itemMatch, xpGain, deathDrop | | `utils.go` | Helper functions (plural, parseQty, parseChoiceIndex, formatPickupList, etc.) | | `help.go` | Help topic YAML loading and display | +| `color.go` | colorMode(), colorize(), colorTag() helpers | +| `table.go` | Unicode/ASCII table rendering for score/option/help | ## Key Conventions -**YAML-driven.** Rooms, items, objects, mobs, behaviors, drops are all YAML files under `data/`. Read from disk on every access — edit a room description and it takes effect immediately, no restart. +**YAML-driven.** Rooms, items, objects, mobs, behaviors, drops are YAML files under `data/`. Read from disk on every access — edit a room description and it takes effect immediately, no restart. **Live state.** Player data (characters, accounts) is written to YAML on every state change. Ground items, mob instances, and object states live in memory only and are lost on restart. @@ -65,151 +73,421 @@ internal/engine/ Tick scheduler — 600ms interval, subscriber pattern + **Transport-agnostic.** The `Conn` interface (`internal/net/conn.go`) abstracts the transport layer. `tcpConn` wraps a raw `net.Conn` for telnet. `wsConn` wraps `gorilla/websocket.Conn` for the web client. Game code works with `*Session` regardless of transport. -**Input sanitization.** `HandleSession` strips Unicode control characters from every input line via `player.StripControlCharacters` before dispatch. Account/character names validated to `[A-Za-z0-9 ]{1,30}`. Input truncated to 1024 bytes. Password echo suppressed via IAC ECHO (telnet) and OSC sequences (web). WebSocket origin checked against host header. +**Input sanitization.** `HandleSession` strips Unicode control characters from every input line via `player.StripControlCharacters` before dispatch. Account/character names validated to `[A-Za-z0-9 ]{1,30}`. Input truncated to 1024 bytes. Password echo suppressed via IAC ECHO (telnet) and OSC sequences (web). WebSocket origin checked against host header + loopback. -**Tick-driven.** The world runs on a 600ms tick. Combat, gathering, regen, wandering, respawns, shared depletion, woodcutting timers all advance per tick. See `cmd/mud/main.go` for the subscription loop. +**Tick-driven.** The world runs on a configurable tick (default 600ms). Combat, gathering, regen, wandering, respawns, shared depletion, fire ticks all advance per tick. **Fractional ticks.** All YAML timer fields (tool_speed, speed, base_wait, deplete_timer, etc.) accept `float64` values. `engine.ToTicks(base)` probabilistically rounds to an integer — a tool_speed of 4.5 means each action cycle has a 50% chance of 4 ticks and 50% of 5 ticks. +**Concurrency.** Every state-bearing package uses its own `sync.Mutex`: +- `world.World` — groundItems, objStates, seeded, objMoves +- `world.MobStore` — defs, instances +- `action.Store` — cache +- `engine.Engine` — subscribers +- `Game` — charsMu (loggedInChars map) +- `combat` — combatants, mobTargets + +The pattern is: lock, snapshot/unmarshal, unlock, then process. The engine creates a subscriber snapshot each tick. + ## Two State Systems - **World flags** (`set_flags` / checked with `flag`): Shared by all players. A door opened by one player is open for everyone. - **Player flags** (`set_player_flags` / checked with `player_flag`): Per-character, saved to character YAML. Quest progress, dialog history. +## Data Files + +``` +data/rooms/<id>.yaml Room definitions (exits, objects, mobs, spawns, on_enter) +data/items/<id>.yaml Item definitions (stats, equip_slot, tool_type, burn_ticks, search_table, etc.) +data/mobs/<id>.yaml Mob definitions (combat stats, drops, behavior — NO wander config) +data/objects/<id>.yaml Object definitions (name, behavior, hidden, inroom_description, removal_item, props) +data/behaviors/<id>.yaml Behaviors (gather, talk, use, toggle) +data/drops/<id>.yaml Shared drop tables (weighted item lists) +data/help/<id>.yaml Help topics +data/players/accounts/ Account YAML (gitignored) +data/players/characters/ Character YAML (gitignored) +``` + +Wander config (`wander_rooms`, `wander_interval`) is set per-instance in room YAML — mob defs are generic. Mobs wander via legal (unconditioned) room exits; objects teleport between rooms in their list. + +See `WORLDBUILDING.md` for full YAML format reference with examples. + +## Commands + +### Instant (execute immediately, no queue) +| Command | Description | +|---------|-------------| +| `say <text>` | Room chat (preserves case from raw input) | +| `score` / `sc` | Character stats and skills | +| `inventory` / `i` / `inv` | Inventory contents (28 slots) | +| `equipment` / `eq` | Equipped items by slot | +| `look` / `l` | Room view (mobs, objects, ground items, exits, players) | +| `look <target>` / `l <target>` | Examine mobs, objects, items, players, directions | +| `exits` | List available exits | +| `help [topic]` | Help topics from data/help/ | +| `map` | BFS ASCII map centered on player | +| `option` / `options [name] [value]` | Character settings (see Option System) | +| `alias <name> <cmd>` | Account-wide command alias with arg passthrough | +| `unalias <name>` | Remove an alias | +| `description` / `desc` | Set custom player description | +| `queued` | Show pending tick actions | + +### Free (stackable, execute before active) +| Command | Description | +|---------|-------------| +| `wear` / `wield <item>` | Equip item to correct slot. `wear all` auto-equips best per slot | +| `wear all` | Auto-equips highest-value item per slot from inventory | +| `remove` / `unwear` / `unwield <item>` | Unequip item back to inventory | +| `style <name>` | Set combat style: accurate, aggressive, defensive, balanced (prefix match) | + +### Active (replaces previous active, queued per tick) +| Command | Description | +|---------|-------------| +| `n` / `s` / `e` / `w` / `u` / `d` | Movement (conditional exits, interrupts combat/actions) | +| `get` / `take` / `grab` / `pick <item>` | Pick up item from ground (reservation-aware) | +| `get all` / `get all <name>` | Pick up all (or matching) items | +| `drop <item>` | Drop item to ground | +| `drop all` / `drop all <name>` | Drop all (or matching) items | +| `attack` / `kill <mob>` | Combat with numbered targeting and smart default | +| `mine` / `chop` / `cut` / `fish <object>` | Gathering with smart default (verbAliases map to "gather") | +| `use <object>` | Crafting station interaction | +| `talk` / `speak` / `ask <target>` | NPC conversations (objects or mobs with behavior) | +| `pull` / `push <object>` | Toggle interactions (levers, gates) | +| `search <item>` | Search bird's nests / searchable items (rolls on search_table) | +| `burn [item]` | Start a fire (smart default: auto-select single log type) | +| `stoke [item]` | Add logs to existing fire for XP | +| `walk <room_number>` | BFS pathfinding to a room by number | +| `walk <directions>` | Direction sequence like `3n2e1u` (capped by agility level) | +| `quit` | 10-tick rest sequence, then return to menu (blocked during combat) | + ## Command Dispatch -`internal/game/game.go` — `handleGameCommand()`: +`handleGameCommand()` in `internal/game/game.go`: 1. Cancel rest timer -2. Resolve account aliases (alias expansion happens BEFORE command lookup) -3. Switch on command word -4. Most commands call into `cmd_*.go` files - -Actions that interact with objects/mobs route through `StartAction()` in `action.go`, which: -1. Normalizes verb (mine/chop/fish/cut → gather, talk/speak/ask → talk, pull/push → toggle) -2. Checks for ambiguous matches (multiple object types) -3. Searches object instances in room +2. Resolve account aliases (alias expansion BEFORE command lookup — can shadow built-ins) +3. Classify command (Instant / Free / Active / Unknown) +4. Instant runs immediately; Free/Active queue for next tick + +## Tick-Based Action Queue + +`ProcessQueuedCommands` runs each tick: +1. Clears stale ActionState from one-tick actions (move, get, drop) +2. Executes all free commands per player in queued order +3. Executes all active commands sorted globally by timestamp (first-to-queue wins conflicts) +4. Advances walk sequences (one step per tick) +5. Flushes pending shared depletions + +**Conflict resolution:** multi-player attack on same mob or get on same item — first to queue wins (timestamp-order). Shared depletion defers to award all successful gatherers on depletion tick. + +**ActionState** tracks player activity for `look` display. Timed actions (gather, combat, burn, stoke, use, rest, walk) persist until interrupted. One-tick actions (move, get, drop) clear after one tick. + +Queue feedback controlled by `queue_silently` option (default on). + +## Action System + +Actions that interact with objects/mobs route through `StartAction()` in `action.go`: +1. Normalizes verb via `verbAliases` map (mine/chop/fish/cut → gather, talk/speak/ask → talk, pull/push → toggle) +2. Multiple object types → "That's ambiguous, which one?" +3. Searches object instances in room (numbered targeting via `N.name`) 4. Falls back to mob instances if no object matches 5. Loads the behavior from YAML 6. Routes to type-specific handler (startGather, startTalk, startUse, startToggle) -## Adding a New Skill +The `verbSkill` map maps verb → skill name for default target resolution: +- mine → mining, chop/cut → woodcutting, fish → fishing -1. Add `SkillName` constant + entry in `AllSkills` + `SkillAbbr` in `internal/player/player.go` -2. Create a gather behavior YAML in `data/behaviors/` -3. Create an object YAML referencing that behavior in `data/objects/` -4. Create an item for the tool in `data/items/` (with `tool_type`) -5. Place the object in a room YAML in `data/rooms/` -6. Create any resource items in `data/items/` +### Action Types -Set `xp` on the behavior to award XP on successful gathers. Set `deplete_timer` for tree-style depletion, or use per-drop `depletes: true` for rock-style depletion. +| ActionType | Trigger | Description | +|---|---|---| +| `gathering` | mine/chop/cut/fish | Resource gathering with skill checks, tool reqs, depletion | +| `combating` | attack/kill | Combat with attack/defense rolls | +| `using` | use | Crafting — consume items, produce output | +| `talking` | talk/speak/ask | NPC conversation trees with choices/conditions/actions | +| `toggling` | pull/push | Levers/switches — set world flags | +| `burning` | burn | Firemaking startup (3 phases) | +| `stoking` | stoke | Adding logs to existing fire | +| `searching` | search | Search bird's nests / items with search_table | +| `resting` | quit | 10-tick rest before disconnect | +| `walking` | walk | Multi-step movement (one dir per tick) | +| `moving` | n/s/e/w/u/d | One-tick movement state | +| `picking_up` | get | One-tick pickup state | +| `dropping` | drop | One-tick drop state | + +### Behavior Types (YAML-driven) + +| Type | Verbs | Use | +|---|---|---| +| `gather` | mine, chop, cut, fish | Resource gathering with skill checks, tool requirements, depletion/respawn | +| `use` | use | Crafting stations — consume items, produce output, optional skill checks | +| `talk` | talk, speak, ask | NPC conversation trees with choices, conditions, actions | +| `toggle` | pull, push | Levers, switches, gates — set world flags, conditional on existing values | -## Adding a New Production Skill +### Behaviors Not in YAML (hardcoded) -Same as gathering but use `type: use` behavior (see `UseConfig` in `internal/action/behavior.go`). Smithing is the reference example planned. +| Action | File | Description | +|---|---|---| +| burn | `action_burn.go` | 3-phase firemaking: drop log → try burn → skill check → create fire object | +| stoke | `action_burn.go` | Add logs to fire, XP per log, increments fire quality | +| search | `action_search.go` | Search items with search_table, consumes one, rolls on drop table | -## Adding a New Command +### GatherConfig Fields -1. Add a `cmd_*.go` file in `internal/game/` with the handler -2. Add a case in `handleGameCommand()` in `internal/game/game.go` -3. Update `data/help/` YAML files +| Field | Description | +|---|---| +| `skill` | Skill name for level check | +| `level` | Required level | +| `xp` | XP per successful gather | +| `base_wait` | Base ticks per action cycle | +| `tools` | Required tool_type list | +| `bait` | Required bait item | +| `success` | SuccessFormula (base + per_level, capped) | +| `drops` | Weighted drop entries (item_id, table ref, depletes, quantity, message, level, xp) | +| `respawn_timer` | Ticks until depleted object respawns | +| `deplete_timer` | Ticks for shared depletion (trees) | +| `nest_chance` | 1/N chance for bird's nest alongside normal drop | +| `gather_message` | Custom "You gather..." message | +| `depleted_message` | Custom "The rock is depleted" message | +| `exhausted_message` | Custom "The tree falls" message | +| `fail_message` | Custom failure message | +| `respawn_broadcast` | Broadcast message when object respawns | + +### SuccessChance Formula + +`SuccessChance(cfg SuccessFormula, level int, requiredLevel int) float64`: +``` +chance = cfg.Base + (level - requiredLevel) * cfg.PerLevel +clamped to [0, cfg.Cap] +``` -## Firemaking (`burn` / `stoke`) +### Node Actions (talk dialog) -Firemaking is implemented as standalone actions (not behavior-YAML-driven) in `internal/game/action_burn.go`. The flow: +| Field | Effect | +|---|---| +| `set_flags` | Sets world flags (global) | +| `set_player_flags` | Sets player-local flags (per-character, saved to YAML) | +| `give_item` | Gives an item to inventory | +| `take_item` | Removes an item from inventory | +| `teleport` | Moves player to a room ID | +| `heal` | Restores hitpoints | + +### Condition System -| Command | Action | Description | -|---------|--------|-------------| -| `burn [item]` | `"burn"` | Start a fire from logs. Checks ground items first, then inventory. Requires a `tool_type: fire` item. | -| `stoke [item]` | `"stoke"` | Add logs from inventory to an existing fire object for XP. No tool required. | +Used by exits, talk options, on-enter scripts, and toggle checks: + +| Field | Scope | Example | +|---|---|---| +| `flag` | World (shared by all players) | `flag: gate_open` | +| `player_flag` | Per-character (quest progress) | `player_flag: finished_tutorial` | +| `has_item` | Player inventory check | `has_item: bronze_key` | +| `all_of` | All sub-conditions must pass | Nested list | +| `any_of` | Any sub-condition passes | Nested list | +| `not` | Invert the check | `not: true` | + +### Firemaking (burn/stoke) + +See `internal/game/action_burn.go`. **Burn flow (3 phases):** - Phase 0 (inventory only): Drops 1 log to ground, outputs "You drop the X and begin to make a fire..." - Phase 1: "You try burning the X on the ground..." (waits tool_speed ticks) -- Phase 2: Skill check (firemaking level vs log's `fire_level`). On fail, outputs "You can't seem to get a fire going" and retries at Phase 1. On success, creates a `fire` object (`quality` = log's `burn_ticks`), gives XP, broadcasts to room. +- Phase 2: Skill check (chance = 0.5 + (level - fireLevel) * 0.02, capped [0.05, 0.95]). On success creates `fire` object (quality = log's burn_ticks), gives XP, broadcasts. On fail retries Phase 1. -**Stoke flow:** -- One-time "You begin to tend to the fire." -- Loops: waits 10 ticks → "You throw X onto the fire", gives XP, adds `burn_ticks/2` to fire `quality` -- Ends when out of the item: "You're out of X and the fire is roaring." +**Stoke flow:** 10-tick loop → "You throw X onto the fire", gives XP, adds burn_ticks/2 to fire quality. Ends when out of item. -**Fire objects:** -- Created dynamically via `World.AddObjInstance()`, tracked by `ObjState.Quality`. -- Each tick, `FireTick()` decrements quality; at 0, `RemoveObjInstance()` cleans up. -- `inroom_description` on object YAML replaces the generic "A X is here." -- `props.quality_description` (with `{quality}` placeholder) is used by `look <fire>`. +**Fire objects:** Created via `World.AddObjInstance()`, tracked by `ObjState.Quality`. Each tick `FireTick()` decrements quality; at 0, `RemoveObjInstance()` cleans up and drops `removal_item` (e.g., ashes). -**Item fields for firemaking:** -- `burn_ticks: <int>` — how long the log burns (fire quality). Only items with `burn_ticks > 0` are burnable. -- `fire_level: <int>` — required firemaking level to burn. -- `fire_xp: <int>` — XP awarded. -- `quality: <int>` / `max_quality: <int>` — for tools like lighter (start value / max value). -- `EquipQuality map[string]int` on Player tracks quality of equipped items with quality. +**Fire tools:** `matches` (stackable, consumed), `firesteel` (non-consumable), `lighter` (quality-based, `MaxQuality: 100`, decreases per use). Tool search: main hand → inventory. -**Fire tools:** `matches` (stackable, consumed on use), `firesteel` (non-consumable), `lighter` (quality-based, `MaxQuality: 100`, decreases per use). +**Item fields for firemaking:** `burn_ticks`, `fire_level`, `fire_xp`, `quality`, `max_quality`. -**Smart defaults:** `burn` auto-selects if only one burnable type on ground OR in inventory. `stoke` auto-selects if only one burnable type in inventory. +## Two Depletion Mechanics -**Level-up messages:** All skills that gain XP (gathering, combat, use, firemaking) output `*** You are now level N <skill>! ***` when the player levels up. +**Per-drop depletion** (mining, regular trees): Set `depletes: true` on a drop entry. The resource depletes on first successful gather of that drop. -**Stoke error messages:** "There's no fire here to stoke" (no fire), "You have no logs to stoke with!" (no burnable items), "Stoke what?" (ambiguous). Fire going out mid-stoke interrupts immediately with "The fire went out!". +**Shared depletion** (higher-tier trees): Set `deplete_timer: <ticks>` on the behavior. A shared timer counts down while anyone is chopping; when it hits 0, the next successful gather depletes the tree for all players. Timer regenerates when no one is chopping. Managed by `SharedDepletionTick` in `tick.go`. -## Two Depletion Mechanics +## Ground Item System -**Per-drop depletion** (mining, regular trees): Set `depletes: true` on a drop entry. The resource depletes on first successful gather of that drop. Rocks don't show despawn timers when not depleted. +- `DropDespawnTicks = 1000` — dropped items despawn after 1000 ticks +- `ReserveTicks = 100` — mob loot is reserved for the killer for 100 ticks +- Items with same ID in the same room are NOT merged — each entry has independent despawn timer +- Spawn items respawn after their respawn delay; nearby matching items are merged on respawn +- Despawn timers visible via `despawn` option; reserve info via `reserve` option -**Shared depletion** (higher-tier trees): Set `deplete_timer: <ticks>` on the behavior. A shared timer counts down while anyone is chopping; when it hits 0, the next successful gather depletes the tree for all players. If multiple players succeed on the depletion tick, all get their drops. The timer regenerates back to max when no one is chopping. `SharedDepletionTick` manages this in `internal/game/tick.go`. +## Combat System -## Data Files +**Combat lock:** `LockedTicks: 15` — players cannot move for 15 ticks after entering combat. Decremented each tick. -``` -data/rooms/<id>.yaml Room definitions (exits, objects, mobs, spawns, on_enter) -data/items/<id>.yaml Item definitions (stats, equip slot, tool type, speed) -data/mobs/<id>.yaml Mob definitions (combat stats, drops, behavior — NO wander config) -data/objects/<id>.yaml Object definitions (name, behavior, hidden) -data/behaviors/<id>.yaml Behaviors (gather, talk, use, toggle configs) -data/drops/<id>.yaml Shared drop tables (weighted item lists) -data/help/<id>.yaml Help topics -data/players/accounts/ Account YAML (gitignored) -data/players/characters/ Character YAML (gitignored) -``` +**Death mechanic:** Player drops credits to ground. If player has >3 items (inventory + equipment), the 3 most valuable are kept and rest are dropped. Player teleports to room 1 at full HP. -Wander config (`wander_rooms`, `wander_interval`) is set per-instance in room YAML — mob defs are generic. Mobs wander via legal (unconditioned) room exits; objects teleport between rooms in their list. +**XP formula:** `baseXP = dmg * 4`, distributed by attack style: +- Accurate: 75% Attack, 25% Hitpoints +- Aggressive: 75% Strength, 25% Hitpoints +- Defensive: 75% Defense, 25% Hitpoints +- Balanced: 25% each Attack/Strength/Defense/Hitpoints -See `WORLDBUILDING.md` for full YAML format reference with examples. +**Mob combat level:** `0.25 * (attack + strength + defense + maxHP)` -## Tick-Based Action Queue +**Player combat level:** Ranged-or-melee dominant calculation. Includes Science (0.25) and Technology (0.125). -All gameplay commands are queued for the next 600ms game tick instead of executing immediately. Commands are classified into three types: +**Mob respawn:** Default 30 ticks. Broadcasts spawn messages if `mob_spawn` option is on. -| Class | Behavior | Commands | -|---|---|---| -| Instant | Execute immediately, no queue | `say`, `score`, `inventory`, `equipment`, `look`, `exits`, `help`, `map`, `option`, `alias`, `unalias`, `description`, `queued` | -| Free | Stackable, execute in order before active | `wear`/`wield`, `remove`/`unwear`, `style` | -| Active | Only the last active per player survives | Everything else: move, attack, mine/chop/fish/cut, use, talk, burn, stoke, get, drop, search, quit | +**Mob DropTable:** `Remains` (guaranteed drop item), `Loot` (weighted DropEntry table). -Each 600ms tick, `ProcessQueuedCommands` in `internal/game/game.go`: -1. Clears stale ActionState from one-tick actions (move, get, drop, etc.) -2. Executes all free commands per player in queued order -3. Executes all active commands sorted globally by real-time timestamp -4. Flushes any pending shared resource depletions +## Option System + +Player options set via `option <name> <value>`: + +| Option | Type | Default | Description | +|---|---|---|---| +| `description` | bool | true | Long room descriptions when moving | +| `tiny_map` | string | "right" | off/right/left — mini-map display | +| `xp_drops` | bool | true | XP drop messages | +| `exits` | bool | true | Long exit display in look | +| `mob_enter` | bool | true | Messages when mobs enter the room | +| `mob_leave` | bool | true | Messages when mobs leave the room | +| `mob_spawn` | bool | true | Messages when mobs spawn | +| `reserve` | bool | true | Show full reserved item details | +| `depletion` | bool | false | Show depletion and despawn timers | +| `despawn` | bool | false | Show ground item despawn timers | +| `color` | string | "none" | none/ansi/xterm256 — color output mode | +| `mapwidth` | int | 30 | Map viewport width | +| `mapheight` | int | 20 | Map viewport height | +| `mappadding` | string | "none" | none/x/y/xy — blank-row stripping | +| `automap` | bool | false | Show map automatically after moving | +| `queue_silently` | bool | true | Suppress queue messages | +| `room_desc_width` | int | 70 | Room desc line wrap width | +| `unicode` | bool | true | Unicode box-drawing characters | +| `prompt` | string | "> " | Custom command prompt | + +## ItemDef Fields + +From `internal/object/item.go`: + +| Field | Description | +|---|---| +| `id` | Unique identifier | +| `name` | Display name | +| `aliases` | Alternate name prefixes for matching | +| `description` | Item description | +| `value` | Credit value | +| `stackable` | Can stack in inventory | +| `equip_slot` | head/neck/torso/legs/hands/feet/back/ammo/main_hand/off_hand/ring | +| `weapon_type` | "melee" / "ranged" / "technology" | +| `stats` | attack/strength/defense/science/technology bonuses | +| `speed` | Attack speed | +| `tool_type` | Tool type string for gather requirements | +| `tool_speed` | Speed bonus for gathering | +| `burn_ticks` | Burn duration for firemaking | +| `fire_level` | Required firemaking level | +| `fire_xp` | XP for burning | +| `quality` | Default quality (e.g., lighter butane) | +| `max_quality` | Maximum quality | +| `search_table` | Drop table for searching this item | +| `search_misc_table` | Secondary drop table | +| `search_ticks` | Ticks per search action | +| `search_message` | Custom search message | + +## ObjectDef Fields + +From `internal/object/object.go`: + +| Field | Description | +|---|---| +| `id` | Unique identifier | +| `name` | Display name | +| `behavior` | Behavior ID for interaction | +| `hidden` | Interactable but not shown in room listing | +| `inroom_description` | Custom "A X is here." text | +| `removal_item` | Item dropped when instance is removed (fire → ashes) | +| `props` | Arbitrary key-value map (description, quality_description with `{quality}` placeholder) | -**Conflict resolution:** -- Multi-player attack on same mob: first player to queue wins (timestamp-order). -- Multi-player get on same item: first player to queue wins. -- Multi-player shared depletion: defer depletion, award all successful gatherers. +## World ObjState Fields -**ActionState** (`internal/game/action_state.go`) tracks what a player is doing for `look` display. Timed actions (gather, combat, burn, stoke, use) persist the state until interrupted. One-tick actions (move, get, drop) clear after one tick. +From `internal/world/world.go`: + +| Field | Description | +|---|---| +| `DefID` | Object definition ID | +| `Name` | Display name | +| `Index` | Index for duplicate objects in same room | +| `RoomID` | Current room | +| `Depleted` | Resource is depleted | +| `DepleteTimer` | Ticks until respawn | +| `SharedMax` | Max shared depletion timer | +| `SharedTimer` | Current shared timer value | +| `WanderRooms` | Rooms for object wandering | +| `WanderInterval` | Ticks between wanders | +| `Quality` | Fire quality, resource health, etc. | +| `JustRespawned` | Flag for respawn broadcast | + +## Session States + +From `internal/net/server.go`: + +| State | Purpose | +|---|---| +| `StateAccountName` | Entering account name | +| `StatePassword` | Entering password | +| `StateNewAccountPass` | Setting password for new account | +| `StateNewAccountConfirm` | Confirming password | +| `StateMenu` | Main menu | +| `StateNewCharName` | Naming new character | +| `StateRenameAccount` | Renaming account | +| `StateRenameChar` | Selecting character to rename | +| `StateRenameCharName` | Entering new character name | +| `StateDeleteChar` | Confirming character deletion | +| `StatePurgeAccount` | Confirming account purge | +| `StateGame` | In-game command mode | +| `StateChangeDescription` | Setting player description | +| `StateTalk` | NPC conversation input | +| `StateDropAllConfirm` | Confirming `drop all` | + +## Adding a New Skill + +1. Add `SkillName` constant + entry in `AllSkills` + `SkillAbbr` in `internal/player/player.go` +2. Create a gather behavior YAML in `data/behaviors/` +3. Create an object YAML referencing that behavior in `data/objects/` +4. Create an item for the tool in `data/items/` (with `tool_type`) +5. Place the object in a room YAML in `data/rooms/` +6. Create any resource items in `data/items/` + +Set `xp` on the behavior to award XP on successful gathers. Set `deplete_timer` for tree-style depletion, or use per-drop `depletes: true` for rock-style depletion. + +## Adding a New Production Skill + +Same as gathering but use `type: use` behavior (see `UseConfig` in `internal/action/behavior.go`). Smithing is the reference example planned. + +## Adding a New Command + +1. Add a `cmd_*.go` file in `internal/game/` with the handler +2. Add a case in `handleGameCommand()` and entry in `classifyCommand()` in `internal/game/game.go` +3. Update `data/help/` YAML files + +## Skills (23 total, 1-99, RSC XP table) + +| Category | Skills | +|---|---| +| Combat | Attack, Strength, Defense, Hitpoints, Ranged, Science, Technology, Assassin | +| Gathering | Fishing, Woodcutting, Mining, Hacking, Scavenging, Farming, Thieving | +| Production | Cooking, Smithing, Crafting, Fletching, Alchemy, Construction, Firemaking | +| Utility | Agility | -Queue feedback is controlled by the `queue_silently` player option (default on). +Implemented: Mining, Fishing, Woodcutting, Firemaking. The rest are stubs (skill defined, XP tracked, but no behaviors/objects/items yet). ## Places to Be Careful - `StartAction` searches objects first, then mobs. A mob must have `behavior:` set on its YAML def to be interactable. -- Exits changed from `map[ExitDir]int` to `map[ExitDir]ExitDef` — old `north: 2` still works via custom `UnmarshalYAML`. Same pattern used for `RoomMob` (supports both `"man"` and `{id: man, ...}`). +- Exits changed from `map[ExitDir]int` to `map[ExitDir]ExitDef` — old `north: 2` still works via custom `UnmarshalYAML`. Same pattern for `RoomMob`. - `checkCondition` is the single condition evaluator — used by exits, talk options, on-enter scripts, and toggle checks. - Object `Hidden: true` means the object doesn't appear in room listings but is still interactable. -- Alias expansion happens before command dispatch. An alias can shadow a built-in command. -- `say` preserves case (alias expansion preserves case, and the dispatch extracts the original input text for say). +- Alias expansion happens before command dispatch and can shadow built-in commands. +- `say` preserves case (alias expansion preserves case, dispatch extracts original input text). - `get`, `drop`, and `quit` cancel the player's active action (gathering, use, etc.). - `findGroundMatches` and `findInventoryMatches` support bidirectional prefix matching — `"iron ax"` matches `"iron axe"`. -- Mob wander config is per-instance in room YAML via `RoomMob`, not on `MobDef`. Mobs without `wander_interval` in the room stay still. +- Mob wander config is per-instance in room YAML via `RoomMob`, not on `MobDef`. +- Walk distance capped by agility level (`len(dirs) > agilityLevel`). +- `wear all` auto-equips the highest-value item per slot from inventory. +- Combat lock prevents movement for 15 ticks after entering combat. +- Level-up messages output `*** You are now level N <skill>! ***` across all skills that gain XP. |
