fix(Core/Immunities): ignore school immunity from friendly caster (#25408)

This commit is contained in:
sogladev
2026-04-10 11:55:47 +02:00
committed by GitHub
parent dbae4648ba
commit b5c8b99ad6
3 changed files with 80 additions and 28 deletions
+48 -12
View File
@@ -74,6 +74,7 @@
#include "WorldPacket.h" #include "WorldPacket.h"
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <limits>
float baseMoveSpeed[MAX_MOVE_TYPE] = float baseMoveSpeed[MAX_MOVE_TYPE] =
{ {
@@ -910,6 +911,9 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit
if (!spellInfo) if (!spellInfo)
return false; return false;
if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura())
return false;
bool immuneToAllEffects = true; bool immuneToAllEffects = true;
bool hasCheckedEffect = false; 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 (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{ {
if (IsImmunedToSchool(spellInfo)) if (spellInfo->Id == 42292 || spellInfo->Id == 59752 || spellInfo->Id == 19574 || spellInfo->Id == 34471)
return true; 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; return false;
@@ -9810,6 +9834,18 @@ uint32 Unit::GetDamageImmunityMask() const
return mask; 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<uint32>::max();
}
bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const
{ {
if (schoolMask == SPELL_SCHOOL_MASK_NONE) 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) for (auto const& [immunitySchoolMask, immunityAuraId] : container)
{ {
SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); 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; continue;
if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo)) if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo))
@@ -9928,7 +9964,10 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const
SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL];
for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) 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; return true;
} }
@@ -9966,7 +10005,7 @@ bool Unit::IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spel
for (auto const& [immunitySchoolMask, immunityAuraId] : container) for (auto const& [immunitySchoolMask, immunityAuraId] : container)
{ {
SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); 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; continue;
schoolImmunityMask |= immunitySchoolMask; schoolImmunityMask |= immunitySchoolMask;
@@ -10045,6 +10084,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES))
{ {
SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask(); SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask();
Unit const* spellCaster = spell ? spell->GetCaster() : nullptr;
if (spell) if (spell)
{ {
spellSchoolMask = spell->GetSpellSchoolMask(); spellSchoolMask = spell->GetSpellSchoolMask();
@@ -10059,12 +10099,8 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell)
if (!(itr->first & spellSchoolMask)) if (!(itr->first & spellSchoolMask))
continue; continue;
if (immuneSpellInfo && !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) if (IgnoresSchoolImmunityFromFriendlyCaster(spellCaster, itr->second, immuneSpellInfo))
{ continue;
Unit const* spellCaster = spell ? spell->GetCaster() : nullptr;
if (spellCaster && spellCaster->IsFriendlyTo(this))
continue;
}
if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) if (spellInfo->CanPierceImmuneAura(immuneSpellInfo))
continue; continue;
@@ -15109,7 +15145,7 @@ Aura* Unit::AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target)
if (!spellInfo) if (!spellInfo)
return nullptr; return nullptr;
if (target->IsImmunedToSpell(spellInfo)) if (target->IsImmunedToSpell(spellInfo, effMask, this))
return nullptr; return nullptr;
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i) for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
+1
View File
@@ -1638,6 +1638,7 @@ public:
void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType = SPELL_BLOCK_TYPE_ALL); 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); virtual bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr);
bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster = 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(SpellSchoolMask schoolMask) const;
[[nodiscard]] bool IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const; [[nodiscard]] bool IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const;
[[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask schoolMask) const; [[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask schoolMask) const;
@@ -22,6 +22,7 @@
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include <algorithm>
#include <array> #include <array>
#include <cstdint> #include <cstdint>
#include "Unit.h" // needed for SpellSchoolMask and mask helper #include "Unit.h" // needed for SpellSchoolMask and mask helper
@@ -79,30 +80,24 @@ namespace
bool HasOnlyDamageEffects(SpellDesc const& spell) bool HasOnlyDamageEffects(SpellDesc const& spell)
{ {
bool hasAny = false; bool hasAny = std::ranges::any_of(spell.effects, [](EffectDesc const& effect)
for (EffectDesc const& e : spell.effects)
{ {
if (e.effect == EFFECT_NONE) return effect.effect != EFFECT_NONE;
continue; });
hasAny = true; return hasAny && std::ranges::all_of(spell.effects, [](EffectDesc const& effect)
if (!IsDamageEffect(e.effect)) {
return false; return effect.effect == EFFECT_NONE || IsDamageEffect(effect.effect);
} });
return hasAny;
} }
// Helper to classify spells which apply a stun aura // Helper to classify spells which apply a stun aura
bool IsStunSpell(SpellDesc const& spell) 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 effect.effect == EFFECT_APPLY_AURA && effect.aura == AURA_MOD_STUN;
return true; });
}
return false;
} }
bool IsEffectBlockedByStunImmunity(EffectDesc const& effect, bool immuneToStun) bool IsEffectBlockedByStunImmunity(EffectDesc const& effect, bool immuneToStun)
@@ -130,6 +125,11 @@ namespace
return hasAnyEffect; return hasAnyEffect;
} }
bool IsBlockedBySchoolImmunity(bool casterFriendly, bool immunityAppliesToFriendly)
{
return !casterFriendly || immunityAppliesToFriendly;
}
// The last parameter defaults to false to avoid updating existing tests // The last parameter defaults to false to avoid updating existing tests
// that don't care about mechanic immunities. Bladestorm grants a // that don't care about mechanic immunities. Bladestorm grants a
// specific mechanic immunity (including stun) that should block // specific mechanic immunity (including stun) that should block
@@ -364,3 +364,18 @@ TEST(SpellImmunityTest, StunImmunity_DoesNotFullyBlockMixedSpell)
EXPECT_TRUE(IsStunSpell(mixedSpell)); EXPECT_TRUE(IsStunSpell(mixedSpell));
EXPECT_EQ(ComputeSpellHitResult(false, false, mixedSpell, true), SPELL_MISS_NONE); 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));
}