From 3274b700a6f655b8ecc3505e814ac5d782ae6ffe Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Mon, 13 Jul 2026 16:10:40 -0400 Subject: feat(admin): reminder text for color tags and item variables, improve placeholder text --- data/objects/000_test_object.yaml | 1 + internal/admin/static/cardeditor.js | 37 +++++++++++++++++++++++++++++------ internal/admin/static/dropeditor.js | 4 ++-- internal/admin/static/hazardeditor.js | 5 ++++- internal/admin/static/itemeditor.js | 23 +++++++++++++--------- internal/admin/static/map.js | 6 +++--- internal/admin/static/mobeditor.js | 11 ++++++++--- internal/admin/static/moduleeditor.js | 2 +- internal/admin/static/objecteditor.js | 33 +++++++++++++++++++------------ internal/admin/static/talktree.js | 2 +- 10 files changed, 85 insertions(+), 39 deletions(-) create mode 100644 data/objects/000_test_object.yaml diff --git a/data/objects/000_test_object.yaml b/data/objects/000_test_object.yaml new file mode 100644 index 0000000..ee03b6d --- /dev/null +++ b/data/objects/000_test_object.yaml @@ -0,0 +1 @@ +id: 000_test_object diff --git a/internal/admin/static/cardeditor.js b/internal/admin/static/cardeditor.js index f32ea7e..6a8cda7 100644 --- a/internal/admin/static/cardeditor.js +++ b/internal/admin/static/cardeditor.js @@ -42,7 +42,7 @@ function ced_collectFieldValue(el, type) { if (type === 'checkbox') return el.checked; if (type === 'select') return el.value; if (type === 'search') return el.value; - if (type === 'number') { var n = parseFloat(el.value); return isNaN(n) ? 0 : n; } + if (type === 'number') { var n = parseFloat(el.value); return isNaN(n) ? '' : n; } if (type === 'json') { var raw = el.value.trim(); if (!raw) return null; @@ -86,7 +86,7 @@ function ced_addKVRow(cardID, subKey) { if (!el) return; var row = document.createElement('div'); row.className = 'obj-kv-row'; - row.innerHTML = ''; + row.innerHTML = ''; el.appendChild(row); } @@ -125,7 +125,7 @@ function ced_addKVSearchRow(cardID, subKey, entity) { if (!el) return; var row = document.createElement('div'); row.className = 'obj-kv-row'; - row.innerHTML = ''; + row.innerHTML = ''; el.appendChild(row); ced_setupSearchFields(row); } @@ -398,7 +398,7 @@ function ced_addSubRow(sec, subKey, dataRef, cardPathFn) { var st = (sec.subtables || []).find(function(s) { return s.key === subKey; }); if (st) { st.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); } arr.push(empty); @@ -579,8 +579,18 @@ function ced_cleanOutput(obj) { } else if (typeof val === 'object' && val !== null && !Array.isArray(val)) { ced_cleanOutput(val); if (Object.keys(val).length === 0) delete obj[key]; - } else if (Array.isArray(val) && val.length === 0) { - delete obj[key]; + } else if (Array.isArray(val)) { + if (val.length === 0) { + delete obj[key]; + } else { + for (var i = val.length - 1; i >= 0; i--) { + if (typeof val[i] === 'object' && val[i] !== null) { + ced_cleanOutput(val[i]); + if (Object.keys(val[i]).length === 0) val.splice(i, 1); + } + } + if (val.length === 0) delete obj[key]; + } } } } @@ -658,3 +668,18 @@ function ced_collectAtkType(widgetID) { if (sci && sci.checked) out.push('science'); return out; } + +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 += '
'; + return h; +} diff --git a/internal/admin/static/dropeditor.js b/internal/admin/static/dropeditor.js index ac1df97..5900930 100644 --- a/internal/admin/static/dropeditor.js +++ b/internal/admin/static/dropeditor.js @@ -7,8 +7,8 @@ var DROP_SECTIONS = [ { id: 'drops', label: 'Drops', always: true, path: 'drops', isArray: true, fields: [ - ['item_id', 'Item ID', 'search', 'e.g. copper_ore', '', null, 'items'], - ['table', 'Table', 'search', 'e.g. gem_table', '', null, 'drops'], + ['item_id', 'Item ID', 'search', 'copper_ore', '', null, 'items'], + ['table', 'Table', 'search', 'gem_table', '', null, 'drops'], ['weight', 'Weight', 'number', '10', 'narrow'], ['quantity', 'Quantity', 'number', '1', 'narrow'], ] diff --git a/internal/admin/static/hazardeditor.js b/internal/admin/static/hazardeditor.js index 89586ac..1f5fdf3 100644 --- a/internal/admin/static/hazardeditor.js +++ b/internal/admin/static/hazardeditor.js @@ -13,7 +13,7 @@ var HAZARD_SECTIONS = [ ['warn_message', 'Warn Message', 'text', 'The air grows thick with toxic fumes...', 'wide'], ['hit_message', 'Hit Message', 'text', 'You cough and choke on the toxic gas!'], ['miss_message', 'Miss Message', 'text', 'You hold your breath and avoid the fumes.'], - ['required_item', 'Required Item', 'search', 'e.g. gas_mask', '', null, 'items'], + ['required_item', 'Required Item', 'search', 'gas_mask', '', null, 'items'], ] }, { @@ -146,6 +146,9 @@ function renderCard(sec, data) { h += renderField(sec, f, data, -1); }); h += ''; + if (sec.id === 'core') { + h += ced_renderTagHelp('color'); + } } h += ''; } diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js index d48d70e..6375c16 100644 --- a/internal/admin/static/itemeditor.js +++ b/internal/admin/static/itemeditor.js @@ -55,14 +55,14 @@ var SECTIONS = [ ['output_qty', 'Output Qty', 'number', '', 'narrow'], ['station', 'Station', 'select', 'dyn:stations'], ['tool', 'Tool', 'select', 'dyn:tools'], - ['fail', 'Fail Item', 'search', 'e.g. burnt_fish', '', null, 'items'], + ['fail', 'Fail Item', 'search', 'burnt_fish', '', null, 'items'], ['success_base', 'Base', 'number', '0.5', 'narrow'], ['success_per_level', 'Per Lvl', 'number', '0.01', 'narrow'], ['success_cap', 'Cap', 'number', '0.95', 'narrow'], - ['start_message', 'Start Message', 'text', 'e.g. You begin smithing %i1.', 'wide'], - ['success_message', 'Success Message', 'text', 'e.g. You smith a %n.', 'wide'], - ['fail_message', 'Fail Message', 'text', 'e.g. You accidentally burn the %i1.', 'wide', function(item) { return item.type === 'cooking' || item.subtype === 'smelt'; }], - ['end_message', 'End Message', 'text', 'e.g. You\'ve finished smithing.', 'wide'], + ['start_message', 'Start Message', 'text', 'You begin smithing %i1.', 'wide'], + ['success_message', 'Success Message', 'text', 'You smith a %n.', 'wide'], + ['fail_message', 'Fail Message', 'text', 'You accidentally burn the %i1.', 'wide', function(item) { return item.type === 'cooking' || item.subtype === 'smelt'; }], + ['end_message', 'End Message', 'text', 'You\'ve finished smithing.', 'wide'], ], fieldGroups: [ ['type', 'subtype', 'level', 'xp', 'ticks_per_cycle', 'output_qty'], @@ -101,7 +101,7 @@ var SECTIONS = [ detect: function(d) { return d.farm_patch_type; }, fields: [ ['farm_patch_type', 'Patch Type', 'select', 'herb|allotment|flower|bush|tree'], - ['farm_product', 'Product', 'search', 'e.g. tomato', '', null, 'items'], + ['farm_product', 'Product', 'search', 'tomato', '', null, 'items'], ['farm_plant_xp', 'Plant XP', 'number', '5', 'narrow'], ['farm_harvest_xp', 'Harvest XP', 'number', '10', 'narrow'], ['farm_level', 'Farm Level', 'number', '1', 'narrow'], @@ -307,6 +307,10 @@ function renderCard(sec, data) { h += renderSubtable(sec, st, data); }); } + + if (sec.id === 'crafting') { + h += ced_renderTagHelp('crafting'); + } } h += ''; } @@ -334,6 +338,7 @@ function renderCoreCard(data) { var descVal = getVal(itemData, sec, 'description'); var dstr = typeof descVal === 'object' ? (Array.isArray(descVal) ? descVal.map(function(v){return v.text||'';}).join('\n') : JSON.stringify(descVal)) : String(descVal); h += ''; + h += ced_renderTagHelp('color'); return h; } @@ -1009,7 +1014,7 @@ function addArrayItem(cardID) { } else { var empty = {}; sec.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); arr.push(empty); } @@ -1061,7 +1066,7 @@ function addSubRow(cardID, subKey) { var st = (sec.subtables || []).find(function(s) { return s.key === subKey; }); if (st) { st.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); } arr.push(empty); @@ -1110,7 +1115,7 @@ function addSubSubRow(cardID, subKey, rowIdx, subSubKey) { var sst = st.subSubtables.find(function(ss) { return ss.key === subSubKey; }); if (sst) { sst.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); } } diff --git a/internal/admin/static/map.js b/internal/admin/static/map.js index 6d56097..390043e 100644 --- a/internal/admin/static/map.js +++ b/internal/admin/static/map.js @@ -1260,8 +1260,8 @@ function courseShowNew() { menu.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);min-width:520px;max-width:90vw;max-height:85vh;overflow-y:auto;overflow-x:hidden;padding:14px;z-index:201'; var h = '

