/* * Copyright (C) 2012-2020 CypherCore * * 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 3 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 . */ using Framework.Constants; using Framework.Dynamic; using Game.BattleGrounds; using Game.Networking.Packets; using Game.Spells; using System; using System.Collections.Generic; using System.Linq; namespace Game.Entities { public partial class Unit { public virtual bool HasSpell(uint spellId) { return false; } public void SetInstantCast(bool set) { _instantCast = set; } public bool CanInstantCast() { return _instantCast; } // function uses real base points (typically value - 1) public int CalculateSpellDamage(Unit target, SpellInfo spellProto, uint effect_index, int? basePoints = null, uint castItemId = 0, int itemLevel = -1) { SpellEffectInfo effect = spellProto.GetEffect(effect_index); return effect != null ? effect.CalcValue(this, basePoints, target, castItemId, itemLevel) : 0; } public int CalculateSpellDamage(Unit target, SpellInfo spellProto, uint effect_index, out float variance, int? basePoints = null, uint castItemId = 0, int itemLevel = -1) { SpellEffectInfo effect = spellProto.GetEffect(effect_index); variance = 0.0f; return effect != null ? effect.CalcValue(out variance, this, basePoints, target, castItemId, itemLevel) : 0; } public int SpellBaseDamageBonusDone(SpellSchoolMask schoolMask) { Player thisPlayer = ToPlayer(); if (thisPlayer) { float overrideSP = thisPlayer.m_activePlayerData.OverrideSpellPowerByAPPercent; if (overrideSP > 0.0f) return (int)(MathFunctions.CalculatePct(GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), overrideSP) + 0.5f); } int DoneAdvertisedBenefit = GetTotalAuraModifierByMiscMask(AuraType.ModDamageDone, (int)schoolMask); if (IsTypeId(TypeId.Player)) { // Base value DoneAdvertisedBenefit += (int)ToPlayer().GetBaseSpellPowerBonus(); // Check if we are ever using mana - PaperDollFrame.lua if (GetPowerIndex(PowerType.Mana) != (uint)PowerType.Max) DoneAdvertisedBenefit += Math.Max(0, (int)GetStat(Stats.Intellect)); // spellpower from intellect // Damage bonus from stats var mDamageDoneOfStatPercent = GetAuraEffectsByType(AuraType.ModSpellDamageOfStatPercent); foreach (var eff in mDamageDoneOfStatPercent) { if (Convert.ToBoolean(eff.GetMiscValue() & (int)schoolMask)) { // stat used stored in miscValueB for this aura Stats usedStat = (Stats)eff.GetMiscValueB(); DoneAdvertisedBenefit += (int)MathFunctions.CalculatePct(GetStat(usedStat), eff.GetAmount()); } } } return DoneAdvertisedBenefit; } public int SpellBaseDamageBonusTaken(SpellSchoolMask schoolMask) { return GetTotalAuraModifierByMiscMask(AuraType.ModDamageTaken, (int)schoolMask); } public uint SpellDamageBonusDone(Unit victim, SpellInfo spellProto, uint pdamage, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) { if (spellProto == null || victim == null || damagetype == DamageEffectType.Direct) return pdamage; // Some spells don't benefit from done mods if (spellProto.HasAttribute(SpellAttr3.NoDoneBonus)) return pdamage; // For totems get damage bonus from owner if (IsTypeId(TypeId.Unit) && IsTotem()) { Unit owner = GetOwner(); if (owner != null) return owner.SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effect, stack); } int DoneTotal = 0; // Done fixed damage bonus auras int DoneAdvertisedBenefit = SpellBaseDamageBonusDone(spellProto.GetSchoolMask()); // Pets just add their bonus damage to their spell damage // note that their spell damage is just gain of their own auras if (HasUnitTypeMask(UnitTypeMask.Guardian)) DoneAdvertisedBenefit += ((Guardian)this).GetBonusDamage(); // Check for table values if (effect.BonusCoefficientFromAP > 0.0f) { float ApCoeffMod = effect.BonusCoefficientFromAP; Player modOwner = GetSpellModOwner(); if (modOwner) { ApCoeffMod *= 100.0f; modOwner.ApplySpellMod(spellProto, SpellModOp.BonusCoefficient, ref ApCoeffMod); ApCoeffMod /= 100.0f; } WeaponAttackType attType = WeaponAttackType.BaseAttack; if ((spellProto.IsRangedWeaponSpell() && spellProto.DmgClass != SpellDmgClass.Melee)) attType = WeaponAttackType.RangedAttack; if (spellProto.HasAttribute(SpellAttr3.ReqOffhand) && !spellProto.HasAttribute(SpellAttr3.MainHand)) attType = WeaponAttackType.OffAttack; float APbonus = (float)(victim.GetTotalAuraModifier(attType != WeaponAttackType.RangedAttack ? AuraType.MeleeAttackPowerAttackerBonus : AuraType.RangedAttackPowerAttackerBonus)); APbonus += GetTotalAttackPowerValue(attType); DoneTotal += (int)(stack * ApCoeffMod * APbonus); } // Default calculation float coeff = effect.BonusCoefficient; if (DoneAdvertisedBenefit != 0) { Player modOwner = GetSpellModOwner(); if (modOwner) { coeff *= 100.0f; modOwner.ApplySpellMod(spellProto, SpellModOp.BonusCoefficient, ref coeff); coeff /= 100.0f; } DoneTotal += (int)(DoneAdvertisedBenefit * coeff * stack); } // Done Percentage for DOT is already calculated, no need to do it again. The percentage mod is applied in Aura.HandleAuraSpecificMods. float tmpDamage = ((int)pdamage + DoneTotal) * (damagetype == DamageEffectType.DOT ? 1.0f : SpellDamagePctDone(victim, spellProto, damagetype)); // apply spellmod to Done damage (flat and pct) Player _modOwner = GetSpellModOwner(); if (_modOwner) { if (damagetype == DamageEffectType.DOT) _modOwner.ApplySpellMod(spellProto, SpellModOp.PeriodicHealingAndDamage, ref tmpDamage); else _modOwner.ApplySpellMod(spellProto, SpellModOp.HealingAndDamage, ref tmpDamage); } return (uint)Math.Max(tmpDamage, 0.0f); } public float SpellDamagePctDone(Unit victim, SpellInfo spellProto, DamageEffectType damagetype) { if (spellProto == null || !victim || damagetype == DamageEffectType.Direct) return 1.0f; // Some spells don't benefit from pct done mods if (spellProto.HasAttribute(SpellAttr6.NoDonePctDamageMods)) return 1.0f; // For totems pct done mods are calculated when its calculation is run on the player in SpellDamageBonusDone. if (IsTypeId(TypeId.Unit) && IsTotem()) return 1.0f; // Done total percent damage auras float DoneTotalMod = 1.0f; // Pet damage? if (IsTypeId(TypeId.Unit) && !IsPet()) DoneTotalMod *= ToCreature().GetSpellDamageMod(ToCreature().GetCreatureTemplate().Rank); // Versatility Player modOwner = GetSpellModOwner(); if (modOwner) MathFunctions.AddPct(ref DoneTotalMod, modOwner.GetRatingBonusValue(CombatRating.VersatilityDamageDone) + modOwner.GetTotalAuraModifier(AuraType.ModVersatility)); float maxModDamagePercentSchool = 0.0f; Player thisPlayer = ToPlayer(); if (thisPlayer) { for (int i = 0; i < (int)SpellSchools.Max; ++i) { if (Convert.ToBoolean((int)spellProto.GetSchoolMask() & (1 << i))) maxModDamagePercentSchool = Math.Max(maxModDamagePercentSchool, thisPlayer.m_activePlayerData.ModDamageDonePercent[i]); } } else maxModDamagePercentSchool = GetTotalAuraMultiplierByMiscMask(AuraType.ModDamagePercentDone, (uint)spellProto.GetSchoolMask()); DoneTotalMod *= maxModDamagePercentSchool; uint creatureTypeMask = victim.GetCreatureTypeMask(); DoneTotalMod *= GetTotalAuraMultiplierByMiscMask(AuraType.ModDamageDoneVersus, creatureTypeMask); // bonus against aurastate DoneTotalMod *= GetTotalAuraMultiplier(AuraType.ModDamageDoneVersusAurastate, aurEff => { if (victim.HasAuraState((AuraStateType)aurEff.GetMiscValue())) return true; return false; }); // Add SPELL_AURA_MOD_DAMAGE_DONE_FOR_MECHANIC percent bonus MathFunctions.AddPct(ref DoneTotalMod, GetTotalAuraModifierByMiscValue(AuraType.ModDamageDoneForMechanic, (int)spellProto.Mechanic)); // Custom scripted damage switch (spellProto.SpellFamilyName) { case SpellFamilyNames.Mage: // Ice Lance (no unique family flag) if (spellProto.Id == 228598) if (victim.HasAuraState(AuraStateType.Frozen, spellProto, this)) DoneTotalMod *= 3.0f; break; case SpellFamilyNames.Warlock: // Shadow Bite (30% increase from each dot) if (spellProto.SpellFamilyFlags[1].HasAnyFlag(0x00400000) && IsPet()) { uint count = victim.GetDoTsByCaster(GetOwnerGUID()); if (count != 0) MathFunctions.AddPct(ref DoneTotalMod, 30 * count); } // Drain Soul - increased damage for targets under 20% HP if (spellProto.Id == 198590) if (HasAuraState(AuraStateType.Wounded20Percent)) DoneTotalMod *= 2; break; } return DoneTotalMod; } public uint SpellDamageBonusTaken(Unit caster, SpellInfo spellProto, uint pdamage, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) { if (spellProto == null || damagetype == DamageEffectType.Direct) return pdamage; int TakenTotal = 0; float TakenTotalMod = 1.0f; float TakenTotalCasterMod = 0.0f; // Mod damage from spell mechanic uint mechanicMask = spellProto.GetAllEffectsMechanicMask(); if (mechanicMask != 0) { TakenTotalMod *= GetTotalAuraMultiplier(AuraType.ModMechanicDamageTakenPercent, aurEff => { if ((mechanicMask & (1 << aurEff.GetMiscValue())) != 0) return true; return false; }); } AuraEffect cheatDeath = GetAuraEffect(45182, 0); if (cheatDeath != null) if (cheatDeath.GetMiscValue().HasAnyFlag((int)SpellSchoolMask.Normal)) MathFunctions.AddPct(ref TakenTotalMod, cheatDeath.GetAmount()); // Spells with SPELL_ATTR4_FIXED_DAMAGE should only benefit from mechanic damage mod auras. if (!spellProto.HasAttribute(SpellAttr4.FixedDamage)) { // get all auras from caster that allow the spell to ignore resistance (sanctified wrath) TakenTotalCasterMod += GetTotalAuraModifierByMiscMask(AuraType.ModIgnoreTargetResist, (int)spellProto.GetSchoolMask()); // Versatility Player modOwner = GetSpellModOwner(); if (modOwner) { // only 50% of SPELL_AURA_MOD_VERSATILITY for damage reduction float versaBonus = modOwner.GetTotalAuraModifier(AuraType.ModVersatility) / 2.0f; MathFunctions.AddPct(ref TakenTotalMod, -(modOwner.GetRatingBonusValue(CombatRating.VersatilityDamageTaken) + versaBonus)); } // from positive and negative SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN // multiplicative bonus, for example Dispersion + Shadowform (0.10*0.85=0.085) TakenTotalMod *= GetTotalAuraMultiplierByMiscMask(AuraType.ModDamagePercentTaken, (uint)spellProto.GetSchoolMask()); // From caster spells TakenTotalMod *= GetTotalAuraMultiplier(AuraType.ModSchoolMaskDamageFromCaster, aurEff => { return aurEff.GetCasterGUID() == caster.GetGUID() && (aurEff.GetMiscValue() & (int)spellProto.GetSchoolMask()) != 0; }); TakenTotalMod *= GetTotalAuraMultiplier(AuraType.ModSpellDamageFromCaster, aurEff => { return aurEff.GetCasterGUID() == caster.GetGUID() && aurEff.IsAffectingSpell(spellProto); }); if (damagetype == DamageEffectType.DOT) TakenTotalMod *= GetTotalAuraMultiplier(AuraType.ModPeriodicDamageTaken, aurEff => (aurEff.GetMiscValue() & (uint)spellProto.GetSchoolMask()) != 0); int TakenAdvertisedBenefit = SpellBaseDamageBonusTaken(spellProto.GetSchoolMask()); // Check for table values float coeff = effect.BonusCoefficient; // Default calculation if (TakenAdvertisedBenefit != 0) { // level penalty still applied on Taken bonus - is it blizzlike? if (modOwner) { coeff *= 100.0f; modOwner.ApplySpellMod(spellProto, SpellModOp.BonusCoefficient, ref coeff); coeff /= 100.0f; } TakenTotal += (int)(TakenAdvertisedBenefit * coeff * stack); } } float tmpDamage = 0.0f; if (TakenTotalCasterMod != 0) { if (TakenTotal < 0) { if (TakenTotalMod < 1) tmpDamage = (((MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenTotal) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod)); else tmpDamage = (((float)(MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod) + TakenTotal) + MathFunctions.CalculatePct(pdamage, TakenTotalCasterMod)) * TakenTotalMod); } else if (TakenTotalMod < 1) tmpDamage = ((MathFunctions.CalculatePct(pdamage + TakenTotal, TakenTotalCasterMod) * TakenTotalMod) + MathFunctions.CalculatePct(pdamage + TakenTotal, TakenTotalCasterMod)); } if (tmpDamage == 0) tmpDamage = (pdamage + TakenTotal) * TakenTotalMod; return (uint)Math.Max(tmpDamage, 0.0f); } public uint SpellBaseHealingBonusDone(SpellSchoolMask schoolMask) { Player thisPlayer = ToPlayer(); if (thisPlayer != null) { float overrideSP = thisPlayer.m_activePlayerData.OverrideSpellPowerByAPPercent; if (overrideSP > 0.0f) return (uint)(MathFunctions.CalculatePct(GetTotalAttackPowerValue(WeaponAttackType.BaseAttack), overrideSP) + 0.5f); } uint advertisedBenefit = (uint)GetTotalAuraModifier(AuraType.ModHealingDone, aurEff => { if (aurEff.GetMiscValue() == 0 || (aurEff.GetMiscValue() & (int)schoolMask) != 0) return true; return false; }); // Healing bonus of spirit, intellect and strength if (IsTypeId(TypeId.Player)) { // Base value advertisedBenefit += ToPlayer().GetBaseSpellPowerBonus(); // Check if we are ever using mana - PaperDollFrame.lua if (GetPowerIndex(PowerType.Mana) != (uint)PowerType.Max) advertisedBenefit += Math.Max(0, (uint)GetStat(Stats.Intellect)); // spellpower from intellect // Healing bonus from stats var mHealingDoneOfStatPercent = GetAuraEffectsByType(AuraType.ModSpellHealingOfStatPercent); foreach (var i in mHealingDoneOfStatPercent) { // stat used dependent from misc value (stat index) Stats usedStat = (Stats)(i.GetSpellEffectInfo().MiscValue); advertisedBenefit += (uint)MathFunctions.CalculatePct(GetStat(usedStat), i.GetAmount()); } } return advertisedBenefit; } int SpellBaseHealingBonusTaken(SpellSchoolMask schoolMask) { return GetTotalAuraModifierByMiscMask(AuraType.ModHealing, (int)schoolMask); } public int SpellCriticalHealingBonus(SpellInfo spellProto, int damage, Unit victim) { // Calculate critical bonus int crit_bonus = damage; // adds additional damage to critBonus (from talents) Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spellProto, SpellModOp.CritDamageAndHealing, ref crit_bonus); damage += crit_bonus; damage = (int)(damage * GetTotalAuraMultiplier(AuraType.ModCriticalHealingAmount)); return damage; } public uint SpellHealingBonusDone(Unit victim, SpellInfo spellProto, uint healamount, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) { // For totems get healing bonus from owner (statue isn't totem in fact) if (IsTypeId(TypeId.Unit) && IsTotem()) { Unit owner = GetOwner(); if (owner) return owner.SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effect, stack); } // No bonus healing for potion spells if (spellProto.SpellFamilyName == SpellFamilyNames.Potion) return healamount; int DoneTotal = 0; // done scripted mod (take it from owner) Unit owner1 = GetOwner() ?? this; var mOverrideClassScript = owner1.GetAuraEffectsByType(AuraType.OverrideClassScripts); foreach (var eff in mOverrideClassScript) { if (!eff.IsAffectingSpell(spellProto)) continue; switch (eff.GetMiscValue()) { case 3736: // Hateful Totem of the Third Wind / Increased Lesser Healing Wave / LK Arena (4/5/6) Totem of the Third Wind / Savage Totem of the Third Wind DoneTotal += eff.GetAmount(); break; default: break; } } // Done fixed damage bonus auras uint DoneAdvertisedBenefit = SpellBaseHealingBonusDone(spellProto.GetSchoolMask()); // Check for table values float coeff = effect.BonusCoefficient; if (effect.BonusCoefficientFromAP > 0.0f) { DoneTotal += (int)(effect.BonusCoefficientFromAP * stack * GetTotalAttackPowerValue( (spellProto.IsRangedWeaponSpell() && spellProto.DmgClass != SpellDmgClass.Melee) ? WeaponAttackType.RangedAttack : WeaponAttackType.BaseAttack)); } else if (coeff <= 0.0f) { // No bonus healing for SPELL_DAMAGE_CLASS_NONE class spells by default if (spellProto.DmgClass == SpellDmgClass.None) return healamount; } // Default calculation if (DoneAdvertisedBenefit != 0) { Player modOwner = GetSpellModOwner(); if (modOwner) { coeff *= 100.0f; modOwner.ApplySpellMod(spellProto, SpellModOp.BonusCoefficient, ref coeff); coeff /= 100.0f; } DoneTotal += (int)(DoneAdvertisedBenefit * coeff * stack); } foreach (SpellEffectInfo eff in spellProto.GetEffects()) { if (eff == null) continue; switch (eff.ApplyAuraName) { // Bonus healing does not apply to these spells case AuraType.PeriodicLeech: case AuraType.PeriodicHealthFunnel: DoneTotal = 0; break; } if (eff.Effect == SpellEffectName.HealthLeech) DoneTotal = 0; } // use float as more appropriate for negative values and percent applying float heal = (healamount + DoneTotal) * (damagetype == DamageEffectType.DOT ? 1.0f : SpellHealingPctDone(victim, spellProto)); // apply spellmod to Done amount Player _modOwner = GetSpellModOwner(); if (_modOwner) { if (damagetype == DamageEffectType.DOT) _modOwner.ApplySpellMod(spellProto, SpellModOp.PeriodicHealingAndDamage, ref heal); else _modOwner.ApplySpellMod(spellProto, SpellModOp.HealingAndDamage, ref heal); } return (uint)Math.Max(heal, 0.0f); } public float SpellHealingPctDone(Unit victim, SpellInfo spellProto) { // For totems pct done mods are calculated when its calculation is run on the player in SpellHealingBonusDone. if (IsTypeId(TypeId.Unit) && IsTotem()) return 1.0f; // No bonus healing for potion spells if (spellProto.SpellFamilyName == SpellFamilyNames.Potion) return 1.0f; Player thisPlayer = ToPlayer(); if (thisPlayer != null) { float maxModDamagePercentSchool = 0.0f; for (int i = 0; i < (int)SpellSchools.Max; ++i) if (((int)spellProto.GetSchoolMask() & (1 << i)) != 0) maxModDamagePercentSchool = Math.Max(maxModDamagePercentSchool, thisPlayer.m_activePlayerData.ModHealingDonePercent[i]); return maxModDamagePercentSchool; } float DoneTotalMod = 1.0f; // bonus against aurastate DoneTotalMod *= GetTotalAuraMultiplier(AuraType.ModDamageDoneVersusAurastate, aurEff => { return victim.HasAuraState((AuraStateType)aurEff.GetMiscValue()); }); // Healing done percent DoneTotalMod *= GetTotalAuraMultiplier(AuraType.ModHealingDonePercent); // bonus from missing health of target float healthPctDiff = 100.0f - victim.GetHealthPct(); foreach (AuraEffect healingDonePctVsTargetHealth in GetAuraEffectsByType(AuraType.ModHealingDonePctVersusTargetHealth)) if (healingDonePctVsTargetHealth.IsAffectingSpell(spellProto)) MathFunctions.AddPct(ref DoneTotalMod, MathFunctions.CalculatePct((float)healingDonePctVsTargetHealth.GetAmount(), healthPctDiff)); return DoneTotalMod; } public uint SpellHealingBonusTaken(Unit caster, SpellInfo spellProto, uint healamount, DamageEffectType damagetype, SpellEffectInfo effect, uint stack = 1) { float TakenTotalMod = 1.0f; // Healing taken percent float minval = GetMaxNegativeAuraModifier(AuraType.ModHealingPct); if (minval != 0) MathFunctions.AddPct(ref TakenTotalMod, minval); float maxval = GetMaxPositiveAuraModifier(AuraType.ModHealingPct); if (maxval != 0) MathFunctions.AddPct(ref TakenTotalMod, maxval); // Tenacity increase healing % taken AuraEffect Tenacity = GetAuraEffect(58549, 0); if (Tenacity != null) MathFunctions.AddPct(ref TakenTotalMod, Tenacity.GetAmount()); // Healing Done int TakenTotal = 0; // Taken fixed damage bonus auras int TakenAdvertisedBenefit = SpellBaseHealingBonusTaken(spellProto.GetSchoolMask()); // Nourish cast if (spellProto.SpellFamilyName == SpellFamilyNames.Druid && spellProto.SpellFamilyFlags[1].HasAnyFlag(0x2000000u)) { // Rejuvenation, Regrowth, Lifebloom, or Wild Growth if (GetAuraEffect(AuraType.PeriodicHeal, SpellFamilyNames.Druid, new FlagArray128(0x50, 0x4000010, 0)) != null) // increase healing by 20% TakenTotalMod *= 1.2f; } // Check for table values float coeff = effect.BonusCoefficient; if (coeff <= 0.0f) { // No bonus healing for SPELL_DAMAGE_CLASS_NONE class spells by default if (spellProto.DmgClass == SpellDmgClass.None) { healamount = (uint)Math.Max((healamount * TakenTotalMod), 0.0f); return healamount; } } // Default calculation if (TakenAdvertisedBenefit != 0) { Player modOwner = GetSpellModOwner(); if (modOwner) { coeff *= 100.0f; modOwner.ApplySpellMod(spellProto, SpellModOp.BonusCoefficient, ref coeff); coeff /= 100.0f; } TakenTotal += (int)(TakenAdvertisedBenefit * coeff * stack); } if (damagetype == DamageEffectType.DOT) { // Healing over time taken percent float minval_hot = (float)GetMaxNegativeAuraModifier(AuraType.ModHotPct); if (minval_hot != 0) MathFunctions.AddPct(ref TakenTotalMod, minval_hot); float maxval_hot = (float)GetMaxPositiveAuraModifier(AuraType.ModHotPct); if (maxval_hot != 0) MathFunctions.AddPct(ref TakenTotalMod, maxval_hot); } TakenTotalMod *= GetTotalAuraMultiplier(AuraType.ModHealingReceived, aurEff => { if (caster.GetGUID() == aurEff.GetCasterGUID() && aurEff.IsAffectingSpell(spellProto)) return true; return false; }); foreach (SpellEffectInfo eff in spellProto.GetEffects()) { if (eff == null) continue; switch (eff.ApplyAuraName) { // Bonus healing does not apply to these spells case AuraType.PeriodicLeech: case AuraType.PeriodicHealthFunnel: TakenTotal = 0; break; } if (eff.Effect == SpellEffectName.HealthLeech) TakenTotal = 0; } float heal = (healamount + TakenTotal) * TakenTotalMod; return (uint)Math.Max(heal, 0.0f); } public bool IsSpellCrit(Unit victim, Spell spell, AuraEffect aurEff, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) { return RandomHelper.randChance(GetUnitSpellCriticalChance(victim, spell, aurEff, schoolMask, attackType)); } public float GetUnitSpellCriticalChance(Unit victim, Spell spell, AuraEffect aurEff, SpellSchoolMask schoolMask, WeaponAttackType attackType = WeaponAttackType.BaseAttack) { SpellInfo spellProto = spell?.GetSpellInfo() ?? aurEff?.GetSpellInfo(); //! Mobs can't crit with spells. Player Totems can //! Fire Elemental (from totem) can too - but this part is a hack and needs more research if (GetGUID().IsCreatureOrVehicle() && !(IsTotem() && GetOwnerGUID().IsPlayer()) && GetEntry() != 15438) return 0.0f; // not critting spell if (spellProto.HasAttribute(SpellAttr2.CantCrit)) return 0.0f; float crit_chance = 0.0f; switch (spellProto.DmgClass) { case SpellDmgClass.None: // We need more spells to find a general way (if there is any) switch (spellProto.Id) { case 379: // Earth Shield case 33778: // Lifebloom Final Bloom case 64844: // Divine Hymn case 71607: // Item - Bauble of True Blood 10m case 71646: // Item - Bauble of True Blood 25m break; default: return 0.0f; } goto case SpellDmgClass.Magic; case SpellDmgClass.Magic: { if (schoolMask.HasAnyFlag(SpellSchoolMask.Normal)) crit_chance = 0.0f; // For other schools else if (IsTypeId(TypeId.Player)) crit_chance = ToPlayer().m_activePlayerData.SpellCritPercentage; else crit_chance = BaseSpellCritChance; // taken if (victim) { if (!spellProto.IsPositive()) { // Modify critical chance by victim SPELL_AURA_MOD_ATTACKER_SPELL_AND_WEAPON_CRIT_CHANCE crit_chance += victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance); } // scripted (increase crit chance ... against ... target by x% var mOverrideClassScript = GetAuraEffectsByType(AuraType.OverrideClassScripts); foreach (var eff in mOverrideClassScript) { if (!eff.IsAffectingSpell(spellProto)) continue; switch (eff.GetMiscValue()) { case 911: // Shatter if (victim.HasAuraState(AuraStateType.Frozen, spellProto, this)) { crit_chance *= 1.5f; AuraEffect _eff = eff.GetBase().GetEffect(1); if (_eff != null) crit_chance += _eff.GetAmount(); } break; default: break; } } // Custom crit by class switch (spellProto.SpellFamilyName) { case SpellFamilyNames.Rogue: // Shiv-applied poisons can't crit if (FindCurrentSpellBySpellId(5938) != null) crit_chance = 0.0f; break; case SpellFamilyNames.Shaman: // Lava Burst if (spellProto.SpellFamilyFlags[1].HasAnyFlag(0x00001000u)) { if (victim.GetAuraEffect(AuraType.PeriodicDamage, SpellFamilyNames.Shaman, new FlagArray128(0x10000000, 0, 0), GetGUID()) != null) if (victim.GetTotalAuraModifier(AuraType.ModAttackerSpellAndWeaponCritChance) > -100) return 100.0f; break; } break; } // Spell crit suppression if (victim.GetTypeId() == TypeId.Unit) { int levelDiff = (int)(victim.GetLevelForTarget(this) - GetLevel()); crit_chance -= levelDiff * 1.0f; } } break; } case SpellDmgClass.Melee: case SpellDmgClass.Ranged: { if (victim) crit_chance += GetUnitCriticalChance(attackType, victim); break; } default: return 0.0f; } // percent done // only players use intelligence for critical chance computations Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spellProto, SpellModOp.CritChance, ref crit_chance); // for this types the bonus was already added in GetUnitCriticalChance, do not add twice if (spellProto.DmgClass != SpellDmgClass.Melee && spellProto.DmgClass != SpellDmgClass.Ranged) { crit_chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCasterWithAbilities, aurEff => aurEff.GetCasterGUID() == GetGUID() && aurEff.IsAffectingSpell(spellProto)); crit_chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCaster, aurEff => aurEff.GetCasterGUID() != GetGUID()); crit_chance += GetTotalAuraModifier(AuraType.ModCritChanceVersusTargetHealth, aurEff => !victim.HealthBelowPct(aurEff.GetMiscValueB())); TempSummon tempSummon = ToTempSummon(); if (tempSummon != null) crit_chance += victim.GetTotalAuraModifier(AuraType.ModCritChanceForCasterPet, aurEff => aurEff.GetCasterGUID() == tempSummon.GetSummonerGUID()); } // call script handlers if (spell) spell.CallScriptCalcCritChanceHandlers(victim, ref crit_chance); else aurEff.GetBase().CallScriptEffectCalcCritChanceHandlers(aurEff, aurEff.GetBase().GetApplicationOfTarget(victim.GetGUID()), victim, ref crit_chance); return Math.Max(crit_chance, 0.0f); } // Calculate spell hit result can be: // Every spell can: Evade/Immune/Reflect/Sucesful hit // For melee based spells: // Miss // Dodge // Parry // For spells // Resist public SpellMissInfo SpellHitResult(Unit victim, SpellInfo spellInfo, bool canReflect = false) { // All positive spells can`t miss // @todo client not show miss log for this spells - so need find info for this in dbc and use it! if (spellInfo.IsPositive() && (!IsHostileTo(victim))) // prevent from affecting enemy by "positive" spell return SpellMissInfo.None; if (this == victim) return SpellMissInfo.None; // Return evade for units in evade mode if (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks()) return SpellMissInfo.Evade; // Try victim reflect spell if (canReflect) { int reflectchance = victim.GetTotalAuraModifier(AuraType.ReflectSpells); reflectchance += victim.GetTotalAuraModifierByMiscMask(AuraType.ReflectSpellsSchool, (int)spellInfo.GetSchoolMask()); if (reflectchance > 0 && RandomHelper.randChance(reflectchance)) return SpellMissInfo.Reflect; } if (spellInfo.HasAttribute(SpellAttr3.IgnoreHitResult)) return SpellMissInfo.None; // Check for immune if (victim.IsImmunedToSpell(spellInfo, this)) return SpellMissInfo.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(spellInfo)) return SpellMissInfo.Immune; switch (spellInfo.DmgClass) { case SpellDmgClass.Ranged: case SpellDmgClass.Melee: return MeleeSpellHitResult(victim, spellInfo); case SpellDmgClass.None: return SpellMissInfo.None; case SpellDmgClass.Magic: return MagicSpellHitResult(victim, spellInfo); } return SpellMissInfo.None; } // Melee based spells hit result calculations SpellMissInfo MeleeSpellHitResult(Unit victim, SpellInfo spellInfo) { WeaponAttackType attType = WeaponAttackType.BaseAttack; // Check damage class instead of attack type to correctly handle judgements // - they are meele, but can't be dodged/parried/deflected because of ranged dmg class if (spellInfo.DmgClass == SpellDmgClass.Ranged) attType = WeaponAttackType.RangedAttack; int roll = RandomHelper.IRand(0, 9999); int missChance = (int)(MeleeSpellMissChance(victim, attType, spellInfo) * 100.0f); // Roll miss int tmp = missChance; if (roll < tmp) return SpellMissInfo.Miss; // Chance resist mechanic int resist_chance = victim.GetMechanicResistChance(spellInfo) * 100; tmp += resist_chance; if (roll < tmp) return SpellMissInfo.Resist; // Same spells cannot be parried/dodged if (spellInfo.HasAttribute(SpellAttr0.ImpossibleDodgeParryBlock)) return SpellMissInfo.None; bool canDodge = true; bool canParry = true; bool canBlock = spellInfo.HasAttribute(SpellAttr3.BlockableSpell); // if victim is casting or cc'd it can't avoid attacks if (victim.IsNonMeleeSpellCast(false) || victim.HasUnitState(UnitState.Controlled)) { canDodge = false; canParry = false; canBlock = false; } // Ranged attacks can only miss, resist and deflect if (attType == WeaponAttackType.RangedAttack) { canParry = false; canDodge = false; // only if in front if (!victim.HasUnitState(UnitState.Controlled) && (victim.HasInArc(MathFunctions.PI, this) || victim.HasAuraType(AuraType.IgnoreHitDirection))) { int deflect_chance = victim.GetTotalAuraModifier(AuraType.DeflectSpells) * 100; tmp += deflect_chance; if (roll < tmp) return SpellMissInfo.Deflect; } return SpellMissInfo.None; } // Check for attack from behind if (!victim.HasInArc(MathFunctions.PI, this)) { if (!victim.HasAuraType(AuraType.IgnoreHitDirection)) { // Can`t dodge from behind in PvP (but its possible in PvE) if (victim.IsTypeId(TypeId.Player)) canDodge = false; // Can`t parry or block canParry = false; canBlock = false; } else // Only deterrence as of 3.3.5 { if (spellInfo.HasAttribute(SpellCustomAttributes.ReqCasterBehindTarget)) canParry = false; } } // Ignore combat result aura var ignore = GetAuraEffectsByType(AuraType.IgnoreCombatResult); foreach (var aurEff in ignore) { if (!aurEff.IsAffectingSpell(spellInfo)) continue; switch ((MeleeHitOutcome)aurEff.GetMiscValue()) { case MeleeHitOutcome.Dodge: canDodge = false; break; case MeleeHitOutcome.Block: canBlock = false; break; case MeleeHitOutcome.Parry: canParry = false; break; default: Log.outDebug(LogFilter.Unit, "Spell {0} SPELL_AURA_IGNORE_COMBAT_RESULT has unhandled state {1}", aurEff.GetId(), aurEff.GetMiscValue()); break; } } if (canDodge) { // Roll dodge int dodgeChance = (int)(GetUnitDodgeChance(attType, victim) * 100.0f); if (dodgeChance < 0) dodgeChance = 0; if (roll < (tmp += dodgeChance)) return SpellMissInfo.Dodge; } if (canParry) { // Roll parry int parryChance = (int)(GetUnitParryChance(attType, victim) * 100.0f); if (parryChance < 0) parryChance = 0; tmp += parryChance; if (roll < tmp) return SpellMissInfo.Parry; } if (canBlock) { int blockChance = (int)(GetUnitBlockChance(attType, victim) * 100.0f); if (blockChance < 0) blockChance = 0; tmp += blockChance; if (roll < tmp) return SpellMissInfo.Block; } return SpellMissInfo.None; } SpellMissInfo MagicSpellHitResult(Unit victim, SpellInfo spell) { // Can`t miss on dead target (on skinning for example) if (!victim.IsAlive() && !victim.IsTypeId(TypeId.Player)) return SpellMissInfo.None; SpellSchoolMask schoolMask = spell.GetSchoolMask(); // PvP - PvE spell misschances per leveldif > 2 int lchance = victim.IsTypeId(TypeId.Player) ? 7 : 11; int thisLevel = (int)GetLevelForTarget(victim); if (IsTypeId(TypeId.Unit) && ToCreature().IsTrigger()) thisLevel = (int)Math.Max(thisLevel, spell.SpellLevel); int leveldif = (int)(victim.GetLevelForTarget(this)) - thisLevel; int levelBasedHitDiff = leveldif; // Base hit chance from attacker and victim levels int modHitChance; if (levelBasedHitDiff >= 0) { if (!victim.IsTypeId(TypeId.Player)) { modHitChance = 94 - 3 * Math.Min(levelBasedHitDiff, 3); levelBasedHitDiff -= 3; } else { modHitChance = 96 - Math.Min(levelBasedHitDiff, 2); levelBasedHitDiff -= 2; } if (levelBasedHitDiff > 0) modHitChance -= lchance * Math.Min(levelBasedHitDiff, 7); } else modHitChance = 97 - levelBasedHitDiff; // Spellmod from SpellModOp.HitChance Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spell, SpellModOp.HitChance, ref modHitChance); // Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will ignore target's avoidance effects if (!spell.HasAttribute(SpellAttr3.IgnoreHitResult)) { // Chance hit from victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras modHitChance += victim.GetTotalAuraModifierByMiscMask(AuraType.ModAttackerSpellHitChance, (int)schoolMask); } int HitChance = modHitChance * 100; // Increase hit chance from attacker SPELL_AURA_MOD_SPELL_HIT_CHANCE and attacker ratings HitChance += (int)(modHitChance * 100.0f); MathFunctions.RoundToInterval(ref HitChance, 0, 10000); int tmp = 10000 - HitChance; int rand = RandomHelper.IRand(0, 9999); if (tmp > 0 && rand < tmp) return SpellMissInfo.Miss; // Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will additionally fully ignore // resist and deflect chances if (spell.HasAttribute(SpellAttr3.IgnoreHitResult)) return SpellMissInfo.None; // Chance resist mechanic (select max value from every mechanic spell effect) int resist_chance = victim.GetMechanicResistChance(spell) * 100; // Roll chance if (resist_chance > 0 && rand < (tmp += resist_chance)) return SpellMissInfo.Resist; // cast by caster in front of victim if (!victim.HasUnitState(UnitState.Controlled) && (victim.HasInArc(MathFunctions.PI, this) || victim.HasAuraType(AuraType.IgnoreHitDirection))) { int deflect_chance = victim.GetTotalAuraModifier(AuraType.DeflectSpells) * 100; if (deflect_chance > 0 && rand < (tmp += deflect_chance)) return SpellMissInfo.Deflect; } return SpellMissInfo.None; } public void CastSpell(SpellCastTargets targets, uint spellId, CastSpellExtraArgs args) { if (args == null) args = new CastSpellExtraArgs(); SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId, args.CastDifficulty != Difficulty.None ? args.CastDifficulty : GetMap().GetDifficultyID()); if (spellInfo == null) { Log.outError(LogFilter.Unit, $"CastSpell: unknown spell {spellId} by caster: {GetGUID()}"); return; } Spell spell = new(this, spellInfo, args.TriggerFlags, args.OriginalCaster); foreach (var pair in args.SpellValueOverrides) spell.SetSpellValue(pair.Key, pair.Value); spell.m_CastItem = args.CastItem; spell.Prepare(targets, args.TriggeringAura); } public void CastSpell(WorldObject target, uint spellId, bool triggered) { CastSpell(target, spellId, new CastSpellExtraArgs(triggered)); } public void CastSpell(WorldObject target, uint spellId, CastSpellExtraArgs args = null) { SpellCastTargets targets = new(); if (target) { Unit unitTarget = target.ToUnit(); GameObject goTarget = target.ToGameObject(); if (unitTarget != null) targets.SetUnitTarget(unitTarget); else if (goTarget != null) targets.SetGOTarget(goTarget); else { Log.outError(LogFilter.Unit, $"CastSpell: Invalid target {target.GetGUID()} passed to spell cast by {GetGUID()}"); return; } } CastSpell(targets, spellId, args); } public void CastSpell(Position dest, uint spellId, CastSpellExtraArgs args = null) { SpellCastTargets targets = new(); targets.SetDst(dest); CastSpell(targets, spellId, args); } public void FinishSpell(CurrentSpellTypes spellType, bool ok = true) { Spell spell = GetCurrentSpell(spellType); if (spell == null) return; if (spellType == CurrentSpellTypes.Channeled) spell.SendChannelUpdate(0); spell.Finish(ok); } uint GetCastingTimeForBonus(SpellInfo spellProto, DamageEffectType damagetype, uint CastingTime) { // Not apply this to creature casted spells with casttime == 0 if (CastingTime == 0 && IsTypeId(TypeId.Unit) && !IsPet()) return 3500; if (CastingTime > 7000) CastingTime = 7000; if (CastingTime < 1500) CastingTime = 1500; if (damagetype == DamageEffectType.DOT && !spellProto.IsChanneled()) CastingTime = 3500; int overTime = 0; byte effects = 0; bool DirectDamage = false; bool AreaEffect = false; foreach (SpellEffectInfo effect in spellProto.GetEffects()) { if (effect == null) continue; switch (effect.Effect) { case SpellEffectName.SchoolDamage: case SpellEffectName.PowerDrain: case SpellEffectName.HealthLeech: case SpellEffectName.EnvironmentalDamage: case SpellEffectName.PowerBurn: case SpellEffectName.Heal: DirectDamage = true; break; case SpellEffectName.ApplyAura: switch (effect.ApplyAuraName) { case AuraType.PeriodicDamage: case AuraType.PeriodicHeal: case AuraType.PeriodicLeech: if (spellProto.GetDuration() != 0) overTime = spellProto.GetDuration(); break; default: // -5% per additional effect ++effects; break; } break; default: break; } if (effect.IsTargetingArea()) AreaEffect = true; } // Combined Spells with Both Over Time and Direct Damage if (overTime > 0 && CastingTime > 0 && DirectDamage) { // mainly for DoTs which are 3500 here otherwise int OriginalCastTime = spellProto.CalcCastTime(); if (OriginalCastTime > 7000) OriginalCastTime = 7000; if (OriginalCastTime < 1500) OriginalCastTime = 1500; // Portion to Over Time float PtOT = (overTime / 15000.0f) / ((overTime / 15000.0f) + (OriginalCastTime / 3500.0f)); if (damagetype == DamageEffectType.DOT) CastingTime = (uint)(CastingTime * PtOT); else if (PtOT < 1.0f) CastingTime = (uint)(CastingTime * (1 - PtOT)); else CastingTime = 0; } // Area Effect Spells receive only half of bonus if (AreaEffect) CastingTime /= 2; // 50% for damage and healing spells for leech spells from damage bonus and 0% from healing foreach (SpellEffectInfo effect in spellProto.GetEffects()) { if (effect != null && (effect.Effect == SpellEffectName.HealthLeech || (effect.Effect == SpellEffectName.ApplyAura && effect.ApplyAuraName == AuraType.PeriodicLeech))) { CastingTime /= 2; break; } } // -5% of total per any additional effect for (byte i = 0; i < effects; ++i) CastingTime *= (uint)0.95f; return CastingTime; } public virtual SpellInfo GetCastSpellInfo(SpellInfo spellInfo) { SpellInfo findMatchingAuraEffectIn(AuraType type) { foreach (AuraEffect auraEffect in GetAuraEffectsByType(type)) { bool matches = auraEffect.GetMiscValue() != 0 ? auraEffect.GetMiscValue() == spellInfo.Id : auraEffect.IsAffectingSpell(spellInfo); if (matches) { SpellInfo info = Global.SpellMgr.GetSpellInfo((uint)auraEffect.GetAmount(), GetMap().GetDifficultyID()); if (info != null) return info; } } return null; } SpellInfo newInfo = findMatchingAuraEffectIn(AuraType.OverrideActionbarSpells); if (newInfo != null) return newInfo; newInfo = findMatchingAuraEffectIn(AuraType.OverrideActionbarSpellsTriggered); if (newInfo != null) return newInfo; return spellInfo; } public uint GetCastSpellXSpellVisualId(SpellInfo spellInfo) { var visualOverrides = GetAuraEffectsByType(AuraType.OverrideSpellVisual); foreach (AuraEffect effect in visualOverrides) { if (effect.GetMiscValue() == spellInfo.Id) { SpellInfo visualSpell = Global.SpellMgr.GetSpellInfo((uint)effect.GetMiscValueB(), GetMap().GetDifficultyID()); if (visualSpell != null) { spellInfo = visualSpell; break; } } } return spellInfo.GetSpellXSpellVisualId(this); } public SpellHistory GetSpellHistory() { return _spellHistory; } public static ProcFlagsHit CreateProcHitMask(SpellNonMeleeDamage damageInfo, SpellMissInfo missCondition) { ProcFlagsHit hitMask = ProcFlagsHit.None; // Check victim state if (missCondition != SpellMissInfo.None) { switch (missCondition) { case SpellMissInfo.Miss: hitMask |= ProcFlagsHit.Miss; break; case SpellMissInfo.Dodge: hitMask |= ProcFlagsHit.Dodge; break; case SpellMissInfo.Parry: hitMask |= ProcFlagsHit.Parry; break; case SpellMissInfo.Block: // spells can't be partially blocked (it's damage can though) hitMask |= ProcFlagsHit.Block | ProcFlagsHit.FullBlock; break; case SpellMissInfo.Evade: hitMask |= ProcFlagsHit.Evade; break; case SpellMissInfo.Immune: case SpellMissInfo.Immune2: hitMask |= ProcFlagsHit.Immune; break; case SpellMissInfo.Deflect: hitMask |= ProcFlagsHit.Deflect; break; case SpellMissInfo.Absorb: hitMask |= ProcFlagsHit.Absorb; break; case SpellMissInfo.Reflect: hitMask |= ProcFlagsHit.Reflect; break; case SpellMissInfo.Resist: hitMask |= ProcFlagsHit.FullResist; break; default: break; } } else { // On block if (damageInfo.blocked != 0) { hitMask |= ProcFlagsHit.Block; if (damageInfo.fullBlock) hitMask |= ProcFlagsHit.FullBlock; } // On absorb if (damageInfo.absorb != 0) hitMask |= ProcFlagsHit.Absorb; // Don't set hit/crit hitMask if damage is nullified bool damageNullified = damageInfo.HitInfo.HasAnyFlag(HitInfo.FullAbsorb | HitInfo.FullResist) || hitMask.HasAnyFlag(ProcFlagsHit.FullBlock); if (!damageNullified) { // On crit if (damageInfo.HitInfo.HasAnyFlag(HitInfo.CriticalHit)) hitMask |= ProcFlagsHit.Critical; else hitMask |= ProcFlagsHit.Normal; } else if (damageInfo.HitInfo.HasAnyFlag(HitInfo.FullResist)) hitMask |= ProcFlagsHit.FullResist; } return hitMask; } public void SetAuraStack(uint spellId, Unit target, uint stack) { Aura aura = target.GetAura(spellId, GetGUID()); if (aura == null) aura = AddAura(spellId, target); if (aura != null && stack != 0) aura.SetStackAmount((byte)stack); } public Spell FindCurrentSpellBySpellId(uint spell_id) { foreach (var spell in m_currentSpells.Values) { if (spell == null) continue; if (spell.m_spellInfo.Id == spell_id) return spell; } return null; } public int GetCurrentSpellCastTime(uint spell_id) { Spell spell = FindCurrentSpellBySpellId(spell_id); if (spell != null) return spell.GetCastTime(); return 0; } public virtual bool IsFocusing(Spell focusSpell = null, bool withDelay = false) { return false; } /// /// Check if our current channel spell has attribute SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING /// public virtual bool IsMovementPreventedByCasting() { // can always move when not casting if (!HasUnitState(UnitState.Casting)) return false; // channeled spells during channel stage (after the initial cast timer) allow movement with a specific spell attribute Spell spell = m_currentSpells.LookupByKey(CurrentSpellTypes.Channeled); if (spell) if (spell.GetState() != SpellState.Finished && spell.IsChannelActive()) if (spell.GetSpellInfo().IsMoveAllowedChannel()) return false; // prohibit movement for all other spell casts return true; } public bool HasAuraTypeWithFamilyFlags(AuraType auraType, uint familyName, FlagArray128 familyFlags) { foreach (AuraEffect aura in GetAuraEffectsByType(auraType)) if (aura.GetSpellInfo().SpellFamilyName == (SpellFamilyNames)familyName && aura.GetSpellInfo().SpellFamilyFlags & familyFlags) return true; return false; } bool HasBreakableByDamageAuraType(AuraType type, uint excludeAura) { var auras = GetAuraEffectsByType(type); foreach (var eff in auras) if ((excludeAura == 0 || excludeAura != eff.GetSpellInfo().Id) && //Avoid self interrupt of channeled Crowd Control spells like Seduction eff.GetSpellInfo().HasAuraInterruptFlag(SpellAuraInterruptFlags.Damage)) return true; return false; } public bool HasBreakableByDamageCrowdControlAura(Unit excludeCasterChannel = null) { uint excludeAura = 0; Spell currentChanneledSpell = excludeCasterChannel?.GetCurrentSpell(CurrentSpellTypes.Channeled); if (currentChanneledSpell != null) excludeAura = currentChanneledSpell.GetSpellInfo().Id; //Avoid self interrupt of channeled Crowd Control spells like Seduction return (HasBreakableByDamageAuraType(AuraType.ModConfuse, excludeAura) || HasBreakableByDamageAuraType(AuraType.ModFear, excludeAura) || HasBreakableByDamageAuraType(AuraType.ModStun, excludeAura) || HasBreakableByDamageAuraType(AuraType.ModRoot, excludeAura) || HasBreakableByDamageAuraType(AuraType.ModRoot2, excludeAura) || HasBreakableByDamageAuraType(AuraType.Transform, excludeAura)); } public uint GetDiseasesByCaster(ObjectGuid casterGUID, bool remove = false) { AuraType[] diseaseAuraTypes = { AuraType.PeriodicDamage, // Frost Fever and Blood Plague AuraType.Linked, // Crypt Fever and Ebon Plague AuraType.None }; uint diseases = 0; foreach (var aura in diseaseAuraTypes) { if (aura == AuraType.None) break; for (var i = 0; i < m_modAuras[aura].Count;) { var eff = m_modAuras[aura][i]; // Get auras with disease dispel type by caster if (eff.GetSpellInfo().Dispel == DispelType.Disease && eff.GetCasterGUID() == casterGUID) { ++diseases; if (remove) { RemoveAura(eff.GetId(), eff.GetCasterGUID()); i = 0; continue; } } i++; } } return diseases; } uint GetDoTsByCaster(ObjectGuid casterGUID) { AuraType[] diseaseAuraTypes = { AuraType.PeriodicDamage, AuraType.PeriodicDamagePercent, AuraType.None }; uint dots = 0; foreach (var aura in diseaseAuraTypes) { if (aura == AuraType.None) break; var auras = GetAuraEffectsByType(aura); foreach (var eff in auras) { // Get auras by caster if (eff.GetCasterGUID() == casterGUID) ++dots; } } return dots; } public void SendEnergizeSpellLog(Unit victim, uint spellId, int amount, int overEnergize, PowerType powerType) { SpellEnergizeLog data = new(); data.CasterGUID = GetGUID(); data.TargetGUID = victim.GetGUID(); data.SpellID = spellId; data.Type = powerType; data.Amount = amount; data.OverEnergize = overEnergize; data.LogData.Initialize(victim); SendCombatLogMessage(data); } public void EnergizeBySpell(Unit victim, SpellInfo spellInfo, int damage, PowerType powerType) { int gain = victim.ModifyPower(powerType, damage, false); int overEnergize = damage - gain; victim.GetThreatManager().ForwardThreatForAssistingMe(this, damage / 2, spellInfo, true); SendEnergizeSpellLog(victim, spellInfo.Id, gain, overEnergize, powerType); } public void ApplySpellImmune(uint spellId, SpellImmunity op, SpellSchoolMask type, bool apply) { ApplySpellImmune(spellId, op, (uint)type, apply); } public void ApplySpellImmune(uint spellId, SpellImmunity op, AuraType type, bool apply) { ApplySpellImmune(spellId, op, (uint)type, apply); } public void ApplySpellImmune(uint spellId, SpellImmunity op, SpellEffectName type, bool apply) { ApplySpellImmune(spellId, op, (uint)type, apply); } public void ApplySpellImmune(uint spellId, SpellImmunity op, uint type, bool apply) { if (apply) { m_spellImmune[(int)op].Add(type, spellId); } else { var bounds = m_spellImmune[(int)op].LookupByKey(type); foreach (var spell in bounds) { if (spell == spellId) { m_spellImmune[(int)op].Remove(type, spell); break; } } } } public virtual bool IsImmunedToSpell(SpellInfo spellInfo, Unit caster) { if (spellInfo == null) return false; // Single spell immunity. var idList = m_spellImmune[(int)SpellImmunity.Id]; if (idList.ContainsKey(spellInfo.Id)) return true; if (spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability)) return false; uint dispel = (uint)spellInfo.Dispel; if (dispel != 0) { var dispelList = m_spellImmune[(int)SpellImmunity.Dispel]; if (dispelList.ContainsKey(dispel)) return true; } // Spells that don't have effectMechanics. uint mechanic = (uint)spellInfo.Mechanic; if (mechanic != 0) { var mechanicList = m_spellImmune[(int)SpellImmunity.Mechanic]; if (mechanicList.ContainsKey(mechanic)) return true; } bool immuneToAllEffects = true; foreach (SpellEffectInfo effect in spellInfo.GetEffects()) { // State/effect immunities applied by aura expect full spell immunity // Ignore effects with mechanic, they are supposed to be checked separately if (effect == null || !effect.IsEffect()) continue; if (!IsImmunedToSpellEffect(spellInfo, effect.EffectIndex, caster)) { immuneToAllEffects = false; break; } } if (immuneToAllEffects) //Return immune only if the target is immune to all spell effects. return true; uint schoolMask = (uint)spellInfo.GetSchoolMask(); if (schoolMask != 0) { uint schoolImmunityMask = 0; var schoolList = m_spellImmune[(int)SpellImmunity.School]; foreach (var pair in schoolList) { if ((pair.Key & schoolMask) == 0) continue; SpellInfo immuneSpellInfo = Global.SpellMgr.GetSpellInfo(pair.Value, GetMap().GetDifficultyID()); if (!(immuneSpellInfo != null && immuneSpellInfo.IsPositive() && spellInfo.IsPositive() && caster && IsFriendlyTo(caster))) if (!spellInfo.CanPierceImmuneAura(immuneSpellInfo)) schoolImmunityMask |= pair.Key; } if ((schoolImmunityMask & schoolMask) == schoolMask) return true; } return false; } public uint GetSchoolImmunityMask() { uint mask = 0; var schoolList = m_spellImmune[(int)SpellImmunity.School]; foreach (var pair in schoolList) mask |= pair.Key; return mask; } public uint GetDamageImmunityMask() { uint mask = 0; var damageList = m_spellImmune[(int)SpellImmunity.Damage]; foreach (var pair in damageList) mask |= pair.Key; return mask; } public uint GetMechanicImmunityMask() { uint mask = 0; var mechanicList = m_spellImmune[(int)SpellImmunity.Mechanic]; foreach (var pair in mechanicList) mask |= (1u << (int)pair.Value); return mask; } public virtual bool IsImmunedToSpellEffect(SpellInfo spellInfo, uint index, Unit caster) { if (spellInfo == null) return false; SpellEffectInfo effect = spellInfo.GetEffect(index); if (effect == null || !effect.IsEffect()) return false; // If m_immuneToEffect type contain this effect type, IMMUNE effect. uint eff = (uint)effect.Effect; var effectList = m_spellImmune[(int)SpellImmunity.Effect]; if (effectList.ContainsKey(eff)) return true; uint mechanic = (uint)effect.Mechanic; if (mechanic != 0) { var mechanicList = m_spellImmune[(int)SpellImmunity.Mechanic]; if (mechanicList.ContainsKey(mechanic)) return true; } if (!spellInfo.HasAttribute(SpellAttr3.IgnoreHitResult)) { uint aura = (uint)effect.ApplyAuraName; if (aura != 0) { var list = m_spellImmune[(int)SpellImmunity.State]; if (list.ContainsKey(aura)) return true; if (!spellInfo.HasAttribute(SpellAttr2.UnaffectedByAuraSchoolImmune)) { // Check for immune to application of harmful magical effects var immuneAuraApply = GetAuraEffectsByType(AuraType.ModImmuneAuraApplySchool); foreach (var auraEffect in immuneAuraApply) if (Convert.ToBoolean(auraEffect.GetMiscValue() & (int)spellInfo.GetSchoolMask()) && // Check school ((caster && !IsFriendlyTo(caster)) || !spellInfo.IsPositiveEffect(index))) // Harmful return true; } } } return false; } public bool IsImmunedToDamage(SpellSchoolMask schoolMask) { if (schoolMask == SpellSchoolMask.None) return false; // If m_immuneToSchool type contain this school type, IMMUNE damage. uint schoolImmunityMask = GetSchoolImmunityMask(); if (((SpellSchoolMask)schoolImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types return true; // If m_immuneToDamage type contain magic, IMMUNE damage. uint damageImmunityMask = GetDamageImmunityMask(); if (((SpellSchoolMask)damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types return true; return false; } public bool IsImmunedToDamage(SpellInfo spellInfo) { if (spellInfo == null) return false; // for example 40175 if (spellInfo.HasAttribute(SpellAttr0.UnaffectedByInvulnerability) && spellInfo.HasAttribute(SpellAttr3.IgnoreHitResult)) return false; if (spellInfo.HasAttribute(SpellAttr1.UnaffectedBySchoolImmune) || spellInfo.HasAttribute(SpellAttr2.UnaffectedByAuraSchoolImmune)) return false; uint schoolMask = (uint)spellInfo.GetSchoolMask(); if (schoolMask != 0) { // If m_immuneToSchool type contain this school type, IMMUNE damage. uint schoolImmunityMask = 0; var schoolList = m_spellImmune[(int)SpellImmunity.School]; foreach (var pair in schoolList) if (Convert.ToBoolean(pair.Key & schoolMask) && !spellInfo.CanPierceImmuneAura(Global.SpellMgr.GetSpellInfo(pair.Value, GetMap().GetDifficultyID()))) schoolImmunityMask |= pair.Key; // // We need to be immune to all types if ((schoolImmunityMask & schoolMask) == schoolMask) return true; // If m_immuneToDamage type contain magic, IMMUNE damage. uint damageImmunityMask = GetDamageImmunityMask(); if ((damageImmunityMask & schoolMask) == schoolMask) // We need to be immune to all types return true; } return false; } public void ProcSkillsAndAuras(Unit actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) { WeaponAttackType attType = damageInfo != null ? damageInfo.GetAttackType() : WeaponAttackType.BaseAttack; if (typeMaskActor != 0) ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); if (typeMaskActionTarget != 0 && actionTarget) actionTarget.ProcSkillsAndReactives(true, this, typeMaskActionTarget, hitMask, attType); TriggerAurasProcOnEvent(null, null, actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); } void ProcSkillsAndReactives(bool isVictim, Unit procTarget, ProcFlags typeMask, ProcFlagsHit hitMask, WeaponAttackType attType) { // Player is loaded now - do not allow passive spell casts to proc if (IsPlayer() && ToPlayer().GetSession().PlayerLoading()) return; // For melee/ranged based attack need update skills and set some Aura states if victim present if (typeMask.HasAnyFlag(ProcFlags.MeleeBasedTriggerMask) && procTarget) { // If exist crit/parry/dodge/block need update aura state (for victim and attacker) if (hitMask.HasAnyFlag(ProcFlagsHit.Critical | ProcFlagsHit.Parry | ProcFlagsHit.Dodge | ProcFlagsHit.Block)) { // for victim if (isVictim) { // if victim and dodge attack if (hitMask.HasAnyFlag(ProcFlagsHit.Dodge)) { // Update AURA_STATE on dodge if (GetClass() != Class.Rogue) // skip Rogue Riposte { ModifyAuraState(AuraStateType.Defensive, true); StartReactiveTimer(ReactiveType.Defense); } } // if victim and parry attack if (hitMask.HasAnyFlag(ProcFlagsHit.Parry)) { ModifyAuraState(AuraStateType.Defensive, true); StartReactiveTimer(ReactiveType.Defense); } // if and victim block attack if (hitMask.HasAnyFlag(ProcFlagsHit.Block)) { ModifyAuraState(AuraStateType.Defensive, true); StartReactiveTimer(ReactiveType.Defense); } } } } } void GetProcAurasTriggeredOnEvent(List> aurasTriggeringProc, List procAuras, ProcEventInfo eventInfo) { DateTime now = GameTime.GetGameTimeSteadyPoint(); // use provided list of auras which can proc if (procAuras != null) { foreach (AuraApplication aurApp in procAuras) { Cypher.Assert(aurApp.GetTarget() == this); uint procEffectMask = aurApp.GetBase().GetProcEffectMask(aurApp, eventInfo, now); if (procEffectMask != 0) { aurApp.GetBase().PrepareProcToTrigger(aurApp, eventInfo, now); aurasTriggeringProc.Add(Tuple.Create(procEffectMask, aurApp)); } } } // or generate one on our own else { foreach (var pair in GetAppliedAuras()) { uint procEffectMask = pair.Value.GetBase().GetProcEffectMask(pair.Value, eventInfo, now); if (procEffectMask != 0) { pair.Value.GetBase().PrepareProcToTrigger(pair.Value, eventInfo, now); aurasTriggeringProc.Add(Tuple.Create(procEffectMask, pair.Value)); } } } } void TriggerAurasProcOnEvent(CalcDamageInfo damageInfo) { DamageInfo dmgInfo = new(damageInfo); TriggerAurasProcOnEvent(null, null, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, ProcFlagsSpellType.None, ProcFlagsSpellPhase.None, dmgInfo.GetHitMask(), null, dmgInfo, null); } void TriggerAurasProcOnEvent(List myProcAuras, List targetProcAuras, Unit actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) { // prepare data for self trigger ProcEventInfo myProcEventInfo = new(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); List> myAurasTriggeringProc = new(); if (typeMaskActor != 0) { GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, myProcAuras, myProcEventInfo); // needed for example for Cobra Strikes, pet does the attack, but aura is on owner Player modOwner = GetSpellModOwner(); if (modOwner) { if (modOwner != this && spell) { List modAuras = new(); foreach (var itr in modOwner.GetAppliedAuras()) { if (spell.m_appliedMods.Contains(itr.Value.GetBase())) modAuras.Add(itr.Value); } modOwner.GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, modAuras, myProcEventInfo); } } } // prepare data for target trigger ProcEventInfo targetProcEventInfo = new(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); List> targetAurasTriggeringProc = new(); if (typeMaskActionTarget != 0 && actionTarget) actionTarget.GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, targetProcAuras, targetProcEventInfo); TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc); if (typeMaskActionTarget != 0 && actionTarget) actionTarget.TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); } void TriggerAurasProcOnEvent(ProcEventInfo eventInfo, List> aurasTriggeringProc) { Spell triggeringSpell = eventInfo.GetProcSpell(); bool disableProcs = triggeringSpell && triggeringSpell.IsProcDisabled(); if (disableProcs) SetCantProc(true); foreach (var aurAppProc in aurasTriggeringProc) { AuraApplication aurApp = aurAppProc.Item2; uint procEffectMask = aurAppProc.Item1; if (aurApp.GetRemoveMode() != 0) continue; SpellInfo spellInfo = aurApp.GetBase().GetSpellInfo(); if (spellInfo.HasAttribute(SpellAttr3.DisableProc)) SetCantProc(true); aurApp.GetBase().TriggerProcOnEvent(procEffectMask, aurApp, eventInfo); if (spellInfo.HasAttribute(SpellAttr3.DisableProc)) SetCantProc(false); } if (disableProcs) SetCantProc(false); } void SetCantProc(bool apply) { if (apply) ++m_procDeep; else { Cypher.Assert(m_procDeep != 0); --m_procDeep; } } public void CastStop(uint except_spellid = 0) { for (var i = CurrentSpellTypes.Generic; i < CurrentSpellTypes.Max; i++) if (GetCurrentSpell(i) != null && GetCurrentSpell(i).m_spellInfo.Id != except_spellid) InterruptSpell(i, false); } public void ModSpellCastTime(SpellInfo spellInfo, ref int castTime, Spell spell = null) { if (spellInfo == null || castTime < 0) return; // called from caster Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spellInfo, SpellModOp.ChangeCastTime, ref castTime, spell); if (!(spellInfo.HasAttribute(SpellAttr0.Ability | SpellAttr0.Tradespell) || spellInfo.HasAttribute(SpellAttr3.NoDoneBonus)) && (IsTypeId(TypeId.Player) && spellInfo.SpellFamilyName != 0) || IsTypeId(TypeId.Unit)) castTime = CanInstantCast() ? 0 : (int)(castTime * m_unitData.ModCastingSpeed); else if (spellInfo.HasAttribute(SpellAttr0.ReqAmmo) && !spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) castTime = (int)(castTime * m_modAttackSpeedPct[(int)WeaponAttackType.RangedAttack]); else if (Global.SpellMgr.IsPartOfSkillLine(SkillType.Cooking, spellInfo.Id) && HasAura(67556)) // cooking with Chef Hat. castTime = 500; } public void ModSpellDurationTime(SpellInfo spellInfo, ref int duration, Spell spell = null) { if (spellInfo == null || duration < 0) return; if (spellInfo.IsChanneled() && !spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration)) return; // called from caster Player modOwner = GetSpellModOwner(); if (modOwner) modOwner.ApplySpellMod(spellInfo, SpellModOp.ChangeCastTime, ref duration, spell); if (!(spellInfo.HasAttribute(SpellAttr0.Ability) || spellInfo.HasAttribute(SpellAttr0.Tradespell) || spellInfo.HasAttribute(SpellAttr3.NoDoneBonus)) && (IsTypeId(TypeId.Player) && spellInfo.SpellFamilyName != 0) || IsTypeId(TypeId.Unit)) duration = (int)(duration * m_unitData.ModCastingSpeed); else if (spellInfo.HasAttribute(SpellAttr0.ReqAmmo) && !spellInfo.HasAttribute(SpellAttr2.AutorepeatFlag)) duration = (int)(duration * m_modAttackSpeedPct[(int)WeaponAttackType.RangedAttack]); } public float ApplyEffectModifiers(SpellInfo spellProto, uint effect_index, float value) { Player modOwner = GetSpellModOwner(); if (modOwner != null) { modOwner.ApplySpellMod(spellProto, SpellModOp.Points, ref value); switch (effect_index) { case 0: modOwner.ApplySpellMod(spellProto, SpellModOp.PointsIndex0, ref value); break; case 1: modOwner.ApplySpellMod(spellProto, SpellModOp.PointsIndex1, ref value); break; case 2: modOwner.ApplySpellMod(spellProto, SpellModOp.PointsIndex2, ref value); break; case 3: modOwner.ApplySpellMod(spellProto, SpellModOp.PointsIndex3, ref value); break; case 4: modOwner.ApplySpellMod(spellProto, SpellModOp.PointsIndex4, ref value); break; } } return value; } public ushort GetMaxSkillValueForLevel(Unit target = null) { return (ushort)(target != null ? GetLevelForTarget(target) : GetLevel() * 5); } public Player GetSpellModOwner() { if (IsTypeId(TypeId.Player)) return ToPlayer(); if (HasUnitTypeMask(UnitTypeMask.Pet | UnitTypeMask.Totem | UnitTypeMask.Guardian)) { Unit owner = GetOwner(); if (owner != null && owner.IsTypeId(TypeId.Player)) return owner.ToPlayer(); } return null; } public Spell GetCurrentSpell(CurrentSpellTypes spellType) { return m_currentSpells.LookupByKey(spellType); } public void SetCurrentCastSpell(Spell pSpell) { Cypher.Assert(pSpell != null); // NULL may be never passed here, use InterruptSpell or InterruptNonMeleeSpells CurrentSpellTypes CSpellType = pSpell.GetCurrentContainer(); if (pSpell == GetCurrentSpell(CSpellType)) // avoid breaking self return; // break same type spell if it is not delayed InterruptSpell(CSpellType, false); // special breakage effects: switch (CSpellType) { case CurrentSpellTypes.Generic: { // generic spells always break channeled not delayed spells InterruptSpell(CurrentSpellTypes.Channeled, false); // autorepeat breaking if (GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null) { // break autorepeat if not Auto Shot if (m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id != 75) InterruptSpell(CurrentSpellTypes.AutoRepeat); m_AutoRepeatFirstCast = true; } if (pSpell.m_spellInfo.CalcCastTime() > 0) AddUnitState(UnitState.Casting); break; } case CurrentSpellTypes.Channeled: { // channel spells always break generic non-delayed and any channeled spells InterruptSpell(CurrentSpellTypes.Generic, false); InterruptSpell(CurrentSpellTypes.Channeled); // it also does break autorepeat if not Auto Shot if (GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null && m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id != 75) InterruptSpell(CurrentSpellTypes.AutoRepeat); AddUnitState(UnitState.Casting); break; } case CurrentSpellTypes.AutoRepeat: { // only Auto Shoot does not break anything if (pSpell.m_spellInfo.Id != 75) { // generic autorepeats break generic non-delayed and channeled non-delayed spells InterruptSpell(CurrentSpellTypes.Generic, false); InterruptSpell(CurrentSpellTypes.Channeled, false); } // special action: set first cast flag m_AutoRepeatFirstCast = true; break; } default: break; // other spell types don't break anything now } // current spell (if it is still here) may be safely deleted now if (GetCurrentSpell(CSpellType) != null) m_currentSpells[CSpellType].SetReferencedFromCurrent(false); // set new current spell m_currentSpells[CSpellType] = pSpell; pSpell.SetReferencedFromCurrent(true); pSpell.m_selfContainer = m_currentSpells[pSpell.GetCurrentContainer()]; } public bool IsNonMeleeSpellCast(bool withDelayed, bool skipChanneled = false, bool skipAutorepeat = false, bool isAutoshoot = false, bool skipInstant = true) { // We don't do loop here to explicitly show that melee spell is excluded. // Maybe later some special spells will be excluded too. // generic spells are cast when they are not finished and not delayed var currentSpell = GetCurrentSpell(CurrentSpellTypes.Generic); if (currentSpell && (currentSpell.GetState() != SpellState.Finished) && (withDelayed || currentSpell.GetState() != SpellState.Delayed)) { if (!skipInstant || currentSpell.GetCastTime() != 0) { if (!isAutoshoot || !currentSpell.m_spellInfo.HasAttribute(SpellAttr2.NotResetAutoActions)) return true; } } currentSpell = GetCurrentSpell(CurrentSpellTypes.Channeled); // channeled spells may be delayed, but they are still considered cast if (!skipChanneled && currentSpell && (currentSpell.GetState() != SpellState.Finished)) { if (!isAutoshoot || !currentSpell.m_spellInfo.HasAttribute(SpellAttr2.NotResetAutoActions)) return true; } currentSpell = GetCurrentSpell(CurrentSpellTypes.AutoRepeat); // autorepeat spells may be finished or delayed, but they are still considered cast if (!skipAutorepeat && currentSpell) return true; return false; } public uint SpellCriticalDamageBonus(SpellInfo spellProto, uint damage, Unit victim = null) { // Calculate critical bonus int crit_bonus = (int)damage * 2; float crit_mod = 0.0f; crit_mod += (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellProto.GetSchoolMask()) - 1.0f) * 100; if (crit_bonus != 0) MathFunctions.AddPct(ref crit_bonus, (int)crit_mod); MathFunctions.AddPct(ref crit_bonus, victim.GetTotalAuraModifier(AuraType.ModCriticalDamageTakenFromCaster, aurEff => { return aurEff.GetCasterGUID() == GetGUID(); })); crit_bonus -= (int)damage; // adds additional damage to critBonus (from talents) Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spellProto, SpellModOp.CritDamageAndHealing, ref crit_bonus); crit_bonus += (int)damage; return (uint)crit_bonus; } bool IsSpellBlocked(Unit victim, SpellInfo spellProto, WeaponAttackType attackType = WeaponAttackType.BaseAttack) { // These spells can't be blocked if (spellProto != null && (spellProto.HasAttribute(SpellAttr0.ImpossibleDodgeParryBlock) || spellProto.HasAttribute(SpellAttr3.IgnoreHitResult))) return false; // Can't block when casting/controlled if (victim.IsNonMeleeSpellCast(false) || victim.HasUnitState(UnitState.Controlled)) return false; if (victim.HasAuraType(AuraType.IgnoreHitDirection) || victim.HasInArc(MathFunctions.PI, this)) { float blockChance = GetUnitBlockChance(attackType, victim); if (blockChance != 0 && RandomHelper.randChance(blockChance)) return true; } return false; } public void _DeleteRemovedAuras() { while (!m_removedAuras.Empty()) { m_removedAuras.Remove(m_removedAuras.First()); } } public bool HasStealthAura() { return HasAuraType(AuraType.ModStealth); } public bool HasInvisibilityAura() { return HasAuraType(AuraType.ModInvisibility); } public bool IsFeared() { return HasAuraType(AuraType.ModFear); } public bool IsFrozen() { return HasAuraState(AuraStateType.Frozen); } public bool IsPolymorphed() { uint transformId = GetTransForm(); if (transformId == 0) return false; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(transformId, GetMap().GetDifficultyID()); if (spellInfo == null) return false; return spellInfo.GetSpellSpecific() == SpellSpecificType.MagePolymorph; } public void DealHeal(HealInfo healInfo) { int gain = 0; Unit victim = healInfo.GetTarget(); uint addhealth = healInfo.GetHeal(); if (victim.IsAIEnabled) victim.GetAI().HealReceived(this, addhealth); if (IsAIEnabled) GetAI().HealDone(victim, addhealth); if (addhealth != 0) gain = (int)victim.ModifyHealth(addhealth); // Hook for OnHeal Event uint tempGain = (uint)gain; Global.ScriptMgr.OnHeal(this, victim, ref tempGain); gain = (int)tempGain; Unit unit = this; if (IsTypeId(TypeId.Unit) && IsTotem()) unit = GetOwner(); Player player = unit.ToPlayer(); if (player != null) { Battleground bg = player.GetBattleground(); if (bg) bg.UpdatePlayerScore(player, ScoreType.HealingDone, (uint)gain); // use the actual gain, as the overheal shall not be counted, skip gain 0 (it ignored anyway in to criteria) if (gain != 0) player.UpdateCriteria(CriteriaTypes.HealingDone, (uint)gain, 0, 0, victim); player.UpdateCriteria(CriteriaTypes.HighestHealCasted, addhealth); } if ((player = victim.ToPlayer()) != null) { player.UpdateCriteria(CriteriaTypes.TotalHealingReceived, (uint)gain); player.UpdateCriteria(CriteriaTypes.HighestHealingReceived, addhealth); } if (gain != 0) healInfo.SetEffectiveHeal(gain > 0 ? (uint)gain : 0u); } void SendHealSpellLog(HealInfo healInfo, bool critical = false) { SpellHealLog spellHealLog = new(); spellHealLog.TargetGUID = healInfo.GetTarget().GetGUID(); spellHealLog.CasterGUID = healInfo.GetHealer().GetGUID(); spellHealLog.SpellID = healInfo.GetSpellInfo().Id; spellHealLog.Health = healInfo.GetHeal(); spellHealLog.OriginalHeal = (int)healInfo.GetOriginalHeal(); spellHealLog.OverHeal = healInfo.GetHeal() - healInfo.GetEffectiveHeal(); spellHealLog.Absorbed = healInfo.GetAbsorb(); spellHealLog.Crit = critical; spellHealLog.LogData.Initialize(healInfo.GetTarget()); SendCombatLogMessage(spellHealLog); } public uint HealBySpell(HealInfo healInfo, bool critical = false) { // calculate heal absorb and reduce healing CalcHealAbsorb(healInfo); DealHeal(healInfo); SendHealSpellLog(healInfo, critical); return healInfo.GetEffectiveHeal(); } public void ApplyCastTimePercentMod(float val, bool apply) { if (val > 0.0f) { ApplyPercentModUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModCastingSpeed), val, !apply); ApplyPercentModUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModSpellHaste), val, !apply); ApplyPercentModUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModHasteRegen), val, !apply); } else { ApplyPercentModUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModCastingSpeed), -val, apply); ApplyPercentModUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModSpellHaste), -val, apply); ApplyPercentModUpdateFieldValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.ModHasteRegen), -val, apply); } } public void RemoveAllGroupBuffsFromCaster(ObjectGuid casterGUID) { foreach (var pair in GetOwnedAuras()) { Aura aura = pair.Value; if (aura.GetCasterGUID() == casterGUID && aura.GetSpellInfo().IsGroupBuff()) RemoveOwnedAura(pair); } } public void DelayOwnedAuras(uint spellId, ObjectGuid caster, int delaytime) { var range = m_ownedAuras.LookupByKey(spellId); foreach (var aura in range) { if (caster.IsEmpty() || aura.GetCasterGUID() == caster) { if (aura.GetDuration() < delaytime) aura.SetDuration(0); else aura.SetDuration(aura.GetDuration() - delaytime); // update for out of range group members (on 1 slot use) aura.SetNeedClientUpdateForTargets(); Log.outDebug(LogFilter.Spells, "Aura {0} partially interrupted on {1}, new duration: {2} ms", aura.GetId(), GetGUID().ToString(), aura.GetDuration()); } } } public void CalculateSpellDamageTaken(SpellNonMeleeDamage damageInfo, int damage, SpellInfo spellInfo, WeaponAttackType attackType = WeaponAttackType.BaseAttack, bool crit = false) { if (damage < 0) return; Unit victim = damageInfo.target; if (victim == null || !victim.IsAlive()) return; SpellSchoolMask damageSchoolMask = damageInfo.schoolMask; if (IsDamageReducedByArmor(damageSchoolMask, spellInfo)) damage = (int)CalcArmorReducedDamage(damageInfo.attacker, victim, (uint)damage, spellInfo, attackType); bool blocked = false; // Per-school calc switch (spellInfo.DmgClass) { // Melee and Ranged Spells case SpellDmgClass.Ranged: case SpellDmgClass.Melee: { // Physical Damage if (damageSchoolMask.HasAnyFlag(SpellSchoolMask.Normal)) { // Spells with this attribute were already calculated in MeleeSpellHitResult if (!spellInfo.HasAttribute(SpellAttr3.BlockableSpell)) { // Get blocked status blocked = IsSpellBlocked(victim, spellInfo, attackType); } } if (crit) { damageInfo.HitInfo |= HitInfo.CriticalHit; // Calculate crit bonus uint crit_bonus = (uint)damage; // Apply crit_damage bonus for melee spells Player modOwner = GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(spellInfo, SpellModOp.CritDamageAndHealing, ref crit_bonus); damage += (int)crit_bonus; // Increase crit damage from SPELL_AURA_MOD_CRIT_DAMAGE_BONUS float critPctDamageMod = (GetTotalAuraMultiplierByMiscMask(AuraType.ModCritDamageBonus, (uint)spellInfo.GetSchoolMask()) - 1.0f) * 100; if (critPctDamageMod != 0) MathFunctions.AddPct(ref damage, (int)critPctDamageMod); } // Spell weapon based damage CAN BE crit & blocked at same time if (blocked) { // double blocked amount if block is critical float value = victim.GetBlockPercent(GetLevel()); if (victim.IsBlockCritical()) value *= 2; // double blocked percent damageInfo.blocked = (uint)MathFunctions.CalculatePct(damage, value); if (damage <= damageInfo.blocked) { damageInfo.blocked = (uint)damage; damageInfo.fullBlock = true; } damage -= (int)damageInfo.blocked; } uint dmg = (uint)damage; ApplyResilience(victim, ref dmg); damage = (int)dmg; break; } // Magical Attacks case SpellDmgClass.None: case SpellDmgClass.Magic: { // If crit add critical bonus if (crit) { damageInfo.HitInfo |= HitInfo.CriticalHit; damage = (int)SpellCriticalDamageBonus(spellInfo, (uint)damage, victim); } uint dmg = (uint)damage; ApplyResilience(victim, ref dmg); damage = (int)dmg; break; } default: break; } // Script Hook For CalculateSpellDamageTaken -- Allow scripts to change the Damage post class mitigation calculations Global.ScriptMgr.ModifySpellDamageTaken(damageInfo.target, damageInfo.attacker, ref damage, spellInfo); // Calculate absorb resist if (damage < 0) damage = 0; damageInfo.damage = (uint)damage; damageInfo.originalDamage = (uint)damage; DamageInfo dmgInfo = new(damageInfo, DamageEffectType.SpellDirect, WeaponAttackType.BaseAttack, ProcFlagsHit.None); CalcAbsorbResist(dmgInfo); damageInfo.absorb = dmgInfo.GetAbsorb(); damageInfo.resist = dmgInfo.GetResist(); if (damageInfo.absorb != 0) damageInfo.HitInfo |= (damageInfo.damage - damageInfo.absorb == 0 ? HitInfo.FullAbsorb : HitInfo.PartialAbsorb); if (damageInfo.resist != 0) damageInfo.HitInfo |= (damageInfo.damage - damageInfo.resist == 0 ? HitInfo.FullResist : HitInfo.PartialResist); damageInfo.damage = dmgInfo.GetDamage(); } public void DealSpellDamage(SpellNonMeleeDamage damageInfo, bool durabilityLoss) { if (damageInfo == null) return; Unit victim = damageInfo.target; if (victim == null) return; if (!victim.IsAlive() || victim.HasUnitState(UnitState.InFlight) || (victim.IsTypeId(TypeId.Unit) && victim.ToCreature().IsEvadingAttacks())) return; if (damageInfo.Spell == null) { Log.outDebug(LogFilter.Unit, "Unit.DealSpellDamage has no spell"); return; } // Call default DealDamage CleanDamage cleanDamage = new(damageInfo.cleanDamage, damageInfo.absorb, WeaponAttackType.BaseAttack, MeleeHitOutcome.Normal); DealDamage(victim, damageInfo.damage, cleanDamage, DamageEffectType.SpellDirect, damageInfo.schoolMask, damageInfo.Spell, durabilityLoss); } public void SendSpellNonMeleeDamageLog(SpellNonMeleeDamage log) { SpellNonMeleeDamageLog packet = new(); packet.Me = log.target.GetGUID(); packet.CasterGUID = log.attacker.GetGUID(); packet.CastID = log.castId; packet.SpellID = (int)(log.Spell != null ? log.Spell.Id : 0); packet.Visual = log.SpellVisual; packet.Damage = (int)log.damage; packet.OriginalDamage = (int)log.originalDamage; if (log.damage > log.preHitHealth) packet.Overkill = (int)(log.damage - log.preHitHealth); else packet.Overkill = -1; packet.SchoolMask = (byte)log.schoolMask; packet.Absorbed = (int)log.absorb; packet.Resisted = (int)log.resist; packet.ShieldBlock = (int)log.blocked; packet.Periodic = log.periodicLog; packet.Flags = (int)log.HitInfo; ContentTuningParams contentTuningParams = new(); if (contentTuningParams.GenerateDataForUnits(log.attacker, log.target)) packet.ContentTuning.Set(contentTuningParams); SendCombatLogMessage(packet); } public void SendPeriodicAuraLog(SpellPeriodicAuraLogInfo info) { AuraEffect aura = info.auraEff; SpellPeriodicAuraLog data = new(); data.TargetGUID = GetGUID(); data.CasterGUID = aura.GetCasterGUID(); data.SpellID = aura.GetId(); data.LogData.Initialize(this); SpellPeriodicAuraLog.SpellLogEffect spellLogEffect = new(); spellLogEffect.Effect = (uint)aura.GetAuraType(); spellLogEffect.Amount = info.damage; spellLogEffect.OriginalDamage = (int)info.originalDamage; spellLogEffect.OverHealOrKill = (uint)info.overDamage; spellLogEffect.SchoolMaskOrPower = (uint)aura.GetSpellInfo().GetSchoolMask(); spellLogEffect.AbsorbedOrAmplitude = info.absorb; spellLogEffect.Resisted = info.resist; spellLogEffect.Crit = info.critical; // @todo: implement debug info ContentTuningParams contentTuningParams = new(); Unit caster = Global.ObjAccessor.GetUnit(this, aura.GetCasterGUID()); if (caster && contentTuningParams.GenerateDataForUnits(caster, this)) spellLogEffect.ContentTuning.Set(contentTuningParams); data.Effects.Add(spellLogEffect); SendCombatLogMessage(data); } public void SendSpellMiss(Unit target, uint spellID, SpellMissInfo missInfo) { SpellMissLog spellMissLog = new(); spellMissLog.SpellID = spellID; spellMissLog.Caster = GetGUID(); spellMissLog.Entries.Add(new SpellLogMissEntry(target.GetGUID(), (byte)missInfo)); SendMessageToSet(spellMissLog, true); } void SendSpellDamageResist(Unit target, uint spellId) { ProcResist procResist = new(); procResist.Caster = GetGUID(); procResist.SpellID = spellId; procResist.Target = target.GetGUID(); SendMessageToSet(procResist, true); } public void SendSpellDamageImmune(Unit target, uint spellId, bool isPeriodic) { SpellOrDamageImmune spellOrDamageImmune = new(); spellOrDamageImmune.CasterGUID = GetGUID(); spellOrDamageImmune.VictimGUID = target.GetGUID(); spellOrDamageImmune.SpellID = spellId; spellOrDamageImmune.IsPeriodic = isPeriodic; SendMessageToSet(spellOrDamageImmune, true); } public void SendSpellInstakillLog(uint spellId, Unit caster, Unit target = null) { SpellInstakillLog spellInstakillLog = new(); spellInstakillLog.Caster = caster.GetGUID(); spellInstakillLog.Target = target ? target.GetGUID() : caster.GetGUID(); spellInstakillLog.SpellID = spellId; SendMessageToSet(spellInstakillLog, false); } public void RemoveAurasOnEvade() { if (IsCharmedOwnedByPlayerOrPlayer()) // if it is a player owned creature it should not remove the aura return; // don't remove vehicle auras, passengers aren't supposed to drop off the vehicle // don't remove clone caster on evade (to be verified) RemoveAllAurasExceptType(AuraType.ControlVehicle, AuraType.CloneCaster); } public void RemoveAllAurasOnDeath() { // used just after dieing to remove all visible auras // and disable the mods for the passive ones foreach (var app in GetAppliedAuras()) { if (app.Value == null) continue; Aura aura = app.Value.GetBase(); if (!aura.IsPassive() && !aura.IsDeathPersistent()) _UnapplyAura(app, AuraRemoveMode.Death); } foreach (var pair in GetOwnedAuras()) { Aura aura = pair.Value; if (pair.Value == null) continue; if (!aura.IsPassive() && !aura.IsDeathPersistent()) RemoveOwnedAura(pair, AuraRemoveMode.Death); } } public void RemoveMovementImpairingAuras(bool withRoot) { if (withRoot) RemoveAurasWithMechanic(1 << (int)Mechanics.Root); // 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 (SpellEffectInfo effect in aura.GetSpellInfo().GetEffects()) { if (effect == null || !effect.IsEffect()) continue; if (Convert.ToBoolean((1 << (int)effect.EffectIndex) & pair.Value.GetEffectMask())) aura.GetEffect(effect.EffectIndex).ChangeAmount(0); } } } public void RemoveAllAurasRequiringDeadTarget() { foreach (var app in GetAppliedAuras()) { Aura aura = app.Value.GetBase(); if (!aura.IsPassive() && aura.GetSpellInfo().IsRequiringDeadTarget()) _UnapplyAura(app, AuraRemoveMode.Default); } foreach (var aura in GetOwnedAuras()) { if (!aura.Value.IsPassive() && aura.Value.GetSpellInfo().IsRequiringDeadTarget()) RemoveOwnedAura(aura, AuraRemoveMode.Default); } } public AuraEffect IsScriptOverriden(SpellInfo spell, int script) { var auras = GetAuraEffectsByType(AuraType.OverrideClassScripts); foreach (var eff in auras) { if (eff.GetMiscValue() == script) if (eff.IsAffectingSpell(spell)) return eff; } return null; } public virtual bool IsAffectedByDiminishingReturns() { return (GetCharmerOrOwnerPlayerOrPlayerItself() != null); } public DiminishingLevels GetDiminishing(DiminishingGroup group) { DiminishingReturn diminish = m_Diminishing[(int)group]; if (diminish.HitCount == 0) return DiminishingLevels.Level1; // If last spell was cast more than 18 seconds ago - reset level. if (diminish.Stack == 0 && Time.GetMSTimeDiffToNow(diminish.HitTime) > 18 * Time.InMilliseconds) return DiminishingLevels.Level1; return diminish.HitCount; } public void IncrDiminishing(SpellInfo auraSpellInfo) { DiminishingGroup group = auraSpellInfo.GetDiminishingReturnsGroupForSpell(); DiminishingLevels currentLevel = GetDiminishing(group); DiminishingLevels maxLevel = auraSpellInfo.GetDiminishingReturnsMaxLevel(); DiminishingReturn diminish = m_Diminishing[(int)group]; if (currentLevel < maxLevel) diminish.HitCount = currentLevel + 1; } public bool ApplyDiminishingToDuration(SpellInfo auraSpellInfo, ref int duration, Unit caster, DiminishingLevels previousLevel) { DiminishingGroup group = auraSpellInfo.GetDiminishingReturnsGroupForSpell(); if (duration == -1 || group == DiminishingGroup.None) return true; int limitDuration = auraSpellInfo.GetDiminishingReturnsLimitDuration(); // test pet/charm masters instead pets/charmeds Unit targetOwner = GetCharmerOrOwner(); Unit casterOwner = caster.GetCharmerOrOwner(); if (limitDuration > 0 && duration > limitDuration) { Unit target = targetOwner ?? this; Unit source = casterOwner ?? caster; if (target.IsAffectedByDiminishingReturns() && source.IsPlayer()) duration = limitDuration; } float mod = 1.0f; switch (group) { case DiminishingGroup.Taunt: if (IsTypeId(TypeId.Unit) && ToCreature().GetCreatureTemplate().FlagsExtra.HasAnyFlag(CreatureFlagsExtra.TauntDiminish)) { DiminishingLevels diminish = previousLevel; switch (diminish) { case DiminishingLevels.Level1: break; case DiminishingLevels.Level2: mod = 0.65f; break; case DiminishingLevels.Level3: mod = 0.4225f; break; case DiminishingLevels.Level4: mod = 0.274625f; break; case DiminishingLevels.TauntImmune: mod = 0.0f; break; default: break; } } break; case DiminishingGroup.AOEKnockback: if (auraSpellInfo.GetDiminishingReturnsGroupType() == DiminishingReturnsType.All || (auraSpellInfo.GetDiminishingReturnsGroupType() == DiminishingReturnsType.Player && (targetOwner ? targetOwner.IsAffectedByDiminishingReturns() : IsAffectedByDiminishingReturns()))) { DiminishingLevels diminish = previousLevel; switch (diminish) { case DiminishingLevels.Level1: break; case DiminishingLevels.Level2: mod = 0.5f; break; default: break; } } break; default: if (auraSpellInfo.GetDiminishingReturnsGroupType() == DiminishingReturnsType.All || (auraSpellInfo.GetDiminishingReturnsGroupType() == DiminishingReturnsType.Player && (targetOwner ? targetOwner.IsAffectedByDiminishingReturns() : IsAffectedByDiminishingReturns()))) { DiminishingLevels diminish = previousLevel; switch (diminish) { case DiminishingLevels.Level1: break; case DiminishingLevels.Level2: mod = 0.5f; break; case DiminishingLevels.Level3: mod = 0.25f; break; case DiminishingLevels.Immune: mod = 0.0f; break; default: break; } } break; } duration = (int)(duration * mod); return duration != 0; } public void ApplyDiminishingAura(DiminishingGroup group, bool apply) { // Checking for existing in the table DiminishingReturn diminish = m_Diminishing[(int)group]; if (apply) ++diminish.Stack; else if (diminish.Stack != 0) { --diminish.Stack; // Remember time after last aura from group removed if (diminish.Stack == 0) diminish.HitTime = GameTime.GetGameTimeMS(); } } void ClearDiminishings() { for (int i = 0; i < (int)DiminishingGroup.Max; ++i) m_Diminishing[i].Clear(); } public uint GetRemainingPeriodicAmount(ObjectGuid caster, uint spellId, AuraType auraType, int effectIndex = 0) { uint amount = 0; var periodicAuras = GetAuraEffectsByType(auraType); foreach (var aurEff in periodicAuras) { if (aurEff.GetCasterGUID() != caster || aurEff.GetId() != spellId || aurEff.GetEffIndex() != effectIndex || aurEff.GetTotalTicks() == 0) continue; amount += (uint)((aurEff.GetAmount() * aurEff.GetRemainingTicks()) / aurEff.GetTotalTicks()); break; } return amount; } // Interrupts public void InterruptNonMeleeSpells(bool withDelayed, uint spell_id = 0, bool withInstant = true) { // generic spells are interrupted if they are not finished or delayed if (GetCurrentSpell(CurrentSpellTypes.Generic) != null && (spell_id == 0 || m_currentSpells[CurrentSpellTypes.Generic].m_spellInfo.Id == spell_id)) InterruptSpell(CurrentSpellTypes.Generic, withDelayed, withInstant); // autorepeat spells are interrupted if they are not finished or delayed if (GetCurrentSpell(CurrentSpellTypes.AutoRepeat) != null && (spell_id == 0 || m_currentSpells[CurrentSpellTypes.AutoRepeat].m_spellInfo.Id == spell_id)) InterruptSpell(CurrentSpellTypes.AutoRepeat, withDelayed, withInstant); // channeled spells are interrupted if they are not finished, even if they are delayed if (GetCurrentSpell(CurrentSpellTypes.Channeled) != null && (spell_id == 0 || m_currentSpells[CurrentSpellTypes.Channeled].m_spellInfo.Id == spell_id)) InterruptSpell(CurrentSpellTypes.Channeled, true, true); } public void InterruptSpell(CurrentSpellTypes spellType, bool withDelayed = true, bool withInstant = true) { Cypher.Assert(spellType < CurrentSpellTypes.Max); Log.outDebug(LogFilter.Unit, "Interrupt spell for unit {0}", GetEntry()); Spell spell = m_currentSpells.LookupByKey(spellType); if (spell != null && (withDelayed || spell.GetState() != SpellState.Delayed) && (withInstant || spell.GetCastTime() > 0 || spell.GetState() == SpellState.Casting)) { // for example, do not let self-stun aura interrupt itself if (!spell.IsInterruptable()) return; // send autorepeat cancel message for autorepeat spells if (spellType == CurrentSpellTypes.AutoRepeat) if (IsTypeId(TypeId.Player)) ToPlayer().SendAutoRepeatCancel(this); m_currentSpells[spellType] = null; if (spell.GetState() != SpellState.Finished) spell.Cancel(); else spell.SetReferencedFromCurrent(false); if (IsCreature() && IsAIEnabled) ToCreature().GetAI().OnSpellCastInterrupt(spell.GetSpellInfo()); } } public void UpdateInterruptMask() { m_interruptMask = SpellAuraInterruptFlags.None; m_interruptMask2 = SpellAuraInterruptFlags2.None; foreach (AuraApplication aurApp in m_interruptableAuras) { m_interruptMask |= aurApp.GetBase().GetSpellInfo().AuraInterruptFlags; m_interruptMask2 |= aurApp.GetBase().GetSpellInfo().AuraInterruptFlags2; } Spell spell = GetCurrentSpell(CurrentSpellTypes.Channeled); if (spell != null) { if (spell.GetState() == SpellState.Casting) { m_interruptMask |= spell.m_spellInfo.ChannelInterruptFlags; m_interruptMask2 |= spell.m_spellInfo.ChannelInterruptFlags2; } } } // Auras public List GetSingleCastAuras() { return m_scAuras; } public List> GetOwnedAuras() { return m_ownedAuras.KeyValueList; } public List> GetAppliedAuras() { return m_appliedAuras.KeyValueList; } public Aura AddAura(uint spellId, Unit target) { if (target == null) return null; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spellId, GetMap().GetDifficultyID()); if (spellInfo == null) return null; if (!target.IsAlive() && !spellInfo.IsPassive() && !spellInfo.HasAttribute(SpellAttr2.CanTargetDead)) return null; return AddAura(spellInfo, SpellConst.MaxEffectMask, target); } public Aura AddAura(SpellInfo spellInfo, uint effMask, Unit target) { if (spellInfo == null) return null; if (target.IsImmunedToSpell(spellInfo, this)) return null; for (byte i = 0; i < SpellConst.MaxEffects; ++i) { if (!Convert.ToBoolean(effMask & (1 << i))) continue; if (target.IsImmunedToSpellEffect(spellInfo, i, this)) effMask &= ~(uint)(1 << i); } ObjectGuid castId = ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellInfo.Id, GetMap().GenerateLowGuid(HighGuid.Cast)); Aura aura = Aura.TryRefreshStackOrCreate(spellInfo, castId, effMask, target, this, GetMap().GetDifficultyID()); if (aura != null) { aura.ApplyForTargets(); return aura; } return null; } public bool HandleSpellClick(Unit clicker, sbyte seatId = -1) { bool result = false; uint spellClickEntry = GetVehicleKit() != null ? GetVehicleKit().GetCreatureEntry() : GetEntry(); TriggerCastFlags flags = GetVehicleKit() ? TriggerCastFlags.IgnoreCasterMountedOrOnVehicle : TriggerCastFlags.None; var clickPair = Global.ObjectMgr.GetSpellClickInfoMapBounds(spellClickEntry); foreach (var clickInfo in clickPair) { //! First check simple relations from clicker to clickee if (!clickInfo.IsFitToRequirements(clicker, this)) continue; //! Check database conditions if (!Global.ConditionMgr.IsObjectMeetingSpellClickConditions(spellClickEntry, clickInfo.spellId, clicker, this)) continue; Unit caster = Convert.ToBoolean(clickInfo.castFlags & (byte)SpellClickCastFlags.CasterClicker) ? clicker : this; Unit target = Convert.ToBoolean(clickInfo.castFlags & (byte)SpellClickCastFlags.TargetClicker) ? clicker : this; ObjectGuid origCasterGUID = Convert.ToBoolean(clickInfo.castFlags & (byte)SpellClickCastFlags.OrigCasterOwner) ? GetOwnerGUID() : clicker.GetGUID(); SpellInfo spellEntry = Global.SpellMgr.GetSpellInfo(clickInfo.spellId, caster.GetMap().GetDifficultyID()); // if (!spellEntry) should be checked at npc_spellclick load if (seatId > -1) { byte i = 0; bool valid = false; foreach (SpellEffectInfo effect in spellEntry.GetEffects()) { if (effect == null) continue; if (effect.ApplyAuraName == AuraType.ControlVehicle) { valid = true; break; } ++i; } if (!valid) { Log.outError(LogFilter.Sql, "Spell {0} specified in npc_spellclick_spells is not a valid vehicle enter aura!", clickInfo.spellId); continue; } if (IsInMap(caster)) { CastSpellExtraArgs args = new(flags); args.OriginalCaster = origCasterGUID; args.SpellValueOverrides.Add(SpellValueMod.BasePoint0 + i, seatId + 1); caster.CastSpell(target, clickInfo.spellId, args); } else // This can happen during Player._LoadAuras { int[] bp0 = new int[SpellConst.MaxEffects]; foreach (SpellEffectInfo effect in spellEntry.GetEffects()) { if (effect != null) bp0[effect.EffectIndex] = effect.BasePoints; } bp0[i] = seatId; Aura.TryRefreshStackOrCreate(spellEntry, ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), SpellConst.MaxEffectMask, this, clicker, GetMap().GetDifficultyID(), bp0, null, origCasterGUID); } } else { if (IsInMap(caster)) caster.CastSpell(target, spellEntry.Id, new CastSpellExtraArgs().SetOriginalCaster(origCasterGUID)); else Aura.TryRefreshStackOrCreate(spellEntry, ObjectGuid.Create(HighGuid.Cast, SpellCastSource.Normal, GetMapId(), spellEntry.Id, GetMap().GenerateLowGuid(HighGuid.Cast)), SpellConst.MaxEffectMask, this, clicker, GetMap().GetDifficultyID(), null, null, origCasterGUID); } result = true; } Creature creature = ToCreature(); if (creature && creature.IsAIEnabled) creature.GetAI().OnSpellClick(clicker, ref result); return result; } public bool HasAura(uint spellId, ObjectGuid casterGUID = default, ObjectGuid itemCasterGUID = default, uint reqEffMask = 0) { if (GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask) != null) return true; return false; } public bool HasAuraEffect(uint spellId, uint effIndex, ObjectGuid casterGUID = default) { var range = m_appliedAuras.LookupByKey(spellId); if (!range.Empty()) { foreach (var aura in range) if (aura.HasEffect(effIndex) && (casterGUID.IsEmpty() || aura.GetBase().GetCasterGUID() == casterGUID)) return true; } return false; } public bool HasAuraWithMechanic(uint mechanicMask) { foreach (var pair in GetAppliedAuras()) { SpellInfo spellInfo = pair.Value.GetBase().GetSpellInfo(); if (spellInfo.Mechanic != 0 && Convert.ToBoolean(mechanicMask & (1 << (int)spellInfo.Mechanic))) return true; foreach (SpellEffectInfo effect in spellInfo.GetEffects()) if (effect != null && effect.Effect != 0 && effect.Mechanic != 0) if (Convert.ToBoolean(mechanicMask & (1 << (int)effect.Mechanic))) return true; } return false; } // target dependent range checks public float GetSpellMaxRangeForTarget(Unit target, SpellInfo spellInfo) { if (spellInfo.RangeEntry == null) return 0; if (spellInfo.RangeEntry.RangeMax[0] == spellInfo.RangeEntry.RangeMax[1]) return spellInfo.GetMaxRange(); if (!target) return spellInfo.GetMaxRange(true); return spellInfo.GetMaxRange(!IsHostileTo(target)); } public float GetSpellMinRangeForTarget(Unit target, SpellInfo spellInfo) { if (spellInfo.RangeEntry == null) return 0; if (spellInfo.RangeEntry.RangeMin[0] == spellInfo.RangeEntry.RangeMin[1]) return spellInfo.GetMinRange(); if (!target) return spellInfo.GetMinRange(true); return spellInfo.GetMinRange(!IsHostileTo(target)); } public bool HasAuraType(AuraType auraType) { return !m_modAuras.LookupByKey(auraType).Empty(); } public bool HasAuraTypeWithCaster(AuraType auratype, ObjectGuid caster) { var mTotalAuraList = GetAuraEffectsByType(auratype); foreach (var aura in mTotalAuraList) if (caster == aura.GetCasterGUID()) return true; return false; } public bool HasAuraTypeWithMiscvalue(AuraType auratype, int miscvalue) { var mTotalAuraList = GetAuraEffectsByType(auratype); foreach (var aura in mTotalAuraList) if (miscvalue == aura.GetMiscValue()) return true; return false; } public bool HasAuraTypeWithAffectMask(AuraType auratype, SpellInfo affectedSpell) { var mTotalAuraList = GetAuraEffectsByType(auratype); foreach (var aura in mTotalAuraList) if (aura.IsAffectingSpell(affectedSpell)) return true; return false; } public bool HasAuraTypeWithValue(AuraType auratype, int value) { var mTotalAuraList = GetAuraEffectsByType(auratype); foreach (var aura in mTotalAuraList) if (value == aura.GetAmount()) return true; return false; } public bool HasNegativeAuraWithInterruptFlag(SpellAuraInterruptFlags flag, ObjectGuid guid = default) { if (!HasInterruptFlag(flag)) return false; foreach (var aura in m_interruptableAuras) { if (!aura.IsPositive() && aura.GetBase().GetSpellInfo().HasAuraInterruptFlag(flag) && (guid.IsEmpty() || aura.GetBase().GetCasterGUID() == guid)) return true; } return false; } public bool HasNegativeAuraWithInterruptFlag(SpellAuraInterruptFlags2 flag, ObjectGuid guid = default) { if (!HasInterruptFlag(flag)) return false; foreach (var aura in m_interruptableAuras) { if (!aura.IsPositive() && aura.GetBase().GetSpellInfo().HasAuraInterruptFlag(flag) && (guid.IsEmpty() || aura.GetBase().GetCasterGUID() == guid)) return true; } return false; } public bool HasStrongerAuraWithDR(SpellInfo auraSpellInfo, Unit caster) { DiminishingGroup diminishGroup = auraSpellInfo.GetDiminishingReturnsGroupForSpell(); DiminishingLevels level = GetDiminishing(diminishGroup); foreach (var itr in GetAppliedAuras()) { SpellInfo spellInfo = itr.Value.GetBase().GetSpellInfo(); if (spellInfo.GetDiminishingReturnsGroupForSpell() != diminishGroup) continue; int existingDuration = itr.Value.GetBase().GetMaxDuration(); int newDuration = auraSpellInfo.GetMaxDuration(); ApplyDiminishingToDuration(auraSpellInfo, ref newDuration, caster, level); if (newDuration > 0 && newDuration < existingDuration) return true; } return false; } public uint GetAuraCount(uint spellId) { uint count = 0; var range = m_appliedAuras.LookupByKey(spellId); foreach (var aura in range) { if (aura.GetBase().GetStackAmount() == 0) ++count; else count += aura.GetBase().GetStackAmount(); } return count; } public Aura GetAuraOfRankedSpell(uint spellId, ObjectGuid casterGUID = default, ObjectGuid itemCasterGUID = default, uint reqEffMask = 0) { var aurApp = GetAuraApplicationOfRankedSpell(spellId, casterGUID, itemCasterGUID, reqEffMask); return aurApp?.GetBase(); } AuraApplication GetAuraApplicationOfRankedSpell(uint spellId, ObjectGuid casterGUID = default, ObjectGuid itemCasterGUID = default, uint reqEffMask = 0, AuraApplication except = null) { uint rankSpell = Global.SpellMgr.GetFirstSpellInChain(spellId); while (rankSpell != 0) { AuraApplication aurApp = GetAuraApplication(rankSpell, casterGUID, itemCasterGUID, reqEffMask, except); if (aurApp != null) return aurApp; rankSpell = Global.SpellMgr.GetNextSpellInChain(rankSpell); } return null; } public List GetDispellableAuraList(Unit caster, uint dispelMask, bool isReflect = false) { List dispelList = new(); var auras = GetOwnedAuras(); foreach (var pair in auras) { Aura aura = pair.Value; AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); if (aurApp == null) continue; // don't try to remove passive auras if (aura.IsPassive()) continue; if (Convert.ToBoolean(aura.GetSpellInfo().GetDispelMask() & dispelMask)) { // do not remove positive auras if friendly target // negative auras if non-friendly // unless we're reflecting (dispeller eliminates one of it's benefitial buffs) if (isReflect != (aurApp.IsPositive() == IsFriendlyTo(caster))) continue; // 2.4.3 Patch Notes: "Dispel effects will no longer attempt to remove effects that have 100% dispel resistance." int chance = aura.CalcDispelChance(this, !IsFriendlyTo(caster)); if (chance == 0) continue; // The charges / stack amounts don't count towards the total number of auras that can be dispelled. // Ie: A dispel on a target with 5 stacks of Winters Chill and a Polymorph has 1 / (1 + 1) . 50% chance to dispell // Polymorph instead of 1 / (5 + 1) . 16%. bool dispelCharges = aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges); byte charges = dispelCharges ? aura.GetCharges() : aura.GetStackAmount(); if (charges > 0) dispelList.Add(new DispelableAura(aura, chance, charges)); } } return dispelList; } bool IsInterruptFlagIgnoredForSpell(SpellAuraInterruptFlags2 flag, Unit unit, SpellInfo spellInfo) { return false; } bool IsInterruptFlagIgnoredForSpell(SpellAuraInterruptFlags flag, Unit unit, SpellInfo spellInfo) { return flag == SpellAuraInterruptFlags.Moving && unit.HasAuraTypeWithAffectMask(AuraType.CastWhileWalking, spellInfo); } public void RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags flag, uint except = 0) { if (!HasInterruptFlag(flag)) return; // interrupt auras for (var i = 0; i < m_interruptableAuras.Count; i++) { Aura aura = m_interruptableAuras[i].GetBase(); if (aura.GetSpellInfo().HasAuraInterruptFlag(flag) && (except == 0 || aura.GetId() != except) && !IsInterruptFlagIgnoredForSpell(flag, this, aura.GetSpellInfo())) { uint removedAuras = m_removedAurasCount; RemoveAura(aura, AuraRemoveMode.Interrupt); if (m_removedAurasCount > removedAuras + 1) i = 0; } } // interrupt channeled spell Spell spell = GetCurrentSpell(CurrentSpellTypes.Channeled); if (spell != null) { if (spell.GetState() == SpellState.Casting && spell.GetSpellInfo().HasChannelInterruptFlag(flag) && spell.GetSpellInfo().Id != except && !IsInterruptFlagIgnoredForSpell(flag, this, spell.GetSpellInfo())) InterruptNonMeleeSpells(false); } UpdateInterruptMask(); } public void RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2 flag, uint except = 0) { if (!HasInterruptFlag(flag)) return; // interrupt auras for (var i = 0; i < m_interruptableAuras.Count; i++) { Aura aura = m_interruptableAuras[i].GetBase(); if (aura.GetSpellInfo().HasAuraInterruptFlag(flag) && (except == 0 || aura.GetId() != except) && !IsInterruptFlagIgnoredForSpell(flag, this, aura.GetSpellInfo())) { uint removedAuras = m_removedAurasCount; RemoveAura(aura, AuraRemoveMode.Interrupt); if (m_removedAurasCount > removedAuras + 1) i = 0; } } // interrupt channeled spell Spell spell = GetCurrentSpell(CurrentSpellTypes.Channeled); if (spell != null) { if (spell.GetState() == SpellState.Casting && spell.GetSpellInfo().HasChannelInterruptFlag(flag) && spell.GetSpellInfo().Id != except && !IsInterruptFlagIgnoredForSpell(flag, this, spell.GetSpellInfo())) InterruptNonMeleeSpells(false); } UpdateInterruptMask(); } public void RemoveAurasWithMechanic(uint mechanic_mask, AuraRemoveMode removemode = AuraRemoveMode.Default, uint except = 0) { foreach (var app in GetAppliedAuras()) { 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; } } } } public void RemoveAurasDueToSpellBySteal(uint spellId, ObjectGuid casterGUID, Unit stealer) { var range = m_ownedAuras.LookupByKey(spellId); foreach (var aura in range) { if (aura.GetCasterGUID() == casterGUID) { int[] damage = new int[SpellConst.MaxEffects]; int[] baseDamage = new int[SpellConst.MaxEffects]; uint effMask = 0; uint recalculateMask = 0; Unit caster = aura.GetCaster(); for (byte i = 0; i < SpellConst.MaxEffects; ++i) { if (aura.GetEffect(i) != null) { baseDamage[i] = aura.GetEffect(i).GetBaseAmount(); damage[i] = aura.GetEffect(i).GetAmount(); effMask |= 1u << i; if (aura.GetEffect(i).CanBeRecalculated()) recalculateMask |= 1u << i; } else { baseDamage[i] = 0; damage[i] = 0; } } bool stealCharge = aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges); // Cast duration to unsigned to prevent permanent aura's such as Righteous Fury being permanently added to caster uint dur = (uint)Math.Min(2u * Time.Minute * Time.InMilliseconds, aura.GetDuration()); Aura oldAura = stealer.GetAura(aura.GetId(), aura.GetCasterGUID()); if (oldAura != null) { if (stealCharge) oldAura.ModCharges(1); else oldAura.ModStackAmount(1); oldAura.SetDuration((int)dur); } else { // single target state must be removed before aura creation to preserve existing single target aura if (aura.IsSingleTarget()) aura.UnregisterSingleTarget(); Aura newAura = Aura.TryRefreshStackOrCreate(aura.GetSpellInfo(), aura.GetCastGUID(), effMask, stealer, null, aura.GetCastDifficulty(), baseDamage, null, aura.GetCasterGUID()); if (newAura != null) { // created aura must not be single target aura, so stealer won't loose it on recast if (newAura.IsSingleTarget()) { newAura.UnregisterSingleTarget(); // bring back single target aura status to the old aura aura.SetIsSingleTarget(true); caster.GetSingleCastAuras().Add(aura); } // FIXME: using aura.GetMaxDuration() maybe not blizzlike but it fixes stealing of spells like Innervate newAura.SetLoadedState(aura.GetMaxDuration(), (int)dur, stealCharge ? 1 : aura.GetCharges(), 1, recalculateMask, damage); newAura.ApplyForTargets(); } } if (stealCharge) aura.ModCharges(-1, AuraRemoveMode.EnemySpell); else aura.ModStackAmount(-1, AuraRemoveMode.EnemySpell); return; } } } public void RemoveAurasDueToItemSpell(uint spellId, ObjectGuid castItemGuid) { var appliedAuras = m_appliedAuras.LookupByKey(spellId); for (var i = 0; i < appliedAuras.Count; ++i) { AuraApplication app = appliedAuras[i]; if (app.GetBase().GetCastItemGUID() == castItemGuid) { RemoveAura(app); } } } public void RemoveAurasByType(AuraType auraType, ObjectGuid casterGUID = default, Aura except = null, bool negative = true, bool positive = true) { var list = m_modAuras[auraType]; for (var i = 0; i < list.Count; i++) { Aura aura = list[i].GetBase(); AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); if (aura != except && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) && ((negative && !aurApp.IsPositive()) || (positive && aurApp.IsPositive()))) { uint removedAuras = m_removedAurasCount; RemoveAura(aurApp); if (m_removedAurasCount > removedAuras + 1) i = 0; } } } public void RemoveNotOwnSingleTargetAuras(bool onPhaseChange = false) { // Iterate m_ownedAuras - aura is marked as single target in Unit::AddAura (and pushed to m_ownedAuras). // m_appliedAuras will NOT contain the aura before first Unit::Update after adding it to m_ownedAuras. // Quickly removing such an aura will lead to it not being unregistered from caster's single cast auras container // leading to assertion failures if the aura was cast on a player that can // (and is changing map at the point where this function is called). // Such situation occurs when player is logging in inside an instance and fails the entry check for any reason. // The aura that was loaded from db (indirectly, via linked casts) gets removed before it has a chance // to register in m_appliedAuras foreach (var pair in GetOwnedAuras()) { Aura aura = pair.Value; if (aura.GetCasterGUID() != GetGUID() && aura.IsSingleTarget()) { if (onPhaseChange) RemoveOwnedAura(pair); else { Unit caster = aura.GetCaster(); if (!caster || !caster.IsInPhase(this)) RemoveOwnedAura(pair); } } } // single target auras at other targets for (var i = 0; i < m_scAuras.Count; i++) { var aura = m_scAuras[i]; if (aura.GetUnitOwner() != this && (!onPhaseChange || !aura.GetUnitOwner().IsInPhase(this))) aura.Remove(); } } // All aura base removes should go threw this function! public void RemoveOwnedAura(KeyValuePair keyValuePair, AuraRemoveMode removeMode = AuraRemoveMode.Default) { Aura aura = keyValuePair.Value; Cypher.Assert(!aura.IsRemoved()); m_ownedAuras.Remove(keyValuePair); m_removedAuras.Add(aura); // Unregister single target aura if (aura.IsSingleTarget()) aura.UnregisterSingleTarget(); aura._Remove(removeMode); } public void RemoveOwnedAura(uint spellId, ObjectGuid casterGUID = default, uint reqEffMask = 0, AuraRemoveMode removeMode = AuraRemoveMode.Default) { foreach (var pair in GetOwnedAuras()) { if (pair.Key != spellId) continue; if (((pair.Value.GetEffectMask() & reqEffMask) == reqEffMask) && (casterGUID.IsEmpty() || pair.Value.GetCasterGUID() == casterGUID)) RemoveOwnedAura(pair, removeMode); } } public void RemoveOwnedAura(Aura auraToRemove, AuraRemoveMode removeMode = AuraRemoveMode.Default) { if (auraToRemove.IsRemoved()) return; Cypher.Assert(auraToRemove.GetOwner() == this); if (removeMode == AuraRemoveMode.None) { Log.outError(LogFilter.Spells, "Unit.RemoveOwnedAura() called with unallowed removeMode AURA_REMOVE_NONE, spellId {0}", auraToRemove.GetId()); return; } uint spellId = auraToRemove.GetId(); foreach (var pair in GetOwnedAuras()) { if (pair.Key != spellId) continue; if (pair.Value == auraToRemove) { RemoveOwnedAura(pair, removeMode); return; } } Cypher.Assert(false); } public void RemoveAurasDueToSpell(uint spellId, ObjectGuid casterGUID = default, uint reqEffMask = 0, AuraRemoveMode removeMode = AuraRemoveMode.Default) { foreach (var pair in GetAppliedAuras()) { if (pair.Key != spellId) continue; Aura aura = pair.Value.GetBase(); if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID)) { RemoveAura(pair, removeMode); } } } public void RemoveAurasDueToSpellByDispel(uint spellId, uint dispellerSpellId, ObjectGuid casterGUID, Unit dispeller, byte chargesRemoved = 1) { foreach (var pair in GetOwnedAuras()) { if (pair.Key != spellId) continue; Aura aura = pair.Value; if (aura.GetCasterGUID() == casterGUID) { DispelInfo dispelInfo = new(dispeller, dispellerSpellId, chargesRemoved); // Call OnDispel hook on AuraScript aura.CallScriptDispel(dispelInfo); if (aura.GetSpellInfo().HasAttribute(SpellAttr7.DispelCharges)) aura.ModCharges(-dispelInfo.GetRemovedCharges(), AuraRemoveMode.EnemySpell); else aura.ModStackAmount(-dispelInfo.GetRemovedCharges(), AuraRemoveMode.EnemySpell); // Call AfterDispel hook on AuraScript aura.CallScriptAfterDispel(dispelInfo); return; } } } public void RemoveAuraFromStack(uint spellId, ObjectGuid casterGUID = default, AuraRemoveMode removeMode = AuraRemoveMode.Default, ushort num = 1) { var range = m_ownedAuras.LookupByKey(spellId); foreach (var aura in range) { if ((aura.GetAuraType() == AuraObjectType.Unit) && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID)) { aura.ModStackAmount(-num, removeMode); return; } } } public void RemoveAura(KeyValuePair appMap, AuraRemoveMode mode = AuraRemoveMode.Default) { var aurApp = appMap.Value; // Do not remove aura which is already being removed if (aurApp.HasRemoveMode()) return; Aura aura = aurApp.GetBase(); _UnapplyAura(appMap, mode); // Remove aura - for Area and Target auras if (aura.GetOwner() == this) aura.Remove(mode); } public void RemoveAura(uint spellId, ObjectGuid caster = default, uint reqEffMask = 0, AuraRemoveMode removeMode = AuraRemoveMode.Default) { var range = m_appliedAuras.LookupByKey(spellId); foreach (var iter in range) { Aura aura = iter.GetBase(); if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) && (caster.IsEmpty() || aura.GetCasterGUID() == caster)) { RemoveAura(iter, removeMode); return; } } } public void RemoveAura(AuraApplication aurApp, AuraRemoveMode mode = AuraRemoveMode.Default) { // we've special situation here, RemoveAura called while during aura removal // this kind of call is needed only when aura effect removal handler // or event triggered by it expects to remove // not yet removed effects of an aura if (aurApp.HasRemoveMode()) { // remove remaining effects of an aura for (byte effectIndex = 0; effectIndex < SpellConst.MaxEffects; ++effectIndex) { if (aurApp.HasEffect(effectIndex)) aurApp._HandleEffect(effectIndex, false); } return; } // no need to remove if (aurApp.GetBase().GetApplicationOfTarget(GetGUID()) != aurApp || aurApp.GetBase().IsRemoved()) return; uint spellId = aurApp.GetBase().GetId(); var range = m_appliedAuras.Where(p => p.Key == spellId); foreach (var pair in range) { if (aurApp == pair.Value) { RemoveAura(pair, mode); return; } } } public void RemoveAura(Aura aura, AuraRemoveMode mode = AuraRemoveMode.Default) { if (aura.IsRemoved()) return; AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); if (aurApp != null) RemoveAura(aurApp, mode); } public void RemoveAurasWithAttribute(SpellAttr0 flags) { foreach (var app in GetAppliedAuras()) { SpellInfo spell = app.Value.GetBase().GetSpellInfo(); if (spell.HasAttribute(flags)) RemoveAura(app); } } public void RemoveAurasWithFamily(SpellFamilyNames family, FlagArray128 familyFlag, ObjectGuid casterGUID) { foreach (var pair in GetAppliedAuras()) { Aura aura = pair.Value.GetBase(); if (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) { SpellInfo spell = aura.GetSpellInfo(); if (spell.SpellFamilyName == family && spell.SpellFamilyFlags & familyFlag) { RemoveAura(pair); continue; } } } } public void RemoveAppliedAuras(Func check) { foreach (var pair in GetAppliedAuras()) { if (check(pair.Value)) RemoveAura(pair); } } public void RemoveOwnedAuras(Func check) { foreach (var pair in GetOwnedAuras()) { if (check(pair.Value)) RemoveOwnedAura(pair); } } void RemoveAppliedAuras(uint spellId, Func check) { var list = m_appliedAuras.LookupByKey(spellId); foreach (var app in list) { if (check(app)) RemoveAura(app); } } void RemoveOwnedAuras(uint spellId, Func check) { var list = m_ownedAuras.LookupByKey(spellId); foreach (var aura in list) { if (check(aura)) RemoveOwnedAura(aura); } } public void RemoveAurasByType(AuraType auraType, Func check) { var list = m_modAuras[auraType]; for (var i = 0; i < list.Count; ++i) { Aura aura = m_modAuras[auraType][i].GetBase(); AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); Cypher.Assert(aurApp != null); if (check(aurApp)) { uint removedAuras = m_removedAurasCount; RemoveAura(aurApp); if (m_removedAurasCount > removedAuras + 1) i = 0; } } } public void RemoveAurasByShapeShift() { uint mechanic_mask = (1 << (int)Mechanics.Snare) | (1 << (int)Mechanics.Root); foreach (var pair in GetAppliedAuras()) { Aura aura = pair.Value.GetBase(); if ((aura.GetSpellInfo().GetAllEffectsMechanicMask() & mechanic_mask) != 0 && !aura.GetSpellInfo().HasAttribute(SpellCustomAttributes.AuraCC)) { RemoveAura(pair); continue; } } } void RemoveAreaAurasDueToLeaveWorld() { // make sure that all area auras not applied on self are removed foreach (var pair in GetOwnedAuras()) { var appMap = pair.Value.GetApplicationMap(); foreach (var aurApp in appMap.Values.ToList()) { Unit target = aurApp.GetTarget(); if (target == this) continue; target.RemoveAura(aurApp); // things linked on aura remove may apply new area aura - so start from the beginning } } // remove area auras owned by others foreach (var pair in GetAppliedAuras()) { if (pair.Value.GetBase().GetOwner() != this) RemoveAura(pair); } } public void RemoveAllAuras() { // this may be a dead loop if some events on aura remove will continiously apply aura on remove // we want to have all auras removed, so use your brain when linking events while (!m_appliedAuras.Empty()) _UnapplyAura(m_appliedAuras.FirstOrDefault(), AuraRemoveMode.Default); while (!m_ownedAuras.Empty()) RemoveOwnedAura(m_ownedAuras.FirstOrDefault()); } public void RemoveArenaAuras() { // in join, remove positive buffs, on end, remove negative // used to remove positive visible auras in arenas RemoveAppliedAuras(aurApp => { Aura aura = aurApp.GetBase(); return !aura.GetSpellInfo().HasAttribute(SpellAttr4.DontRemoveInArena) // don't remove stances, shadowform, pally/hunter auras && !aura.IsPassive() // don't remove passive auras && (aurApp.IsPositive() || !aura.GetSpellInfo().HasAttribute(SpellAttr3.DeathPersistent)); // not negative death persistent auras }); } public void RemoveAllAurasExceptType(AuraType type) { foreach (var pair in GetAppliedAuras()) { if (pair.Value == null) continue; Aura aura = pair.Value.GetBase(); if (!aura.GetSpellInfo().HasAura(type)) _UnapplyAura(pair, AuraRemoveMode.Default); } foreach (var pair in GetOwnedAuras()) { if (pair.Value == null) continue; Aura aura = pair.Value; if (!aura.GetSpellInfo().HasAura(type)) RemoveOwnedAura(pair, AuraRemoveMode.Default); } } public void RemoveAllAurasExceptType(AuraType type1, AuraType type2) { foreach (var pair in GetAppliedAuras()) { Aura aura = pair.Value.GetBase(); if (!aura.GetSpellInfo().HasAura(type1) || !aura.GetSpellInfo().HasAura(type2)) _UnapplyAura(pair, AuraRemoveMode.Default); } foreach (var pair in GetOwnedAuras()) { Aura aura = pair.Value; if (!aura.GetSpellInfo().HasAura(type1) || !aura.GetSpellInfo().HasAura(type2)) RemoveOwnedAura(pair, AuraRemoveMode.Default); } } public void ModifyAuraState(AuraStateType flag, bool apply) { uint mask = 1u << ((int)flag - 1); if (apply) { if ((m_unitData.AuraState & mask) == 0) { SetUpdateFieldFlagValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AuraState), mask); if (IsTypeId(TypeId.Player)) { var sp_list = ToPlayer().GetSpellMap(); foreach (var spell in sp_list) { if (spell.Value.State == PlayerSpellState.Removed || spell.Value.Disabled) continue; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell.Key, Difficulty.None); if (spellInfo == null || !spellInfo.IsPassive()) continue; if (spellInfo.CasterAuraState == flag) CastSpell(this, spell.Key, true); } } else if (IsPet()) { Pet pet = ToPet(); foreach (var spell in pet.m_spells) { if (spell.Value.state == PetSpellState.Removed) continue; SpellInfo spellInfo = Global.SpellMgr.GetSpellInfo(spell.Key, Difficulty.None); if (spellInfo == null || !spellInfo.IsPassive()) continue; if (spellInfo.CasterAuraState == flag) CastSpell(this, spell.Key, true); } } } } else { if ((m_unitData.AuraState & mask) != 0) { RemoveUpdateFieldFlagValue(m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AuraState), mask); foreach (var app in GetAppliedAuras()) { if (app.Value == null) continue; SpellInfo spellProto = app.Value.GetBase().GetSpellInfo(); if (app.Value.GetBase().GetCasterGUID() == GetGUID() && spellProto.CasterAuraState == flag && (spellProto.IsPassive() || flag != AuraStateType.Enraged)) RemoveAura(app); } } } } public bool HasAuraState(AuraStateType flag, SpellInfo spellProto = null, Unit caster = null) { if (caster != null) { if (spellProto != null) { if (caster.HasAuraTypeWithAffectMask(AuraType.AbilityIgnoreAurastate, spellProto)) return true; } // Check per caster aura state // If aura with aurastate by caster not found return false if (Convert.ToBoolean((1 << (int)flag) & (int)AuraStateType.PerCasterAuraStateMask)) { var range = m_auraStateAuras.LookupByKey(flag); foreach (var auraApp in range) if (auraApp.GetBase().GetCasterGUID() == caster.GetGUID()) return true; return false; } } return (m_unitData.AuraState & (1 << ((int)flag - 1))) != 0; } SpellSchools GetSpellSchoolByAuraGroup(UnitMods unitMod) { SpellSchools school = SpellSchools.Normal; switch (unitMod) { case UnitMods.ResistanceHoly: school = SpellSchools.Holy; break; case UnitMods.ResistanceFire: school = SpellSchools.Fire; break; case UnitMods.ResistanceNature: school = SpellSchools.Nature; break; case UnitMods.ResistanceFrost: school = SpellSchools.Frost; break; case UnitMods.ResistanceShadow: school = SpellSchools.Shadow; break; case UnitMods.ResistanceArcane: school = SpellSchools.Arcane; break; } return school; } public void _ApplyAllAuraStatMods() { foreach (var i in GetAppliedAuras()) i.Value.GetBase().HandleAllEffects(i.Value, AuraEffectHandleModes.Stat, true); } public void _RemoveAllAuraStatMods() { foreach (var i in GetAppliedAuras()) i.Value.GetBase().HandleAllEffects(i.Value, AuraEffectHandleModes.Stat, false); } // removes aura application from lists and unapplies effects public void _UnapplyAura(KeyValuePair pair, AuraRemoveMode removeMode) { AuraApplication aurApp = pair.Value; Cypher.Assert(aurApp != null); Cypher.Assert(!aurApp.HasRemoveMode()); Cypher.Assert(aurApp.GetTarget() == this); aurApp.SetRemoveMode(removeMode); Aura aura = aurApp.GetBase(); Log.outDebug(LogFilter.Spells, "Aura {0} now is remove mode {1}", aura.GetId(), removeMode); // dead loop is killing the server probably Cypher.Assert(m_removedAurasCount < 0xFFFFFFFF); ++m_removedAurasCount; Unit caster = aura.GetCaster(); m_appliedAuras.Remove(pair); if (aura.GetSpellInfo().HasAnyAuraInterruptFlag()) { m_interruptableAuras.Remove(aurApp); UpdateInterruptMask(); } bool auraStateFound = false; AuraStateType auraState = aura.GetSpellInfo().GetAuraState(); if (auraState != 0) { bool canBreak = false; // Get mask of all aurastates from remaining auras var list = m_auraStateAuras.LookupByKey(auraState); for (var i = 0; i < list.Count && !(auraStateFound && canBreak);) { if (list[i] == aurApp) { m_auraStateAuras.Remove(auraState, list[i]); list = m_auraStateAuras.LookupByKey(auraState); i = 0; canBreak = true; continue; } auraStateFound = true; ++i; } } aurApp._Remove(); aura._UnapplyForTarget(this, caster, aurApp); // remove effects of the spell - needs to be done after removing aura from lists for (byte c = 0; c < SpellConst.MaxEffects; ++c) { if (aurApp.HasEffect(c)) aurApp._HandleEffect(c, false); } // all effect mustn't be applied Cypher.Assert(aurApp.GetEffectMask() == 0); // Remove totem at next update if totem loses its aura if (aurApp.GetRemoveMode() == AuraRemoveMode.Expire && IsTypeId(TypeId.Unit) && IsTotem()) { if (ToTotem().GetSpell() == aura.GetId() && ToTotem().GetTotemType() == TotemType.Passive) ToTotem().SetDeathState(DeathState.JustDied); } // Remove aurastates only if needed and were not found if (auraState != 0) { if (!auraStateFound) ModifyAuraState(auraState, false); else { // update for casters, some shouldn't 'see' the aura state uint aStateMask = (1u << ((int)auraState - 1)); if ((aStateMask & (uint)AuraStateType.PerCasterAuraStateMask) != 0) { m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AuraState); ForceUpdateFieldChange(); } } } aura.HandleAuraSpecificMods(aurApp, caster, false, false); } public void _UnapplyAura(AuraApplication aurApp, AuraRemoveMode removeMode) { // aura can be removed from unit only if it's applied on it, shouldn't happen Cypher.Assert(aurApp.GetBase().GetApplicationOfTarget(GetGUID()) == aurApp); uint spellId = aurApp.GetBase().GetId(); var range = m_appliedAuras.LookupByKey(spellId); foreach (var app in range) { if (app == aurApp) { _UnapplyAura(new KeyValuePair(spellId, app), removeMode); return; } } Cypher.Assert(false); } public AuraEffect GetAuraEffect(uint spellId, uint effIndex, ObjectGuid casterGUID = default) { var range = m_appliedAuras.LookupByKey(spellId); if (!range.Empty()) { foreach (var aura in range) { if (aura.HasEffect(effIndex) && (casterGUID.IsEmpty() || aura.GetBase().GetCasterGUID() == casterGUID)) { return aura.GetBase().GetEffect(effIndex); } } } return null; } public AuraEffect GetAuraEffectOfRankedSpell(uint spellId, uint effIndex, ObjectGuid casterGUID = default) { uint rankSpell = Global.SpellMgr.GetFirstSpellInChain(spellId); while (rankSpell != 0) { AuraEffect aurEff = GetAuraEffect(rankSpell, effIndex, casterGUID); if (aurEff != null) return aurEff; rankSpell = Global.SpellMgr.GetNextSpellInChain(rankSpell); } return null; } // spell mustn't have familyflags public AuraEffect GetAuraEffect(AuraType type, SpellFamilyNames family, FlagArray128 familyFlag, ObjectGuid casterGUID = default) { var auras = GetAuraEffectsByType(type); foreach (var aura in auras) { SpellInfo spell = aura.GetSpellInfo(); if (spell.SpellFamilyName == family && spell.SpellFamilyFlags & familyFlag) { if (!casterGUID.IsEmpty() && aura.GetCasterGUID() != casterGUID) continue; return aura; } } return null; } public AuraApplication GetAuraApplication(uint spellId, ObjectGuid casterGUID = default, ObjectGuid itemCasterGUID = default, uint reqEffMask = 0, AuraApplication except = null) { var range = m_appliedAuras.LookupByKey(spellId); if (!range.Empty()) { foreach (var app in range) { Aura aura = app.GetBase(); if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) && (itemCasterGUID.IsEmpty() || aura.GetCastItemGUID() == itemCasterGUID) && (except == null || except != app)) { return app; } } } return null; } public Aura GetAura(uint spellId, ObjectGuid casterGUID = default, ObjectGuid itemCasterGUID = default, uint reqEffMask = 0) { AuraApplication aurApp = GetAuraApplication(spellId, casterGUID, itemCasterGUID, reqEffMask); return aurApp?.GetBase(); } public uint BuildAuraStateUpdateForTarget(Unit target) { uint auraStates = m_unitData.AuraState & ~(uint)AuraStateType.PerCasterAuraStateMask; foreach (var state in m_auraStateAuras) if (Convert.ToBoolean((1 << (int)state.Key - 1) & (uint)AuraStateType.PerCasterAuraStateMask)) if (state.Value.GetBase().GetCasterGUID() == target.GetGUID()) auraStates |= (uint)(1 << (int)state.Key - 1); return auraStates; } public bool CanProc() { return m_procDeep == 0; } public void _ApplyAuraEffect(Aura aura, uint effIndex) { Cypher.Assert(aura != null); Cypher.Assert(aura.HasEffect(effIndex)); AuraApplication aurApp = aura.GetApplicationOfTarget(GetGUID()); Cypher.Assert(aurApp != null); if (aurApp.GetEffectMask() == 0) _ApplyAura(aurApp, (uint)(1 << (int)effIndex)); else aurApp._HandleEffect(effIndex, true); } // handles effects of aura application // should be done after registering aura in lists public void _ApplyAura(AuraApplication aurApp, uint effMask) { Aura aura = aurApp.GetBase(); _RemoveNoStackAurasDueToAura(aura); if (aurApp.HasRemoveMode()) return; // Update target aura state flag AuraStateType aState = aura.GetSpellInfo().GetAuraState(); if (aState != 0) { uint aStateMask = (1u << ((int)aState - 1)); // force update so the new caster registers it if (aStateMask.HasAnyFlag((uint)AuraStateType.PerCasterAuraStateMask) && (m_unitData.AuraState & aStateMask) != 0) { m_values.ModifyValue(m_unitData).ModifyValue(m_unitData.AuraState); ForceUpdateFieldChange(); } else ModifyAuraState(aState, true); } if (aurApp.HasRemoveMode()) return; // Sitdown on apply aura req seated if (aura.GetSpellInfo().HasAuraInterruptFlag(SpellAuraInterruptFlags.Standing) && !IsSitState()) SetStandState(UnitStandStateType.Sit); Unit caster = aura.GetCaster(); if (aurApp.HasRemoveMode()) return; aura.HandleAuraSpecificPeriodics(aurApp, caster); aura.HandleAuraSpecificMods(aurApp, caster, true, false); // apply effects of the aura for (byte i = 0; i < SpellConst.MaxEffects; i++) { if (Convert.ToBoolean(effMask & 1 << i) && !(aurApp.HasRemoveMode())) aurApp._HandleEffect(i, true); } } public void _AddAura(UnitAura aura, Unit caster) { Cypher.Assert(!m_cleanupDone); m_ownedAuras.Add(aura.GetId(), aura); _RemoveNoStackAurasDueToAura(aura); if (aura.IsRemoved()) return; aura.SetIsSingleTarget(caster != null && aura.GetSpellInfo().IsSingleTarget() || aura.HasEffectType(AuraType.ControlVehicle)); if (aura.IsSingleTarget()) { // @HACK: Player is not in world during loading auras. //Single target auras are not saved or loaded from database //but may be created as a result of aura links (player mounts with passengers) Cypher.Assert((IsInWorld && !IsDuringRemoveFromWorld()) || (aura.GetCasterGUID() == GetGUID()) || (IsLoading() && aura.HasEffectType(AuraType.ControlVehicle))); // register single target aura caster.m_scAuras.Add(aura); // remove other single target auras var scAuras = caster.GetSingleCastAuras(); for (var i = 0; i < scAuras.Count; ++i) { var aur = scAuras[i]; if (aur != aura && aur.IsSingleTargetWith(aura)) aur.Remove(); } } } public Aura _TryStackingOrRefreshingExistingAura(SpellInfo newAura, uint effMask, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default, bool resetPeriodicTimer = true, ObjectGuid castItemGuid = default, uint castItemId = 0, int castItemLevel = -1) { Cypher.Assert(!casterGUID.IsEmpty() || caster); // Check if these can stack anyway if (casterGUID.IsEmpty() && !newAura.IsStackableOnOneSlotWithDifferentCasters()) casterGUID = caster.GetGUID(); // passive and Incanter's Absorption and auras with different type can stack with themselves any number of times if (!newAura.IsMultiSlotAura()) { // check if cast item changed if (castItem != null) { castItemGuid = castItem.GetGUID(); castItemId = castItem.GetEntry(); Player owner = castItem.GetOwner(); if (owner) castItemLevel = (int)castItem.GetItemLevel(owner); else if (castItem.GetOwnerGUID() == caster.GetGUID()) castItemLevel = (int)castItem.GetItemLevel(caster.ToPlayer()); } // find current aura from spell and change it's stackamount, or refresh it's duration Aura foundAura = GetOwnedAura(newAura.Id, casterGUID, (newAura.HasAttribute(SpellCustomAttributes.EnchantProc) ? castItemGuid : ObjectGuid.Empty), 0); if (foundAura != null) { // effect masks do not match // extremely rare case // let's just recreate aura if (effMask != foundAura.GetEffectMask()) return null; // update basepoints with new values - effect amount will be recalculated in ModStackAmount foreach (SpellEffectInfo effect in newAura.GetEffects()) { if (effect == null) continue; AuraEffect eff = foundAura.GetEffect(effect.EffectIndex); if (eff == null) continue; int bp; if (baseAmount != null) bp = baseAmount[effect.EffectIndex]; else bp = effect.BasePoints; eff.m_baseAmount = bp; } // correct cast item guid if needed if (castItemGuid != foundAura.GetCastItemGUID()) { foundAura.SetCastItemGUID(castItemGuid); foundAura.SetCastItemId(castItemId); foundAura.SetCastItemLevel(castItemLevel); } // try to increase stack amount foundAura.ModStackAmount(1, AuraRemoveMode.Default, resetPeriodicTimer); return foundAura; } } return null; } void _RemoveNoStackAurasDueToAura(Aura aura) { SpellInfo spellProto = aura.GetSpellInfo(); // passive spell special case (only non stackable with ranks) if (spellProto.IsPassiveStackableWithRanks()) return; if (!IsHighestExclusiveAura(aura)) { if (!aura.GetSpellInfo().IsAffectingArea()) { Unit caster = aura.GetCaster(); if (caster && caster.IsTypeId(TypeId.Player)) Spell.SendCastResult(caster.ToPlayer(), aura.GetSpellInfo(), aura.GetSpellVisual(), aura.GetCastGUID(), SpellCastResult.AuraBounced); } aura.Remove(); return; } foreach (var app in GetAppliedAuras()) { if (aura.CanStackWith(app.Value.GetBase())) continue; RemoveAura(app, AuraRemoveMode.Default); } } public int GetHighestExclusiveSameEffectSpellGroupValue(AuraEffect aurEff, AuraType auraType, bool checkMiscValue = false, int miscValue = 0) { int val = 0; var spellGroupList = Global.SpellMgr.GetSpellSpellGroupMapBounds(aurEff.GetSpellInfo().GetFirstRankSpell().Id); foreach (var spellGroup in spellGroupList) { if (Global.SpellMgr.GetSpellGroupStackRule(spellGroup) == SpellGroupStackRule.ExclusiveSameEffect) { var auraEffList = GetAuraEffectsByType(auraType); foreach (var auraEffect in auraEffList) { if (aurEff != auraEffect && (!checkMiscValue || auraEffect.GetMiscValue() == miscValue) && Global.SpellMgr.IsSpellMemberOfSpellGroup(auraEffect.GetSpellInfo().Id, spellGroup)) { // absolute value only if (Math.Abs(val) < Math.Abs(auraEffect.GetAmount())) val = auraEffect.GetAmount(); } } } } return val; } public bool IsHighestExclusiveAura(Aura aura, bool removeOtherAuraApplications = false) { foreach (AuraEffect aurEff in aura.GetAuraEffects()) { if (aurEff == null) continue; AuraType auraType = aurEff.GetSpellEffectInfo().ApplyAuraName; var auras = GetAuraEffectsByType(auraType); for (var i = 0; i < auras.Count;) { AuraEffect existingAurEff = auras[i]; ++i; if (Global.SpellMgr.CheckSpellGroupStackRules(aura.GetSpellInfo(), existingAurEff.GetSpellInfo()) == SpellGroupStackRule.ExclusiveHighest) { int diff = Math.Abs(aurEff.GetAmount()) - Math.Abs(existingAurEff.GetAmount()); if (diff == 0) diff = (int)(aura.GetEffectMask() - existingAurEff.GetBase().GetEffectMask()); if (diff > 0) { Aura auraBase = existingAurEff.GetBase(); // no removing of area auras from the original owner, as that completely cancels them if (removeOtherAuraApplications && (!auraBase.IsArea() || auraBase.GetOwner() != this)) { AuraApplication aurApp = existingAurEff.GetBase().GetApplicationOfTarget(GetGUID()); if (aurApp != null) { bool hasMoreThanOneEffect = auraBase.HasMoreThanOneEffectForType(auraType); uint removedAuras = m_removedAurasCount; RemoveAura(aurApp); if (hasMoreThanOneEffect || m_removedAurasCount > removedAuras + 1) i = 0; } } } else if (diff < 0) return false; } } } return true; } public Aura GetOwnedAura(uint spellId, ObjectGuid casterGUID = default, ObjectGuid itemCasterGUID = default, uint reqEffMask = 0, Aura except = null) { var range = m_ownedAuras.LookupByKey(spellId); foreach (var aura in range) { if (((aura.GetEffectMask() & reqEffMask) == reqEffMask) && (casterGUID.IsEmpty() || aura.GetCasterGUID() == casterGUID) && (itemCasterGUID.IsEmpty() || aura.GetCastItemGUID() == itemCasterGUID) && (except == null || except != aura)) { return aura; } } return null; } public List GetAuraEffectsByType(AuraType type) { return m_modAuras.LookupByKey(type); } public int GetTotalAuraModifier(AuraType auratype) { return GetTotalAuraModifier(auratype, aurEff => true); } public int GetTotalAuraModifier(AuraType auratype, Func predicate) { Dictionary sameEffectSpellGroup = new(); int modifier = 0; var mTotalAuraList = GetAuraEffectsByType(auratype); foreach (AuraEffect aurEff in mTotalAuraList) { if (predicate(aurEff)) { // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual if (!Global.SpellMgr.AddSameEffectStackRuleSpellGroups(aurEff.GetSpellInfo(), auratype, aurEff.GetAmount(), sameEffectSpellGroup)) modifier += aurEff.GetAmount(); } } // Add the highest of the Same Effect Stack Rule SpellGroups to the accumulator foreach (var pair in sameEffectSpellGroup) modifier += pair.Value; return modifier; } public float GetTotalAuraMultiplier(AuraType auratype) { return GetTotalAuraMultiplier(auratype, aurEff => true); } public float GetTotalAuraMultiplier(AuraType auratype, Func predicate) { var mTotalAuraList = GetAuraEffectsByType(auratype); if (mTotalAuraList.Empty()) return 1.0f; Dictionary sameEffectSpellGroup = new(); float multiplier = 1.0f; foreach (var aurEff in mTotalAuraList) { if (predicate(aurEff)) { // Check if the Aura Effect has a the Same Effect Stack Rule and if so, use the highest amount of that SpellGroup // If the Aura Effect does not have this Stack Rule, it returns false so we can add to the multiplier as usual if (!Global.SpellMgr.AddSameEffectStackRuleSpellGroups(aurEff.GetSpellInfo(), auratype, aurEff.GetAmount(), sameEffectSpellGroup)) MathFunctions.AddPct(ref multiplier, aurEff.GetAmount()); } } // Add the highest of the Same Effect Stack Rule SpellGroups to the multiplier foreach (var pair in sameEffectSpellGroup) MathFunctions.AddPct(ref multiplier, pair.Value); return multiplier; } public int GetMaxPositiveAuraModifier(AuraType auratype) { return GetMaxPositiveAuraModifier(auratype, aurEff => true); } public int GetMaxPositiveAuraModifier(AuraType auratype, Func predicate) { var mTotalAuraList = GetAuraEffectsByType(auratype); if (mTotalAuraList.Empty()) return 0; int modifier = 0; foreach (var aurEff in mTotalAuraList) { if (predicate(aurEff)) modifier = Math.Max(modifier, aurEff.GetAmount()); } return modifier; } public int GetMaxNegativeAuraModifier(AuraType auratype) { return GetMaxNegativeAuraModifier(auratype, aurEff => true); } public int GetMaxNegativeAuraModifier(AuraType auratype, Func predicate) { var mTotalAuraList = GetAuraEffectsByType(auratype); if (mTotalAuraList.Empty()) return 0; int modifier = 0; foreach (var aurEff in mTotalAuraList) if (predicate(aurEff)) modifier = Math.Min(modifier, aurEff.GetAmount()); return modifier; } public int GetTotalAuraModifierByMiscMask(AuraType auratype, int miscMask) { return GetTotalAuraModifier(auratype, aurEff => { if ((aurEff.GetMiscValue() & miscMask) != 0) return true; return false; }); } public float GetTotalAuraMultiplierByMiscMask(AuraType auratype, uint miscMask) { return GetTotalAuraMultiplier(auratype, aurEff => { if ((aurEff.GetMiscValue() & miscMask) != 0) return true; return false; }); } public int GetMaxPositiveAuraModifierByMiscMask(AuraType auratype, uint miscMask, AuraEffect except = null) { return GetMaxPositiveAuraModifier(auratype, aurEff => { if (except != aurEff && (aurEff.GetMiscValue() & miscMask) != 0) return true; return false; }); } public int GetMaxNegativeAuraModifierByMiscMask(AuraType auratype, uint miscMask) { return GetMaxNegativeAuraModifier(auratype, aurEff => { if ((aurEff.GetMiscValue() & miscMask) != 0) return true; return false; }); } public int GetTotalAuraModifierByMiscValue(AuraType auratype, int miscValue) { return GetTotalAuraModifier(auratype, aurEff => { if (aurEff.GetMiscValue() == miscValue) return true; return false; }); } public float GetTotalAuraMultiplierByMiscValue(AuraType auratype, int miscValue) { return GetTotalAuraMultiplier(auratype, aurEff => { if (aurEff.GetMiscValue() == miscValue) return true; return false; }); } int GetMaxPositiveAuraModifierByMiscValue(AuraType auratype, int miscValue) { return GetMaxPositiveAuraModifier(auratype, aurEff => { if (aurEff.GetMiscValue() == miscValue) return true; return false; }); } int GetMaxNegativeAuraModifierByMiscValue(AuraType auratype, int miscValue) { return GetMaxNegativeAuraModifier(auratype, aurEff => { if (aurEff.GetMiscValue() == miscValue) return true; return false; }); } public void _RegisterAuraEffect(AuraEffect aurEff, bool apply) { if (apply) m_modAuras.Add(aurEff.GetAuraType(), aurEff); else m_modAuras.Remove(aurEff.GetAuraType(), aurEff); } public float GetTotalAuraModValue(UnitMods unitMod) { if (unitMod >= UnitMods.End) { Log.outError(LogFilter.Unit, "attempt to access non-existing UnitMods in GetTotalAuraModValue()!"); return 0.0f; } float value = MathFunctions.CalculatePct(GetFlatModifierValue(unitMod, UnitModifierFlatType.Base), Math.Max(GetFlatModifierValue(unitMod, UnitModifierFlatType.BasePCTExcludeCreate), -100.0f)); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Base); value += GetFlatModifierValue(unitMod, UnitModifierFlatType.Total); value *= GetPctModifierValue(unitMod, UnitModifierPctType.Total); return value; } public void SetVisibleAura(AuraApplication aurApp) { m_visibleAuras.Add(aurApp); m_visibleAurasToUpdate.Add(aurApp); UpdateAuraForGroup(); } public void RemoveVisibleAura(AuraApplication aurApp) { m_visibleAuras.Remove(aurApp); m_visibleAurasToUpdate.Remove(aurApp); UpdateAuraForGroup(); } void UpdateAuraForGroup() { Player player = ToPlayer(); if (player != null) { if (player.GetGroup() != null) player.SetGroupUpdateFlag(GroupUpdateFlags.Auras); } else if (IsPet()) { Pet pet = ToPet(); if (pet.IsControlled()) pet.SetGroupUpdateFlag(GroupUpdatePetFlags.Auras); } } public SortedSet GetVisibleAuras() { return m_visibleAuras; } public bool HasVisibleAura(AuraApplication aurApp) { return m_visibleAuras.Contains(aurApp); } public void SetVisibleAuraUpdate(AuraApplication aurApp) { m_visibleAurasToUpdate.Add(aurApp); } } }