diff --git a/src/game/SharedDefines.h b/src/game/SharedDefines.h index aeda1c533a..cb9cc6da18 100644 --- a/src/game/SharedDefines.h +++ b/src/game/SharedDefines.h @@ -471,7 +471,7 @@ enum SpellAttributesEx5 SPELL_ATTR_EX5_DONT_SHOW_AURA_IF_NOT_SELF_CAST = 0x10000000,// 28 Auras with this attribute are not visible on units that are not the caster SPELL_ATTR_EX5_UNK29 = 0x20000000,// 29 SPELL_ATTR_EX5_UNK30 = 0x40000000,// 30 - SPELL_ATTR_EX5_UNK31 = 0x80000000,// 31 Forces all nearby enemies to focus attacks caster + SPELL_ATTR_EX5_USE_PHYSICAL_HIT_CHANCE = 0x80000000,// 31 Introduced in patch 2.3: Taunt, Growl, etc spells use ability miss calculation (see implementation for details) }; enum SpellAttributesEx6 diff --git a/src/game/SpellEffects.cpp b/src/game/SpellEffects.cpp index 82f6112e58..3ac7e41fef 100644 --- a/src/game/SpellEffects.cpp +++ b/src/game/SpellEffects.cpp @@ -6141,8 +6141,7 @@ void Spell::EffectDispel(SpellEffectEntry const* effect) std::list > dispel_list; // Create dispel mask by dispel type - uint32 dispel_type = effect->EffectMiscValue; - uint32 dispelMask = GetDispellMask( DispelType(dispel_type) ); + uint32 dispelMask = GetDispellMask(DispelType(effect->EffectMiscValue)); Unit::SpellAuraHolderMap const& auras = unitTarget->GetSpellAuraHolderMap(); for (Unit::SpellAuraHolderMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr) { @@ -6204,6 +6203,7 @@ void Spell::EffectDispel(SpellEffectEntry const* effect) { if (Player* modOwner = caster->GetSpellModOwner()) modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_RESIST_DISPEL_CHANCE, miss_chance); + miss_chance += caster->GetTotalAuraModifier(SPELL_AURA_MOD_DISPEL_RESIST); } // Try dispel if (roll_chance_i(miss_chance)) diff --git a/src/game/Unit.cpp b/src/game/Unit.cpp index 565d2089d0..b2603c4feb 100644 --- a/src/game/Unit.cpp +++ b/src/game/Unit.cpp @@ -2244,60 +2244,12 @@ uint32 Unit::CalcNotIgnoreDamageReduction(uint32 damage, SpellSchoolMask damageS return absorb_affected_rate <= 0.0f ? 0 : (absorb_affected_rate < 1.0f ? uint32(damage * absorb_affected_rate) : damage); } -uint32 Unit::CalculateEffectiveMagicResistance(Unit* attacker, SpellSchoolMask schoolMask) const -{ - // Non-magical or contains Physical as a component (such as Chaos) - no resistance - if (schoolMask & SPELL_SCHOOL_MASK_NORMAL) - return 0; - - // Ignore holy resistance - uint32 schools = uint32(schoolMask & ~uint32(SPELL_SCHOOL_MASK_HOLY)); - - // Select a resistance value matching spell school mask, prefer mininal for multischool spells - uint32 resistance = 0; - for (uint32 school = 0; schools; ++school) - { - if (schools & 1) - { - // Base victim resistance - uint32 amount = GetResistance(SpellSchools(school)); - // Add SPELL_AURA_MOD_TARGET_RESISTANCE aura value at caster - // Negative value is spell penetration, but effective resistance can't be negative since betas - if (const int32 casterMod = attacker->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, (1 << school))) - amount = uint32(std::max((int32(amount) + casterMod), 0)); - if (!amount) - { - resistance = 0; // No resistance for this school: use it! - break; - } - else if (!resistance || amount < resistance) - resistance = amount; // First school encountered or more vulnerable: memorize and continue - } - schools >>= 1; - } - return resistance; -} - -float Unit::CalculateMagicResistanceMitigation(Unit* attacker, uint32 resistance, SpellSchoolMask schoolMask, bool binary) const -{ - // Non-magical or contains Physical as a component (Chaos) - no mitigation - if (schoolMask & SPELL_SCHOOL_MASK_NORMAL) - return 0.0f; - // Total resistance mitigation: final ratio of resistance effectiveness - float ratio = float(float(resistance) / (float(attacker->GetLevelForTarget(this) * 5) + resistance)); - // Add bonus resistance mitigation to victim based on level difference for non-binary spells - if (!binary) - ratio += std::max(int32(GetLevelForTarget(attacker) - attacker->GetLevelForTarget(this)), 0) * 0.02f; - // Magic resistance mitigation (not capped in Wrath) - return std::min(ratio, 1.0f); -} - uint32 Unit::CalcArmorReducedDamage(Unit* pVictim, const uint32 damage) { float armor = (float)pVictim->GetArmor(); - // Ignore enemy armor by SPELL_AURA_MOD_TARGET_RESISTANCE aura - armor += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, SPELL_SCHOOL_MASK_NORMAL); + // Ignore enemy armor by armor penetration + armor -= GetResistancePenetration(SPELL_SCHOOL_NORMAL); // Apply Player CR_ARMOR_PENETRATION rating and percent talents if (GetTypeId() == TYPEID_PLAYER) @@ -2341,8 +2293,8 @@ void Unit::CalculateDamageAbsorbAndResist(Unit* pCaster, SpellSchoolMask schoolM // Magic damage, check for resists if (!ignoreResists && (schoolMask & SPELL_SCHOOL_MASK_MAGIC) && (!binary || damagetype == DOT)) { - const float mitigation = CalculateMagicResistanceMitigation(pCaster, CalculateEffectiveMagicResistance(pCaster, schoolMask), schoolMask, false); - const SpellPartialResistChanceEntry &chances = SPELL_PARTIAL_RESIST_DISTRIBUTION.at(uint32(mitigation * 10000)); + const float percent = CalculateEffectiveMagicResistancePercent(pCaster, schoolMask); + const SpellPartialResistChanceEntry &chances = SPELL_PARTIAL_RESIST_DISTRIBUTION.at(uint32(percent * 100)); // We choose which portion of damage is resisted below, none by default uint8 portion = SPELL_PARTIAL_RESIST_NONE; // If we got to this point, we already rolled for full resist on hit @@ -3076,7 +3028,7 @@ MeleeHitOutcome Unit::RollMeleeOutcomeAgainst(const Unit* pVictim, WeaponAttackT } } - if (pVictim->CanBlockInCombat(this)) + if (pVictim->CanBlockInCombat(this, schoolMask)) { int32 block_chance = int32(pVictim->CalculateEffectiveBlockChance(this, attType) * 100); if (block_chance > 0 && roll < (sum += block_chance)) @@ -3204,60 +3156,17 @@ void Unit::SendMeleeAttackStop(Unit* victim) ((Creature*)victim)->AI().EnterEvadeMode(this);*/ } -float Unit::SpellResistChance(Unit *pVictim, const SpellEntry *spell) -{ - float resistChance = 0.0f; - - // TODO: Verify if this attribute affects other resistances (mechanics, etc) - // Chance to fully resist a spell by magic resistance - if (!spell->HasAttribute(SPELL_ATTR_EX4_IGNORE_RESISTANCES) && spell->DmgClass == SPELL_DAMAGE_CLASS_MAGIC) - { - const bool binary = IsBinarySpell(spell); - const SpellSchoolMask schoolMask = GetSpellSchoolMask(spell); - const float mitigation = pVictim->CalculateMagicResistanceMitigation(this, pVictim->CalculateEffectiveMagicResistance(this, schoolMask), schoolMask, binary); - resistChance += binary ? (mitigation * 100) : (float(SPELL_PARTIAL_RESIST_DISTRIBUTION.at(uint32(mitigation * 10000)).at(SPELL_PARTIAL_RESIST_PCT_100)) / 100.0f); - } - - int32 modResistChance = 0; - - // Increase resist chance for dispel mechanic spells from victim SPELL_AURA_MOD_DISPEL_RESIST - if (IsDispelSpell(spell)) - modResistChance += pVictim->GetTotalAuraModifier(SPELL_AURA_MOD_DISPEL_RESIST); - - // Chance resist mechanic (select max value from every mechanic spell effect) - int32 modResistMechanics = 0; - // Get effects mechanic and chance - for (int eff = 0; eff < MAX_EFFECT_INDEX; ++eff) - { - if (const int32 mechanic = GetEffectMechanic(spell, SpellEffectIndex(eff))) - { - int32 mod = pVictim->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, mechanic); - if (modResistMechanics < mod) - modResistMechanics = mod; - } - } - // Apply mod - modResistChance += modResistMechanics; - - // Chance resist debuff - modResistChance += pVictim->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DEBUFF_RESISTANCE, int32(spell->Dispel)); - - return std::max(0.0f, std::min(100.0f, (resistChance + modResistChance))); -} - // Melee based spells hit result calculations SpellMissInfo Unit::MeleeSpellHitResult(Unit* pVictim, SpellEntry const* spell) { - uint32 roll = urand(0, 10000); + const uint32 roll = urand(1, 10000); uint32 tmp = 0; - // Roll miss tmp += uint32(CalculateAbilityMissChance(pVictim, GetWeaponAttackType(spell), spell) * 100.0f); if (roll < tmp) return SPELL_MISS_MISS; - // Roll mechanic resistance - tmp += uint32(SpellResistChance(pVictim, spell) * 100.0f); + tmp += uint32(CalculateSpellResistChance(pVictim, SPELL_SCHOOL_MASK_NORMAL, spell) * 100); if (roll < tmp) return SPELL_MISS_RESIST; @@ -3294,56 +3203,21 @@ SpellMissInfo Unit::MeleeSpellHitResult(Unit* pVictim, SpellEntry const* spell) return SPELL_MISS_NONE; } -float Unit::MagicSpellMissChance(Unit *pVictim, const SpellEntry *spell) -{ - if (spell->HasAttribute(SPELL_ATTR_EX3_CANT_MISS)) - return 0.0f; - - SpellSchoolMask schoolMask = GetSpellSchoolMask(spell); - // PvP - PvE spell misschances per leveldif > 2 - int32 lchance = (pVictim->GetTypeId() == TYPEID_PLAYER) ? 7 : 11; - int32 leveldif = int32(pVictim->GetLevelForTarget(this)) - int32(GetLevelForTarget(pVictim)); - - // Base hit chance from attacker and victim levels - int32 modHitChance; - if (leveldif < 3) - modHitChance = 96 - leveldif; - else - modHitChance = 94 - (leveldif - 2) * lchance; - - // Spellmod from SPELLMOD_RESIST_MISS_CHANCE - if (Player* modOwner = GetSpellModOwner()) - modOwner->ApplySpellMod(spell->Id, SPELLMOD_RESIST_MISS_CHANCE, modHitChance); - // Chance hit from victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras - modHitChance += pVictim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE, schoolMask); - // Reduce spell hit chance for Area of effect spells from victim SPELL_AURA_MOD_AOE_AVOIDANCE aura - if (IsAreaOfEffectSpell(spell)) - modHitChance -= pVictim->GetTotalAuraModifier(SPELL_AURA_MOD_AOE_AVOIDANCE); - - int32 HitChance = modHitChance * 100; - // Increase hit chance from attacker SPELL_AURA_MOD_SPELL_HIT_CHANCE and attacker ratings - HitChance += int32(m_modSpellHitChance * 100.0f); - - - // TODO: Verify lower 1% bound for hit? - return (float(10000 - std::min(std::max(100, HitChance), 10000)) / 100.0f); -} - SpellMissInfo Unit::MagicSpellHitResult(Unit* pVictim, SpellEntry const* spell) { // Can`t miss on dead target (on skinning for example) if (!pVictim->isAlive()) return SPELL_MISS_NONE; - uint32 rand = urand(0, 10000); + const SpellSchoolMask schoolMask = GetSpellSchoolMask(spell); + const uint32 rand = urand(1, 10000); + uint32 tmp = 0; - // Spell miss - uint32 tmp = uint32(MagicSpellMissChance(pVictim, spell) * 100); + tmp += uint32(CalculateSpellMissChance(pVictim, schoolMask, spell) * 100); if (rand < tmp) return SPELL_MISS_MISS; - // Resist - tmp += uint32(SpellResistChance(pVictim, spell) * 100); + tmp += uint32(CalculateSpellResistChance(pVictim, schoolMask, spell) * 100); if (rand < tmp) return SPELL_MISS_RESIST; @@ -3372,19 +3246,11 @@ SpellMissInfo Unit::SpellHitResult(Unit* pVictim, SpellEntry const* spell, bool if (pVictim->GetTypeId() == TYPEID_UNIT && ((Creature*)pVictim)->IsInEvadeMode()) return SPELL_MISS_EVADE; - // Check for immune - if (pVictim->IsImmuneToSpell(spell, this == pVictim) && !spell->HasAttribute(SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY)) - return SPELL_MISS_IMMUNE; - // All positive spells can`t miss // TODO: client not show miss log for this spells - so need find info for this in dbc and use it! if (IsPositiveSpell(spell->Id, this, pVictim)) return SPELL_MISS_NONE; - // Check for immune - if (pVictim->IsImmunedToDamage(GetSpellSchoolMask(spell)) && !spell->HasAttribute(SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY)) - return SPELL_MISS_IMMUNE; - // Try victim reflect spell if (CanReflect) { @@ -3401,6 +3267,17 @@ SpellMissInfo Unit::SpellHitResult(Unit* pVictim, SpellEntry const* spell, bool } } + if (!spell->HasAttribute(SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY)) + { + // Check for immune + if (pVictim->IsImmuneToSpell(spell, this == pVictim)) + return SPELL_MISS_IMMUNE; + + // Check for immune (use charges) + if (pVictim->IsImmuneToDamage(GetSpellSchoolMask(spell))) + return SPELL_MISS_IMMUNE; + } + switch (spell->GetDmgClass()) { case SPELL_DAMAGE_CLASS_NONE: @@ -3500,7 +3377,7 @@ bool Unit::CanParryInCombat(const Unit *attacker) const return (attacker && CanParryInCombat() && (HasAura(SPELL_AURA_MOD_PARRY_FROM_BEHIND_PERCENT) || attacker->IsFacingTargetsFront(this))); } -bool Unit::CanBlockInCombat() const +bool Unit::CanBlockInCombat(SpellSchoolMask /*weaponSchoolMask*/) const { if (!CanBlock() || !CanReactInCombat() || IsNonMeleeSpellCasted(false)) return false; @@ -3521,9 +3398,9 @@ bool Unit::CanBlockInCombat() const return true; } -bool Unit::CanBlockInCombat(const Unit *attacker) const +bool Unit::CanBlockInCombat(const Unit *attacker, SpellSchoolMask weaponSchoolMask) const { - return (attacker && CanBlockInCombat() && attacker->IsFacingTargetsFront(this)); + return (attacker && CanBlockInCombat(weaponSchoolMask) && attacker->IsFacingTargetsFront(this)); } bool Unit::CanCrushInCombat() const @@ -3581,6 +3458,9 @@ bool Unit::CanParryAbility(const Unit *attacker, const SpellEntry *ability) cons // Only physical melee attacks can be parried, deflect for magical and ranged if (ability->DmgClass != SPELL_DAMAGE_CLASS_MELEE || (GetSpellSchoolMask(ability) & SPELL_SCHOOL_MASK_MAGIC)) return false; + // Only close range melee attacks can be parried, longer range abilities use deflect + if (ability->rangeIndex != SPELL_RANGE_IDX_COMBAT && ability->rangeIndex != SPELL_RANGE_IDX_SELF_ONLY) + return false; // Check if attacker can be parried at the moment if (!CanParryInCombat(attacker)) return false; @@ -3642,6 +3522,8 @@ bool Unit::CanDeflectAbility(const Unit *attacker, const SpellEntry *ability) co case SPELL_DAMAGE_CLASS_MELEE: if (!(GetSpellSchoolMask(ability) & SPELL_SCHOOL_MASK_MAGIC)) return false; + if (ability->rangeIndex == SPELL_RANGE_IDX_COMBAT || ability->rangeIndex == SPELL_RANGE_IDX_SELF_ONLY) + return false; break; default: return false; @@ -3649,6 +3531,13 @@ bool Unit::CanDeflectAbility(const Unit *attacker, const SpellEntry *ability) co // Check if attacker can be parried/deflected at the moment if (!CanParryInCombat(attacker)) return false; + // Check if attacker cuts through parry/deflect with this ability + const AuraList &ignore = attacker->GetAurasByType(SPELL_AURA_IGNORE_COMBAT_RESULT); + for (AuraList::const_iterator i = ignore.begin(); i != ignore.end(); ++i) + { + if ((*i)->isAffectedOnSpell(ability) && (*i)->GetModifier()->m_miscvalue == MELEE_HIT_PARRY) + return false; + } return true; } @@ -3945,11 +3834,7 @@ float Unit::GetParryChance() const float chance = 0.0f; // FIXME: Verify which creatures are eligible for parry together with DB team and implement in a more clean way if (GetTypeId() == TYPEID_UNIT) - { - if (GetCreatureType() != CREATURE_TYPE_HUMANOID) - return 0.0f; chance += 5.0f; - } chance += m_modParryChance; return chance; } @@ -3985,7 +3870,16 @@ float Unit::GetCritChance(WeaponAttackType attType) const { float chance = 0.0f; if (GetTypeId() == TYPEID_UNIT) + { + // Totems use owner's crit chance (when owner is available) + if (((const Creature*)this)->IsTotem()) + { + if (const Unit* owner = GetOwner()) + return owner->GetCritChance(attType); + } + // Base crit chance (needed for units only, already included for players) chance += 5.0f; + } chance += m_modCritChance[attType]; return chance; } @@ -3999,8 +3893,17 @@ float Unit::GetCritChance(SpellSchoolMask schoolMask) const return 0.0f; if (GetTypeId() == TYPEID_UNIT) - chance += 5; - // Pick highest player spell crit available for given school mask + { + // Totems use owner's crit chance (when owner is available) + if (((const Creature*)this)->IsTotem()) + { + if (const Unit* owner = GetOwner()) + return owner->GetCritChance(schoolMask); + } + // Base crit chance (needed for units only, already included for players) + chance += 5.0f; + } + // Pick highest spell crit available for given school mask for (uint8 school = 0; mask; ++school) { if (mask & 1) @@ -4014,6 +3917,38 @@ float Unit::GetCritChance(SpellSchoolMask schoolMask) const return chance; } +float Unit::GetHitChance(WeaponAttackType attackType) const +{ + // Totems use owner's hit chance (when owner is available) + if (GetTypeId() == TYPEID_UNIT && ((const Creature*)this)->IsTotem()) + { + if (const Unit* owner = GetOwner()) + return owner->GetHitChance(attackType); + } + if (attackType == RANGED_ATTACK) + return m_modRangedHitChance; + return m_modMeleeHitChance; +} + +float Unit::GetHitChance(SpellSchoolMask schoolMask) const +{ + float chance = 0.0f; + + if (!uint32(schoolMask)) + return 0.0f; + + // Totems use owner's hit chance (when owner is available) + if (GetTypeId() == TYPEID_UNIT && ((const Creature*)this)->IsTotem()) + { + if (const Unit* owner = GetOwner()) + return owner->GetHitChance(schoolMask); + } + chance += m_modSpellHitChance; + // Pick highest spell hit available for given school mask + chance += GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_INCREASES_SPELL_PCT_TO_HIT, schoolMask); + return chance; +} + float Unit::CalculateEffectiveCritChance(const Unit* victim, WeaponAttackType attType, bool weapon, bool ability) const { float chance = 0.0f; @@ -4114,7 +4049,7 @@ float Unit::CalculateEffectiveMissChance(const Unit *victim, WeaponAttackType at // Victim's auras affecting attacker's hit contribution: chance -= victim->GetTotalAuraModifier(ranged ? SPELL_AURA_MOD_ATTACKER_RANGED_HIT_CHANCE: SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE); // Finally, reduce chance to miss by own hit chance - chance -= (ranged ? m_modRangedHitChance : m_modMeleeHitChance); + chance -= GetHitChance(attType); // Return bounded value if calculation is finalized (for attack) or unbound intermediate to continue (for abilities) return (ability ? chance : std::max(0.0f, std::min(chance, 100.0f))); } @@ -4293,6 +4228,63 @@ float Unit::CalculateSpellCritChance(const Unit *victim, SpellSchoolMask schoolM return std::max(0.0f, std::min(chance, 100.0f)); } +float Unit::CalculateSpellMissChance(const Unit *victim, SpellSchoolMask schoolMask, const SpellEntry *spell) const +{ + if (!spell || spell->HasAttribute(SPELL_ATTR_EX3_CANT_MISS)) + return 0.0f; + + float chance = 0.0f; + const float minimum = 0.0f; // WotLK: no unavoidable miss chance + + if (spell->HasAttribute(SPELL_ATTR_EX5_USE_PHYSICAL_HIT_CHANCE)) + { + // How it works: + // - First introduced in patch 2.3 for Taunt, Growl, Righteous Defense, Challenging Shout and Challenging Roar + // - Switches spell to use ability miss chance calculation, but leaves standard unavoidable miss percent + // In WotLK+: + // - Removed for Taunt, Growl and Righteous Defense, so these spells have 17% miss against bosses + // - Added glyphs for Taunt and Growl to negate remaining 8% of spell miss difference + // - AoE taunts on cooldowns still use ability miss calculation + chance = CalculateAbilityMissChance(victim, GetWeaponAttackType(spell), spell); + return std::max(minimum, chance); + } + // Base miss chance: 4% + chance += 4.0f; + // Level difference: positive adds to miss chance, negative substracts + const bool vsPlayerOrPet = (victim->GetTypeId() == TYPEID_PLAYER || victim->GetOwnerGuid().IsPlayer()); + int32 difference = int32(victim->GetLevelForTarget(this) - GetLevelForTarget(victim)); + // Level difference factor: 1% per level + uint8 factor = 1; + // NPCs and players gain additional bonus to incoming spell hit chance reduction based on positive level difference + if (difference > 2) + { + chance += (2 * factor); + // Miss bonus for each additional level of difference above 2 + factor = (vsPlayerOrPet ? 7 : 11); + chance += ((difference - 2) * factor); + } + else + chance += (difference * factor); + // Reduce by caster's spell hit chance + chance -= GetHitChance(schoolMask); + // Reduce by caster's spell hit mod + if (Player* modOwner = GetSpellModOwner()) + { + float hit = 0.0f; + modOwner->ApplySpellMod(spell->Id, SPELLMOD_RESIST_MISS_CHANCE, hit); + chance -= hit; + } + // Increase by victim's spell hit avoidance rating + if (victim->GetTypeId() == TYPEID_PLAYER) + chance += ((const Player*)victim)->GetRatingBonusValue(CR_HIT_TAKEN_SPELL); + // Increase by victim's AoE avoidance + if (IsAreaOfEffectSpell(spell)) + chance += victim->GetTotalAuraModifier(SPELL_AURA_MOD_AOE_AVOIDANCE); + // Reduce (or increase) by victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras + chance -= victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE, schoolMask); + return std::max(minimum, std::min(chance, 100.0f)); +} + float Unit::GetExpertisePercent(WeaponAttackType attType) const { int32 expertise = 0; @@ -4313,6 +4305,98 @@ float Unit::GetExpertisePercent(WeaponAttackType attType) const return (expertise / 4.0f); } +int32 Unit::GetResistancePenetration(SpellSchools school) const +{ + // Totems use owner's penetration (when owner is available) + if (GetTypeId() == TYPEID_UNIT && ((const Creature*)this)->IsTotem()) + { + if (const Unit* owner = GetOwner()) + return owner->GetResistancePenetration(school); + } + // Technically, it is target resistance mod, but it's used as penetration (negative sign) 99.9% of the time + // So, let's logically reverse the sign and use it as such + return -(GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_TARGET_RESISTANCE, (1 << school))); +} + +float Unit::CalculateEffectiveMagicResistancePercent(const Unit *attacker, SpellSchoolMask schoolMask, bool binary) const +{ + // Non-magical or contains Physical as a component (such as Chaos) - ignores resistances completely + if (schoolMask & SPELL_SCHOOL_MASK_NORMAL) + return 0.0f; + // Completely ignore holy resistance value (since beta stages) + uint32 schools = uint32(schoolMask & ~uint32(SPELL_SCHOOL_MASK_HOLY)); + // Select a resistance value matching given school mask, prefer mininal for multischool spells + int32 resistance = 0; + for (uint32 school = 0; schools; ++school) + { + if (schools & 1) + { + // Base victim resistance + int32 amount = int32(GetResistance(SpellSchools(school))); + // Modify by penetration + amount -= attacker->GetResistancePenetration(SpellSchools(school)); + // If unit has no resistance for this school: use it! + if (amount < 1) + { + resistance = 0; // Resistance can't be negative since early stages of development + break; + } + // Else if first school encountered or more vulnerable: memorize and continue + else if (!resistance || amount < resistance) + resistance = amount; + } + schools >>= 1; + } + // Attacker's level based skill, penalize when calculating for low levels (< 20): + const uint32 skill = std::max(attacker->GetMaxSkillValueForLevel(this), uint16(100)); + float percent = float(float(resistance) / (float(skill) + resistance)) * 100; + // Bonus resistance by level difference when calculating damage hit for NPCs only + if (!binary && GetTypeId() == TYPEID_UNIT && GetCharmerOrOwnerOrOwnGuid().IsCreature()) + percent += (0.4f * std::max(int32(GetMaxSkillValueForLevel(attacker) - skill), 0)); + // Magic resistance percentage cap + const float cap = 100.0f; // Post-WotLK: effectively no artifical hard cap + return std::max(0.0f, std::min(percent, cap)); +} + +float Unit::CalculateSpellResistChance(const Unit *victim, SpellSchoolMask schoolMask, const SpellEntry *spell) const +{ + float chance = 0.0f; + + // Chance to fully resist a spell by magic resistance + if (spell->DmgClass == SPELL_DAMAGE_CLASS_MAGIC && !spell->HasAttribute(SPELL_ATTR_EX4_IGNORE_RESISTANCES)) + { + const bool binary = IsBinarySpell(spell); + const float percent = victim->CalculateEffectiveMagicResistancePercent(this, schoolMask, binary); + if (binary) + chance += percent; + else + chance += float(SPELL_PARTIAL_RESIST_DISTRIBUTION.at(uint32(percent * 100)).at(SPELL_PARTIAL_RESIST_PCT_100)) * 0.01f; + } + // Chance to fully resist entire spell by it's dispel type + if (const int32 type = int32(spell->Dispel)) + chance += victim->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_DEBUFF_RESISTANCE, type); + // Chance to fully resist entire spell by it's mechanic + if (const int32 mechanic = int32(spell->Mechanic)) + chance += victim->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, mechanic); + + // TODO: Move it in the future, effect mechanic resist must be processed differently + float effects = 0.0f; + for (uint32 i = EFFECT_INDEX_0; i < MAX_EFFECT_INDEX; ++i) + { + if (!spell->Effect[i]) + continue; + if (const int32 mechanic = int32(spell->EffectMechanic[i])) + { + const float resist = victim->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_MECHANIC_RESISTANCE, mechanic); + if (resist > effects) + effects = resist; + } + } + chance += effects; + // + return std::max(0.0f, std::min(chance, 100.0f)); +} + uint32 Unit::GetWeaponSkillValue(WeaponAttackType attType, Unit const* target) const { uint32 value; diff --git a/src/game/Unit.h b/src/game/Unit.h index ed1b236abe..909b137a27 100644 --- a/src/game/Unit.h +++ b/src/game/Unit.h @@ -1620,9 +1620,7 @@ class MANGOS_DLL_SPEC Unit : public WorldObject uint32 GetManaDrainReduction(uint32 amount) const { return GetCombatRatingDamageReduction(CR_CRIT_TAKEN_SPELL, 2.2f, 100.0f, amount); /*NOTE: Verify if cases with melee/ranged ratings exist*/ } - float SpellResistChance(Unit* pVictim, SpellEntry const* spell); SpellMissInfo MeleeSpellHitResult(Unit* pVictim, SpellEntry const* spell); - float MagicSpellMissChance(Unit* pVictim, SpellEntry const* spell); SpellMissInfo MagicSpellHitResult(Unit* pVictim, SpellEntry const* spell); SpellMissInfo SpellHitResult(Unit* pVictim, SpellEntry const* spell, bool canReflect = false); @@ -1644,8 +1642,8 @@ class MANGOS_DLL_SPEC Unit : public WorldObject bool CanDodgeInCombat(const Unit* attacker) const; bool CanParryInCombat() const; bool CanParryInCombat(const Unit* attacker) const; - bool CanBlockInCombat() const; - bool CanBlockInCombat(const Unit* attacker) const; + bool CanBlockInCombat(SpellSchoolMask weaponSchoolMask = SPELL_SCHOOL_MASK_NORMAL) const; + bool CanBlockInCombat(const Unit* attacker, SpellSchoolMask weaponSchoolMask = SPELL_SCHOOL_MASK_NORMAL) const; bool CanCrushInCombat() const; bool CanCrushInCombat(const Unit* victim) const; bool CanGlanceInCombat() const { return CanGlance(); } @@ -1683,6 +1681,9 @@ class MANGOS_DLL_SPEC Unit : public WorldObject float GetCritChance(WeaponAttackType attackType) const; float GetCritChance(SpellSchoolMask schoolMask) const; + float GetHitChance(WeaponAttackType attackType) const; + float GetHitChance(SpellSchoolMask schoolMask) const; + float CalculateEffectiveCritChance(const Unit* victim, WeaponAttackType attType, bool weapon = true, bool ability = false) const; float CalculateEffectiveMissChance(const Unit* victim, WeaponAttackType attType, bool weapon = true, bool ability = false) const; @@ -1690,10 +1691,20 @@ class MANGOS_DLL_SPEC Unit : public WorldObject float CalculateAbilityMissChance(const Unit* victim, WeaponAttackType attType, const SpellEntry* ability) const; float CalculateSpellCritChance(const Unit* victim, SpellSchoolMask schoolMask, const SpellEntry* spell) const; + float CalculateSpellMissChance(const Unit* victim, SpellSchoolMask schoolMask, const SpellEntry* spell) const; float GetExpertisePercent(WeaponAttackType attType) const; - virtual uint32 GetShieldBlockDamageValue() const = 0; + int32 GetResistancePenetration(SpellSchools school) const; + + float CalculateEffectiveMagicResistancePercent(const Unit* attacker, SpellSchoolMask schoolMask, bool binary = false) const; + + float CalculateSpellResistChance(const Unit* victim, SpellSchoolMask schoolMask, const SpellEntry* spell) const; + + virtual uint32 GetShieldBlockValue() const = 0; + uint32 GetUnitMeleeSkill(Unit const* target = nullptr) const { return (target ? GetLevelForTarget(target) : getLevel()) * 5; } + uint32 GetDefenseSkillValue(Unit const* target = nullptr) const; + uint32 GetWeaponSkillValue(WeaponAttackType attType, Unit const* target = nullptr) const; float GetWeaponProcChance() const; float GetPPMProcChance(uint32 WeaponSpeed, float PPM) const; @@ -2257,8 +2268,6 @@ class MANGOS_DLL_SPEC Unit : public WorldObject bool IsImmunedToDamage(SpellSchoolMask meleeSchoolMask); virtual bool IsImmuneToSpellEffect(SpellEntry const* spellInfo, SpellEffectIndex index, bool castOnSelf) const; - uint32 CalculateEffectiveMagicResistance(Unit* attacker, SpellSchoolMask schoolMask) const; - float CalculateMagicResistanceMitigation(Unit* attacker, uint32 resistance, SpellSchoolMask schoolMask, bool binary) const; uint32 CalcArmorReducedDamage(Unit* pVictim, const uint32 damage); void CalculateDamageAbsorbAndResist(Unit* pCaster, SpellSchoolMask schoolMask, DamageEffectType damagetype, const uint32 damage, uint32* absorb, uint32* resist, bool canReflect = false, bool ignoreResists = false, bool binary = false); void CalculateAbsorbResistBlock(Unit* pCaster, SpellNonMeleeDamage* damageInfo, SpellEntry const* spellProto, WeaponAttackType attType = BASE_ATTACK);