aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-13 18:36:29 -0400
committerhistoria <[not public]>2026-07-13 18:36:29 -0400
commit1bedf06969b8ab8e51dd4301a0831200df5c8f59 (patch)
treeacdcbcc3a26896c678edeffad0cb0d8ec9b2857b
parent8952653be9bfeeb9aadf59f845de507630d65dc4 (diff)
downloadthehouseoficarus-1bedf06969b8ab8e51dd4301a0831200df5c8f59.tar.gz
feat(admin): tooltips added for confusing fields, hide more optional fields with buttons
-rw-r--r--internal/admin/static/cardeditor.js15
-rw-r--r--internal/admin/static/itemeditor.js74
-rw-r--r--internal/admin/static/mobeditor.js44
-rw-r--r--internal/admin/static/objecteditor.js44
-rw-r--r--internal/admin/templates/items.html1
5 files changed, 115 insertions, 63 deletions
diff --git a/internal/admin/static/cardeditor.js b/internal/admin/static/cardeditor.js
index 6a8cda7..cf3a8b7 100644
--- a/internal/admin/static/cardeditor.js
+++ b/internal/admin/static/cardeditor.js
@@ -301,7 +301,7 @@ function ced_updateSearchHighlight(results) {
// ---- Generic field renderer (used by all card-based editors) ----
function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, fieldIDFn, getValFn) {
- var key = f[0], label = f[1], type = f[2], hint = f[3], wide = f[4], showIf = f[5], searchEntity = f[6];
+ var key = f[0], label = f[1], type = f[2], hint = f[3], wide = f[4], showIf = f[5], searchEntity = f[6], tooltip = f[7];
var fp = subPath ? subPath + '.' + key : key;
var fid = fieldIDFn(sec, fp, idx);
var val;
@@ -343,8 +343,13 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field
return '<label class="' + cls + w + '"' + hiddenStyle + s + '>' + inner + '</label>';
}
+ function lblSpan() {
+ if (tooltip) return '<span title="' + escAttr(tooltip) + '">' + esc(label) + '</span>';
+ return '<span>' + esc(label) + '</span>';
+ }
+
if (type === 'checkbox') {
- return wrap('obj-field obj-check', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + dpAttr + '> <span>' + esc(label) + '</span>');
+ return wrap('obj-field obj-check', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + dpAttr + '> ' + lblSpan());
}
if (type === 'json') {
var jstr = typeof val === 'object' ? JSON.stringify(val) : String(val);
@@ -356,7 +361,7 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field
}
if (type === 'select') {
if (hint && hint.indexOf('dyn:') === 0) {
- var inner = '<span>' + esc(label) + '</span>' + ced_buildDynamicSelect(fid, dpAttr, val, hint.substring(4));
+ var inner = lblSpan() + ced_buildDynamicSelect(fid, dpAttr, val, hint.substring(4));
return wrap('obj-field', inner);
}
var opts = hint ? hint.split('|') : [];
@@ -672,13 +677,11 @@ function ced_collectAtkType(widgetID) {
function ced_renderTagHelp(type) {
var h = '<div style="margin-top:8px;font-size:10px;color:#aaa;line-height:1.5">';
if (type === 'color') {
- h += 'Name is displayed without an article (e.g. "John Smith" instead of "a John Smith")<br>';
- h += 'Cannot be attacked by players. Combat level hidden<br>';
h += 'Text fields support color tags: <code style="color:#aaa;font-family:monospace">{NN}text{/}</code> where <code style="color:#aaa;font-family:monospace">NN</code> is a hex color index (00–FF).';
} else if (type === 'crafting') {
h += 'Message fields support template variables:<br>%i1 = first ingredient name<br>%i2 = second ingredient name<br>%n = output item name.<br>Color tags are also supported: {NN}text{/} where NN is a hex color index (00–FF).';
} else if (type === 'gather') {
- h += 'Message fields support template variables: {name} = object name.<br>Color tags are also supported: {NN}text{/} where NN is a hex color index (00–FF).';
+ h += 'Message fields support template variables: {name} = object name.<br/>Color tags are also supported: {NN}text{/} where NN is a hex color index (00–FF).';
}
h += '</div>';
return h;
diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js
index 04ad20b..2283c91 100644
--- a/internal/admin/static/itemeditor.js
+++ b/internal/admin/static/itemeditor.js
@@ -52,7 +52,7 @@ var SECTIONS = [
['level', 'Level', 'number', '', 'narrow'],
['xp', 'XP', 'number', '', 'narrow'],
['ticks_per_cycle', 'Ticks/Cycle', 'number', '', 'narrow'],
- ['output_qty', 'Output Qty', 'number', '', 'narrow'],
+ ['output_qty', 'Output Qty', 'number', '', 'narrow', null, null, 'Quantity produced per cycle, only relevant for stackable items.'],
['station', 'Station', 'select', 'dyn:stations'],
['tool', 'Tool', 'select', 'dyn:tools'],
['fail', 'Fail Item', 'search', 'burnt_fish', '', null, 'items'],
@@ -65,10 +65,9 @@ var SECTIONS = [
['end_message', 'End Message', 'text', 'You\'ve finished smithing.', 'wide'],
],
fieldGroups: [
- ['type', 'subtype', 'level', 'xp', 'ticks_per_cycle', 'output_qty'],
- {note: 'Output Qty is the quantity produced per cycle, only relevant for stackable items.'},
+ ['type', 'subtype', 'level', 'xp', 'ticks_per_cycle', 'output_qty'],
['station', 'tool'],
- {label: 'Success', fields: ['success_base', 'success_per_level', 'success_cap', 'fail']},
+ {label: 'Success', fields: ['success_base', 'success_per_level', 'success_cap']},
['start_message', 'success_message', 'fail_message', 'end_message'],
],
subSubtables: [
@@ -120,6 +119,7 @@ var SECTIONS = [
]
},
];
+var SKILL_KEYS = ['accuracy','strength','defense','hitpoints','ranged','science','technology','fishing','cooking','woodcutting','firemaking','mining','smithing','crafting','fletching','pharmacy','thieving','agility','construction','scavenging','hacking','assassin','farming'];
function initItemEditor() {
window._ieRenderCurrent = renderCurrent;
@@ -220,7 +220,7 @@ function renderItemEditor(id, data) {
setupCraftTypeChange();
setupEquipTypeChange();
updateOutputQtyVisibility();
- DropView.setupAllInter();
+ if (typeof DropView !== 'undefined') DropView.setupAllInter();
setTimeout(function() {
document.querySelectorAll('.color-field').forEach(function(el) { bindColorField(el); });
}, 50);
@@ -333,7 +333,16 @@ function renderCoreCard(data) {
h += renderField(sec, sec.fields[3], data, -1);
h += '</div>';
h += '<div style="display:flex;gap:8px;margin-left:auto">';
+ var qualFid = fieldID(sec, 'max_quality', -1);
+ var qualVal = data.max_quality;
+ var qgid = 'qg_core_quality';
+ var qShow = qualVal ? '' : 'display:none;';
+ var qBtnShow = qualVal ? 'display:none;' : '';
+ h += '<span id="' + qgid + '" style="display:flex;gap:4px;align-items:center;' + qShow + '">';
h += renderField(sec, sec.fields[4], data, -1);
+ h += '<button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;min-width:auto" onclick="var g=document.getElementById(\'' + qgid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + qgid + '_btn\');b.style.display=\'inline-block\';var inp=document.getElementById(\'' + qualFid + '\');if(inp)inp.value=\'\'">X</button>';
+ h += '</span>';
+ h += '<button class="btn btn-sm" id="' + qgid + '_btn" style="padding:2px 8px;font-size:11px;background:var(--accent);color:var(--text);border:1px solid #1a5a8e;border-radius:3px;cursor:pointer;' + qBtnShow + '" onclick="var b=document.getElementById(\'' + qgid + '_btn\');b.style.display=\'none\';var g=document.getElementById(\'' + qgid + '\');g.style.display=\'flex\'">+ Add Quality</button>';
h += '</div>';
h += '</div>';
var descFid = fieldID(sec, 'description', -1);
@@ -509,7 +518,7 @@ function renderEquipmentCard(data) {
// Requirements KV (at root level, stored in itemData.requirements)
h += '<div style="margin-top:8px">';
- h += renderKV({id: 'equipment_req'}, {label:'Requirements', key:'requirements', type:'kv', valType:'number'}, data);
+ h += renderKV({id: 'equipment_req'}, {label:'Requirements', key:'requirements', type:'kv', valType:'number'}, data, SKILL_KEYS);
h += '</div>';
return h;
@@ -763,6 +772,13 @@ function renderSubtable(sec, st, data) {
h += '<div class="obj-sub-row-line">';
renderGroup(group.fields);
h += '</div>';
+ var failF = st.fields.find(function(sf) { return sf[0] === 'fail'; });
+ if (failF) {
+ var failVal = item.fail || '';
+ var fgid = 'fg_' + sec.id + '_' + st.key + '_' + idx;
+ h += '<div id="' + fgid + '" style="margin-top:4px' + (failVal ? '' : ';display:none') + '"><div style="display:flex;align-items:center;gap:4px"><div style="flex:1">' + renderSubFieldHTML(sec, st.key, idx, failF, failVal) + '</div><button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;min-width:auto;align-self:flex-start;margin-top:18px" onclick="var g=document.getElementById(\'' + fgid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + fgid + '_btn\');b.style.display=\'\';var inp=g.querySelector(\'input\');if(inp)inp.value=\'\'">X</button></div></div>';
+ h += '<button class="btn btn-sm obj-sub-add" id="' + fgid + '_btn" style="margin-top:4px' + (failVal ? ';display:none' : '') + '" onclick="var b=document.getElementById(\'' + fgid + '_btn\');b.style.display=\'none\';var g=document.getElementById(\'' + fgid + '\');g.style.display=\'\'">+ Add Fail Item</button>';
+ }
h += '</div>';
h += '<button class="btn btn-sm obj-sub-add" id="' + gid + '_btn" style="margin:2px 0' + (hasSuccess ? ';display:none' : '') + '" onclick="var b=document.getElementById(\'' + gid + '_btn\');b.style.display=\'none\';var g=document.getElementById(\'' + gid + '\');g.style.display=\'\'">+ Add Success Rates</button>';
} else {
@@ -832,6 +848,7 @@ function addConsumePair(secID, subKey, rowIdx, subSubKey, subIdx) {
var btnRow = document.querySelector('.ingredients-pair-add[data-sec="' + secID + '"][data-subkey="' + subKey + '"][data-row="' + rowIdx + '"][data-subsub="' + subSubKey + '"][data-subidx="' + subIdx + '"]');
var itemFid = ingredientPairID(sec, subKey, rowIdx, subSubKey, subIdx, pairIdx, 'items');
var bypFid = ingredientPairID(sec, subKey, rowIdx, subSubKey, subIdx, pairIdx, 'byproducts');
+ var bpgid = 'bp_' + secID + '_' + subKey + '_' + rowIdx + '_' + subSubKey + '_' + subIdx + '_' + pairIdx;
var row = document.createElement('div');
row.className = 'ingredients-pair';
row.setAttribute('data-sec', secID);
@@ -843,7 +860,7 @@ function addConsumePair(secID, subKey, rowIdx, subSubKey, subIdx) {
row.innerHTML = '<div class="ingredients-col ingredients-col-num"></div>'
+ '<div class="ingredients-col ingredients-col-qty"></div>'
+ '<div class="ingredients-col ingredients-col-items">' + renderConsumeSearchHTML(itemFid, '', 'items', 'Search items...') + '</div>'
- + '<div class="ingredients-col ingredients-col-byproducts">' + renderConsumeSearchHTML(bypFid, '', 'items', '(optional)') + '</div>'
+ + '<div class="ingredients-col ingredients-col-byproducts"><div id="' + bpgid + '" style="display:none;align-items:center;gap:2px">' + renderConsumeSearchHTML(bypFid, '', 'items', '(optional)') + '<button class="btn btn-sm btn-danger" style="padding:2px 4px;font-size:10px;min-width:auto;flex-shrink:0" onclick="var g=document.getElementById(\'' + bpgid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + bpgid + '_btn\');b.style.display=\'block\';var inp=g.querySelector(\'input\');if(inp)inp.value=\'\'">Remove</button></div><button class="btn btn-sm obj-sub-add" id="' + bpgid + '_btn" style="margin-top:0;display:block" onclick="var b=document.getElementById(\'' + bpgid + '_btn\');b.style.display=\'none\';var g=document.getElementById(\'' + bpgid + '\');g.style.display=\'flex\'">+ Add Byproduct</button></div>'
+ '<div class="ingredients-col ingredients-col-x"><button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;background:var(--danger);color:#fff;border:none;border-radius:3px;cursor:pointer" onclick="removeConsumePair(\'' + secID + '\',\'' + subKey + '\',' + rowIdx + ',\'' + subSubKey + '\',' + subIdx + ',' + pairIdx + ')">X</button></div>';
btnRow.parentNode.insertBefore(row, btnRow);
setupSearchFields();
@@ -914,7 +931,12 @@ function renderSubSubtable(sec, st, rowIdx, sst, parentItem) {
var bypVal = bypArr[p] || '';
var bypFid = ingredientPairID(sec, st.key, rowIdx, sst.key, subIdx, p, 'byproducts');
- h += '<div class="ingredients-col ingredients-col-byproducts">' + renderConsumeSearchHTML(bypFid, bypVal, 'items', '(optional)') + '</div>';
+ var bpgid = 'bp_' + sec.id + '_' + st.key + '_' + rowIdx + '_' + sst.key + '_' + subIdx + '_' + p;
+ if (bypVal) {
+ h += '<div class="ingredients-col ingredients-col-byproducts"><div id="' + bpgid + '" style="display:flex;align-items:center;gap:2px">' + renderConsumeSearchHTML(bypFid, bypVal, 'items', '(optional)') + '<button class="btn btn-sm btn-danger" style="padding:2px 4px;font-size:10px;min-width:auto;flex-shrink:0" onclick="var g=document.getElementById(\'' + bpgid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + bpgid + '_btn\');b.style.display=\'block\';var inp=g.querySelector(\'input\');if(inp)inp.value=\'\'">Remove</button></div><button class="btn btn-sm obj-sub-add" id="' + bpgid + '_btn" style="margin-top:0;display:none" onclick="var b=document.getElementById(\'' + bpgid + '_btn\');b.style.display=\'none\';var g=document.getElementById(\'' + bpgid + '\');g.style.display=\'flex\'">+ Add Byproduct</button></div>';
+ } else {
+ h += '<div class="ingredients-col ingredients-col-byproducts"><div id="' + bpgid + '" style="display:none;align-items:center;gap:2px">' + renderConsumeSearchHTML(bypFid, '', 'items', '(optional)') + '<button class="btn btn-sm btn-danger" style="padding:2px 4px;font-size:10px;min-width:auto;flex-shrink:0" onclick="var g=document.getElementById(\'' + bpgid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + bpgid + '_btn\');b.style.display=\'block\';var inp=g.querySelector(\'input\');if(inp)inp.value=\'\'">Remove</button></div><button class="btn btn-sm obj-sub-add" id="' + bpgid + '_btn" style="margin-top:0;display:block" onclick="var b=document.getElementById(\'' + bpgid + '_btn\');b.style.display=\'none\';var g=document.getElementById(\'' + bpgid + '\');g.style.display=\'flex\'">+ Add Byproduct</button></div>';
+ }
h += '<div class="ingredients-col ingredients-col-x"><button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;background:var(--danger);color:#fff;border:none;border-radius:3px;cursor:pointer" onclick="removeConsumePair(\'' + sec.id + '\',\'' + st.key + '\',' + rowIdx + ',\'' + sst.key + '\',' + subIdx + ',' + p + ')">X</button></div>';
@@ -974,15 +996,28 @@ function renderTags(sec, il, data) {
return '<label class="obj-field obj-wide"><span>' + esc(il.label) + '</span><input id="' + fid + '" value="' + escAttr(str) + '" placeholder="comma separated"></label>';
}
-function renderKV(sec, il, data) {
+function renderKV(sec, il, data, keyOptions) {
var p = cardPath(sec);
var sectionRoot = p ? (itemData[p] || {}) : itemData;
var map = sectionRoot[il.key] || {};
+ var hasKeyOpts = Array.isArray(keyOptions) && keyOptions.length > 0;
var h = '<div class="obj-inline-group">';
h += '<span class="obj-inline-label">' + esc(il.label) + '</span>';
- h += '<div class="obj-kv-list" data-sec="' + sec.id + '" data-key="' + il.key + '">';
+ h += '<div class="obj-kv-list" data-sec="' + sec.id + '" data-key="' + il.key + '"' + (hasKeyOpts ? ' data-key-options="' + escAttr(keyOptions.join(',')) + '"' : '') + '>';
Object.keys(map).forEach(function(k, idx) {
- h += '<div class="obj-kv-row"><input value="' + escAttr(k) + '" placeholder="key" class="obj-kv-key">';
+ h += '<div class="obj-kv-row">';
+ if (hasKeyOpts) {
+ var keyOpts = keyOptions.slice();
+ if (k && keyOptions.indexOf(k) === -1) keyOpts.unshift(k);
+ h += '<select class="obj-kv-key">';
+ h += '<option value="">(none)</option>';
+ keyOpts.forEach(function(o) {
+ h += '<option value="' + escAttr(o) + '"' + (k === o ? ' selected' : '') + '>' + esc(o) + '</option>';
+ });
+ h += '</select>';
+ } else {
+ h += '<input value="' + escAttr(k) + '" placeholder="key" class="obj-kv-key">';
+ }
h += '<input value="' + escAttr(String(map[k])) + '" placeholder="val" class="obj-kv-val">';
h += '<button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">X</button></div>';
});
@@ -1192,7 +1227,19 @@ function addKVRow(cardID, subKey) {
if (!el) return;
var row = document.createElement('div');
row.className = 'obj-kv-row';
- row.innerHTML = '<input value="" placeholder="key" class="obj-kv-key"><input value="0" placeholder="val" class="obj-kv-val"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">X</button>';
+ var keyOptsStr = el.getAttribute('data-key-options');
+ if (keyOptsStr) {
+ var keyOpts = keyOptsStr.split(',');
+ var selHTML = '<select class="obj-kv-key">';
+ selHTML += '<option value="">(none)</option>';
+ keyOpts.forEach(function(o) {
+ selHTML += '<option value="' + escAttr(o) + '">' + esc(o) + '</option>';
+ });
+ selHTML += '</select>';
+ row.innerHTML = selHTML + '<input value="0" placeholder="val" class="obj-kv-val"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">X</button>';
+ } else {
+ row.innerHTML = '<input value="" placeholder="key" class="obj-kv-key"><input value="0" placeholder="val" class="obj-kv-val"><button class="btn btn-sm btn-danger" onclick="this.parentElement.remove()">X</button>';
+ }
el.appendChild(row);
}
@@ -1292,9 +1339,6 @@ function updateOutputQtyVisibility() {
var lbl = el.closest('.obj-field');
if (lbl) lbl.style.display = isStackable ? '' : 'none';
});
- document.querySelectorAll('.output-qty-note').forEach(function(el) {
- el.style.display = isStackable ? '' : 'none';
- });
}
function setupSearchFields() { ced_setupSearchFields(); }
diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js
index 5e2b9cc..05d33da 100644
--- a/internal/admin/static/mobeditor.js
+++ b/internal/admin/static/mobeditor.js
@@ -11,8 +11,8 @@ var MOB_SECTIONS = [
fields: [
['name', 'Name', 'text', 'spaceport attendant'],
['description', 'Description', 'textarea', 'A lanky man in a wrinkled grey uniform.'],
- ['unique', 'Unique', 'checkbox'],
- ['protected', 'Protected', 'checkbox'],
+ ['unique', 'Unique', 'checkbox', '', '', null, null, 'Mob name is displayed without an article (e.g. "John Smith" instead of "a John Smith")'],
+ ['protected', 'Protected', 'checkbox', '', '', null, null, 'Cannot be attacked.'],
],
inline: [
{label:'Idle Descriptions', key:'idle_descriptions', type:'tags'},
@@ -53,7 +53,6 @@ var MOB_SECTIONS = [
['size', 'Size', 'text', 'small'],
],
inline: [
- {label:'Combat Descriptions', key:'combat_descriptions', type:'tags'},
]
},
{
@@ -437,9 +436,23 @@ function renderMobCombatCard(data) {
h += renderMobField(sec, byKey['damage_without'], data, -1);
h += '</div>';
- sec.inline.forEach(function(il) {
- h += ced_renderTags(sec, il, data);
+ var combat = data.combat || {};
+ var combatDescs = Array.isArray(combat.combat_descriptions) ? combat.combat_descriptions : [];
+ var cdgid = 'cdg_combat';
+ var hasCD = combatDescs.length > 0;
+ h += '<div class="obj-inline-group" id="' + cdgid + '"' + (hasCD ? '' : ' style="display:none"') + '>';
+ h += '<div style="display:flex;align-items:center;margin-bottom:4px">';
+ h += '<span class="obj-inline-label" style="margin-bottom:0;flex:1">Combat Descriptions</span>';
+ h += '<button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;min-width:auto" onclick="var g=document.getElementById(\'' + cdgid + '\');g.style.display=\'none\';var b=document.getElementById(\'' + cdgid + '_btn\');b.style.display=\'block\';var list=g.querySelector(\'.obj-combat-desc-list\');while(list.firstChild)list.firstChild.remove()">X</button>';
+ h += '</div>';
+ h += '<div class="obj-combat-desc-list">';
+ combatDescs.forEach(function(desc) {
+ h += '<div class="obj-combat-desc-row" style="display:flex;align-items:center;gap:4px;margin-bottom:4px"><input class="obj-combat-desc" value="' + escAttr(desc) + '" placeholder="e.g. slashes at %s with a blade" style="flex:1"><button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;min-width:auto" onclick="this.parentElement.remove()">X</button></div>';
});
+ h += '</div>';
+ h += '<button class="btn btn-sm obj-sub-add" onclick="addCombatDescRow(\'' + cdgid + '\')">+ Add</button>';
+ h += '</div>';
+ h += '<button class="btn btn-sm obj-sub-add" id="' + cdgid + '_btn"' + (hasCD ? ' style="display:none"' : '') + ' onclick="var b=document.getElementById(\'' + cdgid + '_btn\');b.style.display=\'none\';var g=document.getElementById(\'' + cdgid + '\');g.style.display=\'block\';addCombatDescRow(\'' + cdgid + '\')">+ Add Combat Descriptions</button>';
return h;
}
@@ -834,6 +847,16 @@ function addMobLootRow(cardID, subKey, kind) {
renderCurrent();
}
+function addCombatDescRow(cdgid) {
+ var list = document.querySelector('#' + cdgid + ' .obj-combat-desc-list');
+ if (!list) return;
+ var row = document.createElement('div');
+ row.className = 'obj-combat-desc-row';
+ row.style.cssText = 'display:flex;align-items:center;gap:4px;margin-bottom:4px';
+ row.innerHTML = '<input class="obj-combat-desc" value="" placeholder="e.g. slashes at %s with a blade" style="flex:1"><button class="btn btn-sm btn-danger" style="padding:2px 6px;font-size:10px;min-width:auto" onclick="this.parentElement.remove()">X</button>';
+ list.appendChild(row);
+}
+
// ---- Save ----
function saveMob() {
@@ -901,6 +924,17 @@ function saveMob() {
'stats.bonuses.science_percent_bonus','stats.bonuses.ranged_bonus','stats.bonuses.ranged_strength_bonus'];
taskStrip.forEach(function(k) { delete sectionObj[k]; });
}
+ var descRows = document.querySelectorAll('.obj-combat-desc');
+ if (descRows.length > 0) {
+ var descs = [];
+ descRows.forEach(function(inp) {
+ var v = inp.value.trim();
+ if (v) descs.push(v);
+ });
+ if (descs.length > 0) {
+ sectionObj['combat_descriptions'] = descs;
+ }
+ }
}
if (sec.subtables) {
diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js
index 6ae1dba..363cca9 100644
--- a/internal/admin/static/objecteditor.js
+++ b/internal/admin/static/objecteditor.js
@@ -70,14 +70,14 @@ var SECTIONS = [
id: 'safespot', label: 'Safespot', path: 'safespot',
detect: function(d) { return d.safespot; },
fields: [
- ['tier', 'Tier', 'number', '1', 'narrow'],
- ['max_block_size', 'Max Block Size', 'select', '|small|medium|large|massive'],
+ ['tier', 'Tier', 'number', '1', 'narrow', null, null, 'Player tier must be >= safespot tier to hide (+1 with unlocked_rock_cover flag)'],
+ ['max_block_size', 'Max Block Size', 'select', '|small|medium|large|massive', null, null, null, 'Blocks mobs smaller than this size. Empty = blocks everything, massive blocks all four sizes.'],
['max_occupants', 'Max Occupants', 'number', '1', 'narrow'],
- ['unsafe_chance', 'Unsafe Chance', 'number', '0.05', 'narrow'],
- ['decay_ticks', 'Decay Ticks', 'number', '100', 'narrow'],
- ['decay_chance', 'Decay Chance', 'number', '0.01', 'narrow'],
- ['respawn_ticks', 'Respawn Ticks', 'number', '50', 'narrow'],
- ['respawn_on_hide', 'Respawn on hide', 'checkbox'],
+ ['unsafe_chance', 'Unsafe Chance', 'number', '0.05', 'narrow', null, null, 'Per-tick probability of being forced out of cover (0 = never)'],
+ ['decay_ticks', 'Decay Ticks', 'number', '100', 'narrow', null, null, 'Forced degrade after N ticks of use. 0 disables this decay path'],
+ ['decay_chance', 'Decay Chance', 'number', '0.01', 'narrow', null, null, 'Per-tick random degrade chance. 0 disables this decay path'],
+ ['respawn_ticks', 'Respawn Ticks', 'number', '50', 'narrow', null, null, 'Auto-reforms cover after N ticks when fully destroyed. 0 = permanently removed'],
+ ['respawn_on_hide', 'Respawn on hide', 'checkbox', '', '', null, null, 'Resets cover to full levels when first person hides on an empty/unused cover'],
],
subtables: [
{label:'Levels of Decay (lower is more decayed)', key:'levels', vertical: true, fields:[
@@ -321,27 +321,6 @@ function renderCard(sec, data) {
});
}
- if (sec.id === 'safespot') {
- var helpCollapsed = window._safespotHelpCollapsed === undefined ? true : window._safespotHelpCollapsed;
- 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="toggleSafespotHelp()">';
- h += '<span style="font-size:8px;margin-right:4px">' + (helpCollapsed ? '&#9654;' : '&#9660;') + '</span>';
- h += 'How Tiers, Levels, Decay &amp; Respawn 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 += '<b>Tier</b> &mdash; Player tier (base 1; +1 with <code>unlocked_rock_cover</code> flag) must be &ge; safespot tier to hide.<br>';
- h += '<b>Levels of Decay</b> &mdash; Degradation stages (N levels above all subtable entries). Level messages shown when hiding; degrade messages broadcast when damaged. Level 0 = destroyed, all occupants evicted.<br>';
- h += '<b>Decay</b> &mdash; Two independent paths: <code>decay_ticks</code> (forced degrade after N ticks of use) and <code>decay_chance</code> (per-tick random degrade). Either or both can be set; 0 disables that path.<br>';
- h += '<b>Respawn on hide</b> &mdash; Resets the cover to full levels when the first person hides on an empty/unused cover.<br>';
- h += '<b>Respawn ticks</b> &mdash; Auto-reforms the cover after N ticks when fully destroyed (level 0). 0 = instance permanently removed.<br>';
- h += '<b>Max Block Size</b> &mdash; Determines which mob sizes are blocked (small &lt; medium &lt; large &lt; massive). Empty = blocks everything.<br>';
- h += '<b>Unsafe Chance</b> &mdash; Per-tick probability of being forced out of cover regardless of decay (0 = never forced out).';
- h += '</div>';
- }
- h += '</div>';
- }
-
if (sec.id === 'gather') {
h += ced_renderTagHelp('gather');
}
@@ -776,15 +755,6 @@ function hideBaitField() {
renderObjectEditor(objectID, objectData);
}
-function toggleSafespotHelp() {
- if (window._safespotHelpCollapsed === undefined) {
- window._safespotHelpCollapsed = false;
- } else {
- window._safespotHelpCollapsed = !window._safespotHelpCollapsed;
- }
- renderCurrent();
-}
-
function removeSection(id) {
var sec = SECTIONS.find(function(s) { return s.id === id; });
if (!sec) return;
diff --git a/internal/admin/templates/items.html b/internal/admin/templates/items.html
index f1c8ee5..d81fb1d 100644
--- a/internal/admin/templates/items.html
+++ b/internal/admin/templates/items.html
@@ -12,6 +12,7 @@
<script src="/static/editor.js"></script>
<script src="/static/cardeditor.js"></script>
<script src="/static/intereditor.js"></script>
+<script src="/static/dropview.js"></script>
<script src="/static/itemeditor.js"></script>
<script>
initItemEditor();