diff --git a/Source/Game/Entities/Creature/Creature.cs b/Source/Game/Entities/Creature/Creature.cs index 9d9e04cc7..ee66d8cf8 100644 --- a/Source/Game/Entities/Creature/Creature.cs +++ b/Source/Game/Entities/Creature/Creature.cs @@ -2075,12 +2075,12 @@ namespace Game.Entities ApplySpellImmune(placeholderSpellId, SpellImmunity.School, 1u << i, true); } - public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster) + public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster, bool requireImmunityPurgesEffectAttribute = false) { if (GetCreatureTemplate().CreatureType == CreatureType.Mechanical && spellEffectInfo.IsEffect(SpellEffectName.Heal)) return true; - return base.IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster); + return base.IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute); } public bool IsElite() diff --git a/Source/Game/Entities/Player/Player.cs b/Source/Game/Entities/Player/Player.cs index 2e03f5fb3..272776058 100644 --- a/Source/Game/Entities/Player/Player.cs +++ b/Source/Game/Entities/Player/Player.cs @@ -3116,7 +3116,7 @@ namespace Game.Entities return false; } - public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster) + public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster, bool requireImmunityPurgesEffectAttribute = false) { // players are immune to taunt (the aura and the spell effect). if (spellEffectInfo.IsAura(AuraType.ModTaunt)) @@ -3125,7 +3125,7 @@ namespace Game.Entities if (spellEffectInfo.IsEffect(SpellEffectName.AttackMe)) return true; - return base.IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster); + return base.IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute); } void RegenerateAll() diff --git a/Source/Game/Entities/Totem.cs b/Source/Game/Entities/Totem.cs index e16b38b3b..524600ef6 100644 --- a/Source/Game/Entities/Totem.cs +++ b/Source/Game/Entities/Totem.cs @@ -144,7 +144,7 @@ namespace Game.Entities AddObjectToRemoveList(); } - public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster) + public override bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster, bool requireImmunityPurgesEffectAttribute = false) { // immune to all positive spells, except of stoneclaw totem absorb and sentry totem bind sight // totems positive spells have unit_caster target @@ -165,7 +165,7 @@ namespace Game.Entities break; } - return base.IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster); + return base.IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute); } public uint GetSpell(byte slot = 0) { return m_spells[slot]; } diff --git a/Source/Game/Entities/Unit/Unit.Spells.cs b/Source/Game/Entities/Unit/Unit.Spells.cs index 0af8aac53..33fe9e9d6 100644 --- a/Source/Game/Entities/Unit/Unit.Spells.cs +++ b/Source/Game/Entities/Unit/Unit.Spells.cs @@ -1216,14 +1216,31 @@ namespace Game.Entities } } } - public bool IsImmunedToSpell(SpellInfo spellInfo, WorldObject caster) + + public bool IsImmunedToSpell(SpellInfo spellInfo, WorldObject caster, bool requireImmunityPurgesEffectAttribute = false) { if (spellInfo == null) return false; + bool hasImmunity(MultiMap container, uint key) + { + var range = container.LookupByKey(key); + if (!requireImmunityPurgesEffectAttribute) + return !range.Empty(); + + return range.Any(entry => + { + SpellInfo immunitySourceSpell = Global.SpellMgr.GetSpellInfo(entry, Difficulty.None); + if (immunitySourceSpell != null && immunitySourceSpell.HasAttribute(SpellAttr1.ImmunityPurgesEffect)) + return true; + + return false; + }); + } + // Single spell immunity. var idList = m_spellImmune[(int)SpellImmunity.Id]; - if (idList.ContainsKey(spellInfo.Id)) + if (hasImmunity(idList, spellInfo.Id)) return true; if (spellInfo.HasAttribute(SpellAttr0.NoImmunities)) @@ -1233,7 +1250,7 @@ namespace Game.Entities if (dispel != 0) { var dispelList = m_spellImmune[(int)SpellImmunity.Dispel]; - if (dispelList.ContainsKey(dispel)) + if (hasImmunity(dispelList, dispel)) return true; } @@ -1242,7 +1259,7 @@ namespace Game.Entities if (mechanic != 0) { var mechanicList = m_spellImmune[(int)SpellImmunity.Mechanic]; - if (mechanicList.ContainsKey(mechanic)) + if (hasImmunity(mechanicList, mechanic)) return true; } @@ -1254,7 +1271,7 @@ namespace Game.Entities if (!spellEffectInfo.IsEffect()) continue; - if (!IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster)) + if (!IsImmunedToSpellEffect(spellInfo, spellEffectInfo, caster, requireImmunityPurgesEffectAttribute)) { immuneToAllEffects = false; break; @@ -1280,7 +1297,8 @@ namespace Game.Entities SpellInfo immuneSpellInfo = Global.SpellMgr.GetSpellInfo(pair.Value, GetMap().GetDifficultyID()); // Consider the school immune if any of these conditions are not satisfied. // In case of no immuneSpellInfo, ignore that condition and check only the other conditions - if ((immuneSpellInfo != null && !immuneSpellInfo.IsPositive()) || !spellInfo.IsPositive() || caster == null || !IsFriendlyTo(caster)) + if ((immuneSpellInfo != null && !immuneSpellInfo.IsPositive() && (!requireImmunityPurgesEffectAttribute || immuneSpellInfo.HasAttribute(SpellAttr1.ImmunityPurgesEffect))) + || !spellInfo.IsPositive() || caster == null || !IsFriendlyTo(caster)) if (!spellInfo.CanPierceImmuneAura(immuneSpellInfo)) schoolImmunityMask |= pair.Key; } @@ -1300,6 +1318,7 @@ namespace Game.Entities return mask; } + public uint GetDamageImmunityMask() { uint mask = 0; @@ -1309,6 +1328,7 @@ namespace Game.Entities return mask; } + public uint GetMechanicImmunityMask() { uint mask = 0; @@ -1318,24 +1338,42 @@ namespace Game.Entities return mask; } - public virtual bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster) + + public virtual bool IsImmunedToSpellEffect(SpellInfo spellInfo, SpellEffectInfo spellEffectInfo, WorldObject caster, bool requireImmunityPurgesEffectAttribute = false) { if (spellInfo == null) return false; - if (spellEffectInfo == null || !spellEffectInfo.IsEffect()) + if (spellInfo.HasAttribute(SpellAttr0.NoImmunities)) return false; + bool hasImmunity(MultiMap container, uint key) + { + var range = container.LookupByKey(key); + if (!requireImmunityPurgesEffectAttribute) + return !range.Empty(); + + return range.Any(entry => + { + var immunitySourceSpell = Global.SpellMgr.GetSpellInfo(entry, Difficulty.None); + if (immunitySourceSpell != null) + if (immunitySourceSpell.HasAttribute(SpellAttr1.ImmunityPurgesEffect)) + return true; + + return false; + }); + } + // If m_immuneToEffect type contain this effect type, IMMUNE effect. var effectList = m_spellImmune[(int)SpellImmunity.Effect]; - if (effectList.ContainsKey((uint)spellEffectInfo.Effect)) + if (hasImmunity(effectList, (uint)spellEffectInfo.Effect)) return true; uint mechanic = (uint)spellEffectInfo.Mechanic; if (mechanic != 0) { var mechanicList = m_spellImmune[(int)SpellImmunity.Mechanic]; - if (mechanicList.ContainsKey(mechanic)) + if (hasImmunity(mechanicList, mechanic)) return true; } @@ -1345,7 +1383,7 @@ namespace Game.Entities if (!spellInfo.HasAttribute(SpellAttr3.AlwaysHit)) { var list = m_spellImmune[(int)SpellImmunity.State]; - if (list.ContainsKey(aura)) + if (hasImmunity(list, (uint)aura)) return true; } @@ -1362,6 +1400,7 @@ namespace Game.Entities return false; } + public bool IsImmunedToDamage(SpellSchoolMask schoolMask) { if (schoolMask == SpellSchoolMask.None) @@ -1379,6 +1418,7 @@ namespace Game.Entities return false; } + public bool IsImmunedToDamage(SpellInfo spellInfo) { if (spellInfo == null) @@ -2216,29 +2256,15 @@ namespace Game.Entities RemoveOwnedAura(pair, AuraRemoveMode.Death); } } + public void RemoveMovementImpairingAuras(bool withRoot) { if (withRoot) - RemoveAurasWithMechanic(1 << (int)Mechanics.Root); + RemoveAurasWithMechanic(1 << (int)Mechanics.Root, AuraRemoveMode.Default, 0, true); - // Snares - foreach (var pair in GetAppliedAuras()) - { - Aura aura = pair.Value.GetBase(); - if (aura.GetSpellInfo().Mechanic == Mechanics.Snare) - { - RemoveAura(pair); - continue; - } - - // turn off snare auras by setting amount to 0 - foreach (var spellEffectInfo in aura.GetSpellInfo().GetEffects()) - { - if (pair.Value.HasEffect(spellEffectInfo.EffectIndex) && spellEffectInfo.Mechanic == Mechanics.Snare) - aura.GetEffect(spellEffectInfo.EffectIndex).ChangeAmount(0); - } - } + RemoveAurasWithMechanic(1 << (int)Mechanics.Snare, AuraRemoveMode.Default, 0, false); } + public void RemoveAllAurasRequiringDeadTarget() { foreach (var app in GetAppliedAuras()) @@ -2930,22 +2956,26 @@ namespace Game.Entities UpdateInterruptMask(); } - public void RemoveAurasWithMechanic(uint mechanic_mask, AuraRemoveMode removemode = AuraRemoveMode.Default, uint except = 0) + public void RemoveAurasWithMechanic(uint mechanicMaskToRemove, AuraRemoveMode removeMode = AuraRemoveMode.Default, uint exceptSpellId = 0, bool withEffectMechanics = false) { - foreach (var app in GetAppliedAuras()) + RemoveAppliedAuras(aurApp => { - if (app.Value == null) - continue; - Aura aura = app.Value.GetBase(); - if (except == 0 || aura.GetId() != except) - { - if (Convert.ToBoolean(aura.GetSpellInfo().GetAllEffectsMechanicMask() & mechanic_mask)) - { - RemoveAura(app, removemode); - continue; - } - } - } + Aura aura = aurApp.GetBase(); + if (exceptSpellId != 0 && aura.GetId() == exceptSpellId) + return false; + + uint appliedMechanicMask = aura.GetSpellInfo().GetSpellMechanicMaskByEffectMask(aurApp.GetEffectMask()); + if ((appliedMechanicMask & mechanicMaskToRemove) == 0) + return false; + + // spell mechanic matches required mask for removal + if (((1 << (int)aura.GetSpellInfo().Mechanic) & mechanicMaskToRemove) != 0 || withEffectMechanics) + return true; + + // effect mechanic matches required mask for removal - don't remove, only update targets + aura.UpdateTargetMap(aura.GetCaster()); + return false; + }, removeMode); } public void RemoveAurasDueToSpellBySteal(uint spellId, ObjectGuid casterGUID, WorldObject stealer, int stolenCharges = 1) { @@ -3293,45 +3323,45 @@ namespace Game.Entities } } - public void RemoveAppliedAuras(Func check) + public void RemoveAppliedAuras(Func check, AuraRemoveMode removeMode = AuraRemoveMode.Default) { foreach (var pair in GetAppliedAuras()) { if (check(pair.Value)) - RemoveAura(pair); + RemoveAura(pair, removeMode); } } - public void RemoveOwnedAuras(Func check) + public void RemoveOwnedAuras(Func check, AuraRemoveMode removeMode = AuraRemoveMode.Default) { foreach (var pair in GetOwnedAuras()) { if (check(pair.Value)) - RemoveOwnedAura(pair); + RemoveOwnedAura(pair, removeMode); } } - void RemoveAppliedAuras(uint spellId, Func check) + void RemoveAppliedAuras(uint spellId, Func check, AuraRemoveMode removeMode = AuraRemoveMode.Default) { var list = m_appliedAuras.LookupByKey(spellId); foreach (var app in list) { if (check(app)) - RemoveAura(app); + RemoveAura(app, removeMode); } } - void RemoveOwnedAuras(uint spellId, Func check) + void RemoveOwnedAuras(uint spellId, Func check, AuraRemoveMode removeMode = AuraRemoveMode.Default) { var list = m_ownedAuras.LookupByKey(spellId); foreach (var aura in list) { if (check(aura)) - RemoveOwnedAura(aura); + RemoveOwnedAura(aura, removeMode); } } - public void RemoveAurasByType(AuraType auraType, Func check) + public void RemoveAurasByType(AuraType auraType, Func check, AuraRemoveMode removeMode = AuraRemoveMode.Default) { var list = m_modAuras[auraType]; for (var i = 0; i < list.Count; ++i) @@ -3343,7 +3373,7 @@ namespace Game.Entities if (check(aurApp)) { uint removedAuras = m_removedAurasCount; - RemoveAura(aurApp); + RemoveAura(aurApp, removeMode); if (m_removedAurasCount > removedAuras + 1) i = 0; } @@ -3387,6 +3417,7 @@ namespace Game.Entities RemoveAura(pair); } } + public void RemoveAllAuras() { // this may be a dead loop if some events on aura remove will continiously apply aura on remove diff --git a/Source/Game/Spells/Auras/Aura.cs b/Source/Game/Spells/Auras/Aura.cs index 82ea458e1..fc023efbd 100644 --- a/Source/Game/Spells/Auras/Aura.cs +++ b/Source/Game/Spells/Auras/Aura.cs @@ -189,7 +189,7 @@ namespace Game.Spells SetNeedClientUpdate(); } - public void UpdateApplyEffectMask(uint newEffMask) + public void UpdateApplyEffectMask(uint newEffMask, bool canHandleNewEffects) { if (_effectsToApply == newEffMask) return; @@ -204,20 +204,23 @@ namespace Game.Spells return; } + // update real effects only if they were applied already for (uint i = 0; i < SpellConst.MaxEffects; ++i) { - // update real effects only if they were applied already - if ((_effectMask & (1 << (int)i)) == 0) - continue; - - if ((removeEffMask & (1 << (int)i)) != 0) + if (HasEffect(i) && (removeEffMask & (1 << (int)i)) != 0) _HandleEffect(i, false); - - if ((addEffMask & (1 << (int)i)) != 0) - _HandleEffect(i, true); } _effectsToApply = newEffMask; + + if (canHandleNewEffects) + { + for (uint i = 0; i < SpellConst.MaxEffects; ++i) + { + if ((addEffMask & (1 << (int)i)) != 0) + _HandleEffect(i, true); + } + } } public void SetNeedClientUpdate() @@ -499,7 +502,7 @@ namespace Game.Spells } } - void UpdateTargetMap(Unit caster, bool apply = true) + public void UpdateTargetMap(Unit caster, bool apply = true) { if (IsRemoved()) return; @@ -524,12 +527,19 @@ namespace Game.Spells { // needs readding - remove now, will be applied in next update cycle // (dbcs do not have auras which apply on same type of targets but have different radius, so this is not really needed) - if (!CanBeAppliedOn(app.Value.GetTarget())) + if (app.Value.GetTarget().IsImmunedToSpell(GetSpellInfo(), caster, true) || !CanBeAppliedOn(app.Value.GetTarget())) { targetsToRemove.Add(app.Value.GetTarget()); continue; } + // check target immunities (for existing targets) + foreach (var spellEffectInfo in GetSpellInfo().GetEffects()) + if (app.Value.GetTarget().IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster, true)) + existing &= ~(uint)(1 << (int)spellEffectInfo.EffectIndex); + + targets[app.Value.GetTarget()] = existing; + // needs to add/remove effects from application, don't remove from map so it gets updated if (app.Value.GetEffectMask() != existing) continue; @@ -542,18 +552,19 @@ namespace Game.Spells // register auras for units foreach (var unit in targets.Keys.ToList()) { - var value = targets[unit]; - bool addUnit = true; // check target immunities - foreach (var spellEffectInfo in GetSpellInfo().GetEffects()) + AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); + if (aurApp == null) { - if (unit.IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster)) - value &= ~(1u << (int)spellEffectInfo.EffectIndex); + // check target immunities (for new targets) + foreach (var spellEffectInfo in GetSpellInfo().GetEffects()) + if (unit.IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster)) + targets[unit] &= ~(uint)(1 << (int)spellEffectInfo.EffectIndex); + + if (targets[unit] == 0 || unit.IsImmunedToSpell(GetSpellInfo(), caster) || !CanBeAppliedOn(unit)) + addUnit = false; } - - if (value == 0 || unit.IsImmunedToSpell(GetSpellInfo(), caster) || !CanBeAppliedOn(unit)) - addUnit = false; if (addUnit && !unit.IsHighestExclusiveAura(this, true)) addUnit = false; @@ -596,14 +607,13 @@ namespace Game.Spells unit.GetName(), unit.IsInWorld ? (int)unit.GetMap().GetId() : -1); } - AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); if (aurApp != null) { - // aura is already applied, this means we need to update effects of current application - unit._UnapplyAura(aurApp, AuraRemoveMode.Default); + aurApp.UpdateApplyEffectMask(targets[unit], true); // aura is already applied, this means we need to update effects of current application + targets.Remove(unit); } - - unit._CreateAuraApplication(this, value); + else + unit._CreateAuraApplication(this, targets[unit]); } } @@ -2550,7 +2560,7 @@ namespace Game.Spells { AuraApplication aurApp = foundAura.GetApplicationOfTarget(unit.GetGUID()); if (aurApp != null) - aurApp.UpdateApplyEffectMask(effMask); + aurApp.UpdateApplyEffectMask(effMask, false); } return foundAura; diff --git a/Source/Game/Spells/SpellEffects.cs b/Source/Game/Spells/SpellEffects.cs index 9e81a5a8b..c2ee2d153 100644 --- a/Source/Game/Spells/SpellEffects.cs +++ b/Source/Game/Spells/SpellEffects.cs @@ -658,7 +658,7 @@ namespace Game.Spells if (aurApp == null) aurApp = unitTarget._CreateAuraApplication(spellAura, 1u << (int)effectInfo.EffectIndex); else - aurApp.UpdateApplyEffectMask(aurApp.GetEffectsToApply() | 1u << (int)effectInfo.EffectIndex); + aurApp.UpdateApplyEffectMask(aurApp.GetEffectsToApply() | 1u << (int)effectInfo.EffectIndex, false); } [SpellEffectHandler(SpellEffectName.UnlearnSpecialization)] diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index 0cbf56243..52c8a104d 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -2439,13 +2439,23 @@ namespace Game.Spells if (Convert.ToBoolean(mechanicImmunity & (1 << (int)i))) target.ApplySpellImmune(Id, SpellImmunity.Mechanic, i, apply); - if (apply && HasAttribute(SpellAttr1.ImmunityPurgesEffect)) + if (HasAttribute(SpellAttr1.ImmunityPurgesEffect)) { // exception for purely snare mechanic (eg. hands of freedom)! - if (mechanicImmunity == (1 << (int)Mechanics.Snare)) - target.RemoveMovementImpairingAuras(false); - else + if (apply) target.RemoveAurasWithMechanic(mechanicImmunity, AuraRemoveMode.Default, Id); + else + { + target.RemoveAppliedAuras(aurApp => + { + Aura aura = aurApp.GetBase(); + if ((aura.GetSpellInfo().GetAllEffectsMechanicMask() & mechanicImmunity) != 0) + aura.UpdateTargetMap(aura.GetCaster()); + + // only update targets, don't remove anything + return false; + }); + } } }