diff options
| author | historia <[not public]> | 2026-07-10 00:43:29 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-10 00:43:29 -0400 |
| commit | 6e7c0b3ec4253bb58a815979b1f4574d08cb1f53 (patch) | |
| tree | dbff1952bdbf14cd58a6128775e263b53fc64ec1 /internal/admin/static/triggerseditor.js | |
| parent | 9836513d287396fabcba85956366e6410d7363ef (diff) | |
| download | thehouseoficarus-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.js | 463 |
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) + ')">▲</button>'; + if (si < steps.length - 1) h += '<button class="btn btn-sm ie-step-dn" onclick="moveStep(' + si + ',' + (si + 1) + ')">▼</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'); }); +} |
