diff options
Diffstat (limited to 'internal/admin')
| -rw-r--r-- | internal/admin/static/cardeditor.js | 2 | ||||
| -rw-r--r-- | internal/admin/static/itemeditor.js | 2 | ||||
| -rw-r--r-- | internal/admin/static/mobeditor.js | 163 | ||||
| -rw-r--r-- | internal/admin/static/objecteditor.js | 183 |
4 files changed, 237 insertions, 113 deletions
diff --git a/internal/admin/static/cardeditor.js b/internal/admin/static/cardeditor.js index 5e0c7cb..a11388b 100644 --- a/internal/admin/static/cardeditor.js +++ b/internal/admin/static/cardeditor.js @@ -10,7 +10,7 @@ function ced_cardPath(sec) { function ced_getVal(data, sec, key) { var p = ced_cardPath(sec); - if (sec.isArray || p === 'on_look') { + if (sec.isArray) { return data[p] && data[p][key] !== undefined ? data[p][key] : ''; } var fp = p ? p + '.' + key : key; diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js index ed32267..a86d240 100644 --- a/internal/admin/static/itemeditor.js +++ b/internal/admin/static/itemeditor.js @@ -602,7 +602,7 @@ function renderArraySection(sec, data) { h += '<button class="btn btn-sm btn-danger obj-sub-remove" onclick="removeArrayItem(\'' + sec.id + '\',' + idx + ')">X</button>'; h += '</div>'; }); - h += '<button class="btn btn-sm obj-sub-add" onclick="addArrayItem(\'' + sec.id + '\')">+ Add ' + esc(sec.label).slice(0,-1) + '</button>'; + h += '<button class="btn btn-sm obj-sub-add" onclick="addArrayItem(\'' + sec.id + '\')">+ Add ' + esc(sec.label) + '</button>'; return h; } diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js index b5d2395..7f8c0e3 100644 --- a/internal/admin/static/mobeditor.js +++ b/internal/admin/static/mobeditor.js @@ -120,6 +120,16 @@ var MOB_SECTIONS = [ ]} ] }, + { + id: 'on_kill', label: 'On Kill', labelHint: '(fired when this mob is defeated or its task completes — additive over drops)', path: 'on_kill', isArray: true, fullWidth: true, interactionStyle: true, + detect: function(d) { return d.on_kill && Array.isArray(d.on_kill); }, + fields: [ + ['item_id', 'Item ID', 'search', '(optional — only fires if you wield this weapon when the mob is defeated)', '', null, 'items'], + ['condition', 'Condition', 'json', 'e.g. {"global_flag":"quest_active"}'], + ['action', 'Action', 'json', 'e.g. {"set_global_flags":{"boss_killed":true},"broadcast":"%p has slain the boss!","spawn_mob":"mini_boss"}'], + ['message', 'Message', 'text', 'Shown to the killer when this fires...'], + ] + }, ]; function mobCardPath(sec) { return ced_cardPath(sec); } @@ -236,6 +246,8 @@ function renderMobCard(sec, data) { h += renderMobCoreCard(data); } else if (sec.id === 'combat') { h += renderMobCombatCard(data); + } else if (sec.isArray) { + h += renderMobArraySection(sec, data); } else if (sec.subtables) { h += '<div class="obj-card-grid">'; sec.fields.forEach(function(f) { @@ -423,6 +435,135 @@ function renderMobField(sec, f, data, idx, subPath, optStyle) { return ced_renderField(sec, f, data, idx, subPath, optStyle, mobCardPath, mobFieldID, mobGetVal); } +// ---- Interaction-style array rendering (On Kill) ---- + +function renderMobArraySection(sec, data) { + var arr = data[mobCardPath(sec)] || []; + var h = ''; + var isInter = !!sec.interactionStyle; + if (isInter) { + window._mobInterVis = window._mobInterVis || {}; + if (!window._mobInterVis[sec.id]) window._mobInterVis[sec.id] = {}; + } + arr.forEach(function(item, idx) { + var visMap = window._mobInterVis[sec.id]; + var vis = visMap[idx] || {}; + visMap[idx] = vis; + var showItem = vis.item_id || (item.item_id && item.item_id !== ''); + var showCond = vis.condition || (item.condition && item.condition !== ''); + var showAct = vis.action || (item.action && item.action !== ''); + var showMsg = vis.message || (item.message && item.message !== ''); + + h += '<div class="obj-sub-row" data-idx="' + idx + '" style="flex-direction:column;align-items:stretch;position:relative;gap:6px">'; + h += '<button class="btn btn-sm btn-danger" style="position:absolute;top:0;right:0;white-space:nowrap;z-index:1" onclick="mobRemoveArrayItem(\'' + sec.id + '\',' + idx + ')">Remove Interaction</button>'; + h += '<div style="display:flex;gap:4px;align-items:flex-end;flex-wrap:wrap;padding-right:135px">'; + if (!showItem) { + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="mobShowInterField(\'' + sec.id + '\',' + idx + ',\'item_id\')">+ Add Required Item</button>'; + h += '<div style="width:1px;background:var(--border);align-self:stretch;margin:2px 0;flex-shrink:0"></div>'; + } + if (!showCond) { + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="mobShowInterField(\'' + sec.id + '\',' + idx + ',\'condition\')">+ Add Condition</button>'; + } + if (!showAct) { + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="mobShowInterField(\'' + sec.id + '\',' + idx + ',\'action\')">+ Add Action</button>'; + } + if (!showMsg) { + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="mobShowInterField(\'' + sec.id + '\',' + idx + ',\'message\')">+ Add Message</button>'; + } + h += '</div>'; + h += '<div style="display:' + (showItem ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; + h += renderMobField(sec, sec.fields[0], data, idx, null, 'flex:1;min-width:0'); + h += '<button class="btn btn-sm btn-danger" onclick="mobHideInterField(\'' + sec.id + '\',' + idx + ',\'item_id\')" style="margin-top:14px">X</button>'; + h += '</div>'; + h += '<div style="display:' + (showCond ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; + h += renderMobField(sec, sec.fields[1], data, idx, null, 'flex:1;min-width:0'); + h += '<button class="btn btn-sm btn-danger" onclick="mobHideInterField(\'' + sec.id + '\',' + idx + ',\'condition\')" style="margin-top:14px">X</button>'; + h += '</div>'; + h += '<div style="display:' + (showAct ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; + h += renderMobField(sec, sec.fields[2], data, idx, null, 'flex:1;min-width:0'); + h += '<button class="btn btn-sm btn-danger" onclick="mobHideInterField(\'' + sec.id + '\',' + idx + ',\'action\')" style="margin-top:14px">X</button>'; + h += '</div>'; + h += '<div style="display:' + (showMsg ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; + h += renderMobField(sec, sec.fields[3], data, idx, null, 'flex:1;min-width:0'); + h += '<button class="btn btn-sm btn-danger" onclick="mobHideInterField(\'' + sec.id + '\',' + idx + ',\'message\')" style="margin-top:14px">X</button>'; + h += '</div>'; + h += '</div>'; + }); + h += '<button class="btn btn-sm obj-sub-add" onclick="mobAddArrayItem(\'' + sec.id + '\')">+ Add ' + esc(sec.label) + '</button>'; + if (isInter) { + var helpKey = 'mob_' + sec.id + '_helpCollapsed'; + var helpCollapsed = window[helpKey] === undefined ? true : window[helpKey]; + h += '<div style="margin-top:8px;border:1px solid rgba(100,160,255,.15);border-radius:4px;overflow:hidden">'; + h += '<div style="padding:6px 10px;background:rgba(100,160,255,.08);cursor:pointer;font-size:10px;color:#6af;font-weight:bold;text-transform:uppercase;letter-spacing:.5px" onclick="mobToggleInterHelp(\'' + sec.id + '\')">'; + h += '<span style="font-size:8px;margin-right:4px">' + (helpCollapsed ? '▶' : '▼') + '</span>'; + h += 'How Conditions & Actions Work'; + h += '</div>'; + if (!helpCollapsed) { + h += '<div style="padding:8px;background:rgba(100,160,255,.05);font-size:10px;color:#aaa;line-height:1.6">'; + h += 'Entries checked top-to-bottom, first match wins (one fires per kill; additive to standard loot).<br><br>'; + h += '<b>Item ID</b>: only fires if you wield this weapon (in main_hand or off_hand) when the mob is defeated. Leave empty to fire unconditionally.<br>'; + h += '<b>Condition</b>: JSON gate. <code>{"global_flag":"boss_quest"}</code>, <code>{"player_flag":"step","value":2}</code>, <code>{"has_item":"trophy"}</code>, <code>{"all_of":[...]}</code>, <code>{"any_of":[...]}</code>.<br>'; + h += '<b>Action</b>: JSON effects. <code>{"set_global_flags":{...}}</code>, <code>{"set_player_flags":{...}}</code>, <code>{"give_item":"gem"}</code>, <code>{"take_item":"key"}</code>, <code>{"teleport":5}</code>, <code>{"heal":10}</code>, <code>{"credits":100}</code>, <code>{"aps_node":true}</code>, <code>{"spawn_mob":"mini_boss"}</code>, <code>{"despawn_mob":"boss"}</code>, <code>{"broadcast":"%p vanquished the boss!"}</code>, <code>{"broadcast_global":"..."}</code>.<br>'; + h += '<b>Message</b>: shown to the killer when this interaction fires.'; + h += '</div>'; + } + h += '</div>'; + } + return h; +} + +function mobAddArrayItem(cardID) { + var sec = MOB_SECTIONS.find(function(s) { return s.id === cardID; }); + if (!sec || !sec.isArray) return; + var arr = mobData[sec.path] || []; + var empty = {}; + sec.fields.forEach(function(f) { + empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + }); + arr.push(empty); + mobData[sec.path] = arr; + renderCurrent(); +} + +function mobRemoveArrayItem(cardID, idx) { + var sec = MOB_SECTIONS.find(function(s) { return s.id === cardID; }); + if (!sec || !sec.isArray) return; + ced_syncDOMToData(mobData); + var arr = mobData[sec.path] || []; + arr.splice(idx, 1); + renderMobEditor(mobID, mobData); +} + +function mobShowInterField(secId, idx, key) { + window._mobInterVis = window._mobInterVis || {}; + if (!window._mobInterVis[secId]) window._mobInterVis[secId] = {}; + if (!window._mobInterVis[secId][idx]) window._mobInterVis[secId][idx] = {}; + window._mobInterVis[secId][idx][key] = true; + renderCurrent(); +} + +function mobHideInterField(secId, idx, key) { + window._mobInterVis = window._mobInterVis || {}; + if (!window._mobInterVis[secId]) window._mobInterVis[secId] = {}; + if (!window._mobInterVis[secId][idx]) window._mobInterVis[secId][idx] = {}; + window._mobInterVis[secId][idx][key] = false; + ced_syncDOMToData(mobData); + if (mobData[secId] && mobData[secId][idx]) { + mobData[secId][idx][key] = ''; + } + renderMobEditor(mobID, mobData); +} + +function mobToggleInterHelp(secId) { + var key = 'mob_' + secId + '_helpCollapsed'; + if (window[key] === undefined) { + window[key] = false; + } else { + window[key] = !window[key]; + } + renderCurrent(); +} + // ---- Subtable rendering ---- function renderMobSubField(sec, stKey, idx, f, val) { @@ -602,7 +743,9 @@ function mobAddSection() { if (!sec) return; if (sec.path) { - if (id === 'combat') { + if (sec.isArray) { + mobData[sec.path] = []; + } else if (id === 'combat') { mobData[sec.path] = { kind: 'combat', aggressive: false, @@ -694,6 +837,24 @@ function saveMob() { return; } + if (sec.isArray) { + var arr = []; + var card = document.querySelector('.obj-card[data-card="' + sec.id + '"]'); + if (!card) return; + var rows = card.querySelectorAll('.obj-sub-row'); + rows.forEach(function(row, idx) { + var item = {}; + sec.fields.forEach(function(f) { + var fid = mobFieldID(sec, f[0], idx); + var el = document.getElementById(fid); + if (el) item[f[0]] = ced_collectFieldValue(el, f[2]); + }); + if (Object.keys(item).length > 0) arr.push(item); + }); + if (arr.length > 0) out[sec.path] = arr; + return; + } + var sectionObj = {}; var hasValues = sec.always; diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js index 6abec3e..ecb3ced 100644 --- a/internal/admin/static/objecteditor.js +++ b/internal/admin/static/objecteditor.js @@ -86,26 +86,23 @@ var SECTIONS = [ ] }, { - id: 'use_interactions', label: 'Use Interactions', labelHint: '(what happens when you use items on this)', path: 'use_interactions', isArray: true, fullWidth: true, - detect: function(d) { return d.use_interactions && Array.isArray(d.use_interactions); }, + id: 'on_use', label: 'On Use', labelHint: '(what happens when you use items on this)', path: 'on_use', isArray: true, fullWidth: true, interactionStyle: true, + detect: function(d) { return d.on_use && Array.isArray(d.on_use); }, fields: [ - ['item_id', 'Item ID', 'search', 'e.g. keycard', '', null, 'items'], + ['item_id', 'Item ID', 'search', 'e.g. keycard (empty = bare use)', '', null, 'items'], ['condition', 'Condition', 'json', 'e.g. {"flag":"my_flag"} or {"player_flag":"x","value":1} or {"all_of":[{"flag":"a"},{"has_item":"key"}]}'], - ['action', 'Action', 'json', 'e.g. {"set_flags":{"my_flag":true},"message":"You activate it.","give_item":"gem"}'], + ['action', 'Action', 'json', 'e.g. {"set_global_flags":{"my_flag":true},"message":"You activate it.","give_item":"gem","teleport":5,"heal":10,"credits":100,"aps_node":true,"spawn_mob":"rat","despawn_mob":"rat","broadcast":"%p activates it.","broadcast_global":"%p did it!","delay":3}'], ['message', 'Message', 'text', 'You use the object...'], ] }, { - id: 'on_look', label: 'On Look', labelHint: '(what happens when you "look <this>")', path: 'on_look', - detect: function(d) { return d.on_look; }, + id: 'on_look', label: 'On Look', labelHint: '(what happens when you "look <this>")', path: 'on_look', isArray: true, fullWidth: true, interactionStyle: true, + detect: function(d) { return d.on_look && Array.isArray(d.on_look); }, fields: [ - ['set_flags', 'Set Global Flags', 'json', '{"flag":"value"}'], - ['set_player_flags', 'Set Player Flags', 'json', '{"pflag":"value"}'], - ['give_item', 'Give Item', 'search', 'e.g. keycard', '', null, 'items'], - ['take_item', 'Take Item', 'search', 'e.g. bomb', '', null, 'items'], - ['teleport', 'Teleport', 'number', '5', 'narrow'], - ['heal', 'Heal', 'number', '10', 'narrow'], - ['credits', 'Credits', 'number', '100', 'narrow'], + ['item_id', 'Item ID', 'search', '(optional — only fires if you carry this item)', '', null, 'items'], + ['condition', 'Condition', 'json', 'e.g. {"player_flag":"read_sign","value":true}'], + ['action', 'Action', 'json', 'e.g. {"set_player_flags":{"read_sign":true},"give_item":"gem"}'], + ['message', 'Message', 'text', 'Shown to the player after the description...'], ] }, ]; @@ -191,7 +188,7 @@ function cardPath(sec) { function getVal(data, sec, key) { var p = cardPath(sec); - if (p === 'on_look' || sec.isArray) { + if (sec.isArray) { return data[p] && data[p][key] !== undefined ? data[p][key] : ''; } var fp = p ? p + '.' + key : key; @@ -234,8 +231,6 @@ function renderCard(sec, data) { h += renderCoreCard(data); } else if (sec.id === 'steal') { h += renderStealCard(data); - } else if (sec.id === 'on_look') { - h += renderOnLookCard(data); } else { h += '<div class="obj-card-grid">'; sec.fields.forEach(function(f) { @@ -507,56 +502,20 @@ function renderStealCard(data) { return h; } -function renderOnLookCard(data) { - var sec = SECTIONS.find(function(s) { return s.id === 'on_look'; }); - var h = '<div style="display:flex;gap:10px">'; - h += renderField(sec, sec.fields[0], data, -1, null, 'flex:1'); - h += renderField(sec, sec.fields[1], data, -1, null, 'flex:1'); - h += '</div>'; - h += '<div style="display:flex;gap:10px;margin-top:8px">'; - h += renderField(sec, sec.fields[2], data, -1, null, 'flex:1'); - h += renderField(sec, sec.fields[3], data, -1, null, 'flex:1'); - h += '</div>'; - h += '<div style="display:flex;gap:10px;margin-top:8px">'; - h += renderField(sec, sec.fields[4], data, -1); - h += renderField(sec, sec.fields[5], data, -1); - h += renderField(sec, sec.fields[6], data, -1); - h += '</div>'; - var helpCollapsed = window._onLookHelpCollapsed === undefined ? true : window._onLookHelpCollapsed; - h += '<div style="margin-top:8px;border:1px solid rgba(100,160,255,.15);border-radius:4px;overflow:hidden">'; - h += '<div style="padding:6px 10px;background:rgba(100,160,255,.08);cursor:pointer;font-size:10px;color:#6af;font-weight:bold;text-transform:uppercase;letter-spacing:.5px" onclick="toggleOnLookHelp()">'; - h += '<span style="font-size:8px;margin-right:4px">' + (helpCollapsed ? '▶' : '▼') + '</span>'; - h += 'How Set Global Flags & Set Player Flags Work'; - h += '</div>'; - if (!helpCollapsed) { - h += '<div style="padding:8px;background:rgba(100,160,255,.05);font-size:10px;color:#aaa;line-height:1.6">'; - h += 'When a player looks at this object, these fields apply effects.<br><br>'; - h += '<b>Set Global Flags</b> — Sets world flags shared by all players. JSON object mapping flag names to values:<br>'; - h += ' <code>{"gate_open":true,"boss_summoned":false}</code><br>'; - h += ' Flags persist until changed by another on_look, room script, or interact action.<br><br>'; - h += '<b>Set Player Flags</b> — Sets per-character flags saved to the player\'s YAML. JSON object mapping flag names to values:<br>'; - h += ' <code>{"has_seen_cave":true,"dialog_spoke":1}</code><br>'; - h += ' These flags can be used in conditions (<code>player_flag</code>) to track player progress.<br><br>'; - h += '<b>Other Fields</b> — <code>Give Item</code> grants an item, <code>Take Item</code> removes one, '; - h += '<code>Teleport</code> sends player to a room, <code>Heal</code> restores HP, <code>Credits</code> adds/removes credits (use negative for cost).<br><br>'; - h += 'All effects fire simultaneously when the player looks at this object.'; - h += '</div>'; - } - h += '</div>'; - return h; -} - function renderArraySection(sec, data) { var arr = data[cardPath(sec)] || []; var h = ''; - var isUseInter = sec.id === 'use_interactions'; - if (isUseInter) { - window._useInterVis = window._useInterVis || {}; + var isInter = !!sec.interactionStyle; + if (isInter) { + window._interVis = window._interVis || {}; + if (!window._interVis[sec.id]) window._interVis[sec.id] = {}; } arr.forEach(function(item, idx) { - if (isUseInter) { - var vis = window._useInterVis[idx] || {}; - window._useInterVis[idx] = vis; + if (isInter) { + var visMap = window._interVis[sec.id]; + var vis = visMap[idx] || {}; + visMap[idx] = vis; + var showItem = vis.item_id || (item.item_id && item.item_id !== ''); var showCond = vis.condition || (item.condition && item.condition !== ''); var showAct = vis.action || (item.action && item.action !== ''); var showMsg = vis.message || (item.message && item.message !== ''); @@ -566,32 +525,39 @@ function renderArraySection(sec, data) { h += '<button class="btn btn-sm btn-danger" style="position:absolute;top:0;right:0;white-space:nowrap;z-index:1" onclick="removeArrayItem(\'' + sec.id + '\',' + idx + ')">Remove Interaction</button>'; h += '<div style="display:flex;gap:4px;align-items:flex-end;flex-wrap:wrap;padding-right:125px">'; - h += renderField(sec, sec.fields[0], data, idx, null, 'max-width:260px'); - h += '<div style="width:1px;background:var(--border);align-self:stretch;margin:2px 0;flex-shrink:0"></div>'; + if (!showItem) { + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showInterField(\'' + sec.id + '\',' + idx + ',\'item_id\')">+ Add Required Item</button>'; + h += '<div style="width:1px;background:var(--border);align-self:stretch;margin:2px 0;flex-shrink:0"></div>'; + } if (!showCond) { - h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showUseInterField(' + idx + ',\'condition\')">+ Add Condition</button>'; + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showInterField(\'' + sec.id + '\',' + idx + ',\'condition\')">+ Add Condition</button>'; } if (!showAct) { - h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showUseInterField(' + idx + ',\'action\')">+ Add Action</button>'; + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showInterField(\'' + sec.id + '\',' + idx + ',\'action\')">+ Add Action</button>'; } if (!showMsg) { - h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showUseInterField(' + idx + ',\'message\')">+ Add Message</button>'; + h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showInterField(\'' + sec.id + '\',' + idx + ',\'message\')">+ Add Message</button>'; } h += '</div>'; + h += '<div style="display:' + (showItem ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; + h += renderField(sec, sec.fields[0], data, idx, null, 'flex:1;min-width:0'); + h += '<button class="btn btn-sm btn-danger" onclick="hideInterField(\'' + sec.id + '\',' + idx + ',\'item_id\')" style="margin-top:14px">X</button>'; + h += '</div>'; + h += '<div style="display:' + (showCond ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; h += renderField(sec, sec.fields[1], data, idx, null, 'flex:1;min-width:0'); - h += '<button class="btn btn-sm btn-danger" onclick="hideUseInterField(' + idx + ',\'condition\')" style="margin-top:14px">X</button>'; + h += '<button class="btn btn-sm btn-danger" onclick="hideInterField(\'' + sec.id + '\',' + idx + ',\'condition\')" style="margin-top:14px">X</button>'; h += '</div>'; h += '<div style="display:' + (showAct ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; h += renderField(sec, sec.fields[2], data, idx, null, 'flex:1;min-width:0'); - h += '<button class="btn btn-sm btn-danger" onclick="hideUseInterField(' + idx + ',\'action\')" style="margin-top:14px">X</button>'; + h += '<button class="btn btn-sm btn-danger" onclick="hideInterField(\'' + sec.id + '\',' + idx + ',\'action\')" style="margin-top:14px">X</button>'; h += '</div>'; h += '<div style="display:' + (showMsg ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">'; h += renderField(sec, sec.fields[3], data, idx, null, 'flex:1;min-width:0'); - h += '<button class="btn btn-sm btn-danger" onclick="hideUseInterField(' + idx + ',\'message\')" style="margin-top:14px">X</button>'; + h += '<button class="btn btn-sm btn-danger" onclick="hideInterField(\'' + sec.id + '\',' + idx + ',\'message\')" style="margin-top:14px">X</button>'; h += '</div>'; h += '</div>'; @@ -606,32 +572,37 @@ function renderArraySection(sec, data) { h += '</div>'; } }); - h += '<button class="btn btn-sm obj-sub-add" onclick="addArrayItem(\'' + sec.id + '\')">+ Add ' + esc(sec.label).slice(0,-1) + '</button>'; - if (isUseInter) { - var helpCollapsed = window._useInterHelpCollapsed === undefined ? true : window._useInterHelpCollapsed; + h += '<button class="btn btn-sm obj-sub-add" onclick="addArrayItem(\'' + sec.id + '\')">+ Add ' + esc(sec.label) + '</button>'; + if (isInter) { + var helpKey = sec.id + '_helpCollapsed'; + var helpCollapsed = window[helpKey] === undefined ? true : window[helpKey]; h += '<div style="margin-top:8px;border:1px solid rgba(100,160,255,.15);border-radius:4px;overflow:hidden">'; - h += '<div style="padding:6px 10px;background:rgba(100,160,255,.08);cursor:pointer;font-size:10px;color:#6af;font-weight:bold;text-transform:uppercase;letter-spacing:.5px" onclick="toggleUseInterHelp()">'; + h += '<div style="padding:6px 10px;background:rgba(100,160,255,.08);cursor:pointer;font-size:10px;color:#6af;font-weight:bold;text-transform:uppercase;letter-spacing:.5px" onclick="toggleInterHelp(\'' + sec.id + '\')">'; h += '<span style="font-size:8px;margin-right:4px">' + (helpCollapsed ? '▶' : '▼') + '</span>'; h += 'How Conditions & Actions Work'; h += '</div>'; if (!helpCollapsed) { h += '<div style="padding:8px;background:rgba(100,160,255,.05);font-size:10px;color:#aaa;line-height:1.6">'; - h += 'Entries checked top-to-bottom, first match wins.<br><br><b>Item ID</b>: required for <code>use <item> on <object></code>; omit for plain <code>use <object></code>.<br>'; - h += '<b>Condition</b>: JSON. When this interaction is available:<br>'; - h += ' <code>{"flag":"gate_open","not":true}</code> — world flag is NOT set<br>'; + h += 'Entries checked top-to-bottom, first match wins (one fires per use/look/kill).<br><br>'; + h += '<b>Item ID</b>: <b>on_use</b> requires <code>use <item> on <object></code>; empty = bare <code>use <object></code>. <b>on_look</b> = only fires if you carry this item. <b>on_kill</b> = only fires if you wield this weapon when the mob is defeated.<br>'; + h += '<b>Condition</b>: JSON. Available gates:<br>'; + h += ' <code>{"global_flag":"gate_open","not":true}</code> — world flag is NOT set<br>'; h += ' <code>{"player_flag":"has_key","value":1}</code> — player flag matches value<br>'; h += ' <code>{"has_item":"silver_bar"}</code> — player carries item<br>'; - h += ' <code>{"all_of":[{"flag":"a"},{"has_item":"b"}]}</code> — ALL sub-conditions<br>'; - h += ' <code>{"any_of":[...]}</code> — ANY sub-condition<br>'; + h += ' <code>{"min_credits":50}</code> — minimum credits<br>'; + h += ' <code>{"all_of":[{"global_flag":"a"},{"has_item":"b"}]}</code> — ALL<br>'; + h += ' <code>{"any_of":[...]}</code> — ANY<br>'; h += ' Omit for an unconditional interaction.<br>'; - h += '<b>Action</b>: JSON. What happens when the interaction fires:<br>'; - h += ' <code>{"set_flags":{"gate_open":true}}</code> — set world flags<br>'; + h += '<b>Action</b>: JSON. Effects that fire when the interaction runs:<br>'; + h += ' <code>{"set_global_flags":{"gate_open":true}}</code> — set world flags<br>'; h += ' <code>{"set_player_flags":{"spoke":true}}</code> — set player flags<br>'; - h += ' <code>{"give_item":"keycard","teleport":5}</code> — give item + teleport<br>'; - h += ' <code>{"take_item":"bomb","heal":10}</code> — take item + heal<br>'; - h += ' <code>{"credits":-50}</code> — deduct credits<br>'; - h += ' <code>{"reputation_cost":1,"aps_node":true}</code> — rep cost + mark APS<br>'; - h += '<b>Message</b>: Sent to the player when the interaction fires (1-tick action).'; + h += ' <code>{"give_item":"keycard","take_item":"bomb"}</code> — give/take item<br>'; + h += ' <code>{"teleport":5,"heal":10,"credits":100}</code> — move, heal, give credits<br>'; + h += ' <code>{"aps_node":true}</code> — mark current room as APS node<br>'; + h += ' <code>{"spawn_mob":"rat","despawn_mob":"rat"}</code> — spawn/despawn transient mob<br>'; + h += ' <code>{"broadcast":"%p activates it.","broadcast_global":"%p did it!"}</code> — announce (%p = player, %v = flag value)<br>'; + h += ' <code>{"message":"You feel energized.","delay":3}</code> — only meaningful inside on_enter steps / triggers (delay in ticks)<br>'; + h += '<b>Message</b>: Sent to the player when the interaction fires (one-tick action).'; h += '</div>'; } h += '</div>'; @@ -906,8 +877,6 @@ function addSection() { if (sec.path) { if (sec.isArray) { objectData[sec.path] = []; - } else if (sec.id === 'on_look') { - objectData[sec.path] = {}; } else if (sec.id === 'gather') { objectData[sec.path] = {skill:'', tools:[], success:{base:0, per_level:0, cap:1}, drops:[], gather_message:'', fail_message:'', respawn_timer:0, respawn_broadcast:'', deplete_timer:0, nest_chance:0}; } else if (sec.id === 'safespot') { @@ -1009,20 +978,22 @@ function renderCurrent() { renderObjectEditor(objectID, objectData); } -function showUseInterField(idx, key) { - window._useInterVis = window._useInterVis || {}; - if (!window._useInterVis[idx]) window._useInterVis[idx] = {}; - window._useInterVis[idx][key] = true; +function showInterField(secId, idx, key) { + window._interVis = window._interVis || {}; + if (!window._interVis[secId]) window._interVis[secId] = {}; + if (!window._interVis[secId][idx]) window._interVis[secId][idx] = {}; + window._interVis[secId][idx][key] = true; renderCurrent(); } -function hideUseInterField(idx, key) { - window._useInterVis = window._useInterVis || {}; - if (!window._useInterVis[idx]) window._useInterVis[idx] = {}; - window._useInterVis[idx][key] = false; +function hideInterField(secId, idx, key) { + window._interVis = window._interVis || {}; + if (!window._interVis[secId]) window._interVis[secId] = {}; + if (!window._interVis[secId][idx]) window._interVis[secId][idx] = {}; + window._interVis[secId][idx][key] = false; ced_syncDOMToData(objectData); - if (objectData.use_interactions && objectData.use_interactions[idx]) { - objectData.use_interactions[idx][key] = ''; + if (objectData[secId] && objectData[secId][idx]) { + objectData[secId][idx][key] = ''; } renderObjectEditor(objectID, objectData); } @@ -1061,20 +1032,12 @@ function hideGuardMob() { renderObjectEditor(objectID, objectData); } -function toggleUseInterHelp() { - if (window._useInterHelpCollapsed === undefined) { - window._useInterHelpCollapsed = false; - } else { - window._useInterHelpCollapsed = !window._useInterHelpCollapsed; - } - renderCurrent(); -} - -function toggleOnLookHelp() { - if (window._onLookHelpCollapsed === undefined) { - window._onLookHelpCollapsed = false; +function toggleInterHelp(secId) { + var key = secId + '_helpCollapsed'; + if (window[key] === undefined) { + window[key] = false; } else { - window._onLookHelpCollapsed = !window._onLookHelpCollapsed; + window[key] = !window[key]; } renderCurrent(); } |
