Core/Spells: Only check spell immunity against effects that will apply to each target

This commit is contained in:
Shauren
2025-12-25 16:48:32 +01:00
parent 42ebe3b0dc
commit 6cff1622e5
7 changed files with 36 additions and 23 deletions

View File

@@ -2591,10 +2591,10 @@ SpellMissInfo WorldObject::MagicSpellHitResult(Unit* victim, SpellInfo const* sp
// Parry
// For spells
// Resist
SpellMissInfo WorldObject::SpellHitResult(Unit* victim, SpellInfo const* spellInfo, bool canReflect /*= false*/) const
SpellMissInfo WorldObject::SpellHitResult(Unit* victim, SpellInfo const* spellInfo, bool canReflect, bool canImmune) const
{
// Check for immune
if (victim->IsImmunedToSpell(spellInfo, this))
if (canImmune && victim->IsImmunedToSpell(spellInfo, MAX_EFFECT_MASK, this))
return SPELL_MISS_IMMUNE;
// Damage immunity is only checked if the spell has damage effects, this immunity must not prevent aura apply

View File

@@ -837,7 +837,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
virtual float MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo) const;
virtual SpellMissInfo MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const;
SpellMissInfo MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const;
SpellMissInfo SpellHitResult(Unit* victim, SpellInfo const* spellInfo, bool canReflect = false) const;
SpellMissInfo SpellHitResult(Unit* victim, SpellInfo const* spellInfo, bool canReflect, bool canImmune) const;
void SendSpellMiss(Unit* target, uint32 spellID, SpellMissInfo missInfo);
virtual uint32 GetFaction() const = 0;

View File

@@ -53,6 +53,7 @@
#include "Loot.h"
#include "LootMgr.h"
#include "LootPackets.h"
#include "MapUtils.h"
#include "MiscPackets.h"
#include "MotionMaster.h"
#include "MovementGenerator.h"
@@ -1597,7 +1598,7 @@ void Unit::DealMeleeDamage(CalcDamageInfo* damageInfo, bool durabilityLoss)
SpellInfo const* spellInfo = aurEff->GetSpellInfo();
// Damage shield can be resisted...
SpellMissInfo missInfo = victim->SpellHitResult(this, spellInfo, false);
SpellMissInfo missInfo = victim->SpellHitResult(this, spellInfo, false, true);
if (missInfo != SPELL_MISS_NONE)
{
victim->SendSpellMiss(this, spellInfo->Id, missInfo);
@@ -7743,7 +7744,7 @@ int32 Unit::SpellAbsorbBonusTaken(Unit* caster, SpellInfo const* spellProto, int
return static_cast<int32>(std::round(absorb));
}
bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute /*= false*/) const
bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute /*= false*/) const
{
if (!spellInfo)
return false;
@@ -7754,14 +7755,14 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caste
if (!requireImmunityPurgesEffectAttribute)
return range.begin() != range.end();
return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry)
return std::ranges::any_of(range, [](uint32 immunitySpellId)
{
if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second, DIFFICULTY_NONE))
if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(immunitySpellId, DIFFICULTY_NONE))
if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
return true;
return false;
});
}, Trinity::Containers::MapValue);
};
// Single spell immunity.
@@ -7792,7 +7793,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caste
{
// State/effect immunities applied by aura expect full spell immunity
// Ignore effects with mechanic, they are supposed to be checked separately
if (!spellEffectInfo.IsEffect())
if (!spellEffectInfo.IsEffect() || !(effectMask & (1 << spellEffectInfo.EffectIndex)))
continue;
if (!IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute))
{
@@ -7820,7 +7821,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caste
if (!immuneSpellInfo || !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
continue;
if (spellInfo->IsPositive() && !(immuneSpellInfo && immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)))
if ((spellInfo->NegativeEffects & std::bitset<MAX_SPELL_EFFECTS>(effectMask)).none() && !(immuneSpellInfo && immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)))
continue;
if (spellInfo->CanPierceImmuneAura(immuneSpellInfo))
@@ -7955,14 +7956,14 @@ bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, SpellEffectInfo co
if (!requireImmunityPurgesEffectAttribute)
return range.begin() != range.end();
return std::any_of(range.begin(), range.end(), [](SpellImmuneContainer::value_type const& entry)
return std::ranges::any_of(range, [](uint32 immunitySpellId)
{
if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(entry.second, DIFFICULTY_NONE))
if (SpellInfo const* immunitySourceSpell = sSpellMgr->GetSpellInfo(immunitySpellId, DIFFICULTY_NONE))
if (immunitySourceSpell->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT))
return true;
return false;
});
}, Trinity::Containers::MapValue);
};
// If m_immuneToEffect type contain this effect type, IMMUNE effect.
@@ -12283,7 +12284,7 @@ Aura* Unit::AddAura(SpellInfo const* spellInfo, uint32 effMask, Unit* target)
if (!target->IsAlive() && !spellInfo->IsPassive() && !spellInfo->HasAttribute(SPELL_ATTR2_ALLOW_DEAD_TARGET))
return nullptr;
if (target->IsImmunedToSpell(spellInfo, this))
if (target->IsImmunedToSpell(spellInfo, effMask, this))
return nullptr;
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())

