diff options
| author | joschiwald <joschiwald.trinity@gmail.com> | 2017-08-13 12:03:43 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-08-13 12:03:43 +0200 |
| commit | 1f8fc55ac9d5b12acc56fa72e4ae61f24b910b0d (patch) | |
| tree | 6c264542125fa45a169df89d1c16651481fead37 /src/server/game/Spells/SpellMgr.cpp | |
| parent | cb4dd92629149d94e5ded6c2e8b073173bbe042a (diff) | |
Core/Spells: Implementation of QAston proc system (#20131)
* Core/Spells: Implementation of QAston proc system
- Move checks from Unit::IsTriggeredAtSpellProcEvent (old system) to Aura::IsProcTriggeredOnEvent (new system)
- killed charge counter from SpellModifier and Player system for handling charges... no point in having 3 different systems doing the same thing
- Automatically add default entries to spellProcMap, based on spellinfo (else auras won't proc without an entry) Based on old Unit::ProcDamageAndSpellFor
- Old Unit::ProcDamageAndSpellFor renamed to Unit::ProcSkillsAndReactives and made private, will no longer handle auras.
- Start making use of HealInfo::AbsorbHeal in unit calculations, add effective healing info to HealInfo struct
- Changes in spell reflection system, emulates old behaviour, delaying aura drop
- Removed old charge count hacks in SpellMgr::LoadSpellInfoCorrections
- Removed bogus error log when procChance is 0: Some auras have initial 0 procChance but modified by SPELLMOD_CHANCE_OF_SUCCESS
- Fixed TriggerAurasProcOnEvent logic that tried to trigger twice from actor.
- Allow non damaging spells with DamageClass Melee or Ranged to proc character enchants. Ref issue #17034:
* http://web.archive.org/web/20110309092008/http://elitistjerks.com/f47/t49865-paladin_retribution_pve/
* When an auto-attack lands (does not dodge/parry/miss) that can proc a seal the of the following things happen independently of each other (see 2 roll system).
* 1) A "hidden strike" which uses melee combat mechanics occurs. If it lands it refreshes/stacks SoV DoT. Only white swings can trigger a refresh or stack. (This hidden strike mechanic can also proc things like berserking..)
* 2) A weapon damage based proc will occur if you used a special (CS/DS/judge) or if you have a 5 stack (from auto attacks). This attack can not be avoided.
* Holy Vengeance is the "hidden strike" it has an apply aura effect and damage class melee.
- Fixed Blood Tap interaction with Death Runes (btw, don't know what was going on with those MiscValueB, spell 45529 doesn't have any MiscValueB in SPELL_EFFECT_ACTIVATE_RUNE)
- Ported some AuraEffect checks from old Unit.cpp function. added new AuraScript hook to check procs of an specific effect
- Allow only AuraEffects that passed the check to proc, this won't block whole aura from proccing (and lose charges) if at least one of the effects procs, though
- Changes in spell mod system (for SPELLMOD_CASTING_TIME). fixes #17558.
- Added an exception for SPELLMOD_CRITICAL_CHANCE too, fixes #15193
(cherry picked from commit e641d0c7d71bdb21e443bf52bf508b1581e07839)
# Conflicts:
# sql/base/auth_database.sql
# src/server/game/Entities/Player/Player.cpp
# src/server/game/Entities/Player/Player.h
# src/server/game/Entities/Unit/Unit.cpp
# src/server/game/Entities/Unit/Unit.h
# src/server/game/Spells/Auras/SpellAuraEffects.cpp
# src/server/game/Spells/Auras/SpellAuras.cpp
# src/server/game/Spells/Auras/SpellAuras.h
# src/server/game/Spells/Spell.cpp
# src/server/game/Spells/SpellEffects.cpp
# src/server/game/Spells/SpellInfo.cpp
# src/server/game/Spells/SpellMgr.cpp
# src/server/game/Spells/SpellMgr.h
# src/server/game/Spells/SpellScript.cpp
Diffstat (limited to 'src/server/game/Spells/SpellMgr.cpp')
| -rw-r--r-- | src/server/game/Spells/SpellMgr.cpp | 618 |
1 files changed, 260 insertions, 358 deletions
diff --git a/src/server/game/Spells/SpellMgr.cpp b/src/server/game/Spells/SpellMgr.cpp index 58dae0ed12a..2710df635c5 100644 --- a/src/server/game/Spells/SpellMgr.cpp +++ b/src/server/game/Spells/SpellMgr.cpp @@ -28,6 +28,7 @@ #include "ObjectMgr.h" #include "Player.h" #include "SharedDefines.h" +#include "Spell.h" #include "SpellAuraDefines.h" #include "SpellInfo.h" #include <G3D/g3dmath.h> @@ -799,171 +800,6 @@ SpellGroupStackRule SpellMgr::GetSpellGroupStackRule(SpellGroup group) const return SPELL_GROUP_STACK_RULE_DEFAULT; } -SpellProcEventEntry const* SpellMgr::GetSpellProcEvent(uint32 spellId) const -{ - SpellProcEventMap::const_iterator itr = mSpellProcEventMap.find(spellId); - if (itr != mSpellProcEventMap.end()) - return &itr->second; - return NULL; -} - -bool SpellMgr::IsSpellProcEventCanTriggeredBy(SpellInfo const* spellProto, SpellProcEventEntry const* spellProcEvent, uint32 EventProcFlag, SpellInfo const* procSpell, uint32 procFlags, uint32 procExtra, bool active) const -{ - // No extra req need - uint32 procEvent_procEx = PROC_EX_NONE; - - // check prockFlags for condition - if ((procFlags & EventProcFlag) == 0) - return false; - - bool hasFamilyMask = false; - - /** - - * @brief Check auras procced by periodics - - *Only damaging Dots can proc auras with PROC_FLAG_TAKEN_DAMAGE - - *Only Dots can proc if ONLY has PROC_FLAG_DONE_PERIODIC or PROC_FLAG_TAKEN_PERIODIC. - - *Hots can proc if ONLY has PROC_FLAG_DONE_PERIODIC and spellfamily != 0 - - *Only Dots can proc auras with PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG or PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG - - *Only Hots can proc auras with PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS or PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS - - *Only Dots can proc auras with PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG or PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG - - *Only Hots can proc auras with PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS or PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS - - * @param procSpell the spell proccing the aura - * @param procFlags proc_flags of spellProc - * @param procExtra proc_EX of procSpell - * @param EventProcFlag proc_flags of aura to be procced - * @param spellProto SpellInfo of aura to be procced - - */ - - /// 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 & PROC_FLAG_TAKEN_DAMAGE && EventProcFlag & PROC_FLAG_TAKEN_DAMAGE) - return true; - - if (procFlags & PROC_FLAG_DONE_PERIODIC && EventProcFlag & PROC_FLAG_DONE_PERIODIC) - { - if (procExtra & PROC_EX_INTERNAL_HOT) - { - if (EventProcFlag == PROC_FLAG_DONE_PERIODIC) - { - /// no aura with only PROC_FLAG_DONE_PERIODIC and spellFamilyName == 0 can proc from a HOT. - if (!spellProto->SpellFamilyName) - return false; - } - /// Aura must have positive procflags for a HOT to proc - else if (!(EventProcFlag & (PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_POS))) - return false; - } - /// Aura must have negative or neutral(PROC_FLAG_DONE_PERIODIC only) procflags for a DOT to proc - else if (EventProcFlag != PROC_FLAG_DONE_PERIODIC) - if (!(EventProcFlag & (PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG | PROC_FLAG_DONE_SPELL_NONE_DMG_CLASS_NEG))) - return false; - } - - if (procFlags & PROC_FLAG_TAKEN_PERIODIC && EventProcFlag & PROC_FLAG_TAKEN_PERIODIC) - { - if (procExtra & PROC_EX_INTERNAL_HOT) - { - /// No aura that only has PROC_FLAG_TAKEN_PERIODIC can proc from a HOT. - if (EventProcFlag == PROC_FLAG_TAKEN_PERIODIC) - return false; - /// Aura must have positive procflags for a HOT to proc - if (!(EventProcFlag & (PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS | PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS))) - return false; - } - /// Aura must have negative or neutral(PROC_FLAG_TAKEN_PERIODIC only) procflags for a DOT to proc - else if (EventProcFlag != PROC_FLAG_TAKEN_PERIODIC) - if (!(EventProcFlag & (PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_NEG | PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_NEG))) - return false; - } - // Trap casts are active by default - if (procFlags & PROC_FLAG_DONE_TRAP_ACTIVATION) - active = true; - - // Always trigger for this - if (procFlags & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_DEATH)) - return true; - - if (spellProcEvent) // Exist event data - { - // Store extra req - procEvent_procEx = spellProcEvent->procEx; - - // For melee triggers - if (procSpell == NULL) - { - // Check (if set) for school (melee attack has Normal school) - if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0) - return false; - } - else // For spells need check school/spell family/family mask - { - // Check (if set) for school - if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & procSpell->SchoolMask) == 0) - return false; - - // Check (if set) for spellFamilyName - if (spellProcEvent->spellFamilyName && (spellProcEvent->spellFamilyName != procSpell->SpellFamilyName)) - return false; - - // spellFamilyName is Ok need check for spellFamilyMask if present - if (spellProcEvent->spellFamilyMask) - { - if (!(spellProcEvent->spellFamilyMask & procSpell->SpellFamilyFlags)) - return false; - hasFamilyMask = true; - // Some spells are not considered as active even with spellfamilyflags set - if (!(procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL)) - active = true; - } - } - } - - if (procExtra & (PROC_EX_INTERNAL_REQ_FAMILY)) - { - if (!hasFamilyMask) - return false; - } - - // Check for extra req (if none) and hit/crit - if (procEvent_procEx == PROC_EX_NONE) - { - // No extra req, so can trigger only for hit/crit - spell has to be active - if ((procExtra & (PROC_EX_NORMAL_HIT|PROC_EX_CRITICAL_HIT)) && active) - return true; - } - else // Passive spells hits here only if resist/reflect/immune/evade - { - if (procExtra & AURA_SPELL_PROC_EX_MASK) - { - // if spell marked as procing only from not active spells - if (active && procEvent_procEx & PROC_EX_NOT_ACTIVE_SPELL) - return false; - // if spell marked as procing only from active spells - if (!active && procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL) - return false; - // Exist req for PROC_EX_EX_TRIGGER_ALWAYS - if (procEvent_procEx & PROC_EX_EX_TRIGGER_ALWAYS) - return true; - // PROC_EX_NOT_ACTIVE_SPELL and PROC_EX_ONLY_ACTIVE_SPELL flags handle: if passed checks before - if ((procExtra & (PROC_EX_NORMAL_HIT|PROC_EX_CRITICAL_HIT)) && ((procEvent_procEx & (AURA_SPELL_PROC_EX_MASK)) == 0)) - return true; - } - // Check Extra Requirement like (hit/crit/miss/resist/parry/dodge/block/immune/reflect/absorb and other) - if (procEvent_procEx & procExtra) - return true; - } - return false; -} - SpellProcEntry const* SpellMgr::GetSpellProcEntry(uint32 spellId) const { SpellProcMap::const_iterator itr = mSpellProcMap.find(spellId); @@ -984,10 +820,37 @@ bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcE if (eventInfo.GetActionTarget() && !actor->isHonorOrXPTarget(eventInfo.GetActionTarget())) return false; + // check power requirement + if (procEntry.AttributesMask & PROC_ATTR_REQ_POWER_COST) + { + if (!eventInfo.GetProcSpell()) + return false; + + std::vector<SpellPowerCost> const& costs = eventInfo.GetProcSpell()->GetPowerCost(); + auto m = std::find_if(costs.begin(), costs.end(), [](SpellPowerCost const& cost) { return cost.Amount > 0; }); + if (m == costs.end()) + return false; + } + // always trigger for these types if (eventInfo.GetTypeMask() & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_DEATH)) return true; + // do triggered cast checks + if (!(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC)) + { + if (Spell const* spell = eventInfo.GetProcSpell()) + { + if (spell->IsTriggered()) + { + SpellInfo const* spellInfo = spell->GetSpellInfo(); + if (!spellInfo->HasAttribute(SPELL_ATTR3_TRIGGERED_CAN_TRIGGER_PROC_2) && + !spellInfo->HasAttribute(SPELL_ATTR2_TRIGGERED_CAN_TRIGGER_PROC)) + return false; + } + } + } + // check school mask (if set) for other trigger types if (procEntry.SchoolMask && !(eventInfo.GetSchoolMask() & procEntry.SchoolMask)) return false; @@ -1790,226 +1653,283 @@ void SpellMgr::LoadSpellGroupStackRules() TC_LOG_INFO("server.loading", ">> Loaded %u spell group stack rules in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } -void SpellMgr::LoadSpellProcEvents() -{ - uint32 oldMSTime = getMSTime(); +// Used for prepare can/can't triggr aura +static bool InitTriggerAuraData(); +// Define can trigger auras +static bool isTriggerAura[TOTAL_AURAS]; +// Triggered always, even from triggered spells +static bool isAlwaysTriggeredAura[TOTAL_AURAS]; +// Prepare lists +static bool procPrepared = InitTriggerAuraData(); - mSpellProcEventMap.clear(); // need for reload case - - // 0 1 2 3 4 5 6 7 8 9 10 11 - QueryResult result = WorldDatabase.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, SpellFamilyMask3, procFlags, procEx, ppmRate, CustomChance, Cooldown FROM spell_proc_event"); - if (!result) +// List of auras that CAN be trigger but may not exist in spell_proc_event +// in most case need for drop charges +// in some types of aura need do additional check +// for example SPELL_AURA_MECHANIC_IMMUNITY - need check for mechanic +bool InitTriggerAuraData() +{ + for (uint16 i = 0; i < TOTAL_AURAS; ++i) { - TC_LOG_INFO("server.loading", ">> Loaded 0 spell proc event conditions. DB table `spell_proc_event` is empty."); - return; + isTriggerAura[i] = false; + isAlwaysTriggeredAura[i] = false; } + isTriggerAura[SPELL_AURA_DUMMY] = true; + isTriggerAura[SPELL_AURA_MOD_CONFUSE] = true; + isTriggerAura[SPELL_AURA_MOD_THREAT] = true; + isTriggerAura[SPELL_AURA_MOD_STUN] = true; // Aura does not have charges but needs to be removed on trigger + isTriggerAura[SPELL_AURA_MOD_DAMAGE_DONE] = true; + isTriggerAura[SPELL_AURA_MOD_DAMAGE_TAKEN] = true; + isTriggerAura[SPELL_AURA_MOD_RESISTANCE] = true; + isTriggerAura[SPELL_AURA_MOD_STEALTH] = true; + isTriggerAura[SPELL_AURA_MOD_FEAR] = true; // Aura does not have charges but needs to be removed on trigger + isTriggerAura[SPELL_AURA_MOD_ROOT] = true; + isTriggerAura[SPELL_AURA_TRANSFORM] = true; + isTriggerAura[SPELL_AURA_REFLECT_SPELLS] = true; + isTriggerAura[SPELL_AURA_DAMAGE_IMMUNITY] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_DAMAGE] = true; + isTriggerAura[SPELL_AURA_MOD_CASTING_SPEED_NOT_STACK] = true; + isTriggerAura[SPELL_AURA_SCHOOL_ABSORB] = true; // Savage Defense untested + isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL_PCT] = true; + isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL] = true; + isTriggerAura[SPELL_AURA_REFLECT_SPELLS_SCHOOL] = true; + isTriggerAura[SPELL_AURA_MECHANIC_IMMUNITY] = true; + isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN] = true; + isTriggerAura[SPELL_AURA_SPELL_MAGNET] = true; + isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER] = true; + isTriggerAura[SPELL_AURA_MOD_POWER_REGEN_PERCENT] = true; + isTriggerAura[SPELL_AURA_ADD_CASTER_HIT_TRIGGER] = true; + isTriggerAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; + isTriggerAura[SPELL_AURA_MOD_MECHANIC_RESISTANCE] = true; + isTriggerAura[SPELL_AURA_RANGED_ATTACK_POWER_ATTACKER_BONUS] = true; + isTriggerAura[SPELL_AURA_MOD_MELEE_HASTE] = true; + isTriggerAura[SPELL_AURA_MOD_MELEE_HASTE_3] = true; + isTriggerAura[SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE] = true; + isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE] = true; + isTriggerAura[SPELL_AURA_MOD_SPELL_DAMAGE_FROM_CASTER] = true; + isTriggerAura[SPELL_AURA_MOD_SPELL_CRIT_CHANCE] = true; + isTriggerAura[SPELL_AURA_ABILITY_IGNORE_AURASTATE] = true; + isTriggerAura[SPELL_AURA_MOD_ROOT_2] = true; + + isAlwaysTriggeredAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_FEAR] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_ROOT] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_STUN] = true; + isAlwaysTriggeredAura[SPELL_AURA_TRANSFORM] = true; + isAlwaysTriggeredAura[SPELL_AURA_SPELL_MAGNET] = true; + isAlwaysTriggeredAura[SPELL_AURA_SCHOOL_ABSORB] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_STEALTH] = true; + isAlwaysTriggeredAura[SPELL_AURA_MOD_ROOT_2] = true; - uint32 count = 0; + return true; +} - do - { - Field* fields = result->Fetch(); +void SpellMgr::LoadSpellProcs() +{ + uint32 oldMSTime = getMSTime(); - int32 spellId = fields[0].GetInt32(); + mSpellProcMap.clear(); // need for reload case - bool allRanks = false; - if (spellId < 0) - { - allRanks = true; - spellId = -spellId; - } + // 0 1 2 3 4 5 6 + QueryResult result = WorldDatabase.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"); - SpellInfo const* spellInfo = GetSpellInfo(spellId); - if (!spellInfo) + uint32 count = 0; + if (result) + { + do { - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc_event` does not exist.", spellId); - continue; - } + Field* fields = result->Fetch(); - if (allRanks) - { - if (!spellInfo->IsRanked()) - TC_LOG_ERROR("sql.sql", "The spell %u is listed in `spell_proc_event` with all ranks, but spell has no ranks.", spellId); + int32 spellId = fields[0].GetInt32(); - if (spellInfo->GetFirstRankSpell()->Id != uint32(spellId)) + bool allRanks = false; + if (spellId < 0) { - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc_event` is not first rank of spell.", spellId); - continue; + allRanks = true; + spellId = -spellId; } - } - - SpellProcEventEntry spellProcEvent; - spellProcEvent.schoolMask = fields[1].GetInt8(); - spellProcEvent.spellFamilyName = fields[2].GetUInt16(); - spellProcEvent.spellFamilyMask[0] = fields[3].GetUInt32(); - spellProcEvent.spellFamilyMask[1] = fields[4].GetUInt32(); - spellProcEvent.spellFamilyMask[2] = fields[5].GetUInt32(); - spellProcEvent.spellFamilyMask[3] = fields[6].GetUInt32(); - spellProcEvent.procFlags = fields[7].GetUInt32(); - spellProcEvent.procEx = fields[8].GetUInt32(); - spellProcEvent.ppmRate = fields[9].GetFloat(); - spellProcEvent.customChance = fields[10].GetFloat(); - spellProcEvent.cooldown = fields[11].GetUInt32(); - - while (spellInfo) - { - if (mSpellProcEventMap.find(spellInfo->Id) != mSpellProcEventMap.end()) + SpellInfo const* spellInfo = GetSpellInfo(spellId); + if (!spellInfo) { - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc_event` already has its first rank in table.", spellInfo->Id); - break; + TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` does not exist", spellId); + continue; } - if (!spellInfo->ProcFlags && !spellProcEvent.procFlags) - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc_event` is probably not a triggered spell.", spellInfo->Id); + if (allRanks) + { + if (!spellInfo->IsRanked()) + TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` with all ranks, but spell has no ranks.", spellId); - mSpellProcEventMap[spellInfo->Id] = spellProcEvent; + if (spellInfo->GetFirstRankSpell()->Id != uint32(spellId)) + { + TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` is not the first rank of the spell.", spellId); + continue; + } + } - if (allRanks) - spellInfo = spellInfo->GetNextRankSpell(); - else - break; - } + SpellProcEntry baseProcEntry; + + baseProcEntry.SchoolMask = fields[1].GetInt8(); + baseProcEntry.SpellFamilyName = fields[2].GetUInt16(); + baseProcEntry.SpellFamilyMask[0] = fields[3].GetUInt32(); + baseProcEntry.SpellFamilyMask[1] = fields[4].GetUInt32(); + baseProcEntry.SpellFamilyMask[2] = fields[5].GetUInt32(); + baseProcEntry.SpellFamilyMask[3] = fields[6].GetUInt32(); + baseProcEntry.ProcFlags = fields[7].GetUInt32(); + baseProcEntry.SpellTypeMask = fields[8].GetUInt32(); + baseProcEntry.SpellPhaseMask = fields[9].GetUInt32(); + baseProcEntry.HitMask = fields[10].GetUInt32(); + baseProcEntry.AttributesMask = fields[11].GetUInt32(); + baseProcEntry.ProcsPerMinute = fields[12].GetFloat(); + baseProcEntry.Chance = fields[13].GetFloat(); + baseProcEntry.Cooldown = Milliseconds(fields[14].GetUInt32()); + baseProcEntry.Charges = fields[15].GetUInt8(); + + while (spellInfo) + { + if (mSpellProcMap.find(spellInfo->Id) != mSpellProcMap.end()) + { + TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` already has its first rank in the table.", spellInfo->Id); + break; + } - ++count; + SpellProcEntry procEntry = SpellProcEntry(baseProcEntry); + + // take defaults from dbcs + if (!procEntry.ProcFlags) + procEntry.ProcFlags = spellInfo->ProcFlags; + if (!procEntry.Charges) + procEntry.Charges = spellInfo->ProcCharges; + if (!procEntry.Chance && !procEntry.ProcsPerMinute) + procEntry.Chance = float(spellInfo->ProcChance); + + // validate data + if (procEntry.SchoolMask & ~SPELL_SCHOOL_MASK_ALL) + TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has wrong `SchoolMask` set: %u", spellInfo->Id, procEntry.SchoolMask); + if (procEntry.SpellFamilyName && !DB2Manager::IsValidSpellFamiliyName(SpellFamilyNames(procEntry.SpellFamilyName))) + TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has wrong `SpellFamilyName` set: %u", spellInfo->Id, procEntry.SpellFamilyName); + if (procEntry.Chance < 0) + { + TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has negative value in the `Chance` field", spellInfo->Id); + procEntry.Chance = 0; + } + if (procEntry.ProcsPerMinute < 0) + { + TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has negative value in the `ProcsPerMinute` field", spellInfo->Id); + procEntry.ProcsPerMinute = 0; + } + if (!procEntry.ProcFlags) + TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u doesn't have any `ProcFlags` value defined, proc will not be triggered.", spellInfo->Id); + if (procEntry.SpellTypeMask & ~PROC_SPELL_TYPE_MASK_ALL) + TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has wrong `SpellTypeMask` set: %u", spellInfo->Id, procEntry.SpellTypeMask); + if (procEntry.SpellTypeMask && !(procEntry.ProcFlags & (SPELL_PROC_FLAG_MASK | PERIODIC_PROC_FLAG_MASK))) + TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has `SpellTypeMask` value defined, but it will not be used for the defined `ProcFlags` value.", spellInfo->Id); + if (!procEntry.SpellPhaseMask && procEntry.ProcFlags & REQ_SPELL_PHASE_PROC_FLAG_MASK) + TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u doesn't have any `SpellPhaseMask` value defined, but it is required for the defined `ProcFlags` value. Proc will not be triggered.", spellInfo->Id); + if (procEntry.SpellPhaseMask & ~PROC_SPELL_PHASE_MASK_ALL) + TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has wrong `SpellPhaseMask` set: %u", spellInfo->Id, procEntry.SpellPhaseMask); + if (procEntry.SpellPhaseMask && !(procEntry.ProcFlags & REQ_SPELL_PHASE_PROC_FLAG_MASK)) + TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has a `SpellPhaseMask` value defined, but it will not be used for the defined `ProcFlags` value.", spellInfo->Id); + if (procEntry.HitMask & ~PROC_HIT_MASK_ALL) + TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has wrong `HitMask` set: %u", spellInfo->Id, procEntry.HitMask); + if (procEntry.HitMask && !(procEntry.ProcFlags & TAKEN_HIT_PROC_FLAG_MASK || (procEntry.ProcFlags & DONE_HIT_PROC_FLAG_MASK && (!procEntry.SpellPhaseMask || procEntry.SpellPhaseMask & (PROC_SPELL_PHASE_HIT | PROC_SPELL_PHASE_FINISH))))) + TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has `HitMask` value defined, but it will not be used for defined `ProcFlags` and `SpellPhaseMask` values.", spellInfo->Id); + + mSpellProcMap[spellInfo->Id] = procEntry; + + if (allRanks) + spellInfo = spellInfo->GetNextRankSpell(); + else + break; + } + ++count; + } while (result->NextRow()); } - while (result->NextRow()); + else + TC_LOG_INFO("server.loading", ">> Loaded 0 spell proc conditions and data. DB table `spell_proc` is empty."); - TC_LOG_INFO("server.loading", ">> Loaded %u extra spell proc event conditions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); -} + TC_LOG_INFO("server.loading", ">> Loaded %u spell proc conditions and data in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); -void SpellMgr::LoadSpellProcs() -{ - uint32 oldMSTime = getMSTime(); + // This generates default procs to retain compatibility with previous proc system + TC_LOG_INFO("server.loading", "Generating spell proc data from SpellMap..."); + count = 0; + oldMSTime = getMSTime(); - mSpellProcMap.clear(); // need for reload case + for (SpellInfo const* spellInfo : mSpellInfoMap) + { + if (!spellInfo) + continue; - // 0 1 2 3 4 5 6 - QueryResult result = WorldDatabase.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 (mSpellProcMap.find(spellInfo->Id) != mSpellProcMap.end()) + continue; - if (!result) - { - TC_LOG_INFO("server.loading", ">> Loaded 0 spell proc conditions and data. DB table `spell_proc` is empty."); - return; - } + bool found = false, addTriggerFlag = false; + for (SpellEffectInfo const* effect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE)) + { + if (!effect || !effect->IsEffect()) + continue; - uint32 count = 0; - do - { - Field* fields = result->Fetch(); + uint32 auraName = effect->ApplyAuraName; + if (!auraName) + continue; - int32 spellId = fields[0].GetInt32(); + if (!isTriggerAura[auraName]) + continue; - bool allRanks = false; - if (spellId < 0) - { - allRanks = true; - spellId = -spellId; - } + found = true; - SpellInfo const* spellInfo = GetSpellInfo(spellId); - if (!spellInfo) - { - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` does not exist", spellId); - continue; + if (!addTriggerFlag && isAlwaysTriggeredAura[auraName]) + addTriggerFlag = true; + break; } - if (allRanks) - { - if (!spellInfo->IsRanked()) - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` with all ranks, but spell has no ranks.", spellId); + if (!found) + continue; - if (spellInfo->GetFirstRankSpell()->Id != uint32(spellId)) - { - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` is not the first rank of the spell.", spellId); - continue; - } - } + if (!spellInfo->ProcFlags) + continue; - SpellProcEntry baseProcEntry; + SpellProcEntry procEntry; + procEntry.SchoolMask = 0; + procEntry.SpellFamilyName = spellInfo->SpellFamilyName; + procEntry.ProcFlags = spellInfo->ProcFlags; + for (SpellEffectInfo const* effect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE)) + if (effect && effect->IsEffect() && isTriggerAura[effect->ApplyAuraName]) + procEntry.SpellFamilyMask |= effect->SpellClassMask; - baseProcEntry.SchoolMask = fields[1].GetInt8(); - baseProcEntry.SpellFamilyName = fields[2].GetUInt16(); - baseProcEntry.SpellFamilyMask[0] = fields[3].GetUInt32(); - baseProcEntry.SpellFamilyMask[1] = fields[4].GetUInt32(); - baseProcEntry.SpellFamilyMask[2] = fields[5].GetUInt32(); - baseProcEntry.SpellFamilyMask[3] = fields[6].GetUInt32(); - baseProcEntry.ProcFlags = fields[7].GetUInt32(); - baseProcEntry.SpellTypeMask = fields[8].GetUInt32(); - baseProcEntry.SpellPhaseMask = fields[9].GetUInt32(); - baseProcEntry.HitMask = fields[10].GetUInt32(); - baseProcEntry.AttributesMask = fields[11].GetUInt32(); - baseProcEntry.ProcsPerMinute = fields[12].GetFloat(); - baseProcEntry.Chance = fields[13].GetFloat(); - baseProcEntry.Cooldown = Milliseconds(fields[14].GetUInt32()); - baseProcEntry.Charges = fields[15].GetUInt8(); + procEntry.SpellTypeMask = PROC_SPELL_TYPE_MASK_ALL; + procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT; + procEntry.HitMask = PROC_HIT_NONE; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent - while (spellInfo) + // Reflect auras should only proc off reflects + for (SpellEffectInfo const* effect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE)) { - if (mSpellProcMap.find(spellInfo->Id) != mSpellProcMap.end()) + if (effect && (effect->IsAura(SPELL_AURA_REFLECT_SPELLS) || effect->IsAura(SPELL_AURA_REFLECT_SPELLS_SCHOOL))) { - TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_proc` already has its first rank in the table.", spellInfo->Id); + procEntry.HitMask = PROC_HIT_REFLECT; break; } + } - SpellProcEntry procEntry = SpellProcEntry(baseProcEntry); - - // take defaults from dbcs - if (!procEntry.ProcFlags) - procEntry.ProcFlags = spellInfo->ProcFlags; - if (!procEntry.Charges) - procEntry.Charges = spellInfo->ProcCharges; - if (!procEntry.Chance && !procEntry.ProcsPerMinute) - procEntry.Chance = float(spellInfo->ProcChance); - - // validate data - if (procEntry.SchoolMask & ~SPELL_SCHOOL_MASK_ALL) - TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has wrong `SchoolMask` set: %u", spellInfo->Id, procEntry.SchoolMask); - if (procEntry.SpellFamilyName && (procEntry.SpellFamilyName < 3 || procEntry.SpellFamilyName > 17 || procEntry.SpellFamilyName == 14 || procEntry.SpellFamilyName == 16)) - TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has wrong `SpellFamilyName` set: %u", spellInfo->Id, procEntry.SpellFamilyName); - if (procEntry.Chance < 0) - { - TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has negative value in the `Chance` field", spellInfo->Id); - procEntry.Chance = 0; - } - if (procEntry.ProcsPerMinute < 0) - { - TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has negative value in the `ProcsPerMinute` field", spellInfo->Id); - procEntry.ProcsPerMinute = 0; - } - if (procEntry.Chance == 0 && procEntry.ProcsPerMinute == 0) - TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u doesn't have any `Chance` and `ProcsPerMinute` values defined, proc will not be triggered", spellInfo->Id); - if (!procEntry.ProcFlags) - TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u doesn't have any `ProcFlags` value defined, proc will not be triggered.", spellInfo->Id); - if (procEntry.SpellTypeMask & ~PROC_SPELL_TYPE_MASK_ALL) - TC_LOG_ERROR("sql.sql", "`spell_proc` table entry for spellId %u has wrong `SpellTypeMask` set: %u", spellInfo->Id, procEntry.SpellTypeMask); - if (procEntry.SpellTypeMask && !(procEntry.ProcFlags & (SPELL_PROC_FLAG_MASK | PERIODIC_PROC_FLAG_MASK))) - TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has `SpellTypeMask` value defined, but it will not be used for the defined `ProcFlags` value.", spellInfo->Id); - if (!procEntry.SpellPhaseMask && procEntry.ProcFlags & REQ_SPELL_PHASE_PROC_FLAG_MASK) - TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u doesn't have any `SpellPhaseMask` value defined, but it is required for the defined `ProcFlags` value. Proc will not be triggered.", spellInfo->Id); - if (procEntry.SpellPhaseMask & ~PROC_SPELL_PHASE_MASK_ALL) - TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has wrong `SpellPhaseMask` set: %u", spellInfo->Id, procEntry.SpellPhaseMask); - if (procEntry.SpellPhaseMask && !(procEntry.ProcFlags & REQ_SPELL_PHASE_PROC_FLAG_MASK)) - TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has a `SpellPhaseMask` value defined, but it will not be used for the defined `ProcFlags` value.", spellInfo->Id); - if (procEntry.HitMask & ~PROC_HIT_MASK_ALL) - TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has wrong `HitMask` set: %u", spellInfo->Id, procEntry.HitMask); - if (procEntry.HitMask && !(procEntry.ProcFlags & TAKEN_HIT_PROC_FLAG_MASK || (procEntry.ProcFlags & DONE_HIT_PROC_FLAG_MASK && (!procEntry.SpellPhaseMask || procEntry.SpellPhaseMask & (PROC_SPELL_PHASE_HIT | PROC_SPELL_PHASE_FINISH))))) - TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has `HitMask` value defined, but it will not be used for defined `ProcFlags` and `SpellPhaseMask` values.", spellInfo->Id); - - mSpellProcMap[spellInfo->Id] = procEntry; + procEntry.AttributesMask = 0; + if (spellInfo->ProcFlags & PROC_FLAG_KILL) + procEntry.AttributesMask |= PROC_ATTR_REQ_EXP_OR_HONOR; + if (addTriggerFlag) + procEntry.AttributesMask |= PROC_ATTR_TRIGGERED_CAN_PROC; - if (allRanks) - spellInfo = spellInfo->GetNextRankSpell(); - else - break; - } + procEntry.ProcsPerMinute = 0; + procEntry.Chance = spellInfo->ProcChance; + procEntry.Cooldown = Milliseconds::zero(); + procEntry.Charges = spellInfo->ProcCharges; + + mSpellProcMap[spellInfo->Id] = procEntry; ++count; } - while (result->NextRow()); - TC_LOG_INFO("server.loading", ">> Loaded %u spell proc conditions and data in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); + TC_LOG_INFO("server.loading", ">> Generated spell proc data for %u spells in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); } void SpellMgr::LoadSpellThreats() @@ -3105,30 +3025,12 @@ void SpellMgr::LoadSpellInfoCorrections() const_cast<SpellEffectInfo*>(spellInfo->GetEffect(EFFECT_0))->TriggerSpell = 33760; }); - ApplySpellFix({ - 17941, // Shadow Trance - 22008, // Netherwind Focus - 34477, // Misdirection - 48108, // Hot Streak - 51124, // Killing Machine - 64823 // Item - Druid T8 Balance 4P Bonus - }, [](SpellInfo* spellInfo) - { - spellInfo->ProcCharges = 1; - }); - // Fingers of Frost ApplySpellFix({ 44544 }, [](SpellInfo* spellInfo) { const_cast<SpellEffectInfo*>(spellInfo->GetEffect(EFFECT_0))->SpellClassMask = flag128(685904631, 1151048, 0, 0); }); - // Ascendance (Talisman of Ascendance trinket) - ApplySpellFix({ 28200 }, [](SpellInfo* spellInfo) - { - spellInfo->ProcCharges = 6; - }); - // Oscillation Field ApplySpellFix({ 37408 }, [](SpellInfo* spellInfo) { |
