From 1d23f6bc0e51b436f5c44db7ff613c5e2fb34c47 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Thu, 7 May 2020 14:55:22 -0400 Subject: [PATCH] Core/Unit: cleanup and minor fixes of miss and resist calculations Port From (https://github.com/TrinityCore/TrinityCore/commit/1960a95425c63f3b45409bcc1ee544472be598a3) --- Source/Game/Entities/StatSystem.cs | 148 +++++++++++++---------- Source/Game/Entities/Unit/Unit.Combat.cs | 83 ++++++------- Source/Game/Entities/Unit/Unit.Spells.cs | 9 +- 3 files changed, 127 insertions(+), 113 deletions(-) diff --git a/Source/Game/Entities/StatSystem.cs b/Source/Game/Entities/StatSystem.cs index 9c3d2e1aa..142d52332 100644 --- a/Source/Game/Entities/StatSystem.cs +++ b/Source/Game/Entities/StatSystem.cs @@ -716,32 +716,28 @@ namespace Game.Entities //calculate miss chance float missChance = victim.GetUnitMissChance(attType); + // melee attacks while dual wielding have +19% chance to miss if (spellId == 0 && HaveOffhandWeapon() && !IsInFeralForm()) - missChance += 19; - - // Calculate hit chance - float hitChance = 100.0f; + missChance += 19.0f; // Spellmod from SPELLMOD_RESIST_MISS_CHANCE + float resistMissChance = 100.0f; if (spellId != 0) { Player modOwner = GetSpellModOwner(); if (modOwner != null) - modOwner.ApplySpellMod(spellId, SpellModOp.ResistMissChance, ref hitChance); + modOwner.ApplySpellMod(spellId, SpellModOp.ResistMissChance, ref resistMissChance); } - missChance += hitChance - 100.0f; + missChance += resistMissChance - 100.0f; if (attType == WeaponAttackType.RangedAttack) missChance -= ModRangedHitChance; else missChance -= ModMeleeHitChance; - // Limit miss chance from 0 to 77% - if (missChance < 0.0f) - return 0.0f; - if (missChance > 77.0f) - return 77.0f; + // Limit miss chance from 0 to 60% + MathFunctions.RoundToInterval(ref missChance, 0.0f, 60.0f); return missChance; } @@ -870,7 +866,7 @@ namespace Game.Entities } float GetUnitMissChance(WeaponAttackType attType) { - float miss_chance = 5.00f; + float miss_chance = 5.0f; if (attType == WeaponAttackType.RangedAttack) miss_chance -= GetTotalAuraModifier(AuraType.ModAttackerRangedHitChance); @@ -1172,7 +1168,7 @@ namespace Game.Entities } if (HasAuraType(AuraType.OverrideAttackPowerBySpPct)) - { + { UpdateAttackPowerAndDamage(); UpdateAttackPowerAndDamage(true); } @@ -1255,7 +1251,7 @@ namespace Game.Entities UpdateSpellDamageAndHealingBonus(); if (pet != null && pet.IsPetGhoul()) // At melee attack power change for DK pet - pet.UpdateAttackPowerAndDamage(); + pet.UpdateAttackPowerAndDamage(); if (guardian != null && guardian.IsSpiritWolf()) // At melee attack power change for Shaman feral spirit guardian.UpdateAttackPowerAndDamage(); @@ -1451,7 +1447,7 @@ namespace Game.Entities return; for (uint i = 0; i < PlayerConst.MaxMasterySpells; ++i) - { + { Aura aura = GetAura(chrSpec.MasterySpellID[i]); if (aura != null) { @@ -1498,24 +1494,63 @@ namespace Game.Entities // Store Rating Value SetUpdateFieldValue(ref m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.CombatRatings, (int)CombatRating.ArmorPenetration), (uint)amount); } - public void UpdateParryPercentage() + + float CalculateDiminishingReturns(float[] capArray, Class playerClass, float nonDiminishValue, float diminishValue) { - float[] parry_cap = + float[] m_diminishing_k = { - 65.631440f, // Warrior - 65.631440f, // Paladin - 145.560408f, // Hunter - 145.560408f, // Rogue - 0.0f, // Priest - 65.631440f, // DK - 145.560408f, // Shaman - 0.0f, // Mage - 0.0f, // Warlock - 90.6425f, // Monk - 0.0f, // Druid - 65.631440f // Demon Hunter + 0.9560f, // Warrior + 0.9560f, // Paladin + 0.9880f, // Hunter + 0.9880f, // Rogue + 0.9830f, // Priest + 0.9560f, // DK + 0.9880f, // Shaman + 0.9830f, // Mage + 0.9830f, // Warlock + 0.9830f, // Monk + 0.9720f, // Druid + 0.9830f // Demon Hunter }; + // 1 1 k cx + // --- = --- + --- <=> x' = -------- + // x' c x x + ck + + // where: + // k is m_diminishing_k for that class + // c is capArray for that class + // x is chance before DR (diminishValue) + // x' is chance after DR (our result) + + uint classIdx = (byte)playerClass - 1u; + + float k = m_diminishing_k[classIdx]; + float c = capArray[classIdx]; + + float result = c * diminishValue / (diminishValue + c * k); + result += nonDiminishValue; + return result; + } + + float[] parry_cap = + { + 65.631440f, // Warrior + 65.631440f, // Paladin + 145.560408f, // Hunter + 145.560408f, // Rogue + 0.0f, // Priest + 65.631440f, // DK + 145.560408f, // Shaman + 0.0f, // Mage + 0.0f, // Warlock + 90.6425f, // Monk + 0.0f, // Druid + 65.631440f // Demon Hunter + }; + + public void UpdateParryPercentage() + { // No parry float value = 0.0f; int pclass = (int)GetClass() - 1; @@ -1526,33 +1561,34 @@ namespace Game.Entities float diminishing = GetRatingBonusValue(CombatRating.Parry); // Parry from SPELL_AURA_MOD_PARRY_PERCENT aura nondiminishing += GetTotalAuraModifier(AuraType.ModParryPercent); + // apply diminishing formula to diminishing parry chance - value = nondiminishing + diminishing * parry_cap[pclass] / (diminishing + parry_cap[pclass] * m_diminishing_k[pclass]); + value = CalculateDiminishingReturns(parry_cap, GetClass(), nondiminishing, diminishing); if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsParry) : value; } SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.ParryPercentage), value); } + + float[] dodge_cap = + { + 65.631440f, // Warrior + 65.631440f, // Paladin + 145.560408f, // Hunter + 145.560408f, // Rogue + 150.375940f, // Priest + 65.631440f, // DK + 145.560408f, // Shaman + 150.375940f, // Mage + 150.375940f, // Warlock + 145.560408f, // Monk + 116.890707f, // Druid + 145.560408f // Demon Hunter + }; public void UpdateDodgePercentage() { - float[] dodge_cap = - { - 65.631440f, // Warrior - 65.631440f, // Paladin - 145.560408f, // Hunter - 145.560408f, // Rogue - 150.375940f, // Priest - 65.631440f, // DK - 145.560408f, // Shaman - 150.375940f, // Mage - 150.375940f, // Warlock - 145.560408f, // Monk - 116.890707f, // Druid - 145.560408f // Demon Hunter - }; - float diminishing = 0.0f, nondiminishing = 0.0f; GetDodgeFromAgility(diminishing, nondiminishing); // Dodge from SPELL_AURA_MOD_DODGE_PERCENT aura @@ -1560,14 +1596,14 @@ namespace Game.Entities // Dodge from rating diminishing += GetRatingBonusValue(CombatRating.Dodge); // apply diminishing formula to diminishing dodge chance - int pclass = (int)GetClass() - 1; - float value = nondiminishing + (diminishing * dodge_cap[pclass] / (diminishing + dodge_cap[pclass] * m_diminishing_k[pclass])); + float value = CalculateDiminishingReturns(dodge_cap, GetClass(), nondiminishing, diminishing); if (WorldConfig.GetBoolValue(WorldCfg.StatsLimitsEnable)) value = value > WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) ? WorldConfig.GetFloatValue(WorldCfg.StatsLimitsDodge) : value; SetUpdateFieldStatValue(m_values.ModifyValue(m_activePlayerData).ModifyValue(m_activePlayerData.DodgePercentage), value); } + public void UpdateBlockPercentage() { // No block @@ -1844,22 +1880,6 @@ namespace Game.Entities } return apply; } - - float[] m_diminishing_k = - { - 0.9560f, // Warrior - 0.9560f, // Paladin - 0.9880f, // Hunter - 0.9880f, // Rogue - 0.9830f, // Priest - 0.9560f, // DK - 0.9880f, // Shaman - 0.9830f, // Mage - 0.9830f, // Warlock - 0.9830f, // Monk - 0.9720f, // Druid - 0.9830f // Demon Hunter - }; } public partial class Creature diff --git a/Source/Game/Entities/Unit/Unit.Combat.cs b/Source/Game/Entities/Unit/Unit.Combat.cs index e3379a6da..21f99c4aa 100644 --- a/Source/Game/Entities/Unit/Unit.Combat.cs +++ b/Source/Game/Entities/Unit/Unit.Combat.cs @@ -841,26 +841,22 @@ namespace Game.Entities !IsTypeId(TypeId.Player) && !ToCreature().IsControlledByPlayer() && !victim.HasInArc(MathFunctions.PI, this) && (victim.IsTypeId(TypeId.Player) || !victim.ToCreature().IsWorldBoss()) && !victim.IsVehicle()) { - // -probability is between 0% and 40% // 20% base chance - float Probability = 20.0f; + float chance = 20.0f; // there is a newbie protection, at level 10 just 7% base chance; assuming linear function if (victim.GetLevel() < 30) - Probability = 0.65f * victim.GetLevelForTarget(this) + 0.5f; + chance = 0.65f * victim.GetLevelForTarget(this) + 0.5f; - uint VictimDefense = victim.GetMaxSkillValueForLevel(this); - uint AttackerMeleeSkill = GetMaxSkillValueForLevel(); + uint victimDefense = victim.GetMaxSkillValueForLevel(this); + uint attackerMeleeSkill = GetMaxSkillValueForLevel(); - Probability *= (AttackerMeleeSkill / (float)VictimDefense * 0.16f); + chance *= attackerMeleeSkill / (float)victimDefense * 0.16f; - if (Probability < 0) - Probability = 0; + // -probability is between 0% and 40% + MathFunctions.RoundToInterval(ref chance, 0.0f, 40.0f); - if (Probability > 40.0f) - Probability = 40.0f; - - if (RandomHelper.randChance(Probability)) + if (RandomHelper.randChance(chance)) CastSpell(victim, 1604, true); } @@ -2356,31 +2352,30 @@ namespace Game.Entities return 0; } - float averageResist = GetEffectiveResistChance(this, schoolMask, victim, spellInfo); + float averageResist = CalculateAverageResistReduction(schoolMask, victim, spellInfo); float[] discreteResistProbability = new float[11]; - for (uint i = 0; i < 11; ++i) - { - discreteResistProbability[i] = 0.5f - 2.5f * Math.Abs(0.1f * i - averageResist); - if (discreteResistProbability[i] < 0.0f) - discreteResistProbability[i] = 0.0f; - } - if (averageResist <= 0.1f) { discreteResistProbability[0] = 1.0f - 7.5f * averageResist; discreteResistProbability[1] = 5.0f * averageResist; discreteResistProbability[2] = 2.5f * averageResist; } + else + { + for (uint i = 0; i < 11; ++i) + discreteResistProbability[i] = Math.Max(0.5f - 2.5f * Math.Abs(0.1f * i - averageResist), 0.0f); + } + + float roll = (float)RandomHelper.NextDouble(); + float probabilitySum = 0.0f; uint resistance = 0; - float r = (float)RandomHelper.NextDouble(); - float probabilitySum = discreteResistProbability[0]; + for (; resistance < 11; ++resistance) + if (roll < (probabilitySum += discreteResistProbability[resistance])) + break; - while (r >= probabilitySum && resistance < 10) - probabilitySum += discreteResistProbability[++resistance]; - - float damageResisted = damage * resistance / 10; + float damageResisted = damage * resistance / 10f; if (damageResisted > 0.0f) // if any damage was resisted { int ignoredResistance = 0; @@ -2394,31 +2389,31 @@ namespace Game.Entities if (spellInfo != null && spellInfo.HasAttribute(SpellCustomAttributes.SchoolmaskNormalWithMagic)) { uint damageAfterArmor = CalcArmorReducedDamage(attacker, victim, damage, spellInfo, WeaponAttackType.BaseAttack); - uint armorReduction = damage - damageAfterArmor; - if (armorReduction < damageResisted) // pick the lower one, the weakest resistance counts - damageResisted = armorReduction; + float armorReduction = damage - damageAfterArmor; + + // pick the lower one, the weakest resistance counts + damageResisted = Math.Min(damageResisted, armorReduction); } } + damageResisted = Math.Max(damageResisted, 0.0f); return (uint)damageResisted; } - float GetEffectiveResistChance(Unit owner, SpellSchoolMask schoolMask, Unit victim, SpellInfo spellInfo) + float CalculateAverageResistReduction(SpellSchoolMask schoolMask, Unit victim, SpellInfo spellInfo) { float victimResistance = (float)victim.GetResistance(schoolMask); - if (owner) + + // pets inherit 100% of masters penetration + // excluding traps + Player player = GetSpellModOwner(); + if (player != null && GetEntry() != SharedConst.WorldTrigger) { - // pets inherit 100% of masters penetration - // excluding traps - Player player = owner.GetSpellModOwner(); - if (player != null && owner.GetEntry() != SharedConst.WorldTrigger) - { - victimResistance += (float)player.GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); - victimResistance -= (float)player.GetSpellPenetrationItemMod(); - } - else - victimResistance += (float)owner.GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); + victimResistance += (float)player.GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); + victimResistance -= (float)player.GetSpellPenetrationItemMod(); } + else + victimResistance += (float)GetTotalAuraModifierByMiscMask(AuraType.ModTargetResistance, (int)schoolMask); // holy resistance exists in pve and comes from level difference, ignore template values if (schoolMask.HasAnyFlag(SpellSchoolMask.Holy)) @@ -2429,8 +2424,10 @@ namespace Game.Entities victimResistance = 0.0f; victimResistance = Math.Max(victimResistance, 0.0f); - if (owner) - victimResistance += Math.Max(((float)victim.GetLevelForTarget(owner) - (float)owner.GetLevelForTarget(victim)) * 5.0f, 0.0f); + + // level-based resistance does not apply to binary spells, and cannot be overcome by spell penetration + if (!spellInfo.HasAttribute(SpellCustomAttributes.BinarySpell)) + victimResistance += Math.Max((victim.GetLevelForTarget(this) - GetLevelForTarget(victim)) * 5.0f, 0.0f); uint bossLevel = 83; float bossResistanceConstant = 510.0f; diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index 9c7496cea..6718e73cb 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -934,7 +934,6 @@ namespace Game.Entities return SpellMissInfo.None; } - // @todo need use unit spell resistances in calculations SpellMissInfo MagicSpellHitResult(Unit victim, SpellInfo spell) { // Can`t miss on dead target (on skinning for example) @@ -991,7 +990,7 @@ namespace Game.Entities int tmp = 10000 - HitChance; int rand = RandomHelper.IRand(0, 9999); - if (rand < tmp) + if (tmp > 0 && rand < tmp) return SpellMissInfo.Miss; // Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will additionally fully ignore @@ -1001,18 +1000,16 @@ namespace Game.Entities // Chance resist mechanic (select max value from every mechanic spell effect) int resist_chance = victim.GetMechanicResistChance(spell) * 100; - tmp += resist_chance; // Roll chance - if (rand < tmp) + if (resist_chance > 0 && rand < (tmp += resist_chance)) return SpellMissInfo.Resist; // cast by caster in front of victim if (!victim.HasUnitState(UnitState.Controlled) && (victim.HasInArc(MathFunctions.PI, this) || victim.HasAuraType(AuraType.IgnoreHitDirection))) { int deflect_chance = victim.GetTotalAuraModifier(AuraType.DeflectSpells) * 100; - tmp += deflect_chance; - if (rand < tmp) + if (deflect_chance > 0 && rand < (tmp += deflect_chance)) return SpellMissInfo.Deflect; }