View File

@@ -1677,7 +1677,7 @@ class TC_GAME_API Unit : public WorldObject
static uint32 SpellCriticalHealingBonus(Unit const* caster, SpellInfo const* spellProto, uint32 damage, Unit* victim);
void ApplySpellImmune(uint32 spellId, SpellImmunity op, uint32 type, bool apply);
bool IsImmunedToSpell(SpellInfo const* spellInfo, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute = false) const;
bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, WorldObject const* caster, bool requireImmunityPurgesEffectAttribute = false) const;
uint32 GetSchoolImmunityMask() const;
uint32 GetDamageImmunityMask() const;
uint64 GetMechanicImmunityMask() const;

View File

@@ -5629,7 +5629,7 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const
// Consecrate ticks can miss and will not show up in the combat log
// dynobj auras must always have a caster
if (GetSpellEffectInfo().IsEffect(SPELL_EFFECT_PERSISTENT_AREA_AURA) &&
ASSERT_NOTNULL(caster)->SpellHitResult(target, GetSpellInfo(), false) != SPELL_MISS_NONE)
ASSERT_NOTNULL(caster)->SpellHitResult(target, GetSpellInfo(), false, true) != SPELL_MISS_NONE)
return;
CleanDamage cleanDamage = CleanDamage(0, 0, BASE_ATTACK, MELEE_HIT_NORMAL);
@@ -5763,7 +5763,7 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c
// dynobj auras must always have a caster
if (GetSpellEffectInfo().IsEffect(SPELL_EFFECT_PERSISTENT_AREA_AURA) &&
ASSERT_NOTNULL(caster)->SpellHitResult(target, GetSpellInfo(), false) != SPELL_MISS_NONE)
ASSERT_NOTNULL(caster)->SpellHitResult(target, GetSpellInfo(), false, true) != SPELL_MISS_NONE)
return;
CleanDamage cleanDamage = CleanDamage(0, 0, GetSpellInfo()->GetAttackType(), MELEE_HIT_NORMAL);
@@ -5953,7 +5953,7 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con
}
if (GetSpellEffectInfo().IsEffect(SPELL_EFFECT_PERSISTENT_AREA_AURA) &&
caster->SpellHitResult(target, GetSpellInfo(), false) != SPELL_MISS_NONE)
caster->SpellHitResult(target, GetSpellInfo(), false, true) != SPELL_MISS_NONE)
return;
// ignore negative values (can be result apply spellmods to aura damage

View File

@@ -684,7 +684,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply)
{
// needs readding - remove now, will be applied in next update cycle
// (dbcs do not have auras which apply on same type of targets but have different radius, so this is not really needed)
if (itr->first->IsImmunedToSpell(GetSpellInfo(), caster, true) || !CanBeAppliedOn(itr->first))
if (itr->first->IsImmunedToSpell(GetSpellInfo(), itr->second, caster, true) || !CanBeAppliedOn(itr->first))
{
targetsToRemove.push_back(applicationPair.second->GetTarget());
continue;
@@ -717,7 +717,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply)
if (itr->first->IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster))
itr->second &= ~(1 << spellEffectInfo.EffectIndex);
if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo(), caster) || !CanBeAppliedOn(itr->first))
if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo(), itr->second, caster) || !CanBeAppliedOn(itr->first))
addUnit = false;
}

