diff options
Diffstat (limited to 'internal/admin')
| -rw-r--r-- | internal/admin/static/cardeditor.js | 15 | ||||
| -rw-r--r-- | internal/admin/static/itemeditor.js | 74 | ||||
| -rw-r--r-- | internal/admin/static/mobeditor.js | 44 | ||||
| -rw-r--r-- | internal/admin/static/objecteditor.js | 44 | ||||
| -rw-r--r-- | internal/admin/templates/items.html | 1 |
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 ? '▶' : '▼') + '</span>'; - h += 'How Tiers, Levels, Decay & 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> — Player tier (base 1; +1 with <code>unlocked_rock_cover</code> flag) must be ≥ safespot tier to hide.<br>'; - h += '<b>Levels of Decay</b> — 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> — 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> — Resets the cover to full levels when the first person hides on an empty/unused cover.<br>'; - h += '<b>Respawn ticks</b> — Auto-reforms the cover after N ticks when fully destroyed (level 0). 0 = instance permanently removed.<br>'; - h += '<b>Max Block Size</b> — Determines which mob sizes are blocked (small < medium < large < massive). Empty = blocks everything.<br>'; - h += '<b>Unsafe Chance</b> — 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(); |
