diff options
| author | historia <[not public]> | 2026-07-13 15:07:28 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-13 15:07:28 -0400 |
| commit | 93a412aed2b11406399504c81d6d19ed1341908a (patch) | |
| tree | d99f0c283e6684d266676438fbec4e550bf9c774 /internal/admin/static/mobeditor.js | |
| parent | fcc9c0a83df82093efd3a85e8e0dc0768653ea87 (diff) | |
| download | thehouseoficarus-93a412aed2b11406399504c81d6d19ed1341908a.tar.gz | |
feat(admin): hide irrelevant combat stats for task mobs, show relevant combat stats for hazards
Diffstat (limited to 'internal/admin/static/mobeditor.js')
| -rw-r--r-- | internal/admin/static/mobeditor.js | 162 |
1 files changed, 89 insertions, 73 deletions
diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js index 3934fcd..10b256e 100644 --- a/internal/admin/static/mobeditor.js +++ b/internal/admin/static/mobeditor.js @@ -22,7 +22,6 @@ var MOB_SECTIONS = [ id: 'combat', label: 'Combat Stats', path: 'combat', detect: function(d) { return d.combat; }, fields: [ - ['kind', 'Kind', 'select', 'combat|task'], ['aggressive', 'Aggressive', 'checkbox'], ['respawn_ticks', 'Respawn Ticks', 'number', '30', 'narrow'], ['stats.hp', 'HP', 'number', '10', 'narrow'], @@ -46,7 +45,7 @@ var MOB_SECTIONS = [ ['stats.defenses.crush_defense', 'Crush Def', 'number', '0', 'narrow'], ['stats.defenses.science_defense', 'Sci Def', 'number', '0', 'narrow'], ['stats.defenses.ranged_defense', 'Rng Def', 'number', '0', 'narrow'], - ['stats.defenses.weakness', 'Weakness', 'select', '|bio|eco|hydro|solar|chaos'], + ['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'], @@ -211,6 +210,7 @@ function renderMobEditor(id, data) { $('#editorMain').innerHTML = html; ced_setupSearchFields(); setupMobProtectedToggle(); + applyTaskVisibility(); MOB_SECTIONS.forEach(function(sec) { if (sec.id === 'talk' && data.talk && data.talk.nodes) { @@ -248,6 +248,8 @@ function renderMobCard(sec, data) { h += renderMobCoreCard(data); } else if (sec.id === 'combat') { h += renderMobCombatCard(data); + } else if (sec.id === 'task') { + h += renderMobTaskCard(data); } else if (sec.id === 'drops') { h += renderMobDropsCard(data); } else if (sec.isArray) { @@ -305,43 +307,21 @@ function renderMobCoreCard(data) { return h; } -// normalizeAttackTypes coerces a mob attack_types value (array) into an array of -// strings, tolerating a legacy scalar string for display only. -function normalizeAttackTypes(v) { - if (Array.isArray(v)) return v.slice(); - if (typeof v === 'string' && v) return [v]; - return []; -} - -// renderMobAttackTypeWidget renders the Atk Type selector: one required melee -// type (pill: stab/slash/crush) plus optional ranged/science (checkboxes). -function renderMobAttackTypeWidget(data) { +function renderMobKindToggle(data) { var combat = data.combat || {}; - var types = normalizeAttackTypes(combat.attack_types); - var melee = types.filter(function(t) { return t === 'stab' || t === 'slash' || t === 'crush'; })[0] || 'crush'; - var hasRanged = types.indexOf('ranged') >= 0; - var hasScience = types.indexOf('science') >= 0; - var meleeOpts = ['stab','slash','crush']; - var activeIdx = meleeOpts.indexOf(melee); - if (activeIdx < 0) activeIdx = 2; - + var kind = combat.kind === 'task' ? 'task' : 'combat'; + var activeC = kind === 'combat'; var h = '<div class="obj-inline-group">'; - h += '<span class="obj-inline-label">Atk Type</span>'; - h += '<div class="seg-pill seg-pill-3 seg-idx-' + activeIdx + '" id="mob_atktype_pill" style="margin-top:4px">'; - meleeOpts.forEach(function(t, i) { - h += '<button type="button" class="seg-opt' + (i === activeIdx ? ' seg-active' : '') + '" data-v="' + t + '" onclick="mobAtkPillSelect(\'' + t + '\',this)">' + t + '</button>'; - }); - h += '</div>'; - h += '<div class="obj-card-grid" style="margin-top:4px">'; - h += '<label class="obj-field obj-check obj-narrow"><input type="checkbox" id="mob_atktype_ranged"' + (hasRanged ? ' checked' : '') + '> <span>ranged</span></label>'; - h += '<label class="obj-field obj-check obj-narrow"><input type="checkbox" id="mob_atktype_science"' + (hasScience ? ' checked' : '') + '> <span>science</span></label>'; - h += '</div>'; - h += '</div>'; + h += '<span class="obj-inline-label">Kind</span>'; + h += '<div class="seg-pill seg-pill-2 seg-idx-' + (activeC ? 0 : 1) + '" id="mob_kind_pill" style="margin-top:4px">'; + h += '<button type="button" class="seg-opt' + (activeC ? ' seg-active' : '') + '" data-v="combat" onclick="mobKindPillSelect(\'combat\',this)">Combat</button>'; + h += '<button type="button" class="seg-opt' + (activeC ? '' : ' seg-active') + '" data-v="task" onclick="mobKindPillSelect(\'task\',this)">Task</button>'; + h += '</div></div>'; return h; } -function mobAtkPillSelect(val, btn) { - var pill = document.getElementById('mob_atktype_pill'); +function mobKindPillSelect(val, btn) { + var pill = document.getElementById('mob_kind_pill'); if (!pill) return; var opts = pill.querySelectorAll('.seg-opt'); var idx = -1; @@ -354,23 +334,25 @@ function mobAtkPillSelect(val, btn) { } }); pill.className = pill.className.replace(/seg-idx-\d/g, 'seg-idx-' + idx); + if (val === 'task') { + if (!mobData.combat) mobData.combat = {}; + mobData.combat.kind = 'task'; + if (!mobData.task) mobData.task = {verb:'work', progress_noun:'construction', complete_message:'The work is finished.'}; + addedSections.task = true; + expandedCards.task = true; + } else { + if (mobData.combat) mobData.combat.kind = 'combat'; + delete mobData.task; + delete addedSections.task; + } + renderCurrent(); } -// collectMobAttackType reads the Atk Type widget into an array: the selected -// melee type first, then any optional ranged/science types. -function collectMobAttackType() { - var melee = 'crush'; - var pill = document.getElementById('mob_atktype_pill'); - if (pill) { - var active = pill.querySelector('.seg-opt.seg-active'); - if (active) melee = active.getAttribute('data-v'); - } - var out = [melee]; - var rng = document.getElementById('mob_atktype_ranged'); - var sci = document.getElementById('mob_atktype_science'); - if (rng && rng.checked) out.push('ranged'); - if (sci && sci.checked) out.push('science'); - return out; +function collectMobKind() { + var pill = document.getElementById('mob_kind_pill'); + if (!pill) return 'combat'; + var active = pill.querySelector('.seg-opt.seg-active'); + return active ? active.getAttribute('data-v') : 'combat'; } function renderMobCombatCard(data) { @@ -379,28 +361,43 @@ function renderMobCombatCard(data) { var byKey = {}; sec.fields.forEach(function(f) { byKey[f[0]] = f; }); var h = '<div class="obj-card-grid" style="margin-top:6px">'; - h += renderMobField(sec, byKey['kind'], data, -1); - h += renderMobField(sec, byKey['aggressive'], data, -1); + h += renderMobKindToggle(data); h += renderMobField(sec, byKey['respawn_ticks'], data, -1); + h += '<div class="mob-task-hide" style="display:contents">'; + h += renderMobField(sec, byKey['aggressive'], data, -1); + h += '</div>'; h += '</div>'; - h += renderMobAttackTypeWidget(data); + h += '<div class="mob-task-hide" id="mob_atktype_wrap">'; + h += ced_renderAtkTypeWidget(data, 'mob_atktype', 'ced_atkPillSelect'); + h += '</div>'; h += '<div class="obj-inline-group">'; h += '<span class="obj-inline-label">Stats</span>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; - var statKeys = ['stats.hp','stats.attack','stats.strength','stats.defense','stats.ranged','stats.science']; - statKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); + h += renderMobField(sec, byKey['stats.hp'], data, -1); + h += renderMobField(sec, byKey['stats.defense'], data, -1); h += '</div>'; + h += '<div class="mob-task-hide" style="display:contents">'; + h += '<div class="obj-card-grid" style="margin-top:4px">'; + var atkStatKeys = ['stats.attack','stats.strength','stats.ranged','stats.science']; + atkStatKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); + h += '</div>'; + h += '</div>'; + h += '<div class="mob-task-hide" style="display:contents">'; h += '<div class="obj-card-grid" style="margin-top:4px">'; h += renderMobField(sec, byKey['stats.speed'], data, -1); h += '</div>'; + h += '</div>'; + h += '<div class="mob-task-hide" style="display:contents">'; h += '<div class="obj-card-grid" style="margin-top:4px">'; var statKeys3 = ['stats.max_melee_hit','stats.max_ranged_hit','stats.max_science_hit']; statKeys3.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); h += '</div>'; h += '</div>'; + h += '</div>'; + h += '<div class="mob-task-hide" id="mob_bonuses_wrap">'; h += '<div class="obj-inline-group">'; h += '<span class="obj-inline-label">Bonuses</span>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; @@ -409,6 +406,7 @@ function renderMobCombatCard(data) { bonusKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); h += '</div>'; h += '</div>'; + h += '</div>'; h += '<div class="obj-inline-group">'; h += '<span class="obj-inline-label">Defenses</span>'; @@ -438,6 +436,20 @@ function renderMobCombatCard(data) { return h; } +function renderMobTaskCard(data) { + var sec = MOB_SECTIONS.find(function(s) { return s.id === 'task'; }); + if (!sec) return ''; + var byKey = {}; + sec.fields.forEach(function(f) { byKey[f[0]] = f; }); + var h = '<p style="color:#888;font-size:11px;margin:0 0 8px 0;line-height:1.5">Task mobs can be attacked but do not fight back. The Verb and Progress Noun appear in combat messages. Complete Message prints when the task is finished.</p>'; + h += '<div class="obj-card-grid" style="margin-top:6px">'; + h += renderMobField(sec, byKey['verb'], data, -1); + h += renderMobField(sec, byKey['progress_noun'], data, -1); + h += '</div>'; + h += renderMobField(sec, byKey['complete_message'], data, -1, null, 'width:100%'); + return h; +} + // setupMobProtectedToggle hides the entire Combat card when Protected is checked. function setupMobProtectedToggle() { var sec = MOB_SECTIONS.find(function(s) { return s.id === 'core'; }); @@ -457,6 +469,12 @@ function setupMobProtectedToggle() { apply(); } +function applyTaskVisibility() { + var isTask = mobData && mobData.combat && mobData.combat.kind === 'task'; + var hidden = document.querySelectorAll('.mob-task-hide'); + hidden.forEach(function(el) { el.style.display = isTask ? 'none' : ''; }); +} + function renderMobField(sec, f, data, idx, subPath, optStyle) { return ced_renderField(sec, f, data, idx, subPath, optStyle, cardPath, fieldID, getVal); } @@ -728,6 +746,9 @@ function removeMobSection(id) { if (sec.inline) sec.inline.forEach(function(il) { if (il.key) delete mobData[il.key]; }); if (sec.subtables) sec.subtables.forEach(function(st) { delete mobData[st.key]; }); } + if (id === 'task' && mobData.combat && mobData.combat.kind === 'task') { + mobData.combat.kind = 'combat'; + } delete addedSections[id]; renderMobEditor(mobID, mobData); } @@ -805,22 +826,6 @@ function addMobLootRow(cardID, subKey, kind) { renderCurrent(); } -// ---- Nested object helpers ---- - -function unflattenObj(obj) { - var result = {}; - Object.keys(obj).forEach(function(key) { - var parts = key.split('.'); - var current = result; - for (var i = 0; i < parts.length - 1; i++) { - if (!current[parts[i]]) current[parts[i]] = {}; - current = current[parts[i]]; - } - current[parts[parts.length - 1]] = obj[key]; - }); - return result; -} - // ---- Save ---- function saveMob() { @@ -874,9 +879,20 @@ function saveMob() { hasValues = ced_saveSectionFields(sec, sectionObj, cardPath, fieldID) || hasValues; } - if (sec.id === 'combat' && document.getElementById('mob_atktype_pill')) { - sectionObj['attack_types'] = collectMobAttackType(); + if (sec.id === 'combat') { + sectionObj['kind'] = collectMobKind(); hasValues = true; + if (sectionObj['kind'] !== 'task' && document.getElementById('mob_atktype')) { + sectionObj['attack_types'] = ced_collectAtkType('mob_atktype'); + } + if (sectionObj['kind'] === 'task') { + var taskStrip = ['aggressive', + 'stats.attack','stats.strength','stats.ranged','stats.science', + 'stats.max_melee_hit','stats.max_ranged_hit','stats.max_science_hit', + 'stats.bonuses.attack_bonus','stats.bonuses.strength_bonus','stats.bonuses.science_bonus', + 'stats.bonuses.science_percent_bonus','stats.bonuses.ranged_bonus','stats.bonuses.ranged_strength_bonus']; + taskStrip.forEach(function(k) { delete sectionObj[k]; }); + } } if (sec.subtables) { @@ -901,7 +917,7 @@ function saveMob() { if (sec.always) { Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; }); } else if (hasValues && sec.path) { - out[sec.path] = unflattenObj(sectionObj); + out[sec.path] = ced_unflattenObj(sectionObj); } else if (hasValues && !sec.path) { Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; }); } |
