Files
deepseek fc1fa2d51e Diablo2D — Shadows of Tristram: complete browser ARPG
- Isometric canvas renderer (depth-sorted, FOV/fog, additive lighting)
- 3 classes x 20 skills, 4 acts x 4 floors + boss lairs, torment I-X
- Diablo-style loot: rarities, affix tiers, 14 legendaries, vendor, stash
- Rogue camp with 6 NPCs: Charsi/Akara/Kashya/Cain/Gheed/storage
- NPC quest chain (accept -> hunt -> turn in) with rewards & gating
- Procedural WebAudio SFX + generative music, EN/VI localization
- Saves, settings, waypoints, hardcore mode, PWA manifest
- 93-assertion headless suite + browser E2E via CDP
2026-08-23 06:59:36 +00:00

228 lines
17 KiB
JavaScript

/* ============================================================
* Diablo2D — skills.js : class skill trees (data-driven)
* Engine implements each `castType`; params scale with rank.
* ============================================================ */
'use strict';
window.D2 = window.D2 || {};
(function (D2) {
const MAX_RANK = D2.BAL.skillMaxRank;
function L(o) { return o[(D2.i18n && D2.i18n.getLang()) || 'en'] || o.en; }
/* ---------------- CRUSADER ---------------- */
const crusader = [
/* Zeal */
{ id: 'cleave', branch: 'zeal', tier: 0, type: 'active', maxRank: MAX_RANK, icon: 'cleave',
name: { en: 'Cleave', vi: 'Chém Rộng' },
desc: r => ({ en: `Sweeping strike hits all enemies in front for ${100 + 20 * r}% weapon damage.`, vi: `Vung chém trúng mọi kẻ địch phía trước với ${100 + 20 * r}% sát thương vũ khí.` }),
manaCost: () => 0, cooldown: 0.42,
castType: 'meleeArc', params: r => ({ arcDeg: 160, range: 1.65, dmgPct: 100 + 20 * r }) },
{ id: 'whirlwind', branch: 'zeal', tier: 1, type: 'active', maxRank: MAX_RANK, icon: 'whirlwind',
name: { en: 'Whirlwind', vi: 'Vòng Xoáy' },
desc: r => ({ en: `Spin for 1s, shredding nearby enemies for ${55 + 11 * r}% weapon damage per tick. You may move while spinning.`, vi: `Xoay trong 1s, xé nát kẻ địch gần với ${55 + 11 * r}% ST vũ khí mỗi nhịp. Di chuyển được khi xoay.` }),
manaCost: r => 8 + r, cooldown: 5.5,
castType: 'whirlwind', params: r => ({ range: 1.8, dmgPct: 55 + 11 * r, duration: 1.0 }) },
{ id: 'consecrate', branch: 'zeal', tier: 2, type: 'active', maxRank: MAX_RANK, icon: 'consecrate',
name: { en: 'Consecration', vi: 'Thánh Hóa' },
desc: r => ({ en: `Sanctify the ground at your cursor. Deals ${140 + 34 * r}% weapon damage over 3s to enemies within it.`, vi: `Thánh hóa mặt đất tại con trỏ. Gây ${140 + 34 * r}% ST vũ khí trong 3s lên kẻ địch trong vùng.` }),
manaCost: r => 14 + 2 * r, cooldown: 8,
castType: 'groundAoE', params: r => ({ radius: 2.6, delay: 0.25, duration: 3, dmgPct: 140 + 34 * r, elem: 'holy' }) },
/* Wrath */
{ id: 'charge', branch: 'wrath', tier: 0, type: 'active', maxRank: MAX_RANK, icon: 'charge',
name: { en: 'Charge', vi: 'Xung Phong' },
desc: r => ({ en: `Dash forward, damaging enemies in your path for ${130 + 26 * r}% weapon damage and stunning them ${0.5 + 0.14 * r}s.`, vi: `Lao về phía trước, gây ${130 + 26 * r}% ST vũ khí và làm choáng ${0.5 + 0.14 * r}s.` }),
manaCost: () => 6, cooldown: 6,
castType: 'dash', params: r => ({ distance: 5.2, speed: 16, dmgPct: 130 + 26 * r, stun: 0.5 + 0.14 * r }) },
{ id: 'warcry', branch: 'wrath', tier: 1, type: 'active', maxRank: MAX_RANK, icon: 'warcry',
name: { en: 'War Cry', vi: 'Chiến Khúc' },
desc: r => ({ en: `Roar: +${15 + 5 * r}% damage and +${12 + 6 * r}% armor for 8s.`, vi: `Gầm thét: +${15 + 5 * r}% sát thương và +${12 + 6 * r}% giáp trong 8s.` }),
manaCost: r => 10 + r, cooldown: 18,
castType: 'buffSelf', params: r => ({ dur: 8, mods: { dmgPct: 15 + 5 * r, armorPct: 12 + 6 * r }, icon: 'warcry', name: { en: 'War Cry', vi: 'Chiến Khúc' } }) },
{ id: 'seismic', branch: 'wrath', tier: 2, type: 'active', maxRank: MAX_RANK, icon: 'seismic',
name: { en: 'Seismic Slam', vi: 'Trấn Địa Kích' },
desc: r => ({ en: `Slam the earth: cone shockwave deals ${170 + 38 * r}% weapon damage, knocks back and stuns ${0.8 + 0.2 * r}s.`, vi: `Đập đất: sóng xung kích hình nón gây ${170 + 38 * r}% ST vũ khí, hất văng và choáng ${0.8 + 0.2 * r}s.` }),
manaCost: r => 16 + 3 * r, cooldown: 9,
castType: 'cone', params: r => ({ arcDeg: 70, range: 4.6, dmgPct: 170 + 38 * r, knockback: 2.4, stun: 0.8 + 0.2 * r }) },
/* Bulwark (passives) */
{ id: 'ironskin', branch: 'bulwark', tier: 0, type: 'passive', maxRank: MAX_RANK, icon: 'ironskin',
name: { en: 'Iron Skin', vi: 'Da Thép' },
desc: r => ({ en: `+${6 + 4 * r}% total armor.`, vi: `+${6 + 4 * r}% tổng giáp.` }),
mods: r => ({ armorPct: 6 + 4 * r }) },
{ id: 'renewal', branch: 'bulwark', tier: 1, type: 'passive', maxRank: MAX_RANK, icon: 'renewal',
name: { en: 'Renewal', vi: 'Tái Sinh' },
desc: r => ({ en: `Regenerate ${(0.8 + 0.7 * r).toFixed(1)}% max life per second.`, vi: `Hồi ${(0.8 + 0.7 * r).toFixed(1)}% máu tối đa mỗi giây.` }),
mods: r => ({ regenHpPct: 0.8 + 0.7 * r }) },
{ id: 'laststand', branch: 'bulwark', tier: 2, type: 'passive', maxRank: MAX_RANK, icon: 'laststand',
name: { en: 'Last Stand', vi: 'Đường Lui Cuối' },
desc: r => ({ en: `Below 35% life: take ${8 + 4 * r}% less damage and deal +${10 + 5 * r}% more.`, vi: `Dưới 35% máu: nhận ít hơn ${8 + 4 * r}% sát thương và gây thêm +${10 + 5 * r}%.` }),
mods: r => ({ lowLifeDR: 8 + 4 * r, lowLifeDmg: 10 + 5 * r }) },
];
/* ---------------- RANGER ---------------- */
const ranger = [
/* Marksmanship */
{ id: 'multishot', branch: 'mark', tier: 0, type: 'active', maxRank: MAX_RANK, icon: 'multishot',
name: { en: 'Multishot', vi: 'Liên Thanh' },
desc: r => ({ en: `Fire ${4 + Math.floor((r - 1) / 2)} arrows in a wide arc for ${85 + 17 * r}% weapon damage each.`, vi: `Bắn ${4 + Math.floor((r - 1) / 2)} mũi tên hình quạt, mỗi mũi ${85 + 17 * r}% ST vũ khí.` }),
manaCost: r => 8 + r, cooldown: 0.9,
castType: 'projSpread', params: r => ({ count: 4 + Math.floor((r - 1) / 2), spreadDeg: 42, dmgPct: 85 + 17 * r, speed: 11 }) },
{ id: 'piercingbolt', branch: 'mark', tier: 1, type: 'active', maxRank: MAX_RANK, icon: 'piercingbolt',
name: { en: 'Piercing Bolt', vi: 'Mũi Tên Xuyên Phá' },
desc: r => ({ en: `A heavy bolt pierces all enemies in a line for ${190 + 42 * r}% weapon damage.`, vi: `Một mũi tên nặng xuyên qua mọi kẻ địch trên đường đi, gây ${190 + 42 * r}% ST vũ khí.` }),
manaCost: r => 12 + 2 * r, cooldown: 4.5,
castType: 'projSpread', params: r => ({ count: 1, spreadDeg: 0, dmgPct: 190 + 42 * r, speed: 15, pierce: true, size: 1.4 }) },
{ id: 'arrowstorm', branch: 'mark', tier: 2, type: 'active', maxRank: MAX_RANK, icon: 'arrowstorm',
name: { en: 'Arrow Storm', vi: 'Mưa Mũi Tên' },
desc: r => ({ en: `Rain ${3 + Math.floor(r / 2)} waves of arrows on an area, each dealing ${70 + 15 * r}% weapon damage.`, vi: `Gây ${3 + Math.floor(r / 2)} đợt mưa tên lên một vùng, mỗi đợt ${70 + 15 * r}% ST vũ khí.` }),
manaCost: r => 18 + 3 * r, cooldown: 11,
castType: 'volleyArea', params: r => ({ waves: 3 + Math.floor(r / 2), waveDelay: 0.4, radius: 2.4, dmgPct: 70 + 15 * r }) },
/* Shadowcraft */
{ id: 'shadowstep', branch: 'shadow', tier: 0, type: 'active', maxRank: MAX_RANK, icon: 'shadowstep',
name: { en: 'Shadow Step', vi: 'Bóng Đêm Bước' },
desc: r => ({ en: `Blink toward the cursor. Your next attack within 3s is a guaranteed critical (+${50 + 25 * r}% crit damage).`, vi: `Dịch chuyển tới con trỏ. Đòn đánh tiếp theo trong 3s chắc chắn chí mạng (+${50 + 25 * r}% ST chí mạng).` }),
manaCost: () => 10, cooldown: 7,
castType: 'blink', params: r => ({ range: 5.5, guaranteedCritDur: 3, bonusCritDmg: 50 + 25 * r }) },
{ id: 'fanofknives', branch: 'shadow', tier: 1, type: 'active', maxRank: MAX_RANK, icon: 'fanofknives',
name: { en: 'Fan of Knives', vi: 'Quạt Dao Găm' },
desc: r => ({ en: `Hurl ${8 + 2 * r} knives in all directions for ${95 + 19 * r}% weapon damage, causing bleeding.`, vi: `Phóng ${8 + 2 * r} dao găm ra mọi hướng, gây ${95 + 19 * r}% ST vũ khí và làm chảy máu.` }),
manaCost: r => 14 + 2 * r, cooldown: 8,
castType: 'novaProj', params: r => ({ count: 8 + 2 * r, dmgPct: 95 + 19 * r, speed: 9, bleed: true }) },
{ id: 'smokeveil', branch: 'shadow', tier: 2, type: 'active', maxRank: MAX_RANK, icon: 'smokeveil',
name: { en: 'Smoke Veil', vi: 'Màn Khói' },
desc: r => ({ en: `Vanish into smoke: +${25 + 7 * r}% dodge for 5s. Enemies lose track of you.`, vi: `Tan vào khói: +${25 + 7 * r}% né tránh trong 5s. Kẻ địch mất dấu bạn.` }),
manaCost: r => 12 + 2 * r, cooldown: 16,
castType: 'buffSelf', params: r => ({ dur: 5, mods: { dodgePct: 25 + 7 * r }, icon: 'smokeveil', name: { en: 'Smoke Veil', vi: 'Màn Khói' } }) },
/* Poacher (passives) */
{ id: 'predator', branch: 'poach', tier: 0, type: 'passive', maxRank: MAX_RANK, icon: 'predator',
name: { en: 'Predator Instinct', vi: 'Bản Năng Thợ Săn' },
desc: r => ({ en: `+${8 + 6 * r}% critical damage.`, vi: `+${8 + 6 * r}% sát thương chí mạng.` }),
mods: r => ({ critDmgPct: 8 + 6 * r }) },
{ id: 'bleedingedge', branch: 'poach', tier: 1, type: 'passive', maxRank: MAX_RANK, icon: 'bleedingedge',
name: { en: 'Bleeding Edge', vi: 'Lưỡi Chảy Máu' },
desc: r => ({ en: `Attacks cause bleeding: ${10 + 10 * r}% weapon damage over 3s.`, vi: `Đòn đánh gây chảy máu: ${10 + 10 * r}% ST vũ khí trong 3s.` }),
mods: r => ({ bleedOnHitPct: 10 + 10 * r }) },
{ id: 'swiftvantage', branch: 'poach', tier: 2, type: 'passive', maxRank: MAX_RANK, icon: 'swiftvantage',
name: { en: 'Swift Vantage', vi: 'Lợi Thế Thần Tốc' },
desc: r => ({ en: `+${4 + 2 * r}% move speed and +${3 + 2 * r}% attack speed.`, vi: `+${4 + 2 * r}% tốc chạy và +${3 + 2 * r}% tốc đánh.` }),
mods: r => ({ moveSpeedPct: 4 + 2 * r, attackSpeedPct: 3 + 2 * r }) },
];
/* ---------------- SORCERESS ---------------- */
const sorceress = [
/* Pyromancy */
{ id: 'firebolt', branch: 'pyro', tier: 0, type: 'active', maxRank: MAX_RANK, icon: 'firebolt',
name: { en: 'Fire Bolt', vi: 'Tiểu Hỏa' },
desc: r => ({ en: `Hurl a bolt of fire for ${135 + 32 * r}% weapon damage. Cheap and reliable.`, vi: `Phóng một mũi lửa gây ${135 + 32 * r}% ST vũ khí. Rẻ và đáng tin.` }),
manaCost: () => 4, cooldown: 0.55,
castType: 'projSpread', params: r => ({ count: 1, spreadDeg: 0, dmgPct: 135 + 32 * r, speed: 10, elem: 'fire', size: 1.15 }) },
{ id: 'meteor', branch: 'pyro', tier: 1, type: 'active', maxRank: MAX_RANK, icon: 'meteor',
name: { en: 'Meteor', vi: 'Thiên Thạch' },
desc: r => ({ en: `Call a meteor to crash after 0.8s: ${260 + 58 * r}% weapon damage in a large area, leaving burning ground.`, vi: `Triệu hồi thiên thạch rơi sau 0.8s: ${260 + 58 * r}% ST vũ khí vùng lớn, để lại mặt đất cháy.` }),
manaCost: r => 22 + 4 * r, cooldown: 9,
castType: 'meteor', params: r => ({ radius: 2.6, delay: 0.8, dmgPct: 260 + 58 * r, burnGround: 2.5 }) },
{ id: 'flamewave', branch: 'pyro', tier: 2, type: 'active', maxRank: MAX_RANK, icon: 'flamewave',
name: { en: 'Flame Wave', vi: 'Sóng Lửa' },
desc: r => ({ en: `Erupt in a ring of fire: ${200 + 44 * r}% weapon damage around you and ignite enemies.`, vi: `Bùng nổ vòng lửa quanh bạn: ${200 + 44 * r}% ST vũ khí và thiêu rụi kẻ địch.` }),
manaCost: r => 20 + 4 * r, cooldown: 7,
castType: 'nova', params: r => ({ radius: 3.4, dmgPct: 200 + 44 * r, elem: 'fire', ignite: true }) },
/* Cryomancy */
{ id: 'iceshards', branch: 'cryo', tier: 0, type: 'active', maxRank: MAX_RANK, icon: 'iceshards',
name: { en: 'Ice Shards', vi: 'Mảnh Băng' },
desc: r => ({ en: `Launch ${5} piercing shards for ${90 + 20 * r}% weapon damage each, chilling enemies.`, vi: `Phóng 5 mảnh băng xuyên phá, mỗi mảnh ${90 + 20 * r}% ST vũ khí, làm lạnh kẻ địch.` }),
manaCost: r => 9 + 2 * r, cooldown: 1.4,
castType: 'projSpread', params: r => ({ count: 5, spreadDeg: 36, dmgPct: 90 + 20 * r, speed: 9.5, elem: 'cold', pierce: true, slow: 0.35, slowDur: 1.8 }) },
{ id: 'frostnova', branch: 'cryo', tier: 1, type: 'active', maxRank: MAX_RANK, icon: 'frostnova',
name: { en: 'Frost Nova', vi: 'Tinh Băng' },
desc: r => ({ en: `Freeze everything near you for ${1.0 + 0.22 * r}s and deal ${110 + 24 * r}% weapon damage.`, vi: `Đóng băng mọi thứ gần bạn ${1.0 + 0.22 * r}s và gây ${110 + 24 * r}% ST vũ khí.` }),
manaCost: r => 18 + 3 * r, cooldown: 8,
castType: 'nova', params: r => ({ radius: 3.0, dmgPct: 110 + 24 * r, elem: 'cold', freeze: 1.0 + 0.22 * r }) },
{ id: 'blizzard', branch: 'cryo', tier: 2, type: 'active', maxRank: MAX_RANK, icon: 'blizzard',
name: { en: 'Blizzard', vi: 'Bão Tuyết' },
desc: r => ({ en: `A storm batters an area for 3s: ${90 + 20 * r}% weapon damage per second, heavily chilled.`, vi: `Bão tuyết cuồn cuộn 3s: ${90 + 20 * r}% ST vũ khí mỗi giây, làm lạnh mạnh.` }),
manaCost: r => 24 + 5 * r, cooldown: 12,
castType: 'groundAoE', params: r => ({ radius: 2.8, delay: 0.3, duration: 3, dmgPct: 90 + 20 * r, elem: 'cold', slow: 0.5, slowDur: 1.2 }) },
/* Stormweaving */
{ id: 'chainlightning', branch: 'storm', tier: 0, type: 'active', maxRank: MAX_RANK, icon: 'chainlightning',
name: { en: 'Chain Lightning', vi: 'Lôi Điện Xích' },
desc: r => ({ en: `Lightning arcs between ${2 + r} enemies for ${150 + 33 * r}% weapon damage.`, vi: `Sét lan truyền giữa ${2 + r} kẻ địch, gây ${150 + 33 * r}% ST vũ khí.` }),
manaCost: r => 14 + 2 * r, cooldown: 2.6,
castType: 'chainLightning', params: r => ({ jumps: 2 + r, range: 5.5, dmgPct: 150 + 33 * r }) },
{ id: 'teleport', branch: 'storm', tier: 1, type: 'active', maxRank: MAX_RANK, icon: 'teleport',
name: { en: 'Teleport', vi: 'Di Chuyển' },
desc: r => ({ en: `Instantly teleport to the cursor location (${4.5 + 0.5 * r} tile range).`, vi: `Dịch chuyển tức thời đến vị trí con trỏ (${(4.5 + 0.5 * r).toFixed(1)} ô).` }),
manaCost: r => 12 + r, cooldown: 4.5,
castType: 'blink', params: r => ({ range: 4.5 + 0.5 * r }) },
{ id: 'arcaneecho', branch: 'storm', tier: 2, type: 'passive', maxRank: MAX_RANK, icon: 'arcaneecho',
name: { en: 'Arcane Echo', vi: 'Vọng Âm Huyền Thuật' },
desc: r => ({ en: `-(${3 + 3 * r})% cooldowns and +${0.3 + 0.25 * r} mana per second.`, vi: `-(giảm) ${3 + 3 * r}% thời gian hồi kỹ năng và +${(0.3 + 0.25 * r).toFixed(2)} mana/giây.` }),
mods: r => ({ cdrPct: 3 + 3 * r, manaRegenFlat: 0.3 + 0.25 * r }) },
];
const CLASSES = {
crusader: {
id: 'crusader',
name: { en: 'Crusader', vi: 'Thập Tự Quân' },
branches: [
{ id: 'zeal', name: { en: 'Zeal', vi: 'Cuồng Tín' } },
{ id: 'wrath', name: { en: 'Wrath', vi: 'Thịnh Nộ' } },
{ id: 'bulwark', name: { en: 'Bulwark', vi: 'Bức Tường Thép' } },
],
skills: crusader,
baseMods: { str: 8, dex: 4, vit: 8, ene: 3 },
weaponKind: 'melee',
palette: { body: '#8a8f9c', accent: '#c8a35a', cloth: '#5a2a2a' },
basic: { castType: 'meleeArc', params: { arcDeg: 120, range: 1.45, dmgPct: 100 } },
descKey: 'class.crusader.desc',
},
ranger: {
id: 'ranger',
name: { en: 'Shadow Huntress', vi: 'Nữ Thợ Săn Bóng Đêm' },
branches: [
{ id: 'mark', name: { en: 'Marksmanship', vi: 'Bách Bộ Xuyên Dương' } },
{ id: 'shadow', name: { en: 'Shadowcraft', vi: 'Nghệ Thuật Bóng Tối' } },
{ id: 'poach', name: { en: 'Poacher', vi: 'Thợ Săn Mồi' } },
],
skills: ranger,
baseMods: { str: 4, dex: 9, vit: 5, ene: 5 },
weaponKind: 'ranged',
palette: { body: '#3e5a46', accent: '#8adf5a', cloth: '#2a3a30' },
basic: { castType: 'projSpread', params: { count: 1, spreadDeg: 0, dmgPct: 100, speed: 12 } },
descKey: 'class.crusader.desc'.replace('crusader', 'ranger'),
},
sorceress: {
id: 'sorceress',
name: { en: 'Sorceress', vi: 'Pháp Sư' },
branches: [
{ id: 'pyro', name: { en: 'Pyromancy', vi: 'Hỏa Thuật' } },
{ id: 'cryo', name: { en: 'Cryomancy', vi: 'Băng Thuật' } },
{ id: 'storm', name: { en: 'Stormweaving', vi: 'Lôi Pháp' } },
],
skills: sorceress,
baseMods: { str: 3, dex: 4, vit: 5, ene: 10 },
weaponKind: 'cast',
palette: { body: '#5e3a72', accent: '#c86aff', cloth: '#3a2450' },
basic: { castType: 'projSpread', params: { count: 1, spreadDeg: 0, dmgPct: 100, speed: 9, size: 1.2 } },
descKey: 'class.sorceress.desc',
},
};
/** all skills of a class flattened, with tier unlock level attached */
function skillsFor(clsId) {
const c = CLASSES[clsId];
return c.skills.map(s => Object.assign({}, s, { unlockLevel: D2.BAL.tierUnlockLevels[s.tier] || 1 }));
}
function findSkill(clsId, skillId) {
return CLASSES[clsId].skills.find(s => s.id === skillId);
}
D2.Skills = { CLASSES, skillsFor, findSkill, MAX_RANK };
})(window.D2);