// intereditor.js — Structured condition & step editors for trigger blocks. // Drives the unified Trigger model: // Trigger = { lock, item_id?, condition?, steps[], on_player_flag?, on_global_flag?, value? } // Step = { condition?, wait, messages[], broadcast, broadcast_global, // spawn_mob, despawn_mob, set_global_flags, set_player_flags, // give_item, take_item, teleport, heal, credits, aps_node, // drop_table[] } // Shared by objecteditor.js, mobeditor.js, and map.js. // ── Condition leaf types ── var IE_COND_TYPES = [ {type: 'global_flag', label: 'Global Flag', hint: 'global flag name'}, {type: 'player_flag', label: 'Player Flag', hint: 'player flag name'}, {type: 'room', label: 'In Room', hint: 'room ID'}, {type: 'has_item', label: 'Has Item', hint: 'item ID'}, {type: 'min_credits', label: 'Min Credits', hint: 'amount'}, ]; // ── Effect field types ── // // Every block is sequence-scoped now: broadcast / broadcast_global / // spawn_mob / despawn_mob are available everywhere. Messages and condition // are handled separately (plain-string message rows; per-step condition). var IE_ACT_TYPES = [ {key: 'wait', label: 'Wait', kind: 'number', hint: 'ticks before this step'}, {key: 'broadcast', label: 'Broadcast', kind: 'text', hint: '%p = player'}, {key: 'broadcast_global', label: 'Broadcast Global', kind: 'text', hint: '%p = player'}, {key: 'spawn_mob', label: 'Spawn Mob', kind: 'search', entity: 'mobs'}, {key: 'despawn_mob', label: 'Despawn Mob', kind: 'search', entity: 'mobs'}, {key: 'set_global_flags', label: 'Set Global Flags', kind: 'kv'}, {key: 'set_player_flags', label: 'Set Player Flags', kind: 'kv'}, {key: 'give_item', label: 'Give Item', kind: 'search', entity: 'items'}, {key: 'take_item', label: 'Take Item', kind: 'search', entity: 'items'}, {key: 'drop_table', label: 'Drop Table', kind: 'droptable'}, {key: 'teleport', label: 'Teleport', kind: 'number', hint: 'room ID'}, {key: 'heal', label: 'Heal', kind: 'number', hint: 'HP to restore'}, {key: 'credits', label: 'Credits', kind: 'number', hint: 'amount (negative = cost)'}, {key: 'aps_node', label: 'APS Node', kind: 'checkbox'}, ]; // ── Step-kind buttons for the bottom add-bar (each click creates a new // step initialised with that single action kind) and the per-step // "+" toggle bar. messages is handled separately from IE_ACT_TYPES. ── var IE_STEP_KINDS = [ {key: 'messages', label: 'Message', kind: 'messages'}, ]; IE_ACT_TYPES.forEach(function(at) { IE_STEP_KINDS.push(at); }); // ── Scope / Context system ── // Lets condition & action editors target data models outside the // default ed[secPath][idx].steps[si] arrangement (e.g. talk node/option // conditions and single-step actions). When a scope is registered and its // token is placed on a DOM root via data-ie-scope, all mutation onclicks // resolve their data target through the scope instead of the legacy trigger // path. window._ieScopes = {}; var _ieScopeSeq = 0; function ie_registerScope(opts, token) { if (!token) { token = 'sc' + (++_ieScopeSeq); } window._ieScopes[token] = opts; return token; } window.ie_registerScope = ie_registerScope; function ie_unregisterScope(token) { delete window._ieScopes[token]; } window.ie_unregisterScope = ie_unregisterScope; function _ie_scopeFromEl(el) { if (!el || !el.closest) return null; var scopeEl = el.closest('[data-ie-scope]'); if (!scopeEl) return null; var token = scopeEl.getAttribute('data-ie-scope'); return window._ieScopes[token] || null; } // Resolve the .ie-cond-root DOM element scoped to a mutation element el. // Handles both the root itself and the outer addbar (which sits outside // .ie-cond-root as a sibling). function _ie_condScopeRoot(el) { var root = el.closest('.ie-cond-root'); if (root) return root; var addbar = el.closest('.ie-cond-addbar'); if (!addbar) return null; var parent = addbar.parentElement; if (!parent) return null; return parent.querySelector('.ie-cond-root'); } // Unified condition-editing context. // Returns { get, set, change } — `get` always syncs DOM → data store // before returning. Routes through a registered scope when el lives inside // [data-ie-scope], otherwise through the legacy ed[secPath][idx] trigger path. function _ie_condCtx(el, secId, secPath, idx, kind) { var scope = _ie_scopeFromEl(el); if (scope) { return { get: function() { var root = _ie_condScopeRoot(el); if (root) { var cond = ie_collectCond(root); scope.setCond(cond); } return scope.getCond(); }, set: function(c) { scope.setCond(c); }, change: function() { scope.onChange(); } }; } return { get: function() { var ed = window._editorData; if (!ed) return null; _ie_syncCond(ed, secId, secPath, idx, kind); return _ie_getCond(ed, secPath, idx, kind); }, set: function(c) { var ed = window._editorData; if (!ed) return; _ie_setCond(ed, secPath, idx, kind, c); ced_syncDOMToData(ed); }, change: function() { window._ieRenderOnly(); } }; } // Unified action/step-editing context. // Returns { get, set, change }. In scope mode reads the .ie-act-root // container; in legacy mode operates on ed[secPath][idx].steps[si]. function _ie_actCtx(el, secId, secPath, idx, si) { var scope = _ie_scopeFromEl(el); if (scope) { return { get: function() { var container = el.closest('.ie-act-root'); if (!container) return scope.getStep() || {}; var act = ie_collectAct(container); var msgs = ie_collectMessages(container); var step = scope.getStep() || {}; if (act) Object.keys(act).forEach(function(k) { step[k] = act[k]; }); if (msgs !== null) step.messages = msgs; else delete step.messages; scope.setStep(step); return scope.getStep(); }, set: function(s) { scope.setStep(s); }, change: function() { scope.onChange(); } }; } return { get: function() { var ed = window._editorData; if (!ed) return {}; ie_syncStep(ed, secId, secPath, idx, si); if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].steps || !ed[secPath][idx].steps[si]) return {}; return ed[secPath][idx].steps[si]; }, set: function(s) { var ed = window._editorData; if (!ed) return; if (!ed[secPath]) ed[secPath] = []; if (!ed[secPath][idx]) ed[secPath][idx] = {steps: []}; if (!ed[secPath][idx].steps) ed[secPath][idx].steps = []; ed[secPath][idx].steps[si] = s; ced_syncDOMToData(ed); }, change: function() { window._ieRenderOnly(); } }; } // ── Rendering helpers ── // ie_renderStepAddbar: the per-step action add bar revealed by the "+" toggle. // Only shows kinds not already present on this step. function ie_renderStepAddbar(step, secId, idx, si, secPath) { var h = ''; IE_STEP_KINDS.forEach(function(sk) { if (sk.key === 'messages') { if (step && Array.isArray(step.messages)) return; } else { if (step && Object.prototype.hasOwnProperty.call(step, sk.key)) { if (sk.kind === 'checkbox' && step[sk.key] !== true) { /* re-addable */ } else return; } } if (sk.key === 'messages') { h += ''; } else { h += ''; } }); return h; } // ie_renderBottomAddbar: the always-visible bar at the foot of the step list. // Each button creates a new step initialised with that single action kind. function ie_renderBottomAddbar(secId, secPath, idx) { var h = ''; IE_STEP_KINDS.forEach(function(sk) { h += ''; }); return h; } // ── Drop Table block renderer (shared between intereditor and triggerseditor) ── function ie_renderDropTableBlock(drops, onAddItem, onAddTable, onRemove) { drops = drops || []; var h = '
'; drops.forEach(function(row, ri) { var kind = row.table ? 'table' : (row.item_id ? 'item' : (row._kind || 'item')); h += '
'; if (kind === 'table') { h += ''; } else { h += ''; } h += ''; h += ''; h += ''; h += '
'; }); h += '
'; h += ''; h += ''; h += '
'; h += '
'; return h; } function ie_collectDropTableBlock(container) { if (!container) return null; var rows = container.querySelectorAll(':scope > .ie-drop-row'); var out = []; rows.forEach(function(row) { var tableInput = row.querySelector('.ie-drop-table-input'); var itemInput = row.querySelector('.ie-drop-item-input'); var weightInput = row.querySelector('.ie-drop-weight'); var qtyInput = row.querySelector('.ie-drop-qty'); var entry = {}; if (tableInput && tableInput.value.trim()) { entry.table = tableInput.value.trim(); entry._kind = 'table'; } else if (itemInput && itemInput.value.trim()) { entry.item_id = itemInput.value.trim(); entry._kind = 'item'; } else if (itemInput) { entry.item_id = ''; entry._kind = 'item'; } else { entry.table = ''; entry._kind = 'table'; } entry.weight = weightInput ? (parseInt(weightInput.value, 10) || 0) : 0; entry.quantity = qtyInput ? (parseInt(qtyInput.value, 10) || 0) : 0; out.push(entry); }); return out.length > 0 ? out : null; } // ie_addStepKind creates a new step initialised with a single action kind. function ie_addStepKind(el, secId, secPath, idx, kind) { var ed = window._editorData; if (!ed) return; ced_syncDOMToData(ed); if (window._ieSyncAll) window._ieSyncAll(ed); if (!ed[secPath]) ed[secPath] = []; if (!ed[secPath][idx]) ed[secPath][idx] = {}; if (!ed[secPath][idx].steps || !Array.isArray(ed[secPath][idx].steps)) ed[secPath][idx].steps = []; var step = {}; if (kind === 'messages') { step.messages = ['']; } else if (kind === 'wait') { step.wait = 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 === 'droptable') step[kind] = []; else step[kind] = ''; } ed[secPath][idx].steps.push(step); window._ieRenderCurrent(); } // Sentinel child-index for a root-level single leaf (no parent group). var IE_ROOT_LEAF = -1; // ── Condition lens helpers (entry vs step) ── // // kind is the string-encoded condition anchor: 'entry' for the trigger's own // condition, or 'step-' for the nth step's condition. Internally we parse // it to the numeric step index (-1 = entry). function _ie_kindStep(kind) { if (kind === undefined || kind === null || kind === 'entry') return -1; if (typeof kind === 'string' && kind.indexOf('step-') === 0) { var n = parseInt(kind.slice(5), 10); return isNaN(n) ? -1 : n; } return -1; } function _ie_getCond(ed, secPath, idx, kind) { var si = _ie_kindStep(kind); if (!ed[secPath] || !ed[secPath][idx]) return null; if (si < 0) return ed[secPath][idx].condition || null; if (!ed[secPath][idx].steps || !ed[secPath][idx].steps[si]) return null; return ed[secPath][idx].steps[si].condition || null; } function _ie_setCond(ed, secPath, idx, kind, newCond) { var si = _ie_kindStep(kind); if (!ed[secPath]) ed[secPath] = []; if (!ed[secPath][idx]) ed[secPath][idx] = {}; if (si < 0) { if (newCond) ed[secPath][idx].condition = newCond; else delete ed[secPath][idx].condition; } else { if (!ed[secPath][idx].steps) ed[secPath][idx].steps = []; if (!ed[secPath][idx].steps[si]) ed[secPath][idx].steps[si] = {}; if (newCond) ed[secPath][idx].steps[si].condition = newCond; else delete ed[secPath][idx].steps[si].condition; } } function _ie_syncCond(ed, secId, secPath, idx, kind) { var card = document.querySelector('.obj-card[data-card="' + secId + '"]'); if (!card) return; var si = _ie_kindStep(kind); var container; if (si < 0) { container = card.querySelector('.obj-sub-row.ie-inter-row[data-idx="' + idx + '"]'); } else { container = card.querySelector('.obj-sub-row.ie-inter-row[data-idx="' + idx + '"] .ie-step-row[data-step="' + si + '"]'); } if (!container) return; var condRoot = container.querySelector('.ie-cond-root'); if (!condRoot) return; var cond = ie_collectCond(condRoot); _ie_setCond(ed, secPath, idx, kind, cond); } // ── Navigation in condition tree ── function ie_condAt(cond, path) { if (!path) return cond; var parts = path.split('-').map(Number); var cur = cond; for (var i = 0; i < parts.length; i++) { if (!cur || (!cur.all_of && !cur.any_of)) return null; var children = cur.all_of || cur.any_of || []; if (parts[i] >= children.length) return null; cur = children[parts[i]]; } return cur; } function ie_condReplace(cond, path, newVal) { if (!path) return newVal; if (!cond) return cond; var parts = path.split('-').map(Number); function replace(cur, depth) { if (depth === parts.length) return newVal; if (!cur || (!cur.all_of && !cur.any_of)) return cur; var mode = cur.all_of ? 'all_of' : 'any_of'; var children = (cur.all_of || cur.any_of).slice(); children[parts[depth]] = replace(children[parts[depth]], depth + 1); children = children.filter(function(c) { return c !== null; }); if (children.length === 0) return null; var r = {}; if (cur.not) r.not = true; r[mode] = children; return r; } return replace(cond, 0); } // ── Clean condition tree for save ── function ie_cleanCondition(cond) { if (!cond || typeof cond !== 'object') return null; if (cond.all_of || cond.any_of) { var mode = cond.all_of ? 'all_of' : 'any_of'; var cleaned = cond[mode].map(ie_cleanCondition).filter(function(c) { return c !== null; }); if (cleaned.length === 0) return null; if (cleaned.length === 1 && !cond.not) return cleaned[0]; var r = {}; if (cond.not) r.not = true; r[mode] = cleaned; return r; } var not = !!cond.not; var leaf = null; if (cond.global_flag !== undefined) { if (cond.global_flag) { leaf = { global_flag: cond.global_flag }; if (cond.value !== undefined) leaf.value = cond.value; } } else if (cond.player_flag !== undefined) { if (cond.player_flag) { leaf = { player_flag: cond.player_flag }; if (cond.value !== undefined) leaf.value = cond.value; } } else if (cond.room !== undefined) { if (cond.room) leaf = { room: parseInt(cond.room, 10) || 0 }; } else if (cond.has_item !== undefined) { if (cond.has_item) leaf = { has_item: cond.has_item }; } else if (cond.min_credits !== undefined) { var mc = parseInt(cond.min_credits, 10); if (!isNaN(mc)) leaf = { min_credits: mc }; } if (!leaf) return null; if (not) leaf.not = true; return leaf; } // ── Collect condition from DOM ── function ie_collectCond(rootEl) { if (!rootEl) return null; if (rootEl.classList.contains('ie-cond-root')) { var groupEl = rootEl.querySelector(':scope > .ie-cond'); if (groupEl) return ie_collectCond(groupEl); var leafEl = rootEl.querySelector(':scope > .ie-cond-leaf'); if (leafEl) return ie_collectLeaf(leafEl); return null; } var mode = 'all_of'; var modeSel = rootEl.querySelector(':scope > .ie-cond-modebar .ie-cond-modesel'); if (modeSel) mode = modeSel.value; var notCb = rootEl.querySelector(':scope > .ie-cond-modebar .ie-cond-not'); var not = notCb ? notCb.checked : false; var container = rootEl.querySelector(':scope > .ie-cond-children'); var items = []; if (container) { Array.prototype.forEach.call(container.children, function(child) { if (child.classList.contains('ie-cond-leaf')) { var leaf = ie_collectLeaf(child); if (leaf) items.push(leaf); } else if (child.classList.contains('ie-cond')) { var sub = ie_collectCond(child); if (sub) items.push(sub); } }); } var result = {}; if (not) result.not = true; result[mode] = items; return result; } function ie_collectLeaf(el) { var leafType = el.getAttribute('data-ie-leaftype'); if (!leafType) return null; var result = {}; var notCb = el.querySelector('.ie-cond-not'); if (notCb && notCb.checked) result.not = true; var valInput = el.querySelector('.ie-cond-val'); var val = valInput ? valInput.value.trim() : ''; switch (leafType) { case 'global_flag': result.global_flag = val; var gfSubInput = el.querySelector('.ie-cond-subval'); if (gfSubInput && gfSubInput.value.trim()) { var gfsv = gfSubInput.value.trim(); try { result.value = JSON.parse(gfsv); } catch(e) { result.value = gfsv; } } break; case 'player_flag': result.player_flag = val; var subInput = el.querySelector('.ie-cond-subval'); if (subInput && subInput.value.trim()) { var sv = subInput.value.trim(); try { result.value = JSON.parse(sv); } catch(e) { result.value = sv; } } break; case 'room': result.room = parseInt(val) || 0; break; case 'has_item': result.has_item = val; break; case 'min_credits': result.min_credits = parseInt(val) || 0; break; default: return null; } return result; } // ── Render condition ── function ie_renderCond(condObj, secId, idx, secPath, kind, opts) { kind = kind || 'entry'; opts = opts || {}; var scopeAttr = opts.scopeToken ? ' data-ie-scope="' + escAttr(opts.scopeToken) + '"' : ''; var h = ''; var isGroup = condObj && (condObj.all_of || condObj.any_of); var rootChildCount = isGroup ? (condObj.all_of || condObj.any_of || []).length : 0; if (condObj && typeof condObj === 'object') { var condTitleText = ''; if (kind && kind.indexOf('step-') === 0) { var sn = parseInt(kind.slice(5), 10); if (!isNaN(sn)) condTitleText = 'Condition for Step ' + (sn + 1); } else if (kind === 'entry' && !opts.scopeToken) { condTitleText = 'Condition for Action Block'; } h += '
'; if (condTitleText) h += '
' + esc(condTitleText) + '
'; h += ie_renderGroup(condObj, secId, secPath, idx, '', kind); h += '
'; } var showAddbar = (!isGroup || (opts.alwaysOuterAddbar && rootChildCount === 0)) && typeof condObj === 'object'; if (showAddbar) { h += '
'; IE_COND_TYPES.forEach(function(ct) { h += ''; }); h += ''; h += '
'; } return h; } function ie_renderGroup(cond, secId, secPath, idx, gpath, kind) { var mode = null, children = null; if (cond.all_of) { mode = 'all_of'; children = cond.all_of; } else if (cond.any_of) { mode = 'any_of'; children = cond.any_of; } var not = !!cond.not; if (mode && children) { return _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, kind); } if (cond.global_flag !== undefined) return _ie_renderLeaf('global_flag', cond.global_flag, cond.value, not, secId, secPath, idx, gpath, IE_ROOT_LEAF, kind); if (cond.player_flag !== undefined) return _ie_renderLeaf('player_flag', cond.player_flag, cond.value, not, secId, secPath, idx, gpath, IE_ROOT_LEAF, kind); if (cond.room !== undefined) return _ie_renderLeaf('room', cond.room, '', not, secId, secPath, idx, gpath, IE_ROOT_LEAF, kind); if (cond.has_item !== undefined) return _ie_renderLeaf('has_item', cond.has_item, '', not, secId, secPath, idx, gpath, IE_ROOT_LEAF, kind); if (cond.min_credits !== undefined) return _ie_renderLeaf('min_credits', cond.min_credits, '', not, secId, secPath, idx, gpath, IE_ROOT_LEAF, kind); return ''; } function _ie_renderGroupNode(mode, children, not, secId, secPath, idx, gpath, kind) { var h = ''; if (children.length === 0) { h += '
'; h += '
'; h += '
'; IE_COND_TYPES.forEach(function(ct) { h += ''; }); h += ''; h += '
'; h += ''; h += '
'; h += '
'; return h; } h += '
'; h += '
'; if (children.length >= 2) { h += ''; } if (children.length >= 2 || not) { h += ''; } h += '
'; h += '
'; children.forEach(function(child, ci) { var cpath = gpath ? gpath + '-' + ci : String(ci); if (child.all_of || child.any_of) { h += ie_renderGroup(child, secId, secPath, idx, cpath, kind); } else { var cval = '', csub = '', ctype = '', cnot = !!child.not; if (child.global_flag !== undefined) { ctype = 'global_flag'; cval = child.global_flag; csub = child.value; } else if (child.player_flag !== undefined) { ctype = 'player_flag'; cval = child.player_flag; csub = child.value; } else if (child.room !== undefined) { ctype = 'room'; cval = child.room; } else if (child.has_item !== undefined) { ctype = 'has_item'; cval = child.has_item; } else if (child.min_credits !== undefined) { ctype = 'min_credits'; cval = child.min_credits; } if (ctype) h += _ie_renderLeaf(ctype, cval, csub, cnot, secId, secPath, idx, gpath, ci, kind); } }); h += '
'; h += '
'; IE_COND_TYPES.forEach(function(ct) { h += ''; }); h += ''; h += '
'; h += '
'; return h; } function _ie_renderLeaf(leafType, leafVal, leafSub, not, secId, secPath, idx, gpath, ci, kind) { kind = kind || 'entry'; var label = ''; IE_COND_TYPES.forEach(function(ct) { if (ct.type === leafType) label = ct.label; }); var hint = ''; IE_COND_TYPES.forEach(function(ct) { if (ct.type === leafType) hint = ct.hint; }); var isSearch = leafType === 'has_item'; var sv = leafVal === undefined || leafVal === null ? '' : String(leafVal); var ssv = leafSub === undefined || leafSub === null ? '' : String(leafSub); var ciToken = (ci === undefined || ci === null) ? IE_ROOT_LEAF : ci; var h = '
'; h += '' + esc(label) + ''; if (isSearch) { h += ''; } else if (leafType === 'min_credits' || leafType === 'room') { h += ''; } else { h += ''; } if (leafType === 'player_flag' || leafType === 'global_flag') { h += '='; h += ''; } h += ''; h += ''; h += '
'; return h; } // ── Render step effects (everything except messages and condition). // The add-bar is rendered separately via ie_renderStepAddbar. ── function ie_renderAct(stepObj, secId, idx, si, secPath) { var hasAny = false; var rows = ''; IE_ACT_TYPES.forEach(function(at) { if (!stepObj || !Object.prototype.hasOwnProperty.call(stepObj, at.key)) return; var val = stepObj[at.key]; if (at.kind === 'checkbox' && val !== true) return; hasAny = true; if (at.kind === 'kv') { var kvMap = (val && typeof val === 'object') ? val : {}; var kvKeys = Object.keys(kvMap); var kvRmAttr = 'ie_removeKVRow(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ',\'' + escAttr(at.key) + '\')'; rows += '
'; rows += '
' + esc(at.label) + '
'; rows += '
'; if (kvKeys.length === 0) { rows += '
'; rows += ''; rows += '
'; } else { kvKeys.forEach(function(k) { rows += '
'; rows += ''; rows += '
'; }); } rows += '
'; rows += ''; rows += '
'; } else if (at.kind === 'checkbox') { rows += '
' + esc(at.label) + ''; rows += ''; rows += ''; rows += '
'; } else if (at.kind === 'search') { rows += '
' + esc(at.label) + ''; rows += ''; rows += ''; rows += '
'; } else if (at.kind === 'droptable') { rows += '
'; rows += '
Drop Table
'; rows += ie_renderDropTableBlock(val, 'ie_addDropRow(this,\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',\'item\')', 'ie_addDropRow(this,\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',\'table\')', 'ie_removeDropRow(this,\''+escAttr(secId)+'\',\''+escAttr(secPath)+'\','+idx+','+si+',__RI__)'); rows += '
'; rows += '
'; } else { rows += '
' + esc(at.label) + ''; rows += ''; rows += ''; rows += '
'; } }); if (hasAny) { return '
' + rows + '
'; } return ''; } // ── Render messages sub-list (plain strings) ── function ie_renderMessages(step, secId, idx, si, secPath) { var present = step && Array.isArray(step.messages); if (!present) { return ''; } var msgs = step.messages; var rmFn = 'ie_removeMessage(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')'; var h = '
'; h += '
Messages
'; h += '
'; msgs.forEach(function(m, mi) { var upBtn = mi > 0 ? '' : ''; var dnBtn = mi < msgs.length - 1 ? '' : ''; h += '
' + upBtn + dnBtn + '
'; }); h += '
'; h += ''; h += '
'; return h; } // ── Render steps list of a trigger entry ── function ie_renderStepList(steps, secId, idx, secPath) { steps = steps || []; var h = '
'; steps.forEach(function(step, si) { var stepCond = step && step.condition && typeof step.condition === 'object'; h += '
'; h += '
'; h += 'Step ' + (si + 1) + ''; h += ''; if (!stepCond) { h += ''; } h += ''; if (si > 0) h += ''; if (si < steps.length - 1) h += ''; h += ''; h += '
'; if (stepCond) { h += '
'; h += ie_renderCond(step.condition || null, secId, idx, secPath, 'step-' + si); h += '
'; } h += '
' + ie_renderAct(step, secId, idx, si, secPath) + '
'; h += '
' + ie_renderMessages(step, secId, idx, si, secPath) + '
'; h += '
' + ie_renderStepAddbar(step, secId, idx, si, secPath) + '
'; h += '
'; }); h += '
' + ie_renderBottomAddbar(secId, secPath, idx) + '
'; h += '
'; return h; } // ── Collect effects (everything except messages and condition) ── function ie_collectAct(container) { if (!container) return null; var r = {}; container.querySelectorAll('.ie-act-kv').forEach(function(kvEl) { var key = kvEl.getAttribute('data-ie-act-key'); if (!key) return; if (key === 'messages') return; if (key === 'drop_table') { var dt = ie_collectDropTableBlock(kvEl.querySelector('.ie-drop-table')); if (dt) r.drop_table = dt; return; } var map = {}; kvEl.querySelectorAll('.ie-kv-row').forEach(function(row) { var kEl = row.querySelector('.ie-kv-key'); var vEl = row.querySelector('.ie-kv-val'); if (kEl && kEl.value.trim() && vEl) { var v = vEl.value.trim(); var num = parseFloat(v); map[kEl.value.trim()] = v === 'true' ? true : (v === 'false' ? false : (!isNaN(num) ? num : v)); } }); r[key] = map; }); container.querySelectorAll('.ie-act-input').forEach(function(el) { var key = el.getAttribute('data-ie-act-key'); if (!key) return; var kind = el.getAttribute('data-ie-act-kind'); if (kind === 'checkbox') { r[key] = el.checked; } else if (kind === 'number') { var n = parseFloat(el.value); if (isNaN(n)) return; r[key] = n; } else { r[key] = el.value.trim(); } }); if (Object.keys(r).length === 0) return null; return r; } // ── Collect messages (plain strings) ── function ie_collectMessages(container) { if (!container) return null; var msgsKv = container.querySelector('.ie-act-kv[data-ie-act-key="messages"]'); if (!msgsKv) return null; var list = []; msgsKv.querySelectorAll('.ie-kv-row .ie-act-msg').forEach(function(el) { list.push(el.value.trim()); }); return list; } // ── Sync a single step's DOM back into data ── function ie_syncStep(ed, secId, secPath, idx, si) { var card = document.querySelector('.obj-card[data-card="' + secId + '"]'); if (!card) return; var stepRow = card.querySelector('.obj-sub-row.ie-inter-row[data-idx="' + idx + '"] .ie-step-row[data-step="' + si + '"]'); if (!stepRow) return; if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].steps || !ed[secPath][idx].steps[si]) return; var step = ed[secPath][idx].steps[si]; var condRoot = stepRow.querySelector(':scope > .ie-step-cond-panel .ie-cond-root'); if (condRoot) { var cond = ie_collectCond(condRoot); if (cond) step.condition = cond; else delete step.condition; } var effContainer = stepRow.querySelector(':scope > .ie-step-effects'); if (effContainer) { var act = ie_collectAct(effContainer); if (act) { Object.keys(act).forEach(function(k) { step[k] = act[k]; }); } // Drop keys the user X'd out (no longer present in the DOM among the rendered IE_ACT_TYPES set). IE_ACT_TYPES.forEach(function(at) { if (at.key === 'messages') return; if (!Object.prototype.hasOwnProperty.call(act || {}, at.key)) { if (Object.prototype.hasOwnProperty.call(step, at.key)) delete step[at.key]; } }); } var msgsContainer = stepRow.querySelector(':scope > .ie-step-messages'); if (msgsContainer) { var list = ie_collectMessages(msgsContainer); if (list !== null) step.messages = list; else delete step.messages; if (Array.isArray(step.messages) && step.messages.length === 0) { // keep empty array — survives next render, ced_cleanOutput will strip // it on save. } } } // ── Sync all interaction DOM back to data ── function ie_syncAllInteractions(data, secs) { if (!data || !secs) return; secs.forEach(function(sec) { if (!sec.interactionStyle || !sec.isArray) return; var p = sec.path; if (!data[p]) return; var card = document.querySelector('.obj-card[data-card="' + sec.id + '"]'); if (!card) return; var rows = card.querySelectorAll('.obj-sub-row.ie-inter-row'); rows.forEach(function(row, idx) { if (!data[p][idx]) data[p][idx] = {}; var lockCb = row.querySelector(':scope > .ie-inter-header .ie-lock-cb'); if (lockCb) { if (lockCb.checked) data[p][idx].lock = true; else delete data[p][idx].lock; } var entryCondRoot = row.querySelector(':scope > .ie-inter-panel .ie-cond-root'); if (entryCondRoot) { var cond = ie_collectCond(entryCondRoot); if (cond) data[p][idx].condition = cond; else delete data[p][idx].condition; } if (!data[p][idx].steps) data[p][idx].steps = []; var stepRows = row.querySelectorAll(':scope > .ie-inter-steps .ie-step-row'); stepRows.forEach(function(stepRow) { var si = parseInt(stepRow.getAttribute('data-step'), 10); if (isNaN(si)) return; ie_syncStep(data, sec.id, p, idx, si); }); }); }); } // ── Add/Remove/Toggle: Condition ── function ie_addLeaf(el, secId, secPath, idx, kind, groupPath, leafType) { var c = _ie_condCtx(el, secId, secPath, idx, kind); var cond = c.get(); var leaf = {}; leaf[leafType] = (leafType === 'min_credits' || leafType === 'room') ? 0 : ''; var group = ie_condAt(cond, groupPath); var newGroup; if (!group) { newGroup = leaf; } else if (group.all_of || group.any_of) { var mode = group.all_of ? 'all_of' : 'any_of'; var children = (group.all_of || group.any_of).concat([leaf]); newGroup = {}; if (group.not) newGroup.not = true; newGroup[mode] = children; } else { newGroup = {all_of: [group, leaf]}; } cond = ie_condReplace(cond, groupPath, newGroup); c.set(cond); c.change(); } function ie_removeCond(el, secId, secPath, idx, kind, groupPath, childIdx) { var c = _ie_condCtx(el, secId, secPath, idx, kind); var cond = c.get(); if (childIdx === IE_ROOT_LEAF) { c.set(null); c.change(); return; } var group = ie_condAt(cond, groupPath); if (!group) return; if (group.all_of || group.any_of) { var mode = group.all_of ? 'all_of' : 'any_of'; var children = (group.all_of || group.any_of).slice(); children.splice(childIdx, 1); var newGroup; if (children.length === 0) { newGroup = null; } else if (children.length === 1 && !group.not) { newGroup = children[0]; } else { newGroup = {}; if (group.not) newGroup.not = true; newGroup[mode] = children; } cond = ie_condReplace(cond, groupPath, newGroup); } else { cond = ie_condReplace(cond, groupPath, null); } c.set(cond); c.change(); } function ie_removeGroup(el, secId, secPath, idx, kind, groupPath) { var c = _ie_condCtx(el, secId, secPath, idx, kind); var cond = c.get(); if (!groupPath) { c.set(null); c.change(); return; } var parts = groupPath.split('-').map(Number); var childIdx = parts[parts.length - 1]; var parentPath = parts.slice(0, -1).join('-'); var parent = ie_condAt(cond, parentPath); if (!parent || !(parent.all_of || parent.any_of)) return; var mode = parent.all_of ? 'all_of' : 'any_of'; var children = parent[mode].slice(); children.splice(childIdx, 1); var newParent; if (children.length === 0) { newParent = null; } else if (children.length === 1 && !parent.not) { newParent = children[0]; } else { newParent = {}; if (parent.not) newParent.not = true; newParent[mode] = children; } cond = ie_condReplace(cond, parentPath, newParent); c.set(cond); c.change(); } function ie_addRootConditionGroup(secPath, idx, kind) { var ed = window._editorData; if (!ed) return; if (!ed[secPath]) ed[secPath] = []; if (!ed[secPath][idx]) ed[secPath][idx] = {}; var si = _ie_kindStep(kind); if (si < 0) { if (!ed[secPath][idx].condition || typeof ed[secPath][idx].condition !== 'object') { ed[secPath][idx].condition = {all_of: []}; } } else { if (!ed[secPath][idx].steps) ed[secPath][idx].steps = []; if (!ed[secPath][idx].steps[si]) ed[secPath][idx].steps[si] = {}; if (!ed[secPath][idx].steps[si].condition || typeof ed[secPath][idx].steps[si].condition !== 'object') { ed[secPath][idx].steps[si].condition = {all_of: []}; } } ced_syncDOMToData(ed); window._ieRenderOnly(); } function ie_addStepCond(el, secId, secPath, idx, si) { if (el && el.nodeType && _ie_scopeFromEl(el)) { var c = _ie_condCtx(el, secId, secPath, idx, 'step-' + si); c.get(); c.change(); return; } ie_addRootConditionGroup(secPath, idx, 'step-' + si); } function ie_addGroup(el, secId, secPath, idx, kind, groupPath) { var c = _ie_condCtx(el, secId, secPath, idx, kind); var cond = c.get(); var group = ie_condAt(cond, groupPath); var newSub = {all_of: []}; var newGroup; if (!group) { newGroup = newSub; } else if (group.all_of || group.any_of) { var mode = group.all_of ? 'all_of' : 'any_of'; var children = (group.all_of || group.any_of).concat([newSub]); newGroup = {}; if (group.not) newGroup.not = true; newGroup[mode] = children; } else { newGroup = {all_of: [group, newSub]}; } cond = ie_condReplace(cond, groupPath, newGroup); c.set(cond); c.change(); } function ie_addOuterLeaf(el, secId, secPath, idx, kind, leafType) { var c = _ie_condCtx(el, secId, secPath, idx, kind); var cond = c.get(); var leaf = {}; leaf[leafType] = (leafType === 'min_credits' || leafType === 'room') ? 0 : ''; if (!cond) { cond = leaf; } else { cond = {all_of: [cond, leaf]}; } c.set(cond); c.change(); } function ie_addOuterGroup(el, secId, secPath, idx, kind) { var c = _ie_condCtx(el, secId, secPath, idx, kind); var cond = c.get(); var newSub = {all_of: []}; if (!cond) { cond = newSub; } else { cond = {all_of: [cond, newSub]}; } c.set(cond); c.change(); } function ie_toggleCondMode(el, secId, secPath, idx, kind, groupPath) { var c = _ie_condCtx(el, secId, secPath, idx, kind); c.get(); c.change(); } function ie_toggleCondNot(el, secId, secPath, idx, kind, groupPath) { var c = _ie_condCtx(el, secId, secPath, idx, kind); c.get(); c.change(); } function ie_toggleNotLeaf(el, secId, secPath, idx, kind, groupPath, ci) { var c = _ie_condCtx(el, secId, secPath, idx, kind); c.get(); c.change(); } // ── Add/Remove: Steps ── function ie_removeStep(el, secId, secPath, idx, si) { var ed = window._editorData; if (!ed) return; ced_syncDOMToData(ed); if (window._ieSyncAll) window._ieSyncAll(ed); if (!ed[secPath] || !ed[secPath][idx] || !ed[secPath][idx].steps) return; ed[secPath][idx].steps.splice(si, 1); window._ieRenderCurrent(); } function ie_moveStep(el, secId, secPath, idx, fromSi, toSi) { var ed = window._editorData; if (!ed) return; ced_syncDOMToData(ed); if (window._ieSyncAll) window._ieSyncAll(ed); var steps = ed[secPath] && ed[secPath][idx] && ed[secPath][idx].steps; if (!steps || fromSi < 0 || fromSi >= steps.length || toSi < 0 || toSi >= steps.length) return; var m = steps.splice(fromSi, 1)[0]; steps.splice(toSi, 0, m); window._ieRenderOnly(); } function ie_moveRow(secPath, fromIdx, toIdx) { var ed = window._editorData; if (!ed) return; ced_syncDOMToData(ed); if (window._ieSyncAll) window._ieSyncAll(ed); var arr = ed[secPath]; if (!arr || fromIdx < 0 || fromIdx >= arr.length || toIdx < 0 || toIdx >= arr.length) return; var m = arr.splice(fromIdx, 1)[0]; arr.splice(toIdx, 0, m); window._ieRenderOnly(); } function ie_moveMessage(el, secId, secPath, idx, si, fromMi, toMi) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); if (!step || !Array.isArray(step.messages)) return; if (fromMi < 0 || fromMi >= step.messages.length || toMi < 0 || toMi >= step.messages.length) return; var m = step.messages.splice(fromMi, 1)[0]; step.messages.splice(toMi, 0, m); c.set(step); c.change(); } // ── Add/Remove: Action effects ── function ie_addActEffect(el, secId, secPath, idx, si, effectKey) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); var at = IE_ACT_TYPES.find(function(a) { return a.key === effectKey; }); if (!at) return; if (at.kind === 'kv') step[effectKey] = {}; else if (at.kind === 'checkbox') step[effectKey] = true; else if (at.kind === 'number') step[effectKey] = 0; else if (at.kind === 'droptable') step[effectKey] = []; else step[effectKey] = ''; c.set(step); c.change(); } function ie_removeActEffect(el, secId, secPath, idx, si, effectKey) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); delete step[effectKey]; c.set(step); c.change(); } // ── Add/Remove: KV row ── function ie_addKVRow(el, secId, secPath, idx, si, effectKey) { if (_ie_scopeFromEl(el)) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); if (!step[effectKey] || typeof step[effectKey] !== 'object') step[effectKey] = {}; step[effectKey][''] = ''; c.set(step); c.change(); return; } var ed = window._editorData; if (!ed) return; ie_syncStep(ed, secId, secPath, idx, si); var card = document.querySelector('.obj-card[data-card="' + secId + '"]'); if (!card) return; var stepRow = card.querySelector('.obj-sub-row.ie-inter-row[data-idx="' + idx + '"] .ie-step-row[data-step="' + si + '"]'); if (!stepRow) return; var list = stepRow.querySelector('.ie-act-kv[data-ie-act-key="' + effectKey + '"] .ie-kv-list'); if (!list) return; var rowEl = document.createElement('div'); rowEl.className = 'ie-kv-row'; rowEl.innerHTML = ''; list.appendChild(rowEl); rowEl.querySelector('.ie-kv-key').focus(); } function ie_removeKVRow(rowEl, secId, secPath, idx, si, effectKey) { if (rowEl && rowEl.closest) { var kvRow = rowEl.closest('.ie-kv-row'); if (kvRow) rowEl = kvRow; } var c = _ie_actCtx(rowEl, secId, secPath, idx, si); var step = c.get(); if (!step || !step.hasOwnProperty(effectKey)) return; var keyEl = rowEl.querySelector('.ie-kv-key'); var k = keyEl ? keyEl.value.trim() : ''; if (k && step[effectKey].hasOwnProperty(k)) delete step[effectKey][k]; if (Object.keys(step[effectKey]).length === 0) delete step[effectKey]; c.set(step); c.change(); } // ── Add/Remove: Messages ── function ie_addMessages(el, secId, secPath, idx, si) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); if (!step.messages || !Array.isArray(step.messages)) step.messages = []; step.messages.push(''); c.set(step); c.change(); } // ── Drop Table row add/remove ── function ie_addDropRow(el, secId, secPath, idx, si, kind) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); if (!Array.isArray(step.drop_table)) step.drop_table = []; if (kind === 'table') { step.drop_table.push({table: '', weight: 10, quantity: 1, _kind: 'table'}); } else { step.drop_table.push({item_id: '', weight: 10, quantity: 1, _kind: 'item'}); } c.set(step); c.change(); } function ie_removeDropRow(el, secId, secPath, idx, si, ri) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); if (!Array.isArray(step.drop_table)) return; step.drop_table.splice(ri, 1); c.set(step); c.change(); } function ie_addMessage(el, secId, secPath, idx, si) { if (_ie_scopeFromEl(el)) { var c = _ie_actCtx(el, secId, secPath, idx, si); var step = c.get(); if (!step.messages || !Array.isArray(step.messages)) step.messages = []; step.messages.push(''); c.set(step); c.change(); return; } var ed = window._editorData; if (!ed) return; ie_syncStep(ed, secId, secPath, idx, si); var card = document.querySelector('.obj-card[data-card="' + secId + '"]'); if (!card) return; var stepRow = card.querySelector('.obj-sub-row.ie-inter-row[data-idx="' + idx + '"] .ie-step-row[data-step="' + si + '"]'); if (!stepRow) return; var list = stepRow.querySelector('.ie-act-kv[data-ie-act-key="messages"] .ie-kv-list'); if (!list) return; var rowEl = document.createElement('div'); rowEl.className = 'ie-kv-row ie-msg-row'; var rmFn = 'ie_removeMessage(this,\'' + escAttr(secId) + '\',\'' + escAttr(secPath) + '\',' + idx + ',' + si + ')'; rowEl.innerHTML = ''; list.appendChild(rowEl); rowEl.querySelector('.ie-act-msg').focus(); } function ie_removeMessage(rowEl, secId, secPath, idx, si) { if (rowEl && rowEl.closest) { var kvRow = rowEl.closest('.ie-kv-row'); if (kvRow) rowEl = kvRow; } var c = _ie_actCtx(rowEl, secId, secPath, idx, si); var step = c.get(); if (!step || !Array.isArray(step.messages)) return; var kvRows = document.querySelectorAll('.ie-act-kv[data-ie-act-key="messages"] .ie-kv-row'); for (var ri = 0; ri < kvRows.length; ri++) { if (kvRows[ri] === rowEl || kvRows[ri].contains(rowEl)) { step.messages.splice(ri, 1); break; } } if (step.messages.length === 0) delete step.messages; c.set(step); c.change(); } // ── Shared trigger-style array-section renderer ── function ie_renderArraySection(ctx) { var sec = ctx.sec; var data = ctx.data; var visMap = ctx.visMap; var arr = data[sec.path] || []; var h = ''; var allowItem = sec.itemIDAllowed !== false; arr.forEach(function(item, idx) { visMap[idx] = visMap[idx] || {}; var vis = visMap[idx]; var showItem = allowItem && (vis.item_id || (item.item_id && item.item_id !== '')); var condExists = !!(item.condition && typeof item.condition === 'object'); var lockVal = !!item.lock; var steps = item.steps || []; h += '
'; h += '
'; h += ''; if (idx > 0) h += ''; if (idx < arr.length - 1) h += ''; if (allowItem) { if (!showItem) { h += ''; } else { h += ctx.renderField(sec, sec.fields[0], data, idx, 'flex:1;min-width:0'); h += ''; } } if (!condExists) { h += ''; } h += ''; h += ''; h += '
'; h += '
'; h += '
' + ie_renderCond(item.condition || null, sec.id, idx, sec.path, 'entry') + '
'; h += '
'; h += '
'; h += ie_renderStepList(steps, sec.id, idx, sec.path); h += '
'; h += '
'; }); h += ''; return h; } // ── Show/Hide field on a row ── function ie_showField(visMap, secId, idx, key) { if (!visMap[secId]) visMap[secId] = {}; if (!visMap[secId][idx]) visMap[secId][idx] = {}; visMap[secId][idx][key] = true; } function ie_hideField(visMap, secId, idx, key) { if (!visMap[secId]) visMap[secId] = {}; if (!visMap[secId][idx]) visMap[secId][idx] = {}; visMap[secId][idx][key] = false; } // ── Step-cleaning helper used by save paths ── function ie_cleanStep(step) { var s = {}; if (step && step.condition) { var c = ie_cleanCondition(step.condition); if (c) s.condition = c; } if (step && step.wait !== undefined && step.wait !== null && step.wait !== 0) s.wait = step.wait; if (step && Array.isArray(step.messages) && step.messages.filter(function(m){return m;}).length > 0) { s.messages = step.messages.filter(function(m){return m;}); } IE_ACT_TYPES.forEach(function(at) { if (at.key === 'wait' || at.key === 'messages') return; if (!step || !Object.prototype.hasOwnProperty.call(step, at.key)) return; var v = step[at.key]; if (v === undefined || v === null || v === '') return; if (at.key === 'drop_table' && Array.isArray(v)) { var cleaned = v.map(function(e) { var c = {}; if (e.item_id) c.item_id = e.item_id; if (e.table) c.table = e.table; if (e.weight) c.weight = e.weight; if (e.quantity) c.quantity = e.quantity; return c; }).filter(function(e) { return Object.keys(e).length > 0; }); if (cleaned.length > 0) s.drop_table = cleaned; return; } if (typeof v === 'object') { if (Object.keys(v).length === 0) return; s[at.key] = v; } else if (typeof v === 'boolean') { if (!v) return; s[at.key] = v; } else { s[at.key] = v; } }); return s; } // ── Bindings (set by each editor) ── window._ieRenderCurrent = function() {}; window._ieRenderOnly = function() {};