Implementation of QAston proc system

This commit is contained in:
hondacrx
2017-08-14 08:12:31 -04:00
parent df8f6133b3
commit 9a039f35a2
23 changed files with 1083 additions and 1730 deletions
+112 -52
View File
@@ -742,10 +742,9 @@ namespace Game.Spells
m_timeCla = 1 * Time.InMilliseconds;
}
void RefreshTimers()
void RefreshTimers(bool resetPeriodicTimer)
{
m_maxDuration = CalcMaxDuration();
bool resetPeriodic = true;
if (m_spellInfo.HasAttribute(SpellAttr8.DontResetPeriodicTimer))
{
int minPeriod = m_maxDuration;
@@ -764,15 +763,18 @@ namespace Game.Spells
if (GetDuration() <= minPeriod)
{
m_maxDuration += GetDuration();
resetPeriodic = false;
resetPeriodicTimer = false;
}
}
RefreshDuration();
Unit caster = GetCaster();
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
if (HasEffect(i))
GetEffect(i).CalculatePeriodic(caster, resetPeriodic, false);
{
AuraEffect aurEff = GetEffect(i);
if (aurEff != null)
aurEff.CalculatePeriodic(caster, resetPeriodicTimer, false);
}
}
public void SetCharges(int charges)
@@ -853,7 +855,7 @@ namespace Game.Spells
SetNeedClientUpdateForTargets();
}
public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default)
public bool ModStackAmount(int num, AuraRemoveMode removeMode = AuraRemoveMode.Default, bool resetPeriodicTimer = true)
{
int stackAmount = m_stackAmount + num;
@@ -881,22 +883,10 @@ namespace Game.Spells
if (refresh)
{
RefreshSpellMods();
RefreshTimers();
RefreshTimers(resetPeriodicTimer);
// 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;
@@ -1668,7 +1658,7 @@ namespace Game.Spells
m_procCooldown = cooldownEnd;
}
void PrepareProcToTrigger(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now)
public void PrepareProcToTrigger(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now)
{
bool prepare = CallScriptPrepareProcHandlers(aurApp, eventInfo);
if (!prepare)
@@ -1687,44 +1677,81 @@ namespace Game.Spells
// cooldowns should be added to the whole aura (see 51698 area aura)
AddProcCooldown(now + TimeSpan.FromMilliseconds(procEntry.Cooldown));
SetLastProcSuccessTime(now);
}
bool IsProcTriggeredOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo, DateTime now)
public uint 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;
return 0;
// check if we have charges to proc with
if (IsUsingCharges() && GetCharges() == 0)
return false;
if (IsUsingCharges())
{
if (GetCharges() == 0)
return 0;
if (procEntry.AttributesMask.HasAnyFlag(ProcAttributes.ReqSpellmod))
{
Spell spell = eventInfo.GetProcSpell();
if (spell != null)
if (!spell.m_appliedMods.Contains(this))
return 0;
}
}
// check proc cooldown
if (IsProcOnCooldown(now))
return false;
/// @todo
// something about triggered spells triggering, and add extra attack effect
return 0;
// do checks against db data
if (!Global.SpellMgr.CanSpellTriggerProcOnEvent(procEntry, eventInfo))
return false;
return 0;
// check if aura can proc when spell is triggered
if (!procEntry.AttributesMask.HasAnyFlag(ProcAttributes.TriggeredCanProc))
{
Spell spell = eventInfo.GetProcSpell();
if (spell)
if (spell.IsTriggered())
if (!GetSpellInfo().HasAttribute(SpellAttr3.CanProcWithTriggered))
return 0;
}
// do checks using conditions table
if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SpellProc, GetId(), eventInfo.GetActor(), eventInfo.GetActionTarget()))
return false;
return 0;
// AuraScript Hook
bool check = CallScriptCheckProcHandlers(aurApp, eventInfo);
if (!check)
return false;
return 0;
// At least one effect has to pass checks to proc aura
uint procEffectMask = 0;
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
if (aurApp.HasEffect(i))
if (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)
// Aura added by spell can't trigger from self (prevent drop charges/do triggers)
// But except periodic and kill triggers (can triggered from self)
SpellInfo spellInfo = eventInfo.GetSpellInfo();
if (spellInfo != null)
if (spellInfo.Id == GetId() && !eventInfo.GetTypeMask().HasAnyFlag(ProcFlags.TakenPeriodic | ProcFlags.Kill))
return 0;
// Check if current equipment meets aura requirements
// do that only for passive spells
/// @todo this needs to be unified for all kinds of auras
@@ -1734,11 +1761,12 @@ namespace Game.Spells
if (GetSpellInfo().EquippedItemClass == ItemClass.Weapon)
{
if (target.ToPlayer().IsInFeralForm())
return false;
return 0;
if (eventInfo.GetDamageInfo() != null)
DamageInfo damageInfo = eventInfo.GetDamageInfo();
if (damageInfo != null)
{
WeaponAttackType attType = eventInfo.GetDamageInfo().GetAttackType();
WeaponAttackType attType = damageInfo.GetAttackType();
Item item = null;
if (attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.RangedAttack)
item = target.ToPlayer().GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand);
@@ -1746,7 +1774,7 @@ namespace Game.Spells
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;
return 0;
}
}
else if (GetSpellInfo().EquippedItemClass == ItemClass.Armor)
@@ -1754,11 +1782,16 @@ namespace Game.Spells
// 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 0;
}
}
return RandomHelper.randChance(CalcProcChance(procEntry, eventInfo));
SetLastProcAttemptTime(now);
if (RandomHelper.randChance(CalcProcChance(procEntry, eventInfo)))
return procEffectMask;
return 0;
}
float CalcProcChance(SpellProcEntry procEntry, ProcEventInfo eventInfo)
@@ -1775,6 +1808,10 @@ namespace Game.Spells
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)
@@ -1783,18 +1820,23 @@ namespace Game.Spells
return chance;
}
void TriggerProcOnEvent(AuraApplication aurApp, ProcEventInfo eventInfo)
public void TriggerProcOnEvent(uint procEffectMask, AuraApplication aurApp, ProcEventInfo eventInfo)
{
CallScriptProcHandlers(aurApp, eventInfo);
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
bool prevented = CallScriptProcHandlers(aurApp, eventInfo);
if (!prevented)
{
if (aurApp.HasEffect(i))
// OnEffectProc / AfterEffectProc hooks handled in AuraEffect.HandleProc()
GetEffect(i).HandleProc(aurApp, eventInfo);
}
for (byte i = 0; i < SpellConst.MaxEffects; ++i)
{
if (!Convert.ToBoolean(procEffectMask & (1 << i)))
continue;
CallScriptAfterProcHandlers(aurApp, eventInfo);
// OnEffectProc / AfterEffectProc hooks handled in AuraEffect.HandleProc()
if (aurApp.HasEffect(i))
GetEffect(i).HandleProc(aurApp, eventInfo);
}
CallScriptAfterProcHandlers(aurApp, eventInfo);
}
// Remove aura if we've used last charge to proc
if (IsUsingCharges() && GetCharges() == 0)
@@ -2157,6 +2199,23 @@ namespace Game.Spells
}
}
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;
@@ -2308,7 +2367,7 @@ namespace Game.Spells
public void SetLastProcSuccessTime(DateTime lastProcSuccessTime) { m_lastProcSuccessTime = lastProcSuccessTime; }
//Static Methods
public static uint BuildEffectMaskForOwner(SpellInfo spellProto, uint avalibleEffectMask, WorldObject owner)
public static uint BuildEffectMaskForOwner(SpellInfo spellProto, uint availableEffectMask, WorldObject owner)
{
Contract.Assert(spellProto != null);
Contract.Assert(owner != null);
@@ -2333,24 +2392,25 @@ namespace Game.Spells
default:
break;
}
return (effMask & avalibleEffectMask);
return (effMask & availableEffectMask);
}
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)
public static Aura TryRefreshStackOrCreate(SpellInfo spellproto, ObjectGuid castId, uint tryEffMask, WorldObject owner, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), bool resetPeriodicTimer = true, int castItemLevel = -1)
{
bool throwway;
return TryRefreshStackOrCreate(spellproto, castId, tryEffMask, owner, caster, out throwway, baseAmount, castItem, casterGUID);
return TryRefreshStackOrCreate(spellproto, castId, tryEffMask, owner, caster, out throwway, baseAmount, castItem, casterGUID, resetPeriodicTimer, castItemLevel);
}
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)
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), bool resetPeriodicTimer = true, 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);
Aura foundAura = owner.ToUnit()._TryStackingOrRefreshingExistingAura(spellproto, effMask, caster, baseAmount, castItem, casterGUID, resetPeriodicTimer, castItemLevel);
if (foundAura != null)
{
// we've here aura, which script triggered removal after modding stack amount
+129 -156
View File
@@ -232,9 +232,9 @@ namespace Game.Spells
else // aura just created or reapplied
{
m_tickNumber = 0;
// reset periodic timer on aura create or on reapply when aura isn't dot
// possibly we should not reset periodic timers only when aura is triggered by proc
// or maybe there's a spell attribute somewhere
// reset periodic timer on aura create or reapply
// we don't reset periodic timers when aura is triggered by proc
if (resetPeriodicTimer)
{
m_periodicTimer = 0;
@@ -258,7 +258,6 @@ namespace Game.Spells
m_spellmod.type = GetAuraType() == AuraType.AddPctModifier ? SpellModType.Pct : SpellModType.Flat;
m_spellmod.spellId = GetId();
m_spellmod.mask = GetSpellEffectInfo().SpellClassMask;
m_spellmod.charges = GetBase().GetCharges();
}
m_spellmod.value = GetAmount();
break;
@@ -655,6 +654,61 @@ namespace Game.Spells
}
}
public bool CheckEffectProc(AuraApplication aurApp, ProcEventInfo eventInfo)
{
bool result = GetBase().CallScriptCheckEffectProcHandlers(this, aurApp, eventInfo);
if (!result)
return false;
SpellInfo spellInfo = eventInfo.GetSpellInfo();
switch (GetAuraType())
{
case AuraType.MechanicImmunity:
case AuraType.ModMechanicResistance:
// compare mechanic
if (spellInfo == null || (int)spellInfo.Mechanic != GetMiscValue())
result = false;
break;
case AuraType.ModCastingSpeedNotStack:
// skip melee hits and instant cast spells
if (spellInfo == null || spellInfo.CalcCastTime() == 0)
result = false;
break;
case AuraType.ModSpellDamageFromCaster:
// Compare casters
if (GetCasterGUID() != eventInfo.GetActor().GetGUID())
result = false;
break;
case AuraType.ModPowerCostSchool:
case AuraType.ModPowerCostSchoolPct:
{
// Skip melee hits and spells with wrong school or zero cost
if (spellInfo == null || !Convert.ToBoolean((int)spellInfo.GetSchoolMask() & GetMiscValue()) // School Check
|| !eventInfo.GetProcSpell())
{
result = false;
break;
}
// Costs Check
var costs = eventInfo.GetProcSpell().GetPowerCost();
var m = costs.Find(cost => { return cost.Amount > 0; });
if (m == null)
result = false;
break;
}
case AuraType.ReflectSpellsSchool:
// Skip melee hits and spells with wrong school
if (spellInfo == null || !Convert.ToBoolean((int)spellInfo.GetSchoolMask() & GetMiscValue()))
result = false;
break;
default:
break;
}
return result;
}
public void HandleProc(AuraApplication aurApp, ProcEventInfo eventInfo)
{
bool prevented = GetBase().CallScriptEffectProcHandlers(this, aurApp, eventInfo);
@@ -663,6 +717,16 @@ namespace Game.Spells
switch (GetAuraType())
{
// CC Auras which use their amount to drop
// Are there any more auras which need this?
case AuraType.ModConfuse:
case AuraType.ModFear:
case AuraType.ModStun:
case AuraType.ModRoot:
case AuraType.Transform:
HandleBreakableCCAuraProc(aurApp, eventInfo);
break;
case AuraType.Dummy:
case AuraType.ProcTriggerSpell:
HandleProcTriggerSpellAuraProc(aurApp, eventInfo);
break;
@@ -672,16 +736,6 @@ namespace Game.Spells
case AuraType.ProcTriggerDamage:
HandleProcTriggerDamageAuraProc(aurApp, eventInfo);
break;
case AuraType.RaidProcFromCharge:
HandleRaidProcFromChargeAuraProc(aurApp, eventInfo);
break;
case AuraType.RaidProcFromChargeWithValue:
HandleRaidProcFromChargeWithValueAuraProc(aurApp, eventInfo);
break;
case AuraType.ProcOnPowerAmount:
case AuraType.ProcOnPowerAmount2:
HandleProcTriggerSpellOnPowerAmountAuraProc(aurApp, eventInfo);
break;
default:
break;
}
@@ -829,7 +883,6 @@ namespace Game.Spells
public Unit GetCaster() { return auraBase.GetCaster(); }
public ObjectGuid GetCasterGUID() { return auraBase.GetCasterGUID(); }
public Aura GetBase() { return auraBase; }
public SpellModifier GetSpellModifier() { return m_spellmod; }
public SpellInfo GetSpellInfo() { return m_spellInfo; }
public uint GetId() { return m_spellInfo.Id; }
@@ -5131,7 +5184,10 @@ namespace Game.Spells
if (caster != null)
{
uint heal = (uint)caster.CountPctFromMaxHealth(10);
caster.HealBySpell(target, auraSpellInfo, heal);
HealInfo healInfo = new HealInfo(caster, target, heal, auraSpellInfo, auraSpellInfo.GetSchoolMask());
caster.HealBySpell(healInfo);
/// @todo: should proc other auras?
int mana = caster.GetMaxPower(PowerType.Mana);
if (mana != 0)
{
@@ -5535,7 +5591,7 @@ namespace Game.Spells
// Set trigger flag
ProcFlags procAttacker = ProcFlags.DonePeriodic;
ProcFlags procVictim = ProcFlags.TakenPeriodic;
ProcFlagsExLegacy procEx = (crit ? ProcFlagsExLegacy.CriticalHit : ProcFlagsExLegacy.NormalHit) | ProcFlagsExLegacy.InternalDot;
ProcFlagsHit hitMask = crit ? ProcFlagsHit.Critical : ProcFlagsHit.Normal;
damage = (damage <= absorb + resist) ? 0 : (damage - absorb - resist);
if (damage != 0)
procVictim |= ProcFlags.TakenDamage;
@@ -5546,7 +5602,8 @@ namespace Game.Spells
SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, damage, overkill, absorb, resist, 0.0f, crit);
caster.ProcDamageAndSpell(target, procAttacker, procVictim, procEx, damage, WeaponAttackType.BaseAttack, GetSpellInfo());
DamageInfo damageInfo = new DamageInfo(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetSchoolMask(), DamageEffectType.DOT, WeaponAttackType.BaseAttack);
caster.ProcSkillsAndAuras(target, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.None, hitMask, null, damageInfo, null);
caster.DealDamage(target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), true);
target.SendPeriodicAuraLog(pInfo);
@@ -5624,12 +5681,17 @@ namespace Game.Spells
// Set trigger flag
ProcFlags procAttacker = ProcFlags.DonePeriodic;
ProcFlags procVictim = ProcFlags.TakenPeriodic;
ProcFlagsExLegacy procEx = (crit ? ProcFlagsExLegacy.CriticalHit : ProcFlagsExLegacy.NormalHit) | ProcFlagsExLegacy.InternalDot;
ProcFlagsHit hitMask = crit ? ProcFlagsHit.Critical : ProcFlagsHit.Normal;
damage = (damage <= absorb + resist) ? 0 : (damage - absorb - resist);
if (damage != 0)
procVictim |= ProcFlags.TakenDamage;
if (caster.IsAlive())
caster.ProcDamageAndSpell(target, procAttacker, procVictim, procEx, damage, WeaponAttackType.BaseAttack, GetSpellInfo());
{
DamageInfo damageInfo = new DamageInfo(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetSchoolMask(), DamageEffectType.DOT, WeaponAttackType.BaseAttack);
caster.ProcSkillsAndAuras(target, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.None, hitMask, null, damageInfo, null);
}
int new_damage = (int)caster.DealDamage(target, damage, cleanDamage, DamageEffectType.DOT, GetSpellInfo().GetSchoolMask(), GetSpellInfo(), false);
if (caster.IsAlive())
{
@@ -5638,8 +5700,11 @@ namespace Game.Spells
uint heal = (caster.SpellHealingBonusDone(caster, GetSpellInfo(), (uint)(new_damage * gainMultiplier), DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()));
heal = (caster.SpellHealingBonusTaken(caster, GetSpellInfo(), heal, DamageEffectType.DOT, GetSpellEffectInfo(), GetBase().GetStackAmount()));
int gain = caster.HealBySpell(caster, GetSpellInfo(), heal);
caster.getHostileRefManager().threatAssist(caster, gain * 0.5f, GetSpellInfo());
HealInfo healInfo = new HealInfo(caster, caster, heal, GetSpellInfo(), GetSpellInfo().GetSchoolMask());
caster.HealBySpell(healInfo);
caster.getHostileRefManager().threatAssist(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo());
caster.ProcSkillsAndAuras(caster, ProcFlags.DonePeriodic, ProcFlags.TakenPeriodic, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, hitMask, null, null, healInfo);
}
caster.SendSpellNonMeleeDamageLog(log);
@@ -5670,7 +5735,9 @@ namespace Game.Spells
damage = (uint)(damage * gainMultiplier);
caster.HealBySpell(target, GetSpellInfo(), damage);
HealInfo healInfo = new HealInfo(caster, target, damage, GetSpellInfo(), GetSpellInfo().GetSchoolMask());
caster.HealBySpell(healInfo);
caster.ProcSkillsAndAuras(target, ProcFlags.DonePeriodic, ProcFlags.TakenPeriodic, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, ProcFlagsHit.Normal, null, null, healInfo);
}
void HandlePeriodicHealAurasTick(Unit target, Unit caster)
@@ -5750,22 +5817,23 @@ namespace Game.Spells
Log.outDebug(LogFilter.Spells, "PeriodicTick: {0} (TypeId: {1}) heal of {2} (TypeId: {3}) for {4} health inflicted by {5}",
GetCasterGUID().ToString(), GetCaster().GetTypeId(), target.GetGUID().ToString(), target.GetTypeId(), damage, GetId());
uint absorb = 0;
uint heal = (uint)damage;
caster.CalcHealAbsorb(target, GetSpellInfo(), ref heal, ref absorb);
int gain = caster.DealHeal(target, heal);
SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, heal, (int)(heal - gain), absorb, 0, 0.0f, crit);
HealInfo healInfo = new HealInfo(caster, target, heal, GetSpellInfo(), GetSpellInfo().GetSchoolMask());
caster.CalcHealAbsorb(healInfo);
caster.DealHeal(healInfo);
SpellPeriodicAuraLogInfo pInfo = new SpellPeriodicAuraLogInfo(this, heal, (int)(heal - healInfo.GetEffectiveHeal()), healInfo.GetAbsorb(), 0, 0.0f, crit);
target.SendPeriodicAuraLog(pInfo);
target.getHostileRefManager().threatAssist(caster, gain * 0.5f, GetSpellInfo());
target.getHostileRefManager().threatAssist(caster, healInfo.GetEffectiveHeal() * 0.5f, GetSpellInfo());
ProcFlags procAttacker = ProcFlags.DonePeriodic;
ProcFlags procVictim = ProcFlags.TakenPeriodic;
ProcFlagsExLegacy procEx = (crit ? ProcFlagsExLegacy.CriticalHit : ProcFlagsExLegacy.NormalHit) | ProcFlagsExLegacy.InternalHot;
ProcFlagsHit hitMask = crit ? ProcFlagsHit.Critical : ProcFlagsHit.Normal;
// ignore item heals
if (GetBase().GetCastItemGUID().IsEmpty())
caster.ProcDamageAndSpell(target, procAttacker, procVictim, procEx, (uint)damage, WeaponAttackType.BaseAttack, GetSpellInfo());
caster.ProcSkillsAndAuras(target, procAttacker, procVictim, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.None, hitMask, null, null, healInfo);
}
void HandlePeriodicManaLeechAuraTick(Unit target, Unit caster)
@@ -5912,16 +5980,43 @@ namespace Game.Spells
// Set trigger flag
ProcFlags procAttacker = ProcFlags.DonePeriodic;
ProcFlags procVictim = ProcFlags.TakenPeriodic;
ProcFlagsExLegacy procEx = Unit.createProcExtendMask(damageInfo, SpellMissInfo.None) | ProcFlagsExLegacy.InternalDot;
ProcFlagsHit hitMask = Unit.createProcHitMask(damageInfo, SpellMissInfo.None);
ProcFlagsSpellType spellTypeMask = ProcFlagsSpellType.NoDmgHeal;
if (damageInfo.damage != 0)
{
procVictim |= ProcFlags.TakenDamage;
spellTypeMask |= ProcFlagsSpellType.Damage;
}
caster.ProcDamageAndSpell(damageInfo.target, procAttacker, procVictim, procEx, damageInfo.damage, WeaponAttackType.BaseAttack, spellProto);
DamageInfo dotDamageInfo = new DamageInfo(damageInfo, DamageEffectType.DOT, WeaponAttackType.BaseAttack, hitMask);
caster.ProcSkillsAndAuras(target, procAttacker, procVictim, spellTypeMask, ProcFlagsSpellPhase.None, hitMask, null, dotDamageInfo, null);
caster.DealSpellDamage(damageInfo, true);
caster.SendSpellNonMeleeDamageLog(damageInfo);
}
void HandleBreakableCCAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo)
{
DamageInfo damageInfo = eventInfo.GetDamageInfo();
if (damageInfo == null)
return;
// aura own damage at apply won't break CC
if (eventInfo.GetSpellPhaseMask().HasAnyFlag(ProcFlagsSpellPhase.Cast))
{
SpellInfo spellInfo = eventInfo.GetSpellInfo();
if (spellInfo != null)
if (spellInfo == GetSpellInfo())
return;
}
int damageLeft = GetAmount();
if (damageLeft < damageInfo.GetDamage())
aurApp.GetTarget().RemoveAura(aurApp);
else
ChangeAmount((int)(damageLeft - damageInfo.GetDamage()));
}
void HandleProcTriggerSpellAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo)
{
Unit triggerCaster = aurApp.GetTarget();
@@ -5935,7 +6030,7 @@ namespace Game.Spells
triggerCaster.CastSpell(triggerTarget, triggeredSpellInfo, true, null, this);
}
else
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId());
Log.outError(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId());
}
void HandleProcTriggerSpellWithValueAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo)
@@ -5952,7 +6047,7 @@ namespace Game.Spells
triggerCaster.CastCustomSpell(triggerTarget, triggerSpellId, basepoints0, 0, 0, true, null, this);
}
else
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellWithValueAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId());
Log.outError(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellWithValueAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId());
}
public void HandleProcTriggerDamageAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo)
@@ -5968,128 +6063,6 @@ namespace Game.Spells
target.SendSpellNonMeleeDamageLog(damageInfo);
}
void HandleRaidProcFromChargeAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo)
{
Unit target = aurApp.GetTarget();
uint triggerSpellId;
switch (GetId())
{
case 57949: // Shiver
triggerSpellId = 57952;
//animationSpellId = 57951; dummy effects for jump spell have unknown use (see also 41637)
break;
case 59978: // Shiver
triggerSpellId = 59979;
break;
case 43593: // Cold Stare
triggerSpellId = 43594;
break;
default:
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeAuraProc: received not handled spell: {0}", GetId());
return;
}
int jumps = GetBase().GetCharges();
// current aura expire on proc finish
GetBase().SetCharges(0);
GetBase().SetUsingCharges(true);
// next target selection
if (jumps > 0)
{
Unit caster = GetCaster();
if (caster != null)
{
float radius = GetSpellEffectInfo().CalcRadius(caster);
Unit triggerTarget = target.GetNextRandomRaidMemberOrPet(radius);
if (triggerTarget != null)
{
target.CastSpell(triggerTarget, GetSpellInfo(), true, null, this, GetCasterGUID());
Aura aura = triggerTarget.GetAura(GetId(), GetCasterGUID());
if (aura != null)
aura.SetCharges((byte)jumps);
}
}
}
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeAuraProc: Triggering spell {0} from aura {1} proc", triggerSpellId, GetId());
target.CastSpell(target, triggerSpellId, true, null, this, GetCasterGUID());
}
void HandleRaidProcFromChargeWithValueAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo)
{
Unit target = aurApp.GetTarget();
// Currently only Prayer of Mending
if (!(GetSpellInfo().SpellFamilyName == SpellFamilyNames.Priest) && GetSpellInfo().SpellFamilyFlags[1].HasAnyFlag<uint>(0x20))
{
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeWithValueAuraProc: received not handled spell: {0}", GetId());
return;
}
uint triggerSpellId = 33110;
int value = GetAmount();
int jumps = GetBase().GetCharges();
// current aura expire on proc finish
GetBase().SetCharges(0);
GetBase().SetUsingCharges(true);
// next target selection
if (jumps > 0)
{
Unit caster = GetCaster();
if (caster != null)
{
float radius = GetSpellEffectInfo().CalcRadius(caster);
Unit triggerTarget = target.GetNextRandomRaidMemberOrPet(radius);
if (triggerTarget != null)
{
target.CastCustomSpell(triggerTarget, GetId(), value, 0, 0, true, null, this, GetCasterGUID());
Aura aura = triggerTarget.GetAura(GetId(), GetCasterGUID());
if (aura != null)
aura.SetCharges((byte)jumps);
}
}
}
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleRaidProcFromChargeWithValueAuraProc: Triggering spell {0} from aura {1} proc", triggerSpellId, GetId());
target.CastCustomSpell(target, triggerSpellId, value, 0, 0, true, null, this, GetCasterGUID());
}
public void HandleProcTriggerSpellOnPowerAmountAuraProc(AuraApplication aurApp, ProcEventInfo eventInfo)
{
// Power amount required to proc the spell
int powerAmountRequired = GetAmount();
// Power type required to proc
PowerType powerRequired = (PowerType)GetSpellInfo().GetEffect(GetEffIndex()).MiscValue;
if (powerRequired == 0 || powerAmountRequired == 0)
{
Log.outError(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellOnPowerAmountAuraProc: Spell {0} have 0 PowerAmountRequired in EffectAmount[{1}] or 0 PowerRequired in EffectMiscValue", GetId(), GetEffIndex());
return /*false*/;
}
Unit triggerCaster = aurApp.GetTarget();
Unit triggerTarget = eventInfo.GetProcTarget();
if (triggerCaster.GetPower(powerRequired) != powerAmountRequired)
return /*false*/;
uint triggerSpellId = GetSpellInfo().GetEffect(GetEffIndex()).TriggerSpell;
SpellInfo triggeredSpellInfo = Global.SpellMgr.GetSpellInfo(triggerSpellId);
if (triggeredSpellInfo != null)
{
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellOnPowerAmountAuraProc: Triggering spell {0} from aura {1} proc", triggeredSpellInfo.Id, GetId());
triggerCaster.CastSpell(triggerTarget, triggeredSpellInfo, true, null, this);
}
else
Log.outDebug(LogFilter.Spells, "AuraEffect.HandleProcTriggerSpellOnPowerAmountAuraProc: Could not trigger spell {0} from aura {1} proc, because the spell does not have an entry in Spell.dbc.", triggerSpellId, GetId());
}
[AuraEffectHandler(AuraType.ForceWeather)]
void HandleAuraForceWeather(AuraApplication aurApp, AuraEffectHandleModes mode, bool apply)
{
+124 -97
View File
@@ -1504,12 +1504,11 @@ namespace Game.Spells
return searcher.GetTarget();
}
void prepareDataForTriggerSystem(AuraEffect triggeredByAura)
void prepareDataForTriggerSystem()
{
//==========================================================================================
// Now fill data for trigger system, need know:
// can spell trigger another or not (m_canTrigger)
// Create base triggers flags for Attacker and Victim (m_procAttacker, m_procVictim and m_procEx)
// Create base triggers flags for Attacker and Victim (m_procAttacker, m_procVictim and m_hitMask)
//==========================================================================================
m_procVictim = m_procAttacker = 0;
@@ -1549,7 +1548,7 @@ namespace Game.Spells
// For other spells trigger procflags are set in Spell.DoAllEffectOnTarget
// Because spell positivity is dependant on target
}
m_procEx = ProcFlagsExLegacy.None;
m_hitMask = ProcFlagsHit.None;
// Hunter trap spells - activation proc for Lock and Load, Entrapment and Misdirection
if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Hunter &&
@@ -1559,28 +1558,12 @@ namespace Game.Spells
m_procAttacker |= ProcFlags.DoneTrapActivation;
//Effects which are result of aura proc from triggered spell cannot proc to prevent chain proc of these spells
// Hellfire Effect - trigger as DOT
if (m_spellInfo.SpellFamilyName == SpellFamilyNames.Warlock && m_spellInfo.SpellFamilyFlags[0].HasAnyFlag(0x00000040u))
{
m_procAttacker = ProcFlags.DonePeriodic;
m_procVictim = ProcFlags.TakenPeriodic;
}
// Ranged autorepeat attack is set as triggered spell - ignore it
if (!Convert.ToBoolean(m_procAttacker & ProcFlags.DoneRangedAutoAttack))
{
if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents) &&
(m_spellInfo.HasAttribute(SpellAttr2.TriggeredCanTriggerProc) ||
m_spellInfo.HasAttribute(SpellAttr3.TriggeredCanTriggerProc2)))
m_procEx |= ProcFlagsExLegacy.InternalCantProc;
else if (_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DisallowProcEvents))
m_procEx |= ProcFlagsExLegacy.InternalTriggered;
}
// Totem casts require spellfamilymask defined in spell_proc_event to proc
if (m_originalCaster != null && m_caster != m_originalCaster && m_caster.IsTypeId(TypeId.Unit) && m_caster.IsTotem() && m_caster.IsControlledByPlayer())
m_procEx |= ProcFlagsExLegacy.InternalReqFamily;
}
public void CleanupTargetList()
@@ -1687,10 +1670,10 @@ namespace Game.Spells
if (targetInfo.missCondition == SpellMissInfo.Reflect)
{
// Calculate reflected spell result on caster
targetInfo.reflectResult = m_caster.SpellHitResult(m_caster, m_spellInfo, m_canReflect);
targetInfo.reflectResult = m_caster.SpellHitResult(m_caster, m_spellInfo, false);
if (targetInfo.reflectResult == SpellMissInfo.Reflect) // Impossible reflect again, so simply deflect spell
targetInfo.reflectResult = SpellMissInfo.Parry;
// Proc spell reflect aura when missile hits the original target
target.m_Events.AddEvent(new ProcReflectDelayed(target, m_originalCasterGUID), target.m_Events.CalculateTime(targetInfo.timeDelay));
// Increase time interval for reflected spells by 1.5
targetInfo.timeDelay += targetInfo.timeDelay >> 1;
@@ -1858,13 +1841,12 @@ namespace Game.Spells
// Fill base trigger info
ProcFlags procAttacker = m_procAttacker;
ProcFlags procVictim = m_procVictim;
ProcFlagsExLegacy procEx = m_procEx;
ProcFlagsHit hitMask = m_hitMask;
m_spellAura = null;
//Spells with this flag cannot trigger if effect is cast on self
bool canEffectTrigger = !m_spellInfo.HasAttribute(SpellAttr3.CantTriggerProc) && unitTarget.CanProc()
&& (CanExecuteTriggersOnHit(mask) || missInfo == SpellMissInfo.Immune || missInfo == SpellMissInfo.Immune2);
bool canEffectTrigger = !m_spellInfo.HasAttribute(SpellAttr3.CantTriggerProc) && (CanExecuteTriggersOnHit(mask) || missInfo == SpellMissInfo.Immune || missInfo == SpellMissInfo.Immune2);
Unit spellHitTarget = null;
@@ -1901,16 +1883,8 @@ namespace Game.Spells
}
// Do not take combo points on dodge and miss
if (missInfo != SpellMissInfo.None && m_needComboPoints &&
m_targets.GetUnitTargetGUID() == target.targetGUID)
{
if (missInfo != SpellMissInfo.None && m_needComboPoints && m_targets.GetUnitTargetGUID() == target.targetGUID)
m_needComboPoints = false;
// Restore spell mods for a miss/dodge/parry Cold Blood
/// @todo check how broad this rule should be
if (m_caster.IsTypeId(TypeId.Player) && (missInfo == SpellMissInfo.Miss ||
missInfo == SpellMissInfo.Dodge || missInfo == SpellMissInfo.Parry))
m_caster.ToPlayer().RestoreSpellMods(this, 14177);
}
// Trigger info was not filled in spell.preparedatafortriggersystem - we do it now
if (canEffectTrigger && procAttacker == 0 && procVictim == 0)
@@ -1959,19 +1933,20 @@ namespace Game.Spells
uint addhealth = (uint)m_healing;
if (crit)
{
procEx |= ProcFlagsExLegacy.CriticalHit;
hitMask |= ProcFlagsHit.Critical;
addhealth = (uint)caster.SpellCriticalHealingBonus(m_spellInfo, (int)addhealth, null);
}
else
procEx |= ProcFlagsExLegacy.NormalHit;
hitMask |= ProcFlagsHit.Normal;
int gain = caster.HealBySpell(unitTarget, m_spellInfo, addhealth, crit);
unitTarget.getHostileRefManager().threatAssist(caster, gain * 0.5f, m_spellInfo);
m_healing = gain;
HealInfo healInfo = new HealInfo(caster, unitTarget, addhealth, m_spellInfo, m_spellInfo.GetSchoolMask());
caster.HealBySpell(healInfo, crit);
unitTarget.getHostileRefManager().threatAssist(caster, healInfo.GetEffectiveHeal() * 0.5f, m_spellInfo);
m_healing = (int)healInfo.GetEffectiveHeal();
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger && missInfo != SpellMissInfo.Reflect)
caster.ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, addhealth, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
// Do triggers for unit
if (canEffectTrigger)
caster.ProcSkillsAndAuras(unitTarget, procAttacker, procVictim, ProcFlagsSpellType.Heal, ProcFlagsSpellPhase.Hit, hitMask, this, null, healInfo);
}
// Do damage and triggers
else if (m_damage > 0)
@@ -1983,16 +1958,18 @@ namespace Game.Spells
caster.CalculateSpellDamageTaken(damageInfo, m_damage, m_spellInfo, m_attackType, target.crit);
caster.DealDamageMods(damageInfo.target, ref damageInfo.damage, ref damageInfo.absorb);
procEx |= Unit.createProcExtendMask(damageInfo, missInfo);
hitMask |= Unit.createProcHitMask(damageInfo, missInfo);
procVictim |= ProcFlags.TakenDamage;
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger && missInfo != SpellMissInfo.Reflect)
// Do triggers for unit
if (canEffectTrigger)
{
caster.ProcDamageAndSpell(unitTarget, procAttacker, procVictim, procEx, damageInfo.damage, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
if (caster.IsTypeId(TypeId.Player) && !m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget) &&
(m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged))
caster.ToPlayer().CastItemCombatSpell(unitTarget, m_attackType, procVictim, procEx);
DamageInfo spellDamageInfo = new DamageInfo(damageInfo, DamageEffectType.SpellDirect, m_attackType, hitMask);
caster.ProcSkillsAndAuras(unitTarget, procAttacker, procVictim, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.Hit, hitMask, this, spellDamageInfo, null);
if (caster.IsPlayer() && !m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget) &&
(m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged))
caster.ToPlayer().CastItemCombatSpell(spellDamageInfo);
}
m_damage = (int)damageInfo.damage;
@@ -2007,10 +1984,17 @@ namespace Game.Spells
{
// Fill base damage struct (unitTarget - is real spell target)
SpellNonMeleeDamage damageInfo = new SpellNonMeleeDamage(caster, unitTarget, m_spellInfo.Id, m_SpellVisual, m_spellSchoolMask);
procEx |= Unit.createProcExtendMask(damageInfo, missInfo);
// Do triggers for unit (reflect triggers passed on hit phase for correct drop charge)
if (canEffectTrigger && missInfo != SpellMissInfo.Reflect)
caster.ProcDamageAndSpell(unit, procAttacker, procVictim, procEx, 0, m_attackType, m_spellInfo, m_triggeredByAuraSpell);
hitMask |= Unit.createProcHitMask(damageInfo, missInfo);
// Do triggers for unit
if (canEffectTrigger)
{
DamageInfo spellNoDamageInfo = new DamageInfo(damageInfo, DamageEffectType.NoDamage, m_attackType, hitMask);
caster.ProcSkillsAndAuras(unitTarget, procAttacker, procVictim, ProcFlagsSpellType.NoDmgHeal, ProcFlagsSpellPhase.Hit, hitMask, this, spellNoDamageInfo, null);
if (caster.IsPlayer() && !m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget) &&
(m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged))
caster.ToPlayer().CastItemCombatSpell(spellNoDamageInfo);
}
// Failed Pickpocket, reveal rogue
if (missInfo == SpellMissInfo.Resist && m_spellInfo.HasAttribute(SpellCustomAttributes.PickPocket) && unitTarget.IsTypeId(TypeId.Unit))
@@ -2168,8 +2152,9 @@ namespace Game.Spells
if (m_originalCaster != null)
{
bool refresh = false;
bool resetPeriodicTimer = !_triggeredCastFlags.HasAnyFlag(TriggerCastFlags.DontResetPeriodicTimer);
m_spellAura = Aura.TryRefreshStackOrCreate(aurSpellInfo, m_castId, (byte)effectMask, unit,
m_originalCaster, out refresh, (aurSpellInfo == m_spellInfo) ? m_spellValue.EffectBasePoints : basePoints, m_CastItem, ObjectGuid.Empty, m_castItemLevel);
m_originalCaster, out refresh, (aurSpellInfo == m_spellInfo) ? m_spellValue.EffectBasePoints : basePoints, m_CastItem, ObjectGuid.Empty, resetPeriodicTimer, m_castItemLevel);
if (m_spellAura != null)
{
// Set aura stack amount to desired value
@@ -2548,7 +2533,7 @@ namespace Game.Spells
}
// Prepare data for triggers
prepareDataForTriggerSystem(triggeredByAura);
prepareDataForTriggerSystem();
if (m_caster.IsTypeId(TypeId.Player))
{
@@ -2667,10 +2652,6 @@ namespace Game.Spells
SendInterrupted(0);
SendCastResult(SpellCastResult.Interrupted);
// spell is canceled-take mods and clear list
if (m_caster.IsTypeId(TypeId.Player))
m_caster.ToPlayer().RemoveSpellMods(this);
m_appliedMods.Clear();
break;
@@ -2923,6 +2904,25 @@ namespace Game.Spells
Creature creatureCaster = m_caster.ToCreature();
if (creatureCaster)
creatureCaster.ReleaseFocus(this);
if (!m_originalCaster)
return;
// Handle procs on cast
ProcFlags procAttacker = m_procAttacker;
if (procAttacker == 0)
{
if (m_spellInfo.DmgClass == SpellDmgClass.Magic)
procAttacker = m_spellInfo.IsPositive() ? ProcFlags.DoneSpellMagicDmgClassPos : ProcFlags.DoneSpellMagicDmgClassNeg;
else
procAttacker = m_spellInfo.IsPositive() ? ProcFlags.DoneSpellNoneDmgClassPos : ProcFlags.DoneSpellNoneDmgClassNeg;
}
ProcFlagsHit hitMask = m_hitMask;
if (!hitMask.HasAnyFlag(ProcFlagsHit.Critical))
hitMask |= ProcFlagsHit.Normal;
m_originalCaster.ProcSkillsAndAuras(null, procAttacker, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Cast, hitMask, this, null, null);
}
void handle_immediate()
@@ -3076,20 +3076,6 @@ namespace Game.Spells
// process items
foreach (var ihit in m_UniqueItemInfo)
DoAllEffectOnTarget(ihit);
if (m_originalCaster == null)
return;
// Handle procs on cast
// @todo finish new proc system:P
if (m_UniqueTargetInfo.Empty() && m_targets.HasDst())
{
var procAttacker = m_procAttacker;
if (procAttacker == 0)
procAttacker |= ProcFlags.DoneSpellMagicDmgClassPos;
// Proc the spells that have DEST target
m_originalCaster.ProcDamageAndSpell(null, procAttacker, 0, m_procEx | ProcFlagsExLegacy.NormalHit, 0, WeaponAttackType.BaseAttack, m_spellInfo, m_triggeredByAuraSpell);
}
}
void _handle_finish_phase()
@@ -3113,7 +3099,25 @@ namespace Game.Spells
else
m_caster.m_extraAttacks = 0;
}
// @todo trigger proc phase finish here
// Handle procs on finish
if (!m_originalCaster)
return;
ProcFlags procAttacker = m_procAttacker;
if (procAttacker == 0)
{
if (m_spellInfo.DmgClass == SpellDmgClass.Magic)
procAttacker = m_spellInfo.IsPositive() ? ProcFlags.DoneSpellMagicDmgClassPos : ProcFlags.DoneSpellMagicDmgClassNeg;
else
procAttacker = m_spellInfo.IsPositive() ? ProcFlags.DoneSpellNoneDmgClassPos : ProcFlags.DoneSpellNoneDmgClassNeg;
}
ProcFlagsHit hitMask = m_hitMask;
if (!hitMask.HasAnyFlag(ProcFlagsHit.Critical))
hitMask |= ProcFlagsHit.Normal;
m_originalCaster.ProcSkillsAndAuras(null, procAttacker, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.Finish, hitMask, this, null, null);
}
void SendSpellCooldown()
@@ -3281,19 +3285,6 @@ namespace Game.Spells
m_caster.ToPlayer().UpdatePotionCooldown(this);
}
Player modOwner = m_caster.GetSpellModOwner();
if (modOwner)
{
// triggered spell pointer can be not set in some cases
// this is needed for proper apply of triggered spell mods
modOwner.SetSpellModTakingSpell(this, true);
// Take mods after trigger spell (needed for 14177 to affect 48664)
// mods are taken only on succesfull cast and independantly from targets of the spell
modOwner.RemoveSpellMods(this);
modOwner.SetSpellModTakingSpell(this, false);
}
// Stop Attack for some spells
if (m_spellInfo.HasAttribute(SpellAttr0.StopAttackTarget))
m_caster.AttackStop();
@@ -6470,7 +6461,7 @@ namespace Game.Spells
if (effect != null && Convert.ToBoolean(m_applyMultiplierMask & (1 << (int)effect.EffectIndex)))
multiplier[effect.EffectIndex] = effect.CalcDamageMultiplier(m_originalCaster, this);
bool usesAmmo = m_spellInfo.HasAttribute(SpellCustomAttributes.DirectDamage);
PrepareTargetProcessing();
foreach (var ihit in m_UniqueTargetInfo)
{
@@ -6482,6 +6473,8 @@ namespace Game.Spells
DoAllEffectOnLaunchTarget(target, multiplier);
}
FinishTargetProcessing();
}
void DoAllEffectOnLaunchTarget(TargetInfo targetInfo, float[] multiplier)
@@ -6994,18 +6987,25 @@ namespace Game.Spells
if (m_spellInfo.StartRecoveryTime >= 750 && m_spellInfo.StartRecoveryTime <= 1500)
{
// gcd modifier auras are applied only to own spells and only players have such mods
if (m_caster.IsTypeId(TypeId.Player))
m_caster.ToPlayer().ApplySpellMod(m_spellInfo.Id, SpellModOp.GlobalCooldown, ref gcd, this);
Player modOwner = m_caster.GetSpellModOwner();
if (modOwner)
modOwner.ApplySpellMod(m_spellInfo.Id, SpellModOp.GlobalCooldown, ref gcd, this);
bool isMeleeOrRangedSpell = m_spellInfo.DmgClass == SpellDmgClass.Melee || m_spellInfo.DmgClass == SpellDmgClass.Ranged ||
m_spellInfo.HasAttribute(SpellAttr0.ReqAmmo) || m_spellInfo.HasAttribute(SpellAttr0.Ability);
// Apply haste rating
if (gcd > 750 && ((m_spellInfo.StartRecoveryCategory == 133 && !isMeleeOrRangedSpell) || m_caster.HasAuraTypeWithAffectMask(AuraType.ModGlobalCooldownByHaste, m_spellInfo)))
gcd = Math.Min(Math.Max((int)(gcd * m_caster.GetFloatValue(UnitFields.ModCastHaste)), 750), 1500);
{
gcd = (int)(gcd * m_caster.GetFloatValue(UnitFields.ModCastHaste));
MathFunctions.RoundToInterval(ref gcd, 750, 1500);
}
if (gcd > 750 && m_caster.HasAuraTypeWithAffectMask(AuraType.ModGlobalCooldownByHasteRegen, m_spellInfo))
gcd = Math.Min(Math.Max((int)(gcd * m_caster.GetFloatValue(UnitFields.ModHasteRegen)), 750), 1500);
{
gcd = (int)(gcd * m_caster.GetFloatValue(UnitFields.ModHasteRegen));
MathFunctions.RoundToInterval(ref gcd, 750, 1500);
}
}
m_caster.GetSpellHistory().AddGlobalCooldown(m_spellInfo, (uint)gcd);
@@ -7246,7 +7246,7 @@ namespace Game.Spells
// ******************************************
ProcFlags m_procAttacker; // Attacker trigger flags
ProcFlags m_procVictim; // Victim trigger flags
ProcFlagsExLegacy m_procEx;
ProcFlagsHit m_hitMask;
// *****************************************
// Spell target subsystem
@@ -7442,6 +7442,7 @@ namespace Game.Spells
public bool scaleAura;
public int damage;
}
public class GOTargetInfo
{
public ObjectGuid targetGUID;
@@ -7480,11 +7481,10 @@ namespace Game.Spells
// Spell modifier (used for modify other spells)
public class SpellModifier
{
public SpellModifier(Aura _ownerAura = null)
public SpellModifier(Aura _ownerAura)
{
op = SpellModOp.Damage;
type = SpellModType.Flat;
charges = 0;
value = 0;
mask = new FlagArray128();
spellId = 0;
@@ -7493,7 +7493,6 @@ namespace Game.Spells
public SpellModOp op { get; set; }
public SpellModType type { get; set; }
public short charges { get; set; }
public int value { get; set; }
public FlagArray128 mask { get; set; }
public uint spellId { get; set; }
@@ -7763,4 +7762,32 @@ namespace Game.Spells
Spell m_Spell;
}
class ProcReflectDelayed : BasicEvent
{
public ProcReflectDelayed(Unit owner, ObjectGuid casterGuid)
{
_victim = owner;
_casterGuid = casterGuid;
}
public override bool Execute(ulong e_time, uint p_time)
{
Unit caster = Global.ObjAccessor.GetUnit(_victim, _casterGuid);
if (!caster)
return true;
ProcFlags typeMaskActor = ProcFlags.None;
ProcFlags typeMaskActionTarget = ProcFlags.TakenSpellMagicDmgClassNeg | ProcFlags.TakenSpellNoneDmgClassNeg;
ProcFlagsSpellType spellTypeMask = ProcFlagsSpellType.Damage | ProcFlagsSpellType.NoDmgHeal;
ProcFlagsSpellPhase spellPhaseMask = ProcFlagsSpellPhase.None;
ProcFlagsHit hitMask = ProcFlagsHit.Reflect;
caster.ProcSkillsAndAuras(_victim, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, null, null, null);
return true;
}
Unit _victim;
ObjectGuid _casterGuid;
}
}
+3 -2
View File
@@ -1089,7 +1089,8 @@ namespace Game.Spells
healthGain = m_caster.SpellHealingBonusDone(m_caster, m_spellInfo, healthGain, DamageEffectType.Heal, effectInfo);
healthGain = m_caster.SpellHealingBonusTaken(m_caster, m_spellInfo, healthGain, DamageEffectType.Heal, effectInfo);
m_caster.HealBySpell(m_caster, m_spellInfo, healthGain);
HealInfo healInfo = new HealInfo(m_caster, m_caster, healthGain, m_spellInfo, m_spellSchoolMask);
m_caster.HealBySpell(healInfo);
}
}
@@ -2877,7 +2878,7 @@ namespace Game.Spells
[SpellEffectHandler(SpellEffectName.InterruptCast)]
void EffectInterruptCast(uint effIndex)
{
if (effectHandleMode != SpellEffectHandleMode.HitTarget)
if (effectHandleMode != SpellEffectHandleMode.LaunchTarget)
return;
if (unitTarget == null || !unitTarget.IsAlive())
+273 -349
View File
@@ -445,142 +445,6 @@ namespace Game.Entities
return SpellGroupStackRule.Default;
}
public SpellProcEventEntry GetSpellProcEvent(uint spellId)
{
return mSpellProcEventMap.LookupByKey(spellId);
}
public bool IsSpellProcEventCanTriggeredBy(SpellInfo spellProto, SpellProcEventEntry spellProcEvent, ProcFlags EventProcFlag, SpellInfo procSpell, ProcFlags procFlags, ProcFlagsExLegacy procExtra, bool active)
{
// No extra req need
ProcFlagsExLegacy procEvent_procEx = ProcFlagsExLegacy.None;
// check prockFlags for condition
if (!procFlags.HasAnyFlag(EventProcFlag))
return false;
bool hasFamilyMask = false;
// Quick Check - If PROC_FLAG_TAKEN_DAMAGE is set for aura and procSpell dealt damage, proc no matter what kind of spell that deals the damage.
if (procFlags.HasAnyFlag(ProcFlags.TakenDamage) && EventProcFlag.HasAnyFlag(ProcFlags.TakenDamage))
return true;
if (procFlags.HasAnyFlag(ProcFlags.DonePeriodic) && EventProcFlag.HasAnyFlag(ProcFlags.DonePeriodic))
{
if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalHot))
{
if (EventProcFlag == ProcFlags.DonePeriodic)
{
// no aura with only PROC_FLAG_DONE_PERIODIC and spellFamilyName == 0 can proc from a HOT.
if (spellProto.SpellFamilyName == 0)
return false;
}
// Aura must have positive procflags for a HOT to proc
else if (!EventProcFlag.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassPos | ProcFlags.DoneSpellNoneDmgClassPos))
return false;
}
// Aura must have negative or neutral(PROC_FLAG_DONE_PERIODIC only) procflags for a DOT to proc
else if (EventProcFlag != ProcFlags.DonePeriodic)
if (!EventProcFlag.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassNeg | ProcFlags.DoneSpellNoneDmgClassNeg))
return false;
}
if (procFlags.HasAnyFlag(ProcFlags.TakenPeriodic) && EventProcFlag.HasAnyFlag(ProcFlags.TakenPeriodic))
{
if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalHot))
{
// No aura that only has PROC_FLAG_TAKEN_PERIODIC can proc from a HOT.
if (EventProcFlag == ProcFlags.TakenPeriodic)
return false;
// Aura must have positive procflags for a HOT to proc
if (!EventProcFlag.HasAnyFlag(ProcFlags.TakenSpellMagicDmgClassPos | ProcFlags.TakenSpellNoneDmgClassPos))
return false;
}
// Aura must have negative or neutral(PROC_FLAG_TAKEN_PERIODIC only) procflags for a DOT to proc
else if (EventProcFlag != ProcFlags.TakenPeriodic)
if (!EventProcFlag.HasAnyFlag(ProcFlags.TakenSpellMagicDmgClassNeg | ProcFlags.TakenSpellNoneDmgClassNeg))
return false;
}
// Trap casts are active by default
if (procFlags.HasAnyFlag(ProcFlags.DoneTrapActivation))
active = true;
// Always trigger for this
if (procFlags.HasAnyFlag(ProcFlags.Killed | ProcFlags.Kill | ProcFlags.Death))
return true;
if (spellProcEvent != null) // Exist event data
{
// Store extra req
procEvent_procEx = (ProcFlagsExLegacy)spellProcEvent.procEx;
// For melee triggers
if (procSpell == null)
{
// Check (if set) for school (melee attack have Normal school)
if (spellProcEvent.schoolMask != 0 && (spellProcEvent.schoolMask & SpellSchoolMask.Normal) == 0)
return false;
}
else // For spells need check school/spell family/family mask
{
// Check (if set) for school
if (spellProcEvent.schoolMask != 0 && (spellProcEvent.schoolMask & procSpell.SchoolMask) == 0)
return false;
// Check (if set) for spellFamilyName
if (spellProcEvent.spellFamilyName != 0 && (spellProcEvent.spellFamilyName != procSpell.SpellFamilyName))
return false;
// spellFamilyName is Ok need check for spellFamilyMask if present
if (spellProcEvent.spellFamilyMask != null)
{
if (!(spellProcEvent.spellFamilyMask & procSpell.SpellFamilyFlags))
return false;
hasFamilyMask = true;
// Some spells are not considered as active even with have spellfamilyflags
if (!procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.OnlyActiveSpell))
active = true;
}
}
}
if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalReqFamily))
{
if (!hasFamilyMask)
return false;
}
// Check for extra req (if none) and hit/crit
if (procEvent_procEx == ProcFlagsExLegacy.None)
{
// No extra req, so can trigger only for hit/crit - spell has to be active
if (procExtra.HasAnyFlag(ProcFlagsExLegacy.NormalHit | ProcFlagsExLegacy.CriticalHit) && active)
return true;
}
else // Passive spells hits here only if resist/reflect/immune/evade
{
if (procExtra.HasAnyFlag(ProcFlagsExLegacy.AuraProcMask))
{
// if spell marked as procing only from not active spells
if (active && procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.NotActiveSpell))
return false;
// if spell marked as procing only from active spells
if (!active && procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.OnlyActiveSpell))
return false;
// Exist req for PROC_EX_EX_TRIGGER_ALWAYS
if (procEvent_procEx.HasAnyFlag(ProcFlagsExLegacy.ExTriggerAlways))
return true;
// PROC_EX_NOT_ACTIVE_SPELL and PROC_EX_ONLY_ACTIVE_SPELL flags handle: if passed checks before
if ((procExtra.HasAnyFlag(ProcFlagsExLegacy.NormalHit | ProcFlagsExLegacy.CriticalHit) && (procEvent_procEx & ProcFlagsExLegacy.AuraProcMask) == 0))
return true;
}
// Check Extra Requirement like (hit/crit/miss/resist/parry/dodge/block/immune/reflect/absorb and other)
if (procEvent_procEx.HasAnyFlag(procExtra))
return true;
}
return false;
}
public SpellProcEntry GetSpellProcEntry(uint spellId)
{
return mSpellProcMap.LookupByKey(spellId);
@@ -593,7 +457,7 @@ namespace Game.Entities
return false;
// check XP or honor target requirement
if ((procEntry.AttributesMask & 0x0000010) != 0)
if (((uint)procEntry.AttributesMask & 0x0000010) != 0)
{
Player actor = eventInfo.GetActor().ToPlayer();
if (actor)
@@ -601,10 +465,38 @@ namespace Game.Entities
return false;
}
// check power requirement
if (procEntry.AttributesMask.HasAnyFlag(ProcAttributes.ReqPowerCost))
{
if (!eventInfo.GetProcSpell())
return false;
var costs = eventInfo.GetProcSpell().GetPowerCost();
var m = costs.Find(cost => { return cost.Amount > 0; });
if (m == null)
return false;
}
// always trigger for these types
if ((eventInfo.GetTypeMask() & (ProcFlags.Killed | ProcFlags.Kill | ProcFlags.Death)) != 0)
return true;
// do triggered cast checks
if (!procEntry.AttributesMask.HasAnyFlag(ProcAttributes.TriggeredCanProc))
{
Spell spell = eventInfo.GetProcSpell();
if (spell)
{
if (spell.IsTriggered())
{
SpellInfo spellInfo = spell.GetSpellInfo();
if (!spellInfo.HasAttribute(SpellAttr3.TriggeredCanTriggerProc2) &&
!spellInfo.HasAttribute(SpellAttr2.TriggeredCanTriggerProc))
return false;
}
}
}
// check school mask (if set) for other trigger types
if (procEntry.SchoolMask != 0 && !Convert.ToBoolean(eventInfo.GetSchoolMask() & procEntry.SchoolMask))
return false;
@@ -1631,87 +1523,6 @@ namespace Game.Entities
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell group stack rules in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadSpellProcEvents()
{
uint oldMSTime = Time.GetMSTime();
mSpellProcEventMap.Clear(); // need for reload case
// 0 1 2 3 4 5 6 7 8 9 10 11
SQLResult result = DB.World.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, SpellFamilyMask3, procFlags, procEx, ppmRate, CustomChance, Cooldown FROM spell_proc_event");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell proc event conditions. DB table `spell_proc_event` is empty.");
return;
}
uint count = 0;
do
{
int spellId = result.Read<int>(0);
bool allRanks = false;
if (spellId < 0)
{
allRanks = true;
spellId = -spellId;
}
SpellInfo spellInfo = GetSpellInfo((uint)spellId);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` does not exist", spellId);
continue;
}
if (allRanks)
{
if (!spellInfo.IsRanked())
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` with all ranks, but spell has no ranks.", spellId);
if (spellInfo.GetFirstRankSpell().Id != spellId)
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` is not first rank of spell.", spellId);
continue;
}
}
SpellProcEventEntry spellProcEvent = new SpellProcEventEntry();
spellProcEvent.schoolMask = (SpellSchoolMask)result.Read<uint>(1);
spellProcEvent.spellFamilyName = (SpellFamilyNames)result.Read<uint>(2);
spellProcEvent.spellFamilyMask = new FlagArray128(result.Read<uint>(3), result.Read<uint>(4), result.Read<uint>(5), result.Read<uint>(6));
spellProcEvent.procFlags = result.Read<uint>(7);
spellProcEvent.procEx = result.Read<uint>(8);
spellProcEvent.ppmRate = result.Read<float>(9);
spellProcEvent.customChance = result.Read<float>(10);
spellProcEvent.cooldown = result.Read<uint>(11);
while (spellInfo != null)
{
if (mSpellProcEventMap.ContainsKey(spellInfo.Id))
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` already has its first rank in table.", spellInfo.Id);
break;
}
if (spellInfo.ProcFlags == 0 && spellProcEvent.procFlags == 0)
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc_event` probably not triggered spell", spellInfo.Id);
mSpellProcEventMap[spellInfo.Id] = spellProcEvent;
if (allRanks)
spellInfo = spellInfo.GetNextRankSpell();
else
break;
}
++count;
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} extra spell proc event conditions in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadSpellProcs()
{
uint oldMSTime = Time.GetMSTime();
@@ -1722,117 +1533,195 @@ namespace Game.Entities
SQLResult result = DB.World.Query("SELECT SpellId, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, SpellFamilyMask3, " +
// 7 8 9 10 11 12 13 14 15
"ProcFlags, SpellTypeMask, SpellPhaseMask, HitMask, AttributesMask, ProcsPerMinute, Chance, Cooldown, Charges FROM spell_proc");
if (result.IsEmpty())
{
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 spell proc conditions and data. DB table `spell_proc` is empty.");
return;
}
uint count = 0;
do
if (!result.IsEmpty())
{
int spellId = result.Read<int>(0);
bool allRanks = false;
if (spellId < 0)
do
{
allRanks = true;
spellId = -spellId;
}
int spellId = result.Read<int>(0);
SpellInfo spellInfo = GetSpellInfo((uint)spellId);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` does not exist", spellId);
continue;
}
if (allRanks)
{
if (spellInfo.GetFirstRankSpell().Id != (uint)spellId)
bool allRanks = false;
if (spellId < 0)
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` is not first rank of spell.", spellId);
allRanks = true;
spellId = -spellId;
}
SpellInfo spellInfo = GetSpellInfo((uint)spellId);
if (spellInfo == null)
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` does not exist", spellId);
continue;
}
}
SpellProcEntry baseProcEntry = new SpellProcEntry();
baseProcEntry.SchoolMask = (SpellSchoolMask)result.Read<uint>(1);
baseProcEntry.SpellFamilyName = (SpellFamilyNames)result.Read<uint>(2);
baseProcEntry.SpellFamilyMask = new FlagArray128(result.Read<uint>(3), result.Read<uint>(4), result.Read<uint>(5), result.Read<uint>(6));
baseProcEntry.ProcFlags = (ProcFlags)result.Read<uint>(7);
baseProcEntry.SpellTypeMask = (ProcFlagsSpellType)result.Read<uint>(8);
baseProcEntry.SpellPhaseMask = (ProcFlagsSpellPhase)result.Read<uint>(9);
baseProcEntry.HitMask = (ProcFlagsHit)result.Read<uint>(10);
baseProcEntry.AttributesMask = result.Read<uint>(11);
baseProcEntry.ProcsPerMinute = result.Read<float>(12);
baseProcEntry.Chance = result.Read<float>(13);
baseProcEntry.Cooldown = result.Read<uint>(14);
baseProcEntry.Charges = result.Read<uint>(15);
while (spellInfo != null)
{
if (mSpellProcMap.ContainsKey(spellInfo.Id))
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` has duplicate entry in the table", spellInfo.Id);
break;
}
SpellProcEntry procEntry = baseProcEntry;
// take defaults from dbcs
if (procEntry.ProcFlags == 0)
procEntry.ProcFlags = spellInfo.ProcFlags;
if (procEntry.Charges == 0)
procEntry.Charges = spellInfo.ProcCharges;
if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0)
procEntry.Chance = spellInfo.ProcChance;
// validate data
if (Convert.ToBoolean(procEntry.SchoolMask & ~SpellSchoolMask.All))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SchoolMask` set: {1}", spellInfo.Id, procEntry.SchoolMask);
if (procEntry.SpellFamilyName != 0 && ((int)procEntry.SpellFamilyName < 3 || (int)procEntry.SpellFamilyName > 17 || (int)procEntry.SpellFamilyName == 14 || (int)procEntry.SpellFamilyName == 16))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellFamilyName` set: {1}", spellInfo.Id, procEntry.SpellFamilyName);
if (procEntry.Chance < 0)
{
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `Chance` field", spellInfo.Id);
procEntry.Chance = 0;
}
if (procEntry.ProcsPerMinute < 0)
{
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `ProcsPerMinute` field", spellInfo.Id);
procEntry.ProcsPerMinute = 0;
}
if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0)
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `Chance` and `ProcsPerMinute` values defined, proc will not be triggered", spellInfo.Id);
if (procEntry.ProcFlags == 0)
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `ProcFlags` value defined, proc will not be triggered", spellInfo.Id);
if (Convert.ToBoolean(procEntry.SpellTypeMask & ~ProcFlagsSpellType.MaskAll))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellTypeMask` set: {1}", spellInfo.Id, procEntry.SpellTypeMask);
if (procEntry.SpellTypeMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & (ProcFlags.SpellMask | ProcFlags.PeriodicMask)))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellTypeMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id);
if (procEntry.SpellPhaseMask == 0 && Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `SpellPhaseMask` value defined, but it's required for defined `ProcFlags` value, proc will not be triggered", spellInfo.Id);
if (Convert.ToBoolean(procEntry.SpellPhaseMask & ~ProcFlagsSpellPhase.MaskAll))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellPhaseMask` set: {1}", spellInfo.Id, procEntry.SpellPhaseMask);
if (procEntry.SpellPhaseMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellPhaseMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id);
if (Convert.ToBoolean(procEntry.HitMask & ~ProcFlagsHit.MaskAll))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `HitMask` set: {1}", spellInfo.Id, procEntry.HitMask);
if (procEntry.HitMask != 0 && !(Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.TakenHitMask) || (Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.DoneHitMask) && (procEntry.SpellPhaseMask == 0 || Convert.ToBoolean(procEntry.SpellPhaseMask & (ProcFlagsSpellPhase.Hit | ProcFlagsSpellPhase.Finish))))))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `HitMask` value defined, but it won't be used for defined `ProcFlags` and `SpellPhaseMask` values", spellInfo.Id);
mSpellProcMap.Add(spellInfo.Id, procEntry);
++count;
if (allRanks)
spellInfo = spellInfo.GetNextRankSpell();
else
break;
}
} while (result.NextRow());
{
if (spellInfo.GetFirstRankSpell().Id != (uint)spellId)
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` is not first rank of spell.", spellId);
continue;
}
}
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell proc conditions and data in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
SpellProcEntry baseProcEntry = new SpellProcEntry();
baseProcEntry.SchoolMask = (SpellSchoolMask)result.Read<uint>(1);
baseProcEntry.SpellFamilyName = (SpellFamilyNames)result.Read<uint>(2);
baseProcEntry.SpellFamilyMask = new FlagArray128(result.Read<uint>(3), result.Read<uint>(4), result.Read<uint>(5), result.Read<uint>(6));
baseProcEntry.ProcFlags = (ProcFlags)result.Read<uint>(7);
baseProcEntry.SpellTypeMask = (ProcFlagsSpellType)result.Read<uint>(8);
baseProcEntry.SpellPhaseMask = (ProcFlagsSpellPhase)result.Read<uint>(9);
baseProcEntry.HitMask = (ProcFlagsHit)result.Read<uint>(10);
baseProcEntry.AttributesMask = (ProcAttributes)result.Read<uint>(11);
baseProcEntry.ProcsPerMinute = result.Read<float>(12);
baseProcEntry.Chance = result.Read<float>(13);
baseProcEntry.Cooldown = result.Read<uint>(14);
baseProcEntry.Charges = result.Read<uint>(15);
while (spellInfo != null)
{
if (mSpellProcMap.ContainsKey(spellInfo.Id))
{
Log.outError(LogFilter.Sql, "Spell {0} listed in `spell_proc` has duplicate entry in the table", spellInfo.Id);
break;
}
SpellProcEntry procEntry = baseProcEntry;
// take defaults from dbcs
if (procEntry.ProcFlags == 0)
procEntry.ProcFlags = spellInfo.ProcFlags;
if (procEntry.Charges == 0)
procEntry.Charges = spellInfo.ProcCharges;
if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0)
procEntry.Chance = spellInfo.ProcChance;
// validate data
if (Convert.ToBoolean(procEntry.SchoolMask & ~SpellSchoolMask.All))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SchoolMask` set: {1}", spellInfo.Id, procEntry.SchoolMask);
if (procEntry.SpellFamilyName != 0 && ((int)procEntry.SpellFamilyName < 3 || (int)procEntry.SpellFamilyName > 17 || (int)procEntry.SpellFamilyName == 14 || (int)procEntry.SpellFamilyName == 16))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellFamilyName` set: {1}", spellInfo.Id, procEntry.SpellFamilyName);
if (procEntry.Chance < 0)
{
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `Chance` field", spellInfo.Id);
procEntry.Chance = 0;
}
if (procEntry.ProcsPerMinute < 0)
{
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has negative value in `ProcsPerMinute` field", spellInfo.Id);
procEntry.ProcsPerMinute = 0;
}
if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0)
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `Chance` and `ProcsPerMinute` values defined, proc will not be triggered", spellInfo.Id);
if (procEntry.ProcFlags == 0)
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `ProcFlags` value defined, proc will not be triggered", spellInfo.Id);
if (Convert.ToBoolean(procEntry.SpellTypeMask & ~ProcFlagsSpellType.MaskAll))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellTypeMask` set: {1}", spellInfo.Id, procEntry.SpellTypeMask);
if (procEntry.SpellTypeMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & (ProcFlags.SpellMask | ProcFlags.PeriodicMask)))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellTypeMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id);
if (procEntry.SpellPhaseMask == 0 && Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} doesn't have `SpellPhaseMask` value defined, but it's required for defined `ProcFlags` value, proc will not be triggered", spellInfo.Id);
if (Convert.ToBoolean(procEntry.SpellPhaseMask & ~ProcFlagsSpellPhase.MaskAll))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `SpellPhaseMask` set: {1}", spellInfo.Id, procEntry.SpellPhaseMask);
if (procEntry.SpellPhaseMask != 0 && !Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.ReqSpellPhaseMask))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `SpellPhaseMask` value defined, but it won't be used for defined `ProcFlags` value", spellInfo.Id);
if (Convert.ToBoolean(procEntry.HitMask & ~ProcFlagsHit.MaskAll))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has wrong `HitMask` set: {1}", spellInfo.Id, procEntry.HitMask);
if (procEntry.HitMask != 0 && !(Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.TakenHitMask) || (Convert.ToBoolean(procEntry.ProcFlags & ProcFlags.DoneHitMask) && (procEntry.SpellPhaseMask == 0 || Convert.ToBoolean(procEntry.SpellPhaseMask & (ProcFlagsSpellPhase.Hit | ProcFlagsSpellPhase.Finish))))))
Log.outError(LogFilter.Sql, "`spell_proc` table entry for spellId {0} has `HitMask` value defined, but it won't be used for defined `ProcFlags` and `SpellPhaseMask` values", spellInfo.Id);
mSpellProcMap.Add(spellInfo.Id, procEntry);
++count;
if (allRanks)
spellInfo = spellInfo.GetNextRankSpell();
else
break;
}
} while (result.NextRow());
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} spell proc conditions and data in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
else
Log.outInfo(LogFilter.ServerLoading, ">> Loaded 0 spell proc conditions and data. DB table `spell_proc` is empty.");
// This generates default procs to retain compatibility with previous proc system
Log.outInfo(LogFilter.ServerLoading, "Generating spell proc data from SpellMap...");
count = 0;
oldMSTime = Time.GetMSTime();
foreach (SpellInfo spellInfo in mSpellInfoMap.Values)
{
if (spellInfo == null)
continue;
if (mSpellProcMap.ContainsKey(spellInfo.Id))
continue;
bool found = false, addTriggerFlag = false;
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
{
if (effect == null || !effect.IsEffect())
continue;
AuraType auraName = effect.ApplyAuraName;
if (auraName == 0)
continue;
if (!isTriggerAura(auraName))
continue;
found = true;
if (!addTriggerFlag && isAlwaysTriggeredAura(auraName))
addTriggerFlag = true;
break;
}
if (!found)
continue;
if (spellInfo.ProcFlags == 0)
continue;
SpellProcEntry procEntry = new SpellProcEntry();
procEntry.SchoolMask = 0;
procEntry.SpellFamilyName = spellInfo.SpellFamilyName;
procEntry.ProcFlags = spellInfo.ProcFlags;
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
if (effect != null && effect.IsEffect() && isTriggerAura(effect.ApplyAuraName))
procEntry.SpellFamilyMask |= effect.SpellClassMask;
procEntry.SpellTypeMask = ProcFlagsSpellType.MaskAll;
procEntry.SpellPhaseMask = ProcFlagsSpellPhase.Hit;
procEntry.HitMask = ProcFlagsHit.None; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent
// Reflect auras should only proc off reflects
foreach (SpellEffectInfo effect in spellInfo.GetEffectsForDifficulty(Difficulty.None))
{
if (effect != null && (effect.IsAura(AuraType.ReflectSpells) || effect.IsAura(AuraType.ReflectSpellsSchool)))
{
procEntry.HitMask = ProcFlagsHit.Reflect;
break;
}
}
procEntry.AttributesMask = 0;
if (spellInfo.ProcFlags.HasAnyFlag(ProcFlags.Kill))
procEntry.AttributesMask |= ProcAttributes.ReqExpOrHonor;
if (addTriggerFlag)
procEntry.AttributesMask |= ProcAttributes.TriggeredCanProc;
procEntry.ProcsPerMinute = 0;
procEntry.Chance = spellInfo.ProcChance;
procEntry.Cooldown = 0;
procEntry.Charges = spellInfo.ProcCharges;
mSpellProcMap[spellInfo.Id] = procEntry;
++count;
}
Log.outInfo(LogFilter.ServerLoading, "Generated spell proc data for {0} spells in {1} ms", count, Time.GetMSTimeDiffToNow(oldMSTime));
}
public void LoadSpellThreats()
@@ -2004,7 +1893,7 @@ namespace Game.Entities
SpellEnchantProcEntry spe = new SpellEnchantProcEntry();
spe.customChance = result.Read<uint>(1);
spe.PPMChance = result.Read<float>(2);
spe.procEx = (ProcFlagsExLegacy)result.Read<uint>(3);
spe.procEx = result.Read<uint>(3);
mSpellEnchantProcEventMap[enchantId] = spe;
@@ -2153,6 +2042,7 @@ namespace Game.Entities
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} summonable creature templates in {1} ms", countCreature, Time.GetMSTimeDiffToNow(oldMSTime));
}
bool LoadPetDefaultSpells_helper(CreatureTemplate cInfo, PetDefaultSpellsEntry petDefSpells)
{
// skip empty list;
@@ -2812,20 +2702,9 @@ namespace Game.Entities
spellInfo.MaxAffectedTargets = 1;
spellInfo.GetEffect(0).TriggerSpell = 33760;
break;
case 17941: // Shadow Trance
case 22008: // Netherwind Focus
case 34477: // Misdirection
case 48108: // Hot Streak
case 51124: // Killing Machine
case 64823: // Item - Druid T8 Balance 4P Bonus
spellInfo.ProcCharges = 1;
break;
case 44544: // Fingers of Frost
spellInfo.GetEffect(0).SpellClassMask = new FlagArray128(685904631, 1151048, 0, 0);
break;
case 28200: // Ascendance (Talisman of Ascendance trinket)
spellInfo.ProcCharges = 6;
break;
case 37408: // Oscillation Field
spellInfo.AttributesEx3 |= SpellAttr3.StackForDiffCasters;
break;
@@ -3172,10 +3051,6 @@ namespace Game.Entities
// Little hack, Increase the radius so it can hit the Cave In Stalkers in the platform.
spellInfo.GetEffect(0).MaxRadiusEntry = CliDB.SpellRadiusStorage.LookupByKey(EffectRadiusIndex.Yards45);
break;
case 75323: // Reverberating Hymn
// Aura is refreshed at 3 seconds, and the tick should happen at the fourth.
spellInfo.AttributesEx8 |= SpellAttr8.DontResetPeriodicTimer;
break;
case 24314: // Threatening Gaze
spellInfo.AuraInterruptFlags |= SpellAuraInterruptFlags.Cast | SpellAuraInterruptFlags.Move | SpellAuraInterruptFlags.Jump;
break;
@@ -3254,6 +3129,68 @@ namespace Game.Entities
}
#endregion
public bool isTriggerAura(AuraType type)
{
switch (type)
{
case AuraType.Dummy:
case AuraType.ModConfuse:
case AuraType.ModThreat:
case AuraType.ModStun:
case AuraType.ModDamageDone:
case AuraType.ModDamageTaken:
case AuraType.ModResistance:
case AuraType.ModStealth:
case AuraType.ModFear:
case AuraType.ModRoot:
case AuraType.Transform:
case AuraType.ReflectSpells:
case AuraType.DamageImmunity:
case AuraType.ProcTriggerSpell:
case AuraType.ProcTriggerDamage:
case AuraType.ModCastingSpeedNotStack:
case AuraType.SchoolAbsorb:
case AuraType.ModPowerCostSchoolPct:
case AuraType.ModPowerCostSchool:
case AuraType.ReflectSpellsSchool:
case AuraType.MechanicImmunity:
case AuraType.ModDamagePercentTaken:
case AuraType.SpellMagnet:
case AuraType.ModAttackPower:
case AuraType.ModPowerRegenPercent:
case AuraType.AddCasterHitTrigger:
case AuraType.OverrideClassScripts:
case AuraType.ModMechanicResistance:
case AuraType.MeleeAttackPowerAttackerBonus:
case AuraType.ModMeleeHaste:
case AuraType.ModMeleeHaste3:
case AuraType.ModAttackerMeleeHitChance:
case AuraType.ProcTriggerSpellWithValue:
case AuraType.ModSpellDamageFromCaster:
case AuraType.AbilityIgnoreAurastate:
case AuraType.ModRoot2:
return true;
}
return false;
}
public bool isAlwaysTriggeredAura(AuraType type)
{
switch (type)
{
case AuraType.OverrideClassScripts:
case AuraType.ModFear:
case AuraType.ModRoot:
case AuraType.ModStun:
case AuraType.Transform:
case AuraType.SpellMagnet:
case AuraType.SchoolAbsorb:
case AuraType.ModStealth:
case AuraType.ModRoot2:
return true;
}
return false;
}
// SpellInfo object management
public SpellInfo GetSpellInfo(uint spellId)
{
@@ -3368,7 +3305,6 @@ namespace Game.Entities
MultiMap<uint, SpellGroup> mSpellSpellGroup = new MultiMap<uint, SpellGroup>();
MultiMap<SpellGroup, int> mSpellGroupSpell = new MultiMap<SpellGroup, int>();
Dictionary<SpellGroup, SpellGroupStackRule> mSpellGroupStack = new Dictionary<SpellGroup, SpellGroupStackRule>();
Dictionary<uint, SpellProcEventEntry> mSpellProcEventMap = new Dictionary<uint, SpellProcEventEntry>();
Dictionary<uint, SpellProcEntry> mSpellProcMap = new Dictionary<uint, SpellProcEntry>();
Dictionary<uint, SpellThreatEntry> mSpellThreatMap = new Dictionary<uint, SpellThreatEntry>();
Dictionary<uint, PetAura> mSpellPetAuraMap = new Dictionary<uint, PetAura>();
@@ -3454,25 +3390,13 @@ namespace Game.Entities
public ProcFlagsSpellType SpellTypeMask { get; set; } // if nonzero - bitmask for matching proc condition based on candidate spell's damage/heal effects, see enum ProcFlagsSpellType
public ProcFlagsSpellPhase SpellPhaseMask { get; set; } // if nonzero - bitmask for matching phase of a spellcast on which proc occurs, see enum ProcFlagsSpellPhase
public ProcFlagsHit HitMask { get; set; } // if nonzero - bitmask for matching proc condition based on hit result, see enum ProcFlagsHit
public uint AttributesMask { get; set; } // bitmask, see ProcAttributes
public ProcAttributes AttributesMask { get; set; } // bitmask, see ProcAttributes
public float ProcsPerMinute { get; set; } // if nonzero - chance to proc is equal to value * aura caster's weapon speed / 60
public float Chance { get; set; } // if nonzero - owerwrite procChance field for given Spell.dbc entry, defines chance of proc to occur, not used if ProcsPerMinute set
public uint Cooldown { get; set; } // if nonzero - cooldown in secs for aura proc, applied to aura
public uint Charges { get; set; } // if nonzero - owerwrite procCharges field for given Spell.dbc entry, defines how many times proc can occur before aura remove, 0 - infinite
}
public class SpellProcEventEntry
{
public SpellSchoolMask schoolMask; // if nonzero - bit mask for matching proc condition based on spell candidate's school: Fire=2, Mask=1<<(2-1)=2
public SpellFamilyNames spellFamilyName; // if nonzero - for matching proc condition based on candidate spell's SpellFamilyNamer value
public FlagArray128 spellFamilyMask; // if nonzero - for matching proc condition based on candidate spell's SpellFamilyFlags (like auras 107 and 108 do)
public uint procFlags; // bitmask for matching proc event
public uint procEx; // proc Extend info (see ProcFlagsEx)
public float ppmRate; // for melee (ranged?) damage spells - proc rate per minute. if zero, falls back to flat chance from Spell.dbc
public float customChance; // Owerride chance (in most cases for debug only)
public uint cooldown; // hidden cooldown used for some spell proc events, applied to _triggered_spell_
}
public class PetDefaultSpellsEntry
{
public uint[] spellid = new uint[4];
@@ -3637,7 +3561,7 @@ namespace Game.Entities
{
public uint customChance;
public float PPMChance;
public ProcFlagsExLegacy procEx;
public uint procEx;
}
public class SpellTargetPosition