Files
CypherCore/Source/Game/Spells/Auras/Aura.cs
T

3026 lines
121 KiB
C#

// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
using Framework.Constants;
using Framework.Dynamic;
using Game.DataStorage;
using Game.Entities;
using Game.Maps;
using Game.Networking.Packets;
using Game.Scripting;
using System;
using System.Collections.Generic;
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
Interrupt,
Cancel,
EnemySpell, // dispel and absorb aura destroy
Expire, // aura duration has ended
Death
}
[Flags]
public enum AuraFlags
{
None = 0x00,
NoCaster = 0x01,
Positive = 0x02,
Duration = 0x04,
Scalable = 0x08,
Negative = 0x10,
Unk20 = 0x20,
Unk40 = 0x40,
Unk80 = 0x80,
MawPower = 0x100
}
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;
Cypher.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 (var spellEffectInfo in GetBase().GetSpellInfo().GetEffects())
{
if (((1 << (int)spellEffectInfo.EffectIndex) & effMask) != 0 && !GetBase().GetSpellInfo().IsPositiveEffect(spellEffectInfo.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 (var spellEffectInfo in GetBase().GetSpellInfo().GetEffects())
{
if (((1 << (int)spellEffectInfo.EffectIndex) & effMask) != 0 && GetBase().GetSpellInfo().IsPositiveEffect(spellEffectInfo.EffectIndex))
{
positiveFound = true;
break;
}
}
_flags |= positiveFound ? AuraFlags.Positive : AuraFlags.Negative;
}
bool effectNeedsAmount(AuraEffect effect) => effect != null && (GetEffectsToApply() & (1 << (int)effect.GetEffIndex())) != 0 && Aura.EffectTypeNeedsSendingAmount(effect.GetAuraType());
if (GetBase().GetSpellInfo().HasAttribute(SpellAttr8.AuraSendAmount) || GetBase().GetAuraEffects().Any(effectNeedsAmount))
_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;
}
Cypher.Assert(aurEff != null);
Cypher.Assert(HasEffect(effIndex) == (!apply));
Cypher.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)
{
Cypher.Assert(!Convert.ToBoolean(_effectMask & (1 << (int)effIndex)));
_effectMask |= (uint)(1 << (int)effIndex);
aurEff.HandleEffect(this, AuraEffectHandleModes.Real, true);
}
else
{
Cypher.Assert(Convert.ToBoolean(_effectMask & (1 << (int)effIndex)));
_effectMask &= ~(uint)(1 << (int)effIndex);
aurEff.HandleEffect(this, AuraEffectHandleModes.Real, false);
}
SetNeedClientUpdate();
}
public void UpdateApplyEffectMask(uint newEffMask, bool canHandleNewEffects)
{
if (_effectsToApply == newEffMask)
return;
uint removeEffMask = (_effectsToApply ^ newEffMask) & (~newEffMask);
uint addEffMask = (_effectsToApply ^ newEffMask) & (~_effectsToApply);
// quick check, removes application completely
if (removeEffMask == _effectsToApply && addEffMask == 0)
{
_target._UnapplyAura(this, AuraRemoveMode.Default);
return;
}
// update real effects only if they were applied already
for (uint i = 0; i < SpellConst.MaxEffects; ++i)
{
if (HasEffect(i) && (removeEffMask & (1 << (int)i)) != 0)
_HandleEffect(i, false);
}
_effectsToApply = newEffMask;
if (canHandleNewEffects)
{
for (uint i = 0; i < SpellConst.MaxEffects; ++i)
{
if ((addEffMask & (1 << (int)i)) != 0)
_HandleEffect(i, true);
}
}
}
public void SetNeedClientUpdate()
{
if (_needClientUpdate || GetRemoveMode() != AuraRemoveMode.None)
return;
_needClientUpdate = true;
_target.SetVisibleAuraUpdate(this);
}
public void BuildUpdatePacket(ref AuraInfo auraInfo, bool remove)
{
Cypher.Assert(_target.HasVisibleAura(this) != remove);
auraInfo.Slot = GetSlot();
if (remove)
return;
auraInfo.AuraData = new();
Aura aura = GetBase();
AuraDataInfo auraData = auraInfo.AuraData;
auraData.CastID = aura.GetCastId();
auraData.SpellID = (int)aura.GetId();
auraData.Visual = aura.GetSpellVisual();
auraData.Flags = GetFlags();
if (aura.GetAuraType() != AuraObjectType.DynObj && aura.GetMaxDuration() > 0 && !aura.GetSpellInfo().HasAttribute(SpellAttr5.DoNotDisplayDuration))
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.IsUsingStacks() ? aura.GetStackAmount() : aura.GetCharges();
if (!aura.GetCasterGUID().IsUnit())
auraData.CastUnit = ObjectGuid.Empty; // optional data is filled in, but cast unit contains empty guid in packet
else if (!auraData.Flags.HasFlag(AuraFlags.NoCaster))
auraData.CastUnit = aura.GetCasterGUID();
if (auraData.Flags.HasFlag(AuraFlags.Duration))
{
auraData.Duration = aura.GetMaxDuration();
auraData.Remaining = aura.GetDuration();
}
if (auraData.Flags.HasFlag(AuraFlags.Scalable))
{
bool hasEstimatedAmounts = false;
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.Add(effect.GetAmount());
if (effect.GetEstimatedAmount().HasValue)
hasEstimatedAmounts = true;
}
}
if (hasEstimatedAmounts)
{
// When sending EstimatedPoints all effects (at least up to the last one that uses GetEstimatedAmount) must have proper value in packet
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.EstimatedPoints.Add(effect.GetEstimatedAmount().GetValueOrDefault(effect.GetAmount()));
}
}
}
public void ClientUpdate(bool remove = false)
{
_needClientUpdate = false;
AuraUpdate update = new();
update.UpdateAll = false;
update.UnitGUID = GetTarget().GetGUID();
AuraInfo auraInfo = new();
BuildUpdatePacket(ref auraInfo, remove);
update.Auras.Add(auraInfo);
_target.SendMessageToSet(update, true);
}
public string GetDebugInfo()
{
return $"Base: {(GetBase() != null ? GetBase().GetDebugInfo() : "NULL")}\nTarget: {(GetTarget() != null ? GetTarget().GetDebugInfo() : "NULL")}";
}
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)
{
Cypher.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(AuraCreateInfo createInfo)
{
m_spellInfo = createInfo._spellInfo;
m_castDifficulty = createInfo._castDifficulty;
m_castId = createInfo._castId;
m_casterGuid = createInfo.CasterGUID;
m_castItemGuid = createInfo.CastItemGUID;
m_castItemId = createInfo.CastItemId;
m_castItemLevel = createInfo.CastItemLevel;
m_spellVisual = new SpellCastVisual(createInfo.Caster != null ? createInfo.Caster.GetCastSpellXSpellVisualId(createInfo._spellInfo) : createInfo._spellInfo.GetSpellXSpellVisualId(), 0);
m_applyTime = GameTime.GetGameTime();
m_owner = createInfo._owner;
m_timeCla = 0;
m_updateTargetMapInterval = 0;
m_casterLevel = createInfo.Caster != null ? createInfo.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));
foreach (SpellPowerRecord power in m_spellInfo.PowerCosts)
if (power != null && (power.ManaPerSecond != 0 || power.PowerPctPerSecond > 0.0f))
m_periodicCosts.Add(power);
if (!m_periodicCosts.Empty())
m_timeCla = 1 * Time.InMilliseconds;
m_maxDuration = CalcMaxDuration(createInfo.Caster);
m_duration = m_maxDuration;
m_procCharges = CalcMaxCharges(createInfo.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<T>() where T : AuraScript
{
return (T)GetScriptByType(typeof(T));
}
public AuraScript GetScriptByType(Type type)
{
foreach (var auraScript in m_loadedScripts)
if (auraScript.GetType() == type)
return auraScript;
return null;
}
public void _InitEffects(uint effMask, Unit caster, int[] baseAmount)
{
// shouldn't be in constructor - functions in AuraEffect.AuraEffect use polymorphism
_effects = new AuraEffect[SpellConst.MaxEffects];
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
if ((effMask & (1 << (int)spellEffectInfo.EffectIndex)) != 0)
_effects[spellEffectInfo.EffectIndex] = new AuraEffect(this, spellEffectInfo, baseAmount != null ? baseAmount[spellEffectInfo.EffectIndex] : null, caster);
}
}
public virtual void Dispose()
{
// unload scripts
foreach (var itr in m_loadedScripts.ToList())
itr._Unload();
Cypher.Assert(m_applications.Empty());
_DeleteRemovedApplications();
}
public Unit GetCaster()
{
if (m_owner.GetGUID() == m_casterGuid)
return GetUnitOwner();
return Global.ObjAccessor.GetUnit(m_owner, m_casterGuid);
}
WorldObject GetWorldObjectCaster()
{
if (GetCasterGUID().IsUnit())
return GetCaster();
return Global.ObjAccessor.GetWorldObject(GetOwner(), GetCasterGUID());
}
public AuraObjectType GetAuraType()
{
return (m_owner.GetTypeId() == TypeId.DynamicObject) ? AuraObjectType.DynObj : AuraObjectType.Unit;
}
public virtual void _ApplyForTarget(Unit target, Unit caster, AuraApplication auraApp)
{
Cypher.Assert(target != null);
Cypher.Assert(auraApp != null);
// aura mustn't be already applied on target
//Cypher.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.IsCooldownStartedOnEvent())
{
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)
{
Cypher.Assert(target != null);
Cypher.Assert(auraApp.HasRemoveMode());
Cypher.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 != null ? caster.GetGUID().ToString() : "", auraApp.GetBase().GetSpellInfo().Id);
Cypher.Assert(false);
}
// aura has to be already applied
Cypher.Assert(app == auraApp);
m_applications.Remove(target.GetGUID());
_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)
{
Cypher.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;
}
}
public 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<Unit, uint> targets = new();
FillTargetMap(ref targets, caster);
List<Unit> targetsToRemove = new();
// mark all auras as ready to remove
foreach (var app in m_applications)
{
// not found in current area - remove the aura
if (!targets.TryGetValue(app.Value.GetTarget(), out uint existing))
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.GetTarget().IsImmunedToSpell(GetSpellInfo(), caster, true) || !CanBeAppliedOn(app.Value.GetTarget()))
{
targetsToRemove.Add(app.Value.GetTarget());
continue;
}
// check target immunities (for existing targets)
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
if (app.Value.GetTarget().IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster, true))
existing &= ~(uint)(1 << (int)spellEffectInfo.EffectIndex);
targets[app.Value.GetTarget()] = existing;
// needs to add/remove effects from application, don't remove from map so it gets updated
if (app.Value.GetEffectMask() != existing)
continue;
// nothing to do - aura already applied
// remove from auras to register list
targets.Remove(app.Value.GetTarget());
}
}
// register auras for units
foreach (var unit in targets.Keys.ToList())
{
bool addUnit = true;
// check target immunities
AuraApplication aurApp = GetApplicationOfTarget(unit.GetGUID());
if (aurApp == null)
{
// check target immunities (for new targets)
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
if (unit.IsImmunedToSpellEffect(GetSpellInfo(), spellEffectInfo, caster))
targets[unit] &= ~(uint)(1 << (int)spellEffectInfo.EffectIndex);
if (targets[unit] == 0 || unit.IsImmunedToSpell(GetSpellInfo(), caster) || !CanBeAppliedOn(unit))
addUnit = false;
}
if (addUnit && !unit.IsHighestExclusiveAura(this, true))
addUnit = false;
// Dynobj auras don't hit flying targets
if (GetAuraType() == AuraObjectType.DynObj && unit.IsInFlight())
addUnit = false;
// Do not apply aura if it cannot stack with existing auras
if (addUnit)
{
// Allow to remove by stack when aura is going to be applied on owner
if (unit != GetOwner())
{
// 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);
}
if (aurApp != null)
{
aurApp.UpdateApplyEffectMask(targets[unit], true); // aura is already applied, this means we need to update effects of current application
targets.Remove(unit);
}
else
unit._CreateAuraApplication(this, targets[unit]);
}
}
// 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
Cypher.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<Unit> targetList = new();
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
Cypher.Assert((!GetOwner().IsInWorld && GetOwner() == unit) || GetOwner().IsInMap(unit));
unit._ApplyAuraEffect(this, effIndex);
}
}
}
public void UpdateOwner(uint diff, WorldObject owner)
{
Cypher.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 && (caster == GetOwner() || !GetSpellInfo().HasAttribute(SpellAttr2.NoTargetPerSecondCosts)))
{
if (!m_periodicCosts.Empty())
{
m_timeCla += (int)(1000 - diff);
foreach (SpellPowerRecord power in m_periodicCosts)
{
if (power.RequiredAuraSpellID != 0 && !caster.HasAura(power.RequiredAuraSpellID))
continue;
int manaPerSecond = (int)power.ManaPerSecond;
if (power.PowerType != PowerType.Health)
manaPerSecond += MathFunctions.CalculatePct(caster.GetMaxPower(power.PowerType), power.PowerPctPerSecond);
else
manaPerSecond += (int)MathFunctions.CalculatePct(caster.GetMaxHealth(), power.PowerPctPerSecond);
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();
}
}
}
}
}
}
}
public int CalcMaxDuration(Unit caster)
{
return CalcMaxDuration(GetSpellInfo(), caster, null);
}
public static int CalcMaxDuration(SpellInfo spellInfo, WorldObject caster, List<SpellPowerCost> powerCosts)
{
Player modOwner = null;
int maxDuration;
if (caster != null)
{
modOwner = caster.GetSpellModOwner();
maxDuration = caster.CalcSpellDuration(spellInfo, powerCosts);
}
else
maxDuration = spellInfo.GetDuration();
if (spellInfo.IsPassive() && spellInfo.DurationEntry == null)
maxDuration = -1;
// IsPermanent() checks max duration (which we are supposed to calculate here)
if (maxDuration != -1 && modOwner != null)
modOwner.ApplySpellMod(spellInfo, SpellModOp.Duration, ref maxDuration);
return maxDuration;
}
public void SetDuration(int duration, bool withMods = false)
{
if (withMods)
{
Unit caster = GetCaster();
if (caster != null)
{
Player modOwner = caster.GetSpellModOwner();
if (modOwner != null)
modOwner.ApplySpellMod(GetSpellInfo(), SpellModOp.Duration, ref duration);
}
}
m_duration = duration;
SetNeedClientUpdateForTargets();
}
public void RefreshDuration(bool withMods = false)
{
Unit caster = GetCaster();
if (withMods && caster != null)
{
int duration = m_spellInfo.GetMaxDuration();
// Calculate duration of periodics affected by haste.
if (m_spellInfo.HasAttribute(SpellAttr8.HasteAffectsDuration))
duration = (int)(duration * caster.m_unitData.ModCastingSpeed);
SetMaxDuration(duration);
SetDuration(duration);
}
else
SetDuration(GetMaxDuration());
if (!m_periodicCosts.Empty())
m_timeCla = 1 * Time.InMilliseconds;
// also reset periodic counters
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
{
AuraEffect aurEff = GetEffect(i);
if (aurEff != null)
aurEff.ResetTicks();
}
}
void RefreshTimers(bool resetPeriodicTimer)
{
m_maxDuration = CalcMaxDuration();
// Pandemic Mechanic
if (m_spellInfo.HasAttribute(SpellAttr13.PeriodicRefreshExtendsDuration))
{
// Pandemic doesn't reset periodic timer
resetPeriodicTimer = false;
int pandemicDuration = MathFunctions.CalculatePct(m_maxDuration, 30.0f);
m_maxDuration = Math.Max(GetDuration(), Math.Min(pandemicDuration, GetDuration()) + m_maxDuration);
}
RefreshDuration();
Unit caster = GetCaster();
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
{
AuraEffect aurEff = GetEffect(i);
if (aurEff != null)
aurEff.CalculatePeriodic(caster, resetPeriodicTimer, 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 == null)
return;
m_dropEvent = new ChargeDropEvent(this, removeMode);
owner.m_Events.AddEvent(m_dropEvent, owner.m_Events.CalculateTime(TimeSpan.FromMilliseconds(delay)));
}
public void SetStackAmount(byte stackAmount)
{
m_stackAmount = stackAmount;
Unit caster = GetCaster();
List<AuraApplication> applications = GetApplicationList();
foreach (var aurApp in applications)
if (!aurApp.HasRemoveMode())
HandleAuraSpecificMods(aurApp, caster, false, true);
foreach (AuraEffect aurEff in GetAuraEffects())
if (aurEff != null)
aurEff.ChangeAmount(aurEff.CalculateAmount(caster), false, true);
foreach (var aurApp in applications)
if (!aurApp.HasRemoveMode())
HandleAuraSpecificMods(aurApp, caster, true, true);
SetNeedClientUpdateForTargets();
}
public bool IsUsingStacks()
{
return m_spellInfo.StackAmount > 0 || m_stackAmount > 1;
}
public uint CalcMaxStackAmount()
{
uint maxStackAmount = m_spellInfo.StackAmount;
Unit caster = GetCaster();
if (caster != null)
{
Player modOwner = caster.GetSpellModOwner();
if (modOwner != null)
modOwner.ApplySpellMod(m_spellInfo, SpellModOp.MaxAuraStacks, ref maxStackAmount);
}
return maxStackAmount;
}
public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default, bool resetPeriodicTimer = true)
{
int stackAmount = m_stackAmount + num;
uint maxStackAmount = CalcMaxStackAmount();
// limit the stack amount (only on stack increase, stack amount may be changed manually)
if ((num > 0) && (stackAmount > maxStackAmount))
{
// 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() && (m_spellInfo.StackAmount != 0 || (!m_spellInfo.HasAttribute(SpellAttr1.AuraUnique) && !m_spellInfo.HasAttribute(SpellAttr5.AuraUniquePerCaster)));
// Update stack amount
SetStackAmount((byte)stackAmount);
if (refresh)
{
RefreshTimers(resetPeriodicTimer);
// reset charges
SetCharges(CalcMaxCharges());
}
SetNeedClientUpdateForTargets();
return false;
}
public bool HasMoreThanOneEffectForType(AuraType auraType)
{
uint count = 0;
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
if (HasEffect(spellEffectInfo.EffectIndex) && spellEffectInfo.ApplyAuraName == auraType)
++count;
}
return count > 1;
}
public bool IsArea()
{
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
if (HasEffect(spellEffectInfo.EffectIndex) && spellEffectInfo.IsAreaAuraEffect())
return true;
}
return false;
}
public bool IsPassive()
{
return m_spellInfo.IsPassive();
}
public bool IsDeathPersistent()
{
return GetSpellInfo().IsDeathPersistent();
}
public bool IsRemovedOnShapeLost(Unit target)
{
return GetCasterGUID() == target.GetGUID()
&& m_spellInfo.Stances != 0
&& !m_spellInfo.HasAttribute(SpellAttr2.AllowWhileNotShapeshiftedCasterForm)
&& !m_spellInfo.HasAttribute(SpellAttr0.NotShapeshifted);
}
public bool CanBeSaved()
{
if (IsPassive())
return false;
if (GetSpellInfo().IsChanneled())
return false;
// Check if aura is single target, not only spell info
if (GetCasterGUID() != GetOwner().GetGUID())
{
// owner == caster for area auras, check for possible bad data in DB
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
if (!spellEffectInfo.IsEffect())
continue;
if (spellEffectInfo.IsTargetingArea() || spellEffectInfo.IsAreaAuraEffect())
return false;
}
if (IsSingleTarget() || GetSpellInfo().IsSingleTarget())
return false;
}
if (GetSpellInfo().HasAttribute(SpellCustomAttributes.AuraCannotBeSaved))
return false;
// don't save auras removed by proc system
if (IsUsingCharges() && GetCharges() == 0)
return false;
// don't save permanent auras triggered by items, they'll be recasted on login if necessary
if (!GetCastItemGUID().IsEmpty() && IsPermanent())
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.MagePolymorph:
if (aura.GetSpellInfo().GetSpellSpecific() == spec)
return true;
break;
default:
break;
}
return false;
}
public void UnregisterSingleTarget()
{
Cypher.Assert(m_isSingleTarget);
Unit caster = GetCaster();
Cypher.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(GetSpellInfo(), SpellModOp.DispelResistance, ref resistChance);
}
resistChance = resistChance < 0 ? 0 : resistChance;
resistChance = resistChance > 100 ? 100 : resistChance;
return 100 - resistChance;
}
public AuraKey GenerateKey(out uint recalculateMask)
{
AuraKey key = new(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 << (int)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 static bool EffectTypeNeedsSendingAmount(AuraType type)
{
switch (type)
{
case AuraType.OverrideActionbarSpells:
case AuraType.OverrideActionbarSpellsTriggered:
case AuraType.ModSpellCategoryCooldown:
case AuraType.ModMaxCharges:
case AuraType.ChargeRecoveryMod:
case AuraType.ChargeRecoveryMultiplier:
return true;
default:
break;
}
return false;
}
public void RecalculateAmountOfEffects()
{
Cypher.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)
{
Cypher.Assert(!IsRemoved());
foreach (AuraEffect effect in GetAuraEffects())
if (effect != null && !IsRemoved())
effect.HandleEffect(aurApp, mode, apply);
}
public List<AuraApplication> GetApplicationList()
{
var applicationList = new List<AuraApplication>();
foreach (var aurApp in m_applications.Values)
{
if (aurApp.GetEffectMask() != 0)
applicationList.Add(aurApp);
}
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 spellArea in saBounds)
{
// some auras remove at aura remove
if (spellArea.flags.HasAnyFlag(SpellAreaFlag.AutoRemove) && !spellArea.IsFitToRequirements((Player)target, zone, area))
target.RemoveAurasDueToSpell(spellArea.spellId);
// some auras applied at aura apply
else if (spellArea.flags.HasAnyFlag(SpellAreaFlag.AutoCast))
{
if (!target.HasAura(spellArea.spellId))
target.CastSpell(target, spellArea.spellId, new CastSpellExtraArgs(TriggerCastFlags.FullMask).SetOriginalCastId(GetCastId()));
}
}
}
// handle spell_linked_spell table
if (!onReapply)
{
// apply linked auras
if (apply)
{
var spellTriggered = Global.SpellMgr.GetSpellLinked(SpellLinkedType.Aura, GetId());
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(SpellLinkedType.Remove, GetId());
if (spellTriggered != null)
{
foreach (var spell in spellTriggered)
{
if (spell < 0)
target.RemoveAurasDueToSpell((uint)-spell);
else if (removeMode != AuraRemoveMode.Death)
target.CastSpell(target, (uint)spell, new CastSpellExtraArgs(TriggerCastFlags.FullMask)
.SetOriginalCaster(GetCasterGUID())
.SetOriginalCastId(GetCastId()));
}
}
spellTriggered = Global.SpellMgr.GetSpellLinked(SpellLinkedType.Aura, GetId());
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(SpellLinkedType.Aura, GetId());
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 33572: // Gronn Lord's Grasp, becomes stoned
if (GetStackAmount() >= 5 && !target.HasAura(33652))
target.CastSpell(target, 33652, new CastSpellExtraArgs(TriggerCastFlags.FullMask).SetOriginalCastId(GetCastId()));
break;
case 50836: //Petrifying Grip, becomes stoned
if (GetStackAmount() >= 5 && !target.HasAura(50812))
target.CastSpell(target, 50812, new CastSpellExtraArgs(TriggerCastFlags.FullMask).SetOriginalCastId(GetCastId()));
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))
{
CastSpellExtraArgs args = new(GetEffect(0));
args.AddSpellMod(SpellValueMod.BasePoint0, GetEffect(0).GetAmount());
caster.CastSpell(target, 64801, args);
}
}
break;
}
}
// mods at aura remove
else
{
switch (GetSpellInfo().SpellFamilyName)
{
case SpellFamilyNames.Mage:
switch (GetId())
{
case 66: // Invisibility
if (removeMode != AuraRemoveMode.Expire)
break;
target.CastSpell(target, 32612, new CastSpellExtraArgs(GetEffect(1)));
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.GetSpellInfo()))
{
// 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()) <= TimeSpan.FromSeconds(11))
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();
CastSpellExtraArgs args = new(TriggerCastFlags.FullMask);
args.SetOriginalCastId(GetCastId());
args.AddSpellMod(SpellValueMod.BasePoint0, MathFunctions.CalculatePct(caster.GetMaxPower(PowerType.Mana), multiplier));
caster.CastSpell(caster, 47755, args);
}
}
}
break;
case SpellFamilyNames.Rogue:
// Remove Vanish on stealth remove
if (GetId() == 1784)
target.RemoveAurasWithFamily(SpellFamilyNames.Rogue, new FlagArray128(0x0000800, 0, 0), target.GetGUID());
break;
}
}
// mods at aura apply or remove
switch (GetSpellInfo().SpellFamilyName)
{
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, new CastSpellExtraArgs(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, new CastSpellExtraArgs(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, new CastSpellExtraArgs(TriggerCastFlags.FullMask).SetOriginalCastId(GetCastId()));
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, new CastSpellExtraArgs(true));
}
else
{
if (target != caster)
caster.RemoveAurasDueToSpell(GetId());
else
caster.RemoveAurasDueToSpell(100001);
}
}
break;
}
}
bool CanBeAppliedOn(Unit target)
{
foreach (uint label in GetSpellInfo().Labels)
if (target.HasAuraTypeWithMiscvalue(AuraType.SuppressItemPassiveEffectBySpellLabel, (int)label))
return false;
// 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;
bool sameCaster = GetCasterGUID() == existingAura.GetCasterGUID();
SpellInfo existingSpellInfo = existingAura.GetSpellInfo();
// Dynobj auras do not stack when they come from the same spell cast by the same caster
if (GetAuraType() == AuraObjectType.DynObj || existingAura.GetAuraType() == AuraObjectType.DynObj)
{
if (sameCaster && m_spellInfo.Id == existingSpellInfo.Id)
return false;
return true;
}
// 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 (var spellEffectInfo in existingSpellInfo.GetEffects())
{
// prevent remove triggering aura by triggered aura
if (spellEffectInfo.TriggerSpell == GetId())
return true;
}
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
// prevent remove triggered aura by triggering aura refresh
if (spellEffectInfo.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.DotStackingRule))
return true;
// check same periodic auras
bool hasPeriodicNonAreaEffect(SpellInfo spellInfo)
{
foreach (var spellEffectInfo in spellInfo.GetEffects())
{
switch (spellEffectInfo.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:
{
// periodic auras which target areas are not allowed to stack this way (replenishment for example)
if (spellEffectInfo.IsTargetingArea())
return false;
return true;
}
default:
break;
}
}
return false;
}
if (hasPeriodicNonAreaEffect(m_spellInfo) && hasPeriodicNonAreaEffect(existingSpellInfo))
return true;
}
if (HasEffectType(AuraType.ControlVehicle) && existingAura.HasEffectType(AuraType.ControlVehicle))
{
Vehicle veh = null;
if (GetOwner().ToUnit() != null)
veh = GetOwner().ToUnit().GetVehicleKit();
if (veh == null) // 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(SpellProcEntry procEntry, DateTime now)
{
// cooldowns should be added to the whole aura (see 51698 area aura)
int procCooldown = (int)procEntry.Cooldown;
Unit caster = GetCaster();
if (caster != null)
{
Player modOwner = caster.GetSpellModOwner();
if (modOwner != null)
modOwner.ApplySpellMod(GetSpellInfo(), SpellModOp.ProcCooldown, ref procCooldown);
}
m_procCooldown = now + TimeSpan.FromMilliseconds(procCooldown);
}
public void ResetProcCooldown()
{
m_procCooldown = DateTime.Now;
}
public void PrepareProcToTrigger(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now)
{
bool prepare = CallScriptPrepareProcHandlers(aurApp, eventInfo);
if (!prepare)
return;
SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetSpellInfo());
Cypher.Assert(procEntry != null);
PrepareProcChargeDrop(procEntry, eventInfo);
// cooldowns should be added to the whole aura (see 51698 area aura)
AddProcCooldown(procEntry, now);
SetLastProcSuccessTime(now);
}
public void PrepareProcChargeDrop(SpellProcEntry procEntry, ProcEventInfo eventInfo)
{
// take one charge, aura expiration will be handled in Aura.TriggerProcOnEvent (if needed)
if (!procEntry.AttributesMask.HasAnyFlag(ProcAttributes.UseStacksForCharges) && IsUsingCharges() && (eventInfo.GetSpellInfo() == null || !eventInfo.GetSpellInfo().HasAttribute(SpellAttr6.DoNotConsumeResources)))
{
--m_procCharges;
SetNeedClientUpdateForTargets();
}
}
public void ConsumeProcCharges(SpellProcEntry procEntry)
{
// Remove aura if we've used last charge to proc
if (procEntry.AttributesMask.HasFlag(ProcAttributes.UseStacksForCharges))
{
ModStackAmount(-1);
}
else if (IsUsingCharges())
{
if (GetCharges() == 0)
Remove();
}
}
public uint GetProcEffectMask(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now)
{
SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(GetSpellInfo());
// only auras with spell proc entry can trigger proc
if (procEntry == null)
return 0;
// check spell triggering us
Spell spell = eventInfo.GetProcSpell();
if (spell != null)
{
// Do not allow auras to proc from effect triggered from itself
if (spell.IsTriggeredByAura(m_spellInfo))
return 0;
// check if aura can proc when spell is triggered (exception for hunter auto shot & wands)
if (!GetSpellInfo().HasAttribute(SpellAttr3.CanProcFromProcs) && !procEntry.AttributesMask.HasFlag(ProcAttributes.TriggeredCanProc) && !eventInfo.GetTypeMask().HasFlag(ProcFlags.AutoAttackMask))
if (spell.IsTriggered() && !spell.GetSpellInfo().HasAttribute(SpellAttr3.NotAProc))
return 0;
if (spell.m_CastItem != null && procEntry.AttributesMask.HasFlag(ProcAttributes.CantProcFromItemCast))
return 0;
if (spell.GetSpellInfo().HasAttribute(SpellAttr4.SuppressWeaponProcs) && GetSpellInfo().HasAttribute(SpellAttr6.AuraIsWeaponProc))
return 0;
if (GetSpellInfo().HasAttribute(SpellAttr12.OnlyProcFromClassAbilities) && !spell.GetSpellInfo().HasAttribute(SpellAttr13.AllowClassAbilityProcs))
return 0;
}
// check don't break stealth attr present
if (m_spellInfo.HasAura(AuraType.ModStealth))
{
SpellInfo eventSpellInfo = eventInfo.GetSpellInfo();
if (eventSpellInfo != null)
if (eventSpellInfo.HasAttribute(SpellCustomAttributes.DontBreakStealth))
return 0;
}
// check if we have charges to proc with
if (IsUsingCharges())
{
if (GetCharges() == 0)
return 0;
if (procEntry.AttributesMask.HasAnyFlag(ProcAttributes.ReqSpellmod))
{
Spell eventSpell = eventInfo.GetProcSpell();
if (eventSpell != null)
if (!eventSpell.m_appliedMods.Contains(this))
return 0;
}
}
// check proc cooldown
if (IsProcOnCooldown(now))
return 0;
// do checks against db data
if (!SpellManager.CanSpellTriggerProcOnEvent(procEntry, eventInfo))
return 0;
// do checks using conditions table
if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SpellProc, GetId(), eventInfo.GetActor(), eventInfo.GetActionTarget()))
return 0;
// AuraScript Hook
bool check = CallScriptCheckProcHandlers(aurApp, eventInfo);
if (!check)
return 0;
// At least one effect has to pass checks to proc aura
uint procEffectMask = aurApp.GetEffectMask();
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
if ((procEffectMask & (1u << i)) != 0)
if ((procEntry.DisableEffectsMask & (1u << i)) != 0 || !GetEffect(i).CheckEffectProc(aurApp, eventInfo))
procEffectMask &= ~(1u << i);
if (procEffectMask == 0)
return 0;
// @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.IsPlayer() && GetSpellInfo().EquippedItemClass != ItemClass.None)
{
if (!GetSpellInfo().HasAttribute(SpellAttr3.NoProcEquipRequirement))
{
Item item = null;
if (GetSpellInfo().EquippedItemClass == ItemClass.Weapon)
{
if (target.ToPlayer().IsInFeralForm())
return 0;
DamageInfo damageInfo = eventInfo.GetDamageInfo();
if (damageInfo != null)
{
if (damageInfo.GetAttackType() != WeaponAttackType.OffAttack)
item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand);
else
item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand);
}
}
else if (GetSpellInfo().EquippedItemClass == ItemClass.Armor)
{
// Check if player is wearing shield
item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand);
}
if (item == null || item.IsBroken() || !item.IsFitToSpellRequirements(GetSpellInfo()))
return 0;
}
}
if (m_spellInfo.HasAttribute(SpellAttr3.OnlyProcOutdoors))
if (!target.IsOutdoors())
return 0;
if (m_spellInfo.HasAttribute(SpellAttr3.OnlyProcOnCaster))
if (target.GetGUID() != GetCasterGUID())
return 0;
if (!m_spellInfo.HasAttribute(SpellAttr4.AllowProcWhileSitting))
if (!target.IsStandState())
return 0;
bool success = RandomHelper.randChance(CalcProcChance(procEntry, eventInfo));
SetLastProcAttemptTime(now);
if (success)
return procEffectMask;
return 0;
}
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());
}
if (GetSpellInfo().ProcBasePPM > 0.0f)
chance = CalcPPMProcChance(caster);
// apply chance modifer aura, applies also to ppm chance (see improved judgement of light spell)
Player modOwner = caster.GetSpellModOwner();
if (modOwner != null)
modOwner.ApplySpellMod(GetSpellInfo(), SpellModOp.ProcChance, ref chance);
}
// proc chance is reduced by an additional 3.333% per level past 60
if (procEntry.AttributesMask.HasAnyFlag(ProcAttributes.ReduceProc60) && eventInfo.GetActor().GetLevel() > 60)
chance = Math.Max(0.0f, (1.0f - ((eventInfo.GetActor().GetLevel() - 60) * 1.0f / 30.0f)) * chance);
return chance;
}
public void TriggerProcOnEvent(uint procEffectMask, AuraApplication aurApp, ProcEventInfo eventInfo)
{
if (procEffectMask != 0)
{
bool prevented = CallScriptProcHandlers(aurApp, eventInfo);
if (!prevented)
{
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
{
if (!Convert.ToBoolean(procEffectMask & (1 << i)))
continue;
// OnEffectProc / AfterEffectProc hooks handled in AuraEffect.HandleProc()
if (aurApp.HasEffect(i))
GetEffect(i).HandleProc(aurApp, eventInfo);
}
CallScriptAfterProcHandlers(aurApp, eventInfo);
}
}
ConsumeProcCharges(Global.SpellMgr.GetSpellProcEntry(GetSpellInfo()));
}
public float CalcPPMProcChance(Unit actor)
{
// Formula see http://us.battle.net/wow/en/forum/topic/8197741003#1
float ppm = m_spellInfo.CalcProcPPM(actor, GetCastItemLevel());
float averageProcInterval = 60.0f / ppm;
var currentTime = GameTime.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()
{
_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 CallScriptEffectCalcCritChanceHandlers(AuraEffect aurEff, AuraApplication aurApp, Unit victim, ref float critChance)
{
foreach (AuraScript loadedScript in m_loadedScripts)
{
loadedScript._PrepareScriptCall(AuraScriptHookType.EffectCalcCritChance, aurApp);
foreach (var hook in loadedScript.DoEffectCalcCritChance)
if (hook.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
hook.Call(aurEff, victim, ref critChance);
loadedScript._FinishScriptCall();
}
}
public void CallScriptCalcDamageAndHealingHandlers(AuraEffect aurEff, AuraApplication aurApp, Unit victim, ref int damageOrHealing, ref int flatMod, ref float pctMod)
{
foreach (AuraScript script in m_loadedScripts)
{
script._PrepareScriptCall(AuraScriptHookType.EffectCalcDamageAndHealing, aurApp);
foreach (var effectCalcDamageAndHealing in script.DoEffectCalcDamageAndHealing)
if (effectCalcDamageAndHealing.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
effectCalcDamageAndHealing.Call(aurEff, victim, ref damageOrHealing, ref flatMod, ref pctMod);
script._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);
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 CallScriptEffectAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, HealInfo healInfo, ref uint absorbAmount, ref bool defaultPrevented)
{
foreach (var auraScript in m_loadedScripts)
{
auraScript._PrepareScriptCall(AuraScriptHookType.EffectAbsorb, aurApp);
foreach (var eff in auraScript.OnEffectAbsorbHeal)
if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
eff.Call(aurEff, healInfo, ref absorbAmount);
defaultPrevented = auraScript._IsDefaultActionPrevented();
auraScript._FinishScriptCall();
}
}
public void CallScriptEffectAfterAbsorbHandlers(AuraEffect aurEff, AuraApplication aurApp, HealInfo healInfo, ref uint absorbAmount)
{
foreach (var auraScript in m_loadedScripts)
{
auraScript._PrepareScriptCall(AuraScriptHookType.EffectAfterAbsorb, aurApp);
foreach (var eff in auraScript.AfterEffectAbsorbHeal)
if (eff.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
eff.Call(aurEff, healInfo, ref absorbAmount);
auraScript._FinishScriptCall();
}
}
public void CallScriptEffectManaShieldHandlers(AuraEffect aurEff, AuraApplication aurApp, DamageInfo dmgInfo, ref uint absorbAmount, ref 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 void CallScriptEnterLeaveCombatHandlers(AuraApplication aurApp, bool isNowInCombat)
{
foreach (var loadedScript in m_loadedScripts)
{
loadedScript._PrepareScriptCall(AuraScriptHookType.EnterLeaveCombat, aurApp);
foreach (var hook in loadedScript.OnEnterLeaveCombat)
hook.Call(isNowInCombat);
loadedScript._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 CallScriptCheckEffectProcHandlers(AuraEffect aurEff, AuraApplication aurApp, ProcEventInfo eventInfo)
{
bool result = true;
foreach (var auraScript in m_loadedScripts)
{
auraScript._PrepareScriptCall(AuraScriptHookType.CheckEffectProc, aurApp);
foreach (var hook in auraScript.DoCheckEffectProc)
if (hook.IsEffectAffected(m_spellInfo, aurEff.GetEffIndex()))
result &= hook.Call(aurEff, eventInfo);
auraScript._FinishScriptCall();
}
return result;
}
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();
}
}
public virtual string GetDebugInfo()
{
return $"Id: {GetId()} Name: '{GetSpellInfo().SpellName[Global.WorldMgr.GetDefaultDbcLocale()]}' Caster: {GetCasterGUID()}\nOwner: {(GetOwner() != null ? GetOwner().GetDebugInfo() : "NULL")}";
}
#endregion
public SpellInfo GetSpellInfo() { return m_spellInfo; }
public uint GetId() { return m_spellInfo.Id; }
public Difficulty GetCastDifficulty() { return m_castDifficulty; }
public ObjectGuid GetCastId() { return m_castId; }
public ObjectGuid GetCasterGUID() { return m_casterGuid; }
public ObjectGuid GetCastItemGUID() { return m_castItemGuid; }
public uint GetCastItemId() { return m_castItemId; }
public int GetCastItemLevel() { return m_castItemLevel; }
public SpellCastVisual GetSpellVisual() { return m_spellVisual; }
public WorldObject GetOwner() { return m_owner; }
public Unit GetUnitOwner()
{
Cypher.Assert(GetAuraType() == AuraObjectType.Unit);
return m_owner.ToUnit();
}
public DynamicObject GetDynobjOwner()
{
Cypher.Assert(GetAuraType() == AuraObjectType.DynObj);
return m_owner.ToDynamicObject();
}
public void SetCastItemGUID(ObjectGuid guid)
{
m_castItemGuid = guid;
}
public void SetCastItemId(uint id)
{
m_castItemId = id;
}
public void SetCastItemLevel(int level)
{
m_castItemLevel = level;
}
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(GetSpellInfo());
if (procEntry != null)
maxProcCharges = procEntry.Charges;
if (caster != null)
{
Player modOwner = caster.GetSpellModOwner();
if (modOwner != null)
modOwner.ApplySpellMod(GetSpellInfo(), SpellModOp.ProcCharges, 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 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 aurEff in GetAuraEffects())
if (aurEff != null)
effMask |= (uint)(1 << (int)aurEff.GetEffIndex());
return effMask;
}
public Dictionary<ObjectGuid, AuraApplication> 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<Unit, uint> targets, Unit caster) { }
public AuraEffect[] GetAuraEffects() { return _effects; }
public void SetLastProcAttemptTime(DateTime lastProcAttemptTime) { m_lastProcAttemptTime = lastProcAttemptTime; }
public void SetLastProcSuccessTime(DateTime lastProcSuccessTime) { m_lastProcSuccessTime = lastProcSuccessTime; }
public UnitAura ToUnitAura() { if (GetAuraType() == AuraObjectType.Unit) return (UnitAura)this; else return null; }
public DynObjAura ToDynObjAura() { if (GetAuraType() == AuraObjectType.DynObj) return (DynObjAura)this; else return null; }
//Static Methods
public static uint BuildEffectMaskForOwner(SpellInfo spellProto, uint availableEffectMask, WorldObject owner)
{
Cypher.Assert(spellProto != null);
Cypher.Assert(owner != null);
uint effMask = 0;
switch (owner.GetTypeId())
{
case TypeId.Unit:
case TypeId.Player:
foreach (var spellEffectInfo in spellProto.GetEffects())
{
if (spellEffectInfo.IsUnitOwnedAuraEffect())
effMask |= (1u << (int)spellEffectInfo.EffectIndex);
}
break;
case TypeId.DynamicObject:
foreach (var spellEffectInfo in spellProto.GetEffects())
{
if (spellEffectInfo.Effect == SpellEffectName.PersistentAreaAura)
effMask |= (1u << (int)spellEffectInfo.EffectIndex);
}
break;
default:
break;
}
return (effMask & availableEffectMask);
}
public static Aura TryRefreshStackOrCreate(AuraCreateInfo createInfo, bool updateEffectMask = true)
{
Cypher.Assert(createInfo.Caster != null || !createInfo.CasterGUID.IsEmpty());
createInfo.IsRefresh = false;
createInfo._auraEffectMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), createInfo.GetAuraEffectMask(), createInfo.GetOwner());
createInfo._targetEffectMask &= createInfo._auraEffectMask;
uint effMask = createInfo._auraEffectMask;
if (createInfo._targetEffectMask != 0)
effMask = createInfo._targetEffectMask;
if (effMask == 0)
return null;
Aura foundAura = createInfo.GetOwner().ToUnit()._TryStackingOrRefreshingExistingAura(createInfo);
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;
createInfo.IsRefresh = true;
// add owner
Unit unit = createInfo.GetOwner().ToUnit();
// check effmask on owner application (if existing)
if (updateEffectMask)
{
AuraApplication aurApp = foundAura.GetApplicationOfTarget(unit.GetGUID());
if (aurApp != null)
aurApp.UpdateApplyEffectMask(effMask, false);
}
return foundAura;
}
else
return Create(createInfo);
}
public static Aura TryCreate(AuraCreateInfo createInfo)
{
uint effMask = createInfo._auraEffectMask;
if (createInfo._targetEffectMask != 0)
effMask = createInfo._targetEffectMask;
effMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), effMask, createInfo.GetOwner());
if (effMask == 0)
return null;
return Create(createInfo);
}
public static Aura Create(AuraCreateInfo createInfo)
{
// try to get caster of aura
if (!createInfo.CasterGUID.IsEmpty())
{
if (createInfo.CasterGUID.IsUnit())
{
if (createInfo._owner.GetGUID() == createInfo.CasterGUID)
createInfo.Caster = createInfo._owner.ToUnit();
else
createInfo.Caster = Global.ObjAccessor.GetUnit(createInfo._owner, createInfo.CasterGUID);
}
}
else if (createInfo.Caster != null)
createInfo.CasterGUID = createInfo.Caster.GetGUID();
// check if aura can be owned by owner
if (createInfo.GetOwner().IsTypeMask(TypeMask.Unit))
if (!createInfo.GetOwner().IsInWorld || createInfo.GetOwner().ToUnit().IsDuringRemoveFromWorld())
// owner not in world so don't allow to own not self casted single target auras
if (createInfo.CasterGUID != createInfo.GetOwner().GetGUID() && createInfo.GetSpellInfo().IsSingleTarget())
return null;
Aura aura;
switch (createInfo.GetOwner().GetTypeId())
{
case TypeId.Unit:
case TypeId.Player:
aura = new UnitAura(createInfo);
// aura can be removed in Unit::_AddAura call
if (aura.IsRemoved())
return null;
// add owner
uint effMask = createInfo._auraEffectMask;
if (createInfo._targetEffectMask != 0)
effMask = createInfo._targetEffectMask;
effMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), effMask, createInfo.GetOwner());
Cypher.Assert(effMask != 0);
Unit unit = createInfo.GetOwner().ToUnit();
aura.ToUnitAura().AddStaticApplication(unit, effMask);
break;
case TypeId.DynamicObject:
createInfo._auraEffectMask = BuildEffectMaskForOwner(createInfo.GetSpellInfo(), createInfo._auraEffectMask, createInfo.GetOwner());
Cypher.Assert(createInfo._auraEffectMask != 0);
aura = new DynObjAura(createInfo);
break;
default:
Cypher.Assert(false);
return null;
}
// scripts, etc.
if (aura.IsRemoved())
return null;
return aura;
}
#region Fields
List<AuraScript> m_loadedScripts = new();
SpellInfo m_spellInfo;
Difficulty m_castDifficulty;
ObjectGuid m_castId;
ObjectGuid m_casterGuid;
ObjectGuid m_castItemGuid;
uint m_castItemId;
int m_castItemLevel;
SpellCastVisual m_spellVisual;
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<SpellPowerRecord> m_periodicCosts = new();// 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
AuraEffect[] _effects;
Dictionary<ObjectGuid, AuraApplication> m_applications = new();
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<AuraApplication> _removedApplications = new();
#endregion
}
public class UnitAura : Aura
{
public UnitAura(AuraCreateInfo createInfo) : base(createInfo)
{
m_AuraDRGroup = DiminishingGroup.None;
LoadScripts();
_InitEffects(createInfo._auraEffectMask, createInfo.Caster, createInfo.BaseAmount);
GetUnitOwner()._AddAura(this, createInfo.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<Unit, uint> targets, Unit caster)
{
Unit refe = caster;
if (refe == null)
refe = GetUnitOwner();
// add non area aura targets
// static applications go through spell system first, so we assume they meet conditions
foreach (var targetPair in _staticApplications)
{
Unit target = Global.ObjAccessor.GetUnit(GetUnitOwner(), targetPair.Key);
if (target == null && targetPair.Key == GetUnitOwner().GetGUID())
target = GetUnitOwner();
if (target != null)
targets.Add(target, targetPair.Value);
}
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
if (!HasEffect(spellEffectInfo.EffectIndex))
continue;
// area auras only
if (spellEffectInfo.Effect == SpellEffectName.ApplyAura)
continue;
// skip area update if owner is not in world!
if (!GetUnitOwner().IsInWorld)
continue;
if (GetUnitOwner().HasUnitState(UnitState.Isolated))
continue;
List<Unit> units = new();
var condList = spellEffectInfo.ImplicitTargetConditions;
float radius = spellEffectInfo.CalcRadius(refe);
float extraSearchRadius = 0.0f;
SpellTargetCheckTypes selectionType = SpellTargetCheckTypes.Default;
switch (spellEffectInfo.Effect)
{
case SpellEffectName.ApplyAreaAuraParty:
case SpellEffectName.ApplyAreaAuraPartyNonrandom:
selectionType = SpellTargetCheckTypes.Party;
break;
case SpellEffectName.ApplyAreaAuraRaid:
selectionType = SpellTargetCheckTypes.Raid;
break;
case SpellEffectName.ApplyAreaAuraFriend:
selectionType = SpellTargetCheckTypes.Ally;
break;
case SpellEffectName.ApplyAreaAuraEnemy:
selectionType = SpellTargetCheckTypes.Enemy;
extraSearchRadius = radius > 0.0f ? SharedConst.ExtraCellSearchRadius : 0.0f;
break;
case SpellEffectName.ApplyAreaAuraPet:
if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(GetUnitOwner(), refe, condList))
units.Add(GetUnitOwner());
goto case SpellEffectName.ApplyAreaAuraOwner;
/* fallthrough */
case SpellEffectName.ApplyAreaAuraOwner:
{
Unit owner = GetUnitOwner().GetCharmerOrOwner();
if (owner != null)
if (GetUnitOwner().IsWithinDistInMap(owner, radius))
if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(owner, refe, condList))
units.Add(owner);
break;
}
case SpellEffectName.ApplyAuraOnPet:
{
Unit pet = Global.ObjAccessor.GetUnit(GetUnitOwner(), GetUnitOwner().GetPetGUID());
if (pet != null)
if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(pet, refe, condList))
units.Add(pet);
break;
}
case SpellEffectName.ApplyAreaAuraSummons:
{
if (condList == null || Global.ConditionMgr.IsObjectMeetToConditions(GetUnitOwner(), refe, condList))
units.Add(GetUnitOwner());
selectionType = SpellTargetCheckTypes.Summoned;
break;
}
}
if (selectionType != SpellTargetCheckTypes.Default)
{
WorldObjectSpellAreaTargetCheck check = new(radius, GetUnitOwner(), refe, GetUnitOwner(), GetSpellInfo(), selectionType, condList, SpellTargetObjectTypes.Unit);
UnitListSearcher searcher = new(GetUnitOwner(), units, check);
Cell.VisitAllObjects(GetUnitOwner(), searcher, radius + extraSearchRadius);
// by design WorldObjectSpellAreaTargetCheck allows not-in-world units (for spells) but for auras it is not acceptable
units.RemoveAll(unit => !unit.IsSelfOrInSameMap(GetUnitOwner()));
}
foreach (Unit unit in units)
{
if (!targets.ContainsKey(unit))
targets[unit] = 0;
targets[unit] |= 1u << (int)spellEffectInfo.EffectIndex;
}
}
}
public void AddStaticApplication(Unit target, uint effMask)
{
// only valid for non-area auras
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
if ((effMask & (1u << (int)spellEffectInfo.EffectIndex)) != 0 && !spellEffectInfo.IsEffect(SpellEffectName.ApplyAura))
effMask &= ~(1u << (int)spellEffectInfo.EffectIndex);
}
if (effMask == 0)
return;
if (!_staticApplications.ContainsKey(target.GetGUID()))
_staticApplications[target.GetGUID()] = 0;
_staticApplications[target.GetGUID()] |= effMask;
}
// 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
Dictionary<ObjectGuid, uint> _staticApplications = new(); // non-area auras
}
public class DynObjAura : Aura
{
public DynObjAura(AuraCreateInfo createInfo) : base(createInfo)
{
LoadScripts();
Cypher.Assert(GetDynobjOwner() != null);
Cypher.Assert(GetDynobjOwner().IsInWorld);
Cypher.Assert(GetDynobjOwner().GetMap() == createInfo.Caster.GetMap());
_InitEffects(createInfo._auraEffectMask, createInfo.Caster, createInfo.BaseAmount);
GetDynobjOwner().SetAura(this);
}
public override void Remove(AuraRemoveMode removeMode = AuraRemoveMode.Default)
{
if (IsRemoved())
return;
_Remove(removeMode);
}
public override void FillTargetMap(ref Dictionary<Unit, uint> targets, Unit caster)
{
Unit dynObjOwnerCaster = GetDynobjOwner().GetCaster();
float radius = GetDynobjOwner().GetRadius();
foreach (var spellEffectInfo in GetSpellInfo().GetEffects())
{
if (!HasEffect(spellEffectInfo.EffectIndex))
continue;
// we can't use effect type like area auras to determine check type, check targets
SpellTargetCheckTypes selectionType = spellEffectInfo.TargetA.GetCheckType();
if (spellEffectInfo.TargetB.GetReferenceType() == SpellTargetReferenceTypes.Dest)
selectionType = spellEffectInfo.TargetB.GetCheckType();
List<Unit> targetList = new();
var condList = spellEffectInfo.ImplicitTargetConditions;
WorldObjectSpellAreaTargetCheck check = new(radius, GetDynobjOwner(), dynObjOwnerCaster, dynObjOwnerCaster, GetSpellInfo(), selectionType, condList, SpellTargetObjectTypes.Unit);
UnitListSearcher searcher = new(GetDynobjOwner(), targetList, check);
Cell.VisitAllObjects(GetDynobjOwner(), searcher, radius);
// by design WorldObjectSpellAreaTargetCheck allows not-in-world units (for spells) but for auras it is not acceptable
targetList.RemoveAll(unit => !unit.IsSelfOrInSameMap(GetDynobjOwner()));
foreach (var unit in targetList)
{
if (!targets.ContainsKey(unit))
targets[unit] = 0;
targets[unit] |= 1u << (int)spellEffectInfo.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<AuraKey>
{
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 (ReferenceEquals(first, null) || ReferenceEquals(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];
}
public class AuraCreateInfo
{
public ObjectGuid CasterGUID;
public Unit Caster;
public int[] BaseAmount;
public ObjectGuid CastItemGUID;
public uint CastItemId = 0;
public int CastItemLevel = -1;
public bool IsRefresh;
public bool ResetPeriodicTimer = true;
internal ObjectGuid _castId;
internal SpellInfo _spellInfo;
internal Difficulty _castDifficulty;
internal uint _auraEffectMask;
internal WorldObject _owner;
internal uint _targetEffectMask;
public AuraCreateInfo(ObjectGuid castId, SpellInfo spellInfo, Difficulty castDifficulty, uint auraEffMask, WorldObject owner)
{
_castId = castId;
_spellInfo = spellInfo;
_castDifficulty = castDifficulty;
_auraEffectMask = auraEffMask;
_owner = owner;
Cypher.Assert(spellInfo != null);
Cypher.Assert(auraEffMask != 0);
Cypher.Assert(owner != null);
Cypher.Assert(auraEffMask <= SpellConst.MaxEffectMask);
}
public void SetCasterGUID(ObjectGuid guid) { CasterGUID = guid; }
public void SetCaster(Unit caster) { Caster = caster; }
public void SetBaseAmount(int[] bp) { BaseAmount = bp; }
public void SetCastItem(ObjectGuid guid, uint itemId, int itemLevel) { CastItemGUID = guid; CastItemId = itemId; CastItemLevel = itemLevel; }
public void SetPeriodicReset(bool reset) { ResetPeriodicTimer = reset; }
public void SetOwnerEffectMask(uint effMask) { _targetEffectMask = effMask; }
public void SetAuraEffectMask(uint effMask) { _auraEffectMask = effMask; }
public SpellInfo GetSpellInfo() { return _spellInfo; }
public uint GetAuraEffectMask() { return _auraEffectMask; }
public WorldObject GetOwner() { return _owner; }
}
}