From c14c2e403c75a913e1a6d962fb6fe64f013adae5 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Wed, 1 Jul 2026 22:30:31 -0400 Subject: fix: combat formulas and mob attack types (mobs switch attack styles if safespotting) --- internal/admin/static/mobeditor.js | 81 ++++++++++++++++++++++++++++++++++---- 1 file changed, 73 insertions(+), 8 deletions(-) (limited to 'internal/admin/static') 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 = '
'; + h += 'Atk Type'; + h += '
'; + ['stab','slash','crush'].forEach(function(m) { + h += ''; + }); + h += '
'; + h += '
'; + h += ''; + h += ''; + h += '
'; + h += '
'; + 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 = '
'; h += renderMobField(sec, byKey['kind'], data, -1); + h += renderMobField(sec, byKey['aggressive'], data, -1); + h += renderMobField(sec, byKey['respawn_ticks'], data, -1); h += '
'; + h += renderMobAttackTypeWidget(data); + h += '
'; h += 'Stats'; h += '
'; @@ -287,8 +340,11 @@ function renderMobCombatCard(data) { statKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); }); h += '
'; h += '
'; - 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 += '
'; + h += '
'; + 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 += '
'; h += '
'; @@ -310,6 +366,7 @@ function renderMobCombatCard(data) { h += ''; h += '
'; h += renderMobField(sec, byKey['stats.defenses.weakness'], data, -1); + h += renderMobField(sec, byKey['stats.defenses.weakness_percent'], data, -1); h += '
'; h += ''; @@ -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; -- cgit v1.2.3