View File

@@ -2434,7 +2434,9 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*=
// Calculate hit result
WorldObject* caster = m_originalCaster ? m_originalCaster : m_caster;
targetInfo.MissCondition = caster->SpellHitResult(target, m_spellInfo, m_canReflect && !(IsPositive() && m_caster->IsFriendlyTo(target)));
targetInfo.MissCondition = caster->SpellHitResult(target, m_spellInfo,
m_canReflect && !(IsPositive() && m_caster->IsFriendlyTo(target)),
false /*immunity will be checked after complete EffectMask is known*/);
// Spell have speed - need calculate incoming time
// Incoming time is zero for self casts. At least I think so.
@@ -2479,7 +2481,9 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*=
{
// Calculate reflected spell result on caster (shouldn't be able to reflect gameobject spells)
Unit* unitCaster = ASSERT_NOTNULL(m_caster->ToUnit());
targetInfo.ReflectResult = unitCaster->SpellHitResult(unitCaster, m_spellInfo, false); // can't reflect twice
targetInfo.ReflectResult = unitCaster->SpellHitResult(unitCaster, m_spellInfo,
false /*can't reflect twice*/,
false /*immunity will be checked after complete EffectMask is known*/);
// Proc spell reflect aura when missile hits the original target
target->m_Events.AddEvent(new ProcReflectDelayed(target, m_originalCasterGUID), target->m_Events.CalculateTime(Milliseconds(targetInfo.TimeDelay)));
@@ -3099,7 +3103,7 @@ SpellMissInfo Spell::PreprocessSpellHit(Unit* unit, TargetInfo& hitInfo)
return SPELL_MISS_EVADE;
// For delayed spells immunity may be applied between missile launch and hit - check immunity for that case
if (hitInfo.TimeDelay && unit->IsImmunedToSpell(m_spellInfo, m_caster))
if (hitInfo.TimeDelay && unit->IsImmunedToSpell(m_spellInfo, hitInfo.EffectMask, m_caster))
return SPELL_MISS_IMMUNE;
CallScriptBeforeHitHandlers(hitInfo.MissCondition);
@@ -8612,6 +8616,10 @@ void Spell::PreprocessSpellLaunch(TargetInfo& targetInfo)
if (!targetUnit)
return;
// Check immunity now that EffectMask is known
if (targetUnit->IsImmunedToSpell(GetSpellInfo(), targetInfo.EffectMask, m_caster))
targetInfo.MissCondition = SPELL_MISS_IMMUNE;
// This will only cause combat - the target will engage once the projectile hits (in Spell::TargetInfo::PreprocessTarget)
if (m_originalCaster && targetInfo.MissCondition != SPELL_MISS_EVADE && !m_originalCaster->IsFriendlyTo(targetUnit) && (!m_spellInfo->IsPositive() || m_spellInfo->HasEffect(SPELL_EFFECT_DISPEL)) && (m_spellInfo->HasInitialAggro() || targetUnit->IsEngaged()))
m_originalCaster->SetInCombatWith(targetUnit, true);
@@ -8622,7 +8630,11 @@ void Spell::PreprocessSpellLaunch(TargetInfo& targetInfo)
unit = targetUnit;
// In case spell reflect from target, do all effect on caster (if hit)
else if (targetInfo.MissCondition == SPELL_MISS_REFLECT && targetInfo.ReflectResult == SPELL_MISS_NONE)
{
unit = m_caster->ToUnit();
if (unit && unit->IsImmunedToSpell(GetSpellInfo(), targetInfo.EffectMask, unit))
targetInfo.ReflectResult = SPELL_MISS_IMMUNE;
}
if (!unit)
return;