New Course (room #' + roomID + ' = step 1)

'; - h += '
'; - h += '
'; + h += '
'; + h += '
'; h += '
'; h += '
'; h += '
'; @@ -3382,7 +3382,7 @@ function renderTriggerListHTML(arr, secId, secPath, allowItem) { interactionStyle: true, itemIDAllowed: allowItem, label: 'Trigger', - fields: [['item_id', 'Item ID', 'search', 'e.g. keycard', '', null, 'items']] + fields: [['item_id', 'Item ID', 'search', 'keycard', '', null, 'items']] }; var ctx = { sec: sec, diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js index 10b256e..4b136a3 100644 --- a/internal/admin/static/mobeditor.js +++ b/internal/admin/static/mobeditor.js @@ -48,8 +48,8 @@ var MOB_SECTIONS = [ ['stats.defenses.weakness', 'Weakness', 'select', '(none)|bio|eco|hydro|solar|chaos'], ['stats.defenses.weakness_percent', 'Weakness %', 'number', '50', 'narrow'], ['assassin_level', 'Assassin Lvl', 'number', '1', 'narrow'], - ['finishing_blow', 'Finishing Blow', 'search', 'e.g. stake', '', null, 'items'], - ['damage_without', 'Damage Without', 'search', 'e.g. holy_symbol', '', null, 'items'], + ['finishing_blow', 'Finishing Blow', 'search', 'stake', '', null, 'items'], + ['damage_without', 'Damage Without', 'search', 'holy_symbol', '', null, 'items'], ['size', 'Size', 'text', 'small'], ], inline: [ @@ -301,9 +301,14 @@ function renderMobCoreCard(data) { h += ced_renderTags(sec, il, data); }); h += '
'; + h += '
'; h += renderMobField(sec, byKey['unique'], data, -1); + h += '
'; + h += '
'; h += renderMobField(sec, byKey['protected'], data, -1); h += '
'; + h += '
'; + h += ced_renderTagHelp('color'); return h; } @@ -507,7 +512,7 @@ function mobAddArrayItem(cardID) { var arr = mobData[sec.path] || []; var empty = {}; sec.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); arr.push(empty); mobData[sec.path] = arr; diff --git a/internal/admin/static/moduleeditor.js b/internal/admin/static/moduleeditor.js index fe71f88..b2eb79f 100644 --- a/internal/admin/static/moduleeditor.js +++ b/internal/admin/static/moduleeditor.js @@ -221,7 +221,7 @@ function addArrayItem(cardID) { var arr = moduleData[sec.path] || []; var empty = {}; sec.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); arr.push(empty); moduleData[sec.path] = arr; diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js index 88bc5b7..b4d8282 100644 --- a/internal/admin/static/objecteditor.js +++ b/internal/admin/static/objecteditor.js @@ -14,7 +14,7 @@ var SECTIONS = [ ['hidden', 'Hidden', 'checkbox'], ['inroom_description', 'In-Room Description', 'text', 'A copper rock juts from the ground.'], ['description', 'Description', 'textarea', 'A vein of copper runs through this rock.'], - ['removal_item', 'Removal Item ID', 'search', 'e.g. ashes', '', null, 'items'], + ['removal_item', 'Removal Item ID', 'search', 'ashes', '', null, 'items'], ] }, { @@ -73,10 +73,10 @@ var SECTIONS = [ ['tier', 'Tier', 'number', '1', 'narrow'], ['max_block_size', 'Max Block Size', 'select', '|small|medium|large|massive'], ['max_occupants', 'Max Occupants', 'number', '1', 'narrow'], - ['unsafe_chance', 'Unsafe Chance', 'number', '0 = never, e.g. 0.05', 'narrow'], - ['decay_ticks', 'Decay Ticks', 'number', '0 = never, e.g. 100', 'narrow'], - ['decay_chance', 'Decay Chance', 'number', '0 = never, e.g. 0.01', 'narrow'], - ['respawn_ticks', 'Respawn Ticks', 'number', '0 = no respawn, e.g. 50', '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'], ], subtables: [ @@ -89,7 +89,7 @@ var SECTIONS = [ id: 'on_use', label: '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 (empty = bare use)', '', null, 'items'], + ['item_id', 'Item ID', 'search', 'keycard', '', null, 'items'], ] }, { @@ -340,6 +340,10 @@ function renderCard(sec, data) { } h += ''; } + + if (sec.id === 'gather') { + h += ced_renderTagHelp('gather'); + } } h += ''; } @@ -447,13 +451,15 @@ function renderCoreCard(data) { } if (showInRoom) { h += '
'; - h += '
' + renderField(sec, sec.fields[4], data, -1) + '
'; + h += '
' + renderField(sec, sec.fields[4], data, -1); + h += '
Alternative way this object can appear with the \'look\' command
'; h += ''; h += '
'; } if (showRemoval) { h += '
'; - h += '
' + renderField(sec, sec.fields[6], data, -1) + '
'; + h += '
' + renderField(sec, sec.fields[6], data, -1); + h += '
Item left behind when this object is removed
'; h += ''; h += '
'; } @@ -471,6 +477,8 @@ function renderCoreCard(data) { } h += ''; + h += ced_renderTagHelp('color'); + return h; } @@ -672,8 +680,7 @@ function renderToolsCheckbox(sec, il, data) { return 'Loading tool types...'; } if (arr.length === 0) { - var hint = il.hint ? '
' + esc(il.hint) + '
' : ''; - return hint + ''; + return ''; } var h = '
'; h += '' + esc(il.label) + (il.hint ? ' (' + esc(il.hint) + ')' : '') + ''; @@ -822,7 +829,7 @@ function addArrayItem(cardID) { } else { empty = {}; sec.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); } arr.push(empty); @@ -848,7 +855,7 @@ function addSubRow(cardID, subKey) { var st = (sec.subtables || []).find(function(s) { return s.key === subKey; }); if (st) { st.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); } arr.push(empty); @@ -884,7 +891,7 @@ function addDropRow(cardID, subKey, dropType) { var empty = { _type: (dropType === 'table' ? 't' : 'i') }; if (st) { st.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); + empty[f[0]] = f[2] === 'checkbox' ? false : ''; }); } empty.weight = 10; diff --git a/internal/admin/static/talktree.js b/internal/admin/static/talktree.js index 8e35404..ff44ff1 100644 --- a/internal/admin/static/talktree.js +++ b/internal/admin/static/talktree.js @@ -204,7 +204,7 @@ if (tag === 'INPUT' || tag === 'TEXTAREA') { var val; if (type === 'checkbox') { val = el.checked; } - else if (type === 'number') { var n = parseFloat(el.value); val = isNaN(n) ? 0 : n; } + else if (type === 'number') { var n = parseFloat(el.value); val = isNaN(n) ? '' : n; } else { val = el.value; } talkSyncPath(path, val); if (path.endsWith('.goto') && e.type === 'change') renderTalkTree(window._talkData); -- cgit v1.2.3