diff --git a/utils/mods/legacy_combat.lua b/utils/mods/legacy_combat.lua index 9ab70cf4d..9baf918a8 100644 --- a/utils/mods/legacy_combat.lua +++ b/utils/mods/legacy_combat.lua @@ -1,110 +1,147 @@ -MonkACBonusWeight = RuleI.Get(Rule.MonkACBonusWeight); -NPCACFactor = RuleR.Get(Rule.NPCACFactor); -OldACSoftcapRules = RuleB.Get(Rule.OldACSoftcapRules); -ClothACSoftcap = RuleI.Get(Rule.ClothACSoftcap); -LeatherACSoftcap = RuleI.Get(Rule.LeatherACSoftcap); -MonkACSoftcap = RuleI.Get(Rule.MonkACSoftcap); -ChainACSoftcap = RuleI.Get(Rule.ChainACSoftcap); -PlateACSoftcap = RuleI.Get(Rule.PlateACSoftcap); +--[[ + * + * The purpose of this lua file is to backport combat formulas pre-overhaul + * https://github.com/EQEmu/Server/commit/9e824876ba5dac262b121c0e60d022bb2ecc45bd + * + * If your server has years and years of content built on old formulas, it may be appropriate to use this + * instead of taking on the massive task of rescaling tons of carefully tested and layered content + * +]] -AAMitigationACFactor = RuleR.Get(Rule.AAMitigationACFactor); -WarriorACSoftcapReturn = RuleR.Get(Rule.WarriorACSoftcapReturn); -KnightACSoftcapReturn = RuleR.Get(Rule.KnightACSoftcapReturn); -LowPlateChainACSoftcapReturn = RuleR.Get(Rule.LowPlateChainACSoftcapReturn); +MonkACBonusWeight = RuleI.Get(Rule.MonkACBonusWeight); +NPCACFactor = RuleR.Get(Rule.NPCACFactor); +OldACSoftcapRules = RuleB.Get(Rule.OldACSoftcapRules); +ClothACSoftcap = RuleI.Get(Rule.ClothACSoftcap); +LeatherACSoftcap = RuleI.Get(Rule.LeatherACSoftcap); +MonkACSoftcap = RuleI.Get(Rule.MonkACSoftcap); +ChainACSoftcap = RuleI.Get(Rule.ChainACSoftcap); +PlateACSoftcap = RuleI.Get(Rule.PlateACSoftcap); +AAMitigationACFactor = RuleR.Get(Rule.AAMitigationACFactor); +WarriorACSoftcapReturn = RuleR.Get(Rule.WarriorACSoftcapReturn); +KnightACSoftcapReturn = RuleR.Get(Rule.KnightACSoftcapReturn); +LowPlateChainACSoftcapReturn = RuleR.Get(Rule.LowPlateChainACSoftcapReturn); LowChainLeatherACSoftcapReturn = RuleR.Get(Rule.LowChainLeatherACSoftcapReturn); -CasterACSoftcapReturn = RuleR.Get(Rule.CasterACSoftcapReturn); -MiscACSoftcapReturn = RuleR.Get(Rule.MiscACSoftcapReturn); -WarACSoftcapReturn = RuleR.Get(Rule.WarACSoftcapReturn); -ClrRngMnkBrdACSoftcapReturn = RuleR.Get(Rule.ClrRngMnkBrdACSoftcapReturn); -PalShdACSoftcapReturn = RuleR.Get(Rule.PalShdACSoftcapReturn); +CasterACSoftcapReturn = RuleR.Get(Rule.CasterACSoftcapReturn); +MiscACSoftcapReturn = RuleR.Get(Rule.MiscACSoftcapReturn); +WarACSoftcapReturn = RuleR.Get(Rule.WarACSoftcapReturn); +ClrRngMnkBrdACSoftcapReturn = RuleR.Get(Rule.ClrRngMnkBrdACSoftcapReturn); +PalShdACSoftcapReturn = RuleR.Get(Rule.PalShdACSoftcapReturn); DruNecWizEncMagACSoftcapReturn = RuleR.Get(Rule.DruNecWizEncMagACSoftcapReturn); -RogShmBstBerACSoftcapReturn = RuleR.Get(Rule.RogShmBstBerACSoftcapReturn); -SoftcapFactor = RuleR.Get(Rule.SoftcapFactor); -ACthac0Factor = RuleR.Get(Rule.ACthac0Factor); -ACthac20Factor = RuleR.Get(Rule.ACthac20Factor); +RogShmBstBerACSoftcapReturn = RuleR.Get(Rule.RogShmBstBerACSoftcapReturn); +SoftcapFactor = RuleR.Get(Rule.SoftcapFactor); +ACthac0Factor = RuleR.Get(Rule.ACthac0Factor); +ACthac20Factor = RuleR.Get(Rule.ACthac20Factor); +BaseHitChance = RuleR.Get(Rule.BaseHitChance); +NPCBonusHitChance = RuleR.Get(Rule.NPCBonusHitChance); +HitFalloffMinor = RuleR.Get(Rule.HitFalloffMinor); +HitFalloffModerate = RuleR.Get(Rule.HitFalloffModerate); +HitFalloffMajor = RuleR.Get(Rule.HitFalloffMajor); +HitBonusPerLevel = RuleR.Get(Rule.HitBonusPerLevel); +AgiHitFactor = RuleR.Get(Rule.AgiHitFactor); +WeaponSkillFalloff = RuleR.Get(Rule.WeaponSkillFalloff); +ArcheryHitPenalty = RuleR.Get(Rule.ArcheryHitPenalty); +UseOldDamageIntervalRules = RuleB.Get(Rule.UseOldDamageIntervalRules); +CriticalMessageRange = RuleI.Get(Rule.CriticalDamage); -MeleeBaseCritChance = 0.0; -ClientBaseCritChance = 0.0; -BerserkBaseCritChance = 6.0; -WarBerBaseCritChance = 3.0; -RogueCritThrowingChance = 25; -RogueDeadlyStrikeChance = 80; -RogueDeadlyStrikeMod = 2; +--[[ + * + * These are rule values that were removed in the latest combat overhaul, if you are coming from + * older source code, you will need to reference what your rule values were for these variables + * to make sure that things line up correctly + * +]] -BaseHitChance = RuleR.Get(Rule.BaseHitChance); -NPCBonusHitChance = RuleR.Get(Rule.NPCBonusHitChance); -HitFalloffMinor = RuleR.Get(Rule.HitFalloffMinor); -HitFalloffModerate = RuleR.Get(Rule.HitFalloffModerate); -HitFalloffMajor = RuleR.Get(Rule.HitFalloffMajor); -HitBonusPerLevel = RuleR.Get(Rule.HitBonusPerLevel); -AgiHitFactor = RuleR.Get(Rule.AgiHitFactor); -WeaponSkillFalloff = RuleR.Get(Rule.WeaponSkillFalloff); -ArcheryHitPenalty = RuleR.Get(Rule.ArcheryHitPenalty); -UseOldDamageIntervalRules = RuleB.Get(Rule.UseOldDamageIntervalRules); - -CriticalMessageRange = RuleI.Get(Rule.CriticalDamage); +MeleeBaseCritChance = 0.0; +ClientBaseCritChance = 0.0; +BerserkBaseCritChance = 6.0; +WarBerBaseCritChance = 3.0; +RogueCritThrowingChance = 25; +RogueDeadlyStrikeChance = 80; +RogueDeadlyStrikeMod = 2; +-- Source Function: Mob::MeleeMitigation() +-- Partial: Rest happens in DoMeleeMitigation function MeleeMitigation(e) e.IgnoreDefault = true; - + if e.hit.damage_done < 0 or e.hit.base_damage == 0 then return e; end - + + eq.log_combat( + string.format("[%s] [ClientAttack] Damage Table [%i] WeaponDMG [%i]", + e.self:GetCleanName(), + GetDamageTable(e.other, e.hit.skill), + e.hit.base_damage + ) + ); + e.hit.damage_done = 2 * e.hit.base_damage * GetDamageTable(e.other, e.hit.skill) / 100; - e.hit = DoMeleeMitigation(e.self, e.other, e.hit, e.opts); + + eq.log_combat( + string.format("[%s] [ClientAttack] DamageDone [%i] BaseDamage [%i] HitSkill [%i]", + e.self:GetCleanName(), + e.hit.damage_done, + e.hit.base_damage, + e.hit.skill + ) + ); + + e.hit = DoMeleeMitigation(e.self, e.other, e.hit, e.opts); + return e; end +-- Source Function: Mob::CheckHitChance() function CheckHitChance(e) - e.IgnoreDefault = true; - - local other = e.other; - local attacker = other; - local self = e.self; - local defender = self; + e.IgnoreDefault = true; + + local other = e.other; + local attacker = other; + local self = e.self; + local defender = self; local chancetohit = BaseHitChance; - local chance_mod = 0; - - if(e.opts ~= nil) then + local chance_mod = 0; + + if (e.opts ~= nil) then chance_mod = e.opts.hit_chance; end - - if(attacker:IsNPC() and not attacker:IsPet()) then + + if (attacker:IsNPC() and not attacker:IsPet()) then chancetohit = chancetohit + NPCBonusHitChance; end local pvpmode = false; - if(self:IsClient() and other:IsClient()) then + if (self:IsClient() and other:IsClient()) then pvpmode = true; end - + if (chance_mod >= 10000) then e.ReturnValue = true; return e; end - + local avoidanceBonus = 0; - local hitBonus = 0; - + local hitBonus = 0; + local attacker_level = attacker:GetLevel(); - if(attacker_level < 1) then + if (attacker_level < 1) then attacker_level = 1; end - + local defender_level = defender:GetLevel(); - if(defender_level < 1) then + if (defender_level < 1) then defender_level = 1; end local level_difference = attacker_level - defender_level; - local range = defender_level; - range = ((range / 4) + 3); - - if(level_difference < 0) then - if(level_difference >= -range) then + local range = defender_level; + range = ((range / 4) + 3); + + if (level_difference < 0) then + if (level_difference >= -range) then chancetohit = chancetohit + ((level_difference / range) * HitFalloffMinor); - elseif (level_difference >= -(range+3.0)) then + elseif (level_difference >= -(range + 3.0)) then chancetohit = chancetohit - HitFalloffMinor; chancetohit = chancetohit + (((level_difference + range) / 3.0) * HitFalloffModerate); else @@ -116,85 +153,95 @@ function CheckHitChance(e) end chancetohit = chancetohit - (defender:GetAGI() * AgiHitFactor); - - if(attacker:IsClient()) then + + if (attacker:IsClient()) then chancetohit = chancetohit - (WeaponSkillFalloff * (attacker:CastToClient():MaxSkill(e.hit.skill) - attacker:GetSkill(e.hit.skill))); end - - if(defender:IsClient()) then + + if (defender:IsClient()) then chancetohit = chancetohit + (WeaponSkillFalloff * (defender:CastToClient():MaxSkill(Skill.Defense) - defender:GetSkill(Skill.Defense))); end - + local attacker_spellbonuses = attacker:GetSpellBonuses(); - local attacker_itembonuses = attacker:GetItemBonuses(); - local attacker_aabonuses = attacker:GetAABonuses(); + local attacker_itembonuses = attacker:GetItemBonuses(); + local attacker_aabonuses = attacker:GetAABonuses(); local defender_spellbonuses = defender:GetSpellBonuses(); - local defender_itembonuses = defender:GetItemBonuses(); - local defender_aabonuses = defender:GetAABonuses(); - - if(attacker_spellbonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_spellbonuses:MeleeSkillCheckSkill() == 255) then + local defender_itembonuses = defender:GetItemBonuses(); + local defender_aabonuses = defender:GetAABonuses(); + + if (attacker_spellbonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_spellbonuses:MeleeSkillCheckSkill() == 255) then chancetohit = chancetohit + attacker_spellbonuses:MeleeSkillCheck(); end - - if(attacker_itembonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_itembonuses:MeleeSkillCheckSkill() == 255) then - chancetohit = chancetohit + attacker_itembonuses:MeleeSkillCheck(); + + if (attacker_itembonuses:MeleeSkillCheckSkill() == e.hit.skill or attacker_itembonuses:MeleeSkillCheckSkill() == 255) then + chancetohit = chancetohit + attacker_itembonuses:MeleeSkillCheck(); end avoidanceBonus = defender_spellbonuses:AvoidMeleeChanceEffect() + - defender_itembonuses:AvoidMeleeChanceEffect() + - defender_aabonuses:AvoidMeleeChanceEffect() + - (defender_itembonuses:AvoidMeleeChance() / 10.0); - - local owner = Mob(); + defender_itembonuses:AvoidMeleeChanceEffect() + + defender_aabonuses:AvoidMeleeChanceEffect() + + (defender_itembonuses:AvoidMeleeChance() / 10.0); + + local owner = Mob(); if (defender:IsPet()) then owner = defender:GetOwner(); elseif (defender:IsNPC() and defender:CastToNPC():GetSwarmOwner()) then local entity_list = eq.get_entity_list(); - owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); + owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); end - + if (owner.valid) then avoidanceBonus = avoidanceBonus + owner:GetAABonuses():PetAvoidance() + owner:GetSpellBonuses():PetAvoidance() + owner:GetItemBonuses():PetAvoidance(); end - if(defender:IsNPC()) then + if (defender:IsNPC()) then avoidanceBonus = avoidanceBonus + (defender:CastToNPC():GetAvoidanceRating() / 10.0); end hitBonus = hitBonus + attacker_itembonuses:HitChanceEffect(e.hit.skill) + - attacker_spellbonuses:HitChanceEffect(e.hit.skill) + - attacker_aabonuses:HitChanceEffect(e.hit.skill) + - attacker_itembonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + - attacker_spellbonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + - attacker_aabonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1); - + attacker_spellbonuses:HitChanceEffect(e.hit.skill) + + attacker_aabonuses:HitChanceEffect(e.hit.skill) + + attacker_itembonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + + attacker_spellbonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:HitChanceEffect(Skill.HIGHEST_SKILL + 1); + hitBonus = hitBonus + (attacker_itembonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + - attacker_spellbonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + - attacker_aabonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + - attacker_aabonuses:Accuracy(e.hit.skill) + - attacker_itembonuses:HitChance()) / 15.0; - + attacker_spellbonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:Accuracy(Skill.HIGHEST_SKILL + 1) + + attacker_aabonuses:Accuracy(e.hit.skill) + + attacker_itembonuses:HitChance()) / 15.0; + hitBonus = hitBonus + chance_mod; - - if(attacker:IsNPC()) then + + if (attacker:IsNPC()) then hitBonus = hitBonus + (attacker:CastToNPC():GetAccuracyRating() / 10.0); end - + if (e.hit.skill == Skill.Archery) then hitBonus = hitBonus - (hitBonus * ArcheryHitPenalty); end chancetohit = chancetohit + ((chancetohit * (hitBonus - avoidanceBonus)) / 100.0); - if(chancetohit > 1000 or chancetohit < -1000) then - elseif(chancetohit > 95) then + if (chancetohit > 1000 or chancetohit < -1000) then + elseif (chancetohit > 95) then chancetohit = 95; - elseif(chancetohit < 5) then + elseif (chancetohit < 5) then chancetohit = 5; end local tohit_roll = Random.Real(0, 100); - if(tohit_roll <= chancetohit) then + + eq.log_combat( + string.format("[%s] [Mob::CheckHitChance] Chance [%i] ToHitRoll [%i] Hit? [%s]", + e.self:GetCleanName(), + chancetohit, + tohit_roll, + (tohit_roll <= chancetohit) and "true" or "false" + ) + ); + + if (tohit_roll <= chancetohit) then e.ReturnValue = true; else e.ReturnValue = false; @@ -203,32 +250,33 @@ function CheckHitChance(e) return e; end +-- Source Function: Mob::TryCriticalHit() function TryCriticalHit(e) e.IgnoreDefault = true; - - local self = e.self; - local defender = e.other; - - if(e.hit.damage_done < 1 or defender.null) then + + local self = e.self; + local defender = e.other; + + if (e.hit.damage_done < 1 or defender.null) then return e; end - + if ((self:IsPet() and self:GetOwner():IsClient()) or (self:IsNPC() and self:CastToNPC():GetSwarmOwner() ~= 0)) then e.hit = TryPetCriticalHit(self, defender, e.hit); return e; end - + if (self:IsPet() and self:GetOwner().valid and self:GetOwner():IsBot()) then e.hit = TryPetCriticalHit(self, defender, e.hit); return e; end - local critChance = 0.0; + local critChance = 0.0; local IsBerskerSPA = false; - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); local spellbonuses = self:GetSpellBonuses(); - local entity_list = eq.get_entity_list(); + local entity_list = eq.get_entity_list(); if (defender:GetBodyType() == BT.Undead or defender:GetBodyType() == BT.SummonedUndead or defender:GetBodyType() == BT.Vampire) then local SlayRateBonus = aabonuses:SlayUndead(0) + itembonuses:SlayUndead(0) + spellbonuses:SlayUndead(0); @@ -236,28 +284,28 @@ function TryCriticalHit(e) local slayChance = SlayRateBonus / 10000.0; if (Random.RollReal(slayChance)) then local SlayDmgBonus = aabonuses:SlayUndead(1) + itembonuses:SlayUndead(1) + spellbonuses:SlayUndead(1); - e.hit.damage_done = (e.hit.damage_done * SlayDmgBonus * 2.25) / 100; - + e.hit.damage_done = (e.hit.damage_done * SlayDmgBonus * 2.25) / 100; + if (self:GetGender() == 1) then entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses her target! (%d)', self:GetCleanName(), e.hit.damage_done)); else entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s\'s holy blade cleanses his target! (%d)', self:GetCleanName(), e.hit.damage_done)); end - + return e; end end end - + critChance = critChance + MeleeBaseCritChance; - + if (self:IsClient()) then - critChance = critChance + ClientBaseCritChance; - + critChance = critChance + ClientBaseCritChance; + if (spellbonuses:BerserkSPA() or itembonuses:BerserkSPA() or aabonuses:BerserkSPA()) then IsBerskerSPA = true; end - + if (((self:GetClass() == Class.WARRIOR or self:GetClass() == Class.BERSERKER) and self:GetLevel() >= 12) or IsBerskerSPA) then if (self:IsBerserk() or IsBerskerSPA) then critChance = critChance + BerserkBaseCritChance; @@ -266,21 +314,21 @@ function TryCriticalHit(e) end end end - + local deadlyChance = 0; - local deadlyMod = 0; + local deadlyMod = 0; if (e.hit.skill == Skill.Archery and self:GetClass() == Class.RANGER and self:GetSkill(Skill.Archery) >= 65) then critChance = critChance + 6; end if (e.hit.skill == Skill.Throwing and self:GetClass() == Class.ROGUE and self:GetSkill(Skill.Throwing) >= 65) then - critChance = critChance + RogueCritThrowingChance; + critChance = critChance + RogueCritThrowingChance; deadlyChance = RogueDeadlyStrikeChance; - deadlyMod = RogueDeadlyStrikeMod; + deadlyMod = RogueDeadlyStrikeMod; end - + local CritChanceBonus = GetCriticalChanceBonus(self, e.hit.skill); - + if (CritChanceBonus > 0 or critChance > 0) then if (self:GetDEX() <= 255) then critChance = critChance + (self:GetDEX() / 125.0); @@ -289,43 +337,63 @@ function TryCriticalHit(e) end critChance = critChance + (critChance * CritChanceBonus / 100.0); end - - if(opts ~= nil) then + + if (opts ~= nil) then critChance = critChance * opts.crit_percent; critChance = critChance + opts.crit_flat; end - - if(critChance > 0) then - + + eq.log_combat( + string.format("[%s] [Mob::TryCriticalHit] CritChance [%i] CritChanceBonus [%i] Dex [%i] Post-Dex-Block", + e.self:GetCleanName(), + critChance, + CritChanceBonus, + e.self:GetDEX() + ) + ); + + if (critChance > 0) then + critChance = critChance / 100; - - if(Random.RollReal(critChance)) then - local critMod = 200; - local crip_success = false; + + if (Random.RollReal(critChance)) then + local critMod = 200; + local crip_success = false; local CripplingBlowChance = GetCrippBlowChance(self); - + if (CripplingBlowChance > 0 or (self:IsBerserk() or IsBerskerSPA)) then if (not self:IsBerserk() and not IsBerskerSPA) then critChance = critChance * (CripplingBlowChance / 100.0); end - + if ((self:IsBerserk() or IsBerskerSPA) or Random.RollReal(critChance)) then - critMod = 400; + critMod = 400; crip_success = true; end end - + critMod = critMod + GetCritDmgMod(self, e.hit.skill) * 2; - e.hit.damage_done = e.hit.damage_done * critMod / 100; - + + eq.log_combat( + string.format("[%s] [Mob::TryCriticalHit] CritChance [%i] CritMod [%i] GetCritDmgMod [%i] CripSuccess [%s]", + e.self:GetCleanName(), + critChance, + critMod, + GetCritDmgMod(self, e.hit.skill), + (crip_success) and "true" or "false" + ) + ); + + e.hit.damage_done = e.hit.damage_done * critMod / 100; + local deadlySuccess = false; if (deadlyChance > 0 and Random.RollReal(deadlyChance / 100.0)) then if (self:BehindMob(defender, self:GetX(), self:GetY())) then e.hit.damage_done = e.hit.damage_done * deadlyMod; - deadlySuccess = true; + deadlySuccess = true; end end - + if (crip_success) then entity_list:FilteredMessageClose(self, false, CriticalMessageRange, MT.CritMelee, Filter.MeleeCrits, string.format('%s lands a Crippling Blow! (%d)', self:GetCleanName(), e.hit.damage_done)); if (defender:GetLevel() <= 55 and not defender:GetSpecialAbility(SpecialAbility.unstunable)) then @@ -339,24 +407,25 @@ function TryCriticalHit(e) end end end - + return e; end +-- Source Function: Mob::TryPetCriticalHit() function TryPetCriticalHit(self, defender, hit) - if(hit.damage_done < 1) then + if (hit.damage_done < 1) then return hit; end - local owner = Mob(); + local owner = Mob(); local critChance = MeleeBaseCritChance; - local critMod = 163; + local critMod = 163; if (self:IsPet()) then owner = self:GetOwner(); elseif (self:IsNPC() and self:CastToNPC():GetSwarmOwner()) then local entity_list = eq.get_entity_list(); - owner = entity_list:GetMobID(self:CastToNPC():GetSwarmOwner()); + owner = entity_list:GetMobID(self:CastToNPC():GetSwarmOwner()); else return hit; end @@ -364,128 +433,198 @@ function TryPetCriticalHit(self, defender, hit) if (owner.null) then return hit; end - - local CritPetChance = owner:GetAABonuses():PetCriticalHit() + owner:GetItemBonuses():PetCriticalHit() + owner:GetSpellBonuses():PetCriticalHit(); + + local CritPetChance = owner:GetAABonuses():PetCriticalHit() + owner:GetItemBonuses():PetCriticalHit() + owner:GetSpellBonuses():PetCriticalHit(); local CritChanceBonus = GetCriticalChanceBonus(self, hit.skill); + eq.log_combat( + string.format("[%s] [Mob::TryPetCriticalHit] CritPetChance [%i] CritChanceBonus [%i] | Bonuses AA [%i] Item [%i] Spell [%i]", + e.self:GetCleanName(), + CritPetChance, + CritChanceBonus, + owner:GetAABonuses():PetCriticalHit(), + owner:GetItemBonuses():PetCriticalHit(), + owner:GetSpellBonuses():PetCriticalHit() + ) + ); + if (CritPetChance or critChance) then critChance = critChance + CritPetChance; critChance = critChance + (critChance * CritChanceBonus / 100.0); + + eq.log_combat( + string.format("[%s] [Mob::TryPetCriticalHit] critChance [%i] PostCalcs", + e.self:GetCleanName(), + critChance + ) + ); + end - if(critChance > 0) then + if (critChance > 0) then critChance = critChance / 100; - if(Random.RollReal(critChance)) then + if (Random.RollReal(critChance)) then local entity_list = eq.get_entity_list(); - critMod = critMod + GetCritDmgMod(self, hit.skill) * 2; - hit.damage_done = (hit.damage_done * critMod) / 100; - entity_list:FilteredMessageClose(this, false, CriticalMessageRange, - MT.CritMelee, Filter.MeleeCrits, string.format('%s scores a critical hit! (%d)', - self:GetCleanName(), e.hit.damage_done)); + critMod = critMod + GetCritDmgMod(self, hit.skill) * 2; + hit.damage_done = (hit.damage_done * critMod) / 100; + + eq.log_combat( + string.format("[%s] [Mob::TryPetCriticalHit] critMod [%i] DmgMod [%i] DamageDone [%i]", + e.self:GetCleanName(), + critMod, + GetCritDmgMod(self, hit.skill), + hit.damage_done + ) + ); + + entity_list:FilteredMessageClose( + this, + false, + CriticalMessageRange, + MT.CritMelee, + Filter.MeleeCrits, + string.format('%s scores a critical hit! (%d)', self:GetCleanName(), e.hit.damage_done) + ); end end - + return hit; end +-- Source Function: Mob::GetCriticalChanceBonus() function GetCriticalChanceBonus(self, skill) - + local critical_chance = 0; - - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); - local spellbonuses = self:GetSpellBonuses(); - - critical_chance = critical_chance + itembonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); - critical_chance = critical_chance + spellbonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); - critical_chance = critical_chance + aabonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); - critical_chance = critical_chance + itembonuses:CriticalHitChance(skill); - critical_chance = critical_chance + spellbonuses:CriticalHitChance(skill); - critical_chance = critical_chance + aabonuses:CriticalHitChance(skill); - + + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); + local spellbonuses = self:GetSpellBonuses(); + + critical_chance = critical_chance + itembonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + spellbonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + aabonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1); + critical_chance = critical_chance + itembonuses:CriticalHitChance(skill); + critical_chance = critical_chance + spellbonuses:CriticalHitChance(skill); + critical_chance = critical_chance + aabonuses:CriticalHitChance(skill); + + eq.log_combat( + string.format("[%s] [Mob::GetCriticalChanceBonus] Bonuses | Item [%i] Spell [%i] AA [%i] | 2nd Item [%i] Spell [%i] AA [%i] Final Chance [%i]", + self:GetCleanName(), + itembonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1), + spellbonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1), + aabonuses:CriticalHitChance(Skill.HIGHEST_SKILL + 1), + itembonuses:CriticalHitChance(skill), + spellbonuses:CriticalHitChance(skill), + aabonuses:CriticalHitChance(skill), + critical_chance + ) + ); + return critical_chance; end +-- Source Function: Mob::GetCritDmgMob() function GetCritDmgMod(self, skill) - local critDmg_mod = 0; - - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); + local critDmg_mod = 0; + + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); local spellbonuses = self:GetSpellBonuses(); - - critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); - critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); - critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); - critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(skill); - critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(skill); - critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(skill); - + critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(Skill.HIGHEST_SKILL + 1); + critDmg_mod = critDmg_mod + itembonuses:CritDmgMod(skill); + critDmg_mod = critDmg_mod + spellbonuses:CritDmgMod(skill); + critDmg_mod = critDmg_mod + aabonuses:CritDmgMod(skill); + + if (critDmg_mod < -100) then + critDmg_mod = -100; + end + return critDmg_mod; end +-- Source Function: Mob::GetCrippBlowChance() function GetCrippBlowChance(self) - local aabonuses = self:GetAABonuses(); - local itembonuses = self:GetItemBonuses(); + local aabonuses = self:GetAABonuses(); + local itembonuses = self:GetItemBonuses(); local spellbonuses = self:GetSpellBonuses(); - local crip_chance = itembonuses:CrippBlowChance() + spellbonuses:CrippBlowChance() + aabonuses:CrippBlowChance(); + local crip_chance = itembonuses:CrippBlowChance() + spellbonuses:CrippBlowChance() + aabonuses:CrippBlowChance(); - if(crip_chance < 0) then + if (crip_chance < 0) then crip_chance = 0; end return crip_chance; end +-- Source Function: Mob::MeleeMitigation() function DoMeleeMitigation(defender, attacker, hit, opts) if hit.damage_done <= 0 then return hit; end - - local aabonuses = defender:GetAABonuses(); - local itembonuses = defender:GetItemBonuses(); - local spellbonuses = defender:GetSpellBonuses(); - - local aa_mit = (aabonuses:CombatStability() + itembonuses:CombatStability() + spellbonuses:CombatStability()) / 100.0; - local softcap = (defender:GetSkill(15) + defender:GetLevel()) * SoftcapFactor * (1.0 + aa_mit); + + local aabonuses = defender:GetAABonuses(); + local itembonuses = defender:GetItemBonuses(); + local spellbonuses = defender:GetSpellBonuses(); + + local aa_mit = (aabonuses:CombatStability() + itembonuses:CombatStability() + spellbonuses:CombatStability()) / 100.0; + local softcap = (defender:GetSkill(15) + defender:GetLevel()) * SoftcapFactor * (1.0 + aa_mit); local mitigation_rating = 0.0; - local attack_rating = 0.0; - local shield_ac = 0; - local armor = 0; - local weight = 0.0; - local monkweight = MonkACBonusWeight; - + local attack_rating = 0.0; + local shield_ac = 0; + local armor = 0; + local weight = 0.0; + local monkweight = MonkACBonusWeight; + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Stability Bonuses | AA [%i] Item [%i] Spell [%i]", + defender:GetCleanName(), + aabonuses:CombatStability(), + itembonuses:CombatStability(), + spellbonuses:CombatStability() + ) + ); + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Soft Cap [%i]", + defender:GetCleanName(), + softcap + ) + ); + if defender:IsClient() then armor, shield_ac = GetRawACNoShield(defender); - weight = defender:CastToClient():CalcCurrentWeight() / 10; + weight = defender:CastToClient():CalcCurrentWeight() / 10; elseif defender:IsNPC() then - armor = defender:CastToNPC():GetRawAC(); + armor = defender:CastToNPC():GetRawAC(); local PetACBonus = 0; - + if not defender:IsPet() then armor = armor / NPCACFactor; end - + local owner = Mob(); if defender:IsPet() then owner = defender:GetOwner(); elseif defender:CastToNPC():GetSwarmOwner() ~= 0 then local entity_list = eq.get_entity_list(); - owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); + owner = entity_list:GetMobID(defender:CastToNPC():GetSwarmOwner()); end - + if owner.valid then PetACBonus = owner:GetAABonuses():PetMeleeMitigation() + owner:GetItemBonuses():PetMeleeMitigation() + owner:GetSpellBonuses():PetMeleeMitigation(); end - + armor = armor + defender:GetSpellBonuses():AC() + defender:GetItemBonuses():AC() + PetACBonus + 1; end - + if (opts ~= nil) then armor = armor * (1.0 - opts.armor_pen_percent); armor = armor - opts.armor_pen_flat; end - + local defender_class = defender:GetClass(); if OldACSoftcapRules then if defender_class == Class.WIZARD or defender_class == Class.MAGICIAN or defender_class == Class.NECROMANCER or defender_class == Class.ENCHANTER then @@ -502,12 +641,12 @@ function DoMeleeMitigation(defender, attacker, hit, opts) end softcap = softcap + shield_ac; - armor = armor + shield_ac; - + armor = armor + shield_ac; + if OldACSoftcapRules then softcap = softcap + (softcap * (aa_mit * AAMitigationACFactor)); end - + if armor > softcap then local softcap_armor = armor - softcap; if OldACSoftcapRules then @@ -529,7 +668,7 @@ function DoMeleeMitigation(defender, attacker, hit, opts) softcap_armor = softcap_armor * WarACSoftcapReturn; elseif defender_class == Class.PALADIN or defender_class == Class.SHADOWKNIGHT then softcap_armor = softcap_armor * PalShdACSoftcapReturn; - elseif defender_class == Class.CLERIC or defender_class == Class.RANGER or defender_class == Class.MONK or defender_class == Class.BARD then + elseif defender_class == Class.CLERIC or defender_class == Class.RANGER or defender_class == Class.MONK or defender_class == Class.BARD then softcap_armor = softcap_armor * ClrRngMnkBrdACSoftcapReturn; elseif defender_class == Class.DRUID or defender_class == Class.NECROMANCER or defender_class == Class.WIZARD or defender_class == Class.ENCHANTER or defender_class == Class.MAGICIAN then softcap_armor = softcap_armor * DruNecWizEncMagACSoftcapReturn; @@ -539,35 +678,52 @@ function DoMeleeMitigation(defender, attacker, hit, opts) softcap_armor = softcap_armor * MiscACSoftcapReturn; end end - + armor = softcap + softcap_armor; end - + local mitigation_rating; if defender_class == Class.WIZARD or defender_class == Class.MAGICIAN or defender_class == Class.NECROMANCER or defender_class == Class.ENCHANTER then mitigation_rating = ((defender:GetSkill(Skill.Defense) + defender:GetItemBonuses():HeroicAGI() / 10) / 4.0) + armor + 1; else mitigation_rating = ((defender:GetSkill(Skill.Defense) + defender:GetItemBonuses():HeroicAGI() / 10) / 3.0) + (armor * 1.333333) + 1; end - + mitigation_rating = mitigation_rating * 0.847; - + local attack_rating; if attacker:IsClient() then - attack_rating = (attacker:CastToClient():CalcATK() + ((attacker:GetSTR() - 66) * 0.9) + (attacker:GetSkill(Skill.Offense)*1.345)); + attack_rating = (attacker:CastToClient():CalcATK() + ((attacker:GetSTR() - 66) * 0.9) + (attacker:GetSkill(Skill.Offense) * 1.345)); else - attack_rating = (attacker:GetATK() + (attacker:GetSkill(Skill.Offense)*1.345) + ((attacker:GetSTR() - 66) * 0.9)); + attack_rating = (attacker:GetATK() + (attacker:GetSkill(Skill.Offense) * 1.345) + ((attacker:GetSTR() - 66) * 0.9)); end - + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Attack Rating [%02f] Mitigation Rating [%02f] Damage [%i]", + defender:GetCleanName(), + attack_rating, + mitigation_rating, + hit.damage_done + ) + ); + hit.damage_done = GetMeleeMitDmg(defender, attacker, hit.damage_done, hit.min_damage, mitigation_rating, attack_rating); - if hit.damage_done < 0 then + if hit.damage_done < 0 then hit.damage_done = 0; end - + + eq.log_combat( + string.format("[%s] [Mob::MeleeMitigation] Final Damage [%i]", + defender:GetCleanName(), + hit.damage_done + ) + ); + return hit; end +-- Source Function: N/A function GetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) if defender:IsClient() then return ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating); @@ -576,28 +732,29 @@ function GetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_ratin end end +-- Source Function: Client::GetMeleeMitDmg() function ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) if (not attacker:IsNPC() or UseOldDamageIntervalRules) then return MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating); end - - local d = 10; - local dmg_interval = (damage - min_damage) / 19.0; - local dmg_bonus = min_damage - dmg_interval; + + local d = 10; + local dmg_interval = (damage - min_damage) / 19.0; + local dmg_bonus = min_damage - dmg_interval; local spellMeleeMit = (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100.0; if (defender:GetClass() == Class.WARRIOR) then spellMeleeMit = spellMeleeMit - 0.05; end - - dmg_bonus = dmg_bonus - (dmg_bonus * (defender:GetItemBonuses():MeleeMitigation() / 100.0)); - dmg_interval = dmg_interval + (dmg_interval * spellMeleeMit); + + dmg_bonus = dmg_bonus - (dmg_bonus * (defender:GetItemBonuses():MeleeMitigation() / 100.0)); + dmg_interval = dmg_interval + (dmg_interval * spellMeleeMit); local mit_roll = Random.Real(0, mitigation_rating); local atk_roll = Random.Real(0, attack_rating); if (atk_roll > mit_roll) then - local a_diff = atk_roll - mit_roll; - local thac0 = attack_rating * ACthac0Factor; + local a_diff = atk_roll - mit_roll; + local thac0 = attack_rating * ACthac0Factor; local thac0cap = attacker:GetLevel() * 9 + 20; if (thac0 > thac0cap) then thac0 = thac0cap; @@ -605,8 +762,8 @@ function ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation d = d + (10 * (a_diff / thac0)); elseif (mit_roll > atk_roll) then - local m_diff = mit_roll - atk_roll; - local thac20 = mitigation_rating * ACthac20Factor; + local m_diff = mit_roll - atk_roll; + local thac20 = mitigation_rating * ACthac20Factor; local thac20cap = defender:GetLevel() * 9 + 20; if (thac20 > thac20cap) then thac20 = thac20cap; @@ -624,14 +781,23 @@ function ClientGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation return math.floor(dmg_bonus + dmg_interval * d); end -function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) - local d = 10.0; +-- Source Function: Mob::GetMeleeMitDmg() +function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_rating, attack_rating) + local d = 10.0; local mit_roll = Random.Real(0, mitigation_rating); local atk_roll = Random.Real(0, attack_rating); + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] MitigationRoll [%02f] AtkRoll [%02f]", + defender:GetCleanName(), + mit_roll, + atk_roll + ) + ); + if (atk_roll > mit_roll) then - local a_diff = atk_roll - mit_roll; - local thac0 = attack_rating * ACthac0Factor; + local a_diff = atk_roll - mit_roll; + local thac0 = attack_rating * ACthac0Factor; local thac0cap = attacker:GetLevel() * 9 + 20; if (thac0 > thac0cap) then thac0 = thac0cap; @@ -639,8 +805,8 @@ function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_ra d = d - (10.0 * (a_diff / thac0)); elseif (mit_roll > atk_roll) then - local m_diff = mit_roll - atk_roll; - local thac20 = mitigation_rating * ACthac20Factor; + local m_diff = mit_roll - atk_roll; + local thac20 = mitigation_rating * ACthac20Factor; local thac20cap = defender:GetLevel() * 9 + 20; if (thac20 > thac20cap) then thac20 = thac20cap; @@ -654,26 +820,64 @@ function MobGetMeleeMitDmg(defender, attacker, damage, min_damage, mitigation_ra elseif (d > 20.0) then d = 20.0; end - + local interval = (damage - min_damage) / 20.0; - damage = damage - (math.floor(d) * interval); - damage = damage - (min_damage * defender:GetItemBonuses():MeleeMitigation() / 100); - damage = damage + (damage * (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100); - + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Interval [%02f] d [%02f]", + defender:GetCleanName(), + interval, + d + ) + ); + + damage = damage - (math.floor(d) * interval); + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Damage [%02f] Post Interval", + defender:GetCleanName(), + damage + ) + ); + + damage = damage - (min_damage * defender:GetItemBonuses():MeleeMitigation() / 100); + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Damage [%02f] Mitigation [%i] Post Mitigation MinDmg", + defender:GetCleanName(), + damage, + defender:GetItemBonuses():MeleeMitigation() + ) + ); + + -- Changed from positive to negative per original + damage = damage - (damage * (defender:GetSpellBonuses():MeleeMitigationEffect() + defender:GetItemBonuses():MeleeMitigationEffect() + defender:GetAABonuses():MeleeMitigationEffect()) / 100); + + eq.log_combat( + string.format("[%s] [Mob::GetMeleeMitDmg] Damage [%02f] SpellMit [%i] ItemMit [%i] AAMit [%i] Post All Mit Bonuses", + defender:GetCleanName(), + damage, + defender:GetSpellBonuses():MeleeMitigationEffect(), + defender:GetItemBonuses():MeleeMitigationEffect(), + defender:GetAABonuses():MeleeMitigationEffect() + ) + ); + return damage; end +-- Source Function: Client::GetRawACNoShield() function GetRawACNoShield(self) - self = self:CastToClient(); - - local ac = self:GetItemBonuses():AC() + self:GetSpellBonuses():AC() + self:GetAABonuses():AC(); + self = self:CastToClient(); + + local ac = self:GetItemBonuses():AC() + self:GetSpellBonuses():AC() + self:GetAABonuses():AC(); local shield_ac = 0; - local inst = self:GetInventory():GetItem(Slot.Secondary); - + local inst = self:GetInventory():GetItem(Slot.Secondary); + if inst.valid then if inst:GetItem():ItemType() == 8 then shield_ac = inst:GetItem():AC(); - + for i = 1, 6 do local augment = inst:GetAugment(i - 1); if augment.valid then @@ -682,33 +886,45 @@ function GetRawACNoShield(self) end end end - + ac = ac - shield_ac; + + eq.log_combat( + string.format("[%s] [Client::GetRawACNoShield] AC [%i] ItemAC [%i] SpellAC [%i] AAAC [%i]", + self:GetCleanName(), + ac, + self:GetItemBonuses():AC(), + self:GetSpellBonuses():AC(), + self:GetAABonuses():AC() + ) + ); + return ac, shield_ac; end +-- Source Function: Mob::GetDamageTable() function GetDamageTable(attacker, skill) if not attacker:IsClient() then return 100; end - + if attacker:GetLevel() <= 51 then - local ret_table = 0; + local ret_table = 0; local str_over_75 = 0; if attacker:GetSTR() > 75 then str_over_75 = attacker:GetSTR() - 75; end - + if str_over_75 > 255 then ret_table = (attacker:GetSkill(skill) + 255) / 2; else ret_table = (attacker:GetSkill(skill) + str_over_75) / 2; end - + if ret_table < 100 then return 100; end - + return ret_table; elseif attacker:GetLevel() >= 90 then if attacker:GetClass() == 7 then @@ -718,7 +934,7 @@ function GetDamageTable(attacker, skill) end else local dmg_table = { 275, 275, 275, 275, 275, 280, 280, 280, 280, 285, 285, 285, 290, 290, 295, 295, 300, 300, 300, 305, 305, 305, 310, 310, 315, 315, 320, 320, 320, 325, 325, 325, 330, 330, 335, 335, 340, 340, 340 }; - + if attacker:GetClass() == 7 then local monkDamageTableBonus = 20; return (dmg_table[attacker:GetLevel() - 50] * (100 + monkDamageTableBonus) / 100); @@ -729,26 +945,56 @@ function GetDamageTable(attacker, skill) return 100; end +-- Source Function: N/A - Not used function ApplyDamageTable(e) e.IgnoreDefault = true; return e; end +-- Source Function: Mob::CommonOutgoingHitSuccess() function CommonOutgoingHitSuccess(e) e = ApplyMeleeDamageBonus(e); + + eq.log_combat( + string.format("[%s] [Mob::CommonOutgoingHitSuccess] Dmg [%i] Post ApplyMeleeDamageBonus", + e.self:GetCleanName(), + e.hit.damage_done + ) + ); + e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * e.other:GetSkillDmgTaken(e.hit.skill) / 100) + (e.self:GetSkillDmgAmt(e.hit.skill) + e.other:GetFcDamageAmtIncoming(e.self, 0, true, e.hit.skill)); + + eq.log_combat( + string.format("[%s] [Mob::CommonOutgoingHitSuccess] Dmg [%i] SkillDmgTaken [%i] SkillDmgtAmt [%i] FcDmgAmtIncoming [%i] Post DmgCalcs", + e.self:GetCleanName(), + e.hit.damage_done, + e.other:GetSkillDmgTaken(e.hit.skill), + e.self:GetSkillDmgAmt(e.hit.skill), + e.other:GetFcDamageAmtIncoming(e.self, 0, true, e.hit.skill) + ) + ); + e = TryCriticalHit(e); - e.self:CheckNumHitsRemaining(5, -1, 65535); + e.self:CheckNumHitsRemaining(5, -1, 65535); e.IgnoreDefault = true; return e; end +-- Source Function: Mob::ApplyMeleeDamageBonus() function ApplyMeleeDamageBonus(e) local dmgbonusmod = e.self:GetMeleeDamageMod_SE(e.hit.skill); if (opts) then dmgbonusmod = dmgbonusmod + e.opts.melee_damage_bonus_flat; end + eq.log_combat( + string.format("[%s] [Mob::ApplyMeleeDamageBonus] DmgBonusMod [%i] Dmg [%i]", + e.self:GetCleanName(), + dmgbonusmod, + e.hit.damage_done + ) + ); + e.hit.damage_done = e.hit.damage_done + (e.hit.damage_done * dmgbonusmod / 100); return e; -end +end \ No newline at end of file