From 9fac33a1e2f841d796abdbcd5682fb50a2ba1dcf Mon Sep 17 00:00:00 2001 From: troytt <47798984@qq.com> Date: Tue, 22 Sep 2026 07:13:00 +0000 Subject: [PATCH] fix(skills): reconstruct skill tooltips to 1.13c formula and next level preview (Fixes #152) --- scripts/patch-skill-catalog.ts | 168 +++++ src/data/skills-catalog.ts | 1104 +++++++++++++++++++++++++------ src/game/skill-calc-engine.ts | 70 +- src/ui/skill-tree-panel.ts | 315 +++++++-- tests/skill-calc-engine.test.ts | 93 +++ tests/ui-hud.test.ts | 123 ++++ 6 files changed, 1609 insertions(+), 264 deletions(-) create mode 100644 scripts/patch-skill-catalog.ts diff --git a/scripts/patch-skill-catalog.ts b/scripts/patch-skill-catalog.ts new file mode 100644 index 0000000..044b646 --- /dev/null +++ b/scripts/patch-skill-catalog.ts @@ -0,0 +1,168 @@ +import { MpqArchive } from '../src/mpq/archive.ts' +import { fileSource } from '../src/mpq/file-source.ts' +import { MountedArchives } from '../src/mpq/mount.ts' +import { loadActTables } from '../src/game/acts.ts' +import { SKILLS_CATALOG } from '../src/data/skills-catalog.ts' +import { writeFileSync } from 'node:fs' +import { join } from 'node:path' + +async function run() { + console.log('Mounting MPQs to extract 1.13c ToHit and auraLenCalc...') + const archives = new MountedArchives() + for (const m of ['d2char.mpq', 'd2data.mpq', 'd2exp.mpq', 'Patch_D2.mpq']) { + archives.add(m, await MpqArchive.open(await fileSource('samples/d2/' + m))) + } + const tables = await loadActTables(archives) + const st = tables.skills! + const toHitIdx = st.header.indexOf('ToHit') + const levToHitIdx = st.header.indexOf('LevToHit') + const toHitCalcIdx = st.header.indexOf('ToHitCalc') + const auraLenCalcIdx = st.header.indexOf('auralencalc') + const startmanaIdx = st.header.indexOf('startmana') + + const updatedCatalog = SKILLS_CATALOG.map(skill => { + const row = st.rows.find(r => Number(r[1]) === skill.id) + const th = row ? (Number(row[toHitIdx]) || 0) : 0 + const lth = row ? (Number(row[levToHitIdx]) || 0) : 0 + const rawThc = row ? row[toHitCalcIdx] : undefined + const thc = rawThc && rawThc.trim() !== '' ? rawThc.trim() : undefined + const rawAlc = row ? row[auraLenCalcIdx] : undefined + const alc = rawAlc && rawAlc.trim() !== '' ? rawAlc.trim() : undefined + const sm = row ? (Number(row[startmanaIdx]) || 0) : 0 + + return { + ...skill, + toHit: th, + toHitLev: lth, + ...(thc ? { toHitCalc: thc } : {}), + ...(alc ? { auraLenCalc: alc } : {}), + startmana: sm, + } + }) + + const tsContent = `// Auto-generated Diablo II 1.13c 210-Skill Catalog +// Ground truth parsed from Skills.txt, SkillDesc.txt, string.tbl, and MPQs. + +import type { CharacterClassCode } from '../game/classes.ts' + +export interface SynergyBonusInfo { + readonly providerId: number + readonly providerName: string + readonly percentPerLevel: number + readonly type: 'damage' | 'elemental' | 'duration' +} + +export interface SynergyGivenInfo { + readonly targetId: number + readonly targetName: string + readonly targetNameZh: string + readonly percentPerLevel: number +} + +export interface DescLineDefinition { + readonly lineType: number + readonly textaKey?: string | undefined + readonly textbKey?: string | undefined + readonly textaEn?: string | undefined + readonly textaZh?: string | undefined + readonly textbEn?: string | undefined + readonly textbZh?: string | undefined + readonly calca?: string | undefined + readonly calcb?: string | undefined +} + +export interface MinionBaseStats { + readonly code: string + readonly minHP: number + readonly maxHP: number + readonly minDam: number + readonly maxDam: number +} + +export interface SkillCatalogEntry { + readonly id: number + readonly name: string + readonly nameZh: string + readonly classCode: CharacterClassCode + readonly classNameZh: string + readonly tabIndex: number + readonly tabName: string + readonly tabNameZh: string + readonly row: number + readonly col: number + readonly reqlevel: number + readonly reqskills: readonly number[] + readonly dependents: readonly number[] + readonly mana: number + readonly lvlmana: number + readonly minmana: number + readonly manashift: number + readonly delay: number + readonly descdam?: string | undefined + readonly hitshift: number + readonly srcDamage: number + readonly minDam: number + readonly maxDam: number + readonly minLevDam: readonly number[] + readonly maxLevDam: readonly number[] + readonly eType: string + readonly eMin: number + readonly eMax: number + readonly eMinLev: readonly number[] + readonly eMaxLev: readonly number[] + readonly eLen: number + readonly eLevLen: readonly number[] + readonly toHit: number + readonly toHitLev: number + readonly toHitCalc?: string | undefined + readonly auraLenCalc?: string | undefined + readonly startmana: number + readonly params: Readonly> + readonly synergyFormulas: { + readonly physical?: string | undefined + readonly elemental?: string | undefined + readonly duration?: string | undefined + } + readonly synergiesReceived: readonly SynergyBonusInfo[] + readonly synergiesGiven: readonly SynergyGivenInfo[] + readonly masteries: readonly number[] + readonly descLines?: readonly DescLineDefinition[] | undefined + readonly dsc2Lines?: readonly DescLineDefinition[] | undefined + readonly minionStats?: MinionBaseStats | undefined + readonly shortDesc: string + readonly shortDescZh: string + readonly longDesc: string + readonly longDescZh: string + readonly icon: string + readonly iconActive: string +} + +export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze(${JSON.stringify(updatedCatalog, null, 2)}) + +export const SKILLS_BY_ID: Readonly> = Object.freeze( + SKILLS_CATALOG.reduce>((acc, cur) => { + acc[cur.id] = cur + return acc + }, {}) +) + +export const SKILLS_BY_CLASS: Readonly> = Object.freeze({ + ama: SKILLS_CATALOG.filter(s => s.classCode === 'ama'), + sor: SKILLS_CATALOG.filter(s => s.classCode === 'sor'), + nec: SKILLS_CATALOG.filter(s => s.classCode === 'nec'), + pal: SKILLS_CATALOG.filter(s => s.classCode === 'pal'), + bar: SKILLS_CATALOG.filter(s => s.classCode === 'bar'), + dru: SKILLS_CATALOG.filter(s => s.classCode === 'dru'), + ass: SKILLS_CATALOG.filter(s => s.classCode === 'ass'), +}) +` + + const outPath = join(process.cwd(), 'src', 'data', 'skills-catalog.ts') + writeFileSync(outPath, tsContent, 'utf-8') + console.log(`Successfully updated ${outPath} with 1.13c ToHit and auraLenCalc!`) +} + +run().catch(err => { + console.error(err) + process.exit(1) +}) diff --git a/src/data/skills-catalog.ts b/src/data/skills-catalog.ts index c02e873..34d2a0f 100644 --- a/src/data/skills-catalog.ts +++ b/src/data/skills-catalog.ts @@ -70,6 +70,11 @@ export interface SkillCatalogEntry { readonly eMaxLev: readonly number[] readonly eLen: number readonly eLevLen: readonly number[] + readonly toHit: number + readonly toHitLev: number + readonly toHitCalc?: string | undefined + readonly auraLenCalc?: string | undefined + readonly startmana: number readonly params: Readonly> readonly synergyFormulas: { readonly physical?: string | undefined @@ -186,7 +191,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that does extra damage\ncreates a magical arrow or bolt", "longDescZh": "造成額外的傷害\n創造魔法箭或魔法彈", "icon": "/skills/icon_6.png", - "iconActive": "/skills/icon_6_active.png" + "iconActive": "/skills/icon_6_active.png", + "toHit": 10, + "toHitLev": 9, + "startmana": 0 }, { "id": 7, @@ -301,7 +309,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or bolts with fire\nmagically enhances your arrows", "longDescZh": "", "icon": "/skills/icon_7.png", - "iconActive": "/skills/icon_7_active.png" + "iconActive": "/skills/icon_7_active.png", + "toHit": 10, + "toHitLev": 9, + "startmana": 0 }, { "id": 8, @@ -407,7 +418,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "for you and your party\nmaking them easier to hit\nilluminates nearby enemies", "longDescZh": "讓你和你的隊伍成員\n更容易擊中目標\n照亮附近的敵人", "icon": "/skills/icon_8.png", - "iconActive": "/skills/icon_8_active.png" + "iconActive": "/skills/icon_8_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 9, @@ -495,7 +510,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - your attacks have a chance to do double damage", "longDescZh": "被動-你的攻擊可能對目標造成雙倍傷害", "icon": "/skills/icon_9.png", - "iconActive": "/skills/icon_9_active.png" + "iconActive": "/skills/icon_9_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 10, @@ -604,7 +622,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "using a javelin or spear class weapon\nattacks with a series of rapid thrusts", "longDescZh": "使用鏢槍或是長矛類的武器\n用快速的刺擊攻擊數下", "icon": "/skills/icon_10.png", - "iconActive": "/skills/icon_10_active.png" + "iconActive": "/skills/icon_10_active.png", + "toHit": 10, + "toHitLev": 9, + "startmana": 0 }, { "id": 11, @@ -726,7 +747,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "cold arrows only do half of their regular damage\nby adding cold damage and a slowing effect\nmagically enhances your arrows or bolts", "longDescZh": "", "icon": "/skills/icon_11.png", - "iconActive": "/skills/icon_11_active.png" + "iconActive": "/skills/icon_11_active.png", + "toHit": 10, + "toHitLev": 9, + "startmana": 0 }, { "id": 12, @@ -827,7 +851,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or bolt into many\nmagically splits one arrow", "longDescZh": "或十字弓彈,魔法般的\n由一根分為數根飛射", "icon": "/skills/icon_12.png", - "iconActive": "/skills/icon_12_active.png" + "iconActive": "/skills/icon_12_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 13, @@ -915,7 +942,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or standing still\na melee attack when attacking\npassive - you have a chance to dodge", "longDescZh": "或是在肉搏戰之中\n以站立不動的方式攻擊\n被動-你有機會閃避", "icon": "/skills/icon_13.png", - "iconActive": "/skills/icon_13_active.png" + "iconActive": "/skills/icon_13_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 14, @@ -1061,7 +1091,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "javelin and spear class weapons\nadds lightning damage to attacks with", "longDescZh": "鏢槍和長矛之類的武器\n可以附加閃電性質的傷害力", "icon": "/skills/icon_14.png", - "iconActive": "/skills/icon_14_active.png" + "iconActive": "/skills/icon_14_active.png", + "toHit": 20, + "toHitLev": 12, + "startmana": 0 }, { "id": 15, @@ -1160,7 +1193,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to leave a trail of poison clouds\nmagically enhances your javelin", "longDescZh": "你的鏢槍會魔法般的\n留下毒氣般的雲霧", "icon": "/skills/icon_15.png", - "iconActive": "/skills/icon_15_active.png" + "iconActive": "/skills/icon_15_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 16, @@ -1277,7 +1313,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "contact, damaging all nearby enemies\nenchants an arrow or bolt that explodes on", "longDescZh": "", "icon": "/skills/icon_16.png", - "iconActive": "/skills/icon_16_active.png" + "iconActive": "/skills/icon_16_active.png", + "toHit": 20, + "toHitLev": 9, + "startmana": 0 }, { "id": 17, @@ -1387,7 +1426,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "illuminates nearby enemies and slows their ranged attacks", "longDescZh": "在這道光明之中的敵人,他們的長距離攻擊都會減慢", "icon": "/skills/icon_17.png", - "iconActive": "/skills/icon_17_active.png" + "iconActive": "/skills/icon_17_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 18, @@ -1477,7 +1520,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "when attacking or standing still\npassive - you have a chance to dodge enemy missiles", "longDescZh": "當你站立不動或是受到攻擊\n被動-你機會躲開敵人的飛射武器", "icon": "/skills/icon_18.png", - "iconActive": "/skills/icon_18_active.png" + "iconActive": "/skills/icon_18_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 19, @@ -1593,7 +1639,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "increases attack damage but rapidly degrades the weapon", "longDescZh": "可以增加破壞力,但是武器的狀況會迅速下降", "icon": "/skills/icon_19.png", - "iconActive": "/skills/icon_19_active.png" + "iconActive": "/skills/icon_19_active.png", + "toHit": 100, + "toHitLev": 25, + "startmana": 0 }, { "id": 20, @@ -1756,7 +1805,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "magically converts your javelin into a bolt of lightning", "longDescZh": "魔法般的把你的鏢槍變成一個閃電球", "icon": "/skills/icon_20.png", - "iconActive": "/skills/icon_20_active.png" + "iconActive": "/skills/icon_20_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 21, @@ -1884,7 +1936,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to freeze your enemies\nmagically enhances your arrow or bolt", "longDescZh": "", "icon": "/skills/icon_21.png", - "iconActive": "/skills/icon_21_active.png" + "iconActive": "/skills/icon_21_active.png", + "toHit": 20, + "toHitLev": 9, + "startmana": 0 }, { "id": 22, @@ -1977,7 +2032,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "always hits\nor seek one of its own\nto track your target\nenhances your arrows and bolts", "longDescZh": "一定擊中\n或是自行找尋目標\n並追蹤你的目標\n可附加在你的箭矢或十字弓彈上", "icon": "/skills/icon_22.png", - "iconActive": "/skills/icon_22_active.png" + "iconActive": "/skills/icon_22_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 23, @@ -2070,7 +2128,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - increases your attack rating", "longDescZh": "被動-增加準確率", "icon": "/skills/icon_23.png", - "iconActive": "/skills/icon_23_active.png" + "iconActive": "/skills/icon_23_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 24, @@ -2223,7 +2284,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and releases charged bolts upon impact\nadds lightning damage to javelin and spear class weapons", "longDescZh": "並在接觸的時候將光彈的能量釋放出來\n在標槍或是長矛上面附加閃電性傷害", "icon": "/skills/icon_24.png", - "iconActive": "/skills/icon_24_active.png" + "iconActive": "/skills/icon_24_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 25, @@ -2334,7 +2398,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "expanding clouds of poison upon impact\nmagically enhances your javelin to release", "longDescZh": "爆出毒氣煙霧\n在撞擊時魔法般的從你的標槍", "icon": "/skills/icon_25.png", - "iconActive": "/skills/icon_25_active.png" + "iconActive": "/skills/icon_25_active.png", + "toHit": 30, + "toHitLev": 9, + "startmana": 0 }, { "id": 26, @@ -2448,7 +2515,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that target multiple nearby enemies\nmagically splits one arrow into several", "longDescZh": "會瞄準數個靠近的敵人\n讓射出的箭矢自動分裂攻擊", "icon": "/skills/icon_26.png", - "iconActive": "/skills/icon_26_active.png" + "iconActive": "/skills/icon_26_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 27, @@ -2564,7 +2634,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "creates a pyre upon impact\ncause severe fire damage and\nenhances arrows or bolts to", "longDescZh": "", "icon": "/skills/icon_27.png", - "iconActive": "/skills/icon_27_active.png" + "iconActive": "/skills/icon_27_active.png", + "toHit": 30, + "toHitLev": 9, + "startmana": 0 }, { "id": 28, @@ -2674,7 +2747,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that draws fire from enemies\ncreates a duplicate of yourself", "longDescZh": "可以吸引敵人攻擊\n你所創造出來的分身", "icon": "/skills/icon_28.png", - "iconActive": "/skills/icon_28_active.png" + "iconActive": "/skills/icon_28_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 29, @@ -2764,7 +2840,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "when walking or running\na melee or missile attack\npassive - you have a chance to dodge", "longDescZh": "當你在行走或是跑步時有機會\n避開來襲的近戰攻擊或飛箭\n被動-你有機會迴避", "icon": "/skills/icon_29.png", - "iconActive": "/skills/icon_29_active.png" + "iconActive": "/skills/icon_29_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 30, @@ -2867,7 +2946,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "attacks all adjacent targets", "longDescZh": "攻擊所有鄰近的目標", "icon": "/skills/icon_30.png", - "iconActive": "/skills/icon_30_active.png" + "iconActive": "/skills/icon_30_active.png", + "toHit": 40, + "toHitLev": 10, + "startmana": 0 }, { "id": 31, @@ -3000,7 +3082,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to freeze entire groups of monsters\nmagically enhances an arrow or bolt", "longDescZh": "", "icon": "/skills/icon_31.png", - "iconActive": "/skills/icon_31_active.png" + "iconActive": "/skills/icon_31_active.png", + "toHit": 40, + "toHitLev": 9, + "startmana": 0 }, { "id": 32, @@ -3126,7 +3211,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "summons a powerful Valkyrie ally", "longDescZh": "召喚一個威力強大的女武神同伴", "icon": "/skills/icon_32.png", - "iconActive": "/skills/icon_32_active.png" + "iconActive": "/skills/icon_32_active.png", + "toHit": 0, + "toHitLev": 0, + "toHitCalc": "40*lvl+40*skill('Penetrate'.blvl)", + "startmana": 0 }, { "id": 33, @@ -3214,7 +3303,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "pass through enemies that they hit\npassive - your missiles have a chance to", "longDescZh": "在擊中時穿過敵人繼續飛行\n被動-你的飛射武器,有機會", "icon": "/skills/icon_33.png", - "iconActive": "/skills/icon_33_active.png" + "iconActive": "/skills/icon_33_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 34, @@ -3359,7 +3451,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and releases chain lightning upon impact\nadds lightning damage to javelin and spear class weapons", "longDescZh": "加上閃電力量並在撞擊時釋放出來\n在鏢槍或是長矛性的武器", "icon": "/skills/icon_34.png", - "iconActive": "/skills/icon_34_active.png" + "iconActive": "/skills/icon_34_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 35, @@ -3507,7 +3602,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "bolt of lightning that splits on impact\nchanges a thrown javelin into a powerful", "longDescZh": "強大的閃電之球,並在撞擊時自動散開\n把投擲性的鏢槍變成一個威力", "icon": "/skills/icon_35.png", - "iconActive": "/skills/icon_35_active.png" + "iconActive": "/skills/icon_35_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 36, @@ -3635,7 +3733,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "creates a magical flaming missile", "longDescZh": "創造出魔法性的火焰箭", "icon": "/skills/icon_36.png", - "iconActive": "/skills/icon_36_active.png" + "iconActive": "/skills/icon_36_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 37, @@ -3750,7 +3851,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - increases the rate at which you recover mana", "longDescZh": "被動-讓你的法力回復速度加快", "icon": "/skills/icon_37.png", - "iconActive": "/skills/icon_37_active.png" + "iconActive": "/skills/icon_37_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 38, @@ -3866,7 +3970,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "bolts of electrical energy\ncreates multiple, randomly directed", "longDescZh": "電擊性能量球\n創造出多重、方向不定的", "icon": "/skills/icon_38.png", - "iconActive": "/skills/icon_38_active.png" + "iconActive": "/skills/icon_38_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 39, @@ -4018,7 +4125,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that damages and slows your enemies\ncreates a magical bolt of ice", "longDescZh": "讓你的敵人受傷並行動減慢\n創造出一個魔法性的冰之碎片", "icon": "/skills/icon_39.png", - "iconActive": "/skills/icon_39_active.png" + "iconActive": "/skills/icon_39_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 40, @@ -4142,7 +4252,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and freezes enemies that hit you\nincreases your defense rating", "longDescZh": "並讓敵人擊中你時受到冰封\n可以增加你的防禦等級", "icon": "/skills/icon_40.png", - "iconActive": "/skills/icon_40_active.png" + "iconActive": "/skills/icon_40_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34+(skill('Shiver Armor'.blvl)+skill('Chilling Armor'.blvl))*par7", + "startmana": 0 }, { "id": 41, @@ -4274,7 +4388,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to scorch your enemies\ncreates a continuous jet of flame", "longDescZh": "燒焦你的敵人\n創造出一團噴射性火焰", "icon": "/skills/icon_41.png", - "iconActive": "/skills/icon_41_active.png" + "iconActive": "/skills/icon_41_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 6 }, { "id": 42, @@ -4376,7 +4493,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "of all nearby enemies\ncreates an electrical field that reduces life", "longDescZh": "所有敵人的生命\n創造一道閃電結界,降低附近", "icon": "/skills/icon_42.png", - "iconActive": "/skills/icon_42_active.png" + "iconActive": "/skills/icon_42_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 43, @@ -4462,7 +4582,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and knock back enemies\npick up items, use objects,\nuses the power of your mind to", "longDescZh": "並擊退敵人\n拾取物品,使用物品\n利用你的意志力量來", "icon": "/skills/icon_43.png", - "iconActive": "/skills/icon_43_active.png" + "iconActive": "/skills/icon_43_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 44, @@ -4574,7 +4697,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and slows all nearby enemies\ncreates an expanding ring of ice that damages", "longDescZh": "減慢所有附近的敵人\n創造出一個不斷擴張的冰之環,傷害並", "icon": "/skills/icon_44.png", - "iconActive": "/skills/icon_44_active.png" + "iconActive": "/skills/icon_44_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 45, @@ -4719,7 +4845,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "damages and freezes your enemy\ncreates a magical sphere of ice that", "longDescZh": "重傷並冰凍你的敵人\n創造出一個魔法冰球,", "icon": "/skills/icon_45.png", - "iconActive": "/skills/icon_45_active.png" + "iconActive": "/skills/icon_45_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 46, @@ -4830,7 +4959,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "in your wake to scorch your enemies\ncreates a wall of fire", "longDescZh": "如同你復甦一樣燒烤敵人\n創造出一道火牆", "icon": "/skills/icon_46.png", - "iconActive": "/skills/icon_46_active.png" + "iconActive": "/skills/icon_46_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "dm12", + "startmana": 0 }, { "id": 47, @@ -4965,7 +5098,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to engulf your enemies\ncreates an explosive sphere of fiery death", "longDescZh": "將敵人吞沒\n創造出一團爆炸性的致命球體", "icon": "/skills/icon_47.png", - "iconActive": "/skills/icon_47_active.png" + "iconActive": "/skills/icon_47_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 48, @@ -5066,7 +5202,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to shock nearby enemies\ncreates an expanding ring of lightning", "longDescZh": "電擊附近的所有敵人\n創造出一個充滿電擊力量之環", "icon": "/skills/icon_48.png", - "iconActive": "/skills/icon_48_active.png" + "iconActive": "/skills/icon_48_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 49, @@ -5199,7 +5338,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to lay waste to your enemies\ncreates a powerful lightning bolt", "longDescZh": "毀掉你的敵人\n創造一個威力強大的閃電之球", "icon": "/skills/icon_49.png", - "iconActive": "/skills/icon_49_active.png" + "iconActive": "/skills/icon_49_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 50, @@ -5334,7 +5476,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "freezes and damages enemies that hit you\nincreases your defense rating", "longDescZh": "並且可以讓擊中你的敵人冰封並受傷\n增加你的防禦等級", "icon": "/skills/icon_50.png", - "iconActive": "/skills/icon_50_active.png" + "iconActive": "/skills/icon_50_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34+(skill('Frozen Armor'.blvl)+skill('Chilling Armor'.blvl))*par7", + "startmana": 0 }, { "id": 51, @@ -5453,7 +5599,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "blocks or burns your enemies\ncreates a wall of flame that", "longDescZh": "阻檔或是焚燒你的敵人\n創造一道火焰之牆", "icon": "/skills/icon_51.png", - "iconActive": "/skills/icon_51_active.png" + "iconActive": "/skills/icon_51_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 52, @@ -5570,7 +5719,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "adds one-third fire damage to ranged weapons\nadds fire damage to melee weapons\nenchants equipped weapon of targeted character or minion", "longDescZh": "讓長距離武器增加三分之一的破壞力\n在近戰武器附上火焰性傷害\n讓施法目標的人物或是隨從的武器變強", "icon": "/skills/icon_52.png", - "iconActive": "/skills/icon_52_active.png" + "iconActive": "/skills/icon_52_active.png", + "toHit": 20, + "toHitLev": 9, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 53, @@ -5697,7 +5850,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "arcs through several targets\ncreates a bolt of lightning that", "longDescZh": "其中的電弧會流經數個敵人身上\n創造一個閃電之球撞擊後", "icon": "/skills/icon_53.png", - "iconActive": "/skills/icon_53_active.png" + "iconActive": "/skills/icon_53_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 54, @@ -5776,7 +5932,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "instantly moves to a destination within your line of sight", "longDescZh": "馬上移動到你視線所及的目的地", "icon": "/skills/icon_54.png", - "iconActive": "/skills/icon_54_active.png" + "iconActive": "/skills/icon_54_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 55, @@ -5922,7 +6081,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that freezes or kills nearby enemies\ncreates a magical ice comet", "longDescZh": "凍結或是殺死臨近的敵人\n創造出一個魔法的尖冰彗星", "icon": "/skills/icon_55.png", - "iconActive": "/skills/icon_55_active.png" + "iconActive": "/skills/icon_55_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34 * (100 + skill('Blizzard'.blvl) * par7) / 100", + "startmana": 0 }, { "id": 56, @@ -6049,7 +6212,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to crush and incinerate your enemies\nsummons a meteor from the heavens", "longDescZh": "壓碎並焚化你的敵人\n從天堂召喚一枚隕石", "icon": "/skills/icon_56.png", - "iconActive": "/skills/icon_56_active.png" + "iconActive": "/skills/icon_56_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 57, @@ -6148,7 +6314,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "your enemies with bolts of lightning\nsummons a deadly thunderstorm that strikes", "longDescZh": "以連續的電球攻擊你的敵人\n召喚致命的雷電風暴", "icon": "/skills/icon_57.png", - "iconActive": "/skills/icon_57_active.png" + "iconActive": "/skills/icon_57_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 58, @@ -6248,7 +6418,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "instead of health when you take damage\ncreates a magical shield that consumes mana", "longDescZh": "當你受傷時代替耗損的生命\n形成一個耗損法力的魔法護盾", "icon": "/skills/icon_58.png", - "iconActive": "/skills/icon_58_active.png" + "iconActive": "/skills/icon_58_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 59, @@ -6384,7 +6558,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "summons massive shards of ice to destroy your enemies", "longDescZh": "召喚大量的冰塊並摧毀你的敵人", "icon": "/skills/icon_59.png", - "iconActive": "/skills/icon_59_active.png" + "iconActive": "/skills/icon_59_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 60, @@ -6513,7 +6690,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "against ranged attackers\nincreases defense and discharges an ice bolt in retaliation", "longDescZh": "射出復仇的冰彈\n增加防禦力,並對以遠距離攻擊你的敵人", "icon": "/skills/icon_60.png", - "iconActive": "/skills/icon_60_active.png" + "iconActive": "/skills/icon_60_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34+(skill('Frozen Armor'.blvl)+skill('Shiver Armor'.blvl))*par7", + "startmana": 0 }, { "id": 61, @@ -6602,7 +6783,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - increases all damage caused by your fire spells", "longDescZh": "被動-讓你的火焰法術的傷害力量變強", "icon": "/skills/icon_61.png", - "iconActive": "/skills/icon_61_active.png" + "iconActive": "/skills/icon_61_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 62, @@ -6720,7 +6904,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to reduce your enemies to ashes\nsummons a multi-headed beast of flame", "longDescZh": "把你的敵人化為灰\n召喚多頭的火焰獸", "icon": "/skills/icon_62.png", - "iconActive": "/skills/icon_62_active.png" + "iconActive": "/skills/icon_62_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 63, @@ -6809,7 +6996,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - increases all damage caused by your lightning spells", "longDescZh": "被動-增加閃電法術所造成的所有傷害", "icon": "/skills/icon_63.png", - "iconActive": "/skills/icon_63_active.png" + "iconActive": "/skills/icon_63_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 64, @@ -6937,7 +7127,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to lay waste to your enemies\ncreates a magical globe that sprays a torrent of ice bolts", "longDescZh": "毀掉你的敵人\n創造一個魔法之球噴出大量的冰彈", "icon": "/skills/icon_64.png", - "iconActive": "/skills/icon_64_active.png" + "iconActive": "/skills/icon_64_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 65, @@ -7023,7 +7216,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "by piercing enemies' resistances to cold\npassive - increases the damage of your cold attacks", "longDescZh": "增加冰系法術的傷害\n被動-以提供刺穿能力來", "icon": "/skills/icon_65.png", - "iconActive": "/skills/icon_65_active.png" + "iconActive": "/skills/icon_65_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 66, @@ -7133,7 +7329,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "the non-magic damage they receive\ncurses a group of enemies, increasing", "longDescZh": "受到非魔法性傷害時受更重的傷\n詛咒一群敵人,讓他們", "icon": "/skills/icon_66.png", - "iconActive": "/skills/icon_66_active.png" + "iconActive": "/skills/icon_66_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 67, @@ -7265,7 +7465,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "fires a barrage of summoned barbed teeth", "longDescZh": "召喚倒鉤般的牙,連續射向目標", "icon": "/skills/icon_67.png", - "iconActive": "/skills/icon_67_active.png" + "iconActive": "/skills/icon_67_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 68, @@ -7357,7 +7560,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that absorbs melee damage\ncreates an orbiting shield of bone", "longDescZh": "可以吸收近戰中的傷害\n創造出一個環繞著你的白骨外殼", "icon": "/skills/icon_68.png", - "iconActive": "/skills/icon_68_active.png" + "iconActive": "/skills/icon_68_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 69, @@ -7499,7 +7705,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "of raised skeletons and revived creatures\npassive - increases life and damage", "longDescZh": "和召喚生物的生命值和傷害力\n被動-增加你所復甦骷髏", "icon": "/skills/icon_69.png", - "iconActive": "/skills/icon_69_active.png" + "iconActive": "/skills/icon_69_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 70, @@ -7634,7 +7843,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "fights for you\nthis raises a skeleton warrior that\ncast on the corpse of a slain monster,", "longDescZh": "並為你而戰\n會讓一個骷髏戰士從中甦醒\n施展在一個已死的怪物身上,", "icon": "/skills/icon_70.png", - "iconActive": "/skills/icon_70_active.png" + "iconActive": "/skills/icon_70_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 71, @@ -7731,7 +7943,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "reducing their vision radius\ncurses a group of monsters,", "longDescZh": "降低它們的可見範圍\n詛咒一群怪物,", "icon": "/skills/icon_71.png", - "iconActive": "/skills/icon_71_active.png" + "iconActive": "/skills/icon_71_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 72, @@ -7842,7 +8058,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "reducing the amount of damage they inflict\ncurses a group of enemies,", "longDescZh": "降低他們所能造成的傷害\n詛咒一群敵人,", "icon": "/skills/icon_72.png", - "iconActive": "/skills/icon_72_active.png" + "iconActive": "/skills/icon_72_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 73, @@ -7963,7 +8183,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "adds poison to your dagger attacks", "longDescZh": "讓匕首攻擊的目標中毒", "icon": "/skills/icon_73.png", - "iconActive": "/skills/icon_73_active.png" + "iconActive": "/skills/icon_73_active.png", + "toHit": 30, + "toHitLev": 20, + "startmana": 0 }, { "id": 74, @@ -8069,7 +8292,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "it explodes, damaging nearby enemies\ncast on the corpse of a slain monster,", "longDescZh": "如果它爆炸的話,打傷附近的敵人\n施在已經死亡的怪物屍體上,", "icon": "/skills/icon_74.png", - "iconActive": "/skills/icon_74_active.png" + "iconActive": "/skills/icon_74_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 75, @@ -8199,7 +8425,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to fight by your side\ncreates a golem from the earth", "longDescZh": "站在你這邊作戰\n從大地創造出一個石魔", "icon": "/skills/icon_75.png", - "iconActive": "/skills/icon_75_active.png" + "iconActive": "/skills/icon_75_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 76, @@ -8306,7 +8535,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to damage themselves when damaging others\ncurses a group of enemies, causing them", "longDescZh": "在傷害其他人時也傷到自己\n詛咒一群敵人,讓他們", "icon": "/skills/icon_76.png", - "iconActive": "/skills/icon_76_active.png" + "iconActive": "/skills/icon_76_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 77, @@ -8407,7 +8640,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "causing them to flee in terror\ncurses a group of monsters,", "longDescZh": "恐懼中逃之夭夭\n導致一群怪物在", "icon": "/skills/icon_77.png", - "iconActive": "/skills/icon_77_active.png" + "iconActive": "/skills/icon_77_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 78, @@ -8529,7 +8766,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "of bone and debris\ncreates an impassable barrier", "longDescZh": "所組成的的障礙,完全無法通過\n創造一道由骨頭和碎片", "icon": "/skills/icon_78.png", - "iconActive": "/skills/icon_78_active.png" + "iconActive": "/skills/icon_78_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 79, @@ -8642,7 +8882,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "Enhances Speed and Life of all your Golems", "longDescZh": "加強你所有召喚石魔的生命和速度", "icon": "/skills/icon_79.png", - "iconActive": "/skills/icon_79_active.png" + "iconActive": "/skills/icon_79_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 80, @@ -8753,7 +8996,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "fights for you\nthis raises a skeleton mage that\ncast on the corpse of a slain monster,", "longDescZh": "並為你而戰\n可以讓一個骷髏法師站起來\n施展在一個已死的怪物屍體上,", "icon": "/skills/icon_80.png", - "iconActive": "/skills/icon_80_active.png" + "iconActive": "/skills/icon_80_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 81, @@ -8852,7 +9098,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "curses a monster to force it to attack random targets", "longDescZh": "詛咒一個怪物,逼他隨意攻擊目標", "icon": "/skills/icon_81.png", - "iconActive": "/skills/icon_81_active.png" + "iconActive": "/skills/icon_81_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 82, @@ -8964,7 +9214,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "damaging them gives the attacker life\ncurses a group of monsters so that", "longDescZh": "受到的傷害流向攻擊者,成為生命點數\n詛咒一群怪物讓它們", "icon": "/skills/icon_82.png", - "iconActive": "/skills/icon_82_active.png" + "iconActive": "/skills/icon_82_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 83, @@ -9078,7 +9332,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that poisons nearby monsters\ntoxic gas is released\ncast on the corpse of a slain monster,", "longDescZh": "讓附近的怪物統統中毒\n放出大量的劇毒\n施展在一個已死的怪物屍體上,", "icon": "/skills/icon_83.png", - "iconActive": "/skills/icon_83_active.png" + "iconActive": "/skills/icon_83_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 84, @@ -9213,7 +9470,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "summons a deadly spike of bone to impale your enemies", "longDescZh": "召喚一把致命的白骨長矛刺穿你的敵人", "icon": "/skills/icon_84.png", - "iconActive": "/skills/icon_84_active.png" + "iconActive": "/skills/icon_84_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 85, @@ -9340,7 +9600,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "it steals and damage it receives\ncreates a golem that shares with you the life", "longDescZh": "它會承受它的偷取和傷害\n創造出一個和你共享生命的石魔", "icon": "/skills/icon_85.png", - "iconActive": "/skills/icon_85_active.png" + "iconActive": "/skills/icon_85_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 86, @@ -9437,7 +9700,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "this curse may not be overridden by another curse\ntarget of all nearby monsters\ncurses a monster to become the", "longDescZh": "此詛咒無法被其他詛咒撤銷\n其他週邊的怪物目標,\n詛咒一隻怪物變成", "icon": "/skills/icon_86.png", - "iconActive": "/skills/icon_86_active.png" + "iconActive": "/skills/icon_86_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 87, @@ -9537,7 +9804,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "slow, weak and take amplified damage\ncurses a group of enemies to make them", "longDescZh": "緩慢、衰弱而且受攻擊時生命大量降低\n詛咒一群敵人並讓它們", "icon": "/skills/icon_87.png", - "iconActive": "/skills/icon_87_active.png" + "iconActive": "/skills/icon_87_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 88, @@ -9657,7 +9928,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "creates a barrier of fossilized bone around your target", "longDescZh": "在你的目標附近創造出一堆化石般的骨頭", "icon": "/skills/icon_88.png", - "iconActive": "/skills/icon_88_active.png" + "iconActive": "/skills/icon_88_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 89, @@ -9748,7 +10022,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "of all summoned creatures\npassive - increases the resistances", "longDescZh": "增加它們的反抗力\n被動-為所有召喚而來的生物", "icon": "/skills/icon_89.png", - "iconActive": "/skills/icon_89_active.png" + "iconActive": "/skills/icon_89_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 90, @@ -9882,7 +10159,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "the properties of the item\ntransforms a metallic item into a golem that gains", "longDescZh": "該物品的特性\n把一個金屬的物品轉換成石魔,並獲得", "icon": "/skills/icon_90.png", - "iconActive": "/skills/icon_90_active.png" + "iconActive": "/skills/icon_90_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 91, @@ -9992,7 +10272,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "lowers maximum resistances of hostile players\nlowers resistances of monsters\ncurses an enemy to take more damage from all magical attacks", "longDescZh": "降低敵對玩者的最大防禦力\n降低怪物的抵抗力\n詛咒一個敵人在受到魔法攻擊時,會受到更多的傷害", "icon": "/skills/icon_91.png", - "iconActive": "/skills/icon_91_active.png" + "iconActive": "/skills/icon_91_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 92, @@ -10103,7 +10387,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "emits an expanding ring of concentrated poison", "longDescZh": "發出一個不斷擴張的劇毒之環", "icon": "/skills/icon_92.png", - "iconActive": "/skills/icon_92_active.png" + "iconActive": "/skills/icon_92_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 93, @@ -10226,7 +10513,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "tracks its target or finds one of its own\nreleases a spirit of the restless undead that", "longDescZh": "開始追擊它的目標或是自行找個目標\n釋放出一個不知疲累的不死生物", "icon": "/skills/icon_93.png", - "iconActive": "/skills/icon_93_active.png" + "iconActive": "/skills/icon_93_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 94, @@ -10361,7 +10651,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "it receives from fire into life\ncreates a golem that converts the damage", "longDescZh": "火焰傷害轉換成生命\n創造一個石魔,可以把承受到的", "icon": "/skills/icon_94.png", - "iconActive": "/skills/icon_94_active.png" + "iconActive": "/skills/icon_94_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 95, @@ -10480,7 +10773,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to fight by your side\nreturns a monster to life", "longDescZh": "為你而戰\n讓一個怪物重獲生命", "icon": "/skills/icon_95.png", - "iconActive": "/skills/icon_95_active.png" + "iconActive": "/skills/icon_95_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 96, @@ -10595,7 +10891,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "at the cost of life\nincreased accuracy and damage", "longDescZh": "但以生命為代價\n增加準確性和傷害性", "icon": "/skills/icon_96.png", - "iconActive": "/skills/icon_96_active.png" + "iconActive": "/skills/icon_96_active.png", + "toHit": 20, + "toHitLev": 7, + "startmana": 0 }, { "id": 97, @@ -10695,7 +10994,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "by bashing it with your shield\ntemporarily stun your enemy", "longDescZh": "利用你的盾牌重擊\n可以暫時讓對手昏眩", "icon": "/skills/icon_97.png", - "iconActive": "/skills/icon_97_active.png" + "iconActive": "/skills/icon_97_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 98, @@ -10796,7 +11098,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "done by you and your party\nwhen active, aura increases the damage", "longDescZh": "隊伍成員所造成的傷害\n當啟用時,靈氣會增加你和", "icon": "/skills/icon_98.png", - "iconActive": "/skills/icon_98_active.png" + "iconActive": "/skills/icon_98_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 99, @@ -10891,7 +11196,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "the life of you and your party\nwhen active, aura slowly regenerates", "longDescZh": "的為你和你的隊伍補充生命\n當啟用時,靈氣會慢慢", "icon": "/skills/icon_99.png", - "iconActive": "/skills/icon_99_active.png" + "iconActive": "/skills/icon_99_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 100, @@ -10996,7 +11304,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "done to you and your party\nwhen active, aura decreases fire damage", "longDescZh": "所受到的火焰傷害\n當啟用時,靈氣會降低你和你的隊伍", "icon": "/skills/icon_100.png", - "iconActive": "/skills/icon_100_active.png" + "iconActive": "/skills/icon_100_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 101, @@ -11107,7 +11418,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or heals allies\nthat damages undead enemies\na bolt of divine energy", "longDescZh": "或治療你的同盟\n可以傷害不死的敵人\n一個神屬的能源彈", "icon": "/skills/icon_101.png", - "iconActive": "/skills/icon_101_active.png" + "iconActive": "/skills/icon_101_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 102, @@ -11234,7 +11548,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "with heavenly flames\nwhen active, aura damages nearby enemies", "longDescZh": "傷害附近的敵人\n當啟用時,靈氣會以天堂之火", "icon": "/skills/icon_102.png", - "iconActive": "/skills/icon_102_active.png" + "iconActive": "/skills/icon_102_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 103, @@ -11331,7 +11648,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "back at your attacker\nwhen active, aura reflects damage done to you", "longDescZh": "造成的傷害反彈回去\n當啟用時,靈氣會把敵人對你", "icon": "/skills/icon_103.png", - "iconActive": "/skills/icon_103_active.png" + "iconActive": "/skills/icon_103_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 104, @@ -11433,7 +11753,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "of you and your party\nwhen active, aura increases the defense rating", "longDescZh": "隊伍增加防禦等級\n當啟用時,靈氣會為你和你的", "icon": "/skills/icon_104.png", - "iconActive": "/skills/icon_104_active.png" + "iconActive": "/skills/icon_104_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 105, @@ -11540,7 +11863,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "done to you and your party\nwhen active, aura decreases cold damage", "longDescZh": "你的隊伍所受到的傷害\n當啟用時,靈氣會降低你和", "icon": "/skills/icon_105.png", - "iconActive": "/skills/icon_105_active.png" + "iconActive": "/skills/icon_105_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 106, @@ -11653,7 +11979,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "with a single attack\nallows you to attack multiple adjacent enemies", "longDescZh": "攻擊數個鄰近的敵人\n讓你可以在揮動一次武器時", "icon": "/skills/icon_106.png", - "iconActive": "/skills/icon_106_active.png" + "iconActive": "/skills/icon_106_active.png", + "toHit": 10, + "toHitLev": 10, + "startmana": 0 }, { "id": 107, @@ -11760,7 +12089,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "charge into battle and attack an enemy", "longDescZh": "突擊衝入戰場,並攻擊一個敵人", "icon": "/skills/icon_107.png", - "iconActive": "/skills/icon_107_active.png" + "iconActive": "/skills/icon_107_active.png", + "toHit": 50, + "toHitLev": 15, + "startmana": 0 }, { "id": 108, @@ -11872,7 +12204,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "for you and your party\nwhen active, aura increases the attack rating", "longDescZh": "你的隊伍的準確率 \n當啟用時,靈氣會增加你和", "icon": "/skills/icon_108.png", - "iconActive": "/skills/icon_108_active.png" + "iconActive": "/skills/icon_108_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 109, @@ -12002,7 +12337,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "will remain poisoned or cursed\nof time you and your party\nwhen active, aura reduces the length", "longDescZh": "毒氣和詛咒的持續時間\n你和你的隊伍受\n當啟用時,靈氣會降低", "icon": "/skills/icon_109.png", - "iconActive": "/skills/icon_109_active.png" + "iconActive": "/skills/icon_109_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 110, @@ -12109,7 +12447,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "done to you and your party\nwhen active, aura decreases lightning damage", "longDescZh": "對你和隊伍所造成的傷害\n當啟用時,靈氣會降低閃電", "icon": "/skills/icon_110.png", - "iconActive": "/skills/icon_110_active.png" + "iconActive": "/skills/icon_110_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 111, @@ -12250,7 +12591,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to each successful attack\nfire, lightning and cold damage are added", "longDescZh": "火焰、閃電和冰凍的傷害\n在每一次成功的攻擊中,會附加", "icon": "/skills/icon_111.png", - "iconActive": "/skills/icon_111_active.png" + "iconActive": "/skills/icon_111_active.png", + "toHit": 20, + "toHitLev": 10, + "startmana": 0 }, { "id": 112, @@ -12363,7 +12707,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "150 Percent Damage to Undead\nspirals outwards damaging enemies it hits\nsummons an ethereal hammer that", "longDescZh": "對不死系生物造成150%的傷害\n朝敵人旋轉攻擊\n召喚一把永久的鐵鎚", "icon": "/skills/icon_112.png", - "iconActive": "/skills/icon_112_active.png" + "iconActive": "/skills/icon_112_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 113, @@ -12477,7 +12824,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that the attack will be interrupted for you and your party \nwhen active, aura increases the damage and decreases the chance", "longDescZh": "增加破壞力,並降低攻擊中斷的可能\n當啟用時,靈氣會讓你和你的隊伍", "icon": "/skills/icon_113.png", - "iconActive": "/skills/icon_113_active.png" + "iconActive": "/skills/icon_113_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 114, @@ -12613,7 +12963,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "when active, aura freezes nearby monsters", "longDescZh": "當啟用時,靈氣會凍住附近的怪物", "icon": "/skills/icon_114.png", - "iconActive": "/skills/icon_114_active.png" + "iconActive": "/skills/icon_114_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 115, @@ -12745,7 +13098,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and movement speed for you and your party\nwhen active, aura increases stamina recovery rate, maximum stamina", "longDescZh": "耐力的上限以及你的移動速度\n當啟用時,靈氣會增加你和你隊伍的耐力回復速度", "icon": "/skills/icon_115.png", - "iconActive": "/skills/icon_115_active.png" + "iconActive": "/skills/icon_115_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 116, @@ -12847,7 +13203,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "other foul demons and beasts\nconverts monsters to fight against", "longDescZh": "轉頭攻擊惡魔和怪物\n讓正在和你作戰的怪物", "icon": "/skills/icon_116.png", - "iconActive": "/skills/icon_116_active.png" + "iconActive": "/skills/icon_116_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 117, @@ -12967,7 +13327,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "enhances your shield with divine power", "longDescZh": "利用神屬的力量來加強你的盾牌", "icon": "/skills/icon_117.png", - "iconActive": "/skills/icon_117_active.png" + "iconActive": "/skills/icon_117_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 118, @@ -13095,7 +13459,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "adds lightning damage to your attack\nto damage nearby enemies\nwhen active, aura causes pulses of electricity", "longDescZh": "在你的攻擊中增加閃電傷害\n傷害週邊的怪物,\n當動作時,靈氣引發電擊脈衝", "icon": "/skills/icon_118.png", - "iconActive": "/skills/icon_118_active.png" + "iconActive": "/skills/icon_118_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 119, @@ -13213,7 +13580,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and knocks them back\nwhen active, aura damages the undead", "longDescZh": "並逼退它們\n當啟用時,靈氣會傷害不死系生物", "icon": "/skills/icon_119.png", - "iconActive": "/skills/icon_119_active.png" + "iconActive": "/skills/icon_119_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 120, @@ -13333,7 +13703,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "for you and your party\nwhen active, aura increases mana recovery", "longDescZh": "你和你隊伍的法力回復速度\n當啟用時,靈氣會增加", "icon": "/skills/icon_120.png", - "iconActive": "/skills/icon_120_active.png" + "iconActive": "/skills/icon_120_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 121, @@ -13445,7 +13818,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "seek out nearby enemies\nlightning strikes your target as holy bolts", "longDescZh": "附近的敵人,並直擊你的目標\n神聖的閃電光彈會自動找出", "icon": "/skills/icon_121.png", - "iconActive": "/skills/icon_121_active.png" + "iconActive": "/skills/icon_121_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 122, @@ -13577,7 +13953,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and attack rating for you and your party\nwhen active, aura increases damage, attack speed,", "longDescZh": "傷害、攻擊速度和準確率\n當你發揮之後,靈氣會增加你和你隊伍人士的", "icon": "/skills/icon_122.png", - "iconActive": "/skills/icon_122_active.png" + "iconActive": "/skills/icon_122_active.png", + "toHit": 40, + "toHitLev": 5, + "startmana": 0 }, { "id": 123, @@ -13688,7 +14067,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and resistances of nearby enemies\nwhen active, aura reduces the defenses", "longDescZh": "防禦力量和抵抗力量\n當啟用時,靈氣會降低附近敵人的", "icon": "/skills/icon_123.png", - "iconActive": "/skills/icon_123_active.png" + "iconActive": "/skills/icon_123_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 124, @@ -13799,7 +14181,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "you life and mana\nthe souls of slain enemies to give\nwhen active, aura attempts to redeem", "longDescZh": "中的人員重獲法力和生命\n被殺死敵人的靈魂,讓你和隊伍\n當啟用時,靈氣會試著利用", "icon": "/skills/icon_124.png", - "iconActive": "/skills/icon_124_active.png" + "iconActive": "/skills/icon_124_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 125, @@ -13916,7 +14301,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "done to you and your party\nwhen active, aura decreases fire, cold and lightning damage", "longDescZh": "閃電對你和你的隊伍所造成的傷害\n當啟用時,靈氣會降低火焰、冰凍和", "icon": "/skills/icon_125.png", - "iconActive": "/skills/icon_125_active.png" + "iconActive": "/skills/icon_125_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 126, @@ -14029,7 +14417,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to enemies and knocks them back\npowerful blow that increases the damage done", "longDescZh": "並把敵人打退\n威力強大的一擊,可以增加造成的傷害", "icon": "/skills/icon_126.png", - "iconActive": "/skills/icon_126_active.png" + "iconActive": "/skills/icon_126_active.png", + "toHit": 0, + "toHitLev": 0, + "toHitCalc": "15+lvl*5+skill('Concentrate'.blvl)*par7", + "startmana": 0 }, { "id": 127, @@ -14139,7 +14531,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - improves sword fighting skill", "longDescZh": "被動-增加你用劍的戰鬥技能", "icon": "/skills/icon_127.png", - "iconActive": "/skills/icon_127_active.png" + "iconActive": "/skills/icon_127_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 128, @@ -14249,7 +14644,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - improves axe fighting skill", "longDescZh": "被動-增加你用斧的戰鬥技能", "icon": "/skills/icon_128.png", - "iconActive": "/skills/icon_128_active.png" + "iconActive": "/skills/icon_128_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 129, @@ -14359,7 +14757,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - improves mace fighting skill", "longDescZh": "被動-增加你用釘頭鎚的戰鬥技能", "icon": "/skills/icon_129.png", - "iconActive": "/skills/icon_129_active.png" + "iconActive": "/skills/icon_129_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 130, @@ -14466,7 +14867,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "scrambling away in fear\nsends nearby monsters", "longDescZh": "恐懼之中四散奔跑\n讓附近的怪物在", "icon": "/skills/icon_130.png", - "iconActive": "/skills/icon_130_active.png" + "iconActive": "/skills/icon_130_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 131, @@ -14555,7 +14959,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "for a chance to find a potion\nuse on the corpse of a slain monster", "longDescZh": "看看有沒有機會找出葯劑\n使用殺死怪物的屍體", "icon": "/skills/icon_131.png", - "iconActive": "/skills/icon_131_active.png" + "iconActive": "/skills/icon_131_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 132, @@ -14645,7 +15052,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or into the fray\nleaps away from danger", "longDescZh": "跳入爭戰之中\n從危險之中跳離或是", "icon": "/skills/icon_132.png", - "iconActive": "/skills/icon_132_active.png" + "iconActive": "/skills/icon_132_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 133, @@ -14757,7 +15167,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or one target twice\nattacks two targets if possible,\nwhen two weapons are equipped", "longDescZh": "同時傷害二個目標\n就有可能在一次攻擊之中,\n當你裝備了二把武器", "icon": "/skills/icon_133.png", - "iconActive": "/skills/icon_133_active.png" + "iconActive": "/skills/icon_133_active.png", + "toHit": 15, + "toHitLev": 5, + "startmana": 0 }, { "id": 134, @@ -14867,7 +15280,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - improves pole arm skill", "longDescZh": "被動-增加你用長棍的戰鬥技能", "icon": "/skills/icon_134.png", - "iconActive": "/skills/icon_134_active.png" + "iconActive": "/skills/icon_134_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 135, @@ -14977,7 +15393,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - improves thrown weapon skill", "longDescZh": "被動-增加你用投擲性武器的戰鬥技能", "icon": "/skills/icon_135.png", - "iconActive": "/skills/icon_135_active.png" + "iconActive": "/skills/icon_135_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 136, @@ -15087,7 +15506,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - improves spear fighting skill", "longDescZh": "被動-增加你用長矛的戰鬥技能", "icon": "/skills/icon_136.png", - "iconActive": "/skills/icon_136_active.png" + "iconActive": "/skills/icon_136_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 137, @@ -15199,7 +15621,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "enrages a monster into relentlessly attacking", "longDescZh": "觸怒一個怪物,讓他展開無情的攻擊", "icon": "/skills/icon_137.png", - "iconActive": "/skills/icon_137_active.png" + "iconActive": "/skills/icon_137_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 138, @@ -15302,7 +15727,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "rating of you and your party\nwarns of impending danger and improves the defense", "longDescZh": "危險,並加強你的防禦力\n警告你和你的隊伍眼前逼近的", "icon": "/skills/icon_138.png", - "iconActive": "/skills/icon_138_active.png" + "iconActive": "/skills/icon_138_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34+(skill('Battle Orders'.blvl)+skill('Battle Command'.blvl))*par8", + "startmana": 0 }, { "id": 139, @@ -15425,7 +15854,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and increases your attack rating\nstuns your target for a short time", "longDescZh": "並增加你的準確率\n把你的目標擊昏一小段時間", "icon": "/skills/icon_139.png", - "iconActive": "/skills/icon_139_active.png" + "iconActive": "/skills/icon_139_active.png", + "toHit": 0, + "toHitLev": 0, + "toHitCalc": "10+lvl*5+skill('Concentrate'.blvl)*par7", + "startmana": 0 }, { "id": 140, @@ -15528,7 +15961,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "throwing weapons at the same time\nallows you to throw two different", "longDescZh": "丟出二種不同的武器\n可以讓你在同一時間", "icon": "/skills/icon_140.png", - "iconActive": "/skills/icon_140_active.png" + "iconActive": "/skills/icon_140_active.png", + "toHit": 20, + "toHitLev": 10, + "startmana": 0 }, { "id": 141, @@ -15619,7 +16055,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - increases your stamina", "longDescZh": "被動-增加你的耐力", "icon": "/skills/icon_141.png", - "iconActive": "/skills/icon_141_active.png" + "iconActive": "/skills/icon_141_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 142, @@ -15711,7 +16150,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to find hidden treasures\nuse on the corpse of a slain monster", "longDescZh": "中找出隱藏的寶物\n在已殺死的怪物屍體", "icon": "/skills/icon_142.png", - "iconActive": "/skills/icon_142_active.png" + "iconActive": "/skills/icon_142_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 143, @@ -15819,7 +16261,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "in one swift assault\nleaps to and attacks target enemy", "longDescZh": "進行迅速的突擊\n跳躍並對攻擊目標", "icon": "/skills/icon_143.png", - "iconActive": "/skills/icon_143_active.png" + "iconActive": "/skills/icon_143_active.png", + "toHit": 50, + "toHitLev": 15, + "startmana": 0 }, { "id": 144, @@ -15948,7 +16393,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "improves attack and defense rating\nattack that is not interruptible and", "longDescZh": "提高準確率和防禦力\n讓攻擊不致於中斷而且", "icon": "/skills/icon_144.png", - "iconActive": "/skills/icon_144_active.png" + "iconActive": "/skills/icon_144_active.png", + "toHit": 60, + "toHitLev": 10, + "startmana": 0 }, { "id": 145, @@ -16036,7 +16484,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - improves defense rating", "longDescZh": "被動-增加你的防禦力", "icon": "/skills/icon_145.png", - "iconActive": "/skills/icon_145_active.png" + "iconActive": "/skills/icon_145_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 146, @@ -16157,7 +16608,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "enemies' defense rating and damage\nfearsome cry that decreases", "longDescZh": "敵人的防禦力和傷害力\n令人恐懼的狂吼可以降低", "icon": "/skills/icon_146.png", - "iconActive": "/skills/icon_146_active.png" + "iconActive": "/skills/icon_146_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 147, @@ -16311,7 +16766,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "requires you to equip two weapons\neach successful attack increases your overall speed\nallows you to swing two weapons at once", "longDescZh": "必須先裝備二把武器\n每次成功的擊中後可以增加你整體性的攻擊速度\n讓你同時揮動二把武器", "icon": "/skills/icon_147.png", - "iconActive": "/skills/icon_147_active.png" + "iconActive": "/skills/icon_147_active.png", + "toHit": 100, + "toHitLev": 7, + "auraLenCalc": "par7", + "startmana": 0 }, { "id": 148, @@ -16402,7 +16861,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "passive - increases walk and run speed", "longDescZh": "被動-增加行走和跑步的速度", "icon": "/skills/icon_148.png", - "iconActive": "/skills/icon_148_active.png" + "iconActive": "/skills/icon_148_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 149, @@ -16526,7 +16988,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "stamina of you and your party\nimproves the maximum mana, life and", "longDescZh": "法力、生命和耐力上限 \n加強你和你隊伍的", "icon": "/skills/icon_149.png", - "iconActive": "/skills/icon_149_active.png" + "iconActive": "/skills/icon_149_active.png", + "toHit": 50, + "toHitLev": 10, + "auraLenCalc": "ln12+(skill('Shout'.blvl)+skill('Battle Command'.blvl))*par8", + "startmana": 0 }, { "id": 150, @@ -16624,7 +17090,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that causes nearby monsters to flee\nto create a frightening totem\nuse on the corpse of a slain monster", "longDescZh": "導致附近的怪物四散逃避\n創造一個嚇人的圖騰\n施展在已死的怪物屍體上", "icon": "/skills/icon_150.png", - "iconActive": "/skills/icon_150_active.png" + "iconActive": "/skills/icon_150_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "par6", + "startmana": 0 }, { "id": 151, @@ -16727,7 +17197,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "legions of your enemies\nthat cuts a path through the\na whirling dance of death", "longDescZh": "切出一條路\n在你的敵陣之間\n旋轉的死亡之舞", "icon": "/skills/icon_151.png", - "iconActive": "/skills/icon_151_active.png" + "iconActive": "/skills/icon_151_active.png", + "toHit": 0, + "toHitLev": 5, + "startmana": 0 }, { "id": 152, @@ -16839,7 +17312,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "but decreases defense rating\nthat increases damage and attack rating\na powerful but reckless attack", "longDescZh": "但會降低防禦力\n增加傷害力和準確率\n威力強大但攻擊鹵莽", "icon": "/skills/icon_152.png", - "iconActive": "/skills/icon_152_active.png" + "iconActive": "/skills/icon_152_active.png", + "toHit": 100, + "toHitLev": 15, + "auraLenCalc": "\"par4-min(((110*lvl)/(lvl+6)*(par4-par3)/100),(par4-par3))\"", + "startmana": 0 }, { "id": 153, @@ -16929,7 +17406,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to elemental and poison damage\npassive - increases natural resistances", "longDescZh": "降低毒素和元素系的傷害\n被動-增加自然的抵抗力", "icon": "/skills/icon_153.png", - "iconActive": "/skills/icon_153_active.png" + "iconActive": "/skills/icon_153_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 154, @@ -17053,7 +17533,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "injures and stuns all nearby enemies", "longDescZh": "讓附近的敵人受傷並昏眩", "icon": "/skills/icon_154.png", - "iconActive": "/skills/icon_154_active.png" + "iconActive": "/skills/icon_154_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 155, @@ -17142,7 +17625,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "increases all current skill levels for you and your party", "longDescZh": "增加你和你隊伍的技能等級", "icon": "/skills/icon_155.png", - "iconActive": "/skills/icon_155_active.png" + "iconActive": "/skills/icon_155_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12+(skill('Shout'.blvl)+skill('Battle Orders'.blvl))*par8", + "startmana": 0 }, { "id": 221, @@ -17249,7 +17736,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "the eyes of your enemies\nsummon ravens to peck out", "longDescZh": "召唤乌鸦啄击敌人眼睛,降低其视野", "icon": "/skills/icon_221.png", - "iconActive": "/skills/icon_221_active.png" + "iconActive": "/skills/icon_221_active.png", + "toHit": 100, + "toHitLev": 15, + "startmana": 0 }, { "id": 222, @@ -17354,7 +17844,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "disease to all it contacts\nsummon a vine that spreads", "longDescZh": "毒藤在地下穿行,让接触的敌人中毒", "icon": "/skills/icon_222.png", - "iconActive": "/skills/icon_222_active.png" + "iconActive": "/skills/icon_222_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 223, @@ -17488,7 +17981,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "transform into a werewolf", "longDescZh": "提升攻击速度、准确率和移动速度", "icon": "/skills/icon_223.png", - "iconActive": "/skills/icon_223_active.png" + "iconActive": "/skills/icon_223_active.png", + "toHit": 50, + "toHitLev": 15, + "auraLenCalc": "1000+skill('Shape Shifting'.ln12)", + "startmana": 0 }, { "id": 224, @@ -17590,7 +18087,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "when in werewolf or werebear form\npassive - improves duration and life", "longDescZh": "被动提升狼人和熊人形态的最大生命值和变形持续时间", "icon": "/skills/icon_224.png", - "iconActive": "/skills/icon_224_active.png" + "iconActive": "/skills/icon_224_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 225, @@ -17716,7 +18216,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "unleash fiery chaos to burn your enemies", "longDescZh": "在地面释放多道混乱游走的烈焰裂隙", "icon": "/skills/icon_225.png", - "iconActive": "/skills/icon_225_active.png" + "iconActive": "/skills/icon_225_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 226, @@ -17831,7 +18334,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "life for you and your party\nsummon a spirit pet that increases", "longDescZh": "光环增加附近所有队友的最大生命值", "icon": "/skills/icon_226.png", - "iconActive": "/skills/icon_226_active.png" + "iconActive": "/skills/icon_226_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 227, @@ -17963,7 +18469,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to fight by your side\nsummon a wolf with teleporting ability", "longDescZh": "召唤狼群撕咬敌人,提升自身命中", "icon": "/skills/icon_227.png", - "iconActive": "/skills/icon_227_active.png" + "iconActive": "/skills/icon_227_active.png", + "toHit": 75, + "toHitLev": 20, + "startmana": 0 }, { "id": 228, @@ -18085,7 +18594,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "transform into a werebear", "longDescZh": "大幅提升防御力、生命值和打击力量", "icon": "/skills/icon_228.png", - "iconActive": "/skills/icon_228_active.png" + "iconActive": "/skills/icon_228_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "1000+skill('Shape Shifting'.ln12)", + "startmana": 0 }, { "id": 229, @@ -18219,7 +18732,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that knocks back your enemies\nlaunch a boulder of flaming hot magma", "longDescZh": "火石沿直线滚向敌人,造成物理和火焰双重爆炸伤害", "icon": "/skills/icon_229.png", - "iconActive": "/skills/icon_229_active.png" + "iconActive": "/skills/icon_229_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 230, @@ -18348,7 +18864,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to burn your enemies with frost\nblast a continuous jet of ice", "longDescZh": "持续喷射极地寒冰冻结并减速前方敌人", "icon": "/skills/icon_230.png", - "iconActive": "/skills/icon_230_active.png" + "iconActive": "/skills/icon_230_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 6 }, { "id": 231, @@ -18454,7 +18973,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and replenishes your life\nsummon a vine that eats corpses", "longDescZh": "在地下吞噬倒下的怪物尸体,为德鲁伊回复生命", "icon": "/skills/icon_231.png", - "iconActive": "/skills/icon_231_active.png" + "iconActive": "/skills/icon_231_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 232, @@ -18594,7 +19116,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "with successive hits\nincreasing amounts of life from your enemies\ngo into a frenzied rage to steal\nwhen in werewolf form,", "longDescZh": "撕咬使目标感染狂犬病毒,毒素会快速传染给周围怪物", "icon": "/skills/icon_232.png", - "iconActive": "/skills/icon_232_active.png" + "iconActive": "/skills/icon_232_active.png", + "toHit": 20, + "toHitLev": 10, + "auraLenCalc": "par1", + "startmana": 0 }, { "id": 233, @@ -18725,7 +19251,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "with successive hits\nfor increasing extra damage\nmaul your enemies\nwhen in werebear form,", "longDescZh": "吞噬死去的怪物尸体,为德鲁伊持续回复法力值", "icon": "/skills/icon_233.png", - "iconActive": "/skills/icon_233_active.png" + "iconActive": "/skills/icon_233_active.png", + "toHit": 20, + "toHitLev": 10, + "auraLenCalc": "par4", + "startmana": 0 }, { "id": 234, @@ -18852,7 +19382,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "burning them to a crisp\nopen volcanic vents below your enemies,", "longDescZh": "在地面裂隙中喷发出炽热地火", "icon": "/skills/icon_234.png", - "iconActive": "/skills/icon_234_active.png" + "iconActive": "/skills/icon_234_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 235, @@ -18959,7 +19492,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "fire, cold, and lightning\nshield yourself from damage caused by", "longDescZh": "吸收即将到来的火焰、冰冷和闪电元素伤害", "icon": "/skills/icon_235.png", - "iconActive": "/skills/icon_235_active.png" + "iconActive": "/skills/icon_235_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 236, @@ -19086,7 +19622,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "of you and your party\nto the damage and attack rating\nsummon a spirit pet that adds", "longDescZh": "狼獾图腾提升周围所有同伴的物理伤害与攻击命中", "icon": "/skills/icon_236.png", - "iconActive": "/skills/icon_236_active.png" + "iconActive": "/skills/icon_236_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 237, @@ -19229,7 +19768,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "it does to enemies\neating corpses to increase damage\nsummon a wolf that becomes enraged,", "longDescZh": "狂狼可以吃下死者尸体进入狂乱嗜血状态", "icon": "/skills/icon_237.png", - "iconActive": "/skills/icon_237_active.png" + "iconActive": "/skills/icon_237_active.png", + "toHit": 150, + "toHitLev": 20, + "startmana": 0 }, { "id": 238, @@ -19333,7 +19875,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that spreads to other monsters\nto inflict them with disease\nbite your enemies\nwhen in werewolf form,", "longDescZh": "对周围复数敌人发动疾风骤雨般的连续强力撕咬", "icon": "/skills/icon_238.png", - "iconActive": "/skills/icon_238_active.png" + "iconActive": "/skills/icon_238_active.png", + "toHit": 50, + "toHitLev": 7, + "startmana": 0 }, { "id": 239, @@ -19459,7 +20004,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "with a fiery claw attack\nform, maul your enemies\nwhen in werewolf or werebear", "longDescZh": "熊掌沉重拍击地面,将敌人震晕并击退", "icon": "/skills/icon_239.png", - "iconActive": "/skills/icon_239_active.png" + "iconActive": "/skills/icon_239_active.png", + "toHit": 50, + "toHitLev": 15, + "startmana": 0 }, { "id": 240, @@ -19582,7 +20130,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "cut a path through your enemies\nrelease several small whirlwinds that", "longDescZh": "向前方释放穿透撕裂敌人的强力物理龙卷风", "icon": "/skills/icon_240.png", - "iconActive": "/skills/icon_240_active.png" + "iconActive": "/skills/icon_240_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 241, @@ -19687,7 +20238,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and replenishes your mana\nsummon a vine that eats corpses", "longDescZh": "从敌人体内吸取并中和毒素", "icon": "/skills/icon_241.png", - "iconActive": "/skills/icon_241_active.png" + "iconActive": "/skills/icon_241_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 242, @@ -19812,7 +20366,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to gain life and mana\nform, bite your enemies\nwhen in werewolf or werebear", "longDescZh": "在狼人或熊人形态下发动附加高额火焰伤害的利爪攻击", "icon": "/skills/icon_242.png", - "iconActive": "/skills/icon_242_active.png" + "iconActive": "/skills/icon_242_active.png", + "toHit": 50, + "toHitLev": 10, + "startmana": 0 }, { "id": 243, @@ -19916,7 +20473,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "that stuns nearby enemies\nstomp to create a shock wave\nwhen in werebear form,", "longDescZh": "熊吼发出锥形震波,大范围击晕所有前方敌人", "icon": "/skills/icon_243.png", - "iconActive": "/skills/icon_243_active.png" + "iconActive": "/skills/icon_243_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 244, @@ -20060,7 +20620,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and destruction over your enemies\nsummon forth a volcano to rain death", "longDescZh": "在指定位置升起火山,持续喷吐炽热石块砸向周围敌人", "icon": "/skills/icon_244.png", - "iconActive": "/skills/icon_244_active.png" + "iconActive": "/skills/icon_244_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 245, @@ -20190,7 +20753,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to blast your enemies\ncreate a funnel of wind and debris", "longDescZh": "环绕德鲁伊旋转的极寒风暴,持续对近身敌人造成冰冷伤害", "icon": "/skills/icon_245.png", - "iconActive": "/skills/icon_245_active.png" + "iconActive": "/skills/icon_245_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 246, @@ -20299,7 +20865,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "back at your enemies\ntaken by you and your party\nsummon spirit pet that reflects damage", "longDescZh": "反弹受到的物理伤害给近战攻击者", "icon": "/skills/icon_246.png", - "iconActive": "/skills/icon_246_active.png" + "iconActive": "/skills/icon_246_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 247, @@ -20428,7 +20997,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "summon a ferocious grizzly bear", "longDescZh": "召唤强大的巨熊,具备极高的生命值与重击压制力", "icon": "/skills/icon_247.png", - "iconActive": "/skills/icon_247_active.png" + "iconActive": "/skills/icon_247_active.png", + "toHit": 300, + "toHitLev": 20, + "startmana": 0 }, { "id": 248, @@ -20539,7 +21111,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or one target multiple times\neither multiple adjacent targets\nwhen in werewolf form, attack", "longDescZh": "连续撕咬目标", "icon": "/skills/icon_248.png", - "iconActive": "/skills/icon_248_active.png" + "iconActive": "/skills/icon_248_active.png", + "toHit": 50, + "toHitLev": 7, + "startmana": 0 }, { "id": 249, @@ -20673,7 +21248,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "destruction on nearby enemies\ncreate a meteor shower to rain fiery", "longDescZh": "从天空降下极寒冰彗星轰炸全场", "icon": "/skills/icon_249.png", - "iconActive": "/skills/icon_249_active.png" + "iconActive": "/skills/icon_249_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12 + skill('Eruption'.blvl) * par7", + "startmana": 0 }, { "id": 250, @@ -20811,7 +21390,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "debris to pound your enemies to bits\ncreate a massive storm of wind and", "longDescZh": "持续在德鲁伊身边下落炽热陨石与熔岩碎片", "icon": "/skills/icon_250.png", - "iconActive": "/skills/icon_250_active.png" + "iconActive": "/skills/icon_250_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12 + skill('Cyclone Armor'.blvl) * par7", + "startmana": 0 }, { "id": 251, @@ -20962,7 +21545,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to blast your enemies to bits\nthrow a fire bomb", "longDescZh": "投掷可以弹跳并爆炸的手雷,造成小范围火焰伤害", "icon": "/skills/icon_251.png", - "iconActive": "/skills/icon_251_active.png" + "iconActive": "/skills/icon_251_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 252, @@ -21075,7 +21661,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "with claw-class weapons\npassive - improves your skill", "longDescZh": "被动提升使用爪类武器时的战斗效能", "icon": "/skills/icon_252.png", - "iconActive": "/skills/icon_252_active.png" + "iconActive": "/skills/icon_252_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 253, @@ -21168,7 +21757,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to crush and knock back your enemies\nto create a psychic blast\nuse the power of your mind", "longDescZh": "震慑目标心智,使其陷入昏迷甚至暂时为你作战", "icon": "/skills/icon_253.png", - "iconActive": "/skills/icon_253_active.png" + "iconActive": "/skills/icon_253_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 254, @@ -21293,7 +21885,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "normal attack to release charges\nmust use a dragon finishing move or\nto finishing moves\nconsecutive hits add damage bonuses\n\nCharge-up Skill", "longDescZh": "最多积聚3段气劲,释放时造成毁天灭地的巨大物理爆发", "icon": "/skills/icon_254.png", - "iconActive": "/skills/icon_254_active.png" + "iconActive": "/skills/icon_254_active.png", + "toHit": 15, + "toHitLev": 7, + "auraLenCalc": "par3", + "startmana": 0 }, { "id": 255, @@ -21410,7 +22006,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "adds charged-up bonuses to the kick\nkick your enemies out of your way\n\nFinishing Move", "longDescZh": "随着技能等级提高,发动2次至5次超快速的连续旋风踢", "icon": "/skills/icon_255.png", - "iconActive": "/skills/icon_255_active.png" + "iconActive": "/skills/icon_255_active.png", + "toHit": 20, + "toHitLev": 35, + "startmana": 0 }, { "id": 256, @@ -21552,7 +22151,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to shock your enemies\nthrow a web of lightning", "longDescZh": "电能网在地面停留,对踩上的敌人造成持续电击", "icon": "/skills/icon_256.png", - "iconActive": "/skills/icon_256_active.png" + "iconActive": "/skills/icon_256_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 257, @@ -21660,7 +22262,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "between you and target point\nset a spinning blade to patrol", "longDescZh": "投射旋转利刃切入敌群", "icon": "/skills/icon_257.png", - "iconActive": "/skills/icon_257_active.png" + "iconActive": "/skills/icon_257_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 258, @@ -21774,7 +22379,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "for a period of time\nincreases attack and movement speed", "longDescZh": "进入加速状态,脚步如飞,出招极快", "icon": "/skills/icon_258.png", - "iconActive": "/skills/icon_258_active.png" + "iconActive": "/skills/icon_258_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln56", + "startmana": 0 }, { "id": 259, @@ -21909,7 +22518,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "normal attack to release charges\nmust use a dragon finishing move or\ncan only be used with claw-class weapons\nto finishing moves\nconsecutive hits add fire damage\n\nCharge-up Skill", "longDescZh": "双手爪具同时砍向敌人", "icon": "/skills/icon_259.png", - "iconActive": "/skills/icon_259_active.png" + "iconActive": "/skills/icon_259_active.png", + "toHit": 15, + "toHitLev": 7, + "auraLenCalc": "par3", + "startmana": 0 }, { "id": 260, @@ -22015,7 +22628,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "adds charged-up bonuses to both claw attacks\nwith your dual claw-class weapons\nslice and dice your enemies\n\nFinishing Move", "longDescZh": "3段聚气分别吸取生命、法力,以及两者兼顾", "icon": "/skills/icon_260.png", - "iconActive": "/skills/icon_260_active.png" + "iconActive": "/skills/icon_260_active.png", + "toHit": 40, + "toHitLev": 25, + "startmana": 0 }, { "id": 261, @@ -22170,7 +22786,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "at enemies that pass near\na trap that emits charged bolts", "longDescZh": "落地后释放大量蜿蜒前行的充能电火花", "icon": "/skills/icon_261.png", - "iconActive": "/skills/icon_261_active.png" + "iconActive": "/skills/icon_261_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 262, @@ -22303,7 +22922,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "a trap that emits waves of fire", "longDescZh": "当敌人接近时自动向前方喷射烈焰波浪", "icon": "/skills/icon_262.png", - "iconActive": "/skills/icon_262_active.png" + "iconActive": "/skills/icon_262_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 263, @@ -22393,7 +23015,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "you are using dual claw-class weapons\npassive - chance to block when", "longDescZh": "甚至可以格挡法术飞弹与远程攻击", "icon": "/skills/icon_263.png", - "iconActive": "/skills/icon_263_active.png" + "iconActive": "/skills/icon_263_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 264, @@ -22518,7 +23143,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "lowering their defenses for a period of time\ncast a shadow to blind nearby enemies", "longDescZh": "使全场陷入致盲暗影,极大降低怪物的防御力", "icon": "/skills/icon_264.png", - "iconActive": "/skills/icon_264_active.png" + "iconActive": "/skills/icon_264_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln34", + "startmana": 0 }, { "id": 265, @@ -22645,7 +23274,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "normal attack to release charges\nmust use a dragon finishing move or\nto finishing moves\nconsecutive hits add life and mana stealing\n\nCharge-up Skill", "longDescZh": "跨越障碍瞬移至敌人面前施展终结飞踢", "icon": "/skills/icon_265.png", - "iconActive": "/skills/icon_265_active.png" + "iconActive": "/skills/icon_265_active.png", + "toHit": 15, + "toHitLev": 7, + "auraLenCalc": "par3", + "startmana": 0 }, { "id": 266, @@ -22759,7 +23392,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to slice up your enemies\nthrow spinning blades", "longDescZh": "3段聚气释放电球、充能弹以及连锁闪电", "icon": "/skills/icon_266.png", - "iconActive": "/skills/icon_266_active.png" + "iconActive": "/skills/icon_266_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 6 }, { "id": 267, @@ -22875,7 +23511,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "for a period of time\nraise all resistances and resist curses", "longDescZh": "身躯半虚无化,减少受到的诅咒持续时间和所有伤害", "icon": "/skills/icon_267.png", - "iconActive": "/skills/icon_267_active.png" + "iconActive": "/skills/icon_267_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln56", + "startmana": 0 }, { "id": 268, @@ -22990,7 +23630,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "your skills and fights by your side\nsummon a shadow of yourself that mimics", "longDescZh": "引导飞刀风暴绞杀四周一切敌人", "icon": "/skills/icon_268.png", - "iconActive": "/skills/icon_268_active.png" + "iconActive": "/skills/icon_268_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 269, @@ -23127,7 +23770,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "normal attack to release charges\nmust use a dragon finishing move or\ncan only be used with claw-class weapons\nto finishing moves\nconsecutive hits add lightning damage\n\nCharge-up Skill", "longDescZh": "在地面制造一道燃烧的火墙", "icon": "/skills/icon_269.png", - "iconActive": "/skills/icon_269_active.png" + "iconActive": "/skills/icon_269_active.png", + "toHit": 15, + "toHitLev": 7, + "auraLenCalc": "par3", + "startmana": 0 }, { "id": 270, @@ -23242,7 +23889,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "adds charged-up bonuses to the kick\nknock back your enemies with an explosive kick\n\nFinishing Move", "longDescZh": "暗影斗士会模仿刺客当前快捷栏上的技能进行战斗", "icon": "/skills/icon_270.png", - "iconActive": "/skills/icon_270_active.png" + "iconActive": "/skills/icon_270_active.png", + "toHit": 20, + "toHitLev": 15, + "startmana": 0 }, { "id": 271, @@ -23392,7 +24042,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to scorch passing enemies\na trap that shoots lightning", "longDescZh": "3段聚气分别冻结单体、冻结周围以及引发冰爆", "icon": "/skills/icon_271.png", - "iconActive": "/skills/icon_271_active.png" + "iconActive": "/skills/icon_271_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 272, @@ -23534,7 +24187,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "trap that sprays fire at passing enemies", "longDescZh": "释放聚气造成巨额火焰爆破并击退所有近身敌人", "icon": "/skills/icon_272.png", - "iconActive": "/skills/icon_272_active.png" + "iconActive": "/skills/icon_272_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 273, @@ -23649,7 +24305,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "and convert the feeble-minded\nstun a group of enemies\nusing the power of your mind", "longDescZh": "刺客核心陷阱,持续发射高伤害的穿透穿刺穿透雷光束", "icon": "/skills/icon_273.png", - "iconActive": "/skills/icon_273_active.png" + "iconActive": "/skills/icon_273_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 274, @@ -23787,7 +24446,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "normal attack to release charges\nmust use a dragon finishing move or\ncan only be used with claw-class weapons\nto finishing moves\nconsecutive hits add cold damage\n\nCharge-up Skill", "longDescZh": "持续向周围四方喷射火焰新星", "icon": "/skills/icon_274.png", - "iconActive": "/skills/icon_274_active.png" + "iconActive": "/skills/icon_274_active.png", + "toHit": 15, + "toHitLev": 7, + "auraLenCalc": "par3", + "startmana": 0 }, { "id": 275, @@ -23891,7 +24554,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "adds charged-up bonuses to the kick\nand destroy them with a kick\nteleport to your enemies\n\nFinishing Move", "longDescZh": "环绕身躯的飞刀切碎任何企图近身的敌人", "icon": "/skills/icon_275.png", - "iconActive": "/skills/icon_275_active.png" + "iconActive": "/skills/icon_275_active.png", + "toHit": 60, + "toHitLev": 25, + "startmana": 0 }, { "id": 276, @@ -24051,7 +24717,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "or explodes nearby corpses laying waste to more enemies\ntrap that shoots lightning at your enemies", "longDescZh": "短时间内使所有武器攻击附带极高毒素伤害", "icon": "/skills/icon_276.png", - "iconActive": "/skills/icon_276_active.png" + "iconActive": "/skills/icon_276_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 277, @@ -24152,7 +24821,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "who stray too close\nspinning blades slice enemies", "longDescZh": "暗影大师具备极强的抗性与AI,能自主施展刺客全套技能", "icon": "/skills/icon_277.png", - "iconActive": "/skills/icon_277_active.png" + "iconActive": "/skills/icon_277_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 278, @@ -24245,7 +24918,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "add poison damage to your weapons", "longDescZh": "跃向半空踢翻目标并释放积聚的武学能量", "icon": "/skills/icon_278.png", - "iconActive": "/skills/icon_278_active.png" + "iconActive": "/skills/icon_278_active.png", + "toHit": 0, + "toHitLev": 0, + "auraLenCalc": "ln12", + "startmana": 0 }, { "id": 279, @@ -24358,7 +25035,10 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "to fight by your side\nsummon a powerful shadow of yourself", "longDescZh": "引爆死去的怪物尸体造成尸爆,并能发射闪电", "icon": "/skills/icon_279.png", - "iconActive": "/skills/icon_279_active.png" + "iconActive": "/skills/icon_279_active.png", + "toHit": 0, + "toHitLev": 0, + "startmana": 0 }, { "id": 280, @@ -24518,7 +25198,11 @@ export const SKILLS_CATALOG: readonly SkillCatalogEntry[] = Object.freeze([ "longDesc": "normal attack to release charges\nmust use a dragon finishing move or\nadds elemental novas to finishing moves\n\nCharge-up Skill", "longDescZh": "1段聚气陨石,2段聚气连锁闪电,3段聚气极寒冰晶", "icon": "/skills/icon_280.png", - "iconActive": "/skills/icon_280_active.png" + "iconActive": "/skills/icon_280_active.png", + "toHit": 15, + "toHitLev": 7, + "auraLenCalc": "375", + "startmana": 0 } ]) diff --git a/src/game/skill-calc-engine.ts b/src/game/skill-calc-engine.ts index ea5d739..9338324 100644 --- a/src/game/skill-calc-engine.ts +++ b/src/game/skill-calc-engine.ts @@ -12,6 +12,7 @@ */ import type { CharacterClassCode } from './classes.ts' +import { compute3BandDuration } from './engine/calc-ast.ts' import { SKILLS_CATALOG, SKILLS_BY_ID, @@ -480,7 +481,13 @@ export function calculateDetailedDamage( baseMax = evaluateLevelBand(skill.eMax, skill.eMaxLev, lvl) if (skill.eLen > 0 || (skill.eLevLen && skill.eLevLen.length > 0)) { - durationFrames = evaluateLevelBand(skill.eLen, skill.eLevLen, lvl) + durationFrames = compute3BandDuration( + lvl, + skill.eLen, + skill.eLevLen?.[0] ?? 0, + skill.eLevLen?.[1] ?? 0, + skill.eLevLen?.[2] ?? 0, + ) } } else if (skill.minDam > 0 || skill.maxDam > 0 || (skill.minLevDam && skill.minLevDam.some(x => x > 0))) { type = 'physical' @@ -658,22 +665,42 @@ export function evaluateSkillEffects( const dm56 = evalDiminishing(p.par5 ?? 0, p.par6 ?? 0, lvl) const dm78 = evalDiminishing(p.par7 ?? 0, p.par8 ?? 0, lvl) - // Determine duration len - let durationFrames = skill.eLen + (lvl - 1) * (skill.eLevLen?.[0] ?? 0) - if (durationFrames === 0 && (skill.params.par1 ?? 0) > 0 && (skill.params.par2 ?? 0) > 0) { - if (skill.name === 'Battle Orders' || skill.name === 'Shout' || skill.name === 'Battle Command') { + // Determine duration len using authentic 1.13c 3-band duration scaling + let durationFrames = compute3BandDuration( + lvl, + skill.eLen, + skill.eLevLen?.[0] ?? 0, + skill.eLevLen?.[1] ?? 0, + skill.eLevLen?.[2] ?? 0, + ) + if (durationFrames === 0) { + if ( + skill.id === 138 || + skill.id === 149 || + skill.id === 155 || + skill.name === 'Battle Orders' || + skill.name === 'Shout' || + skill.name === 'Battle Command' + ) { const synShout = state.hardPoints[138] ?? 0 const synBC = state.hardPoints[155] ?? 0 const synBO = state.hardPoints[149] ?? 0 let synBonus = 0 - if (skill.id === 149) synBonus = (synShout + synBC) * (skill.params.par8 ?? 125) - else if (skill.id === 138) synBonus = (synBO + synBC) * (skill.params.par8 ?? 125) - else if (skill.id === 155) synBonus = (synShout + synBO) * (skill.params.par8 ?? 125) - durationFrames = (skill.params.par1 ?? 0) + (lvl - 1) * (skill.params.par2 ?? 0) + synBonus + if (skill.id === 149 || skill.name === 'Battle Orders') { + synBonus = (synShout + synBC) * (skill.params.par8 ?? 125) + durationFrames = (skill.params.par1 ?? 0) + (lvl - 1) * (skill.params.par2 ?? 0) + synBonus + } else if (skill.id === 138 || skill.name === 'Shout') { + synBonus = (synBO + synBC) * (skill.params.par8 ?? 125) + durationFrames = (skill.params.par3 ?? 0) + (lvl - 1) * (skill.params.par4 ?? 0) + synBonus + } else if (skill.id === 155 || skill.name === 'Battle Command') { + synBonus = (synShout + synBO) * (skill.params.par8 ?? 125) + durationFrames = (skill.params.par1 ?? 0) + (lvl - 1) * (skill.params.par2 ?? 0) + synBonus + } } } - const ctx: SkillFormulaContext = { + const shiftMultiplier = 2 ** ((skill.hitshift ?? 8) - 8) + const baseCtx: SkillFormulaContext = { lvl, params: skill.params, ln12: (skill.params.par1 ?? 0) + (lvl - 1) * (skill.params.par2 ?? 0), @@ -684,15 +711,24 @@ export function evaluateSkillEffects( dm34, dm56, dm78, - toht: (skill.params.tohit ?? 40) + (lvl - 1) * 5, + toht: 0, len: durationFrames, - edmn: evaluateLevelBand(skill.eMin, skill.eMinLev, lvl), - edmx: evaluateLevelBand(skill.eMax, skill.eMaxLev, lvl), + edmn: Math.round(evaluateLevelBand(skill.eMin, skill.eMinLev, lvl) * shiftMultiplier), + edmx: Math.round(evaluateLevelBand(skill.eMax, skill.eMaxLev, lvl) * shiftMultiplier), mana: calculateManaCost(skill, lvl), hardPoints: state.hardPoints, effLevels, } + const toht = skill.toHitCalc + ? evalSkillDescFormula(skill.toHitCalc, baseCtx) + : (skill.toHit ?? 0) + (lvl - 1) * (skill.toHitLev ?? 0) + + const ctx: SkillFormulaContext = { + ...baseCtx, + toht, + } + const results: FormattedDescLine[] = [] const lines = [...(skill.descLines || []), ...(skill.dsc2Lines || [])] for (const line of lines) { @@ -861,7 +897,7 @@ export function buildTooltipViewModel( const hard = state.hardPoints[skillId] ?? 0 const eff = getEffectiveLevel(state, skillId) - const isLearned = hard > 0 + const isLearned = eff > 0 const levelMet = playerLevel >= skill.reqlevel const canAlloc = canAllocate(state, skillId) @@ -883,10 +919,10 @@ export function buildTooltipViewModel( const currentDamage = calculateDetailedDamage(state, skill, displayLevel) const currentEffects = evaluateSkillEffects(state, skill, displayLevel) - // Next Level stats + // Next Level stats: only show when learned and not maxed let nextLevel: TooltipViewModel['nextLevel'] = null - if (hard < 20) { - const nextLvl = isLearned ? eff + 1 : 1 + if (isLearned && hard < 20) { + const nextLvl = eff + 1 nextLevel = { level: nextLvl, manaCost: calculateManaCost(skill, nextLvl), diff --git a/src/ui/skill-tree-panel.ts b/src/ui/skill-tree-panel.ts index 4ff92a0..174d385 100644 --- a/src/ui/skill-tree-panel.ts +++ b/src/ui/skill-tree-panel.ts @@ -8,7 +8,20 @@ * point allocation, prerequisite checks, and rich hover tooltips. */ -import type { D2FontRenderer } from './font.ts' +import type { D2FontRenderer, D2ColorCode } from './font.ts' +import type { CharacterClassCode } from '../game/classes.ts' +import { + buildTooltipViewModel, + type TooltipViewModel, + type SkillCalcState, +} from '../game/skill-calc-engine.ts' + +export interface FormattedTooltipLine { + readonly text: string + readonly color: D2ColorCode + readonly font: 'fontexocet10' | 'font8' + readonly align?: 'left' | 'center' +} export interface SkillNodeDef { readonly skillId: number @@ -74,6 +87,8 @@ export class SkillTreePanel { activeTab: 0 | 1 | 2 = 2 unspentSkillPoints = 12 bonusAllSkills = 8 + characterClass: CharacterClassCode = 'sor' + characterLevel = 85 readonly hardPoints = new Map([ [39, 1], [40, 1], [44, 1], [45, 5], [55, 10], [59, 20], [64, 20], [65, 10], [38, 1], [42, 1], [43, 1], [49, 5], [54, 1], @@ -107,6 +122,24 @@ export class SkillTreePanel { return hard > 0 ? hard + this.bonusAllSkills : 0 } + getSkillCalcState(): SkillCalcState { + const hardPoints: Record = {} + for (const [id, pts] of this.hardPoints.entries()) { + if (pts > 0) hardPoints[id] = pts + } + return { + classCode: this.characterClass, + hardPoints, + tabSoftPoints: { 0: 0, 1: 0, 2: 0 }, + allSkillsSoftPoints: this.bonusAllSkills, + specificSoftPoints: {}, + } + } + + getTooltipViewModel(skillId: number): TooltipViewModel | null { + return buildTooltipViewModel(this.getSkillCalcState(), skillId, this.characterLevel) + } + canAllocate(skillId: number, charLevel = 85): boolean { if (this.unspentSkillPoints <= 0) return false const node = SORCERESS_SKILL_TREE.find(n => n.skillId === skillId) @@ -317,51 +350,259 @@ export class SkillTreePanel { // 6. Hover Tooltip for Skill Node if (this.hoveredSkill) { - const s = this.hoveredSkill - const pos = SkillTreePanel.nodeScreenPos(s) - const eff = Math.max(1, this.getEffectiveLevel(s.skillId)) - const hard = this.getHardPoints(s.skillId) - const minDmg = s.baseDmgMin + (eff - 1) * s.dmgPerLevel - const maxDmg = s.baseDmgMax + (eff - 1) * Math.round(s.dmgPerLevel * 1.25) - const tw = 240 - const th = 112 - const tx = Math.max(404, Math.min(800 - tw - 8, pos.x - 80)) - const ty = Math.max(12, pos.y - th - 6) + this.drawSkillTooltip(ctx, font) + } + } - ctx.fillStyle = 'rgba(6, 6, 6, 0.94)' - ctx.fillRect(tx, ty, tw, th) - ctx.strokeStyle = '#c8a15a' - ctx.strokeRect(tx + 0.5, ty + 0.5, tw - 1, th - 1) + static formatSkillTooltipLines(vm: TooltipViewModel): FormattedTooltipLine[] { + const lines: FormattedTooltipLine[] = [] - font.drawText(ctx, `${s.nameZh} (${s.nameEn})`, tx + tw / 2, ty + 20, { - font: 'fontexocet10', - color: 'gold', - align: 'center', - }) - font.drawText(ctx, s.descZh, tx + tw / 2, ty + 38, { + // 1. Title: Skill Name in Gold (#c8a15a, fontexocet10, center) + lines.push({ + text: `${vm.skill.nameZh} (${vm.skill.name})`, + color: 'gold', + font: 'fontexocet10', + align: 'center', + }) + + // 2. Requirements: Red (#ff4d4d) if player level < reqlevel or unmet prereqs + if (!vm.levelMet) { + lines.push({ + text: `需要角色等级: ${vm.reqlevel}`, + color: 'red', font: 'font8', - color: 'white', align: 'center', }) - font.drawText(ctx, `当前等级: ${eff} (基础 ${hard} + 装备 ${this.bonusAllSkills})`, tx + tw / 2, ty + 58, { - font: 'font8', - color: 'blue', - align: 'center', - }) - if (s.baseDmgMax > 0) { - font.drawText(ctx, `法术效果/伤害: ${minDmg} - ${maxDmg}`, tx + tw / 2, ty + 76, { + } + for (const prereq of vm.prereqs) { + if (!prereq.met) { + lines.push({ + text: `需要前置技能: ${prereq.nameZh}`, + color: 'red', font: 'font8', - color: 'white', align: 'center', }) } - font.drawText( - ctx, - `法力消耗: ${s.baseMana} · 需要等级: ${s.reqLevel} (左键点击加点)`, - tx + tw / 2, - ty + 96, - { font: 'font8', color: 'tan', align: 'center' }, - ) } + + // 3. Narrative: Short/Long description in White (#e8e4dc, font8) + const desc = vm.skill.shortDescZh || vm.shortDescZh || vm.skill.longDescZh || vm.longDescZh + if (desc) { + lines.push({ + text: desc, + color: 'white', + font: 'font8', + align: 'center', + }) + } + + // 4. Current Level header: Blue/White + if (vm.isLearned) { + const soft = vm.effectiveLevel - vm.hardPoints + const lvlText = `当前技能等级: ${vm.effectiveLevel}${soft > 0 ? ` (基础 ${vm.hardPoints} + 装备 +${soft})` : ''}` + lines.push({ + text: lvlText, + color: soft > 0 ? 'blue' : 'white', + font: 'font8', + align: 'center', + }) + } else { + lines.push({ + text: '未习得 (预览 1 级效果)', + color: 'gray', + font: 'font8', + align: 'center', + }) + } + + // 5. Current Stats: Mana cost, damage ranges (with synergies and masteries), attack bonus, duration, cast delay in Blue/White (#6868ff / #e8e4dc, font8) + if (vm.current.manaCost > 0 || vm.skill.mana > 0) { + lines.push({ + text: `法力消耗: ${vm.current.manaCost}`, + color: 'white', + font: 'font8', + align: 'center', + }) + } + if (vm.current.delaySeconds > 0) { + lines.push({ + text: `施法延迟: ${vm.current.delaySeconds} 秒`, + color: 'white', + font: 'font8', + align: 'center', + }) + } + if (vm.current.damage.hasDamage) { + const d = vm.current.damage + lines.push({ + text: `${d.typeZh || '伤害'}: ${d.finalMin} - ${d.finalMax}`, + color: 'blue', + font: 'font8', + align: 'center', + }) + if (d.durationSeconds !== undefined && d.durationSeconds > 0) { + lines.push({ + text: `持续时间: ${d.durationSeconds} 秒`, + color: 'white', + font: 'font8', + align: 'center', + }) + } + if (d.weaponDamagePercent !== undefined && d.weaponDamagePercent > 0) { + lines.push({ + text: `武器伤害: ${d.weaponDamagePercent}%`, + color: 'white', + font: 'font8', + align: 'center', + }) + } + } + for (const eff of vm.current.effects) { + if (eff.textZh) { + lines.push({ + text: eff.textZh, + color: 'blue', + font: 'font8', + align: 'center', + }) + } + } + + // 6. Next Level Preview: Header [下一等级] (Lv X) in Green (#00fc00, font8), followed by next level values. Omitted if unlearned or maxed (20 base points). + if (vm.nextLevel && vm.isLearned && vm.hardPoints < 20) { + lines.push({ + text: `[下一等级] (Lv ${vm.nextLevel.level})`, + color: 'green', + font: 'font8', + align: 'center', + }) + if (vm.nextLevel.manaCost !== vm.current.manaCost) { + lines.push({ + text: `法力消耗: ${vm.nextLevel.manaCost}`, + color: 'green', + font: 'font8', + align: 'center', + }) + } + if (vm.nextLevel.damage.hasDamage) { + const nd = vm.nextLevel.damage + lines.push({ + text: `${nd.typeZh || '伤害'}: ${nd.finalMin} - ${nd.finalMax}`, + color: 'green', + font: 'font8', + align: 'center', + }) + if (nd.durationSeconds !== undefined && nd.durationSeconds > 0 && nd.durationSeconds !== vm.current.damage.durationSeconds) { + lines.push({ + text: `持续时间: ${nd.durationSeconds} 秒`, + color: 'green', + font: 'font8', + align: 'center', + }) + } + if (nd.weaponDamagePercent !== undefined && nd.weaponDamagePercent !== vm.current.damage.weaponDamagePercent) { + lines.push({ + text: `武器伤害: ${nd.weaponDamagePercent}%`, + color: 'green', + font: 'font8', + align: 'center', + }) + } + } + for (const eff of vm.nextLevel.effects) { + if (eff.textZh) { + lines.push({ + text: eff.textZh, + color: 'green', + font: 'font8', + align: 'center', + }) + } + } + } else if (vm.hardPoints >= 20) { + lines.push({ + text: '[已达技能点投入上限 (20/20)]', + color: 'gold', + font: 'font8', + align: 'center', + }) + } + + // 7. Synergies Section: Header [协同技能加成] in Blue (fontexocet10), followed by each synergy provider line: • : 每个有效技能点增加 % 伤害 (当前 +%) + if (vm.synergiesReceived && vm.synergiesReceived.length > 0) { + lines.push({ + text: '[协同技能加成]', + color: 'blue', + font: 'fontexocet10', + align: 'center', + }) + for (const syn of vm.synergiesReceived) { + const text = syn.hardPoints > 0 + ? `• ${syn.providerNameZh}: 每个有效技能点增加 ${syn.percentPerLevel}% 伤害 (当前 +${syn.currentBonusPct}%)` + : `• ${syn.providerNameZh}: 每个有效技能点增加 ${syn.percentPerLevel}% 伤害` + lines.push({ + text, + color: syn.hardPoints > 0 ? 'blue' : 'gray', + font: 'font8', + align: 'left', + }) + } + } + if (vm.masteryInfo && vm.masteryInfo.active) { + lines.push({ + text: `• ${vm.masteryInfo.descriptionZh}`, + color: 'blue', + font: 'font8', + align: 'left', + }) + } + + return lines + } + + static computeTooltipPlacement( + pos: { x: number; y: number; w: number; h: number }, + lines: readonly FormattedTooltipLine[], + font: D2FontRenderer, + ): { x: number; y: number; w: number; h: number } { + const maxLineWidth = lines.reduce((max, l) => { + const w = typeof font?.measureText === 'function' ? font.measureText(l.text, l.font) : l.text.length * 8 + return Math.max(max, w) + }, 0) + const boxW = Math.max(240, maxLineWidth + 28) + const boxH = lines.length * 17 + 24 + const rawX = pos.x - boxW - 8 + const clampedX = Math.max(8, Math.min(800 - boxW - 8, rawX)) + const rawY = Math.round(pos.y + 24 - boxH / 2) + const clampedY = Math.max(8, Math.min(552 - boxH, rawY)) + return { x: clampedX, y: clampedY, w: boxW, h: boxH } + } + + drawSkillTooltip(ctx: CanvasRenderingContext2D, font: D2FontRenderer): void { + if (!this.hoveredSkill) return + const pos = SkillTreePanel.nodeScreenPos(this.hoveredSkill) + const vm = this.getTooltipViewModel(this.hoveredSkill.skillId) + if (!vm) return + + const lines = SkillTreePanel.formatSkillTooltipLines(vm) + if (lines.length === 0) return + + const placement = SkillTreePanel.computeTooltipPlacement(pos, lines, font) + + ctx.fillStyle = 'rgba(4, 4, 4, 0.94)' + ctx.fillRect(placement.x, placement.y, placement.w, placement.h) + ctx.strokeStyle = '#8c7244' + ctx.lineWidth = 1 + ctx.strokeRect(placement.x + 0.5, placement.y + 0.5, placement.w - 1, placement.h - 1) + + lines.forEach((l, idx) => { + const lineY = placement.y + 12 + 10 + idx * 17 + const lineX = l.align === 'center' ? placement.x + Math.round(placement.w / 2) : placement.x + 14 + font.drawText(ctx, l.text, lineX, lineY, { + font: l.font, + color: l.color, + align: l.align ?? 'center', + }) + }) } } diff --git a/tests/skill-calc-engine.test.ts b/tests/skill-calc-engine.test.ts index 18e5e7e..4b6b7b9 100644 --- a/tests/skill-calc-engine.test.ts +++ b/tests/skill-calc-engine.test.ts @@ -268,7 +268,100 @@ describe('Diablo II Skill Calculator Engine (Issue #26)', () => { expect(vm?.prereqs[0].name).toBe('Fire Bolt') expect(vm?.prereqs[0].met).toBe(false) expect(vm?.current.damage.hasDamage).toBe(true) + // For slvl === 0 (unlearned), nextLevel must be null while main body previews Lv 1 + expect(vm?.isLearned).toBe(false) + expect(vm?.current.level).toBe(1) + expect(vm?.nextLevel).toBeNull() + }) + + it('previews nextLevel when learned (slvl >= 1) and hardPoints < 20', () => { + allocatePoint(state, 36, 1) // Fire Bolt prerequisite + allocatePoint(state, 47, 5) // Fire Ball lvl 5 + state.allSkillsSoftPoints = 2 // +2 from gear -> effLevel 7 + const vm = buildTooltipViewModel(state, 47, 99) + expect(vm).not.toBeNull() + expect(vm?.isLearned).toBe(true) + expect(vm?.hardPoints).toBe(5) + expect(vm?.effectiveLevel).toBe(7) + expect(vm?.current.level).toBe(7) expect(vm?.nextLevel).not.toBeNull() + expect(vm?.nextLevel?.level).toBe(8) + expect(vm?.nextLevel?.damage.finalMin).toBeGreaterThan(vm!.current.damage.finalMin) + }) + + it('omits nextLevel preview when skill has reached maximum base allocation (hardPoints === 20)', () => { + allocatePoint(state, 36, 1) + allocatePoint(state, 47, 20) // Max 20 hard points + state.allSkillsSoftPoints = 5 // +5 gear -> effLevel 25 + const vm = buildTooltipViewModel(state, 47, 99) + expect(vm).not.toBeNull() + expect(vm?.isLearned).toBe(true) + expect(vm?.hardPoints).toBe(20) + expect(vm?.effectiveLevel).toBe(25) + expect(vm?.current.level).toBe(25) + expect(vm?.nextLevel).toBeNull() + }) + + it('evaluates authentic 1.13c Attack Rating (ToHit) scaling for combat skills', () => { + // Jab (10): ToHit = 10, LevToHit = 9 -> 10 + (lvl - 1) * 9 + const amaState = createInitialState('ama') + amaState.hardPoints[10] = 1 + let vmLvl1 = buildTooltipViewModel(amaState, 10, 99) + let arLine1 = vmLvl1?.current.effects.find(e => e.textZh.includes('準確率') || e.textZh.includes('准确率')) + expect(arLine1?.textZh).toBe('準確率:+10%') + + amaState.hardPoints[10] = 10 + let vmLvl10 = buildTooltipViewModel(amaState, 10, 99) + let arLine10 = vmLvl10?.current.effects.find(e => e.textZh.includes('準確率') || e.textZh.includes('准确率')) + expect(arLine10?.textZh).toBe('準確率:+91%') + + amaState.hardPoints[10] = 20 + let vmLvl20 = buildTooltipViewModel(amaState, 10, 99) + let arLine20 = vmLvl20?.current.effects.find(e => e.textZh.includes('準確率') || e.textZh.includes('准确率')) + expect(arLine20?.textZh).toBe('準確率:+181%') + + // Zeal (106): ToHit = 10, LevToHit = 10 -> 10 + (lvl - 1) * 10 = +200% + const palState = createInitialState('pal') + palState.hardPoints[106] = 20 + const vmZeal = buildTooltipViewModel(palState, 106, 99) + const zealAr = vmZeal?.current.effects.find(e => e.textZh.includes('攻擊加成') || e.textZh.includes('準確率')) + expect(zealAr?.textZh).toBe('攻擊加成:+200%') + + // Sacrifice (96): ToHit = 20, LevToHit = 7 -> 20 + (lvl - 1) * 7 = +83% + palState.hardPoints[96] = 10 + const vmSacrifice = buildTooltipViewModel(palState, 96, 99) + const sacAr = vmSacrifice?.current.effects.find(e => e.textZh.includes('準確率')) + expect(sacAr?.textZh).toBe('變動準確率:+83%') + }) + + it('evaluates authentic 1.13c Shout warcry duration using Param3/Param4 (ln34)', () => { + const barState = createInitialState('bar') + barState.hardPoints[138] = 1 // Shout Level 1: Param3 = 500 frames = 20 seconds + let vmShout = buildTooltipViewModel(barState, 138, 99) + let durLine = vmShout?.current.effects.find(e => e.lineType === 12) + expect(durLine?.textZh).toBe('持續:20 秒') + + barState.hardPoints[138] = 20 // Shout Level 20: 500 + 19 * 250 = 5250 frames = 210 seconds + vmShout = buildTooltipViewModel(barState, 138, 99) + durLine = vmShout?.current.effects.find(e => e.lineType === 12) + expect(durLine?.textZh).toBe('持續:210 秒') + }) + + it('strictly enforces that synergies ignore soft gear points (1.13c Marrowwalk fix)', () => { + const sorState1 = createInitialState('sor') + sorState1.hardPoints[36] = 20 // 20 hard points in Fire Bolt + const vmHard = buildTooltipViewModel(sorState1, 47, 99) + const synHard = vmHard?.synergiesReceived.find(s => s.providerId === 36) + expect(synHard?.hardPoints).toBe(20) + expect(synHard?.currentBonusPct).toBe(280) // 20 * 14% = 280% + + const sorState2 = createInitialState('sor') + sorState2.hardPoints[36] = 0 // 0 hard points + sorState2.allSkillsSoftPoints = 20 // +20 soft points from gear + const vmSoft = buildTooltipViewModel(sorState2, 47, 99) + const synSoft = vmSoft?.synergiesReceived.find(s => s.providerId === 36) + expect(synSoft?.hardPoints).toBe(0) + expect(synSoft?.currentBonusPct).toBe(0) // Soft points give 0% synergy }) }) diff --git a/tests/ui-hud.test.ts b/tests/ui-hud.test.ts index c8daaed..61df6ba 100644 --- a/tests/ui-hud.test.ts +++ b/tests/ui-hud.test.ts @@ -37,6 +37,11 @@ import { LEVEL_NAMES_ZH, LEVEL_NAMES_EN, } from '../src/game/level-names-zh.ts' +import { + buildTooltipViewModel, + createInitialState, + allocatePoint, +} from '../src/game/skill-calc-engine.ts' describe('Diablo II v1.13c UI / HUD (Issue #27)', () => { it('verifies offline DC6 UI asset extraction manifest has 0 decode failures', () => { @@ -635,4 +640,122 @@ describe('Diablo II v1.13c UI / HUD (Issue #27)', () => { expect(sceneNameZh(null as any)).toBe('Unknown Area') expect(sceneNameEn(null as any)).toBe('Unknown Area') }) + + it('renders authentic multi-line skill tooltips with 1.13c next-level preview and styling rules (Issue #152)', () => { + // 1. Unlearned skill with requirements not met (Sorceress lvl 1 looking at Fire Ball lvl 12) + const sorcBuild = createInitialState('sor') + const unlearnedFireBall = buildTooltipViewModel(sorcBuild, 47, 1) // Fire Ball requires clvl 12, Fire Bolt + expect(unlearnedFireBall).not.toBeNull() + const unlearnedLines = SkillTreePanel.formatSkillTooltipLines(unlearnedFireBall!) + + // Title: Fire Ball in Gold + expect(unlearnedLines[0]).toEqual({ text: '火球 (Fire Ball)', color: 'gold', font: 'fontexocet10', align: 'center' }) + // Required Level: red when character level < reqlevel + const reqLevelLine = unlearnedLines.find(l => l.text.includes('需要角色等级: 12')) + expect(reqLevelLine).toBeDefined() + expect(reqLevelLine?.color).toBe('red') + // Prerequisites: red when unmet + const prereqLine = unlearnedLines.find(l => l.text.includes('需要前置技能: 火彈')) + expect(prereqLine).toBeDefined() + expect(prereqLine?.color).toBe('red') + // Unlearned skill MUST NOT include [下一等级] preview + expect(unlearnedLines.some(l => l.text.includes('[下一等级]'))).toBe(false) + + // 2. Unlearned skill with requirements met (Sorceress lvl 1 looking at Fire Bolt lvl 1) + const fireBolt = buildTooltipViewModel(sorcBuild, 36, 1) + expect(fireBolt).not.toBeNull() + const fireBoltLines = SkillTreePanel.formatSkillTooltipLines(fireBolt!) + // When level is met, reqlevel line is omitted from tooltip + expect(fireBoltLines.some(l => l.text.includes('需要角色等级'))).toBe(false) + expect(fireBoltLines.some(l => l.text.includes('[下一等级]'))).toBe(false) + + // 3. Learned skill (slvl = 1) with next-level preview in green + const learnedBuild = createInitialState('sor') + allocatePoint(learnedBuild, 36) // Fire Bolt +1 + const learnedFireBolt = buildTooltipViewModel(learnedBuild, 36, 1) + expect(learnedFireBolt).not.toBeNull() + const learnedLines = SkillTreePanel.formatSkillTooltipLines(learnedFireBolt!) + + const currLevelLine = learnedLines.find(l => l.text.includes('当前技能等级: 1')) + expect(currLevelLine).toBeDefined() + expect(currLevelLine?.color).toBe('white') + + const nextLevelHeader = learnedLines.find(l => l.text.includes('[下一等级] (Lv 2)')) + expect(nextLevelHeader).toBeDefined() + expect(nextLevelHeader?.color).toBe('green') + + // Next level damage preview in green + const nextLevelDmg = learnedLines.find(l => l.color === 'green' && l.text.includes('火焰伤害')) + expect(nextLevelDmg).toBeDefined() + + // 4. Maxed skill (hardPoints === 20): omits [下一等级] and shows max allocation notice in gold + const maxedBuild = createInitialState('sor') + allocatePoint(maxedBuild, 36, 20) + const maxedFireBolt = buildTooltipViewModel(maxedBuild, 36, 20) + expect(maxedFireBolt).not.toBeNull() + const maxedLines = SkillTreePanel.formatSkillTooltipLines(maxedFireBolt!) + + expect(maxedLines.some(l => l.text.includes('[下一等级]'))).toBe(false) + const maxedNotice = maxedLines.find(l => l.text.includes('[已达技能点投入上限 (20/20)]')) + expect(maxedNotice).toBeDefined() + expect(maxedNotice?.color).toBe('gold') + + // 5. Synergy section formatting + const fireballBuild = createInitialState('sor') + allocatePoint(fireballBuild, 36) // Fire Bolt +1 + const fbVm = buildTooltipViewModel(fireballBuild, 47, 12) + const fbLines = SkillTreePanel.formatSkillTooltipLines(fbVm!) + const synHeader = fbLines.find(l => l.text.includes('[协同技能加成]')) + expect(synHeader).toBeDefined() + expect(synHeader?.color).toBe('blue') + const synFireBolt = fbLines.find(l => l.text.includes('火彈: 每个有效技能点增加 14% 伤害')) + expect(synFireBolt).toBeDefined() + expect(synFireBolt?.color).toBe('blue') // active synergy (Fire Bolt has 1 hard point) + const synMeteor = fbLines.find(l => l.text.includes('隕石: 每个有效技能点增加 14% 伤害')) + expect(synMeteor).toBeDefined() + expect(synMeteor?.color).toBe('gray') // inactive synergy (Meteor has 0 hard points) + }) + + it('clamps tooltip placement within canvas viewport and strictly above the control bar (Issue #152)', () => { + const dummyLines = [ + { text: '火球', color: 'white' as const, font: 'fontexocet10' as const, align: 'center' as const }, + { text: '当前等级: 1', color: 'white' as const, font: 'font8' as const, align: 'center' as const }, + { text: '火焰伤害: 6 - 14', color: 'blue' as const, font: 'font8' as const, align: 'center' as const }, + { text: '[下一等级] (Lv 2)', color: 'green' as const, font: 'font8' as const, align: 'center' as const }, + { text: '火焰伤害: 10 - 20', color: 'green' as const, font: 'font8' as const, align: 'center' as const }, + { text: '[协同技能加成]', color: 'blue' as const, font: 'fontexocet10' as const, align: 'center' as const }, + { text: '• 火弹: 每个有效技能点增加 14% 伤害 (当前 +14%)', color: 'blue' as const, font: 'font8' as const, align: 'left' as const }, + { text: '• 陨石: 每个有效技能点增加 14% 伤害', color: 'gray' as const, font: 'font8' as const, align: 'left' as const }, + ] + + const mockFont = { + measureText: (txt: string) => txt.length * 8, + } as any + + // Test extreme positions across the 800x600 screen + const testPositions = [ + { x: 420, y: 80, w: 48, h: 48 }, // Top-left node in right-dock panel + { x: 700, y: 120, w: 48, h: 48 }, // Far-right node + { x: 450, y: 400, w: 48, h: 48 }, // Bottom node + { x: 500, y: 500, w: 48, h: 48 }, // Extreme bottom node near control bar + { x: 50, y: 200, w: 48, h: 48 }, // Far left node + ] + + for (const pos of testPositions) { + const placement = SkillTreePanel.computeTooltipPlacement(pos, dummyLines, mockFont) + // Viewport bounds: x >= 8, x + w <= 792 (800 - 8) + expect(placement.x).toBeGreaterThanOrEqual(8) + expect(placement.x + placement.w).toBeLessThanOrEqual(792) + + // Vertical bounds: y >= 8, y + h <= 552 (strictly above control bar y=552) + expect(placement.y).toBeGreaterThanOrEqual(8) + expect(placement.y + placement.h).toBeLessThanOrEqual(552) + + // Minimum width is 240 + expect(placement.w).toBeGreaterThanOrEqual(240) + // Height matches line count * 17 + 24 + expect(placement.h).toBe(dummyLines.length * 17 + 24) + } + }) }) +