diff options
| author | historia <[not public]> | 2026-07-06 21:56:32 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-06 21:56:32 -0400 |
| commit | e9144cad4a8c55dcee4a694149f06588edd8595a (patch) | |
| tree | 5bef00b718d1b7c898064ea98ddd9d8ac630dd42 | |
| parent | cd52f9dbb8c35f37a9b52e8092bf5d722d56b044 (diff) | |
| download | thehouseoficarus-e9144cad4a8c55dcee4a694149f06588edd8595a.tar.gz | |
fix: change one-way modifier on admin map from shift to ctrl for firefox support
| -rw-r--r-- | data/.admin_history.json | 1 | ||||
| -rw-r--r-- | data/rooms/example_unused/town/9999915.yaml | 2 | ||||
| -rw-r--r-- | data/rooms/example_unused/town/9999916.yaml | 17 | ||||
| -rw-r--r-- | data/rooms/example_unused/underground2/9999951.yaml | 23 | ||||
| -rw-r--r-- | data/rooms/example_unused/underground2/9999952.yaml | 21 | ||||
| -rw-r--r-- | data/rooms/example_unused/underground2/9999953.yaml | 21 | ||||
| -rw-r--r-- | data/rooms/intro/1005.yaml | 14 | ||||
| -rw-r--r-- | data/rooms/intro/2001.yaml | 4 | ||||
| -rw-r--r-- | internal/admin/api_map.go | 119 | ||||
| -rw-r--r-- | internal/admin/static/map.js | 51 |
10 files changed, 201 insertions, 72 deletions
diff --git a/data/.admin_history.json b/data/.admin_history.json new file mode 100644 index 0000000..a246035 --- /dev/null +++ b/data/.admin_history.json @@ -0,0 +1 @@ +{"history":[{"time":"2026-07-06T21:55:44-04:00","description":"create room 1006","file_path":"data/rooms/intro/1006.yaml","old_content":"","new_content":"exits:\n south: 1005\nid: 1006\nname: 'Room #1006'\n","is_delete":false,"is_create":true,"extra_files":[{"file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n"}]},{"time":"2026-07-06T21:55:47-04:00","description":"link 1005 north 1006","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-06T21:55:56-04:00","description":"link 1005 north 1006","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-06T21:55:58-04:00","description":"link 1005 north 1006","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-06T21:55:59-04:00","description":"unlink 1005 north 1006","file_path":"data/rooms/intro/1005.yaml","old_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n north:\n room: 1006\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","new_content":"id: 1005\nname: 'Room #1005'\ncolor: \"\"\ndescription: []\nexits:\n south:\n room: 2001\nobjects: []\nitem_spawns: []\nmobs: []\non_enter: []\nhazard: \"\"\nblock_transport: false\ntriggers: []\n","is_delete":false,"is_create":false},{"time":"2026-07-06T21:56:05-04:00","description":"delete room 1006 (cleaned 0 exits)","file_path":"data/rooms/intro/1006.yaml","old_content":"exits:\n south: 1005\nid: 1006\nname: 'Room #1006'\n","new_content":"","is_delete":true,"is_create":false}],"redo":null}
\ No newline at end of file diff --git a/data/rooms/example_unused/town/9999915.yaml b/data/rooms/example_unused/town/9999915.yaml index 95c07cb..cef822b 100644 --- a/data/rooms/example_unused/town/9999915.yaml +++ b/data/rooms/example_unused/town/9999915.yaml @@ -4,8 +4,6 @@ color: "" description: - text: Shelves lined with glass vials, dried herbs, and bubbling cauldrons. This area is not yet accessible. exits: - east: - room: 9999916 west: room: 9999914 objects: [] diff --git a/data/rooms/example_unused/town/9999916.yaml b/data/rooms/example_unused/town/9999916.yaml index 5d3c3c2..a915452 100644 --- a/data/rooms/example_unused/town/9999916.yaml +++ b/data/rooms/example_unused/town/9999916.yaml @@ -1,11 +1,16 @@ -name: "Construction Site" -description: "A half-built structure surrounded by planks, nails, and blueprints. A workbench stands ready for crafting. Signs point north toward the Local Neighborhood and east toward the lumberyard." -exits: - east: 99999171 - west: 9999915 - north: 99999170 +id: 9999916 +name: Construction Site +color: "" +description: + - text: A half-built structure surrounded by planks, nails, and blueprints. A workbench stands ready for crafting. Signs point north toward the Local Neighborhood and east toward the lumberyard. +exits: {} objects: - id: workbench +item_spawns: [] mobs: - id: greater_drone - id: estate_broker +on_enter: [] +hazard: "" +block_transport: false +triggers: [] diff --git a/data/rooms/example_unused/underground2/9999951.yaml b/data/rooms/example_unused/underground2/9999951.yaml index 9c7707a..dd8440e 100644 --- a/data/rooms/example_unused/underground2/9999951.yaml +++ b/data/rooms/example_unused/underground2/9999951.yaml @@ -1,9 +1,18 @@ -name: "Sewer Tunnels" -description: "Dark, damp tunnels beneath the settlement. The floor is slick with moisture and something {82}slimy{/}. The smell is indescribable." +id: 9999951 +name: Sewer Tunnels +color: "" +description: + - text: Dark, damp tunnels beneath the settlement. The floor is slick with moisture and something {82}slimy{/}. The smell is indescribable. exits: - north: 9999950 - south: 9999952 + north: + room: 9999950 +objects: [] +item_spawns: [] mobs: - - "slug" - - "slug" - - "slug" + - id: slug + - id: slug + - id: slug +on_enter: [] +hazard: "" +block_transport: false +triggers: [] diff --git a/data/rooms/example_unused/underground2/9999952.yaml b/data/rooms/example_unused/underground2/9999952.yaml index 40c89a9..27ab51b 100644 --- a/data/rooms/example_unused/underground2/9999952.yaml +++ b/data/rooms/example_unused/underground2/9999952.yaml @@ -1,8 +1,15 @@ -name: "Abandoned Sector" -description: "A decommissioned sector of the asteroid's infrastructure. Broken monitors flicker and exposed wiring {214}sparks{/} dangerously." -exits: - north: 9999951 - south: 9999953 +id: 9999952 +name: Abandoned Sector +color: "" +description: + - text: A decommissioned sector of the asteroid's infrastructure. Broken monitors flicker and exposed wiring {214}sparks{/} dangerously. +exits: {} +objects: [] +item_spawns: [] mobs: - - "drone" - - "drone" + - id: drone + - id: drone +on_enter: [] +hazard: "" +block_transport: false +triggers: [] diff --git a/data/rooms/example_unused/underground2/9999953.yaml b/data/rooms/example_unused/underground2/9999953.yaml index 4396cfb..3cabbe7 100644 --- a/data/rooms/example_unused/underground2/9999953.yaml +++ b/data/rooms/example_unused/underground2/9999953.yaml @@ -1,8 +1,17 @@ -name: "Deep Tunnels" -description: "The tunnels descend deeper into the asteroid's core. Strange chitinous scraping echoes from the darkness. The walls are scarred with {130}claw marks{/}." +id: 9999953 +name: Deep Tunnels +color: "" +description: + - text: The tunnels descend deeper into the asteroid's core. Strange chitinous scraping echoes from the darkness. The walls are scarred with {130}claw marks{/}. exits: - north: 9999952 - south: 9999954 + south: + room: 9999954 +objects: [] +item_spawns: [] mobs: - - "crawler" - - "crawler" + - id: crawler + - id: crawler +on_enter: [] +hazard: "" +block_transport: false +triggers: [] diff --git a/data/rooms/intro/1005.yaml b/data/rooms/intro/1005.yaml new file mode 100644 index 0000000..4f31b7f --- /dev/null +++ b/data/rooms/intro/1005.yaml @@ -0,0 +1,14 @@ +id: 1005 +name: 'Room #1005' +color: "" +description: [] +exits: + south: + room: 2001 +objects: [] +item_spawns: [] +mobs: [] +on_enter: [] +hazard: "" +block_transport: false +triggers: [] diff --git a/data/rooms/intro/2001.yaml b/data/rooms/intro/2001.yaml index d809deb..0387c3e 100644 --- a/data/rooms/intro/2001.yaml +++ b/data/rooms/intro/2001.yaml @@ -6,12 +6,12 @@ description: exits: east: room: 2002 + north: + room: 1005 south: room: 2006 southeast: room: 2005 - west: - room: 1001 objects: - name: hidden guy hidden: true diff --git a/internal/admin/api_map.go b/internal/admin/api_map.go index bb81e59..5168165 100644 --- a/internal/admin/api_map.go +++ b/internal/admin/api_map.go @@ -84,7 +84,6 @@ func (s *AdminServer) handleMap(w http.ResponseWriter, r *http.Request) { if err != nil { continue } - // Try adjacent to a connected neighbour already on grid. for _, exit := range room.Exits { if exit.Room <= 0 { continue @@ -93,32 +92,69 @@ func (s *AdminServer) handleMap(w http.ResponseWriter, r *http.Request) { if !ok { continue } - for dx := -1; dx <= 1; dx++ { - for dy := -1; dy <= 1; dy++ { - if dx == 0 && dy == 0 { - continue - } - want := [3]int{nc[0] + dx, nc[1] + dy, nc[2]} - if _, used := g.RoomAt[want]; !used { - g.Coord[id] = want - g.RoomAt[want] = id - goto placed - } + if tryPlaceAdjacent(&g, id, nc) { + goto placed + } + } + tryPlaceRoom(&g, id, [3]int{}, false, [3]int{0, 0, z}) + placed: + } + + // Place rooms that have a one-way exit into the already-placed set. + // These rooms are not reachable from the seed via outgoing exits but + // their one-way link points into the connected component, so they + // should appear on the map as connected (not disconnected). + { + roomIndex := s.world.RoomIndex() + sortedIDs := make([]int, 0, len(roomIndex)) + for id := range roomIndex { + sortedIDs = append(sortedIDs, id) + } + sort.Ints(sortedIDs) + + type revEdge struct { + src int + dir world.ExitDir + } + rev := make(map[int][]revEdge) + for _, id := range sortedIDs { + room, err := s.world.LoadRoom(id) + if err != nil { + continue + } + for _, dir := range world.ExitOrder { + exit, ok := room.Exits[dir] + if !ok || exit.Room <= 0 || exit.Room == id { + continue } + rev[exit.Room] = append(rev[exit.Room], revEdge{src: id, dir: dir}) } } - // Fallback: scan a generous rectangle for a free cell. - for sx := -10; sx <= 10; sx++ { - for sy := -10; sy <= 10; sy++ { - want := [3]int{sx, sy, z} - if _, used := g.RoomAt[want]; !used { - g.Coord[id] = want - g.RoomAt[want] = id - goto placed + + queue := make([]int, 0, len(g.Coord)) + for rid := range g.Coord { + queue = append(queue, rid) + } + sort.Ints(queue) + + for len(queue) > 0 { + target := queue[0] + queue = queue[1:] + for _, e := range rev[target] { + if _, placed := g.Coord[e.src]; placed || e.src == target { + continue + } + tc := g.Coord[target] + delta := world.DirectionDeltas3D[world.OppositeExit[e.dir]] + want := [3]int{tc[0] + delta[0], tc[1] + delta[1], tc[2] + delta[2]} + if tryPlaceRoom(&g, e.src, want, true, tc) { + if d, ok := g.Dist[target]; ok { + g.Dist[e.src] = d + 1 + } + queue = append(queue, e.src) } } } - placed: } type RoomEntry struct { @@ -262,6 +298,47 @@ func (s *AdminServer) handleMap(w http.ResponseWriter, r *http.Request) { }) } +func tryPlaceAdjacent(g *world.RoomGrid, roomID int, center [3]int) bool { + for dx := -1; dx <= 1; dx++ { + for dy := -1; dy <= 1; dy++ { + if dx == 0 && dy == 0 { + continue + } + cell := [3]int{center[0] + dx, center[1] + dy, center[2]} + if _, used := g.RoomAt[cell]; !used { + g.Coord[roomID] = cell + g.RoomAt[cell] = roomID + return true + } + } + } + return false +} + +func tryPlaceRoom(g *world.RoomGrid, roomID int, want [3]int, allowWant bool, center [3]int) bool { + if allowWant { + if _, used := g.RoomAt[want]; !used { + g.Coord[roomID] = want + g.RoomAt[want] = roomID + return true + } + } + if tryPlaceAdjacent(g, roomID, center) { + return true + } + for sx := -10; sx <= 10; sx++ { + for sy := -10; sy <= 10; sy++ { + cell := [3]int{center[0] + sx, center[1] + sy, center[2]} + if _, used := g.RoomAt[cell]; !used { + g.Coord[roomID] = cell + g.RoomAt[cell] = roomID + return true + } + } + } + return false +} + func getRoomDir(s *AdminServer, roomID int) string { path, ok := s.world.GetRoomPath(roomID) if !ok { diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js index 47dcb27..ba68d80 100644 --- a/internal/admin/static/map.js +++ b/internal/admin/static/map.js @@ -3,9 +3,9 @@ var currentZ = 0, currentDir = '', selectedRoom = null, mapData = null; var panX = 0, panY = 0, scale = 1; var dragging = false, startX = 0, startY = 0, prevX = 0, prevY = 0; var dragRoom = false; -var linkDrag = false, linkFromId = null, linkTargetId = null, linkGhostDir = null; +var linkDrag = false, linkFromId = null, linkTargetId = null, linkTargetDir = null, linkGhostDir = null; var linkDragOneWay = false; -var shiftHeld = false; +var ctrlHeld = false; var delOneWay = false; var delDrag = false, delFromId = null, delTargetId = null; var roomMap = {}, occupied = {}; @@ -22,8 +22,8 @@ var upTarget = {}, downTarget = {}; var _mapMouseCleanup = false; -function isShiftKey(e) { - return e && (e.key === 'Shift' || e.code === 'ShiftLeft' || e.code === 'ShiftRight' || e.keyCode === 16); +function isCtrlKey(e) { + return e && (e.key === 'Control' || e.code === 'ControlLeft' || e.code === 'ControlRight' || e.keyCode === 17); } function onMapMouseMove(e) { @@ -32,7 +32,7 @@ function onMapMouseMove(e) { if (delFromId && moved && !delDrag) { delDrag = true; } if (delDrag && moved) { - delOneWay = e.shiftKey; + delOneWay = e.ctrlKey; updateDelDragTarget(e); showDelDragBadge(); return; @@ -47,7 +47,7 @@ function onMapMouseMove(e) { if (linkFromId && moved) { linkDrag = true; - linkDragOneWay = e.shiftKey; + linkDragOneWay = e.ctrlKey; showLinkDragBadge(); updateLinkDragTarget(e); } @@ -70,7 +70,7 @@ function onMapMouseUp(e) { } if (delDrag && delFromId && delTargetId) { - deleteLink(delFromId, delTargetId, (delOneWay || e.shiftKey) ? 'from' : ''); + deleteLink(delFromId, delTargetId, (delOneWay || e.ctrlKey) ? 'from' : ''); clearDelHighlight(); hideDragBadge(); dragging = false; dragRoom = false; delDrag = false; delFromId = null; delTargetId = null; delOneWay = false; @@ -79,14 +79,14 @@ function onMapMouseUp(e) { } if (linkDrag && linkFromId) { - var oneway = linkDragOneWay || e.shiftKey; - if (linkTargetId) { createLink(linkFromId, linkTargetId, oneway); } + var oneway = linkDragOneWay || e.ctrlKey; + if (linkTargetId) { createLink(linkFromId, linkTargetId, oneway, linkTargetDir); } else if (linkGhostDir) { createRoom(linkFromId, linkGhostDir, oneway); } } else if (!moved && !delDrag) { if (e.target.classList.contains('rm')) { selectRoom(parseInt(e.target.getAttribute('data-id'))); } else if (e.target.classList.contains('ghost')) { - createRoom(selectedRoom, e.target.getAttribute('data-dir'), e.shiftKey); + createRoom(selectedRoom, e.target.getAttribute('data-dir'), e.ctrlKey); } } @@ -94,7 +94,7 @@ function onMapMouseUp(e) { clearDelHighlight(); hideDragBadge(); dragging = false; dragRoom = false; - linkDrag = false; linkFromId = null; linkTargetId = null; linkGhostDir = null; linkDragOneWay = false; + linkDrag = false; linkFromId = null; linkTargetId = null; linkTargetDir = null; linkGhostDir = null; linkDragOneWay = false; delDrag = false; delFromId = null; delTargetId = null; delOneWay = false; if (document.activeElement && document.activeElement.blur) document.activeElement.blur(); } @@ -108,7 +108,7 @@ function cancelMapDrag() { clearDelHighlight(); hideDragBadge(); dragging = false; dragRoom = false; - linkDrag = false; linkFromId = null; linkTargetId = null; linkGhostDir = null; linkDragOneWay = false; + linkDrag = false; linkFromId = null; linkTargetId = null; linkTargetDir = null; linkGhostDir = null; linkDragOneWay = false; delDrag = false; delFromId = null; delTargetId = null; delOneWay = false; if (document.activeElement && document.activeElement.blur) document.activeElement.blur(); } @@ -139,9 +139,9 @@ function showDelDragBadge() { showDragBadge(label, delOneWay); } -function refreshDragMode(shiftDown) { - if (linkDrag && linkFromId) { linkDragOneWay = shiftDown; showLinkDragBadge(); } - if (delDrag && delFromId) { delOneWay = shiftDown; showDelDragBadge(); } +function refreshDragMode(ctrlDown) { + if (linkDrag && linkFromId) { linkDragOneWay = ctrlDown; showLinkDragBadge(); } + if (delDrag && delFromId) { delOneWay = ctrlDown; showDelDragBadge(); } } function changeZ(dz) { @@ -314,18 +314,24 @@ function renderMap(data) { svg.oncontextmenu = function(e) { e.preventDefault(); }; + if (!svg._ctxWired) { + svg.addEventListener('contextmenu', function(e) { e.preventDefault(); e.stopPropagation(); }); + svg._ctxWired = true; + } + if (!svg._mapKeysWired) { svg.setAttribute('tabindex', '0'); svg.addEventListener('keydown', function(e) { - if (isShiftKey(e)) { shiftHeld = true; if (dragging) refreshDragMode(true); } + if (isCtrlKey(e)) { ctrlHeld = true; if (dragging) refreshDragMode(true); } }); svg.addEventListener('keyup', function(e) { - if (isShiftKey(e)) { shiftHeld = false; if (dragging) refreshDragMode(false); } + if (isCtrlKey(e)) { ctrlHeld = false; if (dragging) refreshDragMode(false); } }); svg._mapKeysWired = true; } svg.onmousedown = function(e) { + if (e.button === 2) e.preventDefault(); var onRoom = e.target.classList.contains('rm'); var onGhost = e.target.classList.contains('ghost'); var onUdBtn = e.target.classList.contains('ud-btn'); @@ -373,7 +379,7 @@ function renderMap(data) { linkDrag = false; linkTargetId = null; linkGhostDir = null; - linkDragOneWay = onRoom ? (e.shiftKey || shiftHeld) : false; + linkDragOneWay = onRoom ? (e.ctrlKey || ctrlHeld) : false; delDrag = false; delFromId = null; delTargetId = null; clearLinkHighlight(); document.addEventListener('mousemove', onMapMouseMove); @@ -1989,6 +1995,7 @@ function gridDeltaToDir(dx, dy) { function updateLinkDragTarget(e) { clearLinkHighlight(); linkTargetId = null; + linkTargetDir = null; linkGhostDir = null; var fromRoom = mapData.rooms ? mapData.rooms.find(function(r){return r.id === linkFromId;}) : null; @@ -2007,6 +2014,7 @@ function updateLinkDragTarget(e) { if (neighbor && neighbor !== linkFromId) { linkTargetId = neighbor; + linkTargetDir = dir; linkGhostDir = null; applyLinkHighlight(linkFromId, linkTargetId); } else if (!neighbor) { @@ -2093,9 +2101,10 @@ function clearLinkHighlight() { } } -function createLink(fromId, toId, oneway) { +function createLink(fromId, toId, oneway, dir) { var body = {from: fromId, to: toId}; if (oneway) body.oneway = true; + if (dir) body.dir = dir; API.post('/api/rooms/link', body).then(function(res) { if (res && res.error) { notify('Link failed: ' + res.error, 'error'); return; } notify('Linked rooms #'+fromId+' '+toId + (oneway ? ' (one-way)' : ''), 'success'); @@ -2443,6 +2452,6 @@ function reloadRoomDirs(cb) { } document.addEventListener('DOMContentLoaded', function() { loadRoomDirs(function() { loadMap(); }); initPanelResize(); - window.addEventListener('keydown', function(e) { if (isShiftKey(e)) { shiftHeld = true; if (dragging) refreshDragMode(true); } }); - window.addEventListener('keyup', function(e) { if (isShiftKey(e)) { shiftHeld = false; if (dragging) refreshDragMode(false); } }); + window.addEventListener('keydown', function(e) { if (isCtrlKey(e)) { ctrlHeld = true; if (dragging) refreshDragMode(true); } }); + window.addEventListener('keyup', function(e) { if (isCtrlKey(e)) { ctrlHeld = false; if (dragging) refreshDragMode(false); } }); }); |
