mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-17 16:10:49 +01:00
5814 lines
193 KiB
C++
5814 lines
193 KiB
C++
/*
|
||
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
|
||
*
|
||
* This program is free software; you can redistribute it and/or modify it
|
||
* under the terms of the GNU General Public License as published by the
|
||
* Free Software Foundation; either version 2 of the License, or (at your
|
||
* option) any later version.
|
||
*
|
||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
* more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License along
|
||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include "SpellMgr.h"
|
||
#include "BattlefieldMgr.h"
|
||
#include "BattlefieldWG.h"
|
||
#include "BattlegroundMgr.h"
|
||
#include "Chat.h"
|
||
#include "DatabaseEnv.h"
|
||
#include "DBCStores.h"
|
||
#include "Log.h"
|
||
#include "Map.h"
|
||
#include "MotionMaster.h"
|
||
#include "ObjectMgr.h"
|
||
#include "Player.h"
|
||
#include "SharedDefines.h"
|
||
#include "Spell.h"
|
||
#include "SpellAuraDefines.h"
|
||
#include "SpellInfo.h"
|
||
#include <G3D/g3dmath.h>
|
||
|
||
bool IsPrimaryProfessionSkill(uint32 skill)
|
||
{
|
||
SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(skill);
|
||
return pSkill && pSkill->CategoryID == SKILL_CATEGORY_PROFESSION;
|
||
}
|
||
|
||
bool IsWeaponSkill(uint32 skill)
|
||
{
|
||
SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(skill);
|
||
return pSkill && pSkill->CategoryID == SKILL_CATEGORY_WEAPON;
|
||
}
|
||
|
||
bool IsPartOfSkillLine(uint32 skillId, uint32 spellId)
|
||
{
|
||
SkillLineAbilityMapBounds skillBounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
|
||
for (SkillLineAbilityMap::const_iterator itr = skillBounds.first; itr != skillBounds.second; ++itr)
|
||
if (itr->second->SkillLine == skillId)
|
||
return true;
|
||
|
||
return false;
|
||
}
|
||
|
||
SpellMgr::SpellMgr() { }
|
||
|
||
SpellMgr::~SpellMgr()
|
||
{
|
||
UnloadSpellInfoStore();
|
||
}
|
||
|
||
SpellMgr* SpellMgr::instance()
|
||
{
|
||
static SpellMgr instance;
|
||
return &instance;
|
||
}
|
||
|
||
/// Some checks for spells, to prevent adding deprecated/broken spells for trainers, spell book, etc
|
||
bool SpellMgr::IsSpellValid(SpellInfo const* spellInfo, Player* player, bool msg)
|
||
{
|
||
// not exist
|
||
if (!spellInfo)
|
||
return false;
|
||
|
||
bool needCheckReagents = false;
|
||
|
||
// check effects
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
switch (spellInfo->Effects[i].Effect)
|
||
{
|
||
case 0:
|
||
continue;
|
||
|
||
// craft spell for crafting non-existed item (break client recipes list show)
|
||
case SPELL_EFFECT_CREATE_ITEM:
|
||
case SPELL_EFFECT_CREATE_ITEM_2:
|
||
{
|
||
if (spellInfo->Effects[i].ItemType == 0)
|
||
{
|
||
// skip auto-loot crafting spells, it does not need explicit item info (but has special fake items sometimes).
|
||
if (!spellInfo->IsLootCrafting())
|
||
{
|
||
if (msg)
|
||
{
|
||
if (player)
|
||
ChatHandler(player->GetSession()).PSendSysMessage("The craft spell %u does not have a create item entry.", spellInfo->Id);
|
||
else
|
||
TC_LOG_ERROR("sql.sql", "The craft spell %u does not have a create item entry.", spellInfo->Id);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
}
|
||
// also possible IsLootCrafting case but fake items must exist anyway
|
||
else if (!sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType))
|
||
{
|
||
if (msg)
|
||
{
|
||
if (player)
|
||
ChatHandler(player->GetSession()).PSendSysMessage("Craft spell %u has created a non-existing item in DB (Entry: %u) and then...", spellInfo->Id, spellInfo->Effects[i].ItemType);
|
||
else
|
||
TC_LOG_ERROR("sql.sql", "Craft spell %u has created a non-existing item in DB (Entry: %u) and then...", spellInfo->Id, spellInfo->Effects[i].ItemType);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
needCheckReagents = true;
|
||
break;
|
||
}
|
||
case SPELL_EFFECT_LEARN_SPELL:
|
||
{
|
||
SpellInfo const* spellInfo2 = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell);
|
||
if (!IsSpellValid(spellInfo2, player, msg))
|
||
{
|
||
if (msg)
|
||
{
|
||
if (player)
|
||
ChatHandler(player->GetSession()).PSendSysMessage("Spell %u learn to broken spell %u, and then...", spellInfo->Id, spellInfo->Effects[i].TriggerSpell);
|
||
else
|
||
TC_LOG_ERROR("sql.sql", "Spell %u learn to invalid spell %u, and then...", spellInfo->Id, spellInfo->Effects[i].TriggerSpell);
|
||
}
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (needCheckReagents)
|
||
{
|
||
for (uint8 j = 0; j < MAX_SPELL_REAGENTS; ++j)
|
||
{
|
||
if (spellInfo->Reagent[j] > 0 && !sObjectMgr->GetItemTemplate(spellInfo->Reagent[j]))
|
||
{
|
||
if (msg)
|
||
{
|
||
if (player)
|
||
ChatHandler(player->GetSession()).PSendSysMessage("Craft spell %u refers a non-existing reagent in DB item (Entry: %u) and then...", spellInfo->Id, spellInfo->Reagent[j]);
|
||
else
|
||
TC_LOG_ERROR("sql.sql", "Craft spell %u refers to a non-existing reagent in DB, item (Entry: %u) and then...", spellInfo->Id, spellInfo->Reagent[j]);
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
uint32 SpellMgr::GetSpellDifficultyId(uint32 spellId) const
|
||
{
|
||
SpellDifficultySearcherMap::const_iterator i = mSpellDifficultySearcherMap.find(spellId);
|
||
return i == mSpellDifficultySearcherMap.end() ? 0 : i->second;
|
||
}
|
||
|
||
void SpellMgr::SetSpellDifficultyId(uint32 spellId, uint32 id)
|
||
{
|
||
if (uint32 i = GetSpellDifficultyId(spellId))
|
||
TC_LOG_ERROR("spells", "SpellMgr::SetSpellDifficultyId: The spell %u already has spellDifficultyId %u. Will override with spellDifficultyId %u.", spellId, i, id);
|
||
mSpellDifficultySearcherMap[spellId] = id;
|
||
}
|
||
|
||
uint32 SpellMgr::GetSpellIdForDifficulty(uint32 spellId, Unit const* caster) const
|
||
{
|
||
if (!GetSpellInfo(spellId))
|
||
return spellId;
|
||
|
||
if (!caster || !caster->GetMap() || !caster->GetMap()->IsDungeon())
|
||
return spellId;
|
||
|
||
uint32 mode = uint32(caster->GetMap()->GetSpawnMode());
|
||
if (mode >= MAX_DIFFICULTY)
|
||
{
|
||
TC_LOG_ERROR("spells", "SpellMgr::GetSpellIdForDifficulty: Incorrect difficulty for spell %u.", spellId);
|
||
return spellId; //return source spell
|
||
}
|
||
|
||
uint32 difficultyId = GetSpellDifficultyId(spellId);
|
||
if (!difficultyId)
|
||
return spellId; //return source spell, it has only REGULAR_DIFFICULTY
|
||
|
||
SpellDifficultyEntry const* difficultyEntry = sSpellDifficultyStore.LookupEntry(difficultyId);
|
||
if (!difficultyEntry)
|
||
{
|
||
TC_LOG_ERROR("spells", "SpellMgr::GetSpellIdForDifficulty: SpellDifficultyEntry was not found for spell %u. This should never happen.", spellId);
|
||
return spellId; //return source spell
|
||
}
|
||
|
||
if (difficultyEntry->DifficultySpellID[mode] <= 0 && mode > DUNGEON_DIFFICULTY_HEROIC)
|
||
{
|
||
TC_LOG_DEBUG("spells", "SpellMgr::GetSpellIdForDifficulty: spell %u mode %u spell is nullptr, using mode %u", spellId, mode, mode - 2);
|
||
mode -= 2;
|
||
}
|
||
|
||
if (difficultyEntry->DifficultySpellID[mode] <= 0)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "SpellMgr::GetSpellIdForDifficulty: spell %u mode %u spell is 0. Check spelldifficulty_dbc!", spellId, mode);
|
||
return spellId;
|
||
}
|
||
|
||
TC_LOG_DEBUG("spells", "SpellMgr::GetSpellIdForDifficulty: spellid for spell %u in mode %u is %d", spellId, mode, difficultyEntry->DifficultySpellID[mode]);
|
||
return uint32(difficultyEntry->DifficultySpellID[mode]);
|
||
}
|
||
|
||
SpellInfo const* SpellMgr::GetSpellForDifficultyFromSpell(SpellInfo const* spell, Unit const* caster) const
|
||
{
|
||
if (!spell)
|
||
return nullptr;
|
||
|
||
uint32 newSpellId = GetSpellIdForDifficulty(spell->Id, caster);
|
||
SpellInfo const* newSpell = GetSpellInfo(newSpellId);
|
||
if (!newSpell)
|
||
{
|
||
TC_LOG_DEBUG("spells", "SpellMgr::GetSpellForDifficultyFromSpell: spell %u not found. Check spelldifficulty_dbc!", newSpellId);
|
||
return spell;
|
||
}
|
||
|
||
TC_LOG_DEBUG("spells", "SpellMgr::GetSpellForDifficultyFromSpell: Spell id for instance mode is %u (original %u)", newSpell->Id, spell->Id);
|
||
return newSpell;
|
||
}
|
||
|
||
SpellChainNode const* SpellMgr::GetSpellChainNode(uint32 spell_id) const
|
||
{
|
||
SpellChainMap::const_iterator itr = mSpellChains.find(spell_id);
|
||
if (itr == mSpellChains.end())
|
||
return nullptr;
|
||
|
||
return &itr->second;
|
||
}
|
||
|
||
uint32 SpellMgr::GetFirstSpellInChain(uint32 spell_id) const
|
||
{
|
||
if (SpellChainNode const* node = GetSpellChainNode(spell_id))
|
||
return node->first->Id;
|
||
|
||
return spell_id;
|
||
}
|
||
|
||
uint32 SpellMgr::GetLastSpellInChain(uint32 spell_id) const
|
||
{
|
||
if (SpellChainNode const* node = GetSpellChainNode(spell_id))
|
||
return node->last->Id;
|
||
|
||
return spell_id;
|
||
}
|
||
|
||
uint32 SpellMgr::GetNextSpellInChain(uint32 spell_id) const
|
||
{
|
||
if (SpellChainNode const* node = GetSpellChainNode(spell_id))
|
||
if (node->next)
|
||
return node->next->Id;
|
||
|
||
return 0;
|
||
}
|
||
|
||
uint32 SpellMgr::GetPrevSpellInChain(uint32 spell_id) const
|
||
{
|
||
if (SpellChainNode const* node = GetSpellChainNode(spell_id))
|
||
if (node->prev)
|
||
return node->prev->Id;
|
||
|
||
return 0;
|
||
}
|
||
|
||
uint8 SpellMgr::GetSpellRank(uint32 spell_id) const
|
||
{
|
||
if (SpellChainNode const* node = GetSpellChainNode(spell_id))
|
||
return node->rank;
|
||
|
||
return 0;
|
||
}
|
||
|
||
uint32 SpellMgr::GetSpellWithRank(uint32 spell_id, uint32 rank, bool strict) const
|
||
{
|
||
if (SpellChainNode const* node = GetSpellChainNode(spell_id))
|
||
{
|
||
if (rank != node->rank)
|
||
return GetSpellWithRank(node->rank < rank ? node->next->Id : node->prev->Id, rank, strict);
|
||
}
|
||
else if (strict && rank > 1)
|
||
return 0;
|
||
return spell_id;
|
||
}
|
||
|
||
Trinity::IteratorPair<SpellRequiredMap::const_iterator> SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const
|
||
{
|
||
return Trinity::Containers::MapEqualRange(mSpellReq, spell_id);
|
||
}
|
||
|
||
SpellsRequiringSpellMapBounds SpellMgr::GetSpellsRequiringSpellBounds(uint32 spell_id) const
|
||
{
|
||
return mSpellsReqSpell.equal_range(spell_id);
|
||
}
|
||
|
||
bool SpellMgr::IsSpellRequiringSpell(uint32 spellid, uint32 req_spellid) const
|
||
{
|
||
SpellsRequiringSpellMapBounds spellsRequiringSpell = GetSpellsRequiringSpellBounds(req_spellid);
|
||
for (SpellsRequiringSpellMap::const_iterator itr = spellsRequiringSpell.first; itr != spellsRequiringSpell.second; ++itr)
|
||
{
|
||
if (itr->second == spellid)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
SpellLearnSkillNode const* SpellMgr::GetSpellLearnSkill(uint32 spell_id) const
|
||
{
|
||
SpellLearnSkillMap::const_iterator itr = mSpellLearnSkills.find(spell_id);
|
||
if (itr != mSpellLearnSkills.end())
|
||
return &itr->second;
|
||
else
|
||
return nullptr;
|
||
}
|
||
|
||
SpellLearnSpellMapBounds SpellMgr::GetSpellLearnSpellMapBounds(uint32 spell_id) const
|
||
{
|
||
return mSpellLearnSpells.equal_range(spell_id);
|
||
}
|
||
|
||
bool SpellMgr::IsSpellLearnSpell(uint32 spell_id) const
|
||
{
|
||
return mSpellLearnSpells.find(spell_id) != mSpellLearnSpells.end();
|
||
}
|
||
|
||
bool SpellMgr::IsSpellLearnToSpell(uint32 spell_id1, uint32 spell_id2) const
|
||
{
|
||
SpellLearnSpellMapBounds bounds = GetSpellLearnSpellMapBounds(spell_id1);
|
||
for (SpellLearnSpellMap::const_iterator i = bounds.first; i != bounds.second; ++i)
|
||
if (i->second.spell == spell_id2)
|
||
return true;
|
||
return false;
|
||
}
|
||
|
||
SpellTargetPosition const* SpellMgr::GetSpellTargetPosition(uint32 spell_id, SpellEffIndex effIndex) const
|
||
{
|
||
SpellTargetPositionMap::const_iterator itr = mSpellTargetPositions.find(std::make_pair(spell_id, effIndex));
|
||
if (itr != mSpellTargetPositions.end())
|
||
return &itr->second;
|
||
return nullptr;
|
||
}
|
||
|
||
SpellSpellGroupMapBounds SpellMgr::GetSpellSpellGroupMapBounds(uint32 spell_id) const
|
||
{
|
||
spell_id = GetFirstSpellInChain(spell_id);
|
||
return mSpellSpellGroup.equal_range(spell_id);
|
||
}
|
||
|
||
bool SpellMgr::IsSpellMemberOfSpellGroup(uint32 spellid, SpellGroup groupid) const
|
||
{
|
||
SpellSpellGroupMapBounds spellGroup = GetSpellSpellGroupMapBounds(spellid);
|
||
for (SpellSpellGroupMap::const_iterator itr = spellGroup.first; itr != spellGroup.second; ++itr)
|
||
{
|
||
if (itr->second == groupid)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
SpellGroupSpellMapBounds SpellMgr::GetSpellGroupSpellMapBounds(SpellGroup group_id) const
|
||
{
|
||
return mSpellGroupSpell.equal_range(group_id);
|
||
}
|
||
|
||
void SpellMgr::GetSetOfSpellsInSpellGroup(SpellGroup group_id, std::set<uint32>& foundSpells) const
|
||
{
|
||
std::set<SpellGroup> usedGroups;
|
||
GetSetOfSpellsInSpellGroup(group_id, foundSpells, usedGroups);
|
||
}
|
||
|
||
void SpellMgr::GetSetOfSpellsInSpellGroup(SpellGroup group_id, std::set<uint32>& foundSpells, std::set<SpellGroup>& usedGroups) const
|
||
{
|
||
if (usedGroups.find(group_id) != usedGroups.end())
|
||
return;
|
||
usedGroups.insert(group_id);
|
||
|
||
SpellGroupSpellMapBounds groupSpell = GetSpellGroupSpellMapBounds(group_id);
|
||
for (SpellGroupSpellMap::const_iterator itr = groupSpell.first; itr != groupSpell.second; ++itr)
|
||
{
|
||
if (itr->second < 0)
|
||
{
|
||
SpellGroup currGroup = (SpellGroup)abs(itr->second);
|
||
GetSetOfSpellsInSpellGroup(currGroup, foundSpells, usedGroups);
|
||
}
|
||
else
|
||
{
|
||
foundSpells.insert(itr->second);
|
||
}
|
||
}
|
||
}
|
||
|
||
bool SpellMgr::AddSameEffectStackRuleSpellGroups(SpellInfo const* spellInfo, uint32 auraType, int32 amount, std::map<SpellGroup, int32>& groups) const
|
||
{
|
||
uint32 spellId = spellInfo->GetFirstRankSpell()->Id;
|
||
auto spellGroupBounds = GetSpellSpellGroupMapBounds(spellId);
|
||
// Find group with SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT if it belongs to one
|
||
for (auto itr = spellGroupBounds.first; itr != spellGroupBounds.second; ++itr)
|
||
{
|
||
SpellGroup group = itr->second;
|
||
auto found = mSpellSameEffectStack.find(group);
|
||
if (found != mSpellSameEffectStack.end())
|
||
{
|
||
// check auraTypes
|
||
if (!found->second.count(auraType))
|
||
continue;
|
||
|
||
// Put the highest amount in the map
|
||
auto groupItr = groups.find(group);
|
||
if (groupItr == groups.end())
|
||
groups.emplace(group, amount);
|
||
else
|
||
{
|
||
int32 curr_amount = groups[group];
|
||
// Take absolute value because this also counts for the highest negative aura
|
||
if (std::abs(curr_amount) < std::abs(amount))
|
||
groupItr->second = amount;
|
||
}
|
||
// return because a spell should be in only one SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group per auraType
|
||
return true;
|
||
}
|
||
}
|
||
// Not in a SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT group, so return false
|
||
return false;
|
||
}
|
||
|
||
SpellGroupStackRule SpellMgr::CheckSpellGroupStackRules(SpellInfo const* spellInfo1, SpellInfo const* spellInfo2) const
|
||
{
|
||
ASSERT(spellInfo1);
|
||
ASSERT(spellInfo2);
|
||
|
||
uint32 spellid_1 = spellInfo1->GetFirstRankSpell()->Id;
|
||
uint32 spellid_2 = spellInfo2->GetFirstRankSpell()->Id;
|
||
|
||
// find SpellGroups which are common for both spells
|
||
SpellSpellGroupMapBounds spellGroup1 = GetSpellSpellGroupMapBounds(spellid_1);
|
||
std::set<SpellGroup> groups;
|
||
for (SpellSpellGroupMap::const_iterator itr = spellGroup1.first; itr != spellGroup1.second; ++itr)
|
||
{
|
||
if (IsSpellMemberOfSpellGroup(spellid_2, itr->second))
|
||
{
|
||
bool add = true;
|
||
SpellGroupSpellMapBounds groupSpell = GetSpellGroupSpellMapBounds(itr->second);
|
||
for (SpellGroupSpellMap::const_iterator itr2 = groupSpell.first; itr2 != groupSpell.second; ++itr2)
|
||
{
|
||
if (itr2->second < 0)
|
||
{
|
||
SpellGroup currGroup = (SpellGroup)abs(itr2->second);
|
||
if (IsSpellMemberOfSpellGroup(spellid_1, currGroup) && IsSpellMemberOfSpellGroup(spellid_2, currGroup))
|
||
{
|
||
add = false;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
if (add)
|
||
groups.insert(itr->second);
|
||
}
|
||
}
|
||
|
||
SpellGroupStackRule rule = SPELL_GROUP_STACK_RULE_DEFAULT;
|
||
|
||
for (std::set<SpellGroup>::iterator itr = groups.begin(); itr!= groups.end(); ++itr)
|
||
{
|
||
SpellGroupStackMap::const_iterator found = mSpellGroupStack.find(*itr);
|
||
if (found != mSpellGroupStack.end())
|
||
rule = found->second;
|
||
if (rule)
|
||
break;
|
||
}
|
||
return rule;
|
||
}
|
||
|
||
SpellGroupStackRule SpellMgr::GetSpellGroupStackRule(SpellGroup group) const
|
||
{
|
||
SpellGroupStackMap::const_iterator itr = mSpellGroupStack.find(group);
|
||
if (itr != mSpellGroupStack.end())
|
||
return itr->second;
|
||
|
||
return SPELL_GROUP_STACK_RULE_DEFAULT;
|
||
}
|
||
|
||
SpellProcEntry const* SpellMgr::GetSpellProcEntry(uint32 spellId) const
|
||
{
|
||
SpellProcMap::const_iterator itr = mSpellProcMap.find(spellId);
|
||
if (itr != mSpellProcMap.end())
|
||
return &itr->second;
|
||
return nullptr;
|
||
}
|
||
|
||
bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo)
|
||
{
|
||
// proc type doesn't match
|
||
if (!(eventInfo.GetTypeMask() & procEntry.ProcFlags))
|
||
return false;
|
||
|
||
// check XP or honor target requirement
|
||
if (procEntry.AttributesMask & PROC_ATTR_REQ_EXP_OR_HONOR)
|
||
if (Player* actor = eventInfo.GetActor()->ToPlayer())
|
||
if (eventInfo.GetActionTarget() && !actor->isHonorOrXPTarget(eventInfo.GetActionTarget()))
|
||
return false;
|
||
|
||
// check mana requirement
|
||
if (procEntry.AttributesMask & PROC_ATTR_REQ_MANA_COST)
|
||
if (SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo())
|
||
if (!eventSpellInfo->ManaCost && !eventSpellInfo->ManaCostPercentage)
|
||
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
|
||
// Do not consider autoattacks as triggered spells
|
||
if (!(procEntry.AttributesMask & PROC_ATTR_TRIGGERED_CAN_PROC) && !(eventInfo.GetTypeMask() & AUTO_ATTACK_PROC_FLAG_MASK))
|
||
{
|
||
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;
|
||
|
||
// check spell family name/flags (if set) for spells
|
||
if (eventInfo.GetTypeMask() & SPELL_PROC_FLAG_MASK)
|
||
{
|
||
if (SpellInfo const* eventSpellInfo = eventInfo.GetSpellInfo())
|
||
if (!eventSpellInfo->IsAffected(procEntry.SpellFamilyName, procEntry.SpellFamilyMask))
|
||
return false;
|
||
|
||
// check spell type mask (if set)
|
||
if (procEntry.SpellTypeMask && !(eventInfo.GetSpellTypeMask() & procEntry.SpellTypeMask))
|
||
return false;
|
||
}
|
||
|
||
// check spell phase mask
|
||
if (eventInfo.GetTypeMask() & REQ_SPELL_PHASE_PROC_FLAG_MASK)
|
||
{
|
||
if (!(eventInfo.GetSpellPhaseMask() & procEntry.SpellPhaseMask))
|
||
return false;
|
||
}
|
||
|
||
// check hit mask (on taken hit or on done hit, but not on spell cast phase)
|
||
if ((eventInfo.GetTypeMask() & TAKEN_HIT_PROC_FLAG_MASK) || ((eventInfo.GetTypeMask() & DONE_HIT_PROC_FLAG_MASK) && !(eventInfo.GetSpellPhaseMask() & PROC_SPELL_PHASE_CAST)))
|
||
{
|
||
uint32 hitMask = procEntry.HitMask;
|
||
// get default values if hit mask not set
|
||
if (!hitMask)
|
||
{
|
||
// for taken procs allow normal + critical hits by default
|
||
if (eventInfo.GetTypeMask() & TAKEN_HIT_PROC_FLAG_MASK)
|
||
hitMask |= PROC_HIT_NORMAL | PROC_HIT_CRITICAL;
|
||
// for done procs allow normal + critical + absorbs by default
|
||
else
|
||
hitMask |= PROC_HIT_NORMAL | PROC_HIT_CRITICAL | PROC_HIT_ABSORB;
|
||
}
|
||
if (!(eventInfo.GetHitMask() & hitMask))
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
SpellBonusEntry const* SpellMgr::GetSpellBonusData(uint32 spellId) const
|
||
{
|
||
// Lookup data
|
||
SpellBonusMap::const_iterator itr = mSpellBonusMap.find(spellId);
|
||
if (itr != mSpellBonusMap.end())
|
||
return &itr->second;
|
||
// Not found, try lookup for 1 spell rank if exist
|
||
if (uint32 rank_1 = GetFirstSpellInChain(spellId))
|
||
{
|
||
SpellBonusMap::const_iterator itr2 = mSpellBonusMap.find(rank_1);
|
||
if (itr2 != mSpellBonusMap.end())
|
||
return &itr2->second;
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
SpellThreatEntry const* SpellMgr::GetSpellThreatEntry(uint32 spellID) const
|
||
{
|
||
SpellThreatMap::const_iterator itr = mSpellThreatMap.find(spellID);
|
||
if (itr != mSpellThreatMap.end())
|
||
return &itr->second;
|
||
else
|
||
{
|
||
uint32 firstSpell = GetFirstSpellInChain(spellID);
|
||
itr = mSpellThreatMap.find(firstSpell);
|
||
if (itr != mSpellThreatMap.end())
|
||
return &itr->second;
|
||
}
|
||
return nullptr;
|
||
}
|
||
|
||
SkillLineAbilityMapBounds SpellMgr::GetSkillLineAbilityMapBounds(uint32 spell_id) const
|
||
{
|
||
return mSkillLineAbilityMap.equal_range(spell_id);
|
||
}
|
||
|
||
PetAura const* SpellMgr::GetPetAura(uint32 spell_id, uint8 eff) const
|
||
{
|
||
SpellPetAuraMap::const_iterator itr = mSpellPetAuraMap.find((spell_id<<8) + eff);
|
||
if (itr != mSpellPetAuraMap.end())
|
||
return &itr->second;
|
||
else
|
||
return nullptr;
|
||
}
|
||
|
||
SpellEnchantProcEntry const* SpellMgr::GetSpellEnchantProcEvent(uint32 enchId) const
|
||
{
|
||
SpellEnchantProcEventMap::const_iterator itr = mSpellEnchantProcEventMap.find(enchId);
|
||
if (itr != mSpellEnchantProcEventMap.end())
|
||
return &itr->second;
|
||
return nullptr;
|
||
}
|
||
|
||
bool SpellMgr::IsArenaAllowedEnchancment(uint32 ench_id) const
|
||
{
|
||
return mEnchantCustomAttr[ench_id];
|
||
}
|
||
|
||
const std::vector<int32>* SpellMgr::GetSpellLinked(int32 spell_id) const
|
||
{
|
||
SpellLinkedMap::const_iterator itr = mSpellLinkedMap.find(spell_id);
|
||
return itr != mSpellLinkedMap.end() ? &(itr->second) : nullptr;
|
||
}
|
||
|
||
PetLevelupSpellSet const* SpellMgr::GetPetLevelupSpellList(uint32 petFamily) const
|
||
{
|
||
PetLevelupSpellMap::const_iterator itr = mPetLevelupSpellMap.find(petFamily);
|
||
if (itr != mPetLevelupSpellMap.end())
|
||
return &itr->second;
|
||
else
|
||
return nullptr;
|
||
}
|
||
|
||
PetDefaultSpellsEntry const* SpellMgr::GetPetDefaultSpellsEntry(int32 id) const
|
||
{
|
||
PetDefaultSpellsMap::const_iterator itr = mPetDefaultSpellsMap.find(id);
|
||
if (itr != mPetDefaultSpellsMap.end())
|
||
return &itr->second;
|
||
return nullptr;
|
||
}
|
||
|
||
SpellAreaMapBounds SpellMgr::GetSpellAreaMapBounds(uint32 spell_id) const
|
||
{
|
||
return mSpellAreaMap.equal_range(spell_id);
|
||
}
|
||
|
||
SpellAreaForQuestMapBounds SpellMgr::GetSpellAreaForQuestMapBounds(uint32 quest_id) const
|
||
{
|
||
return mSpellAreaForQuestMap.equal_range(quest_id);
|
||
}
|
||
|
||
SpellAreaForQuestMapBounds SpellMgr::GetSpellAreaForQuestEndMapBounds(uint32 quest_id) const
|
||
{
|
||
return mSpellAreaForQuestEndMap.equal_range(quest_id);
|
||
}
|
||
|
||
SpellAreaForAuraMapBounds SpellMgr::GetSpellAreaForAuraMapBounds(uint32 spell_id) const
|
||
{
|
||
return mSpellAreaForAuraMap.equal_range(spell_id);
|
||
}
|
||
|
||
SpellAreaForAreaMapBounds SpellMgr::GetSpellAreaForAreaMapBounds(uint32 area_id) const
|
||
{
|
||
return mSpellAreaForAreaMap.equal_range(area_id);
|
||
}
|
||
|
||
SpellAreaForQuestAreaMapBounds SpellMgr::GetSpellAreaForQuestAreaMapBounds(uint32 area_id, uint32 quest_id) const
|
||
{
|
||
return mSpellAreaForQuestAreaMap.equal_range(std::pair<uint32, uint32>(area_id, quest_id));
|
||
}
|
||
|
||
bool SpellArea::IsFitToRequirements(Player const* player, uint32 newZone, uint32 newArea) const
|
||
{
|
||
if (gender != GENDER_NONE) // is not expected gender
|
||
if (!player || gender != player->getGender())
|
||
return false;
|
||
|
||
if (raceMask) // is not expected race
|
||
if (!player || !(raceMask & player->getRaceMask()))
|
||
return false;
|
||
|
||
if (areaId) // is not in expected zone
|
||
if (newZone != areaId && newArea != areaId)
|
||
return false;
|
||
|
||
if (questStart) // is not in expected required quest state
|
||
if (!player || (((1 << player->GetQuestStatus(questStart)) & questStartStatus) == 0))
|
||
return false;
|
||
|
||
if (questEnd) // is not in expected forbidden quest state
|
||
if (!player || (((1 << player->GetQuestStatus(questEnd)) & questEndStatus) == 0))
|
||
return false;
|
||
|
||
if (auraSpell) // does not have expected aura
|
||
if (!player || (auraSpell > 0 && !player->HasAura(auraSpell)) || (auraSpell < 0 && player->HasAura(-auraSpell)))
|
||
return false;
|
||
|
||
if (player)
|
||
{
|
||
if (Battleground* bg = player->GetBattleground())
|
||
return bg->IsSpellAllowed(spellId, player);
|
||
|
||
ConditionSourceInfo srcInfo = ConditionSourceInfo(const_cast<Player*>(player));
|
||
if (!sConditionMgr->IsObjectMeetToConditions(srcInfo, Conditions))
|
||
return false;
|
||
}
|
||
|
||
// Extra conditions
|
||
switch (spellId)
|
||
{
|
||
case 91604: // No fly Zone - Wintergrasp
|
||
{
|
||
if (!player)
|
||
return false;
|
||
|
||
Battlefield* Bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetZoneId());
|
||
if (!Bf || Bf->CanFlyIn() || (!player->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !player->HasAuraType(SPELL_AURA_FLY)))
|
||
return false;
|
||
break;
|
||
}
|
||
case 56618: // Horde Controls Factory Phase Shift
|
||
case 56617: // Alliance Controls Factory Phase Shift
|
||
{
|
||
if (!player)
|
||
return false;
|
||
|
||
Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetZoneId());
|
||
|
||
if (!bf || bf->GetTypeId() != BATTLEFIELD_WG)
|
||
return false;
|
||
|
||
// team that controls the workshop in the specified area
|
||
uint32 team = bf->GetData(newArea);
|
||
|
||
if (team == TEAM_HORDE)
|
||
return spellId == 56618;
|
||
else if (team == TEAM_ALLIANCE)
|
||
return spellId == 56617;
|
||
break;
|
||
}
|
||
case 57940: // Essence of Wintergrasp - Northrend
|
||
case 58045: // Essence of Wintergrasp - Wintergrasp
|
||
{
|
||
if (!player)
|
||
return false;
|
||
|
||
if (Battlefield* battlefieldWG = sBattlefieldMgr->GetBattlefieldByBattleId(BATTLEFIELD_BATTLEID_WG))
|
||
return battlefieldWG->IsEnabled() && (player->GetTeamId() == battlefieldWG->GetDefenderTeam()) && !battlefieldWG->IsWarTime();
|
||
break;
|
||
}
|
||
case 74411: // Battleground - Dampening
|
||
{
|
||
if (!player)
|
||
return false;
|
||
|
||
if (Battlefield* bf = sBattlefieldMgr->GetBattlefieldToZoneId(player->GetZoneId()))
|
||
return bf->IsWarTime();
|
||
break;
|
||
}
|
||
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
void SpellMgr::UnloadSpellInfoChains()
|
||
{
|
||
for (SpellChainMap::iterator itr = mSpellChains.begin(); itr != mSpellChains.end(); ++itr)
|
||
mSpellInfoMap[itr->first]->ChainEntry = nullptr;
|
||
|
||
mSpellChains.clear();
|
||
}
|
||
|
||
void SpellMgr::LoadSpellTalentRanks()
|
||
{
|
||
// cleanup core data before reload - remove reference to ChainNode from SpellInfo
|
||
UnloadSpellInfoChains();
|
||
|
||
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
|
||
{
|
||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
|
||
if (!talentInfo)
|
||
continue;
|
||
|
||
SpellInfo const* lastSpell = nullptr;
|
||
for (uint8 rank = MAX_TALENT_RANK - 1; rank > 0; --rank)
|
||
{
|
||
if (talentInfo->SpellRank[rank])
|
||
{
|
||
lastSpell = GetSpellInfo(talentInfo->SpellRank[rank]);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!lastSpell)
|
||
continue;
|
||
|
||
SpellInfo const* firstSpell = GetSpellInfo(talentInfo->SpellRank[0]);
|
||
if (!firstSpell)
|
||
{
|
||
TC_LOG_ERROR("spells", "SpellMgr::LoadSpellTalentRanks: First Rank Spell %u for TalentEntry %u does not exist.", talentInfo->SpellRank[0], i);
|
||
continue;
|
||
}
|
||
|
||
SpellInfo const* prevSpell = nullptr;
|
||
for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
|
||
{
|
||
uint32 spellId = talentInfo->SpellRank[rank];
|
||
if (!spellId)
|
||
break;
|
||
|
||
SpellInfo const* currentSpell = GetSpellInfo(spellId);
|
||
if (!currentSpell)
|
||
{
|
||
TC_LOG_ERROR("spells", "SpellMgr::LoadSpellTalentRanks: Spell %u (Rank: %u) for TalentEntry %u does not exist.", spellId, rank + 1, i);
|
||
break;
|
||
}
|
||
|
||
SpellChainNode node;
|
||
node.first = firstSpell;
|
||
node.last = lastSpell;
|
||
node.rank = rank + 1;
|
||
|
||
node.prev = prevSpell;
|
||
node.next = node.rank < MAX_TALENT_RANK ? GetSpellInfo(talentInfo->SpellRank[node.rank]) : nullptr;
|
||
|
||
mSpellChains[spellId] = node;
|
||
mSpellInfoMap[spellId]->ChainEntry = &mSpellChains[spellId];
|
||
|
||
prevSpell = currentSpell;
|
||
}
|
||
}
|
||
}
|
||
|
||
void SpellMgr::LoadSpellRanks()
|
||
{
|
||
// cleanup data and load spell ranks for talents from dbc
|
||
LoadSpellTalentRanks();
|
||
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
// 0 1 2
|
||
QueryResult result = WorldDatabase.Query("SELECT first_spell_id, spell_id, `rank` from spell_ranks ORDER BY first_spell_id, `rank`");
|
||
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell rank records. DB table `spell_ranks` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
bool finished = false;
|
||
|
||
do
|
||
{
|
||
// spellid, rank
|
||
std::list < std::pair < int32, int32 > > rankChain;
|
||
int32 currentSpell = -1;
|
||
int32 lastSpell = -1;
|
||
|
||
// fill one chain
|
||
while (currentSpell == lastSpell && !finished)
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
currentSpell = fields[0].GetUInt32();
|
||
if (lastSpell == -1)
|
||
lastSpell = currentSpell;
|
||
uint32 spell_id = fields[1].GetUInt32();
|
||
uint32 rank = fields[2].GetUInt8();
|
||
|
||
// don't drop the row if we're moving to the next rank
|
||
if (currentSpell == lastSpell)
|
||
{
|
||
rankChain.push_back(std::make_pair(spell_id, rank));
|
||
if (!result->NextRow())
|
||
finished = true;
|
||
}
|
||
else
|
||
break;
|
||
}
|
||
// check if chain is made with valid first spell
|
||
SpellInfo const* first = GetSpellInfo(lastSpell);
|
||
if (!first)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell rank identifier(first_spell_id) %u listed in `spell_ranks` does not exist!", lastSpell);
|
||
continue;
|
||
}
|
||
// check if chain is long enough
|
||
if (rankChain.size() < 2)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "There is only 1 spell rank for identifier(first_spell_id) %u in `spell_ranks`, entry is not needed!", lastSpell);
|
||
continue;
|
||
}
|
||
int32 curRank = 0;
|
||
bool valid = true;
|
||
// check spells in chain
|
||
for (std::list<std::pair<int32, int32> >::iterator itr = rankChain.begin(); itr!= rankChain.end(); ++itr)
|
||
{
|
||
SpellInfo const* spell = GetSpellInfo(itr->first);
|
||
if (!spell)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u (rank %u) listed in `spell_ranks` for chain %u does not exist!", itr->first, itr->second, lastSpell);
|
||
valid = false;
|
||
break;
|
||
}
|
||
++curRank;
|
||
if (itr->second != curRank)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u (rank %u) listed in `spell_ranks` for chain %u does not have a proper rank value (should be %u)!", itr->first, itr->second, lastSpell, curRank);
|
||
valid = false;
|
||
break;
|
||
}
|
||
}
|
||
if (!valid)
|
||
continue;
|
||
int32 prevRank = 0;
|
||
// insert the chain
|
||
std::list<std::pair<int32, int32> >::iterator itr = rankChain.begin();
|
||
do
|
||
{
|
||
++count;
|
||
int32 addedSpell = itr->first;
|
||
|
||
if (mSpellInfoMap[addedSpell]->ChainEntry)
|
||
TC_LOG_ERROR("sql.sql", "The spell %u (rank: %u, first: %u) listed in `spell_ranks` already has ChainEntry from dbc.", addedSpell, itr->second, lastSpell);
|
||
|
||
mSpellChains[addedSpell].first = GetSpellInfo(lastSpell);
|
||
mSpellChains[addedSpell].last = GetSpellInfo(rankChain.back().first);
|
||
mSpellChains[addedSpell].rank = itr->second;
|
||
mSpellChains[addedSpell].prev = GetSpellInfo(prevRank);
|
||
mSpellInfoMap[addedSpell]->ChainEntry = &mSpellChains[addedSpell];
|
||
prevRank = addedSpell;
|
||
++itr;
|
||
|
||
if (itr == rankChain.end())
|
||
{
|
||
mSpellChains[addedSpell].next = nullptr;
|
||
break;
|
||
}
|
||
else
|
||
mSpellChains[addedSpell].next = GetSpellInfo(itr->first);
|
||
}
|
||
while (true);
|
||
}
|
||
while (!finished);
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell rank records in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellRequired()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellsReqSpell.clear(); // need for reload case
|
||
mSpellReq.clear(); // need for reload case
|
||
|
||
// 0 1
|
||
QueryResult result = WorldDatabase.Query("SELECT spell_id, req_spell from spell_required");
|
||
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell required records. DB table `spell_required` is empty.");
|
||
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 spell_id = fields[0].GetUInt32();
|
||
uint32 spell_req = fields[1].GetUInt32();
|
||
|
||
// check if chain is made with valid first spell
|
||
SpellInfo const* spell = GetSpellInfo(spell_id);
|
||
if (!spell)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "spell_id %u in `spell_required` table could not be found in dbc, skipped.", spell_id);
|
||
continue;
|
||
}
|
||
|
||
SpellInfo const* reqSpell = GetSpellInfo(spell_req);
|
||
if (!reqSpell)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "req_spell %u in `spell_required` table could not be found in dbc, skipped.", spell_req);
|
||
continue;
|
||
}
|
||
|
||
if (spell->IsRankOf(reqSpell))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "req_spell %u and spell_id %u in `spell_required` table are ranks of the same spell, entry not needed, skipped.", spell_req, spell_id);
|
||
continue;
|
||
}
|
||
|
||
if (IsSpellRequiringSpell(spell_id, spell_req))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Duplicate entry of req_spell %u and spell_id %u in `spell_required`, skipped.", spell_req, spell_id);
|
||
continue;
|
||
}
|
||
|
||
mSpellReq.insert (std::pair<uint32, uint32>(spell_id, spell_req));
|
||
mSpellsReqSpell.insert (std::pair<uint32, uint32>(spell_req, spell_id));
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell required records in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
|
||
}
|
||
|
||
void SpellMgr::LoadSpellLearnSkills()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellLearnSkills.clear(); // need for reload case
|
||
|
||
// search auto-learned skills and add its to map also for use in unlearn spells/talents
|
||
uint32 dbc_count = 0;
|
||
for (SpellInfo const* entry : mSpellInfoMap)
|
||
{
|
||
if (!entry)
|
||
continue;
|
||
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
SpellLearnSkillNode dbc_node;
|
||
switch (entry->Effects[i].Effect)
|
||
{
|
||
case SPELL_EFFECT_SKILL:
|
||
dbc_node.skill = entry->Effects[i].MiscValue;
|
||
dbc_node.step = entry->Effects[i].CalcValue();
|
||
if (dbc_node.skill != SKILL_RIDING)
|
||
dbc_node.value = 1;
|
||
else
|
||
dbc_node.value = dbc_node.step * 75;
|
||
dbc_node.maxvalue = dbc_node.step * 75;
|
||
break;
|
||
case SPELL_EFFECT_DUAL_WIELD:
|
||
dbc_node.skill = SKILL_DUAL_WIELD;
|
||
dbc_node.step = 1;
|
||
dbc_node.value = 1;
|
||
dbc_node.maxvalue = 1;
|
||
break;
|
||
default:
|
||
continue;
|
||
}
|
||
|
||
mSpellLearnSkills[entry->Id] = dbc_node;
|
||
++dbc_count;
|
||
break;
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u Spell Learn Skills from DBC in %u ms", dbc_count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellLearnSpells()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellLearnSpells.clear(); // need for reload case
|
||
|
||
// 0 1 2
|
||
QueryResult result = WorldDatabase.Query("SELECT entry, SpellID, Active FROM spell_learn_spell");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell learn spells. DB table `spell_learn_spell` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 spell_id = fields[0].GetUInt32();
|
||
|
||
SpellLearnSpellNode node;
|
||
node.spell = fields[1].GetUInt32();
|
||
node.active = fields[2].GetBool();
|
||
node.autoLearned = false;
|
||
|
||
if (!GetSpellInfo(spell_id))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_learn_spell` does not exist.", spell_id);
|
||
continue;
|
||
}
|
||
|
||
if (!GetSpellInfo(node.spell))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_learn_spell` learning non-existing spell %u.", spell_id, node.spell);
|
||
continue;
|
||
}
|
||
|
||
if (GetTalentSpellCost(node.spell))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_learn_spell` attempts learning talent spell %u, skipped.", spell_id, node.spell);
|
||
continue;
|
||
}
|
||
|
||
mSpellLearnSpells.insert(SpellLearnSpellMap::value_type(spell_id, node));
|
||
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
// copy state loaded from db
|
||
SpellLearnSpellMap dbSpellLearnSpells = mSpellLearnSpells;
|
||
|
||
// search auto-learned spells and add its to map also for use in unlearn spells/talents
|
||
uint32 dbc_count = 0;
|
||
for (uint32 spell = 0; spell < GetSpellInfoStoreSize(); ++spell)
|
||
{
|
||
SpellInfo const* entry = GetSpellInfo(spell);
|
||
|
||
if (!entry)
|
||
continue;
|
||
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
if (entry->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL)
|
||
{
|
||
SpellLearnSpellNode dbc_node;
|
||
dbc_node.spell = entry->Effects[i].TriggerSpell;
|
||
dbc_node.active = true; // all dbc based learned spells is active (show in spell book or hide by client itself)
|
||
|
||
// ignore learning not existed spells (broken/outdated/or generic learnig spell 483
|
||
if (!GetSpellInfo(dbc_node.spell))
|
||
continue;
|
||
|
||
// talent or passive spells or skill-step spells auto-cast and not need dependent learning,
|
||
// pet teaching spells must not be dependent learning (cast)
|
||
// other required explicit dependent learning
|
||
dbc_node.autoLearned = entry->Effects[i].TargetA.GetTarget() == TARGET_UNIT_PET || GetTalentSpellCost(spell) > 0 || entry->IsPassive() || entry->HasEffect(SPELL_EFFECT_SKILL_STEP);
|
||
|
||
SpellLearnSpellMapBounds db_node_bounds = dbSpellLearnSpells.equal_range(spell);
|
||
|
||
bool found = false;
|
||
for (SpellLearnSpellMap::const_iterator itr = db_node_bounds.first; itr != db_node_bounds.second; ++itr)
|
||
{
|
||
if (itr->second.spell == dbc_node.spell)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u is an auto-learn spell %u in spell.dbc and the record in `spell_learn_spell` is redundant. Please update your DB.",
|
||
spell, dbc_node.spell);
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!found) // add new spell-spell pair if not found
|
||
{
|
||
mSpellLearnSpells.insert(SpellLearnSpellMap::value_type(spell, dbc_node));
|
||
++dbc_count;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
uint32 mastery_count = 0;
|
||
for (uint32 i = 0; i < sTalentTabStore.GetNumRows(); ++i)
|
||
{
|
||
TalentTabEntry const* talentTab = sTalentTabStore.LookupEntry(i);
|
||
if (!talentTab)
|
||
continue;
|
||
|
||
for (uint32 c = CLASS_WARRIOR; c < MAX_CLASSES; ++c)
|
||
{
|
||
if (!(talentTab->ClassMask & (1 << (c - 1))))
|
||
continue;
|
||
|
||
uint32 masteryMainSpell = MasterySpells[c];
|
||
|
||
for (uint32 m = 0; m < MAX_MASTERY_SPELLS; ++m)
|
||
{
|
||
uint32 mastery = talentTab->MasterySpellID[m];
|
||
if (!mastery)
|
||
continue;
|
||
|
||
SpellLearnSpellMapBounds db_node_bounds = dbSpellLearnSpells.equal_range(masteryMainSpell);
|
||
bool found = false;
|
||
for (SpellLearnSpellMap::const_iterator itr = db_node_bounds.first; itr != db_node_bounds.second; ++itr)
|
||
{
|
||
if (itr->second.spell == mastery)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Found redundant record (entry: %u, SpellID: %u) in `spell_learn_spell`, spell added automatically as mastery learned spell from TalentTab.dbc", masteryMainSpell, mastery);
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (found)
|
||
continue;
|
||
|
||
// Check if it is already found in Spell.dbc, ignore silently if yes
|
||
SpellLearnSpellMapBounds dbc_node_bounds = GetSpellLearnSpellMapBounds(masteryMainSpell);
|
||
found = false;
|
||
for (SpellLearnSpellMap::const_iterator itr = dbc_node_bounds.first; itr != dbc_node_bounds.second; ++itr)
|
||
{
|
||
if (itr->second.spell == mastery)
|
||
{
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (found)
|
||
continue;
|
||
|
||
SpellLearnSpellNode masteryNode;
|
||
masteryNode.spell = mastery;
|
||
masteryNode.active = true;
|
||
masteryNode.autoLearned = false;
|
||
|
||
mSpellLearnSpells.insert(SpellLearnSpellMap::value_type(masteryMainSpell, masteryNode));
|
||
++mastery_count;
|
||
}
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell learn spells, %u found in Spell.dbc and %u from TalentTab.dbc in %u ms", count, dbc_count, mastery_count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellTargetPositions()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellTargetPositions.clear(); // need for reload case
|
||
|
||
// 0 1 2 3 4 5 6
|
||
QueryResult result = WorldDatabase.Query("SELECT ID, EffectIndex, MapID, PositionX, PositionY, PositionZ, Orientation FROM spell_target_position");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell target coordinates. DB table `spell_target_position` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 Spell_ID = fields[0].GetUInt32();
|
||
SpellEffIndex effIndex = SpellEffIndex(fields[1].GetUInt8());
|
||
|
||
SpellTargetPosition st;
|
||
|
||
st.target_mapId = fields[2].GetUInt16();
|
||
st.target_X = fields[3].GetFloat();
|
||
st.target_Y = fields[4].GetFloat();
|
||
st.target_Z = fields[5].GetFloat();
|
||
st.target_Orientation = fields[6].GetFloat();
|
||
|
||
MapEntry const* mapEntry = sMapStore.LookupEntry(st.target_mapId);
|
||
if (!mapEntry)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Spell (Id: %u, effIndex: %u) target map (ID: %u) does not exist in `Map.dbc`.", Spell_ID, effIndex, st.target_mapId);
|
||
continue;
|
||
}
|
||
|
||
if (st.target_X==0 && st.target_Y==0 && st.target_Z==0)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Spell (Id: %u, effIndex: %u) target coordinates not provided.", Spell_ID, effIndex);
|
||
continue;
|
||
}
|
||
|
||
SpellInfo const* spellInfo = GetSpellInfo(Spell_ID);
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Spell (Id: %u) listed in `spell_target_position` does not exist.", Spell_ID);
|
||
continue;
|
||
}
|
||
|
||
if (spellInfo->Effects[effIndex].TargetA.GetTarget() == TARGET_DEST_DB || spellInfo->Effects[effIndex].TargetB.GetTarget() == TARGET_DEST_DB)
|
||
{
|
||
std::pair<uint32, SpellEffIndex> key = std::make_pair(Spell_ID, effIndex);
|
||
mSpellTargetPositions[key] = st;
|
||
++count;
|
||
}
|
||
else
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Spell (Id: %u, effIndex: %u) listed in `spell_target_position` does not have a target TARGET_DEST_DB (17).", Spell_ID, effIndex);
|
||
continue;
|
||
}
|
||
|
||
} while (result->NextRow());
|
||
|
||
/*
|
||
// Check all spells
|
||
for (uint32 i = 1; i < GetSpellInfoStoreSize; ++i)
|
||
{
|
||
SpellInfo const* spellInfo = GetSpellInfo(i);
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
bool found = false;
|
||
for (int j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
||
{
|
||
switch (spellInfo->Effects[j].TargetA)
|
||
{
|
||
case TARGET_DEST_DB:
|
||
found = true;
|
||
break;
|
||
}
|
||
if (found)
|
||
break;
|
||
switch (spellInfo->Effects[j].TargetB)
|
||
{
|
||
case TARGET_DEST_DB:
|
||
found = true;
|
||
break;
|
||
}
|
||
if (found)
|
||
break;
|
||
}
|
||
if (found)
|
||
{
|
||
if (!sSpellMgr->GetSpellTargetPosition(i))
|
||
TC_LOG_DEBUG("spells", "Spell (ID: %u) does not have a record in `spell_target_position`.", i);
|
||
}
|
||
}*/
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell teleport coordinates in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellGroups()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellSpellGroup.clear(); // need for reload case
|
||
mSpellGroupSpell.clear();
|
||
|
||
// 0 1
|
||
QueryResult result = WorldDatabase.Query("SELECT id, spell_id FROM spell_group");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell group definitions. DB table `spell_group` is empty.");
|
||
return;
|
||
}
|
||
|
||
std::set<uint32> groups;
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 group_id = fields[0].GetUInt32();
|
||
if (group_id <= SPELL_GROUP_DB_RANGE_MIN && group_id >= SPELL_GROUP_CORE_RANGE_MAX)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "SpellGroup id %u listed in `spell_group` is in core range, but is not defined in core!", group_id);
|
||
continue;
|
||
}
|
||
int32 spell_id = fields[1].GetInt32();
|
||
|
||
groups.insert(group_id);
|
||
mSpellGroupSpell.emplace(SpellGroup(group_id), spell_id);
|
||
|
||
} while (result->NextRow());
|
||
|
||
for (auto itr = mSpellGroupSpell.begin(); itr!= mSpellGroupSpell.end();)
|
||
{
|
||
if (itr->second < 0)
|
||
{
|
||
if (groups.find(abs(itr->second)) == groups.end())
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "SpellGroup id %u listed in `spell_group` does not exist", abs(itr->second));
|
||
itr = mSpellGroupSpell.erase(itr);
|
||
}
|
||
else
|
||
++itr;
|
||
}
|
||
else
|
||
{
|
||
SpellInfo const* spellInfo = GetSpellInfo(itr->second);
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_group` does not exist", itr->second);
|
||
itr = mSpellGroupSpell.erase(itr);
|
||
}
|
||
else if (spellInfo->GetRank() > 1)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_group` is not the first rank of the spell.", itr->second);
|
||
itr = mSpellGroupSpell.erase(itr);
|
||
}
|
||
else
|
||
++itr;
|
||
}
|
||
}
|
||
|
||
for (auto groupItr = groups.begin(); groupItr != groups.end(); ++groupItr)
|
||
{
|
||
std::set<uint32> spells;
|
||
GetSetOfSpellsInSpellGroup(SpellGroup(*groupItr), spells);
|
||
|
||
for (auto spellItr = spells.begin(); spellItr != spells.end(); ++spellItr)
|
||
{
|
||
++count;
|
||
mSpellSpellGroup.emplace(*spellItr, SpellGroup(*groupItr));
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell group definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellGroupStackRules()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellGroupStack.clear(); // need for reload case
|
||
mSpellSameEffectStack.clear();
|
||
|
||
std::vector<uint32> sameEffectGroups;
|
||
|
||
// 0 1
|
||
QueryResult result = WorldDatabase.Query("SELECT group_id, stack_rule FROM spell_group_stack_rules");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell group stack rules. DB table `spell_group_stack_rules` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 group_id = fields[0].GetUInt32();
|
||
uint8 stack_rule = fields[1].GetInt8();
|
||
if (stack_rule >= SPELL_GROUP_STACK_RULE_MAX)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "SpellGroupStackRule %u listed in `spell_group_stack_rules` does not exist.", stack_rule);
|
||
continue;
|
||
}
|
||
|
||
auto bounds = GetSpellGroupSpellMapBounds((SpellGroup)group_id);
|
||
if (bounds.first == bounds.second)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "SpellGroup id %u listed in `spell_group_stack_rules` does not exist.", group_id);
|
||
continue;
|
||
}
|
||
|
||
mSpellGroupStack.emplace(SpellGroup(group_id), SpellGroupStackRule(stack_rule));
|
||
|
||
// different container for same effect stack rules, need to check effect types
|
||
if (stack_rule == SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT)
|
||
sameEffectGroups.push_back(group_id);
|
||
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell group stack rules in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
|
||
count = 0;
|
||
oldMSTime = getMSTime();
|
||
TC_LOG_INFO("server.loading", ">> Parsing SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT stack rules...");
|
||
|
||
for (uint32 group_id : sameEffectGroups)
|
||
{
|
||
std::set<uint32> spellIds;
|
||
GetSetOfSpellsInSpellGroup(SpellGroup(group_id), spellIds);
|
||
|
||
std::unordered_set<uint32> auraTypes;
|
||
|
||
// we have to 'guess' what effect this group corresponds to
|
||
{
|
||
std::unordered_multiset<uint32 /*auraName*/> frequencyContainer;
|
||
|
||
// only waylay for the moment (shared group)
|
||
std::vector<std::vector<uint32 /*auraName*/>> const SubGroups =
|
||
{
|
||
{ SPELL_AURA_MOD_MELEE_HASTE, SPELL_AURA_MOD_MELEE_RANGED_HASTE, SPELL_AURA_MOD_RANGED_HASTE }
|
||
};
|
||
|
||
for (uint32 spellId : spellIds)
|
||
{
|
||
SpellInfo const* spellInfo = AssertSpellInfo(spellId);
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
if (!spellInfo->Effects[i].IsAura())
|
||
continue;
|
||
|
||
int32 auraName = static_cast<int32>(spellInfo->Effects[i].ApplyAuraName);
|
||
for (std::vector<uint32> const& subGroup : SubGroups)
|
||
{
|
||
if (std::find(subGroup.begin(), subGroup.end(), auraName) != subGroup.end())
|
||
{
|
||
// count as first aura
|
||
auraName = subGroup.front();
|
||
break;
|
||
}
|
||
}
|
||
|
||
frequencyContainer.insert(auraName);
|
||
}
|
||
}
|
||
|
||
uint32 auraType = 0;
|
||
size_t auraTypeCount = 0;
|
||
for (uint32 auraName : frequencyContainer)
|
||
{
|
||
size_t currentCount = frequencyContainer.count(auraName);
|
||
if (currentCount > auraTypeCount)
|
||
{
|
||
auraType = auraName;
|
||
auraTypeCount = currentCount;
|
||
}
|
||
}
|
||
|
||
for (std::vector<uint32> const& subGroup : SubGroups)
|
||
{
|
||
if (auraType == subGroup.front())
|
||
{
|
||
auraTypes.insert(subGroup.begin(), subGroup.end());
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (auraTypes.empty())
|
||
auraTypes.insert(auraType);
|
||
}
|
||
|
||
// re-check spells against guessed group
|
||
for (uint32 spellId : spellIds)
|
||
{
|
||
SpellInfo const* spellInfo = AssertSpellInfo(spellId);
|
||
|
||
bool found = false;
|
||
while (spellInfo)
|
||
{
|
||
for (uint32 auraType : auraTypes)
|
||
{
|
||
if (spellInfo->HasAura(AuraType(auraType)))
|
||
{
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (found)
|
||
break;
|
||
|
||
spellInfo = spellInfo->GetNextRankSpell();
|
||
}
|
||
|
||
// not found either, log error
|
||
if (!found)
|
||
TC_LOG_ERROR("sql.sql", "SpellId %u listed in `spell_group` with stack rule 3 does not share aura assigned for group %u", spellId, group_id);
|
||
}
|
||
|
||
mSpellSameEffectStack[SpellGroup(group_id)] = auraTypes;
|
||
++count;
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Parsed %u SPELL_GROUP_STACK_RULE_EXCLUSIVE_SAME_EFFECT stack rules in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellProcs()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellProcMap.clear(); // need for reload case
|
||
|
||
// 0 1 2 3 4 5
|
||
QueryResult result = WorldDatabase.Query("SELECT SpellId, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, "
|
||
// 6 7 8 9 10 11 12 13 14
|
||
"ProcFlags, SpellTypeMask, SpellPhaseMask, HitMask, AttributesMask, ProcsPerMinute, Chance, Cooldown, Charges FROM spell_proc");
|
||
|
||
uint32 count = 0;
|
||
if (result)
|
||
{
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
int32 spellId = fields[0].GetInt32();
|
||
|
||
bool allRanks = false;
|
||
if (spellId < 0)
|
||
{
|
||
allRanks = true;
|
||
spellId = -spellId;
|
||
}
|
||
|
||
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 (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 (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;
|
||
}
|
||
}
|
||
|
||
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.ProcFlags = fields[6].GetUInt32();
|
||
baseProcEntry.SpellTypeMask = fields[7].GetUInt32();
|
||
baseProcEntry.SpellPhaseMask = fields[8].GetUInt32();
|
||
baseProcEntry.HitMask = fields[9].GetUInt32();
|
||
baseProcEntry.AttributesMask = fields[10].GetUInt32();
|
||
baseProcEntry.ProcsPerMinute = fields[11].GetFloat();
|
||
baseProcEntry.Chance = fields[12].GetFloat();
|
||
baseProcEntry.Cooldown = Milliseconds(fields[13].GetUInt32());
|
||
baseProcEntry.Charges = fields[14].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;
|
||
}
|
||
|
||
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 < SPELLFAMILY_MAGE || procEntry.SpellFamilyName > SPELLFAMILY_PET || 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.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))
|
||
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);
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
if ((procEntry.AttributesMask & (PROC_ATTR_DISABLE_EFF_0 << i)) && !spellInfo->Effects[i].IsAura())
|
||
TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has Attribute PROC_ATTR_DISABLE_EFF_%u, but effect %u is not an aura effect", spellInfo->Id, static_cast<uint32>(i), static_cast<uint32>(i));
|
||
if (procEntry.AttributesMask & PROC_ATTR_REQ_SPELLMOD)
|
||
{
|
||
bool found = false;
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
if (!spellInfo->Effects[i].IsAura())
|
||
continue;
|
||
|
||
if (spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_ADD_PCT_MODIFIER || spellInfo->Effects[i].ApplyAuraName == SPELL_AURA_ADD_FLAT_MODIFIER)
|
||
{
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!found)
|
||
TC_LOG_ERROR("sql.sql", "The `spell_proc` table entry for spellId %u has Attribute PROC_ATTR_REQ_SPELLMOD, but spell has no spell mods. Proc will not be triggered", spellInfo->Id);
|
||
}
|
||
|
||
mSpellProcMap[spellInfo->Id] = procEntry;
|
||
|
||
if (allRanks)
|
||
spellInfo = spellInfo->GetNextRankSpell();
|
||
else
|
||
break;
|
||
}
|
||
++count;
|
||
} 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 spell proc conditions and data in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
|
||
// Define can trigger auras
|
||
bool isTriggerAura[TOTAL_AURAS];
|
||
// Triggered always, even from triggered spells
|
||
bool isAlwaysTriggeredAura[TOTAL_AURAS];
|
||
// SpellTypeMask to add to the proc
|
||
uint32 spellTypeMask[TOTAL_AURAS];
|
||
|
||
// List of auras that CAN trigger but may not exist in spell_proc
|
||
// in most cases needed to drop charges
|
||
|
||
// some aura types need additional checks (eg SPELL_AURA_MECHANIC_IMMUNITY needs mechanic check)
|
||
// see AuraEffect::CheckEffectProc
|
||
for (uint16 i = 0; i < TOTAL_AURAS; ++i)
|
||
{
|
||
isTriggerAura[i] = false;
|
||
isAlwaysTriggeredAura[i] = false;
|
||
spellTypeMask[i] = PROC_SPELL_TYPE_MASK_ALL;
|
||
}
|
||
|
||
isTriggerAura[SPELL_AURA_DUMMY] = true; // Most dummy auras should require scripting, but there are some exceptions (ie 12311)
|
||
isTriggerAura[SPELL_AURA_MOD_CONFUSE] = true; // "Any direct damaging attack will revive targets"
|
||
isTriggerAura[SPELL_AURA_MOD_THREAT] = true; // Only one spell: 28762 part of Mage T3 8p bonus
|
||
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_MOD_POWER_COST_SCHOOL_PCT] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_POWER_COST_SCHOOL] = true;
|
||
isTriggerAura[SPELL_AURA_REFLECT_SPELLS_SCHOOL] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER] = true;
|
||
isTriggerAura[SPELL_AURA_ADD_CASTER_HIT_TRIGGER] = true;
|
||
isTriggerAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_MELEE_HASTE] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_ATTACKER_MELEE_HIT_CHANCE] = true;
|
||
isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE] = true;
|
||
isTriggerAura[SPELL_AURA_RAID_PROC_FROM_CHARGE_WITH_VALUE] = true;
|
||
isTriggerAura[SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_SPELL_CRIT_CHANCE] = true;
|
||
isTriggerAura[SPELL_AURA_ADD_FLAT_MODIFIER] = true;
|
||
isTriggerAura[SPELL_AURA_ADD_PCT_MODIFIER] = true;
|
||
isTriggerAura[SPELL_AURA_ABILITY_IGNORE_AURASTATE] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_INVISIBILITY] = true;
|
||
isTriggerAura[SPELL_AURA_FORCE_REACTION] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_TAUNT] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_DETAUNT] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_DAMAGE_PERCENT_DONE] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_ATTACK_POWER_PCT] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_HIT_CHANCE] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_WEAPON_CRIT_PERCENT] = true;
|
||
isTriggerAura[SPELL_AURA_MOD_BLOCK_PERCENT] = true;
|
||
isTriggerAura[SPELL_AURA_PROC_ON_POWER_AMOUNT] = true;
|
||
|
||
isAlwaysTriggeredAura[SPELL_AURA_OVERRIDE_CLASS_SCRIPTS] = true;
|
||
isAlwaysTriggeredAura[SPELL_AURA_MOD_STEALTH] = true;
|
||
isAlwaysTriggeredAura[SPELL_AURA_MOD_CONFUSE] = 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_MOD_INVISIBILITY] = true;
|
||
|
||
spellTypeMask[SPELL_AURA_MOD_STEALTH] = PROC_SPELL_TYPE_DAMAGE | PROC_SPELL_TYPE_NO_DMG_HEAL;
|
||
spellTypeMask[SPELL_AURA_MOD_CONFUSE] = PROC_SPELL_TYPE_DAMAGE;
|
||
spellTypeMask[SPELL_AURA_MOD_FEAR] = PROC_SPELL_TYPE_DAMAGE;
|
||
spellTypeMask[SPELL_AURA_MOD_ROOT] = PROC_SPELL_TYPE_DAMAGE;
|
||
spellTypeMask[SPELL_AURA_MOD_STUN] = PROC_SPELL_TYPE_DAMAGE;
|
||
spellTypeMask[SPELL_AURA_TRANSFORM] = PROC_SPELL_TYPE_DAMAGE;
|
||
spellTypeMask[SPELL_AURA_MOD_INVISIBILITY] = PROC_SPELL_TYPE_DAMAGE;
|
||
|
||
// 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();
|
||
|
||
for (SpellInfo const* spellInfo : mSpellInfoMap)
|
||
{
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
// Data already present in DB, overwrites default proc
|
||
if (mSpellProcMap.find(spellInfo->Id) != mSpellProcMap.end())
|
||
continue;
|
||
|
||
// Nothing to do if no flags set
|
||
if (!spellInfo->ProcFlags)
|
||
continue;
|
||
|
||
bool addTriggerFlag = false;
|
||
uint32 procSpellTypeMask = PROC_SPELL_TYPE_NONE;
|
||
uint32 nonProcMask = 0;
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
if (!spellInfo->Effects[i].IsEffect())
|
||
continue;
|
||
|
||
uint32 auraName = spellInfo->Effects[i].ApplyAuraName;
|
||
if (!auraName)
|
||
continue;
|
||
|
||
if (!isTriggerAura[auraName])
|
||
{
|
||
// explicitly disable non proccing auras to avoid losing charges on self proc
|
||
nonProcMask |= 1 << i;
|
||
continue;
|
||
}
|
||
|
||
procSpellTypeMask |= spellTypeMask[auraName];
|
||
if (isAlwaysTriggeredAura[auraName])
|
||
addTriggerFlag = true;
|
||
|
||
// many proc auras with taken procFlag mask don't have attribute "can proc with triggered"
|
||
// they should proc nevertheless (example mage armor spells with judgement)
|
||
if (!addTriggerFlag && (spellInfo->ProcFlags & TAKEN_HIT_PROC_FLAG_MASK) != 0)
|
||
{
|
||
switch (auraName)
|
||
{
|
||
case SPELL_AURA_PROC_TRIGGER_SPELL:
|
||
case SPELL_AURA_PROC_TRIGGER_DAMAGE:
|
||
addTriggerFlag = true;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
if (!procSpellTypeMask)
|
||
{
|
||
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
if (spellInfo->Effects[i].IsAura())
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Spell Id %u has DBC ProcFlags %u, but it's of non-proc aura type, it probably needs an entry in `spell_proc` table to be handled correctly.", spellInfo->Id, spellInfo->ProcFlags);
|
||
break;
|
||
}
|
||
}
|
||
|
||
continue;
|
||
}
|
||
*/
|
||
|
||
SpellProcEntry procEntry;
|
||
procEntry.SchoolMask = 0;
|
||
procEntry.ProcFlags = spellInfo->ProcFlags;
|
||
procEntry.SpellFamilyName = 0;
|
||
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
if (spellInfo->Effects[i].IsEffect() && isTriggerAura[spellInfo->Effects[i].ApplyAuraName])
|
||
procEntry.SpellFamilyMask |= spellInfo->Effects[i].SpellClassMask;
|
||
|
||
if (procEntry.SpellFamilyMask)
|
||
procEntry.SpellFamilyName = spellInfo->SpellFamilyName;
|
||
|
||
procEntry.SpellTypeMask = procSpellTypeMask;
|
||
procEntry.SpellPhaseMask = PROC_SPELL_PHASE_HIT;
|
||
procEntry.HitMask = PROC_HIT_NONE; // uses default proc @see SpellMgr::CanSpellTriggerProcOnEvent
|
||
|
||
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
||
{
|
||
if (!spellInfo->Effects[i].IsAura())
|
||
continue;
|
||
|
||
switch (spellInfo->Effects[i].ApplyAuraName)
|
||
{
|
||
// Reflect auras should only proc off reflects
|
||
case SPELL_AURA_REFLECT_SPELLS:
|
||
case SPELL_AURA_REFLECT_SPELLS_SCHOOL:
|
||
procEntry.HitMask = PROC_HIT_REFLECT;
|
||
break;
|
||
// Only drop charge on crit
|
||
case SPELL_AURA_MOD_WEAPON_CRIT_PERCENT:
|
||
procEntry.HitMask = PROC_HIT_CRITICAL;
|
||
break;
|
||
// Only drop charge on block
|
||
case SPELL_AURA_MOD_BLOCK_PERCENT:
|
||
procEntry.HitMask = PROC_HIT_BLOCK;
|
||
break;
|
||
// proc auras with another aura reducing hit chance (eg 63767) only proc on missed attack
|
||
case SPELL_AURA_MOD_HIT_CHANCE:
|
||
if (spellInfo->Effects[i].CalcValue() <= -100)
|
||
procEntry.HitMask = PROC_HIT_MISS;
|
||
break;
|
||
default:
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
|
||
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 (nonProcMask)
|
||
procEntry.AttributesMask |= nonProcMask * PROC_ATTR_DISABLE_EFF_0;
|
||
|
||
procEntry.ProcsPerMinute = 0;
|
||
procEntry.Chance = spellInfo->ProcChance;
|
||
procEntry.Cooldown = Milliseconds::zero();
|
||
procEntry.Charges = spellInfo->ProcCharges;
|
||
|
||
mSpellProcMap[spellInfo->Id] = procEntry;
|
||
++count;
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Generated spell proc data for %u spells in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellBonuses()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellBonusMap.clear(); // need for reload case
|
||
|
||
// 0 1 2 3 4
|
||
QueryResult result = WorldDatabase.Query("SELECT entry, direct_bonus, dot_bonus, ap_bonus, ap_dot_bonus FROM spell_bonus_data");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell bonus data. DB table `spell_bonus_data` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
uint32 entry = fields[0].GetUInt32();
|
||
|
||
SpellInfo const* spell = GetSpellInfo(entry);
|
||
if (!spell)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_bonus_data` does not exist.", entry);
|
||
continue;
|
||
}
|
||
|
||
SpellBonusEntry& sbe = mSpellBonusMap[entry];
|
||
sbe.direct_damage = fields[1].GetFloat();
|
||
sbe.dot_damage = fields[2].GetFloat();
|
||
sbe.ap_bonus = fields[3].GetFloat();
|
||
sbe.ap_dot_bonus = fields[4].GetFloat();
|
||
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u extra spell bonus data in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellThreats()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellThreatMap.clear(); // need for reload case
|
||
|
||
// 0 1 2 3
|
||
QueryResult result = WorldDatabase.Query("SELECT entry, flatMod, pctMod, apPctMod FROM spell_threat");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 aggro generating spells. DB table `spell_threat` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 entry = fields[0].GetUInt32();
|
||
|
||
if (!GetSpellInfo(entry))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_threat` does not exist.", entry);
|
||
continue;
|
||
}
|
||
|
||
SpellThreatEntry ste;
|
||
ste.flatMod = fields[1].GetInt32();
|
||
ste.pctMod = fields[2].GetFloat();
|
||
ste.apPctMod = fields[3].GetFloat();
|
||
|
||
mSpellThreatMap[entry] = ste;
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u SpellThreatEntries in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSkillLineAbilityMap()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSkillLineAbilityMap.clear();
|
||
|
||
uint32 count = 0;
|
||
|
||
for (uint32 i = 0; i < sSkillLineAbilityStore.GetNumRows(); ++i)
|
||
{
|
||
SkillLineAbilityEntry const* skillInfo = sSkillLineAbilityStore.LookupEntry(i);
|
||
if (!skillInfo)
|
||
continue;
|
||
|
||
mSkillLineAbilityMap.insert(SkillLineAbilityMap::value_type(skillInfo->Spell, skillInfo));
|
||
++count;
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u SkillLineAbility MultiMap Data in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellPetAuras()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellPetAuraMap.clear(); // need for reload case
|
||
|
||
// 0 1 2 3
|
||
QueryResult result = WorldDatabase.Query("SELECT spell, effectId, pet, aura FROM spell_pet_auras");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell pet auras. DB table `spell_pet_auras` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 spell = fields[0].GetUInt32();
|
||
uint8 eff = fields[1].GetUInt8();
|
||
uint32 pet = fields[2].GetUInt32();
|
||
uint32 aura = fields[3].GetUInt32();
|
||
|
||
SpellPetAuraMap::iterator itr = mSpellPetAuraMap.find((spell<<8) + eff);
|
||
if (itr != mSpellPetAuraMap.end())
|
||
itr->second.AddAura(pet, aura);
|
||
else
|
||
{
|
||
SpellInfo const* spellInfo = GetSpellInfo(spell);
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_pet_auras` does not exist.", spell);
|
||
continue;
|
||
}
|
||
if (spellInfo->Effects[eff].Effect != SPELL_EFFECT_DUMMY &&
|
||
(spellInfo->Effects[eff].Effect != SPELL_EFFECT_APPLY_AURA ||
|
||
spellInfo->Effects[eff].ApplyAuraName != SPELL_AURA_DUMMY))
|
||
{
|
||
TC_LOG_ERROR("spells", "The spell %u listed in `spell_pet_auras` does not have any dummy aura or dummy effect.", spell);
|
||
continue;
|
||
}
|
||
|
||
SpellInfo const* spellInfo2 = GetSpellInfo(aura);
|
||
if (!spellInfo2)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The aura %u listed in `spell_pet_auras` does not exist.", aura);
|
||
continue;
|
||
}
|
||
|
||
PetAura pa(pet, aura, spellInfo->Effects[eff].TargetA.GetTarget() == TARGET_UNIT_PET, spellInfo->Effects[eff].CalcValue());
|
||
mSpellPetAuraMap[(spell<<8) + eff] = pa;
|
||
}
|
||
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell pet auras in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
// Fill custom data about enchancments
|
||
void SpellMgr::LoadEnchantCustomAttr()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
uint32 size = sSpellItemEnchantmentStore.GetNumRows();
|
||
mEnchantCustomAttr.resize(size);
|
||
|
||
for (uint32 i = 0; i < size; ++i)
|
||
mEnchantCustomAttr[i] = 0;
|
||
|
||
uint32 count = 0;
|
||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||
{
|
||
SpellInfo const* spellInfo = GetSpellInfo(i);
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
/// @todo find a better check
|
||
if (!spellInfo->HasAttribute(SPELL_ATTR2_PRESERVE_ENCHANT_IN_ARENA) || !spellInfo->HasAttribute(SPELL_ATTR0_NOT_SHAPESHIFT))
|
||
continue;
|
||
|
||
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
||
{
|
||
if (spellInfo->Effects[j].Effect == SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY)
|
||
{
|
||
uint32 enchId = spellInfo->Effects[j].MiscValue;
|
||
SpellItemEnchantmentEntry const* ench = sSpellItemEnchantmentStore.LookupEntry(enchId);
|
||
if (!ench)
|
||
continue;
|
||
mEnchantCustomAttr[enchId] = true;
|
||
++count;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u custom enchant attributes in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellEnchantProcData()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellEnchantProcEventMap.clear(); // need for reload case
|
||
|
||
// 0 1 2 3
|
||
QueryResult result = WorldDatabase.Query("SELECT entry, customChance, PPMChance, procEx FROM spell_enchant_proc_data");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell enchant proc event conditions. DB table `spell_enchant_proc_data` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 enchantId = fields[0].GetUInt32();
|
||
|
||
SpellItemEnchantmentEntry const* ench = sSpellItemEnchantmentStore.LookupEntry(enchantId);
|
||
if (!ench)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The enchancment %u listed in `spell_enchant_proc_data` does not exist.", enchantId);
|
||
continue;
|
||
}
|
||
|
||
SpellEnchantProcEntry spe;
|
||
|
||
spe.customChance = fields[1].GetUInt32();
|
||
spe.PPMChance = fields[2].GetFloat();
|
||
spe.procEx = fields[3].GetUInt32();
|
||
|
||
mSpellEnchantProcEventMap[enchantId] = spe;
|
||
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u enchant proc data definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellLinked()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellLinkedMap.clear(); // need for reload case
|
||
|
||
// 0 1 2
|
||
QueryResult result = WorldDatabase.Query("SELECT spell_trigger, spell_effect, type FROM spell_linked_spell");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 linked spells. DB table `spell_linked_spell` is empty.");
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
int32 trigger = fields[0].GetInt32();
|
||
int32 effect = fields[1].GetInt32();
|
||
int32 type = fields[2].GetUInt8();
|
||
|
||
SpellInfo const* spellInfo = GetSpellInfo(abs(trigger));
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_linked_spell` does not exist.", abs(trigger));
|
||
continue;
|
||
}
|
||
|
||
if (effect >= 0)
|
||
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
||
{
|
||
if (spellInfo->Effects[j].CalcValue() == abs(effect))
|
||
TC_LOG_ERROR("sql.sql", "The spell %u Effect: %u listed in `spell_linked_spell` has same bp%u like effect (possible hack).", abs(trigger), abs(effect), j);
|
||
}
|
||
|
||
spellInfo = GetSpellInfo(abs(effect));
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_linked_spell` does not exist.", abs(effect));
|
||
continue;
|
||
}
|
||
|
||
if (type) //we will find a better way when more types are needed
|
||
{
|
||
if (trigger > 0)
|
||
trigger += SPELL_LINKED_MAX_SPELLS * type;
|
||
else
|
||
trigger -= SPELL_LINKED_MAX_SPELLS * type;
|
||
}
|
||
mSpellLinkedMap[trigger].push_back(effect);
|
||
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u linked spells in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadPetLevelupSpellMap()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mPetLevelupSpellMap.clear(); // need for reload case
|
||
|
||
uint32 count = 0;
|
||
uint32 family_count = 0;
|
||
|
||
for (uint32 i = 0; i < sCreatureFamilyStore.GetNumRows(); ++i)
|
||
{
|
||
CreatureFamilyEntry const* creatureFamily = sCreatureFamilyStore.LookupEntry(i);
|
||
if (!creatureFamily) // not exist
|
||
continue;
|
||
|
||
for (uint8 j = 0; j < 2; ++j)
|
||
{
|
||
if (!creatureFamily->SkillLine[j])
|
||
continue;
|
||
|
||
for (uint32 k = 0; k < sSkillLineAbilityStore.GetNumRows(); ++k)
|
||
{
|
||
SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(k);
|
||
if (!skillLine)
|
||
continue;
|
||
|
||
//if (skillLine->skillId != creatureFamily->skillLine[0] &&
|
||
// (!creatureFamily->skillLine[1] || skillLine->skillId != creatureFamily->skillLine[1]))
|
||
// continue;
|
||
|
||
if (skillLine->SkillLine != creatureFamily->SkillLine[j])
|
||
continue;
|
||
|
||
if (skillLine->AcquireMethod != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN)
|
||
continue;
|
||
|
||
SpellInfo const* spell = GetSpellInfo(skillLine->Spell);
|
||
if (!spell) // not exist or triggered or talent
|
||
continue;
|
||
|
||
if (!spell->SpellLevel)
|
||
continue;
|
||
|
||
PetLevelupSpellSet& spellSet = mPetLevelupSpellMap[creatureFamily->ID];
|
||
if (spellSet.empty())
|
||
++family_count;
|
||
|
||
spellSet.insert(PetLevelupSpellSet::value_type(spell->SpellLevel, spell->Id));
|
||
++count;
|
||
}
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u pet levelup and default spells for %u families in %u ms", count, family_count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
bool LoadPetDefaultSpells_helper(CreatureTemplate const* cInfo, PetDefaultSpellsEntry& petDefSpells)
|
||
{
|
||
// skip empty list;
|
||
bool have_spell = false;
|
||
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
||
{
|
||
if (petDefSpells.spellid[j])
|
||
{
|
||
have_spell = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!have_spell)
|
||
return false;
|
||
|
||
// remove duplicates with levelupSpells if any
|
||
if (PetLevelupSpellSet const* levelupSpells = cInfo->family ? sSpellMgr->GetPetLevelupSpellList(cInfo->family) : nullptr)
|
||
{
|
||
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
||
{
|
||
if (!petDefSpells.spellid[j])
|
||
continue;
|
||
|
||
for (PetLevelupSpellSet::const_iterator itr = levelupSpells->begin(); itr != levelupSpells->end(); ++itr)
|
||
{
|
||
if (itr->second == petDefSpells.spellid[j])
|
||
{
|
||
petDefSpells.spellid[j] = 0;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// skip empty list;
|
||
have_spell = false;
|
||
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
||
{
|
||
if (petDefSpells.spellid[j])
|
||
{
|
||
have_spell = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
return have_spell;
|
||
}
|
||
|
||
void SpellMgr::LoadPetDefaultSpells()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mPetDefaultSpellsMap.clear();
|
||
|
||
uint32 countCreature = 0;
|
||
uint32 countData = 0;
|
||
|
||
CreatureTemplateContainer const* ctc = sObjectMgr->GetCreatureTemplates();
|
||
for (CreatureTemplateContainer::const_iterator itr = ctc->begin(); itr != ctc->end(); ++itr)
|
||
{
|
||
|
||
if (!itr->second.PetSpellDataId)
|
||
continue;
|
||
|
||
// for creature with PetSpellDataId get default pet spells from dbc
|
||
CreatureSpellDataEntry const* spellDataEntry = sCreatureSpellDataStore.LookupEntry(itr->second.PetSpellDataId);
|
||
if (!spellDataEntry)
|
||
continue;
|
||
|
||
int32 petSpellsId = -int32(itr->second.PetSpellDataId);
|
||
PetDefaultSpellsEntry petDefSpells;
|
||
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
||
petDefSpells.spellid[j] = spellDataEntry->Spells[j];
|
||
|
||
if (LoadPetDefaultSpells_helper(&itr->second, petDefSpells))
|
||
{
|
||
mPetDefaultSpellsMap[petSpellsId] = petDefSpells;
|
||
++countData;
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded addition spells for %u pet spell data entries in %u ms", countData, GetMSTimeDiffToNow(oldMSTime));
|
||
|
||
TC_LOG_INFO("server.loading", "Loading summonable creature templates...");
|
||
oldMSTime = getMSTime();
|
||
|
||
// different summon spells
|
||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||
{
|
||
SpellInfo const* spellEntry = GetSpellInfo(i);
|
||
if (!spellEntry)
|
||
continue;
|
||
|
||
for (uint8 k = 0; k < MAX_SPELL_EFFECTS; ++k)
|
||
{
|
||
if (spellEntry->Effects[k].Effect == SPELL_EFFECT_SUMMON || spellEntry->Effects[k].Effect == SPELL_EFFECT_SUMMON_PET)
|
||
{
|
||
uint32 creature_id = spellEntry->Effects[k].MiscValue;
|
||
CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(creature_id);
|
||
if (!cInfo)
|
||
continue;
|
||
|
||
// already loaded
|
||
if (cInfo->PetSpellDataId)
|
||
continue;
|
||
|
||
// for creature without PetSpellDataId get default pet spells from creature_template
|
||
int32 petSpellsId = cInfo->Entry;
|
||
if (mPetDefaultSpellsMap.find(cInfo->Entry) != mPetDefaultSpellsMap.end())
|
||
continue;
|
||
|
||
PetDefaultSpellsEntry petDefSpells;
|
||
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
||
petDefSpells.spellid[j] = cInfo->spells[j];
|
||
|
||
if (LoadPetDefaultSpells_helper(cInfo, petDefSpells))
|
||
{
|
||
mPetDefaultSpellsMap[petSpellsId] = petDefSpells;
|
||
++countCreature;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u summonable creature templates in %u ms", countCreature, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellAreas()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
mSpellAreaMap.clear(); // need for reload case
|
||
mSpellAreaForAreaMap.clear();
|
||
mSpellAreaForQuestMap.clear();
|
||
mSpellAreaForQuestEndMap.clear();
|
||
mSpellAreaForAuraMap.clear();
|
||
mSpellAreaForQuestAreaMap.clear();
|
||
|
||
// 0 1 2 3 4 5 6 7 8 9
|
||
QueryResult result = WorldDatabase.Query("SELECT spell, area, quest_start, quest_start_status, quest_end_status, quest_end, aura_spell, racemask, gender, flags FROM spell_area");
|
||
if (!result)
|
||
{
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell area requirements. DB table `spell_area` is empty.");
|
||
|
||
return;
|
||
}
|
||
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 spell = fields[0].GetUInt32();
|
||
SpellArea spellArea;
|
||
spellArea.spellId = spell;
|
||
spellArea.areaId = fields[1].GetUInt32();
|
||
spellArea.questStart = fields[2].GetUInt32();
|
||
spellArea.questStartStatus = fields[3].GetUInt32();
|
||
spellArea.questEndStatus = fields[4].GetUInt32();
|
||
spellArea.questEnd = fields[5].GetUInt32();
|
||
spellArea.auraSpell = fields[6].GetInt32();
|
||
spellArea.raceMask = fields[7].GetUInt32();
|
||
spellArea.gender = Gender(fields[8].GetUInt8());
|
||
spellArea.flags = fields[9].GetUInt8();
|
||
|
||
if (SpellInfo const* spellInfo = GetSpellInfo(spell))
|
||
{
|
||
if (spellArea.flags & SPELL_AREA_FLAG_AUTOCAST)
|
||
const_cast<SpellInfo*>(spellInfo)->Attributes |= SPELL_ATTR0_CANT_CANCEL;
|
||
}
|
||
else
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` does not exist", spell);
|
||
continue;
|
||
}
|
||
|
||
{
|
||
bool ok = true;
|
||
SpellAreaMapBounds sa_bounds = GetSpellAreaMapBounds(spellArea.spellId);
|
||
for (SpellAreaMap::const_iterator itr = sa_bounds.first; itr != sa_bounds.second; ++itr)
|
||
{
|
||
if (spellArea.spellId != itr->second.spellId)
|
||
continue;
|
||
if (spellArea.areaId != itr->second.areaId)
|
||
continue;
|
||
if (spellArea.questStart != itr->second.questStart)
|
||
continue;
|
||
if (spellArea.auraSpell != itr->second.auraSpell)
|
||
continue;
|
||
if ((spellArea.raceMask & itr->second.raceMask) == 0)
|
||
continue;
|
||
if (spellArea.gender != itr->second.gender)
|
||
continue;
|
||
|
||
// duplicate by requirements
|
||
ok = false;
|
||
break;
|
||
}
|
||
|
||
if (!ok)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` is already listed with similar requirements.", spell);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (spellArea.areaId && !sAreaTableStore.LookupEntry(spellArea.areaId))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has a wrong area (%u) requirement.", spell, spellArea.areaId);
|
||
continue;
|
||
}
|
||
|
||
if (spellArea.questStart && !sObjectMgr->GetQuestTemplate(spellArea.questStart))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has a wrong start quest (%u) requirement.", spell, spellArea.questStart);
|
||
continue;
|
||
}
|
||
|
||
if (spellArea.questEnd)
|
||
{
|
||
if (!sObjectMgr->GetQuestTemplate(spellArea.questEnd))
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has a wrong ending quest (%u) requirement.", spell, spellArea.questEnd);
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (spellArea.auraSpell)
|
||
{
|
||
SpellInfo const* spellInfo = GetSpellInfo(abs(spellArea.auraSpell));
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has wrong aura spell (%u) requirement", spell, abs(spellArea.auraSpell));
|
||
continue;
|
||
}
|
||
|
||
if (uint32(abs(spellArea.auraSpell)) == spellArea.spellId)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has aura spell (%u) requirement for itself", spell, abs(spellArea.auraSpell));
|
||
continue;
|
||
}
|
||
|
||
// not allow autocast chains by auraSpell field (but allow use as alternative if not present)
|
||
if (spellArea.flags & SPELL_AREA_FLAG_AUTOCAST && spellArea.auraSpell > 0)
|
||
{
|
||
bool chain = false;
|
||
SpellAreaForAuraMapBounds saBound = GetSpellAreaForAuraMapBounds(spellArea.spellId);
|
||
for (SpellAreaForAuraMap::const_iterator itr = saBound.first; itr != saBound.second; ++itr)
|
||
{
|
||
if (itr->second->flags & SPELL_AREA_FLAG_AUTOCAST && itr->second->auraSpell > 0)
|
||
{
|
||
chain = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (chain)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has the aura spell (%u) requirement that it autocasts itself from the aura.", spell, spellArea.auraSpell);
|
||
continue;
|
||
}
|
||
|
||
SpellAreaMapBounds saBound2 = GetSpellAreaMapBounds(spellArea.auraSpell);
|
||
for (SpellAreaMap::const_iterator itr2 = saBound2.first; itr2 != saBound2.second; ++itr2)
|
||
{
|
||
if (itr2->second.flags & SPELL_AREA_FLAG_AUTOCAST && itr2->second.auraSpell > 0)
|
||
{
|
||
chain = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (chain)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has the aura spell (%u) requirement that the spell itself autocasts from the aura.", spell, spellArea.auraSpell);
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (spellArea.raceMask && (spellArea.raceMask & RACEMASK_ALL_PLAYABLE) == 0)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has wrong race mask (%u) requirement.", spell, spellArea.raceMask);
|
||
continue;
|
||
}
|
||
|
||
if (spellArea.gender != GENDER_NONE && spellArea.gender != GENDER_FEMALE && spellArea.gender != GENDER_MALE)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "The spell %u listed in `spell_area` has wrong gender (%u) requirement.", spell, spellArea.gender);
|
||
continue;
|
||
}
|
||
|
||
SpellArea* sa = &mSpellAreaMap.insert(SpellAreaMap::value_type(spell, spellArea))->second;
|
||
|
||
// for search by current zone/subzone at zone/subzone change
|
||
if (spellArea.areaId)
|
||
mSpellAreaForAreaMap.insert(SpellAreaForAreaMap::value_type(spellArea.areaId, sa));
|
||
|
||
// for search at quest start/reward
|
||
if (spellArea.questStart)
|
||
mSpellAreaForQuestMap.insert(SpellAreaForQuestMap::value_type(spellArea.questStart, sa));
|
||
|
||
// for search at quest start/reward
|
||
if (spellArea.questEnd)
|
||
mSpellAreaForQuestEndMap.insert(SpellAreaForQuestMap::value_type(spellArea.questEnd, sa));
|
||
|
||
// for search at aura apply
|
||
if (spellArea.auraSpell)
|
||
mSpellAreaForAuraMap.insert(SpellAreaForAuraMap::value_type(abs(spellArea.auraSpell), sa));
|
||
|
||
if (spellArea.areaId && spellArea.questStart)
|
||
mSpellAreaForQuestAreaMap.insert(SpellAreaForQuestAreaMap::value_type(std::pair<uint32, uint32>(spellArea.areaId, spellArea.questStart), sa));
|
||
|
||
if (spellArea.areaId && spellArea.questEnd)
|
||
mSpellAreaForQuestAreaMap.insert(SpellAreaForQuestAreaMap::value_type(std::pair<uint32, uint32>(spellArea.areaId, spellArea.questEnd), sa));
|
||
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell area requirements in %u ms", count, GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellInfoStore()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
UnloadSpellInfoStore();
|
||
mSpellInfoMap.resize(sSpellStore.GetNumRows(), nullptr);
|
||
|
||
// Temporary structure to hold spell effect entries for faster loading
|
||
struct SpellEffectArray
|
||
{
|
||
SpellEffectEntry const* effects[MAX_SPELL_EFFECTS] = { };
|
||
};
|
||
|
||
std::unordered_map<uint32, SpellEffectArray> effectsBySpell;
|
||
for (SpellEffectEntry const* effect : sSpellEffectStore)
|
||
effectsBySpell[effect->SpellID].effects[effect->EffectIndex] = effect;
|
||
|
||
for (SpellEntry const* spellEntry : sSpellStore)
|
||
mSpellInfoMap[spellEntry->ID] = new SpellInfo(spellEntry, effectsBySpell[spellEntry->ID].effects);
|
||
|
||
for (uint32 spellIndex = 0; spellIndex < GetSpellInfoStoreSize(); ++spellIndex)
|
||
{
|
||
if (!mSpellInfoMap[spellIndex])
|
||
continue;
|
||
|
||
for (auto const& effect : mSpellInfoMap[spellIndex]->Effects)
|
||
{
|
||
//ASSERT(effect.EffectIndex < MAX_SPELL_EFFECTS, "MAX_SPELL_EFFECTS must be at least %u", effect.EffectIndex + 1);
|
||
ASSERT(effect.Effect < TOTAL_SPELL_EFFECTS, "TOTAL_SPELL_EFFECTS must be at least %u", effect.Effect + 1);
|
||
ASSERT(effect.ApplyAuraName < TOTAL_AURAS, "TOTAL_AURAS must be at least %u", effect.ApplyAuraName + 1);
|
||
ASSERT(effect.TargetA.GetTarget() < TOTAL_SPELL_TARGETS, "TOTAL_SPELL_TARGETS must be at least %u", effect.TargetA.GetTarget() + 1);
|
||
ASSERT(effect.TargetB.GetTarget() < TOTAL_SPELL_TARGETS, "TOTAL_SPELL_TARGETS must be at least %u", effect.TargetB.GetTarget() + 1);
|
||
}
|
||
}
|
||
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded SpellInfo store in %u ms", GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::UnloadSpellInfoStore()
|
||
{
|
||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||
delete mSpellInfoMap[i];
|
||
|
||
mSpellInfoMap.clear();
|
||
}
|
||
|
||
void SpellMgr::UnloadSpellInfoImplicitTargetConditionLists()
|
||
{
|
||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||
if (mSpellInfoMap[i])
|
||
mSpellInfoMap[i]->_UnloadImplicitTargetConditionLists();
|
||
}
|
||
|
||
void SpellMgr::UnloadSpellAreaConditions()
|
||
{
|
||
for (auto spellAreaData : mSpellAreaForAreaMap)
|
||
spellAreaData.second->Conditions.clear();
|
||
}
|
||
|
||
void SpellMgr::LoadSpellInfoCustomAttributes()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
uint32 oldMSTime2 = oldMSTime;
|
||
SpellInfo* spellInfo = nullptr;
|
||
|
||
QueryResult result = WorldDatabase.Query("SELECT entry, attributes FROM spell_custom_attr");
|
||
|
||
if (!result)
|
||
TC_LOG_INFO("server.loading", ">> Loaded 0 spell custom attributes from DB. DB table `spell_custom_attr` is empty.");
|
||
else
|
||
{
|
||
uint32 count = 0;
|
||
do
|
||
{
|
||
Field* fields = result->Fetch();
|
||
|
||
uint32 spellId = fields[0].GetUInt32();
|
||
uint32 attributes = fields[1].GetUInt32();
|
||
|
||
spellInfo = _GetSpellInfo(spellId);
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("sql.sql", "Table `spell_custom_attr` has wrong spell (entry: %u), ignored.", spellId);
|
||
continue;
|
||
}
|
||
|
||
// TODO: validate attributes
|
||
|
||
spellInfo->AttributesCu |= attributes;
|
||
++count;
|
||
} while (result->NextRow());
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded %u spell custom attributes from DB in %u ms", count, GetMSTimeDiffToNow(oldMSTime2));
|
||
}
|
||
|
||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||
{
|
||
spellInfo = mSpellInfoMap[i];
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
||
{
|
||
switch (spellInfo->Effects[j].ApplyAuraName)
|
||
{
|
||
case SPELL_AURA_PERIODIC_HEAL:
|
||
case SPELL_AURA_PERIODIC_DAMAGE:
|
||
case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
|
||
case SPELL_AURA_PERIODIC_LEECH:
|
||
case SPELL_AURA_PERIODIC_MANA_LEECH:
|
||
case SPELL_AURA_PERIODIC_HEALTH_FUNNEL:
|
||
case SPELL_AURA_PERIODIC_ENERGIZE:
|
||
case SPELL_AURA_OBS_MOD_HEALTH:
|
||
case SPELL_AURA_OBS_MOD_POWER:
|
||
case SPELL_AURA_POWER_BURN:
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_INITIAL_THREAT;
|
||
break;
|
||
}
|
||
|
||
switch (spellInfo->Effects[j].Effect)
|
||
{
|
||
case SPELL_EFFECT_SCHOOL_DAMAGE:
|
||
case SPELL_EFFECT_WEAPON_DAMAGE:
|
||
case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL:
|
||
case SPELL_EFFECT_NORMALIZED_WEAPON_DMG:
|
||
case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE:
|
||
case SPELL_EFFECT_HEAL:
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_DIRECT_DAMAGE;
|
||
break;
|
||
case SPELL_EFFECT_POWER_DRAIN:
|
||
case SPELL_EFFECT_POWER_BURN:
|
||
case SPELL_EFFECT_HEAL_MAX_HEALTH:
|
||
case SPELL_EFFECT_HEALTH_LEECH:
|
||
case SPELL_EFFECT_HEAL_PCT:
|
||
case SPELL_EFFECT_ENERGIZE_PCT:
|
||
case SPELL_EFFECT_ENERGIZE:
|
||
case SPELL_EFFECT_HEAL_MECHANICAL:
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_INITIAL_THREAT;
|
||
break;
|
||
case SPELL_EFFECT_CHARGE:
|
||
case SPELL_EFFECT_CHARGE_DEST:
|
||
case SPELL_EFFECT_JUMP:
|
||
case SPELL_EFFECT_JUMP_DEST:
|
||
case SPELL_EFFECT_LEAP_BACK:
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CHARGE;
|
||
break;
|
||
case SPELL_EFFECT_PICKPOCKET:
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_PICKPOCKET;
|
||
break;
|
||
case SPELL_EFFECT_ENCHANT_ITEM:
|
||
case SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY:
|
||
case SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC:
|
||
case SPELL_EFFECT_ENCHANT_HELD_ITEM:
|
||
{
|
||
// only enchanting profession enchantments procs can stack
|
||
if (IsPartOfSkillLine(SKILL_ENCHANTING, i))
|
||
{
|
||
uint32 enchantId = spellInfo->Effects[j].MiscValue;
|
||
SpellItemEnchantmentEntry const* enchant = sSpellItemEnchantmentStore.LookupEntry(enchantId);
|
||
if (!enchant)
|
||
break;
|
||
|
||
for (uint8 s = 0; s < MAX_ITEM_ENCHANTMENT_EFFECTS; ++s)
|
||
{
|
||
if (enchant->Effect[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL)
|
||
continue;
|
||
|
||
SpellInfo* procInfo = _GetSpellInfo(enchant->EffectArg[s]);
|
||
if (!procInfo)
|
||
continue;
|
||
|
||
// if proced directly from enchantment, not via proc aura
|
||
// NOTE: Enchant Weapon - Blade Ward also has proc aura spell and is proced directly
|
||
// however its not expected to stack so this check is good
|
||
if (procInfo->HasAura(SPELL_AURA_PROC_TRIGGER_SPELL))
|
||
continue;
|
||
|
||
procInfo->AttributesCu |= SPELL_ATTR0_CU_ENCHANT_PROC;
|
||
}
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!spellInfo->_IsPositiveEffect(EFFECT_0, false))
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF0;
|
||
|
||
if (!spellInfo->_IsPositiveEffect(EFFECT_1, false))
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF1;
|
||
|
||
if (!spellInfo->_IsPositiveEffect(EFFECT_2, false))
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF2;
|
||
|
||
spellInfo->_InitializeExplicitTargetMask();
|
||
}
|
||
|
||
// add custom attribute to liquid auras
|
||
for (LiquidTypeEntry const* liquid : sLiquidTypeStore)
|
||
{
|
||
if (liquid->SpellID)
|
||
{
|
||
spellInfo = _GetSpellInfo(liquid->SpellID);
|
||
if (spellInfo)
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_LIQUID_AURA;
|
||
}
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded SpellInfo custom attributes in %u ms", GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
inline void ApplySpellFix(std::initializer_list<uint32> spellIds, void(*fix)(SpellInfo*))
|
||
{
|
||
for (uint32 spellId : spellIds)
|
||
{
|
||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId);
|
||
if (!spellInfo)
|
||
{
|
||
TC_LOG_ERROR("server.loading", "Spell info correction specified for non-existing spell %u", spellId);
|
||
continue;
|
||
}
|
||
|
||
fix(const_cast<SpellInfo*>(spellInfo));
|
||
}
|
||
}
|
||
|
||
void SpellMgr::LoadSpellInfoCorrections()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
ApplySpellFix({
|
||
63026, // Force Cast (HACK: Target shouldn't be changed)
|
||
63137 // Force Cast (HACK: Target shouldn't be changed; summon position should be untied from spell destination)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DB);
|
||
});
|
||
|
||
// Drink! (Brewfest)
|
||
ApplySpellFix({ 42436 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY);
|
||
});
|
||
|
||
// Summon Skeletons
|
||
ApplySpellFix({ 52611, 52612 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MiscValueB = 64;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
40244, // Simon Game Visual
|
||
40245, // Simon Game Visual
|
||
40246, // Simon Game Visual
|
||
40247, // Simon Game Visual
|
||
42835 // Spout, remove damage effect, only anim is needed
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Effect = 0;
|
||
});
|
||
|
||
// Immolate
|
||
ApplySpellFix({ 348 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// copy SP scaling data from direct damage to DoT
|
||
spellInfo->Effects[EFFECT_0].BonusMultiplier = spellInfo->Effects[EFFECT_1].BonusMultiplier;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
82690, // Flame Orb
|
||
84717 // Frostfire Orb
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Amplitude = 1000;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
63665, // Charge (Argent Tournament emote on riders)
|
||
31298, // Sleep (needs target selection script)
|
||
51904, // Summon Ghouls On Scarlet Crusade (this should use conditions table, script for this spell needs to be fixed)
|
||
2895, // Wrath of Air Totem rank 1 (Aura)
|
||
68933, // Wrath of Air Totem rank 2 (Aura)
|
||
29200 // Purify Helboar Meat
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo();
|
||
});
|
||
|
||
ApplySpellFix({
|
||
56690, // Thrust Spear
|
||
60586, // Mighty Spear Thrust
|
||
60776, // Claw Swipe
|
||
60881, // Fatal Strike
|
||
60864 // Jaws of Death
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx4 |= SPELL_ATTR4_FIXED_DAMAGE;
|
||
});
|
||
|
||
// Immolate
|
||
ApplySpellFix({ 348 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// copy SP scaling data from direct damage to DoT
|
||
spellInfo->Effects[EFFECT_0].BonusMultiplier = spellInfo->Effects[EFFECT_1].BonusMultiplier;
|
||
});
|
||
|
||
// Howl of Azgalor
|
||
ApplySpellFix({ 31344 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_100_YARDS); // 100yards instead of 50000?!
|
||
});
|
||
|
||
ApplySpellFix({
|
||
42818, // Headless Horseman - Wisp Flight Port
|
||
42821 // Headless Horseman - Wisp Flight Missile
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); // 100 yards
|
||
});
|
||
|
||
// They Must Burn Bomb Aura (self)
|
||
ApplySpellFix({ 36350 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TriggerSpell = 36325; // They Must Burn Bomb Drop (DND)
|
||
});
|
||
|
||
ApplySpellFix({
|
||
61407, // Energize Cores
|
||
62136, // Energize Cores
|
||
54069, // Energize Cores
|
||
56251 // Energize Cores
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ENTRY);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
50785, // Energize Cores
|
||
59372 // Energize Cores
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ENEMY);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
63320, // Glyph of Life Tap
|
||
53228, // Rapid Killing (Rank 1)
|
||
53232 // Rapid Killing (Rank 2)
|
||
// Entries were not updated after spell effect change, we have to do that manually :/
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_CAN_PROC_WITH_TRIGGERED;
|
||
});
|
||
|
||
// Improved Spell Reflection - aoe aura
|
||
ApplySpellFix({ 59725 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// Target entry seems to be wrong for this spell :/
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER_AREA_PARTY);
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_20_YARDS);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
31347, // Doom
|
||
36327, // Shoot Arcane Explosion Arrow
|
||
39365, // Thundering Storm
|
||
41071, // Raise Dead (HACK)
|
||
42442, // Vengeance Landing Cannonfire
|
||
42611, // Shoot
|
||
44978, // Wild Magic
|
||
45001, // Wild Magic
|
||
45002, // Wild Magic
|
||
45004, // Wild Magic
|
||
45006, // Wild Magic
|
||
45010, // Wild Magic
|
||
45761, // Shoot Gun
|
||
45863, // Cosmetic - Incinerate to Random Target
|
||
48246, // Ball of Flame
|
||
41635, // Prayer of Mending
|
||
44869, // Spectral Blast
|
||
45027, // Revitalize
|
||
45976, // Muru Portal Channel
|
||
52124, // Sky Darkener Assault
|
||
52479, // Gift of the Harvester
|
||
61588, // Blazing Harpoon
|
||
55479, // Force Obedience
|
||
28560, // Summon Blizzard (Sapphiron)
|
||
53096, // Quetz'lun's Judgment
|
||
70743, // AoD Special
|
||
70614, // AoD Special - Vegard
|
||
4020, // Safirdrang's Chill
|
||
52438, // Summon Skittering Swarmer (Force Cast)
|
||
52449, // Summon Skittering Infector (Force Cast)
|
||
53609, // Summon Anub'ar Assassin (Force Cast)
|
||
53457 // Summon Impale Trigger (AoE
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Skartax Purple Beam
|
||
ApplySpellFix({ 36384 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 2;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
28542, // Life Drain - Sapphiron
|
||
29213, // Curse of the Plaguebringer - Noth
|
||
29576, // Multi-Shot
|
||
37790, // Spread Shot
|
||
39992, // Needle Spine
|
||
40816, // Saber Lash
|
||
41303, // Soul Drain
|
||
41376, // Spite
|
||
45248, // Shadow Blades
|
||
46771, // Flame Sear
|
||
54171, // Divine Storm
|
||
54172, // Divine Storm (heal)
|
||
66588 // Flaming Spear
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
42005, // Bloodboil
|
||
38296, // Spitfire Totem
|
||
37676, // Insidious Whisper
|
||
46008, // Negative Energy
|
||
45641, // Fire Bloom
|
||
55665, // Life Drain - Sapphiron (H)
|
||
28796 // Poison Bolt Volly - Faerlina
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 5;
|
||
});
|
||
|
||
// Curse of the Plaguebringer - Noth (H)
|
||
ApplySpellFix({ 54835 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 8;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
40827, // Sinful Beam
|
||
40859, // Sinister Beam
|
||
40860, // Vile Beam
|
||
40861, // Wicked Beam
|
||
54098 // Poison Bolt Volly - Faerlina (H)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 10;
|
||
});
|
||
|
||
// Unholy Frenzy
|
||
ApplySpellFix({ 50312 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 15;
|
||
});
|
||
|
||
// Murmur's Touch
|
||
ApplySpellFix({ 33711, 38794 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
spellInfo->Effects[EFFECT_0].TriggerSpell = 33760;
|
||
});
|
||
|
||
// Magic Suppression - DK
|
||
ApplySpellFix({ 49224, 49610, 49611 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->ProcCharges = 0;
|
||
});
|
||
|
||
// Oscillation Field
|
||
ApplySpellFix({ 37408 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
|
||
});
|
||
|
||
// Everlasting Affliction
|
||
ApplySpellFix({ 47201, 47202, 47203 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// add corruption to affected spells
|
||
spellInfo->Effects[EFFECT_1].SpellClassMask[0] |= 2;
|
||
});
|
||
|
||
// Summon Ravenous Worgen
|
||
// Serverside dummy target so we have to change to spell_target_position
|
||
ApplySpellFix({ 66925, 66836 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = TARGET_DEST_DB;
|
||
});
|
||
|
||
// Pull-to
|
||
ApplySpellFix({ 67357 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MiscValue = 150;
|
||
});
|
||
|
||
// Renewed Hope
|
||
ApplySpellFix({
|
||
57470, // (Rank 1)
|
||
57472 // (Rank 2)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
// should also affect Flash Heal
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask[0] |= 0x800;
|
||
});
|
||
|
||
// Cobra Strikes
|
||
ApplySpellFix({ 53257 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->ProcCharges = 2;
|
||
spellInfo->StackAmount = 0;
|
||
});
|
||
|
||
// Wrecking Crew
|
||
ApplySpellFix({ 46867, 56611, 56612 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask = flag96(0x02000000, 0, 0);
|
||
});
|
||
|
||
// Death and Decay
|
||
ApplySpellFix({ 52212 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_INVISIBLE;
|
||
});
|
||
|
||
// Crafty's Ultra-Advanced Proto-Typical Shortening Blaster
|
||
ApplySpellFix({ 51912 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Amplitude = 3000;
|
||
});
|
||
|
||
// Master Shapeshifter: missing stance data for forms other than bear - bear version has correct data
|
||
// To prevent aura staying on target after talent unlearned
|
||
ApplySpellFix({
|
||
48420, // Master Shapeshifter
|
||
24900 // Heart of the Wild - Cat Effect
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Stances = UI64LIT(1) << (FORM_CAT - 1);
|
||
});
|
||
|
||
ApplySpellFix({ 48421 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Stances = UI64LIT(1) << (FORM_MOONKIN - 1);
|
||
});
|
||
|
||
ApplySpellFix({ 24899 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Stances = UI64LIT(1) << (FORM_BEAR - 1);
|
||
});
|
||
|
||
// Tree of Life passives
|
||
|
||
ApplySpellFix({
|
||
5420,
|
||
81097
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Stances = 1 << (FORM_TREE - 1);
|
||
});
|
||
|
||
// Elemental Oath
|
||
ApplySpellFix({
|
||
51466, // (Rank 1)
|
||
51470 // (Rank 2)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].Effect = SPELL_EFFECT_APPLY_AURA;
|
||
spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_ADD_FLAT_MODIFIER;
|
||
spellInfo->Effects[EFFECT_1].MiscValue = SPELLMOD_EFFECT2;
|
||
spellInfo->Effects[EFFECT_1].SpellClassMask = flag96(0x00000000, 0x00004000, 0x00000000);
|
||
});
|
||
|
||
// Improved Shadowform (Rank 1)
|
||
ApplySpellFix({ 47569 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// with this spell atrribute aura can be stacked several times
|
||
spellInfo->Attributes &= ~SPELL_ATTR0_NOT_SHAPESHIFT;
|
||
});
|
||
|
||
// Hymn of Hope
|
||
ApplySpellFix({ 64904 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_MOD_INCREASE_ENERGY_PERCENT;
|
||
});
|
||
|
||
// Nether Portal - Perseverence
|
||
ApplySpellFix({ 30421 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_2].BasePoints += 30000;
|
||
});
|
||
|
||
// Parasitic Shadowfiend Passive
|
||
ApplySpellFix({ 41913 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// proc debuff, and summon infinite fiends
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_DUMMY;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
27892, // To Anchor 1
|
||
27928, // To Anchor 1
|
||
27935, // To Anchor 1
|
||
27915, // Anchor to Skulls
|
||
27931, // Anchor to Skulls
|
||
27937, // Anchor to Skulls
|
||
16177, // Ancestral Fortitude (Rank 1)
|
||
16236, // Ancestral Fortitude (Rank 2)
|
||
47930, // Grace
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13);
|
||
});
|
||
|
||
// Wrath of the Plaguebringer
|
||
ApplySpellFix({ 29214, 54836 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// target allys instead of enemies, target A is src_caster, spells with effect like that have ally target
|
||
// this is the only known exception, probably just wrong data
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY);
|
||
spellInfo->Effects[EFFECT_1].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ALLY);
|
||
});
|
||
|
||
// Vampiric Embrace
|
||
ApplySpellFix({ 15290 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Vampiric Touch (dispel effect)
|
||
ApplySpellFix({ 64085 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// copy from similar effect of Unstable Affliction (31117)
|
||
spellInfo->AttributesEx4 |= SPELL_ATTR4_FIXED_DAMAGE;
|
||
spellInfo->AttributesEx6 |= SPELL_ATTR6_LIMIT_PCT_DAMAGE_MODS;
|
||
});
|
||
|
||
// Improved Devouring Plague
|
||
ApplySpellFix({ 63675 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS;
|
||
});
|
||
|
||
// Tremor Totem (instant pulse)
|
||
ApplySpellFix({ 8145 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
spellInfo->AttributesEx5 |= SPELL_ATTR5_START_PERIODIC_AT_APPLY;
|
||
});
|
||
|
||
// Earthbind Totem (instant pulse)
|
||
ApplySpellFix({ 6474 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx5 |= SPELL_ATTR5_START_PERIODIC_AT_APPLY;
|
||
});
|
||
|
||
// Marked for Death
|
||
ApplySpellFix({
|
||
53241, // (Rank 1)
|
||
53243, // (Rank 2)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask = flag96(0x00067801, 0x10820001, 0x00000801);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
70728, // Exploit Weakness (needs target selection script)
|
||
70840 // Devious Minds (needs target selection script)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_PET);
|
||
});
|
||
|
||
// Culling The Herd (needs target selection script)
|
||
ApplySpellFix({ 70893 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_MASTER);
|
||
});
|
||
|
||
// Sigil of the Frozen Conscience
|
||
ApplySpellFix({ 54800 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// change class mask to custom extended flags of Icy Touch
|
||
// this is done because another spell also uses the same SpellFamilyFlags as Icy Touch
|
||
// SpellFamilyFlags[0] & 0x00000040 in SPELLFAMILY_DEATHKNIGHT is currently unused (3.3.5a)
|
||
// this needs research on modifier applying rules, does not seem to be in Attributes fields
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask = flag96(0x00000040, 0x00000000, 0x00000000);
|
||
});
|
||
|
||
// Idol of the Flourishing Life
|
||
ApplySpellFix({ 64949 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask = flag96(0x00000000, 0x02000000, 0x00000000);
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_ADD_FLAT_MODIFIER;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
34231, // Libram of the Lightbringer
|
||
60792, // Libram of Tolerance
|
||
64956 // Libram of the Resolute
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask = flag96(0x80000000, 0x00000000, 0x00000000);
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_ADD_FLAT_MODIFIER;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
28851, // Libram of Light
|
||
28853, // Libram of Divinity
|
||
32403 // Blessed Book of Nagrand
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask = flag96(0x40000000, 0x00000000, 0x00000000);
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_ADD_FLAT_MODIFIER;
|
||
});
|
||
|
||
// Ride Carpet
|
||
ApplySpellFix({ 45602 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// force seat 0, vehicle doesn't have the required seat flags for "no seat specified (-1)"
|
||
spellInfo->Effects[EFFECT_0].BasePoints = 0;
|
||
});
|
||
|
||
// Easter Lay Noblegarden Egg Aura
|
||
ApplySpellFix({ 61719 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// Interrupt flags copied from aura which this aura is linked with
|
||
spellInfo->AuraInterruptFlags = AURA_INTERRUPT_FLAG_HITBYSPELL | AURA_INTERRUPT_FLAG_TAKE_DAMAGE;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
71838, // Drain Life - Bryntroll Normal
|
||
71839 // Drain Life - Bryntroll Heroic
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
56606, // Ride Jokkum
|
||
61791 // Ride Vehicle (Yogg-Saron)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
/// @todo: remove this when basepoints of all Ride Vehicle auras are calculated correctly
|
||
spellInfo->Effects[EFFECT_0].BasePoints = 1;
|
||
});
|
||
|
||
// Black Magic
|
||
ApplySpellFix({ 59630 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Attributes |= SPELL_ATTR0_PASSIVE;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
17364, // Stormstrike
|
||
48278, // Paralyze
|
||
53651 // Light's Beacon
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
51798, // Brewfest - Relay Race - Intro - Quest Complete
|
||
47134 // Quest Complete
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
//! HACK: This spell break quest complete for alliance and on retail not used <20>_O
|
||
spellInfo->Effects[EFFECT_0].Effect = 0;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
15487, // Priest - Silence
|
||
47476, // Deathknight - Strangulate
|
||
69179 // Arcane Torrent (Rage)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx7 |= SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
42490, // Energized!
|
||
42492, // Cast Energized
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx |= SPELL_ATTR1_NO_THREAT;
|
||
});
|
||
|
||
// Siege Cannon (Tol Barad)
|
||
ApplySpellFix({ 85123 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS);
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_SRC_AREA_ENTRY);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
46842, // Flame Ring
|
||
46836 // Flame Patch
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo();
|
||
});
|
||
|
||
// Test Ribbon Pole Channel
|
||
ApplySpellFix({ 29726 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->InterruptFlags &= ~AURA_INTERRUPT_FLAG_CAST;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
42767, // Sic'em
|
||
43092 // Stop the Ascension!: Halfdan's Soul Destruction
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_NEARBY_ENTRY);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
19503, // Scatter Shot
|
||
34490 // Silencing Shot
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Speed = 0.f;
|
||
});
|
||
|
||
// Safeguard
|
||
ApplySpellFix({
|
||
46946, // (Rank 1)
|
||
46947 // (Rank 2)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(34); // Twenty-Five yards
|
||
});
|
||
|
||
// Concussive Barrage
|
||
ApplySpellFix({ 35101 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(173); // Anywhere
|
||
});
|
||
|
||
// Survey Sinkholes
|
||
ApplySpellFix({ 45853 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(5); // 40 yards
|
||
});
|
||
|
||
//
|
||
// BLACK TEMPLE SPELLS
|
||
//
|
||
ApplySpellFix({
|
||
41485, // Deadly Poison
|
||
41487 // Envenom
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_INVISIBLE;
|
||
});
|
||
// ENDOF BLACK TEMPLE SPELLS
|
||
|
||
// Summon Corpse Scarabs
|
||
ApplySpellFix({ 28864, 29105 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS);
|
||
});
|
||
|
||
// Tag Greater Felfire Diemetradon
|
||
ApplySpellFix({ 37851 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 3000;
|
||
});
|
||
|
||
// Jormungar Strike
|
||
ApplySpellFix({ 56513 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 2000;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
54997, // Cast Net (tooltip says 10s but sniffs say 6s)
|
||
56524 // Acid Breath
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 6000;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
47911, // EMP
|
||
48620, // Wing Buffet
|
||
51752 // Stampy's Stompy-Stomp
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 10000;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
37727, // Touch of Darkness
|
||
54996 // Ice Slick (tooltip says 20s but sniffs say 12s)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 12000;
|
||
});
|
||
|
||
// Signal Helmet to Attack
|
||
ApplySpellFix({ 51748 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 15000;
|
||
});
|
||
|
||
// Charge
|
||
ApplySpellFix({ 51756 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 20000;
|
||
});
|
||
|
||
// Engulfing Flames
|
||
ApplySpellFix({ 74039 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RecoveryTime = 1000;
|
||
});
|
||
|
||
//
|
||
// VIOLET HOLD SPELLS
|
||
//
|
||
// Water Globule (Ichoron)
|
||
ApplySpellFix({ 54258, 54264, 54265, 54266, 54267 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// in 3.3.5 there is only one radius in dbc which is 0 yards in this
|
||
// use max radius from 4.3.4
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_25_YARDS);
|
||
});
|
||
// ENDOF VIOLET HOLD
|
||
|
||
//
|
||
// ULDUAR SPELLS
|
||
//
|
||
// Pursued (Flame Leviathan)
|
||
ApplySpellFix({ 62374 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
// Focused Eyebeam Summon Trigger (Kologarn)
|
||
ApplySpellFix({ 63342 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
65584, // Growth of Nature (Freya)
|
||
64381 // Strength of the Pack (Auriaya)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
63018, // Searing Light (XT-002)
|
||
65121, // Searing Light (25m) (XT-002)
|
||
63024, // Gravity Bomb (XT-002)
|
||
64234 // Gravity Bomb (25m) (XT-002)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Boom (XT-002)
|
||
ApplySpellFix({ 62834 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// This hack is here because we suspect our implementation of spell effect execution on targets
|
||
// is done in the wrong order. We suspect that EFFECT_0 needs to be applied on all targets,
|
||
// then EFFECT_1, etc - instead of applying each effect on target1, then target2, etc.
|
||
// The above situation causes the visual for this spell to be bugged, so we remove the instakill
|
||
// effect and implement a script hack for that.
|
||
spellInfo->Effects[EFFECT_1].Effect = 0;
|
||
});
|
||
|
||
ApplySpellFix({
|
||
64386, // Terrifying Screech (Auriaya)
|
||
64389, // Sentinel Blast (Auriaya)
|
||
64678 // Sentinel Blast (Auriaya)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(28); // 5 seconds, wrong DBC data?
|
||
});
|
||
|
||
// Summon Swarming Guardian (Auriaya)
|
||
ApplySpellFix({ 64397 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(137); // 8y, Based in BFA effect radius
|
||
});
|
||
|
||
// Potent Pheromones (Freya)
|
||
ApplySpellFix({ 64321 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// spell should dispel area aura, but doesn't have the attribute
|
||
// may be db data bug, or blizz may keep reapplying area auras every update with checking immunity
|
||
// that will be clear if we get more spells with problem like this
|
||
spellInfo->AttributesEx |= SPELL_ATTR1_DISPEL_AURAS_ON_IMMUNITY;
|
||
});
|
||
|
||
// Spinning Up (Mimiron)
|
||
ApplySpellFix({ 63414 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
spellInfo->ChannelInterruptFlags = 0;
|
||
});
|
||
|
||
// Rocket Strike (Mimiron)
|
||
ApplySpellFix({ 63036 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Speed = 0;
|
||
});
|
||
|
||
// Magnetic Field (Mimiron)
|
||
ApplySpellFix({ 64668 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Mechanic = MECHANIC_NONE;
|
||
});
|
||
|
||
// Empowering Shadows (Yogg-Saron)
|
||
ApplySpellFix({ 64468, 64486 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3; // same for both modes?
|
||
});
|
||
|
||
// Cosmic Smash (Algalon the Observer)
|
||
ApplySpellFix({ 62301 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Cosmic Smash (Algalon the Observer)
|
||
ApplySpellFix({ 64598 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Cosmic Smash (Algalon the Observer)
|
||
ApplySpellFix({ 62293 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_DEST_CASTER);
|
||
});
|
||
|
||
// Cosmic Smash (Algalon the Observer)
|
||
ApplySpellFix({ 62311, 64596 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); // 100yd
|
||
});
|
||
|
||
ApplySpellFix({
|
||
64014, // Expedition Base Camp Teleport
|
||
64024, // Conservatory Teleport
|
||
64025, // Halls of Invention Teleport
|
||
64028, // Colossal Forge Teleport
|
||
64029, // Shattered Walkway Teleport
|
||
64030, // Antechamber Teleport
|
||
64031, // Scrapyard Teleport
|
||
64032, // Formation Grounds Teleport
|
||
65042 // Prison of Yogg-Saron Teleport
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DB);
|
||
});
|
||
// ENDOF ULDUAR SPELLS
|
||
|
||
//
|
||
// TRIAL OF THE CRUSADER SPELLS
|
||
//
|
||
// Infernal Eruption
|
||
ApplySpellFix({ 66258, 67901 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// increase duration from 15 to 18 seconds because caster is already
|
||
// unsummoned when spell missile hits the ground so nothing happen in result
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(85);
|
||
});
|
||
// ENDOF TRIAL OF THE CRUSADER SPELLS
|
||
|
||
//
|
||
// HALLS OF REFLECTION SPELLS
|
||
//
|
||
ApplySpellFix({
|
||
72435, // Defiling Horror
|
||
72452 // Defiling Horror
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_60_YARDS); // 60yd
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_60_YARDS); // 60yd
|
||
});
|
||
|
||
// Achievement Check
|
||
ApplySpellFix({ 72830 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
// Start Halls of Reflection Quest AE
|
||
ApplySpellFix({ 72900 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
});
|
||
// ENDOF HALLS OF REFLECTION SPELLS
|
||
|
||
//
|
||
// ICECROWN CITADEL SPELLS
|
||
//
|
||
ApplySpellFix({
|
||
// THESE SPELLS ARE WORKING CORRECTLY EVEN WITHOUT THIS HACK
|
||
// THE ONLY REASON ITS HERE IS THAT CURRENT GRID SYSTEM
|
||
// DOES NOT ALLOW FAR OBJECT SELECTION (dist > 333)
|
||
70781, // Light's Hammer Teleport
|
||
70856, // Oratory of the Damned Teleport
|
||
70857, // Rampart of Skulls Teleport
|
||
70858, // Deathbringer's Rise Teleport
|
||
70859, // Upper Spire Teleport
|
||
70860, // Frozen Throne Teleport
|
||
70861 // Sindragosa's Lair Teleport
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DB);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
69075, // Bone Storm (Lord Marrowgar)
|
||
70834, // Bone Storm (Lord Marrowgar)
|
||
70835, // Bone Storm (Lord Marrowgar)
|
||
70836, // Bone Storm (Lord Marrowgar)
|
||
72864, // Death Plague (Rotting Frost Giant)
|
||
71160, // Plague Stench (Stinky)
|
||
71161, // Plague Stench (Stinky)
|
||
71123 // Decimate (Stinky & Precious)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_100_YARDS); // 100yd
|
||
});
|
||
|
||
// Shadow's Fate
|
||
ApplySpellFix({ 71169 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
|
||
});
|
||
|
||
// Lock Players and Tap Chest
|
||
ApplySpellFix({ 72347 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 &= ~SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Lock and Load (Rank 1)
|
||
ApplySpellFix({ 56342 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1] = spellInfo->Effects[EFFECT_2];
|
||
spellInfo->Effects[EFFECT_2] = SpellEffectInfo();
|
||
});
|
||
|
||
// Highlight (Plants vs. Zombie)
|
||
ApplySpellFix(
|
||
{
|
||
92276,
|
||
94032,
|
||
},
|
||
[](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(35); // 35 Yards
|
||
});
|
||
|
||
ApplySpellFix(
|
||
{
|
||
91748, // Venom Spit
|
||
92441, // Freezya Blast
|
||
},
|
||
[](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_LINE;
|
||
});
|
||
|
||
// Award Reputation - Boss Kill
|
||
ApplySpellFix({ 73843, 73844, 73845, 73846 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
ApplySpellFix({
|
||
72378, // Blood Nova (Deathbringer Saurfang)
|
||
73058, // Blood Nova (Deathbringer Saurfang)
|
||
72769 // Scent of Blood (Deathbringer Saurfang)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS);
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS);
|
||
});
|
||
|
||
// Scent of Blood (Deathbringer Saurfang)
|
||
ApplySpellFix({ 72771 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS);
|
||
});
|
||
|
||
// Resistant Skin (Deathbringer Saurfang adds)
|
||
ApplySpellFix({ 72723 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// this spell initially granted Shadow damage immunity, however it was removed but the data was left in client
|
||
spellInfo->Effects[EFFECT_2].Effect = 0;
|
||
});
|
||
|
||
// Coldflame Jets (Traps after Saurfang)
|
||
ApplySpellFix({ 70460 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(1); // 10 seconds
|
||
});
|
||
|
||
ApplySpellFix({
|
||
71412, // Green Ooze Summon (Professor Putricide)
|
||
71415 // Orange Ooze Summon (Professor Putricide)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY);
|
||
});
|
||
|
||
// Ooze flood
|
||
ApplySpellFix({ 69783, 69797, 69799, 69802 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// Those spells are cast on creatures with same entry as caster while they have TARGET_UNIT_NEARBY_ENTRY.
|
||
spellInfo->AttributesEx |= SPELL_ATTR1_CANT_TARGET_SELF;
|
||
});
|
||
|
||
// Awaken Plagued Zombies
|
||
ApplySpellFix({ 71159 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(21);
|
||
});
|
||
|
||
// Volatile Ooze Beam Protection (Professor Putricide)
|
||
ApplySpellFix({ 70530 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Effect = SPELL_EFFECT_APPLY_AURA; // for an unknown reason this was SPELL_EFFECT_APPLY_AREA_AURA_RAID
|
||
});
|
||
|
||
// Mutated Strength (Professor Putricide)
|
||
ApplySpellFix({ 71604, 72673, 72674, 72675 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// THIS IS HERE BECAUSE COOLDOWN ON CREATURE PROCS WERE NOT IMPLEMENTED WHEN THE SCRIPT WAS WRITTEN
|
||
spellInfo->Effects[EFFECT_1].Effect = 0;
|
||
});
|
||
|
||
// Mutated Plague (Professor Putricide)
|
||
ApplySpellFix({ 72454, 72464, 72506, 72507 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
// Unbound Plague (Professor Putricide) (needs target selection script)
|
||
ApplySpellFix({ 70911, 72854, 72855, 72856 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY);
|
||
});
|
||
|
||
ApplySpellFix({
|
||
71518, // Unholy Infusion Quest Credit (Professor Putricide)
|
||
72934, // Blood Infusion Quest Credit (Blood-Queen Lana'thel)
|
||
72289 // Frost Infusion Quest Credit (Sindragosa)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // another missing radius
|
||
});
|
||
|
||
// Empowered Flare (Blood Prince Council)
|
||
ApplySpellFix({ 71708, 72785, 72786, 72787 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS;
|
||
});
|
||
|
||
// Swarming Shadows
|
||
ApplySpellFix({ 71266, 72890 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AreaGroupId = 0; // originally, these require area 4522, which is... outside of Icecrown Citadel
|
||
});
|
||
|
||
// Corruption
|
||
ApplySpellFix({ 70602 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
|
||
});
|
||
|
||
// Column of Frost (visual marker)
|
||
ApplySpellFix({ 70715 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(32); // 6 seconds (missing)
|
||
});
|
||
|
||
// Mana Void (periodic aura)
|
||
ApplySpellFix({ 71085 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(9); // 30 seconds (missing)
|
||
});
|
||
|
||
// Frostbolt Volley (only heroic)
|
||
ApplySpellFix({ 72015, 72016 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS);
|
||
});
|
||
|
||
// Summon Suppressor (needs target selection script)
|
||
ApplySpellFix({ 70936 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY);
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo();
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(157); // 90yd
|
||
});
|
||
|
||
ApplySpellFix({
|
||
72706, // Achievement Check (Valithria Dreamwalker)
|
||
71357 // Order Whelp
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
});
|
||
|
||
// Sindragosa's Fury
|
||
ApplySpellFix({ 70598 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST);
|
||
});
|
||
|
||
// Frost Bomb
|
||
ApplySpellFix({ 69846 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Speed = 0.0f; // This spell's summon happens instantly
|
||
});
|
||
|
||
// Chilled to the Bone
|
||
ApplySpellFix({ 70106 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS;
|
||
spellInfo->AttributesEx6 |= SPELL_ATTR6_LIMIT_PCT_DAMAGE_MODS;
|
||
});
|
||
|
||
// Ice Lock
|
||
ApplySpellFix({ 71614 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Mechanic = MECHANIC_STUN;
|
||
});
|
||
|
||
// Defile
|
||
ApplySpellFix({ 72762 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(559); // 53 seconds
|
||
});
|
||
|
||
// Defile
|
||
ApplySpellFix({ 72743 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(22); // 45 seconds
|
||
});
|
||
|
||
// Defile
|
||
ApplySpellFix({ 72754, 73708, 73709, 73710 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
});
|
||
|
||
// Val'kyr Target Search
|
||
ApplySpellFix({ 69030 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
spellInfo->Attributes |= SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY;
|
||
});
|
||
|
||
// Raging Spirit Visual
|
||
ApplySpellFix({ 69198 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13); // 50000yd
|
||
});
|
||
|
||
// Harvest Souls
|
||
ApplySpellFix({ 73654, 74295, 74296, 74297 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
spellInfo->Effects[EFFECT_2].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
// Harvest Soul
|
||
ApplySpellFix({ 73655 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS;
|
||
});
|
||
|
||
// Summon Shadow Trap
|
||
ApplySpellFix({ 73540 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(23); // 90 seconds
|
||
});
|
||
|
||
// Shadow Trap (visual)
|
||
ApplySpellFix({ 73530 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(28); // 5 seconds
|
||
});
|
||
|
||
// Shadow Trap
|
||
ApplySpellFix({ 73529 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS); // 10yd
|
||
});
|
||
|
||
// Shadow Trap (searcher)
|
||
ApplySpellFix({ 74282 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS); // 5yd
|
||
});
|
||
|
||
// Restore Soul
|
||
ApplySpellFix({ 72595, 73650 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
});
|
||
|
||
// Destroy Soul
|
||
ApplySpellFix({ 74086 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
});
|
||
|
||
// Summon Spirit Bomb
|
||
ApplySpellFix({ 74302, 74342 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Summon Spirit Bomb
|
||
ApplySpellFix({ 74341, 74343 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS); // 200yd
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Summon Spirit Bomb
|
||
ApplySpellFix({ 73579 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_25_YARDS); // 25yd
|
||
});
|
||
|
||
// Fury of Frostmourne
|
||
ApplySpellFix({ 72350 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
ApplySpellFix(
|
||
{
|
||
75127, // Kill Frostmourne Players
|
||
72351, // Fury of Frostmourne
|
||
72431, // Jump (removes Fury of Frostmourne debuff)
|
||
72429, // Mass Resurrection
|
||
73159, // Play Movie
|
||
73582 // Trigger Vile Spirit (Inside, Heroic)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
// Raise Dead
|
||
ApplySpellFix({ 72376 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS); // 50000yd
|
||
});
|
||
|
||
// Jump
|
||
ApplySpellFix({ 71809 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(5); // 40yd
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS); // 10yd
|
||
spellInfo->Effects[EFFECT_0].MiscValue = 190;
|
||
});
|
||
|
||
// Broken Frostmourne
|
||
ApplySpellFix({ 72405 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_20_YARDS); // 20yd
|
||
spellInfo->AttributesEx |= SPELL_ATTR1_NO_THREAT;
|
||
});
|
||
// ENDOF ICECROWN CITADEL SPELLS
|
||
|
||
//
|
||
// RUBY SANCTUM SPELLS
|
||
//
|
||
// Soul Consumption
|
||
ApplySpellFix({ 74799 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_12_YARDS);
|
||
});
|
||
|
||
// Twilight Cutter
|
||
ApplySpellFix({ 74769, 77844, 77845, 77846 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_100_YARDS); // 100yd
|
||
});
|
||
|
||
// Twilight Mending
|
||
ApplySpellFix({ 75509 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_INVISIBLE;
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Combustion and Consumption Heroic versions lacks radius data
|
||
ApplySpellFix({ 75875 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Mechanic = MECHANIC_NONE;
|
||
spellInfo->Effects[EFFECT_1].Mechanic = MECHANIC_SNARE;
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_6_YARDS);
|
||
});
|
||
|
||
ApplySpellFix({ 75884 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_6_YARDS);
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_6_YARDS);
|
||
});
|
||
|
||
ApplySpellFix({ 75883, 75876 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_6_YARDS);
|
||
});
|
||
// ENDOF RUBY SANCTUM SPELLS
|
||
|
||
//
|
||
// EYE OF ETERNITY SPELLS
|
||
//
|
||
ApplySpellFix({
|
||
// All spells below work even without these changes. The LOS attribute is due to problem
|
||
// from collision between maps & gos with active destroyed state.
|
||
57473, // Arcane Storm bonus explicit visual spell
|
||
57431, // Summon Static Field
|
||
56091, // Flame Spike (Wyrmrest Skytalon)
|
||
56092, // Engulf in Flames (Wyrmrest Skytalon)
|
||
57090, // Revivify (Wyrmrest Skytalon)
|
||
57143 // Life Burst (Wyrmrest Skytalon)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Arcane Barrage (cast by players and NONMELEEDAMAGELOG with caster Scion of Eternity (original caster)).
|
||
ApplySpellFix({ 63934 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// This would never crit on retail and it has attribute for SPELL_ATTR3_NO_DONE_BONUS because is handled from player,
|
||
// until someone figures how to make scions not critting without hack and without making them main casters this should stay here.
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CANT_CRIT;
|
||
});
|
||
// ENDOF EYE OF ETERNITY SPELLS
|
||
|
||
// Introspection
|
||
ApplySpellFix({ 40055, 40165, 40166, 40167 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Attributes |= SPELL_ATTR0_NEGATIVE_1;
|
||
});
|
||
|
||
// Minor Fortitude
|
||
ApplySpellFix({ 2378 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->ManaCost = 0;
|
||
spellInfo->ManaPerSecond = 0;
|
||
});
|
||
|
||
//
|
||
// STONECORE SPELLS
|
||
//
|
||
ApplySpellFix({
|
||
95284, // Teleport (from entrance to Slabhide)
|
||
95285 // Teleport (from Slabhide to entrance)
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_DEST_DB);
|
||
});
|
||
|
||
// Paralyze
|
||
ApplySpellFix({ 92426 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->CasterAuraSpell = 0;
|
||
spellInfo->AuraInterruptFlags = AURA_INTERRUPT_FLAG_HITBYSPELL | AURA_INTERRUPT_FLAG_TAKE_DAMAGE;
|
||
});
|
||
|
||
// Rupture
|
||
ApplySpellFix({ 95669 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_8_YARDS);
|
||
});
|
||
|
||
// ENDOF STONECORE SPELLS
|
||
|
||
//
|
||
// THE VORTEX PINNACLE SPELLS
|
||
//
|
||
// Howling Gale
|
||
ApplySpellFix({ 85085 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS);
|
||
});
|
||
|
||
// Howling Gale
|
||
ApplySpellFix({ 85159 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(84); // To-do: Enum this radius entry - EFFECT_RADIUS_17_YARDS.
|
||
});
|
||
|
||
// Upwind of Altairus
|
||
ApplySpellFix({ 88282 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS);
|
||
});
|
||
|
||
// Twisting Winds
|
||
ApplySpellFix({ 88314 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS);
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS);
|
||
});
|
||
|
||
// Grounding Field
|
||
ApplySpellFix({
|
||
87721, // Beam A
|
||
87722, // Beam B
|
||
87723 // Beam C
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY);
|
||
});
|
||
|
||
// Slipstream
|
||
ApplySpellFix({
|
||
84980,
|
||
84988,
|
||
85394,
|
||
85397,
|
||
85016
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(39); // 2 seconds
|
||
spellInfo->Effects[EFFECT_0].Amplitude = 2000;
|
||
spellInfo->Effects[EFFECT_0].Amplitude = 2000;
|
||
});
|
||
|
||
// Slipstream
|
||
ApplySpellFix({
|
||
89499,
|
||
89501,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
// The target creatures are too far away to be targeted by the core so we have to take their spawn positions instead
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_DEST_DB);
|
||
});
|
||
|
||
// Catch Fall
|
||
ApplySpellFix({ 89526 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(TARGET_DEST_DB);
|
||
});
|
||
|
||
// Catch Fall
|
||
ApplySpellFix({ 89522 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_CASTER);
|
||
});
|
||
|
||
// Summon Skyfall Star
|
||
ApplySpellFix({ 96260 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(2); // Combat Range
|
||
});
|
||
|
||
// Arcane Barrage
|
||
ApplySpellFix({
|
||
87854,
|
||
92756,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// ENDOF THE VORTEX PINNACLE SPELLS
|
||
|
||
//
|
||
// BASTION OF TWILIGHT SPELLS
|
||
//
|
||
// Theralion and Valiona
|
||
// Rift Blast
|
||
ApplySpellFix({
|
||
93019,
|
||
93020,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Dazzling Destruction (10 Player)
|
||
ApplySpellFix({
|
||
86380,
|
||
92924,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 2;
|
||
});
|
||
|
||
// Dazzling Destruction (25 Player)
|
||
ApplySpellFix({
|
||
92923,
|
||
92925,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Fabulous Flames
|
||
ApplySpellFix({ 86495 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Ascendant Council
|
||
// Disperse
|
||
ApplySpellFix({ 83087 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS);
|
||
});
|
||
|
||
// Frozen Orb
|
||
ApplySpellFix({ 92267 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Cho'Gall
|
||
// Darkened Creations
|
||
ApplySpellFix({
|
||
82414,
|
||
93161
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 4;
|
||
});
|
||
|
||
// Darkened Creations
|
||
ApplySpellFix({
|
||
93160,
|
||
93162
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 10;
|
||
});
|
||
|
||
// Summon Spiked Tentacle Trigger
|
||
ApplySpellFix({ 93315 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// ENDOF BASTION OF TWILIGHT
|
||
|
||
//
|
||
// DEADMINES SPELLS
|
||
//
|
||
// Glubtok
|
||
// Fists of Flame
|
||
ApplySpellFix({
|
||
87874,
|
||
91268,
|
||
87896,
|
||
91269
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(2); // Combat Range
|
||
});
|
||
|
||
// Fists of Frost
|
||
ApplySpellFix({
|
||
87899,
|
||
91272,
|
||
87901,
|
||
91273,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(2); // Combat Range
|
||
});
|
||
|
||
// Helix Gearbreaker
|
||
// Charge
|
||
ApplySpellFix({ 88295 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_100_YARDS);
|
||
});
|
||
|
||
// "Captain" Cookie
|
||
// Rotten Aura
|
||
ApplySpellFix({
|
||
89735,
|
||
92065,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_INITIAL_THREAT;
|
||
});
|
||
|
||
// Vanessa VanCleef
|
||
// Spark
|
||
ApplySpellFix({ 95520 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_1_YARD);
|
||
});
|
||
|
||
// Magma Trap Throw To Location
|
||
ApplySpellFix({ 92438 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MiscValue = 200;
|
||
});
|
||
|
||
// Summon Defias
|
||
ApplySpellFix({
|
||
92616,
|
||
92617,
|
||
92618
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST);
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Fiery Blaze
|
||
ApplySpellFix({ 93485 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_INITIAL_THREAT;
|
||
});
|
||
|
||
// END OF DEADMINES SPELLS
|
||
|
||
//
|
||
// THRONE OF THE TIDES SPELLS
|
||
//
|
||
|
||
// Shock Defense
|
||
ApplySpellFix({ 86618 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_TARGET_RANDOM);
|
||
});
|
||
|
||
// Commander Ulthok
|
||
// Dark Fissure
|
||
ApplySpellFix({ 76047 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_1_YARD);
|
||
});
|
||
|
||
ApplySpellFix({ 96311 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(21); // Infinite
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_1_YARD);
|
||
});
|
||
|
||
// Ozumat
|
||
// Purify
|
||
ApplySpellFix({ 76953 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 5;
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_100_YARDS);
|
||
});
|
||
|
||
// Summon Murloc Add Trigger
|
||
ApplySpellFix({ 83360 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Summon Caster Add Trigger
|
||
ApplySpellFix({ 83441 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Summon Lt Add Trigger
|
||
ApplySpellFix({ 83437 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Summon Kite Add Trigger
|
||
ApplySpellFix({ 83648 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Blight of Ozumat
|
||
ApplySpellFix({ 83518 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Grab Neptulon
|
||
ApplySpellFix({ 94171 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); // 100yd
|
||
});
|
||
|
||
// Waterspout
|
||
ApplySpellFix({ 90461 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Speed = 6.0f;
|
||
});
|
||
|
||
// END OF THRONE OF THE TIDES SPELLS
|
||
|
||
//
|
||
// GRIM BATOL SPELLS
|
||
//
|
||
// General Umbriss
|
||
// Ground Siege
|
||
ApplySpellFix({ 74634, 90249 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 &= ~SPELL_ATTR3_ONLY_TARGET_PLAYERS;
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); // 100yd
|
||
});
|
||
|
||
// Drahga Shadowburner
|
||
// Flaming Fixate
|
||
ApplySpellFix({ 82850 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx5 |= SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING;
|
||
});
|
||
|
||
// Ride Vehicle
|
||
ApplySpellFix({ 43671 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Devouring Flames
|
||
ApplySpellFix({ 90945 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Burning Shadowbolt
|
||
ApplySpellFix({
|
||
75245,
|
||
90915
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Erudax
|
||
// Twilight Blast
|
||
ApplySpellFix({ 76194, 91042 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Shadow Gale
|
||
ApplySpellFix({ 75664, 91086 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx5 &= ~SPELL_ATTR5_CAN_CHANNEL_WHEN_MOVING;
|
||
});
|
||
|
||
// Gronn Knockback Cosmetic
|
||
ApplySpellFix({ 76138 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// ENDOF GRIM_BATOL SPELLS
|
||
|
||
//
|
||
// THE LOST CITY OF THE TOL'VIR SPELLS
|
||
//
|
||
// General Husam
|
||
// Summon Shockwave Target N
|
||
ApplySpellFix({ 83131 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_4_YARDS);
|
||
});
|
||
|
||
// Hurl
|
||
ApplySpellFix({ 83235 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Effect = SPELL_EFFECT_APPLY_AURA;
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY);
|
||
});
|
||
// High Prophet Barim
|
||
// Heaven's Fury
|
||
ApplySpellFix({ 81942, 90040 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_4_YARDS);
|
||
});
|
||
// Hallowed Ground
|
||
ApplySpellFix({ 88814, 90010 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_13_YARDS);
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_13_YARDS);
|
||
});
|
||
// Blaze of the Heavens
|
||
ApplySpellFix({ 91196 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS);
|
||
});
|
||
// Repentance
|
||
ApplySpellFix({ 82430 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MiscValue = 150;
|
||
});
|
||
// Blaze of the Heavens
|
||
ApplySpellFix({ 95249 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS);
|
||
});
|
||
// Lockmaw and Augh
|
||
// Dust Flail
|
||
ApplySpellFix({ 81643, 81652 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_11_YARDS);
|
||
});
|
||
// Siamat
|
||
// Deflecting Winds
|
||
// Why would Siamat silence and pacify himself if he is suposed to cast spells?
|
||
ApplySpellFix({ 84589 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_DUMMY;
|
||
});
|
||
// Cloud Burst
|
||
ApplySpellFix({ 83790 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS);
|
||
});
|
||
// Lightning Charge
|
||
ApplySpellFix({ 91872 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS);
|
||
});
|
||
// Generic Spells
|
||
// Slipstream
|
||
ApplySpellFix({ 91872 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
// ENDOF THE LOST CITY OF THE TOL'VIR SPELLS
|
||
|
||
//
|
||
// THE HALLS OF ORIGINATION SPELLS
|
||
//
|
||
ApplySpellFix({
|
||
76606, // Disable Beacon Beams L
|
||
76608 // Disable Beacon Beams R
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
// Little hack, Increase the radius so it can hit the Cave In Stalkers in the platform.
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_45_YARDS);
|
||
});
|
||
|
||
// Reverberating Hymn
|
||
ApplySpellFix({ 75323 }, [](SpellInfo* spellInfo)
|
||
{
|
||
// Aura is refreshed at 3 seconds, and the tick should happen at the fourth.
|
||
spellInfo->AttributesEx8 |= SPELL_ATTR8_DONT_RESET_PERIODIC_TIMER;
|
||
});
|
||
|
||
// Destruction Protocoll
|
||
ApplySpellFix({ 77437 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Crumbling Ruin
|
||
ApplySpellFix({
|
||
75609,
|
||
91206
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50000_YARDS);
|
||
});
|
||
|
||
// Solar Fire
|
||
ApplySpellFix({ 89133, 89878 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_4_YARDS);
|
||
});
|
||
|
||
// Solar Winds
|
||
ApplySpellFix({ 74108, 89130 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_4_YARDS);
|
||
});
|
||
// END OF HALLS OF ORIGINATION SPELLS
|
||
|
||
//
|
||
// SHADOWFANG KEEP SPELLS
|
||
//
|
||
// Summon Spirit of Wolf Master Nandos
|
||
// Summon Spirit of Odo the Blindwatcher
|
||
// Summon Spirit of Razorclaw the Butcher
|
||
// Summon Spirit of Rethilgore
|
||
ApplySpellFix({ 93896, 93859, 93921, 93925 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_15_YARDS);
|
||
});
|
||
|
||
// Desecration Arm
|
||
ApplySpellFix({ 67802 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_7_YARDS);
|
||
});
|
||
|
||
// Desecration
|
||
ApplySpellFix({ 93691, 94370 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_6_YARDS);
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_6_YARDS);
|
||
});
|
||
|
||
// Toxic Coagulant
|
||
ApplySpellFix({ 93617 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AuraInterruptFlags = AURA_INTERRUPT_FLAG_MOVE;
|
||
});
|
||
|
||
// END OF SHADOWFANG KEEP SPELLS
|
||
|
||
// Threatening Gaze
|
||
ApplySpellFix({ 24314 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AuraInterruptFlags |= AURA_INTERRUPT_FLAG_CAST | AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_JUMP;
|
||
});
|
||
|
||
// Feral Charge (Cat Form
|
||
ApplySpellFix({ 49376 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 &= ~SPELL_ATTR3_CANT_TRIGGER_PROC;
|
||
});
|
||
|
||
//
|
||
// BARADIN HOLD SPELLS
|
||
//
|
||
// Gaze of Occu'thar
|
||
ApplySpellFix({ 96942, 101009 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx &= ~SPELL_ATTR1_CHANNELED_1;
|
||
});
|
||
|
||
// Meteor Slash
|
||
ApplySpellFix({ 88942, 95172 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx4 |= SPELL_ATTR4_IGNORE_RESISTANCES;
|
||
});
|
||
|
||
// ENDOF BARADIN HOLD SPELLS
|
||
|
||
//
|
||
// BLACKROCK CAVERNS SPELLS
|
||
//
|
||
// Evolution
|
||
ApplySpellFix({ 75610 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Bound Flames
|
||
ApplySpellFix({ 93499 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Furious Swipe
|
||
ApplySpellFix({ 80340 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(7); // 10yd
|
||
});
|
||
|
||
// Transformation
|
||
ApplySpellFix({ 76196 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Bore
|
||
ApplySpellFix({ 75205 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->ChannelInterruptFlags = 0;
|
||
spellInfo->AuraInterruptFlags &= ~AURA_INTERRUPT_FLAG_TURNING;
|
||
});
|
||
|
||
// ENDOF BLACKROCK CAVERNS SPELLS
|
||
|
||
//
|
||
// ISLE OF CONQUEST SPELLS
|
||
//
|
||
// Teleport
|
||
ApplySpellFix({ 66551 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13); // 50000yd
|
||
});
|
||
// ENDOF ISLE OF CONQUEST SPELLS
|
||
|
||
//
|
||
// FIRELANDS SPELLS
|
||
//
|
||
// Torment
|
||
ApplySpellFix({ 99256, 100230, 100231, 100232 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Attributes |= SPELL_ATTR0_NEGATIVE_1;
|
||
});
|
||
// ENDOF FIRELANDS SPELLS
|
||
|
||
//
|
||
// GILNEAS SPELLS
|
||
//
|
||
// Curse of the Worgen
|
||
ApplySpellFix({ 69123 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(135); // 100yd
|
||
});
|
||
|
||
// Forcecast summon personal Godfrey
|
||
ApplySpellFix({ 68635, 68636 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_SUMMONER);
|
||
});
|
||
|
||
// ENDOF GILNEAS SPELLS
|
||
|
||
//
|
||
// ZUL'GURUB SPELLS
|
||
//
|
||
|
||
// Tears of Blood
|
||
ApplySpellFix({
|
||
96422,
|
||
96957,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_12_YARDS);
|
||
});
|
||
|
||
// Wave of Agony
|
||
ApplySpellFix({ 96461 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_2_YARDS);
|
||
});
|
||
|
||
// Wave of Agony (Damage)
|
||
ApplySpellFix({ 96460 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_LINE;
|
||
});
|
||
|
||
// Gaping Wound
|
||
ApplySpellFix({ 97355 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(5); // 40yd
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_40_YARDS);
|
||
spellInfo->Effects[EFFECT_1].TargetB = SpellImplicitTargetInfo(TARGET_DEST_DEST);
|
||
});
|
||
|
||
// Cave In
|
||
ApplySpellFix({ 97380 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF0;
|
||
});
|
||
|
||
// Zanzil's Resurrection Elixir
|
||
ApplySpellFix({
|
||
96316,
|
||
96319
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_200_YARDS);
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST);
|
||
});
|
||
|
||
// Zanzil's Resurrection Elixir
|
||
ApplySpellFix({
|
||
96317,
|
||
96470
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_DEST);
|
||
});
|
||
|
||
// Zanzili Fire
|
||
ApplySpellFix({ 96916 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_2_YARDS);
|
||
});
|
||
|
||
// Shadow Spike
|
||
ApplySpellFix({ 97158 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Call Spirit
|
||
ApplySpellFix({ 97152 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Spirit Warrior's Gaze
|
||
ApplySpellFix({ 97597 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Rolling Boulders Search Effect
|
||
ApplySpellFix({ 96839 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Sunder Rift
|
||
ApplySpellFix({ 96964 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(18); // 20seconds
|
||
});
|
||
|
||
// Yoga Flame
|
||
ApplySpellFix({
|
||
97001,
|
||
97352
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Poison Bolt Volley
|
||
ApplySpellFix({ 97018 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Sigil Shatter
|
||
ApplySpellFix({ 98033 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].TriggerSpell = 98014;
|
||
});
|
||
|
||
// Sigil Shatter
|
||
ApplySpellFix({ 98038 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_2].TriggerSpell = 98016;
|
||
});
|
||
|
||
// Sigil Shatter
|
||
ApplySpellFix({ 98040 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].TriggerSpell = 98019;
|
||
});
|
||
|
||
// Boulder Smash
|
||
ApplySpellFix({ 96834 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS);
|
||
spellInfo->Effects[EFFECT_1].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS);
|
||
spellInfo->Effects[EFFECT_2].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS);
|
||
});
|
||
|
||
// ENDOF ZUL'GURUB SPELLS
|
||
|
||
//
|
||
// THRONE OF THE FOUR WINDS SPELLS
|
||
//
|
||
|
||
// Conclave of Wind
|
||
// Teleport to Center
|
||
ApplySpellFix({
|
||
89844, // West
|
||
89843 // North
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(135); // 100yd
|
||
});
|
||
|
||
// Nurture
|
||
ApplySpellFix({ 85425 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx &= ~SPELL_ATTR1_CHANNELED_2;
|
||
});
|
||
|
||
// Soothing Breeze
|
||
ApplySpellFix({ 86204 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(9); // 30seconds
|
||
});
|
||
|
||
// Ice Patch
|
||
ApplySpellFix({ 86122 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(9); // 30seconds
|
||
});
|
||
|
||
// Tornado
|
||
ApplySpellFix({
|
||
86189,
|
||
86190,
|
||
86191
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS);
|
||
});
|
||
|
||
// Wind Blast
|
||
ApplySpellFix({
|
||
85483,
|
||
93138,
|
||
93139,
|
||
93140
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CONE_ENEMY_104);
|
||
});
|
||
|
||
// Al'Akir
|
||
|
||
// Wind Burst
|
||
ApplySpellFix({
|
||
87770,
|
||
93261,
|
||
93262,
|
||
93261
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Lightning Strike (Force Cast)
|
||
ApplySpellFix({ 91327 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Lightning Strike (Visual)
|
||
ApplySpellFix({ 88230 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Lightning Strike (Damage)
|
||
ApplySpellFix({
|
||
88214,
|
||
93255,
|
||
93256,
|
||
93257,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->ConeAngle = 60.0f;
|
||
});
|
||
|
||
// Lightning Strike (Periodic Aura)
|
||
ApplySpellFix({ 93247 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(387); // 16 seconds
|
||
});
|
||
|
||
// Lightning Strike (Heroic Chain-Caster Summon)
|
||
ApplySpellFix({ 93247 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(467); // 22 seconds
|
||
});
|
||
|
||
// Ice Storm
|
||
ApplySpellFix({ 87055 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(63); // 25 seconds
|
||
});
|
||
|
||
// Stormling
|
||
ApplySpellFix({ 88272 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(1); // 10 seconds
|
||
});
|
||
|
||
// Squall Line
|
||
ApplySpellFix({
|
||
88781,
|
||
91104,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_50_YARDS);
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(245); // 50 seconds
|
||
});
|
||
|
||
// Lightning Clouds
|
||
ApplySpellFix({
|
||
89583,
|
||
89592,
|
||
89628
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Lightning Clouds
|
||
ApplySpellFix({ 89583 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_80_YARDS);
|
||
});
|
||
|
||
// Lightning Clouds
|
||
ApplySpellFix({
|
||
89588,
|
||
93297,
|
||
93298,
|
||
93299
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Lightning Clouds
|
||
ApplySpellFix({
|
||
89565,
|
||
89577
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(570); // 32 seconds
|
||
});
|
||
|
||
// Lightning Rod
|
||
ApplySpellFix({ 89668 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Lightning
|
||
ApplySpellFix({
|
||
89644,
|
||
101465,
|
||
101466,
|
||
101467
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(0);
|
||
});
|
||
|
||
// Lightning
|
||
ApplySpellFix({
|
||
89644,
|
||
101466,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Lightning
|
||
ApplySpellFix({
|
||
101465,
|
||
101467,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Static Shock
|
||
ApplySpellFix({ 87873 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// ENDOF THRONE OF THE FOUR WINDS SPELLS
|
||
|
||
// DRAGON SOUL SPELLS
|
||
|
||
ApplySpellFix({
|
||
106028, // Alexstrasza's Presence
|
||
109571,
|
||
109572,
|
||
109573,
|
||
106457, // Ysera's Presence
|
||
109640,
|
||
109641,
|
||
109642,
|
||
106027, // Nozdormu's Presence
|
||
109622,
|
||
109623,
|
||
109624,
|
||
106029, // Kalecgos' Presence
|
||
109606,
|
||
109607,
|
||
109608,
|
||
106040, // Spellweaving
|
||
106464 // Enter the Dream
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Root
|
||
ApplySpellFix({ 105451 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_MOD_ROOT;
|
||
});
|
||
|
||
// ENDOF DRAGON SOUL SPELLS
|
||
|
||
// Disenchant
|
||
ApplySpellFix({ 13262 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->BaseLevel = 0;
|
||
spellInfo->SpellLevel = 0;
|
||
});
|
||
|
||
// Glyph of Mirror Image
|
||
ApplySpellFix({ 63093 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Effect = SPELL_EFFECT_APPLY_AURA;
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_DUMMY;
|
||
});
|
||
|
||
// Grace
|
||
ApplySpellFix({
|
||
47930,
|
||
77613
|
||
},[](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(6); // 100yd
|
||
});
|
||
|
||
// Hyjal Intro Flight
|
||
ApplySpellFix({ 73518 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(7); // 10yd
|
||
});
|
||
|
||
// Strengh of Soul
|
||
ApplySpellFix({ 89490 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(5); // 40yd
|
||
});
|
||
|
||
// Explosive Trap
|
||
ApplySpellFix({ 13812 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13); // Anywhere
|
||
});
|
||
|
||
// Ancient Crusader
|
||
ApplySpellFix({ 86701 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(9); // 30 seconds
|
||
});
|
||
|
||
// Blood Craze
|
||
ApplySpellFix({
|
||
16488,
|
||
16490,
|
||
16491
|
||
},[](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_PERIODIC_HEAL;
|
||
});
|
||
|
||
// Desecration
|
||
ApplySpellFix({
|
||
55741,
|
||
68766
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(7); // 10yd
|
||
});
|
||
|
||
// Ebon Plague
|
||
ApplySpellFix({ 65142 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].Effect = SPELL_EFFECT_APPLY_AURA;
|
||
spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN;
|
||
spellInfo->Effects[EFFECT_1].MiscValue = 126;
|
||
spellInfo->Effects[EFFECT_1].TriggerSpell = 0;
|
||
});
|
||
|
||
// Glyph of Exorcism
|
||
ApplySpellFix({ 86701 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
});
|
||
|
||
// Soulburn: Seed of Corruption
|
||
ApplySpellFix({ 86664 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Effect = SPELL_EFFECT_APPLY_AURA;
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_DUMMY;
|
||
});
|
||
|
||
// Gout of Flame
|
||
ApplySpellFix({ 80550 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].BasePoints = 7;
|
||
});
|
||
|
||
// Whack!
|
||
ApplySpellFix({ 102022 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_LINE;
|
||
});
|
||
|
||
// Atonement
|
||
ApplySpellFix({
|
||
81751,
|
||
94472
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
//
|
||
// BLACKWING DESCENT SPELLS
|
||
//
|
||
|
||
// Eject Passenger
|
||
ApplySpellFix({ 78643 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx2 |= SPELL_ATTR2_CAN_TARGET_NOT_IN_LOS;
|
||
});
|
||
|
||
// Ignition
|
||
ApplySpellFix({
|
||
92134,
|
||
92196,
|
||
92197,
|
||
92198
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_2_YARDS);
|
||
});
|
||
|
||
// Armageddon
|
||
ApplySpellFix({ 92182 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_100_YARDS);
|
||
});
|
||
|
||
// Flamethrower
|
||
ApplySpellFix({
|
||
79505,
|
||
91531,
|
||
91532,
|
||
91533
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY);
|
||
});
|
||
|
||
// Power Generator
|
||
ApplySpellFix({ 79624 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo(0);
|
||
});
|
||
|
||
// Arcane Annihiliation
|
||
ApplySpellFix({
|
||
91540,
|
||
91542
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Overcharged Power Generator
|
||
ApplySpellFix({ 91858 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS);
|
||
});
|
||
|
||
// Sonar Pulse (10n, 10h)
|
||
ApplySpellFix({
|
||
77672,
|
||
92412
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Sonar Pulse (25n, 25h)
|
||
ApplySpellFix({
|
||
92411,
|
||
92413
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 4;
|
||
});
|
||
|
||
// Searing Flame
|
||
ApplySpellFix({ 77840 }, [](SpellInfo* spellInfo)
|
||
{
|
||
|
||
spellInfo->Effects[EFFECT_1].Effect = SPELL_EFFECT_APPLY_AURA;
|
||
spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_PERIODIC_TRIGGER_SPELL;
|
||
spellInfo->Effects[EFFECT_1].Amplitude = 2000;
|
||
});
|
||
|
||
// Sonar Pulse
|
||
ApplySpellFix({
|
||
92526,
|
||
92531,
|
||
92532,
|
||
92533
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 4;
|
||
});
|
||
|
||
// Roaring Flame Breath
|
||
ApplySpellFix({ 78207 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Sonic Flames
|
||
ApplySpellFix({ 77782 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ANY);
|
||
});
|
||
|
||
// Pestered!
|
||
ApplySpellFix({ 92685 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF1;
|
||
});
|
||
|
||
// Building Speed Effect
|
||
ApplySpellFix({
|
||
78218,
|
||
92463,
|
||
92464,
|
||
92465
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->StackAmount = 10;
|
||
});
|
||
|
||
// Break
|
||
ApplySpellFix({ 82881 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEGATIVE_EFF0;
|
||
});
|
||
|
||
// Biting Chill
|
||
ApplySpellFix({ 77760 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ENEMY);
|
||
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(0);
|
||
});
|
||
|
||
// Growth Catalyst
|
||
ApplySpellFix({
|
||
77987,
|
||
101440,
|
||
101441,
|
||
101442
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS);
|
||
spellInfo->Effects[EFFECT_1].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS);
|
||
spellInfo->Effects[EFFECT_2].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS);
|
||
});
|
||
|
||
// Release Aberrations
|
||
ApplySpellFix({ 77569 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx8 |= SPELL_ATTR8_CANT_MISS;
|
||
});
|
||
|
||
// Release All Minions
|
||
ApplySpellFix({ 77991 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx8 |= SPELL_ATTR8_CANT_MISS;
|
||
});
|
||
|
||
// Debilitating Slime
|
||
ApplySpellFix({ 77615 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO;
|
||
});
|
||
|
||
// Dragon Orb
|
||
ApplySpellFix({
|
||
78219,
|
||
78220
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_4_YARDS);
|
||
});
|
||
|
||
// Shadowflame Breath
|
||
ApplySpellFix({
|
||
77826,
|
||
94124,
|
||
94125,
|
||
94126,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].Amplitude = 1500;
|
||
});
|
||
|
||
// Electrical Charge
|
||
ApplySpellFix({ 78949 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].Amplitude = 3000;
|
||
});
|
||
|
||
// Lightning Discharge
|
||
ApplySpellFix({
|
||
81435,
|
||
81436,
|
||
81437,
|
||
81438,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_18_YARDS);
|
||
});
|
||
|
||
// Hail of Bones
|
||
ApplySpellFix({
|
||
78684,
|
||
94104,
|
||
94105,
|
||
94106,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(21); // Infinite
|
||
});
|
||
|
||
// Shadowflame Barrage (10 players)
|
||
ApplySpellFix({
|
||
78621,
|
||
94122,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 4;
|
||
});
|
||
|
||
// Shadowflame Barrage (25 players)
|
||
ApplySpellFix({
|
||
94121,
|
||
94123,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 8;
|
||
});
|
||
|
||
// Brushfire Start
|
||
ApplySpellFix({ 79813 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// Lightning Discharge
|
||
ApplySpellFix({
|
||
81435,
|
||
81436,
|
||
81437,
|
||
81438
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].MaxRadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_18_YARDS);
|
||
});
|
||
|
||
// Explosive Cinders
|
||
ApplySpellFix({ 79339 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 1;
|
||
});
|
||
|
||
// ENDOF BLACKWING DESCENT SPELLS
|
||
|
||
// Living Bomb
|
||
ApplySpellFix({ 44457 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAuraTargets = 3;
|
||
});
|
||
|
||
// Living Bomb
|
||
ApplySpellFix({ 44461 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->MaxAffectedTargets = 3;
|
||
});
|
||
|
||
// Lifebloom
|
||
ApplySpellFix({ 33763 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx5 |= SPELL_ATTR5_SINGLE_TARGET_SPELL;
|
||
});
|
||
|
||
// Tree of Life (Passive)
|
||
ApplySpellFix({ 81098 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS;
|
||
});
|
||
|
||
// Earth Shield
|
||
ApplySpellFix({ 379 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS;
|
||
});
|
||
|
||
// Light of Dawn
|
||
ApplySpellFix({ 85222 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->DmgClass = SPELL_DAMAGE_CLASS_MAGIC;
|
||
});
|
||
|
||
// Improved Blood Presence
|
||
ApplySpellFix({
|
||
50365,
|
||
50371,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->SpellFamilyName = SPELLFAMILY_DEATHKNIGHT;
|
||
});
|
||
|
||
// Enhanced Elements
|
||
ApplySpellFix({ 77223 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->SpellFamilyName = SPELLFAMILY_SHAMAN;
|
||
});
|
||
|
||
// Fulmination
|
||
ApplySpellFix({ 88767 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS;
|
||
});
|
||
|
||
// Blood in the Water (Rank 1)
|
||
ApplySpellFix({ 80318 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_DUMMY;
|
||
});
|
||
|
||
// Tamed Pet Passive 07 (DND)
|
||
ApplySpellFix({ 20784 }, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_PROC_TRIGGER_SPELL_WITH_VALUE;
|
||
});
|
||
|
||
// Blade Twisting
|
||
ApplySpellFix({
|
||
31125,
|
||
51585,
|
||
}, [](SpellInfo* spellInfo)
|
||
{
|
||
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(2); // 5 yards (combat range)
|
||
});
|
||
|
||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||
{
|
||
SpellInfo* spellInfo = mSpellInfoMap[i];
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
||
{
|
||
switch (spellInfo->Effects[j].Effect)
|
||
{
|
||
case SPELL_EFFECT_CHARGE:
|
||
case SPELL_EFFECT_CHARGE_DEST:
|
||
case SPELL_EFFECT_JUMP:
|
||
case SPELL_EFFECT_JUMP_DEST:
|
||
case SPELL_EFFECT_LEAP_BACK:
|
||
if (!spellInfo->Speed && !spellInfo->SpellFamilyName)
|
||
spellInfo->Speed = SPEED_CHARGE;
|
||
break;
|
||
}
|
||
|
||
// Passive talent auras cannot target pets
|
||
if (spellInfo->IsPassive() && GetTalentSpellCost(i))
|
||
if (spellInfo->Effects[j].TargetA.GetTarget() == TARGET_UNIT_PET)
|
||
spellInfo->Effects[j].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
if (spellInfo->Effects[j].TargetA.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CONE || spellInfo->Effects[j].TargetB.GetSelectionCategory() == TARGET_SELECT_CATEGORY_CONE)
|
||
if (G3D::fuzzyEq(spellInfo->ConeAngle, 0.f))
|
||
spellInfo->ConeAngle = 90.f;
|
||
// Area auras may not target area (they're self cast)
|
||
if (spellInfo->Effects[j].IsAreaAuraEffect() && spellInfo->Effects[j].IsTargetingArea())
|
||
{
|
||
spellInfo->Effects[j].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
|
||
spellInfo->Effects[j].TargetB = SpellImplicitTargetInfo(0);
|
||
}
|
||
}
|
||
|
||
// disable proc for magnet auras, they're handled differently
|
||
if (spellInfo->HasAura(SPELL_AURA_SPELL_MAGNET))
|
||
spellInfo->ProcFlags = 0;
|
||
|
||
// due to the way spell system works, unit would change orientation in Spell::_cast
|
||
if (spellInfo->HasAura(SPELL_AURA_CONTROL_VEHICLE))
|
||
spellInfo->AttributesEx5 |= SPELL_ATTR5_DONT_TURN_DURING_CAST;
|
||
|
||
if (spellInfo->ActiveIconID == 2158) // flight
|
||
spellInfo->Attributes |= SPELL_ATTR0_PASSIVE;
|
||
|
||
switch (spellInfo->SpellFamilyName)
|
||
{
|
||
case SPELLFAMILY_PALADIN:
|
||
// Seals of the Pure should affect Seal of Righteousness
|
||
if (spellInfo->SpellIconID == 25 && spellInfo->HasAttribute(SPELL_ATTR0_PASSIVE))
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask[1] |= 0x20000000;
|
||
break;
|
||
case SPELLFAMILY_DEATHKNIGHT:
|
||
// Icy Touch - extend FamilyFlags (unused value) for Sigil of the Frozen Conscience to use
|
||
if (spellInfo->SpellIconID == 2721 && spellInfo->SpellFamilyFlags[0] & 0x2)
|
||
spellInfo->SpellFamilyFlags[0] |= 0x40;
|
||
break;
|
||
case SPELLFAMILY_SHAMAN:
|
||
// Nature's Blessing should also affect Greater Healing Wave and Riptide
|
||
if (spellInfo->SpellIconID == 2012 && spellInfo->HasAura(SPELL_AURA_DUMMY))
|
||
spellInfo->Effects[EFFECT_0].SpellClassMask[2] = (0x00000010 | 0x00010000);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (SummonPropertiesEntry* properties = const_cast<SummonPropertiesEntry*>(sSummonPropertiesStore.LookupEntry(121)))
|
||
properties->Title = SUMMON_TYPE_TOTEM;
|
||
if (SummonPropertiesEntry* properties = const_cast<SummonPropertiesEntry*>(sSummonPropertiesStore.LookupEntry(647))) // 52893
|
||
properties->Title = SUMMON_TYPE_TOTEM;
|
||
if (SummonPropertiesEntry* properties = const_cast<SummonPropertiesEntry*>(sSummonPropertiesStore.LookupEntry(3069))) // Wild Mushroom
|
||
properties->Title = SUMMON_TYPE_MINION;
|
||
if (SummonPropertiesEntry* properties = const_cast<SummonPropertiesEntry*>(sSummonPropertiesStore.LookupEntry(628))) // Hungry Plaguehound
|
||
properties->Control = SUMMON_CATEGORY_PET;
|
||
|
||
if (LockEntry* entry = const_cast<LockEntry*>(sLockStore.LookupEntry(36))) // 3366 Opening, allows to open without proper key
|
||
entry->Type[2] = LOCK_KEY_NONE;
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded SpellInfo corrections in %u ms", GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellInfoSpellSpecificAndAuraState()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
for (SpellInfo* spellInfo : mSpellInfoMap)
|
||
{
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
// AuraState depends on SpellSpecific
|
||
spellInfo->_LoadSpellSpecific();
|
||
spellInfo->_LoadAuraState();
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded SpellInfo SpellSpecific and AuraState in %u ms", GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellInfoDiminishing()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
for (SpellInfo* spellInfo : mSpellInfoMap)
|
||
{
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
spellInfo->_LoadSpellDiminishInfo();
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded SpellInfo diminishing infos in %u ms", GetMSTimeDiffToNow(oldMSTime));
|
||
}
|
||
|
||
void SpellMgr::LoadSpellInfoImmunities()
|
||
{
|
||
uint32 oldMSTime = getMSTime();
|
||
|
||
for (SpellInfo* spellInfo : mSpellInfoMap)
|
||
{
|
||
if (!spellInfo)
|
||
continue;
|
||
|
||
spellInfo->_LoadImmunityInfo();
|
||
}
|
||
|
||
TC_LOG_INFO("server.loading", ">> Loaded SpellInfo immunity infos in %u ms", GetMSTimeDiffToNow(oldMSTime));
|
||
}
|