From 849838f9cc6d327ba9dee9ebec36cf686abc8462 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 18 Nov 2025 20:11:15 +1100 Subject: [PATCH 1/2] Use Characters.ot and Monsters.ot files for many magic numbers Port of https://github.com/PathOfBuildingCommunity/PathOfBuilding-PoE2/pull/1184 These 2 files contain many constants we had currently hardcoded in various files --- src/Data/Misc.lua | 147 ++++++++++++++++++++++++++++++++ src/Export/Classes/GGPKData.lua | 6 +- src/Export/Scripts/miscdata.lua | 42 +++++++++ src/Modules/CalcOffence.lua | 8 +- src/Modules/CalcPerform.lua | 19 ++--- src/Modules/CalcSetup.lua | 101 +++++++++++----------- src/Modules/ConfigOptions.lua | 4 +- src/Modules/Data.lua | 11 ++- 8 files changed, 269 insertions(+), 69 deletions(-) diff --git a/src/Data/Misc.lua b/src/Data/Misc.lua index 2f9a93860d..9a4596b59b 100644 --- a/src/Data/Misc.lua +++ b/src/Data/Misc.lua @@ -55,6 +55,153 @@ data.gameConstants = { ["GoldPlusPercentPerAffix"] = 10, ["UniqueBaseGoldCost"] = 2000, } +-- From Metadata/Characters/Character.ot +data.characterConstants = { + ["level"] = 1, + ["is_player"] = 1, + ["energy_shield_recharge_rate_per_minute_%"] = 2000, + ["mana_regeneration_rate_per_minute_%"] = 105, + ["base_maximum_all_resistances_%"] = 75, + ["maximum_physical_damage_reduction_%"] = 90, + ["maximum_block_%"] = 75, + ["base_maximum_spell_block_%"] = 75, + ["base_attack_speed_+%_per_frenzy_charge"] = 4, + ["base_cast_speed_+%_per_frenzy_charge"] = 4, + ["object_inherent_damage_+%_final_per_frenzy_charge"] = 4, + ["physical_damage_reduction_%_per_endurance_charge"] = 4, + ["elemental_damage_reduction_%_per_endurance_charge"] = 4, + ["critical_strike_chance_+%_per_power_charge"] = 50, + ["max_viper_strike_orbs"] = 4, + ["dual_wield_inherent_attack_speed_+%_final"] = 10, + ["max_fuse_arrow_orbs"] = 5, + ["max_fire_beam_stacks"] = 8, + ["base_evasion_rating"] = 15, + ["life_per_level"] = 12, + ["mana_per_level"] = 6, + ["accuracy_rating_per_level"] = 2, + ["inherent_block_while_dual_wielding_%"] = 20, + ["base_critical_strike_multiplier"] = 150, + ["critical_ailment_dot_multiplier_+"] = 50, + ["strength_per_level"] = 0, + ["dexterity_per_level"] = 0, + ["intelligence_per_level"] = 0, + ["max_endurance_charges"] = 3, + ["max_frenzy_charges"] = 3, + ["max_power_charges"] = 3, + ["maximum_righteous_charges"] = 5, + ["maximum_blood_scythe_charges"] = 5, + ["base_number_of_totems_allowed"] = 1, + ["base_number_of_traps_allowed"] = 15, + ["base_number_of_remote_mines_allowed"] = 15, + ["max_charged_attack_stacks"] = 6, + ["max_talisman_degen_stacks"] = 20, + ["max_frost_nova_stacks"] = 20, + ["max_rampage_stacks"] = 1000, + ["damage_+%_per_10_rampage_stacks"] = 2, + ["movement_velocity_+%_per_10_rampage_stacks"] = 1, + ["pvp_shield_damage_+%_final"] = -15, + ["maximum_life_leech_rate_%_per_minute"] = 1200, + ["maximum_mana_leech_rate_%_per_minute"] = 1200, + ["maximum_energy_shield_leech_rate_%_per_minute"] = 600, + ["minions_have_labyrinth_trap_degen_effect_+%"] = -90, + ["minions_are_immune_to_labyrinth_degen_effect"] = 0, + ["minion_damage_taken_+%_from_spike_traps_final"] = -90, + ["minion_damage_taken_+%_from_arrow_traps_final"] = 0, + ["minion_damage_taken_+%_from_guillotine_traps_final"] = -90, + ["minion_global_always_hit"] = 1, + ["traps_explode_on_timeout"] = 1, + ["maximum_rage"] = 30, + ["max_delve_degen_stacks"] = 5000, + ["max_azurite_debuff_stacks"] = 10, + ["melee_variation"] = 1, + ["impaled_debuff_base_duration_ms"] = 8000, + ["impaled_debuff_number_of_reflected_hits"] = 5, + ["base_total_number_of_sigils_allowed"] = 3, + ["maximum_life_leech_amount_per_leech_%_max_life"] = 10, + ["maximum_mana_leech_amount_per_leech_%_max_mana"] = 10, + ["maximum_energy_shield_leech_amount_per_leech_%_max_energy_shield"] = 10, + ["enable_movement_skill_animation_skipping"] = 1, + ["melee_hit_damage_stun_multiplier_+%"] = 25, + ["non_physical_hit_damage_stun_multiplier_+%"] = -25, + ["non_melee_hit_damage_stun_multiplier_+%_final"] = -25, + ["object_inherent_melee_hit_stun_duration_+%_final"] = 50, + ["additional_insanity_effects_while_delirious"] = 1, + ["max_steel_ammo"] = 12, + ["chance_to_deal_triple_damage_%_per_brutal_charge"] = 3, + ["stun_threshold_+%_per_brutal_charge"] = 10, + ["ailment_damage_+%_final_per_affliction_charge"] = 8, + ["non_damaging_ailment_effect_+%_final_per_affliction_charge"] = 8, + ["elemental_damage_taken_goes_to_energy_shield_over_4_seconds_%_per_absorption_charge"] = 12, + ["actor_scale_+%_limit"] = 100, + ["mines_invulnerable_for_duration_ms"] = 2000, + ["traps_invulnerable_for_duration_ms"] = 2000, + ["damage_taken_when_hit_+%_final_per_fortification"] = -1, + ["base_max_fortification"] = 20, + ["base_presence_radius"] = 80, + ["mtx_max_killcounter_stacks"] = 30000, + ["soul_eater_maximum_stacks"] = 45, + ["rage_loss_delay_ms"] = 2000, + ["trigger_attack_graft_each_second_channeling"] = 1, + ["base_speed"] = 37, +} +-- From Metadata/Monsters/Monster.ot +data.monsterConstants = { + ["item_drop_slots"] = 1, + ["energy_shield_recharge_rate_per_minute_%"] = 2000, + ["mana_regeneration_rate_per_minute_%"] = 100, + ["base_maximum_mana"] = 200, + ["maximum_physical_damage_reduction_%"] = 75, + ["max_viper_strike_orbs"] = 4, + ["base_maximum_all_resistances_%"] = 75, + ["max_fuse_arrow_orbs"] = 5, + ["max_fire_beam_stacks"] = 8, + ["max_charged_attack_stacks"] = 10, + ["base_critical_strike_multiplier"] = 130, + ["critical_ailment_dot_multiplier_+"] = 30, + ["max_endurance_charges"] = 3, + ["max_frenzy_charges"] = 3, + ["max_power_charges"] = 3, + ["base_attack_speed_+%_per_frenzy_charge"] = 4, + ["base_attack_speed_+%_per_frenzy_charge_if_not_player_minion"] = 11, + ["base_cast_speed_+%_per_frenzy_charge"] = 4, + ["base_cast_speed_+%_per_frenzy_charge_if_not_player_minion"] = 11, + ["movement_velocity_+%_per_frenzy_charge_if_not_player_minion"] = 5, + ["object_inherent_damage_+%_final_per_frenzy_charge"] = 4, + ["physical_damage_reduction_%_per_endurance_charge"] = 4, + ["physical_damage_reduction_%_per_endurance_charge_if_not_player_minion"] = 11, + ["elemental_damage_reduction_%_per_endurance_charge_if_player_minion"] = 4, + ["resist_all_elements_%_per_endurance_charge_if_not_player_minion"] = 15, + ["critical_strike_chance_+%_per_power_charge"] = 50, + ["critical_strike_chance_+%_per_power_charge_if_not_player_minion"] = 150, + ["maximum_block_%"] = 75, + ["base_maximum_spell_block_%"] = 75, + ["base_number_of_totems_allowed"] = 1, + ["base_number_of_traps_allowed"] = 3, + ["base_number_of_remote_mines_allowed"] = 5, + ["movement_velocity_cap"] = 128, + ["maximum_life_leech_rate_%_per_minute"] = 1200, + ["maximum_mana_leech_rate_%_per_minute"] = 1200, + ["maximum_energy_shield_leech_rate_%_per_minute"] = 600, + ["monster_ignite_damage_+%_final"] = -72, + ["monster_bleeding_damage_+%_final"] = -86, + ["monster_poison_damage_+%_final"] = -50, + ["bleeding_moving_damage_%_of_base_override"] = 500, + ["max_azurite_debuff_stacks"] = 10, + ["impaled_debuff_base_duration_ms"] = 8000, + ["impaled_debuff_number_of_reflected_hits"] = 5, + ["ignore_skill_weapon_restrictions"] = 1, + ["base_total_number_of_sigils_allowed"] = 3, + ["maximum_life_leech_amount_per_leech_%_max_life"] = 10, + ["maximum_mana_leech_amount_per_leech_%_max_mana"] = 10, + ["maximum_energy_shield_leech_amount_per_leech_%_max_energy_shield"] = 10, + ["object_inherent_melee_hit_stun_duration_+%_final"] = 20, + ["scale_melee_range_to_actor_scale"] = 1, + ["use_melee_pattern_range"] = 1, + ["actor_scale_+%_limit"] = 100, + ["damage_taken_when_hit_+%_final_per_fortification"] = -1, + ["base_max_fortification"] = 20, + ["soul_eater_maximum_stacks"] = 45, +} -- From MonsterVarieties.dat combined with SkillTotemVariations.dat data.totemLifeMult = { [1] = 1, [2] = 1, [3] = 1, [4] = 1.2, [5] = 1, [6] = 1.2, [7] = 1.2, [8] = 1.2, [9] = 1, [10] = 1, [11] = 1, [12] = 1, [13] = 1.2, [15] = 1.2, [16] = 7.44, [17] = 1.2, [18] = 1, [19] = 1, [20] = 1.2, [21] = 1.2, } data.monsterVarietyLifeMult = { diff --git a/src/Export/Classes/GGPKData.lua b/src/Export/Classes/GGPKData.lua index d6e896b14c..0fb747a709 100644 --- a/src/Export/Classes/GGPKData.lua +++ b/src/Export/Classes/GGPKData.lua @@ -308,6 +308,10 @@ function GGPKClass:GetNeededFiles() "Metadata/StatDescriptions/tincture_stat_descriptions.txt", "Metadata/StatDescriptions/graft_stat_descriptions.txt", } + local otFiles = { + "Metadata/Characters/Character.ot", + "Metadata/Monsters/Monster.ot", + } local itFiles = { "Metadata/Items/Quivers/AbstractQuiver.it", "Metadata/Items/Rings/AbstractRing.it", @@ -351,5 +355,5 @@ function GGPKClass:GetNeededFiles() "Metadata/Items/Tinctures/AbstractTincture.it", "Metadata/Items/Jewels/AbstractAnimalCharm.it", } - return datFiles, txtFiles, itFiles + return datFiles, txtFiles, otFiles, itFiles end diff --git a/src/Export/Scripts/miscdata.lua b/src/Export/Scripts/miscdata.lua index 2c1702496f..fd0cb2863b 100644 --- a/src/Export/Scripts/miscdata.lua +++ b/src/Export/Scripts/miscdata.lua @@ -48,6 +48,48 @@ for row in dat("GameConstants"):Rows() do end out:write('}\n') +out:write('-- From Metadata/Characters/Character.ot\n') +out:write('data.characterConstants = {\n') +local file = getFile("Metadata/Characters/Character.ot") +if not file then return nil end +local text = convertUTF16to8(file) +local inWantedBlock = false +for line in text:gmatch("[^\r\n]+") do + -- Detect start of a block + if line:match("^Stats") or line:match("^Pathfinding") then + inWantedBlock = true + elseif inWantedBlock and line:match("^}") then + inWantedBlock = false + elseif inWantedBlock and line:find("=") then + local key, value = line:gsub("%s+",""):match("^(.-)=(.+)$") + if key and value then + out:write('\t["' .. key .. '"] = ' .. value .. ',\n') + end + end +end +out:write('}\n') + +out:write('-- From Metadata/Monsters/Monster.ot\n') +out:write('data.monsterConstants = {\n') +local file = getFile("Metadata/Monsters/Monster.ot") +if not file then return nil end +local text = convertUTF16to8(file) +local inWantedBlock = false +for line in text:gmatch("[^\r\n]+") do + -- Detect start of a block + if line:match("^Stats") then + inWantedBlock = true + elseif inWantedBlock and line:match("^}") then + inWantedBlock = false + elseif inWantedBlock and line:find("=") then + local key, value = line:gsub("%s+",""):match("^(.-)=(.+)$") + if key and value then + out:write('\t["' .. key .. '"] = ' .. value .. ',\n') + end + end +end +out:write('}\n') + local totemMult = "" local keys = { } for var in dat("SkillTotemVariations"):Rows() do diff --git a/src/Modules/CalcOffence.lua b/src/Modules/CalcOffence.lua index e64fe6dd86..591f74b5f0 100644 --- a/src/Modules/CalcOffence.lua +++ b/src/Modules/CalcOffence.lua @@ -3198,7 +3198,7 @@ function calcs.offence(env, actor, activeSkill) if skillModList:Flag(cfg, "IgnoreEnemyPhysicalDamageReduction") or ChanceToIgnoreEnemyPhysicalDamageReduction >= 100 then resist = 0 else - resist = m_min(m_max(0, enemyDB:Sum("BASE", nil, "PhysicalDamageReduction") + skillModList:Sum("BASE", cfg, "EnemyPhysicalDamageReduction") + armourReduction), data.misc.DamageReductionCap) + resist = m_min(m_max(0, enemyDB:Sum("BASE", nil, "PhysicalDamageReduction") + skillModList:Sum("BASE", cfg, "EnemyPhysicalDamageReduction") + armourReduction), data.misc.EnemyPhysicalDamageReductionCap) resist = resist > 0 and resist * (1 - (skillModList:Sum("BASE", nil, "PartialIgnoreEnemyPhysicalDamageReduction") / 100 + ChanceToIgnoreEnemyPhysicalDamageReduction / 100)) or resist end else @@ -4204,7 +4204,7 @@ function calcs.offence(env, actor, activeSkill) skillFlags.duration = true local effMult = 1 if env.mode_effective then - local resist = m_min(m_max(0, enemyDB:Sum("BASE", nil, "PhysicalDamageReduction")), data.misc.DamageReductionCap) + local resist = m_min(m_max(0, enemyDB:Sum("BASE", nil, "PhysicalDamageReduction")), data.misc.EnemyPhysicalDamageReductionCap) local takenInc = enemyDB:Sum("INC", dotCfg, "DamageTaken", "DamageTakenOverTime", "PhysicalDamageTaken", "PhysicalDamageTakenOverTime") local takenMore = enemyDB:More(dotCfg, "DamageTaken", "DamageTakenOverTime", "PhysicalDamageTaken", "PhysicalDamageTakenOverTime") effMult = (1 - resist / 100) * (1 + takenInc / 100) * takenMore @@ -5209,7 +5209,7 @@ function calcs.offence(env, actor, activeSkill) local enemyArmour = m_max(calcLib.val(enemyDB, "Armour"), 0) local impaleArmourReduction = calcs.armourReductionF(enemyArmour, impaleHitDamageMod * output.impaleStoredHitAvg) - local impaleResist = m_min(m_max(0, enemyDB:Sum("BASE", nil, "PhysicalDamageReduction") + skillModList:Sum("BASE", cfg, "EnemyImpalePhysicalDamageReduction") + impaleArmourReduction), data.misc.DamageReductionCap) + local impaleResist = m_min(m_max(0, enemyDB:Sum("BASE", nil, "PhysicalDamageReduction") + skillModList:Sum("BASE", cfg, "EnemyImpalePhysicalDamageReduction") + impaleArmourReduction), data.misc.EnemyPhysicalDamageReductionCap) if skillModList:Flag(cfg, "IgnoreEnemyImpalePhysicalDamageReduction") then impaleResist = 0 end @@ -5422,7 +5422,7 @@ function calcs.offence(env, actor, activeSkill) local takenInc = enemyDB:Sum("INC", dotTakenCfg, "DamageTaken", "DamageTakenOverTime", damageType.."DamageTaken", damageType.."DamageTakenOverTime") + (isElemental[damageType] and enemyDB:Sum("INC", dotTakenCfg, "ElementalDamageTaken") or 0) local takenMore = enemyDB:More(dotTakenCfg, "DamageTaken", "DamageTakenOverTime", damageType.."DamageTaken", damageType.."DamageTakenOverTime") * (isElemental[damageType] and enemyDB:More(dotTakenCfg, "ElementalDamageTaken") or 1) if damageType == "Physical" then - resist = m_max(0, m_min(enemyDB:Sum("BASE", nil, "PhysicalDamageReduction"), data.misc.DamageReductionCap)) + resist = m_max(0, m_min(enemyDB:Sum("BASE", nil, "PhysicalDamageReduction"), data.misc.EnemyPhysicalDamageReductionCap)) else resist = calcResistForType(damageType, dotTypeCfg) end diff --git a/src/Modules/CalcPerform.lua b/src/Modules/CalcPerform.lua index a938b8eb10..a00d405b41 100644 --- a/src/Modules/CalcPerform.lua +++ b/src/Modules/CalcPerform.lua @@ -1098,22 +1098,21 @@ function calcs.perform(env, skipEHP) -- Minion Attacks now inherently always hit (Patch 3.27) env.minion.modDB:NewMod("CannotBeEvaded", "FLAG", 1, "Minion Attacks always hit") end - env.minion.modDB:NewMod("CritMultiplier", "BASE", 30, "Base") - env.minion.modDB:NewMod("CritDegenMultiplier", "BASE", 30, "Base") + env.minion.modDB:NewMod("CritMultiplier", "BASE", env.data.monsterConstants["base_critical_strike_multiplier"] - 100, "Base") + env.minion.modDB:NewMod("DotMultiplier", "BASE", env.data.monsterConstants["critical_ailment_dot_multiplier_+"], "Base", { type = "Condition", var = "CriticalStrike" }) env.minion.modDB:NewMod("FireResist", "BASE", env.minion.minionData.fireResist, "Base") env.minion.modDB:NewMod("ColdResist", "BASE", env.minion.minionData.coldResist, "Base") env.minion.modDB:NewMod("LightningResist", "BASE", env.minion.minionData.lightningResist, "Base") env.minion.modDB:NewMod("ChaosResist", "BASE", env.minion.minionData.chaosResist, "Base") - env.minion.modDB:NewMod("CritChance", "INC", 50, "Base", { type = "Multiplier", var = "PowerCharge" }) - env.minion.modDB:NewMod("Speed", "INC", 4, "Base", ModFlag.Attack, { type = "Multiplier", var = "FrenzyCharge" }) - env.minion.modDB:NewMod("Speed", "INC", 4, "Base", ModFlag.Cast, { type = "Multiplier", var = "FrenzyCharge" }) - env.minion.modDB:NewMod("Damage", "MORE", 4, "Base", { type = "Multiplier", var = "FrenzyCharge" }) - env.minion.modDB:NewMod("PhysicalDamageReduction", "BASE", 4, "Base", { type = "Multiplier", var = "EnduranceCharge" }) - env.minion.modDB:NewMod("ElementalDamageReduction", "BASE", 4, "Base", { type = "Multiplier", var = "EnduranceCharge" }) + env.minion.modDB:NewMod("CritChance", "INC", env.data.monsterConstants["critical_strike_chance_+%_per_power_charge"], "Base", { type = "Multiplier", var = "PowerCharge" }) + env.minion.modDB:NewMod("Speed", "INC", env.data.monsterConstants["base_attack_speed_+%_per_frenzy_charge"], "Base", ModFlag.Attack, { type = "Multiplier", var = "FrenzyCharge" }) + env.minion.modDB:NewMod("Speed", "INC", env.data.monsterConstants["base_cast_speed_+%_per_frenzy_charge"], "Base", ModFlag.Cast, { type = "Multiplier", var = "FrenzyCharge" }) + env.minion.modDB:NewMod("Damage", "MORE", env.data.monsterConstants["object_inherent_damage_+%_final_per_frenzy_charge"], "Base", { type = "Multiplier", var = "FrenzyCharge" }) + env.minion.modDB:NewMod("PhysicalDamageReduction", "BASE", env.data.monsterConstants["physical_damage_reduction_%_per_endurance_charge"], "Base", { type = "Multiplier", var = "EnduranceCharge" }) + env.minion.modDB:NewMod("ElementalDamageReduction", "BASE", env.data.monsterConstants["elemental_damage_reduction_%_per_endurance_charge_if_player_minion"], "Base", { type = "Multiplier", var = "EnduranceCharge" }) env.minion.modDB:NewMod("ProjectileCount", "BASE", 1, "Base") - env.minion.modDB:NewMod("MaximumFortification", "BASE", 20, "Base") + env.minion.modDB:NewMod("MaximumFortification", "BASE", env.data.monsterConstants["base_max_fortification"], "Base") env.minion.modDB:NewMod("Damage", "MORE", 200, "Base", 0, KeywordFlag.Bleed, { type = "ActorCondition", actor = "enemy", var = "Moving" }) - env.minion.modDB:NewMod("DotMultiplier", "BASE", 30, "Base", { type = "Condition", var = "CriticalStrike" }) for _, mod in ipairs(env.minion.minionData.modList) do env.minion.modDB:AddMod(mod) end diff --git a/src/Modules/CalcSetup.lua b/src/Modules/CalcSetup.lua index 8d5b486472..bd2cabed0e 100644 --- a/src/Modules/CalcSetup.lua +++ b/src/Modules/CalcSetup.lua @@ -16,44 +16,44 @@ local tempTable1 = { } -- Initialise modifier database with stats and conditions common to all actors function calcs.initModDB(env, modDB) - modDB:NewMod("FireResistMax", "BASE", 75, "Base") - modDB:NewMod("ColdResistMax", "BASE", 75, "Base") - modDB:NewMod("LightningResistMax", "BASE", 75, "Base") - modDB:NewMod("ChaosResistMax", "BASE", 75, "Base") - modDB:NewMod("TotemFireResistMax", "BASE", 75, "Base") - modDB:NewMod("TotemColdResistMax", "BASE", 75, "Base") - modDB:NewMod("TotemLightningResistMax", "BASE", 75, "Base") - modDB:NewMod("TotemChaosResistMax", "BASE", 75, "Base") - modDB:NewMod("BlockChanceMax", "BASE", 75, "Base") - modDB:NewMod("SpellBlockChanceMax", "BASE", 75, "Base") + modDB:NewMod("FireResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("ColdResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("LightningResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("ChaosResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("TotemFireResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("TotemColdResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("TotemLightningResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("TotemChaosResistMax", "BASE", data.characterConstants["base_maximum_all_resistances_%"], "Base") + modDB:NewMod("BlockChanceMax", "BASE", data.characterConstants["maximum_block_%"], "Base") + modDB:NewMod("SpellBlockChanceMax", "BASE", data.characterConstants["base_maximum_spell_block_%"], "Base") modDB:NewMod("SpellDodgeChanceMax", "BASE", 75, "Base") modDB:NewMod("ChargeDuration", "BASE", 10, "Base") - modDB:NewMod("PowerChargesMax", "BASE", 3, "Base") - modDB:NewMod("FrenzyChargesMax", "BASE", 3, "Base") - modDB:NewMod("EnduranceChargesMax", "BASE", 3, "Base") + modDB:NewMod("PowerChargesMax", "BASE", data.characterConstants["max_power_charges"], "Base") + modDB:NewMod("FrenzyChargesMax", "BASE", data.characterConstants["max_frenzy_charges"], "Base") + modDB:NewMod("EnduranceChargesMax", "BASE", data.characterConstants["max_endurance_charges"], "Base") modDB:NewMod("SiphoningChargesMax", "BASE", 0, "Base") modDB:NewMod("ChallengerChargesMax", "BASE", 0, "Base") modDB:NewMod("BlitzChargesMax", "BASE", 0, "Base") - modDB:NewMod("InspirationChargesMax", "BASE", 5, "Base") + modDB:NewMod("InspirationChargesMax", "BASE", data.characterConstants["maximum_righteous_charges"], "Base") modDB:NewMod("CrabBarriersMax", "BASE", 0, "Base") modDB:NewMod("BrutalChargesMax", "BASE", 0, "Base") modDB:NewMod("AbsorptionChargesMax", "BASE", 0, "Base") modDB:NewMod("AfflictionChargesMax", "BASE", 0, "Base") - modDB:NewMod("BloodChargesMax", "BASE", 5, "Base") - modDB:NewMod("MaxLifeLeechRate", "BASE", 20, "Base") - modDB:NewMod("MaxManaLeechRate", "BASE", 20, "Base") - modDB:NewMod("ImpaleStacksMax", "BASE", 5, "Base") + modDB:NewMod("BloodChargesMax", "BASE", data.characterConstants["maximum_blood_scythe_charges"], "Base") + modDB:NewMod("MaxLifeLeechRate", "BASE", data.characterConstants["maximum_life_leech_rate_%_per_minute"] / 60, "Base") + modDB:NewMod("MaxManaLeechRate", "BASE", data.characterConstants["maximum_mana_leech_rate_%_per_minute"] / 60, "Base") + modDB:NewMod("ImpaleStacksMax", "BASE", data.characterConstants["impaled_debuff_number_of_reflected_hits"], "Base") modDB:NewMod("BleedStacksMax", "BASE", 1, "Base") modDB:NewMod("MaxEnergyShieldLeechRate", "BASE", 10, "Base") - modDB:NewMod("MaxLifeLeechInstance", "BASE", 10, "Base") - modDB:NewMod("MaxManaLeechInstance", "BASE", 10, "Base") - modDB:NewMod("MaxEnergyShieldLeechInstance", "BASE", 10, "Base") + modDB:NewMod("MaxLifeLeechInstance", "BASE", data.characterConstants["maximum_life_leech_amount_per_leech_%_max_life"] , "Base") + modDB:NewMod("MaxManaLeechInstance", "BASE", data.characterConstants["maximum_mana_leech_amount_per_leech_%_max_mana"], "Base") + modDB:NewMod("MaxEnergyShieldLeechInstance", "BASE", data.characterConstants["maximum_energy_shield_leech_amount_per_leech_%_max_energy_shield"], "Base") modDB:NewMod("TrapThrowingTime", "BASE", 0.6, "Base") modDB:NewMod("MineLayingTime", "BASE", 0.3, "Base") modDB:NewMod("WarcryCastTime", "BASE", 0.8, "Base") modDB:NewMod("TotemPlacementTime", "BASE", 0.6, "Base") modDB:NewMod("BallistaPlacementTime", "BASE", 0.35, "Base") - modDB:NewMod("ActiveTotemLimit", "BASE", 1, "Base") + modDB:NewMod("ActiveTotemLimit", "BASE", data.characterConstants["base_number_of_totems_allowed"], "Base") modDB:NewMod("ShockStacksMax", "BASE", 1, "Base") modDB:NewMod("ScorchStacksMax", "BASE", 1, "Base") modDB:NewMod("MovementSpeed", "INC", -30, "Base", { type = "Condition", var = "Maimed" }) @@ -468,14 +468,14 @@ function calcs.initEnv(build, mode, override, specEnv) end modDB.multipliers["Level"] = m_max(1, m_min(100, build.characterLevel)) calcs.initModDB(env, modDB) - modDB:NewMod("Life", "BASE", 12, "Base", { type = "Multiplier", var = "Level", base = 38 }) - modDB:NewMod("Mana", "BASE", 6, "Base", { type = "Multiplier", var = "Level", base = 34 }) - modDB:NewMod("ManaRegen", "BASE", 0.0175, "Base", { type = "PerStat", stat = "Mana", div = 1 }) + modDB:NewMod("Life", "BASE", data.characterConstants["life_per_level"], "Base", { type = "Multiplier", var = "Level", base = 38 }) + modDB:NewMod("Mana", "BASE", data.characterConstants["mana_per_level"], "Base", { type = "Multiplier", var = "Level", base = 34 }) + modDB:NewMod("ManaRegen", "BASE", env.data.misc.ManaRegenBase, "Base", { type = "PerStat", stat = "Mana", div = 1 }) modDB:NewMod("Devotion", "BASE", 0, "Base") - modDB:NewMod("Evasion", "BASE", 15, "Base") - modDB:NewMod("Accuracy", "BASE", 2, "Base", { type = "Multiplier", var = "Level", base = -2 }) - modDB:NewMod("CritMultiplier", "BASE", 50, "Base") - modDB:NewMod("DotMultiplier", "BASE", 50, "Base", { type = "Condition", var = "CriticalStrike" }) + modDB:NewMod("Evasion", "BASE", data.characterConstants["base_evasion_rating"], "Base") + modDB:NewMod("Accuracy", "BASE", data.characterConstants["accuracy_rating_per_level"], "Base", { type = "Multiplier", var = "Level", base = -data.characterConstants["accuracy_rating_per_level"] }) + modDB:NewMod("CritMultiplier", "BASE", data.characterConstants["base_critical_strike_multiplier"] - 100, "Base") + modDB:NewMod("DotMultiplier", "BASE", data.characterConstants["critical_ailment_dot_multiplier_+"], "Base", { type = "Condition", var = "CriticalStrike" }) modDB:NewMod("FireResist", "BASE", env.configInput.resistancePenalty or -60, "Base") modDB:NewMod("ColdResist", "BASE", env.configInput.resistancePenalty or -60, "Base") modDB:NewMod("LightningResist", "BASE", env.configInput.resistancePenalty or -60, "Base") @@ -484,45 +484,46 @@ function calcs.initEnv(build, mode, override, specEnv) modDB:NewMod("TotemColdResist", "BASE", 40, "Base") modDB:NewMod("TotemLightningResist", "BASE", 40, "Base") modDB:NewMod("TotemChaosResist", "BASE", 20, "Base") - modDB:NewMod("CritChance", "INC", 50, "Base", { type = "Multiplier", var = "PowerCharge" }) - modDB:NewMod("Speed", "INC", 4, "Base", ModFlag.Attack, { type = "Multiplier", var = "FrenzyCharge" }) - modDB:NewMod("Speed", "INC", 4, "Base", ModFlag.Cast, { type = "Multiplier", var = "FrenzyCharge" }) - modDB:NewMod("Damage", "MORE", 4, "Base", { type = "Multiplier", var = "FrenzyCharge" }) - modDB:NewMod("PhysicalDamageReduction", "BASE", 4, "Base", { type = "Multiplier", var = "EnduranceCharge" }) - modDB:NewMod("ElementalDamageReduction", "BASE", 4, "Base", { type = "Multiplier", var = "EnduranceCharge" }) - modDB:NewMod("MaximumRage", "BASE", 30, "Base") + modDB:NewMod("CritChance", "INC", data.characterConstants["critical_strike_chance_+%_per_power_charge"], "Base", { type = "Multiplier", var = "PowerCharge" }) + modDB:NewMod("Speed", "INC", data.characterConstants["base_attack_speed_+%_per_frenzy_charge"], "Base", ModFlag.Attack, { type = "Multiplier", var = "FrenzyCharge" }) + modDB:NewMod("Speed", "INC", data.characterConstants["base_cast_speed_+%_per_frenzy_charge"], "Base", ModFlag.Cast, { type = "Multiplier", var = "FrenzyCharge" }) + modDB:NewMod("Damage", "MORE", data.characterConstants["object_inherent_damage_+%_final_per_frenzy_charge"], "Base", { type = "Multiplier", var = "FrenzyCharge" }) + modDB:NewMod("PhysicalDamageReduction", "BASE", data.characterConstants["physical_damage_reduction_%_per_endurance_charge"], "Base", { type = "Multiplier", var = "EnduranceCharge" }) + modDB:NewMod("ElementalDamageReduction", "BASE", data.characterConstants["elemental_damage_reduction_%_per_endurance_charge"], "Base", { type = "Multiplier", var = "EnduranceCharge" }) + modDB:NewMod("MaximumRage", "BASE", data.characterConstants["maximum_rage"], "Base") modDB:NewMod("Multiplier:GaleForce", "BASE", 0, "Base") modDB:NewMod("MaximumGaleForce", "BASE", 10, "Base") - modDB:NewMod("MaximumFortification", "BASE", 20, "Base") + modDB:NewMod("MaximumFortification", "BASE", data.characterConstants["base_max_fortification"], "Base") modDB:NewMod("MaximumValour", "BASE", 50, "Base") - modDB:NewMod("SoulEaterMax", "BASE", 45, "Base") + modDB:NewMod("SoulEaterMax", "BASE", data.characterConstants["soul_eater_maximum_stacks"], "Base") modDB:NewMod("Multiplier:IntensityLimit", "BASE", 3, "Base") - modDB:NewMod("Damage", "INC", 2, "Base", { type = "Multiplier", var = "Rampage", limit = 50, div = 20 }) - modDB:NewMod("MovementSpeed", "INC", 1, "Base", { type = "Multiplier", var = "Rampage", limit = 50, div = 20 }) + modDB:NewMod("Damage", "INC", data.characterConstants["damage_+%_per_10_rampage_stacks"], "Base", { type = "Multiplier", var = "Rampage", limit = data.characterConstants["max_rampage_stacks"] / 20, div = 20 }) + modDB:NewMod("MovementSpeed", "INC", data.characterConstants["movement_velocity_+%_per_10_rampage_stacks"], "Base", { type = "Multiplier", var = "Rampage", limit = data.characterConstants["max_rampage_stacks"] / 20, div = 20 }) modDB:NewMod("Speed", "INC", 5, "Base", ModFlag.Attack, { type = "Multiplier", var = "SoulEater"}) modDB:NewMod("Speed", "INC", 5, "Base", ModFlag.Cast, { type = "Multiplier", var = "SoulEater" }) - modDB:NewMod("ActiveTrapLimit", "BASE", 15, "Base") - modDB:NewMod("ActiveMineLimit", "BASE", 15, "Base") + modDB:NewMod("ActiveTrapLimit", "BASE", data.characterConstants["base_number_of_traps_allowed"], "Base") + modDB:NewMod("ActiveMineLimit", "BASE", data.characterConstants["base_number_of_remote_mines_allowed"], "Base") modDB:NewMod("MineThrowCount", "BASE", 1, "Base") modDB:NewMod("TrapThrowCount", "BASE", 1, "Base") modDB:NewMod("ActiveBrandLimit", "BASE", 3, "Base") modDB:NewMod("EnemyCurseLimit", "BASE", 1, "Base") modDB:NewMod("SocketedCursesHexLimitValue", "BASE", 1, "Base") modDB:NewMod("ProjectileCount", "BASE", 1, "Base") - modDB:NewMod("Speed", "MORE", 10, "Base", ModFlag.Attack, { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "DoubledInherentDualWieldingSpeed", neg = true }) - modDB:NewMod("Speed", "MORE", 20, "Base", ModFlag.Attack, { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "DoubledInherentDualWieldingSpeed"}) - modDB:NewMod("BlockChance", "BASE", 20, "Base", { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "NoInherentBlock", neg = true}, { type = "Condition", var = "DoubledInherentDualWieldingBlock", neg = true}) - modDB:NewMod("BlockChance", "BASE", 40, "Base", { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "NoInherentBlock", neg = true}, { type = "Condition", var = "DoubledInherentDualWieldingBlock"}) + modDB:NewMod("Speed", "MORE", data.characterConstants["dual_wield_inherent_attack_speed_+%_final"], "Base", ModFlag.Attack, { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "DoubledInherentDualWieldingSpeed", neg = true }) + modDB:NewMod("Speed", "MORE", 2 * data.characterConstants["dual_wield_inherent_attack_speed_+%_final"], "Base", ModFlag.Attack, { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "DoubledInherentDualWieldingSpeed"}) + modDB:NewMod("BlockChance", "BASE", data.characterConstants["inherent_block_while_dual_wielding_%"], "Base", { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "NoInherentBlock", neg = true}, { type = "Condition", var = "DoubledInherentDualWieldingBlock", neg = true}) + modDB:NewMod("BlockChance", "BASE", 2 * data.characterConstants["inherent_block_while_dual_wielding_%"], "Base", { type = "Condition", var = "DualWielding" }, { type = "Condition", var = "NoInherentBlock", neg = true}, { type = "Condition", var = "DoubledInherentDualWieldingBlock"}) modDB:NewMod("Damage", "MORE", 200, "Base", 0, KeywordFlag.Bleed, { type = "ActorCondition", actor = "enemy", var = "Moving" }, { type = "Condition", var = "NoExtraBleedDamageToMovingEnemy", neg = true }) modDB:NewMod("Condition:BloodStance", "FLAG", true, "Base", { type = "Condition", var = "SandStance", neg = true }) modDB:NewMod("Condition:PrideMinEffect", "FLAG", true, "Base", { type = "Condition", var = "PrideMaxEffect", neg = true }) - modDB:NewMod("PerBrutalTripleDamageChance", "BASE", 3, "Base") - modDB:NewMod("PerAfflictionAilmentDamage", "BASE", 8, "Base") - modDB:NewMod("PerAfflictionNonDamageEffect", "BASE", 8, "Base") - modDB:NewMod("PerAbsorptionElementalEnergyShieldRecoup", "BASE", 12, "Base") + modDB:NewMod("PerBrutalTripleDamageChance", "BASE", data.characterConstants["chance_to_deal_triple_damage_%_per_brutal_charge"], "Base") + modDB:NewMod("PerAfflictionAilmentDamage", "BASE", data.characterConstants["ailment_damage_+%_final_per_affliction_charge"], "Base") + modDB:NewMod("PerAfflictionNonDamageEffect", "BASE", data.characterConstants["non_damaging_ailment_effect_+%_final_per_affliction_charge"], "Base") + modDB:NewMod("PerAbsorptionElementalEnergyShieldRecoup", "BASE", data.characterConstants["elemental_damage_taken_goes_to_energy_shield_over_4_seconds_%_per_absorption_charge"], "Base") modDB:NewMod("TinctureLimit", "BASE", 1, "Base") modDB:NewMod("ManaDegenPercent", "BASE", 1, "Base", { type = "Multiplier", var = "EffectiveManaBurnStacks" }) modDB:NewMod("LifeDegenPercent", "BASE", 1, "Base", { type = "Multiplier", var = "WeepingWoundsStacks" }) + modDB:NewMod("PresenceRadius", "BASE", data.characterConstants["base_presence_radius"], "Base") -- Add bandit mods if env.configInput.bandit == "Alira" then diff --git a/src/Modules/ConfigOptions.lua b/src/Modules/ConfigOptions.lua index 6ae5d8796c..81705e0c4c 100644 --- a/src/Modules/ConfigOptions.lua +++ b/src/Modules/ConfigOptions.lua @@ -1875,7 +1875,7 @@ Huge sets the radius to 11. -- These defaults are here so that the placeholders get reset correctly build.configTab.varControls['enemySpeed']:SetPlaceholder(700, true) build.configTab.varControls['enemyCritChance']:SetPlaceholder(5, true) - build.configTab.varControls['enemyCritDamage']:SetPlaceholder(30, true) + build.configTab.varControls['enemyCritDamage']:SetPlaceholder(data.monsterConstants["base_critical_strike_multiplier"] - 100, true) if val == "None" then local defaultResist = "" build.configTab.varControls['enemyLightningResist']:SetPlaceholder(defaultResist, true) @@ -2152,7 +2152,7 @@ Huge sets the radius to 11. enemyModList:NewMod("MultiplierPvpDamage", "BASE", val, "Config") end }, { var = "enemyCritChance", type = "countAllowZero", label = "Enemy critical strike chance:", defaultPlaceholderState = 5 }, - { var = "enemyCritDamage", type = "countAllowZero", label = "Enemy critical strike multiplier:", defaultPlaceholderState = 30 }, + { var = "enemyCritDamage", type = "countAllowZero", label = "Enemy critical strike multiplier:", defaultPlaceholderState = data.monsterConstants["base_critical_strike_multiplier"] - 100 }, { var = "enemyPhysicalDamage", type = "countAllowZero", label = "Enemy Skill Physical Damage:", tooltip = "This overrides the default damage amount used to estimate your damage reduction from armour.\nThe default is 1.5 times the enemy's base damage, which is the same value\nused in-game to calculate the estimate shown on the character sheet.", defaultPlaceholderState = 7 }, { var = "enemyPhysicalOverwhelm", type = "countAllowZero", label = "Enemy Skill Physical Overwhelm:"}, { var = "enemyLightningDamage", type = "countAllowZero", label = "Enemy Skill ^xADAA47Lightning Damage:"}, diff --git a/src/Modules/Data.lua b/src/Modules/Data.lua index ff9783327a..7396429838 100644 --- a/src/Modules/Data.lua +++ b/src/Modules/Data.lua @@ -105,6 +105,9 @@ end data = { } +-- Misc data tables +LoadModule("Data/Misc", data) + data.powerStatList = { { stat=nil, label="Offence/Defence", combinedOffDef=true, ignoreForItems=true }, { stat=nil, label="Name", itemField="Name", ignoreForNodes=true, reverseSort=true, transform=function(value) return value:gsub("^The ","") end}, @@ -164,7 +167,8 @@ data.misc = { -- magic numbers LowPoolThreshold = 0.5, TemporalChainsEffectCap = 75, BuffExpirationSlowCap = 0.25, - DamageReductionCap = 90, + DamageReductionCap = data.characterConstants["maximum_physical_damage_reduction_%"], + EnemyPhysicalDamageReductionCap = data.monsterConstants["maximum_physical_damage_reduction_%"], ResistFloor = -200, MaxResistCap = 90, EvadeChanceCap = 95, @@ -173,11 +177,13 @@ data.misc = { -- magic numbers SuppressionChanceCap = 100, SuppressionEffect = 40, AvoidChanceCap = 75, + ManaRegenBase = data.characterConstants["mana_regeneration_rate_per_minute_%"] / 60 / 100, + EnergyShieldRechargeBase = data.characterConstants["energy_shield_recharge_rate_per_minute_%"] / 60 / 100, EnergyShieldRechargeBase = 0.33, EnergyShieldRechargeDelay = 2, WardRechargeDelay = 2, Transfiguration = 0.3, - EnemyMaxResist = 75, + EnemyMaxResist = data.monsterConstants["base_maximum_all_resistances_%"], LeechRateBase = 0.02, DotDpsCap = 35791394, -- (2 ^ 31 - 1) / 60 (int max / 60 seconds) BleedPercentBase = 70, @@ -192,6 +198,7 @@ data.misc = { -- magic numbers MineAuraRadiusBase = 35, BrandAttachmentRangeBase = 30, ProjectileDistanceCap = 150, + PlayerMovementSpeed = data.characterConstants["base_speed"], MinStunChanceNeeded = 20, StunBaseMult = 200, StunBaseDuration = 0.35, From ccc2cd69b92e89397a78c151f4e8ba28fdf23eae Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 18 Nov 2025 21:04:24 +1100 Subject: [PATCH 2/2] Fix impale tests New phys damage reduction cap was breaking them --- spec/System/TestImpale_spec.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/System/TestImpale_spec.lua b/spec/System/TestImpale_spec.lua index 3254673375..95c4a4f33d 100644 --- a/spec/System/TestImpale_spec.lua +++ b/spec/System/TestImpale_spec.lua @@ -112,34 +112,34 @@ describe("TestAttacks", function() build.configTab.input.customMods = "\z never deal critical strikes\n\z " - build.configTab.input.enemyPhysicalReduction = 10 + build.configTab.input.enemyPhysicalReduction = 8 build.configTab.input.enemyArmour = 1000 -- 50% dr for 200 damage, 66.6% dr for 100 dmg (impale stacks) build.configTab:BuildModList() runCallback("OnFrame") -- dam * (1 - (armourDR + additionalDR) - assert.are.equals(200 * (1 - (0.5 + 0.1)), build.calcsTab.mainOutput.MainHand.PhysicalHitAverage) + assert.are.equals(200 * (1 - (0.5 + 0.08)), build.calcsTab.mainOutput.MainHand.PhysicalHitAverage) assert.are.equals(200, build.calcsTab.mainOutput.MainHand.impaleStoredHitAvg) assert.are.equals(200, build.calcsTab.mainOutput.impaleStoredHitAvg) -- [5 impales * 10% stored damage] * 1.3 attacks * (armour mod - phys DR) - assert.are.near(100 * 1.3 * (1 - (2/3 + 0.1)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math + assert.are.near(100 * 1.3 * (1 - (2/3 + 0.08)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math -- 100% crit build.configTab.input.customMods = "\z +100% critical strike chance\n\z " - build.configTab.input.enemyPhysicalReduction = 10 + build.configTab.input.enemyPhysicalReduction = 8 build.configTab.input.enemyArmour = 1500 -- 50% dr for 300 damage, 66.6% dr for 150 dmg (impale stacks) build.configTab:BuildModList() runCallback("OnFrame") -- dam * (1 - (armourDR + additionalDR) - assert.are.equals(300 * (1 - (0.5 + 0.1)), build.calcsTab.mainOutput.MainHand.PhysicalCritAverage) + assert.are.equals(300 * (1 - (0.5 + 0.08)), build.calcsTab.mainOutput.MainHand.PhysicalCritAverage) assert.are.equals(300, build.calcsTab.mainOutput.MainHand.impaleStoredHitAvg) assert.are.equals(300, build.calcsTab.mainOutput.impaleStoredHitAvg) -- [5 impales * 10% stored damage] * 1.3 attacks * (armour mod - phys DR) - assert.are.near(150 * 1.3 * (1 - (2/3 + 0.1)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math + assert.are.near(150 * 1.3 * (1 - (2/3 + 0.08)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math end) @@ -149,17 +149,17 @@ describe("TestAttacks", function() never deal critical strikes\n\z Nearby enemies take 100% increased physical damage\n\z " - build.configTab.input.enemyPhysicalReduction = 10 + build.configTab.input.enemyPhysicalReduction = 8 build.configTab.input.enemyArmour = 1000 -- 50% dr for 200 damage, 66.6% dr for 100 dmg (impale stacks) .. damage taken is after armour build.configTab:BuildModList() runCallback("OnFrame") -- taken * dam * (1 - (armourDR + additionalDR) - assert.are.equals(2 * 200 * (1 - (0.5 + 0.1)), build.calcsTab.mainOutput.MainHand.PhysicalHitAverage) + assert.are.equals(2 * 200 * (1 - (0.5 + 0.08)), build.calcsTab.mainOutput.MainHand.PhysicalHitAverage) assert.are.equals(200, build.calcsTab.mainOutput.MainHand.impaleStoredHitAvg) assert.are.equals(200, build.calcsTab.mainOutput.impaleStoredHitAvg) -- taken * [5 impales * 10% stored damage] * 1.3 attacks * (armour mod - phys DR) - assert.are.near(2 * 100 * 1.3 * (1 - (2/3 + 0.1)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math + assert.are.near(2 * 100 * 1.3 * (1 - (2/3 + 0.08)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math -- 100% crit @@ -167,17 +167,17 @@ describe("TestAttacks", function() +100% critical strike chance\n\z Nearby enemies take 100% increased physical damage\n\z " - build.configTab.input.enemyPhysicalReduction = 10 + build.configTab.input.enemyPhysicalReduction = 8 build.configTab.input.enemyArmour = 1500 -- 50% dr for 300 damage, 66.6% dr for 150 dmg (impale stacks) build.configTab:BuildModList() runCallback("OnFrame") -- taken * dam * (1 - (armourDR + additionalDR) - assert.are.equals(2 * 300 * (1 - (0.5 + 0.1)), build.calcsTab.mainOutput.MainHand.PhysicalCritAverage) + assert.are.equals(2 * 300 * (1 - (0.5 + 0.08)), build.calcsTab.mainOutput.MainHand.PhysicalCritAverage) assert.are.equals(300, build.calcsTab.mainOutput.MainHand.impaleStoredHitAvg) assert.are.equals(300, build.calcsTab.mainOutput.impaleStoredHitAvg) -- taken * [5 impales * 10% stored damage] * 1.3 attacks * (armour mod - phys DR) - assert.are.near(2 * 150 * 1.3 * (1 - (2/3 + 0.1)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math + assert.are.near(2 * 150 * 1.3 * (1 - (2/3 + 0.08)), build.calcsTab.mainOutput.ImpaleDPS, 0.0000001) -- floating point math end)