aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/static
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-05 03:45:48 -0400
committerhistoria <[not public]>2026-07-05 03:45:48 -0400
commit21c1997a3fd0f5ec5dcff1add45e4dfaf7a338ca (patch)
tree3a63fb257837c0f7545eab0cada4c24b8ce67e84 /internal/admin/static
parent81f3e3bae7d82f0e0ad72d89ea5a5fdae8804712 (diff)
downloadthehouseoficarus-21c1997a3fd0f5ec5dcff1add45e4dfaf7a338ca.tar.gz
fix: use interaction cards in admin gui work, fix card css on object tab
Diffstat (limited to 'internal/admin/static')
-rw-r--r--internal/admin/static/admin.css6
-rw-r--r--internal/admin/static/cardeditor.js41
-rw-r--r--internal/admin/static/itemeditor.js15
-rw-r--r--internal/admin/static/mobeditor.js4
-rw-r--r--internal/admin/static/objecteditor.js216
5 files changed, 241 insertions, 41 deletions
diff --git a/internal/admin/static/admin.css b/internal/admin/static/admin.css
index 33dbdc4..3c6aaa4 100644
--- a/internal/admin/static/admin.css
+++ b/internal/admin/static/admin.css
@@ -277,9 +277,9 @@ g.ud-hover:hover text{font-weight:bold}
.obj-card-header:hover{background:rgba(15,52,96,.5)}
.obj-card-arrow{font-size:10px;width:14px;color:#888}
.obj-card-label{flex:1;color:var(--text)}
-.obj-card-remove{padding:2px 6px;font-size:10px;min-width:auto}
-.obj-card-expand{padding:2px 5px;font-size:11px;min-width:auto;background:transparent;color:#888;border:1px solid transparent;border-radius:3px;cursor:pointer;font-family:monospace;line-height:1}
-.obj-card-expand:hover{color:var(--text);border-color:var(--border);background:rgba(255,255,255,.05)}
+.obj-card-remove{width:18px;height:18px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:10px;min-width:auto;flex-shrink:0}
+.obj-card-expand{width:18px;height:18px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:9px;min-width:auto;background:rgba(255,255,255,0.07);color:#888;border:1px solid rgba(255,255,255,0.12);border-radius:3px;cursor:pointer;font-family:monospace;line-height:1;flex-shrink:0}
+.obj-card-expand:hover{color:var(--text);border-color:var(--border);background:rgba(255,255,255,0.12)}
.obj-card-body{padding:10px 12px}
.obj-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:6px 8px}
.obj-field{display:flex;flex-direction:column;gap:2px;font-size:11px}
diff --git a/internal/admin/static/cardeditor.js b/internal/admin/static/cardeditor.js
index 0235894..f2316e1 100644
--- a/internal/admin/static/cardeditor.js
+++ b/internal/admin/static/cardeditor.js
@@ -166,12 +166,42 @@ function ced_selectSearchResult(el) {
var idx = Array.prototype.indexOf.call(results.children, el);
if (results._items && results._items[idx] && results._targetInput) {
results._targetInput.value = results._items[idx].id;
+ ced_persistSearchToData(results._targetInput, results._items[idx].id);
results._targetInput.dispatchEvent(new Event('input'));
results.style.display = 'none';
results.innerHTML = '';
}
}
+function ced_persistSearchToData(input, value) {
+ var ed = window._editorData;
+ var dataPath = input.getAttribute('data-data-path');
+ if (!ed || !dataPath) return;
+ var m = dataPath.match(/^(.+)\[(\d+)\]\.(.+)$/);
+ if (m) {
+ var arr = ced_resolveDataPath(ed, m[1]);
+ if (arr && Array.isArray(arr)) {
+ if (!arr[m[2]]) arr[m[2]] = {};
+ arr[m[2]][m[3]] = value;
+ }
+ } else {
+ var m2 = dataPath.match(/^(.+)\.(.+)$/);
+ if (m2) {
+ var obj = ced_resolveDataPath(ed, m2[1]);
+ if (obj) obj[m2[2]] = value;
+ } else {
+ ed[dataPath] = value;
+ }
+ }
+}
+
+function ced_resolveDataPath(obj, path) {
+ return path.split('.').reduce(function(o, k) {
+ if (o === undefined || o === null) return undefined;
+ return o[k] !== undefined ? o[k] : undefined;
+ }, obj);
+}
+
function ced_updateSearchHighlight(results) {
for (var i = 0; i < results.children.length; i++) {
results.children[i].classList.toggle('active', i === results._selectedIndex);
@@ -240,7 +270,16 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field
if (type === 'search') {
var entity = searchEntity || 'items';
var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."';
- return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
+ var pfx = cardPathFn(sec);
+ var dp;
+ if (idx >= 0) {
+ dp = pfx + '[' + idx + '].' + key;
+ } else if (subPath) {
+ dp = (pfx ? pfx + '.' : '') + subPath + '.' + key;
+ } else {
+ dp = (pfx ? pfx + '.' : '') + key;
+ }
+ return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
}
var ph = hint ? ' placeholder="' + esc(hint) + '"' : '';
return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + '>');
diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js
index f1450ab..5a2b371 100644
--- a/internal/admin/static/itemeditor.js
+++ b/internal/admin/static/itemeditor.js
@@ -166,6 +166,7 @@ function loadItemEditor(id) {
window.location.hash = id;
API.get('/api/items/' + encodeURIComponent(id)).then(function(data) {
itemData = data;
+ window._editorData = itemData;
renderItemEditor(id, data);
}).catch(function(e) {
$('#editorMain').innerHTML = '<p style="color:var(--danger)">Failed to load: ' + esc(e.message) + '</p>';
@@ -524,7 +525,16 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
if (type === 'search') {
var entity = searchEntity || 'items';
var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."';
- return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
+ var pfx = cardPath(sec);
+ var dp;
+ if (idx >= 0) {
+ dp = pfx + '[' + idx + '].' + key;
+ } else if (subPath) {
+ dp = (pfx ? pfx + '.' : '') + subPath + '.' + key;
+ } else {
+ dp = (pfx ? pfx + '.' : '') + key;
+ }
+ return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
}
var ph = hint ? ' placeholder="' + esc(hint) + '"' : '';
return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + '>');
@@ -568,7 +578,8 @@ function renderSubFieldHTML(sec, stKey, idx, f, val) {
if (f[2] === 'search') {
var entity = f[6] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
- return '<label class="obj-field obj-search' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ var dp = cardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0];
+ return '<label class="obj-field obj-search' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
}
if (f[2] === 'checkbox') {
return '<label class="obj-field obj-check' + extraCls + '"' + extraStyle + '><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js
index 1758673..c564088 100644
--- a/internal/admin/static/mobeditor.js
+++ b/internal/admin/static/mobeditor.js
@@ -138,6 +138,7 @@ function loadMob(id) {
renderList('');
API.get('/api/mobs/' + encodeURIComponent(id)).then(function(data) {
mobData = data;
+ window._editorData = mobData;
if (mobData.shop && mobData.shop.buys_anything === undefined) {
mobData.shop.buys_anything = true;
}
@@ -427,7 +428,8 @@ function renderMobSubField(sec, stKey, idx, f, val) {
if (f[2] === 'search') {
var entity = f[6] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
- return '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ var dp = mobCardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0];
+ return '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
}
if (f[2] === 'checkbox') {
return '<label class="obj-field obj-check' + extraCls + '"><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js
index 5153458..c80cb57 100644
--- a/internal/admin/static/objecteditor.js
+++ b/internal/admin/static/objecteditor.js
@@ -19,7 +19,7 @@ var SECTIONS = [
},
{
id: 'steal', label: 'Steal',
- detect: function(d) { return d.steal_table || d.steal_level || d.steal_speed || d.steal_xp || d.guard_mob; },
+ detect: function(d) { return d.steal_table !== undefined || d.steal_level !== undefined || d.steal_speed !== undefined || d.steal_xp !== undefined || d.guard_mob !== undefined; },
fields: [
['steal_table', 'Table ID', 'text'],
['steal_level', 'Level', 'number', '', 'narrow'],
@@ -89,7 +89,7 @@ var SECTIONS = [
detect: function(d) { return d.safespot; },
fields: [
['tier', 'Tier', 'number', '', 'narrow'],
- ['max_block_size', 'Max Block Size', 'text'],
+ ['max_block_size', 'Max Block Size', 'select', '|small|medium|large|massive'],
['max_occupants', 'Max Occupants', 'number', '', 'narrow'],
['unsafe_chance', 'Unsafe Chance', 'number', '', 'narrow'],
['decay_ticks', 'Decay Ticks', 'number', '', 'narrow'],
@@ -104,13 +104,13 @@ var SECTIONS = [
]
},
{
- id: 'use_interactions', label: 'Use Interactions', path: 'use_interactions', isArray: true,
+ 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); },
fields: [
- ['item_id', 'Item ID', 'text'],
+ ['item_id', 'Item ID', 'search', '', '', 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"}'],
['message', 'Message', 'text'],
- ['condition', 'Condition', 'json'],
- ['action', 'Action', 'json'],
]
},
{
@@ -145,6 +145,9 @@ function loadObject(id) {
renderList('');
API.get('/api/objects/' + encodeURIComponent(id)).then(function(data) {
objectData = data;
+ window._editorData = objectData;
+ window._useInterVis = {};
+ window._useInterHelpCollapsed = undefined;
renderObjectEditor(id, data);
}).catch(function(e) {
$('#editorMain').innerHTML = '<p style="color:var(--danger)">Failed to load: ' + esc(e.message) + '</p>';
@@ -230,7 +233,11 @@ function renderCard(sec, data) {
var h = '<div class="obj-card' + fullClass + '" data-card="' + sec.id + '">';
h += '<div class="obj-card-header" onclick="toggleCard(\'' + sec.id + '\')">';
h += '<span class="obj-card-arrow">' + (collapsed ? '&#9654;' : '&#9660;') + '</span>';
- h += '<span class="obj-card-label">' + sec.label + '</span>';
+ h += '<span class="obj-card-label">' + sec.label;
+ if (sec.labelHint) {
+ h += ' <span style="color:#666;font-size:10px;font-weight:normal">' + esc(sec.labelHint) + '</span>';
+ }
+ h += '</span>';
h += '<button class="btn btn-sm obj-card-expand" onclick="event.stopPropagation();toggleCardWidth(\'' + sec.id + '\')" title="' + (isFull ? 'Half width' : 'Full width') + '">' + (isFull ? '\u2190' : '\u2192') + '</button>';
if (!sec.always) {
h += '<button class="btn btn-sm btn-danger obj-card-remove" onclick="event.stopPropagation();removeSection(\'' + sec.id + '\')">X</button>';
@@ -269,9 +276,7 @@ function renderCard(sec, data) {
h += renderTags(sec, il, data);
h += '</div>';
} else if (il.type === 'tools_checkbox') {
- h += '<div class="obj-inline-group" style="flex:1">';
h += renderToolsCheckbox(sec, il, data);
- h += '</div>';
} else if (il.type === 'kv') {
h += '<div class="obj-inline-group" style="flex:1">';
h += renderKV(sec, il, data);
@@ -368,7 +373,7 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
var opts = hint ? hint.split('|') : [];
var inner = '<span>' + esc(label) + '</span><select id="' + fid + '" onchange="toggleGatherConditionals()">';
opts.forEach(function(o) {
- inner += '<option value="' + escAttr(o) + '"' + (String(val) === o ? ' selected' : '') + '>' + esc(o) + '</option>';
+ inner += '<option value="' + escAttr(o) + '"' + (String(val) === o ? ' selected' : '') + '>' + (o === '' ? 'All' : esc(o)) + '</option>';
});
inner += '</select>';
return wrap('obj-field', inner);
@@ -380,7 +385,16 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
if (type === 'search') {
var entity = searchEntity || 'items';
var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."';
- return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
+ var pfx = cardPath(sec);
+ var dp;
+ if (idx >= 0) {
+ dp = pfx + '[' + idx + '].' + key;
+ } else if (subPath) {
+ dp = (pfx ? pfx + '.' : '') + subPath + '.' + key;
+ } else {
+ dp = (pfx ? pfx + '.' : '') + key;
+ }
+ return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
}
var ph = hint ? ' placeholder="' + esc(hint) + '"' : '';
return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + '>');
@@ -405,17 +419,92 @@ function renderCoreCard(data) {
function renderArraySection(sec, data) {
var arr = data[cardPath(sec)] || [];
var h = '';
+ var isUseInter = sec.id === 'use_interactions';
+ if (isUseInter) {
+ window._useInterVis = window._useInterVis || {};
+ }
arr.forEach(function(item, idx) {
- h += '<div class="obj-sub-row" data-idx="' + idx + '">';
- h += '<div class="obj-card-grid">';
- sec.fields.forEach(function(f) {
- h += renderField(sec, f, data, idx);
- });
- h += '</div>';
- h += '<button class="btn btn-sm btn-danger obj-sub-remove" onclick="removeArrayItem(\'' + sec.id + '\',' + idx + ')">X</button>';
- h += '</div>';
+ if (isUseInter) {
+ var vis = window._useInterVis[idx] || {};
+ window._useInterVis[idx] = vis;
+ 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 += '<div style="display:flex;gap:6px;align-items:flex-end;flex-wrap:wrap">';
+ 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 (!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>';
+ }
+ 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>';
+ }
+ 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 btn-danger" style="margin-left:auto;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="removeArrayItem(\'' + sec.id + '\',' + idx + ')">Remove Interaction</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 += '</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 += '</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 += '</div>';
+
+ h += '</div>';
+ } else {
+ h += '<div class="obj-sub-row" data-idx="' + idx + '">';
+ h += '<div class="obj-card-grid">';
+ sec.fields.forEach(function(f) {
+ h += renderField(sec, f, data, idx);
+ });
+ h += '</div>';
+ 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>';
+ if (isUseInter) {
+ var helpCollapsed = window._useInterHelpCollapsed === undefined ? true : window._useInterHelpCollapsed;
+ 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 += '<span style="font-size:8px;margin-right:4px">' + (helpCollapsed ? '&#9654;' : '&#9660;') + '</span>';
+ h += 'How Conditions &amp; 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 &lt;item&gt; on &lt;object&gt;</code>; omit for plain <code>use &lt;object&gt;</code>.<br>';
+ h += '<b>Condition</b>: JSON. When this interaction is available:<br>';
+ h += '&nbsp;&nbsp;<code>{"flag":"gate_open","not":true}</code> — world flag is NOT set<br>';
+ h += '&nbsp;&nbsp;<code>{"player_flag":"has_key","value":1}</code> — player flag matches value<br>';
+ h += '&nbsp;&nbsp;<code>{"has_item":"silver_bar"}</code> — player carries item<br>';
+ h += '&nbsp;&nbsp;<code>{"all_of":[{"flag":"a"},{"has_item":"b"}]}</code> — ALL sub-conditions<br>';
+ h += '&nbsp;&nbsp;<code>{"any_of":[...]}</code> — ANY sub-condition<br>';
+ h += '&nbsp;&nbsp;Omit for an unconditional interaction.<br>';
+ h += '<b>Action</b>: JSON. What happens when the interaction fires:<br>';
+ h += '&nbsp;&nbsp;<code>{"set_flags":{"gate_open":true}}</code> — set world flags<br>';
+ h += '&nbsp;&nbsp;<code>{"set_player_flags":{"spoke":true}}</code> — set player flags<br>';
+ h += '&nbsp;&nbsp;<code>{"give_item":"keycard","teleport":5}</code> — give item + teleport<br>';
+ h += '&nbsp;&nbsp;<code>{"take_item":"bomb","heal":10}</code> — take item + heal<br>';
+ h += '&nbsp;&nbsp;<code>{"credits":-50}</code> — deduct credits<br>';
+ h += '&nbsp;&nbsp;<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 += '</div>';
+ }
+ h += '</div>';
+ }
return h;
}
@@ -431,8 +520,11 @@ function renderSubtable(sec, st, data) {
var h = '<div class="obj-subtable">';
h += '<div class="obj-subtable-header">' + esc(st.label) + '</div>';
arr.forEach(function(item, idx) {
- h += '<div class="obj-sub-row"' + (isDrops ? ' style="flex-direction:column;align-items:stretch"' : '') + '>';
+ h += '<div class="obj-sub-row"' + (isDrops ? ' style="flex-direction:column;align-items:stretch;position:relative"' : '') + '>';
if (isDrops) {
+ if (!st.single) {
+ h += '<button class="btn btn-sm btn-danger" style="position:absolute;top:4px;right:6px" onclick="removeSubRow(\'' + sec.id + '\',\'' + st.key + '\',' + idx + ')">X</button>';
+ }
var isItemDrop = item._type !== undefined ? item._type !== 't' : !item.table;
var showField = isItemDrop ? 'item_id' : 'table';
var hideField = isItemDrop ? 'table' : 'item_id';
@@ -448,16 +540,14 @@ function renderSubtable(sec, st, data) {
if (f[2] === 'search') {
var entity = f[5] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
- h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ var dp = cardPath(sec) + '.' + st.key + '[' + idx + '].' + f[0];
+ h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
} else if (f[2] === 'checkbox') {
h += '<label class="obj-field obj-check' + extraCls + '"><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
} else {
h += '<label class="obj-field' + extraCls + '"><span>' + esc(f[1]) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '" placeholder="' + escAttr(f[3] || '') + '"></label>';
}
});
- if (!st.single) {
- h += '<button class="btn btn-sm btn-danger obj-sub-remove" style="margin-left:auto" onclick="removeSubRow(\'' + sec.id + '\',\'' + st.key + '\',' + idx + ')">X</button>';
- }
h += '</div>';
h += '<div class="obj-sub-row-line">';
var fidMsg = fieldIDSub(sec, st.key, idx, 'message');
@@ -476,7 +566,8 @@ function renderSubtable(sec, st, data) {
if (f[2] === 'search') {
var entity = f[5] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
- h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ var dp = cardPath(sec) + '.' + st.key + '[' + idx + '].' + f[0];
+ h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
} else if (f[2] === 'checkbox') {
h += '<label class="obj-field obj-check' + extraCls + '"><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
} else {
@@ -530,21 +621,52 @@ function renderToolsCheckbox(sec, il, data) {
var sectionRoot = data[cardPath(sec)] || {};
var arr = sectionRoot[il.key] || [];
var choices = toolTypes || [];
+ if (choices.length === 0) {
+ return '<span style="color:#888;font-size:11px">Loading tool types...</span>';
+ }
+ if (arr.length === 0) {
+ return '<button class="btn btn-sm obj-sub-add" onclick="addToolRow(\'' + sec.id + '\',\'' + il.key + '\')">+ Add Tools</button>';
+ }
var h = '<div class="obj-inline-group">';
h += '<span class="obj-inline-label">' + esc(il.label) + '</span>';
- h += '<div class="obj-tools-checkboxes" style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-top:4px">';
- choices.forEach(function(t) {
- var checked = arr.indexOf(t) >= 0 ? ' checked' : '';
- h += '<label class="obj-field obj-check" style="margin:0;padding:2px 0"><input type="checkbox" class="obj-tool-chk" data-tool="' + escAttr(t) + '"' + checked + '> <span style="font-size:12px">' + esc(t) + '</span></label>';
+ h += '<div style="display:flex;flex-direction:column;gap:3px;margin-top:4px">';
+ arr.forEach(function(t, idx) {
+ h += '<div style="display:flex;gap:4px">';
+ h += '<select class="obj-tool-select" style="flex:1;font-size:12px;padding:2px 4px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px">';
+ choices.forEach(function(c) {
+ var sel = c === t ? ' selected' : '';
+ h += '<option' + sel + '>' + esc(c) + '</option>';
+ });
+ h += '</select>';
+ h += '<button class="btn btn-sm btn-danger" onclick="removeToolRow(\'' + sec.id + '\',\'' + il.key + '\',' + idx + ')">X</button>';
+ h += '</div>';
});
h += '</div>';
- if (choices.length === 0) {
- h += '<span style="color:#888;font-size:11px">Loading tool types...</span>';
- }
+ h += '<button class="btn btn-sm obj-sub-add" onclick="addToolRow(\'' + sec.id + '\',\'' + il.key + '\')">+ Add</button>';
h += '</div>';
return h;
}
+function addToolRow(secID, key) {
+ var sec = SECTIONS.find(function(s) { return s.id === secID; });
+ if (!sec) return;
+ var p = cardPath(sec);
+ if (!objectData[p]) objectData[p] = {};
+ objectData[p][key] = (objectData[p][key] || []).concat(['']);
+ renderCurrent();
+}
+
+function removeToolRow(secID, key, idx) {
+ var sec = SECTIONS.find(function(s) { return s.id === secID; });
+ if (!sec) return;
+ var p = cardPath(sec);
+ var arr = objectData[p] && objectData[p][key];
+ if (!arr) return;
+ arr.splice(idx, 1);
+ if (arr.length === 0) delete objectData[p][key];
+ renderCurrent();
+}
+
function fetchToolTypes() {
API.get('/api/tools').then(function(tools) {
toolTypes = tools || [];
@@ -688,6 +810,32 @@ 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;
+ renderCurrent();
+}
+
+function hideUseInterField(idx, key) {
+ window._useInterVis = window._useInterVis || {};
+ if (!window._useInterVis[idx]) window._useInterVis[idx] = {};
+ window._useInterVis[idx][key] = false;
+ if (objectData.use_interactions && objectData.use_interactions[idx]) {
+ objectData.use_interactions[idx][key] = '';
+ }
+ renderCurrent();
+}
+
+function toggleUseInterHelp() {
+ if (window._useInterHelpCollapsed === undefined) {
+ window._useInterHelpCollapsed = false;
+ } else {
+ window._useInterHelpCollapsed = !window._useInterHelpCollapsed;
+ }
+ renderCurrent();
+}
+
function toggleGatherConditionals() {
var skillEl = document.getElementById('f_gather_skill');
var skill = skillEl ? skillEl.value : '';
@@ -780,9 +928,9 @@ function saveObject() {
hasValues = true;
}
} else if (il.type === 'tools_checkbox') {
- var chks = document.querySelectorAll('.obj-tool-chk:checked');
+ var selects = document.querySelectorAll('.obj-tool-select');
var tools = [];
- chks.forEach(function(c) { tools.push(c.getAttribute('data-tool')); });
+ selects.forEach(function(s) { if (s.value) tools.push(s.value); });
if (tools.length > 0) { sectionObj[il.key] = tools; hasValues = true; }
} else if (il.type === 'kv') {
var kvEl = document.querySelector('.obj-kv-list[data-sec="' + sec.id + '"][data-key="' + il.key + '"]');