/* ============================================================ * Diablo2D — balance.js : tuning constants & progression curves * ============================================================ */ 'use strict'; window.D2 = window.D2 || {}; (function (D2) { const BAL = { /* --- progression --- */ maxLevel: 40, statPointsPerLevel: 5, skillPointsPerLevel: 1, xpForLevel(l) { return Math.floor(58 * Math.pow(l, 1.72) + 24 * l); }, /* --- player base --- */ baseHp: 42, hpPerVit: 3.4, hpPerLevel: 7.5, baseMana: 18, manaPerEne: 2.2, manaPerLevel: 3.5, baseArmor: 3, baseCrit: 0.05, baseCritDmg: 1.5, baseSpeed: 3.4, // tiles / second baseAttackCd: 0.62, // seconds (modified by weapon aps) hpRegenPerSec: 0.35, manaRegenPerSec: 1.4, /* primary stat -> derived */ meleeDmgPerStr: 0.011, // +1.1% weapon dmg / str rangedDmgPerDex: 0.011, castDmgPerEne: 0.012, armorPerStr: 0.25, critPerDex: 0.0018, // +0.18% / dex dodgePerDex: 0.0022, /* caps */ resistCap: 0.72, armorDrCap: 0.78, critCap: 0.75, speedCap: 6.2, /* --- monsters --- */ monsterLevel(act, floorIdx, torment) { return 1 + act * 4 + floorIdx + (torment || 0) * 9; }, mHp(mlvl) { return Math.floor(15 + 9.2 * Math.pow(mlvl, 1.36)); }, mDmg(mlvl) { return Math.floor(2.4 + 2.1 * Math.pow(mlvl, 1.28)); }, mXp(mlvl) { return Math.floor(7 + 5.2 * Math.pow(mlvl, 1.35)); }, mArmor(mlvl) { return Math.floor(2 + 1.6 * mlvl); }, eliteHpMult: 2.2, eliteDmgMult: 1.38, eliteXpMult: 2.4, eliteSizeMult: 1.22, bossHpMult: 7.0, bossDmgMult: 1.55, bossXpMult: 12, bossSizeMult: 1.85, /* --- combat --- */ armorK: 34 + 6.2, // DR = armor/(armor + K*mlvl) — K applied as armorK*mlvl knockback: 0.16, // tiles hitFlashTime: 0.12, corpseTime: 14, goldDropBase(mlvl) { return Math.floor(3 + 2.6 * mlvl * (0.8 + Math.random() * 0.5)); }, /* --- drops --- */ dropChances: { gold: 0.42, potionHp: 0.11, potionMp: 0.075, gear: 0.145, gearEliteBonus: 0.35, gearBoss: 1.0, }, rarityWeights: { common: 100, magic: 34, rare: 9.5, legendary: 1.35 }, magicFindRarityBoost(mf) { return 1 + mf / 100 * 0.85; }, // mf stat in percent points potionStackMax: 12, potionHealPct: 0.32, potionManaPct: 0.38, potionHealFlat(mlvl) { return 14 + 6 * mlvl; }, /* --- economy --- */ vendorStockSize: [8, 13], vendorRerollCostMult: 0.0, sellRatio: 0.28, buyMarkup: 1.0, healerCost(level) { return 12 + level * 6; }, stashRows: 7, /* --- death --- */ deathGoldPenalty: 0.12, /* --- acts --- */ /* ---- quest chain (Diablo-style: accept from NPCs, hunt, turn in) ---- */ questKillBase(act) { return 12 + act * 4; }, gambleCost(level) { return 80 + level * 45; }, tomeCost(level) { return 120 + level * 60; }, respecCost(level) { return 300 + level * 90; }, QUESTS: [ // Act I { id: 'a0_cull', giver: 'kashya', act: 0, type: 'kills', target: 15, title: 'quest.cull', desc: 'quest.cull.desc', reward: { gold: 180, xp: 90 } }, { id: 'a0_boss', giver: 'cain', act: 0, requires: 'a0_cull', type: 'boss', title: 'quest.boss', desc: 'quest.boss.desc', reward: { gold: 450, xp: 320, itemLevelBonus: 2 } }, // Act II { id: 'a1_cull', giver: 'kashya', act: 1, type: 'kills', target: 18, title: 'quest.cull', desc: 'quest.cull.desc', reward: { gold: 380, xp: 700 } }, { id: 'a1_boss', giver: 'cain', act: 1, requires: 'a1_cull', type: 'boss', title: 'quest.boss', desc: 'quest.boss.desc', reward: { gold: 800, xp: 1500, itemLevelBonus: 3 } }, // Act III { id: 'a2_cull', giver: 'kashya', act: 2, type: 'kills', target: 22, title: 'quest.cull', desc: 'quest.cull.desc', reward: { gold: 900, xp: 4200 } }, { id: 'a2_boss', giver: 'cain', act: 2, requires: 'a2_cull', type: 'boss', title: 'quest.boss', desc: 'quest.boss.desc', reward: { gold: 1600, xp: 8000, itemLevelBonus: 4 } }, // Act IV { id: 'a3_cull', giver: 'kashya', act: 3, type: 'kills', target: 26, title: 'quest.cull', desc: 'quest.cull.desc', reward: { gold: 2000, xp: 22000 } }, { id: 'a3_boss', giver: 'cain', act: 3, requires: 'a3_cull', type: 'boss', title: 'quest.boss', desc: 'quest.boss.desc', reward: { gold: 3500, xp: 40000, itemLevelBonus: 5 } }, // Post-victory repeatable hook { id: 'torment_1', giver: 'cain', act: 3, requiresVictory: true, type: 'boss', torment: true, title: 'quest.torment', desc: 'quest.torment.desc', reward: { gold: 5000, xp: 60000, itemLevelBonus: 6 } }, ], questById(id) { return this.QUESTS.find(q => q.id === id); }, acts: [ { name: 'act.names.0', floors: 4, boss: 'butcher', theme: 'cathedral', music: 'crypt' }, { name: 'act.names.1', floors: 4, boss: 'boneking', theme: 'catacombs', music: 'crypt' }, { name: 'act.names.2', floors: 4, boss: 'spiderqueen', theme: 'caves', music: 'cave' }, { name: 'act.names.3', floors: 4, boss: 'terrorlord', theme: 'hell', music: 'hell' }, ], floorsPerAct: 4, // last floor of each act is the boss lair maxTorment: 10, /* --- shrine buffs --- */ shrineDuration: 45, shrineBuffPower: { dmg: 0.35, speed: 0.3, armor: 0.5, xp: 0.5, regen: 4 }, /* --- skills --- */ skillMaxRank: 5, tierUnlockLevels: [1, 4, 8, 13, 19], manaCostRank(base) { return Math.round(base * (1 + 0.14 * 4)); }, // computed per rank elsewhere cooldownReductionCap: 0.55, /* --- misc --- */ interactRange: 1.35, pickupRange: 1.15, aggroRange: 7.5, deaggroLeash: 14, maxProjectiles: 260, maxParticles: 700, maxFloatingText: 90, }; BAL.armorK = 40; // final constant D2.BAL = BAL; })(window.D2);