diff --git a/data/sql/updates/pending_db_world/rev_1772292641644296192.sql b/data/sql/updates/pending_db_world/rev_1772292641644296192.sql new file mode 100644 index 000000000..d34f54d4d --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1772292641644296192.sql @@ -0,0 +1,23 @@ +-- +CREATE TABLE `creature_immunities` ( + `ID` int NOT NULL, + `SchoolMask` tinyint NOT NULL DEFAULT '0', + `DispelTypeMask` smallint NOT NULL DEFAULT '0', + `MechanicsMask` bigint NOT NULL DEFAULT '0', + `Effects` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `Auras` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + `ImmuneAoE` tinyint(1) NOT NULL DEFAULT '0', + `ImmuneChain` tinyint(1) NOT NULL DEFAULT '0', + `Comment` mediumtext COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`ID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +ALTER TABLE `creature_template` ADD `CreatureImmunitiesId` int NOT NULL DEFAULT '0' AFTER `RegenHealth`; + +UPDATE `creature_template` SET `CreatureImmunitiesId`=COALESCE((SELECT ci.`ID` FROM `creature_immunities` ci WHERE ci.`SchoolMask`=`spell_school_immune_mask` AND ci.`MechanicsMask`=`mechanic_immune_mask`*2),0); + +ALTER TABLE `creature_template` + DROP `spell_school_immune_mask`, + DROP `mechanic_immune_mask`; + +ALTER TABLE `creature_template` DROP COLUMN `scale`; diff --git a/data/sql/updates/pending_db_world/rev_1772402317724411598.sql b/data/sql/updates/pending_db_world/rev_1772402317724411598.sql new file mode 100644 index 000000000..aeaac02a2 --- /dev/null +++ b/data/sql/updates/pending_db_world/rev_1772402317724411598.sql @@ -0,0 +1,37 @@ +-- + +DELETE FROM `creature_immunities` WHERE `ID` IN (95,96,315,477,679,878,1537,1557,1614,1615,1630,1632,1664,1676,1682,1693,1694,1695,1733); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(95, 0, 0, 32, '', '', 0, 0, 'Immune to Fear'), +(96, 0, 0, 1234599598, '', '5,7,12,26,33', 0, 0, 'Immune to CC (Free Friend, Uncontrollable Frenzy, Warlord\'s Presence)'), +(315, 127, 0, 0, '', '', 0, 0, 'Immune to damage'), +(477, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(679, 0, 0, 1234599078, '', '5,7,12,26,33', 0, 0, 'Immune to CC'), +(878, 0, 0, 4096, '', '12', 0, 0, 'Immune to Stun'), +(1537, 0, 0, 32, '', '7', 0, 0, 'Immune to Fear'), +(1557, 0, 0, 1234599078, '98,124,144,145', '', 0, 0, 'Immune CC+Stun+Move'), +(1614, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(1615, 0, 0, 1301707942, '68', '5,7,12,26,27,33,60', 0, 0, 'Immune to CC+Interrupt'), +(1630, 0, 0, 69954075814, '114', '5,7,11,12,26,33', 0, 0, 'Immune to CC+Taunt'), +(1632, 0, 0, 584472182, '', '', 0, 0, 'Immune to hard CC'), +(1664, 0, 0, 2176, '', '', 0, 0, 'Immune to slow'), +(1676, 0, 0, 570425344, '', '', 0, 0, 'Immune to immunities'), +(1682, 0, 0, 4096, '', '', 0, 0, 'Immune to Stun'), +(1693, 0, 0, 584472182, '68', '', 0, 0, 'Immune to hard CC+Interrupt'), +(1694, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(1695, 0, 0, 4096, '', '12', 0, 0, 'Immune to Stun'), +(1733, 0, 0, 1234599078, '98,124,144,145', '', 0, 0, 'Immune CC+Stun+Move'); + +DELETE FROM `creature_immunities` WHERE `ID` IN (1762,1771,1794,1799,1808,1835,1887,1893,1944,1964,1971); +INSERT INTO `creature_immunities` (`ID`, `SchoolMask`, `DispelTypeMask`, `MechanicsMask`, `Effects`, `Auras`, `ImmuneAoE`, `ImmuneChain`, `Comment`) VALUES +(1762, 0, 4, 0, '', '', 0, 0, 'Immune to Curses'), +(1771, 0, 0, 0, '', '', 1, 0, 'Immune to AoE'), +(1794, 0, 0, 67108864, '68', '', 0, 0, 'Immune to Interrupts'), +(1799, 0, 0, 67109376, '68', '27,60', 0, 0, 'Immune to Interrupts+Silence'), +(1808, 0, 0, 0, '98,124,144,145', '', 0, 0, 'Immune to knockback'), +(1835, 0, 0, 1075183616, '', '', 0, 0, 'Immune to polymorph+banish+shackle+sap'), +(1887, 0, 0, 0, '98,124,144,145', '', 0, 0, 'Immune to knockback'), +(1893, 0, 0, 1091728546, '', '', 0, 0, 'Immune to Charm+Fear+Freeze+Horror+Knockout+Polymorph+Root+Sap+Shackle+Sleep+Stun'), +(1944, 0, 0, 68719476736, '114', '11', 0, 0, 'Immune to Taunt'), +(1964, 0, 0, 67109376, '68', '27,60', 0, 0, 'Immune to Interrupts+Silence'), +(1971, 0, 0, 4096, '', '', 0, 0, 'Immune to Stun'); diff --git a/src/server/database/Database/Implementation/WorldDatabase.cpp b/src/server/database/Database/Implementation/WorldDatabase.cpp index ea97d9a8c..32b568b77 100644 --- a/src/server/database/Database/Implementation/WorldDatabase.cpp +++ b/src/server/database/Database/Implementation/WorldDatabase.cpp @@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements() PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC); PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH); - PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); + PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, CreatureImmunitiesId, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH); PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH); diff --git a/src/server/game/Entities/Creature/Creature.cpp b/src/server/game/Entities/Creature/Creature.cpp index 815644e68..d714343c9 100644 --- a/src/server/game/Entities/Creature/Creature.cpp +++ b/src/server/game/Entities/Creature/Creature.cpp @@ -643,7 +643,7 @@ bool Creature::UpdateEntry(uint32 Entry, const CreatureData* data, bool changele SetControlled(true, UNIT_STATE_ROOT); UpdateMovementFlags(); - LoadSpellTemplateImmunity(); + LoadTemplateImmunities(cInfo->CreatureImmunitiesId); if (updateAI) { @@ -2132,39 +2132,55 @@ void Creature::InitializeReactState() SetReactState(REACT_AGGRESSIVE); } -bool Creature::HasMechanicTemplateImmunity(uint32 mask) const +bool Creature::HasMechanicTemplateImmunity(uint64 mask) const { - return !GetOwnerGUID().IsPlayer() && (GetCreatureTemplate()->MechanicImmuneMask & mask); + if (GetOwnerGUID().IsPlayer()) + return false; + + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(_creatureImmunitiesId)) + return (immunities->Mechanic.to_ullong() & mask) != 0; + + // no custom immunity entry => no mechanic immunity + return false; } -void Creature::LoadSpellTemplateImmunity() +void Creature::LoadTemplateImmunities(int32 creatureImmunitiesId) { // uint32 max used for "spell id", the immunity system will not perform SpellInfo checks against invalid spells // used so we know which immunities were loaded from template - static uint32 const placeholderSpellId = std::numeric_limits::max(); + static uint32 constexpr placeholderSpellId = std::numeric_limits::max(); - // unapply template immunities (in case we're updating entry) - for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i) + auto applyCreatureImmunities = [this](CreatureImmunities const* immunities, bool apply) { - ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, i, false); - } + if (!immunities) + return; + for (std::size_t i = 0; i < immunities->School.size(); ++i) + if (immunities->School[i]) + ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, apply); + for (std::size_t i = 0; i < immunities->DispelType.size(); ++i) + if (immunities->DispelType[i]) + ApplySpellImmune(placeholderSpellId, IMMUNITY_DISPEL, i, apply); + for (std::size_t i = 0; i < immunities->Mechanic.size(); ++i) + if (immunities->Mechanic[i]) + ApplySpellImmune(placeholderSpellId, IMMUNITY_MECHANIC, i, apply); + for (SpellEffects effect : immunities->Effect) + ApplySpellImmune(placeholderSpellId, IMMUNITY_EFFECT, effect, apply); + for (AuraType aura : immunities->Aura) + ApplySpellImmune(placeholderSpellId, IMMUNITY_STATE, aura, apply); + }; - // don't inherit immunities for hunter pets - if (GetOwnerGUID().IsPlayer() && IsHunterPet()) - { - return; - } + // unapply old template if any + if (CreatureImmunities const* oldImmunities = sSpellMgr->GetCreatureImmunities(_creatureImmunitiesId)) + applyCreatureImmunities(oldImmunities, false); - if (uint8 mask = GetCreatureTemplate()->SpellSchoolImmuneMask) + // apply requested immunities + if (CreatureImmunities const* newImmunities = sSpellMgr->GetCreatureImmunities(creatureImmunitiesId)) { - for (uint8 i = SPELL_SCHOOL_NORMAL; i <= SPELL_SCHOOL_ARCANE; ++i) - { - if (mask & (1 << i)) - { - ApplySpellImmune(placeholderSpellId, IMMUNITY_SCHOOL, 1 << i, true); - } - } + _creatureImmunitiesId = creatureImmunitiesId; + applyCreatureImmunities(newImmunities, true); } + else + _creatureImmunitiesId = 0; } bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) @@ -2179,11 +2195,13 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) // Xinef: this should exclude self casts... // Spells that don't have effectMechanics. - if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Mechanic - 1))) + if (spellInfo->Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Mechanic)) return true; - // This check must be done instead of 'if (GetCreatureTemplate()->MechanicImmuneMask & (1 << (spellInfo->Mechanic - 1)))' for not break - // the check of mechanic immunity on DB (tested) because GetCreatureTemplate()->MechanicImmuneMask and m_spellImmune[IMMUNITY_MECHANIC] don't have same data. + // The above helper uses the creature_immunities table rather than a + // simple mask on creature_template. We can't rely on the old mask field + // (which has been removed) because it no longer exists and did not always + // match the runtime immunity set stored in m_spellImmune. bool immunedToAllEffects = true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (spellInfo->Effects[i].IsEffect() && !IsImmunedToSpellEffect(spellInfo, i)) @@ -2197,16 +2215,16 @@ bool Creature::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) return Unit::IsImmunedToSpell(spellInfo, spell); } -bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const +bool Creature::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { // Xinef: this should exclude self casts... - if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(1 << (spellInfo->Effects[index].Mechanic - 1))) + if (spellInfo->Effects[index].Mechanic > MECHANIC_NONE && HasMechanicTemplateImmunity(UI64LIT(1) << spellInfo->Effects[index].Mechanic)) return true; if (GetCreatureTemplate()->type == CREATURE_TYPE_MECHANICAL && spellInfo->Effects[index].Effect == SPELL_EFFECT_HEAL) return true; - return Unit::IsImmunedToSpellEffect(spellInfo, index); + return Unit::IsImmunedToSpellEffect(spellInfo, index, caster); } SpellInfo const* Creature::reachWithSpellAttack(Unit* victim) diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 0669d6e45..2218ab8ec 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -105,12 +105,11 @@ public: bool isCanInteractWithBattleMaster(Player* player, bool msg) const; bool CanResetTalents(Player* player) const; bool CanCreatureAttack(Unit const* victim, bool skipDistCheck = false) const; - void LoadSpellTemplateImmunity(); bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr) override; - [[nodiscard]] bool HasMechanicTemplateImmunity(uint32 mask) const; + [[nodiscard]] bool HasMechanicTemplateImmunity(uint64 mask) const; // redefine Unit::IsImmunedToSpell - bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override; + bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const override; // redefine Unit::IsImmunedToSpellEffect [[nodiscard]] bool isElite() const { @@ -186,6 +185,8 @@ public: void UpdateAttackPowerAndDamage(bool ranged = false) override; void CalculateMinMaxDamage(WeaponAttackType attType, bool normalized, bool addTotalPct, float& minDamage, float& maxDamage, uint8 damageIndex) override; + void LoadTemplateImmunities(int32 creatureImmunitiesId); + void LoadSparringPct(); [[nodiscard]] float GetSparringPct() const { return _sparringPct; } @@ -486,6 +487,7 @@ protected: SpellSchoolMask m_meleeDamageSchoolMask; uint32 m_originalEntry; + int32 _creatureImmunitiesId; uint32 _gossipMenuId; bool m_moveInLineOfSightDisabled; diff --git a/src/server/game/Entities/Creature/CreatureData.h b/src/server/game/Entities/Creature/CreatureData.h index f94ae69f5..1ba2226a7 100644 --- a/src/server/game/Entities/Creature/CreatureData.h +++ b/src/server/game/Entities/Creature/CreatureData.h @@ -202,7 +202,6 @@ struct CreatureTemplate float speed_swim; float speed_flight; float detection_range; // Detection Range for Line of Sight aggro - float scale; uint32 rank; uint32 dmgschool; float DamageModifier; @@ -237,8 +236,7 @@ struct CreatureTemplate bool RacialLeader; uint32 movementId; bool RegenHealth; - uint32 MechanicImmuneMask; - uint8 SpellSchoolImmuneMask; + int32 CreatureImmunitiesId; uint32 flags_extra; uint32 ScriptID; WorldPacket queryData; // pussywizard diff --git a/src/server/game/Entities/Pet/Pet.cpp b/src/server/game/Entities/Pet/Pet.cpp index 95e4d878d..23df0fa0f 100644 --- a/src/server/game/Entities/Pet/Pet.cpp +++ b/src/server/game/Entities/Pet/Pet.cpp @@ -491,7 +491,7 @@ bool Pet::LoadPetFromDB(Player* owner, uint32 petEntry, uint32 petnumber, bool c } // must be after SetMinion (owner guid check) - //LoadTemplateImmunities(); + LoadTemplateImmunities(0); //LoadMechanicTemplateImmunity(); m_loading = false; }); diff --git a/src/server/game/Entities/Totem/Totem.cpp b/src/server/game/Entities/Totem/Totem.cpp index 9f28ac743..20253a0c3 100644 --- a/src/server/game/Entities/Totem/Totem.cpp +++ b/src/server/game/Entities/Totem/Totem.cpp @@ -179,7 +179,7 @@ void Totem::UnSummon(Milliseconds msTime) AddObjectToRemoveList(); } -bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const +bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { // xinef: immune to all positive spells, except of stoneclaw totem absorb, sentry totem bind sight and intervene // totems positive spells have unit_caster target @@ -209,5 +209,5 @@ bool Totem::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) con break; } - return Creature::IsImmunedToSpellEffect(spellInfo, index); + return Creature::IsImmunedToSpellEffect(spellInfo, index, caster); } diff --git a/src/server/game/Entities/Totem/Totem.h b/src/server/game/Entities/Totem/Totem.h index 3149e6398..08583d027 100644 --- a/src/server/game/Entities/Totem/Totem.h +++ b/src/server/game/Entities/Totem/Totem.h @@ -70,7 +70,7 @@ public: void UpdateAttackPowerAndDamage(bool /*ranged*/) override {} void UpdateDamagePhysical(WeaponAttackType /*attType*/) override {} - bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const override; + bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const override; protected: TotemType m_type; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index 3f96d8049..49a437543 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -72,6 +72,7 @@ #include "Vehicle.h" #include "World.h" #include "WorldPacket.h" +#include #include float baseMoveSpeed[MAX_MOVE_TYPE] = @@ -372,9 +373,6 @@ Unit::Unit() : WorldObject(), m_transform = 0; m_canModifyStats = false; - for (uint8 i = 0; i < MAX_SPELL_IMMUNITY; ++i) - m_spellImmune[i].clear(); - for (uint8 i = 0; i < UNIT_MOD_END; ++i) { m_auraFlatModifiersGroup[i][BASE_VALUE] = 0.0f; @@ -891,6 +889,29 @@ bool Unit::HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint return false; } +bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster /*= nullptr*/) +{ + if (!spellInfo) + return false; + + for (uint8 i = EFFECT_0; i < MAX_SPELL_EFFECTS; ++i) + { + if (!(effectMask & (1u << i))) + continue; + + if (IsImmunedToSpellEffect(spellInfo, i, caster)) + return true; + } + + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + if (IsImmunedToSchool(spellInfo)) + return true; + } + + return false; +} + bool Unit::HasBreakableByDamageAuraType(AuraType type, uint32 excludeAura) const { AuraEffectList const& auras = GetAuraEffectsByType(type); @@ -2075,7 +2096,7 @@ void Unit::DealDamageShieldDamage(Unit* victim) } // ...or immuned - if (IsImmunedToDamageOrSchool(i_spellProto)) + if (IsImmunedToDamage(victim, i_spellProto)) { victim->SendSpellDamageImmune(this, i_spellProto->Id); continue; @@ -3203,7 +3224,7 @@ void Unit::SendMeleeAttackStop(Unit* victim) bool Unit::isSpellBlocked(Unit* victim, SpellInfo const* spellProto, WeaponAttackType attackType) { // These spells can't be blocked - if (spellProto && spellProto->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE)) + if (spellProto && (spellProto->HasAttribute(SPELL_ATTR0_NO_ACTIVE_DEFENSE) || spellProto->HasAttribute(SPELL_ATTR3_ALWAYS_HIT))) return false; if (victim->HasIgnoreHitDirectionAura() || victim->HasInArc(M_PI, this)) @@ -3571,6 +3592,9 @@ SpellMissInfo Unit::MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo // Resist SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool CanReflect) { + if (spell->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) + return SPELL_MISS_NONE; + // Check for immune if (victim->IsImmunedToSpell(spell)) return SPELL_MISS_IMMUNE; @@ -3581,11 +3605,6 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, SpellInfo const* spell, bool Ca && (!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell return SPELL_MISS_NONE; - // Check for immune - // xinef: check for school immunity only - if (victim->IsImmunedToSchool(spell)) - return SPELL_MISS_IMMUNE; - if (this == victim) return SPELL_MISS_NONE; @@ -3650,13 +3669,17 @@ SpellMissInfo Unit::SpellHitResult(Unit* victim, Spell const* spell, bool CanRef return SPELL_MISS_NONE; } - // Check for immune - // xinef: check for school immunity only - if (victim->IsImmunedToSchool(spell)) + // Check for immune to spell effects (includes school, mechanics, state, dispel immunities) + if (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 + // returns SPELL_MISS_IMMUNE in that case, for other spells, the SMSG_SPELL_GO must show hit + if (spellInfo->HasOnlyDamageEffects() && victim->IsImmunedToDamage(this, spellInfo)) + return SPELL_MISS_IMMUNE; + if (this == victim) { return SPELL_MISS_NONE; @@ -5447,7 +5470,7 @@ void Unit::RemoveMovementImpairingAuras(bool withRoot) } } -void Unit::RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemode, uint32 except) +void Unit::RemoveAurasWithMechanic(uint64 mechanic_mask, AuraRemoveMode removemode, uint32 except) { for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { @@ -5466,7 +5489,7 @@ void Unit::RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemo void Unit::RemoveAurasByShapeShift() { - uint32 mechanic_mask = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT); + uint64 mechanic_mask = (UI64LIT(1) << MECHANIC_SNARE) | (UI64LIT(1) << MECHANIC_ROOT); for (AuraApplicationMap::iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end();) { Aura const* aura = iter->second->GetBase(); @@ -6015,17 +6038,17 @@ bool Unit::HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid) return false; } -bool Unit::HasAuraWithMechanic(uint32 mechanicMask) const +bool Unit::HasAuraWithMechanic(uint64 mechanicMask) const { for (AuraApplicationMap::const_iterator iter = m_appliedAuras.begin(); iter != m_appliedAuras.end(); ++iter) { SpellInfo const* spellInfo = iter->second->GetBase()->GetSpellInfo(); - if (spellInfo->Mechanic && (mechanicMask & (1 << spellInfo->Mechanic))) + if (spellInfo->Mechanic && (mechanicMask & (UI64LIT(1) << spellInfo->Mechanic))) return true; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (iter->second->HasEffect(i) && spellInfo->Effects[i].Effect && spellInfo->Effects[i].Mechanic) - if (mechanicMask & (1 << spellInfo->Effects[i].Mechanic)) + if (mechanicMask & (UI64LIT(1) << spellInfo->Effects[i].Mechanic)) return true; } @@ -8833,7 +8856,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui }); } - if (uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask()) + if (uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask()) { int32 modifierMax = 0; int32 modifierMin = 0; @@ -8848,7 +8871,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellInfo const* spellProto, ui if (!caster || caster->GetGUID() != (*i)->GetCasterGUID()) continue; - if (mechanicMask & uint32(1 << (*i)->GetMiscValue())) + if (mechanicMask & uint64(UI64LIT(1) << (*i)->GetMiscValue())) { if ((*i)->GetAmount() > 0) { @@ -9739,107 +9762,90 @@ int32 Unit::SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) return AdvertisedBenefit; } -bool Unit::IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const +uint32 Unit::GetDamageImmunityMask() const { - if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) - { - return false; - } - - // If m_immuneToDamage type contain magic, IMMUNE damage. - SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; - for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) - if ((itr->type & meleeSchoolMask) == meleeSchoolMask) - return true; - - return false; + uint32 mask = 0; + SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; + for (auto const& [immunitySchoolMask, _] : damageList) + mask |= immunitySchoolMask; + return mask; } -bool Unit::IsImmunedToDamage(SpellInfo const* spellInfo) const +bool Unit::IsImmunedToDamage(SpellSchoolMask schoolMask) const { - if (!spellInfo) - { - return false; - } - - if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) - { - return false; - } - - if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - return false; - } - - uint32 schoolMask = spellInfo->GetSchoolMask(); if (schoolMask == SPELL_SCHOOL_MASK_NONE) - { return false; - } - // If m_immuneToDamage type contain magic, IMMUNE damage. - SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; - for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) - if ((itr->type & schoolMask) == schoolMask) + // If m_immuneToSchool type contains all requested schools, IMMUNE damage. + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto const& itr : schoolList) + if (IsImmuneMaskFully(SpellSchoolMask(itr.first), schoolMask)) return true; - return false; + // If m_immuneToDamage type contains all requested schools, IMMUNE damage. + SpellImmuneContainer const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; + return std::ranges::any_of(damageList, [schoolMask](auto const& immune) + { + return Unit::IsImmuneMaskFully(SpellSchoolMask(immune.first), schoolMask); + }); } -bool Unit::IsImmunedToDamage(Spell const* spell) const +bool Unit::IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const { - SpellInfo const* spellInfo = spell->GetSpellInfo(); if (!spellInfo) - { return false; - } - if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) - { + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) return false; - } - if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - return false; - } - - uint32 schoolMask = spell->GetSpellSchoolMask(); + SpellSchoolMask schoolMask = SpellSchoolMask(spellInfo->GetSchoolMask()); if (schoolMask == SPELL_SCHOOL_MASK_NONE) - { return false; - } - // If m_immuneToDamage type contain magic, IMMUNE damage. - SpellImmuneList const& damageList = m_spellImmune[IMMUNITY_DAMAGE]; - for (SpellImmuneList::const_iterator itr = damageList.begin(); itr != damageList.end(); ++itr) + auto hasImmunity = [&](SpellImmuneContainer const& container) { - if ((itr->type & schoolMask) == schoolMask) + uint32 schoolImmunityMask = 0; + for (auto const& [immunitySchoolMask, immunityAuraId] : container) { - return true; + SpellInfo const* immuneAuraInfo = sSpellMgr->GetSpellInfo(immunityAuraId); + if (immuneAuraInfo && !immuneAuraInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && caster && caster->IsFriendlyTo(this)) + continue; + + if (immuneAuraInfo && spellInfo->CanPierceImmuneAura(immuneAuraInfo)) + continue; + + schoolImmunityMask |= immunitySchoolMask; } - } - return false; -} - -bool Unit::IsImmunedToSchool(SpellSchoolMask meleeSchoolMask) const -{ - if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) - { - return false; - } + // We need to be immune to all types + return (schoolImmunityMask & schoolMask) == schoolMask; + }; // If m_immuneToSchool type contain this school type, IMMUNE damage. - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) - if ((itr->type & meleeSchoolMask) == meleeSchoolMask) - return true; + if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL])) + return true; + + // If m_immuneToDamage type contain magic, IMMUNE damage. + if (hasImmunity(m_spellImmune[IMMUNITY_DAMAGE])) + return true; return false; } +bool Unit::IsImmunedToSchool(SpellSchoolMask schoolMask) const +{ + if (schoolMask == SPELL_SCHOOL_MASK_NONE) + return false; + + // Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered by at least one immunity + // (e.g., Anti-Magic Shell provides school immunity to all magic schools) + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + return std::ranges::any_of(schoolList, [schoolMask](auto const& immune) + { + return (immune.first & schoolMask) == schoolMask; + }); +} + bool Unit::IsImmunedToSchool(SpellInfo const* spellInfo) const { if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) @@ -9853,10 +9859,10 @@ bool Unit::IsImmunedToSchool(SpellInfo const* spellInfo) const if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) { - // If m_immuneToSchool type contain this school type, IMMUNE damage. - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) - if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId))) + // Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered and spell can't pierce + 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))) return true; } @@ -9879,11 +9885,11 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) { - // If m_immuneToSchool type contain this school type, IMMUNE damage. - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) + // Check IMMUNITY_SCHOOL: returns true if ALL schools in the mask are covered and spell can't pierce + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) { - if ((itr->type & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->spellId))) + if ((itr->first & schoolMask) == schoolMask && !spellInfo->CanPierceImmuneAura(sSpellMgr->GetSpellInfo(itr->second))) { return true; } @@ -9893,19 +9899,49 @@ bool Unit::IsImmunedToSchool(Spell const* spell) const return false; } -bool Unit::IsImmunedToDamageOrSchool(SpellSchoolMask meleeSchoolMask) const +bool Unit::IsImmunedToDamageOrSchool(SpellSchoolMask schoolMask) const { - if (meleeSchoolMask == SPELL_SCHOOL_MASK_NONE) + if (schoolMask == SPELL_SCHOOL_MASK_NONE) { return false; } - return IsImmunedToDamage(meleeSchoolMask) || IsImmunedToSchool(meleeSchoolMask); + return IsImmunedToDamage(schoolMask) || IsImmunedToSchool(schoolMask); } -bool Unit::IsImmunedToDamageOrSchool(SpellInfo const* spellInfo) const +bool Unit::IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spellInfo) const { - return IsImmunedToDamage(spellInfo) || IsImmunedToSchool(spellInfo); + if (!spellInfo) + return false; + + if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) || spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + return false; + + SpellSchoolMask schoolMask = SpellSchoolMask(spellInfo->GetSchoolMask()); + if (schoolMask == SPELL_SCHOOL_MASK_NONE) + return false; + + auto hasImmunity = [&](SpellImmuneContainer const& container) + { + uint32 schoolImmunityMask = 0; + 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)) + continue; + + schoolImmunityMask |= immunitySchoolMask; + } + + // We need to be immune to all types + return (schoolImmunityMask & schoolMask) == schoolMask; + }; + + // If m_immuneToSchool type contain this school type, IMMUNE periodic tick. + if (hasImmunity(m_spellImmune[IMMUNITY_SCHOOL])) + return true; + + return false; } bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) @@ -9914,39 +9950,35 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) return false; // Single spell immunity. - SpellImmuneList const& idList = m_spellImmune[IMMUNITY_ID]; - for (SpellImmuneList::const_iterator itr = idList.begin(); itr != idList.end(); ++itr) - if (itr->type == spellInfo->Id) - return true; + SpellImmuneContainer const& idList = m_spellImmune[IMMUNITY_ID]; + if (idList.count(spellInfo->Id) > 0) + return true; // xinef: my special immunity, if spellid is not on this list it means npc is immune - SpellImmuneList const& allowIdList = m_spellImmune[IMMUNITY_ALLOW_ID]; + SpellImmuneContainer const& allowIdList = m_spellImmune[IMMUNITY_ALLOW_ID]; if (!allowIdList.empty()) { - for (SpellImmuneList::const_iterator itr = allowIdList.begin(); itr != allowIdList.end(); ++itr) - if (itr->type == spellInfo->Id) - return false; - return true; + if (allowIdList.count(spellInfo->Id) == 0) + return true; + return false; } if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) return false; - if (spellInfo->Dispel) + if (uint32 dispel = spellInfo->Dispel) { - SpellImmuneList const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; - for (SpellImmuneList::const_iterator itr = dispelList.begin(); itr != dispelList.end(); ++itr) - if (itr->type == spellInfo->Dispel) - return true; + SpellImmuneContainer const& dispelList = m_spellImmune[IMMUNITY_DISPEL]; + if (dispelList.count(dispel) > 0) + return true; } // Spells that don't have effectMechanics. - if (spellInfo->Mechanic) + if (uint32 mechanic = spellInfo->Mechanic) { - SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; - for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) - if (itr->type == spellInfo->Mechanic) - return true; + SpellImmuneContainer const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (mechanicList.count(mechanic) > 0) + return true; } bool immuneToAllEffects = true; @@ -9971,7 +10003,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. return true; - if (spellInfo->Id != 42292 && spellInfo->Id != 59752 && spellInfo->Id != 19574 && spellInfo->Id != 34471) + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) { SpellSchoolMask spellSchoolMask = spellInfo->GetSchoolMask(); if (spell) @@ -9981,16 +10013,24 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) if (spellSchoolMask != SPELL_SCHOOL_MASK_NONE) { - SpellImmuneList const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; - for (SpellImmuneList::const_iterator itr = schoolList.begin(); itr != schoolList.end(); ++itr) + SpellImmuneContainer const& schoolList = m_spellImmune[IMMUNITY_SCHOOL]; + for (auto itr = schoolList.begin(); itr != schoolList.end(); ++itr) { - SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->spellId); - if (((itr->type & spellSchoolMask) == spellSchoolMask) - && (!immuneSpellInfo || immuneSpellInfo->IsPositive()) && !spellInfo->IsPositive() - && !spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + SpellInfo const* immuneSpellInfo = sSpellMgr->GetSpellInfo(itr->second); + if (!(itr->first & spellSchoolMask)) + continue; + + if (immuneSpellInfo && !immuneSpellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) { - return true; + Unit const* spellCaster = spell ? spell->GetCaster() : nullptr; + if (spellCaster && spellCaster->IsFriendlyTo(this)) + continue; } + + if (spellInfo->CanPierceImmuneAura(immuneSpellInfo)) + continue; + + return true; } } } @@ -9998,7 +10038,7 @@ bool Unit::IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell) return false; } -bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const +bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster /*= nullptr*/) const { if (!spellInfo || !spellInfo->Effects[index].IsEffect()) return false; @@ -10010,12 +10050,16 @@ bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) cons if (spellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES) && !HasSpiritOfRedemptionAura()) return false; + // Special-case handling for Vezax's Aura of Despair. + static constexpr uint32 SPELL_AURA_OF_DESPAIR_1 = 62692; + static constexpr uint32 SPELL_AURA_OF_DESPAIR_2 = 64848; + //If m_immuneToEffect type contain this effect type, IMMUNE effect. uint32 effect = spellInfo->Effects[index].Effect; - SpellImmuneList const& effectList = m_spellImmune[IMMUNITY_EFFECT]; - for (SpellImmuneList::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr) + SpellImmuneContainer const& effectList = m_spellImmune[IMMUNITY_EFFECT]; + for (SpellImmuneContainer::const_iterator itr = effectList.begin(); itr != effectList.end(); ++itr) { - if (itr->type == effect && (itr->spellId != 62692 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) + if (itr->first == effect && (itr->second != SPELL_AURA_OF_DESPAIR_1 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) { return true; } @@ -10023,41 +10067,32 @@ bool Unit::IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) cons if (uint32 mechanic = spellInfo->Effects[index].Mechanic) { - SpellImmuneList const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; - for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) - if (itr->type == mechanic) - return true; + auto const& mechanicList = m_spellImmune[IMMUNITY_MECHANIC]; + if (mechanicList.count(mechanic) > 0) + return true; } - if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) + if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) { - SpellImmuneList const& list = m_spellImmune[IMMUNITY_STATE]; - for (SpellImmuneList::const_iterator itr = list.begin(); itr != list.end(); ++itr) + if (uint32 aura = spellInfo->Effects[index].ApplyAuraName) { - if (itr->type == aura && (itr->spellId != 64848 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) + SpellImmuneContainer const& list = m_spellImmune[IMMUNITY_STATE]; + for (SpellImmuneContainer::const_iterator itr = list.begin(); itr != list.end(); ++itr) { - if (!spellInfo->HasAttribute(SPELL_ATTR3_ALWAYS_HIT)) - { - if (itr->blockType == SPELL_BLOCK_TYPE_ALL || spellInfo->IsPositive()) // xinef: added for pet scaling - { - return true; - } - } - } - } - - if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - // Check for immune to application of harmful magical effects - AuraEffectList const& immuneAuraApply = GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL); - for (AuraEffectList::const_iterator iter = immuneAuraApply.begin(); iter != immuneAuraApply.end(); ++iter) - { - if (/*(spellInfo->Dispel == DISPEL_MAGIC || spellInfo->Dispel == DISPEL_CURSE || spellInfo->Dispel == DISPEL_DISEASE) &&*/ // Magic debuff, xinef: all kinds? - ((*iter)->GetMiscValue() & spellInfo->GetSchoolMask()) && // Check school - !spellInfo->IsPositiveEffect(index) && // Harmful - spellInfo->Effects[index].Effect != SPELL_EFFECT_PERSISTENT_AREA_AURA) // Not Persistent area auras - { + if (itr->first == aura && (itr->second != SPELL_AURA_OF_DESPAIR_2 || (spellInfo->Effects[index].MiscValue == POWER_MANA && !CanRestoreMana(spellInfo)))) return true; + } + + if (!spellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + // Check for immune to application of harmful magical effects + for (AuraEffect* immuneAuraApply : GetAuraEffectsByType(SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL)) + { + if (!(immuneAuraApply->GetMiscValue() & spellInfo->GetSchoolMask())) // Check school + continue; + + if (spellInfo->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || (caster && !IsFriendlyTo(caster))) // Harmful + return true; } } } @@ -10286,17 +10321,17 @@ uint32 Unit::MeleeDamageBonusTaken(Unit* attacker, uint32 pdamage, WeaponAttackT }); // Mod damage from spell mechanic - uint32 mechanicMask = spellProto->GetAllEffectsMechanicMask(); + uint64 mechanicMask = spellProto->GetAllEffectsMechanicMask(); // Shred, Maul - "Effects which increase Bleed damage also increase Shred damage" if (spellProto->SpellFamilyName == SPELLFAMILY_DRUID && spellProto->SpellFamilyFlags[0] & 0x00008800) - mechanicMask |= (1 << MECHANIC_BLEED); + mechanicMask |= (UI64LIT(1) << MECHANIC_BLEED); if (mechanicMask) { TakenTotalMod *= GetTotalAuraMultiplier(SPELL_AURA_MOD_MECHANIC_DAMAGE_TAKEN_PERCENT, [mechanicMask](AuraEffect const* aurEff) -> bool { - if (mechanicMask & uint32(1 << (aurEff->GetMiscValue()))) + if (mechanicMask & uint64(UI64LIT(1) << (aurEff->GetMiscValue()))) return true; return false; }); @@ -10364,51 +10399,17 @@ private: uint32 _type; }; -void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType) +void Unit::ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType /*blockType*/) { if (apply) - { - // xinef: immunities with spellId 0 are intended to be applied only once (script purposes mosty) - if (spellId == 0 && std::find_if(m_spellImmune[op].begin(), m_spellImmune[op].end(), spellIdImmunityPredicate(type)) != m_spellImmune[op].end()) - return; - - SpellImmune immune; - immune.spellId = spellId; - immune.type = type; - immune.blockType = blockType; - m_spellImmune[op].push_back(std::move(immune)); - } + m_spellImmune[op].emplace(type, spellId); else { - for (SpellImmuneList::iterator itr = m_spellImmune[op].begin(); itr != m_spellImmune[op].end(); ++itr) + auto bounds = m_spellImmune[op].equal_range(type); + for (auto itr = bounds.first; itr != bounds.second;) { - if (itr->spellId == spellId && itr->type == type) - { - m_spellImmune[op].erase(itr); - break; - } - } - } -} - -void Unit::ApplySpellDispelImmunity(SpellInfo const* spellProto, DispelType type, bool apply) -{ - ApplySpellImmune(spellProto->Id, IMMUNITY_DISPEL, type, apply); - - if (apply && spellProto->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) - { - // Create dispel mask by dispel type - uint32 dispelMask = SpellInfo::GetDispelMask(type); - // Dispel all existing auras vs current dispel type - AuraApplicationMap& auras = GetAppliedAuras(); - for (AuraApplicationMap::iterator itr = auras.begin(); itr != auras.end();) - { - SpellInfo const* spell = itr->second->GetBase()->GetSpellInfo(); - if (spell->GetDispelMask() & dispelMask) - { - // Dispel aura - RemoveAura(itr); - } + if (itr->second == spellId) + itr = m_spellImmune[op].erase(itr); else ++itr; } @@ -11174,8 +11175,9 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) { if (Creature* creature = ToCreature()) { - uint32 immuneMask = creature->GetCreatureTemplate()->MechanicImmuneMask; - if (immuneMask & (1 << (MECHANIC_SNARE - 1)) || immuneMask & (1 << (MECHANIC_DAZE - 1))) + // use creature helper which now consults creature_immunities table + if (creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE) || + creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_DAZE)) break; } @@ -11196,7 +11198,7 @@ void Unit::UpdateSpeed(UnitMoveType mtype, bool forced) if (creature && !IsPet() && !(IsControlledByPlayer() && IsVehicle()) - && !(creature->HasMechanicTemplateImmunity(MECHANIC_SNARE)) + && !(creature->HasMechanicTemplateImmunity(UI64LIT(1) << MECHANIC_SNARE)) && !(creature->IsDungeonBoss())) { // 1.6% for each % under 30. @@ -15075,7 +15077,7 @@ Aura* Unit::AddAura(SpellInfo const* spellInfo, uint8 effMask, Unit* target) { if (!(effMask & (1 << i))) continue; - if (target->IsImmunedToSpellEffect(spellInfo, i)) + if (target->IsImmunedToSpellEffect(spellInfo, i, this)) effMask &= ~(1 << i); } diff --git a/src/server/game/Entities/Unit/Unit.h b/src/server/game/Entities/Unit/Unit.h index 286548115..b784c702e 100644 --- a/src/server/game/Entities/Unit/Unit.h +++ b/src/server/game/Entities/Unit/Unit.h @@ -1408,7 +1408,7 @@ public: void RemoveAurasWithInterruptFlags(uint32 flag, uint32 except = 0, bool isAutoshot = false); void RemoveAurasWithAttribute(uint32 flags); void RemoveAurasWithFamily(SpellFamilyNames family, uint32 familyFlag1, uint32 familyFlag2, uint32 familyFlag3, ObjectGuid casterGUID); - void RemoveAurasWithMechanic(uint32 mechanic_mask, AuraRemoveMode removemode = AURA_REMOVE_BY_DEFAULT, uint32 except = 0); + void RemoveAurasWithMechanic(uint64 mechanic_mask, AuraRemoveMode removemode = AURA_REMOVE_BY_DEFAULT, uint32 except = 0); void RemoveMovementImpairingAuras(bool withRoot); void RemoveAurasByShapeShift(); @@ -1500,7 +1500,7 @@ public: bool HasNegativeAuraWithInterruptFlag(uint32 flag, ObjectGuid guid = ObjectGuid::Empty); [[nodiscard]] bool HasVisibleAuraType(AuraType auraType) const; bool HasNegativeAuraWithAttribute(uint32 flag, ObjectGuid guid = ObjectGuid::Empty); - [[nodiscard]] bool HasAuraWithMechanic(uint32 mechanicMask) const; + [[nodiscard]] bool HasAuraWithMechanic(uint64 mechanicMask) const; [[nodiscard]] bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint32 familyName, uint32 familyFlags) const; @@ -1636,17 +1636,22 @@ public: // Spells immunities void ApplySpellImmune(uint32 spellId, uint32 op, uint32 type, bool apply, SpellImmuneBlockType blockType = SPELL_BLOCK_TYPE_ALL); - void ApplySpellDispelImmunity(SpellInfo const* spellProto, DispelType type, bool apply); virtual bool IsImmunedToSpell(SpellInfo const* spellInfo, Spell const* spell = nullptr); - [[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask meleeSchoolMask) const; - [[nodiscard]] bool IsImmunedToDamage(SpellInfo const* spellInfo) const; - [[nodiscard]] bool IsImmunedToDamage(Spell const* spell) const; - [[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask meleeSchoolMask) const; + bool IsImmunedToSpell(SpellInfo const* spellInfo, uint32 effectMask, Unit const* caster = nullptr); + [[nodiscard]] bool IsImmunedToDamage(SpellSchoolMask schoolMask) const; + [[nodiscard]] bool IsImmunedToDamage(Unit const* caster, SpellInfo const* spellInfo) const; + [[nodiscard]] bool IsImmunedToSchool(SpellSchoolMask schoolMask) const; + + static bool IsImmuneMaskFully(SpellSchoolMask immuneMask, SpellSchoolMask schoolMask) { return (immuneMask & schoolMask) == schoolMask; } + + [[nodiscard]] uint32 GetSchoolImmunityMask() const; + [[nodiscard]] uint32 GetDamageImmunityMask() const; + [[nodiscard]] bool IsImmunedToSchool(SpellInfo const* spellInfo) const; [[nodiscard]] bool IsImmunedToSchool(Spell const* spell) const; - [[nodiscard]] bool IsImmunedToDamageOrSchool(SpellSchoolMask meleeSchoolMask) const; - bool IsImmunedToDamageOrSchool(SpellInfo const* spellInfo) const; - virtual bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index) const; + [[nodiscard]] bool IsImmunedToDamageOrSchool(SpellSchoolMask schoolMask) const; + [[nodiscard]] bool IsImmunedToAuraPeriodicTick(Unit const* caster, SpellInfo const* spellInfo) const; + virtual bool IsImmunedToSpellEffect(SpellInfo const* spellInfo, uint32 index, Unit const* caster = nullptr) const; // Critic chances bool isBlockCritical(); @@ -2080,7 +2085,8 @@ public: float m_modAttackSpeedPct[3]; - SpellImmuneList m_spellImmune[MAX_SPELL_IMMUNITY]; + typedef std::unordered_multimap SpellImmuneContainer; + SpellImmuneContainer m_spellImmune[MAX_SPELL_IMMUNITY]; uint32 m_lastSanctuaryTime; // pet auras diff --git a/src/server/game/Globals/ObjectMgr.cpp b/src/server/game/Globals/ObjectMgr.cpp index c3d639022..5d9ca3dc8 100644 --- a/src/server/game/Globals/ObjectMgr.cpp +++ b/src/server/game/Globals/ObjectMgr.cpp @@ -522,18 +522,18 @@ void ObjectMgr::LoadCreatureTemplates() // 0 1 2 3 4 5 6 7 8 QueryResult result = WorldDatabase.Query("SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, " -// 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - "gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, " -// 23 24 25 26 27 28 29 30 31 32 +// 9 10 11 12 13 14 15 16 17 18 19 20 21 + "gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, `rank`, dmgschool, " +// 22 23 24 25 26 27 28 29 30 31 "DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, " -// 33 34 35 36 37 +// 32 33 34 35 36 "type, type_flags, lootid, pickpocketloot, skinloot, " -// 38 39 40 41 42 43 44 45 46 47 +// 37 38 39 40 41 42 43 44 45 46 "PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, " -// 48 49 50 51 52 53 54 55 56 57 58 59 - "ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, " -// 60 61 62 - "spell_school_immune_mask, flags_extra, ScriptName " +// 47 48 49 50 51 52 53 54 55 56 57 + "ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, " +// 58 59 60 + "CreatureImmunitiesId, flags_extra, ScriptName " "FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId ORDER BY entry DESC;"); if (!result) @@ -619,24 +619,23 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook) creatureTemplate.speed_swim = fields[17].Get(); creatureTemplate.speed_flight = fields[18].Get(); creatureTemplate.detection_range = fields[19].Get(); - creatureTemplate.scale = fields[20].Get(); - creatureTemplate.rank = uint32(fields[21].Get()); - creatureTemplate.dmgschool = uint32(fields[22].Get()); - creatureTemplate.DamageModifier = fields[23].Get(); - creatureTemplate.BaseAttackTime = fields[24].Get(); - creatureTemplate.RangeAttackTime = fields[25].Get(); - creatureTemplate.BaseVariance = fields[26].Get(); - creatureTemplate.RangeVariance = fields[27].Get(); - creatureTemplate.unit_class = uint32(fields[28].Get()); - creatureTemplate.unit_flags = fields[29].Get(); - creatureTemplate.unit_flags2 = fields[30].Get(); - creatureTemplate.dynamicflags = fields[31].Get(); - creatureTemplate.family = uint32(fields[32].Get()); - creatureTemplate.type = uint32(fields[33].Get()); - creatureTemplate.type_flags = fields[34].Get(); - creatureTemplate.lootid = fields[35].Get(); - creatureTemplate.pickpocketLootId = fields[36].Get(); - creatureTemplate.SkinLootId = fields[37].Get(); + creatureTemplate.rank = uint32(fields[20].Get()); + creatureTemplate.dmgschool = uint32(fields[21].Get()); + creatureTemplate.DamageModifier = fields[22].Get(); + creatureTemplate.BaseAttackTime = fields[23].Get(); + creatureTemplate.RangeAttackTime = fields[24].Get(); + creatureTemplate.BaseVariance = fields[25].Get(); + creatureTemplate.RangeVariance = fields[26].Get(); + creatureTemplate.unit_class = uint32(fields[27].Get()); + creatureTemplate.unit_flags = fields[28].Get(); + creatureTemplate.unit_flags2 = fields[29].Get(); + creatureTemplate.dynamicflags = fields[30].Get(); + creatureTemplate.family = uint32(fields[31].Get()); + creatureTemplate.type = uint32(fields[32].Get()); + creatureTemplate.type_flags = fields[33].Get(); + creatureTemplate.lootid = fields[34].Get(); + creatureTemplate.pickpocketLootId = fields[35].Get(); + creatureTemplate.SkinLootId = fields[36].Get(); for (uint8 i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) { @@ -648,49 +647,56 @@ void ObjectMgr::LoadCreatureTemplate(Field* fields, bool triggerHook) creatureTemplate.spells[i] = 0; } - creatureTemplate.PetSpellDataId = fields[38].Get(); - creatureTemplate.VehicleId = fields[39].Get(); - creatureTemplate.mingold = fields[40].Get(); - creatureTemplate.maxgold = fields[41].Get(); - creatureTemplate.AIName = fields[42].Get(); - creatureTemplate.MovementType = uint32(fields[43].Get()); - if (!fields[44].IsNull()) + creatureTemplate.PetSpellDataId = fields[37].Get(); + creatureTemplate.VehicleId = fields[38].Get(); + creatureTemplate.mingold = fields[39].Get(); + creatureTemplate.maxgold = fields[40].Get(); + creatureTemplate.AIName = fields[41].Get(); + creatureTemplate.MovementType = uint32(fields[42].Get()); + if (!fields[43].IsNull()) { - creatureTemplate.Movement.Ground = static_cast(fields[44].Get()); + creatureTemplate.Movement.Ground = static_cast(fields[43].Get()); } - creatureTemplate.Movement.Swim = fields[45].Get(); - if (!fields[46].IsNull()) + creatureTemplate.Movement.Swim = fields[44].Get(); + if (!fields[45].IsNull()) { - creatureTemplate.Movement.Flight = static_cast(fields[46].Get()); + creatureTemplate.Movement.Flight = static_cast(fields[45].Get()); } - creatureTemplate.Movement.Rooted = fields[47].Get(); + creatureTemplate.Movement.Rooted = fields[46].Get(); + if (!fields[47].IsNull()) + { + creatureTemplate.Movement.Chase = static_cast(fields[47].Get()); + } if (!fields[48].IsNull()) { - creatureTemplate.Movement.Chase = static_cast(fields[48].Get()); + creatureTemplate.Movement.Random = static_cast(fields[48].Get()); } if (!fields[49].IsNull()) { - creatureTemplate.Movement.Random = static_cast(fields[49].Get()); - } - if (!fields[50].IsNull()) - { - creatureTemplate.Movement.InteractionPauseTimer = fields[50].Get(); + creatureTemplate.Movement.InteractionPauseTimer = fields[49].Get(); } - creatureTemplate.HoverHeight = fields[51].Get(); - creatureTemplate.ModHealth = fields[52].Get(); - creatureTemplate.ModMana = fields[53].Get(); - creatureTemplate.ModArmor = fields[54].Get(); - creatureTemplate.ModExperience = fields[55].Get(); - creatureTemplate.RacialLeader = fields[56].Get(); - creatureTemplate.movementId = fields[57].Get(); - creatureTemplate.RegenHealth = fields[58].Get(); - creatureTemplate.MechanicImmuneMask = fields[59].Get(); - creatureTemplate.SpellSchoolImmuneMask = fields[60].Get(); - creatureTemplate.flags_extra = fields[61].Get(); - creatureTemplate.ScriptID = GetScriptId(fields[62].Get()); + creatureTemplate.HoverHeight = fields[50].Get(); + creatureTemplate.ModHealth = fields[51].Get(); + creatureTemplate.ModMana = fields[52].Get(); + creatureTemplate.ModArmor = fields[53].Get(); + creatureTemplate.ModExperience = fields[54].Get(); + creatureTemplate.RacialLeader = fields[55].Get(); + creatureTemplate.movementId = fields[56].Get(); + creatureTemplate.RegenHealth = fields[57].Get(); + creatureTemplate.CreatureImmunitiesId = fields[58].Get(); + creatureTemplate.flags_extra = fields[59].Get(); + creatureTemplate.ScriptID = GetScriptId(fields[60].Get()); + + // Warn about deprecated immunity flags that should be moved to `creature_immunities` table + if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_NO_TAUNT) + LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit NO_TAUNT (0x100) set. This will be migrated to the `creature_immunities` table in a future update.", entry); + if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_AVOID_AOE) + LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit AVOID_AOE (0x400000) set. This will be migrated to the `creature_immunities` table in a future update.", entry); + if (creatureTemplate.flags_extra & CREATURE_FLAG_EXTRA_IMMUNITY_KNOCKBACK) + LOG_WARN("server.loading", "Creature (Entry: {}) has deprecated flags_extra bit IMMUNITY_KNOCKBACK (0x40000000) set. This will be migrated to the `creature_immunities` table in a future update.", entry); // useful if the creature template load is being triggered from outside this class if (triggerHook) diff --git a/src/server/game/Spells/Auras/SpellAuraEffects.cpp b/src/server/game/Spells/Auras/SpellAuraEffects.cpp index e7c030cc3..5506a9461 100644 --- a/src/server/game/Spells/Auras/SpellAuraEffects.cpp +++ b/src/server/game/Spells/Auras/SpellAuraEffects.cpp @@ -1223,7 +1223,7 @@ bool AuraEffect::CheckEffectProc(AuraApplication* aurApp, ProcEventInfo& eventIn case SPELL_AURA_MECHANIC_IMMUNITY: case SPELL_AURA_MOD_MECHANIC_RESISTANCE: // compare mechanic - if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (1 << GetMiscValue()))) + if (!spellInfo || !(spellInfo->GetAllEffectsMechanicMask() & (UI64LIT(1) << GetMiscValue()))) return false; break; case SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK: @@ -3933,312 +3933,7 @@ void AuraEffect::HandleModStateImmunityMask(AuraApplication const* aurApp, uint8 return; Unit* target = aurApp->GetTarget(); - std::list aura_immunity_list; - uint32 mechanic_immunity_list = 0; - int32 miscVal = GetMiscValue(); - - switch (miscVal) - { - case 96: - case 1615: - { - if (!GetAmount()) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_CHARM); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 679: - { - if (GetId() == 57742) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 1557: - { - if (GetId() == 64187) - { - mechanic_immunity_list = (1 << MECHANIC_STUN); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - } - else - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 1614: - case 1694: - { - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT); - break; - } - case 1630: - { - if (!GetAmount()) - { - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_ATTACK_ME, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT); - } - else - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 477: - case 1733: - case 1632: - { - if (!GetAmount()) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT) - | (1 << MECHANIC_FEAR) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_SLEEP) | (1 << MECHANIC_CHARM) - | (1 << MECHANIC_SAPPED) | (1 << MECHANIC_HORROR) - | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED) - | (1 << MECHANIC_FREEZE) | (1 << MECHANIC_TURN) | (1 << MECHANIC_BANISH); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_BANISH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK_DEST, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - break; - } - case 878: - { - if (GetAmount() == 1) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE) | (1 << MECHANIC_STUN) - | (1 << MECHANIC_DISORIENTED) | (1 << MECHANIC_FREEZE); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - } - break; - } - default: - break; - } - - if (aura_immunity_list.empty()) - { - // Roots, OK - if (GetMiscValue() & (1 << 0)) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_ROOT); - } - // Taunt, OK - if (GetMiscValue() & (1 << 1)) - { - aura_immunity_list.push_back(SPELL_AURA_MOD_TAUNT); - } - // Crowd-Control auras? - if (GetMiscValue() & (1 << 2)) - { - mechanic_immunity_list = (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_DISORIENTED); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_CONFUSE); - } - // Interrupt, OK - if (GetMiscValue() & (1 << 3)) - { - mechanic_immunity_list = (1 << MECHANIC_INTERRUPT); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_INTERRUPT, apply); - } - // Transform? - if (GetMiscValue() & (1 << 4)) - { - aura_immunity_list.push_back(SPELL_AURA_TRANSFORM); - } - // Stun auras breakable by damage (Incapacitate effects), OK - if (GetMiscValue() & (1 << 5)) - { - mechanic_immunity_list = (1 << MECHANIC_KNOCKOUT); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_KNOCKOUT, apply); - } - // // Slowing effects - if (GetMiscValue() & (1 << 6)) - { - mechanic_immunity_list = (1 << MECHANIC_SNARE); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_DECREASE_SPEED); - } - // Charm auras?, 90% - if ((GetMiscValue() & (1 << 7))) - { - mechanic_immunity_list = (1 << MECHANIC_CHARM); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_CHARM); - aura_immunity_list.push_back(SPELL_AURA_MOD_POSSESS); - } - // UNK - // if ((GetMiscValue() & (1 << 8))) - // { - // } - // Fear, OK - if (GetMiscValue() & (1 << 9)) - { - mechanic_immunity_list = (1 << MECHANIC_FEAR); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_FEAR); - } - // Stuns, OK - if (GetMiscValue() & (1 << 10)) - { - mechanic_immunity_list = (1 << MECHANIC_STUN); - - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - aura_immunity_list.push_back(SPELL_AURA_MOD_STUN); - } - } - - // apply immunities - for (std::list ::iterator iter = aura_immunity_list.begin(); iter != aura_immunity_list.end(); ++iter) - target->ApplySpellImmune(GetId(), IMMUNITY_STATE, *iter, apply); - - // Patch 3.0.3 Bladestorm now breaks all snares and roots on the warrior when activated. - if (GetId() == 46924) - { - // Knockback and hex - target->ApplySpellImmune(GetId(), IMMUNITY_EFFECT, SPELL_EFFECT_KNOCK_BACK, apply); - } - - if (apply && GetSpellInfo()->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) - { - target->RemoveAurasWithMechanic(mechanic_immunity_list, AURA_REMOVE_BY_DEFAULT, GetId()); - for (std::list ::iterator iter = aura_immunity_list.begin(); iter != aura_immunity_list.end(); ++iter) - target->RemoveAurasByType(*iter); - } + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); } void AuraEffect::HandleModMechanicImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -4247,67 +3942,7 @@ void AuraEffect::HandleModMechanicImmunity(AuraApplication const* aurApp, uint8 return; Unit* target = aurApp->GetTarget(); - uint32 mechanic = 0; - - switch (GetId()) - { - case 46924: // BladeStorm - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - break; - case 34471: // The Beast Within - case 19574: // Bestial Wrath - case 38484: // Bestial Wrath - case 40081: // Free friend (Black Temple) - mechanic = IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_CHARM, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DISORIENTED, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FEAR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SLEEP, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_FREEZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_KNOCKOUT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_POLYMORPH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_BANISH, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SHACKLE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_TURN, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_HORROR, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_DAZE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SAPPED, apply); - break; - case 42292: // PvP trinket - case 59752: // Every Man for Himself - case 65547: // PvP trinket for Faction Champions (ToC 25) - case 53490: // Bullheaded - case 46227: // Medalion of Immunity - mechanic = IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; - target->RemoveAurasByType(SPELL_AURA_PREVENTS_FLEEING); // xinef: Patch 2.3.0 PvP Trinkets: Insignia of the Alliance, Insignia of the Horde, Medallion of the Alliance, and Medallion of the Horde now clear the debuff from Judgement of Justice. - // Actually we should apply immunities here, too, but the aura has only 100 ms duration, so there is practically no point - break; - case 54508: // Demonic Empowerment - mechanic = (1 << MECHANIC_SNARE) | (1 << MECHANIC_ROOT); - target->RemoveAurasByType(SPELL_AURA_MOD_STUN); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_SNARE, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_ROOT, apply); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, MECHANIC_STUN, apply); - break; - default: - if (GetMiscValue() < 1) - return; - mechanic = 1 << GetMiscValue(); - target->ApplySpellImmune(GetId(), IMMUNITY_MECHANIC, GetMiscValue(), apply); - break; - } - - if (apply && GetSpellInfo()->HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) - { - // Xinef: exception for purely snare mechanic (eg. hands of freedom)! - if (mechanic == (1 << MECHANIC_SNARE)) - target->RemoveMovementImpairingAuras(false); - else - target->RemoveAurasWithMechanic(mechanic, AURA_REMOVE_BY_DEFAULT, GetId()); - } + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); } void AuraEffect::HandleAuraModEffectImmunity(AuraApplication const* aurApp, uint8 mode, bool apply) const @@ -4415,7 +4050,7 @@ void AuraEffect::HandleAuraModDmgImmunity(AuraApplication const* aurApp, uint8 m Unit* target = aurApp->GetTarget(); - target->ApplySpellImmune(GetId(), IMMUNITY_DAMAGE, GetMiscValue(), apply); + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); if (apply) target->GetThreatMgr().EvaluateSuppressed(); @@ -4427,8 +4062,7 @@ void AuraEffect::HandleAuraModDispelImmunity(AuraApplication const* aurApp, uint return; Unit* target = aurApp->GetTarget(); - - target->ApplySpellDispelImmunity(m_spellInfo, DispelType(GetMiscValue()), (apply)); + m_spellInfo->ApplyAllSpellImmunitiesTo(target, &m_spellInfo->Effects[m_effIndex], apply); } /*********************************************************/ @@ -6643,7 +6277,7 @@ void AuraEffect::HandlePeriodicDamageAurasTick(Unit* target, Unit* caster) const if (!target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo()) || target->IsTotem()) + if (target->IsImmunedToDamage(caster, GetSpellInfo()) || target->IsTotem()) { SendTickImmune(target, caster); return; @@ -6795,7 +6429,7 @@ void AuraEffect::HandlePeriodicHealthLeechAuraTick(Unit* target, Unit* caster) c if (!target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo())) + if (target->IsImmunedToDamage(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -6905,7 +6539,7 @@ void AuraEffect::HandlePeriodicHealthFunnelAuraTick(Unit* target, Unit* caster) if (!caster || !caster->IsAlive() || !target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -6934,7 +6568,7 @@ void AuraEffect::HandlePeriodicHealAurasTick(Unit* target, Unit* caster) const if (!target->IsAlive()) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7073,7 +6707,7 @@ void AuraEffect::HandlePeriodicManaLeechAuraTick(Unit* target, Unit* caster) con if (!caster || !caster->IsAlive() || !target->IsAlive() || !target->HasActivePowerType(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo())) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7148,7 +6782,7 @@ void AuraEffect::HandleObsModPowerAuraTick(Unit* target, Unit* caster) const if (!target->IsAlive() || !target->GetMaxPower(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7181,7 +6815,7 @@ void AuraEffect::HandlePeriodicEnergizeAuraTick(Unit* target, Unit* caster) cons if (!target->IsAlive() || !target->GetMaxPower(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED)) + if (target->IsImmunedToAuraPeriodicTick(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7212,7 +6846,7 @@ void AuraEffect::HandlePeriodicPowerBurnAuraTick(Unit* target, Unit* caster) con if (!caster || !target->IsAlive() || !target->HasActivePowerType(PowerType)) return; - if (target->HasUnitState(UNIT_STATE_ISOLATED) || target->IsImmunedToDamageOrSchool(GetSpellInfo())) + if (target->IsImmunedToDamage(caster, GetSpellInfo())) { SendTickImmune(target, caster); return; @@ -7306,7 +6940,8 @@ void AuraEffect::HandleProcTriggerDamageAuraProc(AuraApplication* aurApp, ProcEv Unit* triggerTarget = target == eventInfo.GetActor() ? eventInfo.GetActionTarget() : eventInfo.GetActor(); if (!triggerTarget) return; - if (triggerTarget->HasUnitState(UNIT_STATE_ISOLATED) || triggerTarget->IsImmunedToDamageOrSchool(GetSpellInfo())) + + if (triggerTarget->IsImmunedToDamage(target, GetSpellInfo())) { SendTickImmune(triggerTarget, target); return; diff --git a/src/server/game/Spells/Auras/SpellAuras.cpp b/src/server/game/Spells/Auras/SpellAuras.cpp index e4b59781e..3ca928b47 100644 --- a/src/server/game/Spells/Auras/SpellAuras.cpp +++ b/src/server/game/Spells/Auras/SpellAuras.cpp @@ -552,7 +552,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply) if (IsArea()) for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) { - if ((existing->second & (1 << effIndex)) && existing->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex)) + if ((existing->second & (1 << effIndex)) && existing->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex, GetCaster())) existing->second &= ~(1 << effIndex); } @@ -594,7 +594,7 @@ void Aura::UpdateTargetMap(Unit* caster, bool apply) // check target immunities for (uint8 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) { - if ((itr->second & (1 << effIndex)) && itr->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex)) + if ((itr->second & (1 << effIndex)) && itr->first->IsImmunedToSpellEffect(GetSpellInfo(), effIndex, GetCaster())) itr->second &= ~(1 << effIndex); } if (!itr->second || itr->first->IsImmunedToSpell(GetSpellInfo()) || !CanBeAppliedOn(itr->first)) @@ -2822,7 +2822,7 @@ void UnitAura::FillTargetMap(std::map& targets, Unit* caster) { float radius = GetSpellInfo()->Effects[effIndex].CalcRadius(caster); - if (!GetUnitOwner()->HasUnitState(UNIT_STATE_ISOLATED)) + if (!GetUnitOwner()->HasAuraState(AURA_STATE_BANISHED, GetSpellInfo(), caster)) { switch (GetSpellInfo()->Effects[effIndex].Effect) { diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp index a901be115..4283d5a13 100644 --- a/src/server/game/Spells/Spell.cpp +++ b/src/server/game/Spells/Spell.cpp @@ -1315,7 +1315,7 @@ void Spell::SelectImplicitAreaTargets(SpellEffIndex effIndex, SpellImplicitTarge // Xinef: the distance should be increased by caster size, it is neglected in latter calculations std::list targets; float radius = m_spellInfo->Effects[effIndex].CalcRadius(m_caster) * m_spellValue->RadiusMod; - SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions); + SearchAreaTargets(targets, radius, center, referer, targetType.GetObjectType(), targetType.GetCheckType(), m_spellInfo->Effects[effIndex].ImplicitTargetConditions, Acore::WorldObjectSpellAreaTargetSearchReason::Area); CallScriptObjectAreaTargetSelectHandlers(targets, effIndex, targetType); @@ -2091,12 +2091,12 @@ WorldObject* Spell::SearchNearbyTarget(float range, SpellTargetObjectTypes objec return target; } -void Spell::SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList) +void Spell::SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason) { uint32 containerTypeMask = GetSearcherTypeMask(objectType, condList); if (!containerTypeMask) return; - Acore::WorldObjectSpellAreaTargetCheck check(range, position, m_caster, referer, m_spellInfo, selectionType, condList); + Acore::WorldObjectSpellAreaTargetCheck check(range, position, m_caster, referer, m_spellInfo, selectionType, condList, searchReason); Acore::WorldObjectListSearcher searcher(m_caster, targets, check, containerTypeMask); SearchTargets > (searcher, containerTypeMask, m_caster, position, range); } @@ -2142,7 +2142,7 @@ void Spell::SearchChainTargets(std::list& targets, uint32 chainTar WorldObject* chainSource = m_spellInfo->HasAttribute(SPELL_ATTR2_CHAIN_FROM_CASTER) ? m_caster : target; std::list tempTargets; - SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, condList); + SearchAreaTargets(tempTargets, searchRadius, chainSource, m_caster, objectType, selectType, condList, Acore::WorldObjectSpellAreaTargetSearchReason::Chain); tempTargets.remove(target); // remove targets which are always invalid for chain spells @@ -2310,7 +2310,7 @@ void Spell::AddUnitTarget(Unit* target, uint32 effectMask, bool checkIfValid /*= // Check for effect immune skip if immuned for (uint32 effIndex = 0; effIndex < MAX_SPELL_EFFECTS; ++effIndex) - if (target->IsImmunedToSpellEffect(m_spellInfo, effIndex)) + if (target->IsImmunedToSpellEffect(m_spellInfo, effIndex, m_caster)) effectMask &= ~(1 << effIndex); ObjectGuid targetGUID = target->GetGUID(); @@ -2777,83 +2777,93 @@ void Spell::DoAllEffectOnTarget(TargetInfo* target) // Fill base damage struct (unitTarget - is real spell target) SpellNonMeleeDamage damageInfo(caster, unitTarget, m_spellInfo, m_spellSchoolMask); - // Add bonuses and fill damageInfo struct - // Dancing Rune Weapon... - if (m_caster->GetEntry() == 27893) + // Check damage immunity + if (unitTarget->IsImmunedToDamage(caster, m_spellInfo)) { - if (Unit* owner = m_caster->GetOwner()) - owner->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); + m_damage = 0; + + // no packet found in sniffs } else - caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); - - // xinef: override miss info after absorb / block calculations - if (missInfo == SPELL_MISS_NONE && damageInfo.damage == 0) { - //if (damageInfo.absorb > 0) - // missInfo = SPELL_MISS_ABSORB; - if (damageInfo.blocked) - missInfo = SPELL_MISS_BLOCK; - } - - // Xinef: override with forced crit, only visual result - if (GetSpellValue()->ForcedCritResult) - { - damageInfo.HitInfo |= SPELL_HIT_TYPE_CRIT; - } - - Unit::DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb); - - // xinef: health leech handling - if (m_spellInfo->HasEffect(SPELL_EFFECT_HEALTH_LEECH)) - { - uint8 effIndex = EFFECT_0; - for (; effIndex < MAX_SPELL_EFFECTS; ++effIndex) - if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_HEALTH_LEECH) - break; - - float healMultiplier = m_spellInfo->Effects[effIndex].CalcValueMultiplier(m_originalCaster, this); - - // get max possible damage, don't count overkill for heal - uint32 healthGain = uint32(-unitTarget->GetHealthGain(-int32(damageInfo.damage)) * healMultiplier); - - if (m_caster->IsAlive()) + // Add bonuses and fill damageInfo struct + // Dancing Rune Weapon... + if (m_caster->GetEntry() == 27893) { - healthGain = m_caster->SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, HEAL, effIndex); - healthGain = m_caster->SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, HEAL); - - HealInfo healInfo(m_caster, m_caster, healthGain, m_spellInfo, m_spellInfo->GetSchoolMask()); - m_caster->HealBySpell(healInfo); + if (Unit* owner = m_caster->GetOwner()) + owner->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); } + else + caster->CalculateSpellDamageTaken(&damageInfo, m_damage, m_spellInfo, m_attackType, target->crit); + + // xinef: override miss info after absorb / block calculations + if (missInfo == SPELL_MISS_NONE && damageInfo.damage == 0) + { + //if (damageInfo.absorb > 0) + // missInfo = SPELL_MISS_ABSORB; + if (damageInfo.blocked) + missInfo = SPELL_MISS_BLOCK; + } + + // Xinef: override with forced crit, only visual result + if (GetSpellValue()->ForcedCritResult) + { + damageInfo.HitInfo |= SPELL_HIT_TYPE_CRIT; + } + + Unit::DealDamageMods(damageInfo.target, damageInfo.damage, &damageInfo.absorb); + + // xinef: health leech handling + if (m_spellInfo->HasEffect(SPELL_EFFECT_HEALTH_LEECH)) + { + uint8 effIndex = EFFECT_0; + for (; effIndex < MAX_SPELL_EFFECTS; ++effIndex) + if (m_spellInfo->Effects[effIndex].Effect == SPELL_EFFECT_HEALTH_LEECH) + break; + + float healMultiplier = m_spellInfo->Effects[effIndex].CalcValueMultiplier(m_originalCaster, this); + + // get max possible damage, don't count overkill for heal + uint32 healthGain = uint32(-unitTarget->GetHealthGain(-int32(damageInfo.damage)) * healMultiplier); + + if (m_caster->IsAlive()) + { + healthGain = m_caster->SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, HEAL, effIndex); + healthGain = m_caster->SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, HEAL); + + HealInfo healInfo(m_caster, m_caster, healthGain, m_spellInfo, m_spellInfo->GetSchoolMask()); + m_caster->HealBySpell(healInfo); + } + } + + // Send log damage message to client + caster->SendSpellNonMeleeDamageLog(&damageInfo); + // Xinef: send info to target about reflect + if (reflectedSpell) + effectUnit->SendSpellNonMeleeReflectLog(&damageInfo, effectUnit); + + procVictim |= PROC_FLAG_TAKEN_DAMAGE; + + caster->DealSpellDamage(&damageInfo, true, this); + + // do procs after damage, eg healing effects + // no need to check if target is alive, done in procdamageandspell + + // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) + if (canEffectTrigger) + { + DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, missInfo); + uint32 hitMask = m_procEx | dmgInfo.GetHitMask(); + Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, + m_triggeredByAuraSpell.effectIndex, this, &dmgInfo); + + if (caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 && + m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) + caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, dmgInfo.GetHitMask()); + } + + m_damage = damageInfo.damage; } - - // Send log damage message to client - caster->SendSpellNonMeleeDamageLog(&damageInfo); - // Xinef: send info to target about reflect - if (reflectedSpell) - effectUnit->SendSpellNonMeleeReflectLog(&damageInfo, effectUnit); - - procVictim |= PROC_FLAG_TAKEN_DAMAGE; - - caster->DealSpellDamage(&damageInfo, true, this); - - // do procs after damage, eg healing effects - // no need to check if target is alive, done in procdamageandspell - - // Do triggers for unit (reflect triggers passed on hit phase for correct drop charge) - if (canEffectTrigger) - { - DamageInfo dmgInfo(damageInfo, SPELL_DIRECT_DAMAGE, m_attackType, missInfo); - uint32 hitMask = m_procEx | dmgInfo.GetHitMask(); - Unit::ProcSkillsAndAuras(caster, unitTarget, procAttacker, procVictim, hitMask, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell.spellInfo, - m_triggeredByAuraSpell.effectIndex, this, &dmgInfo); - - if (caster->IsPlayer() && m_spellInfo->HasAttribute(SPELL_ATTR0_CANCELS_AUTO_ATTACK_COMBAT) == 0 && - m_spellInfo->HasAttribute(SPELL_ATTR4_SUPPRESS_WEAPON_PROCS) == 0 && (m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_MELEE || m_spellInfo->DmgClass == SPELL_DAMAGE_CLASS_RANGED)) - caster->ToPlayer()->CastItemCombatSpell(unitTarget, m_attackType, procVictim, dmgInfo.GetHitMask()); - } - - m_damage = damageInfo.damage; } // Passive spell hits/misses or active spells only misses (only triggers) else @@ -2939,7 +2949,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA return SPELL_MISS_EVADE; // For delayed spells immunity may be applied between missile launch and hit - check immunity for that case - if (m_spellInfo->Speed && ((m_damage > 0 && unit->IsImmunedToDamage(this)) || unit->IsImmunedToSchool(this) || unit->IsImmunedToSpell(m_spellInfo, this))) + if (m_spellInfo->Speed && ((m_damage > 0 && unit->IsImmunedToDamage(m_caster, m_spellInfo)) || unit->IsImmunedToSchool(this) || unit->IsImmunedToSpell(m_spellInfo, this))) { return SPELL_MISS_IMMUNE; } @@ -2950,7 +2960,7 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA { if (effectMask & (1 << effectNumber)) { - if (unit->IsImmunedToSpellEffect(m_spellInfo, effectNumber)) + if (unit->IsImmunedToSpellEffect(m_spellInfo, effectNumber, m_caster)) effectMask &= ~(1 << effectNumber); // Xinef: Buggs out polymorph // Xinef: And this is checked in MagicSpellHitResult, why we check resistance twice? @@ -3096,14 +3106,6 @@ SpellMissInfo Spell::DoSpellHitOnUnit(Unit* unit, uint32 effectMask, bool scaleA if (m_spellAura) { - // Prevent aura application if target is banished and immuned - if (m_targets.GetUnitTarget() && m_targets.GetUnitTarget()->IsImmunedToDamageOrSchool(m_spellAura->GetSpellInfo()) - && m_targets.GetUnitTarget()->HasUnitState(UNIT_STATE_ISOLATED)) - { - m_spellAura->Remove(); - return SPELL_MISS_IMMUNE; - } - // Set aura stack amount to desired value if (m_spellValue->AuraStackAmount > 1) { @@ -5606,7 +5608,7 @@ void Spell::HandleEffects(Unit* pUnitTarget, Item* pItemTarget, GameObject* pGOT } } -SpellCastResult Spell::CheckCast(bool strict) +SpellCastResult Spell::CheckCast(bool strict, uint32* /*param1*/, uint32* /*param2*/) { // check death state if (!m_caster->IsAlive() && !m_spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE) && !(m_spellInfo->HasAttribute(SPELL_ATTR0_ALLOW_CAST_WHILE_DEAD) || (IsTriggered() && !m_triggeredByAuraSpell))) @@ -6015,10 +6017,9 @@ SpellCastResult Spell::CheckCast(bool strict) // xinef: Enraged Regeneration: While this is active, the warrior is blocked from using abilities that trigger being enraged (which would do nothing and waste the cooldowns). if (m_spellInfo->Mechanic && m_spellInfo->IsSelfCast()) { - SpellImmuneList const& mechanicList = m_caster->m_spellImmune[IMMUNITY_MECHANIC]; - for (SpellImmuneList::const_iterator itr = mechanicList.begin(); itr != mechanicList.end(); ++itr) - if (itr->type == m_spellInfo->Mechanic) - return SPELL_FAILED_DAMAGE_IMMUNE; + auto const& mechanicList = m_caster->m_spellImmune[IMMUNITY_MECHANIC]; + if (mechanicList.count(m_spellInfo->Mechanic) > 0) + return SPELL_FAILED_DAMAGE_IMMUNE; } } @@ -7158,7 +7159,7 @@ SpellCastResult Spell::CheckPower() return SPELL_CAST_OK; } -SpellCastResult Spell::CheckItems() +SpellCastResult Spell::CheckItems(uint32* param1, uint32* param2) { Player* player = m_caster->ToPlayer(); if (!player) @@ -7566,21 +7567,29 @@ SpellCastResult Spell::CheckItems() } case SPELL_EFFECT_PROSPECTING: { - if (!m_targets.GetItemTarget()) + Item* item = m_targets.GetItemTarget(); + if (!item) return SPELL_FAILED_CANT_BE_PROSPECTED; //ensure item is a prospectable ore - if (!(m_targets.GetItemTarget()->GetTemplate()->HasFlag(ITEM_FLAG_IS_PROSPECTABLE))) + if (!(item->GetTemplate()->HasFlag(ITEM_FLAG_IS_PROSPECTABLE))) return SPELL_FAILED_CANT_BE_PROSPECTED; //prevent prospecting in trade slot - if (m_targets.GetItemTarget()->GetOwnerGUID() != m_caster->GetGUID()) + if (item->GetOwnerGUID() != m_caster->GetGUID()) return SPELL_FAILED_CANT_BE_PROSPECTED; //Check for enough skill in jewelcrafting - uint32 item_prospectingskilllevel = m_targets.GetItemTarget()->GetTemplate()->RequiredSkillRank; + uint32 item_prospectingskilllevel = item->GetTemplate()->RequiredSkillRank; if (item_prospectingskilllevel > player->GetSkillValue(SKILL_JEWELCRAFTING)) return SPELL_FAILED_LOW_CASTLEVEL; //make sure the player has the required ores in inventory - if (m_targets.GetItemTarget()->GetCount() < 5) + if (item->GetCount() < 5) + { + if (param1 && param2) + { + *param1 = item->GetEntry(); + *param2 = 5; + } return SPELL_FAILED_NEED_MORE_ITEMS; + } if (!LootTemplates_Prospecting.HaveLootFor(m_targets.GetItemTargetEntry())) return SPELL_FAILED_CANT_BE_PROSPECTED; @@ -7589,21 +7598,29 @@ SpellCastResult Spell::CheckItems() } case SPELL_EFFECT_MILLING: { - if (!m_targets.GetItemTarget()) + Item* item = m_targets.GetItemTarget(); + if (!item) return SPELL_FAILED_CANT_BE_MILLED; //ensure item is a millable herb - if (!(m_targets.GetItemTarget()->GetTemplate()->HasFlag(ITEM_FLAG_IS_MILLABLE))) + if (!(item->GetTemplate()->HasFlag(ITEM_FLAG_IS_MILLABLE))) return SPELL_FAILED_CANT_BE_MILLED; //prevent milling in trade slot - if (m_targets.GetItemTarget()->GetOwnerGUID() != m_caster->GetGUID()) + if (item->GetOwnerGUID() != m_caster->GetGUID()) return SPELL_FAILED_CANT_BE_MILLED; //Check for enough skill in inscription - uint32 item_millingskilllevel = m_targets.GetItemTarget()->GetTemplate()->RequiredSkillRank; + uint32 item_millingskilllevel = item->GetTemplate()->RequiredSkillRank; if (item_millingskilllevel > player->GetSkillValue(SKILL_INSCRIPTION)) return SPELL_FAILED_LOW_CASTLEVEL; //make sure the player has the required herbs in inventory - if (m_targets.GetItemTarget()->GetCount() < 5) + if (item->GetCount() < 5) + { + if (param1 && param2) + { + *param1 = item->GetEntry(); + *param2 = 5; + } return SPELL_FAILED_NEED_MORE_ITEMS; + } if (!LootTemplates_Milling.HaveLootFor(m_targets.GetItemTargetEntry())) return SPELL_FAILED_CANT_BE_MILLED; @@ -9063,8 +9080,8 @@ namespace Acore } WorldObjectSpellAreaTargetCheck::WorldObjectSpellAreaTargetCheck(float range, Position const* position, Unit* caster, - Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList) - : WorldObjectSpellTargetCheck(caster, referer, spellInfo, selectionType, condList), _range(range), _position(position) + Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason) + : WorldObjectSpellTargetCheck(caster, referer, spellInfo, selectionType, condList), _range(range), _position(position), _searchReason(searchReason) { } @@ -9077,8 +9094,27 @@ namespace Acore } else if (!target->IsWithinDist3d(_position, _range)) return false; - else if (target->IsCreature() && target->ToCreature()->IsAvoidingAOE()) // pussywizard - return false; + else if (Creature* c = target->ToCreature()) + { + if (c->IsAvoidingAOE()) // pussywizard + return false; + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(c->GetCreatureTemplate()->CreatureImmunitiesId)) + { + switch (_searchReason) + { + case Acore::WorldObjectSpellAreaTargetSearchReason::Area: + if (immunities->ImmuneAoE) + return false; + break; + case Acore::WorldObjectSpellAreaTargetSearchReason::Chain: + if (immunities->ImmuneChain) + return false; + break; + default: + break; + } + } + } return WorldObjectSpellTargetCheck::operator ()(target); } diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h index 700137f17..40ebfaa6f 100644 --- a/src/server/game/Spells/Spell.h +++ b/src/server/game/Spells/Spell.h @@ -38,6 +38,15 @@ class SpellEvent; class ByteBuffer; class BasicEvent; +namespace Acore +{ + enum class WorldObjectSpellAreaTargetSearchReason + { + Area, + Chain + }; +} + #define SPELL_CHANNEL_UPDATE_INTERVAL (1 * IN_MILLISECONDS) #define TRAJECTORY_MISSILE_SIZE 3.0f @@ -444,7 +453,7 @@ public: template void SearchTargets(SEARCHER& searcher, uint32 containerMask, Unit* referer, Position const* pos, float radius); WorldObject* SearchNearbyTarget(float range, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList = nullptr); - void SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList); + void SearchAreaTargets(std::list& targets, float range, Position const* position, Unit* referer, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason = Acore::WorldObjectSpellAreaTargetSearchReason::Area); void SearchChainTargets(std::list& targets, uint32 chainTargets, WorldObject* target, SpellTargetObjectTypes objectType, SpellTargetCheckTypes selectType, SpellTargetSelectionCategories selectCategory, ConditionList* condList, bool isChainHeal); SpellCastResult prepare(SpellCastTargets const* targets, AuraEffect const* triggeredByAura = nullptr); @@ -460,7 +469,7 @@ public: void TakeReagents(); void TakeCastItem(); - SpellCastResult CheckCast(bool strict); + SpellCastResult CheckCast(bool strict, uint32* param1 = nullptr, uint32* param2 = nullptr); SpellCastResult CheckPetCast(Unit* target); // handlers @@ -472,7 +481,7 @@ public: void OnSpellLaunch(); - SpellCastResult CheckItems(); + SpellCastResult CheckItems(uint32* param1 = nullptr, uint32* param2 = nullptr); SpellCastResult CheckSpellFocus(); SpellCastResult CheckRange(bool strict); SpellCastResult CheckPower(); @@ -827,8 +836,9 @@ namespace Acore { float _range; Position const* _position; + Acore::WorldObjectSpellAreaTargetSearchReason _searchReason; WorldObjectSpellAreaTargetCheck(float range, Position const* position, Unit* caster, - Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList); + Unit* referer, SpellInfo const* spellInfo, SpellTargetCheckTypes selectionType, ConditionList* condList, Acore::WorldObjectSpellAreaTargetSearchReason searchReason = Acore::WorldObjectSpellAreaTargetSearchReason::Area); bool operator()(WorldObject* target); }; diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp index 7c47c9d41..e8f6a6aa3 100644 --- a/src/server/game/Spells/SpellEffects.cpp +++ b/src/server/game/Spells/SpellEffects.cpp @@ -856,7 +856,7 @@ void Spell::EffectTriggerSpell(SpellEffIndex effIndex) SpellInfo const* spell = iter->second->GetBase()->GetSpellInfo(); // Pounce Bleed shouldn't be removed by Cloak of Shadows. - if (spell->GetAllEffectsMechanicMask() & 1 << MECHANIC_BLEED) + if (spell->GetAllEffectsMechanicMask() & (UI64LIT(1) << MECHANIC_BLEED)) return; bool dmgClassNone = false; @@ -1889,7 +1889,7 @@ void Spell::EffectEnergize(SpellEffIndex effIndex) if (!unitTarget->IsAlive()) return; - if (unitTarget->HasUnitState(UNIT_STATE_ISOLATED)) + if (unitTarget->IsImmunedToAuraPeriodicTick(m_caster, m_spellInfo)) { m_caster->SendSpellDamageImmune(unitTarget, GetSpellInfo()->Id); return; @@ -3681,7 +3681,7 @@ void Spell::EffectHealMaxHealth(SpellEffIndex /*effIndex*/) if (!unitTarget || !unitTarget->IsAlive()) return; - if (unitTarget->HasUnitState(UNIT_STATE_ISOLATED)) + if (unitTarget->IsImmunedToAuraPeriodicTick(m_caster, m_spellInfo)) { m_caster->SendSpellDamageImmune(unitTarget, GetSpellInfo()->Id); return; @@ -5152,7 +5152,7 @@ void Spell::EffectDispelMechanic(SpellEffIndex effIndex) continue; if (roll_chance_i(aura->CalcDispelChance(unitTarget, !unitTarget->IsFriendlyTo(m_caster)))) { - if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (1 << mechanic))) + if ((aura->GetSpellInfo()->GetAllEffectsMechanicMask() & (UI64LIT(1) << mechanic))) { dispel_list.push(std::make_pair(aura->GetId(), aura->GetCasterGUID())); diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp index 86eb2d4dd..1775e9ec1 100644 --- a/src/server/game/Spells/SpellInfo.cpp +++ b/src/server/game/Spells/SpellInfo.cpp @@ -605,6 +605,14 @@ SpellTargetObjectTypes SpellEffectInfo::GetUsedTargetObjectType() const return _data[Effect].UsedTargetObjectType; } +ImmunityInfo const* SpellEffectInfo::GetImmunityInfo() const +{ + if (!_spellInfo) + return nullptr; + + return _spellInfo->GetImmunityInfo(EffectIndex); +} + std::array SpellEffectInfo::_data = { { // implicit target type used target object type @@ -915,6 +923,29 @@ bool SpellInfo::HasAreaAuraEffect() const return false; } +bool SpellInfo::HasOnlyDamageEffects() const +{ + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + if (Effects[i].IsEffect()) + { + switch (Effects[i].Effect) + { + case SPELL_EFFECT_SCHOOL_DAMAGE: + case SPELL_EFFECT_HEALTH_LEECH: + case SPELL_EFFECT_POWER_DRAIN: + case SPELL_EFFECT_POWER_BURN: + case SPELL_EFFECT_NORMALIZED_WEAPON_DMG: + case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE: + continue; + default: + return false; + } + } + } + return true; +} + bool SpellInfo::IsExplicitDiscovery() const { return ((Effects[0].Effect == SPELL_EFFECT_CREATE_RANDOM_ITEM @@ -1328,54 +1359,25 @@ bool SpellInfo::IsAffectedBySpellMod(SpellModifier const* mod) const return IsAffected(affectSpell->SpellFamilyName, mod->mask); } -bool SpellInfo::CanPierceImmuneAura(SpellInfo const* aura) const +bool SpellInfo::CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const { - // aura can't be pierced - if (!aura || aura->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) - { - return false; - } - - // these spells pierce all avalible spells (Resurrection Sickness for example) - if (HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + // Dispels other auras on immunity, check if this spell makes the unit immune to aura + if (HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT) && CanSpellProvideImmunityAgainstAura(auraSpellInfo)) return true; - // these spells (Cyclone for example) can pierce all... - if (HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) || HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - if (aura->Mechanic != MECHANIC_IMMUNE_SHIELD && - aura->Mechanic != MECHANIC_INVULNERABILITY && - aura->Mechanic != MECHANIC_BANISH) - { - return true; - } - - } - return false; } -bool SpellInfo::CanDispelAura(SpellInfo const* aura) const +bool SpellInfo::CanDispelAura(SpellInfo const* auraSpellInfo) const { // Xinef: Passive auras cannot be dispelled - if (aura->IsPassive()) + if (auraSpellInfo->IsPassive()) return false; // These auras (like Divine Shield) can't be dispelled - if (aura->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + if (auraSpellInfo->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) return false; - // These spells (like Mass Dispel) can dispell all auras - if (HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) - return true; - - // These auras (Cyclone for example) are not dispelable - if ((aura->HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS) && aura->Mechanic != MECHANIC_NONE) - || aura->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) - { - return false; - } - return true; } @@ -1666,6 +1668,12 @@ SpellCastResult SpellInfo::CheckTarget(Unit const* caster, WorldObject const* ta if (AttributesEx & SPELL_ATTR1_ONLY_PEACEFUL_TARGETS && (unitTarget->IsInCombat() || unitTarget->IsPetInCombat())) return SPELL_FAILED_TARGET_AFFECTING_COMBAT; + if (HasAttribute(SPELL_ATTR3_NOT_ON_AOE_IMMUNE)) + if (auto creature = unitTarget->ToCreature()) + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(creature->GetCreatureTemplate()->CreatureImmunitiesId)) + if (immunities->ImmuneAoE) + return SPELL_FAILED_BAD_TARGETS; + // only spells with SPELL_ATTR3_ONLY_ON_GHOSTS can target ghosts if (IsRequiringDeadTarget()) { @@ -1888,35 +1896,35 @@ SpellSchoolMask SpellInfo::GetSchoolMask() const return SpellSchoolMask(SchoolMask); } -uint32 SpellInfo::GetAllEffectsMechanicMask() const +uint64 SpellInfo::GetAllEffectsMechanicMask() const { - uint32 mask = 0; + uint64 mask = 0; if (Mechanic) - mask |= 1 << Mechanic; + mask |= UI64LIT(1) << Mechanic; for (int i = 0; i < MAX_SPELL_EFFECTS; ++i) if (Effects[i].IsEffect() && Effects[i].Mechanic) - mask |= 1 << Effects[i].Mechanic; + mask |= UI64LIT(1) << Effects[i].Mechanic; return mask; } -uint32 SpellInfo::GetEffectMechanicMask(uint8 effIndex) const +uint64 SpellInfo::GetEffectMechanicMask(uint8 effIndex) const { - uint32 mask = 0; + uint64 mask = 0; if (Mechanic) - mask |= 1 << Mechanic; + mask |= UI64LIT(1) << Mechanic; if (Effects[effIndex].IsEffect() && Effects[effIndex].Mechanic) - mask |= 1 << Effects[effIndex].Mechanic; + mask |= UI64LIT(1) << Effects[effIndex].Mechanic; return mask; } -uint32 SpellInfo::GetSpellMechanicMaskByEffectMask(uint32 effectMask) const +uint64 SpellInfo::GetSpellMechanicMaskByEffectMask(uint32 effectMask) const { - uint32 mask = 0; + uint64 mask = 0; if (Mechanic) - mask |= 1 << Mechanic; + mask |= UI64LIT(1) << Mechanic; for (int i = 0; i < MAX_SPELL_EFFECTS; ++i) if ((effectMask & (1 << i)) && Effects[i].Mechanic) - mask |= 1 << Effects[i].Mechanic; + mask |= UI64LIT(1) << Effects[i].Mechanic; return mask; } @@ -2000,9 +2008,13 @@ AuraStateType SpellInfo::LoadAuraState() const return AURA_STATE_ENRAGE; // Bleeding aura state - if (GetAllEffectsMechanicMask() & 1 << MECHANIC_BLEED) + if (GetAllEffectsMechanicMask() & (UI64LIT(1) << MECHANIC_BLEED)) return AURA_STATE_BLEEDING; + // Banished aura state + if (Mechanic == MECHANIC_BANISH) + return AURA_STATE_BANISHED; + if (GetSchoolMask() & SPELL_SCHOOL_MASK_FROST) for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) if (Effects[i].IsAura() && (Effects[i].ApplyAuraName == SPELL_AURA_MOD_STUN @@ -2213,6 +2225,492 @@ SpellSpecificType SpellInfo::LoadSpellSpecific() const return SPELL_SPECIFIC_NORMAL; } +// immunity helper functions moved from SpellAuraEffects and global switches +void SpellInfo::_LoadImmunityInfo() +{ + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + SpellEffectInfo& effectInfo = Effects[i]; + if (!effectInfo.Effect) + continue; + + uint32 schoolImmunityMask = 0; + uint32 applyHarmfulAuraImmunityMask = 0; + uint64 mechanicImmunityMask = 0; + uint32 dispelImmunityMask = 0; + uint32 damageImmunityMask = 0; + + int32 miscVal = effectInfo.MiscValue; + int32 amount = effectInfo.CalcValue(); + + ImmunityInfo& immuneInfo = _immunityInfo[i]; + + switch (effectInfo.ApplyAuraName) + { + case SPELL_AURA_MECHANIC_IMMUNITY_MASK: + { + switch (miscVal) + { + case 27: + mechanicImmunityMask |= (1ULL << MECHANIC_SILENCE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_SILENCE); + break; + case 96: + case 1615: + { + if (amount) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 679: + { + if (Id == 57742) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + } + break; + } + case 1557: + { + if (Id == 64187) + { + mechanicImmunityMask |= (1ULL << MECHANIC_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + } + else + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 1614: + case 1694: + { + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_ATTACK_ME); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_TAUNT); + break; + } + case 1630: + { + if (Id == 64112) + { + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_ATTACK_ME); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_TAUNT); + } + else + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 477: + case 1733: + { + if (!amount) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) + | (1ULL << MECHANIC_FEAR) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_SLEEP) | (1ULL << MECHANIC_CHARM) + | (1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_HORROR) + | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_DISORIENTED) + | (1ULL << MECHANIC_FREEZE) | (1ULL << MECHANIC_TURN); + + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_KNOCK_BACK); + immuneInfo.SpellEffectImmune.insert(SPELL_EFFECT_KNOCK_BACK_DEST); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + break; + } + case 878: + { + if (Id == 66092) + { + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_STUN) + | (1ULL << MECHANIC_DISORIENTED) | (1ULL << MECHANIC_FREEZE); + + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + } + break; + } + default: + break; + } + + if (immuneInfo.AuraTypeImmune.empty()) + { + if (miscVal & (1 << 10)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_STUN); + if (miscVal & (1 << 1)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_TRANSFORM); + + if (miscVal & (1 << 6)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DECREASE_SPEED); + if (miscVal & (1 << 0)) + { + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_ROOT); + } + if (miscVal & (1 << 2)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_CONFUSE); + if (miscVal & (1 << 9)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_FEAR); + if (miscVal & (1 << 7)) + immuneInfo.AuraTypeImmune.insert(SPELL_AURA_MOD_DISARM); + } + break; + } + case SPELL_AURA_MECHANIC_IMMUNITY: + { + switch (Id) + { + case 34471: // The Beast Within + case 19574: // Bestial Wrath + case 42292: // PvP trinket + case 46227: // Medallion of Immunity + case 59752: // Every Man for Himself + case 53490: // Bullheaded + case 65547: // PvP Trinket + case 134946: // Supremacy of the Alliance + case 134956: // Supremacy of the Horde + case 195710: // Honorable Medallion + case 208683: // Gladiator's Medallion + mechanicImmunityMask |= IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK; + break; + case 54508: // Demonic Empowerment + mechanicImmunityMask |= (1ULL << MECHANIC_SNARE) | (1ULL << MECHANIC_ROOT) | (1ULL << MECHANIC_STUN); + break; + default: + if (miscVal < 1) + break; + + mechanicImmunityMask |= 1ULL << miscVal; + break; + } + break; + } + case SPELL_AURA_EFFECT_IMMUNITY: + { + immuneInfo.SpellEffectImmune.insert(static_cast(miscVal)); + break; + } + case SPELL_AURA_STATE_IMMUNITY: + { + immuneInfo.AuraTypeImmune.insert(static_cast(miscVal)); + break; + } + case SPELL_AURA_SCHOOL_IMMUNITY: + { + schoolImmunityMask |= uint32(miscVal); + break; + } + case SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL: + { + applyHarmfulAuraImmunityMask |= uint32(miscVal); + break; + } + case SPELL_AURA_DAMAGE_IMMUNITY: + { + damageImmunityMask |= uint32(miscVal); + break; + } + case SPELL_AURA_DISPEL_IMMUNITY: + { + dispelImmunityMask = uint32(miscVal); + break; + } + default: + break; + } + + immuneInfo.SchoolImmuneMask = schoolImmunityMask; + immuneInfo.ApplyHarmfulAuraImmuneMask = applyHarmfulAuraImmunityMask; + immuneInfo.MechanicImmuneMask = mechanicImmunityMask; + immuneInfo.DispelImmuneMask = dispelImmunityMask; + immuneInfo.DamageSchoolMask = damageImmunityMask; + } +} + +void SpellInfo::ApplyAllSpellImmunitiesTo(Unit* target, SpellEffectInfo const* effect, bool apply) const +{ + ImmunityInfo const* immuneInfo = nullptr; + if (effect) + { + uint8 effIndex = effect->EffectIndex; + if (effIndex < MAX_SPELL_EFFECTS) + immuneInfo = &_immunityInfo[effIndex]; + } + + if (!immuneInfo) + return; + + if (uint32 schoolImmunity = immuneInfo->SchoolImmuneMask) + { + target->ApplySpellImmune(Id, IMMUNITY_SCHOOL, schoolImmunity, apply); + + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) +{ + target->RemoveAppliedAuras([this, target, schoolImmunity](AuraApplication const* aurApp) -> bool + { + SpellInfo const* auraSpellInfo = aurApp->GetBase()->GetSpellInfo(); + if (auraSpellInfo->Id == Id) // Don't remove self + return false; + if (auraSpellInfo->IsPassive()) // Don't remove passive auras + return false; + if (!(auraSpellInfo->GetSchoolMask() & schoolImmunity)) // Check for school mask + return false; + if (!CanDispelAura(auraSpellInfo)) + return false; + if (!HasAttribute(SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS)) + { + Unit* existingAuraCaster = aurApp->GetBase()->GetCaster(); + if (existingAuraCaster && existingAuraCaster->IsFriendlyTo(target)) // Don't remove friendly auras + return false; + } + return true; + }); + } + } + + if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask) + { + for (uint32 i = 0; i < MAX_MECHANIC; ++i) + if (mechanicImmunity & (1ULL << i)) + target->ApplySpellImmune(Id, IMMUNITY_MECHANIC, i, apply); + + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + target->RemoveAurasWithMechanic(mechanicImmunity, AURA_REMOVE_BY_DEFAULT, Id); + } + + if (uint32 dispelImmunity = immuneInfo->DispelImmuneMask) + { + target->ApplySpellImmune(Id, IMMUNITY_DISPEL, dispelImmunity, apply); + + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + { + target->RemoveAppliedAuras([dispelImmunity](AuraApplication const* aurApp) -> bool + { + SpellInfo const* spellInfo = aurApp->GetBase()->GetSpellInfo(); + if (spellInfo->Dispel == dispelImmunity) + return true; + + return false; + }); + } + } + + if (uint32 damageImmunity = immuneInfo->DamageSchoolMask) + target->ApplySpellImmune(Id, IMMUNITY_DAMAGE, damageImmunity, apply); + + for (AuraType auraType : immuneInfo->AuraTypeImmune) + { + target->ApplySpellImmune(Id, IMMUNITY_STATE, auraType, apply); + if (apply && HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + { + target->RemoveAppliedAuras([this, auraType](AuraApplication const* aurApp) -> bool + { + Aura const* aura = aurApp->GetBase(); + if (!aura->GetSpellInfo()->HasAura(auraType)) + return false; + return CanDispelAura(aura->GetSpellInfo()); + }); + } + } + + for (SpellEffects effectType : immuneInfo->SpellEffectImmune) + target->ApplySpellImmune(Id, IMMUNITY_EFFECT, effectType, apply); +} + +bool SpellInfo::CanSpellProvideImmunityAgainstAura(SpellInfo const* auraSpellInfo) const +{ + if (!auraSpellInfo) + return false; + + for (SpellEffectInfo const& effectInfo : Effects) + { + if (!effectInfo.Effect) + continue; + + ImmunityInfo const* immuneInfo = effectInfo.GetImmunityInfo(); + if (!immuneInfo) + continue; + + if (!auraSpellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + if (uint32 schoolImmunity = immuneInfo->SchoolImmuneMask) + if ((auraSpellInfo->SchoolMask & schoolImmunity) != 0) + return true; + } + + if (uint64 mechanicImmunity = immuneInfo->MechanicImmuneMask) + if ((mechanicImmunity & (1ULL << auraSpellInfo->Mechanic)) != 0) + return true; + + if (uint32 dispelImmunity = immuneInfo->DispelImmuneMask) + if (auraSpellInfo->Dispel == dispelImmunity) + return true; + + bool immuneToAllEffects = true; + for (SpellEffectInfo const& auraSpellEffectInfo : auraSpellInfo->Effects) + { + if (!auraSpellEffectInfo.Effect) + continue; + + uint32 effectName = auraSpellEffectInfo.Effect; + if (!effectName) + continue; + + if (immuneInfo->SpellEffectImmune.find(static_cast(effectName)) == immuneInfo->SpellEffectImmune.cend()) + { + immuneToAllEffects = false; + break; + } + + if (uint32 mechanic = auraSpellEffectInfo.Mechanic) + { + if (!(immuneInfo->MechanicImmuneMask & (1ULL << mechanic))) + { + immuneToAllEffects = false; + break; + } + } + + if (uint32 auraName = auraSpellEffectInfo.ApplyAuraName) + { + bool isImmuneToAuraEffectApply = false; + if (immuneInfo->AuraTypeImmune.find(static_cast(auraName)) != immuneInfo->AuraTypeImmune.cend()) + isImmuneToAuraEffectApply = true; + + if (!isImmuneToAuraEffectApply && !auraSpellInfo->IsPositiveEffect(auraSpellEffectInfo.EffectIndex) && + !auraSpellInfo->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES)) + { + if (uint32 applyHarmfulAuraImmuneMask = immuneInfo->ApplyHarmfulAuraImmuneMask) + if ((auraSpellInfo->SchoolMask & applyHarmfulAuraImmuneMask) != 0) + isImmuneToAuraEffectApply = true; + } + + if (!isImmuneToAuraEffectApply) + { + immuneToAllEffects = false; + break; + } + } + } + + if (immuneToAllEffects) + return true; + } + + return false; +} + +// based on client sub_007FDFA0 +bool SpellInfo::CanSpellCastOverrideAuraEffect(AuraEffect const* aurEff) const +{ + if (!HasAttribute(SPELL_ATTR1_IMMUNITY_PURGES_EFFECT)) + return false; + + if (aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR0_NO_IMMUNITIES)) + return false; + + SpellEffectInfo const* aurEffInfo = &aurEff->GetSpellInfo()->Effects[aurEff->GetEffIndex()]; + + for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) + { + SpellEffectInfo const& effectInfo = Effects[i]; + if (!effectInfo.Effect) + continue; + + if (effectInfo.Effect != SPELL_EFFECT_APPLY_AURA) + continue; + + uint32 const miscValue = static_cast(effectInfo.MiscValue); + switch (effectInfo.ApplyAuraName) + { + case SPELL_AURA_STATE_IMMUNITY: + if (miscValue != aurEffInfo->ApplyAuraName) + continue; + break; + case SPELL_AURA_SCHOOL_IMMUNITY: + case SPELL_AURA_MOD_IMMUNE_AURA_APPLY_SCHOOL: + if (aurEff->GetSpellInfo()->HasAttribute(SPELL_ATTR2_NO_SCHOOL_IMMUNITIES) || !(aurEff->GetSpellInfo()->SchoolMask & miscValue)) + continue; + break; + case SPELL_AURA_DISPEL_IMMUNITY: + if (miscValue != aurEff->GetSpellInfo()->Dispel) + continue; + break; + case SPELL_AURA_MECHANIC_IMMUNITY: + if (miscValue != aurEff->GetSpellInfo()->Mechanic) + { + if (miscValue != aurEffInfo->Mechanic) + continue; + } + break; + default: + continue; + } + + return true; + } + + return false; +} + float SpellInfo::GetMinRange(bool positive) const { if (!RangeEntry) diff --git a/src/server/game/Spells/SpellInfo.h b/src/server/game/Spells/SpellInfo.h index 70f0097f7..b87b5e37d 100644 --- a/src/server/game/Spells/SpellInfo.h +++ b/src/server/game/Spells/SpellInfo.h @@ -22,6 +22,7 @@ #include "Object.h" #include "SharedDefines.h" #include "SpellAuraDefines.h" +#include #include "Util.h" class Unit; @@ -213,6 +214,26 @@ enum SpellCustomAttributes uint32 GetTargetFlagMask(SpellTargetObjectTypes objType); +struct SpellDiminishInfo +{ + DiminishingGroup DiminishGroup = DIMINISHING_NONE; + DiminishingReturnsType DiminishReturnType = DRTYPE_NONE; + int32 DiminishMaxLevel = 3; // DIMINISHING_LEVEL_IMMUNE + int32 DiminishDurationLimit = 0; +}; + +struct AC_GAME_API ImmunityInfo +{ + uint32 SchoolImmuneMask = 0; + uint32 ApplyHarmfulAuraImmuneMask = 0; + uint64 MechanicImmuneMask = 0; + uint32 DispelImmuneMask = 0; + uint32 DamageSchoolMask = 0; + + boost::container::flat_set AuraTypeImmune; + boost::container::flat_set SpellEffectImmune; +}; + class SpellImplicitTargetInfo { private: @@ -302,6 +323,8 @@ public: SpellEffectImplicitTargetTypes GetImplicitTargetType() const; SpellTargetObjectTypes GetUsedTargetObjectType() const; + [[nodiscard]] ImmunityInfo const* GetImmunityInfo() const; + private: struct StaticData { @@ -310,6 +333,7 @@ private: }; static std::array _data; + }; class AC_GAME_API SpellInfo @@ -412,6 +436,7 @@ public: bool HasAura(AuraType aura) const; bool HasAnyAura() const; bool HasAreaAuraEffect() const; + bool HasOnlyDamageEffects() const; inline bool HasAttribute(SpellAttr0 attribute) const { return (Attributes & attribute) != 0; } inline bool HasAttribute(SpellAttr1 attribute) const { return (AttributesEx & attribute) != 0; } @@ -473,8 +498,12 @@ public: bool IsAffectedBySpellMods() const; bool IsAffectedBySpellMod(SpellModifier const* mod) const; - bool CanPierceImmuneAura(SpellInfo const* aura) const; - bool CanDispelAura(SpellInfo const* aura) const; + bool CanPierceImmuneAura(SpellInfo const* auraSpellInfo) const; + bool CanDispelAura(SpellInfo const* auraSpellInfo) const; + + void ApplyAllSpellImmunitiesTo(Unit* target, SpellEffectInfo const* effect, bool apply) const; + bool CanSpellProvideImmunityAgainstAura(SpellInfo const* auraSpellInfo) const; + bool CanSpellCastOverrideAuraEffect(AuraEffect const* aurEff) const; bool IsSingleTarget() const; bool IsAuraExclusiveBySpecificWith(SpellInfo const* spellInfo) const; @@ -490,11 +519,12 @@ public: bool ValidateAttribute6SpellDamageMods(Unit const* caster, const AuraEffect* auraEffect, bool isDot) const; SpellSchoolMask GetSchoolMask() const; - uint32 GetAllEffectsMechanicMask() const; - uint32 GetEffectMechanicMask(uint8 effIndex) const; - uint32 GetSpellMechanicMaskByEffectMask(uint32 effectMask) const; + uint64 GetAllEffectsMechanicMask() const; + uint64 GetEffectMechanicMask(uint8 effIndex) const; + uint64 GetSpellMechanicMaskByEffectMask(uint32 effectMask) const; Mechanics GetEffectMechanic(uint8 effIndex) const; bool HasAnyEffectMechanic() const; + [[nodiscard]] ImmunityInfo const* GetImmunityInfo(uint8 effIndex) const { return effIndex < MAX_SPELL_EFFECTS ? &_immunityInfo[effIndex] : nullptr; } uint32 GetDispelMask() const; static uint32 GetDispelMask(DispelType type); uint32 GetExplicitTargetMask() const; @@ -541,7 +571,13 @@ public: // unloading helpers void _UnloadImplicitTargetConditionLists(); -private: + // immunity helpers + void _LoadImmunityInfo(); + + SpellDiminishInfo _diminishInfoNonTriggered; + SpellDiminishInfo _diminishInfoTriggered; + + ImmunityInfo _immunityInfo[MAX_SPELL_EFFECTS]; std::array& _GetEffects() { return Effects; } SpellEffectInfo& _GetEffect(SpellEffIndex index) { ASSERT(index < Effects.size()); return Effects[index]; } }; diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 42bf4ed7d..af70ec147 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -16,6 +16,7 @@ */ #include "SpellMgr.h" +#include "Log.h" #include "BattlefieldMgr.h" #include "BattlegroundIC.h" #include "Chat.h" @@ -29,6 +30,7 @@ #include "Spell.h" #include "SpellAuraDefines.h" #include "SpellInfo.h" +#include "Tokenize.h" #include "World.h" bool IsPrimaryProfessionSkill(uint32 skill) @@ -53,6 +55,53 @@ bool IsPartOfSkillLine(uint32 skillId, uint32 spellId) return false; } +CreatureImmunities const* SpellMgr::GetCreatureImmunities(int32 creatureImmunitiesId) const +{ + return Acore::Containers::MapGetValuePtr(mCreatureImmunities, creatureImmunitiesId); +} + +void SpellMgr::LoadCreatureImmunities() +{ + mCreatureImmunities.clear(); + if (QueryResult result = WorldDatabase.Query("SELECT ID, SchoolMask, DispelTypeMask, MechanicsMask, Effects, Auras, ImmuneAoE, ImmuneChain FROM creature_immunities")) + { + do + { + Field* fields = result->Fetch(); + int32 id = fields[0].Get(); + uint8 school = fields[1].Get(); + uint16 dispelType = fields[2].Get(); + uint64 mechanics = fields[3].Get(); + CreatureImmunities& immunities = mCreatureImmunities[id]; + immunities.School = school; + immunities.DispelType = dispelType; + immunities.Mechanic = mechanics; + immunities.ImmuneAoE = fields[6].Get(); + immunities.ImmuneChain = fields[7].Get(); + { + std::string effects = fields[4].Get(); + for (std::string_view token : Acore::Tokenize(effects, ',', false)) + { + if (Optional val = Acore::StringTo(token); val && *val < uint32(TOTAL_SPELL_EFFECTS)) + immunities.Effect.push_back(SpellEffects(*val)); + else + LOG_ERROR("sql.sql", "Invalid effect type in `Effects` {} for creature immunities {}, skipped", token, id); + } + } + { + std::string auras = fields[5].Get(); + for (std::string_view token : Acore::Tokenize(auras, ',', false)) + { + if (Optional val = Acore::StringTo(token); val && *val < TOTAL_AURAS) + immunities.Aura.push_back(AuraType(*val)); + else + LOG_ERROR("sql.sql", "Invalid aura type in `Auras` {} for creature immunities {}, skipped", token, id); + } + } + } while (result->NextRow()); + } +} + DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo const* spellproto, bool triggered) { if (spellproto->IsPositive()) @@ -212,29 +261,29 @@ DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo const* spellproto, } // Lastly - Set diminishing depending on mechanic - uint32 mechanic = spellproto->GetAllEffectsMechanicMask(); - if (mechanic & (1 << MECHANIC_CHARM)) + uint64 mechanic = spellproto->GetAllEffectsMechanicMask(); + if (mechanic & (1ULL << MECHANIC_CHARM)) return DIMINISHING_MIND_CONTROL; - if (mechanic & (1 << MECHANIC_SILENCE)) + if (mechanic & (1ULL << MECHANIC_SILENCE)) return DIMINISHING_SILENCE; - if (mechanic & (1 << MECHANIC_SLEEP)) + if (mechanic & (1ULL << MECHANIC_SLEEP)) return DIMINISHING_SLEEP; - if (mechanic & ((1 << MECHANIC_SAPPED) | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_SHACKLE))) + if (mechanic & ((1ULL << MECHANIC_SAPPED) | (1ULL << MECHANIC_POLYMORPH) | (1ULL << MECHANIC_SHACKLE))) return DIMINISHING_DISORIENT; // Mechanic Knockout, except Blast Wave - if (mechanic & (1 << MECHANIC_KNOCKOUT) && spellproto->SpellIconID != 292) + if (mechanic & (1ULL << MECHANIC_KNOCKOUT) && spellproto->SpellIconID != 292) return DIMINISHING_DISORIENT; - if (mechanic & (1 << MECHANIC_DISARM)) + if (mechanic & (1ULL << MECHANIC_DISARM)) return DIMINISHING_DISARM; - if (mechanic & (1 << MECHANIC_FEAR)) + if (mechanic & (1ULL << MECHANIC_FEAR)) return DIMINISHING_FEAR; - if (mechanic & (1 << MECHANIC_STUN)) + if (mechanic & (1ULL << MECHANIC_STUN)) return triggered ? DIMINISHING_STUN : DIMINISHING_CONTROLLED_STUN; - if (mechanic & (1 << MECHANIC_BANISH)) + if (mechanic & (1ULL << MECHANIC_BANISH)) return DIMINISHING_BANISH; - if (mechanic & (1 << MECHANIC_ROOT)) + if (mechanic & (1ULL << MECHANIC_ROOT)) return triggered ? DIMINISHING_ROOT : DIMINISHING_CONTROLLED_ROOT; - if (mechanic & (1 << MECHANIC_HORROR)) + if (mechanic & (1ULL << MECHANIC_HORROR)) return DIMINISHING_HORROR; return DIMINISHING_NONE; @@ -2876,6 +2925,8 @@ void SpellMgr::LoadSpellInfoStore() } } + LoadCreatureImmunities(); + LOG_INFO("server.loading", ">> Loaded Spell Custom Attributes in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } @@ -3714,3 +3765,17 @@ void SpellMgr::LoadSpellInfoCustomAttributes() LOG_INFO("server.loading", ">> Loaded SpellInfo Custom Attributes in {} ms", GetMSTimeDiffToNow(oldMSTime)); LOG_INFO("server.loading", " "); } + +void SpellMgr::LoadSpellInfoImmunities() +{ + uint32 oldMSTime = getMSTime(); + + for (SpellInfo* spellInfo : mSpellInfoMap) + { + if (!spellInfo) + continue; + spellInfo->_LoadImmunityInfo(); + } + + LOG_INFO("server.loading", ">> Loaded SpellInfo immunity infos in {} ms", GetMSTimeDiffToNow(oldMSTime)); +} diff --git a/src/server/game/Spells/SpellMgr.h b/src/server/game/Spells/SpellMgr.h index ccedf1bff..32f34b030 100644 --- a/src/server/game/Spells/SpellMgr.h +++ b/src/server/game/Spells/SpellMgr.h @@ -25,6 +25,9 @@ #include "SharedDefines.h" #include "Unit.h" +#include +#include + class SpellInfo; class Player; class Unit; @@ -557,6 +560,18 @@ typedef std::multimap SpellsRequiringSpellMap; typedef std::pair SpellsRequiringSpellMapBounds; // Spell learning properties (accessed using SpellMgr functions) +struct CreatureImmunities +{ + std::bitset School; + std::bitset DispelType; + std::bitset Mechanic; + std::vector Effect; + std::vector Aura; + bool ImmuneAoE = false; + bool ImmuneChain = false; +}; + +typedef std::unordered_map CreatureImmunitiesMap; struct SpellLearnSkillNode { uint16 skill; @@ -634,6 +649,9 @@ private: public: static SpellMgr* instance(); + // creature immunity definitions loaded from DB + CreatureImmunities const* GetCreatureImmunities(int32 creatureImmunitiesId) const; + // Spell correctness for client using static bool ComputeIsSpellValid(SpellInfo const* spellInfo, bool msg = true); static bool IsSpellValid(SpellInfo const* spellInfo); @@ -772,10 +790,12 @@ public: void LoadPetDefaultSpells(); void LoadSpellAreas(); void LoadSpellInfoStore(); + void LoadCreatureImmunities(); void LoadSpellCooldownOverrides(); void UnloadSpellInfoStore(); void UnloadSpellInfoImplicitTargetConditionLists(); void LoadSpellInfoCustomAttributes(); + void LoadSpellInfoImmunities(); void LoadSpellInfoCorrections(); void LoadSpellSpecificAndAuraState(); void LoadSpellJumpDistances(); @@ -792,6 +812,7 @@ private: SpellGroupStackMap mSpellGroupStack; SameEffectStackMap mSpellSameEffectStack; SpellProcMap mSpellProcMap; + CreatureImmunitiesMap mCreatureImmunities; SpellBonusMap mSpellBonusMap; SpellThreatMap mSpellThreatMap; SpellMixologyMap mSpellMixologyMap; diff --git a/src/server/game/World/World.cpp b/src/server/game/World/World.cpp index 0db7ffd3c..b11b77c60 100644 --- a/src/server/game/World/World.cpp +++ b/src/server/game/World/World.cpp @@ -436,6 +436,9 @@ void World::SetInitialWorldSettings() LOG_INFO("server.loading", "Loading Spell Jump Distances..."); sSpellMgr->LoadSpellJumpDistances(); + LOG_INFO("server.loading", "Loading SpellInfo Immunity infos..."); + sSpellMgr->LoadSpellInfoImmunities(); + LOG_INFO("server.loading", "Loading Player Totem models..."); sObjectMgr->LoadPlayerTotemModels(); diff --git a/src/server/scripts/Commands/cs_npc.cpp b/src/server/scripts/Commands/cs_npc.cpp index ce6b78213..6eb781317 100644 --- a/src/server/scripts/Commands/cs_npc.cpp +++ b/src/server/scripts/Commands/cs_npc.cpp @@ -655,8 +655,17 @@ public: CreatureTemplate const* cInfo = target->GetCreatureTemplate(); uint32 faction = target->GetFaction(); uint32 npcflags = target->GetNpcFlags(); - uint32 mechanicImmuneMask = cInfo->MechanicImmuneMask; - uint32 spellSchoolImmuneMask = cInfo->SpellSchoolImmuneMask; + uint64 mechanicImmuneMask = 0; + uint32 spellSchoolImmuneMask = 0; + + if (CreatureImmunities const* immunities = sSpellMgr->GetCreatureImmunities(cInfo->CreatureImmunitiesId)) + { + mechanicImmuneMask = immunities->Mechanic.to_ullong(); + for (std::size_t j = 0; j < immunities->School.size(); ++j) + if (immunities->School[j]) + spellSchoolImmuneMask |= (1u << j); + } + uint32 displayid = target->GetDisplayId(); uint32 nativeid = target->GetNativeDisplayId(); uint32 entry = target->GetEntry(); @@ -673,6 +682,7 @@ public: int64 curRespawnDelay = target->GetRespawnTimeEx() - GameTime::GetGameTime().count(); if (curRespawnDelay < 0) curRespawnDelay = 0; + std::string curRespawnDelayStr = secsToTimeString(uint64(curRespawnDelay), true); std::string defRespawnDelayStr = secsToTimeString(target->GetRespawnDelay(), true); @@ -689,31 +699,19 @@ public: handler->PSendSysMessage(LANG_NPCINFO_POSITION, float(target->GetPositionX()), float(target->GetPositionY()), float(target->GetPositionZ())); handler->PSendSysMessage(LANG_NPCINFO_AIINFO, target->GetAIName(), target->GetScriptName()); - for (uint8 i = 0; i < NPCFLAG_COUNT; i++) - { - if (npcflags & npcFlagTexts[i].flag) - { - handler->PSendSysMessage(npcFlagTexts[i].text, npcFlagTexts[i].flag); - } - } + for (auto npcFlagText : npcFlagTexts) + if (npcflags & npcFlagText.flag) + handler->PSendSysMessage(npcFlagText.text, npcFlagText.flag); - handler->PSendSysMessage(LANG_NPCINFO_MECHANIC_IMMUNE, mechanicImmuneMask); + handler->PSendSysMessage(LANG_NPCINFO_MECHANIC_IMMUNE, Acore::StringFormat("0x{:X}", mechanicImmuneMask).c_str()); for (uint8 i = 1; i < MAX_MECHANIC; ++i) - { - if (mechanicImmuneMask & (1 << (mechanicImmunes[i].flag - 1))) - { + if (mechanicImmuneMask & (UI64LIT(1) << i)) handler->PSendSysMessage(mechanicImmunes[i].text, mechanicImmunes[i].flag); - } - } handler->PSendSysMessage(LANG_NPCINFO_SPELL_SCHOOL_IMMUNE, spellSchoolImmuneMask); - for (uint8 i = 0; i < MAX_SPELL_SCHOOL; ++i) - { - if (spellSchoolImmuneMask & (1 << spellSchoolImmunes[i].flag)) - { - handler->PSendSysMessage(spellSchoolImmunes[i].text, spellSchoolImmunes[i].flag); - } - } + for (auto spellSchoolImmune : spellSchoolImmunes) + if (spellSchoolImmuneMask & (1 << spellSchoolImmune.flag)) + handler->PSendSysMessage(spellSchoolImmune.text, spellSchoolImmune.flag); return true; } diff --git a/src/server/shared/SharedDefines.h b/src/server/shared/SharedDefines.h index 87b6c6ab3..23d31745d 100644 --- a/src/server/shared/SharedDefines.h +++ b/src/server/shared/SharedDefines.h @@ -420,7 +420,7 @@ enum SpellAttr1 : uint32 SPELL_ATTR1_TOGGLE_FAR_SIGHT = 0x00002000, // TITLE Farsight aura (client only) SPELL_ATTR1_TRACK_TARGET_IN_CHANNEL = 0x00004000, // TITLE Track target while channeling DESCRIPTION While channeling, adjust facing to face target SPELL_ATTR1_IMMUNITY_PURGES_EFFECT = 0x00008000, // TITLE Immunity cancels preapplied auras DESCRIPTION For immunity spells, cancel all auras that this spell would make you immune to when the spell is applied - SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS = 0x00010000, // TITLE Unaffected by school immunities DESCRIPTION Will not pierce Divine Shield, Ice Block and other full invulnerabilities + SPELL_ATTR1_IMMUNITY_TO_HOSTILE_AND_FRIENDLY_EFFECTS = 0x00010000, // TITLE Immunity to Hostile & Friendly Effects DESCRIPTION Immunity applied by this aura will also be checked for friendly spells (school immunity only) - used by Cyclone for example to cause friendly spells and healing over time to be immune SPELL_ATTR1_NO_AUTOCAST_AI = 0x00020000, // TITLE Cannot be autocast by pet DESCRIPTION (AI) SPELL_ATTR1_PREVENTS_ANIM = 0x00040000, // TITLE NYI, auras apply UNIT_FLAG_PREVENT_EMOTES_FROM_CHAT_TEXT SPELL_ATTR1_EXCLUDE_CASTER = 0x00080000, // TITLE Cannot be self-cast @@ -1285,7 +1285,7 @@ enum AuraStateType //AURA_STATE_UNKNOWN6 = 6, // | not used AURA_STATE_HUNTER_PARRY = 7, // C | //AURA_STATE_UNKNOWN7 = 7, // c | creature cheap shot / focused bursts spells - //AURA_STATE_UNKNOWN8 = 8, // t| test spells + AURA_STATE_BANISHED = 8, // c t| banished //AURA_STATE_UNKNOWN9 = 9, // | AURA_STATE_WARRIOR_VICTORY_RUSH = 10, // C | warrior victory rush //AURA_STATE_UNKNOWN11 = 11, // C t| 60348 - Maelstrom Ready!, test spells @@ -1342,17 +1342,34 @@ enum Mechanics : uint32 MECHANIC_IMMUNE_SHIELD = 29, // Divine (Blessing) Shield/Protection and Ice Block MECHANIC_SAPPED = 30, MECHANIC_ENRAGED = 31, - MAX_MECHANIC = 32 // SKIP + MECHANIC_WOUNDED = 32, + MECHANIC_INFECTED_2 = 33, + MECHANIC_INFECTED_3 = 34, + MECHANIC_INFECTED_4 = 35, + MECHANIC_TAUNTED = 36, + MAX_MECHANIC = 37 // SKIP }; // Used for spell 42292 Immune Movement Impairment and Loss of Control (0x49967ca6) -#define IMMUNE_TO_MOVEMENT_IMPAIRMENT_AND_LOSS_CONTROL_MASK (\ - (1<speed_run = 1.14286f; _fakeCreatureTemplate->speed_swim = 1.0f; _fakeCreatureTemplate->speed_flight = 1.0f; - _fakeCreatureTemplate->scale = 1.0f; _fakeCreatureTemplate->DamageModifier = 1.0f; _fakeCreatureTemplate->BaseAttackTime = 2000; _fakeCreatureTemplate->RangeAttackTime = 2000; diff --git a/src/test/server/game/Spells/SpellImmunityTest.cpp b/src/test/server/game/Spells/SpellImmunityTest.cpp new file mode 100644 index 000000000..342e2818f --- /dev/null +++ b/src/test/server/game/Spells/SpellImmunityTest.cpp @@ -0,0 +1,330 @@ +/* + * This file is part of the AzerothCore Project. See AUTHORS file for Copyright information + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/** + * @file SpellImmunityTest.cpp + * @brief Tests for spell immunity mechanics + */ + +#include "gtest/gtest.h" + +#include +#include +#include "Unit.h" // needed for SpellSchoolMask and mask helper + +namespace +{ + enum EffectType : uint8_t + { + EFFECT_NONE, + EFFECT_SCHOOL_DAMAGE, + EFFECT_HEALTH_LEECH, + EFFECT_POWER_DRAIN, + EFFECT_POWER_BURN, + EFFECT_NORMALIZED_WEAPON_DMG, + EFFECT_WEAPON_PERCENT_DAMAGE, + EFFECT_APPLY_AURA, + EFFECT_DUMMY + }; + + enum AuraType : uint8_t + { + AURA_NONE, + AURA_MOD_DECREASE_SPEED, + AURA_PERIODIC_DAMAGE, + AURA_TRANSFORM, + AURA_MOD_STUN + }; + + struct EffectDesc + { + EffectType effect = EFFECT_NONE; + AuraType aura = AURA_NONE; + }; + + struct SpellDesc + { + std::array effects{}; + }; + + bool IsDamageEffect(EffectType effect) + { + switch (effect) + { + case EFFECT_SCHOOL_DAMAGE: + case EFFECT_HEALTH_LEECH: + case EFFECT_POWER_DRAIN: + case EFFECT_POWER_BURN: + case EFFECT_NORMALIZED_WEAPON_DMG: + case EFFECT_WEAPON_PERCENT_DAMAGE: + return true; + default: + return false; + } + } + + bool HasOnlyDamageEffects(SpellDesc const& spell) + { + bool hasAny = false; + + for (EffectDesc const& e : spell.effects) + { + if (e.effect == EFFECT_NONE) + continue; + + hasAny = true; + if (!IsDamageEffect(e.effect)) + return false; + } + + return hasAny; + } + + // Helper to classify spells which apply a stun aura + bool IsStunSpell(SpellDesc const& spell) + { + for (EffectDesc const& e : spell.effects) + { + if (e.effect == EFFECT_APPLY_AURA && e.aura == AURA_MOD_STUN) + return true; + } + return false; + } + + // 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 + // a spell like Lethargy. + SpellMissInfo ComputeSpellHitResult(bool isImmunedToSpell, bool isImmunedToDamage, + SpellDesc const& spell, bool immuneToStun = false) + { + // Mirrors current core ordering: + // 1) full spell immunity check + // 2) mechanic immunity check (e.g. bladestorm vs stun) + // 3) damage immunity only for damage-only spells + if (isImmunedToSpell) + return SPELL_MISS_IMMUNE; + + if (immuneToStun && IsStunSpell(spell)) + return SPELL_MISS_IMMUNE; + + if (HasOnlyDamageEffects(spell) && isImmunedToDamage) + return SPELL_MISS_IMMUNE; + + return SPELL_MISS_NONE; + } + + struct EffectApplyResult + { + bool damageApplied = false; + bool slowApplied = false; + }; + + EffectApplyResult ApplyEffectsWithMovementImmunity(SpellDesc const& spell, bool immuneToMovementImpairing) + { + EffectApplyResult result; + + for (EffectDesc const& e : spell.effects) + { + if (e.effect == EFFECT_NONE) + continue; + + if (IsDamageEffect(e.effect)) + result.damageApplied = true; + + if (e.effect == EFFECT_APPLY_AURA && e.aura == AURA_MOD_DECREASE_SPEED) + { + if (!immuneToMovementImpairing) + result.slowApplied = true; + } + } + + return result; + } + + SpellDesc MakeDamageOnlySpell() + { + SpellDesc spell; + spell.effects[0] = { EFFECT_SCHOOL_DAMAGE, AURA_NONE }; + return spell; + } + + SpellDesc MakeFrostboltLikeSpell() + { + SpellDesc spell; + spell.effects[0] = { EFFECT_SCHOOL_DAMAGE, AURA_NONE }; + spell.effects[1] = { EFFECT_APPLY_AURA, AURA_MOD_DECREASE_SPEED }; + return spell; + } + + SpellDesc MakeCycloneLikeSpell() + { + SpellDesc spell; + spell.effects[0] = { EFFECT_APPLY_AURA, AURA_TRANSFORM }; + return spell; + } + + SpellDesc MakeSlowOnlySpell() + { + // represents effects such as Frost Trap or Desecration, which only slow + SpellDesc spell; + spell.effects[0] = { EFFECT_APPLY_AURA, AURA_MOD_DECREASE_SPEED }; + return spell; + } +} + +TEST(SpellImmunityTest, HasOnlyDamageEffects_TrueForPureDamage) +{ + SpellDesc spell = MakeDamageOnlySpell(); + EXPECT_TRUE(HasOnlyDamageEffects(spell)); +} + +TEST(SpellImmunityTest, HasOnlyDamageEffects_FalseForDamagePlusAura) +{ + SpellDesc spell = MakeFrostboltLikeSpell(); + EXPECT_FALSE(HasOnlyDamageEffects(spell)); +} + +TEST(SpellImmunityTest, SpellImmunity_BlocksAllSpells) +{ + SpellDesc damageOnly = MakeDamageOnlySpell(); + SpellDesc cycloneLike = MakeCycloneLikeSpell(); + + EXPECT_EQ(ComputeSpellHitResult(true, false, damageOnly), SPELL_MISS_IMMUNE); + EXPECT_EQ(ComputeSpellHitResult(true, false, cycloneLike), SPELL_MISS_IMMUNE); +} + +TEST(SpellImmunityTest, DamageImmunity_BlocksDamageOnlySpell) +{ + SpellDesc damageOnly = MakeDamageOnlySpell(); + + EXPECT_EQ(ComputeSpellHitResult(false, true, damageOnly), SPELL_MISS_IMMUNE); +} + +// Specific case for spell ID 16621 "Self Invulnerability". +// This aura grants immunity to melee/physical damage only. A rogue +// using Sinister Strike (a physical melee attack) should be completely +// blocked by the effect. The test uses the same simplified damage-only +// spell description as above but documents the physical context. +TEST(SpellImmunityTest, SelfInvulnerability_BlocksMeleeDamage) +{ + SpellDesc meleeAttack = MakeDamageOnlySpell(); + // simulate physical damage coming from a rogue melee ability + + EXPECT_EQ(ComputeSpellHitResult(false, true, meleeAttack), SPELL_MISS_IMMUNE); +} + +TEST(SpellImmunityTest, DamageImmunity_DoesNotMissMixedSpell) +{ + // This is the key fix: damage immunity must not force SPELL_MISS_IMMUNE + // for mixed spells that include non-damage effects. + SpellDesc frostboltLike = MakeFrostboltLikeSpell(); + + EXPECT_EQ(ComputeSpellHitResult(false, true, frostboltLike), SPELL_MISS_NONE); +} + +TEST(SpellImmunityTest, HandOfFreedomStyle_MovementImmunity_AllowsDamageBlocksSlow) +{ + SpellDesc frostboltLike = MakeFrostboltLikeSpell(); + + EffectApplyResult result = ApplyEffectsWithMovementImmunity(frostboltLike, true); + + EXPECT_TRUE(result.damageApplied); + EXPECT_FALSE(result.slowApplied); +} + +TEST(SpellImmunityTest, NoMovementImmunity_FrostboltStyle_AppliesDamageAndSlow) +{ + SpellDesc frostboltLike = MakeFrostboltLikeSpell(); + + EffectApplyResult result = ApplyEffectsWithMovementImmunity(frostboltLike, false); + + EXPECT_TRUE(result.damageApplied); + EXPECT_TRUE(result.slowApplied); +} + +TEST(SpellImmunityTest, CycloneLikeSpell_DivineShieldStyle_Immune) +{ + SpellDesc cycloneLike = MakeCycloneLikeSpell(); + + EXPECT_EQ(ComputeSpellHitResult(true, false, cycloneLike), SPELL_MISS_IMMUNE); +} + +// Regression test for issue #10671: +// Divine Shield (full spell immunity) should block purely slowing spells +// such as Hunter Frost Trap or DK Desecration. Previously the effect was +// applied because the core only checked damage-only spells when deciding +// immunity based on damage or spell state. +TEST(SpellImmunityTest, SpellImmunity_BlocksSlowOnlySpell) +{ + SpellDesc slowOnly = MakeSlowOnlySpell(); + EXPECT_EQ(ComputeSpellHitResult(true, false, slowOnly), SPELL_MISS_IMMUNE); +} + +// Ensure that damage-only immunity (e.g. from Hand of Protection) does not +// accidentally prevent slow-only spells. This covers the regression when +// Divine Shield was incorrectly modelled as damage immunity only. +TEST(SpellImmunityTest, DamageImmunity_DoesNotBlockSlowOnlySpell) +{ + SpellDesc slowOnly = MakeSlowOnlySpell(); + EXPECT_EQ(ComputeSpellHitResult(false, true, slowOnly), SPELL_MISS_NONE); +} + +// New coverage for school-mask logic. These exercises the helper used by +// Unit::IsImmunedToDamage to ensure broad masks are handled correctly. +TEST(SpellImmunityTest, ImmunityMask_PartialOverlapDoesNotCount) +{ + SpellSchoolMask immune = SPELL_SCHOOL_MASK_FROST; + SpellSchoolMask checkAll = SPELL_SCHOOL_MASK_ALL; + + // a frost-only immunity should *not* make you immune to all damage + EXPECT_FALSE(Unit::IsImmuneMaskFully(immune, checkAll)); +} + +TEST(SpellImmunityTest, ImmunityMask_FullCoverageAccepted) +{ + SpellSchoolMask immune = SPELL_SCHOOL_MASK_MAGIC; // holy+spell + SpellSchoolMask checkMagic = SPELL_SCHOOL_MASK_MAGIC; + SpellSchoolMask checkSpell = SPELL_SCHOOL_MASK_SPELL; + + EXPECT_TRUE(Unit::IsImmuneMaskFully(immune, checkMagic)); + EXPECT_TRUE(Unit::IsImmuneMaskFully(immune, checkSpell)); +} + +TEST(SpellImmunityTest, ImmunityMask_SupersetMatches) +{ + SpellSchoolMask immune = SPELL_SCHOOL_MASK_ALL; + SpellSchoolMask checkMagic = SPELL_SCHOOL_MASK_MAGIC; + + EXPECT_TRUE(Unit::IsImmuneMaskFully(immune, checkMagic)); +} + +// Bladestorm grants a mechanic immunity mask which includes stuns (e.g. +// Lethargy 69133). The following test mirrors that behaviour by +// modelling a simple spell that applies a stun aura and exercising the +// new `immuneToStun` flag in ComputeSpellHitResult. +TEST(SpellImmunityTest, Bladestorm_ImmuneToStun) +{ + SpellDesc stunSpell; + stunSpell.effects[0] = {EFFECT_APPLY_AURA, AURA_MOD_STUN}; + + // without any special immunity the stun should land + EXPECT_EQ(ComputeSpellHitResult(false, false, stunSpell), SPELL_MISS_NONE); + + // with a bladestorm‑style stun immunity it is blocked + EXPECT_EQ(ComputeSpellHitResult(false, false, stunSpell, true), SPELL_MISS_IMMUNE); +}