diff --git a/src/game/skills.ts b/src/game/skills.ts index 9a033e5..a41a204 100644 --- a/src/game/skills.ts +++ b/src/game/skills.ts @@ -4147,14 +4147,13 @@ export const BATCH1_SKILLS: Readonly> = Object.fr 10: { id: 10, name: 'Jab', classCode: 'ama', charClass: 'amazon', type: 'melee', mana: 16, lvlmana: 2, minmana: 0, manashift: 5, delay: 0, hitshift: 8, emin: 0, emax: 0, elev1: 0, elev2: 0, elev3: 0, elev4: 0, elev5: 0, srcDamage: 128 }, 14: { id: 14, name: 'Power Strike', classCode: 'ama', charClass: 'amazon', type: 'melee', mana: 16, lvlmana: 2, minmana: 0, manashift: 5, delay: 0, hitshift: 8, emin: 1, emax: 16, elev1: 8, elev2: 12, elev3: 16, elev4: 20, elev5: 24, srcDamage: 128 }, - // Sorceress (7 skills) + // Sorceress (6 skills) 36: { id: 36, name: 'Fire Bolt', classCode: 'sor', charClass: 'sorceress', type: 'projectile', mana: 5, lvlmana: 0, minmana: 1, manashift: 7, delay: 0, hitshift: 7, emin: 6, emax: 12, elev1: 2, elev2: 4, elev3: 6, elev4: 8, elev5: 10, srcDamage: 0, range: 1000, speed: 500, castOverlay: 'fire_cast_1' }, 37: { id: 37, name: 'Warmth', classCode: 'sor', charClass: 'sorceress', type: 'passive', mana: 0, lvlmana: 0, minmana: 0, manashift: 8, delay: 0, hitshift: 8, emin: 0, emax: 0, elev1: 0, elev2: 0, elev3: 0, elev4: 0, elev5: 0, srcDamage: 0 }, 39: { id: 39, name: 'Ice Bolt', classCode: 'sor', charClass: 'sorceress', type: 'projectile', mana: 6, lvlmana: 0, minmana: 1, manashift: 7, delay: 0, hitshift: 7, emin: 6, emax: 10, elev1: 2, elev2: 4, elev3: 6, elev4: 8, elev5: 10, srcDamage: 0, range: 400, speed: 450, castOverlay: 'ice_cast_1' }, 44: { id: 44, name: 'Frost Nova', classCode: 'sor', charClass: 'sorceress', type: 'area', mana: 9, lvlmana: 1, minmana: 1, manashift: 8, delay: 0, hitshift: 8, emin: 2, emax: 4, elev1: 1, elev2: 2, elev3: 3, elev4: 4, elev5: 5, srcDamage: 0, castOverlay: 'ice_cast_2' }, 47: { id: 47, name: 'Fire Ball', classCode: 'sor', charClass: 'sorceress', type: 'projectile', mana: 10, lvlmana: 1, minmana: 1, manashift: 7, delay: 0, hitshift: 7, emin: 12, emax: 28, elev1: 6, elev2: 12, elev3: 18, elev4: 24, elev5: 30, srcDamage: 0, range: 450, speed: 450, castOverlay: 'fire_cast_2' }, 54: { id: 54, name: 'Teleport', classCode: 'sor', charClass: 'sorceress', type: 'movement', mana: 24, lvlmana: -1, minmana: 1, manashift: 8, delay: 0, hitshift: 8, emin: 0, emax: 0, elev1: 0, elev2: 0, elev3: 0, elev4: 0, elev5: 0, srcDamage: 0, castOverlay: 'teleport' }, - 62: { id: 62, name: 'Hydra', classCode: 'sor', charClass: 'sorceress', type: 'summon', mana: 40, lvlmana: 1, minmana: 1, manashift: 7, delay: 40, hitshift: 7, emin: 28, emax: 39, elev1: 11, elev2: 15, elev3: 19, elev4: 23, elev5: 27, emaxlev1: 13, emaxlev2: 17, emaxlev3: 21, emaxlev4: 25, emaxlev5: 29, srcDamage: 0, range: 450, speed: 400, castOverlay: 'fire_cast_2' }, // Necromancer (5 skills) 66: { id: 66, name: 'Amplify Damage', classCode: 'nec', charClass: 'necromancer', type: 'curse', mana: 4, lvlmana: 0, minmana: 1, manashift: 8, delay: 0, hitshift: 8, emin: 0, emax: 0, elev1: 0, elev2: 0, elev3: 0, elev4: 0, elev5: 0, srcDamage: 0 }, @@ -4222,6 +4221,21 @@ export function getSkillManaCost(skillId: number, effectiveLevel: number): numbe // 1.13c Skills.txt: mana = 34, lvlmana = 1, manashift = 7 (factor = 0.5) return (34 + (lvl - 1)) * 0.5 } + if (skillId === 59) { + const lvl = Number.isFinite(effectiveLevel) ? Math.max(1, Math.floor(effectiveLevel)) : 1 + // 1.13c Skills.txt: mana = 23, lvlmana = 1, manashift = 8 (factor = 1.0) + return 23 + (lvl - 1) + } + if (skillId === 62) { + const lvl = Number.isFinite(effectiveLevel) ? Math.max(1, Math.floor(effectiveLevel)) : 1 + // 1.13c Skills.txt: mana = 40, lvlmana = 1, manashift = 7 (factor = 0.5) + return (40 + (lvl - 1)) * 0.5 + } + if (skillId === 64) { + const lvl = Number.isFinite(effectiveLevel) ? Math.max(1, Math.floor(effectiveLevel)) : 1 + // 1.13c Skills.txt: mana = 30, lvlmana = 1, manashift = 8 (factor = 1.0) + return 30 + (lvl - 1) + } const def = getBatch1SkillDef(skillId) if (!def) return 0 const lvl = Number.isFinite(effectiveLevel) ? Math.max(1, Math.floor(effectiveLevel)) : 1 @@ -4237,12 +4251,24 @@ export function getSkillManaCost(skillId: number, effectiveLevel: number): numbe * - Firestorm: 15 frames (0.6s) * - Molten Boulder: 50 frames (2.0s) * - Meteor: 30 frames (1.2s) + * - Blizzard: 45 frames (1.8s) + * - Hydra: 40 frames (1.6s) + * - Frozen Orb: 25 frames (1.0s) * - All other Batch 1 skills: 0 frames */ export function getSkillCooldownTicks(skillId: number): number { if (skillId === 56) { return 30 } + if (skillId === 59) { + return 45 + } + if (skillId === 62) { + return 40 + } + if (skillId === 64) { + return 25 + } const def = getBatch1SkillDef(skillId) return def?.delay ?? 0 } @@ -4276,6 +4302,9 @@ export function calculateSkillDamage( if (skillId === 56 || skillId === '56' || skillId === 'meteor') { return calculateMeteorDamage(slvl, synergies as any) } + if (skillId === 62 || skillId === '62' || skillId === 'hydra') { + return calculateHydraDamage(slvl, synergies as any) + } const numericSkillId = typeof skillId === 'number' ? skillId : Number(skillId) const def = Number.isFinite(numericSkillId) ? getBatch1SkillDef(numericSkillId) : undefined @@ -4607,6 +4636,85 @@ export function calculateMeteorDetailedDamage( } } +/** + * Calculates Hydra (Skill 62) synergy multiplier: + * +3% fire damage per hard point in Fire Bolt (36) and Fire Ball (47). + */ +export function calculateHydraSynergyMultiplier(synergies: { + readonly fireBolt?: number | undefined + readonly fireBall?: number | undefined + readonly fireMastery?: number | undefined +} | number = {}): number { + if (typeof synergies === 'number') { + return Number.isFinite(synergies) && synergies > 0 ? synergies : 1.0 + } + const points = (synergies.fireBolt ?? 0) + (synergies.fireBall ?? 0) + return 1.0 + points * 0.03 +} + +/** + * Calculates Hydra (Skill 62) min and max damage with 1.13c 5-band level scaling and synergies. + * 1.13c Ground Truth: + * - EMin: 28, EMax: 39 + * - 5 bands: elev = [11, 15, 19, 23, 27], emaxlev = [13, 17, 21, 25, 29] + * - HitShift: 7 (factor = 0.5) + * - Synergies: +3% per hard point of Fire Bolt (36) and Fire Ball (47). + */ +export function calculateHydraDamage( + effectiveLevel: number, + synergies: { + readonly fireBolt?: number | undefined + readonly fireBall?: number | undefined + readonly fireMastery?: number | undefined + } | number = {}, +): { min: number; max: number } { + let syn = 1.0 + if (typeof synergies === 'number') { + syn = Number.isFinite(synergies) && synergies > 0 ? synergies : 1.0 + } else if (synergies && typeof synergies === 'object') { + syn = calculateHydraSynergyMultiplier(synergies) + } + const lvl = Number.isFinite(effectiveLevel) ? Math.max(1, Math.floor(effectiveLevel)) : 1 + let min = 28 + let max = 39 + + if (lvl > 1) { + const b1 = Math.min(lvl - 1, 7) + min += b1 * 11 + max += b1 * 13 + + if (lvl > 8) { + const b2 = Math.min(lvl - 8, 8) + min += b2 * 15 + max += b2 * 17 + } + if (lvl > 16) { + const b3 = Math.min(lvl - 16, 6) + min += b3 * 19 + max += b3 * 21 + } + if (lvl > 22) { + const b4 = Math.min(lvl - 22, 6) + min += b4 * 23 + max += b4 * 25 + } + if (lvl > 28) { + const b5 = lvl - 28 + min += b5 * 27 + max += b5 * 29 + } + } + + // HitShift 7 = 0.5 (128/256 precision) + min *= 0.5 + max *= 0.5 + + return { + min: Math.floor(min * syn), + max: Math.floor(max * syn), + } +} + /** Result of executing a Batch 1 skill. */ export type SkillExecutionResult = | { readonly kind: 'cooldown'; readonly ticksLeft: number }