diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index e15d68f60..644906240 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -74,6 +74,7 @@ #include "WorldPacket.h" #include #include +#include float baseMoveSpeed[MAX_MOVE_TYPE] = { @@ -910,6 +911,9 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit if (!spellInfo) return false; + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) + return false; + bool immuneToAllEffects = true; bool hasCheckedEffect = false; @@ -940,8 +944,28 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { - if (IsImmunedToSchool(spellInfo)) - return true; + if (spellInfo->Id == 42292 || spellInfo->Id == 59752 || spellInfo->Id == 19574 || spellInfo->Id == 34471) + return false; + + SpellSchoolMask schoolMask = spellInfo->GetSchoolMask(); + if (schoolMask != SPELL_SCHOOL_MASK_NONE) + { + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto const& [immunitySchoolMask, immunityAuraId] : schoolList) + { + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(immunityAuraId); + if ((immunitySchoolMask & schoolMask) != schoolMask) + continue; + + if (IgnoresSchoolImmunityFromFriendlyCaster(caster, immunityAuraId, immuneSpellInfo)) + continue; + + if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + continue; + + return true; + } + } } return false; @@ -9810,6 +9834,18 @@ uint32 Unit::GetDamageImmunityMask() const return mask; } +bool Unit::IgnoresSchoolImmunityFromFriendlyCaster(Unit const* caster, uint32 immunityAuraId, SpellInfo const* immunitySpellInfo) const +{ + if (!caster || !caster->IsFriendlyTo(this)) + return false; + + if (immunitySpellInfo) + return !immunitySpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS); + + // Creature template immunities are loaded with a placeholder spell id. + return immunityAuraId == std::numeric_limits::max(); +} + bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const { if (schoolMask == SPELL_SCHOOL_MASK_NONE) @@ -9847,7 +9883,7 @@ bool Unit::IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) con for (auto const& [immunitySchoolMask, immunityAuraId] : container) { SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); - if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this)) + if (IgnoresSchoolImmunityFromFriendlyCaster(caster, immunityAuraId, immuneAuraInfo)) continue; if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo)) @@ -9928,7 +9964,10 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) { - if ((itr->first & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second); + if ((itr->first & schoolMask) == schoolMask + && !IgnoresSchoolImmunityFromFriendlyCaster(spell->GetCaster(), itr->second, immuneSpellInfo) + && !spellInfo->CanPierceImmuneAura(immuneSpellInfo)) { return true; } @@ -9966,7 +10005,7 @@ bool Unit::IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spel for (auto const& [immunitySchoolMask, immunityAuraId] : container) { SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); - if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this)) + if (IgnoresSchoolImmunityFromFriendlyCaster(caster, immunityAuraId, immuneAuraInfo)) continue; schoolImmunityMask |= immunitySchoolMask; @@ -10045,6 +10084,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask(); + Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; if (spell) { spellSchoolMask = spell->GetSpellSchoolMask(); @@ -10059,12 +10099,8 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (!(itr->first & spellSchoolMask)) continue; - if (immuneSpellInfo && !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) - { - Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; - if (spellCaster && spellCaster->IsFriendlyTo(this)) - continue; - } + if (IgnoresSchoolImmunityFromFriendlyCaster(spellCaster, itr->second, immuneSpellInfo)) + continue; if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) continue; @@ -15109,7 +15145,7 @@ Aura* Unit::AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target) if (!spellInfo) return nullptr; - if (target->IsImmunedToSpell(spellInfo)) + if (target->IsImmunedToSpell(spellInfo, effMask, this)) return nullptr; for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index b784c702e..50d104081 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1638,6 +1638,7 @@ public: void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType = SPELL_BLOCK_TYPE_ALL); virtual bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr); bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster = nullptr); + bool IgnoresSchoolImmunityFromFriendlyCaster(Unit const* caster, uint32 immunityAuraId, SpellInfo const* immunitySpellInfo) const; [[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask schoolMask) const; [[nodiscard]] bool IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const; [[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask schoolMask) const; diff --git a/src/test/server/game/Spells/SpellImmunityTest.cpp b/src/test/server/game/Spells/SpellImmunityTest.cpp index a88e8c048..47ffc4fee 100644 --- a/src/test/server/game/Spells/SpellImmunityTest.cpp +++ b/src/test/server/game/Spells/SpellImmunityTest.cpp @@ -22,6 +22,7 @@ #include "gtest/gtest.h" +#include #include #include #include "Unit.h" // needed for SpellSchoolMask and mask helper @@ -79,30 +80,24 @@ namespace bool HasOnlyDamageEffects(SpellDesc const& spell) { - bool hasAny = false; - - for (EffectDesc const& e : spell.effects) + bool hasAny = std::ranges::any_of(spell.effects, [](EffectDesc const& effect) { - if (e.effect == EFFECT_NONE) - continue; + return effect.effect != EFFECT_NONE; + }); - hasAny = true; - if (!IsDamageEffect(e.effect)) - return false; - } - - return hasAny; + return hasAny && std::ranges::all_of(spell.effects, [](EffectDesc const& effect) + { + return effect.effect == EFFECT_NONE || IsDamageEffect(effect.effect); + }); } // Helper to classify spells which apply a stun aura bool IsStunSpell(SpellDesc const& spell) { - for (EffectDesc const& e : spell.effects) + return std::ranges::any_of(spell.effects, [](EffectDesc const& effect) { - if (e.effect == EFFECT_APPLY_AURA && e.aura == AURA_MOD_STUN) - return true; - } - return false; + return effect.effect == EFFECT_APPLY_AURA && effect.aura == AURA_MOD_STUN; + }); } bool IsEffectBlockedByStunImmunity(EffectDesc const& effect, bool immuneToStun) @@ -130,6 +125,11 @@ namespace return hasAnyEffect; } + bool IsBlockedBySchoolImmunity(bool casterFriendly, bool immunityAppliesToFriendly) + { + return !casterFriendly || immunityAppliesToFriendly; + } + // The last parameter defaults to false to avoid updating existing tests // that don't care about mechanic immunities. Bladestorm grants a // specific mechanic immunity (including stun) that should block @@ -364,3 +364,18 @@ TEST(SpellImmunityTest, StunImmunity_DoesNotFullyBlockMixedSpell) EXPECT_TRUE(IsStunSpell(mixedSpell)); EXPECT_EQ(ComputeSpellHitResult(false, false, mixedSpell, true), SPELL_MISS_NONE); } + +TEST(SpellImmunityTest, SchoolImmunity_TemplateStyle_AllowsFriendlySpell) +{ + EXPECT_FALSE(IsBlockedBySchoolImmunity(true, false)); +} + +TEST(SpellImmunityTest, SchoolImmunity_ExplicitFriendlyBlockStillApplies) +{ + EXPECT_TRUE(IsBlockedBySchoolImmunity(true, true)); +} + +TEST(SpellImmunityTest, SchoolImmunity_BlocksHostileSpell) +{ + EXPECT_TRUE(IsBlockedBySchoolImmunity(false, false)); +}