diff options
| author | historia <[not public]> | 2026-07-01 22:30:31 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-01 22:30:31 -0400 |
| commit | c14c2e403c75a913e1a6d962fb6fe64f013adae5 (patch) | |
| tree | 75070293fe2535b594ac72e501cbf08a3f1146e0 /internal/admin/static/mobeditor.js | |
| parent | 8eec7b75ff9c8c368b252373db45fb891732d2ca (diff) | |
| download | thehouseoficarus-c14c2e403c75a913e1a6d962fb6fe64f013adae5.tar.gz | |
fix: combat formulas and mob attack types (mobs switch attack styles if safespotting)
Diffstat (limited to 'internal/admin/static/mobeditor.js')
| -rw-r--r-- | internal/admin/static/mobeditor.js | 81 |
1 files changed, 73 insertions, 8 deletions
diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js index 3d6f4d4..4895c6f 100644 --- a/internal/admin/static/mobeditor.js +++ b/internal/admin/static/mobeditor.js @@ -22,16 +22,18 @@ var MOB_SECTIONS = [ detect: function(d) { return d.combat; }, fields: [ ['kind', 'Kind', 'select', 'combat|task'], + ['aggressive', 'Aggressive', 'checkbox'], + ['respawn_ticks', 'Respawn Ticks', 'number', '', 'narrow'], ['stats.hp', 'HP', 'number', '', 'narrow'], ['stats.attack', 'Attack', 'number', '', 'narrow'], ['stats.strength', 'Strength', 'number', '', 'narrow'], ['stats.defense', 'Defense', 'number', '', 'narrow'], ['stats.ranged', 'Ranged', 'number', '', 'narrow'], ['stats.science', 'Science', 'number', '', 'narrow'], - ['stats.attack_type', 'Atk Type', 'select', 'stab|slash|crush|ranged|science'], ['stats.speed', 'Speed', 'number', '', 'narrow'], - ['stats.aggressive', 'Aggressive', 'checkbox'], - ['stats.respawn_ticks', 'Respawn Ticks', 'number', '', 'narrow'], + ['stats.max_melee_hit', 'Max Melee Hit', 'number', '', 'narrow'], + ['stats.max_ranged_hit', 'Max Ranged Hit', 'number', '', 'narrow'], + ['stats.max_science_hit', 'Max Science Hit', 'number', '', 'narrow'], ['stats.bonuses.attack_bonus', 'Atk Bonus', 'number', '', 'narrow'], ['stats.bonuses.strength_bonus', 'Str Bonus', 'number', '', 'narrow'], ['stats.bonuses.science_bonus', 'Sci Bonus', 'number', '', 'narrow'], @@ -44,6 +46,7 @@ var MOB_SECTIONS = [ ['stats.defenses.science_defense', 'Sci Def', 'number', '', 'narrow'], ['stats.defenses.ranged_defense', 'Rng Def', 'number', '', 'narrow'], ['stats.defenses.weakness', 'Weakness', 'text'], + ['stats.defenses.weakness_percent', 'Weakness %', 'number', '', 'narrow'], ['assassin_level', 'Assassin Lvl', 'number', '', 'narrow'], ['finishing_blow', 'Finishing Blow', 'search', '', '', null, 'items'], ['damage_without', 'Damage Without', 'search', '', '', null, 'items'], @@ -271,6 +274,52 @@ 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 (radio: stab/slash/crush) plus optional ranged/science (checkboxes). +function renderMobAttackTypeWidget(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 h = '<div class="obj-inline-group">'; + h += '<span class="obj-inline-label">Atk Type</span>'; + h += '<div class="obj-card-grid" style="margin-top:4px">'; + ['stab','slash','crush'].forEach(function(m) { + h += '<label class="obj-field obj-check obj-narrow"><input type="radio" name="mob_atktype_melee" value="' + m + '"' + (melee === m ? ' checked' : '') + '> <span>' + m + '</span></label>'; + }); + 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>'; + return h; +} + +// 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 r = document.querySelector('input[name="mob_atktype_melee"]:checked'); + if (r) melee = r.value; + 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 renderMobCombatCard(data) { var sec = MOB_SECTIONS.find(function(s) { return s.id === 'combat'; }); if (!sec) return ''; @@ -278,8 +327,12 @@ function renderMobCombatCard(data) { 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 += renderMobField(sec, byKey['respawn_ticks'], data, -1); h += '</div>'; + h += renderMobAttackTypeWidget(data); + h += '<div class="obj-inline-group">'; h += '<span class="obj-inline-label">Stats</span>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; @@ -287,8 +340,11 @@ function renderMobCombatCard(data) { statKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); h += '</div>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; - var statKeys2 = ['stats.attack_type','stats.speed','stats.aggressive','stats.respawn_ticks']; - statKeys2.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); + h += renderMobField(sec, byKey['stats.speed'], data, -1); + h += '</div>'; + 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>'; @@ -310,6 +366,7 @@ function renderMobCombatCard(data) { h += '</div>'; h += '<div class="obj-card-grid" style="margin-top:4px">'; h += renderMobField(sec, byKey['stats.defenses.weakness'], data, -1); + h += renderMobField(sec, byKey['stats.defenses.weakness_percent'], data, -1); h += '</div>'; h += '</div>'; @@ -523,12 +580,15 @@ function mobAddSection() { if (id === 'combat') { mobData[sec.path] = { kind: 'combat', + aggressive: false, + attack_types: ['crush'], + respawn_ticks: 30, stats: { hp: 1, attack: 1, strength: 1, defense: 1, - attack_type: 'crush', speed: 5, - aggressive: false, respawn_ticks: 30, + speed: 5, + max_melee_hit: 0, max_ranged_hit: 0, max_science_hit: 0, bonuses: {attack_bonus:0, strength_bonus:0, science_bonus:0, science_percent_bonus:0, ranged_bonus:0, ranged_strength_bonus:0}, - defenses: {stab_defense:0, slash_defense:0, crush_defense:0, science_defense:0, ranged_defense:0} + defenses: {stab_defense:0, slash_defense:0, crush_defense:0, science_defense:0, ranged_defense:0, weakness_percent:0} }, size: 'small' }; @@ -610,6 +670,11 @@ function saveMob() { hasValues = ced_saveSectionFields(sec, sectionObj, mobCardPath, mobFieldID) || hasValues; } + if (sec.id === 'combat' && document.querySelector('input[name="mob_atktype_melee"]')) { + sectionObj['attack_types'] = collectMobAttackType(); + hasValues = true; + } + if (sec.subtables) { sec.subtables.forEach(function(st) { if (ced_saveSubtable(sec, st, sectionObj, mobCardPath, mobFieldIDSub)) hasValues = true; |
