aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static/map.js
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin/static/map.js')
-rw-r--r--internal/admin/static/map.js229
1 files changed, 229 insertions, 0 deletions
diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js
index 00230a4..ab20a86 100644
--- a/internal/admin/static/map.js
+++ b/internal/admin/static/map.js
@@ -575,6 +575,8 @@ function renderSidePanel(data) {
html += '<button class="panel-tab' + (currentPanelTab === 5 ? ' active' : '') + '" onclick="switchPanelTab(5)">Items</button>';
html += '<button class="panel-tab' + (currentPanelTab === 6 ? ' active' : '') + '" onclick="switchPanelTab(6)">Hazard</button>';
html += '<button class="panel-tab' + (currentPanelTab === 7 ? ' active' : '') + '" onclick="switchPanelTab(7)">Agility</button>';
+ html += '<button class="panel-tab' + (currentPanelTab === 8 ? ' active' : '') + '" onclick="switchPanelTab(8)">Enter/Exit</button>';
+ html += '<button class="panel-tab' + (currentPanelTab === 9 ? ' active' : '') + '" onclick="switchPanelTab(9)">Triggers</button>';
html += '</div>';
html += '<div class="panel-tab-content">';
@@ -595,6 +597,8 @@ function renderSidePanel(data) {
html += renderCourseTab();
}
}
+ else if (currentPanelTab === 8) html += renderEnterExitTab(r);
+ else if (currentPanelTab === 9) html += renderTriggersTab(r);
html += '</div>';
panel.innerHTML = html;
@@ -1401,6 +1405,8 @@ function switchPanelTab(idx) {
html = renderCourseTab();
}
}
+ else if (idx === 8) html = renderEnterExitTab(r);
+ else if (idx === 9) html = renderTriggersTab(r);
var content = document.querySelector('.panel-tab-content');
if (content) content.innerHTML = html;
var tabs = document.querySelectorAll('.panel-tab');
@@ -2096,6 +2102,10 @@ async function doSavePanel() {
r.mobs = currentRoomData.room.mobs;
r.exits = currentRoomData.room.exits;
r.hazard = currentRoomData.room.hazard || '';
+ r.on_enter = currentRoomData.room.on_enter;
+ r.on_exit = currentRoomData.room.on_exit;
+ r.on_flag_change = currentRoomData.room.on_flag_change;
+ r.on_global_flag_change = currentRoomData.room.on_global_flag_change;
if (r.block_transport === undefined) r.block_transport = currentRoomData.room.block_transport;
}
@@ -3350,3 +3360,222 @@ document.addEventListener('DOMContentLoaded', function() { loadRoomDirs(function
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); } });
});
+
+// ── Enter/Exit tab ──
+// Renders on_enter and on_exit trigger lists, wiring the shared ie_* helpers
+// from intereditor.js so the same condition/step/message editors used by the
+// object/mob editors are reused here. Edits mutate currentRoomData.room.*
+// and the debounced autoSave() persists them.
+
+function renderEnterExitTab(r) {
+ var onEnter = Array.isArray(r.on_enter) ? r.on_enter : [];
+ var onExit = Array.isArray(r.on_exit) ? r.on_exit : [];
+ window._editorData = { on_enter: onEnter, on_exit: onExit };
+
+ window._ieRenderCurrent = function() {
+ if (window._ieSyncAll) window._ieSyncAll(window._editorData, [
+ {id: 'on_enter', path: 'on_enter', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'},
+ {id: 'on_exit', path: 'on_exit', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'}
+ ]);
+ if (currentRoomData && currentRoomData.room) {
+ currentRoomData.room.on_enter = window._editorData.on_enter;
+ currentRoomData.room.on_exit = window._editorData.on_exit;
+ }
+ switchPanelTab(8);
+ autoSave();
+ };
+ window._ieRenderOnly = window._ieRenderCurrent;
+ window._ieSyncAll = ie_syncAllInteractions;
+
+ var h = '<h3 style="margin:8px 0 4px">On-Enter Triggers</h3>';
+ h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a player enters this room. First matching entry wins.</p>';
+ h += renderTriggerListHTML(r.on_enter, 'on_enter', 'on_enter', false);
+ h += '<h3 style="margin:16px 0 4px">On-Exit Triggers</h3>';
+ h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a player leaves this room. First matching entry wins.</p>';
+ h += renderTriggerListHTML(r.on_exit, 'on_exit', 'on_exit', false);
+ return h;
+}
+
+// ── Triggers tab (on_flag_change / on_global_flag_change) ──
+
+function renderTriggersTab(r) {
+ var fc = Array.isArray(r.on_flag_change) ? r.on_flag_change : [];
+ var gfc = Array.isArray(r.on_global_flag_change) ? r.on_global_flag_change : [];
+ window._editorData = { on_flag_change: fc, on_global_flag_change: gfc };
+
+ window._ieRenderCurrent = function() {
+ if (window._ieSyncAll) window._ieSyncAll(window._editorData, [
+ {id: 'on_flag_change', path: 'on_flag_change', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'},
+ {id: 'on_global_flag_change', path: 'on_global_flag_change', isArray: true, interactionStyle: true, itemIDAllowed: false, label: 'Trigger'}
+ ]);
+ if (currentRoomData && currentRoomData.room) {
+ currentRoomData.room.on_flag_change = window._editorData.on_flag_change;
+ currentRoomData.room.on_global_flag_change = window._editorData.on_global_flag_change;
+ }
+ switchPanelTab(9);
+ autoSave();
+ };
+ window._ieRenderOnly = window._ieRenderCurrent;
+ window._ieSyncAll = ie_syncAllInteractions;
+
+ var h = '<h3 style="margin:8px 0 4px">On-Flag-Change Triggers</h3>';
+ h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a player flag changes. Use the condition\'s "In Room" gate to scope to this room (delete it to make global).</p>';
+ h += renderFlagTriggerListHTML(r.on_flag_change, 'on_flag_change', 'on_flag_change', 'player');
+ h += '<h3 style="margin:16px 0 4px">On-Global-Flag-Change Triggers</h3>';
+ h += '<p style="color:#888;font-size:11px;margin:0 0 6px">Fires when a global flag changes (no player involved).</p>';
+ h += renderFlagTriggerListHTML(r.on_global_flag_change, 'on_global_flag_change', 'on_global_flag_change', 'global');
+ return h;
+}
+
+// renderTriggerListHTML emits the HTML for a []Trigger block using the shared
+// ie_renderArraySection. It's a thin shim that builds the ctx the card
+// editors construct.
+function renderTriggerListHTML(arr, secId, secPath, allowItem) {
+ var visMap = {};
+ arr = Array.isArray(arr) ? arr : [];
+ var sec = {
+ id: secId,
+ path: secPath,
+ isArray: true,
+ interactionStyle: true,
+ itemIDAllowed: allowItem,
+ label: 'Trigger',
+ fields: [['item_id', 'Item ID', 'search', 'e.g. keycard', '', null, 'items']]
+ };
+ var ctx = {
+ sec: sec,
+ data: window._editorData,
+ visMap: visMap,
+ showField: function(sid, idx, key) {
+ ie_showField(visMap, sid, idx, key);
+ return 'void(0)';
+ },
+ hideField: function(sid, idx, key) {
+ ie_hideField(visMap, sid, idx, key);
+ window._ieRenderCurrent();
+ return 'void(0)';
+ },
+ addCondition: function(sid, idx) {
+ return 'ie_addRootConditionGroup(\'' + sid + '\',' + idx + ',\'entry\')';
+ },
+ showRemove: function(idx) {
+ return '_mapRemoveTrigger(\'' + secPath + '\',' + idx + ')';
+ },
+ renderField: function(s, field, data, idx, style) {
+ var val = (data[secPath] && data[secPath][idx] && data[secPath][idx].item_id) || '';
+ return '<div class="obj-search" style="position:relative;flex:1;min-width:0"><input class="search-input" data-field-path="' + secPath + '.' + idx + '.item_id" data-search-type="items" placeholder="item ID" value="' + escAttr(val) + '"><div class="search-results" style="display:none"></div></div>';
+ },
+ onAdd: function() {
+ return '_mapAddTrigger(\'' + secPath + '\')';
+ }
+ };
+ return ie_renderArraySection(ctx);
+}
+
+// renderFlagTriggerListHTML is like renderTriggerListHTML but adds the
+// on_player_flag / on_global_flag subscription header per entry.
+function renderFlagTriggerListHTML(arr, secId, secPath, kind) {
+ var visMap = {};
+ arr = Array.isArray(arr) ? arr : [];
+ var sec = {
+ id: secId,
+ path: secPath,
+ isArray: true,
+ interactionStyle: true,
+ itemIDAllowed: false,
+ label: 'Trigger',
+ fields: []
+ };
+ var flagKey = kind === 'player' ? 'on_player_flag' : 'on_global_flag';
+
+ var h = '';
+ arr.forEach(function(item, idx) {
+ visMap[idx] = visMap[idx] || {};
+ var condExists = !!(item.condition && typeof item.condition === 'object');
+ var lockVal = !!item.lock;
+ var steps = item.steps || [];
+ var flagName = item[flagKey] || '';
+
+ h += '<div class="obj-sub-row ie-inter-row" data-idx="' + idx + '">';
+ h += '<div class="ie-inter-header">';
+ h += '<label class="ie-lock-lbl"><input type="checkbox" class="ie-lock-cb"' + (lockVal ? ' checked' : '') + '> Lock Player</label>';
+ if (idx > 0) h += '<button class="btn btn-sm ie-trig-up" onclick="ie_moveRow(\'' + escAttr(secPath) + '\',' + idx + ',' + (idx - 1) + ')" title="Move up">&#9650;</button>';
+ if (idx < arr.length - 1) h += '<button class="btn btn-sm ie-trig-dn" onclick="ie_moveRow(\'' + escAttr(secPath) + '\',' + idx + ',' + (idx + 1) + ')" title="Move down">&#9660;</button>';
+ h += '<span class="ie-cond-leaf-label">' + esc(kind === 'player' ? 'Player Flag' : 'Global Flag') + '</span>';
+ h += '<input class="ie-flag-name" data-ie-flag-key="' + escAttr(flagKey) + '" data-idx="' + idx + '" value="' + escAttr(flagName) + '" placeholder="' + escAttr(kind === 'player' ? 'player flag name' : 'global flag name') + '" style="flex:1;min-width:80px" oninput="_mapSyncFlag(\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(flagKey) + '\')">';
+ h += '<span class="ie-cond-eq">=</span>';
+ h += '<input class="ie-flag-value" data-idx="' + idx + '" value="' + escAttr(item.value !== undefined ? String(item.value) : '') + '" placeholder="exists" style="width:60px" oninput="_mapSyncFlag(\'' + escAttr(secPath) + '\',' + idx + ',\'' + escAttr(flagKey) + '\')">';
+ h += '<span class="ie-inter-header-spacer"></span>';
+ if (!condExists) {
+ h += '<button class="btn btn-sm ie-add-btn" onclick="ie_addRootConditionGroup(\'' + escAttr(secPath) + '\',' + idx + ',\'entry\')">+ Add Condition</button>';
+ }
+ h += '<button class="btn btn-sm btn-danger ie-inter-remove" onclick="_mapRemoveTrigger(\'' + escAttr(secPath) + '\',' + idx + ')">Remove Trigger</button>';
+ h += '</div>';
+ h += '<div class="ie-inter-panel" style="display:' + (condExists ? 'block' : 'none') + '">';
+ h += '<div class="ie-inter-panel-body">' + ie_renderCond(item.condition || null, secId, idx, secPath, 'entry') + '</div>';
+ h += '</div>';
+ h += '<div class="ie-inter-steps">';
+ h += ie_renderStepList(steps, secId, idx, secPath);
+ h += '</div>';
+ h += '</div>';
+ });
+ h += '<button class="btn btn-sm obj-sub-add" onclick="_mapAddFlagTrigger(\'' + escAttr(secPath) + '\',\'' + escAttr(flagKey) + '\')">+ Add Trigger</button>';
+ return h;
+}
+
+// _mapAddTrigger / _mapRemoveTrigger / _mapAddFlagTrigger: mutation helpers
+// called from the inline onclick handlers. They sync the DOM, splice the
+// _editorData array, and re-render.
+function _mapAddTrigger(secPath) {
+ if (!window._editorData) return;
+ if (window._ieSyncAll) window._ieSyncAll(window._editorData, []);
+ if (!window._editorData[secPath]) window._editorData[secPath] = [];
+ window._editorData[secPath].push({steps: []});
+ if (currentRoomData && currentRoomData.room) {
+ currentRoomData.room[secPath] = window._editorData[secPath];
+ }
+ window._ieRenderCurrent();
+}
+
+function _mapAddFlagTrigger(secPath, flagKey) {
+ if (!window._editorData) return;
+ if (!window._editorData[secPath]) window._editorData[secPath] = [];
+ var entry = {steps: []};
+ entry[flagKey] = '';
+ window._editorData[secPath].push(entry);
+ if (currentRoomData && currentRoomData.room) {
+ currentRoomData.room[secPath] = window._editorData[secPath];
+ }
+ window._ieRenderCurrent();
+}
+
+function _mapRemoveTrigger(secPath, idx) {
+ if (!window._editorData || !window._editorData[secPath]) return;
+ window._editorData[secPath].splice(idx, 1);
+ if (currentRoomData && currentRoomData.room) {
+ currentRoomData.room[secPath] = window._editorData[secPath];
+ }
+ window._ieRenderCurrent();
+}
+
+function _mapSyncFlag(secPath, idx, flagKey) {
+ if (!window._editorData || !window._editorData[secPath]) return;
+ var row = document.querySelector('.ie-inter-row[data-idx="' + idx + '"]');
+ if (!row) return;
+ var nameEl = row.querySelector('.ie-flag-name');
+ var valEl = row.querySelector('.ie-flag-value');
+ if (nameEl) {
+ window._editorData[secPath][idx][flagKey] = nameEl.value.trim();
+ }
+ if (valEl) {
+ var v = valEl.value.trim();
+ if (v === '') delete window._editorData[secPath][idx].value;
+ else {
+ try { window._editorData[secPath][idx].value = JSON.parse(v); } catch(e) { window._editorData[secPath][idx].value = v; }
+ }
+ }
+ if (currentRoomData && currentRoomData.room) {
+ currentRoomData.room[secPath] = window._editorData[secPath];
+ }
+ autoSave();
+}