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