From 1bedf06969b8ab8e51dd4301a0831200df5c8f59 Mon Sep 17 00:00:00 2001
From: historia <[not public]>
Date: Mon, 13 Jul 2026 18:36:29 -0400
Subject: feat(admin): tooltips added for confusing fields, hide more optional
fields with buttons
---
internal/admin/static/cardeditor.js | 15 ++++---
internal/admin/static/itemeditor.js | 74 ++++++++++++++++++++++++++++-------
internal/admin/static/mobeditor.js | 44 ++++++++++++++++++---
internal/admin/static/objecteditor.js | 44 ++++-----------------
4 files changed, 114 insertions(+), 63 deletions(-)
(limited to 'internal/admin/static')
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 '';
}
+ function lblSpan() {
+ if (tooltip) return '' + esc(label) + '';
+ return '' + esc(label) + '';
+ }
+
if (type === 'checkbox') {
- return wrap('obj-field obj-check', ' ' + esc(label) + '');
+ return wrap('obj-field obj-check', ' ' + 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 = '' + esc(label) + '' + 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 = '
';
if (type === 'color') {
- h += 'Name is displayed without an article (e.g. "John Smith" instead of "a John Smith") ';
- h += 'Cannot be attacked by players. Combat level hidden ';
h += 'Text fields support color tags: {NN}text{/} where NN is a hex color index (00–FF).';
} else if (type === 'crafting') {
h += 'Message fields support template variables: %i1 = first ingredient name %i2 = second ingredient name %n = output item name. 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. 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. Color tags are also supported: {NN}text{/} where NN is a hex color index (00–FF).';
}
h += '
';
+ }
h += '';
@@ -974,15 +996,28 @@ function renderTags(sec, il, data) {
return '';
}
-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 = '
';
h += '' + esc(il.label) + '';
- h += '
';
+ h += '
';
Object.keys(map).forEach(function(k, idx) {
- h += '
';
+ h += '
';
+ if (hasKeyOpts) {
+ var keyOpts = keyOptions.slice();
+ if (k && keyOptions.indexOf(k) === -1) keyOpts.unshift(k);
+ h += '';
+ } else {
+ h += '';
+ }
h += '';
h += '
';
});
@@ -1192,7 +1227,19 @@ function addKVRow(cardID, subKey) {
if (!el) return;
var row = document.createElement('div');
row.className = 'obj-kv-row';
- row.innerHTML = '';
+ var keyOptsStr = el.getAttribute('data-key-options');
+ if (keyOptsStr) {
+ var keyOpts = keyOptsStr.split(',');
+ var selHTML = '';
+ row.innerHTML = selHTML + '';
+ } else {
+ row.innerHTML = '';
+ }
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 += '
';
- 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 += '
';
+ h += '
';
+ h += 'Combat Descriptions';
+ h += '';
+ h += '
';
+ h += '
';
+ combatDescs.forEach(function(desc) {
+ h += '';
});
+ h += '
';
+ h += '';
+ h += '
';
+ h += '';
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 = '';
+ 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 += '
';
- h += '
';
- h += '' + (helpCollapsed ? '▶' : '▼') + '';
- h += 'How Tiers, Levels, Decay & Respawn Work';
- h += '
';
- if (!helpCollapsed) {
- h += '
';
- h += 'Tier — Player tier (base 1; +1 with unlocked_rock_cover flag) must be ≥ safespot tier to hide. ';
- h += 'Levels of Decay — Degradation stages (N levels above all subtable entries). Level messages shown when hiding; degrade messages broadcast when damaged. Level 0 = destroyed, all occupants evicted. ';
- h += 'Decay — Two independent paths: decay_ticks (forced degrade after N ticks of use) and decay_chance (per-tick random degrade). Either or both can be set; 0 disables that path. ';
- h += 'Respawn on hide — Resets the cover to full levels when the first person hides on an empty/unused cover. ';
- h += 'Respawn ticks — Auto-reforms the cover after N ticks when fully destroyed (level 0). 0 = instance permanently removed. ';
- h += 'Max Block Size — Determines which mob sizes are blocked (small < medium < large < massive). Empty = blocks everything. ';
- h += 'Unsafe Chance — Per-tick probability of being forced out of cover regardless of decay (0 = never forced out).';
- h += '
';
- }
- h += '
';
- }
-
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;
--
cgit v1.2.3