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
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 80 additions and 28 deletions

View file

@ -74,6 +74,7 @@
#include "WorldPacket.h"
#include <algorithm>
#include <cmath>
#include <limits>
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<uint32>::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)

View file

@ -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;

View file

@ -22,6 +22,7 @@
#include "gtest/gtest.h"
#include <algorithm>
#include <array>
#include <cstdint>
#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));
}