/* * Copyright (C) 2008-2011 TrinityCore * Copyright (C) 2005-2009 MaNGOS * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "SpellMgr.h" #include "SpellInfo.h" #include "ObjectMgr.h" #include "SpellAuras.h" #include "SpellAuraDefines.h" #include "SharedDefines.h" #include "DBCStores.h" #include "World.h" #include "Chat.h" #include "Spell.h" #include "BattlegroundMgr.h" #include "CreatureAI.h" #include "MapManager.h" #include "BattlegroundIC.h" bool IsPrimaryProfessionSkill(uint32 skill) { SkillLineEntry const *pSkill = sSkillLineStore.LookupEntry(skill); if (!pSkill) return false; if (pSkill->categoryId != SKILL_CATEGORY_PROFESSION) return false; return true; } bool IsPartOfSkillLine(uint32 skillId, uint32 spellId) { SkillLineAbilityMapBounds skillBounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId); for (SkillLineAbilityMap::const_iterator itr = skillBounds.first; itr != skillBounds.second; ++itr) if (itr->second->skillId == skillId) return true; return false; } DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellInfo const* spellproto, bool triggered) { if (spellproto->IsPositive()) return DIMINISHING_NONE; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (spellproto->Effects[i].ApplyAuraName == SPELL_AURA_MOD_TAUNT) return DIMINISHING_TAUNT; } // Explicit Diminishing Groups switch (spellproto->SpellFamilyName) { case SPELLFAMILY_GENERIC: { // Pet charge effects (Infernal Awakening, Demon Charge) if (spellproto->SpellVisual[0] == 2816 && spellproto->SpellIconID == 15) return DIMINISHING_CONTROLLED_STUN; // Gnaw else if (spellproto->Id == 47481) return DIMINISHING_CONTROLLED_STUN; break; } // Event spells case SPELLFAMILY_UNK1: return DIMINISHING_NONE; case SPELLFAMILY_MAGE: { // Frostbite if (spellproto->SpellFamilyFlags[1] & 0x80000000) return DIMINISHING_ROOT; // Shattered Barrier else if (spellproto->SpellVisual[0] == 12297) return DIMINISHING_ROOT; // Deep Freeze else if (spellproto->SpellIconID == 2939 && spellproto->SpellVisual[0] == 9963) return DIMINISHING_CONTROLLED_STUN; // Frost Nova / Freeze (Water Elemental) else if (spellproto->SpellIconID == 193) return DIMINISHING_CONTROLLED_ROOT; // Dragon's Breath else if (spellproto->SpellFamilyFlags[0] & 0x800000) return DIMINISHING_DISORIENT; break; } case SPELLFAMILY_WARRIOR: { // Hamstring - limit duration to 10s in PvP if (spellproto->SpellFamilyFlags[0] & 0x2) return DIMINISHING_LIMITONLY; // Improved Hamstring else if (spellproto->AttributesEx3 & 0x80000 && spellproto->SpellIconID == 23) return DIMINISHING_ROOT; // Charge Stun (own diminishing) else if (spellproto->SpellFamilyFlags[0] & 0x01000000) return DIMINISHING_CHARGE; break; } case SPELLFAMILY_WARLOCK: { // Death Coil if (spellproto->SpellFamilyFlags[0] & 0x80000) return DIMINISHING_HORROR; // Curses/etc else if ((spellproto->SpellFamilyFlags[0] & 0x80000000) || (spellproto->SpellFamilyFlags[1] & 0x200)) return DIMINISHING_LIMITONLY; // Seduction else if (spellproto->SpellFamilyFlags[1] & 0x10000000) return DIMINISHING_FEAR; break; } case SPELLFAMILY_PRIEST: { // Psychic Horror if (spellproto->SpellFamilyFlags[2] & 0x2000) return DIMINISHING_HORROR; break; } case SPELLFAMILY_DRUID: { // Pounce if (spellproto->SpellFamilyFlags[0] & 0x20000) return DIMINISHING_OPENING_STUN; // Cyclone else if (spellproto->SpellFamilyFlags[1] & 0x20) return DIMINISHING_CYCLONE; // Entangling Roots // Nature's Grasp else if (spellproto->SpellFamilyFlags[0] & 0x00000200) return DIMINISHING_CONTROLLED_ROOT; // Faerie Fire else if (spellproto->SpellFamilyFlags[0] & 0x400) return DIMINISHING_LIMITONLY; break; } case SPELLFAMILY_ROGUE: { // Gouge if (spellproto->SpellFamilyFlags[0] & 0x8) return DIMINISHING_DISORIENT; // Blind else if (spellproto->SpellFamilyFlags[0] & 0x1000000) return DIMINISHING_FEAR; // Cheap Shot else if (spellproto->SpellFamilyFlags[0] & 0x400) return DIMINISHING_OPENING_STUN; // Crippling poison - Limit to 10 seconds in PvP (No SpellFamilyFlags) else if (spellproto->SpellIconID == 163) return DIMINISHING_LIMITONLY; break; } case SPELLFAMILY_HUNTER: { // Hunter's mark if ((spellproto->SpellFamilyFlags[0] & 0x400) && spellproto->SpellIconID == 538) return DIMINISHING_LIMITONLY; // Scatter Shot (own diminishing) else if ((spellproto->SpellFamilyFlags[0] & 0x40000) && spellproto->SpellIconID == 132) return DIMINISHING_SCATTER_SHOT; // Entrapment (own diminishing) else if (spellproto->SpellVisual[0] == 7484 && spellproto->SpellIconID == 20) return DIMINISHING_ENTRAPMENT; // Wyvern Sting mechanic is MECHANIC_SLEEP but the diminishing is DIMINISHING_DISORIENT else if ((spellproto->SpellFamilyFlags[1] & 0x1000) && spellproto->SpellIconID == 1721) return DIMINISHING_DISORIENT; // Freezing Arrow else if (spellproto->SpellFamilyFlags[0] & 0x8) return DIMINISHING_DISORIENT; break; } case SPELLFAMILY_PALADIN: { // Judgement of Justice - limit duration to 10s in PvP if (spellproto->SpellFamilyFlags[0] & 0x100000) return DIMINISHING_LIMITONLY; // Turn Evil else if ((spellproto->SpellFamilyFlags[1] & 0x804000) && spellproto->SpellIconID == 309) return DIMINISHING_FEAR; break; } case SPELLFAMILY_DEATHKNIGHT: { // Hungering Cold (no flags) if (spellproto->SpellIconID == 2797) return DIMINISHING_DISORIENT; // Mark of Blood else if ((spellproto->SpellFamilyFlags[0] & 0x10000000) && spellproto->SpellIconID == 2285) return DIMINISHING_LIMITONLY; break; } default: break; } // Lastly - Set diminishing depending on mechanic uint32 mechanic = spellproto->GetAllEffectsMechanicMask(); if (mechanic & (1 << MECHANIC_CHARM)) return DIMINISHING_MIND_CONTROL; if (mechanic & (1 << MECHANIC_SILENCE)) return DIMINISHING_SILENCE; if (mechanic & (1 << MECHANIC_SLEEP)) return DIMINISHING_SLEEP; if (mechanic & ((1 << MECHANIC_SAPPED) | (1 << MECHANIC_POLYMORPH) | (1 << MECHANIC_SHACKLE))) return DIMINISHING_DISORIENT; // Mechanic Knockout, except Blast Wave if (mechanic & (1 << MECHANIC_KNOCKOUT) && spellproto->SpellIconID != 292) return DIMINISHING_DISORIENT; if (mechanic & (1 << MECHANIC_DISARM)) return DIMINISHING_DISARM; if (mechanic & (1 << MECHANIC_FEAR)) return DIMINISHING_FEAR; if (mechanic & (1 << MECHANIC_STUN)) return triggered ? DIMINISHING_STUN : DIMINISHING_CONTROLLED_STUN; if (mechanic & (1 << MECHANIC_BANISH)) return DIMINISHING_BANISH; if (mechanic & (1 << MECHANIC_ROOT)) return triggered ? DIMINISHING_ROOT : DIMINISHING_CONTROLLED_ROOT; return DIMINISHING_NONE; } DiminishingReturnsType GetDiminishingReturnsGroupType(DiminishingGroup group) { switch (group) { case DIMINISHING_TAUNT: case DIMINISHING_CONTROLLED_STUN: case DIMINISHING_STUN: case DIMINISHING_OPENING_STUN: case DIMINISHING_CYCLONE: case DIMINISHING_CHARGE: return DRTYPE_ALL; case DIMINISHING_LIMITONLY: case DIMINISHING_NONE: return DRTYPE_NONE; default: return DRTYPE_PLAYER; } } DiminishingLevels GetDiminishingReturnsMaxLevel(DiminishingGroup group) { switch (group) { case DIMINISHING_TAUNT: return DIMINISHING_LEVEL_TAUNT_IMMUNE; default: return DIMINISHING_LEVEL_IMMUNE; } } int32 GetDiminishingReturnsLimitDuration(DiminishingGroup group, SpellInfo const* spellproto) { if (!IsDiminishingReturnsGroupDurationLimited(group)) return 0; // Explicit diminishing duration switch (spellproto->SpellFamilyName) { case SPELLFAMILY_DRUID: { // Faerie Fire - limit to 40 seconds in PvP (3.1) if (spellproto->SpellFamilyFlags[0] & 0x400) return 40 * IN_MILLISECONDS; break; } case SPELLFAMILY_HUNTER: { // Wyvern Sting if (spellproto->SpellFamilyFlags[1] & 0x1000) return 6 * IN_MILLISECONDS; // Hunter's Mark if (spellproto->SpellFamilyFlags[0] & 0x400) return 120 * IN_MILLISECONDS; break; } case SPELLFAMILY_PALADIN: { // Repentance - limit to 6 seconds in PvP if (spellproto->SpellFamilyFlags[0] & 0x4) return 6 * IN_MILLISECONDS; break; } case SPELLFAMILY_WARLOCK: { // Banish - limit to 6 seconds in PvP if (spellproto->SpellFamilyFlags[1] & 0x8000000) return 6 * IN_MILLISECONDS; // Curse of Tongues - limit to 12 seconds in PvP else if (spellproto->SpellFamilyFlags[2] & 0x800) return 12 * IN_MILLISECONDS; // Curse of Elements - limit to 120 seconds in PvP else if (spellproto->SpellFamilyFlags[1] & 0x200) return 120 * IN_MILLISECONDS; break; } default: break; } return 10 * IN_MILLISECONDS; } bool IsDiminishingReturnsGroupDurationLimited(DiminishingGroup group) { switch (group) { case DIMINISHING_CONTROLLED_STUN: case DIMINISHING_STUN: case DIMINISHING_ENTRAPMENT: case DIMINISHING_CONTROLLED_ROOT: case DIMINISHING_ROOT: case DIMINISHING_FEAR: case DIMINISHING_MIND_CONTROL: case DIMINISHING_DISORIENT: case DIMINISHING_CYCLONE: case DIMINISHING_BANISH: case DIMINISHING_LIMITONLY: case DIMINISHING_OPENING_STUN: case DIMINISHING_HORROR: case DIMINISHING_SLEEP: return true; default: return false; } } SpellMgr::SpellMgr() { } SpellMgr::~SpellMgr() { UnloadSpellInfoStore(); } /// Some checks for spells, to prevent adding deprecated/broken spells for trainers, spell book, etc bool SpellMgr::IsSpellValid(SpellInfo const *spellInfo, Player *pl, bool msg) { // not exist if (!spellInfo) return false; bool need_check_reagents = 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, its not need explicit item info (but have special fake items sometime) if (!spellInfo->IsLootCrafting()) { if (msg) { if (pl) ChatHandler(pl).PSendSysMessage("Craft spell %u not have create item entry.", spellInfo->Id); else sLog->outErrorDb("Craft spell %u not have create item entry.", spellInfo->Id); } return false; } } // also possible IsLootCrafting case but fake item must exist anyway else if (!sObjectMgr->GetItemTemplate(spellInfo->Effects[i].ItemType)) { if (msg) { if (pl) ChatHandler(pl).PSendSysMessage("Craft spell %u create not-exist in DB item (Entry: %u) and then...", spellInfo->Id, spellInfo->Effects[i].ItemType); else sLog->outErrorDb("Craft spell %u create not-exist in DB item (Entry: %u) and then...", spellInfo->Id, spellInfo->Effects[i].ItemType); } return false; } need_check_reagents = true; break; } case SPELL_EFFECT_LEARN_SPELL: { SpellInfo const* spellInfo2 = sSpellMgr->GetSpellInfo(spellInfo->Effects[i].TriggerSpell); if (!IsSpellValid(spellInfo2, pl, msg)) { if (msg) { if (pl) ChatHandler(pl).PSendSysMessage("Spell %u learn to broken spell %u, and then...", spellInfo->Id, spellInfo->Effects[i].TriggerSpell); else sLog->outErrorDb("Spell %u learn to invalid spell %u, and then...", spellInfo->Id, spellInfo->Effects[i].TriggerSpell); } return false; } break; } } } if (need_check_reagents) { for (uint8 j = 0; j < MAX_SPELL_REAGENTS; ++j) { if (spellInfo->Reagent[j] > 0 && !sObjectMgr->GetItemTemplate(spellInfo->Reagent[j])) { if (msg) { if (pl) ChatHandler(pl).PSendSysMessage("Craft spell %u have not-exist reagent in DB item (Entry: %u) and then...", spellInfo->Id, spellInfo->Reagent[j]); else sLog->outErrorDb("Craft spell %u have not-exist 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) { 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) { sLog->outError("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) { sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SpellMgr::GetSpellIdForDifficulty: SpellDifficultyEntry not found for spell %u. This should never happen.", spellId); return spellId; //return source spell } if (difficultyEntry->SpellID[mode] <= 0 && mode > DUNGEON_DIFFICULTY_HEROIC) { sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SpellMgr::GetSpellIdForDifficulty: spell %u mode %u spell is NULL, using mode %u", spellId, mode, mode - 2); mode -= 2; } if (difficultyEntry->SpellID[mode] <= 0) { sLog->outErrorDb("SpellMgr::GetSpellIdForDifficulty: spell %u mode %u spell is 0. Check spelldifficulty_dbc!", spellId, mode); return spellId; } sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SpellMgr::GetSpellIdForDifficulty: spellid for spell %u in mode %u is %d", spellId, mode, difficultyEntry->SpellID[mode]); return uint32(difficultyEntry->SpellID[mode]); } SpellInfo const* SpellMgr::GetSpellForDifficultyFromSpell(SpellInfo const* spell, Unit const* caster) const { uint32 newSpellId = GetSpellIdForDifficulty(spell->Id, caster); SpellInfo const* newSpell = GetSpellInfo(newSpellId); if (!newSpell) { sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "SpellMgr::GetSpellForDifficultyFromSpell: spell %u not found. Check spelldifficulty_dbc!", newSpellId); return spell; } sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "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 NULL; 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; } SpellRequiredMapBounds SpellMgr::GetSpellsRequiredForSpellBounds(uint32 spell_id) const { return SpellRequiredMapBounds(mSpellReq.lower_bound(spell_id), mSpellReq.upper_bound(spell_id)); } SpellsRequiringSpellMapBounds SpellMgr::GetSpellsRequiringSpellBounds(uint32 spell_id) const { return SpellsRequiringSpellMapBounds(mSpellsReqSpell.lower_bound(spell_id), mSpellsReqSpell.upper_bound(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; } const SpellsRequiringSpellMap SpellMgr::GetSpellsRequiringSpell() { return this->mSpellsReqSpell; } uint32 SpellMgr::GetSpellRequired(uint32 spell_id) const { SpellRequiredMap::const_iterator itr = mSpellReq.find(spell_id); if (itr == mSpellReq.end()) return 0; return itr->second; } 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 NULL; } SpellLearnSpellMapBounds SpellMgr::GetSpellLearnSpellMapBounds(uint32 spell_id) const { return SpellLearnSpellMapBounds(mSpellLearnSpells.lower_bound(spell_id), mSpellLearnSpells.upper_bound(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) const { SpellTargetPositionMap::const_iterator itr = mSpellTargetPositions.find(spell_id); if (itr != mSpellTargetPositions.end()) return &itr->second; return NULL; } SpellSpellGroupMapBounds SpellMgr::GetSpellSpellGroupMapBounds(uint32 spell_id) const { spell_id = GetFirstSpellInChain(spell_id); return SpellSpellGroupMapBounds(mSpellSpellGroup.lower_bound(spell_id), mSpellSpellGroup.upper_bound(spell_id)); } uint32 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 SpellGroupSpellMapBounds(mSpellGroupSpell.lower_bound(group_id), mSpellGroupSpell.upper_bound(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); } } } SpellGroupStackRule SpellMgr::CheckSpellGroupStackRules(SpellInfo const* spellInfo1, SpellInfo const* spellInfo2) const { uint32 spellid_1 = spellInfo1->GetFirstRankSpell()->Id; uint32 spellid_2 = spellInfo2->GetFirstRankSpell()->Id; if (spellid_1 == spellid_2) return SPELL_GROUP_STACK_RULE_DEFAULT; // 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; } SpellProcEventEntry const* SpellMgr::GetSpellProcEvent(uint32 spellId) const { SpellProcEventMap::const_iterator itr = mSpellProcEventMap.find(spellId); if (itr != mSpellProcEventMap.end()) return &itr->second; return NULL; } bool SpellMgr::IsSpellProcEventCanTriggeredBy(SpellProcEventEntry const* spellProcEvent, uint32 EventProcFlag, SpellInfo const* procSpell, uint32 procFlags, uint32 procExtra, bool active) { // No extra req need uint32 procEvent_procEx = PROC_EX_NONE; // check prockFlags for condition if ((procFlags & EventProcFlag) == 0) return false; bool hasFamilyMask = false; /* Check Periodic Auras *Dots can trigger if spell has no PROC_FLAG_SUCCESSFUL_NEGATIVE_MAGIC_SPELL nor PROC_FLAG_TAKEN_POSITIVE_MAGIC_SPELL *Only Hots can trigger if spell has PROC_FLAG_TAKEN_POSITIVE_MAGIC_SPELL *Only dots can trigger if spell has both positivity flags or PROC_FLAG_SUCCESSFUL_NEGATIVE_MAGIC_SPELL *Aura has to have PROC_FLAG_TAKEN_POSITIVE_MAGIC_SPELL or spellfamily specified to trigger from Hot */ if (procFlags & PROC_FLAG_DONE_PERIODIC) { if (EventProcFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG) { if (!(procExtra & PROC_EX_INTERNAL_DOT)) return false; } else if (procExtra & PROC_EX_INTERNAL_HOT) procExtra |= PROC_EX_INTERNAL_REQ_FAMILY; else if (EventProcFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS) return false; } if (procFlags & PROC_FLAG_TAKEN_PERIODIC) { if (EventProcFlag & PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS) { if (!(procExtra & PROC_EX_INTERNAL_DOT)) return false; } else if (procExtra & PROC_EX_INTERNAL_HOT) procExtra |= PROC_EX_INTERNAL_REQ_FAMILY; else if (EventProcFlag & PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS) return false; } // Trap casts are active by default if (procFlags & PROC_FLAG_DONE_TRAP_ACTIVATION) active = true; // Always trigger for this if (procFlags & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_DEATH)) return true; if (spellProcEvent) // Exist event data { // Store extra req procEvent_procEx = spellProcEvent->procEx; // For melee triggers if (procSpell == NULL) { // Check (if set) for school (melee attack have Normal school) if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0) return false; } else // For spells need check school/spell family/family mask { // Check (if set) for school if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & procSpell->SchoolMask) == 0) return false; // Check (if set) for spellFamilyName if (spellProcEvent->spellFamilyName && (spellProcEvent->spellFamilyName != procSpell->SpellFamilyName)) return false; // spellFamilyName is Ok need check for spellFamilyMask if present if (spellProcEvent->spellFamilyMask) { if (!(spellProcEvent->spellFamilyMask & procSpell->SpellFamilyFlags)) return false; hasFamilyMask = true; // Some spells are not considered as active even with have spellfamilyflags if (!(procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL)) active = true; } } } if (procExtra & (PROC_EX_INTERNAL_REQ_FAMILY)) { if (!hasFamilyMask) return false; } // Check for extra req (if none) and hit/crit if (procEvent_procEx == PROC_EX_NONE) { // No extra req, so can trigger only for hit/crit - spell has to be active if ((procExtra & (PROC_EX_NORMAL_HIT|PROC_EX_CRITICAL_HIT)) && active) return true; } else // Passive spells hits here only if resist/reflect/immune/evade { if (procExtra & AURA_SPELL_PROC_EX_MASK) { // if spell marked as procing only from not active spells if (active && procEvent_procEx & PROC_EX_NOT_ACTIVE_SPELL) return false; // if spell marked as procing only from active spells if (!active && procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL) return false; // Exist req for PROC_EX_EX_TRIGGER_ALWAYS if (procEvent_procEx & PROC_EX_EX_TRIGGER_ALWAYS) return true; // PROC_EX_NOT_ACTIVE_SPELL and PROC_EX_ONLY_ACTIVE_SPELL flags handle: if passed checks before if ((procExtra & (PROC_EX_NORMAL_HIT|PROC_EX_CRITICAL_HIT)) && ((procEvent_procEx & (AURA_SPELL_PROC_EX_MASK)) == 0)) return true; } // Check Extra Requirement like (hit/crit/miss/resist/parry/dodge/block/immune/reflect/absorb and other) if (procEvent_procEx & procExtra) return true; } return false; } SpellProcEntry const* SpellMgr::GetSpellProcEntry(uint32 spellId) const { SpellProcMap::const_iterator itr = mSpellProcMap.find(spellId); if (itr != mSpellProcMap.end()) return &itr->second; return NULL; } bool SpellMgr::CanSpellTriggerProcOnEvent(SpellProcEntry const& procEntry, ProcEventInfo& eventInfo) { // proc type doesn't match if (!(eventInfo.GetTypeMask() & procEntry.typeMask)) 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; // always trigger for these types if (eventInfo.GetTypeMask() & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_DEATH)) return true; // 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() & (PERIODIC_PROC_FLAG_MASK | SPELL_PROC_FLAG_MASK | PROC_FLAG_DONE_TRAP_ACTIVATION)) { if (procEntry.spellFamilyName && (procEntry.spellFamilyName != eventInfo.GetSpellInfo()->SpellFamilyName)) return false; if (procEntry.spellFamilyMask && !(procEntry.spellFamilyMask & eventInfo.GetSpellInfo()->SpellFamilyFlags)) return false; } // check spell type mask (if set) if (eventInfo.GetTypeMask() & (SPELL_PROC_FLAG_MASK | PERIODIC_PROC_FLAG_MASK)) { 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 NULL; } 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); SpellThreatMap::const_iterator itr = mSpellThreatMap.find(firstSpell); if (itr != mSpellThreatMap.end()) return &itr->second; } return NULL; } SkillLineAbilityMapBounds SpellMgr::GetSkillLineAbilityMapBounds(uint32 spell_id) const { return SkillLineAbilityMapBounds(mSkillLineAbilityMap.lower_bound(spell_id), mSkillLineAbilityMap.upper_bound(spell_id)); } PetAura const* SpellMgr::GetPetAura(uint32 spell_id, uint8 eff) { SpellPetAuraMap::const_iterator itr = mSpellPetAuraMap.find((spell_id<<8) + eff); if (itr != mSpellPetAuraMap.end()) return &itr->second; else return NULL; } SpellEnchantProcEntry const* SpellMgr::GetSpellEnchantProcEvent(uint32 enchId) const { SpellEnchantProcEventMap::const_iterator itr = mSpellEnchantProcEventMap.find(enchId); if (itr != mSpellEnchantProcEventMap.end()) return &itr->second; return NULL; } 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) : NULL; } PetLevelupSpellSet const* SpellMgr::GetPetLevelupSpellList(uint32 petFamily) const { PetLevelupSpellMap::const_iterator itr = mPetLevelupSpellMap.find(petFamily); if (itr != mPetLevelupSpellMap.end()) return &itr->second; else return NULL; } PetDefaultSpellsEntry const* SpellMgr::GetPetDefaultSpellsEntry(int32 id) const { PetDefaultSpellsMap::const_iterator itr = mPetDefaultSpellsMap.find(id); if (itr != mPetDefaultSpellsMap.end()) return &itr->second; return NULL; } SpellAreaMapBounds SpellMgr::GetSpellAreaMapBounds(uint32 spell_id) const { return SpellAreaMapBounds(mSpellAreaMap.lower_bound(spell_id), mSpellAreaMap.upper_bound(spell_id)); } SpellAreaForQuestMapBounds SpellMgr::GetSpellAreaForQuestMapBounds(uint32 quest_id, bool active) const { if (active) return SpellAreaForQuestMapBounds(mSpellAreaForActiveQuestMap.lower_bound(quest_id), mSpellAreaForActiveQuestMap.upper_bound(quest_id)); else return SpellAreaForQuestMapBounds(mSpellAreaForQuestMap.lower_bound(quest_id), mSpellAreaForQuestMap.upper_bound(quest_id)); } SpellAreaForQuestMapBounds SpellMgr::GetSpellAreaForQuestEndMapBounds(uint32 quest_id) const { return SpellAreaForQuestMapBounds(mSpellAreaForQuestEndMap.lower_bound(quest_id), mSpellAreaForQuestEndMap.upper_bound(quest_id)); } SpellAreaForAuraMapBounds SpellMgr::GetSpellAreaForAuraMapBounds(uint32 spell_id) const { return SpellAreaForAuraMapBounds(mSpellAreaForAuraMap.lower_bound(spell_id), mSpellAreaForAuraMap.upper_bound(spell_id)); } SpellAreaForAreaMapBounds SpellMgr::GetSpellAreaForAreaMapBounds(uint32 area_id) const { return SpellAreaForAreaMapBounds(mSpellAreaForAreaMap.lower_bound(area_id), mSpellAreaForAreaMap.upper_bound(area_id)); } bool SpellArea::IsFitToRequirements(Player const* player, uint32 newZone, uint32 newArea) const { if (gender != GENDER_NONE) // not in expected gender if (!player || gender != player->getGender()) return false; if (raceMask) // not in expected race if (!player || !(raceMask & player->getRaceMask())) return false; if (areaId) // not in expected zone if (newZone != areaId && newArea != areaId) return false; if (questStart) // not in expected required quest state if (!player || ((!questStartCanActive || !player->IsActiveQuest(questStart)) && !player->GetQuestRewardStatus(questStart))) return false; if (questEnd) // not in expected forbidden quest state if (!player || player->GetQuestRewardStatus(questEnd)) return false; if (auraSpell) // not have expected aura if (!player || (auraSpell > 0 && !player->HasAura(auraSpell)) || (auraSpell < 0 && player->HasAura(-auraSpell))) return false; // Extra conditions -- leaving the possibility add extra conditions... switch (spellId) { case 58600: // No fly Zone - Dalaran { if (!player) return false; AreaTableEntry const* pArea = GetAreaEntryByAreaID(player->GetAreaId()); if (!(pArea && pArea->flags & AREA_FLAG_NO_FLY_ZONE)) return false; if (!player->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !player->HasAuraType(SPELL_AURA_FLY)) return false; break; } case 68719: // Oil Refinery - Isle of Conquest. case 68720: // Quarry - Isle of Conquest. { if (player->GetBattlegroundTypeId() != BATTLEGROUND_IC || !player->GetBattleground()) return false; uint8 nodeType = spellId == 68719 ? NODE_TYPE_REFINERY : NODE_TYPE_QUARRY; uint8 nodeState = player->GetTeamId() == TEAM_ALLIANCE ? NODE_STATE_CONTROLLED_A : NODE_STATE_CONTROLLED_H; BattlegroundIC* pIC = static_cast(player->GetBattleground()); if (pIC->GetNodeState(nodeType) == nodeState) return true; return false; } } return true; } void SpellMgr::LoadSpellInfos() { } void SpellMgr::LoadSpellRanks() { uint32 oldMSTime = getMSTime(); // cleanup core data before reload - remove reference to ChainNode from SpellInfo for (SpellChainMap::iterator itr = mSpellChains.begin(); itr != mSpellChains.end(); ++itr) { mSpellInfoMap[itr->first]->ChainEntry = NULL; } mSpellChains.clear(); QueryResult result = WorldDatabase.Query("SELECT first_spell_id, spell_id, rank from spell_ranks ORDER BY first_spell_id , rank"); if (!result) { sLog->outString(">> Loaded 0 spell rank records"); sLog->outString(); sLog->outErrorDb("`spell_ranks` table is empty!"); return; } uint32 rows = 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].GetUInt32(); // 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) { sLog->outErrorDb("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) { sLog->outErrorDb("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) { sLog->outErrorDb("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) { sLog->outErrorDb("Spell %u (rank %u) listed in `spell_ranks` for chain %u does not have 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 { ++rows; int32 addedSpell = itr->first; 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 = NULL; break; } else mSpellChains[addedSpell].next = GetSpellInfo(itr->first); } while (true); } while (!finished); sLog->outString(">> Loaded %u spell rank records in %u ms", rows, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellRequired() { uint32 oldMSTime = getMSTime(); mSpellsReqSpell.clear(); // need for reload case mSpellReq.clear(); // need for reload case QueryResult result = WorldDatabase.Query("SELECT spell_id, req_spell from spell_required"); if (!result) { sLog->outString(">> Loaded 0 spell required records"); sLog->outString(); sLog->outErrorDb("`spell_required` table is empty!"); return; } uint32 rows = 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) { sLog->outErrorDb("spell_id %u in `spell_required` table is not found in dbcs, skipped", spell_id); continue; } SpellInfo const* req_spell = GetSpellInfo(spell_req); if (!req_spell) { sLog->outErrorDb("req_spell %u in `spell_required` table is not found in dbcs, skipped", spell_req); continue; } if (GetFirstSpellInChain(spell_id) == GetFirstSpellInChain(spell_req)) { sLog->outErrorDb("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)) { sLog->outErrorDb("duplicated 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)); ++rows; } while (result->NextRow()); sLog->outString(">> Loaded %u spell required records in %u ms", rows, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } 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 (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_SKILL) { SpellLearnSkillNode dbc_node; 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; mSpellLearnSkills[spell] = dbc_node; ++dbc_count; break; } } } sLog->outString(">> Loaded %u Spell Learn Skills from DBC in %u ms", dbc_count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } 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) { sLog->outString(">> Loaded 0 spell learn spells"); sLog->outString(); sLog->outErrorDb("`spell_learn_spell` table 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)) { sLog->outErrorDb("Spell %u listed in `spell_learn_spell` does not exist", spell_id); continue; } if (!GetSpellInfo(node.spell)) { sLog->outErrorDb("Spell %u listed in `spell_learn_spell` learning not existed spell %u", spell_id, node.spell); continue; } if (GetTalentSpellCost(node.spell)) { sLog->outErrorDb("Spell %u listed in `spell_learn_spell` attempt learning talent spell %u, skipped", spell_id, node.spell); continue; } mSpellLearnSpells.insert(SpellLearnSpellMap::value_type(spell_id, node)); ++count; } while (result->NextRow()); // search auto-learned spells and add its to map also for use in unlearn spells/talents uint32 dbc_count = 0; for (uint32 spell = 0; spell < GetSpellInfoStoreSize(); ++spell) { SpellInfo const* entry = GetSpellInfo(spell); if (!entry) continue; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (entry->Effects[i].Effect == SPELL_EFFECT_LEARN_SPELL) { SpellLearnSpellNode dbc_node; dbc_node.spell = entry->Effects[i].TriggerSpell; dbc_node.active = true; // all dbc based learned spells is active (show in spell book or hide by client itself) // ignore learning not existed spells (broken/outdated/or generic learnig spell 483 if (!GetSpellInfo(dbc_node.spell)) continue; // talent or passive spells or skill-step spells auto-casted and not need dependent learning, // pet teaching spells must not be dependent learning (casted) // other required explicit dependent learning dbc_node.autoLearned = entry->Effects[i].TargetA.GetTarget() == TARGET_UNIT_PET || GetTalentSpellCost(spell) > 0 || entry->IsPassive() || entry->HasEffect(SPELL_EFFECT_SKILL_STEP); SpellLearnSpellMapBounds db_node_bounds = GetSpellLearnSpellMapBounds(spell); bool found = false; for (SpellLearnSpellMap::const_iterator itr = db_node_bounds.first; itr != db_node_bounds.second; ++itr) { if (itr->second.spell == dbc_node.spell) { sLog->outErrorDb("Spell %u auto-learn spell %u in spell.dbc then the record in `spell_learn_spell` is redundant, please fix 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; } } } } sLog->outString(">> Loaded %u spell learn spells + %u found in DBC in %u ms", count, dbc_count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellTargetPositions() { uint32 oldMSTime = getMSTime(); mSpellTargetPositions.clear(); // need for reload case // 0 1 2 3 4 5 QueryResult result = WorldDatabase.Query("SELECT id, target_map, target_position_x, target_position_y, target_position_z, target_orientation FROM spell_target_position"); if (!result) { sLog->outString(">> Loaded 0 spell target coordinates. DB table `spell_target_position` is empty."); sLog->outString(); return; } uint32 count = 0; do { Field *fields = result->Fetch(); uint32 Spell_ID = fields[0].GetUInt32(); SpellTargetPosition st; st.target_mapId = fields[1].GetUInt32(); st.target_X = fields[2].GetFloat(); st.target_Y = fields[3].GetFloat(); st.target_Z = fields[4].GetFloat(); st.target_Orientation = fields[5].GetFloat(); MapEntry const* mapEntry = sMapStore.LookupEntry(st.target_mapId); if (!mapEntry) { sLog->outErrorDb("Spell (ID:%u) target map (ID: %u) does not exist in `Map.dbc`.", Spell_ID, st.target_mapId); continue; } if (st.target_X==0 && st.target_Y==0 && st.target_Z==0) { sLog->outErrorDb("Spell (ID:%u) target coordinates not provided.", Spell_ID); continue; } SpellInfo const* spellInfo = GetSpellInfo(Spell_ID); if (!spellInfo) { sLog->outErrorDb("Spell (ID:%u) listed in `spell_target_position` does not exist.", Spell_ID); continue; } bool found = false; for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i) { if (spellInfo->Effects[i].TargetA.GetTarget() == TARGET_DEST_DB || spellInfo->Effects[i].TargetB.GetTarget() == TARGET_DEST_DB) { // additional requirements if (spellInfo->Effects[i].Effect == SPELL_EFFECT_BIND && spellInfo->Effects[i].MiscValue) { uint32 area_id = sMapMgr->GetAreaId(st.target_mapId, st.target_X, st.target_Y, st.target_Z); if (area_id != uint32(spellInfo->Effects[i].MiscValue)) { sLog->outErrorDb("Spell (Id: %u) listed in `spell_target_position` expected point to zone %u bit point to zone %u.", Spell_ID, spellInfo->Effects[i].MiscValue, area_id); break; } } found = true; break; } } if (!found) { sLog->outErrorDb("Spell (Id: %u) listed in `spell_target_position` does not have target TARGET_DEST_DB (17).", Spell_ID); continue; } mSpellTargetPositions[Spell_ID] = st; ++count; } 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)) sLog->outDebug(LOG_FILTER_SPELLS_AURAS, "Spell (ID: %u) does not have record in `spell_target_position`", i); } }*/ sLog->outString(">> Loaded %u spell teleport coordinates in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellGroups() { uint32 oldMSTime = getMSTime(); mSpellSpellGroup.clear(); // need for reload case mSpellGroupSpell.clear(); uint32 count = 0; // 0 1 QueryResult result = WorldDatabase.Query("SELECT id, spell_id FROM spell_group"); if (!result) { sLog->outString(); sLog->outString(">> Loaded %u spell group definitions", count); return; } std::set groups; 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) { sLog->outErrorDb("SpellGroup id %u listed in `spell_groups` is in core range, but is not defined in core!", group_id); continue; } int32 spell_id = fields[1].GetInt32(); groups.insert(std::set::value_type(group_id)); mSpellGroupSpell.insert(SpellGroupSpellMap::value_type((SpellGroup)group_id, spell_id)); } while (result->NextRow()); for (SpellGroupSpellMap::iterator itr = mSpellGroupSpell.begin(); itr!= mSpellGroupSpell.end() ;) { if (itr->second < 0) { if (groups.find(abs(itr->second)) == groups.end()) { sLog->outErrorDb("SpellGroup id %u listed in `spell_groups` does not exist", abs(itr->second)); mSpellGroupSpell.erase(itr++); } else ++itr; } else { SpellInfo const* spellInfo = GetSpellInfo(itr->second); if (!spellInfo) { sLog->outErrorDb("Spell %u listed in `spell_group` does not exist", itr->second); mSpellGroupSpell.erase(itr++); } else if (spellInfo->GetRank() > 1) { sLog->outErrorDb("Spell %u listed in `spell_group` is not first rank of spell", itr->second); mSpellGroupSpell.erase(itr++); } else ++itr; } } for (std::set::iterator groupItr = groups.begin() ; groupItr != groups.end() ; ++groupItr) { std::set spells; GetSetOfSpellsInSpellGroup(SpellGroup(*groupItr), spells); for (std::set::iterator spellItr = spells.begin() ; spellItr != spells.end() ; ++spellItr) { ++count; mSpellSpellGroup.insert(SpellSpellGroupMap::value_type(*spellItr, SpellGroup(*groupItr))); } } sLog->outString(">> Loaded %u spell group definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellGroupStackRules() { uint32 oldMSTime = getMSTime(); mSpellGroupStack.clear(); // need for reload case uint32 count = 0; // 0 1 QueryResult result = WorldDatabase.Query("SELECT group_id, stack_rule FROM spell_group_stack_rules"); if (!result) { sLog->outString(">> Loaded 0 spell group stack rules"); sLog->outString(); return; } do { Field *fields = result->Fetch(); uint32 group_id = fields[0].GetUInt32(); uint8 stack_rule = fields[1].GetUInt32(); if (stack_rule >= SPELL_GROUP_STACK_RULE_MAX) { sLog->outErrorDb("SpellGroupStackRule %u listed in `spell_group_stack_rules` does not exist", stack_rule); continue; } SpellGroupSpellMapBounds spellGroup = GetSpellGroupSpellMapBounds((SpellGroup)group_id); if (spellGroup.first == spellGroup.second) { sLog->outErrorDb("SpellGroup id %u listed in `spell_group_stack_rules` does not exist", group_id); continue; } mSpellGroupStack[(SpellGroup)group_id] = (SpellGroupStackRule)stack_rule; ++count; } while (result->NextRow()); sLog->outString(">> Loaded %u spell group stack rules in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellProcEvents() { uint32 oldMSTime = getMSTime(); mSpellProcEventMap.clear(); // need for reload case uint32 count = 0; // 0 1 2 3 4 5 6 7 8 9 10 QueryResult result = WorldDatabase.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, procFlags, procEx, ppmRate, CustomChance, Cooldown FROM spell_proc_event"); if (!result) { sLog->outString(">> Loaded %u spell proc event conditions", count); sLog->outString(); return; } uint32 customProc = 0; do { Field *fields = result->Fetch(); uint32 entry = fields[0].GetUInt32(); SpellInfo const* spell = GetSpellInfo(entry); if (!spell) { sLog->outErrorDb("Spell %u listed in `spell_proc_event` does not exist", entry); continue; } SpellProcEventEntry spe; spe.schoolMask = fields[1].GetUInt32(); spe.spellFamilyName = fields[2].GetUInt32(); spe.spellFamilyMask[0] = fields[3].GetUInt32(); spe.spellFamilyMask[1] = fields[4].GetUInt32(); spe.spellFamilyMask[2] = fields[5].GetUInt32(); spe.procFlags = fields[6].GetUInt32(); spe.procEx = fields[7].GetUInt32(); spe.ppmRate = fields[8].GetFloat(); spe.customChance = fields[9].GetFloat(); spe.cooldown = fields[10].GetUInt32(); mSpellProcEventMap[entry] = spe; if (spell->ProcFlags == 0) { if (spe.procFlags == 0) { sLog->outErrorDb("Spell %u listed in `spell_proc_event` probally not triggered spell", entry); continue; } customProc++; } ++count; } while (result->NextRow()); if (customProc) sLog->outString(">> Loaded %u extra and %u custom spell proc event conditions in %u ms", count, customProc, GetMSTimeDiffToNow(oldMSTime)); else sLog->outString(">> Loaded %u extra spell proc event conditions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellProcs() { uint32 oldMSTime = getMSTime(); mSpellProcMap.clear(); // need for reload case uint32 count = 0; // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 QueryResult result = WorldDatabase.Query("SELECT spellId, schoolMask, spellFamilyName, spellFamilyMask0, spellFamilyMask1, spellFamilyMask2, typeMask, spellTypeMask, spellPhaseMask, hitMask, attributesMask, ratePerMinute, chance, cooldown, charges FROM spell_proc"); if (!result) { sLog->outString(">> Loaded %u spell proc conditions and data", count); sLog->outString(); return; } do { Field* fields = result->Fetch(); int32 spellId = fields[0].GetInt32(); bool allRanks = false; if (spellId <=0) { allRanks = true; spellId = -spellId; } SpellInfo const* spellEntry = GetSpellInfo(spellId); if (!spellEntry) { sLog->outErrorDb("Spell %u listed in `spell_proc` does not exist", spellId); continue; } if (allRanks) { if (GetFirstSpellInChain(spellId) != uint32(spellId)) { sLog->outErrorDb("Spell %u listed in `spell_proc` is not first rank of spell.", fields[0].GetInt32()); continue; } } SpellProcEntry baseProcEntry; baseProcEntry.schoolMask = fields[1].GetUInt32(); baseProcEntry.spellFamilyName = fields[2].GetUInt32(); baseProcEntry.spellFamilyMask[0] = fields[3].GetUInt32(); baseProcEntry.spellFamilyMask[1] = fields[4].GetUInt32(); baseProcEntry.spellFamilyMask[2] = fields[5].GetUInt32(); baseProcEntry.typeMask = fields[6].GetUInt32(); baseProcEntry.spellTypeMask = fields[7].GetUInt32(); baseProcEntry.spellPhaseMask = fields[8].GetUInt32(); baseProcEntry.hitMask = fields[9].GetUInt32(); baseProcEntry.attributesMask = fields[10].GetUInt32(); baseProcEntry.ratePerMinute = fields[11].GetFloat(); baseProcEntry.chance = fields[12].GetFloat(); float cooldown = fields[13].GetFloat(); baseProcEntry.cooldown = uint32(cooldown); baseProcEntry.charges = fields[14].GetUInt32(); while(true) { if (mSpellProcMap.find(spellId) != mSpellProcMap.end()) { sLog->outErrorDb("Spell %u listed in `spell_proc` has duplicate entry in the table", spellId); break; } SpellProcEntry procEntry = SpellProcEntry(baseProcEntry); // take defaults from dbcs if (!procEntry.typeMask) procEntry.typeMask = spellEntry->ProcFlags; if (!procEntry.charges) procEntry.charges = spellEntry->ProcCharges; if (!procEntry.chance && !procEntry.ratePerMinute) procEntry.chance = float(spellEntry->ProcChance); // validate data if (procEntry.schoolMask & ~SPELL_SCHOOL_MASK_ALL) sLog->outErrorDb("`spell_proc` table entry for spellId %u has wrong `schoolMask` set: %u", spellId, procEntry.schoolMask); if (procEntry.spellFamilyName && (procEntry.spellFamilyName < 3 || procEntry.spellFamilyName > 17 || procEntry.spellFamilyName == 14 || procEntry.spellFamilyName == 16)) sLog->outErrorDb("`spell_proc` table entry for spellId %u has wrong `spellFamilyName` set: %u", spellId, procEntry.spellFamilyName); if (procEntry.chance < 0) { sLog->outErrorDb("`spell_proc` table entry for spellId %u has negative value in `chance` field", spellId); procEntry.chance = 0; } if (procEntry.ratePerMinute < 0) { sLog->outErrorDb("`spell_proc` table entry for spellId %u has negative value in `ratePerMinute` field", spellId); procEntry.ratePerMinute = 0; } if (cooldown < 0) { sLog->outErrorDb("`spell_proc` table entry for spellId %u has negative value in `cooldown` field", spellId); procEntry.cooldown = 0; } if (procEntry.chance == 0 && procEntry.ratePerMinute == 0) sLog->outErrorDb("`spell_proc` table entry for spellId %u doesn't have `chance` and `ratePerMinute` values defined, proc will not be triggered", spellId); if (procEntry.charges > 99) { sLog->outErrorDb("`spell_proc` table entry for spellId %u has too big value in `charges` field", spellId); procEntry.charges = 99; } if (!procEntry.typeMask) sLog->outErrorDb("`spell_proc` table entry for spellId %u doesn't have `typeMask` value defined, proc will not be triggered", spellId); if (procEntry.spellTypeMask & ~PROC_SPELL_PHASE_MASK_ALL) sLog->outErrorDb("`spell_proc` table entry for spellId %u has wrong `spellTypeMask` set: %u", spellId, procEntry.spellTypeMask); if (procEntry.spellTypeMask && !(procEntry.typeMask & (SPELL_PROC_FLAG_MASK | PERIODIC_PROC_FLAG_MASK))) sLog->outErrorDb("`spell_proc` table entry for spellId %u has `spellTypeMask` value defined, but it won't be used for defined `typeMask` value", spellId); if (!procEntry.spellPhaseMask && procEntry.typeMask & REQ_SPELL_PHASE_PROC_FLAG_MASK) sLog->outErrorDb("`spell_proc` table entry for spellId %u doesn't have `spellPhaseMask` value defined, but it's required for defined `typeMask` value, proc will not be triggered", spellId); if (procEntry.spellPhaseMask & ~PROC_SPELL_PHASE_MASK_ALL) sLog->outErrorDb("`spell_proc` table entry for spellId %u has wrong `spellPhaseMask` set: %u", spellId, procEntry.spellPhaseMask); if (procEntry.spellPhaseMask && !(procEntry.typeMask & REQ_SPELL_PHASE_PROC_FLAG_MASK)) sLog->outErrorDb("`spell_proc` table entry for spellId %u has `spellPhaseMask` value defined, but it won't be used for defined `typeMask` value", spellId); if (procEntry.hitMask & ~PROC_HIT_MASK_ALL) sLog->outErrorDb("`spell_proc` table entry for spellId %u has wrong `hitMask` set: %u", spellId, procEntry.hitMask); if (procEntry.hitMask && !(procEntry.typeMask & TAKEN_HIT_PROC_FLAG_MASK || (procEntry.typeMask & DONE_HIT_PROC_FLAG_MASK && (!procEntry.spellPhaseMask || procEntry.spellPhaseMask & (PROC_SPELL_PHASE_HIT | PROC_SPELL_PHASE_FINISH))))) sLog->outErrorDb("`spell_proc` table entry for spellId %u has `hitMask` value defined, but it won't be used for defined `typeMask` and `spellPhaseMask` values", spellId); mSpellProcMap[spellId] = procEntry; if (allRanks) { spellId = GetNextSpellInChain(spellId); spellEntry = GetSpellInfo(spellId); } else break; } ++count; } while (result->NextRow()); sLog->outString(">> Loaded %u spell proc conditions and data in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellBonusess() { uint32 oldMSTime = getMSTime(); mSpellBonusMap.clear(); // need for reload case uint32 count = 0; // 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) { sLog->outString(">> Loaded %u spell bonus data", count); sLog->outString(); return; } do { Field *fields = result->Fetch(); uint32 entry = fields[0].GetUInt32(); SpellInfo const* spell = GetSpellInfo(entry); if (!spell) { sLog->outErrorDb("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()); sLog->outString(">> Loaded %u extra spell bonus data in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellThreats() { uint32 oldMSTime = getMSTime(); mSpellThreatMap.clear(); // need for reload case uint32 count = 0; // 0 1 2 3 QueryResult result = WorldDatabase.Query("SELECT entry, flatMod, pctMod, apPctMod FROM spell_threat"); if (!result) { sLog->outString(">> Loaded 0 aggro generating spells"); sLog->outString(); return; } do { Field *fields = result->Fetch(); uint32 entry = fields[0].GetUInt32(); if (!GetSpellInfo(entry)) { sLog->outErrorDb("Spell %u listed in `spell_threat` does not exist", entry); continue; } SpellThreatEntry ste; ste.flatMod = fields[1].GetInt16(); ste.pctMod = fields[2].GetFloat(); ste.apPctMod = fields[3].GetFloat(); mSpellThreatMap[entry] = ste; ++count; } while (result->NextRow()); sLog->outString(">> Loaded %u SpellThreatEntries in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSkillLineAbilityMap() { uint32 oldMSTime = getMSTime(); mSkillLineAbilityMap.clear(); uint32 count = 0; for (uint32 i = 0; i < sSkillLineAbilityStore.GetNumRows(); ++i) { SkillLineAbilityEntry const* SkillInfo = sSkillLineAbilityStore.LookupEntry(i); if (!SkillInfo) continue; mSkillLineAbilityMap.insert(SkillLineAbilityMap::value_type(SkillInfo->spellId, SkillInfo)); ++count; } sLog->outString(">> Loaded %u SkillLineAbility MultiMap Data in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } 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) { sLog->outString(">> Loaded 0 spell pet auras. DB table `spell_pet_auras` is empty."); sLog->outString(); 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) { sLog->outErrorDb("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)) { sLog->outError("Spell %u listed in `spell_pet_auras` does not have dummy aura or dummy effect", spell); continue; } SpellInfo const* spellInfo2 = GetSpellInfo(aura); if (!spellInfo2) { sLog->outErrorDb("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()); sLog->outString(">> Loaded %u spell pet auras in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } // Fill custom data about enchancments void SpellMgr::LoadEnchantCustomAttr() { uint32 oldMSTime = getMSTime(); uint32 size = sSpellItemEnchantmentStore.GetNumRows(); mEnchantCustomAttr.resize(size); uint32 count = 0; for (uint32 i = 0; i < size; ++i) mEnchantCustomAttr[i] = 0; for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i) { SpellInfo const* spellInfo = GetSpellInfo(i); if (!spellInfo) continue; // TODO: find a better check if (!(spellInfo->AttributesEx2 & SPELL_ATTR2_UNK13) || !(spellInfo->Attributes & 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; } } } sLog->outString(">> Loaded %u custom enchant attributes in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellEnchantProcData() { uint32 oldMSTime = getMSTime(); mSpellEnchantProcEventMap.clear(); // need for reload case uint32 count = 0; // 0 1 2 3 QueryResult result = WorldDatabase.Query("SELECT entry, customChance, PPMChance, procEx FROM spell_enchant_proc_data"); if (!result) { sLog->outString(">> Loaded %u spell enchant proc event conditions", count); sLog->outString(); return; } do { Field *fields = result->Fetch(); uint32 enchantId = fields[0].GetUInt32(); SpellItemEnchantmentEntry const* ench = sSpellItemEnchantmentStore.LookupEntry(enchantId); if (!ench) { sLog->outErrorDb("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()); sLog->outString(">> Loaded %u enchant proc data definitions in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } 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) { sLog->outString(">> Loaded 0 linked spells. DB table `spell_linked_spell` is empty."); sLog->outString(); return; } uint32 count = 0; do { Field *fields = result->Fetch(); int32 trigger = fields[0].GetInt32(); int32 effect = fields[1].GetInt32(); int32 type = fields[2].GetInt32(); SpellInfo const* spellInfo = GetSpellInfo(abs(trigger)); if (!spellInfo) { sLog->outErrorDb("Spell %u listed in `spell_linked_spell` does not exist", abs(trigger)); continue; } spellInfo = GetSpellInfo(abs(effect)); if (!spellInfo) { sLog->outErrorDb("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()); sLog->outString(">> Loaded %u linked spells in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadPetLevelupSpellMap() { uint32 oldMSTime = getMSTime(); mPetLevelupSpellMap.clear(); // need for reload case uint32 count = 0; uint32 family_count = 0; for (uint32 i = 0; i < sCreatureFamilyStore.GetNumRows(); ++i) { CreatureFamilyEntry const* creatureFamily = sCreatureFamilyStore.LookupEntry(i); if (!creatureFamily) // not exist continue; for (uint8 j = 0; j < 2; ++j) { if (!creatureFamily->skillLine[j]) continue; for (uint32 k = 0; k < sSkillLineAbilityStore.GetNumRows(); ++k) { SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(k); if (!skillLine) continue; //if (skillLine->skillId != creatureFamily->skillLine[0] && // (!creatureFamily->skillLine[1] || skillLine->skillId != creatureFamily->skillLine[1])) // continue; if (skillLine->skillId != creatureFamily->skillLine[j]) continue; if (skillLine->learnOnGetSkill != ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL) continue; SpellInfo const* spell = GetSpellInfo(skillLine->spellId); if (!spell) // not exist or triggered or talent continue; if (!spell->SpellLevel) continue; PetLevelupSpellSet& spellSet = mPetLevelupSpellMap[creatureFamily->ID]; if (spellSet.empty()) ++family_count; spellSet.insert(PetLevelupSpellSet::value_type(spell->SpellLevel, spell->Id)); ++count; } } } sLog->outString(">> Loaded %u pet levelup and default spells for %u families in %u ms", count, family_count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } 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) : NULL) { 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->spellId[j]; if (LoadPetDefaultSpells_helper(&itr->second, petDefSpells)) { mPetDefaultSpellsMap[petSpellsId] = petDefSpells; ++countData; } } sLog->outString(">> Loaded addition spells for %u pet spell data entries in %u ms", countData, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); sLog->outString("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; } } } } sLog->outString(">> Loaded %u summonable creature templates in %u ms", countCreature, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellAreas() { uint32 oldMSTime = getMSTime(); mSpellAreaMap.clear(); // need for reload case mSpellAreaForQuestMap.clear(); mSpellAreaForActiveQuestMap.clear(); mSpellAreaForQuestEndMap.clear(); mSpellAreaForAuraMap.clear(); // 0 1 2 3 4 5 6 7 8 QueryResult result = WorldDatabase.Query("SELECT spell, area, quest_start, quest_start_active, quest_end, aura_spell, racemask, gender, autocast FROM spell_area"); if (!result) { sLog->outString(">> Loaded 0 spell area requirements. DB table `spell_area` is empty."); sLog->outString(); 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.questStartCanActive = fields[3].GetBool(); spellArea.questEnd = fields[4].GetUInt32(); spellArea.auraSpell = fields[5].GetInt32(); spellArea.raceMask = fields[6].GetUInt32(); spellArea.gender = Gender(fields[7].GetUInt8()); spellArea.autocast = fields[8].GetBool(); if (SpellInfo const* spellInfo = GetSpellInfo(spell)) { if (spellArea.autocast) const_cast(spellInfo)->Attributes |= SPELL_ATTR0_CANT_CANCEL; } else { sLog->outErrorDb("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) { sLog->outErrorDb("Spell %u listed in `spell_area` already listed with similar requirements.", spell); continue; } } if (spellArea.areaId && !GetAreaEntryByAreaID(spellArea.areaId)) { sLog->outErrorDb("Spell %u listed in `spell_area` have wrong area (%u) requirement", spell, spellArea.areaId); continue; } if (spellArea.questStart && !sObjectMgr->GetQuestTemplate(spellArea.questStart)) { sLog->outErrorDb("Spell %u listed in `spell_area` have wrong start quest (%u) requirement", spell, spellArea.questStart); continue; } if (spellArea.questEnd) { if (!sObjectMgr->GetQuestTemplate(spellArea.questEnd)) { sLog->outErrorDb("Spell %u listed in `spell_area` have wrong end quest (%u) requirement", spell, spellArea.questEnd); continue; } if (spellArea.questEnd == spellArea.questStart && !spellArea.questStartCanActive) { sLog->outErrorDb("Spell %u listed in `spell_area` have quest (%u) requirement for start and end in same time", spell, spellArea.questEnd); continue; } } if (spellArea.auraSpell) { SpellInfo const* spellInfo = GetSpellInfo(abs(spellArea.auraSpell)); if (!spellInfo) { sLog->outErrorDb("Spell %u listed in `spell_area` have wrong aura spell (%u) requirement", spell, abs(spellArea.auraSpell)); continue; } if (uint32(abs(spellArea.auraSpell)) == spellArea.spellId) { sLog->outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement for itself", spell, abs(spellArea.auraSpell)); continue; } // not allow autocast chains by auraSpell field (but allow use as alternative if not present) if (spellArea.autocast && spellArea.auraSpell > 0) { bool chain = false; SpellAreaForAuraMapBounds saBound = GetSpellAreaForAuraMapBounds(spellArea.spellId); for (SpellAreaForAuraMap::const_iterator itr = saBound.first; itr != saBound.second; ++itr) { if (itr->second->autocast && itr->second->auraSpell > 0) { chain = true; break; } } if (chain) { sLog->outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement that itself autocast from aura", spell, spellArea.auraSpell); continue; } SpellAreaMapBounds saBound2 = GetSpellAreaMapBounds(spellArea.auraSpell); for (SpellAreaMap::const_iterator itr2 = saBound2.first; itr2 != saBound2.second; ++itr2) { if (itr2->second.autocast && itr2->second.auraSpell > 0) { chain = true; break; } } if (chain) { sLog->outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement that itself autocast from aura", spell, spellArea.auraSpell); continue; } } } if (spellArea.raceMask && (spellArea.raceMask & RACEMASK_ALL_PLAYABLE) == 0) { sLog->outErrorDb("Spell %u listed in `spell_area` have wrong race mask (%u) requirement", spell, spellArea.raceMask); continue; } if (spellArea.gender != GENDER_NONE && spellArea.gender != GENDER_FEMALE && spellArea.gender != GENDER_MALE) { sLog->outErrorDb("Spell %u listed in `spell_area` have wrong gender (%u) requirement", spell, spellArea.gender); continue; } SpellArea const* sa = &mSpellAreaMap.insert(SpellAreaMap::value_type(spell, spellArea))->second; // for search by current zone/subzone at zone/subzone change if (spellArea.areaId) mSpellAreaForAreaMap.insert(SpellAreaForAreaMap::value_type(spellArea.areaId, sa)); // for search at quest start/reward if (spellArea.questStart) { if (spellArea.questStartCanActive) mSpellAreaForActiveQuestMap.insert(SpellAreaForQuestMap::value_type(spellArea.questStart, sa)); else 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)); ++count; } while (result->NextRow()); sLog->outString(">> Loaded %u spell area requirements in %u ms", count, GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadSpellInfoStore() { uint32 oldMSTime = getMSTime(); UnloadSpellInfoStore(); mSpellInfoMap.resize(sSpellStore.GetNumRows(), NULL); for (uint32 i = 0; i < sSpellStore.GetNumRows(); ++i) { if (SpellEntry const* spellEntry = sSpellStore.LookupEntry(i)) mSpellInfoMap[i] = new SpellInfo(spellEntry); } sLog->outString(">> Loaded spell custom attributes in %u ms", GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::UnloadSpellInfoStore() { for (uint32 i = 0; i < mSpellInfoMap.size(); ++i) { if (mSpellInfoMap[i]) delete mSpellInfoMap[i]; } mSpellInfoMap.clear(); } void SpellMgr::LoadSpellCustomAttr() { uint32 oldMSTime = getMSTime(); SpellInfo* spellInfo = NULL; 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_MOD_POSSESS: case SPELL_AURA_MOD_CONFUSE: case SPELL_AURA_MOD_CHARM: case SPELL_AURA_AOE_CHARM: case SPELL_AURA_MOD_FEAR: case SPELL_AURA_MOD_STUN: spellInfo->AttributesCu |= SPELL_ATTR0_CU_AURA_CC; break; case SPELL_AURA_PERIODIC_HEAL: case SPELL_AURA_PERIODIC_DAMAGE: case SPELL_AURA_PERIODIC_DAMAGE_PERCENT: case SPELL_AURA_PERIODIC_LEECH: case SPELL_AURA_PERIODIC_MANA_LEECH: case SPELL_AURA_PERIODIC_HEALTH_FUNNEL: case SPELL_AURA_PERIODIC_ENERGIZE: case SPELL_AURA_OBS_MOD_HEALTH: case SPELL_AURA_OBS_MOD_POWER: case SPELL_AURA_POWER_BURN: spellInfo->AttributesCu |= SPELL_ATTR0_CU_NO_INITIAL_THREAT; break; } switch (spellInfo->Effects[j].Effect) { case SPELL_EFFECT_SCHOOL_DAMAGE: case SPELL_EFFECT_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); for (uint8 s = 0; s < MAX_ITEM_ENCHANTMENT_EFFECTS; ++s) { if (enchant->type[s] != ITEM_ENCHANTMENT_TYPE_COMBAT_SPELL) continue; SpellInfo* procInfo = (SpellInfo*)GetSpellInfo(enchant->spellid[s]); if (!procInfo) continue; // if proced directly from enchantment, not via proc aura // NOTE: Enchant Weapon - Blade Ward also has proc aura spell and is proced directly // however its not expected to stack so this check is good if (procInfo->HasAura(SPELL_AURA_PROC_TRIGGER_SPELL)) continue; procInfo->AttributesCu |= SPELL_ATTR0_CU_ENCHANT_PROC; } } break; } } } 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; if (spellInfo->SpellVisual[0] == 3879) spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_BACK; switch (spellInfo->Id) { case 1776: // Gouge case 1777: case 8629: case 11285: case 11286: case 12540: case 13579: case 24698: case 28456: case 29425: case 34940: case 36862: case 38764: case 38863: case 52743: // Head Smack spellInfo->AttributesCu |= SPELL_ATTR0_CU_REQ_TARGET_FACING_CASTER; break; case 53: // Backstab case 2589: case 2590: case 2591: case 8721: case 11279: case 11280: case 11281: case 25300: case 26863: case 48656: case 48657: case 703: // Garrote case 8631: case 8632: case 8633: case 11289: case 11290: case 26839: case 26884: case 48675: case 48676: case 5221: // Shred case 6800: case 8992: case 9829: case 9830: case 27001: case 27002: case 48571: case 48572: case 8676: // Ambush case 8724: case 8725: case 11267: case 11268: case 11269: case 27441: case 48689: case 48690: case 48691: case 21987: // Lash of Pain case 23959: // Test Stab R50 case 24825: // Test Backstab case 58563: // Assassinate Restless Lookout spellInfo->AttributesCu |= SPELL_ATTR0_CU_REQ_CASTER_BEHIND_TARGET; break; case 26029: // Dark Glare case 37433: // Spout case 43140: // Flame Breath case 43215: // Flame Breath case 70461: // Coldflame Trap spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_LINE; break; case 24340: // Meteor case 26558: // Meteor case 28884: // Meteor case 36837: // Meteor case 38903: // Meteor case 41276: // Meteor case 57467: // Meteor case 26789: // Shard of the Fallen Star case 31436: // Malevolent Cleave case 35181: // Dive Bomb case 40810: // Saber Lash case 43267: // Saber Lash case 43268: // Saber Lash case 42384: // Brutal Swipe case 45150: // Meteor Slash case 64688: // Sonic Screech case 72373: // Shared Suffering case 71904: // Chaos Bane case 70492: // Ooze Eruption case 72505: // Ooze Eruption case 72624: // Ooze Eruption case 72625: // Ooze Eruption // ONLY SPELLS WITH SPELLFAMILY_GENERIC and EFFECT_SCHOOL_DAMAGE spellInfo->AttributesCu |= SPELL_ATTR0_CU_SHARE_DAMAGE; break; case 18500: // Wing Buffet case 33086: // Wild Bite case 49749: // Piercing Blow case 52890: // Penetrating Strike case 53454: // Impale case 59446: // Impale case 62383: // Shatter case 64777: // Machine Gun case 65239: // Machine Gun case 65919: // Impale case 67858: // Impale case 67859: // Impale case 67860: // Impale case 69293: // Wing Buffet case 74439: // Machine Gun case 63278: // Mark of the Faceless (General Vezax) spellInfo->AttributesCu |= SPELL_ATTR0_CU_IGNORE_ARMOR; break; case 64422: // Sonic Screech (Auriaya) spellInfo->AttributesCu |= SPELL_ATTR0_CU_SHARE_DAMAGE; spellInfo->AttributesCu |= SPELL_ATTR0_CU_IGNORE_ARMOR; break; default: break; } switch (spellInfo->SpellFamilyName) { case SPELLFAMILY_WARRIOR: // Shout if (spellInfo->SpellFamilyFlags[0] & 0x20000 || spellInfo->SpellFamilyFlags[1] & 0x20) spellInfo->AttributesCu |= SPELL_ATTR0_CU_AURA_CC; break; case SPELLFAMILY_DRUID: // Roar if (spellInfo->SpellFamilyFlags[0] & 0x8) spellInfo->AttributesCu |= SPELL_ATTR0_CU_AURA_CC; break; default: break; } } CreatureAI::FillAISpellInfo(); sLog->outString(">> Loaded spell custom attributes in %u ms", GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); } void SpellMgr::LoadDbcDataCorrections() { uint32 oldMSTime = getMSTime(); SpellEntry* spellInfo = NULL; for (uint32 i = 0; i < sSpellStore.GetNumRows(); ++i) { spellInfo = (SpellEntry*)sSpellStore.LookupEntry(i); if (!spellInfo) continue; for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j) { switch (spellInfo->Effect[j]) { case SPELL_EFFECT_CHARGE: case SPELL_EFFECT_CHARGE_DEST: case SPELL_EFFECT_JUMP: case SPELL_EFFECT_JUMP_DEST: case SPELL_EFFECT_LEAP_BACK: if (!spellInfo->speed && !spellInfo->SpellFamilyName) spellInfo->speed = SPEED_CHARGE; break; case SPELL_EFFECT_TRIGGER_SPELL: if (SpellImplicitTargetInfo::IsPosition(spellInfo->EffectImplicitTargetA[j]) || spellInfo->Targets & (TARGET_FLAG_SOURCE_LOCATION | TARGET_FLAG_DEST_LOCATION)) spellInfo->Effect[j] = SPELL_EFFECT_TRIGGER_MISSILE; break; } } if (spellInfo->activeIconID == 2158) // flight spellInfo->Attributes |= SPELL_ATTR0_PASSIVE; switch (spellInfo->Id) { case 42835: // Spout spellInfo->Effect[0] = 0; // remove damage effect, only anim is needed break; case 30657: // Quake spellInfo->EffectTriggerSpell[0] = 30571; break; case 30541: // Blaze (needs conditions entry) spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_TARGET_ENEMY; spellInfo->EffectImplicitTargetB[0] = 0; break; case 31447: // Mark of Kaz'rogal (needs target selection script) case 31298: // Sleep (needs target selection script) spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER; spellInfo->EffectImplicitTargetB[0] = 0; break; case 31344: // Howl of Azgalor spellInfo->EffectRadiusIndex[0] = 12; // 100yards instead of 50000?! break; case 42818: // Headless Horseman - Wisp Flight Port case 42821: // Headless Horseman - Wisp Flight Missile spellInfo->rangeIndex = 6; // 100 yards break; case 36350: //They Must Burn Bomb Aura (self) spellInfo->EffectTriggerSpell[0] = 36325; // They Must Burn Bomb Drop (DND) break; case 49838: // Stop Time spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_INITIAL_AGGRO; break; case 61407: // Energize Cores case 62136: // Energize Cores case 54069: // Energize Cores case 56251: // Energize Cores spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_SRC_AREA_ENTRY; break; case 50785: // Energize Cores case 59372: // Energize Cores spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_SRC_AREA_ENEMY; break; case 3286: // Bind spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_TARGET_ENEMY; spellInfo->EffectImplicitTargetA[1] = TARGET_UNIT_TARGET_ENEMY; break; case 8494: // Mana Shield (rank 2) // because of bug in dbc spellInfo->procChance = 0; break; case 32182: // Heroism spellInfo->excludeCasterAuraSpell = 57723; // Exhaustion break; case 2825: // Bloodlust spellInfo->excludeCasterAuraSpell = 57724; // Sated break; case 20335: // Heart of the Crusader case 20336: case 20337: case 63320: // Glyph of Life Tap // Entries were not updated after spell effect change, we have to do that manually :/ spellInfo->AttributesEx3 |= SPELL_ATTR3_CAN_PROC_WITH_TRIGGERED; break; case 16007: // Draco-Incarcinatrix 900 // was 46, but effect is aura effect spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_NEARBY_ENTRY; spellInfo->EffectImplicitTargetB[0] = TARGET_DEST_NEARBY_ENTRY; break; case 19465: // Improved Stings, only rank 2 of this spell has target for effect 2 = TARGET_DST_DB spellInfo->EffectImplicitTargetA[2] = TARGET_UNIT_CASTER; break; case 59725: // Improved Spell Reflection - aoe aura // Target entry seems to be wrong for this spell :/ spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER_AREA_PARTY; spellInfo->EffectRadiusIndex[0] = 45; break; case 44978: case 45001: case 45002: // Wild Magic case 45004: case 45006: case 45010: // Wild Magic case 31347: // Doom case 41635: // Prayer of Mending case 44869: // Spectral Blast case 45027: // Revitalize case 45976: // Muru Portal Channel case 39365: // Thundering Storm case 41071: // Raise Dead (HACK) case 52124: // Sky Darkener Assault case 42442: // Vengeance Landing Cannonfire case 45863: // Cosmetic - Incinerate to Random Target case 25425: // Shoot case 45761: // Shoot case 42611: // Shoot case 62374: // Pursued case 61588: // Blazing Harpoon spellInfo->MaxAffectedTargets = 1; break; case 52479: // Gift of the Harvester spellInfo->MaxAffectedTargets = 1; // a trap always has dst = src? spellInfo->EffectImplicitTargetA[0] = TARGET_DEST_CASTER; spellInfo->EffectImplicitTargetA[1] = TARGET_DEST_CASTER; break; case 41376: // Spite case 39992: // Needle Spine case 29576: // Multi-Shot case 40816: // Saber Lash case 37790: // Spread Shot case 46771: // Flame Sear case 45248: // Shadow Blades case 41303: // Soul Drain case 54172: // Divine Storm (heal) case 29213: // Curse of the Plaguebringer - Noth case 28542: // Life Drain - Sapphiron case 66588: // Flaming Spear case 54171: // Divine Storm spellInfo->MaxAffectedTargets = 3; break; case 38310: // Multi-Shot case 53385: // Divine Storm (Damage) spellInfo->MaxAffectedTargets = 4; break; case 42005: // Bloodboil case 38296: // Spitfire Totem case 37676: // Insidious Whisper case 46008: // Negative Energy case 45641: // Fire Bloom case 55665: // Life Drain - Sapphiron (H) case 28796: // Poison Bolt Volly - Faerlina spellInfo->MaxAffectedTargets = 5; break; case 40827: // Sinful Beam case 40859: // Sinister Beam case 40860: // Vile Beam case 40861: // Wicked Beam case 54835: // Curse of the Plaguebringer - Noth (H) case 54098: // Poison Bolt Volly - Faerlina (H) spellInfo->MaxAffectedTargets = 10; break; case 50312: // Unholy Frenzy spellInfo->MaxAffectedTargets = 15; break; case 38794: case 33711: //Murmur's Touch spellInfo->MaxAffectedTargets = 1; spellInfo->EffectTriggerSpell[0] = 33760; break; case 17941: // Shadow Trance case 22008: // Netherwind Focus case 31834: // Light's Grace case 34754: // Clearcasting case 34936: // Backlash case 48108: // Hot Streak case 51124: // Killing Machine case 54741: // Firestarter case 57761: // Fireball! case 39805: // Lightning Overload case 64823: // Item - Druid T8 Balance 4P Bonus case 34477: // Misdirection case 44401: // Missile Barrage spellInfo->procCharges = 1; break; case 44544: // Fingers of Frost spellInfo->EffectSpellClassMask[0] = flag96(685904631, 1151048, 0); break; case 74396: // Fingers of Frost visual buff spellInfo->procCharges = 2; spellInfo->StackAmount = 0; break; case 28200: // Ascendance (Talisman of Ascendance trinket) spellInfo->procCharges = 6; break; case 47201: // Everlasting Affliction case 47202: case 47203: case 47204: case 47205: // add corruption to affected spells spellInfo->EffectSpellClassMask[1][0] |= 2; break; case 49305: // Teleport to Boss 1 DND case 64981: // Summon Random Vanquished Tentacle spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_CASTER; break; case 51852: // The Eye of Acherus (no spawn in phase 2 in db) spellInfo->EffectMiscValue[0] |= 1; break; case 51904: // Summon Ghouls On Scarlet Crusade (core does not know the triggered spell is summon spell) spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER; break; case 29809: // Desecration Arm - 36 instead of 37 - typo? :/ spellInfo->EffectRadiusIndex[0] = 37; break; // Master Shapeshifter: missing stance data for forms other than bear - bear version has correct data // To prevent aura staying on target after talent unlearned case 48420: spellInfo->Stances = 1 << (FORM_CAT - 1); break; case 48421: spellInfo->Stances = 1 << (FORM_MOONKIN - 1); break; case 48422: spellInfo->Stances = 1 << (FORM_TREE - 1); break; case 47569: // Improved Shadowform (Rank 1) // with this spell atrribute aura can be stacked several times spellInfo->Attributes &= ~SPELL_ATTR0_NOT_SHAPESHIFT; break; case 30421: // Nether Portal - Perseverence spellInfo->EffectBasePoints[2] += 30000; break; case 16834: // Natural shapeshifter case 16835: spellInfo->DurationIndex = 21; break; case 51735: // Ebon Plague case 51734: case 51726: spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS; spellInfo->SpellFamilyFlags[2] = 0x10; break; case 41913: // Parasitic Shadowfiend Passive spellInfo->EffectApplyAuraName[0] = 4; // proc debuff, and summon infinite fiends break; case 27892: // To Anchor 1 case 27928: // To Anchor 1 case 27935: // To Anchor 1 case 27915: // Anchor to Skulls case 27931: // Anchor to Skulls case 27937: // Anchor to Skulls spellInfo->rangeIndex = 13; break; // 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 case 29214: // Wrath of the Plaguebringer case 54836: // Wrath of the Plaguebringer spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_SRC_AREA_ALLY; spellInfo->EffectImplicitTargetB[1] = TARGET_UNIT_SRC_AREA_ALLY; break; case 31687: // Summon Water Elemental // 322-330 switch - effect changed to dummy, target entry not changed in client:( spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER; break; case 57994: // Wind Shear - improper data for EFFECT_1 in 3.3.5 DBC, but is correct in 4.x spellInfo->Effect[EFFECT_1] = SPELL_EFFECT_MODIFY_THREAT_PERCENT; spellInfo->EffectBasePoints[EFFECT_1] = -6; // -5% break; case 63675: // Improved Devouring Plague spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS; break; case 8145: // Tremor Totem (instant pulse) case 6474: // Earthbind Totem (instant pulse) spellInfo->AttributesEx5 |= SPELL_ATTR5_START_PERIODIC_AT_APPLY; break; case 53241: // Marked for Death (Rank 1) case 53243: // Marked for Death (Rank 2) case 53244: // Marked for Death (Rank 3) case 53245: // Marked for Death (Rank 4) case 53246: // Marked for Death (Rank 5) spellInfo->EffectSpellClassMask[0] = flag96(423937, 276955137, 2049); break; case 70728: // Exploit Weakness case 70840: // Devious Minds spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER; spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_PET; break; case 70893: // Culling The Herd spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER; spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_MASTER; break; case 54800: // Sigil of the Frozen Conscience - 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->EffectSpellClassMask[0] = flag96(0x00000040, 0x00000000, 0x00000000); break; case 19970: // Entangling Roots (Rank 6) -- Nature's Grasp Proc case 19971: // Entangling Roots (Rank 5) -- Nature's Grasp Proc case 19972: // Entangling Roots (Rank 4) -- Nature's Grasp Proc case 19973: // Entangling Roots (Rank 3) -- Nature's Grasp Proc case 19974: // Entangling Roots (Rank 2) -- Nature's Grasp Proc case 19975: // Entangling Roots (Rank 1) -- Nature's Grasp Proc case 27010: // Entangling Roots (Rank 7) -- Nature's Grasp Proc case 53313: // Entangling Roots (Rank 8) -- Nature's Grasp Proc spellInfo->CastingTimeIndex = 1; break; case 61719: // Easter Lay Noblegarden Egg Aura - Interrupt flags copied from aura which this aura is linked with spellInfo->AuraInterruptFlags = AURA_INTERRUPT_FLAG_HITBYSPELL | AURA_INTERRUPT_FLAG_TAKE_DAMAGE; break; // ULDUAR SPELLS // case 63342: // Focused Eyebeam Summon Trigger (Kologarn) spellInfo->MaxAffectedTargets = 1; break; case 62716: // Growth of Nature (Freya) case 65584: // Growth of Nature (Freya) case 64381: // Strength of the Pack (Auriaya) spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS; break; case 63018: // Searing Light (XT-002) case 65121: // Searing Light (25m) (XT-002) case 63024: // Gravity Bomb (XT-002) case 64234: // Gravity Bomb (25m) (XT-002) spellInfo->MaxAffectedTargets = 1; break; case 62834: // Boom (XT-002) // 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->Effect[EFFECT_1] = 0; break; case 64386: // Terrifying Screech (Auriaya) case 64389: // Sentinel Blast (Auriaya) case 64678: // Sentinel Blast (Auriaya) spellInfo->DurationIndex = 28; // 5 seconds, wrong DBC data? break; case 64321: // Potent Pheromones (Freya) // 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; break; case 62584: // Lifebinder's Gift case 64185: // Lifebinder's Gift spellInfo->EffectImplicitTargetB[1] = TARGET_UNIT_NEARBY_ENTRY; spellInfo->EffectImplicitTargetB[2] = TARGET_UNIT_NEARBY_ENTRY; break; // ENDOF ULDUAR SPELLS // // TRIAL OF THE CRUSADER SPELLS // case 66258: // Infernal Eruption (10N) case 67901: // Infernal Eruption (25N) // increase duration from 15 to 18 seconds because caster is already // unsummoned when spell missile hits the ground so nothing happen in result spellInfo->DurationIndex = 85; break; // ENDOF TRIAL OF THE CRUSADER SPELLS // // ICECROWN CITADEL SPELLS // // 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) case 70781: // Light's Hammer Teleport case 70856: // Oratory of the Damned Teleport case 70857: // Rampart of Skulls Teleport case 70858: // Deathbringer's Rise Teleport case 70859: // Upper Spire Teleport case 70860: // Frozen Throne Teleport case 70861: // Sindragosa's Lair Teleport spellInfo->EffectImplicitTargetA[0] = TARGET_DEST_DB; break; case 69055: // Saber Lash (Lord Marrowgar) case 70814: // Saber Lash (Lord Marrowgar) spellInfo->EffectRadiusIndex[0] = 8; // 5yd break; case 69075: // Bone Storm (Lord Marrowgar) case 70834: // Bone Storm (Lord Marrowgar) case 70835: // Bone Storm (Lord Marrowgar) case 70836: // Bone Storm (Lord Marrowgar) case 72864: // Death Plague (Rotting Frost Giant) case 72378: // Blood Nova (Deathbringer Saurfang) case 73058: // Blood Nova (Deathbringer Saurfang) case 71160: // Plague Stench (Stinky) case 71161: // Plague Stench (Stinky) case 71123: // Decimate (Stinky & Precious) spellInfo->EffectRadiusIndex[0] = 12; // 100yd break; case 72723: // Resistant Skin (Deathbringer Saurfang adds) // this spell initially granted Shadow damage immunity, however it was removed but the data was left in client spellInfo->Effect[2] = 0; break; case 70460: // Coldflame Jets (Traps after Saurfang) spellInfo->DurationIndex = 1; // 10 seconds break; case 71413: // Green Ooze Summon (Professor Putricide) case 71414: // Orange Ooze Summon (Professor Putricide) spellInfo->EffectImplicitTargetA[0] = TARGET_DEST_DEST; break; case 71159: // Awaken Plagued Zombies spellInfo->DurationIndex = 21; break; // THIS IS HERE BECAUSE COOLDOWN ON CREATURE PROCS IS NOT IMPLEMENTED case 71604: // Mutated Strength (Professor Putricide) case 72673: // Mutated Strength (Professor Putricide) case 72674: // Mutated Strength (Professor Putricide) case 72675: // Mutated Strength (Professor Putricide) spellInfo->Effect[1] = 0; break; case 72454: // Mutated Plague (Professor Putricide) case 72464: // Mutated Plague (Professor Putricide) case 72506: // Mutated Plague (Professor Putricide) case 72507: // Mutated Plague (Professor Putricide) spellInfo->EffectRadiusIndex[0] = 28; // 50000yd break; case 70911: // Unbound Plague (Professor Putricide) case 72854: // Unbound Plague (Professor Putricide) case 72855: // Unbound Plague (Professor Putricide) case 72856: // Unbound Plague (Professor Putricide) spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_TARGET_ENEMY; break; case 71518: // Unholy Infusion Quest Credit (Professor Putricide) case 72934: // Blood Infusion Quest Credit (Blood-Queen Lana'thel) case 72289: // Frost Infusion Quest Credit (Sindragosa) spellInfo->EffectRadiusIndex[0] = 28; // another missing radius break; case 71708: // Empowered Flare (Blood Prince Council) case 72785: // Empowered Flare (Blood Prince Council) case 72786: // Empowered Flare (Blood Prince Council) case 72787: // Empowered Flare (Blood Prince Council) spellInfo->AttributesEx3 |= SPELL_ATTR3_NO_DONE_BONUS; break; case 71266: // Swarming Shadows case 72890: // Swarming Shadows spellInfo->AreaGroupId = 0; // originally, these require area 4522, which is... outside of Icecrown Citadel break; case 70602: // Corruption spellInfo->AttributesEx3 |= SPELL_ATTR3_STACK_FOR_DIFF_CASTERS; break; case 70715: // Column of Frost (visual marker) spellInfo->DurationIndex = 32; // 6 seconds (missing) break; case 71085: // Mana Void (periodic aura) spellInfo->DurationIndex = 9; // 30 seconds (missing) break; case 70936: // Summon Suppressor spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_TARGET_ANY; spellInfo->EffectImplicitTargetB[0] = 0; break; case 72706: // Achievement Check (Valithria Dreamwalker) case 71357: // Order Whelp spellInfo->EffectRadiusIndex[0] = 22; // 200yd break; case 70598: // Sindragosa's Fury spellInfo->EffectImplicitTargetA[0] = TARGET_DEST_CASTER; break; case 69846: // Frost Bomb spellInfo->speed = 10; spellInfo->EffectImplicitTargetA[0] = TARGET_DEST_TARGET_ANY; spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_TARGET_ANY; spellInfo->Effect[1] = 0; break; default: break; } switch (spellInfo->SpellFamilyName) { case SPELLFAMILY_DRUID: // Starfall Target Selection if (spellInfo->SpellFamilyFlags[2] & 0x100) spellInfo->MaxAffectedTargets = 2; break; case SPELLFAMILY_PALADIN: // Seals of the Pure should affect Seal of Righteousness if (spellInfo->SpellIconID == 25 && spellInfo->Attributes & SPELL_ATTR0_PASSIVE) spellInfo->EffectSpellClassMask[0][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; } } SummonPropertiesEntry* properties = const_cast(sSummonPropertiesStore.LookupEntry(121)); properties->Type = SUMMON_TYPE_TOTEM; properties = const_cast(sSummonPropertiesStore.LookupEntry(647)); // 52893 properties->Type = SUMMON_TYPE_TOTEM; sLog->outString(">> Loading spell dbc data corrections in %u ms", GetMSTimeDiffToNow(oldMSTime)); sLog->outString(); }