/* * Copyright (C) 2012-2017 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.DataStorage; using Game.Entities; using Game.Maps; using Game.Network.Packets; using Game.Scripting; using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; namespace Game.Spells { public enum AuraObjectType { Unit, DynObj } public enum AuraRemoveMode { None = 0, Default = 1, // scripted remove, remove by stack with aura with different ids and sc aura remove Cancel, EnemySpell, // dispel and absorb aura destroy Expire, // aura duration has ended ByDeath } public enum AuraFlags { None = 0x00, NoCaster = 0x01, Positive = 0x02, Duration = 0x04, Scalable = 0x08, Negative = 0x10, Unk20 = 0x20 } public class AuraApplication { Unit _target; Aura _base; AuraRemoveMode _removeMode; // Store info for know remove aura reason byte _slot; // Aura slot on unit AuraFlags _flags; // Aura info flag uint _effectsToApply; // Used only at spell hit to determine which effect should be applied bool _needClientUpdate; uint _effectMask; public AuraApplication(Unit target, Unit caster, Aura aura, uint effMask) { _target = target; _base = aura; _removeMode = AuraRemoveMode.None; _slot = SpellConst.MaxAuras; _flags = AuraFlags.None; _effectsToApply = effMask; _needClientUpdate = false; Contract.Assert(GetTarget() != null && GetBase() != null); // Try find slot for aura byte slot = 0; // Lookup for auras already applied from spell foreach (AuraApplication visibleAura in GetTarget().GetVisibleAuras()) { if (slot < visibleAura.GetSlot()) break; ++slot; } // Register Visible Aura if (slot < SpellConst.MaxAuras) { _slot = slot; GetTarget().SetVisibleAura(this); _needClientUpdate = true; Log.outDebug(LogFilter.Spells, "Aura: {0} Effect: {1} put to unit visible auras slot: {2}", GetBase().GetId(), GetEffectMask(), slot); } else Log.outError(LogFilter.Spells, "Aura: {0} Effect: {1} could not find empty unit visible slot", GetBase().GetId(), GetEffectMask()); _InitFlags(caster, effMask); } public void _Remove() { // update for out of range group members if (GetSlot() < SpellConst.MaxAuras) { GetTarget().RemoveVisibleAura(this); ClientUpdate(true); } } void _InitFlags(Unit caster, uint effMask) { // mark as selfcasted if needed _flags |= (GetBase().GetCasterGUID() == GetTarget().GetGUID()) ? AuraFlags.NoCaster : AuraFlags.None; // aura is casted by self or an enemy // one negative effect and we know aura is negative if (IsSelfcasted() || caster == null || !caster.IsFriendlyTo(GetTarget())) { bool negativeFound = false; foreach (SpellEffectInfo effect in GetBase().GetSpellEffectInfos()) { if (effect != null && (Convert.ToBoolean((1 << (int)effect.EffectIndex) & effMask) && !GetBase().GetSpellInfo().IsPositiveEffect(effect.EffectIndex))) { negativeFound = true; break; } } _flags |= negativeFound ? AuraFlags.Negative : AuraFlags.Positive; } // aura is casted by friend // one positive effect and we know aura is positive else { bool positiveFound = false; foreach (SpellEffectInfo effect in GetBase().GetSpellEffectInfos()) { if (effect != null && (Convert.ToBoolean((1 << (int)effect.EffectIndex) & effMask) && GetBase().GetSpellInfo().IsPositiveEffect(effect.EffectIndex))) { positiveFound = true; break; } } _flags |= positiveFound ? AuraFlags.Positive : AuraFlags.Negative; } if (GetBase().GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount) || GetBase().HasEffectType(AuraType.ModMaxCharges) || GetBase().HasEffectType(AuraType.ChargeRecoveryMod) || GetBase().HasEffectType(AuraType.ChargeRecoveryMultiplier)) _flags |= AuraFlags.Scalable; } public void _HandleEffect(uint effIndex, bool apply) { AuraEffect aurEff = GetBase().GetEffect(effIndex); if (aurEff == null) { Log.outError(LogFilter.Spells, "Aura {0} has no effect at effectIndex {1} but _HandleEffect was called", GetBase().GetSpellInfo().Id, effIndex); return; } Contract.Assert(aurEff != null); Contract.Assert(HasEffect(effIndex) == (!apply)); Contract.Assert(Convert.ToBoolean((1 << (int)effIndex) & _effectsToApply)); Log.outDebug(LogFilter.Spells, "AuraApplication._HandleEffect: {0}, apply: {1}: amount: {2}", aurEff.GetAuraType(), apply, aurEff.GetAmount()); if (apply) { Contract.Assert(!Convert.ToBoolean(_effectMask & (1 << (int)effIndex))); _effectMask |= (uint)(1 << (int)effIndex); aurEff.HandleEffect(this, AuraEffectHandleModes.Real, true); } else { Contract.Assert(Convert.ToBoolean(_effectMask & (1 << (int)effIndex))); _effectMask &= ~(uint)(1 << (int)effIndex); aurEff.HandleEffect(this, AuraEffectHandleModes.Real, false); } SetNeedClientUpdate(); } public void SetNeedClientUpdate() { if (_needClientUpdate || GetRemoveMode() != AuraRemoveMode.None) return; _needClientUpdate = true; _target.SetVisibleAuraUpdate(this); } public void BuildUpdatePacket(ref AuraInfo auraInfo, bool remove) { Contract.Assert(_target.HasVisibleAura(this) != remove); auraInfo.Slot = GetSlot(); if (remove) return; auraInfo.AuraData.HasValue = true; Aura aura = GetBase(); AuraDataInfo auraData = auraInfo.AuraData.Value; auraData.CastID = aura.GetCastGUID(); auraData.SpellID = (int)aura.GetId(); auraData.SpellXSpellVisualID = (int)aura.GetSpellXSpellVisualId(); auraData.Flags = GetFlags(); if (aura.GetMaxDuration() > 0 && !aura.GetSpellInfo().HasAttribute(SpellAttr5.HideDuration)) auraData.Flags |= AuraFlags.Duration; auraData.ActiveFlags = GetEffectMask(); if (!aura.GetSpellInfo().HasAttribute(SpellAttr11.ScalesWithItemLevel)) auraData.CastLevel = aura.GetCasterLevel(); else auraData.CastLevel = (ushort)aura.GetCastItemLevel(); // send stack amount for aura which could be stacked (never 0 - causes incorrect display) or charges // stack amount has priority over charges (checked on retail with spell 50262) auraData.Applications = aura.GetSpellInfo().StackAmount != 0 ? aura.GetStackAmount() : aura.GetCharges(); if (!auraData.Flags.HasAnyFlag(AuraFlags.NoCaster)) auraData.CastUnit.Set(aura.GetCasterGUID()); if (auraData.Flags.HasAnyFlag(AuraFlags.Duration)) { auraData.Duration.Set(aura.GetMaxDuration()); auraData.Remaining.Set(aura.GetDuration()); } if (auraData.Flags.HasAnyFlag(AuraFlags.Scalable)) { auraData.Points = new float[GetBase().GetAuraEffects().Length]; foreach (AuraEffect effect in GetBase().GetAuraEffects()) if (effect != null && HasEffect(effect.GetEffIndex())) // Not all of aura's effects have to be applied on every target auraData.Points[effect.GetEffIndex()] = effect.GetAmount(); } } public void ClientUpdate(bool remove = false) { _needClientUpdate = false; AuraUpdate update = new AuraUpdate(); update.UpdateAll = false; update.UnitGUID = GetTarget().GetGUID(); AuraInfo auraInfo = new AuraInfo(); BuildUpdatePacket(ref auraInfo, remove); update.Auras.Add(auraInfo); _target.SendMessageToSet(update, true); } public Unit GetTarget() { return _target; } public Aura GetBase() { return _base; } public byte GetSlot() { return _slot; } public AuraFlags GetFlags() { return _flags; } public uint GetEffectMask() { return _effectMask; } public bool HasEffect(uint effect) { Contract.Assert(effect < SpellConst.MaxEffects); return Convert.ToBoolean(_effectMask & (1 << (int)effect)); } public bool IsPositive() { return _flags.HasAnyFlag(AuraFlags.Positive); } bool IsSelfcasted() { return !_flags.HasAnyFlag(AuraFlags.NoCaster); } public uint GetEffectsToApply() { return _effectsToApply; } public void SetRemoveMode(AuraRemoveMode mode) { _removeMode = mode; } public AuraRemoveMode GetRemoveMode() { return _removeMode; } public bool HasRemoveMode() { return _removeMode != 0; } public bool IsNeedClientUpdate() { return _needClientUpdate; } } public class Aura { const int UPDATE_TARGET_MAP_INTERVAL = 500; public Aura(SpellInfo spellproto, ObjectGuid castId, WorldObject owner, Unit caster, Item castItem, ObjectGuid casterGUID, int castItemLevel) { m_spellInfo = spellproto; m_castGuid = castId; m_casterGuid = !casterGUID.IsEmpty() ? casterGUID : caster.GetGUID(); m_castItemGuid = castItem != null ? castItem.GetGUID() : ObjectGuid.Empty; m_castItemLevel = castItemLevel; m_spellXSpellVisualId = caster ? caster.GetCastSpellXSpellVisualId(spellproto) : spellproto.GetSpellXSpellVisualId(); m_applyTime = Time.UnixTime; m_owner = owner; m_timeCla = 0; m_updateTargetMapInterval = 0; m_casterLevel = caster != null ? caster.getLevel() : m_spellInfo.SpellLevel; m_procCharges = 0; m_stackAmount = 1; m_isRemoved = false; m_isSingleTarget = false; m_isUsingCharges = false; m_lastProcAttemptTime = (DateTime.Now - TimeSpan.FromSeconds(10)); m_lastProcSuccessTime = (DateTime.Now - TimeSpan.FromSeconds(120)); var powers = Global.DB2Mgr.GetSpellPowers(GetId(), caster ? caster.GetMap().GetDifficultyID() : Difficulty.None); foreach (var power in powers) if (power.ManaCostPerSecond != 0 || power.ManaCostPercentagePerSecond > 0.0f) m_periodicCosts.Add(power); if (!m_periodicCosts.Empty()) m_timeCla = 1 * Time.InMilliseconds; m_maxDuration = CalcMaxDuration(caster); m_duration = m_maxDuration; m_procCharges = CalcMaxCharges(caster); m_isUsingCharges = m_procCharges != 0; // m_casterLevel = cast item level/caster level, caster level should be saved to db, confirmed with sniffs } public T GetScript(string scriptName) where T : AuraScript { return (T)GetScriptByName(scriptName); } public AuraScript GetScriptByName(string scriptName) { foreach (var auraScript in m_loadedScripts) if (auraScript._GetScriptName().Equals(scriptName)) return auraScript; return null; } public void _InitEffects(uint effMask, Unit caster, int[] baseAmount) { // shouldn't be in constructor - functions in AuraEffect.AuraEffect use polymorphism _spellEffectInfos = m_spellInfo.GetEffectsForDifficulty(GetOwner().GetMap().GetDifficultyID()); _effects = new AuraEffect[GetSpellEffectInfos().Length]; foreach (SpellEffectInfo effect in GetSpellEffectInfos()) { if (effect != null && Convert.ToBoolean(effMask & (1 << (int)effect.EffectIndex))) _effects[effect.EffectIndex] = new AuraEffect(this, effect.EffectIndex, baseAmount != null ? baseAmount[effect.EffectIndex] : (int?)null, caster); } } public Unit GetCaster() { if (m_owner.GetGUID() == m_casterGuid) return GetUnitOwner(); AuraApplication aurApp = GetApplicationOfTarget(m_casterGuid); if (aurApp != null) return aurApp.GetTarget(); return Global.ObjAccessor.GetUnit(m_owner, m_casterGuid); } public AuraObjectType GetAuraType() { return (m_owner.GetTypeId() == TypeId.DynamicObject) ? AuraObjectType.DynObj : AuraObjectType.Unit; } public virtual void _ApplyForTarget(Unit target, Unit caster, AuraApplication auraApp) { Contract.Assert(target != null); Contract.Assert(auraApp != null); // aura mustn't be already applied on target //Contract.Assert(!IsAppliedOnTarget(target.GetGUID()) && "Aura._ApplyForTarget: aura musn't be already applied on target"); m_applications[target.GetGUID()] = auraApp; // set infinity cooldown state for spells if (caster != null && caster.IsTypeId(TypeId.Player)) { if (m_spellInfo.HasAttribute(SpellAttr0.DisabledWhileActive)) { Item castItem = !m_castItemGuid.IsEmpty() ? caster.ToPlayer().GetItemByGuid(m_castItemGuid) : null; caster.GetSpellHistory().StartCooldown(m_spellInfo, castItem != null ? castItem.GetEntry() : 0, null, true); } } } public virtual void _UnapplyForTarget(Unit target, Unit caster, AuraApplication auraApp) { Contract.Assert(target != null); Contract.Assert(auraApp.HasRemoveMode()); Contract.Assert(auraApp != null); var app = m_applications.LookupByKey(target.GetGUID()); /// @todo Figure out why this happens if (app == null) { Log.outError(LogFilter.Spells, "Aura._UnapplyForTarget, target: {0}, caster: {1}, spell: {2} was not found in owners application map!", target.GetGUID().ToString(), caster ? caster.GetGUID().ToString() : "", auraApp.GetBase().GetSpellInfo().Id); Contract.Assert(false); } // aura has to be already applied Contract.Assert(app == auraApp); m_applications.Remove(target.GetGUID()); m_removedApplications.Add(auraApp); // reset cooldown state for spells if (caster != null && GetSpellInfo().IsCooldownStartedOnEvent()) // note: item based cooldowns and cooldown spell mods with charges ignored (unknown existed cases) caster.GetSpellHistory().SendCooldownEvent(GetSpellInfo()); } // removes aura from all targets // and marks aura as removed public void _Remove(AuraRemoveMode removeMode) { Contract.Assert(!m_isRemoved); m_isRemoved = true; foreach (var pair in m_applications.ToList()) { AuraApplication aurApp = pair.Value; Unit target = aurApp.GetTarget(); target._UnapplyAura(aurApp, removeMode); } if (m_dropEvent != null) { m_dropEvent.ScheduleAbort(); m_dropEvent = null; } } void UpdateTargetMap(Unit caster, bool apply = true) { if (IsRemoved()) return; m_updateTargetMapInterval = UPDATE_TARGET_MAP_INTERVAL; // fill up to date target list // target, effMask Dictionary targets = new Dictionary(); FillTargetMap(ref targets, caster); List targetsToRemove = new List(); // mark all auras as ready to remove foreach (var app in m_applications) { var existing = targets.FirstOrDefault(p => p.Key == app.Value.GetTarget()); // not found in current area - remove the aura if (existing.Key == null) targetsToRemove.Add(app.Value.GetTarget()); else { // needs readding - remove now, will be applied in next update cycle // (dbcs do not have auras which apply on same type of targets but have different radius, so this is not really needed) if (app.Value.GetEffectMask() != existing.Value || !CanBeAppliedOn(existing.Key)) targetsToRemove.Add(app.Value.GetTarget()); // nothing todo - aura already applied // remove from auras to register list targets.Remove(existing.Key); } } // register auras for units foreach (var unit in targets.Keys.ToList()) { // aura mustn't be already applied on target AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); if (aurApp != null) { // the core created 2 different units with same guid // this is a major failue, which i can't fix right now // let's remove one unit from aura list // this may cause area aura "bouncing" between 2 units after each update // but because we know the reason of a crash we can remove the assertion for now if (aurApp.GetTarget() != unit) { // remove from auras to register list targets.Remove(unit); continue; } else { // ok, we have one unit twice in target map (impossible, but...) Contract.Assert(false); } } var value = targets[unit]; bool addUnit = true; // check target immunities for (byte effIndex = 0; effIndex < SpellConst.MaxEffects; ++effIndex) { if (unit.IsImmunedToSpellEffect(GetSpellInfo(), effIndex)) value &= (byte)~(1 << effIndex); } if (value == 0 || unit.IsImmunedToSpell(GetSpellInfo()) || !CanBeAppliedOn(unit)) addUnit = false; if (addUnit && !unit.IsHighestExclusiveAura(this, true)) addUnit = false; if (addUnit) { // persistent area aura does not hit flying targets if (GetAuraType() == AuraObjectType.DynObj) { if (unit.IsInFlight()) addUnit = false; } // unit auras can not stack with each other else // (GetAuraType() == UNIT_AURA_TYPE) { // Allow to remove by stack when aura is going to be applied on owner if (unit != m_owner) { // check if not stacking aura already on target // this one prevents unwanted usefull buff loss because of stacking and prevents overriding auras periodicaly by 2 near area aura owners foreach (var iter in unit.GetAppliedAuras()) { Aura aura = iter.Value.GetBase(); if (!CanStackWith(aura)) { addUnit = false; break; } } } } } if (!addUnit) targets.Remove(unit); else { // owner has to be in world, or effect has to be applied to self if (!m_owner.IsSelfOrInSameMap(unit)) { /// @todo There is a crash caused by shadowfiend load addon Log.outFatal(LogFilter.Spells, "Aura {0}: Owner {1} (map {2}) is not in the same map as target {3} (map {4}).", GetSpellInfo().Id, m_owner.GetName(), m_owner.IsInWorld ? (int)m_owner.GetMap().GetId() : -1, unit.GetName(), unit.IsInWorld ? (int)unit.GetMap().GetId() : -1); } unit._CreateAuraApplication(this, value); } } // remove auras from units no longer needing them foreach (var unit in targetsToRemove) { AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID()); if (aurApp != null) unit._UnapplyAura(aurApp, AuraRemoveMode.Default); } if (!apply) return; // apply aura effects for units foreach (var pair in targets) { AuraApplication aurApp = GetApplicationOfTarget(pair.Key.GetGUID()); if (aurApp != null) { // owner has to be in world, or effect has to be applied to self Contract.Assert((!m_owner.IsInWorld && m_owner == pair.Key) || m_owner.IsInMap(pair.Key)); pair.Key._ApplyAura(aurApp, pair.Value); } } } // targets have to be registered and not have effect applied yet to use this function public void _ApplyEffectForTargets(uint effIndex) { // prepare list of aura targets List targetList = new List(); foreach (var app in m_applications.Values) { if (Convert.ToBoolean(app.GetEffectsToApply() & (1 << (int)effIndex)) && !app.HasEffect(effIndex)) targetList.Add(app.GetTarget()); } // apply effect to targets foreach (var unit in targetList) { if (GetApplicationOfTarget(unit.GetGUID()) != null) { // owner has to be in world, or effect has to be applied to self Contract.Assert((!GetOwner().IsInWorld && GetOwner() == unit) || GetOwner().IsInMap(unit)); unit._ApplyAuraEffect(this, effIndex); } } } public void UpdateOwner(uint diff, WorldObject owner) { Contract.Assert(owner == m_owner); Unit caster = GetCaster(); // Apply spellmods for channeled auras // used for example when triggered spell of spell:10 is modded Spell modSpell = null; Player modOwner = null; if (caster != null) { modOwner = caster.GetSpellModOwner(); if (modOwner != null) { modSpell = modOwner.FindCurrentSpellBySpellId(GetId()); if (modSpell != null) modOwner.SetSpellModTakingSpell(modSpell, true); } } Update(diff, caster); if (m_updateTargetMapInterval <= diff) UpdateTargetMap(caster); else m_updateTargetMapInterval -= (int)diff; // update aura effects foreach (AuraEffect effect in GetAuraEffects()) if (effect != null) effect.Update(diff, caster); // remove spellmods after effects update if (modSpell != null) modOwner.SetSpellModTakingSpell(modSpell, false); _DeleteRemovedApplications(); } void Update(uint diff, Unit caster) { if (m_duration > 0) { m_duration -= (int)diff; if (m_duration < 0) m_duration = 0; // handle manaPerSecond/manaPerSecondPerLevel if (m_timeCla != 0) { if (m_timeCla > diff) m_timeCla -= (int)diff; else if (caster != null) { if (!m_periodicCosts.Empty()) { m_timeCla += (int)(1000 - diff); foreach (SpellPowerRecord power in m_periodicCosts) { if (power.RequiredAura != 0 && !caster.HasAura(power.RequiredAura)) continue; int manaPerSecond = (int)power.ManaCostPerSecond; if (power.PowerType != PowerType.Health) manaPerSecond += MathFunctions.CalculatePct(caster.GetMaxPower(power.PowerType), power.ManaCostPercentagePerSecond); else manaPerSecond += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.ManaCostPercentagePerSecond); if (manaPerSecond != 0) { if (power.PowerType == PowerType.Health) { if ((int)caster.GetHealth() > manaPerSecond) caster.ModifyHealth(-manaPerSecond); else Remove(); } else if (caster.GetPower(power.PowerType) >= manaPerSecond) caster.ModifyPower(power.PowerType, -manaPerSecond); else Remove(); } } } } } } } int CalcMaxDuration(Unit caster) { Player modOwner = null; int maxDuration; if (caster != null) { modOwner = caster.GetSpellModOwner(); maxDuration = caster.CalcSpellDuration(m_spellInfo); } else maxDuration = m_spellInfo.GetDuration(); if (IsPassive() && m_spellInfo.DurationEntry == null) maxDuration = -1; // IsPermanent() checks max duration (which we are supposed to calculate here) if (maxDuration != -1 && modOwner != null) modOwner.ApplySpellMod(GetId(), SpellModOp.Duration, ref maxDuration); return maxDuration; } public void SetDuration(int duration, bool withMods = false) { if (withMods) { Unit caster = GetCaster(); if (caster) { Player modOwner = caster.GetSpellModOwner(); if (modOwner) modOwner.ApplySpellMod(GetId(), SpellModOp.Duration, ref duration); } } m_duration = duration; SetNeedClientUpdateForTargets(); } public void RefreshDuration(bool withMods = false) { Unit caster = GetCaster(); if (withMods && caster) { int duration = m_spellInfo.GetMaxDuration(); // Calculate duration of periodics affected by haste. if (caster.HasAuraTypeWithAffectMask(AuraType.PeriodicHaste, m_spellInfo) || m_spellInfo.HasAttribute(SpellAttr5.HasteAffectDuration)) duration = (int)(duration * caster.GetFloatValue(UnitFields.ModCastSpeed)); SetMaxDuration(duration); SetDuration(duration); } else SetDuration(GetMaxDuration()); if (!m_periodicCosts.Empty()) m_timeCla = 1 * Time.InMilliseconds; } void RefreshTimers() { m_maxDuration = CalcMaxDuration(); bool resetPeriodic = true; if (m_spellInfo.HasAttribute(SpellAttr8.DontResetPeriodicTimer)) { int minPeriod = m_maxDuration; for (byte i = 0; i < SpellConst.MaxEffects; ++i) { AuraEffect eff = GetEffect(i); if (eff != null) { int period = eff.GetPeriod(); if (period != 0) minPeriod = Math.Min(period, minPeriod); } } // If only one tick remaining, roll it over into new duration if (GetDuration() <= minPeriod) { m_maxDuration += GetDuration(); resetPeriodic = false; } } RefreshDuration(); Unit caster = GetCaster(); for (byte i = 0; i < SpellConst.MaxEffects; ++i) if (HasEffect(i)) GetEffect(i).CalculatePeriodic(caster, resetPeriodic, false); } public void SetCharges(int charges) { if (m_procCharges == charges) return; m_procCharges = (byte)charges; m_isUsingCharges = m_procCharges != 0; SetNeedClientUpdateForTargets(); } public bool ModCharges(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default) { if (IsUsingCharges()) { int charges = m_procCharges + num; int maxCharges = CalcMaxCharges(); // limit charges (only on charges increase, charges may be changed manually) if ((num > 0) && (charges > maxCharges)) charges = maxCharges; // we're out of charges, remove else if (charges <= 0) { Remove(removeMode); return true; } SetCharges((byte)charges); } return false; } public void ModChargesDelayed(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default) { m_dropEvent = null; ModCharges(num, removeMode); } public void DropChargeDelayed(uint delay, AuraRemoveMode removeMode = AuraRemoveMode.Default) { // aura is already during delayed charge drop if (m_dropEvent != null) return; // only units have events Unit owner = m_owner.ToUnit(); if (!owner) return; m_dropEvent = new ChargeDropEvent(this, removeMode); owner.m_Events.AddEvent(m_dropEvent, owner.m_Events.CalculateTime(delay)); } public void SetStackAmount(byte stackAmount) { m_stackAmount = stackAmount; Unit caster = GetCaster(); List applications = GetApplicationList(); foreach (var appt in applications) if (!appt.HasRemoveMode()) HandleAuraSpecificMods(appt, caster, false, true); foreach (AuraEffect effect in GetAuraEffects()) if (effect != null) effect.ChangeAmount(effect.CalculateAmount(caster), false, true); foreach (var app in applications) { if (!app.HasRemoveMode()) { HandleAuraSpecificMods(app, caster, true, true); HandleAuraSpecificPeriodics(app, caster); } } SetNeedClientUpdateForTargets(); } public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default) { int stackAmount = m_stackAmount + num; // limit the stack amount (only on stack increase, stack amount may be changed manually) if ((num > 0) && (stackAmount > (int)m_spellInfo.StackAmount)) { // not stackable aura - set stack amount to 1 if (m_spellInfo.StackAmount == 0) stackAmount = 1; else stackAmount = (int)m_spellInfo.StackAmount; } // we're out of stacks, remove else if (stackAmount <= 0) { Remove(removeMode); return true; } bool refresh = stackAmount >= GetStackAmount(); // Update stack amount SetStackAmount((byte)stackAmount); if (refresh) { RefreshSpellMods(); RefreshTimers(); // reset charges SetCharges(CalcMaxCharges()); // FIXME: not a best way to synchronize charges, but works for (byte i = 0; i < SpellConst.MaxEffects; ++i) { AuraEffect aurEff = GetEffect(i); if (aurEff != null) if (aurEff.GetAuraType() == AuraType.AddFlatModifier || aurEff.GetAuraType() == AuraType.AddPctModifier) { SpellModifier mod = aurEff.GetSpellModifier(); if (mod != null) mod.charges = GetCharges(); } } } SetNeedClientUpdateForTargets(); return false; } void RefreshSpellMods() { foreach (var app in m_applications.Values) { Player player = app.GetTarget().ToPlayer(); if (player != null) player.RestoreAllSpellMods(0, this); } } public bool HasMoreThanOneEffectForType(AuraType auraType) { uint count = 0; foreach (SpellEffectInfo effect in GetSpellEffectInfos()) { if (effect != null && HasEffect(effect.EffectIndex) && effect.ApplyAuraName == auraType) ++count; } return count > 1; } public bool IsArea() { foreach (SpellEffectInfo effect in GetSpellEffectInfos()) { if (effect != null && HasEffect(effect.EffectIndex) && effect.IsAreaAuraEffect()) return true; } return false; } public bool IsPassive() { return m_spellInfo.IsPassive(); } public bool IsDeathPersistent() { return GetSpellInfo().IsDeathPersistent(); } public bool CanBeSaved() { if (IsPassive()) return false; if (GetCasterGUID() != GetOwner().GetGUID()) if (GetSpellInfo().IsSingleTarget()) return false; // No point in saving this, since the stable dialog can't be open on aura load anyway. if (HasEffectType(AuraType.OpenStable)) return false; // Can't save vehicle auras, it requires both caster & target to be in world if (HasEffectType(AuraType.ControlVehicle)) return false; // Incanter's Absorbtion - considering the minimal duration and problems with aura stacking // we skip saving this aura // Also for some reason other auras put as MultiSlot crash core on keeping them after restart, // so put here only these for which you are sure they get removed switch (GetId()) { case 44413: // Incanter's Absorption case 40075: // Fel Flak Fire case 55849: // Power Spark return false; } // When a druid logins, he doesnt have either eclipse power, nor the marker auras, nor the eclipse buffs. Dont save them. if (GetId() == 67483 || GetId() == 67484 || GetId() == 48517 || GetId() == 48518) return false; // don't save auras removed by proc system if (IsUsingCharges() && GetCharges() == 0) return false; return true; } public bool IsSingleTargetWith(Aura aura) { // Same spell? if (GetSpellInfo().IsRankOf(aura.GetSpellInfo())) return true; SpellSpecificType spec = GetSpellInfo().GetSpellSpecific(); // spell with single target specific types switch (spec) { case SpellSpecificType.Judgement: case SpellSpecificType.MagePolymorph: if (aura.GetSpellInfo().GetSpellSpecific() == spec) return true; break; default: break; } if (HasEffectType(AuraType.ControlVehicle) && aura.HasEffectType(AuraType.ControlVehicle)) return true; return false; } public void UnregisterSingleTarget() { Contract.Assert(m_isSingleTarget); Unit caster = GetCaster(); Contract.Assert(caster != null); caster.GetSingleCastAuras().Remove(this); SetIsSingleTarget(false); } public int CalcDispelChance(Unit auraTarget, bool offensive) { // we assume that aura dispel chance is 100% on start // need formula for level difference based chance int resistChance = 0; // Apply dispel mod from aura caster Unit caster = GetCaster(); if (caster != null) { Player modOwner = caster.GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(GetId(), SpellModOp.ResistDispelChance, ref resistChance); } // Dispel resistance from target SPELL_AURA_MOD_DISPEL_RESIST // Only affects offensive dispels if (offensive && auraTarget != null) resistChance += auraTarget.GetTotalAuraModifier(AuraType.ModDispelResist); resistChance = resistChance < 0 ? 0 : resistChance; resistChance = resistChance > 100 ? 100 : resistChance; return 100 - resistChance; } public AuraKey GenerateKey(out uint recalculateMask) { AuraKey key = new AuraKey(GetCasterGUID(), GetCastItemGUID(), GetId(), 0); recalculateMask = 0; for (int i = 0; i < _effects.Length; ++i) { AuraEffect effect = _effects[i]; if (effect != null) { key.EffectMask |= 1u << i; if (effect.CanBeRecalculated()) recalculateMask |= 1u << i; } } return key; } public void SetLoadedState(int maxduration, int duration, int charges, byte stackamount, uint recalculateMask, int[] amount) { m_maxDuration = maxduration; m_duration = duration; m_procCharges = (byte)charges; m_isUsingCharges = m_procCharges != 0; m_stackAmount = stackamount; Unit caster = GetCaster(); foreach (AuraEffect effect in GetAuraEffects()) { if (effect == null) continue; effect.SetAmount(amount[effect.GetEffIndex()]); effect.SetCanBeRecalculated(Convert.ToBoolean(recalculateMask & (1 << effect.GetEffIndex()))); effect.CalculatePeriodic(caster, false, true); effect.CalculateSpellMod(); effect.RecalculateAmount(caster); } } public bool HasEffectType(AuraType type) { foreach (var eff in GetAuraEffects()) if (eff != null && HasEffect(eff.GetEffIndex()) && eff.GetAuraType() == type) return true; return false; } public void RecalculateAmountOfEffects() { Contract.Assert(!IsRemoved()); Unit caster = GetCaster(); foreach (AuraEffect effect in GetAuraEffects()) if (effect != null && !IsRemoved()) effect.RecalculateAmount(caster); } public void HandleAllEffects(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply) { Contract.Assert(!IsRemoved()); foreach (AuraEffect effect in GetAuraEffects()) if (effect != null && !IsRemoved()) effect.HandleEffect(aurApp, mode, apply); } public List GetApplicationList() { var applicationList = new List(); foreach (var app in m_applications.Values) { if (app.GetEffectMask() != 0) applicationList.Add(app); } return applicationList; } public void SetNeedClientUpdateForTargets() { foreach (var app in m_applications.Values) app.SetNeedClientUpdate(); } // trigger effects on real aura apply/remove public void HandleAuraSpecificMods(AuraApplication aurApp, Unit caster, bool apply, bool onReapply) { Unit target = aurApp.GetTarget(); AuraRemoveMode removeMode = aurApp.GetRemoveMode(); // handle spell_area table var saBounds = Global.SpellMgr.GetSpellAreaForAuraMapBounds(GetId()); if (saBounds != null) { uint zone, area; target.GetZoneAndAreaId(out zone, out area); foreach (var bound in saBounds) { // some auras remove at aura remove if (!bound.IsFitToRequirements((Player)target, zone, area)) target.RemoveAurasDueToSpell(bound.spellId); // some auras applied at aura apply else if (bound.autocast) { if (!target.HasAura(bound.spellId)) target.CastSpell(target, bound.spellId, true); } } } // handle spell_linked_spell table if (!onReapply) { // apply linked auras if (apply) { var spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura); if (spellTriggered != null) { foreach (var spell in spellTriggered) { if (spell < 0) target.ApplySpellImmune(GetId(), SpellImmunity.Id, (uint)-spell, true); else if (caster != null) caster.AddAura((uint)spell, target); } } } else { // remove linked auras var spellTriggered = Global.SpellMgr.GetSpellLinked(-(int)GetId()); if (spellTriggered != null) { foreach (var spell in spellTriggered) { if (spell < 0) target.RemoveAurasDueToSpell((uint)-spell); else if (removeMode != AuraRemoveMode.ByDeath) target.CastSpell(target, (uint)spell, true, null, null, GetCasterGUID()); } } spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura); if (spellTriggered != null) { foreach (var id in spellTriggered) { if (id < 0) target.ApplySpellImmune(GetId(), SpellImmunity.Id, (uint)-id, false); else target.RemoveAura((uint)id, GetCasterGUID(), 0, removeMode); } } } } else if (apply) { // modify stack amount of linked auras var spellTriggered = Global.SpellMgr.GetSpellLinked((int)GetId() + (int)SpellLinkedType.Aura); if (spellTriggered != null) { foreach (var id in spellTriggered) { if (id > 0) { Aura triggeredAura = target.GetAura((uint)id, GetCasterGUID()); if (triggeredAura != null) triggeredAura.ModStackAmount(GetStackAmount() - triggeredAura.GetStackAmount()); } } } } // mods at aura apply if (apply) { switch (GetSpellInfo().SpellFamilyName) { case SpellFamilyNames.Generic: switch (GetId()) { case 32474: // Buffeting Winds of Susurrus if (target.IsTypeId(TypeId.Player)) target.ToPlayer().ActivateTaxiPathTo(506, GetId()); break; case 33572: // Gronn Lord's Grasp, becomes stoned if (GetStackAmount() >= 5 && !target.HasAura(33652)) target.CastSpell(target, 33652, true); break; case 50836: //Petrifying Grip, becomes stoned if (GetStackAmount() >= 5 && !target.HasAura(50812)) target.CastSpell(target, 50812, true); break; case 60970: // Heroic Fury (remove Intercept cooldown) if (target.IsTypeId(TypeId.Player)) target.GetSpellHistory().ResetCooldown(20252, true); break; } break; case SpellFamilyNames.Druid: if (caster == null) break; // Rejuvenation if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x10u) && GetEffect(0) != null) { // Druid T8 Restoration 4P Bonus if (caster.HasAura(64760)) { int heal = GetEffect(0).GetAmount(); caster.CastCustomSpell(target, 64801, heal, 0, 0, true, null, GetEffect(0)); } } break; case SpellFamilyNames.Rogue: // Sprint (skip non player casted spells by category) if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x40u) && GetSpellInfo().GetCategory() == 44) // in official maybe there is only one icon? if (target.HasAura(58039)) // Glyph of Blurred Speed target.CastSpell(target, 61922, true); // Sprint (waterwalk) break; } } // mods at aura remove else { switch (GetSpellInfo().SpellFamilyName) { case SpellFamilyNames.Generic: switch (GetId()) { case 61987: // Avenging Wrath // Remove the immunity shield marker on Avenging Wrath removal if Forbearance is not present if (target.HasAura(61988) && !target.HasAura(25771)) target.RemoveAura(61988); break; case 72368: // Shared Suffering case 72369: if (caster != null) { AuraEffect aurEff = GetEffect(0); if (aurEff != null) { int remainingDamage = (int)(aurEff.GetAmount() * (aurEff.GetTotalTicks() - aurEff.GetTickNumber())); if (remainingDamage > 0) caster.CastCustomSpell(caster, 72373, 0, remainingDamage, 0, true); } } break; } break; case SpellFamilyNames.Mage: switch (GetId()) { case 66: // Invisibility if (removeMode != AuraRemoveMode.Expire) break; target.CastSpell(target, 32612, true, null, GetEffect(1)); target.CombatStop(); break; default: break; } break; case SpellFamilyNames.Priest: if (caster == null) break; // Power word: shield if (removeMode == AuraRemoveMode.EnemySpell && GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00000001u)) { // Rapture Aura aura = caster.GetAuraOfRankedSpell(47535); if (aura != null) { // check cooldown if (caster.IsTypeId(TypeId.Player)) { if (caster.GetSpellHistory().HasCooldown(aura.GetId())) { // This additional check is needed to add a minimal delay before cooldown in in effect // to allow all bubbles broken by a single damage source proc mana return if (caster.GetSpellHistory().GetRemainingCooldown(aura.GetSpellInfo()) <= 11 * Time.InMilliseconds) break; } else // and add if needed caster.GetSpellHistory().AddCooldown(aura.GetId(), 0, TimeSpan.FromSeconds(12)); } // effect on caster AuraEffect aurEff = aura.GetEffect(0); if (aurEff != null) { float multiplier = aurEff.GetAmount(); int basepoints0 = MathFunctions.CalculatePct(caster.GetMaxPower(PowerType.Mana), multiplier); caster.CastCustomSpell(caster, 47755, basepoints0, 0, 0, true); } } } break; case SpellFamilyNames.Rogue: // Remove Vanish on stealth remove if (GetId() == 1784) target.RemoveAurasWithFamily(SpellFamilyNames.Rogue, new FlagArray128(0x0000800, 0, 0), target.GetGUID()); break; case SpellFamilyNames.Paladin: // Remove the immunity shield marker on Forbearance removal if AW marker is not present if (GetId() == 25771 && target.HasAura(61988) && !target.HasAura(61987)) target.RemoveAura(61988); break; case SpellFamilyNames.Hunter: // Glyph of Freezing Trap if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00000008u)) if (caster != null && caster.HasAura(56845)) target.CastSpell(target, 61394, true); break; } } // mods at aura apply or remove switch (GetSpellInfo().SpellFamilyName) { case SpellFamilyNames.Rogue: // Stealth if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00400000u)) { // Master of subtlety AuraEffect aurEff = target.GetAuraEffect(31223, 0); if (aurEff != null) { if (!apply) target.CastSpell(target, 31666, true); else { int basepoints0 = aurEff.GetAmount(); target.CastCustomSpell(target, 31665, basepoints0, 0, 0, true); } } break; } break; case SpellFamilyNames.Hunter: switch (GetId()) { case 19574: // Bestial Wrath // The Beast Within cast on owner if talent present Unit owner = target.GetOwner(); if (owner != null) { // Search talent if (owner.HasAura(34692)) { if (apply) owner.CastSpell(owner, 34471, true, null, GetEffect(0)); else owner.RemoveAurasDueToSpell(34471); } } break; } break; case SpellFamilyNames.Paladin: switch (GetId()) { case 31821: // Aura Mastery Triggered Spell Handler // If apply Concentration Aura . trigger . apply Aura Mastery Immunity // If remove Concentration Aura . trigger . remove Aura Mastery Immunity // If remove Aura Mastery . trigger . remove Aura Mastery Immunity // Do effects only on aura owner if (GetCasterGUID() != target.GetGUID()) break; if (apply) { if ((GetSpellInfo().Id == 31821 && target.HasAura(19746, GetCasterGUID())) || (GetSpellInfo().Id == 19746 && target.HasAura(31821))) target.CastSpell(target, 64364, true); } else target.RemoveAurasDueToSpell(64364, GetCasterGUID()); break; case 31842: // Divine Favor // Item - Paladin T10 Holy 2P Bonus if (target.HasAura(70755)) { if (apply) target.CastSpell(target, 71166, true); else target.RemoveAurasDueToSpell(71166); } break; } break; case SpellFamilyNames.Warlock: // Drain Soul - If the target is at or below 25% health, Drain Soul causes four times the normal damage if (GetSpellInfo().SpellFamilyFlags[0].HasAnyFlag(0x00004000u)) { if (caster == null) break; if (apply) { if (target != caster && !target.HealthAbovePct(25)) caster.CastSpell(caster, 100001, true); } else { if (target != caster) caster.RemoveAurasDueToSpell(GetId()); else caster.RemoveAurasDueToSpell(100001); } } break; } } public void HandleAuraSpecificPeriodics(AuraApplication aurApp, Unit caster) { Unit target = aurApp.GetTarget(); if (!caster || aurApp.HasRemoveMode()) return; foreach (AuraEffect effect in GetAuraEffects()) { if (effect == null || effect.IsAreaAuraEffect() || effect.IsEffect(SpellEffectName.PersistentAreaAura)) continue; switch (effect.GetSpellEffectInfo().ApplyAuraName) { case AuraType.PeriodicDamage: case AuraType.PeriodicDamagePercent: case AuraType.PeriodicLeech: { // ignore non positive values (can be result apply spellmods to aura damage uint damage = (uint)Math.Max(effect.GetAmount(), 0); // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); effect.SetDonePct(caster.SpellDamagePctDone(target, m_spellInfo, DamageEffectType.DOT)); // Calculate done percentage first! effect.SetDamage((int)(caster.SpellDamageBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask())); break; } case AuraType.PeriodicHeal: case AuraType.ObsModHealth: { // ignore non positive values (can be result apply spellmods to aura damage uint damage = (uint)Math.Max(effect.GetAmount(), 0); // Script Hook For HandlePeriodicDamageAurasTick -- Allow scripts to change the Damage pre class mitigation calculations Global.ScriptMgr.ModifyPeriodicDamageAurasTick(target, caster, ref damage); effect.SetDonePct(caster.SpellHealingPctDone(target, m_spellInfo)); // Calculate done percentage first! effect.SetDamage((int)(caster.SpellHealingBonusDone(target, m_spellInfo, damage, DamageEffectType.DOT, effect.GetSpellEffectInfo(), GetStackAmount()) * effect.GetDonePct())); effect.SetCritChance(caster.GetUnitSpellCriticalChance(target, m_spellInfo, m_spellInfo.GetSchoolMask())); break; } default: break; } } } bool CanBeAppliedOn(Unit target) { // unit not in world or during remove from world if (!target.IsInWorld || target.IsDuringRemoveFromWorld()) { // area auras mustn't be applied if (GetOwner() != target) return false; // not selfcasted single target auras mustn't be applied if (GetCasterGUID() != GetOwner().GetGUID() && GetSpellInfo().IsSingleTarget()) return false; return true; } else return CheckAreaTarget(target); } bool CheckAreaTarget(Unit target) { return CallScriptCheckAreaTargetHandlers(target); } public bool CanStackWith(Aura existingAura) { // Can stack with self if (this == existingAura) return true; // Dynobj auras always stack if (GetAuraType() == AuraObjectType.DynObj || existingAura.GetAuraType() == AuraObjectType.DynObj) return true; SpellInfo existingSpellInfo = existingAura.GetSpellInfo(); bool sameCaster = GetCasterGUID() == existingAura.GetCasterGUID(); // passive auras don't stack with another rank of the spell cast by same caster if (IsPassive() && sameCaster && (m_spellInfo.IsDifferentRankOf(existingSpellInfo) || (m_spellInfo.Id == existingSpellInfo.Id && m_castItemGuid.IsEmpty()))) return false; foreach (SpellEffectInfo effect in existingAura.GetSpellEffectInfos()) { // prevent remove triggering aura by triggered aura if (effect != null && effect.TriggerSpell == GetId()) return true; } foreach (SpellEffectInfo effect in GetSpellEffectInfos()) { // prevent remove triggered aura by triggering aura refresh if (effect != null && effect.TriggerSpell == existingAura.GetId()) return true; } // check spell specific stack rules if (m_spellInfo.IsAuraExclusiveBySpecificWith(existingSpellInfo) || (sameCaster && m_spellInfo.IsAuraExclusiveBySpecificPerCasterWith(existingSpellInfo))) return false; // check spell group stack rules switch (Global.SpellMgr.CheckSpellGroupStackRules(m_spellInfo, existingSpellInfo)) { case SpellGroupStackRule.Exclusive: case SpellGroupStackRule.ExclusiveHighest: // if it reaches this point, existing aura is lower/equal return false; case SpellGroupStackRule.ExclusiveFromSameCaster: if (sameCaster) return false; break; case SpellGroupStackRule.Default: case SpellGroupStackRule.ExclusiveSameEffect: default: break; } if (m_spellInfo.SpellFamilyName != existingSpellInfo.SpellFamilyName) return true; if (!sameCaster) { // Channeled auras can stack if not forbidden by db or aura type if (existingAura.GetSpellInfo().IsChanneled()) return true; if (m_spellInfo.HasAttribute(SpellAttr3.StackForDiffCasters)) return true; // check same periodic auras for (byte i = 0; i < SpellConst.MaxEffects; i++) { SpellEffectInfo effect = GetSpellEffectInfo(i); if (effect == null) continue; switch (effect.ApplyAuraName) { // DOT or HOT from different casters will stack case AuraType.PeriodicDamage: case AuraType.PeriodicDummy: case AuraType.PeriodicHeal: case AuraType.PeriodicTriggerSpell: case AuraType.PeriodicEnergize: case AuraType.PeriodicManaLeech: case AuraType.PeriodicLeech: case AuraType.PowerBurn: case AuraType.ObsModPower: case AuraType.ObsModHealth: case AuraType.PeriodicTriggerSpellWithValue: SpellEffectInfo existingEffect = GetSpellEffectInfo(i); // periodic auras which target areas are not allowed to stack this way (replenishment for example) if (effect.IsTargetingArea() || (existingEffect != null && existingEffect.IsTargetingArea())) break; return true; default: break; } } } if (HasEffectType(AuraType.ControlVehicle) && existingAura.HasEffectType(AuraType.ControlVehicle)) { Vehicle veh = null; if (GetOwner().ToUnit()) veh = GetOwner().ToUnit().GetVehicleKit(); if (!veh) // We should probably just let it stack. Vehicle system will prevent undefined behaviour later return true; if (veh.GetAvailableSeatCount() == 0) return false; // No empty seat available return true; // Empty seat available (skip rest) } if (HasEffectType(AuraType.ShowConfirmationPrompt) || HasEffectType(AuraType.ShowConfirmationPromptWithDifficulty)) if (existingAura.HasEffectType(AuraType.ShowConfirmationPrompt) || existingAura.HasEffectType(AuraType.ShowConfirmationPromptWithDifficulty)) return false; // spell of same spell rank chain if (m_spellInfo.IsRankOf(existingSpellInfo)) { // don't allow passive area auras to stack if (m_spellInfo.IsMultiSlotAura() && !IsArea()) return true; if (!GetCastItemGUID().IsEmpty() && !existingAura.GetCastItemGUID().IsEmpty()) if (GetCastItemGUID() != existingAura.GetCastItemGUID() && m_spellInfo.HasAttribute(SpellCustomAttributes.EnchantProc)) return true; // same spell with same caster should not stack return false; } return true; } public bool IsProcOnCooldown(DateTime now) { return m_procCooldown > now; } public void AddProcCooldown(DateTime cooldownEnd) { m_procCooldown = cooldownEnd; } void PrepareProcToTrigger(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now) { bool prepare = CallScriptPrepareProcHandlers(aurApp, eventInfo); if (!prepare) return; // take one charge, aura expiration will be handled in Aura.TriggerProcOnEvent (if needed) if (IsUsingCharges()) { --m_procCharges; SetNeedClientUpdateForTargets(); } SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetId()); Contract.Assert(procEntry != null); // cooldowns should be added to the whole aura (see 51698 area aura) AddProcCooldown(now + TimeSpan.FromMilliseconds(procEntry.Cooldown)); } bool IsProcTriggeredOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now) { SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetId()); // only auras with spell proc entry can trigger proc if (procEntry == null) return false; // check if we have charges to proc with if (IsUsingCharges() && GetCharges() == 0) return false; // check proc cooldown if (IsProcOnCooldown(now)) return false; /// @todo // something about triggered spells triggering, and add extra attack effect // do checks against db data if (!Global.SpellMgr.CanSpellTriggerProcOnEvent(procEntry, eventInfo)) return false; // do checks using conditions table if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SpellProc, GetId(), eventInfo.GetActor(), eventInfo.GetActionTarget())) return false; // AuraScript Hook bool check = CallScriptCheckProcHandlers(aurApp, eventInfo); if (!check) return false; /// @todo // do allow additional requirements for procs // this is needed because this is the last moment in which you can prevent aura charge drop on proc // and possibly a way to prevent default checks (if there're going to be any) // Check if current equipment meets aura requirements // do that only for passive spells /// @todo this needs to be unified for all kinds of auras Unit target = aurApp.GetTarget(); if (IsPassive() && target.IsTypeId(TypeId.Player)) { if (GetSpellInfo().EquippedItemClass == ItemClass.Weapon) { if (target.ToPlayer().IsInFeralForm()) return false; if (eventInfo.GetDamageInfo() != null) { WeaponAttackType attType = eventInfo.GetDamageInfo().GetAttackType(); Item item = null; if (attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.RangedAttack) item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); else if (attType == WeaponAttackType.OffAttack) item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Weapon || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & GetSpellInfo().EquippedItemSubClassMask)) return false; } } else if (GetSpellInfo().EquippedItemClass == ItemClass.Armor) { // Check if player is wearing shield Item item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Armor || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & GetSpellInfo().EquippedItemSubClassMask)) return false; } } return RandomHelper.randChance(CalcProcChance(procEntry, eventInfo)); } float CalcProcChance(SpellProcEntry procEntry, ProcEventInfo eventInfo) { float chance = procEntry.Chance; // calculate chances depending on unit with caster's data // so talents modifying chances and judgements will have properly calculated proc chance Unit caster = GetCaster(); if (caster != null) { // calculate ppm chance if present and we're using weapon if (eventInfo.GetDamageInfo() != null && procEntry.ProcsPerMinute != 0) { uint WeaponSpeed = caster.GetBaseAttackTime(eventInfo.GetDamageInfo().GetAttackType()); chance = caster.GetPPMProcChance(WeaponSpeed, procEntry.ProcsPerMinute, GetSpellInfo()); } // apply chance modifer aura, applies also to ppm chance (see improved judgement of light spell) Player modOwner = caster.GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(GetId(), SpellModOp.ChanceOfSuccess, ref chance); } return chance; } void TriggerProcOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo) { CallScriptProcHandlers(aurApp, eventInfo); for (byte i = 0; i < SpellConst.MaxEffects; ++i) { if (aurApp.HasEffect(i)) // OnEffectProc / AfterEffectProc hooks handled in AuraEffect.HandleProc() GetEffect(i).HandleProc(aurApp, eventInfo); } CallScriptAfterProcHandlers(aurApp, eventInfo); // Remove aura if we've used last charge to proc if (IsUsingCharges() && GetCharges() == 0) Remove(); } public float CalcPPMProcChance(Unit actor) { // Formula see http://us.battle.net/wow/en/forum/topic/8197741003#1 float ppm = m_spellInfo.CalcProcPPM(actor, m_castItemLevel); float averageProcInterval = 60.0f / ppm; var currentTime = DateTime.Now; float secondsSinceLastAttempt = Math.Min((float)(currentTime - m_lastProcAttemptTime).TotalSeconds, 10.0f); float secondsSinceLastProc = Math.Min((float)(currentTime - m_lastProcSuccessTime).TotalSeconds, 1000.0f); float chance = Math.Max(1.0f, 1.0f + ((secondsSinceLastProc / averageProcInterval - 1.5f) * 3.0f)) * ppm * secondsSinceLastAttempt / 60.0f; MathFunctions.RoundToInterval(ref chance, 0.0f, 1.0f); return chance * 100.0f; } void _DeleteRemovedApplications() { m_removedApplications.Clear(); } public void LoadScripts() { m_loadedScripts = Global.ScriptMgr.CreateAuraScripts(m_spellInfo.Id, this); foreach (var script in m_loadedScripts) { Log.outDebug(LogFilter.Spells, "Aura.LoadScripts: Script `{0}` for aura `{1}` is loaded now", script._GetScriptName(), m_spellInfo.Id); script.Register(); } } public virtual void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default) { } #region CallScripts bool CallScriptCheckAreaTargetHandlers(Unit target) { bool result = true; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.CheckAreaTarget); foreach (var hook in auraScript.DoCheckAreaTarget) result &= hook.Call(target); auraScript._FinishScriptCall(); } return result; } public void CallScriptDispel(DispelInfo dispelInfo) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.Dispel); foreach (var hook in auraScript.OnDispel) hook.Call(dispelInfo); auraScript._FinishScriptCall(); } } public void CallScriptAfterDispel(DispelInfo dispelInfo) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.AfterDispel); foreach (var hook in auraScript.AfterDispel) hook.Call(dispelInfo); auraScript._FinishScriptCall(); } } public bool CallScriptEffectApplyHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) { bool preventDefault = false; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectApply, aurApp); foreach (var eff in auraScript.OnEffectApply) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, mode); if (!preventDefault) preventDefault = auraScript._IsDefaultActionPrevented(); auraScript._FinishScriptCall(); } return preventDefault; } public bool CallScriptEffectRemoveHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) { bool preventDefault = false; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectRemove, aurApp); foreach (var eff in auraScript.OnEffectRemove) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, mode); if (!preventDefault) preventDefault = auraScript._IsDefaultActionPrevented(); auraScript._FinishScriptCall(); } return preventDefault; } public void CallScriptAfterEffectApplyHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterApply, aurApp); foreach (var eff in auraScript.AfterEffectApply) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, mode); auraScript._FinishScriptCall(); } } public void CallScriptAfterEffectRemoveHandlers(AuraEffect aurEff, AuraApplication aurApp, AuraEffectHandleModes mode) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterRemove, aurApp); foreach (var eff in auraScript.AfterEffectRemove) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, mode); auraScript._FinishScriptCall(); } } public bool CallScriptEffectPeriodicHandlers(AuraEffect aurEff, AuraApplication aurApp) { bool preventDefault = false; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectPeriodic, aurApp); foreach (var eff in auraScript.OnEffectPeriodic) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff); if (!preventDefault) preventDefault = auraScript._IsDefaultActionPrevented(); auraScript._FinishScriptCall(); } return preventDefault; } public void CallScriptEffectUpdatePeriodicHandlers(AuraEffect aurEff) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectUpdatePeriodic); foreach (var eff in auraScript.OnEffectUpdatePeriodic) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff); auraScript._FinishScriptCall(); } } public void CallScriptEffectCalcAmountHandlers(AuraEffect aurEff, ref int amount, ref bool canBeRecalculated) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcAmount); foreach (var eff in auraScript.DoEffectCalcAmount) { if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, ref amount, ref canBeRecalculated); } auraScript._FinishScriptCall(); } } public void CallScriptEffectCalcPeriodicHandlers(AuraEffect aurEff, ref bool isPeriodic, ref int amplitude) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcPeriodic); foreach (var eff in auraScript.DoEffectCalcPeriodic) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, ref isPeriodic, ref amplitude); auraScript._FinishScriptCall(); } } public void CallScriptEffectCalcSpellModHandlers(AuraEffect aurEff, ref SpellModifier spellMod) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectCalcSpellmod); foreach (var eff in auraScript.DoEffectCalcSpellMod) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, ref spellMod); auraScript._FinishScriptCall(); } } public void CallScriptEffectAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, ref bool defaultPrevented) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectAbsorb, aurApp); foreach (var eff in auraScript.OnEffectAbsorb) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, dmgInfo, ref absorbAmount); if (!defaultPrevented) defaultPrevented = auraScript._IsDefaultActionPrevented(); auraScript._FinishScriptCall(); } } public void CallScriptEffectAfterAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterAbsorb, aurApp); foreach (var eff in auraScript.AfterEffectAbsorb) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, dmgInfo, ref absorbAmount); auraScript._FinishScriptCall(); } } public void CallScriptEffectManaShieldHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, bool defaultPrevented) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectManaShield, aurApp); foreach (var eff in auraScript.OnEffectManaShield) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, dmgInfo, ref absorbAmount); auraScript._FinishScriptCall(); } } public void CallScriptEffectAfterManaShieldHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterManaShield, aurApp); foreach (var eff in auraScript.AfterEffectManaShield) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, dmgInfo, ref absorbAmount); auraScript._FinishScriptCall(); } } public void CallScriptEffectSplitHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, uint splitAmount) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectSplit, aurApp); foreach (var eff in auraScript.OnEffectSplit) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, dmgInfo, splitAmount); auraScript._FinishScriptCall(); } } public bool CallScriptCheckProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) { bool result = true; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.CheckProc, aurApp); foreach (var hook in auraScript.DoCheckProc) result &= hook.Call(eventInfo); auraScript._FinishScriptCall(); } return result; } public bool CallScriptPrepareProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) { bool prepare = true; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.PrepareProc, aurApp); foreach (var eff in auraScript.DoPrepareProc) eff.Call(eventInfo); if (prepare) prepare = !auraScript._IsDefaultActionPrevented(); auraScript._FinishScriptCall(); } return prepare; } public bool CallScriptProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) { bool handled = false; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.Proc, aurApp); foreach (var hook in auraScript.OnProc) hook.Call(eventInfo); handled |= auraScript._IsDefaultActionPrevented(); auraScript._FinishScriptCall(); } return handled; } public void CallScriptAfterProcHandlers(AuraApplication aurApp, ProcEventInfo eventInfo) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.AfterProc, aurApp); foreach (var hook in auraScript.AfterProc) hook.Call(eventInfo); auraScript._FinishScriptCall(); } } public bool CallScriptEffectProcHandlers(AuraEffect aurEff, AuraApplication aurApp, ProcEventInfo eventInfo) { bool preventDefault = false; foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectProc, aurApp); foreach (var eff in auraScript.OnEffectProc) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, eventInfo); if (!preventDefault) preventDefault = auraScript._IsDefaultActionPrevented(); auraScript._FinishScriptCall(); } return preventDefault; } public void CallScriptAfterEffectProcHandlers(AuraEffect aurEff, AuraApplication aurApp, ProcEventInfo eventInfo) { foreach (var auraScript in m_loadedScripts) { auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterProc, aurApp); foreach (var eff in auraScript.AfterEffectProc) if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex())) eff.Call(aurEff, eventInfo); auraScript._FinishScriptCall(); } } #endregion public SpellInfo GetSpellInfo() { return m_spellInfo; } public uint GetId() { return m_spellInfo.Id; } public ObjectGuid GetCastGUID() { return m_castGuid; } public ObjectGuid GetCasterGUID() { return m_casterGuid; } public ObjectGuid GetCastItemGUID() { return m_castItemGuid; } public int GetCastItemLevel() { return m_castItemLevel; } public uint GetSpellXSpellVisualId() { return m_spellXSpellVisualId; } public WorldObject GetOwner() { return m_owner; } public Unit GetUnitOwner() { Contract.Assert(GetAuraType() == AuraObjectType.Unit); return m_owner.ToUnit(); } public DynamicObject GetDynobjOwner() { Contract.Assert(GetAuraType() == AuraObjectType.DynObj); return m_owner.ToDynamicObject(); } public void _RegisterForTargets() { Unit caster = GetCaster(); UpdateTargetMap(caster, false); } public void ApplyForTargets() { Unit caster = GetCaster(); UpdateTargetMap(caster, true); } public long GetApplyTime() { return m_applyTime; } public int GetMaxDuration() { return m_maxDuration; } public void SetMaxDuration(int duration) { m_maxDuration = duration; } public int CalcMaxDuration() { return CalcMaxDuration(GetCaster()); } public int GetDuration() { return m_duration; } public bool IsExpired() { return GetDuration() == 0 && m_dropEvent == null; } public bool IsPermanent() { return m_maxDuration == -1; } public byte GetCharges() { return m_procCharges; } public byte CalcMaxCharges() { return CalcMaxCharges(GetCaster()); } byte CalcMaxCharges(Unit caster) { uint maxProcCharges = m_spellInfo.ProcCharges; var procEntry = Global.SpellMgr.GetSpellProcEntry(GetId()); if (procEntry != null) maxProcCharges = procEntry.Charges; if (caster != null) { Player modOwner = caster.GetSpellModOwner(); if (modOwner != null) modOwner.ApplySpellMod(GetId(), SpellModOp.Charges, ref maxProcCharges); } return (byte)maxProcCharges; } public bool DropCharge(AuraRemoveMode removeMode = AuraRemoveMode.Default) { return ModCharges(-1, removeMode); } public byte GetStackAmount() { return m_stackAmount; } public byte GetCasterLevel() { return (byte)m_casterLevel; } public bool IsRemovedOnShapeLost(Unit target) { return GetCasterGUID() == target.GetGUID() && m_spellInfo.Stances != 0 && !m_spellInfo.HasAttribute(SpellAttr2.NotNeedShapeshift) && !m_spellInfo.HasAttribute(SpellAttr0.NotShapeshift); } public bool IsRemoved() { return m_isRemoved; } public bool IsSingleTarget() { return m_isSingleTarget; } public void SetIsSingleTarget(bool val) { m_isSingleTarget = val; } public bool HasEffect(uint index) { return GetEffect(index) != null; } public AuraEffect GetEffect(uint index) { if (index >= _effects.Length) return null; return _effects[index]; } public uint GetEffectMask() { uint effMask = 0; foreach (AuraEffect effect in GetAuraEffects()) if (effect != null) effMask |= (uint)(1 << effect.GetEffIndex()); return effMask; } public Dictionary GetApplicationMap() { return m_applications; } public AuraApplication GetApplicationOfTarget(ObjectGuid guid) { return m_applications.LookupByKey(guid); } public bool IsAppliedOnTarget(ObjectGuid guid) { return m_applications.ContainsKey(guid); } public bool IsUsingCharges() { return m_isUsingCharges; } public void SetUsingCharges(bool val) { m_isUsingCharges = val; } public virtual void FillTargetMap(ref Dictionary targets, Unit caster) { } public AuraEffect[] GetAuraEffects() { return _effects; } public SpellEffectInfo[] GetSpellEffectInfos() { return _spellEffectInfos; } public SpellEffectInfo GetSpellEffectInfo(uint index) { if (index >= _spellEffectInfos.Length) return null; return _spellEffectInfos[index]; } public void SetLastProcAttemptTime(DateTime lastProcAttemptTime) { m_lastProcAttemptTime = lastProcAttemptTime; } public void SetLastProcSuccessTime(DateTime lastProcSuccessTime) { m_lastProcSuccessTime = lastProcSuccessTime; } //Static Methods public static uint BuildEffectMaskForOwner(SpellInfo spellProto, uint avalibleEffectMask, WorldObject owner) { Contract.Assert(spellProto != null); Contract.Assert(owner != null); uint effMask = 0; switch (owner.GetTypeId()) { case TypeId.Unit: case TypeId.Player: foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(owner.GetMap().GetDifficultyID())) { if (effect != null && effect.IsUnitOwnedAuraEffect()) effMask |= (uint)(1 << (int)effect.EffectIndex); } break; case TypeId.DynamicObject: foreach (SpellEffectInfo effect in spellProto.GetEffectsForDifficulty(owner.GetMap().GetDifficultyID())) { if (effect != null && effect.Effect == SpellEffectName.PersistentAreaAura) effMask |= (uint)(1 << (int)effect.EffectIndex); } break; default: break; } return (effMask & avalibleEffectMask); } public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) { bool throwway; return TryRefreshStackOrCreate(spellproto, castId, tryEffMask, owner, caster, out throwway, baseAmount, castItem, casterGUID); } public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, out bool refresh, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) { Contract.Assert(spellproto != null); Contract.Assert(owner != null); Contract.Assert(caster || !casterGUID.IsEmpty()); Contract.Assert(tryEffMask <= SpellConst.MaxEffectMask); refresh = false; uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner); if (effMask == 0) return null; Aura foundAura = owner.ToUnit()._TryStackingOrRefreshingExistingAura(spellproto, effMask, caster, baseAmount, castItem, casterGUID, castItemLevel); if (foundAura != null) { // we've here aura, which script triggered removal after modding stack amount // check the state here, so we won't create new Aura object if (foundAura.IsRemoved()) return null; refresh = true; return foundAura; } else return Create(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); } public static Aura TryCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) { Contract.Assert(spellproto != null); Contract.Assert(owner != null); Contract.Assert(caster != null || !casterGUID.IsEmpty()); Contract.Assert(tryEffMask <= SpellConst.MaxEffectMask); uint effMask = BuildEffectMaskForOwner(spellproto, tryEffMask, owner); if (effMask == 0) return null; return Create(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); } public static Aura Create(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel) { Contract.Assert(effMask != 0); Contract.Assert(spellproto != null); Contract.Assert(owner != null); Contract.Assert(caster != null || !casterGUID.IsEmpty()); Contract.Assert(effMask <= SpellConst.MaxEffectMask); // try to get caster of aura if (!casterGUID.IsEmpty()) { if (owner.GetGUID() == casterGUID) caster = owner.ToUnit(); else caster = Global.ObjAccessor.GetUnit(owner, casterGUID); } else casterGUID = caster.GetGUID(); // check if aura can be owned by owner if (owner.isTypeMask(TypeMask.Unit)) if (!owner.IsInWorld || owner.ToUnit().IsDuringRemoveFromWorld()) // owner not in world so don't allow to own not self casted single target auras if (casterGUID != owner.GetGUID() && spellproto.IsSingleTarget()) return null; Aura aura = null; switch (owner.GetTypeId()) { case TypeId.Unit: case TypeId.Player: aura = new UnitAura(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); break; case TypeId.DynamicObject: aura = new DynObjAura(spellproto, castId, effMask, owner, caster, baseAmount, castItem, casterGUID, castItemLevel); break; default: Contract.Assert(false); return null; } // aura can be removed in Unit:_AddAura call if (aura.IsRemoved()) return null; return aura; } #region Fields List m_loadedScripts = new List(); SpellInfo m_spellInfo; ObjectGuid m_castGuid; ObjectGuid m_casterGuid; ObjectGuid m_castItemGuid; int m_castItemLevel; uint m_spellXSpellVisualId; long m_applyTime; WorldObject m_owner; int m_maxDuration; // Max aura duration int m_duration; // Current time int m_timeCla; // Timer for power per sec calcultion List m_periodicCosts = new List();// Periodic costs int m_updateTargetMapInterval; // Timer for UpdateTargetMapOfEffect uint m_casterLevel; // Aura level (store caster level for correct show level dep amount) byte m_procCharges; // Aura charges (0 for infinite) byte m_stackAmount; // Aura stack amount //might need to be arrays still SpellEffectInfo[] _spellEffectInfos; AuraEffect[] _effects; Dictionary m_applications = new Dictionary(); bool m_isRemoved; bool m_isSingleTarget; // true if it's a single target spell and registered at caster - can change at spell steal for example bool m_isUsingCharges; ChargeDropEvent m_dropEvent; DateTime m_procCooldown; DateTime m_lastProcAttemptTime; DateTime m_lastProcSuccessTime; List m_removedApplications = new List(); #endregion } public class UnitAura : Aura { public UnitAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel) : base(spellproto, castId, owner, caster, castItem, casterGUID, castItemLevel) { m_AuraDRGroup = DiminishingGroup.None; LoadScripts(); _InitEffects(effMask, caster, baseAmount); GetUnitOwner()._AddAura(this, caster); } public override void _ApplyForTarget(Unit target, Unit caster, AuraApplication aurApp) { base._ApplyForTarget(target, caster, aurApp); // register aura diminishing on apply if (m_AuraDRGroup != DiminishingGroup.None) target.ApplyDiminishingAura(m_AuraDRGroup, true); } public override void _UnapplyForTarget(Unit target, Unit caster, AuraApplication aurApp) { base._UnapplyForTarget(target, caster, aurApp); // unregister aura diminishing (and store last time) if (m_AuraDRGroup != DiminishingGroup.None) target.ApplyDiminishingAura(m_AuraDRGroup, false); } public override void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default) { if (IsRemoved()) return; GetUnitOwner().RemoveOwnedAura(this, removeMode); } public override void FillTargetMap(ref Dictionary targets, Unit caster) { foreach (SpellEffectInfo effect in GetSpellEffectInfos()) { if (effect == null || !HasEffect(effect.EffectIndex)) continue; List targetList = new List(); // non-area aura if (effect.Effect == SpellEffectName.ApplyAura) { targetList.Add(GetUnitOwner()); } else { float radius = effect.CalcRadius(caster); if (!GetUnitOwner().HasUnitState(UnitState.Isolated)) { switch (effect.Effect) { case SpellEffectName.ApplyAreaAuraParty: case SpellEffectName.ApplyAreaAuraRaid: { targetList.Add(GetUnitOwner()); var u_check = new AnyGroupedUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius, effect.Effect == SpellEffectName.ApplyAreaAuraRaid); var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check); Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); break; } case SpellEffectName.ApplyAreaAuraFriend: { targetList.Add(GetUnitOwner()); var u_check = new AnyFriendlyUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius); var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check); Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); break; } case SpellEffectName.ApplyAreaAuraEnemy: { var u_check = new AnyAoETargetUnitInObjectRangeCheck(GetUnitOwner(), GetUnitOwner(), radius); var searcher = new UnitListSearcher(GetUnitOwner(), targetList, u_check); Cell.VisitAllObjects(GetUnitOwner(), searcher, radius); break; } case SpellEffectName.ApplyAreaAuraPet: targetList.Add(GetUnitOwner()); goto case SpellEffectName.ApplyAreaAuraOwner; case SpellEffectName.ApplyAreaAuraOwner: { Unit owner = GetUnitOwner().GetCharmerOrOwner(); if (owner != null) if (GetUnitOwner().IsWithinDistInMap(owner, radius)) targetList.Add(owner); break; } } } } foreach (var unit in targetList) { if (targets.ContainsKey(unit)) targets[unit] |= (byte)(1 << (int)effect.EffectIndex); else targets[unit] = (byte)(1 << (int)effect.EffectIndex); } } } // Allow Apply Aura Handler to modify and access m_AuraDRGroup public void SetDiminishGroup(DiminishingGroup group) { m_AuraDRGroup = group; } public DiminishingGroup GetDiminishGroup() { return m_AuraDRGroup; } DiminishingGroup m_AuraDRGroup; // Diminishing } public class DynObjAura : Aura { public DynObjAura(SpellInfo spellproto, ObjectGuid castId, uint effMask, WorldObject owner, Unit caster, int[] baseAmount, Item castItem, ObjectGuid casterGUID, int castItemLevel) : base(spellproto, castId, owner, caster, castItem, casterGUID, castItemLevel) { LoadScripts(); Contract.Assert(GetDynobjOwner() != null); Contract.Assert(GetDynobjOwner().IsInWorld); Contract.Assert(GetDynobjOwner().GetMap() == caster.GetMap()); _InitEffects(effMask, caster, baseAmount); GetDynobjOwner().SetAura(this); } public override void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default) { if (IsRemoved()) return; _Remove(removeMode); } public override void FillTargetMap(ref Dictionary targets, Unit caster) { Unit dynObjOwnerCaster = GetDynobjOwner().GetCaster(); float radius = GetDynobjOwner().GetRadius(); foreach (SpellEffectInfo effect in GetSpellEffectInfos()) { if (effect == null || !HasEffect(effect.EffectIndex)) continue; List targetList = new List(); if (effect.TargetB.GetTarget() == Targets.DestDynobjAlly || effect.TargetB.GetTarget() == Targets.UnitDestAreaAlly) { var u_check = new AnyFriendlyUnitInObjectRangeCheck(GetDynobjOwner(), dynObjOwnerCaster, radius); var searcher = new UnitListSearcher(GetDynobjOwner(), targetList, u_check); Cell.VisitAllObjects(GetDynobjOwner(), searcher, radius); } else { var u_check = new AnyAoETargetUnitInObjectRangeCheck(GetDynobjOwner(), dynObjOwnerCaster, radius); var searcher = new UnitListSearcher(GetDynobjOwner(), targetList, u_check); Cell.VisitAllObjects(GetDynobjOwner(), searcher, radius); } foreach (var unit in targetList) { if (targets.ContainsKey(unit)) targets[unit] |= (byte)(1 << (int)effect.EffectIndex); else targets[unit] = (byte)(1 << (int)effect.EffectIndex); } } } } public class ChargeDropEvent : BasicEvent { public ChargeDropEvent(Aura aura, AuraRemoveMode mode) { _base = aura; _mode = mode; } public override bool Execute(ulong e_time, uint p_time) { // _base is always valid (look in Aura._Remove()) _base.ModChargesDelayed(-1, _mode); return true; } Aura _base; AuraRemoveMode _mode; } public class AuraKey : IEquatable { public AuraKey(ObjectGuid caster, ObjectGuid item, uint spellId, uint effectMask) { Caster = caster; Item = item; SpellId = spellId; EffectMask = effectMask; } public static bool operator ==(AuraKey first, AuraKey other) { if (ReferenceEquals(first, other)) return true; if ((object)first == null || (object)other == null) return false; return first.Equals(other); } public static bool operator !=(AuraKey first, AuraKey other) { return !(first == other); } public override bool Equals(object obj) { return obj != null && obj is AuraKey && Equals((AuraKey)obj); } public bool Equals(AuraKey other) { return other.Caster == Caster && other.Item == Item && other.SpellId == SpellId && other.EffectMask == EffectMask; } public override int GetHashCode() { return new { Caster, Item, SpellId, EffectMask }.GetHashCode(); } public ObjectGuid Caster; public ObjectGuid Item; public uint SpellId; public uint EffectMask; } public class AuraLoadEffectInfo { public int[] Amounts = new int[SpellConst.MaxEffects]; public int[] BaseAmounts = new int[SpellConst.MaxEffects]; } }