aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-30 02:17:22 -0400
committerhistoria <[not public]>2026-06-30 02:17:22 -0400
commit7a4b91304061777e7b3365b505d1e74878043d35 (patch)
tree4440b95c6ed4425f19919a4e74890607d008feec
parent069d3c1c81d71042706372df153254487a765dfc (diff)
downloadthehouseoficarus-7a4b91304061777e7b3365b505d1e74878043d35.tar.gz
feat: web gui mapping and obj/item/mob placement improvements
-rw-r--r--config.yaml2
-rw-r--r--data/.admin_history.yaml2
-rw-r--r--data/rooms/example_unused/town/9999917.yaml20
-rw-r--r--data/rooms/example_unused/town/999997.yaml16
-rw-r--r--data/rooms/intro/1.yaml16
-rw-r--r--data/rooms/intro/10.yaml12
-rw-r--r--data/rooms/intro/1008.yaml2
-rw-r--r--data/rooms/intro/1010.yaml12
-rw-r--r--data/rooms/intro/1012.yaml4
-rw-r--r--data/rooms/intro/1013.yaml10
-rw-r--r--data/rooms/intro/1014.yaml12
-rw-r--r--data/rooms/intro/1016.yaml10
-rw-r--r--data/rooms/intro/1018.yaml8
-rw-r--r--data/rooms/intro/12.yaml4
-rw-r--r--data/rooms/intro/2.yaml16
-rw-r--r--data/rooms/intro/3.yaml18
-rw-r--r--data/rooms/intro/4.yaml16
-rw-r--r--data/rooms/intro/5.yaml6
-rw-r--r--data/rooms/intro/6.yaml4
-rw-r--r--data/rooms/intro/7.yaml4
-rw-r--r--data/rooms/intro/8.yaml14
-rw-r--r--data/rooms/intro/9.yaml12
-rw-r--r--internal/admin/api_map.go81
-rw-r--r--internal/admin/api_rooms.go192
-rw-r--r--internal/admin/server.go25
-rw-r--r--internal/admin/static/admin.css42
-rw-r--r--internal/admin/static/map.js853
-rw-r--r--internal/admin/templates/map.html8
-rw-r--r--internal/config/config.go10
29 files changed, 1159 insertions, 272 deletions
diff --git a/config.yaml b/config.yaml
index 55dfa3e..29056c1 100644
--- a/config.yaml
+++ b/config.yaml
@@ -28,8 +28,6 @@ https:
admin_https:
enabled: true
port: 9090
- admin_accounts:
- - admin
default_colors:
room_name: "51 bold"
diff --git a/data/.admin_history.yaml b/data/.admin_history.yaml
index 01b724b..dd085f6 100644
--- a/data/.admin_history.yaml
+++ b/data/.admin_history.yaml
@@ -1 +1 @@
-{"history":[{"time":"2026-06-29T20:43:59-04:00","description":"create room 1","file_path":"data/rooms/1.yaml","old_content":"","new_content":"exits:\n north: 1018\nid: 1\nname: 'New Room #1'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T20:44:31-04:00","description":"update room 1018 (add exit east to 1)","file_path":"data/rooms/intro/1018.yaml","old_content":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1\n northwest:\n room: 1016\n south:\n room: 1\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1\n northwest:\n room: 1016\n south:\n room: 1\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T20:44:31-04:00","description":"create room 1","file_path":"data/rooms/1.yaml","old_content":"","new_content":"exits:\n west: 1018\nid: 1\nname: 'New Room #1'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T20:44:39-04:00","description":"update room 1018 (add exit east to 1)","file_path":"data/rooms/intro/1018.yaml","old_content":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1\n northwest:\n room: 1016\n south:\n room: 1\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1\n northwest:\n room: 1016\n south:\n room: 1\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T20:44:39-04:00","description":"create room 1","file_path":"data/rooms/1.yaml","old_content":"","new_content":"exits:\n west: 1018\nid: 1\nname: 'New Room #1'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T20:44:43-04:00","description":"delete room 1","file_path":"data/rooms/1.yaml","old_content":"exits:\n west: 1018\nid: 1\nname: 'New Room #1'\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-29T20:44:46-04:00","description":"update room 1018 (add exit east to 1)","file_path":"data/rooms/intro/1018.yaml","old_content":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1\n northwest:\n room: 1016\n south:\n room: 1\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1\n northwest:\n room: 1016\n south:\n room: 1\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T20:44:46-04:00","description":"create room 1","file_path":"data/rooms/1.yaml","old_content":"","new_content":"exits:\n west: 1018\nid: 1\nname: 'New Room #1'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T20:47:34-04:00","description":"update room 1014 (add exit east to 1)","file_path":"data/rooms/intro/1014.yaml","old_content":"id: 1014\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1007\n up:\n room: 1015\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1014\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1\n north:\n room: 1007\n up:\n room: 1015\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T20:47:34-04:00","description":"create room 1","file_path":"data/rooms/1.yaml","old_content":"","new_content":"exits:\n west: 1014\nid: 1\nname: 'New Room #1'\n","is_delete":false,"is_create":true},{"time":"2026-06-30T00:52:27Z","description":"update room 1001 (add exit west to 1)","file_path":"data/rooms/intro/1001.yaml","old_content":"name: \"Inside Transport Shuttle\"\ndescription:\n - text: \"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 condition:\n player_flag: 1001_touchdown\n not: true\n - text: \"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.\"\nobjects:\n - name: framed sign\n hidden: true\n description: |-\n We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\n\n When 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\n Type {0C bold}what{/} for a list of possible common actions\n\n Make sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\n on_look:\n set_player_flags:\n 1001_look_sign: true\n - name: door\n hidden: true\n description: \"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: \"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: [cockpit]\n hidden: true\n description: \"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: \"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.\"\nmobs:\n - flight_attendant\nexits:\n east: 2001\n down:\n room: 1002\n condition:\n player_flag: 1001_touchdown\n blocked_message: \"The piston-powered staircase is firmly closed.\"\non_enter:\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 5\n message: \"{0B bold}Welcome to The House of Icarus{/}\"\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 7\n message: \"{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}\"\n set_player_flags:\n 1001_welcome: true\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 7\n message: \"{0B}Type{/} {0A}help newplayer{/} {0B}for the new player's guide. That's it. Explore and have fun.{/}\"\ntriggers:\n - on_player_flag: 1001_look_sign\n steps:\n - delay: 5\n message: \"The cabin shakes as the small craft touches down\"\n - delay: 5\n message: \"The pistons hiss as the rear staircase opens\"\n - set_player_flags:\n 1001_touchdown: true\nblock_transport: true\n","new_content":"id: 1001\nname: Inside Transport Shuttle\ncolor: \"\"\ndescription:\n - text: 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 condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_touchdown\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n - text: 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\n west:\n room: 1\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","is_delete":false,"is_create":false},{"time":"2026-06-30T00:52:27Z","description":"create room 1","file_path":"data/rooms/intro/1.yaml","old_content":"","new_content":"exits:\n east: 1001\nid: 1\nname: Test Room\n","is_delete":false,"is_create":true},{"time":"2026-06-30T00:53:43Z","description":"update room 1001 (add exit west to 1)","file_path":"data/rooms/intro/1001.yaml","old_content":"name: \"Inside Transport Shuttle\"\ndescription:\n - text: \"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 condition:\n player_flag: 1001_touchdown\n not: true\n - text: \"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.\"\nobjects:\n - name: framed sign\n hidden: true\n description: |-\n We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\n\n When 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\n Type {0C bold}what{/} for a list of possible common actions\n\n Make sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\n on_look:\n set_player_flags:\n 1001_look_sign: true\n - name: door\n hidden: true\n description: \"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: \"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: [cockpit]\n hidden: true\n description: \"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: \"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.\"\nmobs:\n - flight_attendant\nexits:\n east: 2001\n down:\n room: 1002\n condition:\n player_flag: 1001_touchdown\n blocked_message: \"The piston-powered staircase is firmly closed.\"\non_enter:\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 5\n message: \"{0B bold}Welcome to The House of Icarus{/}\"\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 7\n message: \"{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}\"\n set_player_flags:\n 1001_welcome: true\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 7\n message: \"{0B}Type{/} {0A}help newplayer{/} {0B}for the new player's guide. That's it. Explore and have fun.{/}\"\ntriggers:\n - on_player_flag: 1001_look_sign\n steps:\n - delay: 5\n message: \"The cabin shakes as the small craft touches down\"\n - delay: 5\n message: \"The pistons hiss as the rear staircase opens\"\n - set_player_flags:\n 1001_touchdown: true\nblock_transport: true\n","new_content":"id: 1001\nname: Inside Transport Shuttle\ncolor: \"\"\ndescription:\n - text: 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 condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_touchdown\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n - text: 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\n west:\n room: 1\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","is_delete":false,"is_create":false},{"time":"2026-06-30T00:53:43Z","description":"create room 1","file_path":"data/rooms/intro/1.yaml","old_content":"","new_content":"exits:\n east: 1001\nid: 1\nname: Test West Room\n","is_delete":false,"is_create":true},{"time":"2026-06-30T00:53:43Z","description":"delete room 1014","file_path":"data/rooms/intro/1014.yaml","old_content":"id: 1014\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1007\n up:\n room: 1015\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-29T21:12:59-04:00","description":"update room 2002 (add exit northwest to 1)","file_path":"data/rooms/intro/2002.yaml","old_content":"id: 2002\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n southeast:\n room: 2004\n west:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 2002\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:12:59-04:00","description":"create room 1","file_path":"data/rooms/intro/1.yaml","old_content":"","new_content":"exits:\n southeast: 2002\nid: 1\nname: 'Room #1'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:13:00-04:00","description":"update room 1 (add exit northwest to 2)","file_path":"data/rooms/intro/1.yaml","old_content":"exits:\n southeast: 2002\nid: 1\nname: 'Room #1'\n","new_content":"id: 1\nname: 'Room #1'\ncolor: \"\"\ndescription: []\nexits:\n northwest:\n room: 2\n southeast:\n room: 2002\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:13:00-04:00","description":"create room 2","file_path":"data/rooms/intro/2.yaml","old_content":"","new_content":"exits:\n southeast: 1\nid: 2\nname: 'Room #2'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:13:01-04:00","description":"update room 2 (add exit northeast to 3)","file_path":"data/rooms/intro/2.yaml","old_content":"exits:\n southeast: 1\nid: 2\nname: 'Room #2'\n","new_content":"id: 2\nname: 'Room #2'\ncolor: \"\"\ndescription: []\nexits:\n northeast:\n room: 3\n southeast:\n room: 1\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:13:01-04:00","description":"create room 3","file_path":"data/rooms/intro/3.yaml","old_content":"","new_content":"exits:\n southwest: 2\nid: 3\nname: 'Room #3'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:13:02-04:00","description":"update room 3 (add exit southeast to 4)","file_path":"data/rooms/intro/3.yaml","old_content":"exits:\n southwest: 2\nid: 3\nname: 'Room #3'\n","new_content":"id: 3\nname: 'Room #3'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 4\n southwest:\n room: 2\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:13:02-04:00","description":"create room 4","file_path":"data/rooms/intro/4.yaml","old_content":"","new_content":"exits:\n northwest: 3\nid: 4\nname: 'Room #4'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:13:03-04:00","description":"update room 4 (add exit west to 5)","file_path":"data/rooms/intro/4.yaml","old_content":"exits:\n northwest: 3\nid: 4\nname: 'Room #4'\n","new_content":"id: 4\nname: 'Room #4'\ncolor: \"\"\ndescription: []\nexits:\n northwest:\n room: 3\n west:\n room: 5\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:13:03-04:00","description":"create room 5","file_path":"data/rooms/intro/5.yaml","old_content":"","new_content":"exits:\n east: 4\nid: 5\nname: 'Room #5'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:24:30-04:00","description":"update room 1001 (add exit up to 6)","file_path":"data/rooms/intro/1001.yaml","old_content":"name: \"Inside Transport Shuttle\"\ndescription:\n - text: \"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 condition:\n player_flag: 1001_touchdown\n not: true\n - text: \"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.\"\nobjects:\n - name: framed sign\n hidden: true\n description: |-\n We hope you enjoy your trip on our shuttle! Your pilot is [TYLER].\n\n When 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\n Type {0C bold}what{/} for a list of possible common actions\n\n Make sure to check the {0A}help{/} pages, especially {0A}help newplayer{/}!\n on_look:\n set_player_flags:\n 1001_look_sign: true\n - name: door\n hidden: true\n description: \"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: \"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: [cockpit]\n hidden: true\n description: \"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: \"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.\"\nmobs:\n - flight_attendant\nexits:\n east: 2001\n down:\n room: 1002\n condition:\n player_flag: 1001_touchdown\n blocked_message: \"The piston-powered staircase is firmly closed.\"\non_enter:\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 5\n message: \"{0B bold}Welcome to The House of Icarus{/}\"\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 7\n message: \"{0B}Type{/} {0A}look sign{/} {0B}or{/} {0A}talk attendant{/} {0B}to get started{/}\"\n set_player_flags:\n 1001_welcome: true\n - condition:\n player_flag: 1001_welcome\n not: true\n delay: 7\n message: \"{0B}Type{/} {0A}help newplayer{/} {0B}for the new player's guide. That's it. Explore and have fun.{/}\"\ntriggers:\n - on_player_flag: 1001_look_sign\n steps:\n - delay: 5\n message: \"The cabin shakes as the small craft touches down\"\n - delay: 5\n message: \"The pistons hiss as the rear staircase opens\"\n - set_player_flags:\n 1001_touchdown: true\nblock_transport: true\n","new_content":"id: 1001\nname: Inside Transport Shuttle\ncolor: \"\"\ndescription:\n - text: 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 condition:\n flag: \"\"\n value: null\n not: true\n player_flag: 1001_touchdown\n has_item: \"\"\n min_credits: 0\n all_of: []\n any_of: []\n - text: 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\n up:\n room: 6\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","is_delete":false,"is_create":false},{"time":"2026-06-29T21:24:30-04:00","description":"create room 6","file_path":"data/rooms/intro/6.yaml","old_content":"","new_content":"exits:\n down: 1001\nid: 6\nname: 'Room #6'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:24:50-04:00","description":"update room 5","file_path":"data/rooms/intro/5.yaml","old_content":"exits:\n east: 4\nid: 5\nname: 'Room #5'\n","new_content":"exits:\n east: 4\n north:\n room: 3\nid: 5\nname: 'Room #5'\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:24:50-04:00","description":"update room 3","file_path":"data/rooms/intro/3.yaml","old_content":"id: 3\nname: 'Room #3'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 4\n southwest:\n room: 2\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 5\n southeast:\n room: 4\n southwest:\n room: 2\nhazard: \"\"\nid: 3\nitem_spawns: []\nmobs: []\nname: 'Room #3'\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:25:42-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"id: 1008\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n southeast:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:25:42-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 east:\n room: 1006\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":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:17-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:17-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"id: 2002\nname: Test Room\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:18-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:18-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:19-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:19-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:21-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:21-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:26-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:26-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:26-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:26-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:27-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:27-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:27-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:27-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:39:47-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"id: 2001\nname: Test Room\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\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\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","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:47-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:49-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 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: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:49-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:55-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 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","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:55-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:57-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 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: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:57-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:43:52-04:00","description":"update room 1008 (add exit north to 7)","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 1008\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:43:52-04:00","description":"create room 7","file_path":"data/rooms/intro/7.yaml","old_content":"","new_content":"exits:\n south: 1008\nid: 7\nname: 'Room #7'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:44:39-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"id: 1008\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: 00FFFF\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:45:30-04:00","description":"update room 1016","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southeast:\n room: 1018\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nhazard: \"\"\nid: 1016\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:45:56-04:00","description":"update room 1005","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nhazard: \"\"\nid: 1005\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:46:03-04:00","description":"update room 1009 (add exit northwest to 8)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n southwest:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n northwest:\n room: 8\n southwest:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:46:03-04:00","description":"create room 8","file_path":"data/rooms/intro/8.yaml","old_content":"","new_content":"exits:\n southeast: 1009\nid: 8\nname: 'Room #8'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:04:39-04:00","description":"link 1005 north 8","file_path":"data/rooms/intro/1005.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nhazard: \"\"\nid: 1005\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:39-04:00","description":"link 8 south 1005 (reverse)","file_path":"data/rooms/intro/8.yaml","old_content":"exits:\n southeast: 1009\nid: 8\nname: 'Room #8'\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:39-04:00","description":"unlink 8 south 1005","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:39-04:00","description":"unlink 1005 north 8 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:40-04:00","description":"link 1005 north 8","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:40-04:00","description":"link 8 south 1005 (reverse)","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:41-04:00","description":"unlink 8 south 1005","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:41-04:00","description":"unlink 1005 north 8 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"link 1005 north 8","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"link 8 south 1005 (reverse)","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"unlink 8 south 1005","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"unlink 1005 north 8 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:45-04:00","description":"link 1005 east 1009","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:45-04:00","description":"link 1009 west 1005 (reverse)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n northwest:\n room: 8\n southwest:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n northwest:\n room: 8\n southwest:\n room: 1006\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-29T22:04:48-04:00","description":"link 1016 southwest 1005","file_path":"data/rooms/intro/1016.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nhazard: \"\"\nid: 1016\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 1016\nname: New Room\ncolor: \"\"\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","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:48-04:00","description":"link 1005 northeast 1016 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:49-04:00","description":"unlink 1005 northeast 1016","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:49-04:00","description":"unlink 1016 southwest 1005 (reverse)","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\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":"id: 1016\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\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-29T22:04:51-04:00","description":"link 1016 southwest 1005","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1016\nname: New Room\ncolor: \"\"\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","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:53-04:00","description":"link 1016 southwest 1005","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\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":"id: 1016\nname: New Room\ncolor: \"\"\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","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:53-04:00","description":"link 1005 northeast 1016 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:05:39-04:00","description":"update room 1016","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\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":"block_transport: false\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nhazard: \"\"\nid: 1016\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:20:52-04:00","description":"update room 1007 (add exit east to 9)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 9\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:20:52-04:00","description":"create room 9","file_path":"data/rooms/intro/9.yaml","old_content":"","new_content":"exits:\n west: 1007\nid: 9\nname: 'Room #9'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:20:54-04:00","description":"unlink 9 west 1007","file_path":"data/rooms/intro/9.yaml","old_content":"exits:\n west: 1007\nid: 9\nname: 'Room #9'\n","new_content":"id: 9\nname: 'Room #9'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:20:54-04:00","description":"unlink 1007 east 9 (reverse)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 9\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:00-04:00","description":"update room 1007 (add exit east to 10)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 10\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:00-04:00","description":"create room 10","file_path":"data/rooms/intro/10.yaml","old_content":"","new_content":"exits:\n west: 1007\nid: 10\nname: 'Room #10'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:21:04-04:00","description":"unlink 1007 east 10","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 10\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:04-04:00","description":"unlink 10 west 1007 (reverse)","file_path":"data/rooms/intro/10.yaml","old_content":"exits:\n west: 1007\nid: 10\nname: 'Room #10'\n","new_content":"id: 10\nname: 'Room #10'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:41-04:00","description":"update room 1007 (add exit up to 11)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n up:\n room: 11\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:41-04:00","description":"create room 11","file_path":"data/rooms/intro/11.yaml","old_content":"","new_content":"exits:\n down: 1007\nid: 11\nname: 'Room #11'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:21:45-04:00","description":"update room 1007 (add exit down to 12)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n up:\n room: 11\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 12\n northwest:\n room: 1005\n south:\n room: 1014\n up:\n room: 11\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:45-04:00","description":"create room 12","file_path":"data/rooms/intro/12.yaml","old_content":"","new_content":"exits:\n up: 1007\nid: 12\nname: 'Room #12'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:29:07-04:00","description":"delete room 11","file_path":"data/rooms/intro/11.yaml","old_content":"exits:\n down: 1007\nid: 11\nname: 'Room #11'\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-29T22:29:12-04:00","description":"delete room 1015","file_path":"data/rooms/intro/1015.yaml","old_content":"id: 0\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1014\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-29T22:29:22-04:00","description":"unlink 1012 west 1011","file_path":"data/rooms/intro/1012.yaml","old_content":"id: 1012\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1013\n west:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1012\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1013\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false}],"redo":[{"time":"2026-06-29T22:29:22-04:00","description":"unlink 1011 east 1012 (reverse)","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\n south:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1011\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false}]} \ No newline at end of file
+{"history":[{"time":"2026-06-29T21:28:26-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:27-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:28:27-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:27-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:28:27-04:00","description":"update room 2003","file_path":"data/rooms/intro/2003.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 1006\n north:\n room: 1008\n northeast:\n room: 1005\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-06-29T21:39:47-04:00","description":"update room 2001","file_path":"data/rooms/intro/2001.yaml","old_content":"id: 2001\nname: Test Room\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\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\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","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:47-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:49-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 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: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:49-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:55-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 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","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:55-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:57-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 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: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:39:57-04:00","description":"update room 2002","file_path":"data/rooms/intro/2002.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: It's a test room\nexits:\n east:\n room: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\nhazard: \"\"\nid: 2002\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: 2003\n northwest:\n room: 1\n southeast:\n room: 2004\n west:\n room: 2001\nhazard: \"\"\nid: 2002\nitem_spawns: []\nmobs: []\nname: Test Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:43:52-04:00","description":"update room 1008 (add exit north to 7)","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 1008\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:43:52-04:00","description":"create room 7","file_path":"data/rooms/intro/7.yaml","old_content":"","new_content":"exits:\n south: 1008\nid: 7\nname: 'Room #7'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T21:44:39-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"id: 1008\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: 00FFFF\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:45:30-04:00","description":"update room 1016","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southeast:\n room: 1018\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nhazard: \"\"\nid: 1016\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:45:56-04:00","description":"update room 1005","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nhazard: \"\"\nid: 1005\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:46:03-04:00","description":"update room 1009 (add exit northwest to 8)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n southwest:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n northwest:\n room: 8\n southwest:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T21:46:03-04:00","description":"create room 8","file_path":"data/rooms/intro/8.yaml","old_content":"","new_content":"exits:\n southeast: 1009\nid: 8\nname: 'Room #8'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:04:39-04:00","description":"link 1005 north 8","file_path":"data/rooms/intro/1005.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nhazard: \"\"\nid: 1005\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:39-04:00","description":"link 8 south 1005 (reverse)","file_path":"data/rooms/intro/8.yaml","old_content":"exits:\n southeast: 1009\nid: 8\nname: 'Room #8'\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:39-04:00","description":"unlink 8 south 1005","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:39-04:00","description":"unlink 1005 north 8 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:40-04:00","description":"link 1005 north 8","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:40-04:00","description":"link 8 south 1005 (reverse)","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:41-04:00","description":"unlink 8 south 1005","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:41-04:00","description":"unlink 1005 north 8 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"link 1005 north 8","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"link 8 south 1005 (reverse)","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"unlink 8 south 1005","file_path":"data/rooms/intro/8.yaml","old_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 1005\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 8\nname: 'Room #8'\ncolor: \"\"\ndescription: []\nexits:\n southeast:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:42-04:00","description":"unlink 1005 north 8 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 8\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:45-04:00","description":"link 1005 east 1009","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:45-04:00","description":"link 1009 west 1005 (reverse)","file_path":"data/rooms/intro/1009.yaml","old_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n northwest:\n room: 8\n southwest:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1009\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1016\n northwest:\n room: 8\n southwest:\n room: 1006\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-29T22:04:48-04:00","description":"link 1016 southwest 1005","file_path":"data/rooms/intro/1016.yaml","old_content":"block_transport: false\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nhazard: \"\"\nid: 1016\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 1016\nname: New Room\ncolor: \"\"\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","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:48-04:00","description":"link 1005 northeast 1016 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:49-04:00","description":"unlink 1005 northeast 1016","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:49-04:00","description":"unlink 1016 southwest 1005 (reverse)","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\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":"id: 1016\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\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-29T22:04:51-04:00","description":"link 1016 southwest 1005","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1016\nname: New Room\ncolor: \"\"\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","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:53-04:00","description":"link 1016 southwest 1005","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\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":"id: 1016\nname: New Room\ncolor: \"\"\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","is_delete":false,"is_create":false},{"time":"2026-06-29T22:04:53-04:00","description":"link 1005 northeast 1016 (reverse)","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1009\n northeast:\n room: 1016\n south:\n room: 1006\n southeast:\n room: 1007\n southwest:\n room: 2003\n west:\n room: 1008\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:05:39-04:00","description":"update room 1016","file_path":"data/rooms/intro/1016.yaml","old_content":"id: 1016\nname: New Room\ncolor: \"\"\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":"block_transport: false\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nhazard: \"\"\nid: 1016\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:20:52-04:00","description":"update room 1007 (add exit east to 9)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 9\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:20:52-04:00","description":"create room 9","file_path":"data/rooms/intro/9.yaml","old_content":"","new_content":"exits:\n west: 1007\nid: 9\nname: 'Room #9'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:20:54-04:00","description":"unlink 9 west 1007","file_path":"data/rooms/intro/9.yaml","old_content":"exits:\n west: 1007\nid: 9\nname: 'Room #9'\n","new_content":"id: 9\nname: 'Room #9'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:20:54-04:00","description":"unlink 1007 east 9 (reverse)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 9\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:00-04:00","description":"update room 1007 (add exit east to 10)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 10\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:00-04:00","description":"create room 10","file_path":"data/rooms/intro/10.yaml","old_content":"","new_content":"exits:\n west: 1007\nid: 10\nname: 'Room #10'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:21:04-04:00","description":"unlink 1007 east 10","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 10\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:04-04:00","description":"unlink 10 west 1007 (reverse)","file_path":"data/rooms/intro/10.yaml","old_content":"exits:\n west: 1007\nid: 10\nname: 'Room #10'\n","new_content":"id: 10\nname: 'Room #10'\ncolor: \"\"\ndescription: []\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:41-04:00","description":"update room 1007 (add exit up to 11)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n up:\n room: 11\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:41-04:00","description":"create room 11","file_path":"data/rooms/intro/11.yaml","old_content":"","new_content":"exits:\n down: 1007\nid: 11\nname: 'Room #11'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:21:45-04:00","description":"update room 1007 (add exit down to 12)","file_path":"data/rooms/intro/1007.yaml","old_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1005\n south:\n room: 1014\n up:\n room: 11\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1007\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 12\n northwest:\n room: 1005\n south:\n room: 1014\n up:\n room: 11\n west:\n room: 1006\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T22:21:45-04:00","description":"create room 12","file_path":"data/rooms/intro/12.yaml","old_content":"","new_content":"exits:\n up: 1007\nid: 12\nname: 'Room #12'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T22:29:07-04:00","description":"delete room 11","file_path":"data/rooms/intro/11.yaml","old_content":"exits:\n down: 1007\nid: 11\nname: 'Room #11'\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-29T22:29:12-04:00","description":"delete room 1015","file_path":"data/rooms/intro/1015.yaml","old_content":"id: 0\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1014\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"","is_delete":true,"is_create":false},{"time":"2026-06-29T22:29:22-04:00","description":"unlink 1012 west 1011","file_path":"data/rooms/intro/1012.yaml","old_content":"id: 1012\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1013\n west:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1012\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\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-29T23:18:02-04:00","description":"update room 1016 (add exit east to 11)","file_path":"data/rooms/intro/1016.yaml","old_content":"block_transport: false\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n south:\n room: 1009\n southwest:\n room: 1005\nhazard: \"\"\nid: 1016\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"id: 1016\nname: New Room\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n east:\n room: 11\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:02-04:00","description":"create room 11","file_path":"data/rooms/intro/11.yaml","old_content":"","new_content":"color: \"97\"\nexits:\n west: 1016\nid: 11\nname: 'Room #11'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T23:18:05-04:00","description":"update room 1016 (add exit east to 13)","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 east:\n room: 11\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1016\nname: New Room\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n east:\n room: 13\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:10-04:00","description":"update room 1016 (add exit east to 13)","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 east:\n room: 13\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1016\nname: New Room\ncolor: \"97\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1017\n east:\n room: 13\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:10-04:00","description":"create room 13","file_path":"data/rooms/intro/13.yaml","old_content":"","new_content":"color: \"97\"\nexits:\n west: 1016\nid: 13\nname: 'Room #13'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T23:18:11-04:00","description":"update room 13 (add exit east to 14)","file_path":"data/rooms/intro/13.yaml","old_content":"color: \"97\"\nexits:\n west: 1016\nid: 13\nname: 'Room #13'\n","new_content":"id: 13\nname: 'Room #13'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 14\n west:\n room: 1016\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:11-04:00","description":"create room 14","file_path":"data/rooms/intro/14.yaml","old_content":"","new_content":"color: \"97\"\nexits:\n west: 13\nid: 14\nname: 'Room #14'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T23:18:12-04:00","description":"update room 14 (add exit north to 15)","file_path":"data/rooms/intro/14.yaml","old_content":"color: \"97\"\nexits:\n west: 13\nid: 14\nname: 'Room #14'\n","new_content":"id: 14\nname: 'Room #14'\ncolor: \"97\"\ndescription: []\nexits:\n north:\n room: 15\n west:\n room: 13\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:12-04:00","description":"create room 15","file_path":"data/rooms/intro/15.yaml","old_content":"","new_content":"color: \"97\"\nexits:\n south: 14\nid: 15\nname: 'Room #15'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T23:18:12-04:00","description":"update room 15 (add exit west to 16)","file_path":"data/rooms/intro/15.yaml","old_content":"color: \"97\"\nexits:\n south: 14\nid: 15\nname: 'Room #15'\n","new_content":"id: 15\nname: 'Room #15'\ncolor: \"97\"\ndescription: []\nexits:\n south:\n room: 14\n west:\n room: 16\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:12-04:00","description":"create room 16","file_path":"data/rooms/intro/16.yaml","old_content":"","new_content":"color: \"97\"\nexits:\n east: 15\nid: 16\nname: 'Room #16'\n","is_delete":false,"is_create":true},{"time":"2026-06-29T23:18:14-04:00","description":"link 16 south 13","file_path":"data/rooms/intro/16.yaml","old_content":"color: \"97\"\nexits:\n east: 15\nid: 16\nname: 'Room #16'\n","new_content":"id: 16\nname: 'Room #16'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 15\n south:\n room: 13\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:14-04:00","description":"link 13 north 16 (reverse)","file_path":"data/rooms/intro/13.yaml","old_content":"id: 13\nname: 'Room #13'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 14\n west:\n room: 1016\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 13\nname: 'Room #13'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 14\n north:\n room: 16\n west:\n room: 1016\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:16-04:00","description":"unlink 16 south 13","file_path":"data/rooms/intro/16.yaml","old_content":"id: 16\nname: 'Room #16'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 15\n south:\n room: 13\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 16\nname: 'Room #16'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 15\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:18:16-04:00","description":"unlink 13 north 16 (reverse)","file_path":"data/rooms/intro/13.yaml","old_content":"id: 13\nname: 'Room #13'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 14\n north:\n room: 16\n west:\n room: 1016\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 13\nname: 'Room #13'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 14\n west:\n room: 1016\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:19:56-04:00","description":"unlink 14 north 15","file_path":"data/rooms/intro/14.yaml","old_content":"id: 14\nname: 'Room #14'\ncolor: \"97\"\ndescription: []\nexits:\n north:\n room: 15\n west:\n room: 13\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 14\nname: 'Room #14'\ncolor: \"97\"\ndescription: []\nexits:\n west:\n room: 13\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:19:56-04:00","description":"unlink 15 south 14 (reverse)","file_path":"data/rooms/intro/15.yaml","old_content":"id: 15\nname: 'Room #15'\ncolor: \"97\"\ndescription: []\nexits:\n south:\n room: 14\n west:\n room: 16\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 15\nname: 'Room #15'\ncolor: \"97\"\ndescription: []\nexits:\n west:\n room: 16\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:19:59-04:00","description":"unlink 1016 east 13","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 east:\n room: 13\n south:\n room: 1009\n southwest:\n room: 1005\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_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","is_delete":false,"is_create":false},{"time":"2026-06-29T23:19:59-04:00","description":"unlink 13 west 1016 (reverse)","file_path":"data/rooms/intro/13.yaml","old_content":"id: 13\nname: 'Room #13'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 14\n west:\n room: 1016\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 13\nname: 'Room #13'\ncolor: \"97\"\ndescription: []\nexits:\n east:\n room: 14\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:40:49-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: 00FFFF\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:41:04-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:41:11-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:41:14-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\n - id: silver_rock\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:41:16-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\n - id: silver_rock\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-29T23:41:17-04:00","description":"update room 1008","file_path":"data/rooms/intro/1008.yaml","old_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects:\n - id: silver_rock\non_enter: []\ntriggers: []\n","new_content":"block_transport: false\ncolor: \"33\"\ndescription:\n - text: A featureless room.\nexits:\n east:\n room: 1005\n north:\n room: 7\n south:\n room: 2003\n southeast:\n room: 1006\nhazard: \"\"\nid: 1008\nitem_spawns: []\nmobs: []\nname: New Room\nobjects: []\non_enter: []\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:45:22-04:00","description":"link 9999917 north 999997","file_path":"data/rooms/example_unused/town/9999917.yaml","old_content":"name: \"Bonfire\"\ndescription: \"A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\"\nexits:\n east: 9999918\n west: 9999916\n north: 99999200\nmobs:\n - id: scrap_bot\n - id: scrap_bot\n","new_content":"id: 9999917\nname: Bonfire\ncolor: \"\"\ndescription:\n - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\nexits:\n east:\n room: 9999918\n north:\n room: 999997\n west:\n room: 9999916\nobjects: []\nitem_spawns: []\nmobs:\n - id: scrap_bot\n - id: scrap_bot\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:45:22-04:00","description":"link 999997 south 9999917 (reverse)","file_path":"data/rooms/example_unused/town/999997.yaml","old_content":"name: \"Deep Mine\"\ndescription: \"A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\"\nexits:\n south: 999996\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\n","new_content":"id: 999997\nname: Deep Mine\ncolor: \"\"\ndescription:\n - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\nexits:\n south:\n room: 9999917\nobjects: []\nitem_spawns: []\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:45:32-04:00","description":"link 9999917 south 999997","file_path":"data/rooms/example_unused/town/9999917.yaml","old_content":"id: 9999917\nname: Bonfire\ncolor: \"\"\ndescription:\n - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\nexits:\n east:\n room: 9999918\n north:\n room: 999997\n west:\n room: 9999916\nobjects: []\nitem_spawns: []\nmobs:\n - id: scrap_bot\n - id: scrap_bot\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 9999917\nname: Bonfire\ncolor: \"\"\ndescription:\n - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\nexits:\n east:\n room: 9999918\n north:\n room: 999997\n south:\n room: 999997\n west:\n room: 9999916\nobjects: []\nitem_spawns: []\nmobs:\n - id: scrap_bot\n - id: scrap_bot\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:45:32-04:00","description":"link 999997 north 9999917 (reverse)","file_path":"data/rooms/example_unused/town/999997.yaml","old_content":"id: 999997\nname: Deep Mine\ncolor: \"\"\ndescription:\n - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\nexits:\n south:\n room: 9999917\nobjects: []\nitem_spawns: []\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 999997\nname: Deep Mine\ncolor: \"\"\ndescription:\n - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\nexits:\n north:\n room: 9999917\n south:\n room: 9999917\nobjects: []\nitem_spawns: []\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:45:37-04:00","description":"unlink 9999917 north 999997","file_path":"data/rooms/example_unused/town/9999917.yaml","old_content":"id: 9999917\nname: Bonfire\ncolor: \"\"\ndescription:\n - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\nexits:\n east:\n room: 9999918\n north:\n room: 999997\n south:\n room: 999997\n west:\n room: 9999916\nobjects: []\nitem_spawns: []\nmobs:\n - id: scrap_bot\n - id: scrap_bot\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 9999917\nname: Bonfire\ncolor: \"\"\ndescription:\n - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\nexits:\n east:\n room: 9999918\n south:\n room: 999997\n west:\n room: 9999916\nobjects: []\nitem_spawns: []\nmobs:\n - id: scrap_bot\n - id: scrap_bot\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:45:37-04:00","description":"unlink 999997 south 9999917 (reverse)","file_path":"data/rooms/example_unused/town/999997.yaml","old_content":"id: 999997\nname: Deep Mine\ncolor: \"\"\ndescription:\n - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\nexits:\n north:\n room: 9999917\n south:\n room: 9999917\nobjects: []\nitem_spawns: []\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 999997\nname: Deep Mine\ncolor: \"\"\ndescription:\n - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\nexits:\n north:\n room: 9999917\nobjects: []\nitem_spawns: []\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:48:48-04:00","description":"unlink 9999917 south 999997","file_path":"data/rooms/example_unused/town/9999917.yaml","old_content":"id: 9999917\nname: Bonfire\ncolor: \"\"\ndescription:\n - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\nexits:\n east:\n room: 9999918\n south:\n room: 999997\n west:\n room: 9999916\nobjects: []\nitem_spawns: []\nmobs:\n - id: scrap_bot\n - id: scrap_bot\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 9999917\nname: Bonfire\ncolor: \"\"\ndescription:\n - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.\nexits:\n east:\n room: 9999918\n west:\n room: 9999916\nobjects: []\nitem_spawns: []\nmobs:\n - id: scrap_bot\n - id: scrap_bot\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:48:48-04:00","description":"unlink 999997 north 9999917 (reverse)","file_path":"data/rooms/example_unused/town/999997.yaml","old_content":"id: 999997\nname: Deep Mine\ncolor: \"\"\ndescription:\n - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\nexits:\n north:\n room: 9999917\nobjects: []\nitem_spawns: []\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 999997\nname: Deep Mine\ncolor: \"\"\ndescription:\n - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.\nexits: {}\nobjects: []\nitem_spawns: []\nmobs:\n - id: goblin\n - id: goblin\n - id: bandit\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:52:49-04:00","description":"unlink 1013 north 1012","file_path":"data/rooms/intro/1013.yaml","old_content":"id: 1013\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1012\n west:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1013\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\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-30T01:52:49-04:00","description":"unlink 1012 south 1013 (reverse)","file_path":"data/rooms/intro/1012.yaml","old_content":"id: 1012\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n south:\n room: 1013\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_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","is_delete":false,"is_create":false},{"time":"2026-06-30T01:52:52-04:00","description":"unlink 1013 west 1010","file_path":"data/rooms/intro/1013.yaml","old_content":"id: 1013\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n west:\n room: 1010\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1013\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:52:52-04:00","description":"unlink 1010 east 1013 (reverse)","file_path":"data/rooms/intro/1010.yaml","old_content":"id: 1010\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1006\n east:\n room: 1013\n north:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1010\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n down:\n room: 1006\n north:\n room: 1011\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:53:57-04:00","description":"link 1014 south 1013","file_path":"data/rooms/intro/1014.yaml","old_content":"id: 1014\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1007\n up:\n room: 1015\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1014\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1007\n south:\n room: 1013\n up:\n room: 1015\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:53:58-04:00","description":"link 1013 north 1014 (reverse)","file_path":"data/rooms/intro/1013.yaml","old_content":"id: 1013\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":"id: 1013\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1014\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:54:48-04:00","description":"unlink 1018 northwest 1016","file_path":"data/rooms/intro/1018.yaml","old_content":"id: 0\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n northwest:\n room: 1016\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits: {}\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:54:48-04:00","description":"unlink 1016 southeast 1018 (reverse)","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":"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","is_delete":false,"is_create":false},{"time":"2026-06-30T01:54:56-04:00","description":"link 1013 south 1018","file_path":"data/rooms/intro/1013.yaml","old_content":"id: 1013\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1014\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1013\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1014\n south:\n room: 1018\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-06-30T01:54:56-04:00","description":"link 1018 north 1013 (reverse)","file_path":"data/rooms/intro/1018.yaml","old_content":"id: 1018\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":"id: 1018\nname: New Room\ncolor: \"\"\ndescription:\n - text: A featureless room.\nexits:\n north:\n room: 1013\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false}],"redo":null} \ No newline at end of file
diff --git a/data/rooms/example_unused/town/9999917.yaml b/data/rooms/example_unused/town/9999917.yaml
index 5980181..c3202ee 100644
--- a/data/rooms/example_unused/town/9999917.yaml
+++ b/data/rooms/example_unused/town/9999917.yaml
@@ -1,9 +1,19 @@
-name: "Bonfire"
-description: "A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal."
+id: 9999917
+name: Bonfire
+color: ""
+description:
+ - text: A ring of stones surrounds a bed of ash. A few unlit logs wait nearby. A narrow passage leads north toward what sounds like echoing footsteps and clanging metal.
exits:
- east: 9999918
- west: 9999916
- north: 99999200
+ east:
+ room: 9999918
+ west:
+ room: 9999916
+objects: []
+item_spawns: []
mobs:
- id: scrap_bot
- id: scrap_bot
+on_enter: []
+hazard: ""
+block_transport: false
+triggers: []
diff --git a/data/rooms/example_unused/town/999997.yaml b/data/rooms/example_unused/town/999997.yaml
index aff9a6c..9bd30a5 100644
--- a/data/rooms/example_unused/town/999997.yaml
+++ b/data/rooms/example_unused/town/999997.yaml
@@ -1,8 +1,16 @@
-name: "Deep Mine"
-description: "A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible."
-exits:
- south: 999996
+id: 999997
+name: Deep Mine
+color: ""
+description:
+ - text: A deeper mine shaft. Higher quality ores can be found here. This area is not yet accessible.
+exits: {}
+objects: []
+item_spawns: []
mobs:
- id: goblin
- id: goblin
- id: bandit
+on_enter: []
+hazard: ""
+block_transport: false
+triggers: []
diff --git a/data/rooms/intro/1.yaml b/data/rooms/intro/1.yaml
deleted file mode 100644
index fb1ba49..0000000
--- a/data/rooms/intro/1.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: 1
-name: 'Room #1'
-color: ""
-description: []
-exits:
- northwest:
- room: 2
- southeast:
- room: 2002
-objects: []
-item_spawns: []
-mobs: []
-on_enter: []
-hazard: ""
-block_transport: false
-triggers: []
diff --git a/data/rooms/intro/10.yaml b/data/rooms/intro/10.yaml
deleted file mode 100644
index a98b261..0000000
--- a/data/rooms/intro/10.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: 10
-name: 'Room #10'
-color: ""
-description: []
-exits: {}
-objects: []
-item_spawns: []
-mobs: []
-on_enter: []
-hazard: ""
-block_transport: false
-triggers: []
diff --git a/data/rooms/intro/1008.yaml b/data/rooms/intro/1008.yaml
index a7a8a2f..c3fab06 100644
--- a/data/rooms/intro/1008.yaml
+++ b/data/rooms/intro/1008.yaml
@@ -1,5 +1,5 @@
block_transport: false
-color: 00FFFF
+color: "33"
description:
- text: A featureless room.
exits:
diff --git a/data/rooms/intro/1010.yaml b/data/rooms/intro/1010.yaml
index ef09603..fca6471 100644
--- a/data/rooms/intro/1010.yaml
+++ b/data/rooms/intro/1010.yaml
@@ -2,14 +2,12 @@ id: 1010
name: New Room
color: ""
description:
- - text: A featureless room.
+ - text: A featureless room.
exits:
- down:
- room: 1006
- east:
- room: 1013
- north:
- room: 1011
+ down:
+ room: 1006
+ north:
+ room: 1011
objects: []
item_spawns: []
mobs: []
diff --git a/data/rooms/intro/1012.yaml b/data/rooms/intro/1012.yaml
index 20f6aa8..93fc6d1 100644
--- a/data/rooms/intro/1012.yaml
+++ b/data/rooms/intro/1012.yaml
@@ -3,9 +3,7 @@ name: New Room
color: ""
description:
- text: A featureless room.
-exits:
- south:
- room: 1013
+exits: {}
objects: []
item_spawns: []
mobs: []
diff --git a/data/rooms/intro/1013.yaml b/data/rooms/intro/1013.yaml
index 9b7a7ce..8d83946 100644
--- a/data/rooms/intro/1013.yaml
+++ b/data/rooms/intro/1013.yaml
@@ -2,12 +2,12 @@ id: 1013
name: New Room
color: ""
description:
- - text: A featureless room.
+ - text: A featureless room.
exits:
- north:
- room: 1012
- west:
- room: 1010
+ north:
+ room: 1014
+ south:
+ room: 1018
objects: []
item_spawns: []
mobs: []
diff --git a/data/rooms/intro/1014.yaml b/data/rooms/intro/1014.yaml
index 7d23484..ce76c4c 100644
--- a/data/rooms/intro/1014.yaml
+++ b/data/rooms/intro/1014.yaml
@@ -2,12 +2,14 @@ id: 1014
name: New Room
color: ""
description:
- - text: A featureless room.
+ - text: A featureless room.
exits:
- north:
- room: 1007
- up:
- room: 1015
+ north:
+ room: 1007
+ south:
+ room: 1013
+ up:
+ room: 1015
objects: []
item_spawns: []
mobs: []
diff --git a/data/rooms/intro/1016.yaml b/data/rooms/intro/1016.yaml
index 8023b39..3834f84 100644
--- a/data/rooms/intro/1016.yaml
+++ b/data/rooms/intro/1016.yaml
@@ -1,4 +1,5 @@
-block_transport: false
+id: 1016
+name: New Room
color: "97"
description:
- text: A featureless room.
@@ -9,11 +10,10 @@ exits:
room: 1009
southwest:
room: 1005
-hazard: ""
-id: 1016
+objects: []
item_spawns: []
mobs: []
-name: New Room
-objects: []
on_enter: []
+hazard: ""
+block_transport: false
triggers: []
diff --git a/data/rooms/intro/1018.yaml b/data/rooms/intro/1018.yaml
index 1309d65..38540b5 100644
--- a/data/rooms/intro/1018.yaml
+++ b/data/rooms/intro/1018.yaml
@@ -1,11 +1,11 @@
-id: 0
+id: 1018
name: New Room
color: ""
description:
- - text: A featureless room.
+ - text: A featureless room.
exits:
- northwest:
- room: 1016
+ north:
+ room: 1013
objects: []
item_spawns: []
mobs: []
diff --git a/data/rooms/intro/12.yaml b/data/rooms/intro/12.yaml
deleted file mode 100644
index 89bfe35..0000000
--- a/data/rooms/intro/12.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-exits:
- up: 1007
-id: 12
-name: 'Room #12'
diff --git a/data/rooms/intro/2.yaml b/data/rooms/intro/2.yaml
deleted file mode 100644
index 37f7c70..0000000
--- a/data/rooms/intro/2.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: 2
-name: 'Room #2'
-color: ""
-description: []
-exits:
- northeast:
- room: 3
- southeast:
- room: 1
-objects: []
-item_spawns: []
-mobs: []
-on_enter: []
-hazard: ""
-block_transport: false
-triggers: []
diff --git a/data/rooms/intro/3.yaml b/data/rooms/intro/3.yaml
deleted file mode 100644
index 75a05c5..0000000
--- a/data/rooms/intro/3.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-block_transport: false
-color: ""
-description: []
-exits:
- south:
- room: 5
- southeast:
- room: 4
- southwest:
- room: 2
-hazard: ""
-id: 3
-item_spawns: []
-mobs: []
-name: 'Room #3'
-objects: []
-on_enter: []
-triggers: []
diff --git a/data/rooms/intro/4.yaml b/data/rooms/intro/4.yaml
deleted file mode 100644
index e7b81a8..0000000
--- a/data/rooms/intro/4.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-id: 4
-name: 'Room #4'
-color: ""
-description: []
-exits:
- northwest:
- room: 3
- west:
- room: 5
-objects: []
-item_spawns: []
-mobs: []
-on_enter: []
-hazard: ""
-block_transport: false
-triggers: []
diff --git a/data/rooms/intro/5.yaml b/data/rooms/intro/5.yaml
deleted file mode 100644
index 0e11af4..0000000
--- a/data/rooms/intro/5.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-exits:
- east: 4
- north:
- room: 3
-id: 5
-name: 'Room #5'
diff --git a/data/rooms/intro/6.yaml b/data/rooms/intro/6.yaml
deleted file mode 100644
index 5f16cfc..0000000
--- a/data/rooms/intro/6.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-exits:
- down: 1001
-id: 6
-name: 'Room #6'
diff --git a/data/rooms/intro/7.yaml b/data/rooms/intro/7.yaml
deleted file mode 100644
index 9c73429..0000000
--- a/data/rooms/intro/7.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-exits:
- south: 1008
-id: 7
-name: 'Room #7'
diff --git a/data/rooms/intro/8.yaml b/data/rooms/intro/8.yaml
deleted file mode 100644
index 2edf88c..0000000
--- a/data/rooms/intro/8.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-id: 8
-name: 'Room #8'
-color: ""
-description: []
-exits:
- southeast:
- room: 1009
-objects: []
-item_spawns: []
-mobs: []
-on_enter: []
-hazard: ""
-block_transport: false
-triggers: []
diff --git a/data/rooms/intro/9.yaml b/data/rooms/intro/9.yaml
deleted file mode 100644
index 2476d76..0000000
--- a/data/rooms/intro/9.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-id: 9
-name: 'Room #9'
-color: ""
-description: []
-exits: {}
-objects: []
-item_spawns: []
-mobs: []
-on_enter: []
-hazard: ""
-block_transport: false
-triggers: []
diff --git a/internal/admin/api_map.go b/internal/admin/api_map.go
index 1520279..5de41a7 100644
--- a/internal/admin/api_map.go
+++ b/internal/admin/api_map.go
@@ -162,11 +162,13 @@ func (s *AdminServer) handleMap(w http.ResponseWriter, r *http.Request) {
}
writeJSON(w, map[string]any{
- "rooms": rooms,
- "links": links,
- "upLinks": upLinks,
- "downLinks": downLinks,
- "dir": getRoomDir(s, seed),
+ "rooms": rooms,
+ "links": links,
+ "upLinks": upLinks,
+ "downLinks": downLinks,
+ "dir": getRoomDir(s, seed),
+ "seed": seed,
+ "disconnected": findDisconnectedRooms(s, dir, seed, g),
"bounds": map[string]int{
"minX": minX,
"maxX": maxX,
@@ -222,6 +224,75 @@ func findLowestRoom(dir string) (int, bool) {
return lowest, found
}
+func findDisconnectedRooms(s *AdminServer, dir string, seed int, g world.RoomGrid) []map[string]any {
+ var result []map[string]any
+ scanDir := dir
+ if scanDir == "" {
+ scanDir = getRoomDir(s, seed)
+ }
+ allIDs := listRoomIDsInDir(s, scanDir)
+ for _, id := range allIDs {
+ if _, inGrid := g.Coord[id]; inGrid {
+ continue
+ }
+ room, err := s.world.LoadRoom(id)
+ if err != nil {
+ continue
+ }
+ result = append(result, map[string]any{
+ "id": id,
+ "name": room.Name,
+ })
+ }
+ return result
+}
+
+func listRoomIDsInDir(s *AdminServer, dir string) []int {
+ base := filepath.Join(s.dataDir, "rooms")
+ var dirs []string
+ if dir == "" {
+ dirs = append(dirs, base)
+ entries, err := os.ReadDir(base)
+ if err != nil {
+ return nil
+ }
+ for _, e := range entries {
+ if e.IsDir() {
+ dirs = append(dirs, filepath.Join(base, e.Name()))
+ subE, err := os.ReadDir(filepath.Join(base, e.Name()))
+ if err != nil {
+ continue
+ }
+ for _, se := range subE {
+ if se.IsDir() {
+ dirs = append(dirs, filepath.Join(base, e.Name(), se.Name()))
+ }
+ }
+ }
+ }
+ } else {
+ dirs = append(dirs, filepath.Join(base, dir))
+ }
+ var ids []int
+ for _, d := range dirs {
+ entries, err := os.ReadDir(d)
+ if err != nil {
+ continue
+ }
+ for _, e := range entries {
+ if e.IsDir() || filepath.Ext(e.Name()) != ".yaml" {
+ continue
+ }
+ id, err := strconv.Atoi(e.Name()[:len(e.Name())-5])
+ if err != nil {
+ continue
+ }
+ ids = append(ids, id)
+ }
+ }
+ return ids
+}
+
func (s *AdminServer) handleNextRoomID(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
diff --git a/internal/admin/api_rooms.go b/internal/admin/api_rooms.go
index 985eb49..4075534 100644
--- a/internal/admin/api_rooms.go
+++ b/internal/admin/api_rooms.go
@@ -269,68 +269,164 @@ func (s *AdminServer) handleRoomDirs(w http.ResponseWriter, r *http.Request) {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
- entries, err := os.ReadDir(filepath.Join(s.dataDir, "rooms"))
+ base := filepath.Join(s.dataDir, "rooms")
+ entries, err := os.ReadDir(base)
if err != nil {
writeJSON(w, []string{""})
return
}
dirs := []string{""}
for _, e := range entries {
- if e.IsDir() {
+ if !e.IsDir() {
+ continue
+ }
+ if hasYAMLFiles(filepath.Join(base, e.Name())) {
dirs = append(dirs, e.Name())
}
+ subEntries, err := os.ReadDir(filepath.Join(base, e.Name()))
+ if err != nil {
+ continue
+ }
+ for _, se := range subEntries {
+ if se.IsDir() && hasYAMLFiles(filepath.Join(base, e.Name(), se.Name())) {
+ dirs = append(dirs, e.Name()+"/"+se.Name())
+ }
+ }
}
writeJSON(w, dirs)
}
+func hasYAMLFiles(dir string) bool {
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return false
+ }
+ for _, e := range entries {
+ if !e.IsDir() && filepath.Ext(e.Name()) == ".yaml" {
+ return true
+ }
+ }
+ return false
+}
+
func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodPost:
var body struct {
- From int `json:"from"`
- To int `json:"to"`
+ From int `json:"from"`
+ To int `json:"to"`
+ Dir string `json:"dir"`
}
if err := readJSON(r, &body); err != nil || body.From <= 0 || body.To <= 0 {
writeJSON(w, map[string]any{"error": "invalid body, need from and to"})
return
}
- grid := world.BuildGrid(s.cfg.StartingRoom, func(id int) (*world.Room, bool) {
- room, err := s.world.LoadRoom(id)
- if err != nil {
- return nil, false
- }
- return room, true
- }, nil, nil)
- cA, okA := grid.Coord[body.From]
- cB, okB := grid.Coord[body.To]
- if !okA || !okB || cA[2] != cB[2] {
- writeJSON(w, map[string]any{"error": "rooms must be on same z-level"})
+ roomA, errA := s.world.LoadRoom(body.From)
+ roomB, errB := s.world.LoadRoom(body.To)
+ if errA != nil || errB != nil {
+ writeJSON(w, map[string]any{"error": "one or both rooms not found"})
return
}
- dx, dy := cB[0]-cA[0], cB[1]-cA[1]
+
var dir, oppDir world.ExitDir
- for d, delta := range world.DirectionDeltas3D {
- if delta[0] == dx && delta[1] == dy && delta[2] == 0 {
- dir = d
- oppDir = world.OppositeExit[d]
- break
+ if body.Dir != "" {
+ dir = world.ExitDir(strings.ToLower(body.Dir))
+ if opp, ok := world.OppositeExit[dir]; ok {
+ oppDir = opp
+ } else {
+ writeJSON(w, map[string]any{"error": "invalid direction: " + body.Dir})
+ return
+ }
+ } else {
+ grid := world.BuildGrid(s.cfg.StartingRoom, func(id int) (*world.Room, bool) {
+ room, err := s.world.LoadRoom(id)
+ if err != nil {
+ return nil, false
+ }
+ return room, true
+ }, nil, nil)
+
+ cA, okA := grid.Coord[body.From]
+ cB, okB := grid.Coord[body.To]
+ if !okA || !okB || cA[2] != cB[2] {
+ writeJSON(w, map[string]any{"error": "rooms must be on same z-level"})
+ return
+ }
+ dx, dy := cB[0]-cA[0], cB[1]-cA[1]
+ for d, delta := range world.DirectionDeltas3D {
+ if delta[0] == dx && delta[1] == dy && delta[2] == 0 {
+ dir = d
+ oppDir = world.OppositeExit[d]
+ break
+ }
+ }
+ if dir == "" {
+ writeJSON(w, map[string]any{"error": "rooms are not adjacent"})
+ return
+ }
+ }
+
+ if roomA.Exits != nil {
+ for d, exit := range roomA.Exits {
+ if exit.Room == body.To {
+ delete(roomA.Exits, d)
+ break
+ }
+ }
+ }
+ if roomB.Exits != nil {
+ for d, exit := range roomB.Exits {
+ if exit.Room == body.From {
+ delete(roomB.Exits, d)
+ break
+ }
}
}
- if dir == "" {
- writeJSON(w, map[string]any{"error": "rooms are not adjacent"})
+
+ conflicts := 0
+ testGrid := world.BuildGrid(s.cfg.StartingRoom, func(id int) (*world.Room, bool) {
+ room, err := s.world.LoadRoom(id)
+ if err != nil {
+ return nil, false
+ }
+ roomCopy := *room
+ if room.Exits != nil {
+ roomCopy.Exits = make(map[world.ExitDir]world.ExitDef, len(room.Exits))
+ for k, v := range room.Exits {
+ if (id == body.From && v.Room == body.To) || (id == body.To && v.Room == body.From) {
+ continue
+ }
+ roomCopy.Exits[k] = v
+ }
+ } else {
+ roomCopy.Exits = make(map[world.ExitDir]world.ExitDef)
+ }
+ if id == body.From {
+ roomCopy.Exits[dir] = world.ExitDef{Room: body.To}
+ }
+ if id == body.To {
+ roomCopy.Exits[oppDir] = world.ExitDef{Room: body.From}
+ }
+ return &roomCopy, true
+ }, nil, func(gc world.GridConflict) {
+ conflicts++
+ })
+ _ = testGrid
+
+ if conflicts > 0 {
+ writeJSONError(w, "link would cause map conflicts (twists or overlaps)", http.StatusConflict)
return
}
- roomA, _ := s.world.LoadRoom(body.From)
- roomB, _ := s.world.LoadRoom(body.To)
if roomA.Exits == nil {
roomA.Exits = make(map[world.ExitDir]world.ExitDef)
}
+ roomA.Exits[dir] = world.ExitDef{Room: body.To}
+
if roomB.Exits == nil {
roomB.Exits = make(map[world.ExitDir]world.ExitDef)
}
- roomA.Exits[dir] = world.ExitDef{Room: body.To}
roomB.Exits[oppDir] = world.ExitDef{Room: body.From}
pathA, okA2 := s.world.GetRoomPath(body.From)
@@ -378,36 +474,42 @@ func (s *AdminServer) handleRoomLink(w http.ResponseWriter, r *http.Request) {
writeJSON(w, map[string]any{"error": "invalid body"})
return
}
- grid := world.BuildGrid(s.cfg.StartingRoom, func(id int) (*world.Room, bool) {
- room, err := s.world.LoadRoom(id)
- if err != nil {
- return nil, false
- }
- return room, true
- }, nil, nil)
- cA, okA := grid.Coord[body.From]
- cB, okB := grid.Coord[body.To]
- if !okA || !okB || cA[2] != cB[2] {
- writeJSON(w, map[string]any{"error": "rooms must be on same z-level"})
+ roomA, errA := s.world.LoadRoom(body.From)
+ roomB, errB := s.world.LoadRoom(body.To)
+ if errA != nil || errB != nil {
+ writeJSON(w, map[string]any{"error": "one or both rooms not found"})
return
}
- dx, dy := cB[0]-cA[0], cB[1]-cA[1]
+ if roomA == nil || roomB == nil {
+ writeJSON(w, map[string]any{"error": "one or both rooms not found"})
+ return
+ }
+
var dir, oppDir world.ExitDir
- for d, delta := range world.DirectionDeltas3D {
- if delta[0] == dx && delta[1] == dy && delta[2] == 0 {
+ for _, d := range world.ExitOrder {
+ if exit, ok := roomA.Exits[d]; ok && exit.Room == body.To {
dir = d
- oppDir = world.OppositeExit[d]
break
}
}
- if dir == "" {
- writeJSON(w, map[string]any{"error": "rooms are not adjacent"})
+ if dir != "" {
+ oppDir = world.OppositeExit[dir]
+ } else {
+ for _, d := range world.ExitOrder {
+ if exit, ok := roomB.Exits[d]; ok && exit.Room == body.From {
+ oppDir = d
+ dir = world.OppositeExit[d]
+ break
+ }
+ }
+ }
+
+ if dir == "" && oppDir == "" {
+ writeJSONError(w, "no exit found between these rooms", http.StatusNotFound)
return
}
- roomA, _ := s.world.LoadRoom(body.From)
- roomB, _ := s.world.LoadRoom(body.To)
if roomA.Exits != nil {
delete(roomA.Exits, dir)
}
diff --git a/internal/admin/server.go b/internal/admin/server.go
index 418a693..8facc4a 100644
--- a/internal/admin/server.go
+++ b/internal/admin/server.go
@@ -212,12 +212,11 @@ func (s *AdminServer) checkAuth(r *http.Request) bool {
}
func (s *AdminServer) isAdminAccount(name string) bool {
- for _, a := range s.cfg.AdminHTTPS.AdminAccounts {
- if strings.EqualFold(a, name) {
- return true
- }
+ acc, err := s.accountStore.LoadAccount(name)
+ if err != nil {
+ return false
}
- return false
+ return acc.Admin
}
func (s *AdminServer) setAuthCookie(w http.ResponseWriter, account string) {
@@ -249,17 +248,17 @@ func (s *AdminServer) handleLogin(w http.ResponseWriter, r *http.Request) {
account := strings.TrimSpace(r.FormValue("account"))
password := r.FormValue("password")
- if !s.isAdminAccount(account) {
- s.renderPage(w, r, "login.html", map[string]any{"Error": "Account is not authorized as admin."})
- return
- }
-
acc, err := s.accountStore.LoadAccount(account)
if err != nil || !player.CheckPassword(password, acc.PasswordHash) {
s.renderPage(w, r, "login.html", map[string]any{"Error": "Invalid account name or password."})
return
}
+ if !acc.Admin {
+ s.renderPage(w, r, "login.html", map[string]any{"Error": "Account is not authorized as admin."})
+ return
+ }
+
s.setAuthCookie(w, account)
http.Redirect(w, r, "/", http.StatusFound)
}
@@ -355,6 +354,12 @@ func writeJSON(w http.ResponseWriter, data any) {
}
}
+func writeJSONError(w http.ResponseWriter, msg string, code int) {
+ w.Header().Set("Content-Type", "application/json")
+ w.WriteHeader(code)
+ json.NewEncoder(w).Encode(map[string]any{"error": msg})
+}
+
func readJSON(r *http.Request, v any) error {
body, err := io.ReadAll(r.Body)
if err != nil {
diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css
index c9d0ed5..684a9c6 100644
--- a/internal/admin/static/admin.css
+++ b/internal/admin/static/admin.css
@@ -11,7 +11,9 @@ body{font-family:monospace;background:var(--bg);color:var(--text);min-height:100
.nav .undo-bar span{color:#888}
.layout{display:flex;height:calc(100vh - 38px)}
.main{flex:1;overflow:auto;position:relative}
-.panel{width:380px;background:var(--panel);border-left:1px solid var(--border);overflow-y:auto;padding:16px;flex-shrink:0}
+.panel{width:580px;background:var(--panel);border-left:1px solid var(--border);overflow-y:auto;padding:16px;flex-shrink:0;position:relative}
+.panel-resize-handle{position:absolute;left:-3px;top:0;bottom:0;width:6px;cursor:col-resize;z-index:5;background:transparent}
+.panel-resize-handle:hover{background:rgba(255,255,255,.06)}
.panel h2{font-size:15px;margin-bottom:12px;padding-bottom:6px;border-bottom:1px solid var(--border)}
.form-group{margin-bottom:10px}
.form-group label{display:block;font-size:11px;color:#aaa;margin-bottom:3px;text-transform:uppercase;letter-spacing:.5px}
@@ -100,3 +102,41 @@ g.ud-hover:hover text{font-weight:bold}
.cm-menu button:hover{background:var(--accent)}
.cm-menu button.danger{color:#f55}
.cm-menu button.danger:hover{background:var(--danger)}
+.panel-left{width:200px;background:var(--panel);border-right:1px solid var(--border);overflow-y:auto;display:flex;flex-direction:column;flex-shrink:0}
+.panel-left .panel-header{font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.5px;padding:12px 12px 8px;border-bottom:1px solid var(--border);flex-shrink:0}
+.panel-left .dc-item{display:block;padding:6px 12px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03);font-family:monospace}
+.panel-left .dc-item:hover{background:rgba(15,52,96,.4)}
+.panel-left .dc-item .dc-id{color:#6cf}
+.panel-left .dc-item .dc-name{color:#888;font-size:10px;margin-left:6px}
+.panel-tabs{display:flex;gap:2px;margin-bottom:10px;border-bottom:1px solid var(--border)}
+.panel-tab{padding:4px 12px;font-size:11px;background:transparent;color:#888;border:none;border-bottom:2px solid transparent;cursor:pointer;font-family:monospace}
+.panel-tab:hover{color:var(--text)}
+.panel-tab.active{color:var(--text);border-bottom-color:var(--accent)}
+.panel-tab-content{padding:4px 0}
+.cond-clause input,.cond-clause select{padding:4px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:2px;font-family:monospace}
+.cond-clause select{min-width:120px}.exit-section{margin-top:12px;border-top:1px solid var(--border);padding-top:8px}
+.exit-section h3{font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}
+.exit-row{display:flex;gap:4px;align-items:center;margin-bottom:4px;padding:3px 4px;background:rgba(255,255,255,.02);border-radius:3px;font-size:11px}
+.exit-row .exit-dir{color:#6cf;font-weight:bold;min-width:28px;text-transform:uppercase}
+.exit-row .exit-target{color:var(--text);flex:1;cursor:pointer}.exit-row .exit-target:hover{color:#fff}
+.exit-row .exit-meta{font-size:9px;color:#888;display:block}
+.exit-row input,.exit-row select{padding:2px 4px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:2px;font-family:monospace}
+.exit-row select{min-width:70px}
+.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 .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)}
+.panel-tab-content .entry-fields{flex:1;min-width:0}
+.panel-tab-content .entry-fields input:not([type=checkbox]),.panel-tab-content .entry-fields textarea{width:100%;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;margin-bottom:3px}
+.panel-tab-content .entry-fields textarea{resize:vertical;min-height:28px}
+.panel-tab-content .entry-fields .entry-id{font-size:11px;color:#6cf;display:block;padding:3px 0}
+.panel-tab-content .section-search{position:relative;margin-top:4px}
+.panel-tab-content .search-input{width:100%;padding:4px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace}
+.panel-tab-content .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)}
+.panel-tab-content .search-results .sr-item{padding:4px 8px;font-size:11px;cursor:pointer;color:var(--text);border-bottom:1px solid rgba(255,255,255,.03)}
+.panel-tab-content .search-results .sr-item:hover{background:var(--accent)}
+.panel-tab-content .section-add-btn{display:block;width:100%;margin-top:4px;background:var(--accent);color:var(--text);border:1px solid #1a5a8e;padding:4px 8px;font-size:11px;border-radius:3px;cursor:pointer;font-family:monospace}
+.panel-tab-content .section-add-btn:hover{background:var(--hover)}
+.panel-tab-content .mini-label{font-size:9px;color:#888;display:block;margin-bottom:1px;margin-top:3px}
+.panel-tab-content .mini-input{width:60px!important;display:inline-block;margin-right:4px}
diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js
index 3443339..ca454a0 100644
--- a/internal/admin/static/map.js
+++ b/internal/admin/static/map.js
@@ -6,6 +6,8 @@ var dragRoom = false;
var linkDrag = false, linkFromId = null, linkTargetId = null, linkGhostDir = null;
var delDrag = false, delFromId = null, delTargetId = null;
var roomMap = {}, occupied = {};
+var dirChangedByUser = false;
+var currentPanelTab = 0;
var gridDirs = [
{dx:0,dy:-1,dir:'north'},{dx:0,dy:1,dir:'south'},
{dx:1,dy:0,dir:'east'},{dx:-1,dy:0,dir:'west'},
@@ -22,13 +24,29 @@ function changeZ(dz) {
function changeDir(dir) {
currentDir = dir;
+ dirChangedByUser = true;
+ selectedRoom = null;
loadMap();
}
+function zoomIn() {
+ scale *= 1.1;
+ if (scale > 5) scale = 5;
+ updateView();
+}
+
+function zoomOut() {
+ scale *= 0.9;
+ if (scale < 0.1) scale = 0.1;
+ updateView();
+}
+
function loadMap() {
$('#zLabel').textContent = 'Z=' + currentZ;
var url = '/api/map?z=' + currentZ;
if (currentDir) url += '&dir=' + encodeURIComponent(currentDir);
+ var wasDirChange = dirChangedByUser;
+ dirChangedByUser = false;
API.get(url).then(function(data) {
mapData = data;
if (!currentDir && data.dir) {
@@ -36,11 +54,36 @@ function loadMap() {
}
updateDirSelect();
renderMap(data);
+ renderDisconnectedList(data);
+ if (wasDirChange && data.seed) {
+ selectRoom(data.seed);
+ }
}).catch(function(e) {
notify('Failed to load map: ' + e.message, 'error');
});
}
+function blendColors(colorA, colorB) {
+ var idxA = parseXtermIdx(colorA);
+ var idxB = parseXtermIdx(colorB);
+ if (colorA && !colorB) return xtermToCss(idxA);
+ if (!colorA && colorB) return xtermToCss(idxB);
+ if (!colorA && !colorB) return '#666666';
+ var rgbA = xtermToRGB(idxA);
+ var rgbB = xtermToRGB(idxB);
+ var r = Math.round((rgbA[0] + rgbB[0]) / 2);
+ var g = Math.round((rgbA[1] + rgbB[1]) / 2);
+ var b = Math.round((rgbA[2] + rgbB[2]) / 2);
+ return '#' + ((1 << 24) | (r << 16) | (g << 8) | b).toString(16).slice(1).toUpperCase();
+}
+
+function parseXtermIdx(v) {
+ if (!v) return 0;
+ v = String(v).toUpperCase().replace(/[^0-9A-F]/g, '');
+ if (v.length >= 2) return parseInt(v.substring(0, 2), 16) || 0;
+ return 0;
+}
+
function renderMap(data) {
var svg = $('#mapSvg');
var container = $('#mapContainer');
@@ -66,9 +109,9 @@ function renderMap(data) {
if (!fr || !tr) return;
var x1 = fr.x * CELL + CELL/2, y1 = fr.y * CELL + CELL/2;
var x2 = tr.x * CELL + CELL/2, y2 = tr.y * CELL + CELL/2;
- var c = fr.color ? resolveColor(fr.color) : '#666';
+ var c = blendColors(fr.color, tr.color);
c = c.replace('#','');
- g += '<line x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'" stroke="#'+c+'" stroke-width="6" opacity="0.35"'+ (l.bidirectional ? '' : ' stroke-dasharray="4,3"')+' vector-effect="non-scaling-stroke"/>';
+ g += '<line x1="'+x1+'" y1="'+y1+'" x2="'+x2+'" y2="'+y2+'" stroke="#'+c+'" stroke-width="6" opacity="0.55"'+ (l.bidirectional ? '' : ' stroke-dasharray="4,3"')+' vector-effect="non-scaling-stroke"/>';
});
}
@@ -84,6 +127,8 @@ function renderMap(data) {
var txtColor = hexLuminance(fill) > 0.35 ? '#111' : '#fff';
var sel = selectedRoom === r.id;
roomHTML += '<rect class="rm" x="'+x+'" y="'+y+'" width="'+w+'" height="'+h+'" rx="5" fill="'+fill+'" stroke="'+(sel?'#fff':fill)+'" stroke-width="'+(sel?3:1.5)+'" data-id="'+r.id+'" data-name="'+esc(r.name)+'"/>';
+ var dimColor = hexLuminance(fill) > 0.35 ? 'rgba(0,0,0,0.45)' : 'rgba(255,255,255,0.4)';
+ roomHTML += '<text x="'+(x+w/2)+'" y="'+(y+h/2-10)+'" text-anchor="middle" font-size="8" fill="'+dimColor+'" font-family="monospace" pointer-events="none">#'+r.id+'</text>';
roomHTML += wrapText(r.name, x+w/2, y+h/2, w, txtColor);
var btnY = y+h-4;
@@ -296,29 +341,39 @@ function selectRoom(id) {
});
}
+var currentRoomData = null;
+
function renderSidePanel(data) {
var panel = $('#panelContent');
if (!data || !data.room) {
panel.innerHTML = '<p style="color:#888;text-align:center;margin-top:40px">Room not found</p>';
return;
}
+ currentRoomData = data;
var r = data.room;
var file = data.file || '';
- var html = '<div class="form-group"><label>File</label><span style="font-size:11px;color:#888">' + esc(file) + '</span></div>';
- html += '<div class="form-group"><label>ID</label><input id="roomID" type="number" value="' + r.id + '" onchange="autoSave()"></div>';
- html += '<div class="form-group"><label>Name</label><input id="roomName" value="' + escAttr(r.name || '') + '" oninput="autoSave()"></div>';
- html += '<div class="form-group color-field"><label>Color</label><span class="color-swatch" style="background:'+resolveColor(r.color)+'"></span><input id="roomColor" value="' + escAttr(r.color || '') + '" oninput="autoSave()"></div>';
- html += '<div class="form-group"><label>Directory</label><select id="roomDir" onchange="autoSave()"></select></div>';
- var descText = '';
- if (Array.isArray(r.description)) {
- descText = r.description.map(function(d) { return d.text || ''; }).join('\n\n');
- } else if (typeof r.description === 'string') {
- descText = r.description;
- }
- html += '<div class="form-group"><label>Description</label><textarea id="roomDesc" rows="4" oninput="autoSave()">' + esc(descText) + '</textarea></div>';
- html += '<div class="form-group"><label>Hazard</label><input id="roomHazard" value="' + escAttr(r.hazard || '') + '" oninput="autoSave()"></div>';
- html += '<div class="form-group"><label><input type="checkbox" id="roomBlockTransport" onchange="autoSave()"' + (r.block_transport ? ' checked' : '') + '> Block Transport</label></div>';
+ var objects = Array.isArray(r.objects) ? r.objects : [];
+ var localObjs = objects.filter(function(o) { return !o.id; });
+ var refObjs = objects.filter(function(o) { return !!o.id; });
+ var spawns = Array.isArray(r.item_spawns) ? r.item_spawns : [];
+ var mobs = Array.isArray(r.mobs) ? r.mobs : [];
+
+ var html = '<div class="panel-tabs">';
+ html += '<button class="panel-tab' + (currentPanelTab === 0 ? ' active' : '') + '" onclick="switchPanelTab(0)">Room</button>';
+ html += '<button class="panel-tab' + (currentPanelTab === 1 ? ' active' : '') + '" onclick="switchPanelTab(1)">Local</button>';
+ html += '<button class="panel-tab' + (currentPanelTab === 2 ? ' active' : '') + '" onclick="switchPanelTab(2)">Objects</button>';
+ html += '<button class="panel-tab' + (currentPanelTab === 3 ? ' active' : '') + '" onclick="switchPanelTab(3)">Mobs</button>';
+ html += '<button class="panel-tab' + (currentPanelTab === 4 ? ' active' : '') + '" onclick="switchPanelTab(4)">Items</button>';
+ html += '</div>';
+
+ html += '<div class="panel-tab-content">';
+ if (currentPanelTab === 0) html += renderRoomTab(r, file);
+ else if (currentPanelTab === 1) html += renderLocalTab(localObjs);
+ else if (currentPanelTab === 2) html += renderObjectsTab(refObjs);
+ else if (currentPanelTab === 3) html += renderMobsTab(mobs);
+ else if (currentPanelTab === 4) html += renderItemsTab(spawns);
+ html += '</div>';
panel.innerHTML = html;
setTimeout(function() {
@@ -331,7 +386,8 @@ function renderSidePanel(data) {
loadRoomDirs(function(dirs) {
var sel = $('#roomDir');
if (!sel) return;
- var current = (file || '').replace(/^data\/rooms\/?/, '').split('/')[0] || '';
+ var current = getRoomDirFromFile(file);
+ sel.innerHTML = '';
dirs.forEach(function(d) {
var opt = document.createElement('option');
opt.value = d;
@@ -343,6 +399,656 @@ function renderSidePanel(data) {
}, 50);
}
+function renderRoomTab(r, file) {
+ var html = '<div class="form-group"><label>File</label><span style="font-size:11px;color:#888">' + esc(file) + '</span></div>';
+ html += '<div class="form-group"><label>ID</label><input id="roomID" type="number" value="' + r.id + '" onchange="autoSave()"></div>';
+ html += '<div class="form-group"><label>Name</label><input id="roomName" value="' + escAttr(r.name || '') + '" oninput="autoSave()"></div>';
+ html += '<div class="form-group color-field"><label>Color</label><span class="color-swatch" style="background:'+resolveColor(r.color)+'"></span><input id="roomColor" value="' + escAttr(r.color || '') + '" oninput="autoSave()"></div>';
+ html += '<div class="form-group"><label>Directory</label><select id="roomDir" onchange="autoSave()"></select></div>';
+ var descText = '';
+ if (Array.isArray(r.description)) {
+ descText = r.description.map(function(d) { return d.text || ''; }).join('\n\n');
+ } else if (typeof r.description === 'string') {
+ descText = r.description;
+ }
+ html += '<div class="form-group"><label>Description</label><textarea id="roomDesc" rows="4" oninput="autoSave()">' + esc(descText) + '</textarea></div>';
+ html += '<div class="form-group"><label>Hazard</label><input id="roomHazard" value="' + escAttr(r.hazard || '') + '" oninput="autoSave()"></div>';
+ html += '<div class="form-group"><label><input type="checkbox" id="roomBlockTransport" onchange="autoSave()"' + (r.block_transport ? ' checked' : '') + '> Block Transport</label></div>';
+ html += renderExitsSection(r);
+ return html;
+}
+
+function getRoomDirFromFile(file) {
+ var rel = (file || '').replace(/^data\/rooms\/?/, '');
+ var idx = rel.indexOf('/');
+ if (idx >= 0) {
+ var subIdx = rel.indexOf('/', idx + 1);
+ if (subIdx >= 0) return rel.substring(0, subIdx);
+ return rel.substring(0, idx);
+ }
+ return '';
+}
+
+function switchPanelTab(idx) {
+ currentPanelTab = idx;
+ if (!currentRoomData) return;
+ var r = currentRoomData.room;
+ var file = currentRoomData.file || '';
+ var objects = Array.isArray(r.objects) ? r.objects : [];
+ var localObjs = objects.filter(function(o) { return !o.id; });
+ var refObjs = objects.filter(function(o) { return !!o.id; });
+ var spawns = Array.isArray(r.item_spawns) ? r.item_spawns : [];
+ var mobs = Array.isArray(r.mobs) ? r.mobs : [];
+ var html = '';
+ if (idx === 0) html = renderRoomTab(r, file);
+ else if (idx === 1) html = renderLocalTab(localObjs);
+ else if (idx === 2) html = renderObjectsTab(refObjs);
+ else if (idx === 3) html = renderMobsTab(mobs);
+ else if (idx === 4) html = renderItemsTab(spawns);
+ var content = document.querySelector('.panel-tab-content');
+ if (content) content.innerHTML = html;
+ var tabs = document.querySelectorAll('.panel-tab');
+ tabs.forEach(function(t, i) { t.classList.toggle('active', i === idx); });
+ if (idx === 0) {
+ setTimeout(function() {
+ var cf = document.querySelector('#panelContent .color-field');
+ if (cf) bindColorField(cf);
+ loadRoomDirs(function(dirs) {
+ var sel = $('#roomDir');
+ if (!sel) return;
+ sel.innerHTML = '';
+ var current = getRoomDirFromFile(file);
+ dirs.forEach(function(d) {
+ var opt = document.createElement('option');
+ opt.value = d;
+ opt.textContent = d || '(root)';
+ if (d === current) opt.selected = true;
+ sel.appendChild(opt);
+ });
+ });
+ }, 50);
+ }
+}
+
+function renderLocalTab(localObjs) {
+ var h = '';
+ localObjs.forEach(function(obj, i) {
+ var descText = '';
+ if (Array.isArray(obj.description)) {
+ descText = obj.description.map(function(d) { return d.text || ''; }).join('\n');
+ } else if (typeof obj.description === 'string') {
+ descText = obj.description;
+ }
+ h += '<div class="entry-row" data-idx="' + i + '">';
+ h += '<div class="entry-fields">';
+ h += '<input class="local-name" value="' + escAttr(obj.name || '') + '" placeholder="name" onchange="updateLocalObject(' + i + ')" style="font-weight:bold">';
+ h += '<textarea class="local-desc" rows="2" placeholder="description (one per line)" onchange="updateLocalObject(' + i + ')">' + esc(descText) + '</textarea>';
+ h += '<input class="local-aliases" value="' + escAttr((obj.aliases || []).join(', ')) + '" placeholder="aliases (comma separated)" onchange="updateLocalObject(' + i + ')">';
+ h += '</div>';
+ h += '<button class="btn btn-sm btn-danger" onclick="removeLocalObject(' + i + ')">X</button>';
+ h += '</div>';
+ });
+ h += '<button class="btn btn-sm section-add-btn" onclick="addLocalObject()">+ Add Local Object</button>';
+ return h;
+}
+
+function renderObjectsTab(refObjs) {
+ var h = '';
+ refObjs.forEach(function(obj, i) {
+ h += '<div class="entry-row" data-idx="' + i + '">';
+ h += '<div class="entry-fields"><span class="entry-id">' + esc(obj.id || '') + '</span></div>';
+ h += '<button class="btn btn-sm" onclick="openEditor(\'objects\', \'' + escAttr(obj.id || '') + '\')">Edit</button>';
+ h += '<button class="btn btn-sm btn-danger" onclick="removeRefObject(' + i + ')">X</button>';
+ h += '</div>';
+ });
+ h += '<div class="section-search">';
+ h += '<input type="text" class="search-input" placeholder="Search objects..." oninput="searchRefObjects(this)">';
+ h += '<div class="search-results" style="display:none"></div>';
+ h += '</div>';
+ return h;
+}
+
+function renderMobsTab(mobs) {
+ var h = '';
+ mobs.forEach(function(mob, i) {
+ h += '<div class="entry-row" data-idx="' + i + '">';
+ h += '<div class="entry-fields"><span class="entry-id">' + esc(mob.id || '') + '</span></div>';
+ h += '<button class="btn btn-sm" onclick="openEditor(\'mobs\', \'' + escAttr(mob.id || '') + '\')">Edit</button>';
+ h += '<button class="btn btn-sm btn-danger" onclick="removeMob(' + i + ')">X</button>';
+ h += '</div>';
+ });
+ h += '<div class="section-search">';
+ h += '<input type="text" class="search-input" placeholder="Search mobs..." oninput="searchMobs(this)">';
+ h += '<div class="search-results" style="display:none"></div>';
+ h += '</div>';
+ return h;
+}
+
+function renderItemsTab(spawns) {
+ var h = '';
+ spawns.forEach(function(spawn, i) {
+ h += '<div class="entry-row" data-idx="' + i + '">';
+ h += '<div class="entry-fields">';
+ h += '<span class="entry-id">' + esc(spawn.id || '') + '</span>';
+ h += '<label class="mini-label">Qty</label><input class="mini-input spawn-qty" type="number" value="' + (spawn.quantity || 0) + '" onchange="updateSpawn(' + i + ')">';
+ h += '<label class="mini-label">Respawn</label><input class="mini-input spawn-respawn" type="number" value="' + (spawn.respawn_ticks || 0) + '" onchange="updateSpawn(' + i + ')">';
+ h += '</div>';
+ h += '<button class="btn btn-sm" onclick="openEditor(\'items\', \'' + escAttr(spawn.id || '') + '\')">Edit</button>';
+ h += '<button class="btn btn-sm btn-danger" onclick="removeSpawn(' + i + ')">X</button>';
+ h += '</div>';
+ });
+ h += '<div class="section-search">';
+ h += '<input type="text" class="search-input" placeholder="Search items..." oninput="searchItemSpawns(this)">';
+ h += '<div class="search-results" style="display:none"></div>';
+ h += '</div>';
+ return h;
+}
+
+function renderExitsSection(r) {
+ var exits = r.exits || {};
+ var exitDirs = ['north','south','east','west','northeast','northwest','southeast','southwest','up','down'];
+ var dirAliases = {n:'north',s:'south',e:'east',w:'west',ne:'northeast',nw:'northwest',se:'southeast',sw:'southwest',u:'up',d:'down'};
+ var h = '<div class="exit-section"><h3>Exits</h3>';
+ exitDirs.forEach(function(d) {
+ var exit = exits[d];
+ if (!exit) return;
+ var target = typeof exit === 'object' ? exit.room : exit;
+ var cond = (typeof exit === 'object' && exit.condition) ? exit.condition : null;
+ var bmsg = (typeof exit === 'object' && exit.blocked_message) ? exit.blocked_message : '';
+ var setFlags = (typeof exit === 'object' && exit.set_flags) ? exit.set_flags : null;
+ var setPlayerFlags = (typeof exit === 'object' && exit.set_player_flags) ? exit.set_player_flags : null;
+ h += '<div class="exit-row" data-dir="' + d + '">';
+ h += '<span class="exit-dir">' + d + '</span>';
+ h += '<span class="exit-target" onclick="selectRoom(' + target + ')"># ' + target + '</span>';
+ if (bmsg) h += '<span class="exit-meta">Blocked: ' + esc(bmsg) + '</span>';
+ if (cond) h += '<span class="exit-meta">Conditional</span>';
+ h += '<button class="btn btn-sm" onclick="editExitCondition(\'' + d + '\')">Edit</button>';
+ h += '<button class="btn btn-sm btn-danger" onclick="deleteExit(\'' + d + '\', ' + target + ')">X</button>';
+ h += '</div>';
+ });
+ h += '<div class="exit-add">';
+ h += '<select id="exitDirSelect">';
+ exitDirs.forEach(function(d) { h += '<option value="' + d + '">' + d + '</option>'; });
+ h += '</select>';
+ h += '<input id="exitTargetID" type="number" placeholder="Target ID">';
+ h += '<button class="exit-add-btn" onclick="addExit()">Add</button>';
+ h += '</div>';
+ h += '</div>';
+ return h;
+}
+
+var editingExitDir = null;
+
+function editExitCondition(dir) {
+ editingExitDir = dir;
+ var exits = (currentRoomData && currentRoomData.room && currentRoomData.room.exits) || {};
+ var exit = exits[dir];
+ if (!exit) return;
+ if (typeof exit !== 'object') { exit = {room: exit}; }
+ var cond = exit.condition || null;
+ var bmsg = exit.blocked_message || '';
+ var setFlags = exit.set_flags || null;
+ var setPlayerFlags = exit.set_player_flags || null;
+
+ var flagsStr = '';
+ if (setFlags) flagsStr = Object.keys(setFlags).map(function(k) { return k + '=' + setFlags[k]; }).join(', ');
+ var pflagsStr = '';
+ if (setPlayerFlags) pflagsStr = Object.keys(setPlayerFlags).map(function(k) { return k + '=' + setPlayerFlags[k]; }).join(', ');
+
+ var parsed = parseCondition(cond);
+
+ var overlay = document.createElement('div');
+ overlay.className = 'cm-overlay exit-overlay';
+ var menu = document.createElement('div');
+ menu.className = 'cm-menu exit-modal';
+ menu.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);min-width:420px;max-height:85vh;overflow-y:auto;padding:14px;z-index:201';
+
+ var h = '<h3 style="margin-bottom:8px;font-size:13px">Exit: ' + esc(dir) + ' &rarr; #' + exit.room + '</h3>';
+ h += '<div class="form-group"><label>Blocked Message</label><textarea id="exitBmsg" rows="2">' + esc(bmsg) + '</textarea></div>';
+ h += '<div class="form-group"><label>Set Flags (key=value, comma separated)</label><input id="exitFlags" value="' + escAttr(flagsStr) + '"></div>';
+ h += '<div class="form-group"><label>Set Player Flags (key=value, comma separated)</label><input id="exitPFlags" value="' + escAttr(pflagsStr) + '"></div>';
+
+ h += '<div style="margin-top:10px;border-top:1px solid var(--border);padding-top:8px">';
+ h += '<label style="font-size:11px;color:#aaa;text-transform:uppercase;letter-spacing:.5px">Conditions</label>';
+ h += '<div id="condClauses" style="margin-top:4px"></div>';
+ h += '<button class="btn btn-sm section-add-btn" onclick="addConditionClause()">+ Add Condition</button>';
+ h += '<div class="btn-row" style="margin-top:10px">';
+ h += '<button class="btn btn-primary" onclick="saveExitCondition()">Save</button>';
+ h += '<button class="btn btn-danger" onclick="clearExitCondition()">Clear</button>';
+ h += '<button class="btn" onclick="closeExitModal()">Cancel</button>';
+ h += '</div></div>';
+
+ menu.innerHTML = h;
+ document.body.appendChild(overlay);
+ document.body.appendChild(menu);
+ overlay.onclick = closeExitModal;
+
+ menu._condClauses = parsed.clauses;
+ menu._condMode = parsed.mode;
+ // Fix: mode is always 'all' for our editor, but store what was parsed
+ renderConditionClauses(menu);
+}
+
+function parseCondition(cond) {
+ var clauses = [];
+ if (!cond || Object.keys(cond).length === 0) return {clauses: clauses, mode: 'all'};
+ var arr = cond.all_of;
+ if (!arr || !Array.isArray(arr) || arr.length === 0) arr = cond.any_of;
+ if (arr && Array.isArray(arr) && arr.length > 0) {
+ arr.forEach(function(c) {
+ var clause = extractClause(c);
+ if (clause) clauses.push(clause);
+ });
+ return {clauses: clauses, mode: 'all'};
+ }
+ var clause = extractClause(cond);
+ if (clause) clauses.push(clause);
+ return {clauses: clauses, mode: 'all'};
+}
+
+function extractClause(c) {
+ var not = c.not === true;
+ if (c.player_flag && c.player_flag !== '') return {type: 'player_flag', value: c.player_flag, not: not};
+ if (c.flag && c.flag !== '') return {type: 'flag', value: c.flag, not: not};
+ if (c.has_item && c.has_item !== '') return {type: 'has_item', value: c.has_item, not: not};
+ if (c.min_credits && c.min_credits !== 0) return {type: 'min_credits', value: String(c.min_credits), not: not};
+ if (c.value && typeof c.value === 'object' && c.value.key) return {type: 'value', value: c.value.key + '=' + (c.value.value || ''), not: not};
+ return null;
+}
+
+function renderConditionClauses(menu) {
+ var container = menu.querySelector('#condClauses');
+ if (!container) return;
+ var clauses = menu._condClauses || [];
+ var h = '';
+ clauses.forEach(function(clause, i) {
+ h += '<div class="cond-clause" data-idx="' + i + '" style="display:flex;gap:4px;align-items:center;margin-bottom:4px">';
+ h += '<select class="cond-type" onchange="var c=this.parentElement;var m=this.closest(\'.exit-modal\');var idx=parseInt(c.getAttribute(\'data-idx\'));m._condClauses[idx].type=this.value;renderConditionClauses(m)">';
+ ['player_flag','flag','value','has_item','min_credits'].forEach(function(t) {
+ h += '<option value="' + t + '"' + (clause.type === t ? ' selected' : '') + '>' + t + '</option>';
+ });
+ h += '</select>';
+ if (clause.type === 'value') {
+ var kv = clause.value ? clause.value.split('=', 2) : ['',''];
+ var kn = (kv[0] || '').trim();
+ var kvv = (kv[1] || '').trim();
+ h += '<input class="cond-val-key" placeholder="key" value="' + escAttr(kn) + '" onchange="updateClauseData(this)" style="width:80px">';
+ h += '<span style="color:#888">=</span>';
+ h += '<input class="cond-val-val" placeholder="value" value="' + escAttr(kvv) + '" onchange="updateClauseData(this)" style="flex:1">';
+ } else {
+ h += '<input class="cond-val" value="' + escAttr(clause.value || '') + '" onchange="updateClauseData(this)" style="flex:1">';
+ }
+ h += '<label style="font-size:10px;color:#aaa;white-space:nowrap"><input type="checkbox" class="cond-not"' + (clause.not ? ' checked' : '') + ' onchange="updateClauseData(this)"> not</label>';
+ h += '<button class="btn btn-sm btn-danger" onclick="removeConditionClause(this)" style="flex-shrink:0">X</button>';
+ h += '</div>';
+ });
+ container.innerHTML = h;
+}
+
+function updateClauseData(el) {
+ var clauseEl = el.closest('.cond-clause');
+ var menu = el.closest('.exit-modal');
+ if (!clauseEl || !menu) return;
+ var idx = parseInt(clauseEl.getAttribute('data-idx'));
+ var clause = menu._condClauses[idx];
+ if (!clause) return;
+ clause.type = clauseEl.querySelector('.cond-type').value;
+ clause.not = clauseEl.querySelector('.cond-not').checked;
+ if (clause.type === 'value') {
+ var k = clauseEl.querySelector('.cond-val-key');
+ var v = clauseEl.querySelector('.cond-val-val');
+ clause.value = (k ? k.value.trim() : '') + '=' + (v ? v.value.trim() : '');
+ } else {
+ var val = clauseEl.querySelector('.cond-val');
+ clause.value = val ? val.value : '';
+ }
+}
+
+function addConditionClause() {
+ var menu = document.querySelector('.exit-modal');
+ if (!menu) return;
+ if (!menu._condClauses) menu._condClauses = [];
+ menu._condClauses.push({type: 'player_flag', value: '', not: false});
+ renderConditionClauses(menu);
+}
+
+function removeConditionClause(btn) {
+ var menu = btn.closest('.exit-modal');
+ var clauseEl = btn.closest('.cond-clause');
+ if (!menu || !clauseEl) return;
+ var idx = parseInt(clauseEl.getAttribute('data-idx'));
+ menu._condClauses.splice(idx, 1);
+ renderConditionClauses(menu);
+}
+
+function closeExitModal() {
+ editingExitDir = null;
+ var ov = document.querySelector('.exit-overlay');
+ if (ov) ov.remove();
+ var m = document.querySelector('.exit-modal');
+ if (m) m.remove();
+}
+
+function saveExitCondition() {
+ if (!editingExitDir || !currentRoomData || !currentRoomData.room) return;
+ var menu = document.querySelector('.exit-modal');
+ if (!menu) return;
+ var r = currentRoomData.room;
+ var exits = r.exits || {};
+ var exit = exits[editingExitDir];
+ if (!exit) return;
+ if (typeof exit !== 'object') {
+ exit = {room: exit};
+ exits[editingExitDir] = exit;
+ r.exits = exits;
+ }
+
+ var bmsg = document.getElementById('exitBmsg');
+ exit.blocked_message = (bmsg && bmsg.value) ? bmsg.value : '';
+ var flagsStr = document.getElementById('exitFlags');
+ var pflagsStr = document.getElementById('exitPFlags');
+ exit.set_flags = parseFlagInput(flagsStr ? flagsStr.value : '');
+ exit.set_player_flags = parseFlagInput(pflagsStr ? pflagsStr.value : '');
+
+ // Update clause data from DOM before saving
+ var clauseEls = menu.querySelectorAll('.cond-clause');
+ clauseEls.forEach(function(el) { updateClauseData(el); });
+
+ var clauses = (menu._condClauses || []).filter(function(c) { return c.value && c.value !== '' && c.value !== '='; });
+ var condition = null;
+ if (clauses.length === 1) {
+ condition = buildSingleCondition(clauses[0]);
+ } else if (clauses.length > 1) {
+ condition = {all_of: clauses.map(function(c) { return buildSingleCondition(c); })};
+ }
+ exit.condition = condition;
+
+ r.exits = exits;
+ autoSave();
+ closeExitModal();
+ reRenderPanel();
+}
+
+function buildSingleCondition(clause) {
+ var obj = {not: !!clause.not};
+ if (clause.type === 'value') {
+ var parts = (clause.value || '').split('=', 2);
+ obj.value = {key: (parts[0] || '').trim(), value: (parts[1] || '').trim()};
+ } else {
+ obj[clause.type] = clause.value;
+ }
+ return obj;
+}
+
+function clearExitCondition() {
+ var menu = document.querySelector('.exit-modal');
+ if (!menu) return;
+ menu._condClauses = [];
+ renderConditionClauses(menu);
+ var bmsg = document.getElementById('exitBmsg');
+ if (bmsg) bmsg.value = '';
+ var ef = document.getElementById('exitFlags');
+ if (ef) ef.value = '';
+ var pf = document.getElementById('exitPFlags');
+ if (pf) pf.value = '';
+}
+
+function parseFlagInput(val) {
+ if (!val || !val.trim()) return null;
+ var m = {};
+ val.split(',').forEach(function(pair) {
+ var kv = pair.trim().split('=');
+ if (kv.length === 2) {
+ var v = kv[1].trim();
+ if (v === 'true') v = true;
+ else if (v === 'false') v = false;
+ else if (!isNaN(v)) v = parseFloat(v);
+ m[kv[0].trim()] = v;
+ }
+ });
+ return Object.keys(m).length > 0 ? m : null;
+}
+
+function addExit() {
+ if (!selectedRoom || !currentRoomData) return;
+ var dir = $('#exitDirSelect').value;
+ var target = parseInt($('#exitTargetID').value);
+ if (!dir || !target) { notify('Enter target room ID', 'error'); return; }
+ API.post('/api/rooms/link', {from: selectedRoom, to: target, dir: dir}).then(function() {
+ notify('Exit added: ' + dir + ' #' + target, 'success');
+ updateUndoBar();
+ loadMap();
+ selectRoom(selectedRoom);
+ }).catch(function(e) { notify('Add exit failed: ' + extractError(e), 'error'); });
+}
+
+function deleteExit(dir, target) {
+ if (!selectedRoom || !currentRoomData) return;
+ if (!confirm('Delete exit ' + dir + ' to #' + target + '?')) return;
+ fetch('/api/rooms/link', {
+ method: 'DELETE',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({from: selectedRoom, to: target}),
+ credentials: 'same-origin'
+ }).then(function(r) {
+ if (!r.ok) return r.json().then(function(j) { throw new Error(j.error || 'request failed'); });
+ return r.json();
+ }).then(function() {
+ notify('Removed exit ' + dir + ' to #' + target, 'success');
+ updateUndoBar();
+ loadMap();
+ selectRoom(selectedRoom);
+ }).catch(function(e) { notify('Delete exit failed: ' + extractError(e), 'error'); });
+}
+
+function extractError(e) {
+ var msg = e.message || 'unknown error';
+ try { var j = JSON.parse(msg); if (j.error) msg = j.error; } catch(_) {}
+ return msg;
+}
+
+function getRoomObjects() {
+ if (!currentRoomData || !currentRoomData.room) return [];
+ var objects = currentRoomData.room.objects || [];
+ if (!Array.isArray(objects)) return [];
+ return objects;
+}
+
+function setRoomObjects(objs) {
+ if (!currentRoomData || !currentRoomData.room) return;
+ currentRoomData.room.objects = objs;
+ autoSave();
+}
+
+function getRoomSpawns() {
+ if (!currentRoomData || !currentRoomData.room) return [];
+ var spawns = currentRoomData.room.item_spawns || [];
+ if (!Array.isArray(spawns)) return [];
+ return spawns;
+}
+
+function setRoomSpawns(spawns) {
+ if (!currentRoomData || !currentRoomData.room) return;
+ currentRoomData.room.item_spawns = spawns;
+ autoSave();
+}
+
+function getRoomMobs() {
+ if (!currentRoomData || !currentRoomData.room) return [];
+ var mobs = currentRoomData.room.mobs || [];
+ if (!Array.isArray(mobs)) return [];
+ return mobs;
+}
+
+function setRoomMobs(mobs) {
+ if (!currentRoomData || !currentRoomData.room) return;
+ currentRoomData.room.mobs = mobs;
+ autoSave();
+}
+
+function addLocalObject() {
+ var objs = getRoomObjects();
+ objs.push({name: '', description: [{text: ''}], aliases: [], hidden: false});
+ setRoomObjects(objs);
+ reRenderPanel();
+}
+
+function updateLocalObject(i) {
+ var objs = getRoomObjects();
+ var localObjs = objs.filter(function(o) { return !o.id; });
+ if (i < 0 || i >= localObjs.length) return;
+ var row = document.querySelector('.panel-tab-content .entry-row[data-idx="' + i + '"]');
+ if (!row) return;
+ var nameEl = row.querySelector('.local-name');
+ var descEl = row.querySelector('.local-desc');
+ var aliasesEl = row.querySelector('.local-aliases');
+ localObjs[i].name = nameEl ? nameEl.value : '';
+ var descVal = descEl ? descEl.value : '';
+ if (descVal) {
+ localObjs[i].description = descVal.split('\n').map(function(line) { return {text: line}; });
+ } else {
+ localObjs[i].description = [];
+ }
+ if (aliasesEl && aliasesEl.value) {
+ localObjs[i].aliases = aliasesEl.value.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s; });
+ } else {
+ localObjs[i].aliases = [];
+ }
+ var refObjs = objs.filter(function(o) { return !!o.id; });
+ setRoomObjects(localObjs.concat(refObjs));
+}
+
+function removeLocalObject(i) {
+ var objs = getRoomObjects();
+ var localObjs = objs.filter(function(o) { return !o.id; });
+ if (i < 0 || i >= localObjs.length) return;
+ localObjs.splice(i, 1);
+ var refObjs = objs.filter(function(o) { return !!o.id; });
+ setRoomObjects(localObjs.concat(refObjs));
+ reRenderPanel();
+}
+
+function removeRefObject(i) {
+ var objs = getRoomObjects();
+ var refObjs = objs.filter(function(o) { return !!o.id; });
+ if (i < 0 || i >= refObjs.length) return;
+ refObjs.splice(i, 1);
+ var localObjs = objs.filter(function(o) { return !o.id; });
+ setRoomObjects(localObjs.concat(refObjs));
+ reRenderPanel();
+}
+
+function addRefObject(id) {
+ var objs = getRoomObjects();
+ objs.push({id: id});
+ setRoomObjects(objs);
+ reRenderPanel();
+}
+
+function searchRefObjects(input) {
+ searchAndShow(input, 'objects', function(r) {
+ addRefObject(r.id);
+ });
+}
+
+function removeSpawn(i) {
+ var spawns = getRoomSpawns();
+ spawns.splice(i, 1);
+ setRoomSpawns(spawns);
+ reRenderPanel();
+}
+
+function updateSpawn(i) {
+ var spawns = getRoomSpawns();
+ if (i < 0 || i >= spawns.length) return;
+ var row = document.querySelector('.panel-tab-content .entry-row[data-idx="' + i + '"]');
+ if (!row) return;
+ var qtyEl = row.querySelector('.spawn-qty');
+ var respawnEl = row.querySelector('.spawn-respawn');
+ if (qtyEl) spawns[i].quantity = parseInt(qtyEl.value) || 0;
+ if (respawnEl) spawns[i].respawn_ticks = parseInt(respawnEl.value) || 0;
+ setRoomSpawns(spawns);
+}
+
+function addSpawn(id) {
+ var spawns = getRoomSpawns();
+ spawns.push({id: id, quantity: 1, respawn_ticks: 0});
+ setRoomSpawns(spawns);
+ reRenderPanel();
+}
+
+function searchItemSpawns(input) {
+ searchAndShow(input, 'items', function(r) {
+ addSpawn(r.id);
+ });
+}
+
+function removeMob(i) {
+ var mobs = getRoomMobs();
+ mobs.splice(i, 1);
+ setRoomMobs(mobs);
+ reRenderPanel();
+}
+
+function addMob(id) {
+ var mobs = getRoomMobs();
+ mobs.push({id: id});
+ setRoomMobs(mobs);
+ reRenderPanel();
+}
+
+function searchMobs(input) {
+ searchAndShow(input, 'mobs', function(r) {
+ addMob(r.id);
+ });
+}
+
+function searchAndShow(input, type, onSelect) {
+ var val = input.value.trim();
+ var results = input.parentElement.querySelector('.search-results');
+ if (!val) { results.style.display = 'none'; results.innerHTML = ''; return; }
+ API.get('/api/search?q=' + encodeURIComponent(val)).then(function(data) {
+ var items = [];
+ if (type === 'objects' && data.objects) items = data.objects;
+ if (type === 'items' && data.items) items = data.items;
+ if (type === 'mobs' && data.mobs) items = data.mobs;
+ if (items.length === 0) {
+ results.style.display = 'block';
+ results.innerHTML = '<div class="sr-item" style="color:#888">No results</div>';
+ return;
+ }
+ results.style.display = 'block';
+ results.innerHTML = items.map(function(r) {
+ return '<div class="sr-item" onclick="event.stopPropagation(); selectSearchResult(this)">' + esc(r.id) + ' <span style="color:#aaa">' + esc(r.name || '') + '</div>';
+ }).join('');
+ results._items = items;
+ results._onSelect = onSelect;
+ }).catch(function() {
+ results.style.display = 'none';
+ });
+}
+
+function selectSearchResult(el) {
+ var results = el.parentElement;
+ var idx = Array.prototype.indexOf.call(results.children, el);
+ if (results._items && results._items[idx] && results._onSelect) {
+ results._onSelect(results._items[idx]);
+ results.style.display = 'none';
+ results.innerHTML = '';
+ var input = results.parentElement.querySelector('.search-input');
+ if (input) input.value = '';
+ }
+}
+
+function openEditor(type, id) {
+ window.open('/editor/' + type + '#' + id, '_blank');
+}
+
+function reRenderPanel() {
+ if (!currentRoomData) return;
+ renderSidePanel(currentRoomData);
+}
+
function autoSave() {
if (saveTimer) clearTimeout(saveTimer);
saveTimer = setTimeout(function() { doSavePanel(); }, 600);
@@ -351,6 +1057,12 @@ function autoSave() {
function doSavePanel() {
var id = selectedRoom;
if (!id) return;
+ var save = function(r) {
+ API.put('/api/rooms/' + id, r).then(function() {
+ updateUndoBar();
+ }).catch(function(e) { notify('Save failed: '+e.message, 'error'); });
+ };
+
API.get('/api/rooms/' + id).then(function(data) {
var r = data.room || {};
r.id = id;
@@ -360,29 +1072,30 @@ function doSavePanel() {
el = $('#roomHazard'); if (el) r.hazard = el.value;
el = $('#roomBlockTransport'); if (el) r.block_transport = el.checked;
+ if (currentRoomData && currentRoomData.room) {
+ r.objects = currentRoomData.room.objects;
+ r.item_spawns = currentRoomData.room.item_spawns;
+ r.mobs = currentRoomData.room.mobs;
+ r.exits = currentRoomData.room.exits;
+ }
+
var newID = parseInt($('#roomID').value) || id;
var newDir = $('#roomDir').value;
var currentDir = (data.file || '').replace(/^data\/rooms\/?/, '').split('/')[0] || '';
- var save = function() {
- API.put('/api/rooms/' + id, r).then(function() {
- updateUndoBar();
- }).catch(function(e) { notify('Save failed: '+e.message, 'error'); });
- };
-
if (newDir !== currentDir && newDir !== undefined) {
API.post('/api/rooms/move', {id: id, dir: newDir}).then(function() {
if (newID !== id) {
API.post('/api/rooms/rename', {id: id, new_id: newID}).then(function() {
selectRoom(newID);
- }).catch(function(e) { notify('Rename failed: '+e.message, 'error'); save(); });
- } else { save(); }
- }).catch(function(e) { notify('Move failed: '+e.message, 'error'); save(); });
+ }).catch(function(e) { notify('Rename failed: '+e.message, 'error'); save(r); });
+ } else { save(r); }
+ }).catch(function(e) { notify('Move failed: '+e.message, 'error'); save(r); });
} else if (newID !== id) {
API.post('/api/rooms/rename', {id: id, new_id: newID}).then(function() {
selectRoom(newID);
- }).catch(function(e) { notify('Rename failed: '+e.message, 'error'); save(); });
- } else { save(); }
+ }).catch(function(e) { notify('Rename failed: '+e.message, 'error'); save(r); });
+ } else { save(r); }
});
}
@@ -699,20 +1412,22 @@ function clearDelHighlight() {
}
function deleteLink(fromId, toId) {
+ var orphanedRooms = [];
if (mapData && mapData.links) {
var checkOrphan = function(roomId, otherId) {
- var room = roomMap[roomId];
- if (!room) return false;
var otherLinks = mapData.links.filter(function(l) {
return (l.from === roomId || l.to === roomId) && !((l.from === roomId && l.to === otherId) || (l.from === otherId && l.to === roomId));
});
if (otherLinks.length === 0) {
- if (!confirm('Removing this link will orphan room #'+roomId+' and it will disappear from the map. Continue?')) return false;
+ orphanedRooms.push(roomId);
}
- return true;
};
- if (!checkOrphan(toId, fromId)) return;
- if (!checkOrphan(fromId, toId)) return;
+ checkOrphan(toId, fromId);
+ checkOrphan(fromId, toId);
+ if (orphanedRooms.length > 0) {
+ var names = orphanedRooms.map(function(id) { return '#'+id; }).join(', ');
+ if (!confirm('Deleting this link will also delete room(s) ' + names + ' because they have no other connections. Continue?')) return;
+ }
}
fetch('/api/rooms/link', {
@@ -724,19 +1439,75 @@ function deleteLink(fromId, toId) {
if (!r.ok) throw new Error('unlink failed');
return r.json();
}).then(function() {
- notify('Removed link #'+fromId+' '+toId, 'success');
+ return Promise.all(orphanedRooms.map(function(id) {
+ return API.del('/api/rooms/' + id);
+ }));
+ }).then(function() {
+ var msg = 'Removed link #'+fromId+' '+toId;
+ if (orphanedRooms.length > 0) {
+ msg += ' and deleted room(s) ' + orphanedRooms.map(function(id) { return '#'+id; }).join(', ');
+ }
+ notify(msg, 'success');
updateUndoBar();
- loadMap();
- if (selectedRoom) {
- API.get('/api/rooms/' + selectedRoom).then(function(data) {
- renderSidePanel(data);
- });
+ if (orphanedRooms.indexOf(selectedRoom) !== -1) {
+ selectedRoom = null;
+ $('#panelContent').innerHTML = '<p style="color:#888;text-align:center;margin-top:40px">Room deleted</p>';
}
+ loadMap();
}).catch(function(e) {
notify('Delete link failed: ' + e.message, 'error');
});
}
+function renderDisconnectedList(data) {
+ var panel = $('#disconnectedPanel');
+ var list = $('#disconnectedList');
+ if (!panel || !list) return;
+ var dc = data.disconnected || [];
+ if (dc.length === 0) {
+ panel.style.display = 'none';
+ return;
+ }
+ panel.style.display = 'flex';
+ dc.sort(function(a, b) { return a.id - b.id; });
+ list.innerHTML = dc.map(function(r) {
+ return '<div class="dc-item" onclick="selectRoom(' + r.id + ')" title="' + esc(r.name || '') + '">' +
+ '<span class="dc-id">#' + r.id + '</span>' +
+ '<span class="dc-name">' + esc(r.name || '') + '</span>' +
+ '</div>';
+ }).join('');
+}
+
+function initPanelResize() {
+ var handle = document.createElement('div');
+ handle.className = 'panel-resize-handle';
+ var panel = $('#sidePanel');
+ if (!panel) return;
+ panel.appendChild(handle);
+ var startX, startW;
+ handle.addEventListener('mousedown', function(e) {
+ e.preventDefault();
+ startX = e.clientX;
+ startW = panel.offsetWidth;
+ document.body.style.userSelect = 'none';
+ document.body.style.cursor = 'col-resize';
+ document.addEventListener('mousemove', onMove);
+ document.addEventListener('mouseup', onUp);
+ });
+ function onMove(e) {
+ var newW = startW + (startX - e.clientX);
+ if (newW < 300) newW = 300;
+ if (newW > 800) newW = 800;
+ panel.style.width = newW + 'px';
+ }
+ function onUp() {
+ document.body.style.userSelect = '';
+ document.body.style.cursor = '';
+ document.removeEventListener('mousemove', onMove);
+ document.removeEventListener('mouseup', onUp);
+ }
+}
+
function esc(s) {
if (!s) return '';
return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
@@ -772,4 +1543,4 @@ function updateDirSelect() {
}
}
-document.addEventListener('DOMContentLoaded', function() { loadRoomDirs(function() { loadMap(); }); });
+document.addEventListener('DOMContentLoaded', function() { loadRoomDirs(function() { loadMap(); }); initPanelResize(); });
diff --git a/internal/admin/templates/map.html b/internal/admin/templates/map.html
index 7f8d5a1..869ee67 100644
--- a/internal/admin/templates/map.html
+++ b/internal/admin/templates/map.html
@@ -1,5 +1,11 @@
{{define "body-map"}}
<div class="layout">
+ <div class="panel panel-left" id="disconnectedPanel">
+ <div class="panel-header">Disconnected Rooms</div>
+ <div id="disconnectedList" style="overflow-y:auto;flex:1">
+ <p style="color:#888;text-align:center;margin-top:20px;font-size:11px">None</p>
+ </div>
+ </div>
<div class="main" id="mapContainer">
<div class="z-controls">
<button onclick="changeZ(-1)">-</button>
@@ -7,6 +13,8 @@
<button onclick="changeZ(1)">+</button>
<button onclick="changeZ(0)" style="width:auto;font-size:11px">Reset</button>
<select id="dirSelect" onchange="changeDir(this.value)" style="margin-left:8px;padding:3px 6px;font-size:11px;background:var(--input-bg);color:var(--text);border:1px solid var(--input-border);border-radius:3px;font-family:monospace"></select>
+ <button onclick="zoomIn()" title="Zoom in" style="margin-left:8px;width:auto;font-size:14px">&#x2795;</button>
+ <button onclick="zoomOut()" title="Zoom out" style="width:auto;font-size:14px">&#x2796;</button>
</div>
<svg id="mapSvg" style="width:100%;height:100%"></svg>
<div class="room-tooltip" id="tooltip" style="display:none"></div>
diff --git a/internal/config/config.go b/internal/config/config.go
index 15f75d4..40f4891 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -100,9 +100,8 @@ type HTTPSConfig struct {
}
type AdminHTTPSConfig struct {
- Enabled bool `yaml:"enabled"`
- Port int `yaml:"port"`
- AdminAccounts []string `yaml:"admin_accounts"`
+ Enabled bool `yaml:"enabled"`
+ Port int `yaml:"port"`
}
func Default() *Config {
@@ -135,9 +134,8 @@ func Default() *Config {
Port: 8443,
},
AdminHTTPS: AdminHTTPSConfig{
- Enabled: false,
- Port: 9090,
- AdminAccounts: []string{},
+ Enabled: false,
+ Port: 9090,
},
}
}