aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-01 16:37:45 -0400
committerhistoria <[not public]>2026-07-01 16:37:45 -0400
commite6b4cb9f5816c6ee239233bc85f74ee446a161c2 (patch)
tree1c3bf71c8b7f095baae989afe5e33183a7ed3929
parent649ef4b7416ce7053145878841fc9b0bff2f5fec (diff)
downloadthehouseoficarus-e6b4cb9f5816c6ee239233bc85f74ee446a161c2.tar.gz
feat: shop system overhauled, gui conversation tree editor overhauled
-rw-r--r--AGENTS.md6
-rw-r--r--building_guide/behaviors.md116
-rw-r--r--building_guide/mobs.md20
-rw-r--r--data/.admin_history.json2
-rw-r--r--data/help/shop.yaml42
-rw-r--r--data/items/ammo/adamant_arrow.yaml34
-rw-r--r--data/mobs/examples/client.yaml62
-rw-r--r--data/mobs/examples/estate_broker.yaml19
-rw-r--r--data/mobs/examples/general_store_clerk.yaml67
-rw-r--r--data/mobs/examples/guard.yaml12
-rw-r--r--data/mobs/examples/sawmill_operator.yaml24
-rw-r--r--data/mobs/examples/tanner.yaml13
-rw-r--r--data/mobs/examples/weapons_smith.yaml58
-rw-r--r--data/mobs/flight_attendant.yaml6
-rw-r--r--data/mobs/flight_attendant_copy.yaml23
-rw-r--r--data/mobs/spaceport_attendant.yaml14
-rw-r--r--data/objects/netrunner.yaml18
-rw-r--r--data/objects/tool_shed.yaml21
-rw-r--r--data/rooms/intro/1001.yaml7
-rw-r--r--data/rooms/intro/2001.yaml1
-rw-r--r--internal/admin/api_mobs.go1
-rw-r--r--internal/admin/api_objects.go1
-rw-r--r--internal/admin/static/admin.css30
-rw-r--r--internal/admin/static/mobeditor.js32
-rw-r--r--internal/admin/static/objecteditor.js7
-rw-r--r--internal/admin/static/talktree.js330
-rw-r--r--internal/behavior/behavior.go128
-rw-r--r--internal/behavior/types.go24
-rw-r--r--internal/game/act_steal.go10
-rw-r--r--internal/game/act_talk.go53
-rw-r--r--internal/game/cmd_registry.go284
-rw-r--r--internal/game/cmd_shop.go525
-rw-r--r--internal/game/game.go6
-rw-r--r--internal/net/server.go17
-rw-r--r--internal/validate/checks.go52
-rw-r--r--internal/world/mob.go154
36 files changed, 1255 insertions, 964 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 789b361..f7061a2 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -90,6 +90,10 @@ Used by exits, talk options, on-enter, use_interactions: `flag` (world), `player
`DropDespawnTicks = 1000`. `ReserveTicks = 100` (mob loot reserved for killer). Same-ID items NOT merged. Spawn items respawn with merge. Despawn/reserve visible via options.
+## Shop System (`cmd_shop.go`)
+
+Root-level `shop:` on a MobDef (`behavior.ShopConfig`) — NOT part of the talk tree. Traded via plain `StateGame` commands: `list`, `buy <item>`, `sell <item>`, with `list <mob>` / `buy <item> from <mob>` / `sell <item> to <mob>` to disambiguate when a room has multiple shop mobs (else "Which shop?"). No bespoke shop CLI/session state. **Prices derive from item `value:`** — shops sell at 100% of value; buy back via OSRS formula `value×max(10, buy_percentage−stock×change_percentage)/100` (defaults `buy_percentage: 40`, `change_percentage: 3`, floor 10%). Per-item `stock` (target+starting); buying depletes (out of stock at 0), selling raises it (lowering next sell price). Restock moves current stock toward target one step per `restock_ticks` (item override → shop default → `ShopDefaultRestock=100`) in **both** directions; dynamic (unlisted) items decay to 0. `buys_anything` (default true) lets specialty shops refuse unlisted items. **Live stock is per-MobInstance, in-memory only** (`ShopStock`/`ShopRestock` maps), mutated only under `MobStore.mu` via `ShopTake`/`ShopAdd`/`ShopStockOf`; restock runs in `MobStore.Tick()`.
+
## Combat System
`attack_type` (stab/slash/crush/ranged/science) selects attack/defense bonuses. Ranged consumes 1 ammo/attack. Aggressive mobs auto-attack if `playerLevel <= mobLevel * 2`. Movement during combat = flee attempt (mob hits abort). Death: drop credits + keep 3 most valuable items, respawn at room 1, deactivate techs. Equipment `requirements` checked before equipping. Formulas and XP splits in `combat/tracker.go`.
@@ -123,7 +127,7 @@ Minigame interface: 3 games at terminals (levels 1/20/40). Interface in `game/ha
## Session States
-20 states in `net/server.go` covering login flow, game, NPC talk, menus (bank/shop/recipe/confirm), and hacking.
+States in `net/server.go` covering login flow, game, NPC talk, menus (bank/recipe/confirm), and hacking. (Shops are command-driven in `StateGame` — no dedicated shop state.)
## ItemDef Fields
diff --git a/building_guide/behaviors.md b/building_guide/behaviors.md
index 3d35036..c94f787 100644
--- a/building_guide/behaviors.md
+++ b/building_guide/behaviors.md
@@ -263,7 +263,6 @@ talk:
| `teleport` | Moves the player to a room ID. |
| `heal` | Restores that many hitpoints. |
| `cost` | Credits charged for the action. |
-| `shop` | Opens a buy/sell shop interface. See Shop section below. |
| `assign_task` | Assigns a random assassin task based on assassin level. |
| `skip_task` | Cancels current assassin task, costs 30 reputation, resets streak. |
| `extend_task` | Adds 50% more kills to current task, costs 30 reputation. |
@@ -385,7 +384,7 @@ nodes:
```
If a node has both `goto` and options, the options take priority — `goto` is only used
-when no options are visible. A node with a shop action ignores `goto` entirely.
+when no options are visible.
#### Conditional nodes
@@ -428,62 +427,95 @@ passes, each option's own condition is then checked independently to determine
visibility — exactly as option conditions always work. When the node condition
fails, the node (and all its options) are skipped entirely.
-#### Shop (buy/sell interface)
+### Shops
-The `shop` node action opens a dedicated buy/sell interface. The player can browse items, buy
-with credits, and sell items back. Define a shop on a talk node:
+A shop is a **root-level `shop:` property on a mob def** (not part of the talk tree).
+Any mob in a room that has a `shop:` block can be traded with directly from the game
+prompt — there is no separate shop mode. Players use the ordinary commands:
-```yaml
-action:
- shop:
- message: "What would you like to buy or sell?"
- items:
- - item_id: fishing_rod
- buy_price: 10
- sell_price: 2
- - item_id: fishing_bait
- buy_price: 2
- sell_price: 0
```
+list Show the shop inventory of the mob in the room
+buy <item> Buy an item (partial names work, e.g. "buy bait")
+buy <N> <item> Buy a quantity
+buy all <item> Buy as many as you can afford and carry
+sell <item> Sell an item from your inventory
+sell <N> <item> Sell a quantity
+sell all <item> Sell your entire held stack
+```
+
+When a room has more than one shop mob, disambiguate with the mob name:
+
+```
+list <mob>
+buy <item> from <mob>
+sell <item> to <mob>
+```
+
+If several shops are present and no mob is named, the game replies "Which shop?"
+and lists the options.
+
+#### Pricing
+
+Shops never define per-item prices — prices derive from each item's `value:` field:
+
+- **Buy price** (player buying) is always **100% of the item's value**.
+- **Sell price** (player selling) uses the OSRS store formula:
+
+ ```
+ sell = value × max(10, buy_percentage − stock × change_percentage) / 100
+ ```
+
+ where `stock` is the shop's current holding of that item. With the defaults
+ (`buy_percentage: 40`, `change_percentage: 3`), an item the shop has none of
+ sells for 40% of value, dropping 3% per unit already in stock down to a floor
+ of 10% of value (reached at 10 in stock). Selling multiple units in one command
+ prices each unit at the rising stock level.
+
+#### Stock and restocking
+
+Each shop item carries a `stock` (its target/maximum, and its starting amount).
+Buying depletes stock — an item at 0 stock is out of stock. Selling raises stock,
+lowering subsequent sell prices. Every `restock_ticks` the current stock moves one
+step toward the configured target (refilling when low, decaying overstock when
+high). Items a player sold that the shop didn't originally list are held as dynamic
+stock that decays back to 0.
+
+#### Shop fields
| Field | Description |
|---|---|
-| `shop.message` | Greeting shown when entering the shop. |
-| `shop.items` | List of items for sale. |
-| `item_id` | Item definition ID. |
-| `buy_price` | Credits to buy from shop. |
-| `sell_price` | Credits shop pays (0 = won't buy). |
+| `shop.message` | Optional greeting shown at the top of `list`. |
+| `shop.buy_percentage` | Sell price at zero stock, as % of value (default 40). |
+| `shop.change_percentage` | Sell price drop per unit of stock, as % of value (default 3). |
+| `shop.restock_ticks` | Shop-wide default restock interval (default 100). |
+| `shop.buys_anything` | Whether the shop buys items not in its list (default true). |
+| `shop.items[].item_id` | Item definition ID. |
+| `shop.items[].stock` | Target/maximum and starting stock. |
+| `shop.items[].restock_ticks` | Per-item restock interval (overrides shop default). |
Full example — General Store:
```yaml
-name: General Store Clerk
+name: Shopkeeper
+protected: true
+hp: 50
+shop:
+ message: "\"Welcome to the General Store! Have a look around.\""
+ items:
+ - item_id: fishing_rod
+ stock: 10
+ - item_id: fishing_bait
+ stock: 1000
+ - item_id: hammer
+ stock: 10
talk:
nodes:
start:
- message: "\"Welcome to the General Store!\""
+ message: "\"Welcome! Type 'list' to see my wares.\""
options:
- - text: "\"I'd like to browse.\""
- goto: shop
- text: "\"Goodbye.\""
- shop:
- message: "\"Take your time.\""
- action:
- shop:
- message: "What would you like to buy or sell?"
- items:
- - item_id: fishing_rod
- buy_price: 10
- sell_price: 2
- - item_id: hammer
- buy_price: 20
- sell_price: 5
- options:
- - text: "\"I'm done.\""
- goto: start
```
-When the player leaves the shop, the current node's options are shown (or auto-advance
-fires if no options are visible). Use `goto: start` to loop back to the main greeting.
+A specialty shop that only buys back its own stock sets `buys_anything: false`.
### Object Interactions (levers, switches, gates)
diff --git a/building_guide/mobs.md b/building_guide/mobs.md
index c7331da..745fb49 100644
--- a/building_guide/mobs.md
+++ b/building_guide/mobs.md
@@ -126,6 +126,26 @@ showing the "Okay" option.
See the [Talk section of behaviors](behaviors.md#talk-dialog-trees) for the full
dialog tree format, including sequences and conditional nodes.
+### Shops
+
+Give a mob a root-level `shop:` block to make it a shopkeeper. Players trade with
+it using the ordinary `list`, `buy`, and `sell` commands from the game prompt.
+Prices derive from each item's `value:` field. See the
+[Shops section of behaviors](behaviors.md#shops) for the full format, pricing
+formula, and stock/restock rules.
+
+```yaml
+name: Shopkeeper
+protected: true
+hp: 50
+shop:
+ items:
+ - item_id: fishing_rod
+ stock: 10
+ - item_id: hammer
+ stock: 10
+```
+
Protected mob with combat stats (for internal testing or debug purposes):
```yaml
name: "Guard"
diff --git a/data/.admin_history.json b/data/.admin_history.json
index 72e0c51..fd4ad87 100644
--- a/data/.admin_history.json
+++ b/data/.admin_history.json
@@ -1 +1 @@
-{"history":[{"time":"2026-06-30T14:29:37-04:00","description":"delete room 1014 (cleaned 0 exits)","file_path":"data/rooms/intro/1014.yaml","old_content":"id: 1014\nname: 'Room #1014'\ncolor: D5\ndescription: []\nexits:\n southwest:\n room: 1013\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:37-04:00","description":"delete room 1016 (cleaned 0 exits)","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:38-04:00","description":"delete room 1015 (cleaned 0 exits)","file_path":"data/rooms/intro/1015.yaml","old_content":"id: 1015\nname: 'Room #1015'\ncolor: 0C\ndescription: []\nexits:\n northwest:\n room: 1018\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:39-04:00","description":"delete room 1017 (cleaned 0 exits)","file_path":"data/rooms/intro/1017.yaml","old_content":"id: 1017\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:40-04:00","description":"delete room 1018 (cleaned 0 exits)","file_path":"data/rooms/intro/1018.yaml","old_content":"id: 1018\nname: 'Room #1018'\ncolor: D0\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:40-04:00","description":"delete room 1019 (cleaned 0 exits)","file_path":"data/rooms/intro/1019.yaml","old_content":"id: 1019\nname: 'Room #1019'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:41-04:00","description":"delete room 1020 (cleaned 0 exits)","file_path":"data/rooms/intro/1020.yaml","old_content":"id: 1020\nname: 'Room #1020'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:45-04:00","description":"delete room 1013 (cleaned 1 exits)","file_path":"data/rooms/intro/1013.yaml","old_content":"id: 1013\nname: 'Room #1013'\ncolor: \"72\"\ndescription: []\nexits:\n west:\n room: 1007\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:49-04:00","description":"delete room 1009 (cleaned 0 exits)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: New Room\ncolor: \"27\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 8\n southwest:\n room: 1006\n west:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs:\n - id: man\n - id: man\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:51-04:00","description":"delete room 1007 (cleaned 1 exits)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: 'Room #1007'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 2004\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:53-04:00","description":"delete room 1006 (cleaned 4 exits)","file_path":"data/rooms/intro/1006.yaml","old_content":"id: 1006\nname: New Room\ncolor: E2\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1005\n northwest:\n room: 1008\n up:\n room: 1010\n west:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:59-04:00","description":"delete room 1010 (cleaned 1 exits)","file_path":"data/rooms/intro/1010.yaml","old_content":"id: 1010\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:00-04:00","description":"delete room 1011 (cleaned 0 exits)","file_path":"data/rooms/intro/1011.yaml","old_content":"id: 1011\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1012\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:01-04:00","description":"delete room 1012 (cleaned 0 exits)","file_path":"data/rooms/intro/1012.yaml","old_content":"id: 1012\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:02-04:00","description":"delete room 1005 (cleaned 2 exits)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: CC\ndescription:\n - text: A featureless room.\nexits:\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:03-04:00","description":"delete room 1008 (cleaned 1 exits)","file_path":"data/rooms/intro/1008.yaml","old_content":"id: 1008\nname: New Room\ncolor: A9\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 7\n south:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:29-04:00","description":"update room 2003 (add exit north to 1005)","file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n north:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:29-04:00","description":"create room 1005","file_path":"data/rooms/intro/1005.yaml","old_content":"","new_content":"exits:\n south: 2003\nid: 1005\nname: 'Room #1005'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:30-04:00","description":"update room 1005 (add exit east to 1006)","file_path":"data/rooms/intro/1005.yaml","old_content":"exits:\n south: 2003\nid: 1005\nname: 'Room #1005'\n","new_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n east:\n room: 1006\n south:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:30-04:00","description":"create room 1006","file_path":"data/rooms/intro/1006.yaml","old_content":"","new_content":"exits:\n west: 1005\nid: 1006\nname: 'Room #1006'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:31-04:00","description":"update room 1006 (add exit south to 1007)","file_path":"data/rooms/intro/1006.yaml","old_content":"exits:\n west: 1005\nid: 1006\nname: 'Room #1006'\n","new_content":"id: 1006\nname: 'Room #1006'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1007\n west:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:31-04:00","description":"create room 1007","file_path":"data/rooms/intro/1007.yaml","old_content":"","new_content":"exits:\n north: 1006\nid: 1007\nname: 'Room #1007'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:32-04:00","description":"update room 1007 (add exit east to 1008)","file_path":"data/rooms/intro/1007.yaml","old_content":"exits:\n north: 1006\nid: 1007\nname: 'Room #1007'\n","new_content":"id: 1007\nname: 'Room #1007'\ncolor: \"\"\ndescription: []\nexits:\n east:\n room: 1008\n north:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:32-04:00","description":"create room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"","new_content":"exits:\n west: 1007\nid: 1008\nname: 'Room #1008'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:32-04:00","description":"update room 1008 (add exit north to 1009)","file_path":"data/rooms/intro/1008.yaml","old_content":"exits:\n west: 1007\nid: 1008\nname: 'Room #1008'\n","new_content":"id: 1008\nname: 'Room #1008'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1009\n west:\n room: 1007\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:32-04:00","description":"create room 1009","file_path":"data/rooms/intro/1009.yaml","old_content":"","new_content":"exits:\n south: 1008\nid: 1009\nname: 'Room #1009'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:33-04:00","description":"update room 1009 (add exit north to 1010)","file_path":"data/rooms/intro/1009.yaml","old_content":"exits:\n south: 1008\nid: 1009\nname: 'Room #1009'\n","new_content":"id: 1009\nname: 'Room #1009'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1010\n south:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:33-04:00","description":"create room 1010","file_path":"data/rooms/intro/1010.yaml","old_content":"","new_content":"exits:\n south: 1009\nid: 1010\nname: 'Room #1010'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:33-04:00","description":"update room 1010 (add exit east to 1011)","file_path":"data/rooms/intro/1010.yaml","old_content":"exits:\n south: 1009\nid: 1010\nname: 'Room #1010'\n","new_content":"id: 1010\nname: 'Room #1010'\ncolor: \"\"\ndescription: []\nexits:\n east:\n room: 1011\n south:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:33-04:00","description":"create room 1011","file_path":"data/rooms/intro/1011.yaml","old_content":"","new_content":"exits:\n west: 1010\nid: 1011\nname: 'Room #1011'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:34-04:00","description":"update room 1011 (add exit south to 1012)","file_path":"data/rooms/intro/1011.yaml","old_content":"exits:\n west: 1010\nid: 1011\nname: 'Room #1011'\n","new_content":"id: 1011\nname: 'Room #1011'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1012\n west:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:34-04:00","description":"create room 1012","file_path":"data/rooms/intro/1012.yaml","old_content":"","new_content":"exits:\n north: 1011\nid: 1012\nname: 'Room #1012'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:34-04:00","description":"update room 1012 (add exit south to 1013)","file_path":"data/rooms/intro/1012.yaml","old_content":"exits:\n north: 1011\nid: 1012\nname: 'Room #1012'\n","new_content":"id: 1012\nname: 'Room #1012'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1011\n south:\n room: 1013\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:34-04:00","description":"create room 1013","file_path":"data/rooms/intro/1013.yaml","old_content":"","new_content":"exits:\n north: 1012\nid: 1013\nname: 'Room #1013'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:35-04:00","description":"delete room 1013 (cleaned 1 exits)","file_path":"data/rooms/intro/1013.yaml","old_content":"exits:\n north: 1012\nid: 1013\nname: 'Room #1013'\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:36-04:00","description":"delete room 1012 (cleaned 1 exits)","file_path":"data/rooms/intro/1012.yaml","old_content":"id: 1012\nname: 'Room #1012'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:37-04:00","description":"delete room 1011 (cleaned 1 exits)","file_path":"data/rooms/intro/1011.yaml","old_content":"id: 1011\nname: 'Room #1011'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:37-04:00","description":"delete room 1010 (cleaned 1 exits)","file_path":"data/rooms/intro/1010.yaml","old_content":"id: 1010\nname: 'Room #1010'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:38-04:00","description":"delete room 1009 (cleaned 1 exits)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: 'Room #1009'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:39-04:00","description":"delete room 1008 (cleaned 1 exits)","file_path":"data/rooms/intro/1008.yaml","old_content":"id: 1008\nname: 'Room #1008'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 1007\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:40-04:00","description":"delete room 1007 (cleaned 1 exits)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: 'Room #1007'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:40-04:00","description":"delete room 1006 (cleaned 1 exits)","file_path":"data/rooms/intro/1006.yaml","old_content":"id: 1006\nname: 'Room #1006'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:41-04:00","description":"delete room 1005 (cleaned 1 exits)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T15:05:42-04:00","description":"update room 1001","file_path":"data/rooms/intro/1001.yaml","old_content":"id: 1001\nname: Inside Transport Shuttle\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n room: 1002\n condition:\n flag: \"\"\n value: null\n not: false\n player_flag: 1001_touchdown\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n blocked_message: The piston-powered staircase is firmly closed.\n east:\n room: 2001\nobjects:\n - name: framed sign\n hidden: true\n description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n on_look:\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n cost: 0\n shop: null\n assign_task: false\n skip_task: false\n extend_task: false\n reputation_cost: 0\n sawmill: false\n aps_node: false\n - name: door\n hidden: true\n description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n - name: window\n hidden: true\n description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n - name: instrument panel\n aliases:\n - cockpit\n hidden: true\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n - name: pilot\n hidden: true\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\nitem_spawns: []\nmobs:\n - id: flight_attendant\non_enter:\n - message: '{0B bold}Welcome to The House of Icarus{/}'\n condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_welcome\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n delay: 5\n set_flags: {}\n set_player_flags: {}\n broadcast: \"\"\n broadcast_global: \"\"\n spawn_mob: null\n despawn_mob: \"\"\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n - message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_welcome\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n delay: 7\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n broadcast: \"\"\n broadcast_global: \"\"\n spawn_mob: null\n despawn_mob: \"\"\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n - message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_welcome\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n delay: 7\n set_flags: {}\n set_player_flags: {}\n broadcast: \"\"\n broadcast_global: \"\"\n spawn_mob: null\n despawn_mob: \"\"\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\nhazard: \"\"\nblock_transport: true\ntriggers:\n - id: \"\"\n on_player_flag: 1001_look_sign\n on_flag: \"\"\n value: null\n room: 0\n steps:\n - delay: 5\n message: The cabin shakes as the small craft touches down\n broadcast: \"\"\n broadcast_global: \"\"\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n despawn_mob: \"\"\n - delay: 5\n message: The pistons hiss as the rear staircase opens\n broadcast: \"\"\n broadcast_global: \"\"\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n despawn_mob: \"\"\n - delay: 0\n message: \"\"\n broadcast: \"\"\n broadcast_global: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n despawn_mob: \"\"\n","new_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: false\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","is_delete":false,"is_create":false},{"time":"2026-06-30T15:05:43-04:00","description":"update room 1001","file_path":"data/rooms/intro/1001.yaml","old_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: false\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","new_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: true\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","is_delete":false,"is_create":false},{"time":"2026-06-30T16:34:21-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T16:35:02-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T16:35:15-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T17:43:28-04:00","description":"Update object copper_rock","file_path":"data/objects/copper_rock.yaml","old_content":"color: \"B2\"\ngather:\n depleted_message: You don't see any ore in this rock.\n drops:\n - depletes: true\n item_id: copper_ore\n level: 1\n message: You manage to mine some {B2}copper ore{/}.\n weight: 90\n xp: 17\n - depletes: false\n message: You spot a glint of something valuable!\n table: gem_table\n weight: 10\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the rock...\n respawn_broadcast: A glint of copper catches your eye from some {name}.\n respawn_timer: 50\n skill: mining\n success:\n base: 0.5\n cap: 0.95\n per_level: 0.01\n tools:\n - pickaxe\nname: copper rock\n","new_content":"aliases: []\ncolor: B2\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: 0\n depleted_message: You don't see any ore in this rock.\n drops:\n - depletes: true\n item_id: copper_ore\n level: 1\n message: You manage to mine some {B2}copper ore{/}.\n quantity: 0\n table: \"\"\n weight: 90\n xp: 17\n - depletes: false\n item_id: \"\"\n level: 0\n message: You spot a glint of something valuable!\n quantity: 0\n table: gem_table\n weight: 10\n xp: 0\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the rock...\n nest_chance: 0\n respawn_broadcast: A glint of copper catches your eye from some {name}.\n respawn_timer: 50\n skill: mining\n success:\n base: 0.5\n cap: 0.95\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: copper_rock\ninroom_description: \"\"\nname: copper rock\nremoval_item: \"\"\n","is_delete":false,"is_create":false},{"time":"2026-06-30T23:48:18-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T23:48:23-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T23:53:08-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: \"DE\"\ncraft:\n type: combine\n consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n wait: 2\ndescription: A ball of uncooked bread dough. It needs to be baked.\nname: bread dough\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n wait: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T00:23:04-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: DE\ncraft:\n - consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n wait: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n wait: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T01:22:55-04:00","description":"update room 1001","file_path":"data/rooms/intro/1001.yaml","old_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: true\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","new_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: true\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:00:10-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:00:11-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:52:58-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: DE\ncraft:\n - ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n success_message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n ticks_per_cycle: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n level: 0\n output_qty: 0\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: smithing\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:53:08-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: DE\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n level: 0\n output_qty: 0\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: smithing\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n level: 0\n output_qty: 0\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: combine\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:15-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: solar_radiation\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:19-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: solar_radiation\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:26-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:32-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\n - aliases: []\n description:\n - text: \"\"\n hidden: false\n name: \"\"\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:37-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\n - aliases: []\n description:\n - text: \"\"\n hidden: false\n name: \"\"\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description: []\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:43-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description: []\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:44-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: true\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:41-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:41-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:44-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:44-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:47-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:47-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:49-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:49-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:51-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:53-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:55-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:56-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:58-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:58-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:10:01-04:00","description":"update room 2003 (add exit northeast to 1005)","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n northeast:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:10:01-04:00","description":"create room 1005","file_path":"data/rooms/intro/1005.yaml","old_content":"","new_content":"color: A5\nexits:\n southwest: 2003\nid: 1005\nname: 'Room #1005'\n","is_delete":false,"is_create":true},{"time":"2026-07-01T04:10:03-04:00","description":"delete room 1005 (cleaned 1 exits)","file_path":"data/rooms/intro/1005.yaml","old_content":"color: A5\nexits:\n southwest: 2003\nid: 1005\nname: 'Room #1005'\n","new_content":"","is_delete":true,"is_create":false,"extra_files":[{"file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n northeast:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n"}]},{"time":"2026-07-01T04:11:43-04:00","description":"update room 2003 (add exit north to 1005)","file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n north:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:22:38-04:00","description":"Update drop credit_stick_drop","file_path":"data/drops/credit_stick_drop.yaml","old_content":"drops:\n - item_id: credits\n weight: 40\n quantity: 15\n - item_id: credits\n weight: 30\n quantity: 30\n - item_id: credits\n weight: 20\n quantity: 50\n - item_id: credits\n weight: 8\n quantity: 100\n - item_id: credits\n weight: 2\n quantity: 250","new_content":"drops:\n - item_id: credits\n quantity: 14\n weight: 40\n - item_id: credits\n quantity: 30\n weight: 30\n - item_id: credits\n quantity: 50\n weight: 20\n - item_id: credits\n quantity: 100\n weight: 8\n - item_id: credits\n quantity: 250\n weight: 2\nid: credit_stick_drop\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:22:42-04:00","description":"Update drop credit_stick_drop","file_path":"data/drops/credit_stick_drop.yaml","old_content":"drops:\n - item_id: credits\n quantity: 14\n weight: 40\n - item_id: credits\n quantity: 30\n weight: 30\n - item_id: credits\n quantity: 50\n weight: 20\n - item_id: credits\n quantity: 100\n weight: 8\n - item_id: credits\n quantity: 250\n weight: 2\nid: credit_stick_drop\n","new_content":"drops:\n - item_id: credits\n quantity: 15\n weight: 40\n - item_id: credits\n quantity: 30\n weight: 30\n - item_id: credits\n quantity: 50\n weight: 20\n - item_id: credits\n quantity: 100\n weight: 8\n - item_id: credits\n quantity: 250\n weight: 2\nid: credit_stick_drop\n","is_delete":false,"is_create":false}],"redo":null} \ No newline at end of file
+{"history":[{"time":"2026-06-30T14:29:37-04:00","description":"delete room 1014 (cleaned 0 exits)","file_path":"data/rooms/intro/1014.yaml","old_content":"id: 1014\nname: 'Room #1014'\ncolor: D5\ndescription: []\nexits:\n southwest:\n room: 1013\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:37-04:00","description":"delete room 1016 (cleaned 0 exits)","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:38-04:00","description":"delete room 1015 (cleaned 0 exits)","file_path":"data/rooms/intro/1015.yaml","old_content":"id: 1015\nname: 'Room #1015'\ncolor: 0C\ndescription: []\nexits:\n northwest:\n room: 1018\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:39-04:00","description":"delete room 1017 (cleaned 0 exits)","file_path":"data/rooms/intro/1017.yaml","old_content":"id: 1017\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:40-04:00","description":"delete room 1018 (cleaned 0 exits)","file_path":"data/rooms/intro/1018.yaml","old_content":"id: 1018\nname: 'Room #1018'\ncolor: D0\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:40-04:00","description":"delete room 1019 (cleaned 0 exits)","file_path":"data/rooms/intro/1019.yaml","old_content":"id: 1019\nname: 'Room #1019'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:29:41-04:00","description":"delete room 1020 (cleaned 0 exits)","file_path":"data/rooms/intro/1020.yaml","old_content":"id: 1020\nname: 'Room #1020'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:45-04:00","description":"delete room 1013 (cleaned 1 exits)","file_path":"data/rooms/intro/1013.yaml","old_content":"id: 1013\nname: 'Room #1013'\ncolor: \"72\"\ndescription: []\nexits:\n west:\n room: 1007\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:49-04:00","description":"delete room 1009 (cleaned 0 exits)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: New Room\ncolor: \"27\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 8\n southwest:\n room: 1006\n west:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs:\n - id: man\n - id: man\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:51-04:00","description":"delete room 1007 (cleaned 1 exits)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: 'Room #1007'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 2004\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:53-04:00","description":"delete room 1006 (cleaned 4 exits)","file_path":"data/rooms/intro/1006.yaml","old_content":"id: 1006\nname: New Room\ncolor: E2\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1005\n northwest:\n room: 1008\n up:\n room: 1010\n west:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:30:59-04:00","description":"delete room 1010 (cleaned 1 exits)","file_path":"data/rooms/intro/1010.yaml","old_content":"id: 1010\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:00-04:00","description":"delete room 1011 (cleaned 0 exits)","file_path":"data/rooms/intro/1011.yaml","old_content":"id: 1011\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1012\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:01-04:00","description":"delete room 1012 (cleaned 0 exits)","file_path":"data/rooms/intro/1012.yaml","old_content":"id: 1012\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:02-04:00","description":"delete room 1005 (cleaned 2 exits)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: CC\ndescription:\n - text: A featureless room.\nexits:\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:31:03-04:00","description":"delete room 1008 (cleaned 1 exits)","file_path":"data/rooms/intro/1008.yaml","old_content":"id: 1008\nname: New Room\ncolor: A9\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 7\n south:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:29-04:00","description":"update room 2003 (add exit north to 1005)","file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n north:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:29-04:00","description":"create room 1005","file_path":"data/rooms/intro/1005.yaml","old_content":"","new_content":"exits:\n south: 2003\nid: 1005\nname: 'Room #1005'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:30-04:00","description":"update room 1005 (add exit east to 1006)","file_path":"data/rooms/intro/1005.yaml","old_content":"exits:\n south: 2003\nid: 1005\nname: 'Room #1005'\n","new_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n east:\n room: 1006\n south:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:30-04:00","description":"create room 1006","file_path":"data/rooms/intro/1006.yaml","old_content":"","new_content":"exits:\n west: 1005\nid: 1006\nname: 'Room #1006'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:31-04:00","description":"update room 1006 (add exit south to 1007)","file_path":"data/rooms/intro/1006.yaml","old_content":"exits:\n west: 1005\nid: 1006\nname: 'Room #1006'\n","new_content":"id: 1006\nname: 'Room #1006'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1007\n west:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:31-04:00","description":"create room 1007","file_path":"data/rooms/intro/1007.yaml","old_content":"","new_content":"exits:\n north: 1006\nid: 1007\nname: 'Room #1007'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:32-04:00","description":"update room 1007 (add exit east to 1008)","file_path":"data/rooms/intro/1007.yaml","old_content":"exits:\n north: 1006\nid: 1007\nname: 'Room #1007'\n","new_content":"id: 1007\nname: 'Room #1007'\ncolor: \"\"\ndescription: []\nexits:\n east:\n room: 1008\n north:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:32-04:00","description":"create room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"","new_content":"exits:\n west: 1007\nid: 1008\nname: 'Room #1008'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:32-04:00","description":"update room 1008 (add exit north to 1009)","file_path":"data/rooms/intro/1008.yaml","old_content":"exits:\n west: 1007\nid: 1008\nname: 'Room #1008'\n","new_content":"id: 1008\nname: 'Room #1008'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1009\n west:\n room: 1007\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:32-04:00","description":"create room 1009","file_path":"data/rooms/intro/1009.yaml","old_content":"","new_content":"exits:\n south: 1008\nid: 1009\nname: 'Room #1009'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:33-04:00","description":"update room 1009 (add exit north to 1010)","file_path":"data/rooms/intro/1009.yaml","old_content":"exits:\n south: 1008\nid: 1009\nname: 'Room #1009'\n","new_content":"id: 1009\nname: 'Room #1009'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1010\n south:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:33-04:00","description":"create room 1010","file_path":"data/rooms/intro/1010.yaml","old_content":"","new_content":"exits:\n south: 1009\nid: 1010\nname: 'Room #1010'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:33-04:00","description":"update room 1010 (add exit east to 1011)","file_path":"data/rooms/intro/1010.yaml","old_content":"exits:\n south: 1009\nid: 1010\nname: 'Room #1010'\n","new_content":"id: 1010\nname: 'Room #1010'\ncolor: \"\"\ndescription: []\nexits:\n east:\n room: 1011\n south:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:33-04:00","description":"create room 1011","file_path":"data/rooms/intro/1011.yaml","old_content":"","new_content":"exits:\n west: 1010\nid: 1011\nname: 'Room #1011'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:34-04:00","description":"update room 1011 (add exit south to 1012)","file_path":"data/rooms/intro/1011.yaml","old_content":"exits:\n west: 1010\nid: 1011\nname: 'Room #1011'\n","new_content":"id: 1011\nname: 'Room #1011'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1012\n west:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:34-04:00","description":"create room 1012","file_path":"data/rooms/intro/1012.yaml","old_content":"","new_content":"exits:\n north: 1011\nid: 1012\nname: 'Room #1012'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:34-04:00","description":"update room 1012 (add exit south to 1013)","file_path":"data/rooms/intro/1012.yaml","old_content":"exits:\n north: 1011\nid: 1012\nname: 'Room #1012'\n","new_content":"id: 1012\nname: 'Room #1012'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1011\n south:\n room: 1013\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T14:32:34-04:00","description":"create room 1013","file_path":"data/rooms/intro/1013.yaml","old_content":"","new_content":"exits:\n north: 1012\nid: 1013\nname: 'Room #1013'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T14:32:35-04:00","description":"delete room 1013 (cleaned 1 exits)","file_path":"data/rooms/intro/1013.yaml","old_content":"exits:\n north: 1012\nid: 1013\nname: 'Room #1013'\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:36-04:00","description":"delete room 1012 (cleaned 1 exits)","file_path":"data/rooms/intro/1012.yaml","old_content":"id: 1012\nname: 'Room #1012'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:37-04:00","description":"delete room 1011 (cleaned 1 exits)","file_path":"data/rooms/intro/1011.yaml","old_content":"id: 1011\nname: 'Room #1011'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:37-04:00","description":"delete room 1010 (cleaned 1 exits)","file_path":"data/rooms/intro/1010.yaml","old_content":"id: 1010\nname: 'Room #1010'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:38-04:00","description":"delete room 1009 (cleaned 1 exits)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: 'Room #1009'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:39-04:00","description":"delete room 1008 (cleaned 1 exits)","file_path":"data/rooms/intro/1008.yaml","old_content":"id: 1008\nname: 'Room #1008'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 1007\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:40-04:00","description":"delete room 1007 (cleaned 1 exits)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: 'Room #1007'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:40-04:00","description":"delete room 1006 (cleaned 1 exits)","file_path":"data/rooms/intro/1006.yaml","old_content":"id: 1006\nname: 'Room #1006'\ncolor: \"\"\ndescription: []\nexits:\n west:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T14:32:41-04:00","description":"delete room 1005 (cleaned 1 exits)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 2003\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-30T15:05:42-04:00","description":"update room 1001","file_path":"data/rooms/intro/1001.yaml","old_content":"id: 1001\nname: Inside Transport Shuttle\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n room: 1002\n condition:\n flag: \"\"\n value: null\n not: false\n player_flag: 1001_touchdown\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n blocked_message: The piston-powered staircase is firmly closed.\n east:\n room: 2001\nobjects:\n - name: framed sign\n hidden: true\n description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n on_look:\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n cost: 0\n shop: null\n assign_task: false\n skip_task: false\n extend_task: false\n reputation_cost: 0\n sawmill: false\n aps_node: false\n - name: door\n hidden: true\n description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n - name: window\n hidden: true\n description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n - name: instrument panel\n aliases:\n - cockpit\n hidden: true\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n - name: pilot\n hidden: true\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\nitem_spawns: []\nmobs:\n - id: flight_attendant\non_enter:\n - message: '{0B bold}Welcome to The House of Icarus{/}'\n condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_welcome\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n delay: 5\n set_flags: {}\n set_player_flags: {}\n broadcast: \"\"\n broadcast_global: \"\"\n spawn_mob: null\n despawn_mob: \"\"\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n - message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_welcome\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n delay: 7\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n broadcast: \"\"\n broadcast_global: \"\"\n spawn_mob: null\n despawn_mob: \"\"\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n - message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_welcome\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n delay: 7\n set_flags: {}\n set_player_flags: {}\n broadcast: \"\"\n broadcast_global: \"\"\n spawn_mob: null\n despawn_mob: \"\"\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\nhazard: \"\"\nblock_transport: true\ntriggers:\n - id: \"\"\n on_player_flag: 1001_look_sign\n on_flag: \"\"\n value: null\n room: 0\n steps:\n - delay: 5\n message: The cabin shakes as the small craft touches down\n broadcast: \"\"\n broadcast_global: \"\"\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n despawn_mob: \"\"\n - delay: 5\n message: The pistons hiss as the rear staircase opens\n broadcast: \"\"\n broadcast_global: \"\"\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n despawn_mob: \"\"\n - delay: 0\n message: \"\"\n broadcast: \"\"\n broadcast_global: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n give_item: \"\"\n take_item: \"\"\n teleport: 0\n heal: 0\n despawn_mob: \"\"\n","new_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: false\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","is_delete":false,"is_create":false},{"time":"2026-06-30T15:05:43-04:00","description":"update room 1001","file_path":"data/rooms/intro/1001.yaml","old_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: false\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","new_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: true\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","is_delete":false,"is_create":false},{"time":"2026-06-30T16:34:21-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T16:35:02-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T16:35:15-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T17:43:28-04:00","description":"Update object copper_rock","file_path":"data/objects/copper_rock.yaml","old_content":"color: \"B2\"\ngather:\n depleted_message: You don't see any ore in this rock.\n drops:\n - depletes: true\n item_id: copper_ore\n level: 1\n message: You manage to mine some {B2}copper ore{/}.\n weight: 90\n xp: 17\n - depletes: false\n message: You spot a glint of something valuable!\n table: gem_table\n weight: 10\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the rock...\n respawn_broadcast: A glint of copper catches your eye from some {name}.\n respawn_timer: 50\n skill: mining\n success:\n base: 0.5\n cap: 0.95\n per_level: 0.01\n tools:\n - pickaxe\nname: copper rock\n","new_content":"aliases: []\ncolor: B2\ndescription: \"\"\ngather:\n bait: \"\"\n deplete_timer: 0\n depleted_message: You don't see any ore in this rock.\n drops:\n - depletes: true\n item_id: copper_ore\n level: 1\n message: You manage to mine some {B2}copper ore{/}.\n quantity: 0\n table: \"\"\n weight: 90\n xp: 17\n - depletes: false\n item_id: \"\"\n level: 0\n message: You spot a glint of something valuable!\n quantity: 0\n table: gem_table\n weight: 10\n xp: 0\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the rock...\n nest_chance: 0\n respawn_broadcast: A glint of copper catches your eye from some {name}.\n respawn_timer: 50\n skill: mining\n success:\n base: 0.5\n cap: 0.95\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: copper_rock\ninroom_description: \"\"\nname: copper rock\nremoval_item: \"\"\n","is_delete":false,"is_create":false},{"time":"2026-06-30T23:48:18-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T23:48:23-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T23:53:08-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: \"DE\"\ncraft:\n type: combine\n consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n wait: 2\ndescription: A ball of uncooked bread dough. It needs to be baked.\nname: bread dough\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n wait: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T00:23:04-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: DE\ncraft:\n - consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n wait: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - consume:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n wait: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T01:22:55-04:00","description":"update room 1001","file_path":"data/rooms/intro/1001.yaml","old_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: true\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","new_content":"block_transport: true\ncolor: \"\"\ndescription:\n - text: |-\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large framed sign hangs next to the closed cockpit door. The piston-powered staircase is firmly closed at the rear of the craft.\n\n A dim, windowless shuttle interior with a few uncomfortable rows of steel seats facing forward. A large {11}framed sign{/} hangs next to the closed cockpit door. A piston-powered staircase leads down out the back of the small craft with a smiling attendant ready to help disembark.\nexits:\n down:\n blocked_message: The piston-powered staircase is firmly closed.\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: false\n player_flag: 1001_touchdown\n value: null\n room: 1002\n east:\n room: 2001\nhazard: \"\"\nid: 1001\nitem_spawns: []\nmobs:\n - id: flight_attendant\nname: Inside Transport Shuttle\nobjects:\n - description:\n - text: \"We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\\n\\nWhen you arrive at your final destination, please be aware of the following basic commands:\\n\\n{0B bold}Movement:{/} {0A}north{/}, {0A}south{/}, {0A}east{/}, {0A}west{/}, {0A}up{/}, {0A}down{/}\\n{0B bold}Look:{/} {0A}look{/}, {0A}look \u003citem/object/mob\u003e{/}, and {0A}map{/}\\n{0B bold}Items:{/} {0A}inv{/} to see your 28-slot inventory\\n {0A}get \u003citem\u003e{/}, {0A}drop \u003citem\u003e{/}, {0A}wear \u003citem\u003e{/}, {0A}remove \u003citem\u003e{/}\\n {0A}use \u003citem\u003e on \u003citem/object\u003e{/}\\n {0A}eq{/} for equiped gear\\n{0B bold}Comms:{/} {0A}talk \u003cmob\u003e{/} for NPC chat\\n {0A}say \u003cmsg\u003e{/} and {0A}global \u003cmsg\u003e{/} for human chat\\n{0B bold}Character:{/} {0A}score{/} and {0A}skills{/} for stats. \\n{0B bold}Combat{/} {0A}attack \u003cmob\u003e{/}\\n{0B bold}Skills{/} Many skills have shortcuts like {0A}mine{/}, {0A}fish{/}, or {0A}chop{/}\\n{0B bold}Options{/} {0A}options{/} for a list of account options\\n {0A}options \u003copt\u003e \u003cvalue\u003e{/} to change an option\\n\\nType {0C bold}what{/} for a list of possible common actions\\n\\nMake sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\"\n hidden: true\n name: framed sign\n on_look:\n aps_node: false\n assign_task: false\n cost: 0\n extend_task: false\n give_item: \"\"\n heal: 0\n reputation_cost: 0\n sawmill: false\n set_flags: {}\n set_player_flags:\n 1001_look_sign: true\n shop: null\n skip_task: false\n take_item: \"\"\n teleport: 0\n - description:\n - text: A sturdy, locked steel door that keeps passengers out of the cockpit. There is a tiny, thin window at eye level.\n hidden: true\n name: door\n - description:\n - text: A thick trim with rounded bolt heads frames the tiny window. It looks like it was cut into the door at some point, potentially as a simple replacement to computerized security systems. Inside the cockpit there's a pilot finishing up landing procedures on a large panel of analog instruments.\n hidden: true\n name: window\n - aliases:\n - cockpit\n description:\n - text: The cramped cockpit is surrounded by buttons, swiches, dials, and analog gauges. The parts are a mix of technologies from the last century. Part of the panel is obscured by a draped cloth as if to hide it from view. Odd.\n hidden: true\n name: instrument panel\n - aliases: []\n description:\n - text: That's the guy who took you from the Passenger Barge Denali down here to Vesta. A middle-aged man in a neat white jumpsuit with a matching white sort of conductor's hat. He's following a written procedure checklist and seems to take his job seriously.\n hidden: true\n name: pilot\non_enter:\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B bold}Welcome to The House of Icarus{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}'\n set_flags: {}\n set_player_flags:\n 1001_welcome: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n condition:\n all_of: []\n any_of: []\n flag: \"\"\n has_item: \"\"\n min_credits: 0\n not: true\n player_flag: 1001_welcome\n value: null\n delay: 7\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: '{0B}Type{/} {0A}help newplayer{/} {0B}for the new player''s guide. That''s it. Explore and have fun.{/}'\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\ntriggers:\n - id: \"\"\n on_flag: \"\"\n on_player_flag: 1001_look_sign\n room: 0\n steps:\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The cabin shakes as the small craft touches down\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 5\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: The pistons hiss as the rear staircase opens\n set_flags: {}\n set_player_flags: {}\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n - broadcast: \"\"\n broadcast_global: \"\"\n delay: 0\n despawn_mob: \"\"\n give_item: \"\"\n heal: 0\n message: \"\"\n set_flags: {}\n set_player_flags:\n 1001_touchdown: true\n spawn_mob: null\n take_item: \"\"\n teleport: 0\n value: null\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:00:10-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 0\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:00:11-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 0\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:52:58-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: DE\ncraft:\n - ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n end_message: testend\n fail: \"\"\n level: 0\n success_message: testmsg\n output_qty: 0\n start_message: teststart\n station: []\n subtype: \"\"\n tool: \"\"\n type: combine\n ticks_per_cycle: 2\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n level: 0\n output_qty: 0\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: smithing\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T03:53:08-04:00","description":"Update item bread_dough","file_path":"data/items/consumables/bread_dough.yaml","old_content":"color: DE\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n level: 0\n output_qty: 0\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: smithing\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","new_content":"color: DE\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - byproducts:\n - empty_pot\n items:\n - pot_of_flour\n quantity: 1\n - byproducts:\n - empty_bucket\n - empty_jug\n items:\n - bucket_of_water\n - jug_of_water\n quantity: 1\n level: 0\n output_qty: 0\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: combine\n xp: 0\ndescription: A ball of uncooked bread dough. It needs to be baked.\nid: bread_dough\nmax_quality: 0\nname: bread dough\nquality: 0\nstackable: false\nvalue: 3\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:15-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: solar_radiation\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:19-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: solar_radiation\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:26-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs: []\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:32-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\n - aliases: []\n description:\n - text: \"\"\n hidden: false\n name: \"\"\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:37-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - id: tree\n - aliases: []\n description:\n - text: \"\"\n hidden: false\n name: \"\"\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description: []\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:43-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description: []\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:08:44-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: false\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: true\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:41-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:41-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:44-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:44-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:47-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:47-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:49-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:49-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:51-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"00\"\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:53-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:55-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:56-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:58-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:09:58-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:10:01-04:00","description":"update room 2003 (add exit northeast to 1005)","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nhazard: \"\"\nid: 2003\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n northeast:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:10:01-04:00","description":"create room 1005","file_path":"data/rooms/intro/1005.yaml","old_content":"","new_content":"color: A5\nexits:\n southwest: 2003\nid: 1005\nname: 'Room #1005'\n","is_delete":false,"is_create":true},{"time":"2026-07-01T04:10:03-04:00","description":"delete room 1005 (cleaned 1 exits)","file_path":"data/rooms/intro/1005.yaml","old_content":"color: A5\nexits:\n southwest: 2003\nid: 1005\nname: 'Room #1005'\n","new_content":"","is_delete":true,"is_create":false,"extra_files":[{"file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n northeast:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n"}]},{"time":"2026-07-01T04:11:43-04:00","description":"update room 2003 (add exit north to 1005)","file_path":"data/rooms/intro/2003.yaml","old_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 2003\nname: Test Room\ncolor: A5\ndescription:\n - text: It's a test room\nexits:\n north:\n room: 1005\n south:\n room: 2004\n southwest:\n room: 2005\n west:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:22:38-04:00","description":"Update drop credit_stick_drop","file_path":"data/drops/credit_stick_drop.yaml","old_content":"drops:\n - item_id: credits\n weight: 40\n quantity: 15\n - item_id: credits\n weight: 30\n quantity: 30\n - item_id: credits\n weight: 20\n quantity: 50\n - item_id: credits\n weight: 8\n quantity: 100\n - item_id: credits\n weight: 2\n quantity: 250","new_content":"drops:\n - item_id: credits\n quantity: 14\n weight: 40\n - item_id: credits\n quantity: 30\n weight: 30\n - item_id: credits\n quantity: 50\n weight: 20\n - item_id: credits\n quantity: 100\n weight: 8\n - item_id: credits\n quantity: 250\n weight: 2\nid: credit_stick_drop\n","is_delete":false,"is_create":false},{"time":"2026-07-01T04:22:42-04:00","description":"Update drop credit_stick_drop","file_path":"data/drops/credit_stick_drop.yaml","old_content":"drops:\n - item_id: credits\n quantity: 14\n weight: 40\n - item_id: credits\n quantity: 30\n weight: 30\n - item_id: credits\n quantity: 50\n weight: 20\n - item_id: credits\n quantity: 100\n weight: 8\n - item_id: credits\n quantity: 250\n weight: 2\nid: credit_stick_drop\n","new_content":"drops:\n - item_id: credits\n quantity: 15\n weight: 40\n - item_id: credits\n quantity: 30\n weight: 30\n - item_id: credits\n quantity: 50\n weight: 20\n - item_id: credits\n quantity: 100\n weight: 8\n - item_id: credits\n quantity: 250\n weight: 2\nid: credit_stick_drop\n","is_delete":false,"is_create":false},{"time":"2026-07-01T13:06:32-04:00","description":"Create mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"","new_content":"id: flight_attendant_copy\n","is_delete":false,"is_create":true},{"time":"2026-07-01T13:06:32-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"id: flight_attendant_copy\n","new_content":"description: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nname: flight attendant\nprotected: true\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - text: '\"Thank you.\"'\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T13:07:46-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"description: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nname: flight attendant\nprotected: true\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - text: '\"Thank you.\"'\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n sequence:\n - Test\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - text: '\"Thank you.\"'\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T13:08:04-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n sequence:\n - Test\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - text: '\"Thank you.\"'\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n sequence:\n - Test\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - text: '\"Thank you.\"'\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T13:09:12-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n sequence:\n - Test\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - text: '\"Thank you.\"'\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - goto: Test\n text: '\"Thank you.\"'\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T13:16:15-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - goto: Test\n text: '\"Thank you.\"'\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n sequence:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n message: '\"Hi there, how can I help?\"'\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n thanks:\n message: '\"Thank for flying with Denali. Watch your step on the way out please.\"'\n options:\n - goto: Test\n text: '\"Thank you.\"'\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T13:16:46-04:00","description":"Update item adamant_arrow","file_path":"data/items/ammo/adamant_arrow.yaml","old_content":"color: \"78\"\ncraft:\n ingredients:\n - items:\n - headless_arrow\n quantity: 15\n - items:\n - adamant_arrowtips\n quantity: 15\n level: 60\n output_qty: 15\n type: fletching\n ticks_per_cycle: 2\n xp: 10\ndescription: An adamant-tipped arrow.\nequipment:\n other:\n ranged: 31\n slot: ammo\n type: ammo\nname: adamant arrow\nrecoverable: true\nstackable: true\nvalue: 20\n","new_content":"color: \"78\"\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - items:\n - headless_arrow\n quantity: 15\n - items:\n - adamant_arrowtips\n quantity: 15\n level: 60\n output_qty: 15\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: fletching\n xp: 10\ndescription: An adamant-tipped arrow.2\nequipment:\n other:\n ranged: 31\n slot: ammo\n type: ammo\nid: adamant_arrow\nmax_quality: 0\nname: adamant arrow\nquality: 0\nrecoverable: true\nstackable: true\nvalue: 20\n","is_delete":false,"is_create":false},{"time":"2026-07-01T13:16:50-04:00","description":"Update item adamant_arrow","file_path":"data/items/ammo/adamant_arrow.yaml","old_content":"color: \"78\"\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - items:\n - headless_arrow\n quantity: 15\n - items:\n - adamant_arrowtips\n quantity: 15\n level: 60\n output_qty: 15\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: fletching\n xp: 10\ndescription: An adamant-tipped arrow.2\nequipment:\n other:\n ranged: 31\n slot: ammo\n type: ammo\nid: adamant_arrow\nmax_quality: 0\nname: adamant arrow\nquality: 0\nrecoverable: true\nstackable: true\nvalue: 20\n","new_content":"color: \"78\"\ncraft:\n - end_message: \"\"\n fail: \"\"\n ingredients:\n - items:\n - headless_arrow\n quantity: 15\n - items:\n - adamant_arrowtips\n quantity: 15\n level: 60\n output_qty: 15\n start_message: \"\"\n station: []\n subtype: \"\"\n success_message: \"\"\n ticks_per_cycle: 2\n tool: \"\"\n type: fletching\n xp: 10\ndescription: An adamant-tipped arrow.\nequipment:\n other:\n ranged: 31\n slot: ammo\n type: ammo\nid: adamant_arrow\nmax_quality: 0\nname: adamant arrow\nquality: 0\nrecoverable: true\nstackable: true\nvalue: 20\n","is_delete":false,"is_create":false},{"time":"2026-07-01T15:41:47-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n sign_reminder:\n messages:\n - '\"Yeah, just us two! Not a lot of people heading into the belt these days.\"'\n - '\"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination.\"'\n start:\n condition:\n not: true\n player_flag: 1001_look_sign\n goto: thanks\n messages: ['\"Hi there, how can I help?\"']\n options:\n - goto: sign_reminder\n text: '\"Oh, nothing... Small crowd on this ship. Just looking for conversation.\"'\n - text: '\"Goodbye.\"'\n thanks:\n messages: ['\"Thank for flying with Denali. Watch your step on the way out please.\"']\n options:\n - goto: Test\n text: '\"Thank you.\"'\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n start:\n options:\n - goto: \"\"\n text: None!\n - text: None\n - condition:\n min_credits: 500000\n goto: \"\"\n text: 500000!!\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T15:41:55-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: flight attendant\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n start:\n options:\n - goto: \"\"\n text: None!\n - text: None\n - condition:\n min_credits: 500000\n goto: \"\"\n text: 500000!!\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: Credit Checker!\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n start:\n options:\n - goto: \"\"\n text: None!\n - text: None\n - condition:\n min_credits: 500000\n goto: \"\"\n text: 500000!!\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T15:42:56-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: true\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2002\n south:\n room: 2006\n southeast:\n room: 2005\n west:\n room: 1001\nhazard: \"\"\nid: 2001\nitem_spawns:\n - id: bronze_axe\n quantity: 1\n respawn_ticks: 1\n - id: pot_of_flour\n quantity: 1\n respawn_ticks: 1\n - id: bucket_of_water\n quantity: 1\n respawn_ticks: 1\nmobs:\n - id: man\n - id: flight_attendant_copy\nname: Test Room\nobjects:\n - aliases: []\n description:\n - text: YOU FOUND HIM\n hidden: true\n name: hidden guy\n - id: tree\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-01T15:47:02-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: Credit Checker!\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n start:\n options:\n - goto: \"\"\n text: None!\n - text: None\n - condition:\n min_credits: 500000\n goto: \"\"\n text: 500000!!\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: Credit Checker!\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n start:\n options:\n - goto: \"\"\n text: None!\n - text: None\n - action:\n shop:\n items:\n - buy_price: 1\n item_id: bronze_pickaxe\n sell_price: 1\n message: SHOP SHOP\n goto: \"\"\n text: 500000!!\nunique: false\n","is_delete":false,"is_create":false},{"time":"2026-07-01T15:47:37-04:00","description":"Update mob flight_attendant_copy","file_path":"data/mobs/flight_attendant_copy.yaml","old_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: Credit Checker!\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n start:\n options:\n - goto: \"\"\n text: None!\n - text: None\n - action:\n shop:\n items:\n - buy_price: 1\n item_id: bronze_pickaxe\n sell_price: 1\n message: SHOP SHOP\n goto: \"\"\n text: 500000!!\nunique: false\n","new_content":"aggressive: false\ndescription: A smiling flight attendant in a crisp uniform, ready to assist passengers.\nid: flight_attendant_copy\nidle_descriptions:\n - adjusts her name badge\n - smiles politely at you\nkind: combat\nname: Credit Checker!\nprotected: true\nrespawn_ticks: 0\ntalk:\n nodes:\n start:\n options:\n - goto: \"\"\n text: None!\n - text: None\n - action:\n shop:\n items:\n - buy_price: 1\n item_id: bronze_pickaxe\n sell_price: 1\n message: SHOP SHOP\n goto: \"\"\n text: 500000!!\nunique: false\n","is_delete":false,"is_create":false}],"redo":null} \ No newline at end of file
diff --git a/data/help/shop.yaml b/data/help/shop.yaml
index cc20420..92f26db 100644
--- a/data/help/shop.yaml
+++ b/data/help/shop.yaml
@@ -1,27 +1,31 @@
name: "shop"
category: "Interaction"
description: |
- Buy and sell items at shops.
+ Buy and sell items with shopkeeper NPCs, straight from the game prompt.
- Find a shopkeeper NPC and talk to them to open their shop. While
- browsing a shop, these commands are available:
+ Find a mob that runs a shop and use these commands in the room:
- browse List all items for sale with buy/sell prices
- list Same as browse
- buy <item> Buy an item by name (e.g. "buy fishing bait")
- buy <N> <item> Buy a quantity of an item (e.g. "buy 5 bait")
- buy all <item> Buy as many as you can afford and carry
- sell <item> Sell an item by name (e.g. "sell bronze sword")
- sell <N> <item> Sell a quantity of an item (e.g. "sell 3 logs")
- sell all <item> Sell all of that item from your inventory
- leave Exit the shop and return to conversation
+ list Show the shop's inventory (item, stock, buy, sell prices)
+ buy <item> Buy an item by name (e.g. "buy fishing bait")
+ buy <N> <item> Buy a quantity (e.g. "buy 5 bait")
+ buy all <item> Buy as many as you can afford and carry
+ sell <item> Sell an item from your inventory (e.g. "sell bronze sword")
+ sell <N> <item> Sell a quantity (e.g. "sell 3 logs")
+ sell all <item> Sell your entire held stack of that item
- Partial names work: "buy bait" matches "fishing bait". If a name
- matches multiple items, a list is shown to disambiguate.
+ Partial names work: "buy bait" matches "fishing bait". If a name matches
+ multiple items, a list is shown to disambiguate.
- Shops buy items at a lower price than they sell them. Some items
- cannot be sold back.
+ If a room has more than one shopkeeper, name the one you mean:
- Shopkeepers can be found throughout the world:
- - General Store: Town Square (sells tools and supplies)
- - Smithy: Forge (sells weapons, armor, and tools)
+ list <mob>
+ buy <item> from <mob>
+ sell <item> to <mob>
+
+ Prices are based on each item's value. Shops sell items for 100% of their
+ value. They buy items back for less, starting around 40% of value and paying
+ less the more of that item they already hold (down to a floor of 10%). Buy
+ low, sell where a shop is short on stock.
+
+ Shops keep limited stock that refills over time. An item can sell out, and
+ selling to a shop lowers what it will pay for the next one.
diff --git a/data/items/ammo/adamant_arrow.yaml b/data/items/ammo/adamant_arrow.yaml
index 68e9f18..9cacf2f 100644
--- a/data/items/ammo/adamant_arrow.yaml
+++ b/data/items/ammo/adamant_arrow.yaml
@@ -1,24 +1,34 @@
color: "78"
craft:
- ingredients:
- - items:
- - headless_arrow
- quantity: 15
- - items:
- - adamant_arrowtips
- quantity: 15
- level: 60
- output_qty: 15
- type: fletching
- ticks_per_cycle: 2
- xp: 10
+ - end_message: ""
+ fail: ""
+ ingredients:
+ - items:
+ - headless_arrow
+ quantity: 15
+ - items:
+ - adamant_arrowtips
+ quantity: 15
+ level: 60
+ output_qty: 15
+ start_message: ""
+ station: []
+ subtype: ""
+ success_message: ""
+ ticks_per_cycle: 2
+ tool: ""
+ type: fletching
+ xp: 10
description: An adamant-tipped arrow.
equipment:
other:
ranged: 31
slot: ammo
type: ammo
+id: adamant_arrow
+max_quality: 0
name: adamant arrow
+quality: 0
recoverable: true
stackable: true
value: 20
diff --git a/data/mobs/examples/client.yaml b/data/mobs/examples/client.yaml
index 91b6802..0d5f27d 100644
--- a/data/mobs/examples/client.yaml
+++ b/data/mobs/examples/client.yaml
@@ -19,17 +19,18 @@ talk:
nodes:
assign_task:
action:
- assign_task: true
- message: '"Let me check what''s available..." The Client scrolls through their data pad.'
+ messages:
+ - '"Let me check what''s available..." The Client scrolls through their data pad.'
options:
- text: '"Understood."'
- goto: start
text: '"What else do you have?"'
buy_acid:
action:
- cost: 10
+ credits: -10
give_item: acid_vial
- message: '"Handle with care." The Client passes you a vial of corrosive acid.'
+ messages:
+ - '"Handle with care." The Client passes you a vial of corrosive acid.'
options:
- condition:
min_credits: 10
@@ -42,7 +43,8 @@ talk:
reputation_cost: 200
set_player_flags:
assassin_unlocked_auto_acid_vial: true
- message: '"Auto-acid purchased. Acid vials will no longer be consumed."'
+ messages:
+ - '"Auto-acid purchased. Acid vials will no longer be consumed."'
options:
- goto: rep_shop
text: '"Thanks."'
@@ -51,7 +53,8 @@ talk:
reputation_cost: 200
set_player_flags:
assassin_unlocked_auto_salt: true
- message: '"Auto-salt purchased. You''ll no longer consume salt on finishing blows."'
+ messages:
+ - '"Auto-salt purchased. You''ll no longer consume salt on finishing blows."'
options:
- goto: rep_shop
text: '"Thanks."'
@@ -60,23 +63,26 @@ talk:
reputation_cost: 100
set_player_flags:
assassin_unlocked_extend: true
- message: '"You can now extend tasks via our conversation."'
+ messages:
+ - '"You can now extend tasks via our conversation."'
options:
- goto: rep_shop
text: '"Thanks."'
buy_gloves:
action:
- cost: 50
+ credits: -50
give_item: insulated_gloves
- message: '"These''ll keep the current from frying your hands." The Client tosses you a pair of thick rubber gloves.'
+ messages:
+ - '"These''ll keep the current from frying your hands." The Client tosses you a pair of thick rubber gloves.'
options:
- goto: supplies
text: '"Thanks."'
buy_salt:
action:
- cost: 5
+ credits: -5
give_item: salt
- message: '"Here you go." The Client slides a packet of salt across the table.'
+ messages:
+ - '"Here you go." The Client slides a packet of salt across the table.'
options:
- condition:
min_credits: 5
@@ -89,24 +95,28 @@ talk:
reputation_cost: 300
set_player_flags:
assassin_unlocked_superiors: true
- message: '"Superior encounters unlocked. Watch yourself out there."'
+ messages:
+ - '"Superior encounters unlocked. Watch yourself out there."'
options:
- goto: rep_shop
text: '"Thanks."'
buy_visor:
action:
- cost: 75
+ credits: -75
give_item: spectral_visor
- message: '"Spectral frequency filter. Makes the invisible visible — and keeps their attacks from scrambling your brain."'
+ messages:
+ - '"Spectral frequency filter. Makes the invisible visible — and keeps their attacks from scrambling your brain."'
options:
- goto: supplies
text: '"Thanks."'
current_task:
- message: '"Let me check your file." The Client pulls up your record.'
+ messages:
+ - '"Let me check your file." The Client pulls up your record.'
options:
- text: '"Okay."'
extend_confirm:
- message: '"Extending your task costs 30 Reputation and adds more kills. Want to proceed?"'
+ messages:
+ - '"Extending your task costs 30 Reputation and adds more kills. Want to proceed?"'
options:
- goto: extend_done
text: '"Yes, extend it."'
@@ -114,12 +124,13 @@ talk:
text: '"Never mind."'
extend_done:
action:
- extend_task: true
- message: '"Done. I''ve added more targets to your contract."'
+ messages:
+ - '"Done. I''ve added more targets to your contract."'
options:
- text: '"Thanks."'
rep_shop:
- message: '"Here''s what I''ve got. All purchases are permanent."'
+ messages:
+ - '"Here''s what I''ve got. All purchases are permanent."'
options:
- condition:
not: true
@@ -144,7 +155,8 @@ talk:
- goto: start
text: '"Back."'
skip_confirm:
- message: '"Skipping a task costs 30 Reputation and resets your streak. Are you sure?"'
+ messages:
+ - '"Skipping a task costs 30 Reputation and resets your streak. Are you sure?"'
options:
- goto: skip_done
text: '"Yes, skip it."'
@@ -152,14 +164,15 @@ talk:
text: '"Never mind."'
skip_done:
action:
- skip_task: true
- message: '"Task cancelled. Your streak has been reset."'
+ messages:
+ - '"Task cancelled. Your streak has been reset."'
options:
- goto: assign_task
text: '"Give me a new one."'
- text: '"Goodbye."'
start:
- message: The Client looks up from their data pad. "What do you need?"
+ messages:
+ - The Client looks up from their data pad. "What do you need?"
options:
- condition:
not: true
@@ -190,7 +203,8 @@ talk:
text: '"I need supplies."'
- text: '"Goodbye."'
supplies:
- message: '"I stock everything you need for the job. Cheap, too."'
+ messages:
+ - '"I stock everything you need for the job. Cheap, too."'
options:
- condition:
min_credits: 5
diff --git a/data/mobs/examples/estate_broker.yaml b/data/mobs/examples/estate_broker.yaml
index 68c3eb6..b095214 100644
--- a/data/mobs/examples/estate_broker.yaml
+++ b/data/mobs/examples/estate_broker.yaml
@@ -6,17 +6,20 @@ protected: true
talk:
nodes:
already_own:
- message: Yes, you're already a proud homeowner here! Your house is waiting for you. Use the Estate Directory to enter it, or expand your workshop.
+ messages:
+ - Yes, you're already a proud homeowner here! Your house is waiting for you. Use the Estate Directory to enter it, or expand your workshop.
options:
- goto: start
text: Good to know.
goodbye:
- message: Come back when you're ready to invest in your future!
+ messages:
+ - Come back when you're ready to invest in your future!
info:
- message: Each house comes with an entrance hall and a workshop equipped with a workbench. You can use the workbench with a saw to turn logs into planks, or assemble planks into furniture. Furniture can be sold for a profit or displayed in your home. Future developments will allow adding more rooms to your house.
+ messages:
+ - Each house comes with an entrance hall and a workshop equipped with a workbench. You can use the workbench with a saw to turn logs into planks, or assemble planks into furniture. Furniture can be sold for a profit or displayed in your home. Future developments will allow adding more rooms to your house.
options:
- action:
- cost: 10
+ credits: -10
set_player_flags:
owns_house_local_neighborhood: local_neighborhood
condition:
@@ -27,15 +30,17 @@ talk:
- goto: goodbye
text: Maybe later.
purchased:
- message: Excellent! The plot is yours. You'll find your new home in the Local Neighborhood just north of here. Use the Estate Directory terminal to enter your house. The home comes with a workshop — perfect for construction projects.
+ messages:
+ - Excellent! The plot is yours. You'll find your new home in the Local Neighborhood just north of here. Use the Estate Directory terminal to enter your house. The home comes with a workshop — perfect for construction projects.
options:
- goto: start
text: Thanks!
start:
- message: Welcome to the Local Neighborhood development! I'm selling premium housing plots for only 10 credits. Interested in buying a house?
+ messages:
+ - Welcome to the Local Neighborhood development! I'm selling premium housing plots for only 10 credits. Interested in buying a house?
options:
- action:
- cost: 10
+ credits: -10
set_player_flags:
owns_house_local_neighborhood: local_neighborhood
condition:
diff --git a/data/mobs/examples/general_store_clerk.yaml b/data/mobs/examples/general_store_clerk.yaml
index 8a07191..bee395d 100644
--- a/data/mobs/examples/general_store_clerk.yaml
+++ b/data/mobs/examples/general_store_clerk.yaml
@@ -19,52 +19,35 @@ slash_defense: 0
speed: 5
stab_defense: 0
strength: 1
+shop:
+ message: '"Welcome to the General Store! Have a look around."'
+ items:
+ - item_id: fishing_rod
+ stock: 10
+ - item_id: fishing_bait
+ stock: 1000
+ - item_id: matches
+ stock: 20
+ - item_id: firesteel
+ stock: 10
+ - item_id: lighter
+ stock: 10
+ - item_id: hammer
+ stock: 10
+ - item_id: knife
+ stock: 10
+ - item_id: chisel
+ stock: 10
+ - item_id: bread
+ stock: 30
+ - item_id: bowstring
+ stock: 50
talk:
nodes:
- shop:
- action:
- shop:
- items:
- - buy_price: 10
- item_id: fishing_rod
- sell_price: 2
- - buy_price: 2
- item_id: fishing_bait
- sell_price: 0
- - buy_price: 10
- item_id: matches
- sell_price: 2
- - buy_price: 40
- item_id: firesteel
- sell_price: 10
- - buy_price: 30
- item_id: lighter
- sell_price: 7
- - buy_price: 20
- item_id: hammer
- sell_price: 5
- - buy_price: 10
- item_id: knife
- sell_price: 2
- - buy_price: 10
- item_id: chisel
- sell_price: 2
- - buy_price: 10
- item_id: bread
- sell_price: 2
- - buy_price: 20
- item_id: bowstring
- sell_price: 5
- message: What would you like to buy or sell?
- message: '"Take your time. What can I get you?"'
- options:
- - goto: start
- text: '"I''m done."'
start:
- message: '"Welcome to the General Store! Need any supplies?"'
+ messages:
+ - '"Welcome to the General Store! Type ''list'' to see my wares."'
options:
- - goto: shop
- text: '"I''d like to browse."'
- text: '"Goodbye."'
unique: true
size: small
diff --git a/data/mobs/examples/guard.yaml b/data/mobs/examples/guard.yaml
index 7bd7d19..cba448b 100644
--- a/data/mobs/examples/guard.yaml
+++ b/data/mobs/examples/guard.yaml
@@ -27,7 +27,8 @@ talk:
action:
set_player_flags:
talked_to_guard: true
- message: '"Supplies. Weapons. Things you don''t need to worry about." He shifts his weight. "Tell you what — bring me some copper ore and I''ll stamp you a pass."'
+ messages:
+ - '"Supplies. Weapons. Things you don''t need to worry about." He shifts his weight. "Tell you what — bring me some copper ore and I''ll stamp you a pass."'
options:
- text: '"I''ll be back."'
- condition:
@@ -39,11 +40,13 @@ talk:
action:
set_flags:
gate_open: true
- message: '"Let me see that..." He examines your pass and nods. "Alright, I''ll open the gate for you."'
+ messages:
+ - '"Let me see that..." He examines your pass and nods. "Alright, I''ll open the gate for you."'
options:
- text: '"Thanks."'
start:
- message: '"Halt! This area is restricted." The guard eyes you suspiciously.'
+ messages:
+ - '"Halt! This area is restricted." The guard eyes you suspiciously.'
options:
- goto: about_gate
text: '"What''s behind that gate?"'
@@ -64,7 +67,8 @@ talk:
got_pass: true
talked_to_guard: true
take_item: copper_ore
- message: '"Good quality ore. This''ll do." He stamps a slip of paper and hands it to you. "That''s your pass. Don''t lose it."'
+ messages:
+ - '"Good quality ore. This''ll do." He stamps a slip of paper and hands it to you. "That''s your pass. Don''t lose it."'
options:
- text: '"Thanks."'
unique: true
diff --git a/data/mobs/examples/sawmill_operator.yaml b/data/mobs/examples/sawmill_operator.yaml
index 7abd881..366cfe3 100644
--- a/data/mobs/examples/sawmill_operator.yaml
+++ b/data/mobs/examples/sawmill_operator.yaml
@@ -6,16 +6,18 @@ protected: true
talk:
nodes:
goodbye:
- message: Come back anytime you need more planks!
+ messages:
+ - Come back anytime you need more planks!
prices:
- message: |-
- Here's what I charge per log:
- Regular logs: 5 credits
- Oak logs: 10 credits
- Teak logs: 20 credits
- Mahogany logs: 40 credits
+ messages:
+ - |-
+ Here's what I charge per log:
+ Regular logs: 5 credits
+ Oak logs: 10 credits
+ Teak logs: 20 credits
+ Mahogany logs: 40 credits
- Want me to process your logs?
+ Want me to process your logs?
options:
- condition:
any_of:
@@ -30,9 +32,11 @@ talk:
process:
action:
sawmill: true
- message: ""
+ messages:
+ - ""
start:
- message: Welcome to the lumberyard! I can turn your logs into planks — for a small fee, of course. What can I do for ya?
+ messages:
+ - Welcome to the lumberyard! I can turn your logs into planks — for a small fee, of course. What can I do for ya?
options:
- condition:
any_of:
diff --git a/data/mobs/examples/tanner.yaml b/data/mobs/examples/tanner.yaml
index 2e5d89a..4c1f38b 100644
--- a/data/mobs/examples/tanner.yaml
+++ b/data/mobs/examples/tanner.yaml
@@ -22,7 +22,8 @@ strength: 1
talk:
nodes:
start:
- message: '"Bring me some cowhides and I''ll tan them for you. Leather is 10 credits, hard leather is 50."'
+ messages:
+ - '"Bring me some cowhides and I''ll tan them for you. Leather is 10 credits, hard leather is 50."'
options:
- condition:
all_of:
@@ -39,10 +40,11 @@ talk:
- text: '"Never mind."'
tan_hard_leather:
action:
- cost: 50
+ credits: -50
give_item: hard_leather
take_item: cowhide
- message: The tanner takes your cowhide and subjects it to an intense curing process. "Here's your hard leather."
+ messages:
+ - The tanner takes your cowhide and subjects it to an intense curing process. "Here's your hard leather."
options:
- condition:
has_item: cowhide
@@ -51,10 +53,11 @@ talk:
- text: '"Thanks."'
tan_leather:
action:
- cost: 10
+ credits: -10
give_item: leather
take_item: cowhide
- message: The tanner takes your cowhide and works it skillfully. "Here's your leather."
+ messages:
+ - The tanner takes your cowhide and works it skillfully. "Here's your leather."
options:
- condition:
has_item: cowhide
diff --git a/data/mobs/examples/weapons_smith.yaml b/data/mobs/examples/weapons_smith.yaml
index 0ba8237..b12a543 100644
--- a/data/mobs/examples/weapons_smith.yaml
+++ b/data/mobs/examples/weapons_smith.yaml
@@ -19,46 +19,32 @@ slash_defense: 0
speed: 5
stab_defense: 0
strength: 1
+shop:
+ message: The Smithy — buy or sell weapons and armor.
+ buys_anything: false
+ items:
+ - item_id: bronze_sword
+ stock: 10
+ - item_id: iron_sword
+ stock: 5
+ - item_id: bronze_axe
+ stock: 10
+ - item_id: iron_axe
+ stock: 5
+ - item_id: bronze_pickaxe
+ stock: 10
+ - item_id: iron_pickaxe
+ stock: 5
+ - item_id: bronze_med_helm
+ stock: 10
+ - item_id: iron_med_helm
+ stock: 5
talk:
nodes:
- shop:
- action:
- shop:
- items:
- - buy_price: 25
- item_id: bronze_sword
- sell_price: 6
- - buy_price: 60
- item_id: iron_sword
- sell_price: 15
- - buy_price: 20
- item_id: bronze_axe
- sell_price: 5
- - buy_price: 50
- item_id: iron_axe
- sell_price: 12
- - buy_price: 20
- item_id: bronze_pickaxe
- sell_price: 5
- - buy_price: 50
- item_id: iron_pickaxe
- sell_price: 12
- - buy_price: 16
- item_id: bronze_med_helm
- sell_price: 4
- - buy_price: 40
- item_id: iron_med_helm
- sell_price: 10
- message: The Smithy — buy or sell weapons and armor.
- message: '"Here''s the current inventory. All quality guaranteed."'
- options:
- - goto: start
- text: '"That''s all for now."'
start:
- message: '"Welcome to the Smithy! Finest weapons and armor on the asteroid."'
+ messages:
+ - '"Welcome to the Smithy! Type ''list'' to see my wares."'
options:
- - goto: shop
- text: '"Show me what you''ve got."'
- text: '"Nothing right now."'
unique: true
size: small
diff --git a/data/mobs/flight_attendant.yaml b/data/mobs/flight_attendant.yaml
index 5b18762..e595e67 100644
--- a/data/mobs/flight_attendant.yaml
+++ b/data/mobs/flight_attendant.yaml
@@ -11,17 +11,17 @@ talk:
condition:
player_flag: 1001_look_sign
not: true
- message: '"Hi there, how can I help?"'
+ messages: ['"Hi there, how can I help?"']
goto: thanks
options:
- text: '"Oh, nothing... Small crowd on this ship. Just looking for conversation."'
goto: sign_reminder
- text: '"Goodbye."'
thanks:
- message: '"Thank for flying with Denali. Watch your step on the way out please."'
+ messages: ['"Thank for flying with Denali. Watch your step on the way out please."']
options:
- text: '"Thank you."'
sign_reminder:
- sequence:
+ messages:
- '"Yeah, just us two! Not a lot of people heading into the belt these days."'
- '"But we''ll be landing shortly! Please be sure {0B bold}look{/} at the information {0B bold}sign{/} so you''ll be all set for your final destination."'
diff --git a/data/mobs/flight_attendant_copy.yaml b/data/mobs/flight_attendant_copy.yaml
new file mode 100644
index 0000000..fc62564
--- /dev/null
+++ b/data/mobs/flight_attendant_copy.yaml
@@ -0,0 +1,23 @@
+aggressive: false
+description: A smiling flight attendant in a crisp uniform, ready to assist passengers.
+id: flight_attendant_copy
+idle_descriptions:
+ - adjusts her name badge
+ - smiles politely at you
+kind: combat
+name: Credit Checker!
+protected: true
+respawn_ticks: 0
+shop:
+ message: SHOP SHOP
+ items:
+ - item_id: bronze_pickaxe
+ stock: 5
+talk:
+ nodes:
+ start:
+ options:
+ - goto: ""
+ text: None!
+ - text: None
+unique: false
diff --git a/data/mobs/spaceport_attendant.yaml b/data/mobs/spaceport_attendant.yaml
index ca69ccc..58081f6 100644
--- a/data/mobs/spaceport_attendant.yaml
+++ b/data/mobs/spaceport_attendant.yaml
@@ -14,7 +14,7 @@ talk:
player_flag: 1003_attendant_moved
not: true
goto: show_datapad
- message: '"Oh! Change your mind about the flight?"'
+ messages: ['"Oh! Change your mind about the flight?"']
options:
- text: '"No, I''m an arrival... but no one''s at the other desk."'
goto: attendant_moved
@@ -23,7 +23,7 @@ talk:
action:
set_player_flags:
1003_attendant_moved: true
- sequence:
+ messages:
- '"An... arrival!" he says, failing to contain his nerves. "Oh my, one second."'
- 'The lanky man scurries around the counter, across the room, and to the apparently identical counter on the other side.'
- '"Can I see your datapad?"'
@@ -37,30 +37,30 @@ talk:
player_flag: 1003_talked
not: true
goto: already_talked
- message: '"Can I see your datapad?"'
+ messages: ['"Can I see your datapad?"']
options:
- text: '"No"'
goto: denied
- text: '"Present the datapad"'
goto: datapad_done
denied:
- message: '"Well, I can''t let you onto Vesta until we can confirm your identity."'
+ messages: ['"Well, I can''t let you onto Vesta until we can confirm your identity."']
datapad_done:
action:
set_player_flags:
1003_talked: true
- sequence:
+ messages:
- 'The man taps through menus on your PDA and writes down some of your personal details onto a clipboard. He flips through a notebook, apparently refreshing himself on arrival procedure, before he seems satisfied and hands back the datapad.'
- '"Just needed to make sure you''re not a terrorist!" he says, not a hint of sarcasm in his voice.'
- 'He presses a button. "I''ve opened the north door for you. Follow the tunnel all the way through to Numisia."'
already_talked:
- message: '"Hello again."'
+ messages: ['"Hello again."']
options:
- text: '"Where should I go in town?"'
goto: town_info
- text: '"Goodbye."'
town_info:
- sequence:
+ messages:
- '"Well, there''s nearby places to mine, fish, cook, and chop wood if you''re here for work. We haven''t had new labor since we lost contact with Earth, so surely people are looking for help who can get you started."'
- '"The attendant tries to hide how he''s measuring your appearance. He still can''t tell if you''re an escaped prisoner, businessman, or terrorist."'
- '"Or, err, if you have business in Numisia, you''re probably looking for Director Joanna Lucci. She''s on the west side of town in the Governance and Gaming building."'
diff --git a/data/objects/netrunner.yaml b/data/objects/netrunner.yaml
index fa4db20..7d75a51 100644
--- a/data/objects/netrunner.yaml
+++ b/data/objects/netrunner.yaml
@@ -5,7 +5,8 @@ name: Netrunner
talk:
nodes:
start:
- message: The Netrunner looks up. "You want in on the network? I can give you some pointers."
+ messages:
+ - The Netrunner looks up. "You want in on the network? I can give you some pointers."
options:
- goto: what_is
text: '"What is hacking?"'
@@ -25,7 +26,8 @@ talk:
text: '"Tell me about the secure terminal."'
- text: '"Goodbye."'
terminals:
- message: '"There''s a basic terminal right here in this lab -- good for beginners. The station down east has an advanced one if you''ve got the chops. And if you''re really good, there''s a secure terminal deep in the compound. You''ll need the skills to match though."'
+ messages:
+ - '"There''s a basic terminal right here in this lab -- good for beginners. The station down east has an advanced one if you''ve got the chops. And if you''re really good, there''s a secure terminal deep in the compound. You''ll need the skills to match though."'
options:
- goto: tips_basic
text: '"Any tips?"'
@@ -34,7 +36,8 @@ talk:
action:
set_player_flags:
hacking_asked_advanced: true
- message: '"Advanced terminal runs a cipher crack. Four symbols, no repeats. You get feedback after each guess -- locked means right spot, found means right symbol wrong spot. Process of elimination. Ten tries."'
+ messages:
+ - '"Advanced terminal runs a cipher crack. Four symbols, no repeats. You get feedback after each guess -- locked means right spot, found means right symbol wrong spot. Process of elimination. Ten tries."'
options:
- goto: tips_secure
text: '"Tell me about the secure terminal."'
@@ -43,17 +46,20 @@ talk:
action:
set_player_flags:
hacking_asked_basic: true
- message: '"The basic terminal runs an old rogue AI hunt. Twenty nodes in a network. You''ve got probes to flush it out. Listen for the warnings -- corrupted data means it''s close, voids mean a trap, static means ICE. Shoot into adjacent nodes, don''t walk into danger."'
+ messages:
+ - '"The basic terminal runs an old rogue AI hunt. Twenty nodes in a network. You''ve got probes to flush it out. Listen for the warnings -- corrupted data means it''s close, voids mean a trap, static means ICE. Shoot into adjacent nodes, don''t walk into danger."'
options:
- goto: tips_advanced
text: '"What about the advanced terminal?"'
- text: '"Thanks."'
tips_secure:
- message: '"Secure terminal? Signal Bluff. It''s a bluffing game -- you and the system each roll hidden dice and take turns making claims about the total. Ones are wild. Call the bluff or raise the stakes. Read the probabilities, play the odds, and don''t let it psych you out."'
+ messages:
+ - '"Secure terminal? Signal Bluff. It''s a bluffing game -- you and the system each roll hidden dice and take turns making claims about the total. Ones are wild. Call the bluff or raise the stakes. Read the probabilities, play the odds, and don''t let it psych you out."'
options:
- text: '"Thanks."'
what_is:
- message: '"Hacking''s about jacking into terminals and running their gauntlets. Each terminal runs a different challenge -- puzzles, hunts, pattern games. Crack ''em and you get better at it. Simple as that."'
+ messages:
+ - '"Hacking''s about jacking into terminals and running their gauntlets. Each terminal runs a different challenge -- puzzles, hunts, pattern games. Crack ''em and you get better at it. Simple as that."'
options:
- goto: terminals
text: '"Where can I find terminals?"'
diff --git a/data/objects/tool_shed.yaml b/data/objects/tool_shed.yaml
index 3490360..119d3e2 100644
--- a/data/objects/tool_shed.yaml
+++ b/data/objects/tool_shed.yaml
@@ -10,7 +10,8 @@ talk:
give_item: rake
set_player_flags:
tool_shed_rake: false
- message: You retrieve the rake from the tool shed.
+ messages:
+ - You retrieve the rake from the tool shed.
options:
- goto: start
text: Continue
@@ -19,7 +20,8 @@ talk:
give_item: spade
set_player_flags:
tool_shed_spade: false
- message: You retrieve the spade from the tool shed.
+ messages:
+ - You retrieve the spade from the tool shed.
options:
- goto: start
text: Continue
@@ -28,12 +30,14 @@ talk:
give_item: watering_can
set_player_flags:
tool_shed_watering_can: false
- message: You retrieve the watering can from the tool shed.
+ messages:
+ - You retrieve the watering can from the tool shed.
options:
- goto: start
text: Continue
start:
- message: The tool shed is open. What would you like to do?
+ messages:
+ - The tool shed is open. What would you like to do?
options:
- condition:
has_item: rake
@@ -65,7 +69,8 @@ talk:
set_player_flags:
tool_shed_rake: true
take_item: rake
- message: You store the rake in the tool shed.
+ messages:
+ - You store the rake in the tool shed.
options:
- goto: start
text: Continue
@@ -74,7 +79,8 @@ talk:
set_player_flags:
tool_shed_spade: true
take_item: spade
- message: You store the spade in the tool shed.
+ messages:
+ - You store the spade in the tool shed.
options:
- goto: start
text: Continue
@@ -83,7 +89,8 @@ talk:
set_player_flags:
tool_shed_watering_can: true
take_item: watering_can
- message: You store the watering can in the tool shed.
+ messages:
+ - You store the watering can in the tool shed.
options:
- goto: start
text: Continue
diff --git a/data/rooms/intro/1001.yaml b/data/rooms/intro/1001.yaml
index defd1b1..66e8b4a 100644
--- a/data/rooms/intro/1001.yaml
+++ b/data/rooms/intro/1001.yaml
@@ -32,10 +32,7 @@ objects:
hidden: true
name: framed sign
on_look:
- aps_node: false
- assign_task: false
- cost: 0
- extend_task: false
+ credits: 0
give_item: ""
heal: 0
reputation_cost: 0
@@ -43,8 +40,6 @@ objects:
set_flags: {}
set_player_flags:
1001_look_sign: true
- shop: null
- skip_task: false
take_item: ""
teleport: 0
- description:
diff --git a/data/rooms/intro/2001.yaml b/data/rooms/intro/2001.yaml
index 9977a79..842c94c 100644
--- a/data/rooms/intro/2001.yaml
+++ b/data/rooms/intro/2001.yaml
@@ -25,6 +25,7 @@ item_spawns:
respawn_ticks: 1
mobs:
- id: man
+ - id: flight_attendant_copy
name: Test Room
objects:
- aliases: []
diff --git a/internal/admin/api_mobs.go b/internal/admin/api_mobs.go
index da088d3..a575076 100644
--- a/internal/admin/api_mobs.go
+++ b/internal/admin/api_mobs.go
@@ -95,6 +95,7 @@ func (s *AdminServer) updateMob(w http.ResponseWriter, r *http.Request, id strin
http.Error(w, `{"error":"failed to write mob"}`, http.StatusInternalServerError)
return
}
+ m["_raw"] = string(newContent)
s.mobStore.ReloadDefs(s.dataDir)
s.undoStack.Push(ChangeDesc{
Description: fmt.Sprintf("Update mob %s", id),
diff --git a/internal/admin/api_objects.go b/internal/admin/api_objects.go
index 01a0803..7bcda1c 100644
--- a/internal/admin/api_objects.go
+++ b/internal/admin/api_objects.go
@@ -95,6 +95,7 @@ func (s *AdminServer) updateObject(w http.ResponseWriter, r *http.Request, id st
http.Error(w, `{"error":"failed to write object"}`, http.StatusInternalServerError)
return
}
+ m["_raw"] = string(newContent)
s.undoStack.Push(ChangeDesc{
Description: fmt.Sprintf("Update object %s", id),
diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css
index 43354f9..2bebf85 100644
--- a/internal/admin/static/admin.css
+++ b/internal/admin/static/admin.css
@@ -1,5 +1,7 @@
:root{--bg:#1a1a2e;--panel:#16213e;--text:#e0e0e0;--accent:#0f3460;--border:#2a2a4a;--hover:#1a3a6e;--danger:#c0392b;--success:#27ae60;--warn:#f39c12;--input-bg:#0d1b36;--input-border:#3a3a6a}
*{box-sizing:border-box;margin:0;padding:0}
+input[type=number]{-moz-appearance:textfield}
+input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100vh}
.nav{background:var(--panel);border-bottom:1px solid var(--border);padding:8px 16px;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.nav a{color:var(--text);text-decoration:none;padding:4px 10px;border-radius:3px;font-size:13px}
@@ -19,8 +21,6 @@ body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100
.form-group{margin-bottom:10px}
.form-group label{display:block;font-size:11px;color:#aaa;margin-bottom:3px;text-transform:uppercase;letter-spacing:.5px}
.form-group input,.form-group textarea,.form-group select{width:100%;padding:6px 8px;font-size:13px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace}
-.form-group input[type=number]{-moz-appearance:textfield}
-.form-group input[type=number]::-webkit-inner-spin-button,.form-group input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.form-group textarea{resize:vertical;min-height:60px}
.form-group input[type=checkbox]{width:auto;margin-right:6px}
.btn{padding:6px 14px;border:none;border-radius:3px;cursor:pointer;font-size:12px;font-weight:bold;font-family:monospace}
@@ -76,14 +76,14 @@ body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100
.talk-seq-line{display:flex;gap:6px;margin:2px 0 2px 10px;font-size:11px;color:#bbb;align-items:center}
.talk-seq-num{color:#666;min-width:14px;text-align:right;flex-shrink:0}
.talk-action-summary{color:#e8c47c;font-size:10px}
-.talk-options{margin-top:6px;padding-top:4px;border-top:1px solid rgba(255,255,255,.05)}
+.talk-options{margin-top:6px;padding-top:4px;padding-bottom:6px;border-top:1px solid rgba(255,255,255,.05)}
.talk-options-label{font-size:10px;color:#888;text-transform:uppercase;letter-spacing:.3px}
.talk-option{display:flex;align-items:center;gap:5px;margin:3px 0;padding:3px 6px;font-size:11px;border-left:1px solid #444;border-radius:0 3px 3px 0}
.talk-option-empty{color:#666;font-style:italic;border-left-color:transparent}
.talk-opt-arrow{color:#555;flex-shrink:0}
.talk-opt-text{color:var(--text);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.talk-opt-goto{color:#8cf;font-weight:bold;white-space:nowrap;min-width:30px}
-.talk-opt-tag{display:inline-block;padding:0 3px;font-size:9px;border-radius:2px;line-height:1.4;flex-shrink:0}
+.talk-opt-tag{display:inline-block;padding:1px 6px;font-size:9px;border-radius:2px;line-height:1.4;flex-shrink:0;cursor:pointer;border:1px solid transparent}
.talk-opt-tag-cond{background:rgba(100,160,255,.2);color:#6af}
.talk-opt-tag-act{background:rgba(232,196,124,.2);color:#e8c47c}
.talk-opt-del{padding:0 4px;font-size:10px;line-height:1.4;border:none;border-radius:2px;background:var(--danger);color:#fff;cursor:pointer;flex-shrink:0}
@@ -119,18 +119,14 @@ body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100
.talk-kv-del{font-size:9px;padding:0 4px;line-height:1.4;border:none;border-radius:2px;background:var(--danger);color:#fff;cursor:pointer;font-family:monospace;flex-shrink:0}
.talk-action-block{margin-top:3px;padding:6px;background:rgba(255,255,255,.02);border:1px solid var(--border);border-radius:4px}
.talk-action-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:3px 8px}
-.talk-shop-block{margin-top:4px;padding:6px;background:rgba(232,196,124,.04);border:1px solid rgba(232,196,124,.15);border-radius:4px}
-.talk-shop-header{font-size:10px;color:#e8c47c;text-transform:uppercase;letter-spacing:.3px;margin-bottom:4px}
-.talk-shop-item{display:flex;gap:3px;align-items:center;margin-bottom:2px;padding:2px 4px;background:rgba(255,255,255,.02);border-radius:2px}
-.talk-shop-item .talk-input{flex:1;min-width:0;padding:2px 4px;font-size:10px}
-.talk-shop-item .talk-input-num{width:55px}
.talk-add-cond-btn{font-size:9px;padding:1px 6px;line-height:1.4;border:1px solid rgba(100,160,255,.25);border-radius:2px;background:transparent;color:#6af;cursor:pointer;font-family:monospace;margin-bottom:6px}
.talk-add-act-btn{font-size:9px;padding:1px 6px;line-height:1.4;border:1px solid rgba(232,196,124,.25);border-radius:2px;background:transparent;color:#e8c47c;cursor:pointer;font-family:monospace;margin-bottom:6px}
.talk-cond-card{border:1px solid rgba(100,160,255,.25);border-radius:5px;margin-bottom:6px;overflow:hidden}
.talk-cond-card-header{display:flex;align-items:center;justify-content:space-between;padding:6px 8px;background:rgba(100,160,255,.08);border-bottom:1px solid rgba(100,160,255,.15);font-size:10px;color:#aaa;text-transform:uppercase;letter-spacing:.3px}
.talk-cond-card-body{padding:6px 8px}
-.talk-cond-card-sep{border-top:1px solid rgba(100,160,255,.12);margin:1px 0}
+.talk-cond-card-body .talk-action-block{margin-top:0;border:none;padding:0;border-radius:0;background:transparent}
+.talk-cond-card-sep{border-top:1px solid rgba(100,160,255,.12);margin:1px 0 4px 0}
.talk-cond-mode-row{display:flex;align-items:center;gap:6px;margin-bottom:4px}
.talk-cond-mode-row .talk-prop-label{font-size:9px;color:#888;text-transform:uppercase;letter-spacing:.3px}
@@ -141,7 +137,7 @@ body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100
.talk-clause-label{color:#888;font-size:9px;white-space:nowrap}
.talk-clause-del{padding:0 4px;font-size:9px;line-height:1.4;border:none;border-radius:2px;background:var(--danger);color:#fff;cursor:pointer;font-family:monospace;flex-shrink:0}
-.talk-clause-add-bar{display:flex;gap:4px;margin-top:4px;flex-wrap:wrap}
+.talk-clause-add-bar{display:flex;gap:4px;margin-top:4px;flex-wrap:wrap;padding-bottom:6px}
.talk-clause-add-btn{font-size:9px;padding:1px 6px;line-height:1.4;border:1px solid rgba(100,160,255,.2);border-radius:2px;background:transparent;color:#6af;cursor:pointer;font-family:monospace}
.talk-clause-add-btn:hover{background:rgba(100,160,255,.1)}
@@ -149,24 +145,29 @@ body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100
.talk-goto-fallback .talk-prop-label{font-size:9px;color:#888;text-transform:uppercase;letter-spacing:.3px;white-space:nowrap;flex-shrink:0}
.talk-goto-fallback .talk-input{flex:1;min-width:0;padding:2px 4px;font-size:10px}
-.talk-input-num::-webkit-inner-spin-button,
-.talk-input-num::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
-.talk-input-num{-moz-appearance:textfield}
.talk-add-node-row{display:flex;gap:4px;align-items:center;margin-top:8px}
.talk-add-node-row .talk-input{flex:1;max-width:200px}
.talk-add-node-row .talk-add-btn{flex-shrink:0}
.talk-opt-edit-row{display:flex;gap:4px;align-items:center;margin:3px 0;padding:3px 6px;font-size:11px}
.talk-opt-edit-row .talk-input{flex:1;min-width:0;padding:2px 4px;font-size:10px}
+.talk-opt-edit-row .talk-opt-text{flex:3}
+.talk-opt-edit-row .talk-opt-goto{flex:1;max-width:130px}
.talk-opt-edit-row .talk-opt-arrow{flex-shrink:0}
.talk-opt-edit-actions{display:flex;gap:2px;align-items:center;flex-shrink:0}
.talk-add-opt-row{display:flex;gap:4px;align-items:center;margin-top:4px;padding:0 6px}
.talk-add-opt-row .talk-input{flex:1;min-width:0;padding:2px 4px;font-size:10px}
+.talk-add-opt-row .talk-opt-text{flex:8}
+.talk-add-opt-row .talk-opt-goto{flex:1;max-width:130px}
.talk-add-opt-row .talk-add-btn{flex-shrink:0;margin-top:0}
+.talk-opt-gap{width:52px;flex-shrink:0}
.talk-seq-add-row{display:flex;gap:6px;align-items:center;margin:2px 0 2px 10px}
.talk-seq-add-row .talk-input{flex:1;padding:2px 4px;font-size:10px}
.talk-seq-add-row .talk-add-btn{flex-shrink:0;margin-top:0}
.talk-seq-del{font-size:9px;padding:0 4px;line-height:1.4;border:none;border-radius:2px;background:var(--danger);color:#fff;cursor:pointer;font-family:monospace;flex-shrink:0}
+#talktree .search-results{position:absolute;top:100%;left:0;right:0;max-height:160px;overflow-y:auto;background:var(--panel);border:1px solid var(--border);border-radius:3px;z-index:50;box-shadow:0 4px 12px rgba(0,0,0,.4);margin-top:2px}
+#talktree .search-results .sr-item{padding:4px 8px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03)}
+#talktree .search-results .sr-item:hover,#talktree .search-results .sr-item.active{background:var(--accent)}
.talk-orphans{margin-top:12px;padding-top:8px;border-top:1px dashed var(--border)}
.talk-orphans-label{font-size:10px;color:#f39c12;text-transform:uppercase;letter-spacing:.3px;margin-bottom:2px}
.talk-orphans-hint{font-size:9px;color:#888;margin-bottom:6px;font-style:italic}
@@ -247,7 +248,6 @@ g.ud-hover:hover text{font-weight:bold}
.exit-add{display:flex;gap:4px;align-items:center;margin-top:6px}
.exit-add select,.exit-add input{width:auto;flex:1;padding:3px 5px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:2px;font-family:monospace}
.exit-add input[type=number]{-moz-appearance:textfield}
-.exit-add input[type=number]::-webkit-inner-spin-button,.exit-add input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
.exit-add .exit-add-btn{flex-shrink:0;padding:3px 8px;font-size:11px;background:var(--accent);color:var(--text);border:1px solid #1a5a8e;border-radius:2px;cursor:pointer;font-family:monospace}
.exit-add .exit-add-btn:hover{background:var(--hover)}.panel-tab-content .entry-row{display:flex;gap:4px;align-items:flex-start;margin-bottom:6px;padding:4px;background:rgba(255,255,255,.02);border-radius:3px;border:1px solid transparent}
.panel-tab-content .entry-row:hover{border-color:var(--border)}
diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js
index a65e578..59eb16c 100644
--- a/internal/admin/static/mobeditor.js
+++ b/internal/admin/static/mobeditor.js
@@ -90,6 +90,24 @@ var MOB_SECTIONS = [
fields: []
},
{
+ id: 'shop', label: 'Shop', path: 'shop',
+ detect: function(d) { return !!d.shop; },
+ fields: [
+ ['message', 'Greeting', 'text', 'What would you like to buy or sell?'],
+ ['buy_percentage', 'Buy %', 'number', '40', 'narrow'],
+ ['change_percentage', 'Change %', 'number', '3', 'narrow'],
+ ['restock_ticks', 'Restock Ticks', 'number', '', 'narrow'],
+ ['buys_anything', 'Buys Anything', 'checkbox'],
+ ],
+ subtables: [
+ {label:'Items', key:'items', addLabel:'+ Add Item', fields:[
+ ['item_id', 'Item ID', 'search', '', '', null, 'items'],
+ ['stock', 'Stock', 'number', '', 'narrow'],
+ ['restock_ticks', 'Restock Ticks', 'number', '', 'narrow'],
+ ]}
+ ]
+ },
+ {
id: 'drops', label: 'Drops', path: 'drops',
detect: function(d) { return d.drops && (d.drops.remains || (d.drops.loot && d.drops.loot.length > 0)); },
fields: [
@@ -197,7 +215,7 @@ function renderCurrent() {
function renderMobCard(sec, data) {
var collapsed = expandedCards[sec.id] === false;
- var h = '<div class="obj-card" data-card="' + sec.id + '">';
+ var h = '<div class="obj-card' + (sec.id === 'talk' ? ' obj-card-full' : '') + '" data-card="' + sec.id + '">';
h += '<div class="obj-card-header" onclick="toggleMobCard(\'' + sec.id + '\')">';
h += '<span class="obj-card-arrow">' + (collapsed ? '&#9654;' : '&#9660;') + '</span>';
h += '<span class="obj-card-label">' + sec.label + '</span>';
@@ -336,7 +354,7 @@ function renderMobSubtable(sec, st, data) {
h += '</div>';
});
if (!st.single || arr.length === 0) {
- h += '<button class="btn btn-sm obj-sub-add" onclick="addMobSubRow(\'' + sec.id + '\',\'' + st.key + '\')">+ Add Loot</button>';
+ h += '<button class="btn btn-sm obj-sub-add" onclick="addMobSubRow(\'' + sec.id + '\',\'' + st.key + '\')">' + esc(st.addLabel || '+ Add') + '</button>';
}
h += '</div>';
return h;
@@ -374,7 +392,9 @@ function mobAddSection() {
if (sec.id === 'drops') {
mobData[sec.path] = {remains:'', loot:[]};
} else if (sec.id === 'talk') {
- mobData[sec.path] = {nodes:{start:{message:'',options:[]}}};
+ mobData[sec.path] = {nodes:{start:{messages:[],options:[]}}};
+ } else if (sec.id === 'shop') {
+ mobData[sec.path] = {buy_percentage:40, change_percentage:3, buys_anything:true, items:[]};
} else {
mobData[sec.path] = {};
}
@@ -467,7 +487,11 @@ function saveMob() {
API.put('/api/mobs/' + encodeURIComponent(mobID), out).then(function(resp) {
notify('Mob ' + mobID + ' saved', 'success');
updateUndoBar();
- if (resp && resp._raw) mobData._raw = resp._raw;
+ if (resp && resp._raw) {
+ mobData._raw = resp._raw;
+ var pre = document.querySelector('#tabYaml pre');
+ if (pre) pre.textContent = mobData._raw;
+ }
}).catch(function(e) { notify('Save failed: ' + e.message, 'error'); });
}
diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js
index c2374a7..4d02295 100644
--- a/internal/admin/static/objecteditor.js
+++ b/internal/admin/static/objecteditor.js
@@ -775,9 +775,14 @@ function saveObject() {
out.id = objectID;
- API.put('/api/objects/' + encodeURIComponent(objectID), out).then(function() {
+ API.put('/api/objects/' + encodeURIComponent(objectID), out).then(function(resp) {
notify('Object ' + objectID + ' saved', 'success');
updateUndoBar();
+ if (resp && resp._raw) {
+ objectData._raw = resp._raw;
+ var pre = document.querySelector('#tabYaml pre');
+ if (pre) pre.textContent = objectData._raw;
+ }
}).catch(function(e) { notify('Save failed: ' + e.message, 'error'); });
}
diff --git a/internal/admin/static/talktree.js b/internal/admin/static/talktree.js
index e7079f5..4758918 100644
--- a/internal/admin/static/talktree.js
+++ b/internal/admin/static/talktree.js
@@ -86,11 +86,11 @@
}
// Single clause from flat fields
for (var i = 0; i < CLAUSE_TYPES.length; i++) {
- if (cond[CLAUSE_TYPES[i].key] !== undefined && cond[CLAUSE_TYPES[i].key] !== '') {
+ if (cond[CLAUSE_TYPES[i].key] !== undefined) {
var c = {};
c[CLAUSE_TYPES[i].key] = cond[CLAUSE_TYPES[i].key];
if (cond.not) c.not = cond.not;
- if (cond.value !== undefined && cond.value !== '') c.value = cond.value;
+ if (cond.value !== undefined) c.value = cond.value;
if (cond.has_item && CLAUSE_TYPES[i].key !== 'has_item') c.has_item = cond.has_item;
if (cond.player_flag && CLAUSE_TYPES[i].key !== 'player_flag') c.player_flag = cond.player_flag;
if (cond.flag && CLAUSE_TYPES[i].key !== 'flag') c.flag = cond.flag;
@@ -100,11 +100,11 @@
// Check for has_item or min_credits as the only key
for (var j = 0; j < CLAUSE_TYPES.length; j++) {
var ck = CLAUSE_TYPES[j].key;
- if (cond[ck] !== undefined && cond[ck] !== '') {
+ if (cond[ck] !== undefined) {
var cc = {};
cc[ck] = cond[ck];
if (cond.not) cc.not = cond.not;
- if (cond.value !== undefined && cond.value !== '') cc.value = cond.value;
+ if (cond.value !== undefined) cc.value = cond.value;
return { mode: 'single', clauses: [cc], groupKey: null };
}
}
@@ -115,7 +115,7 @@
function clauseObjType(clause) {
for (var i = 0; i < CLAUSE_TYPES.length; i++) {
var k = CLAUSE_TYPES[i].key;
- if (clause[k] !== undefined && clause[k] !== '') return k;
+ if (clause[k] !== undefined) return k;
}
return null;
}
@@ -126,13 +126,13 @@
if (clauses.length === 1) {
var flat = {};
var c = clauses[0];
- for (var k in c) { if (c[k] !== undefined && c[k] !== '') flat[k] = c[k]; }
+ for (var k in c) { if (c[k] !== undefined) flat[k] = c[k]; }
return flat;
}
var cond = {};
cond[groupKey || 'all_of'] = clauses.map(function(c) {
var sub = {};
- for (var k in c) { if (c[k] !== undefined && c[k] !== '') sub[k] = c[k]; }
+ for (var k in c) { if (c[k] !== undefined) sub[k] = c[k]; }
return sub;
});
return cond;
@@ -141,21 +141,14 @@
// ---- Action field definitions ----
var ACTION_FIELDS = [
- { key: 'give_item', label: 'Give Item', type: 'text' },
- { key: 'take_item', label: 'Take Item', type: 'text' },
- { key: 'teleport', label: 'Teleport Room', type: 'number' },
+ { key: 'give_item', label: 'Give Item', type: 'item_search' },
+ { key: 'take_item', label: 'Take Item', type: 'item_search' },
+ { key: 'credits', label: 'Credits (+/-)', type: 'number' },
+ { key: 'teleport', label: 'TP Room', type: 'number' },
{ key: 'heal', label: 'Heal HP', type: 'number' },
- { key: 'cost', label: 'Cost (Credits)', type: 'number' },
- { key: 'reputation_cost', label: 'Rep Cost', type: 'number' },
];
- var ACTION_BOOLS = [
- { key: 'assign_task', label: 'Assign Task' },
- { key: 'skip_task', label: 'Skip Task' },
- { key: 'extend_task', label: 'Extend Task' },
- { key: 'sawmill', label: 'Sawmill' },
- { key: 'aps_node', label: 'APS Node' },
- ];
+ var ACTION_BOOLS = [];
// ---- Event delegation ----
@@ -173,6 +166,7 @@
else if (type === 'number') { var n = parseFloat(el.value); val = isNaN(n) ? 0 : n; }
else { val = el.value; }
talkSyncPath(path, val);
+ if (path.endsWith('.goto')) renderTalkTree(window._talkData);
} else if (tag === 'SELECT') {
talkSyncPath(path, el.value);
}
@@ -207,11 +201,11 @@
case 'add-node':
addNode();
break;
- case 'add-sequence':
- addSequenceLine(nodeKey);
+ case 'add-message':
+ addMessageLine(nodeKey);
break;
- case 'delete-sequence':
- deleteSequenceLine(nodeKey, parseInt(optIdx, 10));
+ case 'delete-message':
+ deleteMessageLine(nodeKey, parseInt(optIdx, 10));
break;
case 'add-condition':
toggleCondition(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
@@ -240,12 +234,6 @@
case 'remove-kv':
removeKVPair(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, el.getAttribute('data-kvk'), subKey);
break;
- case 'add-shop-item':
- addShopItem(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null);
- break;
- case 'remove-shop-item':
- removeShopItem(nodeKey, optIdx !== null ? parseInt(optIdx, 10) : null, parseInt(subIdx, 10));
- break;
}
}
@@ -255,6 +243,8 @@
var container = document.getElementById('talktree');
if (!container) return;
window._talkData = data;
+ window._talkOpenNodes = window._talkOpenNodes || new Set();
+ window._talkOpenNodes.add('start');
if (!data || !data.talk || !data.talk.nodes) {
container.innerHTML = '<p style="color:#888">No conversation tree defined.</p>';
return;
@@ -285,6 +275,9 @@
html += renderAddNodeRow();
html += '</div>';
container.innerHTML = html;
+ if (typeof ced_setupSearchFields === 'function') {
+ setTimeout(function() { ced_setupSearchFields(); }, 10);
+ }
};
// ---- Add/Delete Node ----
@@ -302,7 +295,7 @@
var key = input.value.trim();
if (!key) return;
if (talkNodes()[key]) { notify('Node "' + key + '" already exists.', 'error'); return; }
- talkNodes()[key] = { message: '', options: [] };
+ talkNodes()[key] = { messages: [], options: [] };
input.value = '';
renderTalkTree(window._talkData);
talkSync();
@@ -328,7 +321,7 @@
function renderOrphanNode(key, node, nodes) {
var h = '<div class="talk-node">';
- h += '<div class="talk-node-header" onclick="if(!event.target.closest(\'[data-action]\'))this.parentElement.parentElement.classList.toggle(\'open\')">';
+ h += '<div class="talk-node-header" onclick="if(!event.target.closest(\'[data-action]\'))talkToggleNode(\'' + escAttr(key) + '\',this.parentElement.parentElement)">';
h += '<span class="talk-node-arrow"></span>';
h += '<span class="talk-node-key">' + esc(key) + '</span>';
h += '<span class="talk-node-preview">' + esc((getNodePreview(node) || '').substring(0, 60)) + '</span>';
@@ -349,16 +342,17 @@
var node = nodes[rootKey];
if (!node) return '';
if (seen[rootKey]) {
- return '<div class="talk-node ref"><span class="talk-node-key">' + esc(rootKey) + '</span> <span class="talk-node-preview">(already shown)</span></div>';
+ return '<div class="talk-node ref"><span class="talk-node-key">' + esc(rootKey) + '</span> <a href="javascript:void(0)" onclick="document.getElementById(\'node-' + escAttr(rootKey) + '\').scrollIntoView({behavior:\'smooth\',block:\'center\'});return false" style="color:inherit;text-decoration:underline">(see above)</a></div>';
}
seen[rootKey] = true;
var previewText = getNodePreview(node);
+ var isOpen = window._talkOpenNodes.has(rootKey);
- var html = '<div class="talk-node' + (rootKey === 'start' ? ' open' : '') + '">';
+ var html = '<div class="talk-node' + (isOpen ? ' open' : '') + '" id="node-' + escAttr(rootKey) + '">';
// Header — fix: check for data-action before toggling
- html += '<div class="talk-node-header" onclick="if(!event.target.closest(\'[data-action]\'))this.parentElement.classList.toggle(\'open\')">';
+ html += '<div class="talk-node-header" onclick="if(!event.target.closest(\'[data-action]\'))talkToggleNode(\'' + escAttr(rootKey) + '\',this.parentElement)">';
html += '<span class="talk-node-arrow"></span>';
html += '<span class="talk-node-key">' + esc(rootKey) + '</span>';
html += '<span class="talk-node-preview">' + esc((previewText || '').substring(0, 60)) + '</span>';
@@ -390,16 +384,19 @@
}
function getNodePreview(node) {
- if (node.message) return typeof node.message === 'string' ? node.message : (Array.isArray(node.message) ? node.message[0] : '');
- if (node.sequence && node.sequence.length) return node.sequence[0];
+ if (node.messages && node.messages.length) return node.messages[0];
return '';
}
// ---- Node body: condition-gated content ----
+ // Track open node state across re-renders
+ window._talkOpenNodes = window._talkOpenNodes || new Set();
+
function renderNodeBody(nodeKey, node, nodes) {
var ec = extractClauses(node.condition);
- var hasCond = ec.mode !== 'none';
+ var hasCond = ec.mode !== 'none' || (node.condition && typeof node.condition === 'object' && !Array.isArray(node.condition));
+ var hasOpts = node.options && node.options.length > 0;
var html = '';
@@ -412,23 +409,38 @@
html += '<div class="talk-cond-card-body">';
html += renderConditionClauses(ec, nodeKey, null);
html += '<div class="talk-cond-card-sep"></div>';
- html += renderMessageSection(nodeKey, node);
- html += renderSequenceSection(nodeKey, node);
- html += renderActionSection(nodeKey, null, node.action);
+ html += renderMessagesSection(nodeKey, node);
html += renderOptionsSection(nodeKey, node);
+ html += '<div class="talk-cond-card-sep"></div>';
+
+ var nodeAction = node.action;
+ var actionPrefix = 'nodes.' + escAttr(nodeKey) + '.action';
+ if (nodeAction && typeof nodeAction === 'object' && !Array.isArray(nodeAction)) {
+ html += '<div class="talk-cond-card" style="margin-top:4px">';
+ html += '<div class="talk-cond-card-header">' +
+ '<span class="talk-prop-label">Action</span>' +
+ '<button class="talk-section-remove" data-action="remove-action" data-nk="' + escAttr(nodeKey) + '">Remove</button></div>';
+ html += '<div class="talk-cond-card-body">';
+ html += renderActionForm(nodeAction, actionPrefix, nodeKey, null);
+ html += '</div>';
+ html += '</div>';
+ } else {
+ html += '<button class="talk-add-act-btn" style="margin-top:4px" data-action="add-action" data-nk="' + escAttr(nodeKey) + '">+ Add Action</button>';
+ }
+
+ html += '<div class="talk-cond-card-sep"></div>';
+ html += renderGotoFallback(nodeKey, node, 'If condition fails, go to');
html += '</div>'; // .talk-cond-card-body
html += '</div>'; // .talk-cond-card
} else {
html += '<button class="talk-add-cond-btn" data-action="add-condition" data-nk="' + escAttr(nodeKey) + '">+ Add Condition</button>';
- html += renderMessageSection(nodeKey, node);
- html += renderSequenceSection(nodeKey, node);
- html += renderActionSection(nodeKey, null, node.action);
+ html += renderMessagesSection(nodeKey, node);
html += renderOptionsSection(nodeKey, node);
- }
-
- // Goto fallback — only when no options
- if (!node.options || node.options.length === 0) {
- html += renderGotoFallback(nodeKey, node);
+ html += '<div class="talk-cond-card-sep"></div>';
+ html += renderActionSection(nodeKey, null, node.action);
+ if (!hasOpts) {
+ html += renderGotoFallback(nodeKey, node, 'Auto-advance to');
+ }
}
return html;
@@ -436,39 +448,31 @@
// ---- Goto fallback ----
- function renderGotoFallback(nodeKey, node) {
+ function renderGotoFallback(nodeKey, node, label) {
+ label = label || 'Auto-advance to';
return '<div class="talk-goto-fallback">' +
- '<span class="talk-prop-label">Auto-advance to</span>' +
- '<input class="talk-input talk-input-sm" data-tp="nodes.' + escAttr(nodeKey) + '.goto" list="talkNodeKeys" value="' + escAttr(node.goto || '') + '" placeholder="Fallback node when no options are available...">' +
+ '<span class="talk-prop-label">' + esc(label) + '</span>' +
+ '<input class="talk-input talk-input-sm" data-tp="nodes.' + escAttr(nodeKey) + '.goto" list="talkNodeKeys" value="' + escAttr(node.goto || '') + '" placeholder="Fallback node...">' +
'</div>';
}
- // ---- Message / Sequence ----
-
- function renderMessageSection(nodeKey, node) {
- var msg = node.message || '';
- if (Array.isArray(msg)) msg = msg.join('\n');
- return '<div class="talk-section">' +
- '<div class="talk-section-header"><span class="talk-prop-label">Message</span></div>' +
- '<textarea class="talk-textarea" data-tp="nodes.' + escAttr(nodeKey) + '.message" rows="2" placeholder="NPC dialogue text...">' + esc(msg) + '</textarea>' +
- '</div>';
- }
+ // ---- Messages section ----
- function renderSequenceSection(nodeKey, node) {
- var seq = node.sequence || [];
+ function renderMessagesSection(nodeKey, node) {
+ var msgs = node.messages || [];
var html = '<div class="talk-section">';
- html += '<div class="talk-section-header"><span class="talk-prop-label">Sequence</span></div>';
- seq.forEach(function(line, i) {
+ html += '<div class="talk-section-header"><span class="talk-prop-label">Messages</span></div>';
+ msgs.forEach(function(line, i) {
html += '<div class="talk-seq-line">' +
- '<span class="talk-seq-num">' + (i + 1) + '</span>' +
- '<input class="talk-input talk-input-sm" data-tp="nodes.' + escAttr(nodeKey) + '.sequence.' + i + '" value="' + escAttr(line) + '" placeholder="Sequence line...">' +
- '<button class="talk-seq-del" data-action="delete-sequence" data-nk="' + escAttr(nodeKey) + '" data-oi="' + i + '">x</button>' +
+ '<span class="talk-opt-arrow">&#10132;</span>' +
+ '<input class="talk-input" data-tp="nodes.' + escAttr(nodeKey) + '.messages.' + i + '" value="' + escAttr(line) + '" placeholder="Message line...">' +
+ '<button class="talk-seq-del" data-action="delete-message" data-nk="' + escAttr(nodeKey) + '" data-oi="' + i + '">x</button>' +
'</div>';
});
html += '<div class="talk-seq-add-row">' +
- '<span class="talk-seq-num">' + (seq.length + 1) + '</span>' +
- '<input class="talk-input talk-input-sm" id="talkSeqAdd_' + escAttr(nodeKey) + '" placeholder="Add sequence line...">' +
- '<button class="talk-add-btn" data-action="add-sequence" data-nk="' + escAttr(nodeKey) + '">Add</button>' +
+ '<span class="talk-opt-arrow">&#10132;</span>' +
+ '<input class="talk-input" id="talkMsgAdd_' + escAttr(nodeKey) + '" placeholder="Add message line...">' +
+ '<button class="talk-add-btn" data-action="add-message" data-nk="' + escAttr(nodeKey) + '">Add</button>' +
'</div>';
html += '</div>';
return html;
@@ -518,15 +522,16 @@
html += '<span class="talk-clause-type">' + esc(clauseTypeLabel(typeKey)) + '</span>';
if (typeKey === 'flag' || typeKey === 'player_flag') {
+ var notLabel = (clause.value !== undefined && clause.value !== '') ? 'Not' : 'Not Exists';
html += '<span class="talk-clause-label">key:</span>';
html += '<input class="talk-input talk-input-sm" data-tp="' + escAttr(pathPrefix + '.' + typeKey) + '" value="' + escAttr(clause[typeKey] || '') + '" placeholder="Flag name...">';
- html += '<label class="talk-check-label"><input type="checkbox" data-tp="' + escAttr(pathPrefix + '.not') + '"' + (clause.not ? ' checked' : '') + '> Not</label>';
+ html += '<label class="talk-check-label"><input type="checkbox" data-tp="' + escAttr(pathPrefix + '.not') + '"' + (clause.not ? ' checked' : '') + '> ' + notLabel + '</label>';
html += '<span class="talk-clause-label">val:</span>';
html += '<input class="talk-input talk-input-sm" data-tp="' + escAttr(pathPrefix + '.value') + '" value="' + escAttr(clause.value || '') + '" placeholder="Value match..." style="max-width:80px">';
} else if (typeKey === 'has_item') {
html += '<span class="talk-clause-label">item:</span>';
html += '<input class="talk-input talk-input-sm" data-tp="' + escAttr(pathPrefix + '.has_item') + '" value="' + escAttr(clause.has_item || '') + '" placeholder="Item ID...">';
- html += '<label class="talk-check-label"><input type="checkbox" data-tp="' + escAttr(pathPrefix + '.not') + '"' + (clause.not ? ' checked' : '') + '> Not</label>';
+ html += '<label class="talk-check-label"><input type="checkbox" data-tp="' + escAttr(pathPrefix + '.not') + '"' + (clause.not ? ' checked' : '') + '> Not Exists</label>';
} else if (typeKey === 'min_credits') {
html += '<span class="talk-clause-label">cr:</span>';
html += '<input class="talk-input-num" data-tp="' + escAttr(pathPrefix + '.min_credits') + '" type="number" value="' + (clause.min_credits || 0) + '" placeholder="0">';
@@ -620,6 +625,10 @@
delete target.condition;
} else {
target.condition = {};
+ if (optIdx !== null) {
+ window._talkAutoOpenDetails = new Set();
+ window._talkAutoOpenDetails.add('cond_' + optIdx + '_' + escAttr(nodeKey));
+ }
}
renderTalkTree(window._talkData);
talkSync();
@@ -661,10 +670,21 @@
ACTION_FIELDS.forEach(function(f) {
var fpath = prefix + '.' + f.key;
var val = (action && action[f.key] !== undefined) ? action[f.key] : '';
- html += '<div class="talk-field-row">' +
- '<span class="talk-prop-label">' + esc(f.label) + '</span>' +
- '<input class="talk-input talk-input-sm" data-tp="' + escAttr(fpath) + '" type="' + f.type + '" value="' + escAttr(String(val)) + '">' +
- '</div>';
+ if (f.type === 'item_search') {
+ html += '<div class="talk-field-row">' +
+ '<span class="talk-prop-label">' + esc(f.label) + '</span>' +
+ '<div style="position:relative">' +
+ '<input class="talk-input talk-input-sm search-input" data-tp="' + escAttr(fpath) + '" data-search-type="items" value="' + escAttr(String(val)) + '" placeholder="Search items...">' +
+ '<div class="search-results" style="display:none"></div>' +
+ '</div>' +
+ '</div>';
+ } else {
+ var narrowStyle = (f.key === 'teleport' || f.key === 'heal') ? ' style="max-width:70px"' : '';
+ html += '<div class="talk-field-row">' +
+ '<span class="talk-prop-label">' + esc(f.label) + '</span>' +
+ '<input class="talk-input talk-input-sm"' + narrowStyle + ' data-tp="' + escAttr(fpath) + '" type="' + f.type + '" value="' + escAttr(String(val)) + '">' +
+ '</div>';
+ }
});
html += '</div>';
@@ -678,7 +698,6 @@
html += renderKVEditor(action, prefix, 'set_flags', 'Set Flags', nodeKey, optIdx);
html += renderKVEditor(action, prefix, 'set_player_flags', 'Set Player Flags', nodeKey, optIdx);
- html += renderShopEditor(action, prefix, nodeKey, optIdx);
html += '</div>'; // .talk-action-block
return html;
@@ -697,14 +716,14 @@
html += '</div>';
keys.forEach(function(k) {
html += '<div class="talk-kv-row">' +
+ '<input class="talk-input" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '" data-oldkey="' + escAttr(k) + '" onchange="talkRenameKV(this)" value="' + escAttr(k) + '" placeholder="key" style="max-width:150px">' +
'<input class="talk-input" data-tp="' + escAttr(prefix + '.' + key + '.' + k) + '" value="' + escAttr(String(kv[k])) + '" placeholder="value">' +
- '<span style="font-size:9px;color:#888;white-space:nowrap;min-width:60px">key: ' + esc(k) + '</span>' +
'<button class="talk-kv-del" data-action="remove-kv" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '" data-sk="' + escAttr(k) + '">x</button>' +
'</div>';
});
html += '<div class="talk-kv-row">' +
- '<input class="talk-input" id="' + escAttr(addKeyID) + '" placeholder="New key name" style="max-width:100px" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '">' +
- '<input class="talk-input" placeholder="Value" style="display:none">' +
+ '<input class="talk-input" id="' + escAttr(addKeyID) + '" placeholder="New key name" style="max-width:150px" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '">' +
+ '<input class="talk-input" id="' + escAttr(addKeyID) + '_val" placeholder="value" style="flex:1">' +
'<span style="font-size:9px;color:#888;white-space:nowrap;min-width:60px"></span>' +
'<button class="talk-kv-add" data-action="add-kv" data-nk="' + nkAttr + '"' + oiAttr + ' data-kvk="' + escAttr(key) + '">Add</button>' +
'</div>';
@@ -712,62 +731,30 @@
return html;
}
- function renderShopEditor(action, prefix, nodeKey, optIdx) {
- var shop = action && action.shop;
- var hasShop = shop && typeof shop === 'object' && !Array.isArray(shop);
- var html = '<div class="talk-shop-block">';
- html += '<div class="talk-shop-header">Shop</div>';
-
- if (hasShop) {
- html += '<div class="talk-field-row" style="margin-bottom:4px">' +
- '<span class="talk-prop-label">Shop Message</span>' +
- '<textarea class="talk-textarea" data-tp="' + escAttr(prefix + '.shop.message') + '" rows="2" style="min-height:24px">' + esc(shop.message || '') + '</textarea>' +
- '</div>';
-
- var items = shop.items || [];
- html += '<div class="talk-prop-label" style="margin-bottom:2px">Items</div>';
- items.forEach(function(item, si) {
- var ip = prefix + '.shop.items.' + si;
- html += '<div class="talk-shop-item">' +
- '<input class="talk-input" data-tp="' + escAttr(ip + '.item_id') + '" value="' + escAttr(item.item_id || '') + '" placeholder="Item ID">' +
- '<input class="talk-input-num" data-tp="' + escAttr(ip + '.buy_price') + '" type="number" value="' + (item.buy_price || 0) + '" placeholder="Buy" title="Buy price">' +
- '<input class="talk-input-num" data-tp="' + escAttr(ip + '.sell_price') + '" type="number" value="' + (item.sell_price || 0) + '" placeholder="Sell" title="Sell price">' +
- '<button class="talk-kv-del" data-action="remove-shop-item" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + ' data-si="' + si + '">x</button>' +
- '</div>';
- });
- html += '<button class="talk-kv-add" data-action="add-shop-item" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + '>+ Add Item</button>';
- } else {
- html += '<button class="talk-kv-add" data-action="add-shop-item" data-nk="' + escAttr(nodeKey) + '"' + (optIdx !== null ? ' data-oi="' + optIdx + '"' : '') + '>+ Add Shop</button>';
- }
-
- html += '</div>';
- return html;
- }
-
// ---- Options section ----
function renderOptionsSection(nodeKey, node) {
var options = node.options || [];
var html = '<div class="talk-options">';
- html += '<b class="talk-options-label">Options</b>';
+ html += '<span class="talk-options-label">Options</span>';
if (options.length > 0) {
options.forEach(function(opt, idx) {
html += '<div class="talk-opt-edit-row">';
- html += '<span class="talk-opt-arrow">&#10132;</span>';
- html += '<input class="talk-input" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.text" value="' + escAttr(opt.text || '') + '" placeholder="Option text...">';
- html += '<input class="talk-input" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.goto" list="talkNodeKeys" value="' + escAttr(opt.goto || '') + '" placeholder="Goto..." style="max-width:120px">';
+ html += '<span class="talk-seq-num">' + (idx + 1) + '</span>';
+ html += '<input class="talk-input talk-opt-text" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.text" value="' + escAttr(opt.text || '') + '" placeholder="Option text...">';
+ html += '<input class="talk-input talk-opt-goto" data-tp="nodes.' + escAttr(nodeKey) + '.options.' + idx + '.goto" list="talkNodeKeys" value="' + escAttr(opt.goto || '') + '" placeholder="Goto...">';
html += '<div class="talk-opt-edit-actions">';
if (opt.condition && typeof opt.condition === 'object' && !Array.isArray(opt.condition)) {
html += '<span class="talk-opt-tag talk-opt-tag-cond" title="Has condition" onclick="toggleOptionDetail(event,\'cond\',' + idx + ',\'' + escAttr(nodeKey) + '\')">cond</span>';
} else {
- html += '<button class="talk-add-cond-btn" style="margin:0;margin-bottom:0" data-action="add-condition" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add condition">c</button>';
+ html += '<button class="talk-add-cond-btn" style="margin:0;margin-bottom:0" data-action="add-condition" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add condition">cond</button>';
}
if (opt.action) {
html += '<span class="talk-opt-tag talk-opt-tag-act" title="Has action" onclick="toggleOptionDetail(event,\'act\',' + idx + ',\'' + escAttr(nodeKey) + '\')">act</span>';
} else {
- html += '<button class="talk-add-act-btn" style="margin:0;margin-bottom:0" data-action="add-action" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add action">a</button>';
+ html += '<button class="talk-add-act-btn" style="margin:0;margin-bottom:0" data-action="add-action" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '" title="Add action">act</button>';
}
html += '</div>';
@@ -775,7 +762,9 @@
html += '</div>';
if (opt.condition && typeof opt.condition === 'object' && !Array.isArray(opt.condition)) {
- html += '<div class="talk-opt-detail" id="optDetail_cond_' + idx + '_' + escAttr(nodeKey) + '" style="display:none;margin-left:18px">';
+ var condDetailKey = 'cond_' + idx + '_' + escAttr(nodeKey);
+ var condOpen = window._talkAutoOpenDetails && window._talkAutoOpenDetails.has(condDetailKey) ? 'block' : 'none';
+ html += '<div class="talk-opt-detail" id="optDetail_' + condDetailKey + '" style="display:' + condOpen + ';margin-left:18px">';
html += '<div class="talk-cond-card" style="margin-top:2px">';
html += '<div class="talk-cond-card-header">Condition' +
'<button class="talk-section-remove" data-action="remove-condition" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '">Remove</button></div>';
@@ -786,12 +775,18 @@
html += '</div>';
}
if (opt.action) {
- html += '<div class="talk-opt-detail" id="optDetail_act_' + idx + '_' + escAttr(nodeKey) + '" style="display:none;margin-left:18px">';
- html += '<div class="talk-cond-card-header" style="border:1px solid var(--border);border-radius:5px 5px 0 0">' +
+ var actDetailKey = 'act_' + idx + '_' + escAttr(nodeKey);
+ var actOpen = window._talkAutoOpenDetails && window._talkAutoOpenDetails.has(actDetailKey) ? 'block' : 'none';
+ html += '<div class="talk-opt-detail" id="optDetail_' + actDetailKey + '" style="display:' + actOpen + ';margin-left:18px">';
+ html += '<div class="talk-cond-card" style="margin-top:2px">';
+ html += '<div class="talk-cond-card-header">' +
'<span class="talk-prop-label">Action</span>' +
'<button class="talk-section-remove" data-action="remove-action" data-nk="' + escAttr(nodeKey) + '" data-oi="' + idx + '">Remove</button></div>';
+ html += '<div class="talk-cond-card-body">';
html += renderActionForm(opt.action, 'nodes.' + escAttr(nodeKey) + '.options.' + idx + '.action', nodeKey, idx);
html += '</div>';
+ html += '</div>';
+ html += '</div>';
}
});
} else {
@@ -799,10 +794,10 @@
}
html += '<div class="talk-add-opt-row">' +
- '<span class="talk-opt-arrow" style="color:#555">&#10132;</span>' +
- '<input class="talk-input" id="talkOptText_' + escAttr(nodeKey) + '" placeholder="Option text...">' +
- '<input class="talk-input" id="talkOptGoto_' + escAttr(nodeKey) + '" list="talkNodeKeys" placeholder="Goto..." style="max-width:120px">' +
- '<span style="width:62px;flex-shrink:0"></span>' +
+ '<span class="talk-seq-num" style="color:#555">' + (options.length + 1) + '</span>' +
+ '<input class="talk-input talk-opt-text" id="talkOptText_' + escAttr(nodeKey) + '" placeholder="Option text...">' +
+ '<input class="talk-input talk-opt-goto" id="talkOptGoto_' + escAttr(nodeKey) + '" list="talkNodeKeys" placeholder="Goto...">' +
+ '<span class="talk-opt-gap"></span>' +
'<button class="talk-add-btn" data-action="add-option" data-nk="' + escAttr(nodeKey) + '">Add</button>' +
'</div>';
@@ -816,6 +811,15 @@
if (el) el.style.display = el.style.display === 'none' ? 'block' : 'none';
};
+ window.talkToggleNode = function(nodeKey, nodeEl) {
+ var open = nodeEl.classList.toggle('open');
+ if (open) {
+ window._talkOpenNodes.add(nodeKey);
+ } else {
+ window._talkOpenNodes.delete(nodeKey);
+ }
+ };
+
// ---- Add/Delete Option ----
function addOption(nodeKey) {
@@ -843,22 +847,22 @@
// ---- Add/Delete Sequence Line ----
- function addSequenceLine(nodeKey) {
- var input = document.getElementById('talkSeqAdd_' + nodeKey);
+ function addMessageLine(nodeKey) {
+ var input = document.getElementById('talkMsgAdd_' + nodeKey);
var text = input ? input.value.trim() : '';
var node = talkNodes()[nodeKey];
if (!node) return;
- if (!node.sequence) node.sequence = [];
- node.sequence.push(text);
+ if (!node.messages) node.messages = [];
+ node.messages.push(text);
renderTalkTree(window._talkData);
talkSync();
}
- function deleteSequenceLine(nodeKey, idx) {
+ function deleteMessageLine(nodeKey, idx) {
var node = talkNodes()[nodeKey];
- if (!node || !node.sequence) return;
- node.sequence.splice(idx, 1);
- if (node.sequence.length === 0) delete node.sequence;
+ if (!node || !node.messages) return;
+ node.messages.splice(idx, 1);
+ if (node.messages.length === 0) delete node.messages;
renderTalkTree(window._talkData);
talkSync();
}
@@ -869,6 +873,10 @@
var target = getConditionFor(nodeKey, optIdx);
if (!target) return;
target.action = {};
+ if (optIdx !== null) {
+ window._talkAutoOpenDetails = new Set();
+ window._talkAutoOpenDetails.add('act_' + optIdx + '_' + escAttr(nodeKey));
+ }
renderTalkTree(window._talkData);
talkSync();
}
@@ -899,6 +907,11 @@
var input = document.getElementById(addKeyID);
var key = input ? input.value.trim() : '';
if (!key) return;
+ var valInput = document.getElementById(addKeyID + '_val');
+ var rawVal = valInput ? valInput.value.trim() : 'true';
+ var val = rawVal === 'true' ? true : rawVal === 'false' ? false : rawVal;
+ var numVal = parseFloat(rawVal);
+ if (!isNaN(numVal) && String(numVal) === rawVal) val = numVal;
var basePath = kvPath(nodeKey, optIdx, kvKey);
var obj = talkGet(basePath);
if (!obj || typeof obj !== 'object') {
@@ -906,7 +919,7 @@
obj = talkGet(basePath);
}
if (obj[key] !== undefined) { notify('Flag "' + key + '" already exists.', 'error'); return; }
- obj[key] = true;
+ obj[key] = val;
renderTalkTree(window._talkData);
talkSync();
}
@@ -923,31 +936,28 @@
talkSync();
}
- // ---- Add/Remove Shop Items ----
-
- function addShopItem(nodeKey, optIdx) {
- var actionPath = optIdx !== null ? 'nodes.' + nodeKey + '.options.' + optIdx + '.action' : 'nodes.' + nodeKey + '.action';
- var action = talkGet(actionPath);
- if (!action || typeof action !== 'object') {
- action = {};
- talkSet(actionPath, action);
+ window.talkRenameKV = function(el) {
+ var nodeKey = el.getAttribute('data-nk');
+ var optIdxStr = el.getAttribute('data-oi');
+ var optIdx = optIdxStr !== null ? parseInt(optIdxStr, 10) : null;
+ var kvKey = el.getAttribute('data-kvk');
+ var oldKey = el.getAttribute('data-oldkey');
+ var newKey = el.value.trim();
+ if (!newKey || newKey === oldKey) return;
+ var basePath = optIdx !== null ?
+ 'nodes.' + nodeKey + '.options.' + optIdx + '.action.' + kvKey :
+ 'nodes.' + nodeKey + '.action.' + kvKey;
+ var obj = talkGet(basePath);
+ if (!obj || obj[newKey] !== undefined) {
+ el.value = oldKey;
+ if (obj && obj[newKey] !== undefined) notify('Key "' + newKey + '" already exists.', 'error');
+ return;
}
- if (!action.shop) { action.shop = { items: [] }; }
- if (!action.shop.items) action.shop.items = [];
- action.shop.items.push({ item_id: '', buy_price: 0, sell_price: 0 });
- renderTalkTree(window._talkData);
- talkSync();
- }
-
- function removeShopItem(nodeKey, optIdx, idx) {
- var actionPath = optIdx !== null ? 'nodes.' + nodeKey + '.options.' + optIdx + '.action' : 'nodes.' + nodeKey + '.action';
- var action = talkGet(actionPath);
- if (!action || !action.shop || !action.shop.items) return;
- action.shop.items.splice(idx, 1);
- if (action.shop.items.length === 0) delete action.shop;
+ obj[newKey] = obj[oldKey];
+ delete obj[oldKey];
renderTalkTree(window._talkData);
talkSync();
- }
+ };
// ---- Global event listeners ----
@@ -988,10 +998,10 @@
return;
}
- if (el.id && el.id.startsWith('talkSeqAdd_')) {
+ if (el.id && el.id.startsWith('talkMsgAdd_')) {
e.preventDefault();
- var nk = el.id.replace('talkSeqAdd_', '');
- addSequenceLine(nk);
+ var nk = el.id.replace('talkMsgAdd_', '');
+ addMessageLine(nk);
return;
}
diff --git a/internal/behavior/behavior.go b/internal/behavior/behavior.go
index 2ef7fae..646495a 100644
--- a/internal/behavior/behavior.go
+++ b/internal/behavior/behavior.go
@@ -28,8 +28,7 @@ type TalkConfig struct {
}
type TalkNode struct {
- Message MessageList `yaml:"message"`
- Sequence []string `yaml:"sequence,omitempty"`
+ Messages []string `yaml:"messages,omitempty"`
Condition *Condition `yaml:"condition,omitempty"`
Options []TalkOption `yaml:"options"`
Action *NodeAction `yaml:"action"`
@@ -37,9 +36,9 @@ type TalkNode struct {
}
type TalkOption struct {
- Text string `yaml:"text"`
- Goto string `yaml:"goto"`
- Condition *Condition `yaml:"condition"`
+ Text string `yaml:"text"`
+ Goto string `yaml:"goto"`
+ Condition *Condition `yaml:"condition"`
Action *NodeAction `yaml:"action,omitempty"`
}
@@ -50,31 +49,102 @@ type NodeAction struct {
TakeItem string `yaml:"take_item"`
Teleport int `yaml:"teleport"`
Heal int `yaml:"heal"`
- Cost int `yaml:"cost"`
- Shop *ShopConfig `yaml:"shop"`
- AssignTask bool `yaml:"assign_task"`
- SkipTask bool `yaml:"skip_task"`
- ExtendTask bool `yaml:"extend_task"`
+ Credits int `yaml:"credits"`
ReputationCost int `yaml:"reputation_cost"`
- Sawmill bool `yaml:"sawmill"`
ApsNode bool `yaml:"aps_node"`
}
+// ShopConfig is a root-level mob property (mob YAML `shop:`). Buy prices are
+// always 100% of the item's value; sell prices follow the OSRS store formula:
+//
+// sell = value * max(10, buy_percentage - stock*change_percentage) / 100
+//
+// where stock is the shop's current holding of that item. BuyPercentage
+// defaults to 40 and ChangePercentage to 3 when unset (see ShopBuyPercentage /
+// ShopChangePercentage).
type ShopConfig struct {
- Message string `yaml:"message"`
- Items []ShopItem `yaml:"items"`
+ Message string `yaml:"message"`
+ BuyPercentage float64 `yaml:"buy_percentage"` // sell price at zero stock, % of value (default 40)
+ ChangePercentage float64 `yaml:"change_percentage"` // sell price drop per unit of stock, % of value (default 3)
+ RestockTicks float64 `yaml:"restock_ticks"` // shop-wide default restock interval
+ BuysAnything *bool `yaml:"buys_anything"` // buy items not in the list (default true)
+ Items []ShopItem `yaml:"items"`
}
type ShopItem struct {
- ItemID string `yaml:"item_id"`
- BuyPrice int `yaml:"buy_price"`
- SellPrice int `yaml:"sell_price"`
+ ItemID string `yaml:"item_id"`
+ Stock int `yaml:"stock"` // target/max stock; also starting stock
+ RestockTicks float64 `yaml:"restock_ticks"` // per-item restock interval (overrides shop default)
+}
+
+// Shop pricing defaults, applied when the YAML leaves a field at zero.
+const (
+ ShopBuyPercentage = 40.0
+ ShopChangePercentage = 3.0
+ ShopMinSellPercent = 10.0
+ ShopDefaultRestock = 100.0
+)
+
+// EffectiveBuyPercentage returns BuyPercentage or the default.
+func (c *ShopConfig) EffectiveBuyPercentage() float64 {
+ if c.BuyPercentage > 0 {
+ return c.BuyPercentage
+ }
+ return ShopBuyPercentage
+}
+
+// EffectiveChangePercentage returns ChangePercentage or the default.
+func (c *ShopConfig) EffectiveChangePercentage() float64 {
+ if c.ChangePercentage > 0 {
+ return c.ChangePercentage
+ }
+ return ShopChangePercentage
+}
+
+// Buys reports whether the shop will buy items not in its item list.
+func (c *ShopConfig) Buys() bool {
+ return c.BuysAnything == nil || *c.BuysAnything
+}
+
+// SellPrice computes the per-unit price the shop pays for an item of the given
+// value when the shop currently holds `stock` of it.
+func (c *ShopConfig) SellPrice(value, stock int) int {
+ if stock < 0 {
+ stock = 0
+ }
+ pct := c.EffectiveBuyPercentage() - float64(stock)*c.EffectiveChangePercentage()
+ if pct < ShopMinSellPercent {
+ pct = ShopMinSellPercent
+ }
+ return int(float64(value) * pct / 100.0)
+}
+
+// RestockInterval returns the effective restock interval (in ticks) for an item,
+// honoring the per-item override, then the shop default, then the global default.
+func (c *ShopConfig) RestockInterval(item *ShopItem) int {
+ if item != nil && item.RestockTicks > 0 {
+ return int(item.RestockTicks)
+ }
+ if c.RestockTicks > 0 {
+ return int(c.RestockTicks)
+ }
+ return int(ShopDefaultRestock)
+}
+
+// FindItem returns the configured shop item with the given id, or nil.
+func (c *ShopConfig) FindItem(itemID string) *ShopItem {
+ for i := range c.Items {
+ if c.Items[i].ItemID == itemID {
+ return &c.Items[i]
+ }
+ }
+ return nil
}
type Condition struct {
Flag string `yaml:"flag"`
Value any `yaml:"value"`
- Not bool `yaml:"not"`
+ Not bool `yaml:"not"`
PlayerFlag string `yaml:"player_flag"`
HasItem string `yaml:"has_item"`
MinCredits int `yaml:"min_credits"`
@@ -83,16 +153,16 @@ type Condition struct {
}
type UseConfig struct {
- StartMessage string `yaml:"start_message"`
- TicksPerCycle float64 `yaml:"ticks_per_cycle"`
- Consume map[string]int `yaml:"consume"`
- Reward DropEntry `yaml:"reward"`
- FailMessage string `yaml:"fail_message"`
- Success *SuccessFormula `yaml:"success"`
- Skill string `yaml:"skill"`
- Level int `yaml:"level"`
- XP int `yaml:"xp"`
- SuccessMessage string `yaml:"success_message"`
- EndMessage string `yaml:"end_message"`
- BroadcastMessage string `yaml:"broadcast_message"`
+ StartMessage string `yaml:"start_message"`
+ TicksPerCycle float64 `yaml:"ticks_per_cycle"`
+ Consume map[string]int `yaml:"consume"`
+ Reward DropEntry `yaml:"reward"`
+ FailMessage string `yaml:"fail_message"`
+ Success *SuccessFormula `yaml:"success"`
+ Skill string `yaml:"skill"`
+ Level int `yaml:"level"`
+ XP int `yaml:"xp"`
+ SuccessMessage string `yaml:"success_message"`
+ EndMessage string `yaml:"end_message"`
+ BroadcastMessage string `yaml:"broadcast_message"`
}
diff --git a/internal/behavior/types.go b/internal/behavior/types.go
index c59a95e..29e2631 100644
--- a/internal/behavior/types.go
+++ b/internal/behavior/types.go
@@ -1,10 +1,7 @@
package behavior
import (
- "math/rand"
"strings"
-
- "gopkg.in/yaml.v3"
)
func WordPrefixMatch(input, name string) bool {
@@ -224,24 +221,3 @@ type DropEntry struct {
type DropTableDef struct {
Drops []DropEntry `yaml:"drops"`
}
-
-type MessageList []string
-
-func (ml *MessageList) UnmarshalYAML(value *yaml.Node) error {
- if value.Kind == yaml.ScalarNode {
- var s string
- if err := value.Decode(&s); err != nil {
- return err
- }
- *ml = MessageList{s}
- return nil
- }
- return value.Decode((*[]string)(ml))
-}
-
-func (ml MessageList) Pick() string {
- if len(ml) == 0 {
- return ""
- }
- return ml[rand.Intn(len(ml))]
-}
diff --git a/internal/game/act_steal.go b/internal/game/act_steal.go
index 1c76474..f5d3dbf 100644
--- a/internal/game/act_steal.go
+++ b/internal/game/act_steal.go
@@ -28,7 +28,7 @@ func (g *Game) executeSteal(sess *net.Session, args []string, rawInput string) {
var stallGuardTalk = &behavior.TalkConfig{
Nodes: map[string]behavior.TalkNode{
"start": {
- Message: behavior.MessageList{"Caught you red-handed! You have three options, thief."},
+ Messages: []string{"Caught you red-handed! You have three options, thief."},
Options: []behavior.TalkOption{
{Text: "\"I'll pay a fine. (500 credits)\"", Goto: "bribe", Condition: &behavior.Condition{MinCredits: 500}},
{Text: "\"Take me to jail.\"", Goto: "jail"},
@@ -36,17 +36,17 @@ var stallGuardTalk = &behavior.TalkConfig{
},
},
"bribe": {
- Message: behavior.MessageList{"Smart choice. Hand over 500 credits and we'll forget this happened."},
- Action: &behavior.NodeAction{Cost: 500},
+ Messages: []string{"Smart choice. Hand over 500 credits and we'll forget this happened."},
+ Action: &behavior.NodeAction{Credits: -500},
Options: []behavior.TalkOption{{Text: "\"Fine, take it.\""}},
},
"jail": {
- Message: behavior.MessageList{"Off to the detention cell with you!"},
+ Messages: []string{"Off to the detention cell with you!"},
Action: &behavior.NodeAction{Teleport: 162},
Options: []behavior.TalkOption{{Text: "(You are dragged away)"}},
},
"fight": {
- Message: behavior.MessageList{"Then defend yourself!"},
+ Messages: []string{"Then defend yourself!"},
Action: &behavior.NodeAction{SetFlags: map[string]any{"guard_hostile": true}},
Options: []behavior.TalkOption{{Text: "(The guard attacks!)"}},
},
diff --git a/internal/game/act_talk.go b/internal/game/act_talk.go
index bd5a091..7cfdf7c 100644
--- a/internal/game/act_talk.go
+++ b/internal/game/act_talk.go
@@ -58,11 +58,6 @@ func (g *Game) startTalk(sess *net.Session, p *player.Player, obj *object.Object
}
func (g *Game) handleNodeAction(sess *net.Session, na *behavior.NodeAction) (intercepted bool) {
- if na.Shop != nil {
- g.applyNodeAction(sess, na)
- g.enterShop(sess, na.Shop)
- return true
- }
g.applyNodeAction(sess, na)
if v, ok := g.Flags.Get("guard_hostile"); ok && v != nil {
@@ -97,28 +92,24 @@ func (g *Game) showTalkNode(sess *net.Session, node behavior.TalkNode) {
for {
if node.Condition == nil || g.checkCondition(sess, node.Condition) {
- if len(node.Sequence) > 0 {
+ if len(node.Messages) > 0 {
if node.Action != nil {
if g.handleNodeAction(sess, node.Action) {
return
}
}
td.SeqIndex = 0
- msg := node.Sequence[0]
+ msg := node.Messages[0]
if msg != "" {
sess.WriteLine(color.ExpandDialogTags(g.colorMode(sess), dialogSpec, msg))
}
td.SeqIndex = 1
- if td.SeqIndex < len(node.Sequence) {
+ if td.SeqIndex < len(node.Messages) {
sess.State = net.StateTalkSequence
sess.Write(g.colorize(sess, "dialog_options", "[enter to continue]\n"))
return
}
} else {
- msg := node.Message.Pick()
- if msg != "" {
- sess.WriteLine(color.ExpandDialogTags(g.colorMode(sess), dialogSpec, msg))
- }
if node.Action != nil {
if g.handleNodeAction(sess, node.Action) {
return
@@ -301,14 +292,14 @@ func (g *Game) advanceTalk(sess *net.Session, p *player.Player) {
return
}
dialogSpec := g.resolveColor(sess, "dialog")
- if td.SeqIndex < len(node.Sequence) {
- msg := node.Sequence[td.SeqIndex]
+ if td.SeqIndex < len(node.Messages) {
+ msg := node.Messages[td.SeqIndex]
if msg != "" {
sess.WriteLine(color.ExpandDialogTags(g.colorMode(sess), dialogSpec, msg))
}
td.SeqIndex++
}
- if td.SeqIndex >= len(node.Sequence) {
+ if td.SeqIndex >= len(node.Messages) {
g.finishSequence(sess, node)
return
}
@@ -341,28 +332,12 @@ func (g *Game) advanceTalk(sess *net.Session, p *player.Player) {
}
}
-func (g *Game) enterShop(sess *net.Session, cfg *behavior.ShopConfig) {
- sess.Shop = cfg
- sess.State = net.StateShop
- g.showShopBrowse(sess)
- g.writeShopPrompt(sess)
-}
-
func (g *Game) applyNodeAction(sess *net.Session, na *behavior.NodeAction) {
p := sess.Player
if p == nil {
return
}
- if na.AssignTask {
- g.assignAssassinTask(sess, p)
- }
- if na.SkipTask {
- g.skipAssassinTask(sess, p)
- }
- if na.ExtendTask {
- g.extendAssassinTask(sess, p)
- }
if na.ReputationCost > 0 {
rep := getPlayerFlagInt(p, "assassin_reputation")
if rep < na.ReputationCost {
@@ -414,15 +389,19 @@ func (g *Game) applyNodeAction(sess *net.Session, na *behavior.NodeAction) {
g.AccountStore.SaveCharacter(p)
sess.WriteLine(fmt.Sprintf("You regain %d hitpoints.", na.Heal))
}
- if na.Cost > 0 {
- if p.Credits >= na.Cost {
- p.Credits -= na.Cost
+ if na.Credits != 0 {
+ if na.Credits < 0 {
+ if p.Credits >= -na.Credits {
+ p.Credits += na.Credits
+ g.AccountStore.SaveCharacter(p)
+ } else {
+ sess.WriteLine(fmt.Sprintf("You don't have enough credits. (Need %d, have %d)", -na.Credits, p.Credits))
+ }
+ } else {
+ p.Credits += na.Credits
g.AccountStore.SaveCharacter(p)
}
}
- if na.Sawmill {
- g.processSawmill(sess, p)
- }
if na.ApsNode {
g.setPlayerFlag(p, "aps_node_"+strconv.Itoa(p.RoomID), true)
}
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index bc77936..1247fc3 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -15,147 +15,151 @@ type commandDef struct {
}
var commandRegistry = map[string]commandDef{
- "get": {(*Game).executeGet, ClassActive},
- "take": {(*Game).executeGet, ClassActive},
- "grab": {(*Game).executeGet, ClassActive},
- "pick": {(*Game).executeGet, ClassActive},
- "drop": {(*Game).executeDrop, ClassActive},
- "attack": {(*Game).executeAttack, ClassActive},
- "kill": {(*Game).executeAttack, ClassActive},
- "work": {(*Game).executeWork, ClassActive},
- "style": {(*Game).executeStyle, ClassInstant},
- "look": {(*Game).executeLook, ClassInstant},
- "l": {(*Game).executeLook, ClassInstant},
- "north": {(*Game).executeMove, ClassActive},
- "n": {(*Game).executeMove, ClassActive},
- "south": {(*Game).executeMove, ClassActive},
- "s": {(*Game).executeMove, ClassActive},
- "east": {(*Game).executeMove, ClassActive},
- "e": {(*Game).executeMove, ClassActive},
- "west": {(*Game).executeMove, ClassActive},
- "w": {(*Game).executeMove, ClassActive},
- "northeast": {(*Game).executeMove, ClassActive},
- "ne": {(*Game).executeMove, ClassActive},
- "northwest": {(*Game).executeMove, ClassActive},
- "nw": {(*Game).executeMove, ClassActive},
- "southeast": {(*Game).executeMove, ClassActive},
- "se": {(*Game).executeMove, ClassActive},
- "southwest": {(*Game).executeMove, ClassActive},
- "sw": {(*Game).executeMove, ClassActive},
- "up": {(*Game).executeMove, ClassActive},
- "u": {(*Game).executeMove, ClassActive},
- "down": {(*Game).executeMove, ClassActive},
- "d": {(*Game).executeMove, ClassActive},
- "say": {(*Game).executeSay, ClassInstant},
- "global": {(*Game).executeGlobal, ClassInstant},
- "who": {(*Game).executeWho, ClassInstant},
- "sc": {(*Game).executeScore, ClassInstant},
- "score": {(*Game).executeScore, ClassInstant},
- "skills": {(*Game).executeSkills, ClassInstant},
- "sk": {(*Game).executeSkills, ClassInstant},
- "task": {(*Game).executeTask, ClassInstant},
- "stats": {(*Game).executeStats, ClassInstant},
- "tech": {(*Game).executeTech, ClassInstant},
- "t": {(*Game).executeTech, ClassInstant},
- "i": {(*Game).executeInventory, ClassInstant},
- "inv": {(*Game).executeInventory, ClassInstant},
- "inventory": {(*Game).executeInventory, ClassInstant},
- "quit": {(*Game).executeQuit, ClassActive},
- "description": {(*Game).executeDescription, ClassInstant},
- "desc": {(*Game).executeDescription, ClassInstant},
- "option": {(*Game).executeOption, ClassInstant},
- "options": {(*Game).executeOption, ClassInstant},
- "color": {(*Game).executeColor, ClassInstant},
- "colors": {(*Game).executeColor, ClassInstant},
- "colortable": {(*Game).executeColortable, ClassInstant},
- "prompt": {(*Game).executePrompt, ClassInstant},
- "exits": {(*Game).executeExits, ClassInstant},
- "map": {(*Game).executeMap, ClassInstant},
- "symbol": {(*Game).executeSymbol, ClassInstant},
- "queued": {(*Game).executeQueued, ClassInstant},
- "help": {(*Game).executeHelp, ClassInstant},
- "mine": {(*Game).executeGather, ClassActive},
- "chop": {(*Game).executeGather, ClassActive},
- "fish": {(*Game).executeGather, ClassActive},
- "cut": {(*Game).executeGather, ClassActive},
- "pull": {(*Game).executeUse, ClassActive},
- "push": {(*Game).executeUse, ClassActive},
- "use": {(*Game).executeUse, ClassActive},
- "cook": {(*Game).executeCook, ClassActive},
- "smelt": {(*Game).executeSmelt, ClassActive},
- "smith": {(*Game).executeSmith, ClassActive},
- "craft": {(*Game).executeCraft, ClassActive},
- "eat": {(*Game).executeEat, ClassFree},
- "drink": {(*Game).executeDrink, ClassFree},
- "fletch": {(*Game).executeFletch, ClassFree},
- "clean": {(*Game).executeClean, ClassFree},
- "mix": {(*Game).executeMix, ClassActive},
- "id": {(*Game).executeIdentify, ClassActive},
- "identify": {(*Game).executeIdentify, ClassActive},
- "autotrigger": {(*Game).executeAutotrigger, ClassInstant},
- "auto": {(*Game).executeAutotrigger, ClassInstant},
- "mods": {(*Game).executeMods, ClassInstant},
- "modlist": {(*Game).executeMods, ClassInstant},
- "mod": {(*Game).executeMods, ClassInstant},
- "modules": {(*Game).executeMods, ClassInstant},
- "module": {(*Game).executeMods, ClassInstant},
- "sneak": {(*Game).executeSneak, ClassInstant},
- "trigger": {(*Game).executeTrigger, ClassActive},
- "home": {(*Game).executeHome, ClassInstant},
- "recall": {(*Game).executeHome, ClassInstant},
- "talk": {(*Game).executeTalk, ClassActive},
- "speak": {(*Game).executeTalk, ClassActive},
- "ask": {(*Game).executeTalk, ClassActive},
- "burn": {(*Game).executeBurn, ClassActive},
- "stoke": {(*Game).executeStoke, ClassActive},
- "alias": {(*Game).executeAlias, ClassInstant},
- "unalias": {(*Game).executeUnalias, ClassInstant},
- "search": {(*Game).executeSearch, ClassActive},
- "steal": {(*Game).executeSteal, ClassActive},
- "thieve": {(*Game).executeSteal, ClassActive},
- "plant": {(*Game).executePlant, ClassActive},
- "harvest": {(*Game).executeHarvest, ClassActive},
- "rake": {(*Game).executeRake, ClassActive},
- "water": {(*Game).executeWater, ClassActive},
- "cure": {(*Game).executeCure, ClassActive},
- "inspect": {(*Game).executeInspect, ClassInstant},
- "walk": {(*Game).executeWalk, ClassActive},
- "bank": {(*Game).executeBank, ClassActive},
- "construct": {(*Game).executeConstruct, ClassActive},
- "make": {(*Game).executeConstruct, ClassActive},
- "jack": {(*Game).executeJack, ClassActive},
- "jackin": {(*Game).executeJack, ClassActive},
- "eq": {(*Game).executeWear, ClassFree},
- "equipment": {(*Game).executeWear, ClassFree},
- "equip": {(*Game).executeWear, ClassFree},
- "wear": {(*Game).executeWear, ClassFree},
- "wield": {(*Game).executeWear, ClassFree},
- "remove": {(*Game).executeRemove, ClassFree},
- "unwear": {(*Game).executeRemove, ClassFree},
- "unwield": {(*Game).executeRemove, ClassFree},
- "aps": {(*Game).executeAps, ClassActive},
- "verbs": {(*Game).executeVerbs, ClassInstant},
- "what": {(*Game).executeVerbs, ClassInstant},
- "syntax": {(*Game).executeVerbs, ClassInstant},
- "commands": {(*Game).executeVerbs, ClassInstant},
- "stop": {(*Game).executeStop, ClassInstant},
- "hide": {(*Game).executeHide, ClassActive},
- "safespot": {(*Game).executeHide, ClassActive},
- "unhide": {(*Game).executeUnhide, ClassActive},
- "unsafespot": {(*Game).executeUnhide, ClassActive},
- "goto": {(*Game).executeGoto, ClassInstant},
- "summon": {(*Game).executeSummon, ClassInstant},
- "dig": {(*Game).executeDig, ClassInstant},
- "setflag": {(*Game).executeSetFlag, ClassInstant},
+ "get": {(*Game).executeGet, ClassActive},
+ "take": {(*Game).executeGet, ClassActive},
+ "grab": {(*Game).executeGet, ClassActive},
+ "pick": {(*Game).executeGet, ClassActive},
+ "drop": {(*Game).executeDrop, ClassActive},
+ "attack": {(*Game).executeAttack, ClassActive},
+ "kill": {(*Game).executeAttack, ClassActive},
+ "work": {(*Game).executeWork, ClassActive},
+ "style": {(*Game).executeStyle, ClassInstant},
+ "look": {(*Game).executeLook, ClassInstant},
+ "l": {(*Game).executeLook, ClassInstant},
+ "north": {(*Game).executeMove, ClassActive},
+ "n": {(*Game).executeMove, ClassActive},
+ "south": {(*Game).executeMove, ClassActive},
+ "s": {(*Game).executeMove, ClassActive},
+ "east": {(*Game).executeMove, ClassActive},
+ "e": {(*Game).executeMove, ClassActive},
+ "west": {(*Game).executeMove, ClassActive},
+ "w": {(*Game).executeMove, ClassActive},
+ "northeast": {(*Game).executeMove, ClassActive},
+ "ne": {(*Game).executeMove, ClassActive},
+ "northwest": {(*Game).executeMove, ClassActive},
+ "nw": {(*Game).executeMove, ClassActive},
+ "southeast": {(*Game).executeMove, ClassActive},
+ "se": {(*Game).executeMove, ClassActive},
+ "southwest": {(*Game).executeMove, ClassActive},
+ "sw": {(*Game).executeMove, ClassActive},
+ "up": {(*Game).executeMove, ClassActive},
+ "u": {(*Game).executeMove, ClassActive},
+ "down": {(*Game).executeMove, ClassActive},
+ "d": {(*Game).executeMove, ClassActive},
+ "say": {(*Game).executeSay, ClassInstant},
+ "global": {(*Game).executeGlobal, ClassInstant},
+ "who": {(*Game).executeWho, ClassInstant},
+ "sc": {(*Game).executeScore, ClassInstant},
+ "score": {(*Game).executeScore, ClassInstant},
+ "skills": {(*Game).executeSkills, ClassInstant},
+ "sk": {(*Game).executeSkills, ClassInstant},
+ "task": {(*Game).executeTask, ClassInstant},
+ "stats": {(*Game).executeStats, ClassInstant},
+ "tech": {(*Game).executeTech, ClassInstant},
+ "t": {(*Game).executeTech, ClassInstant},
+ "i": {(*Game).executeInventory, ClassInstant},
+ "inv": {(*Game).executeInventory, ClassInstant},
+ "inventory": {(*Game).executeInventory, ClassInstant},
+ "quit": {(*Game).executeQuit, ClassActive},
+ "description": {(*Game).executeDescription, ClassInstant},
+ "desc": {(*Game).executeDescription, ClassInstant},
+ "option": {(*Game).executeOption, ClassInstant},
+ "options": {(*Game).executeOption, ClassInstant},
+ "color": {(*Game).executeColor, ClassInstant},
+ "colors": {(*Game).executeColor, ClassInstant},
+ "colortable": {(*Game).executeColortable, ClassInstant},
+ "prompt": {(*Game).executePrompt, ClassInstant},
+ "exits": {(*Game).executeExits, ClassInstant},
+ "map": {(*Game).executeMap, ClassInstant},
+ "symbol": {(*Game).executeSymbol, ClassInstant},
+ "queued": {(*Game).executeQueued, ClassInstant},
+ "help": {(*Game).executeHelp, ClassInstant},
+ "mine": {(*Game).executeGather, ClassActive},
+ "chop": {(*Game).executeGather, ClassActive},
+ "fish": {(*Game).executeGather, ClassActive},
+ "cut": {(*Game).executeGather, ClassActive},
+ "pull": {(*Game).executeUse, ClassActive},
+ "push": {(*Game).executeUse, ClassActive},
+ "use": {(*Game).executeUse, ClassActive},
+ "cook": {(*Game).executeCook, ClassActive},
+ "smelt": {(*Game).executeSmelt, ClassActive},
+ "smith": {(*Game).executeSmith, ClassActive},
+ "craft": {(*Game).executeCraft, ClassActive},
+ "eat": {(*Game).executeEat, ClassFree},
+ "drink": {(*Game).executeDrink, ClassFree},
+ "fletch": {(*Game).executeFletch, ClassFree},
+ "clean": {(*Game).executeClean, ClassFree},
+ "mix": {(*Game).executeMix, ClassActive},
+ "id": {(*Game).executeIdentify, ClassActive},
+ "identify": {(*Game).executeIdentify, ClassActive},
+ "autotrigger": {(*Game).executeAutotrigger, ClassInstant},
+ "auto": {(*Game).executeAutotrigger, ClassInstant},
+ "mods": {(*Game).executeMods, ClassInstant},
+ "modlist": {(*Game).executeMods, ClassInstant},
+ "mod": {(*Game).executeMods, ClassInstant},
+ "modules": {(*Game).executeMods, ClassInstant},
+ "module": {(*Game).executeMods, ClassInstant},
+ "sneak": {(*Game).executeSneak, ClassInstant},
+ "trigger": {(*Game).executeTrigger, ClassActive},
+ "home": {(*Game).executeHome, ClassInstant},
+ "recall": {(*Game).executeHome, ClassInstant},
+ "talk": {(*Game).executeTalk, ClassActive},
+ "speak": {(*Game).executeTalk, ClassActive},
+ "ask": {(*Game).executeTalk, ClassActive},
+ "burn": {(*Game).executeBurn, ClassActive},
+ "stoke": {(*Game).executeStoke, ClassActive},
+ "alias": {(*Game).executeAlias, ClassInstant},
+ "unalias": {(*Game).executeUnalias, ClassInstant},
+ "search": {(*Game).executeSearch, ClassActive},
+ "steal": {(*Game).executeSteal, ClassActive},
+ "thieve": {(*Game).executeSteal, ClassActive},
+ "plant": {(*Game).executePlant, ClassActive},
+ "harvest": {(*Game).executeHarvest, ClassActive},
+ "rake": {(*Game).executeRake, ClassActive},
+ "water": {(*Game).executeWater, ClassActive},
+ "cure": {(*Game).executeCure, ClassActive},
+ "inspect": {(*Game).executeInspect, ClassInstant},
+ "walk": {(*Game).executeWalk, ClassActive},
+ "bank": {(*Game).executeBank, ClassActive},
+ "list": {(*Game).executeShopList, ClassInstant},
+ "browse": {(*Game).executeShopList, ClassInstant},
+ "buy": {(*Game).executeShopBuy, ClassInstant},
+ "sell": {(*Game).executeShopSell, ClassInstant},
+ "construct": {(*Game).executeConstruct, ClassActive},
+ "make": {(*Game).executeConstruct, ClassActive},
+ "jack": {(*Game).executeJack, ClassActive},
+ "jackin": {(*Game).executeJack, ClassActive},
+ "eq": {(*Game).executeWear, ClassFree},
+ "equipment": {(*Game).executeWear, ClassFree},
+ "equip": {(*Game).executeWear, ClassFree},
+ "wear": {(*Game).executeWear, ClassFree},
+ "wield": {(*Game).executeWear, ClassFree},
+ "remove": {(*Game).executeRemove, ClassFree},
+ "unwear": {(*Game).executeRemove, ClassFree},
+ "unwield": {(*Game).executeRemove, ClassFree},
+ "aps": {(*Game).executeAps, ClassActive},
+ "verbs": {(*Game).executeVerbs, ClassInstant},
+ "what": {(*Game).executeVerbs, ClassInstant},
+ "syntax": {(*Game).executeVerbs, ClassInstant},
+ "commands": {(*Game).executeVerbs, ClassInstant},
+ "stop": {(*Game).executeStop, ClassInstant},
+ "hide": {(*Game).executeHide, ClassActive},
+ "safespot": {(*Game).executeHide, ClassActive},
+ "unhide": {(*Game).executeUnhide, ClassActive},
+ "unsafespot": {(*Game).executeUnhide, ClassActive},
+ "goto": {(*Game).executeGoto, ClassInstant},
+ "summon": {(*Game).executeSummon, ClassInstant},
+ "dig": {(*Game).executeDig, ClassInstant},
+ "setflag": {(*Game).executeSetFlag, ClassInstant},
"setplayerflag": {(*Game).executeSetPlayerFlag, ClassInstant},
- "reload": {(*Game).executeReload, ClassInstant},
- "shutdown": {(*Game).executeShutdown, ClassInstant},
- "room": {(*Game).executeRoom, ClassInstant},
- "swapid": {(*Game).executeSwapID, ClassInstant},
- "undig": {(*Game).executeUndig, ClassInstant},
- "close": {(*Game).executeClose, ClassInstant},
- "god": {(*Game).executeGod, ClassInstant},
- "ungod": {(*Game).executeUnGod, ClassInstant},
+ "reload": {(*Game).executeReload, ClassInstant},
+ "shutdown": {(*Game).executeShutdown, ClassInstant},
+ "room": {(*Game).executeRoom, ClassInstant},
+ "swapid": {(*Game).executeSwapID, ClassInstant},
+ "undig": {(*Game).executeUndig, ClassInstant},
+ "close": {(*Game).executeClose, ClassInstant},
+ "god": {(*Game).executeGod, ClassInstant},
+ "ungod": {(*Game).executeUnGod, ClassInstant},
}
func classifyCommand(cmd string) CommandClass {
diff --git a/internal/game/cmd_shop.go b/internal/game/cmd_shop.go
index 69de750..e47c84d 100644
--- a/internal/game/cmd_shop.go
+++ b/internal/game/cmd_shop.go
@@ -8,39 +8,72 @@ import (
"thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/item"
"thehouseoficarus/internal/net"
+ "thehouseoficarus/internal/player"
"thehouseoficarus/internal/ui"
+ "thehouseoficarus/internal/world"
)
-func (g *Game) handleShopInput(sess *net.Session, input string) {
- if sess.Player == nil {
- sess.State = net.StateGame
- g.writePrompt(sess)
+// ---- Command entry points ----
+
+func (g *Game) executeShopList(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ if p == nil {
return
}
-
- input = strings.TrimSpace(input)
- parts := strings.Fields(strings.ToLower(input))
- if len(parts) == 0 {
- g.showShopBrowse(sess)
+ mob := g.resolveShopMob(sess, p, strings.Join(args, " "))
+ if mob == nil {
return
}
+ g.showShopInventory(sess, mob)
+}
- cmd := parts[0]
+func (g *Game) executeShopBuy(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ if p == nil {
+ return
+ }
+ itemArgs, mobName := splitOnKeyword(args, "from")
+ qty, itemName, all := parseShopQty(itemArgs)
+ if itemName == "" {
+ sess.WriteLine("Buy what? Use 'list' to see what's for sale.")
+ return
+ }
+ mob := g.resolveShopMob(sess, p, mobName)
+ if mob == nil {
+ return
+ }
+ g.doShopBuy(sess, mob, itemName, qty, all)
+}
- switch cmd {
- case "buy":
- g.cmdShopBuy(sess, parts[1:])
- case "sell":
- g.cmdShopSell(sess, parts[1:])
- case "browse", "list":
- g.showShopBrowse(sess)
- case "leave", "bye", "exit", "quit":
- g.leaveShop(sess)
+func (g *Game) executeShopSell(sess *net.Session, args []string, rawInput string) {
+ p := sess.Player
+ if p == nil {
+ return
+ }
+ itemArgs, mobName := splitOnKeyword(args, "to")
+ qty, itemName, all := parseShopQty(itemArgs)
+ if itemName == "" {
+ sess.WriteLine("Sell what?")
+ return
+ }
+ mob := g.resolveShopMob(sess, p, mobName)
+ if mob == nil {
return
- default:
- sess.WriteLine("Commands: buy <item>, sell <item>, browse, leave")
}
- g.writeShopPrompt(sess)
+ g.doShopSell(sess, mob, itemName, qty, all)
+}
+
+// ---- Helpers ----
+
+// splitOnKeyword splits args around the first standalone keyword (e.g. "from",
+// "to"), returning the words before it and the joined words after it.
+func splitOnKeyword(args []string, kw string) (before []string, after string) {
+ for i, a := range args {
+ if strings.EqualFold(a, kw) {
+ return args[:i], strings.Join(args[i+1:], " ")
+ }
+ }
+ return args, ""
}
func parseShopQty(args []string) (int, string, bool) {
@@ -48,8 +81,7 @@ func parseShopQty(args []string) (int, string, bool) {
return 1, "", false
}
if strings.EqualFold(args[0], "all") {
- rest := strings.Join(args[1:], " ")
- return 0, rest, true
+ return 0, strings.Join(args[1:], " "), true
}
if n, err := strconv.Atoi(args[0]); err == nil && n > 0 {
return n, strings.Join(args[1:], " "), false
@@ -57,63 +89,100 @@ func parseShopQty(args []string) (int, string, bool) {
return 1, strings.Join(args, " "), false
}
-func (g *Game) cmdShopBuy(sess *net.Session, args []string) {
- cfg := g.shopConfig(sess)
- if cfg == nil {
- return
+// resolveShopMob finds the shop-owning mob in the player's room. If name is
+// empty and exactly one shop exists it is returned; multiple shops prompt
+// "Which shop?". A non-empty name matches a specific mob. Errors are written to
+// the session and nil is returned when resolution fails.
+func (g *Game) resolveShopMob(sess *net.Session, p *player.Player, name string) *world.MobInstance {
+ var shops []*world.MobInstance
+ for _, m := range g.MobStore.MobsInRoom(p.RoomID) {
+ if m.HasShop() {
+ shops = append(shops, m)
+ }
}
-
- qty, itemName, all := parseShopQty(args)
- if itemName == "" {
- sess.WriteLine("Buy what? Use 'browse' to see available items.")
- return
+ if len(shops) == 0 {
+ sess.WriteLine("There is no shop here.")
+ return nil
}
- g.doShopBuy(sess, itemName, qty, all)
-}
-
-func (g *Game) cmdShopSell(sess *net.Session, args []string) {
- cfg := g.shopConfig(sess)
- if cfg == nil {
- return
+ name = strings.TrimSpace(name)
+ if name != "" {
+ var matched []*world.MobInstance
+ for _, m := range shops {
+ if m.MatchQuality(name) > world.MatchNone {
+ matched = append(matched, m)
+ }
+ }
+ if len(matched) == 0 {
+ sess.WriteLine(fmt.Sprintf("There is no shop here run by \"%s\".", name))
+ return nil
+ }
+ if len(matched) > 1 {
+ g.promptWhichShop(sess, matched)
+ return nil
+ }
+ return matched[0]
}
- qty, itemName, all := parseShopQty(args)
- if itemName == "" {
- sess.WriteLine("Sell what?")
- return
+ if len(shops) == 1 {
+ return shops[0]
}
-
- g.doShopSell(sess, itemName, qty, all)
-}
-
-func (g *Game) writeShopPrompt(sess *net.Session) {
- sess.WritePrompt(g.colorize(sess, "dialog", "> "))
+ g.promptWhichShop(sess, shops)
+ return nil
}
-func (g *Game) shopConfig(sess *net.Session) *behavior.ShopConfig {
- cfg := sess.Shop
- return cfg
+func (g *Game) promptWhichShop(sess *net.Session, shops []*world.MobInstance) {
+ sess.WriteLine("Which shop? Try again naming one of:")
+ for _, m := range shops {
+ sess.WriteLine(" - " + m.Name)
+ }
}
-type shopMatch struct {
- si *behavior.ShopItem
- name string
- def *item.ItemDef
+type shopCandidate struct {
+ itemID string
+ def *item.ItemDef
+ name string
+ stock int
}
-func (g *Game) showShopBrowse(sess *net.Session) {
- cfg := g.shopConfig(sess)
- if cfg == nil {
- return
+// shopCandidates lists everything the shop currently offers for sale: its
+// configured items (even when out of stock) plus any dynamically-held items
+// with stock remaining.
+func (g *Game) shopCandidates(mob *world.MobInstance) []shopCandidate {
+ stock := g.MobStore.ShopStockSnapshot(mob.InstanceID)
+ seen := map[string]bool{}
+ var out []shopCandidate
+ add := func(id string) {
+ if id == "" || seen[id] {
+ return
+ }
+ seen[id] = true
+ def, _ := g.ItemStore.Load(id)
+ name := id
+ if def != nil {
+ name = def.Name
+ }
+ out = append(out, shopCandidate{itemID: id, def: def, name: name, stock: stock[id]})
+ }
+ for i := range mob.Shop.Items {
+ add(mob.Shop.Items[i].ItemID)
}
+ for id, n := range stock {
+ if n > 0 {
+ add(id)
+ }
+ }
+ return out
+}
- if cfg.Message != "" {
- sess.WriteLine(fmt.Sprintf("%s", g.colorize(sess, "dialog", cfg.Message)))
+func (g *Game) showShopInventory(sess *net.Session, mob *world.MobInstance) {
+ if mob.Shop.Message != "" {
+ sess.WriteLine(g.colorize(sess, "dialog", mob.Shop.Message))
}
- if len(cfg.Items) == 0 {
- sess.WriteLine("This shop has nothing for sale.")
+ candidates := g.shopCandidates(mob)
+ if len(candidates) == 0 {
+ sess.WriteLine(fmt.Sprintf("%s has nothing for sale.", mob.Name))
return
}
@@ -125,199 +194,113 @@ func (g *Game) showShopBrowse(sess *net.Session) {
}
t := ui.Table{
- Title: "Shop Inventory",
- Columns: []string{"Item", "Buy Price", "Sell Price"},
+ Title: mob.Name,
+ Columns: []string{"Item", "Stock", "Buy", "Sell"},
}
-
- for _, si := range cfg.Items {
- def, _ := g.ItemStore.Load(si.ItemID)
- itemName := si.ItemID
- if def != nil {
- itemName = def.Name
+ for _, c := range candidates {
+ value := 0
+ if c.def != nil {
+ value = c.def.Value
}
- buyStr := fmt.Sprintf("%d cr", si.BuyPrice)
sellStr := "\u2014"
- if si.SellPrice > 0 {
- sellStr = fmt.Sprintf("%d cr", si.SellPrice)
+ if mob.Shop.FindItem(c.itemID) != nil || mob.Shop.Buys() {
+ sellStr = fmt.Sprintf("%d cr", mob.Shop.SellPrice(value, c.stock))
}
t.Rows = append(t.Rows, []string{
- g.itemColorize(sess, def, itemName),
- g.colorize(sess, "credits_pickup", buyStr),
+ g.itemColorize(sess, c.def, c.name),
+ fmt.Sprintf("%d", c.stock),
+ g.colorize(sess, "credits_pickup", fmt.Sprintf("%d cr", value)),
g.colorize(sess, "credits_pickup", sellStr),
})
}
-
for _, line := range t.Render(unicode, wrapWidth) {
sess.WriteLine(line)
}
}
-func (g *Game) findShopMatches(input string, cfg *behavior.ShopConfig) []shopMatch {
- var matches []shopMatch
- for i := range cfg.Items {
- si := &cfg.Items[i]
- itemName := si.ItemID
- def, _ := g.ItemStore.Load(si.ItemID)
- if def != nil {
- itemName = def.Name
+func (g *Game) doShopBuy(sess *net.Session, mob *world.MobInstance, input string, qty int, all bool) {
+ var matches []shopCandidate
+ for _, c := range g.shopCandidates(mob) {
+ if behavior.WordPrefixMatch(input, c.name) {
+ matches = append(matches, c)
}
- if behavior.WordPrefixMatch(input, itemName) {
- matches = append(matches, shopMatch{si: si, name: itemName, def: def})
- }
- }
- return matches
-}
-
-func (g *Game) doShopBuy(sess *net.Session, input string, qty int, all bool) {
- cfg := g.shopConfig(sess)
- if cfg == nil {
- return
}
-
- p := sess.Player
-
- matches := g.findShopMatches(input, cfg)
if len(matches) == 0 {
- sess.WriteLine("That item isn't for sale here. Use 'browse' to see shop inventory.")
+ sess.WriteLine("That item isn't for sale here. Use 'list' to see the inventory.")
return
}
-
if len(matches) > 1 {
sess.WriteLine("That's ambiguous, which one?")
for _, m := range matches {
- sess.WriteLine(fmt.Sprintf(" - %s", g.itemColorize(sess, m.def, m.name)))
+ sess.WriteLine(" - " + g.itemColorize(sess, m.def, m.name))
}
return
}
- si := matches[0].si
- def := matches[0].def
-
- actualQty := qty
- if all || qty == 0 {
- affordable := p.Credits / si.BuyPrice
- if def != nil && def.Stackable {
- actualQty = affordable
- } else {
- freeSlots := p.FreeSlots()
- actualQty = affordable
- if actualQty > freeSlots {
- actualQty = freeSlots
- }
- }
+ c := matches[0]
+ p := sess.Player
+ price := 0
+ if c.def != nil {
+ price = c.def.Value
}
- if actualQty <= 0 {
- if p.Credits < si.BuyPrice {
- sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", si.BuyPrice, p.Credits))
- } else {
- sess.WriteLine("Your inventory is full.")
- }
+ stock := g.MobStore.ShopStockOf(mob.InstanceID, c.itemID)
+ if stock <= 0 {
+ sess.WriteLine(fmt.Sprintf("%s is out of stock.", g.itemColorize(sess, c.def, c.name)))
return
}
- g.buyShopItem(sess, *si, actualQty)
-}
-
-func (g *Game) buyShopItem(sess *net.Session, si behavior.ShopItem, qty int) {
- p := sess.Player
-
- totalCost := si.BuyPrice * qty
- if p.Credits < totalCost {
- qty = p.Credits / si.BuyPrice
- if qty <= 0 {
- sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", si.BuyPrice, p.Credits))
- return
- }
- totalCost = si.BuyPrice * qty
+ const unlimited = 1 << 30
+ want := qty
+ if all || qty == 0 {
+ want = unlimited
}
-
- def, _ := g.ItemStore.Load(si.ItemID)
- displayName := si.ItemID
- if def != nil {
- displayName = def.Name
+ if want > stock {
+ want = stock
}
-
- if def != nil && def.Stackable {
- for i := 0; i < 28; i++ {
- slot := p.InvSlot(i)
- if slot != nil && slot.ItemID == si.ItemID {
- p.Credits -= totalCost
- slot.Quantity += qty
- g.AccountStore.SaveCharacter(p)
- itemColor := g.itemColorize(sess, def, displayName)
- if qty == 1 {
- sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice))))
- } else {
- sess.WriteLine(fmt.Sprintf("You buy %d %s for %s credits.", qty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCost))))
- }
- return
- }
- }
-
- freeSlot := p.FirstFreeSlot()
- if freeSlot == -1 {
- sess.WriteLine("Your inventory is full.")
- return
- }
-
- p.Credits -= totalCost
- p.SetInvSlot(freeSlot, g.newInventorySlot(si.ItemID, qty))
- g.AccountStore.SaveCharacter(p)
-
- itemColor := g.itemColorize(sess, def, displayName)
- if qty == 1 {
- sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice))))
- } else {
- sess.WriteLine(fmt.Sprintf("You buy %d %s for %s credits.", qty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCost))))
- }
- return
+ capacity := g.shopInvCapacity(p, c.itemID, c.def)
+ if want > capacity {
+ want = capacity
}
-
- freeSlots := p.FreeSlots()
- if qty > freeSlots {
- if freeSlots == 0 {
- sess.WriteLine("Your inventory is full.")
- return
- }
- qty = freeSlots
- totalCost = si.BuyPrice * qty
- if p.Credits < totalCost {
- qty = p.Credits / si.BuyPrice
- totalCost = si.BuyPrice * qty
+ if price > 0 {
+ if afford := p.Credits / price; want > afford {
+ want = afford
}
}
- if qty <= 0 {
- if p.Credits < si.BuyPrice {
- sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", si.BuyPrice, p.Credits))
- } else {
+ if want <= 0 {
+ switch {
+ case capacity <= 0:
sess.WriteLine("Your inventory is full.")
+ case price > 0 && p.Credits < price:
+ sess.WriteLine(fmt.Sprintf("You need %d credits to buy that (you have %d).", price, p.Credits))
+ default:
+ sess.WriteLine("You can't buy that right now.")
}
return
}
- p.Credits -= totalCost
- for j := 0; j < qty; j++ {
- freeSlot := p.FirstFreeSlot()
- p.SetInvSlot(freeSlot, g.newInventorySlot(si.ItemID, 1))
+ taken := g.MobStore.ShopTake(mob.InstanceID, c.itemID, want)
+ if taken <= 0 {
+ sess.WriteLine(fmt.Sprintf("%s is out of stock.", g.itemColorize(sess, c.def, c.name)))
+ return
}
+
+ total := price * taken
+ p.Credits -= total
+ g.giveShopItems(p, c.itemID, taken, c.def)
g.AccountStore.SaveCharacter(p)
- itemColor := g.itemColorize(sess, def, displayName)
- if qty == 1 {
- sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", si.BuyPrice))))
+ itemColor := g.itemColorize(sess, c.def, c.name)
+ credits := g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", total))
+ if taken == 1 {
+ sess.WriteLine(fmt.Sprintf("You buy a %s for %s credits.", itemColor, credits))
} else {
- sess.WriteLine(fmt.Sprintf("You buy %d %s for %s credits.", qty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCost))))
+ sess.WriteLine(fmt.Sprintf("You buy %d %s for %s credits.", taken, itemColor, credits))
}
}
-func (g *Game) doShopSell(sess *net.Session, input string, qty int, all bool) {
- cfg := g.shopConfig(sess)
- if cfg == nil {
- return
- }
-
+func (g *Game) doShopSell(sess *net.Session, mob *world.MobInstance, input string, qty int, all bool) {
p := sess.Player
matches := g.findInventoryMatches(input, p)
@@ -325,35 +308,28 @@ func (g *Game) doShopSell(sess *net.Session, input string, qty int, all bool) {
sess.WriteLine("You don't have that item.")
return
}
-
if len(matches) > 1 {
sess.WriteLine("That's ambiguous, which one?")
for _, m := range matches {
def, _ := g.ItemStore.Load(m.ID)
- sess.WriteLine(fmt.Sprintf(" - %s", g.itemColorize(sess, def, m.Name)))
+ sess.WriteLine(" - " + g.itemColorize(sess, def, m.Name))
}
return
}
match := matches[0]
+ def, _ := g.ItemStore.Load(match.ID)
+ displayName := match.Name
+ itemColor := g.itemColorize(sess, def, displayName)
- var shopItem *behavior.ShopItem
- for i := range cfg.Items {
- si := &cfg.Items[i]
- if si.ItemID == match.ID && si.SellPrice > 0 {
- shopItem = si
- break
- }
+ if mob.Shop.FindItem(match.ID) == nil && !mob.Shop.Buys() {
+ sess.WriteLine(fmt.Sprintf("%s doesn't want to buy %s.", mob.Name, itemColor))
+ return
}
- if shopItem == nil {
- def, _ := g.ItemStore.Load(match.ID)
- displayName := match.ID
- if def != nil {
- displayName = def.Name
- }
- sess.WriteLine(fmt.Sprintf("The shop doesn't want to buy %s.", g.itemColorize(sess, def, displayName)))
- return
+ value := 0
+ if def != nil {
+ value = def.Value
}
totalInInv := p.CountItem(match.ID)
@@ -363,79 +339,66 @@ func (g *Game) doShopSell(sess *net.Session, input string, qty int, all bool) {
} else if actualQty > totalInInv {
actualQty = totalInInv
}
-
if actualQty <= 0 {
sess.WriteLine("You don't have that item.")
return
}
+ stock := g.MobStore.ShopStockOf(mob.InstanceID, match.ID)
+ total := 0
+ for k := 0; k < actualQty; k++ {
+ total += mob.Shop.SellPrice(value, stock+k)
+ }
+
p.RemoveItem(match.ID, actualQty)
- p.Credits += shopItem.SellPrice * actualQty
+ p.Credits += total
+ g.MobStore.ShopAdd(mob.InstanceID, match.ID, actualQty)
g.AccountStore.SaveCharacter(p)
- def, _ := g.ItemStore.Load(match.ID)
- displayName := match.Name
- itemColor := g.itemColorize(sess, def, displayName)
+ credits := g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", total))
if actualQty == 1 {
- sess.WriteLine(fmt.Sprintf("You sell a %s for %s credits.", itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", shopItem.SellPrice))))
+ sess.WriteLine(fmt.Sprintf("You sell a %s for %s credits.", itemColor, credits))
} else {
- totalCredits := shopItem.SellPrice * actualQty
- sess.WriteLine(fmt.Sprintf("You sell %d %s for %s credits.", actualQty, itemColor, g.colorize(sess, "credits_pickup", fmt.Sprintf("%d", totalCredits))))
+ sess.WriteLine(fmt.Sprintf("You sell %d %s for %s credits.", actualQty, itemColor, credits))
}
}
-func (g *Game) leaveShop(sess *net.Session) {
- sess.Shop = nil
- p := sess.Player
- if p == nil || p.Action == nil || p.Action.Type != "talk" {
- sess.State = net.StateGame
- if p != nil {
- g.cancelAction(p)
+// shopInvCapacity reports how many of an item the player can still carry.
+func (g *Game) shopInvCapacity(p *player.Player, itemID string, def *item.ItemDef) int {
+ if def != nil && def.Stackable {
+ for i := 0; i < 28; i++ {
+ if slot := p.InvSlot(i); slot != nil && slot.ItemID == itemID {
+ return 1 << 30
+ }
}
- g.writePrompt(sess)
- return
- }
-
- td, ok := p.Action.Data.(*behavior.TalkData)
- if !ok || td.Cfg == nil {
- sess.State = net.StateGame
- g.cancelAction(p)
- g.writePrompt(sess)
- return
+ if p.FirstFreeSlot() != -1 {
+ return 1 << 30
+ }
+ return 0
}
+ return p.FreeSlots()
+}
- node, ok := td.Cfg.Nodes[td.Node]
- if !ok {
- sess.State = net.StateGame
- g.cancelAction(p)
- g.writePrompt(sess)
+// giveShopItems places qty of an item into the player's inventory, stacking when
+// possible. Callers must ensure capacity via shopInvCapacity first.
+func (g *Game) giveShopItems(p *player.Player, itemID string, qty int, def *item.ItemDef) {
+ if def != nil && def.Stackable {
+ for i := 0; i < 28; i++ {
+ if slot := p.InvSlot(i); slot != nil && slot.ItemID == itemID {
+ slot.Quantity += qty
+ return
+ }
+ }
+ if slot := p.FirstFreeSlot(); slot != -1 {
+ p.SetInvSlot(slot, g.newInventorySlot(itemID, qty))
+ }
return
}
-
- for {
- visible := g.visibleOptions(sess, node.Options)
- if len(visible) > 0 {
- for i, opt := range visible {
- sess.WriteLine(fmt.Sprintf(" %d. %s", i+1, opt.Text))
- }
- sess.State = net.StateTalk
- g.reprompt(sess)
+ for j := 0; j < qty; j++ {
+ slot := p.FirstFreeSlot()
+ if slot == -1 {
return
}
-
- if node.Goto != "" {
- next, ok := td.Cfg.Nodes[node.Goto]
- if !ok {
- break
- }
- td.Node = node.Goto
- node = next
- } else {
- break
- }
+ p.SetInvSlot(slot, g.newInventorySlot(itemID, 1))
}
-
- sess.State = net.StateGame
- g.cancelAction(p)
- g.writePrompt(sess)
}
diff --git a/internal/game/game.go b/internal/game/game.go
index c30f9aa..4cd3b30 100644
--- a/internal/game/game.go
+++ b/internal/game/game.go
@@ -56,14 +56,14 @@ type Game struct {
queue *CommandQueue
safespot *SafespotManager
ValidationConfig config.ValidationConfig
- StartingRoom int
+ StartingRoom int
// Per-tick / per-session runtime bookkeeping.
charsMu sync.Mutex
loggedInChars map[string]*net.Session
restTimers map[string]uint64
guardWatchTimers map[string]int
- hackingStates map[string]*hacking.Session
+ hackingStates map[string]*hacking.Session
pendingDepletions []pendingDepletion
farmTickCounter int
enterMu sync.Mutex
@@ -162,8 +162,6 @@ func (g *Game) HandleSession(sess *net.Session, input string) {
g.handleDescChange(sess, input)
case net.StateTalk, net.StateTalkSequence:
g.handleTalkInput(sess, input)
- case net.StateShop:
- g.handleShopInput(sess, input)
case net.StateBank:
g.handleBankInput(sess, input)
case net.StateDropAllConfirm:
diff --git a/internal/net/server.go b/internal/net/server.go
index d212dbf..11339d7 100644
--- a/internal/net/server.go
+++ b/internal/net/server.go
@@ -14,7 +14,6 @@ import (
"sync"
"time"
- "thehouseoficarus/internal/behavior"
"thehouseoficarus/internal/color"
"thehouseoficarus/internal/config"
"thehouseoficarus/internal/player"
@@ -47,7 +46,6 @@ const (
StateCraftProduct
StateProductChoice
StateColorChoice
- StateShop
StateBank
StateHacking
StateDangerConfirm
@@ -68,7 +66,6 @@ type Session struct {
PendingBackground bool
Disconnecting bool
DisconnectTicks int
- Shop *behavior.ShopConfig
PendingUndigDir string
PendingUndigRoom int
promptVisible bool
@@ -76,14 +73,14 @@ type Session struct {
}
type Server struct {
- config *config.Config
- telnetLn net.Listener
- telnetTLSLn net.Listener
- httpServer *http.Server
- httpsServer *http.Server
+ config *config.Config
+ telnetLn net.Listener
+ telnetTLSLn net.Listener
+ httpServer *http.Server
+ httpsServer *http.Server
httpsTLSConfig *tls.Config
- hub *Hub
- handler func(*Session, string)
+ hub *Hub
+ handler func(*Session, string)
}
type Hub struct {
diff --git a/internal/validate/checks.go b/internal/validate/checks.go
index b35a3de..cdeee44 100644
--- a/internal/validate/checks.go
+++ b/internal/validate/checks.go
@@ -377,6 +377,44 @@ func validateMobs(s Source) []Issue {
id, def.FinishingBlow),
})
}
+
+ if def.Shop != nil {
+ if def.Shop.BuyPercentage < 0 || def.Shop.ChangePercentage < 0 {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: shop buy_percentage/change_percentage must not be negative", id),
+ })
+ }
+ seenShop := map[string]bool{}
+ for _, si := range def.Shop.Items {
+ if si.ItemID == "" {
+ continue
+ }
+ if !itemIDs[si.ItemID] {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: shop item %q does not exist", id, si.ItemID),
+ })
+ }
+ if seenShop[si.ItemID] {
+ issues = append(issues, Issue{
+ Level: "WARN",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: shop lists item %q more than once", id, si.ItemID),
+ })
+ }
+ seenShop[si.ItemID] = true
+ if si.Stock < 0 {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Mob %q: shop item %q has negative stock", id, si.ItemID),
+ })
+ }
+ }
+ }
}
return issues
@@ -865,7 +903,6 @@ func validateRoomWiring(s Source) []Issue {
return issues
}
-
// validateRoomGrid embeds the entire reachable world on a single 3D grid
// starting from the configured root rooms and reports when the exit layout
// cannot be embedded without conflict:
@@ -1170,18 +1207,5 @@ func validateNodeAction(prefix string, na *behavior.NodeAction, itemIDs map[stri
}
}
- if na.Shop != nil {
- for _, si := range na.Shop.Items {
- if si.ItemID != "" && !itemIDs[si.ItemID] {
- issues = append(issues, Issue{
- Level: "ERROR",
- Type: "reference",
- Message: fmt.Sprintf("%s: shop item %q does not exist",
- prefix, si.ItemID),
- })
- }
- }
- }
-
return issues
}
diff --git a/internal/world/mob.go b/internal/world/mob.go
index cc2d2b2..58bf769 100644
--- a/internal/world/mob.go
+++ b/internal/world/mob.go
@@ -68,10 +68,13 @@ type MobDef struct {
CompleteMessage string `yaml:"complete_message"` // shown on completion
Talk *behavior.TalkConfig `yaml:"talk,omitempty"`
+ Shop *behavior.ShopConfig `yaml:"shop,omitempty"`
}
func (d *MobDef) IsTalkable() bool { return d.Talk != nil }
+func (d *MobDef) HasShop() bool { return d.Shop != nil }
+
func (d *MobDef) IsTask() bool { return d.Kind == "task" }
type MobInstance struct {
@@ -128,17 +131,26 @@ type MobInstance struct {
TalkConfig *behavior.TalkConfig
- Owner string
- OwnerOnly bool
- SpawnedByTrigger bool
- DespawnOnLeave bool
- DespawnRooms []int
- DespawnTicks float64
- DespawnCounter int
+ // Shop is the (shared, read-only) shop config from the def. Per-instance
+ // live stock is tracked in ShopStock; ShopRestock holds per-item countdown
+ // timers. All three are only mutated under MobStore.mu.
+ Shop *behavior.ShopConfig
+ ShopStock map[string]int
+ ShopRestock map[string]int
+
+ Owner string
+ OwnerOnly bool
+ SpawnedByTrigger bool
+ DespawnOnLeave bool
+ DespawnRooms []int
+ DespawnTicks float64
+ DespawnCounter int
}
func (m *MobInstance) IsTalkable() bool { return m.TalkConfig != nil }
+func (m *MobInstance) HasShop() bool { return m.Shop != nil }
+
func (m *MobInstance) IsTask() bool { return m.Kind == "task" }
func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []int, wanderInterval float64) *MobInstance {
@@ -146,7 +158,7 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in
if def.Protected && hp <= 0 {
hp = 1
}
- return &MobInstance{
+ inst := &MobInstance{
InstanceID: instanceID,
DefID: def.ID,
Name: def.Name,
@@ -191,6 +203,20 @@ func NewMobInstance(def *MobDef, instanceID string, roomID int, wanderRooms []in
CompleteMessage: def.CompleteMessage,
TalkConfig: def.Talk,
}
+ if def.Shop != nil {
+ inst.Shop = def.Shop
+ inst.ShopStock = make(map[string]int)
+ inst.ShopRestock = make(map[string]int)
+ for i := range def.Shop.Items {
+ it := &def.Shop.Items[i]
+ if it.ItemID == "" {
+ continue
+ }
+ inst.ShopStock[it.ItemID] = it.Stock
+ inst.ShopRestock[it.ItemID] = def.Shop.RestockInterval(it)
+ }
+ }
+ return inst
}
const (
@@ -330,6 +356,9 @@ func (s *MobStore) Tick() {
defer s.mu.Unlock()
for _, inst := range s.instances {
+ if inst.Shop != nil {
+ s.tickShopLocked(inst)
+ }
if inst.HP <= 0 || inst.HP >= inst.MaxHP {
inst.regenerateTick = 0
continue
@@ -347,6 +376,115 @@ func (s *MobStore) Tick() {
}
}
+// tickShopLocked advances restock timers for one shop instance. Current stock
+// converges toward each item's target (configured stock for listed items, 0 for
+// dynamically-acquired items) one unit per restock interval, in either
+// direction. Caller must hold s.mu.
+func (s *MobStore) tickShopLocked(inst *MobInstance) {
+ for id, cur := range inst.ShopStock {
+ target := 0
+ var cfgItem *behavior.ShopItem
+ if cfgItem = inst.Shop.FindItem(id); cfgItem != nil {
+ target = cfgItem.Stock
+ }
+ if cur == target {
+ if cur == 0 && cfgItem == nil {
+ delete(inst.ShopStock, id)
+ delete(inst.ShopRestock, id)
+ }
+ continue
+ }
+ c := inst.ShopRestock[id]
+ if c <= 0 {
+ c = inst.Shop.RestockInterval(cfgItem)
+ }
+ c--
+ if c <= 0 {
+ if cur < target {
+ cur++
+ } else {
+ cur--
+ }
+ inst.ShopStock[id] = cur
+ c = inst.Shop.RestockInterval(cfgItem)
+ }
+ inst.ShopRestock[id] = c
+ if cur == 0 && cfgItem == nil {
+ delete(inst.ShopStock, id)
+ delete(inst.ShopRestock, id)
+ }
+ }
+}
+
+// ShopStockSnapshot returns a copy of the instance's current stock map.
+func (s *MobStore) ShopStockSnapshot(instanceID string) map[string]int {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ inst := s.instances[instanceID]
+ if inst == nil || inst.ShopStock == nil {
+ return nil
+ }
+ out := make(map[string]int, len(inst.ShopStock))
+ for k, v := range inst.ShopStock {
+ out[k] = v
+ }
+ return out
+}
+
+// ShopStockOf returns the current stock the instance holds of an item.
+func (s *MobStore) ShopStockOf(instanceID, itemID string) int {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ inst := s.instances[instanceID]
+ if inst == nil || inst.ShopStock == nil {
+ return 0
+ }
+ return inst.ShopStock[itemID]
+}
+
+// ShopTake removes up to qty of an item from the shop's stock, returning the
+// amount actually taken (capped by availability). Used when a player buys.
+func (s *MobStore) ShopTake(instanceID, itemID string, qty int) int {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ inst := s.instances[instanceID]
+ if inst == nil || inst.ShopStock == nil {
+ return 0
+ }
+ have := inst.ShopStock[itemID]
+ if qty > have {
+ qty = have
+ }
+ if qty <= 0 {
+ return 0
+ }
+ inst.ShopStock[itemID] = have - qty
+ return qty
+}
+
+// ShopAdd adds qty of an item to the shop's stock. Used when a player sells.
+func (s *MobStore) ShopAdd(instanceID, itemID string, qty int) {
+ if qty <= 0 {
+ return
+ }
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ inst := s.instances[instanceID]
+ if inst == nil || inst.Shop == nil {
+ return
+ }
+ if inst.ShopStock == nil {
+ inst.ShopStock = make(map[string]int)
+ }
+ if inst.ShopRestock == nil {
+ inst.ShopRestock = make(map[string]int)
+ }
+ if _, ok := inst.ShopStock[itemID]; !ok {
+ inst.ShopRestock[itemID] = inst.Shop.RestockInterval(inst.Shop.FindItem(itemID))
+ }
+ inst.ShopStock[itemID] += qty
+}
+
func (s *MobStore) RollIdleDescription(inst *MobInstance) {
def, err := s.LoadDef(inst.DefID)
if err != nil {