aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static/triggerseditor.js
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-10 00:43:29 -0400
committerhistoria <[not public]>2026-07-10 00:43:29 -0400
commit6e7c0b3ec4253bb58a815979b1f4574d08cb1f53 (patch)
treedbff1952bdbf14cd58a6128775e263b53fc64ec1 /internal/admin/static/triggerseditor.js
parent9836513d287396fabcba85956366e6410d7363ef (diff)
downloadthehouseoficarus-6e7c0b3ec4253bb58a815979b1f4574d08cb1f53.tar.gz
feat(admin): add triggers to rooms, add global triggers
Diffstat (limited to 'internal/admin/static/triggerseditor.js')
-rw-r--r--internal/admin/static/triggerseditor.js463
1 files changed, 463 insertions, 0 deletions
diff --git a/internal/admin/static/triggerseditor.js b/internal/admin/static/triggerseditor.js
new file mode 100644
index 0000000..29759eb
--- /dev/null
+++ b/internal/admin/static/triggerseditor.js
@@ -0,0 +1,463 @@
+var triggerData = null;
+var triggerID = null;
+var triggerKind = '';
+
+function initTriggerEditor() {
+ editorType = 'triggers';
+ editorFields = [];
+ loadTriggerList();
+ if (window.location.hash) loadTriggerItem(window.location.hash.substring(1));
+}
+
+function loadTriggerList() {
+ API.get('/api/triggers').then(function(data) {
+ allIDs = (data || []).map(function(e) { return e.id || e; });
+ window._triggerEntries = data || [];
+ renderTriggerList('');
+ }).catch(function(e) {
+ notify('Failed to load list: ' + e.message, 'error');
+ });
+}
+
+function renderTriggerList(filter) {
+ var el = $('#listEntries');
+ var f = (filter || '').toLowerCase();
+ var entries = window._triggerEntries || [];
+ var html = '';
+ entries.forEach(function(entry) {
+ var id = entry.id || entry;
+ if (f && id.toLowerCase().indexOf(f) === -1) return;
+ var badge = entry.kind === 'player' ? ' [Player]' : (entry.kind === 'global' ? ' [Global]' : '');
+ var badgeClass = entry.kind === 'player' ? 'dc-item' : (entry.kind === 'global' ? 'dc-item' : '');
+ html += '<div class="item' + (triggerID === id ? ' active' : '') + '" onclick="loadTriggerItem(\'' + escAttr(id) + '\')">';
+ html += '<span class="dc-id">' + esc(id) + '</span>';
+ html += '<span class="dc-name" style="color:' + (entry.kind === 'player' ? '#6af' : '#e8c47c') + '">' + esc(badge) + '</span>';
+ html += '</div>';
+ });
+ el.innerHTML = html || '<p style="color:#888;text-align:center;margin-top:20px;font-size:11px">No triggers</p>';
+}
+
+function loadTriggerItem(id) {
+ loadTrigger(id);
+}
+
+function loadTrigger(id) {
+ triggerID = id;
+ currentID = id;
+ window.location.hash = id;
+ renderTriggerList('');
+ API.get('/api/triggers/' + encodeURIComponent(id)).then(function(data) {
+ triggerData = data;
+ triggerKind = data._kind || '';
+ if (!Array.isArray(triggerData.steps)) triggerData.steps = [];
+ window._editorData = triggerData;
+ window._ieRenderCurrent = renderCurrent;
+ window._ieRenderOnly = function() { if (triggerID && triggerData) renderTriggerEditor(triggerID, triggerData); };
+ window._ieSyncAll = function(ed) { if (ed) syncCondDOM(ed); };
+ renderTriggerEditor(id, data);
+ }).catch(function(e) {
+ $('#editorMain').innerHTML = '<p style="color:var(--danger)">Failed to load: ' + esc(e.message) + '</p>';
+ });
+}
+
+function syncCondDOM(ed) {
+ if (!ed) return;
+ var condRoot = document.querySelector('.obj-card[data-card="condition"] .ie-cond-root');
+ if (condRoot) {
+ var cond = ie_collectCond(condRoot);
+ if (cond) ed.condition = cond;
+ else delete ed.condition;
+ }
+}
+
+function renderCurrent() {
+ if (!triggerData || !triggerID) return;
+ syncCondDOM(triggerData);
+ var flagTypeEl = document.querySelector('input[name="trigFlagType"]:checked');
+ if (flagTypeEl) {
+ delete triggerData.on_player_flag;
+ delete triggerData.on_global_flag;
+ if (flagTypeEl.value === 'player') {
+ var n = $('#trigFlagName'); triggerData.on_player_flag = n ? n.value.trim() : '';
+ } else {
+ var n = $('#trigFlagName'); triggerData.on_global_flag = n ? n.value.trim() : '';
+ }
+ }
+ var valEl = $('#trigValue');
+ if (valEl) {
+ var v = valEl.value.trim();
+ if (v === '') delete triggerData.value;
+ else { try { triggerData.value = JSON.parse(v); } catch(e) { triggerData.value = v; } }
+ }
+ var lockEl = $('#trigLock');
+ if (lockEl) { if (lockEl.checked) triggerData.lock = true; else delete triggerData.lock; }
+ var stepRows = document.querySelectorAll('.obj-card[data-card="steps"] .ie-step-row');
+ if (!Array.isArray(triggerData.steps)) triggerData.steps = [];
+ stepRows.forEach(function(stepRow) {
+ var si = parseInt(stepRow.getAttribute('data-step'), 10);
+ if (isNaN(si)) return;
+ if (!triggerData.steps[si]) triggerData.steps[si] = {};
+ var waitEl = stepRow.querySelector('.wait-input');
+ if (waitEl) triggerData.steps[si].wait = parseInt(waitEl.value) || 0;
+ syncStepEffects(stepRow, si);
+ });
+ if (triggerData.steps.length > stepRows.length) triggerData.steps.length = stepRows.length;
+ renderTriggerEditor(triggerID, triggerData);
+}
+
+function syncStepEffects(row, si) {
+ var effectsEl = row.querySelector('.ie-act-root');
+ if (!effectsEl) return;
+ var step = triggerData.steps[si] || {};
+ ['messages','broadcast','broadcast_global','teleport','heal','credits',
+ 'give_item','take_item','despawn_mob','aps_node'].forEach(function(k) { delete step[k]; });
+ delete step.set_global_flags; delete step.set_player_flags; delete step.spawn_mob;
+
+ var msgInputs = effectsEl.querySelectorAll('.trig-msg-input');
+ var msgs = [];
+ msgInputs.forEach(function(inp) { if (inp.value !== '') msgs.push(inp.value); });
+ if (msgs.length > 0) step.messages = msgs;
+
+ effectsEl.querySelectorAll('.trig-effect-input').forEach(function(inp) {
+ var key = inp.getAttribute('data-key');
+ var v = inp.value.trim();
+ if (v !== '') { if (key === 'teleport' || key === 'heal' || key === 'credits') step[key] = parseInt(v)||0; else step[key] = v; }
+ });
+
+ var cb = effectsEl.querySelector('.trig-aps-cb');
+ if (cb && cb.checked) step.aps_node = true;
+
+ effectsEl.querySelectorAll('.trig-kv-section').forEach(function(kvSec) {
+ var kvKey = kvSec.getAttribute('data-kv');
+ var kvObj = {};
+ kvSec.querySelectorAll('.trig-kv-row').forEach(function(kvRow) {
+ var kEl = kvRow.querySelector('.trig-kv-key');
+ var vEl = kvRow.querySelector('.trig-kv-val');
+ if (kEl && kEl.value) {
+ var v = vEl ? vEl.value : '';
+ try { kvObj[kEl.value] = JSON.parse(v); } catch(e) { kvObj[kEl.value] = v; }
+ }
+ });
+ if (Object.keys(kvObj).length > 0) step[kvKey] = kvObj;
+ });
+
+ var smInput = effectsEl.querySelector('.trig-spawn-mob');
+ if (smInput && smInput.value.trim()) {
+ if (!step.spawn_mob) step.spawn_mob = {};
+ step.spawn_mob.id = smInput.value.trim();
+ }
+
+ triggerData.steps[si] = step;
+}
+
+function renderTriggerEditor(id, data) {
+ var html = renderRenameableHeader('Trigger', id);
+
+ var kindLabel = data._kind === 'global' ? 'Global Flag' : (data._kind === 'player' ? 'Player Flag' : '');
+ if (kindLabel) html += '<div style="font-size:11px;color:#888;margin-bottom:8px">' + esc(kindLabel) + ' Trigger</div>';
+
+ html += '<div class="tabs"><button class="tab active" onclick="switchTab(event,\'fields\')">Fields</button>';
+ html += '<button class="tab" onclick="switchTab(event,\'yaml\')">Raw YAML</button></div>';
+
+ html += '<div class="tab-content" id="tabFields">';
+
+ var isPlayerFlag = data.on_player_flag !== undefined;
+ var isGlobalFlag = data.on_global_flag !== undefined;
+ if (!isPlayerFlag && !isGlobalFlag) isPlayerFlag = true;
+ var flagName = isPlayerFlag ? (data.on_player_flag || '') : (data.on_global_flag || '');
+
+ html += '<div class="obj-card" style="max-width:100%">';
+ html += '<div class="obj-card-header">';
+ html += '<span class="obj-card-label">Flag Subscription</span>';
+ html += '</div>';
+ html += '<div class="obj-card-body">';
+ html += '<div class="obj-card-grid">';
+ html += '<div class="obj-field obj-wide">';
+ html += '<span>Flag Type</span>';
+ html += '<div style="display:flex;gap:16px;align-items:center">';
+ html += '<label style="font-size:11px;color:var(--text);cursor:pointer"><input type="radio" name="trigFlagType" value="player"' + (isPlayerFlag ? ' checked' : '') + '> Player Flag</label>';
+ html += '<label style="font-size:11px;color:var(--text);cursor:pointer"><input type="radio" name="trigFlagType" value="global"' + (isGlobalFlag ? ' checked' : '') + '> Global Flag</label>';
+ html += '</div>';
+ html += '</div>';
+ html += '<div class="obj-field" style="flex:2">';
+ html += '<span>Flag Name</span>';
+ html += '<input id="trigFlagName" value="' + escAttr(flagName) + '" placeholder="flag name">';
+ html += '</div>';
+ html += '<div class="obj-field" style="flex:1">';
+ html += '<span>Value (optional)</span>';
+ html += '<input id="trigValue" value="' + escAttr(data.value !== undefined ? String(data.value) : '') + '" placeholder="any value">';
+ html += '</div>';
+ html += '<div class="obj-field obj-check">';
+ html += '<input type="checkbox" id="trigLock"' + (data.lock ? ' checked' : '') + '>';
+ html += '<span>Lock Player (atomic sequence)</span>';
+ html += '</div>';
+ html += '</div>';
+ html += '</div>';
+ html += '</div>';
+
+ html += '<div class="obj-card" style="max-width:100%" data-card="condition">';
+ html += '<div class="obj-card-header">';
+ html += '<span class="obj-card-label">Entry Condition</span>';
+ html += '</div>';
+ html += '<div class="obj-card-body">';
+ html += '<div class="ie-cond-root">' + ie_renderCond(data.condition || null, 'condition', 0, '', 'entry') + '</div>';
+ html += '</div>';
+ html += '</div>';
+
+ var steps = Array.isArray(data.steps) ? data.steps : [];
+ html += '<div class="obj-card" style="max-width:100%" data-card="steps">';
+ html += '<div class="obj-card-header">';
+ html += '<span class="obj-card-label">Steps (' + steps.length + ')</span>';
+ html += '</div>';
+ html += '<div class="obj-card-body">';
+ html += renderSteps(steps);
+ html += '<button class="btn btn-sm obj-sub-add" onclick="addStep()">+ Add Step</button>';
+ html += '</div>';
+ html += '</div>';
+
+ html += '</div>';
+
+ html += '<div class="tab-content" id="tabYaml" style="display:none">';
+ html += '<pre>' + esc(data._raw || JSON.stringify(data, null, 2)) + '</pre>';
+ html += '</div>';
+
+ html += '<div class="btn-row">';
+ html += '<button class="btn btn-primary" onclick="saveTrigger()">Save</button>';
+ html += '<button class="btn" style="margin-left:auto" onclick="deleteTrigger()">Delete</button>';
+ html += '</div>';
+
+ $('#editorMain').innerHTML = html;
+ ced_setupSearchFields();
+}
+
+function renderSteps(steps) {
+ var h = '';
+ (steps || []).forEach(function(step, si) {
+ h += '<div class="ie-step-row" data-step="' + si + '">';
+ h += '<div class="ie-step-header">';
+ h += '<span class="ie-step-label">Step ' + (si + 1) + '</span>';
+ h += '<span style="font-size:10px;color:#888">Wait:</span>';
+ h += '<input class="ie-act-input wait-input" type="number" value="' + (step.wait || 0) + '" style="width:60px" min="0">';
+ if (si > 0) h += '<button class="btn btn-sm ie-step-up" onclick="moveStep(' + si + ',' + (si - 1) + ')">&#9650;</button>';
+ if (si < steps.length - 1) h += '<button class="btn btn-sm ie-step-dn" onclick="moveStep(' + si + ',' + (si + 1) + ')">&#9660;</button>';
+ h += '<span class="ie-step-spacer"></span>';
+ h += '<button class="btn btn-sm btn-danger" onclick="removeStep(' + si + ')">Remove</button>';
+ h += '</div>';
+
+ h += '<div class="ie-step-effects">';
+ h += '<div class="ie-act-root">';
+ h += renderStepEffects(step, si);
+ h += '</div>';
+ h += '</div>';
+
+ h += '<div class="ie-step-addbar-bottom">';
+ IE_STEP_KINDS.forEach(function(sk) {
+ h += '<button class="btn btn-sm ie-add-btn" onclick="addStepEffect(' + si + ',\'' + escAttr(sk.key) + '\')">+ ' + esc(sk.label) + '</button>';
+ });
+ h += '</div>';
+ h += '</div>';
+ });
+ return h;
+}
+
+function renderStepEffects(step, si) {
+ var h = '';
+
+ var msgs = Array.isArray(step.messages) ? step.messages : [];
+ msgs.forEach(function(msg, mi) {
+ h += '<div class="ie-act-row">';
+ h += '<span class="ie-act-label">Message</span>';
+ h += '<input class="ie-act-input ie-act-msg trig-msg-input" value="' + escAttr(msg) + '" placeholder="message to player">';
+ h += '</div>';
+ });
+
+ if (step.broadcast !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Broadcast</span>';
+ h += '<input class="ie-act-input ie-act-msg trig-effect-input" data-key="broadcast" value="' + escAttr(String(step.broadcast)) + '" placeholder="%p = player"></div>';
+ }
+ if (step.broadcast_global !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Broadcast Global</span>';
+ h += '<input class="ie-act-input ie-act-msg trig-effect-input" data-key="broadcast_global" value="' + escAttr(String(step.broadcast_global)) + '" placeholder="%p = player"></div>';
+ }
+ if (step.teleport !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Teleport</span>';
+ h += '<input class="ie-act-input trig-effect-input" data-key="teleport" type="number" value="' + (step.teleport || 0) + '" style="width:80px" placeholder="room ID"></div>';
+ }
+ if (step.heal !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Heal</span>';
+ h += '<input class="ie-act-input trig-effect-input" data-key="heal" type="number" value="' + (step.heal || 0) + '" style="width:80px" placeholder="HP"></div>';
+ }
+ if (step.credits !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Credits</span>';
+ h += '<input class="ie-act-input trig-effect-input" data-key="credits" type="number" value="' + (step.credits || 0) + '" style="width:80px" placeholder="amount"></div>';
+ }
+ if (step.spawn_mob && step.spawn_mob.id) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Spawn Mob</span>';
+ h += '<div class="obj-search" style="position:relative;flex:1;min-width:0">';
+ h += '<input class="search-input trig-spawn-mob" value="' + escAttr(step.spawn_mob.id) + '" placeholder="mob ID" data-search-type="mobs">';
+ h += '<div class="search-results" style="display:none"></div></div></div>';
+ }
+ if (step.despawn_mob !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Despawn Mob</span>';
+ h += '<input class="ie-act-input trig-effect-input" data-key="despawn_mob" value="' + escAttr(String(step.despawn_mob)) + '" placeholder="mob ID"></div>';
+ }
+ if (step.give_item !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Give Item</span>';
+ h += '<input class="ie-act-input trig-effect-input" data-key="give_item" value="' + escAttr(String(step.give_item)) + '" placeholder="item ID"></div>';
+ }
+ if (step.take_item !== undefined) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">Take Item</span>';
+ h += '<input class="ie-act-input trig-effect-input" data-key="take_item" value="' + escAttr(String(step.take_item)) + '" placeholder="item ID"></div>';
+ }
+ if (step.aps_node) {
+ h += '<div class="ie-act-row"><span class="ie-act-label">APS Node</span>';
+ h += '<label style="font-size:11px;color:var(--text)"><input type="checkbox" class="trig-aps-cb" checked> Enabled</label></div>';
+ }
+
+ function renderKVSection(label, key, obj) {
+ var h2 = '<div class="ie-act-kv trig-kv-section" data-kv="' + key + '">';
+ h2 += '<div class="ie-act-kv-header"><span class="ie-act-label">' + label + '</span></div>';
+ h2 += '<div class="ie-kv-list">';
+ Object.keys(obj).forEach(function(k) {
+ h2 += '<div class="ie-kv-row trig-kv-row">';
+ h2 += '<input class="ie-kv-key trig-kv-key" value="' + escAttr(k) + '" placeholder="key">';
+ h2 += '<span class="ie-cond-eq">=</span>';
+ h2 += '<input class="ie-kv-val trig-kv-val" value="' + escAttr(String(obj[k])) + '" placeholder="value">';
+ h2 += '</div>';
+ });
+ h2 += '</div></div>';
+ return h2;
+ }
+
+ if (step.set_global_flags && typeof step.set_global_flags === 'object' && Object.keys(step.set_global_flags).length > 0) {
+ h += renderKVSection('Set Global Flags', 'set_global_flags', step.set_global_flags);
+ }
+ if (step.set_player_flags && typeof step.set_player_flags === 'object' && Object.keys(step.set_player_flags).length > 0) {
+ h += renderKVSection('Set Player Flags', 'set_player_flags', step.set_player_flags);
+ }
+
+ return h;
+}
+
+function addStep() {
+ if (!triggerData) return;
+ if (!Array.isArray(triggerData.steps)) triggerData.steps = [];
+ triggerData.steps.push({wait: 0});
+ renderCurrent();
+}
+
+function removeStep(si) {
+ if (!triggerData || !Array.isArray(triggerData.steps)) return;
+ triggerData.steps.splice(si, 1);
+ renderCurrent();
+}
+
+function moveStep(from, to) {
+ if (!triggerData || !Array.isArray(triggerData.steps)) return;
+ var s = triggerData.steps[from];
+ triggerData.steps.splice(from, 1);
+ triggerData.steps.splice(to, 0, s);
+ renderCurrent();
+}
+
+function addStepEffect(si, kind) {
+ if (!triggerData) return;
+ syncCondDOM(triggerData);
+ syncAllStepEffects();
+ if (!Array.isArray(triggerData.steps)) triggerData.steps = [];
+ if (!triggerData.steps[si]) triggerData.steps[si] = {};
+ var step = triggerData.steps[si];
+
+ if (kind === 'messages') {
+ if (!Array.isArray(step.messages)) step.messages = [];
+ step.messages.push('');
+ } else if (kind === 'wait') {
+ step.wait = (step.wait || 0) + 1;
+ } else {
+ var at = IE_ACT_TYPES.find(function(a) { return a.key === kind; });
+ if (!at) return;
+ if (at.kind === 'kv') step[kind] = {};
+ else if (at.kind === 'checkbox') step[kind] = true;
+ else if (at.kind === 'number') step[kind] = 0;
+ else if (at.kind === 'search') step[kind] = '';
+ else step[kind] = '';
+ }
+ renderCurrent();
+}
+
+function syncAllStepEffects() {
+ var stepRows = document.querySelectorAll('.obj-card[data-card="steps"] .ie-step-row');
+ if (!Array.isArray(triggerData.steps)) triggerData.steps = [];
+ stepRows.forEach(function(stepRow) {
+ var si = parseInt(stepRow.getAttribute('data-step'), 10);
+ if (isNaN(si)) return;
+ if (!triggerData.steps[si]) triggerData.steps[si] = {};
+ var waitEl = stepRow.querySelector('.wait-input');
+ if (waitEl) triggerData.steps[si].wait = parseInt(waitEl.value) || 0;
+ syncStepEffects(stepRow, si);
+ });
+}
+
+function saveTrigger() {
+ syncCondDOM(triggerData);
+ syncAllStepEffects();
+ if (!triggerData || !triggerID) return;
+ var flagTypeEl = document.querySelector('input[name="trigFlagType"]:checked');
+ if (flagTypeEl) {
+ delete triggerData.on_player_flag;
+ delete triggerData.on_global_flag;
+ var n = $('#trigFlagName');
+ if (flagTypeEl.value === 'player') triggerData.on_player_flag = n ? n.value.trim() : '';
+ else triggerData.on_global_flag = n ? n.value.trim() : '';
+ }
+ var valEl = $('#trigValue');
+ if (valEl) {
+ var v = valEl.value.trim();
+ if (v === '') delete triggerData.value;
+ else { try { triggerData.value = JSON.parse(v); } catch(e) { triggerData.value = v; } }
+ }
+ var lockEl = $('#trigLock');
+ if (lockEl) { if (lockEl.checked) triggerData.lock = true; else delete triggerData.lock; }
+
+ var out = JSON.parse(JSON.stringify(triggerData));
+ delete out.id;
+ delete out._path;
+ delete out._raw;
+ delete out._kind;
+ ced_cleanOutput(out);
+ API.put('/api/triggers/' + encodeURIComponent(triggerID), out).then(function(resp) {
+ notify('Trigger ' + triggerID + ' saved', 'success');
+ updateUndoBar();
+ if (resp) {
+ if (resp._raw) triggerData._raw = resp._raw;
+ if (resp.kind) triggerKind = resp.kind;
+ }
+ loadTriggerList();
+ }).catch(function(e) { notify('Save failed: ' + e.message, 'error'); });
+}
+
+function deleteTrigger() {
+ if (!confirm('Delete trigger "' + triggerID + '"?')) return;
+ API.del('/api/triggers/' + encodeURIComponent(triggerID)).then(function() {
+ notify('Trigger ' + triggerID + ' deleted', 'success');
+ updateUndoBar();
+ triggerID = null;
+ currentID = null;
+ triggerData = null;
+ triggerKind = '';
+ $('#editorMain').innerHTML = '<p style="color:#888;text-align:center;margin-top:60px">Deleted</p>';
+ loadTriggerList();
+ }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); });
+}
+
+function createNew() {
+ var baseID = prompt('Trigger ID (filename without .yaml):');
+ if (!baseID || !baseID.trim()) return;
+ var id = baseID.trim();
+ API.post('/api/triggers', {id: id, on_player_flag: '', steps: []}).then(function(resp) {
+ notify('Trigger ' + id + ' created', 'success');
+ updateUndoBar();
+ loadTriggerList();
+ loadTrigger(id);
+ }).catch(function(e) { notify('Create failed: ' + e.message, 'error'); });
+}