';
return;
}
+ currentRoomData = data;
var r = data.room;
var file = data.file || '';
- var html = '
' + esc(file) + '
';
- html += '';
- html += '';
- html += '
';
- html += '';
- 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 += '';
- html += '';
- html += '';
+ 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 = '
';
+ html += '';
+ html += '';
+ html += '';
+ html += '';
+ html += '';
+ html += '
';
+
+ html += '
';
+ 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 += '
';
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 = '
' + esc(file) + '
';
+ html += '';
+ html += '';
+ html += '
';
+ html += '';
+ 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 += '';
+ html += '';
+ html += '';
+ 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 += '
';
+ h += '
';
+ h += '';
+ h += '';
+ h += '';
+ h += '
';
+ h += '';
+ h += '
';
+ });
+ h += '';
+ return h;
+}
+
+function renderObjectsTab(refObjs) {
+ var h = '';
+ refObjs.forEach(function(obj, i) {
+ h += '
';
+ h += '
' + esc(obj.id || '') + '
';
+ h += '';
+ h += '';
+ h += '
';
+ });
+ h += '
';
+ h += '';
+ h += '';
+ h += '
';
+ return h;
+}
+
+function renderMobsTab(mobs) {
+ var h = '';
+ mobs.forEach(function(mob, i) {
+ h += '
';
+ h += '
' + esc(mob.id || '') + '
';
+ h += '';
+ h += '';
+ h += '
';
+ });
+ h += '
';
+ h += '';
+ h += '';
+ h += '
';
+ return h;
+}
+
+function renderItemsTab(spawns) {
+ var h = '';
+ spawns.forEach(function(spawn, i) {
+ h += '
';
+ h += '
';
+ h += '' + esc(spawn.id || '') + '';
+ h += '';
+ h += '';
+ h += '
';
+ h += '';
+ h += '';
+ h += '
';
+ });
+ h += '
';
+ h += '';
+ h += '';
+ h += '
';
+ 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 = '
';
+ h += '' + d + '';
+ h += '# ' + target + '';
+ if (bmsg) h += 'Blocked: ' + esc(bmsg) + '';
+ if (cond) h += 'Conditional';
+ h += '';
+ h += '';
+ h += '
';
+ });
+ h += '
';
+ h += '';
+ h += '';
+ h += '';
+ h += '
';
+ h += '
';
+ 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 = '
Exit: ' + esc(dir) + ' → #' + exit.room + '
';
+ h += '';
+ h += '';
+ h += '';
+
+ h += '
';
+ h += '';
+ h += '';
+ h += '';
+ h += '
';
+ h += '';
+ h += '';
+ h += '';
+ h += '
';
+
+ 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 += '
';
+ h += '';
+ if (clause.type === 'value') {
+ var kv = clause.value ? clause.value.split('=', 2) : ['',''];
+ var kn = (kv[0] || '').trim();
+ var kvv = (kv[1] || '').trim();
+ h += '';
+ h += '=';
+ h += '';
+ } else {
+ h += '';
+ }
+ h += '';
+ h += '';
+ h += '
';
+ });
+ 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 = '