/*
* Copyright (C) 2008-2019 TrinityCore
* Copyright (C) 2005-2009 MaNGOS
*
* 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 .
*/
#include "SpellMgr.h"
#include "BattlefieldMgr.h"
#include "BattlefieldWG.h"
#include "BattlegroundMgr.h"
#include "Chat.h"
#include "Containers.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"
bool IsPrimaryProfessionSkill(uint32 skill)
{
SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(skill);
if (!pSkill)
return false;
if (pSkill->categoryId != SKILL_CATEGORY_PROFESSION)
return false;
return true;
}
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->skillId == 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, WorldObject 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->SpellID[mode] <= 0 && mode > DUNGEON_DIFFICULTY_HEROIC)
{
TC_LOG_DEBUG("spells", "SpellMgr::GetSpellIdForDifficulty: spell %u mode %u spell is NULL, using mode %u", spellId, mode, mode - 2);
mode -= 2;
}
if (difficultyEntry->SpellID[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->SpellID[mode]);
return uint32(difficultyEntry->SpellID[mode]);
}
SpellInfo const* SpellMgr::GetSpellForDifficultyFromSpell(SpellInfo const* spell, WorldObject 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 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& foundSpells) const
{
std::set usedGroups;
GetSetOfSpellsInSpellGroup(group_id, foundSpells, usedGroups);
}
void SpellMgr::GetSetOfSpellsInSpellGroup(SpellGroup group_id, std::set& foundSpells, std::set& 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& 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 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::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];
}
std::vector const* SpellMgr::GetSpellLinked(int32 spell_id) const
{
return Trinity::Containers::MapGetValuePtr(mSpellLinkedMap, spell_id);
}
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(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);
}
// Extra conditions
switch (spellId)
{
case 58600: // No fly Zone - Dalaran
{
if (!player)
return false;
AreaTableEntry const* pArea = sAreaTableStore.LookupEntry(player->GetAreaId());
if (!(pArea && pArea->flags & AREA_FLAG_NO_FLY_ZONE))
return false;
if (!player->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !player->HasAuraType(SPELL_AURA_FLY))
return false;
break;
}
case 58730: // 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->RankID[rank])
{
lastSpell = GetSpellInfo(talentInfo->RankID[rank]);
break;
}
}
if (!lastSpell)
continue;
SpellInfo const* firstSpell = GetSpellInfo(talentInfo->RankID[0]);
if (!firstSpell)
{
TC_LOG_ERROR("spells", "SpellMgr::LoadSpellTalentRanks: First Rank Spell %u for TalentEntry %u does not exist.", talentInfo->RankID[0], i);
continue;
}
SpellInfo const* prevSpell = nullptr;
for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
{
uint32 spellId = talentInfo->RankID[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->RankID[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 >::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 >::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(spell_id, spell_req));
mSpellsReqSpell.insert (std::pair(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].GetUInt16();
SpellLearnSpellNode node;
node.spell = fields[1].GetUInt16();
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());
// 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 = GetSpellLearnSpellMapBounds(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;
}
}
}
}
TC_LOG_INFO("server.loading", ">> Loaded %u spell learn spells + %u found in DBC in %u ms", count, dbc_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 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 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 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 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 spellIds;
GetSetOfSpellsInSpellGroup(SpellGroup(group_id), spellIds);
std::unordered_set auraTypes;
// we have to 'guess' what effect this group corresponds to
{
std::unordered_multiset frequencyContainer;
// only waylay for the moment (shared group)
std::vector> 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;
uint32 auraName = spellInfo->Effects[i].ApplyAuraName;
for (std::vector 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 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(i), static_cast(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;
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->spellId, 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 4
QueryResult result = WorldDatabase.Query("SELECT EnchantID, Chance, ProcsPerMinute, HitMask, AttributesMask 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.Chance = fields[1].GetFloat();
spe.ProcsPerMinute = fields[2].GetFloat();
spe.HitMask = fields[3].GetUInt32();
spe.AttributesMask = fields[4].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->skillId != creatureFamily->skillLine[j])
continue;
if (skillLine->AutolearnType != SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN)
continue;
SpellInfo const* spell = GetSpellInfo(skillLine->spellId);
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 (auto const& creatureTemplatePair : ctc)
{
if (!creatureTemplatePair.second.PetSpellDataId)
continue;
// for creature with PetSpellDataId get default pet spells from dbc
CreatureSpellDataEntry const* spellDataEntry = sCreatureSpellDataStore.LookupEntry(creatureTemplatePair.second.PetSpellDataId);
if (!spellDataEntry)
continue;
int32 petSpellsId = -int32(creatureTemplatePair.second.PetSpellDataId);
PetDefaultSpellsEntry petDefSpells;
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
petDefSpells.spellid[j] = spellDataEntry->spellId[j];
if (LoadPetDefaultSpells_helper(&creatureTemplatePair.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, autocast 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.autocast = fields[9].GetBool();
if (SpellInfo const* spellInfo = GetSpellInfo(spell))
{
if (spellArea.autocast)
const_cast(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.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->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.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 const* 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(spellArea.areaId, spellArea.questStart), sa));
if (spellArea.areaId && spellArea.questEnd)
mSpellAreaForQuestAreaMap.insert(SpellAreaForQuestAreaMap::value_type(std::pair(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);
for (SpellEntry const* spellEntry : sSpellStore)
mSpellInfoMap[spellEntry->Id] = new SpellInfo(spellEntry);
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::LoadSpellInfoCustomAttributes()
{
uint32 oldMSTime = getMSTime();
uint32 oldMSTime2 = oldMSTime;
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* spellInfo = _GetSpellInfo(spellId);
if (!spellInfo)
{
TC_LOG_ERROR("sql.sql", "Table `spell_custom_attr` has wrong spell (entry: %u), ignored.", spellId);
continue;
}
if ((attributes & SPELL_ATTR0_CU_NEGATIVE) != 0)
{
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
{
if (spellInfo->Effects[i].IsEffect())
continue;
if ((attributes & (SPELL_ATTR0_CU_NEGATIVE_EFF0 << i)) != 0)
{
TC_LOG_ERROR("sql.sql", "Table `spell_custom_attr` has attribute SPELL_ATTR0_CU_NEGATIVE_EFF%u for spell %u with no EFFECT_%u", uint32(i), spellId, uint32(i));
continue;
}
}
}
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 (SpellInfo* spellInfo : mSpellInfoMap)
{
if (!spellInfo)
continue;
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{
// all bleed effects and spells ignore armor
if (spellInfo->GetEffectMechanicMask(j) & (1 << MECHANIC_BLEED))
spellInfo->AttributesCu |= SPELL_ATTR0_CU_IGNORE_ARMOR;
switch (spellInfo->Effects[j].ApplyAuraName)
{
case SPELL_AURA_MOD_POSSESS:
case SPELL_AURA_MOD_CONFUSE:
case SPELL_AURA_MOD_CHARM:
case SPELL_AURA_AOE_CHARM:
case SPELL_AURA_MOD_FEAR:
case SPELL_AURA_MOD_STUN:
spellInfo->AttributesCu |= SPELL_ATTR0_CU_AURA_CC;
break;
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_HEALTH_LEECH:
case SPELL_EFFECT_HEAL:
case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL:
case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE:
case SPELL_EFFECT_WEAPON_DAMAGE:
case SPELL_EFFECT_POWER_BURN:
case SPELL_EFFECT_HEAL_MECHANICAL:
case SPELL_EFFECT_NORMALIZED_WEAPON_DMG:
case SPELL_EFFECT_HEAL_PCT:
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CAN_CRIT;
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, spellInfo->Id))
{
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->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL)
continue;
SpellInfo* procInfo = _GetSpellInfo(enchant->spellid[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;
}
}
}
// spells ignoring hit result should not be binary
if (!spellInfo->HasAttribute(SPELL_ATTR3_IGNORE_HIT_RESULT))
{
bool setFlag = false;
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{
if (spellInfo->Effects[j].IsEffect())
{
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_TRIGGER_SPELL:
case SPELL_EFFECT_TRIGGER_SPELL_WITH_VALUE:
break;
case SPELL_EFFECT_PERSISTENT_AREA_AURA:
case SPELL_EFFECT_APPLY_AURA:
case SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
case SPELL_EFFECT_APPLY_AREA_AURA_RAID:
case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
case SPELL_EFFECT_APPLY_AREA_AURA_PET:
case SPELL_EFFECT_APPLY_AREA_AURA_OWNER:
{
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE ||
spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE_PERCENT ||
spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_DUMMY ||
spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_PERIODIC_LEECH ||
spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_PERIODIC_HEALTH_FUNNEL ||
spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_PERIODIC_DUMMY)
break;
}
default:
{
// No value and not interrupt cast or crowd control without SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY flag
if (!spellInfo->Effects[j].CalcValue() && !((spellInfo->Effects[j].Effect == SPELL_EFFECT_INTERRUPT_CAST || spellInfo->HasAttribute(SPELL_ATTR0_CU_AURA_CC)) && !spellInfo->HasAttribute(SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY)))
break;
// Sindragosa Frost Breath
if (spellInfo->Id == 69649 || spellInfo->Id == 71056 || spellInfo->Id == 71057 || spellInfo->Id == 71058 || spellInfo->Id == 73061 || spellInfo->Id == 73062 || spellInfo->Id == 73063 || spellInfo->Id == 73064)
break;
// Frostbolt
if (spellInfo->SpellFamilyName == SPELLFAMILY_MAGE && (spellInfo->SpellFamilyFlags[0] & 0x20))
break;
// Frost Fever
if (spellInfo->Id == 55095)
break;
// Haunt
if (spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK && (spellInfo->SpellFamilyFlags[1] & 0x40000))
break;
setFlag = true;
break;
}
}
if (setFlag)
{
spellInfo->AttributesCu |= SPELL_ATTR0_CU_BINARY_SPELL;
break;
}
}
}
}
// Remove normal school mask to properly calculate damage
if ((spellInfo->SchoolMask & SPELL_SCHOOL_MASK_NORMAL) && (spellInfo->SchoolMask & SPELL_SCHOOL_MASK_MAGIC))
{
spellInfo->SchoolMask &= ~SPELL_SCHOOL_MASK_NORMAL;
spellInfo->AttributesCu |= SPELL_ATTR0_CU_SCHOOLMASK_NORMAL_WITH_MAGIC;
}
spellInfo->_InitializeSpellPositivity();
if (spellInfo->SpellVisual[0] == 3879)
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_BACK;
switch (spellInfo->SpellFamilyName)
{
case SPELLFAMILY_WARRIOR:
// Shout / Piercing Howl
if (spellInfo->SpellFamilyFlags[0] & 0x20000/* || spellInfo->SpellFamilyFlags[1] & 0x20*/)
spellInfo->AttributesCu |= SPELL_ATTR0_CU_AURA_CC;
break;
case SPELLFAMILY_DRUID:
// Roar
if (spellInfo->SpellFamilyFlags[0] & 0x8)
spellInfo->AttributesCu |= SPELL_ATTR0_CU_AURA_CC;
break;
case SPELLFAMILY_GENERIC:
// Stoneclaw Totem effect
if (spellInfo->Id == 5729)
spellInfo->AttributesCu |= SPELL_ATTR0_CU_AURA_CC;
break;
default:
break;
}
spellInfo->_InitializeExplicitTargetMask();
if (spellInfo->Speed > 0.0f)
if (SpellVisualEntry const* spellVisual = sSpellVisualStore.LookupEntry(spellInfo->SpellVisual[0]))
if (spellVisual->HasMissile)
if (spellVisual->MissileModel == -4 || spellVisual->MissileModel == -5)
spellInfo->AttributesCu |= SPELL_ATTR0_CU_NEEDS_AMMO_DATA;
}
// addition for binary spells, omit spells triggering other spells
for (SpellInfo* spellInfo : mSpellInfoMap)
{
if (!spellInfo)
continue;
if (spellInfo->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL))
continue;
bool allNonBinary = true;
bool overrideAttr = false;
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{
if (spellInfo->Effects[j].IsAura() && spellInfo->Effects[j].TriggerSpell)
{
switch (spellInfo->Effects[j].ApplyAuraName)
{
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE:
if (SpellInfo const* triggerSpell = sSpellMgr->GetSpellInfo(spellInfo->Effects[j].TriggerSpell))
{
overrideAttr = true;
if (triggerSpell->HasAttribute(SPELL_ATTR0_CU_BINARY_SPELL))
allNonBinary = false;
}
break;
default:
break;
}
}
}
if (overrideAttr && allNonBinary)
spellInfo->AttributesCu &= ~SPELL_ATTR0_CU_BINARY_SPELL;
}
// remove attribute from spells that can't crit
// and mark triggering spell (instead of triggered spell) for spells with SPELL_ATTR4_INHERIT_CRIT_FROM_AURA
for (SpellInfo* spellInfo : mSpellInfoMap)
{
if (!spellInfo)
continue;
if (spellInfo->HasAttribute(SPELL_ATTR2_CANT_CRIT))
spellInfo->AttributesCu &= ~SPELL_ATTR0_CU_CAN_CRIT;
else if (spellInfo->HasAttribute(SPELL_ATTR4_INHERIT_CRIT_FROM_AURA))
{
bool found = false;
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{
switch (spellInfo->Effects[j].ApplyAuraName)
{
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE:
if (SpellInfo* triggerSpell = const_cast(sSpellMgr->GetSpellInfo(spellInfo->Effects[j].TriggerSpell)))
if (triggerSpell->HasAttribute(SPELL_ATTR0_CU_CAN_CRIT))
found = true;
break;
default:
continue;
}
}
if (found)
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CAN_CRIT;
}
}
// add custom attribute to liquid auras
for (LiquidTypeEntry const* liquid : sLiquidTypeStore)
{
if (uint32 spellId = liquid->SpellId)
if (SpellInfo* spellInfo = _GetSpellInfo(spellId))
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 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));
}
}
void SpellMgr::LoadSpellInfoCorrections()
{
uint32 oldMSTime = getMSTime();
// Some spells have no amplitude set
{
ApplySpellFix({
6727, // Poison Mushroom
7288, // Immolate Cumulative (TEST) (Rank 1)
7291, // Food (TEST)
7331, // Healing Aura (TEST) (Rank 1)
/*
30400, // Nether Beam - Perseverance
Blizzlike to have it disabled? DBC says:
"This is currently turned off to increase performance. Enable this to make it fire more frequently."
*/
34589, // Dangerous Water
52562, // Arthas Zombie Catcher
57550, // Tirion Aggro
65755
}, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].Amplitude = 1 * IN_MILLISECONDS;
});
ApplySpellFix({
24707, // Food
26263, // Dim Sum
29055, // Refreshing Red Apple
37504 // Karazhan - Chess NPC AI, action timer
}, [](SpellInfo* spellInfo)
{
// first effect has correct amplitude
spellInfo->Effects[EFFECT_1].Amplitude = spellInfo->Effects[EFFECT_0].Amplitude;
});
// Vomit
ApplySpellFix({ 43327 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_1].Amplitude = 1 * IN_MILLISECONDS;
});
// Strider Presence
ApplySpellFix({ 4312 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].Amplitude = 1 * IN_MILLISECONDS;
spellInfo->Effects[EFFECT_1].Amplitude = 1 * IN_MILLISECONDS;
});
// Food
ApplySpellFix({ 64345 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].Amplitude = 1 * IN_MILLISECONDS;
spellInfo->Effects[EFFECT_2].Amplitude = 1 * IN_MILLISECONDS;
});
}
// specific code for cases with no trigger spell provided in field
{
// Brood Affliction: Bronze
ApplySpellFix({ 23170 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TriggerSpell = 23171;
});
// Feed Captured Animal
ApplySpellFix({ 29917 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TriggerSpell = 29916;
});
// Remote Toy
ApplySpellFix({ 37027 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TriggerSpell = 37029;
});
// Eye of Grillok
ApplySpellFix({ 38495 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TriggerSpell = 38530;
});
// Tear of Azzinoth Summon Channel - it's not really supposed to do anything, and this only prevents the console spam
ApplySpellFix({ 39857 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TriggerSpell = 39856;
});
// Personalized Weather
ApplySpellFix({ 46736 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_1].TriggerSpell = 46737;
});
}
// Allows those to crit
ApplySpellFix({
379, // Earth Shield
33778, // Lifebloom Final Bloom
52042, // Healing Stream Totem
// this one is here because we have no SP bonus for dmgclass none spell
// but this one should since it's DBC data, it won't crit because it already has can't crit attr
64844, // Divine Hymn
71607, // Item - Bauble of True Blood 10m
71646, // Item - Bauble of True Blood 25m
71610, // Item - Althor's Abacus trigger 10m
71641 // Item - Althor's Abacus trigger 25m
}, [](SpellInfo* spellInfo)
{
// We need more spells to find a general way (if there is any)
spellInfo->DmgClass = SPELL_DAMAGE_CLASS_MAGIC;
});
// Spell Reflection
ApplySpellFix({ 57643 }, [](SpellInfo* spellInfo)
{
spellInfo->EquippedItemClass = -1;
});
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);
});
// Immolate
ApplySpellFix({
348,
707,
1094,
2941,
11665,
11667,
11668,
25309,
27215,
47810,
47811
}, [](SpellInfo* spellInfo)
{
// copy SP scaling data from direct damage to DoT
spellInfo->Effects[EFFECT_0].BonusMultiplier = spellInfo->Effects[EFFECT_1].BonusMultiplier;
});
// Detect Undead
ApplySpellFix({ 11389 }, [](SpellInfo* spellInfo)
{
spellInfo->PowerType = POWER_MANA;
spellInfo->ManaCost = 0;
spellInfo->ManaPerSecond = 0;
});
// 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;
});
// Battlegear of Eternal Justice
ApplySpellFix({
26135, // Battlegear of Eternal Justice
37557 // Mark of Light
}, [](SpellInfo* spellInfo)
{
spellInfo->SpellFamilyFlags = flag96();
});
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;
});
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
10872, // Abolish Disease Effect
3137 // Abolish Poison Effect
}, [](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;
});
// Missile Barrage
ApplySpellFix({ 44401 }, [](SpellInfo* spellInfo)
{
// should be consumed before Clearcasting
spellInfo->Priority = 100;
});
// 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);
});
// Mana Shield (rank 2)
ApplySpellFix({ 8494 }, [](SpellInfo* spellInfo)
{
// because of bug in dbc
spellInfo->ProcChance = 0;
});
// Maelstrom Weapon
ApplySpellFix({
51528, // (Rank 1)
51529, // (Rank 2)
51530, // (Rank 3)
51531, // (Rank 4)
51532 // (Rank 5)
}, [](SpellInfo* spellInfo)
{
// due to discrepancies between ranks
spellInfo->EquippedItemSubClassMask = 0x0000FC33;
spellInfo->AttributesEx3 |= SPELL_ATTR3_CAN_PROC_WITH_TRIGGERED;
});
ApplySpellFix({
20335, // Heart of the Crusader
20336,
20337,
53228, // Rapid Killing (Rank 1)
53232, // Rapid Killing (Rank 2)
63320 // Glyph of Life Tap
}, [](SpellInfo* spellInfo)
{
// Entries were not updated after spell effect change, we have to do that manually :/
spellInfo->AttributesEx3 |= SPELL_ATTR3_CAN_PROC_WITH_TRIGGERED;
});
ApplySpellFix({
51627, // Turn the Tables (Rank 1)
51628, // Turn the Tables (Rank 2)
51629 // Turn the Tables (Rank 3)
}, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
});
ApplySpellFix({
52910, // Turn the Tables
52914, // Turn the Tables
52915 // Turn the Tables
}, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
});
// Magic Absorption
ApplySpellFix({
29441, // (Rank 1)
29444 // (Rank 2)
}, [](SpellInfo* spellInfo)
{
// Caused off by 1 calculation (ie 79 resistance at level 80)
spellInfo->SpellLevel = 0;
});
// Execute
ApplySpellFix({
5308, // (Rank 1)
20658, // (Rank 2)
20660, // (Rank 3)
20661, // (Rank 4)
20662, // (Rank 5)
25234, // (Rank 6)
25236, // (Rank 7)
47470, // (Rank 8)
47471 // (Rank 9)
}, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx3 |= SPELL_ATTR3_CANT_TRIGGER_PROC;
});
// 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({
44978, // Wild Magic
45001, // Wild Magic
45002, // Wild Magic
45004, // Wild Magic
45006, // Wild Magic
45010, // Wild Magic
31347, // Doom
41635, // Prayer of Mending
44869, // Spectral Blast
45027, // Revitalize
45976, // Muru Portal Channel
39365, // Thundering Storm
41071, // Raise Dead (HACK)
52124, // Sky Darkener Assault
42442, // Vengeance Landing Cannonfire
45863, // Cosmetic - Incinerate to Random Target
25425, // Shoot
45761, // Shoot
42611, // Shoot
61588, // Blazing Harpoon
52479, // Gift of the Harvester
48246, // Ball of Flame
36327, // Shoot Arcane Explosion Arrow
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)
45907, // Torch Target Picker
52953, // Torch
58121 // Torch
}, [](SpellInfo* spellInfo)
{
spellInfo->MaxAffectedTargets = 1;
});
ApplySpellFix({
36384 // Skartax Purple Beam
}, [](SpellInfo* spellInfo)
{
spellInfo->MaxAffectedTargets = 2;
});
ApplySpellFix({
41376, // Spite
39992, // Needle Spine
29576, // Multi-Shot
40816, // Saber Lash
37790, // Spread Shot
46771, // Flame Sear
45248, // Shadow Blades
41303, // Soul Drain
54172, // Divine Storm (heal)
29213, // Curse of the Plaguebringer - Noth
28542, // Life Drain - Sapphiron
66588, // Flaming Spear
54171 // Divine Storm
}, [](SpellInfo* spellInfo)
{
spellInfo->MaxAffectedTargets = 3;
});
ApplySpellFix({
38310, // Multi-Shot
53385 // Divine Storm (Damage)
}, [](SpellInfo* spellInfo)
{
spellInfo->MaxAffectedTargets = 4;
});
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;
});
ApplySpellFix({
54835 // Curse of the Plaguebringer - Noth (H)
}, [](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;
});
ApplySpellFix({
50312 // Unholy Frenzy
}, [](SpellInfo* spellInfo)
{
spellInfo->MaxAffectedTargets = 15;
});
ApplySpellFix({
47977, // Magic Broom
48025, // Headless Horseman's Mount
54729, // Winged Steed of the Ebon Blade
58983, // Big Blizzard Bear
65917, // Magic Rooster
71342, // Big Love Rocket
72286, // Invincible
74856, // Blazing Hippogryph
75614, // Celestial Steed
75973 // X-53 Touring Rocket
}, [](SpellInfo* spellInfo)
{
// First two effects apply auras, which shouldn't be there
// due to NO_TARGET applying aura on current caster (core bug)
// Just wipe effect data, to mimic blizz-behavior
spellInfo->Effects[EFFECT_0].Effect = 0;
spellInfo->Effects[EFFECT_1].Effect = 0;
});
// Lock and Load (Rank 1)
ApplySpellFix({ 56342 }, [](SpellInfo* spellInfo)
{
// @workaround: Delete dummy effect from rank 1
// effect apply aura has NO_TARGET but core still applies it to caster (same as above)
spellInfo->Effects[EFFECT_2].Effect = 0;
});
// Roar of Sacrifice
ApplySpellFix({ 53480 }, [](SpellInfo* spellInfo)
{
// missing spell effect 2 data, taken from 4.3.4
spellInfo->Effects[EFFECT_1].Effect = SPELL_EFFECT_APPLY_AURA;
spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_DUMMY;
spellInfo->Effects[EFFECT_1].MiscValue = 127;
spellInfo->Effects[EFFECT_1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_TARGET_ALLY);
});
// Murmur's Touch
ApplySpellFix({ 33711, 38794 }, [](SpellInfo* spellInfo)
{
spellInfo->MaxAffectedTargets = 1;
spellInfo->Effects[EFFECT_0].TriggerSpell = 33760;
});
// Fingers of Frost
ApplySpellFix({ 44544 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].SpellClassMask = flag96(685904631, 1151048, 0);
});
// Magic Suppression - DK
ApplySpellFix({ 49224, 49610, 49611 }, [](SpellInfo* spellInfo)
{
spellInfo->ProcCharges = 0;
});
// Death and Decay
ApplySpellFix({ 52212 }, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_INVISIBLE;
});
// Oscillation Field
ApplySpellFix({ 37408 }, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
});
// Everlasting Affliction
ApplySpellFix({ 47201, 47202, 47203, 47204, 47205 }, [](SpellInfo* spellInfo)
{
// add corruption to affected spells
spellInfo->Effects[EFFECT_1].SpellClassMask[0] |= 2;
});
// Renewed Hope
ApplySpellFix({
57470, // (Rank 1)
57472 // (Rank 2)
}, [](SpellInfo* spellInfo)
{
// should also affect Flash Heal
spellInfo->Effects[EFFECT_0].SpellClassMask[0] |= 0x800;
});
// Crafty's Ultra-Advanced Proto-Typical Shortening Blaster
ApplySpellFix({ 51912 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].Amplitude = 3000;
});
// Desecration Arm - 36 instead of 37 - typo? :/
ApplySpellFix({ 29809 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_7_YARDS);
});
// 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 }, [](SpellInfo* spellInfo)
{
spellInfo->Stances = UI64LIT(1) << (FORM_CAT - 1);
});
ApplySpellFix({ 48421 }, [](SpellInfo* spellInfo)
{
spellInfo->Stances = UI64LIT(1) << (FORM_MOONKIN - 1);
});
ApplySpellFix({ 48422 }, [](SpellInfo* spellInfo)
{
spellInfo->Stances = UI64LIT(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;
});
// Improved Stings (Rank 2)
ApplySpellFix({ 19465 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_2].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
});
// Nether Portal - Perseverence
ApplySpellFix({ 30421 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_2].BasePoints += 30000;
});
// Natural shapeshifter
ApplySpellFix({ 16834, 16835 }, [](SpellInfo* spellInfo)
{
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(21);
});
// Ebon Plague
ApplySpellFix({ 65142 }, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx3 &= ~SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
});
// Ebon Plague
ApplySpellFix({ 51735, 51734, 51726 }, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS;
spellInfo->SpellFamilyFlags[2] = 0x10;
spellInfo->Effects[EFFECT_1].ApplyAuraName = SPELL_AURA_MOD_DAMAGE_PERCENT_TAKEN;
});
// 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)
16237, // Ancestral Fortitude (Rank 3)
47930, // Grace
45145, // Snake Trap Effect (Rank 1)
13812, // Explosive Trap Effect (Rank 1)
14314, // Explosive Trap Effect (Rank 2)
14315, // Explosive Trap Effect (Rank 3)
27026, // Explosive Trap Effect (Rank 4)
49064, // Explosive Trap Effect (Rank 5)
49065, // Explosive Trap Effect (Rank 6)
43446, // Explosive Trap Effect (Hexlord Malacrass)
50661, // Weakened Resolve
68979, // Unleashed Souls
48714, // Compelled
7853 // The Art of Being a Water Terror: Force Cast on Player
}, [](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);
});
// Wind Shear
ApplySpellFix({ 57994 }, [](SpellInfo* spellInfo)
{
// improper data for EFFECT_1 in 3.3.5 DBC, but is correct in 4.x
spellInfo->Effects[EFFECT_1].Effect = SPELL_EFFECT_MODIFY_THREAT_PERCENT;
spellInfo->Effects[EFFECT_1].BasePoints = -6; // -5%
});
ApplySpellFix({
50526, // Wandering Plague
15290 // Vampiric Embrace
}, [](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;
});
// Deep Wounds
ApplySpellFix({ 12721 }, [](SpellInfo* spellInfo)
{
// shouldnt ignore resillience or damage taken auras because its damage is not based off a spell.
spellInfo->AttributesEx4 &= ~SPELL_ATTR4_FIXED_DAMAGE;
});
// 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;
});
// Flametongue Totem (Aura)
ApplySpellFix({
52109, // rank 1
52110, // rank 2
52111, // rank 3
52112, // rank 4
52113, // rank 5
58651, // rank 6
58654, // rank 7
58655 // rank 8
}, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
spellInfo->Effects[EFFECT_1].TargetA = SpellImplicitTargetInfo(TARGET_UNIT_CASTER);
spellInfo->Effects[EFFECT_0].TargetB = SpellImplicitTargetInfo();
spellInfo->Effects[EFFECT_1].TargetB = SpellImplicitTargetInfo();
});
// Marked for Death
ApplySpellFix({
53241, // (Rank 1)
53243, // (Rank 2)
53244, // (Rank 3)
53245, // (Rank 4)
53246 // (Rank 5)
}, [](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;
});
ApplySpellFix({
64745, // Item - Death Knight T8 Tank 4P Bonus
64936 // Item - Warrior T8 Protection 4P Bonus
}, [](SpellInfo* spellInfo)
{
// 100% chance of procc'ing, not -10% (chance calculated in PrepareTriggersExecutedOnHit)
spellInfo->Effects[EFFECT_0].BasePoints = 100;
});
// Entangling Roots -- Nature's Grasp Proc
ApplySpellFix({
19970, // (Rank 6)
19971, // (Rank 5)
19972, // (Rank 4)
19973, // (Rank 3)
19974, // (Rank 2)
19975, // (Rank 1)
27010, // (Rank 7)
53313 // (Rank 8)
}, [](SpellInfo* spellInfo)
{
spellInfo->CastTimeEntry = sSpellCastTimesStore.LookupEntry(1);
});
// 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;
});
// Death Knight T10 Tank 2P Bonus
ApplySpellFix({ 70650 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_ADD_PCT_MODIFIER;
});
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
spellInfo->Effects[EFFECT_0].Effect = 0;
});
ApplySpellFix({
47476, // Deathknight - Strangulate
15487, // Priest - Silence
5211, // Druid - Bash - R1
6798, // Druid - Bash - R2
8983 // Druid - Bash - R3
}, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx7 |= SPELL_ATTR7_INTERRUPT_ONLY_NONPLAYER;
});
// Guardian Spirit
ApplySpellFix({ 47788 }, [](SpellInfo* spellInfo)
{
spellInfo->ExcludeTargetAuraSpell = 72232; // Weakened Spirit
});
ApplySpellFix({
42490, // Energized!
42492, // Cast Energized
43115 // Plague Vial
}, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx |= SPELL_ATTR1_NO_THREAT;
});
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);
});
// Polymorph (Six Demon Bag)
ApplySpellFix({ 14621 }, [](SpellInfo* spellInfo)
{
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(4); // Medium Range
});
// Concussive Barrage
ApplySpellFix({ 35101 }, [](SpellInfo* spellInfo)
{
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(155); // Hunter Range (Long)
});
ApplySpellFix({
19503, // Scatter Shot
34490 // Silencing Shot
}, [](SpellInfo* spellInfo)
{
spellInfo->Speed = 0.f;
});
ApplySpellFix({
55741, // Desecration (Rank 1)
68766, // Desecration (Rank 2)
57842 // Killing Spree (Off hand damage)
}, [](SpellInfo* spellInfo)
{
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(2); // Melee Range
});
// Safeguard
ApplySpellFix({
46946, // (Rank 1)
46947 // (Rank 2)
}, [](SpellInfo* spellInfo)
{
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(34); // Twenty-Five yards
});
// Summon Corpse Scarabs
ApplySpellFix({ 28864, 29105 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_10_YARDS);
});
//
// 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({
62716, // Growth of Nature (Freya)
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;
});
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;
});
// Blizzard (Thorim)
ApplySpellFix({ 62576, 62602 }, [](SpellInfo* spellInfo)
{
// DBC data is wrong for EFFECT_0, it's a different dynobject target than EFFECT_1
// Both effects should be shared by the same DynObject
spellInfo->Effects[EFFECT_0].TargetA = SpellImplicitTargetInfo(TARGET_DEST_CASTER_LEFT);
});
// 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);
});
// Bone Slice (Lord Marrowgar)
ApplySpellFix({ 69055, 70814 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_5_YARDS); // 5yd
});
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
});
// Coldflame (Lord Marrowgar)
ApplySpellFix({ 69146, 70823, 70824, 70825 }, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx4 &= ~SPELL_ATTR4_IGNORE_RESISTANCES;
});
// 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;
});
// 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(3); // 60 seconds
});
// Shadow Trap (visual)
ApplySpellFix({ 73530 }, [](SpellInfo* spellInfo)
{
spellInfo->DurationEntry = sSpellDurationStore.LookupEntry(27); // 3 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
//
// OCULUS SPELLS
//
ApplySpellFix({
// The spells below are here because their effect 1 is giving warning due to
// triggered spell not found in any dbc and is missing from encounter source* of data.
// Even judged as clientside these spells can't be guessed for* now.
49462, // Call Ruby Drake
49461, // Call Amber Drake
49345 // Call Emerald Drake
}, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_1].Effect = 0;
});
// ENDOF OCULUS SPELLS
// Introspection
ApplySpellFix({ 40055, 40165, 40166, 40167 }, [](SpellInfo* spellInfo)
{
spellInfo->Attributes |= SPELL_ATTR0_NEGATIVE_1;
});
// Chains of Ice
ApplySpellFix({ 45524 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_2].TargetA = SpellImplicitTargetInfo();
});
// Minor Fortitude
ApplySpellFix({ 2378 }, [](SpellInfo* spellInfo)
{
spellInfo->ManaCost = 0;
spellInfo->ManaPerSecond = 0;
});
// Threatening Gaze
ApplySpellFix({ 24314 }, [](SpellInfo* spellInfo)
{
spellInfo->AuraInterruptFlags |= AURA_INTERRUPT_FLAG_CAST | AURA_INTERRUPT_FLAG_MOVE | AURA_INTERRUPT_FLAG_JUMP;
});
//
// ISLE OF CONQUEST SPELLS
//
// Teleport
ApplySpellFix({ 66551 }, [](SpellInfo* spellInfo)
{
spellInfo->RangeEntry = sSpellRangeStore.LookupEntry(13); // 50000yd
});
// ENDOF ISLE OF CONQUEST SPELLS
// Aura of Fear
ApplySpellFix({ 40453 }, [](SpellInfo* spellInfo)
{
// Bad DBC data? Copying 25820 here due to spell description
// either is a periodic with chance on tick, or a proc
spellInfo->Effects[EFFECT_0].ApplyAuraName = SPELL_AURA_PROC_TRIGGER_SPELL;
spellInfo->Effects[EFFECT_0].Amplitude = 0;
spellInfo->ProcChance = 10;
});
ApplySpellFix({
41485, // Deadly Poison - Black Temple
41487 // Envenom - Black Temple
}, [](SpellInfo* spellInfo)
{
spellInfo->AttributesEx6 |= SPELL_ATTR6_CAN_TARGET_INVISIBLE;
});
ApplySpellFix({
// Proc attribute correction
// Remove procflags from test/debug/deprecated spells to avoid DB Errors
2479, // Honorless Target
3232, // Gouge Stun Test
3409, // Crippling Poison
4312, // Strider Presence
5707, // Lifestone Regeneration
5760, // Mind-numbing Poison
6727, // Poison Mushroom
6940, // Hand of Sacrifice (handled remove in split hook)
6984, // Frost Shot (Rank 2)
7164, // Defensive Stance
7288, // Immolate Cumulative (TEST) (Rank 1)
7291, // Food (TEST)
7331, // Healing Aura (TEST) (Rank 1)
7366, // Berserker Stance
7824, // Blacksmithing Skill +10
12551, // Frost Shot
13218, // Wound Poison (Rank 1)
13222, // Wound Poison II (Rank 2)
13223, // Wound Poison III (Rank 3)
13224, // Wound Poison IV (Rank 4)
14795, // Venomhide Poison
16610, // Razorhide
18099, // Chill Nova
18499, // Berserker Rage (extra rage implemented in Unit::RewardRage)
18802, // Frost Shot
20000, // Alexander's Test Periodic Aura
21163, // Polished Armor (Rank 1)
22818, // Mol'dar's Moxie
22820, // Slip'kik's Savvy
23333, // Warsong Flag
23335, // Silverwing Flag
25160, // Sand Storm
27189, // Wound Poison V (Rank 5)
28313, // Aura of Fear
28726, // Nightmare Seed
28754, // Fury of the Ashbringer
30802, // Unleashed Rage (Rank 1)
31481, // Lung Burst
32430, // Battle Standard
32431, // Battle Standard
32447, // Travel Form
33370, // Spell Haste
33807, // Abacus of Violent Odds
33891, // Tree of Life (Shapeshift)
34132, // Gladiator's Totem of the Third Wind
34135, // Libram of Justice
34666, // Tamed Pet Passive 08 (DND)
34667, // Tamed Pet Passive 09 (DND)
34775, // Dragonspine Flurry
34889, // Fire Breath (Rank 1)
34976, // Netherstorm Flag
35131, // Bladestorm
35244, // Choking Vines
35323, // Fire Breath (Rank 2)
35336, // Energizing Spores
36148, // Chill Nova
36613, // Aspect of the Spirit Hunter
36786, // Soul Chill
37174, // Perceived Weakness
37482, // Exploited Weakness
37526, // Battle Rush
37588, // Dive
37985, // Fire Breath
38317, // Forgotten Knowledge
38843, // Soul Chill
39015, // Atrophic Blow
40396, // Fel Infusion
40603, // Taunt Gurtogg
40803, // Ron's Test Buff
40879, // Prismatic Shield (no longer used since patch 2.2/adaptive prismatic shield)
41341, // Balance of Power (implemented by hooking absorb)
41435, // The Twin Blades of Azzinoth
42369, // Merciless Libram of Justice
42371, // Merciless Gladiator's Totem of the Third Wind
42636, // Birmingham Tools Test 3
43727, // Vengeful Libram of Justice
43729, // Vengeful Gladiator's Totem of the Third Wind
43817, // Focused Assault
44305, // You're a ...! (Effects2)
44586, // Prayer of Mending (unknown, unused aura type)
45384, // Birmingham Tools Test 4
45433, // Birmingham Tools Test 5
46093, // Brutal Libram of Justice
46099, // Brutal Gladiator's Totem of the Third Wind
46705, // Honorless Target
49145, // Spell Deflection (Rank 1) (implemented by hooking absorb)
49883, // Flames
50365, // Improved Blood Presence (Rank 1)
50371, // Improved Blood Presence (Rank 2)
50462, // Anti-Magic Zone (implemented by hooking absorb)
50498, // Savage Rend (Rank 1) - proc from Savage Rend moved from attack itself to autolearn aura 50871
53578, // Savage Rend (Rank 2)
53579, // Savage Rend (Rank 3)
53580, // Savage Rend (Rank 4)
53581, // Savage Rend (Rank 5)
53582, // Savage Rend (Rank 6)
50655, // Frost Cut
50995, // Empowered Blood Presence (Rank 1)
51809, // First Aid
53032, // Flurry of Claws
55482, // Fire Breath (Rank 3)
55483, // Fire Breath (Rank 4)
55484, // Fire Breath (Rank 5)
55485, // Fire Breath (Rank 6)
57974, // Wound Poison VI (Rank 6)
57975, // Wound Poison VII (Rank 7)
60062, // Essence of Life
60302, // Meteorite Whetstone
60437, // Grim Toll
60492, // Embrace of the Spider
62142, // Improved Chains of Ice (Rank 3)
63024, // Gravity Bomb
64205, // Divine Sacrifice (handled remove in split hook)
64772, // Comet's Trail
65004, // Alacrity of the Elements
65019, // Mjolnir Runestone
65024, // Implosion
66334, // Mistress' Kiss - currently not used in script, need implement?
67905, // Mistress' Kiss
67906, // Mistress' Kiss
67907, // Mistress' Kiss
71003, // Vegard's Touch
72151, // Frenzied Bloodthirst - currently not used in script, need implement?
72648, // Frenzied Bloodthirst
72649, // Frenzied Bloodthirst
72650, // Frenzied Bloodthirst
72559, // Birmingham Tools Test 3
72560, // Birmingham Tools Test 3
72561, // Birmingham Tools Test 5
72980 // Shadow Resonance
}, [](SpellInfo* spellInfo)
{
spellInfo->ProcFlags = 0;
});
// Shadowstep
ApplySpellFix({ 36563 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_0].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_4_YARDS); // 4yd
});
// Feral Charge - Cat
ApplySpellFix({ 49376 }, [](SpellInfo* spellInfo)
{
spellInfo->Effects[EFFECT_1].RadiusEntry = sSpellRadiusStore.LookupEntry(EFFECT_RADIUS_3_YARDS); // 3yd
});
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
{
SpellInfo* spellInfo = mSpellInfoMap[i];
if (!spellInfo)
continue;
// Fix range for trajectory triggered spell
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
{
if (spellInfo->Effects[j].IsEffect() && (spellInfo->Effects[j].TargetA.GetTarget() == TARGET_DEST_TRAJ || spellInfo->Effects[j].TargetB.GetTarget() == TARGET_DEST_TRAJ))
{
// Get triggered spell if any
if (SpellInfo* spellInfoTrigger = const_cast(GetSpellInfo(spellInfo->Effects[j].TriggerSpell)))
{
float maxRangeMain = spellInfo->RangeEntry ? spellInfo->RangeEntry->maxRangeHostile : 0.0f;
float maxRangeTrigger = spellInfoTrigger->RangeEntry ? spellInfoTrigger->RangeEntry->maxRangeHostile : 0.0f;
// check if triggered spell has enough max range to cover trajectory
if (maxRangeTrigger < maxRangeMain)
spellInfoTrigger->RangeEntry = spellInfo->RangeEntry;
}
}
}
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;
case SPELL_EFFECT_APPLY_AURA:
// special aura updates each 30 seconds
if (spellInfo->Effects[j].ApplyAuraName == SPELL_AURA_MOD_ATTACK_POWER_OF_ARMOR)
spellInfo->Effects[j].Amplitude = 30 * IN_MILLISECONDS;
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);
// 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;
}
// allows those to calculate proper crit chance, that needs to be passed on to triggered spell
if (spellInfo->HasAttribute(SPELL_ATTR4_INHERIT_CRIT_FROM_AURA) && spellInfo->DmgClass == SPELL_DAMAGE_CLASS_NONE)
spellInfo->DmgClass = SPELL_DAMAGE_CLASS_MAGIC;
}
if (SummonPropertiesEntry* properties = const_cast(sSummonPropertiesStore.LookupEntry(121)))
properties->Type = SUMMON_TYPE_TOTEM;
if (SummonPropertiesEntry* properties = const_cast(sSummonPropertiesStore.LookupEntry(647))) // 52893
properties->Type = SUMMON_TYPE_TOTEM;
if (SummonPropertiesEntry* properties = const_cast(sSummonPropertiesStore.LookupEntry(628))) // Hungry Plaguehound
properties->Category = SUMMON_CATEGORY_PET;
if (LockEntry* entry = const_cast(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));
}