From 9a039f35a21d2eb66b27fce67142f588fd95fd46 Mon Sep 17 00:00:00 2001 From: hondacrx Date: Mon, 14 Aug 2017 08:12:31 -0400 Subject: [PATCH] Implementation of QAston proc system --- Framework/Constants/AccountConst.cs | 2 +- Framework/Constants/ScriptsConst.cs | 1 + Framework/Constants/Spells/SpellAuraConst.cs | 10 +- Framework/Constants/Spells/SpellConst.cs | 45 +- Game/Chat/Commands/ReloadCommand.cs | 10 - Game/DataStorage/DB2Manager.cs | 9 + Game/Entities/Player/Player.Items.cs | 14 + Game/Entities/Player/Player.Spells.cs | 279 +++--- Game/Entities/Unit/Unit.Combat.cs | 64 +- Game/Entities/Unit/Unit.Fields.cs | 53 +- Game/Entities/Unit/Unit.Spells.cs | 941 +++---------------- Game/Scripting/SpellScript.cs | 27 + Game/Server/WorldManager.cs | 3 - Game/Spells/Auras/Aura.cs | 164 +++- Game/Spells/Auras/AuraEffect.cs | 285 +++--- Game/Spells/Spell.cs | 221 +++-- Game/Spells/SpellEffects.cs | 5 +- Game/Spells/SpellManager.cs | 622 ++++++------ Scripts/Spells/Druid.cs | 6 +- Scripts/Spells/Generic.cs | 35 +- Scripts/Spells/Items.cs | 6 +- Scripts/Spells/Mage.cs | 4 +- Scripts/Spells/Shaman.cs | 7 +- 23 files changed, 1083 insertions(+), 1730 deletions(-) diff --git a/Framework/Constants/AccountConst.cs b/Framework/Constants/AccountConst.cs index 84a04702e..43cdc65e0 100644 --- a/Framework/Constants/AccountConst.cs +++ b/Framework/Constants/AccountConst.cs @@ -604,7 +604,7 @@ namespace Framework.Constants CommandReloadSpellLootTemplate = 695, CommandReloadSpellLinkedSpell = 696, CommandReloadSpellPetAuras = 697, - CommandReloadSpellProcEvent = 698, + // 698 - reuse CommandReloadSpellProc = 699, CommandReloadSpellScripts = 700, CommandReloadSpellTargetPosition = 701, diff --git a/Framework/Constants/ScriptsConst.cs b/Framework/Constants/ScriptsConst.cs index b0572e25a..f221961d1 100644 --- a/Framework/Constants/ScriptsConst.cs +++ b/Framework/Constants/ScriptsConst.cs @@ -43,6 +43,7 @@ namespace Framework.Constants AfterDispel, // Spell Proc Hooks CheckProc, + CheckEffectProc, PrepareProc, Proc, EffectProc, diff --git a/Framework/Constants/Spells/SpellAuraConst.cs b/Framework/Constants/Spells/SpellAuraConst.cs index 35a0e2133..713b04398 100644 --- a/Framework/Constants/Spells/SpellAuraConst.cs +++ b/Framework/Constants/Spells/SpellAuraConst.cs @@ -244,9 +244,9 @@ namespace Framework.Constants ModRatingFromStat = 220, ModDetaunt = 221, Unk222 = 222, - RaidProcFromCharge = 223, + Unk223 = 223, Unk224 = 224, - RaidProcFromChargeWithValue = 225, + ModVisibilityRange = 225, PeriodicDummy = 226, PeriodicTriggerSpellWithValue = 227, DetectStealth = 228, @@ -349,7 +349,7 @@ namespace Framework.Constants Unk325 = 325, // Not Used In 4.3.4 PhaseGroup = 326, // Phase Related Unk327 = 327, // Not Used In 4.3.4 - ProcOnPowerAmount = 328, + TriggerSpellOnPowerPct = 328, ModPowerGainPct = 329, // Nyi CastWhileWalking = 330, ForceWeather = 331, @@ -417,7 +417,7 @@ namespace Framework.Constants Unk393 = 393, ShowConfirmationPrompt = 394, AreaTrigger = 395, // NYI - ProcOnPowerAmount2 = 396, + TriggerSpellOnPowerAmount = 396, Unk397 = 397, Unk398 = 398, Unk399 = 399, @@ -489,7 +489,7 @@ namespace Framework.Constants ModBonusArmor = 465, // NYI ModBonusArmorPct = 466, ModStatBonusPct = 467, - TriggerSpellOnHealthBelowPct = 468, + TriggerSpellOnHealthPct = 468, ShowConfirmationPromptWithDifficulty = 469, Unk470 = 470, ModVersatility = 471, // NYI diff --git a/Framework/Constants/Spells/SpellConst.cs b/Framework/Constants/Spells/SpellConst.cs index 8b43b7fac..27922b82b 100644 --- a/Framework/Constants/Spells/SpellConst.cs +++ b/Framework/Constants/Spells/SpellConst.cs @@ -341,7 +341,9 @@ namespace Framework.Constants Effect5 = 33, SpellCost2 = 34, // Used when SpellPowerEntry.PowerIndex == 1 JumpDistance = 35, + // spellmod 36 StackAmount2 = 37, // same as SPELLMOD_STACK_AMOUNT but affects tooltips + // spellmod 38 Max = 39 } @@ -1171,7 +1173,7 @@ namespace Framework.Constants IgnoreCasterAurastate = 0x800, //! Will Ignore Caster Aura States Including Combat Requirements And Death State IgnoreCasterMountedOrOnVehicle = 0x2000, //! Will Ignore Mounted/On Vehicle Restrictions IgnoreCasterAuras = 0x10000, //! Will Ignore Caster Aura Restrictions Or Requirements - DisallowProcEvents = 0x20000, //! Disallows Proc Events From Triggered Spell (Default) + DontResetPeriodicTimer = 0x20000, //! Will allow periodic aura timers to keep ticking (instead of resetting) DontReportCastError = 0x40000, //! Will Return SpellFailedDontReport In Checkcast Functions IgnoreEquippedItemRequirement = 0x80000, IgnoreTargetCheck = 0x100000, @@ -2116,39 +2118,6 @@ namespace Framework.Constants MeleeBasedTriggerMask = (DoneMeleeAutoAttack | TakenMeleeAutoAttack | DoneSpellMeleeDmgClass | TakenSpellMeleeDmgClass | DoneRangedAutoAttack | TakenRangedAutoAttack | DoneSpellRangedDmgClass | TakenSpellRangedDmgClass) } - public enum ProcFlagsExLegacy - { - None = 0x0, // If none can tigger on Hit/Crit only (passive spells MUST defined by SpellFamily flag) - NormalHit = 0x01, // If set only from normal hit (only damage spells) - CriticalHit = 0x02, - Miss = 0x04, - Resist = 0x08, - Dodge = 0x10, - Parry = 0x20, - Block = 0x40, - Evade = 0x80, - Immune = 0x100, - Deflect = 0x200, - Absorb = 0x400, - Reflect = 0x800, - Interrupt = 0x1000, // Melee hit result can be Interrupt (not used) - FullBlock = 0x2000, // block al attack damage - Reserved2 = 0x4000, - NotActiveSpell = 0x8000, // Spell mustn't do damage/heal to proc - ExTriggerAlways = 0x10000, // If set trigger always no matter of hit result - ExOneTimeTrigger = 0x20000, // If set trigger always but only one time (not implemented yet) - OnlyActiveSpell = 0x40000, // Spell has to do damage/heal to proc - - // Flags for internal use - do not use these in db! - InternalCantProc = 0x800000, - InternalDot = 0x1000000, - InternalHot = 0x2000000, - InternalTriggered = 0x4000000, - InternalReqFamily = 0x8000000, - - AuraProcMask = (NormalHit | CriticalHit | Miss | Resist | Dodge | Parry - | Block | Evade | Immune | Deflect | Absorb | Reflect | Interrupt) - } public enum ProcFlagsSpellPhase { None = 0x0, @@ -2177,6 +2146,14 @@ namespace Framework.Constants MaskAll = 0x0003FFF } + public enum ProcAttributes + { + ReqExpOrHonor = 0x01, // requires proc target to give exp or honor for aura proc + TriggeredCanProc = 0x02, // aura can proc even with triggered spells + ReqPowerCost = 0x04, // requires triggering spell to have a power cost for aura proc + ReqSpellmod = 0x08 // requires triggering spell to be affected by proccing aura to drop charges + } + // Spell aura states public enum AuraStateType { // (C) used in caster aura state (T) used in target aura state diff --git a/Game/Chat/Commands/ReloadCommand.cs b/Game/Chat/Commands/ReloadCommand.cs index 36e069836..7a35dc764 100644 --- a/Game/Chat/Commands/ReloadCommand.cs +++ b/Game/Chat/Commands/ReloadCommand.cs @@ -800,15 +800,6 @@ namespace Game.Chat return true; } - [Command("spell_proc_event", RBACPermissions.CommandReloadSpellLinkedSpell, true)] - static bool HandleReloadSpellProcEventCommand(StringArguments args, CommandHandler handler) - { - Log.outInfo(LogFilter.Server, "Re-Loading Spell Proc Event conditions..."); - Global.SpellMgr.LoadSpellProcEvents(); - handler.SendGlobalGMSysMessage("DB table `spell_proc_event` (spell proc trigger requirements) reloaded."); - return true; - } - [Command("spell_proc", RBACPermissions.CommandReloadSpellProc, true)] static bool HandleReloadSpellProcsCommand(StringArguments args, CommandHandler handler) { @@ -1103,7 +1094,6 @@ namespace Game.Chat HandleReloadSpellGroupsCommand(null, handler); HandleReloadSpellLearnSpellCommand(null, handler); HandleReloadSpellLinkedSpellCommand(null, handler); - HandleReloadSpellProcEventCommand(null, handler); HandleReloadSpellProcsCommand(null, handler); HandleReloadSpellTargetPositionCommand(null, handler); HandleReloadSpellThreatsCommand(null, handler); diff --git a/Game/DataStorage/DB2Manager.cs b/Game/DataStorage/DB2Manager.cs index e3486a7d4..bbff8519d 100644 --- a/Game/DataStorage/DB2Manager.cs +++ b/Game/DataStorage/DB2Manager.cs @@ -332,6 +332,9 @@ namespace Game.DataStorage foreach (var specSpells in CliDB.SpecializationSpellsStorage.Values) _specializationSpellsBySpec.Add(specSpells.SpecID, specSpells); + foreach (SpellClassOptionsRecord classOption in CliDB.SpellClassOptionsStorage.Values) + _spellFamilyNames.Add(classOption.SpellClassSet); + foreach (var power in CliDB.SpellPowerStorage.Values) { SpellPowerDifficultyRecord powerDifficulty = CliDB.SpellPowerDifficultyStorage.LookupByKey(power.Id); @@ -1109,6 +1112,11 @@ namespace Game.DataStorage return _specializationSpellsBySpec.LookupByKey(specId); } + bool IsValidSpellFamiliyName(SpellFamilyNames family) + { + return _spellFamilyNames.Contains((byte)family); + } + public List GetSpellPowers(uint spellId, Difficulty difficulty = Difficulty.None) { bool notUsed; @@ -1334,6 +1342,7 @@ namespace Game.DataStorage Dictionary _rulesetItemUpgrade = new Dictionary(); MultiMap _skillRaceClassInfoBySkill = new MultiMap(); MultiMap _specializationSpellsBySpec = new MultiMap(); + List _spellFamilyNames = new List(); Dictionary> _spellPowers = new Dictionary>(); Dictionary>> _spellPowerDifficulties = new Dictionary>>(); MultiMap _spellProcsPerMinuteMods = new MultiMap(); diff --git a/Game/Entities/Player/Player.Items.cs b/Game/Entities/Player/Player.Items.cs index 1c32f8d78..bfa7b4536 100644 --- a/Game/Entities/Player/Player.Items.cs +++ b/Game/Entities/Player/Player.Items.cs @@ -4289,10 +4289,24 @@ namespace Game.Entities if (proto.GetFlags().HasAnyFlag(ItemFlags.NoEquipCooldown)) return; + DateTime now = DateTime.Now; for (byte i = 0; i < proto.Effects.Count; ++i) { var effectData = proto.Effects[i]; + // apply proc cooldown to equip auras if we have any + if (effectData.Trigger == ItemSpelltriggerType.OnEquip) + { + SpellProcEntry procEntry = Global.SpellMgr.GetSpellProcEntry(effectData.SpellID); + if (procEntry == null) + continue; + + Aura itemAura = GetAura(effectData.SpellID, GetGUID(), pItem.GetGUID()); + if (itemAura != null) + itemAura.AddProcCooldown(now + TimeSpan.FromMilliseconds(procEntry.Cooldown)); + continue; + } + // no spell if (effectData.SpellID == 0) continue; diff --git a/Game/Entities/Player/Player.Spells.cs b/Game/Entities/Player/Player.Spells.cs index a0efaafc6..967ca9c52 100644 --- a/Game/Entities/Player/Player.Spells.cs +++ b/Game/Entities/Player/Player.Spells.cs @@ -1797,19 +1797,18 @@ namespace Game.Entities return; List aurasQueue = new List(); - for (var i = 0; i < (int)SpellModOp.Max; ++i) { for (var j = 0; j < (int)SpellModType.End; ++j) { foreach (var mod in m_spellMods[i][j]) { - // spellmods without aura set cannot be charged - if (mod.ownerAura == null || !mod.ownerAura.IsUsingCharges()) + // Spellmods without charged aura set cannot be charged + if (!mod.ownerAura.IsUsingCharges()) continue; // Restore only specific owner aura mods - if (ownerAuraId != 0 && (ownerAuraId != mod.ownerAura.GetSpellInfo().Id)) + if (ownerAuraId != 0 && mod.spellId != ownerAuraId) continue; if (aura != null && mod.ownerAura != aura) @@ -1831,70 +1830,37 @@ namespace Game.Entities // only see the first of its modifier restored) aurasQueue.Add(mod.ownerAura); - // add mod charges back to mod - if (mod.charges == -1) - mod.charges = 1; - else - mod.charges++; - - // Do not set more spellmods than avalible - if (mod.ownerAura.GetCharges() < mod.charges) - mod.charges = mod.ownerAura.GetCharges(); - - // Skip this check for now - aura charges may change due to various reason - /// @todo track these changes correctly - //ASSERT (mod->ownerAura->GetCharges() <= mod->charges); + // add charges back to aura + mod.ownerAura.ModCharges(1); } } } foreach (var removeAura in aurasQueue) + spell.m_appliedMods.Remove(aura); + } + + public void RestoreAllSpellMods(uint ownerAuraId = 0, Aura aura = null) + { + for (CurrentSpellTypes i = 0; i < CurrentSpellTypes.Max; ++i) { - var appliedMod = spell.m_appliedMods.Find(p => p == removeAura); - if (appliedMod != null) - spell.m_appliedMods.Remove(appliedMod); + Spell spell = m_currentSpells[i]; + if (spell != null) + RestoreSpellMods(spell, ownerAuraId, aura); } } - public void RestoreAllSpellMods(uint ownerAuraId, Aura aura) - { - for (CurrentSpellTypes i = 0; i < CurrentSpellTypes.Max; ++i) - if (GetCurrentSpell(i) != null) - RestoreSpellMods(m_currentSpells[i], ownerAuraId, aura); - } - - public void RemoveSpellMods(Spell spell) + public void ApplyModToSpell(SpellModifier mod, Spell spell) { if (spell == null) return; - if (spell.m_appliedMods.Empty()) + // don't do anything with no charges + if (mod.ownerAura.IsUsingCharges() && mod.ownerAura.GetCharges() == 0) return; - for (var i = 0; i < (int)SpellModOp.Max; ++i) - { - for (var j = 0; j < (int)SpellModType.End; ++j) - { - for (var c = 0; c < m_spellMods[i][j].Count; c++) - { - SpellModifier mod = m_spellMods[i][j][c]; - // spellmods without aura set cannot be charged - if (mod.ownerAura == null || !mod.ownerAura.IsUsingCharges()) - continue; - - // check if mod affected this spell - var iterMod = spell.m_appliedMods.Find(p => p == mod.ownerAura); - if (iterMod == null) - continue; - - // remove from list - spell.m_appliedMods.Remove(iterMod); - - if (mod.ownerAura.DropCharge(AuraRemoveMode.Expire)) - c = 0; - } - } - } + // register inside spell, proc system uses this to drop charges + spell.m_appliedMods.Add(mod.ownerAura); } public void LearnCustomSpells() @@ -2643,25 +2609,71 @@ namespace Game.Entities if (m_spellModTakingSpell != null) spell = m_spellModTakingSpell; + switch (op) + { + // special case, if a mod makes spell instant, only consume that mod + case SpellModOp.CastingTime: + { + SpellModifier modInstantSpell = null; + foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Pct]) + { + if (!IsAffectedBySpellmod(spellInfo, mod, spell)) + continue; + + if (Convert.ToInt64(basevalue) < 10000 && mod.value <= -100) + { + modInstantSpell = mod; + break; + } + } + + if (modInstantSpell != null) + { + ApplyModToSpell(modInstantSpell, spell); + basevalue = default(T); + return; + } + break; + } + // special case if two mods apply 100% critical chance, only consume one + case SpellModOp.CriticalChance: + { + SpellModifier modCritical = null; + foreach (SpellModifier mod in m_spellMods[(int)op][(int)SpellModType.Flat]) + { + if (!IsAffectedBySpellmod(spellInfo, mod, spell)) + continue; + + if (mod.value >= 100) + { + modCritical = mod; + break; + } + } + + if (modCritical != null) + { + ApplyModToSpell(modCritical, spell); + basevalue = (T)Convert.ChangeType(100, typeof(T)); + return; + } + break; + } + default: + break; + } + foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Flat]) { - // Charges can be set only for mods with auras - if (mod.ownerAura == null) - Contract.Assert(mod.charges == 0); - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) continue; totalflat += mod.value; - DropModCharge(mod, spell); + ApplyModToSpell(mod, spell); } foreach (var mod in m_spellMods[(int)op][(int)SpellModType.Pct]) { - // Charges can be set only for mods with auras - if (mod.ownerAura == null) - Contract.Assert(mod.charges == 0); - if (!IsAffectedBySpellmod(spellInfo, mod, spell)) continue; @@ -2670,22 +2682,26 @@ namespace Game.Entities continue; // special case (skip > 10sec spell casts for instant cast setting) - if (mod.op == SpellModOp.CastingTime && Convert.ToInt64(basevalue) >= 10000 && mod.value <= -100) - continue; + if (op == SpellModOp.CastingTime) + { + if (Convert.ToInt32(basevalue) >= 10000 && mod.value <= -100) + continue; + } totalmul *= 1.0f + MathFunctions.CalculatePct(1.0f, mod.value); - DropModCharge(mod, spell); + ApplyModToSpell(mod, spell); } basevalue = (T)Convert.ChangeType(((Convert.ToSingle(basevalue) + totalflat) * totalmul), typeof(T)); } + bool IsAffectedBySpellmod(SpellInfo spellInfo, SpellModifier mod, Spell spell) { if (mod == null || spellInfo == null) return false; - // Mod out of charges - if (spell != null && mod.charges == -1 && !spell.m_appliedMods.Contains(mod.ownerAura)) + // First time this aura applies a mod to us and is out of charges + if (spell && mod.ownerAura.IsUsingCharges() && mod.ownerAura.GetCharges() == 0 && !spell.m_appliedMods.Contains(mod.ownerAura)) return false; // +duration to infinite duration spells making them limited @@ -2694,21 +2710,6 @@ namespace Game.Entities return spellInfo.IsAffectedBySpellMod(mod); } - void DropModCharge(SpellModifier mod, Spell spell) - { - // don't handle spells with proc_event entry defined - // this is a temporary workaround, because all spellmods should be handled like that - if (Global.SpellMgr.GetSpellProcEvent(mod.spellId) != null) - return; - - if (spell != null && mod.ownerAura != null && mod.charges > 0) - { - if (--mod.charges == 0) - mod.charges = -1; - - spell.m_appliedMods.Add(mod.ownerAura); - } - } public void SetSpellModTakingSpell(Spell spell, bool apply) { @@ -2992,10 +2993,62 @@ namespace Game.Entities return false; } - public void CastItemCombatSpell(Unit target, WeaponAttackType attType, ProcFlags procVictim, ProcFlagsExLegacy procEx, Item item, ItemTemplate proto) + public void CastItemCombatSpell(DamageInfo damageInfo) + { + Unit target = damageInfo.GetVictim(); + if (target == null || !target.IsAlive() || target == this) + return; + + for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) + { + // If usable, try to cast item spell + Item item = GetItemByPos(InventorySlots.Bag0, i); + if (item != null) + { + if (!item.IsBroken() && CanUseAttackType(damageInfo.GetAttackType())) + { + ItemTemplate proto = item.GetTemplate(); + if (proto != null) + { + // Additional check for weapons + if (proto.GetClass() == ItemClass.Weapon) + { + // offhand item cannot proc from main hand hit etc + byte slot; + switch (damageInfo.GetAttackType()) + { + case WeaponAttackType.BaseAttack: + case WeaponAttackType.RangedAttack: + slot = EquipmentSlot.MainHand; + break; + case WeaponAttackType.OffAttack: + slot = EquipmentSlot.OffHand; + break; + default: + slot = EquipmentSlot.End; + break; + } + if (slot != i) + continue; + // Check if item is useable (forms or disarm) + if (damageInfo.GetAttackType() == WeaponAttackType.BaseAttack) + if (!IsUseEquipedWeapon(true) && !IsInFeralForm()) + continue; + } + + CastItemCombatSpell(damageInfo, item, proto); + } + } + } + } + } + + public void CastItemCombatSpell(DamageInfo damageInfo, Item item, ItemTemplate proto) { // Can do effect if any damage done to target - if (procVictim.HasAnyFlag(ProcFlags.TakenDamage)) + // for done procs allow normal + critical + absorbs by default + bool canTrigger = damageInfo.GetHitMask().HasAnyFlag(ProcFlagsHit.Normal | ProcFlagsHit.Critical | ProcFlagsHit.Absorb); + if (canTrigger) { for (byte i = 0; i < proto.Effects.Count; ++i) { @@ -3024,14 +3077,14 @@ namespace Game.Entities if (proto.SpellPPMRate != 0) { - uint WeaponSpeed = GetBaseAttackTime(attType); + uint WeaponSpeed = GetBaseAttackTime(damageInfo.GetAttackType()); chance = GetPPMProcChance(WeaponSpeed, proto.SpellPPMRate, spellInfo); } else if (chance > 100.0f) chance = GetWeaponProcChance(); if (RandomHelper.randChance(chance)) - CastSpell(target, spellInfo.Id, true, item); + CastSpell(damageInfo.GetVictim(), spellInfo.Id, true, item); } } @@ -3053,13 +3106,13 @@ namespace Game.Entities if (entry != null && entry.procEx != 0) { // Check hit/crit/dodge/parry requirement - if ((entry.procEx & procEx) == 0) + if (((uint)entry.procEx & (uint)damageInfo.GetHitMask()) == 0) continue; } else { - // Can do effect if any damage done to target - if (!Convert.ToBoolean(procVictim & ProcFlags.TakenDamage)) + // for done procs allow normal + critical + absorbs by default + if (!canTrigger) continue; } @@ -3093,58 +3146,12 @@ namespace Game.Entities if (spellInfo.IsPositive()) CastSpell(this, spellInfo, true, item); else - CastSpell(target, spellInfo, true, item); + CastSpell(damageInfo.GetVictim(), spellInfo, true, item); } } } } - public void CastItemCombatSpell(Unit target, WeaponAttackType attType, ProcFlags procVictim, ProcFlagsExLegacy procEx) - { - if (target == null || !target.IsAlive() || target == this) - return; - - for (byte i = EquipmentSlot.Start; i < EquipmentSlot.End; ++i) - { - // If usable, try to cast item spell - Item item = GetItemByPos(InventorySlots.Bag0, i); - if (item != null) - if (!item.IsBroken() && CanUseAttackType(attType)) - { - ItemTemplate proto = item.GetTemplate(); - if (proto != null) - { - // Additional check for weapons - if (proto.GetClass() == ItemClass.Weapon) - { - // offhand item cannot proc from main hand hit etc - byte slot; - switch (attType) - { - case WeaponAttackType.BaseAttack: - case WeaponAttackType.RangedAttack: - slot = EquipmentSlot.MainHand; - break; - case WeaponAttackType.OffAttack: - slot = EquipmentSlot.OffHand; - break; - default: - slot = EquipmentSlot.End; - break; - } - if (slot != i) - continue; - // Check if item is useable (forms or disarm) - if (attType == WeaponAttackType.BaseAttack) - if (!IsUseEquipedWeapon(true) && !IsInFeralForm()) - continue; - } - CastItemCombatSpell(target, attType, procVictim, procEx, item, proto); - } - } - } - } - float GetWeaponProcChance() { // normalized proc chance for weapon attack speed diff --git a/Game/Entities/Unit/Unit.Combat.cs b/Game/Entities/Unit/Unit.Combat.cs index 84079ff86..c825bf922 100644 --- a/Game/Entities/Unit/Unit.Combat.cs +++ b/Game/Entities/Unit/Unit.Combat.cs @@ -595,7 +595,8 @@ namespace Game.Entities DealDamageMods(victim, ref damageInfo.damage, ref damageInfo.absorb); SendAttackStateUpdate(damageInfo); - ProcDamageAndSpell(damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, damageInfo.procEx, damageInfo.damage, damageInfo.attackType); + DamageInfo dmgInfo = new DamageInfo(damageInfo); + ProcSkillsAndAuras(damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, ProcFlagsSpellType.None, ProcFlagsSpellPhase.None, dmgInfo.GetHitMask(), null, dmgInfo, null); DealMeleeDamage(damageInfo, true); @@ -646,7 +647,6 @@ namespace Game.Entities damageInfo.procAttacker = ProcFlags.None; damageInfo.procVictim = ProcFlags.None; - damageInfo.procEx = ProcFlagsExLegacy.None; damageInfo.hitOutCome = MeleeHitOutcome.Normal; SendAttackStateUpdate(damageInfo); @@ -808,7 +808,10 @@ namespace Game.Entities } if (IsTypeId(TypeId.Player)) - ToPlayer().CastItemCombatSpell(victim, damageInfo.attackType, damageInfo.procVictim, damageInfo.procEx); + { + DamageInfo dmgInfo = new DamageInfo(damageInfo); + ToPlayer().CastItemCombatSpell(dmgInfo); + } // Do effect if any damage done to target if (damageInfo.damage != 0) @@ -1431,16 +1434,17 @@ namespace Game.Entities // Do KILL and KILLED procs. KILL proc is called only for the unit who landed the killing blow (and its owner - for pets and totems) regardless of who tapped the victim if (IsPet() || IsTotem()) { + // proc only once for victim Unit owner = GetOwner(); if (owner != null) - owner.ProcDamageAndSpell(victim, ProcFlags.Kill, ProcFlags.None, ProcFlagsExLegacy.None, 0); + owner.ProcSkillsAndAuras(victim, ProcFlags.Kill, ProcFlags.None, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); } if (!victim.IsCritter()) - ProcDamageAndSpell(victim, ProcFlags.Kill, ProcFlags.Killed, ProcFlagsExLegacy.None, 0); + ProcSkillsAndAuras(victim, ProcFlags.Kill, ProcFlags.Killed, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); // Proc auras on death - must be before aura/combat remove - victim.ProcDamageAndSpell(null, ProcFlags.Death, ProcFlags.None, ProcFlagsExLegacy.None, 0, WeaponAttackType.BaseAttack, null); + victim.ProcSkillsAndAuras(victim, ProcFlags.None, ProcFlags.Death, ProcFlagsSpellType.MaskAll, ProcFlagsSpellPhase.None, ProcFlagsHit.None, null, null, null); // update get killing blow achievements, must be done before setDeathState to be able to require auras on target // and before Spirit of Redemption as it also removes auras @@ -1808,7 +1812,6 @@ namespace Game.Entities damageInfo.HitInfo = 0; damageInfo.procAttacker = ProcFlags.None; damageInfo.procVictim = ProcFlags.None; - damageInfo.procEx = ProcFlagsExLegacy.None; damageInfo.hitOutCome = MeleeHitOutcome.Evade; if (victim == null) @@ -1839,7 +1842,6 @@ namespace Game.Entities damageInfo.HitInfo |= HitInfo.NormalSwing; damageInfo.TargetState = VictimState.Immune; - damageInfo.procEx |= ProcFlagsExLegacy.Immune; damageInfo.damage = 0; damageInfo.cleanDamage = 0; return; @@ -1869,25 +1871,22 @@ namespace Game.Entities case MeleeHitOutcome.Evade: damageInfo.HitInfo |= HitInfo.Miss | HitInfo.SwingNoHitSound; damageInfo.TargetState = VictimState.Evades; - damageInfo.procEx |= ProcFlagsExLegacy.Evade; + damageInfo.damage = 0; damageInfo.cleanDamage = 0; return; case MeleeHitOutcome.Miss: damageInfo.HitInfo |= HitInfo.Miss; damageInfo.TargetState = VictimState.Intact; - damageInfo.procEx |= ProcFlagsExLegacy.Miss; damageInfo.damage = 0; damageInfo.cleanDamage = 0; break; case MeleeHitOutcome.Normal: damageInfo.TargetState = VictimState.Hit; - damageInfo.procEx |= ProcFlagsExLegacy.NormalHit; break; case MeleeHitOutcome.Crit: damageInfo.HitInfo |= HitInfo.CriticalHit; damageInfo.TargetState = VictimState.Hit; - damageInfo.procEx |= ProcFlagsExLegacy.CriticalHit; // Crit bonus calc damageInfo.damage += damageInfo.damage; float mod = 0.0f; @@ -1905,19 +1904,16 @@ namespace Game.Entities break; case MeleeHitOutcome.Parry: damageInfo.TargetState = VictimState.Parry; - damageInfo.procEx |= ProcFlagsExLegacy.Parry; damageInfo.cleanDamage += damageInfo.damage; damageInfo.damage = 0; break; case MeleeHitOutcome.Dodge: damageInfo.TargetState = VictimState.Dodge; - damageInfo.procEx |= ProcFlagsExLegacy.Dodge; damageInfo.cleanDamage += damageInfo.damage; damageInfo.damage = 0; break; case MeleeHitOutcome.Block: damageInfo.TargetState = VictimState.Hit; - damageInfo.procEx |= ProcFlagsExLegacy.Block | ProcFlagsExLegacy.NormalHit; // 30% damage blocked, double blocked amount if block is critical damageInfo.blocked_amount = MathFunctions.CalculatePct(damageInfo.damage, damageInfo.target.isBlockCritical() ? damageInfo.target.GetBlockPercent() * 2 : damageInfo.target.GetBlockPercent()); damageInfo.damage -= damageInfo.blocked_amount; @@ -1926,7 +1922,6 @@ namespace Game.Entities case MeleeHitOutcome.Glancing: damageInfo.HitInfo |= HitInfo.Glancing; damageInfo.TargetState = VictimState.Hit; - damageInfo.procEx |= ProcFlagsExLegacy.NormalHit; int leveldif = (int)victim.getLevel() - (int)getLevel(); if (leveldif > 3) leveldif = 3; @@ -1938,7 +1933,6 @@ namespace Game.Entities case MeleeHitOutcome.Crushing: damageInfo.HitInfo |= HitInfo.Crushing; damageInfo.TargetState = VictimState.Hit; - damageInfo.procEx |= ProcFlagsExLegacy.NormalHit; // 150% normal damage damageInfo.damage += (damageInfo.damage / 2); break; @@ -1965,10 +1959,7 @@ namespace Game.Entities CalcAbsorbResist(damageInfo.target, (SpellSchoolMask)damageInfo.damageSchoolMask, DamageEffectType.Direct, damageInfo.damage, ref damageInfo.absorb, ref damageInfo.resist); if (damageInfo.absorb != 0) - { damageInfo.HitInfo |= (damageInfo.damage - damageInfo.absorb == 0 ? HitInfo.FullAbsorb : HitInfo.PartialAbsorb); - damageInfo.procEx |= ProcFlagsExLegacy.Absorb; - } if (damageInfo.resist != 0) damageInfo.HitInfo |= (damageInfo.damage - damageInfo.resist == 0 ? HitInfo.FullResist : HitInfo.PartialResist); @@ -2437,7 +2428,7 @@ namespace Game.Entities if (eff.GetAmount() >= 0) { // Reduce shield amount - eff.SetAmount(eff.GetAmount() - currentAbsorb); + eff.ChangeAmount(eff.GetAmount() - currentAbsorb); // Aura cannot absorb anything more - remove it if (eff.GetAmount() <= 0) eff.GetBase().Remove(AuraRemoveMode.EnemySpell); @@ -2498,7 +2489,7 @@ namespace Game.Entities // Check if our aura is using amount to count damage if (absorbAurEff.GetAmount() >= 0) { - absorbAurEff.SetAmount(absorbAurEff.GetAmount() - currentAbsorb); + absorbAurEff.ChangeAmount(absorbAurEff.GetAmount() - currentAbsorb); if ((absorbAurEff.GetAmount() <= 0)) absorbAurEff.GetBase().Remove(AuraRemoveMode.EnemySpell); } @@ -2558,7 +2549,8 @@ namespace Game.Entities SendSpellNonMeleeDamageLog(log); // break 'Fear' and similar auras - caster.ProcDamageAndSpellFor(true, this, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsExLegacy.NormalHit, WeaponAttackType.BaseAttack, eff.GetSpellInfo(), splitDamage); + DamageInfo damageInfo = new DamageInfo(caster, this, splitDamage, eff.GetSpellInfo(), schoolMask, DamageEffectType.Direct, WeaponAttackType.BaseAttack); + ProcSkillsAndAuras(caster, ProcFlags.None, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsSpellType.Damage, ProcFlagsSpellPhase.Hit, ProcFlagsHit.None, null, damageInfo, null); } } @@ -2566,24 +2558,22 @@ namespace Game.Entities absorb = dmgInfo.GetAbsorb(); } - public void CalcHealAbsorb(Unit victim, SpellInfo healSpell, ref uint healAmount, ref uint absorb) + public void CalcHealAbsorb(HealInfo healInfo) { - if (healAmount == 0) + if (healInfo.GetHeal() == 0) return; - int RemainingHeal = (int)healAmount; - // Need remove expired auras after bool existExpired = false; // absorb without mana cost - var vHealAbsorb = victim.GetAuraEffectsByType(AuraType.SchoolHealAbsorb); + var vHealAbsorb = healInfo.GetTarget().GetAuraEffectsByType(AuraType.SchoolHealAbsorb); foreach (var eff in vHealAbsorb) { - if (RemainingHeal <= 0) + if (healInfo.GetHeal() <= 0) break; - if (!Convert.ToBoolean(eff.GetMiscValue() & (int)healSpell.SchoolMask)) + if (!Convert.ToBoolean(eff.GetMiscValue() & (int)healInfo.GetSpellInfo().SchoolMask)) continue; // Max Amount can be absorbed by this aura @@ -2598,13 +2588,12 @@ namespace Game.Entities // currentAbsorb - damage can be absorbed by shield // If need absorb less damage - if (RemainingHeal < currentAbsorb) - currentAbsorb = RemainingHeal; + currentAbsorb = (int)Math.Min(healInfo.GetHeal(), currentAbsorb); - RemainingHeal -= currentAbsorb; + healInfo.AbsorbHeal((uint)currentAbsorb); // Reduce shield amount - eff.SetAmount(eff.GetAmount() - currentAbsorb); + eff.ChangeAmount(eff.GetAmount() - currentAbsorb); // Need remove it later if (eff.GetAmount() <= 0) existExpired = true; @@ -2619,16 +2608,13 @@ namespace Game.Entities ++i; if (auraEff.GetAmount() <= 0) { - uint removedAuras = victim.m_removedAurasCount; + uint removedAuras = healInfo.GetTarget().m_removedAurasCount; auraEff.GetBase().Remove(AuraRemoveMode.EnemySpell); - if (removedAuras + 1 < victim.m_removedAurasCount) + if (removedAuras + 1 < healInfo.GetTarget().m_removedAurasCount) i = 0; } } } - - absorb = (uint)(RemainingHeal > 0 ? (healAmount - RemainingHeal) : healAmount); - healAmount = (uint)RemainingHeal; } public uint CalcArmorReducedDamage(Unit attacker, Unit victim, uint damage, SpellInfo spellInfo, WeaponAttackType attackType = WeaponAttackType.Max) diff --git a/Game/Entities/Unit/Unit.Fields.cs b/Game/Entities/Unit/Unit.Fields.cs index d1c4e1484..f3abc5ab1 100644 --- a/Game/Entities/Unit/Unit.Fields.cs +++ b/Game/Entities/Unit/Unit.Fields.cs @@ -154,23 +154,10 @@ namespace Game.Entities public DiminishingLevels hitCount; } - public class ProcTriggeredData - { - public ProcTriggeredData(Aura _aura) - { - aura = _aura; - effMask = 0; - spellProcEvent = null; - } - public SpellProcEventEntry spellProcEvent; - public Aura aura; - public uint effMask; - } - public class ProcEventInfo { - public ProcEventInfo(Unit actor, Unit actionTarget, Unit procTarget, uint typeMask, uint spellTypeMask, - uint spellPhaseMask, uint hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) + public ProcEventInfo(Unit actor, Unit actionTarget, Unit procTarget, ProcFlags typeMask, ProcFlagsSpellType spellTypeMask, + ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) { _actor = actor; _actionTarget = actionTarget; @@ -188,10 +175,10 @@ namespace Game.Entities public Unit GetActionTarget() { return _actionTarget; } public Unit GetProcTarget() { return _procTarget; } - public ProcFlags GetTypeMask() { return (ProcFlags)_typeMask; } - public ProcFlagsSpellType GetSpellTypeMask() { return (ProcFlagsSpellType)_spellTypeMask; } - public ProcFlagsSpellPhase GetSpellPhaseMask() { return (ProcFlagsSpellPhase)_spellPhaseMask; } - public ProcFlagsHit GetHitMask() { return (ProcFlagsHit)_hitMask; } + public ProcFlags GetTypeMask() { return _typeMask; } + public ProcFlagsSpellType GetSpellTypeMask() { return _spellTypeMask; } + public ProcFlagsSpellPhase GetSpellPhaseMask() { return _spellPhaseMask; } + public ProcFlagsHit GetHitMask() { return _hitMask; } public SpellInfo GetSpellInfo() { @@ -221,13 +208,15 @@ namespace Game.Entities public DamageInfo GetDamageInfo() { return _damageInfo; } public HealInfo GetHealInfo() { return _healInfo; } + public Spell GetProcSpell() { return _spell; } + Unit _actor; Unit _actionTarget; Unit _procTarget; - uint _typeMask; - uint _spellTypeMask; - uint _spellPhaseMask; - uint _hitMask; + ProcFlags _typeMask; + ProcFlagsSpellType _spellTypeMask; + ProcFlagsSpellPhase _spellPhaseMask; + ProcFlagsHit _hitMask; Spell _spell; DamageInfo _damageInfo; HealInfo _healInfo; @@ -245,6 +234,7 @@ namespace Game.Entities m_damageType = damageType; m_attackType = attackType; } + public DamageInfo(CalcDamageInfo dmgInfo) { m_attacker = dmgInfo.attacker; @@ -291,11 +281,20 @@ namespace Game.Entities case MeleeHitOutcome.Evade: m_hitMask |= ProcFlagsHit.Evade; break; + case MeleeHitOutcome.Crushing: + case MeleeHitOutcome.Glancing: + case MeleeHitOutcome.Normal: + m_hitMask |= ProcFlagsHit.Normal; + break; + case MeleeHitOutcome.Crit: + m_hitMask |= ProcFlagsHit.Critical; + break; default: break; } } - public DamageInfo(SpellNonMeleeDamage spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType) + + public DamageInfo(SpellNonMeleeDamage spellNonMeleeDamage, DamageEffectType damageType, WeaponAttackType attackType, ProcFlagsHit hitMask) { m_attacker = spellNonMeleeDamage.attacker; m_victim = spellNonMeleeDamage.target; @@ -307,6 +306,7 @@ namespace Game.Entities m_absorb = spellNonMeleeDamage.absorb; m_resist = spellNonMeleeDamage.resist; m_block = spellNonMeleeDamage.blocked; + m_hitMask = hitMask; if (spellNonMeleeDamage.blocked != 0) m_hitMask |= ProcFlagsHit.Block; @@ -354,7 +354,7 @@ namespace Game.Entities public uint GetAbsorb() { return m_absorb; } public uint GetResist() { return m_resist; } uint GetBlock() { return m_block; } - ProcFlagsHit GetHitMask() { return m_hitMask; } + public ProcFlagsHit GetHitMask() { return m_hitMask; } Unit m_attacker; Unit m_victim; @@ -380,7 +380,7 @@ namespace Game.Entities _schoolMask = schoolMask; } - void AbsorbHeal(uint amount) + public void AbsorbHeal(uint amount) { amount = Math.Min(amount, GetHeal()); _absorb += amount; @@ -425,7 +425,6 @@ namespace Game.Entities public WeaponAttackType attackType { get; set; } public ProcFlags procAttacker { get; set; } public ProcFlags procVictim { get; set; } - public ProcFlagsExLegacy procEx { get; set; } public uint cleanDamage { get; set; } // Used only for rage calculation public MeleeHitOutcome hitOutCome { get; set; } // TODO: remove this field (need use TargetState) } diff --git a/Game/Entities/Unit/Unit.Spells.cs b/Game/Entities/Unit/Unit.Spells.cs index 9f1ee9f9e..fd4c78b68 100644 --- a/Game/Entities/Unit/Unit.Spells.cs +++ b/Game/Entities/Unit/Unit.Spells.cs @@ -116,9 +116,9 @@ namespace Game.Entities // For totems get damage bonus from owner if (IsTypeId(TypeId.Unit) && IsTotem()) { - Unit owner1 = GetOwner(); - if (owner1 != null) - return owner1.SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effect, stack); + Unit owner = GetOwner(); + if (owner != null) + return owner.SpellDamageBonusDone(victim, spellProto, pdamage, damagetype, effect, stack); } int DoneTotal = 0; @@ -168,7 +168,12 @@ namespace Game.Entities // apply spellmod to Done damage (flat and pct) Player _modOwner = GetSpellModOwner(); if (_modOwner) - _modOwner.ApplySpellMod(spellProto.Id, damagetype == DamageEffectType.DOT ? SpellModOp.Dot : SpellModOp.Damage, ref tmpDamage); + { + if (damagetype == DamageEffectType.DOT) + _modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Dot, ref tmpDamage); + else + _modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Damage, ref tmpDamage); + } return (uint)Math.Max(tmpDamage, 0.0f); } @@ -435,9 +440,9 @@ namespace Game.Entities // For totems get healing bonus from owner (statue isn't totem in fact) if (IsTypeId(TypeId.Unit) && IsTotem()) { - Unit owner1 = GetOwner(); - if (owner1) - return owner1.SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effect, stack); + Unit owner = GetOwner(); + if (owner) + return owner.SpellHealingBonusDone(victim, spellProto, healamount, damagetype, effect, stack); } // No bonus healing for potion spells @@ -447,8 +452,8 @@ namespace Game.Entities int DoneTotal = 0; // done scripted mod (take it from owner) - Unit owner = GetOwner() ?? this; - var mOverrideClassScript = owner.GetAuraEffectsByType(AuraType.OverrideClassScripts); + Unit owner1 = GetOwner() ?? this; + var mOverrideClassScript = owner1.GetAuraEffectsByType(AuraType.OverrideClassScripts); foreach (var eff in mOverrideClassScript) { if (!eff.IsAffectingSpell(spellProto)) @@ -519,7 +524,12 @@ namespace Game.Entities // apply spellmod to Done amount Player _modOwner = GetSpellModOwner(); if (_modOwner) - _modOwner.ApplySpellMod(spellProto.Id, damagetype == DamageEffectType.DOT ? SpellModOp.Dot : SpellModOp.Damage, ref heal); + { + if (damagetype == DamageEffectType.DOT) + _modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Dot, ref heal); + else + _modOwner.ApplySpellMod(spellProto.Id, SpellModOp.Damage, ref heal); + } return (uint)Math.Max(heal, 0.0f); } @@ -807,12 +817,9 @@ namespace Game.Entities foreach (var eff in mReflectSpellsSchool) if (Convert.ToBoolean(eff.GetMiscValue() & (int)spell.GetSchoolMask())) reflectchance += eff.GetAmount(); + if (reflectchance > 0 && RandomHelper.randChance(reflectchance)) - { - // Start triggers for remove charges if need (trigger only for victim, and mark as active spell) - ProcDamageAndSpell(victim, ProcFlags.None, ProcFlags.TakenSpellMagicDmgClassNeg, ProcFlagsExLegacy.Reflect, 1, WeaponAttackType.BaseAttack, spell); return SpellMissInfo.Reflect; - } } switch (spell.DmgClass) @@ -1329,61 +1336,62 @@ namespace Game.Entities public SpellHistory GetSpellHistory() { return _spellHistory; } - public static ProcFlagsExLegacy createProcExtendMask(SpellNonMeleeDamage damageInfo, SpellMissInfo missCondition) + public static ProcFlagsHit createProcHitMask(SpellNonMeleeDamage damageInfo, SpellMissInfo missCondition) { - ProcFlagsExLegacy procEx = ProcFlagsExLegacy.None; + ProcFlagsHit hitMask = ProcFlagsHit.None; // Check victim state if (missCondition != SpellMissInfo.None) + { switch (missCondition) { case SpellMissInfo.Miss: - procEx |= ProcFlagsExLegacy.Miss; - break; - case SpellMissInfo.Resist: - procEx |= ProcFlagsExLegacy.Resist; + hitMask |= ProcFlagsHit.Miss; break; case SpellMissInfo.Dodge: - procEx |= ProcFlagsExLegacy.Dodge; + hitMask |= ProcFlagsHit.Dodge; break; case SpellMissInfo.Parry: - procEx |= ProcFlagsExLegacy.Parry; + hitMask |= ProcFlagsHit.Parry; break; case SpellMissInfo.Block: - procEx |= ProcFlagsExLegacy.Block; + hitMask |= ProcFlagsHit.Block; break; case SpellMissInfo.Evade: - procEx |= ProcFlagsExLegacy.Evade; + hitMask |= ProcFlagsHit.Evade; break; case SpellMissInfo.Immune: - procEx |= ProcFlagsExLegacy.Immune; + case SpellMissInfo.Immune2: + hitMask |= ProcFlagsHit.Immune; break; case SpellMissInfo.Deflect: - procEx |= ProcFlagsExLegacy.Deflect; + hitMask |= ProcFlagsHit.Deflect; break; case SpellMissInfo.Absorb: - procEx |= ProcFlagsExLegacy.Absorb; + hitMask |= ProcFlagsHit.Absorb; break; case SpellMissInfo.Reflect: - procEx |= ProcFlagsExLegacy.Reflect; + hitMask |= ProcFlagsHit.Reflect; break; default: break; } + } else { // On block if (damageInfo.blocked != 0) - procEx |= ProcFlagsExLegacy.Block; + hitMask |= ProcFlagsHit.Block; // On absorb if (damageInfo.absorb != 0) - procEx |= ProcFlagsExLegacy.Absorb; + hitMask |= ProcFlagsHit.Absorb; // On crit if (damageInfo.HitInfo.HasAnyFlag(SpellHitType.Crit)) - procEx |= ProcFlagsExLegacy.CriticalHit; + hitMask |= ProcFlagsHit.Critical; else - procEx |= ProcFlagsExLegacy.NormalHit; + hitMask |= ProcFlagsHit.Normal; } - return procEx; + + return hitMask; } public void SetAuraStack(uint spellId, Unit target, uint stack) @@ -1728,32 +1736,35 @@ namespace Game.Entities return false; } - public void ProcDamageAndSpell(Unit victim, ProcFlags procAttacker, ProcFlags procVictim, ProcFlagsExLegacy procExtra, uint amount, WeaponAttackType attType = WeaponAttackType.BaseAttack, SpellInfo procSpell = null, SpellInfo procAura = null) + public void ProcSkillsAndAuras(Unit actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) { - // Not much to do if no flags are set. - if (procAttacker != 0) - ProcDamageAndSpellFor(false, victim, procAttacker, procExtra, attType, procSpell, amount, procAura); - // Now go on with a victim's events'n'auras - // Not much to do if no flags are set or there is no victim - if (victim != null && victim.IsAlive() && procVictim != 0) - victim.ProcDamageAndSpellFor(true, this, procVictim, procExtra, attType, procSpell, amount, procAura); + WeaponAttackType attType = damageInfo != null ? damageInfo.GetAttackType() : WeaponAttackType.BaseAttack; + if (typeMaskActor != 0 && CanProc()) + ProcSkillsAndReactives(false, actionTarget, typeMaskActor, hitMask, attType); + + if (typeMaskActionTarget != 0 && actionTarget && actionTarget.CanProc()) + actionTarget.ProcSkillsAndReactives(true, this, typeMaskActionTarget, hitMask, attType); + + TriggerAurasProcOnEvent(null, null, actionTarget, typeMaskActor, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); } - void ProcDamageAndSpellFor(bool isVictim, Unit target, ProcFlags procFlag, ProcFlagsExLegacy procExtra, WeaponAttackType attType, SpellInfo procSpell, uint damage, SpellInfo procAura = null) + + void ProcSkillsAndReactives(bool isVictim, Unit procTarget, ProcFlags typeMask, ProcFlagsHit hitMask, WeaponAttackType attType) { // Player is loaded now - do not allow passive spell casts to proc - if (IsTypeId(TypeId.Player) && ToPlayer().GetSession().PlayerLoading()) + if (IsPlayer() && ToPlayer().GetSession().PlayerLoading()) return; + // For melee/ranged based attack need update skills and set some Aura states if victim present - if (Convert.ToBoolean(procFlag & ProcFlags.MeleeBasedTriggerMask) && target != null) + if (typeMask.HasAnyFlag(ProcFlags.MeleeBasedTriggerMask) && procTarget) { // If exist crit/parry/dodge/block need update aura state (for victim and attacker) - if (Convert.ToBoolean(procExtra & (ProcFlagsExLegacy.CriticalHit | ProcFlagsExLegacy.Parry | ProcFlagsExLegacy.Dodge | ProcFlagsExLegacy.Block))) + if (hitMask.HasAnyFlag(ProcFlagsHit.Critical | ProcFlagsHit.Parry | ProcFlagsHit.Dodge | ProcFlagsHit.Block)) { // for victim if (isVictim) { // if victim and dodge attack - if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Dodge)) + if (hitMask.HasAnyFlag(ProcFlagsHit.Dodge)) { // Update AURA_STATE on dodge if (GetClass() != Class.Rogue) // skip Rogue Riposte @@ -1763,9 +1774,9 @@ namespace Game.Entities } } // if victim and parry attack - if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Parry)) + if (hitMask.HasAnyFlag(ProcFlagsHit.Parry)) { - // For Hunters only Counterattack + // For Hunters only Counterattack (skip Mongoose bite) if (GetClass() == Class.Hunter) { ModifyAuraState(AuraStateType.HunterParry, true); @@ -1778,7 +1789,7 @@ namespace Game.Entities } } // if and victim block attack - if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Block)) + if (hitMask.HasAnyFlag(ProcFlagsHit.Block)) { ModifyAuraState(AuraStateType.Defense, true); StartReactiveTimer(ReactiveType.Defense); @@ -1787,7 +1798,7 @@ namespace Game.Entities else // For attacker { // Overpower on victim dodge - if (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Dodge) && IsTypeId(TypeId.Player) && GetClass() == Class.Warrior) + if (hitMask.HasAnyFlag(ProcFlagsHit.Dodge) && IsPlayer() && GetClass() == Class.Warrior) { ToPlayer().AddComboPoints(1); StartReactiveTimer(ReactiveType.OverPower); @@ -1795,342 +1806,86 @@ namespace Game.Entities } } } + } - Unit actor = isVictim ? target : this; - Unit actionTarget = !isVictim ? target : this; + void GetProcAurasTriggeredOnEvent(List> aurasTriggeringProc, List procAuras, ProcEventInfo eventInfo) + { + DateTime now = DateTime.Now; - DamageInfo damageInfo = new DamageInfo(actor, actionTarget, damage, procSpell, (procSpell != null ? procSpell.SchoolMask : SpellSchoolMask.Normal), DamageEffectType.Direct, attType); - HealInfo healInfo = new HealInfo(actor, actionTarget, damage, procSpell, (procSpell != null ? procSpell.SchoolMask : SpellSchoolMask.Normal)); - ProcEventInfo eventInfo = new ProcEventInfo(actor, actionTarget, target, (uint)procFlag, 0, 0, (uint)procExtra, null, damageInfo, healInfo); - - var now = DateTime.Now; - List procTriggered = new List(); - // Fill procTriggered list - foreach (var pair in GetAppliedAuras()) + // use provided list of auras which can proc + if (!procAuras.Empty()) { - // Do not allow auras to proc from effect triggered by itself - if (procAura != null && procAura.Id == pair.Key || pair.Value == null) - continue; - - if (pair.Value.GetBase().IsProcOnCooldown(now)) - continue; - - ProcTriggeredData triggerData = new ProcTriggeredData(pair.Value.GetBase()); - // Defensive procs are active on absorbs (so absorption effects are not a hindrance) - bool active = damage != 0 || (Convert.ToBoolean(procExtra & ProcFlagsExLegacy.Block) && isVictim); - if (isVictim) - procExtra &= ~ProcFlagsExLegacy.InternalReqFamily; - - SpellInfo spellProto = pair.Value.GetBase().GetSpellInfo(); - - // only auras that has triggered spell should proc from fully absorbed damage - if (procExtra.HasAnyFlag(ProcFlagsExLegacy.Absorb) && isVictim && damage != 0) + foreach (AuraApplication aurApp in procAuras) { - foreach (SpellEffectInfo effect in pair.Value.GetBase().GetSpellEffectInfos()) + Contract.Assert(aurApp.GetTarget() == this); + uint procEffectMask = aurApp.GetBase().IsProcTriggeredOnEvent(aurApp, eventInfo, now); + if (procEffectMask != 0) { - if (effect != null && effect.TriggerSpell != 0) - { - active = true; - break; - } + aurApp.GetBase().PrepareProcToTrigger(aurApp, eventInfo, now); + aurasTriggeringProc.Add(Tuple.Create(procEffectMask, aurApp)); } } - - if (!IsTriggeredAtSpellProcEvent(target, triggerData.aura, procSpell, procFlag, procExtra, attType, isVictim, active, ref triggerData.spellProcEvent)) - continue; - - // do checks using conditions table - if (!Global.ConditionMgr.IsObjectMeetingNotGroupedConditions(ConditionSourceType.SpellProc, spellProto.Id, eventInfo.GetActor(), eventInfo.GetActionTarget())) - continue; - - // AuraScript Hook - if (!triggerData.aura.CallScriptCheckProcHandlers(pair.Value, eventInfo)) - continue; - - bool procSuccess = RollProcResult(target, triggerData.aura, attType, isVictim, triggerData.spellProcEvent); - triggerData.aura.SetLastProcAttemptTime(now); - if (!procSuccess) - continue; - - bool triggeredCanProcAura = true; - // Additional checks for triggered spells (ignore trap casts) - if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalTriggered) && !procFlag.HasAnyFlag(ProcFlags.DoneTrapActivation)) - { - if (!spellProto.HasAttribute(SpellAttr3.CanProcWithTriggered)) - triggeredCanProcAura = false; - } - - foreach (AuraEffect aurEff in pair.Value.GetBase().GetAuraEffects()) - { - if (aurEff != null) - { - // Skip this auras - if (isNonTriggerAura(aurEff.GetAuraType())) - continue; - // If not trigger by default and spellProcEvent == null - skip - if (!isTriggerAura(aurEff.GetAuraType()) && triggerData.spellProcEvent == null) - continue; - // Some spells must always trigger - if (triggeredCanProcAura || isAlwaysTriggeredAura(aurEff.GetAuraType())) - triggerData.effMask |= (uint)(1 << aurEff.GetEffIndex()); - } - } - if (triggerData.effMask != 0) - procTriggered.Add(triggerData); } - - // Nothing found - if (procTriggered.Empty()) - return; - - // Note: must SetCantProc(false) before return - if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalTriggered | ProcFlagsExLegacy.InternalCantProc)) - SetCantProc(true); - - // Handle effects proceed this time - foreach (var proc in procTriggered) + // or generate one on our own + else { - // look for aura in auras list, it may be removed while proc event processing - if (proc.aura.IsRemoved()) - continue; - - bool useCharges = proc.aura.IsUsingCharges(); - // no more charges to use, prevent proc - if (useCharges && proc.aura.GetCharges() == 0) - continue; - - bool takeCharges = false; - SpellInfo spellInfo = proc.aura.GetSpellInfo(); - uint Id = proc.aura.GetId(); - - AuraApplication aurApp = proc.aura.GetApplicationOfTarget(GetGUID()); - - bool prepare = proc.aura.CallScriptPrepareProcHandlers(aurApp, eventInfo); - - TimeSpan cooldown = TimeSpan.Zero; - if (prepare) + foreach (var pair in GetAppliedAuras()) { - cooldown = TimeSpan.FromMilliseconds(spellInfo.ProcCooldown); - if (proc.spellProcEvent != null && proc.spellProcEvent.cooldown != 0) - cooldown = TimeSpan.FromSeconds(proc.spellProcEvent.cooldown); + uint procEffectMask = pair.Value.GetBase().IsProcTriggeredOnEvent(pair.Value, eventInfo, now); + if (procEffectMask != 0) + { + pair.Value.GetBase().PrepareProcToTrigger(pair.Value, eventInfo, now); + aurasTriggeringProc.Add(Tuple.Create(procEffectMask, pair.Value)); + } } + } + } - proc.aura.SetLastProcSuccessTime(now); + void TriggerAurasProcOnEvent(CalcDamageInfo damageInfo) + { + DamageInfo dmgInfo = new DamageInfo(damageInfo); + TriggerAurasProcOnEvent(null, null, damageInfo.target, damageInfo.procAttacker, damageInfo.procVictim, ProcFlagsSpellType.None, ProcFlagsSpellPhase.None, dmgInfo.GetHitMask(), null, dmgInfo, null); + } - // Note: must SetCantProc(false) before return + void TriggerAurasProcOnEvent(List myProcAuras, List targetProcAuras, Unit actionTarget, ProcFlags typeMaskActor, ProcFlags typeMaskActionTarget, ProcFlagsSpellType spellTypeMask, ProcFlagsSpellPhase spellPhaseMask, ProcFlagsHit hitMask, Spell spell, DamageInfo damageInfo, HealInfo healInfo) + { + // prepare data for self trigger + ProcEventInfo myProcEventInfo = new ProcEventInfo(this, actionTarget, actionTarget, typeMaskActor, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + List> myAurasTriggeringProc = new List>(); + if (typeMaskActor != 0 && CanProc()) + GetProcAurasTriggeredOnEvent(myAurasTriggeringProc, myProcAuras, myProcEventInfo); + + // prepare data for target trigger + ProcEventInfo targetProcEventInfo = new ProcEventInfo(this, actionTarget, this, typeMaskActionTarget, spellTypeMask, spellPhaseMask, hitMask, spell, damageInfo, healInfo); + List> targetAurasTriggeringProc = new List>(); + if (typeMaskActionTarget != 0 && actionTarget && actionTarget.CanProc()) + actionTarget.GetProcAurasTriggeredOnEvent(targetAurasTriggeringProc, targetProcAuras, targetProcEventInfo); + + TriggerAurasProcOnEvent(myProcEventInfo, myAurasTriggeringProc); + + if (typeMaskActionTarget != 0 && actionTarget) + actionTarget.TriggerAurasProcOnEvent(targetProcEventInfo, targetAurasTriggeringProc); + } + + void TriggerAurasProcOnEvent(ProcEventInfo eventInfo, List> aurasTriggeringProc) + { + foreach (var aurAppProc in aurasTriggeringProc) + { + AuraApplication aurApp = aurAppProc.Item2; + uint procEffectMask = aurAppProc.Item1; + + if (aurApp.GetRemoveMode() != 0) + continue; + + SpellInfo spellInfo = aurApp.GetBase().GetSpellInfo(); if (spellInfo.HasAttribute(SpellAttr3.DisableProc)) SetCantProc(true); - bool handled = proc.aura.CallScriptProcHandlers(aurApp, eventInfo); - - // "handled" is needed as long as proc can be handled in multiple places - if (!handled) - { - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell {0} (triggered with value by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), Id); - takeCharges = true; - } - - if (!handled) - { - for (byte effIndex = 0; effIndex < SpellConst.MaxEffects; ++effIndex) - { - if (!Convert.ToBoolean(proc.effMask & (1 << effIndex))) - continue; - - AuraEffect triggeredByAura = proc.aura.GetEffect(effIndex); - Contract.Assert(triggeredByAura != null); - - bool prevented = proc.aura.CallScriptEffectProcHandlers(triggeredByAura, aurApp, eventInfo); - if (prevented) - { - takeCharges = true; - continue; - } - - switch (triggeredByAura.GetAuraType()) - { - case AuraType.ProcTriggerSpell: - { - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell {0} (triggered by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); - // Don`t drop charge or add cooldown for not started trigger - if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpell, procFlag, procExtra)) - takeCharges = true; - break; - } - case AuraType.ProcTriggerDamage: - { - // target has to be valid - if (eventInfo.GetProcTarget() == null) - break; - - triggeredByAura.HandleProcTriggerDamageAuraProc(aurApp, eventInfo); // this function is part of the new proc system - takeCharges = true; - break; - } - case AuraType.ManaShield: - case AuraType.Dummy: - { - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell id {0} (triggered by {1} dummy aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); - if (HandleDummyAuraProc(target, damage, triggeredByAura, procSpell, procFlag, procExtra)) - takeCharges = true; - break; - } - case AuraType.ProcOnPowerAmount2: - case AuraType.ProcOnPowerAmount: - { - triggeredByAura.HandleProcTriggerSpellOnPowerAmountAuraProc(aurApp, eventInfo); - takeCharges = true; - break; - } - case AuraType.ObsModPower: - case AuraType.ModSpellCritChance: - case AuraType.ModDamagePercentTaken: - case AuraType.ModMeleeHaste: - case AuraType.ModMeleeHaste3: - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell id {0} (triggered by {1} aura of spell {2})", spellInfo.Id, isVictim ? "a victim's" : "an attacker's", triggeredByAura.GetId()); - takeCharges = true; - break; - case AuraType.OverrideClassScripts: - { - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell id {0} (triggered by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); - if (HandleOverrideClassScriptAuraProc(target, damage, triggeredByAura, procSpell)) - takeCharges = true; - break; - } - case AuraType.RaidProcFromChargeWithValue: - { - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting mending (triggered by {0} dummy aura of spell {1})", - (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); - - HandleAuraRaidProcFromChargeWithValue(triggeredByAura); - takeCharges = true; - break; - } - case AuraType.RaidProcFromCharge: - { - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting mending (triggered by {0} dummy aura of spell {1})", - (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); - - HandleAuraRaidProcFromCharge(triggeredByAura); - takeCharges = true; - break; - } - case AuraType.ProcTriggerSpellWithValue: - { - Log.outDebug(LogFilter.Spells, "ProcDamageAndSpell: casting spell {0} (triggered with value by {1} aura of spell {2})", spellInfo.Id, (isVictim ? "a victim's" : "an attacker's"), triggeredByAura.GetId()); - - if (HandleProcTriggerSpell(target, damage, triggeredByAura, procSpell, procFlag, procExtra)) - takeCharges = true; - break; - } - case AuraType.ModCastingSpeedNotStack: - // Skip melee hits or instant cast spells - if (procSpell != null && procSpell.CalcCastTime() != 0) - takeCharges = true; - break; - case AuraType.ReflectSpellsSchool: - // Skip Melee hits and spells ws wrong school - if (procSpell != null && Convert.ToBoolean(triggeredByAura.GetMiscValue() & (int)procSpell.SchoolMask)) // School check - takeCharges = true; - break; - case AuraType.SpellMagnet: - // Skip Melee hits and targets with magnet aura - if (procSpell != null && (triggeredByAura.GetBase().GetUnitOwner().ToUnit() == ToUnit())) // Magnet - takeCharges = true; - break; - case AuraType.ModPowerCostSchoolPct: - case AuraType.ModPowerCostSchool: - // Skip melee hits and spells ws wrong school or zero cost - if (procSpell != null && Convert.ToBoolean(triggeredByAura.GetMiscValue() & (int)procSpell.SchoolMask)) // School check - { - var costs = procSpell.CalcPowerCost(this, procSpell.GetSchoolMask()); - var m = costs.Find(cost => cost.Amount > 0); - if (m != null) - takeCharges = true; - } - break; - case AuraType.MechanicImmunity: - // Compare mechanic - if (procSpell != null && procSpell.Mechanic == (Mechanics)triggeredByAura.GetMiscValue()) - takeCharges = true; - break; - case AuraType.ModMechanicResistance: - // Compare mechanic - if (procSpell != null && procSpell.Mechanic == (Mechanics)triggeredByAura.GetMiscValue()) - takeCharges = true; - break; - case AuraType.ModSpellDamageFromCaster: - // Compare casters - if (triggeredByAura.GetCasterGUID() == target.GetGUID()) - takeCharges = true; - break; - // CC Auras which use their amount 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: - case AuraType.ModRoot2: - { - // chargeable mods are breaking on hit - if (useCharges) - takeCharges = true; - else - { - // Spell own direct damage at apply wont break the CC - if (procSpell != null && (procSpell.Id == triggeredByAura.GetId())) - { - Aura aura = triggeredByAura.GetBase(); - // called from spellcast, should not have ticked yet - if (aura.GetDuration() == aura.GetMaxDuration()) - break; - } - int damageLeft = triggeredByAura.GetAmount(); - // No damage left - if (damageLeft < damage) - proc.aura.Remove(); - else - triggeredByAura.SetAmount((int)(damageLeft - damage)); - } - break; - } - default: - // nothing do, just charges counter - takeCharges = true; - break; - } - proc.aura.CallScriptAfterEffectProcHandlers(triggeredByAura, aurApp, eventInfo); - } - } - - if (prepare && takeCharges && cooldown != TimeSpan.Zero) - proc.aura.AddProcCooldown(now + cooldown); - - // Remove charge (aura can be removed by triggers) - if (prepare && useCharges && takeCharges) - { - // Set charge drop delay (only for missiles) - if (procExtra.HasAnyFlag(ProcFlagsExLegacy.Reflect) && target && procSpell != null && procSpell.Speed > 0.0f) - { - // Set up missile speed based delay (from Spell.cpp: Spell.AddUnitTarget().L2237) - uint delay = (uint)Math.Floor(Math.Max(target.GetDistance(this), 5.0f) / procSpell.Speed * 1000.0f); - // Schedule charge drop - proc.aura.DropChargeDelayed(delay); - } - else - proc.aura.DropCharge(); - } - - proc.aura.CallScriptAfterProcHandlers(aurApp, eventInfo); + aurApp.GetBase().TriggerProcOnEvent(procEffectMask, aurApp, eventInfo); if (spellInfo.HasAttribute(SpellAttr3.DisableProc)) SetCantProc(false); } - - // Cleanup proc requirements - if (procExtra.HasAnyFlag(ProcFlagsExLegacy.InternalTriggered | ProcFlagsExLegacy.InternalCantProc)) - SetCantProc(false); } void SetCantProc(bool apply) @@ -2415,114 +2170,6 @@ namespace Game.Entities } } - bool IsTriggeredAtSpellProcEvent(Unit victim, Aura aura, SpellInfo procSpell, ProcFlags procFlag, ProcFlagsExLegacy procExtra, WeaponAttackType attType, bool isVictim, bool active, ref SpellProcEventEntry spellProcEvent) - { - SpellInfo spellInfo = aura.GetSpellInfo(); - - // let the aura be handled by new proc system if it has new entry - if (Global.SpellMgr.GetSpellProcEntry(spellInfo.Id) != null) - return false; - - // Get proc Event Entry - spellProcEvent = Global.SpellMgr.GetSpellProcEvent(spellInfo.Id); - - // Get EventProcFlag - ProcFlags EventProcFlag; - if (spellProcEvent != null && spellProcEvent.procFlags != 0) // if exist get custom spellProcEvent.procFlags - EventProcFlag = (ProcFlags)spellProcEvent.procFlags; - else - EventProcFlag = spellInfo.ProcFlags; // else get from spell proto - // Continue if no trigger exist - if (EventProcFlag == 0) - return false; - - // Check spellProcEvent data requirements - if (!Global.SpellMgr.IsSpellProcEventCanTriggeredBy(spellInfo, spellProcEvent, EventProcFlag, procSpell, procFlag, procExtra, active)) - return false; - // In most cases req get honor or XP from kill - if (EventProcFlag.HasAnyFlag(ProcFlags.Kill) && IsTypeId(TypeId.Player)) - { - bool allow = false; - - if (victim != null) - allow = ToPlayer().isHonorOrXPTarget(victim); - - // Shadow Word: Death - can trigger from every kill - if (aura.GetId() == 32409) - allow = true; - if (!allow) - return false; - } - // Aura added by spell can`t trigger from self (prevent drop charges/do triggers) - // But except periodic and kill triggers (can triggered from self) - if (procSpell != null && procSpell.Id == spellInfo.Id - && !spellInfo.ProcFlags.HasAnyFlag(ProcFlags.TakenPeriodic | ProcFlags.Kill)) - return false; - - // Check if current equipment allows aura to proc - if (!isVictim && IsTypeId(TypeId.Player)) - { - Player player = ToPlayer(); - if (spellInfo.EquippedItemClass == ItemClass.Weapon) - { - Item item = null; - if (attType == WeaponAttackType.BaseAttack || attType == WeaponAttackType.RangedAttack) - item = player.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.MainHand); - else if (attType == WeaponAttackType.OffAttack) - item = player.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); - - if (player.IsInFeralForm()) - return false; - - if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Weapon || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & spellInfo.EquippedItemSubClassMask)) - return false; - } - else if (spellInfo.EquippedItemClass == ItemClass.Armor) - { - // Check if player is wearing shield - Item item = player.GetUseableItemByPos(InventorySlots.Bag0, EquipmentSlot.OffHand); - if (item == null || item.IsBroken() || item.GetTemplate().GetClass() != ItemClass.Armor || !Convert.ToBoolean((1 << (int)item.GetTemplate().GetSubClass()) & spellInfo.EquippedItemSubClassMask)) - return false; - } - } - - return true; - } - - bool RollProcResult(Unit victim, Aura aura, WeaponAttackType attType, bool isVictim, SpellProcEventEntry spellProcEvent) - { - SpellInfo spellInfo = aura.GetSpellInfo(); - // Get chance from spell - float chance = spellInfo.ProcChance; - // If in spellProcEvent exist custom chance, chance = spellProcEvent.customChance; - if (spellProcEvent != null && spellProcEvent.customChance != 0.0f) - chance = spellProcEvent.customChance; - // If PPM exist calculate chance from PPM - if (spellProcEvent != null && spellProcEvent.ppmRate != 0) - { - if (!isVictim) - { - uint WeaponSpeed = GetBaseAttackTime(attType); - chance = GetPPMProcChance(WeaponSpeed, spellProcEvent.ppmRate, spellInfo); - } - else - { - uint WeaponSpeed = victim.GetBaseAttackTime(attType); - chance = victim.GetPPMProcChance(WeaponSpeed, spellProcEvent.ppmRate, spellInfo); - } - } - - if (spellInfo.ProcBasePPM > 0.0f) - chance = aura.CalcPPMProcChance(isVictim ? victim : this); - - // Apply chance modifer aura - Player modOwner = GetSpellModOwner(); - if (modOwner != null) - modOwner.ApplySpellMod(spellInfo.Id, SpellModOp.ChanceOfSuccess, ref chance); - - return RandomHelper.randChance(chance); - } - uint getTransForm() { return m_transform; } public bool HasStealthAura() { return HasAuraType(AuraType.ModStealth); } @@ -2542,19 +2189,11 @@ namespace Game.Entities return spellInfo.GetSpellSpecific() == SpellSpecificType.MagePolymorph; } - public int HealBySpell(Unit victim, SpellInfo spellInfo, uint addHealth, bool critical = false) - { - uint absorb = 0; - // calculate heal absorb and reduce healing - CalcHealAbsorb(victim, spellInfo, ref addHealth, ref absorb); - - int gain = DealHeal(victim, addHealth); - SendHealSpellLog(victim, spellInfo.Id, addHealth, (uint)(addHealth - gain), absorb, critical); - return gain; - } - public int DealHeal(Unit victim, uint addhealth) + public void DealHeal(HealInfo healInfo) { int gain = 0; + Unit victim = healInfo.GetTarget(); + uint addhealth = healInfo.GetHeal(); if (victim.IsAIEnabled) victim.GetAI().HealReceived(this, addhealth); @@ -2595,27 +2234,26 @@ namespace Game.Entities player.UpdateCriteria(CriteriaTypes.HighestHealingReceived, addhealth); } - return gain; + if (gain != 0) + healInfo.SetEffectiveHeal(gain > 0 ? (uint)gain : 0u); } - void SendHealSpellLog(Unit victim, uint spellID, uint health, uint overHeal, uint absorbed, bool crit) + void SendHealSpellLog(HealInfo healInfo, bool critical = false) { SpellHealLog spellHealLog = new SpellHealLog(); - spellHealLog.TargetGUID = victim.GetGUID(); + spellHealLog.TargetGUID = healInfo.GetTarget().GetGUID(); spellHealLog.CasterGUID = GetGUID(); - spellHealLog.SpellID = spellID; - spellHealLog.Health = health; - spellHealLog.OverHeal = overHeal; - spellHealLog.Absorbed = absorbed; + spellHealLog.SpellID = healInfo.GetSpellInfo().Id; + spellHealLog.Health = healInfo.GetHeal(); + spellHealLog.OverHeal = healInfo.GetHeal() - healInfo.GetEffectiveHeal(); + spellHealLog.Absorbed = healInfo.GetAbsorb(); - spellHealLog.Crit = crit; + spellHealLog.Crit = critical; /// @todo: 6.x Has to be implemented /* - spellHealLog.WriteBit("Multistrike"); - var hasCritRollMade = spellHealLog.WriteBit("HasCritRollMade"); var hasCritRollNeeded = spellHealLog.WriteBit("HasCritRollNeeded"); var hasLogData = spellHealLog.WriteBit("HasLogData"); @@ -2629,237 +2267,18 @@ namespace Game.Entities if (hasLogData) SpellParsers.ReadSpellCastLogData(packet); */ - spellHealLog.LogData.Initialize(victim); + spellHealLog.LogData.Initialize(healInfo.GetTarget()); SendCombatLogMessage(spellHealLog); } - bool HandleDummyAuraProc(Unit victim, uint damage, AuraEffect triggeredByAura, SpellInfo procSpell, ProcFlags procFlag, ProcFlagsExLegacy procEx) + public uint HealBySpell(HealInfo healInfo, bool critical = false) { - SpellInfo dummySpell = triggeredByAura.GetSpellInfo(); - int triggerAmount = triggeredByAura.GetAmount(); + // calculate heal absorb and reduce healing + CalcHealAbsorb(healInfo); - Item castItem = !triggeredByAura.GetBase().GetCastItemGUID().IsEmpty() && IsTypeId(TypeId.Player) - ? ToPlayer().GetItemByGuid(triggeredByAura.GetBase().GetCastItemGUID()) : null; - - Unit target = victim; - uint triggered_spell_id = triggeredByAura.GetSpellEffectInfo().TriggerSpell; - - // processed charge only counting case - if (triggered_spell_id == 0) - return true; - - SpellInfo triggerEntry = Global.SpellMgr.GetSpellInfo(triggered_spell_id); - if (triggerEntry == null) - { - Log.outError(LogFilter.Unit, "Unit.HandleDummyAuraProc: Spell {0} has non-existing triggered spell {1}", dummySpell.Id, triggered_spell_id); - return false; - } - - CastSpell(target, triggered_spell_id, true, castItem, triggeredByAura); - - return true; - } - - bool HandleProcTriggerSpell(Unit victim, uint damage, AuraEffect triggeredByAura, SpellInfo procSpell, ProcFlags procFlags, ProcFlagsExLegacy procEx) - { - // Get triggered aura spell info - SpellInfo auraSpellInfo = triggeredByAura.GetSpellInfo(); - - // Basepoints of trigger aura - int triggerAmount = triggeredByAura.GetAmount(); - - // Set trigger spell id, target, custom basepoints - uint trigger_spell_id = triggeredByAura.GetSpellEffectInfo().TriggerSpell; - - Unit target = null; - int basepoints0 = 0; - - if (triggeredByAura.GetAuraType() == AuraType.ProcTriggerSpellWithValue) - basepoints0 = triggerAmount; - - Item castItem = !triggeredByAura.GetBase().GetCastItemGUID().IsEmpty() && IsTypeId(TypeId.Player) ? ToPlayer().GetItemByGuid(triggeredByAura.GetBase().GetCastItemGUID()) : null; - - // All ok. Check current trigger spell - SpellInfo triggerEntry = Global.SpellMgr.GetSpellInfo(trigger_spell_id); - if (triggerEntry == null) - { - // Don't cast unknown spell - Log.outError(LogFilter.Unit, "Unit.HandleProcTriggerSpell: Spell {0} has 0 in EffectTriggered[{1}]. Unhandled custom case?", auraSpellInfo.Id, triggeredByAura.GetEffIndex()); - return false; - } - - // not allow proc extra attack spell at extra attack - if (m_extraAttacks != 0 && triggerEntry.HasEffect(SpellEffectName.AddExtraAttacks)) - return false; - - // Custom basepoints/target for exist spell - // dummy basepoints or other customs - switch (trigger_spell_id) - { - // Maelstrom Weapon - case 53817: - { - // Item - Shaman T10 Enhancement 4P Bonus - AuraEffect aurEff = GetAuraEffect(70832, 0); - if (aurEff != null) - { - Aura maelstrom = GetAura(53817); - if (maelstrom != null) - if ((maelstrom.GetStackAmount() == maelstrom.GetSpellInfo().StackAmount) && RandomHelper.randChance(aurEff.GetAmount())) - CastSpell(this, 70831, true, castItem, triggeredByAura); - } - break; - } - } - - // extra attack should hit same target - if (triggerEntry.HasEffect(SpellEffectName.AddExtraAttacks)) - target = victim; - - // try detect target manually if not set - if (target == null) - target = !procFlags.HasAnyFlag(ProcFlags.DoneSpellMagicDmgClassPos | ProcFlags.DoneSpellNoneDmgClassPos) && triggerEntry.IsPositive() ? this : victim; - - if (basepoints0 != 0) - CastCustomSpell(target, trigger_spell_id, basepoints0, 0, 0, true, castItem, triggeredByAura); - else - CastSpell(target, trigger_spell_id, true, castItem, triggeredByAura); - - return true; - } - bool HandleOverrideClassScriptAuraProc(Unit victim, uint damage, AuraEffect triggeredByAura, SpellInfo procSpell) - { - int scriptId = triggeredByAura.GetMiscValue(); - - if (victim == null || !victim.IsAlive()) - return false; - - Item castItem = !triggeredByAura.GetBase().GetCastItemGUID().IsEmpty() && IsTypeId(TypeId.Player) ? ToPlayer().GetItemByGuid(triggeredByAura.GetBase().GetCastItemGUID()) : null; - - uint triggered_spell_id = 0; - - switch (scriptId) - { - case 4537: // Dreamwalker Raiment 6 pieces bonus - triggered_spell_id = 28750; // Blessing of the Claw - break; - default: - break; - } - - // not processed - if (triggered_spell_id == 0) - return false; - - // standard non-dummy case - SpellInfo triggerEntry = Global.SpellMgr.GetSpellInfo(triggered_spell_id); - - if (triggerEntry == null) - { - Log.outError(LogFilter.Unit, "Unit.HandleOverrideClassScriptAuraProc: Spell {0} triggering for class script id {1}", triggered_spell_id, scriptId); - return false; - } - - CastSpell(victim, triggered_spell_id, true, castItem, triggeredByAura); //Do not allow auras to proc from ef - - return true; - } - bool HandleAuraRaidProcFromChargeWithValue(AuraEffect triggeredByAura) - { - // aura can be deleted at casts - SpellInfo spellProto = triggeredByAura.GetSpellInfo(); - uint triggered_spell_id = 0; - int heal = triggeredByAura.GetAmount(); - ObjectGuid caster_guid = triggeredByAura.GetCasterGUID(); - - // Currently only Prayer of Mending - if (triggered_spell_id == 0) - { - Log.outDebug(LogFilter.Spells, "Unit.HandleAuraRaidProcFromChargeWithValue, received not handled spell: {0}", spellProto.Id); - return false; - } - - // jumps - int jumps = triggeredByAura.GetBase().GetCharges() - 1; - - // current aura expire - triggeredByAura.GetBase().SetCharges(1); // will removed at next charges decrease - - // next target selection - if (jumps > 0) - { - Unit caster = triggeredByAura.GetCaster(); - if (caster != null) - { - SpellEffectInfo effect = triggeredByAura.GetSpellEffectInfo(); - float radius = effect.CalcRadius(caster); - Unit target = GetNextRandomRaidMemberOrPet(radius); - if (target != null) - { - CastCustomSpell(target, spellProto.Id, heal, 0, 0, true, null, triggeredByAura, caster_guid); - Aura aura = target.GetAura(spellProto.Id, caster.GetGUID()); - if (aura != null) - aura.SetCharges(jumps); - } - } - } - - // heal - CastSpell(this, triggered_spell_id, true, null, triggeredByAura, caster_guid); - return true; - } - bool HandleAuraRaidProcFromCharge(AuraEffect triggeredByAura) - { - // aura can be deleted at casts - SpellInfo spellProto = triggeredByAura.GetSpellInfo(); - - uint damageSpellId; - switch (spellProto.Id) - { - case 57949: // shiver - damageSpellId = 57952; - break; - case 59978: // shiver - damageSpellId = 59979; - break; - case 43593: // Cold Stare - damageSpellId = 43594; - break; - default: - Log.outError(LogFilter.Unit, "Unit.HandleAuraRaidProcFromCharge, received unhandled spell: {0}", spellProto.Id); - return false; - } - - ObjectGuid caster_guid = triggeredByAura.GetCasterGUID(); - - // jumps - int jumps = triggeredByAura.GetBase().GetCharges() - 1; - - // current aura expire - triggeredByAura.GetBase().SetCharges(1); // will removed at next charges decrease - - // next target selection - if (jumps > 0) - { - Unit caster = triggeredByAura.GetCaster(); - if (caster != null) - { - SpellEffectInfo effect = triggeredByAura.GetSpellEffectInfo(); - float radius = effect.CalcRadius(caster); - Unit target = GetNextRandomRaidMemberOrPet(radius); - if (target != null) - { - CastSpell(target, spellProto, true, null, triggeredByAura, caster_guid); - Aura aura = target.GetAura(spellProto.Id, caster.GetGUID()); - if (aura != null) - aura.SetCharges(jumps); - } - } - } - - CastSpell(this, damageSpellId, true, null, triggeredByAura, caster_guid); - - return true; + DealHeal(healInfo); + SendHealSpellLog(healInfo, critical); + return healInfo.GetEffectiveHeal(); } public int GetMaxPositiveAuraModifier(AuraType auratype) @@ -3242,82 +2661,6 @@ namespace Game.Entities } } - public bool isNonTriggerAura(AuraType type) - { - switch (type) - { - case AuraType.ModPowerRegen: - case AuraType.ReducePushback: - return true; - } - return false; - } - public bool isTriggerAura(AuraType type) - { - switch (type) - { - case AuraType.ProcOnPowerAmount: - case AuraType.ProcOnPowerAmount2: - 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.RaidProcFromCharge: - case AuraType.RaidProcFromChargeWithValue: - 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; - } - public DiminishingLevels GetDiminishing(DiminishingGroup group) { foreach (var dim in m_Diminishing) @@ -4810,7 +4153,7 @@ namespace Game.Entities } } } - public Aura _TryStackingOrRefreshingExistingAura(SpellInfo newAura, uint effMask, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), int castItemLevel = -1) + public Aura _TryStackingOrRefreshingExistingAura(SpellInfo newAura, uint effMask, Unit caster, int[] baseAmount = null, Item castItem = null, ObjectGuid casterGUID = default(ObjectGuid), bool resetPeriodicTimer = true, int castItemLevel = -1) { Contract.Assert(!casterGUID.IsEmpty() || caster); @@ -4867,7 +4210,7 @@ namespace Game.Entities } // try to increase stack amount - foundAura.ModStackAmount(1); + foundAura.ModStackAmount(1, AuraRemoveMode.Default, resetPeriodicTimer); return foundAura; } } diff --git a/Game/Scripting/SpellScript.cs b/Game/Scripting/SpellScript.cs index 64ec59c92..c228eb832 100644 --- a/Game/Scripting/SpellScript.cs +++ b/Game/Scripting/SpellScript.cs @@ -775,6 +775,7 @@ namespace Game.Scripting public delegate void AuraEffectAbsorbDelegate(AuraEffect aura, DamageInfo damageInfo, ref uint absorbAmount); public delegate void AuraEffectSplitDelegate(AuraEffect aura, DamageInfo damageInfo, uint splitAmount); public delegate bool AuraCheckProcDelegate(ProcEventInfo info); + public delegate bool AuraCheckEffectProcDelegate(AuraEffect aura , ProcEventInfo info); public delegate void AuraProcDelegate(ProcEventInfo info); public delegate void AuraEffectProcDelegate(AuraEffect aura, ProcEventInfo info); @@ -961,12 +962,28 @@ namespace Game.Scripting AuraCheckProcDelegate _HandlerScript; } + public class CheckEffectProcHandler : EffectBase + { + public CheckEffectProcHandler(AuraCheckEffectProcDelegate handlerScript, uint effIndex, AuraType effName) : base(effIndex, effName) + { + _HandlerScript = handlerScript; + } + + public bool Call(AuraEffect aurEff, ProcEventInfo eventInfo) + { + return _HandlerScript(aurEff, eventInfo); + } + + AuraCheckEffectProcDelegate _HandlerScript; + } + public class AuraProcHandler { public AuraProcHandler(AuraProcDelegate handlerScript) { _HandlerScript = handlerScript; } + public void Call(ProcEventInfo eventInfo) { _HandlerScript(eventInfo); @@ -1069,6 +1086,10 @@ namespace Game.Scripting if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `DoCheckProc` of AuraScript won't be executed", entry.Id, m_scriptName); + foreach (var eff in DoCheckEffectProc) + if (eff.GetAffectedEffectsMask(entry) == 0) + Log.outError(LogFilter.Scripts, "Spell `{0}` Effect `{1}` of script `{2}` did not match dbc effect data - handler bound to hook `DoCheckEffectProc` of AuraScript won't be executed", entry.Id, eff.ToString(), m_scriptName); + foreach (var eff in DoPrepareProc) if (!entry.HasEffect(SpellEffectName.ApplyAura) && !entry.HasAreaAuraEffect()) Log.outError(LogFilter.Scripts, "Spell `{0}` of script `{1}` does not have apply aura effect - handler bound to hook `DoPrepareProc` of AuraScript won't be executed", entry.Id, m_scriptName); @@ -1246,6 +1267,11 @@ namespace Game.Scripting // where function is: bool function (ProcEventInfo& eventInfo); public List DoCheckProc = new List(); + // executed when aura effect checks if it can proc the aura + // example: DoCheckEffectProc += AuraCheckEffectProcFn(class::function, EffectIndexSpecifier, EffectAuraNameSpecifier); + // where function is bool function (AuraEffect const* aurEff, ProcEventInfo& eventInfo); + public List DoCheckEffectProc = new List(); + // executed before aura procs (possibility to prevent charge drop/cooldown) // example: DoPrepareProc += AuraProcFn(class.function); // where function is: void function (ProcEventInfo& eventInfo); @@ -1383,6 +1409,7 @@ namespace Game.Scripting case AuraScriptHookType.EffectAfterManaShield: case AuraScriptHookType.EffectSplit: case AuraScriptHookType.CheckProc: + case AuraScriptHookType.CheckEffectProc: case AuraScriptHookType.PrepareProc: case AuraScriptHookType.Proc: case AuraScriptHookType.AfterProc: diff --git a/Game/Server/WorldManager.cs b/Game/Server/WorldManager.cs index b1f702cb7..cf6c83ff1 100644 --- a/Game/Server/WorldManager.cs +++ b/Game/Server/WorldManager.cs @@ -433,9 +433,6 @@ namespace Game Log.outInfo(LogFilter.ServerLoading, "Loading Spell Learn Spells..."); Global.SpellMgr.LoadSpellLearnSpells(); - Log.outInfo(LogFilter.ServerLoading, "Loading Spell Proc Event conditions..."); - Global.SpellMgr.LoadSpellProcEvents(); - Log.outInfo(LogFilter.ServerLoading, "Loading Spell Proc conditions and data..."); Global.SpellMgr.LoadSpellProcs(); diff --git a/Game/Spells/Auras/Aura.cs b/Game/Spells/Auras/Aura.cs index 786f2c6a9..1c2222cda 100644 --- a/Game/Spells/Auras/Aura.cs +++ b/Game/Spells/Auras/Aura.cs @@ -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 diff --git a/Game/Spells/Auras/AuraEffect.cs b/Game/Spells/Auras/AuraEffect.cs index cc583dcf1..65f041560 100644 --- a/Game/Spells/Auras/AuraEffect.cs +++ b/Game/Spells/Auras/AuraEffect.cs @@ -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(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) { diff --git a/Game/Spells/Spell.cs b/Game/Spells/Spell.cs index 89aae4d39..ab0730644 100644 --- a/Game/Spells/Spell.cs +++ b/Game/Spells/Spell.cs @@ -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; + } } diff --git a/Game/Spells/SpellEffects.cs b/Game/Spells/SpellEffects.cs index af763db1d..2ff6352ae 100644 --- a/Game/Spells/SpellEffects.cs +++ b/Game/Spells/SpellEffects.cs @@ -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()) diff --git a/Game/Spells/SpellManager.cs b/Game/Spells/SpellManager.cs index b6b828a47..61324af7a 100644 --- a/Game/Spells/SpellManager.cs +++ b/Game/Spells/SpellManager.cs @@ -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(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(1); - spellProcEvent.spellFamilyName = (SpellFamilyNames)result.Read(2); - spellProcEvent.spellFamilyMask = new FlagArray128(result.Read(3), result.Read(4), result.Read(5), result.Read(6)); - spellProcEvent.procFlags = result.Read(7); - spellProcEvent.procEx = result.Read(8); - spellProcEvent.ppmRate = result.Read(9); - spellProcEvent.customChance = result.Read(10); - spellProcEvent.cooldown = result.Read(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(0); - - bool allRanks = false; - if (spellId < 0) + do { - allRanks = true; - spellId = -spellId; - } + int spellId = result.Read(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(1); - baseProcEntry.SpellFamilyName = (SpellFamilyNames)result.Read(2); - baseProcEntry.SpellFamilyMask = new FlagArray128(result.Read(3), result.Read(4), result.Read(5), result.Read(6)); - baseProcEntry.ProcFlags = (ProcFlags)result.Read(7); - baseProcEntry.SpellTypeMask = (ProcFlagsSpellType)result.Read(8); - baseProcEntry.SpellPhaseMask = (ProcFlagsSpellPhase)result.Read(9); - baseProcEntry.HitMask = (ProcFlagsHit)result.Read(10); - baseProcEntry.AttributesMask = result.Read(11); - baseProcEntry.ProcsPerMinute = result.Read(12); - baseProcEntry.Chance = result.Read(13); - baseProcEntry.Cooldown = result.Read(14); - baseProcEntry.Charges = result.Read(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(1); + baseProcEntry.SpellFamilyName = (SpellFamilyNames)result.Read(2); + baseProcEntry.SpellFamilyMask = new FlagArray128(result.Read(3), result.Read(4), result.Read(5), result.Read(6)); + baseProcEntry.ProcFlags = (ProcFlags)result.Read(7); + baseProcEntry.SpellTypeMask = (ProcFlagsSpellType)result.Read(8); + baseProcEntry.SpellPhaseMask = (ProcFlagsSpellPhase)result.Read(9); + baseProcEntry.HitMask = (ProcFlagsHit)result.Read(10); + baseProcEntry.AttributesMask = (ProcAttributes)result.Read(11); + baseProcEntry.ProcsPerMinute = result.Read(12); + baseProcEntry.Chance = result.Read(13); + baseProcEntry.Cooldown = result.Read(14); + baseProcEntry.Charges = result.Read(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(1); spe.PPMChance = result.Read(2); - spe.procEx = (ProcFlagsExLegacy)result.Read(3); + spe.procEx = result.Read(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 mSpellSpellGroup = new MultiMap(); MultiMap mSpellGroupSpell = new MultiMap(); Dictionary mSpellGroupStack = new Dictionary(); - Dictionary mSpellProcEventMap = new Dictionary(); Dictionary mSpellProcMap = new Dictionary(); Dictionary mSpellThreatMap = new Dictionary(); Dictionary mSpellPetAuraMap = new Dictionary(); @@ -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 diff --git a/Scripts/Spells/Druid.cs b/Scripts/Spells/Druid.cs index a8c5ea288..0c9873720 100644 --- a/Scripts/Spells/Druid.cs +++ b/Scripts/Spells/Druid.cs @@ -643,12 +643,12 @@ namespace Scripts.Spells.Druid void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) { PreventDefaultAction(); - SpellInfo spellInfo = eventInfo.GetSpellInfo(); - if (spellInfo == null) + Spell spell = eventInfo.GetProcSpell(); + if (spell == null) return; Unit caster = eventInfo.GetActor(); - var costs = spellInfo.CalcPowerCost(caster, spellInfo.GetSchoolMask()); + var costs = spell.GetPowerCost(); var m = costs.First(cost => { return cost.Power == PowerType.Mana; }); if (m == null) return; diff --git a/Scripts/Spells/Generic.cs b/Scripts/Spells/Generic.cs index 1ed6a068b..891ee3e51 100644 --- a/Scripts/Spells/Generic.cs +++ b/Scripts/Spells/Generic.cs @@ -2536,30 +2536,51 @@ namespace Scripts.Spells.Generic } } - [Script] + [Script("spell_pvp_trinket_shared_cd", SpellIds.WillOfTheForsakenCooldownTrigger)] + [Script("spell_wotf_shared_cd", SpellIds.WillOfTheForsakenCooldownTriggerWotf)] class spell_pvp_trinket_wotf_shared_cd : SpellScript { - public override bool Load() + public spell_pvp_trinket_wotf_shared_cd(uint triggered) { - return GetCaster().IsTypeId(TypeId.Player); + _triggered = triggered; } public override bool Validate(SpellInfo spellInfo) { - return ValidateSpellInfo(SpellIds.WillOfTheForsakenCooldownTrigger, SpellIds.WillOfTheForsakenCooldownTriggerWotf); + return ValidateSpellInfo(_triggered); } void HandleScript() { - // This is only needed because spells cast from spell_linked_spell are triggered by default - // Spell.SendSpellCooldown() skips all spells with TRIGGERED_IGNORE_SPELL_AND_CATEGORY_CD - GetCaster().GetSpellHistory().StartCooldown(GetSpellInfo(), 0, GetSpell()); + /* + * @workaround: PendingCast flag normally means 'triggered' spell, however + * if the spell is cast triggered, the core won't send SMSG_SPELL_GO packet + * so client never registers the cooldown (see Spell::IsNeedSendToClient) + * + * ServerToClient: SMSG_SPELL_GO (0x0132) Length: 42 ConnIdx: 0 Time: 07/19/2010 02:32:35.000 Number: 362675 + * Caster GUID: Full: Player + * Caster Unit GUID: Full: Player + * Cast Count: 0 + * Spell ID: 72752 (72752) + * Cast Flags: PendingCast, Unknown3, Unknown7 (265) + * Time: 3901468825 + * Hit Count: 1 + * [0] Hit GUID: Player + * Miss Count: 0 + * Target Flags: Unit (2) + * Target GUID: 0x0 + */ + + // Spell flags need further research, until then just cast not triggered + GetCaster().CastSpell((Unit)null, _triggered, false); } public override void Register() { AfterCast.Add(new CastHandler(HandleScript)); } + + uint _triggered; } [Script] diff --git a/Scripts/Spells/Items.cs b/Scripts/Spells/Items.cs index c188c3bcd..344d348e3 100644 --- a/Scripts/Spells/Items.cs +++ b/Scripts/Spells/Items.cs @@ -1444,10 +1444,10 @@ namespace Scripts.Spells.Items { bool CheckProc(ProcEventInfo eventInfo) { - SpellInfo spellInfo = eventInfo.GetSpellInfo(); - if (spellInfo != null) + Spell spell = eventInfo.GetProcSpell(); + if (spell != null) { - var costs = spellInfo.CalcPowerCost(GetTarget(), spellInfo.GetSchoolMask()); + var costs = spell.GetPowerCost(); var m = costs.FirstOrDefault(cost => { return cost.Power == PowerType.Mana && cost.Amount > 0; }); if (m != null) return true; diff --git a/Scripts/Spells/Mage.cs b/Scripts/Spells/Mage.cs index 647d7adf0..9b9ab7ac4 100644 --- a/Scripts/Spells/Mage.cs +++ b/Scripts/Spells/Mage.cs @@ -417,14 +417,14 @@ namespace Scripts.Spells.Mage bool CheckProc(ProcEventInfo eventInfo) { - return eventInfo.GetDamageInfo().GetSpellInfo() != null; // eventInfo.GetSpellInfo() + return eventInfo.GetProcSpell() != null; } void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) { PreventDefaultAction(); - var costs = eventInfo.GetDamageInfo().GetSpellInfo().CalcPowerCost(GetTarget(), eventInfo.GetDamageInfo().GetSchoolMask()); + var costs = eventInfo.GetProcSpell().GetPowerCost(); var m = costs.Find(cost => cost.Power == PowerType.Mana); if (m != null) { diff --git a/Scripts/Spells/Shaman.cs b/Scripts/Spells/Shaman.cs index 8a468d8ab..a52b8d0b7 100644 --- a/Scripts/Spells/Shaman.cs +++ b/Scripts/Spells/Shaman.cs @@ -442,17 +442,14 @@ namespace Scripts.Spells.Shaman bool CheckProc(ProcEventInfo eventInfo) { - return eventInfo.GetSpellInfo() != null; + return eventInfo.GetProcSpell() != null; } void HandleProc(AuraEffect aurEff, ProcEventInfo eventInfo) { PreventDefaultAction(); - SpellInfo spellInfo = eventInfo.GetSpellInfo(); - if (spellInfo == null) - return; - var costs = eventInfo.GetDamageInfo().GetSpellInfo().CalcPowerCost(GetTarget(), eventInfo.GetDamageInfo().GetSchoolMask()); + var costs = eventInfo.GetProcSpell().GetPowerCost(); var m = costs.Find(cost => cost.Power == PowerType.Mana); if (m != null) {