server/src/game/SpellMgr.cpp

4773 lines
186 KiB
C++

/*
* This file is part of the CMaNGOS Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "SpellMgr.h"
#include "ObjectMgr.h"
#include "SpellAuraDefines.h"
#include "ProgressBar.h"
#include "DBCStores.h"
#include "SQLStorages.h"
#include "World.h"
#include "Chat.h"
#include "Spell.h"
#include "BattleGround/BattleGroundMgr.h"
#include "MapManager.h"
#include "Unit.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;
}
SpellMgr::SpellMgr()
{
}
SpellMgr::~SpellMgr()
{
}
SpellMgr& SpellMgr::Instance()
{
static SpellMgr spellMgr;
return spellMgr;
}
int32 GetSpellDuration(SpellEntry const* spellInfo)
{
if (!spellInfo)
return 0;
SpellDurationEntry const* du = sSpellDurationStore.LookupEntry(spellInfo->DurationIndex);
if (!du)
return 0;
return (du->Duration[0] == -1) ? -1 : abs(du->Duration[0]);
}
int32 GetSpellMaxDuration(SpellEntry const* spellInfo)
{
if (!spellInfo)
return 0;
SpellDurationEntry const* du = sSpellDurationStore.LookupEntry(spellInfo->DurationIndex);
if (!du)
return 0;
return (du->Duration[2] == -1) ? -1 : abs(du->Duration[2]);
}
int32 CalculateSpellDuration(SpellEntry const* spellInfo, Unit const* caster)
{
int32 duration = GetSpellDuration(spellInfo);
if (duration != -1 && caster)
{
int32 maxduration = GetSpellMaxDuration(spellInfo);
if (duration != maxduration && caster->GetTypeId() == TYPEID_PLAYER)
duration += int32((maxduration - duration) * ((Player*)caster)->GetComboPoints() / 5);
if (Player* modOwner = caster->GetSpellModOwner())
{
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_DURATION, duration);
if (duration < 0)
duration = 0;
}
}
return duration;
}
uint32 GetSpellCastTime(SpellEntry const* spellInfo, Spell const* spell)
{
if (spell)
{
// some triggered spells have data only usable for client
if (spell->IsTriggeredSpellWithRedundentData())
return 0;
// spell targeted to non-trading trade slot item instant at trade success apply
if (spell->GetCaster()->GetTypeId() == TYPEID_PLAYER)
if (TradeData* my_trade = ((Player*)(spell->GetCaster()))->GetTradeData())
if (Item* nonTrade = my_trade->GetTraderData()->GetItem(TRADE_SLOT_NONTRADED))
if (nonTrade == spell->m_targets.getItemTarget())
return 0;
}
int32 castTime = 0;
SpellScalingEntry const* spellScalingEntry = spellInfo->GetSpellScaling();
if (spell && spellScalingEntry && (spell->GetCaster()->GetTypeId() == TYPEID_PLAYER || spell->GetCaster()->GetObjectGuid().IsPet()))
{
uint32 level = spell->GetCaster()->getLevel();
if (level == 1)
castTime = int32(spellScalingEntry->castTimeMin);
else if (level < spellScalingEntry->castScalingMaxLevel)
castTime = int32(spellScalingEntry->castTimeMin + float(level - 1) *
(spellScalingEntry->castTimeMax - spellScalingEntry->castTimeMin) / (spellScalingEntry->castScalingMaxLevel - 1));
else
castTime = int32(spellScalingEntry->castTimeMax);
}
else if (SpellCastTimesEntry const* spellCastTimeEntry = sSpellCastTimesStore.LookupEntry(spellInfo->CastingTimeIndex))
{
if (spell)
{
uint32 level = spell->GetCaster()->getLevel();
if (SpellLevelsEntry const* levelsEntry = spellInfo->GetSpellLevels())
{
if (levelsEntry->maxLevel)
level = std::min(level, levelsEntry->maxLevel);
level = std::max(level, levelsEntry->baseLevel) - levelsEntry->baseLevel;
}
// currently only profession spells have CastTimePerLevel data filled, always negative
castTime = spellCastTimeEntry->CastTime + spellCastTimeEntry->CastTimePerLevel * level;
}
else
castTime = spellCastTimeEntry->CastTime;
if (castTime < spellCastTimeEntry->MinCastTime)
castTime = spellCastTimeEntry->MinCastTime;
}
else
// not all spells have cast time index and this is all is pasiive abilities
return 0;
if (spell)
{
if (Player* modOwner = spell->GetCaster()->GetSpellModOwner())
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_CASTING_TIME, castTime, spell);
if (!spellInfo->HasAttribute(SPELL_ATTR_UNK4) && !spellInfo->HasAttribute(SPELL_ATTR_TRADESPELL))
castTime = int32(castTime * spell->GetCaster()->GetFloatValue(UNIT_MOD_CAST_SPEED));
else
{
if (spell->IsRangedSpell() && !spell->IsAutoRepeat())
castTime = int32(castTime * spell->GetCaster()->m_modAttackSpeedPct[RANGED_ATTACK]);
}
}
if (spellInfo->HasAttribute(SPELL_ATTR_RANGED) && (!spell || !spell->IsAutoRepeat()))
castTime += 500;
return (castTime > 0) ? uint32(castTime) : 0;
}
uint32 GetSpellCastTimeForBonus(SpellEntry const* spellProto, DamageEffectType damagetype)
{
uint32 CastingTime = !IsChanneledSpell(spellProto) ? GetSpellCastTime(spellProto) : GetSpellDuration(spellProto);
if (CastingTime > 7000) CastingTime = 7000;
if (CastingTime < 1500) CastingTime = 1500;
if (damagetype == DOT && !IsChanneledSpell(spellProto))
CastingTime = 3500;
int32 overTime = 0;
uint8 effects = 0;
bool DirectDamage = false;
bool AreaEffect = false;
for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellProto->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
if (IsAreaEffectTarget(Targets(spellEffect->EffectImplicitTargetA)) || IsAreaEffectTarget(Targets(spellEffect->EffectImplicitTargetB)))
AreaEffect = true;
}
for (uint32 i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellProto->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
switch (spellEffect->Effect)
{
case SPELL_EFFECT_SCHOOL_DAMAGE:
case SPELL_EFFECT_POWER_DRAIN:
case SPELL_EFFECT_HEALTH_LEECH:
case SPELL_EFFECT_ENVIRONMENTAL_DAMAGE:
case SPELL_EFFECT_POWER_BURN:
case SPELL_EFFECT_HEAL:
DirectDamage = true;
break;
case SPELL_EFFECT_APPLY_AURA:
switch (spellEffect->EffectApplyAuraName)
{
case SPELL_AURA_PERIODIC_DAMAGE:
case SPELL_AURA_PERIODIC_HEAL:
case SPELL_AURA_PERIODIC_LEECH:
if (GetSpellDuration(spellProto))
overTime = GetSpellDuration(spellProto);
break;
// Penalty for additional effects
case SPELL_AURA_DUMMY:
++effects;
break;
case SPELL_AURA_MOD_DECREASE_SPEED:
++effects;
break;
case SPELL_AURA_MOD_CONFUSE:
case SPELL_AURA_MOD_STUN:
case SPELL_AURA_MOD_ROOT:
// -10% per effect
effects += 2;
break;
default:
break;
}
break;
default:
break;
}
}
// Combined Spells with Both Over Time and Direct Damage
if (overTime > 0 && CastingTime > 0 && DirectDamage)
{
// mainly for DoTs which are 3500 here otherwise
uint32 OriginalCastTime = GetSpellCastTime(spellProto);
if (OriginalCastTime > 7000) OriginalCastTime = 7000;
if (OriginalCastTime < 1500) OriginalCastTime = 1500;
// Portion to Over Time
float PtOT = (overTime / 15000.0f) / ((overTime / 15000.0f) + (OriginalCastTime / 3500.0f));
if (damagetype == DOT)
CastingTime = uint32(CastingTime * PtOT);
else if (PtOT < 1.0f)
CastingTime = uint32(CastingTime * (1 - PtOT));
else
CastingTime = 0;
}
// Area Effect Spells receive only half of bonus
if (AreaEffect)
CastingTime /= 2;
// 50% for damage and healing spells for leech spells from damage bonus and 0% from healing
for (int j = 0; j < MAX_EFFECT_INDEX; ++j)
{
SpellEffectEntry const* spellEffect = spellProto->GetSpellEffect(SpellEffectIndex(j));
if (!spellEffect)
continue;
if (spellEffect->Effect == SPELL_EFFECT_HEALTH_LEECH ||
spellEffect->Effect == SPELL_EFFECT_APPLY_AURA && spellEffect->EffectApplyAuraName == SPELL_AURA_PERIODIC_LEECH)
{
CastingTime /= 2;
break;
}
}
// -5% of total per any additional effect (multiplicative)
for (int i = 0; i < effects; ++i)
CastingTime *= 0.95f;
return CastingTime;
}
uint16 GetSpellAuraMaxTicks(SpellEntry const* spellInfo)
{
int32 DotDuration = GetSpellDuration(spellInfo);
if (DotDuration == 0)
return 1;
// 200% limit
if (DotDuration > 30000)
DotDuration = 30000;
for (int j = 0; j < MAX_EFFECT_INDEX; ++j)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(j));
if(!spellEffect)
continue;
if (spellEffect->Effect == SPELL_EFFECT_APPLY_AURA && (
spellEffect->EffectApplyAuraName == SPELL_AURA_PERIODIC_DAMAGE ||
spellEffect->EffectApplyAuraName == SPELL_AURA_PERIODIC_HEAL ||
spellEffect->EffectApplyAuraName == SPELL_AURA_PERIODIC_LEECH) )
{
if (spellEffect->EffectAmplitude != 0)
return DotDuration / spellEffect->EffectAmplitude;
break;
}
}
return 6;
}
uint16 GetSpellAuraMaxTicks(uint32 spellId)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId);
if (!spellInfo)
{
sLog.outError("GetSpellAuraMaxTicks: Spell %u not exist!", spellId);
return 1;
}
return GetSpellAuraMaxTicks(spellInfo);
}
float CalculateDefaultCoefficient(SpellEntry const* spellProto, DamageEffectType const damagetype)
{
// Damage over Time spells bonus calculation
float DotFactor = 1.0f;
if (damagetype == DOT)
{
if (!IsChanneledSpell(spellProto))
DotFactor = GetSpellDuration(spellProto) / 15000.0f;
if (uint16 DotTicks = GetSpellAuraMaxTicks(spellProto))
DotFactor /= DotTicks;
}
// Distribute Damage over multiple effects, reduce by AoE
float coeff = GetSpellCastTimeForBonus(spellProto, damagetype) / 3500.0f;
return coeff * DotFactor;
}
WeaponAttackType GetWeaponAttackType(SpellEntry const* spellInfo)
{
if (!spellInfo)
return BASE_ATTACK;
switch (spellInfo->GetDmgClass())
{
case SPELL_DAMAGE_CLASS_MELEE:
if (spellInfo->HasAttribute(SPELL_ATTR_EX3_REQ_OFFHAND))
return OFF_ATTACK;
else
return BASE_ATTACK;
break;
case SPELL_DAMAGE_CLASS_RANGED:
return RANGED_ATTACK;
break;
default:
// Wands
if (spellInfo->HasAttribute(SPELL_ATTR_EX2_AUTOREPEAT_FLAG))
return RANGED_ATTACK;
else
return BASE_ATTACK;
break;
}
}
bool IsPassiveSpell(uint32 spellId)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId);
if (!spellInfo)
return false;
return IsPassiveSpell(spellInfo);
}
bool IsPassiveSpell(SpellEntry const* spellInfo)
{
return spellInfo->HasAttribute(SPELL_ATTR_PASSIVE);
}
bool IsNoStackAuraDueToAura(uint32 spellId_1, uint32 spellId_2)
{
SpellEntry const* spellInfo_1 = sSpellStore.LookupEntry(spellId_1);
SpellEntry const* spellInfo_2 = sSpellStore.LookupEntry(spellId_2);
if (!spellInfo_1 || !spellInfo_2) return false;
if (spellInfo_1->Id == spellId_2) return false;
for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* effect_1 = spellInfo_1->GetSpellEffect(SpellEffectIndex(i));
for (int32 j = 0; j < MAX_EFFECT_INDEX; ++j)
{
SpellEffectEntry const* effect_2 = spellInfo_2->GetSpellEffect(SpellEffectIndex(j));
if(!effect_1 || !effect_2)
continue;
if (effect_1->Effect == effect_2->Effect
&& effect_1->EffectApplyAuraName == effect_2->EffectApplyAuraName
&& effect_1->EffectMiscValue == effect_2->EffectMiscValue
&& effect_1->EffectItemType == effect_2->EffectItemType
&& (effect_1->Effect != 0 || effect_1->EffectApplyAuraName != 0 ||
effect_1->EffectMiscValue != 0 || effect_1->EffectItemType != 0))
return true;
}
}
return false;
}
int32 CompareAuraRanks(uint32 spellId_1, uint32 spellId_2)
{
SpellEntry const* spellInfo_1 = sSpellStore.LookupEntry(spellId_1);
SpellEntry const* spellInfo_2 = sSpellStore.LookupEntry(spellId_2);
if (!spellInfo_1 || !spellInfo_2) return 0;
if (spellId_1 == spellId_2) return 0;
for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect_1 = spellInfo_1->GetSpellEffect(SpellEffectIndex(i));
SpellEffectEntry const* spellEffect_2 = spellInfo_2->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect_1 || !spellEffect_2)
continue;
if (spellEffect_1->Effect != 0 && spellEffect_2->Effect != 0 && spellEffect_1->Effect == spellEffect_2->Effect)
{
int32 diff = spellEffect_1->EffectBasePoints - spellEffect_2->EffectBasePoints;
if (spellInfo_1->CalculateSimpleValue(SpellEffectIndex(i)) < 0 && spellInfo_2->CalculateSimpleValue(SpellEffectIndex(i)) < 0)
return -diff;
else return diff;
}
}
return 0;
}
SpellSpecific GetSpellSpecific(uint32 spellId)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId);
if (!spellInfo)
return SPELL_NORMAL;
SpellClassOptionsEntry const* classOpt = spellInfo->GetSpellClassOptions();
SpellInterruptsEntry const* interrupts = spellInfo->GetSpellInterrupts();
switch(spellInfo->GetSpellFamilyName())
{
case SPELLFAMILY_GENERIC:
{
// Food / Drinks (mostly)
if(interrupts && interrupts->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED)
{
bool food = false;
bool drink = false;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
switch(spellEffect->EffectApplyAuraName)
{
// Food
case SPELL_AURA_MOD_REGEN:
case SPELL_AURA_OBS_MOD_HEALTH:
food = true;
break;
// Drink
case SPELL_AURA_MOD_POWER_REGEN:
case SPELL_AURA_OBS_MOD_MANA:
drink = true;
break;
default:
break;
}
}
if (food && drink)
return SPELL_FOOD_AND_DRINK;
else if (food)
return SPELL_FOOD;
else if (drink)
return SPELL_DRINK;
}
else
{
// Well Fed buffs (must be exclusive with Food / Drink replenishment effects, or else Well Fed will cause them to be removed)
// SpellIcon 2560 is Spell 46687, does not have this flag
if (spellInfo->HasAttribute(SPELL_ATTR_EX2_FOOD_BUFF) || spellInfo->SpellIconID == 2560)
return SPELL_WELL_FED;
}
break;
}
case SPELLFAMILY_MAGE:
{
// family flags 18(Molten), 25(Frost/Ice), 28(Mage)
if (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x12040000))
return SPELL_MAGE_ARMOR;
SpellEffectEntry const* mageSpellEffect = spellInfo->GetSpellEffect(EFFECT_INDEX_0);
if (classOpt && (classOpt->SpellFamilyFlags & UI64LIT(0x1000000)) && mageSpellEffect && mageSpellEffect->EffectApplyAuraName == SPELL_AURA_MOD_CONFUSE)
return SPELL_MAGE_POLYMORPH;
break;
}
case SPELLFAMILY_WARRIOR:
{
if (classOpt && classOpt->SpellFamilyFlags & UI64LIT(0x00008000010000))
return SPELL_POSITIVE_SHOUT;
break;
}
case SPELLFAMILY_WARLOCK:
{
// only warlock curses have this
if (spellInfo->GetDispel() == DISPEL_CURSE)
return SPELL_CURSE;
// Warlock (Demon Armor | Demon Skin | Fel Armor)
if (spellInfo->IsFitToFamilyMask(UI64LIT(0x2000002000000000), 0x00000010))
return SPELL_WARLOCK_ARMOR;
// Unstable Affliction | Immolate
if (spellInfo->IsFitToFamilyMask(UI64LIT(0x0000010000000004)))
return SPELL_UA_IMMOLATE;
break;
}
// Need Fix
case SPELLFAMILY_PRIEST:
{
// "Well Fed" buff from Blessed Sunfruit, Blessed Sunfruit Juice, Alterac Spring Water
if (spellInfo->HasAttribute(SPELL_ATTR_CASTABLE_WHILE_SITTING) &&
(interrupts && interrupts->InterruptFlags & SPELL_INTERRUPT_FLAG_AUTOATTACK) &&
(spellInfo->SpellIconID == 52 || spellInfo->SpellIconID == 79))
return SPELL_WELL_FED;
break;
}
case SPELLFAMILY_HUNTER:
{
// only hunter stings have this
if (spellInfo->GetDispel() == DISPEL_POISON)
return SPELL_STING;
// only hunter aspects have this
if (spellInfo->IsFitToFamilyMask(UI64LIT(0x0044000000380000), 0x00001010))
return SPELL_ASPECT;
break;
}
case SPELLFAMILY_PALADIN:
{
if (IsSealSpell(spellInfo))
return SPELL_SEAL;
if (spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000011010002)))
return SPELL_BLESSING;
if (spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000000002190)))
return SPELL_HAND;
// skip Heart of the Crusader that have also same spell family mask
if (spellInfo->IsFitToFamilyMask(UI64LIT(0x00000820180400)) && spellInfo->HasAttribute(SPELL_ATTR_EX3_UNK9) && (spellInfo->SpellIconID != 237))
return SPELL_JUDGEMENT;
// only paladin auras have this (for palaldin class family)
if (spellInfo->IsFitToFamilyMask(UI64LIT(0x0000000000000000), 0x00000020))
return SPELL_AURA;
break;
}
case SPELLFAMILY_SHAMAN:
{
if (IsElementalShield(spellInfo))
return SPELL_ELEMENTAL_SHIELD;
break;
}
case SPELLFAMILY_POTION:
return sSpellMgr.GetSpellElixirSpecific(spellInfo->Id);
case SPELLFAMILY_DEATHKNIGHT:
if (spellInfo->GetCategory() == 47)
return SPELL_PRESENCE;
break;
}
// Tracking spells (exclude Well Fed, some other always allowed cases)
if ((IsSpellHaveAura(spellInfo, SPELL_AURA_TRACK_CREATURES) ||
IsSpellHaveAura(spellInfo, SPELL_AURA_TRACK_RESOURCES) ||
IsSpellHaveAura(spellInfo, SPELL_AURA_TRACK_STEALTHED)) &&
(spellInfo->HasAttribute(SPELL_ATTR_EX_UNK17) || spellInfo->HasAttribute(SPELL_ATTR_EX6_UNK12)))
return SPELL_TRACKER;
// elixirs can have different families, but potion most ofc.
if (SpellSpecific sp = sSpellMgr.GetSpellElixirSpecific(spellInfo->Id))
return sp;
return SPELL_NORMAL;
}
// target not allow have more one spell specific from same caster
bool IsSingleFromSpellSpecificPerTargetPerCaster(SpellSpecific spellSpec1, SpellSpecific spellSpec2)
{
switch (spellSpec1)
{
case SPELL_BLESSING:
case SPELL_AURA:
case SPELL_STING:
case SPELL_CURSE:
case SPELL_ASPECT:
case SPELL_POSITIVE_SHOUT:
case SPELL_JUDGEMENT:
case SPELL_HAND:
case SPELL_UA_IMMOLATE:
return spellSpec1 == spellSpec2;
default:
return false;
}
}
// target not allow have more one ranks from spell from spell specific per target
bool IsSingleFromSpellSpecificSpellRanksPerTarget(SpellSpecific spellSpec1, SpellSpecific spellSpec2)
{
switch (spellSpec1)
{
case SPELL_BLESSING:
case SPELL_AURA:
case SPELL_CURSE:
case SPELL_ASPECT:
case SPELL_HAND:
return spellSpec1 == spellSpec2;
default:
return false;
}
}
// target not allow have more one spell specific per target from any caster
bool IsSingleFromSpellSpecificPerTarget(SpellSpecific spellSpec1, SpellSpecific spellSpec2)
{
switch (spellSpec1)
{
case SPELL_SEAL:
case SPELL_TRACKER:
case SPELL_WARLOCK_ARMOR:
case SPELL_MAGE_ARMOR:
case SPELL_ELEMENTAL_SHIELD:
case SPELL_MAGE_POLYMORPH:
case SPELL_PRESENCE:
case SPELL_WELL_FED:
return spellSpec1 == spellSpec2;
case SPELL_BATTLE_ELIXIR:
return spellSpec2 == SPELL_BATTLE_ELIXIR
|| spellSpec2 == SPELL_FLASK_ELIXIR;
case SPELL_GUARDIAN_ELIXIR:
return spellSpec2 == SPELL_GUARDIAN_ELIXIR
|| spellSpec2 == SPELL_FLASK_ELIXIR;
case SPELL_FLASK_ELIXIR:
return spellSpec2 == SPELL_BATTLE_ELIXIR
|| spellSpec2 == SPELL_GUARDIAN_ELIXIR
|| spellSpec2 == SPELL_FLASK_ELIXIR;
case SPELL_FOOD:
return spellSpec2 == SPELL_FOOD
|| spellSpec2 == SPELL_FOOD_AND_DRINK;
case SPELL_DRINK:
return spellSpec2 == SPELL_DRINK
|| spellSpec2 == SPELL_FOOD_AND_DRINK;
case SPELL_FOOD_AND_DRINK:
return spellSpec2 == SPELL_FOOD
|| spellSpec2 == SPELL_DRINK
|| spellSpec2 == SPELL_FOOD_AND_DRINK;
default:
return false;
}
}
bool IsPositiveTarget(uint32 targetA, uint32 targetB)
{
switch (targetA)
{
// non-positive targets
case TARGET_CHAIN_DAMAGE:
case TARGET_ALL_ENEMY_IN_AREA:
case TARGET_ALL_ENEMY_IN_AREA_INSTANT:
case TARGET_IN_FRONT_OF_CASTER:
case TARGET_ALL_ENEMY_IN_AREA_CHANNELED:
case TARGET_CURRENT_ENEMY_COORDINATES:
case TARGET_SINGLE_ENEMY:
case TARGET_IN_FRONT_OF_CASTER_30:
return false;
// positive or dependent
case TARGET_CASTER_COORDINATES:
return (targetB == TARGET_ALL_PARTY || targetB == TARGET_ALL_FRIENDLY_UNITS_AROUND_CASTER);
default:
break;
}
if (targetB)
return IsPositiveTarget(targetB, 0);
return true;
}
bool IsExplicitPositiveTarget(uint32 targetA)
{
// positive targets that in target selection code expect target in m_targers, so not that auto-select target by spell data by m_caster and etc
switch (targetA)
{
case TARGET_SINGLE_FRIEND:
case TARGET_SINGLE_PARTY:
case TARGET_CHAIN_HEAL:
case TARGET_SINGLE_FRIEND_2:
case TARGET_AREAEFFECT_PARTY_AND_CLASS:
return true;
default:
break;
}
return false;
}
bool IsExplicitNegativeTarget(uint32 targetA)
{
// non-positive targets that in target selection code expect target in m_targers, so not that auto-select target by spell data by m_caster and etc
switch (targetA)
{
case TARGET_CHAIN_DAMAGE:
case TARGET_CURRENT_ENEMY_COORDINATES:
case TARGET_SINGLE_ENEMY:
return true;
default:
break;
}
return false;
}
bool IsPositiveEffect(SpellEntry const* spellproto, SpellEffectIndex effIndex)
{
SpellEffectEntry const* spellEffect = spellproto->GetSpellEffect(effIndex);
switch(spellproto->GetSpellEffectIdByIndex(effIndex))
{
case SPELL_EFFECT_DUMMY:
// some explicitly required dummy effect sets
switch (spellproto->Id)
{
case 28441: // AB Effect 000
return false;
case 18153: // Kodo Kombobulator
case 49634: // Sergeant's Flare
case 54530: // Opening
case 62105: // To'kini's Blowgun
return true;
default:
break;
}
break;
// always positive effects (check before target checks that provided non-positive result in some case for positive effects)
case SPELL_EFFECT_HEAL:
case SPELL_EFFECT_LEARN_SPELL:
case SPELL_EFFECT_SKILL_STEP:
case SPELL_EFFECT_HEAL_PCT:
case SPELL_EFFECT_ENERGIZE_PCT:
case SPELL_EFFECT_QUEST_COMPLETE:
case SPELL_EFFECT_KILL_CREDIT_PERSONAL:
case SPELL_EFFECT_KILL_CREDIT_GROUP:
return true;
// non-positive aura use
case SPELL_EFFECT_APPLY_AURA:
case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
{
switch(spellEffect->EffectApplyAuraName)
{
case SPELL_AURA_DUMMY:
{
// dummy aura can be positive or negative dependent from casted spell
switch (spellproto->Id)
{
case 13139: // net-o-matic special effect
case 23445: // evil twin
case 35679: // Protectorate Demolitionist
case 38637: // Nether Exhaustion (red)
case 38638: // Nether Exhaustion (green)
case 38639: // Nether Exhaustion (blue)
case 11196: // Recently Bandaged
case 44689: // Relay Race Accept Hidden Debuff - DND
case 58600: // Restricted Flight Area
return false;
// some spells have unclear target modes for selection, so just make effect positive
case 27184:
case 27190:
case 27191:
case 27201:
case 27202:
case 27203:
case 47669:
return true;
default:
break;
}
} break;
case SPELL_AURA_MOD_DAMAGE_DONE: // dependent from base point sign (negative -> negative)
case SPELL_AURA_MOD_RESISTANCE:
case SPELL_AURA_MOD_STAT:
case SPELL_AURA_MOD_SKILL:
case SPELL_AURA_MOD_DODGE_PERCENT:
case SPELL_AURA_MOD_HEALING_PCT:
case SPELL_AURA_MOD_HEALING_DONE:
if (spellEffect->CalculateSimpleValue() < 0)
return false;
break;
case SPELL_AURA_MOD_DAMAGE_TAKEN: // dependent from bas point sign (positive -> negative)
if (spellEffect->CalculateSimpleValue() < 0)
return true;
// let check by target modes (for Amplify Magic cases/etc)
break;
case SPELL_AURA_MOD_SPELL_CRIT_CHANCE:
case SPELL_AURA_MOD_INCREASE_HEALTH_PERCENT:
case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE:
if (spellEffect->CalculateSimpleValue() > 0)
return true; // some expected positive spells have SPELL_ATTR_EX_NEGATIVE or unclear target modes
break;
case SPELL_AURA_ADD_TARGET_TRIGGER:
return true;
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
if (spellproto->Id != spellEffect->EffectTriggerSpell)
{
uint32 spellTriggeredId = spellEffect->EffectTriggerSpell;
SpellEntry const *spellTriggeredProto = sSpellStore.LookupEntry(spellTriggeredId);
if (spellTriggeredProto)
{
// non-positive targets of main spell return early
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* triggerSpellEffect = spellTriggeredProto->GetSpellEffect(SpellEffectIndex(i));
if (!triggerSpellEffect)
continue;
// if non-positive trigger cast targeted to positive target this main cast is non-positive
// this will place this spell auras as debuffs
if (triggerSpellEffect->Effect &&
IsPositiveTarget(triggerSpellEffect->EffectImplicitTargetA,triggerSpellEffect->EffectImplicitTargetB) &&
!IsPositiveEffect(spellTriggeredProto, SpellEffectIndex(i)))
return false;
}
}
}
break;
case SPELL_AURA_PROC_TRIGGER_SPELL:
// many positive auras have negative triggered spells at damage for example and this not make it negative (it can be canceled for example)
break;
case SPELL_AURA_MOD_STUN: //have positive and negative spells, we can't sort its correctly at this moment.
if (effIndex == EFFECT_INDEX_0 && spellproto->GetSpellEffectIdByIndex(EFFECT_INDEX_1) == 0 && spellproto->GetSpellEffectIdByIndex(EFFECT_INDEX_2) == 0)
return false; // but all single stun aura spells is negative
// Petrification
if (spellproto->Id == 17624)
return false;
break;
case SPELL_AURA_MOD_PACIFY_SILENCE:
switch (spellproto->Id)
{
case 24740: // Wisp Costume
case 47585: // Dispersion
return true;
default: break;
}
return false;
case SPELL_AURA_MOD_ROOT:
case SPELL_AURA_MOD_SILENCE:
case SPELL_AURA_GHOST:
case SPELL_AURA_PERIODIC_LEECH:
case SPELL_AURA_MOD_STALKED:
case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
return false;
case SPELL_AURA_PERIODIC_DAMAGE: // used in positive spells also.
// part of negative spell if casted at self (prevent cancel)
if (spellEffect->EffectImplicitTargetA == TARGET_SELF ||
spellEffect->EffectImplicitTargetA == TARGET_SELF2)
return false;
break;
case SPELL_AURA_MOD_DECREASE_SPEED: // used in positive spells also
// part of positive spell if casted at self
if ((spellEffect->EffectImplicitTargetA == TARGET_SELF ||
spellEffect->EffectImplicitTargetA == TARGET_SELF2) &&
spellproto->GetSpellFamilyName() == SPELLFAMILY_GENERIC)
return false;
// but not this if this first effect (don't found better check)
if (spellproto->HasAttribute(SPELL_ATTR_UNK26) && effIndex == EFFECT_INDEX_0)
return false;
break;
case SPELL_AURA_TRANSFORM:
// some spells negative
switch (spellproto->Id)
{
case 36897: // Transporter Malfunction (race mutation to horde)
case 36899: // Transporter Malfunction (race mutation to alliance)
return false;
}
break;
case SPELL_AURA_MOD_SCALE:
// some spells negative
switch (spellproto->Id)
{
case 802: // Mutate Bug, wrongly negative by target modes
return true;
case 36900: // Soul Split: Evil!
case 36901: // Soul Split: Good
case 36893: // Transporter Malfunction (decrease size case)
case 36895: // Transporter Malfunction (increase size case)
return false;
}
break;
case SPELL_AURA_MECHANIC_IMMUNITY:
{
// non-positive immunities
switch(spellEffect->EffectMiscValue)
{
case MECHANIC_BANDAGE:
case MECHANIC_SHIELD:
case MECHANIC_MOUNT:
case MECHANIC_INVULNERABILITY:
return false;
default:
break;
}
} break;
case SPELL_AURA_ADD_FLAT_MODIFIER: // mods
case SPELL_AURA_ADD_PCT_MODIFIER:
{
// non-positive mods
switch(spellEffect->EffectMiscValue)
{
case SPELLMOD_COST: // dependent from bas point sign (negative -> positive)
if (spellproto->CalculateSimpleValue(effIndex) > 0)
return false;
break;
default:
break;
}
} break;
case SPELL_AURA_FORCE_REACTION:
{
switch (spellproto->Id)
{
case 42792: // Recently Dropped Flag (prevent cancel)
case 46221: // Animal Blood
return false;
default:
break;
}
break;
}
default:
break;
}
break;
}
default:
break;
}
// non-positive targets
if (spellEffect && !IsPositiveTarget(spellEffect->EffectImplicitTargetA,spellEffect->EffectImplicitTargetB))
return false;
// AttributesEx check
if (spellproto->HasAttribute(SPELL_ATTR_EX_NEGATIVE))
return false;
// ok, positive
return true;
}
bool IsPositiveSpell(uint32 spellId)
{
SpellEntry const* spellproto = sSpellStore.LookupEntry(spellId);
if (!spellproto)
return false;
return IsPositiveSpell(spellproto);
}
bool IsPositiveSpell(SpellEntry const* spellproto)
{
// spells with at least one negative effect are considered negative
// some self-applied spells have negative effects but in self casting case negative check ignored.
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
if (spellproto->GetSpellEffectIdByIndex(SpellEffectIndex(i)) && !IsPositiveEffect(spellproto, SpellEffectIndex(i)))
return false;
return true;
}
bool IsSingleTargetSpell(SpellEntry const* spellInfo)
{
// all other single target spells have if it has AttributesEx5
if (spellInfo->HasAttribute(SPELL_ATTR_EX5_SINGLE_TARGET_SPELL))
return true;
// TODO - need found Judgements rule
switch (GetSpellSpecific(spellInfo->Id))
{
case SPELL_JUDGEMENT:
return true;
default:
break;
}
// single target triggered spell.
// Not real client side single target spell, but it' not triggered until prev. aura expired.
// This is allow store it in single target spells list for caster for spell proc checking
if (spellInfo->Id == 38324) // Regeneration (triggered by 38299 (HoTs on Heals))
return true;
return false;
}
bool IsSingleTargetSpells(SpellEntry const* spellInfo1, SpellEntry const* spellInfo2)
{
// TODO - need better check
// Equal icon and spellfamily
if( spellInfo1->GetSpellFamilyName() == spellInfo2->GetSpellFamilyName() &&
spellInfo1->SpellIconID == spellInfo2->SpellIconID )
return true;
// TODO - need found Judgements rule
SpellSpecific spec1 = GetSpellSpecific(spellInfo1->Id);
// spell with single target specific types
switch (spec1)
{
case SPELL_JUDGEMENT:
case SPELL_MAGE_POLYMORPH:
if (GetSpellSpecific(spellInfo2->Id) == spec1)
return true;
break;
default:
break;
}
return false;
}
SpellCastResult GetErrorAtShapeshiftedCast(SpellEntry const* spellInfo, uint32 form)
{
// talents that learn spells can have stance requirements that need ignore
// (this requirement only for client-side stance show in talent description)
if( GetTalentSpellCost(spellInfo->Id) > 0 &&
(spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_0) == SPELL_EFFECT_LEARN_SPELL || spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_1) == SPELL_EFFECT_LEARN_SPELL || spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_2) == SPELL_EFFECT_LEARN_SPELL) )
return SPELL_CAST_OK;
uint32 stanceMask = (form ? 1 << (form - 1) : 0);
SpellShapeshiftEntry const* shapeShift = spellInfo->GetSpellShapeshift();
if (shapeShift && stanceMask & shapeShift->StancesNot) // can explicitly not be casted in this stance
return SPELL_FAILED_NOT_SHAPESHIFT;
if (shapeShift && stanceMask & shapeShift->Stances) // can explicitly be casted in this stance
return SPELL_CAST_OK;
bool actAsShifted = false;
if (form > 0)
{
SpellShapeshiftFormEntry const* shapeInfo = sSpellShapeshiftFormStore.LookupEntry(form);
if (!shapeInfo)
{
sLog.outError("GetErrorAtShapeshiftedCast: unknown shapeshift %u", form);
return SPELL_CAST_OK;
}
actAsShifted = !(shapeInfo->flags1 & 1); // shapeshift acts as normal form for spells
}
if (actAsShifted)
{
if (spellInfo->HasAttribute(SPELL_ATTR_NOT_SHAPESHIFT)) // not while shapeshifted
return SPELL_FAILED_NOT_SHAPESHIFT;
else if (shapeShift && shapeShift->Stances != 0) // needs other shapeshift
return SPELL_FAILED_ONLY_SHAPESHIFT;
}
else
{
// needs shapeshift
if(!(spellInfo->AttributesEx2 & SPELL_ATTR_EX2_NOT_NEED_SHAPESHIFT) && shapeShift && shapeShift->Stances != 0)
return SPELL_FAILED_ONLY_SHAPESHIFT;
}
return SPELL_CAST_OK;
}
void SpellMgr::LoadSpellTargetPositions()
{
mSpellTargetPositions.clear(); // need for reload case
uint32 count = 0;
// 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)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded %u spell target destination coordinates", count);
return;
}
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
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;
}
SpellEntry const* spellInfo = sSpellStore.LookupEntry(Spell_ID);
if (!spellInfo)
{
sLog.outErrorDb("Spell (ID:%u) listed in `spell_target_position` does not exist.", Spell_ID);
continue;
}
bool found = false;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
if (spellEffect->EffectImplicitTargetA==TARGET_TABLE_X_Y_Z_COORDINATES || spellEffect->EffectImplicitTargetB==TARGET_TABLE_X_Y_Z_COORDINATES)
{
// additional requirements
if (spellEffect->Effect==SPELL_EFFECT_BIND && spellEffect->EffectMiscValue)
{
uint32 zone_id = sTerrainMgr.GetAreaId(st.target_mapId, st.target_X, st.target_Y, st.target_Z);
if (int32(zone_id) != spellEffect->EffectMiscValue)
{
sLog.outErrorDb("Spell (Id: %u) listed in `spell_target_position` expected point to zone %u bit point to zone %u.",Spell_ID, spellEffect->EffectMiscValue, zone_id);
break;
}
}
found = true;
break;
}
}
if (!found)
{
sLog.outErrorDb("Spell (Id: %u) listed in `spell_target_position` does not have target TARGET_TABLE_X_Y_Z_COORDINATES (17).", Spell_ID);
continue;
}
mSpellTargetPositions[Spell_ID] = st;
++count;
}
while (result->NextRow());
delete result;
sLog.outString();
sLog.outString(">> Loaded %u spell target destination coordinates", count);
}
template <typename EntryType, typename WorkerType, typename StorageType>
struct SpellRankHelper
{
SpellRankHelper(SpellMgr& _mgr, StorageType& _storage): mgr(_mgr), worker(_storage), customRank(0) {}
void RecordRank(EntryType& entry, uint32 spell_id)
{
const SpellEntry* spell = sSpellStore.LookupEntry(spell_id);
if (!spell)
{
sLog.outErrorDb("Spell %u listed in `%s` does not exist", spell_id, worker.TableName());
return;
}
uint32 first_id = mgr.GetFirstSpellInChain(spell_id);
// most spell ranks expected same data
if (first_id)
{
firstRankSpells.insert(first_id);
if (first_id != spell_id)
{
if (!worker.IsValidCustomRank(entry, spell_id, first_id))
return;
// for later check that first rank also added
else
{
firstRankSpellsWithCustomRanks.insert(first_id);
++customRank;
}
}
}
worker.AddEntry(entry, spell);
}
void FillHigherRanks()
{
// check that first rank added for custom ranks
for (std::set<uint32>::const_iterator itr = firstRankSpellsWithCustomRanks.begin(); itr != firstRankSpellsWithCustomRanks.end(); ++itr)
if (!worker.HasEntry(*itr))
sLog.outErrorDb("Spell %u must be listed in `%s` as first rank for listed custom ranks of spell but not found!", *itr, worker.TableName());
// fill absent non first ranks data base at first rank data
for (std::set<uint32>::const_iterator itr = firstRankSpells.begin(); itr != firstRankSpells.end(); ++itr)
{
if (worker.SetStateToEntry(*itr))
mgr.doForHighRanks(*itr, worker);
}
}
std::set<uint32> firstRankSpells;
std::set<uint32> firstRankSpellsWithCustomRanks;
SpellMgr& mgr;
WorkerType worker;
uint32 customRank;
};
struct DoSpellProcEvent
{
DoSpellProcEvent(SpellProcEventMap& _spe_map) : spe_map(_spe_map), customProc(0), count(0) {}
void operator()(uint32 spell_id)
{
SpellProcEventEntry const& spe = state->second;
// add ranks only for not filled data (some ranks have ppm data different for ranks for example)
SpellProcEventMap::const_iterator spellItr = spe_map.find(spell_id);
if (spellItr == spe_map.end())
spe_map[spell_id] = spe;
// if custom rank data added then it must be same except ppm
else
{
SpellProcEventEntry const& r_spe = spellItr->second;
if (spe.schoolMask != r_spe.schoolMask)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` as custom rank have different schoolMask from first rank in chain", spell_id);
if (spe.spellFamilyName != r_spe.spellFamilyName)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` as custom rank have different spellFamilyName from first rank in chain", spell_id);
for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
{
if (spe.spellFamilyMask[i] != r_spe.spellFamilyMask[i])
{
sLog.outErrorDb("Spell %u listed in `spell_proc_event` as custom rank have different spellFamilyMask/spellFamilyMask2 from first rank in chain", spell_id);
break;
}
}
if (spe.procFlags != r_spe.procFlags)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` as custom rank have different procFlags from first rank in chain", spell_id);
if (spe.procEx != r_spe.procEx)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` as custom rank have different procEx from first rank in chain", spell_id);
// only ppm allowed has been different from first rank
if (spe.customChance != r_spe.customChance)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` as custom rank have different customChance from first rank in chain", spell_id);
if (spe.cooldown != r_spe.cooldown)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` as custom rank have different cooldown from first rank in chain", spell_id);
}
}
const char* TableName() { return "spell_proc_event"; }
bool IsValidCustomRank(SpellProcEventEntry const& spe, uint32 entry, uint32 first_id)
{
// let have independent data in table for spells with ppm rates (exist rank dependent ppm rate spells)
if (!spe.ppmRate)
{
sLog.outErrorDb("Spell %u listed in `spell_proc_event` is not first rank (%u) in chain", entry, first_id);
// prevent loading since it won't have an effect anyway
return false;
}
return true;
}
void AddEntry(SpellProcEventEntry const& spe, SpellEntry const* spell)
{
spe_map[spell->Id] = spe;
bool isCustom = false;
if (spe.procFlags == 0)
{
if (spell->GetProcFlags()==0)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` probally not triggered spell (no proc flags)", spell->Id);
}
else
{
if (spell->GetProcFlags()==spe.procFlags)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` has exactly same proc flags as in spell.dbc, field value redundant", spell->Id);
else
isCustom = true;
}
if (spe.customChance == 0)
{
/* enable for re-check cases, 0 chance ok for some cases because in some cases it set by another spell/talent spellmod)
if (spell->procChance==0 && !spe.ppmRate)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` probally not triggered spell (no chance or ppm)", spell->Id);
*/
}
else
{
if (spell->GetProcChance()==spe.customChance)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` has exactly same custom chance as in spell.dbc, field value redundant", spell->Id);
else
isCustom = true;
}
// totally redundant record
if (!spe.schoolMask && !spe.procFlags &&
!spe.procEx && !spe.ppmRate && !spe.customChance && !spe.cooldown)
{
bool empty = !spe.spellFamilyName ? true : false;
for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
{
if (spe.spellFamilyMask[i])
{
empty = false;
ClassFamilyMask const& mask = spell->GetEffectSpellClassMask(SpellEffectIndex(i));
if (mask == spe.spellFamilyMask[i])
sLog.outErrorDb("Spell %u listed in `spell_proc_event` has same class mask as in Spell.dbc (EffectIndex %u) and doesn't have any other data", spell->Id, i);
}
}
if (empty)
sLog.outErrorDb("Spell %u listed in `spell_proc_event` doesn't have any useful data", spell->Id);
}
if (isCustom)
++customProc;
else
++count;
}
bool HasEntry(uint32 spellId) { return spe_map.count(spellId) > 0; }
bool SetStateToEntry(uint32 spellId) { return (state = spe_map.find(spellId)) != spe_map.end(); }
SpellProcEventMap& spe_map;
SpellProcEventMap::const_iterator state;
uint32 customProc;
uint32 count;
};
void SpellMgr::LoadSpellProcEvents()
{
mSpellProcEventMap.clear(); // need for reload case
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
QueryResult* result = WorldDatabase.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMaskA0, SpellFamilyMaskA1, SpellFamilyMaskA2, SpellFamilyMaskB0, SpellFamilyMaskB1, SpellFamilyMaskB2, SpellFamilyMaskC0, SpellFamilyMaskC1, SpellFamilyMaskC2, procFlags, procEx, ppmRate, CustomChance, Cooldown FROM spell_proc_event");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> No spell proc event conditions loaded");
return;
}
SpellRankHelper<SpellProcEventEntry, DoSpellProcEvent, SpellProcEventMap> rankHelper(*this, mSpellProcEventMap);
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
uint32 entry = fields[0].GetUInt32();
SpellProcEventEntry spe;
spe.schoolMask = fields[1].GetUInt32();
spe.spellFamilyName = fields[2].GetUInt32();
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
spe.spellFamilyMask[i] = ClassFamilyMask(
(uint64)fields[i + 3].GetUInt32() | ((uint64)fields[i + 6].GetUInt32() << 32),
fields[i + 9].GetUInt32());
}
spe.procFlags = fields[12].GetUInt32();
spe.procEx = fields[13].GetUInt32();
spe.ppmRate = fields[14].GetFloat();
spe.customChance = fields[15].GetFloat();
spe.cooldown = fields[16].GetUInt32();
rankHelper.RecordRank(spe, entry);
}
while (result->NextRow());
rankHelper.FillHigherRanks();
delete result;
sLog.outString();
sLog.outString(">> Loaded %u extra spell proc event conditions +%u custom proc (inc. +%u custom ranks)", rankHelper.worker.count, rankHelper.worker.customProc, rankHelper.customRank);
}
struct DoSpellProcItemEnchant
{
DoSpellProcItemEnchant(SpellProcItemEnchantMap& _procMap, float _ppm) : procMap(_procMap), ppm(_ppm) {}
void operator()(uint32 spell_id) { procMap[spell_id] = ppm; }
SpellProcItemEnchantMap& procMap;
float ppm;
};
void SpellMgr::LoadSpellProcItemEnchant()
{
mSpellProcItemEnchantMap.clear(); // need for reload case
uint32 count = 0;
// 0 1
QueryResult* result = WorldDatabase.Query("SELECT entry, ppmRate FROM spell_proc_item_enchant");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded %u proc item enchant definitions", count);
return;
}
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
uint32 entry = fields[0].GetUInt32();
float ppmRate = fields[1].GetFloat();
SpellEntry const* spellInfo = sSpellStore.LookupEntry(entry);
if (!spellInfo)
{
sLog.outErrorDb("Spell %u listed in `spell_proc_item_enchant` does not exist", entry);
continue;
}
uint32 first_id = GetFirstSpellInChain(entry);
if (first_id != entry)
{
sLog.outErrorDb("Spell %u listed in `spell_proc_item_enchant` is not first rank (%u) in chain", entry, first_id);
// prevent loading since it won't have an effect anyway
continue;
}
mSpellProcItemEnchantMap[entry] = ppmRate;
// also add to high ranks
DoSpellProcItemEnchant worker(mSpellProcItemEnchantMap, ppmRate);
doForHighRanks(entry, worker);
++count;
}
while (result->NextRow());
delete result;
sLog.outString();
sLog.outString(">> Loaded %u proc item enchant definitions", count);
}
bool IsCastEndProcModifierAura(SpellEntry const* spellInfo, SpellEffectIndex effecIdx, SpellEntry const* procSpell)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(effecIdx));
if (!spellEffect)
return false;
// modifier auras that can proc on cast end
switch (AuraType(spellEffect->EffectApplyAuraName))
{
case SPELL_AURA_ADD_FLAT_MODIFIER:
case SPELL_AURA_ADD_PCT_MODIFIER:
{
switch (spellEffect->EffectMiscValue)
{
case SPELLMOD_RANGE:
case SPELLMOD_RADIUS:
case SPELLMOD_CRITICAL_CHANCE:
case SPELLMOD_NOT_LOSE_CASTING_TIME:
case SPELLMOD_CASTING_TIME:
case SPELLMOD_COOLDOWN:
case SPELLMOD_COST:
case SPELLMOD_GLOBAL_COOLDOWN:
return true;
default:
break;
}
}
case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE:
{
for (int32 i = 0; i < MAX_EFFECT_INDEX; ++i)
if (IsEffectHandledOnDelayedSpellLaunch(procSpell, SpellEffectIndex(i)))
return true;
return false;
}
default:
return false;
}
}
struct DoSpellBonuses
{
DoSpellBonuses(SpellBonusMap& _spellBonusMap, SpellBonusEntry const& _spellBonus) : spellBonusMap(_spellBonusMap), spellBonus(_spellBonus) {}
void operator()(uint32 spell_id) { spellBonusMap[spell_id] = spellBonus; }
SpellBonusMap& spellBonusMap;
SpellBonusEntry const& spellBonus;
};
void SpellMgr::LoadSpellBonuses()
{
mSpellBonusMap.clear(); // need for reload case
uint32 count = 0;
// 0 1 2 3
QueryResult* result = WorldDatabase.Query("SELECT entry, direct_bonus, dot_bonus, ap_bonus, ap_dot_bonus FROM spell_bonus_data");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded %u spell bonus data", count);
return;
}
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
uint32 entry = fields[0].GetUInt32();
SpellEntry const* spell = sSpellStore.LookupEntry(entry);
if (!spell)
{
sLog.outErrorDb("Spell %u listed in `spell_bonus_data` does not exist", entry);
continue;
}
uint32 first_id = GetFirstSpellInChain(entry);
if (first_id != entry)
{
sLog.outErrorDb("Spell %u listed in `spell_bonus_data` is not first rank (%u) in chain", entry, first_id);
// prevent loading since it won't have an effect anyway
continue;
}
SpellBonusEntry sbe;
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();
bool need_dot = false;
bool need_direct = false;
uint32 x = 0; // count all, including empty, meaning: not all existing effect is DoTs/HoTs
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spell->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
if (!spellEffect->Effect)
{
++x;
continue;
}
// DoTs/HoTs
switch(spellEffect->EffectApplyAuraName)
{
case SPELL_AURA_PERIODIC_DAMAGE:
case SPELL_AURA_PERIODIC_DAMAGE_PERCENT:
case SPELL_AURA_PERIODIC_LEECH:
case SPELL_AURA_PERIODIC_HEAL:
case SPELL_AURA_OBS_MOD_HEALTH:
case SPELL_AURA_PERIODIC_MANA_LEECH:
case SPELL_AURA_OBS_MOD_MANA:
case SPELL_AURA_POWER_BURN_MANA:
need_dot = true;
++x;
break;
default:
break;
}
}
// TODO: maybe add explicit list possible direct damage spell effects...
if (x < MAX_EFFECT_INDEX)
need_direct = true;
// Check if direct_bonus is needed in `spell_bonus_data`
float direct_calc = 0.0f;
float direct_diff = 1000.0f; // for have big diff if no DB field value
if (sbe.direct_damage)
{
bool isHeal = false;
for(int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spell->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
// Heals (Also count Mana Shield and Absorb effects as heals)
if (spellEffect->Effect == SPELL_EFFECT_HEAL || spellEffect->Effect == SPELL_EFFECT_HEAL_MAX_HEALTH ||
(spellEffect->Effect == SPELL_EFFECT_APPLY_AURA && (spellEffect->EffectApplyAuraName == SPELL_AURA_SCHOOL_ABSORB || spellEffect->EffectApplyAuraName == SPELL_AURA_PERIODIC_HEAL)) )
{
isHeal = true;
break;
}
}
direct_calc = CalculateDefaultCoefficient(spell, SPELL_DIRECT_DAMAGE) * (isHeal ? 1.88f : 1.0f);
direct_diff = std::abs(sbe.direct_damage - direct_calc);
}
// Check if dot_bonus is needed in `spell_bonus_data`
float dot_calc = 0.0f;
float dot_diff = 1000.0f; // for have big diff if no DB field value
if (sbe.dot_damage)
{
bool isHeal = false;
for(int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spell->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
// Periodic Heals
if (spellEffect->Effect == SPELL_EFFECT_APPLY_AURA && spellEffect->EffectApplyAuraName == SPELL_AURA_PERIODIC_HEAL)
{
isHeal = true;
break;
}
}
dot_calc = CalculateDefaultCoefficient(spell, DOT) * (isHeal ? 1.88f : 1.0f);
dot_diff = std::abs(sbe.dot_damage - dot_calc);
}
if (direct_diff < 0.02f && !need_dot && !sbe.ap_bonus && !sbe.ap_dot_bonus)
sLog.outErrorDb("`spell_bonus_data` entry for spell %u `direct_bonus` not needed (data from table: %f, calculated %f, difference of %f) and `dot_bonus` also not used",
entry, sbe.direct_damage, direct_calc, direct_diff);
else if (direct_diff < 0.02f && dot_diff < 0.02f && !sbe.ap_bonus && !sbe.ap_dot_bonus)
{
sLog.outErrorDb("`spell_bonus_data` entry for spell %u `direct_bonus` not needed (data from table: %f, calculated %f, difference of %f) and ",
entry, sbe.direct_damage, direct_calc, direct_diff);
sLog.outErrorDb(" ... `dot_bonus` not needed (data from table: %f, calculated %f, difference of %f)",
sbe.dot_damage, dot_calc, dot_diff);
}
else if (!need_direct && dot_diff < 0.02f && !sbe.ap_bonus && !sbe.ap_dot_bonus)
sLog.outErrorDb("`spell_bonus_data` entry for spell %u `dot_bonus` not needed (data from table: %f, calculated %f, difference of %f) and direct also not used",
entry, sbe.dot_damage, dot_calc, dot_diff);
else if (!need_direct && sbe.direct_damage)
sLog.outErrorDb("`spell_bonus_data` entry for spell %u `direct_bonus` not used (spell not have non-periodic affects)", entry);
else if (!need_dot && sbe.dot_damage)
sLog.outErrorDb("`spell_bonus_data` entry for spell %u `dot_bonus` not used (spell not have periodic affects)", entry);
if (!need_direct && sbe.ap_bonus)
sLog.outErrorDb("`spell_bonus_data` entry for spell %u `ap_bonus` not used (spell not have non-periodic affects)", entry);
else if (!need_dot && sbe.ap_dot_bonus)
sLog.outErrorDb("`spell_bonus_data` entry for spell %u `ap_dot_bonus` not used (spell not have periodic affects)", entry);
mSpellBonusMap[entry] = sbe;
// also add to high ranks
DoSpellBonuses worker(mSpellBonusMap, sbe);
doForHighRanks(entry, worker);
++count;
}
while (result->NextRow());
delete result;
sLog.outString();
sLog.outString(">> Loaded %u extra spell bonus data", count);
}
bool SpellMgr::IsSpellProcEventCanTriggeredBy(SpellProcEventEntry const* spellProcEvent, uint32 EventProcFlag, SpellEntry const* procSpell, uint32 procFlags, uint32 procExtra)
{
// No extra req need
uint32 procEvent_procEx = PROC_EX_NONE;
// check prockFlags for condition
if ((procFlags & EventProcFlag) == 0)
return false;
// Always trigger for this
if (EventProcFlag & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_ON_TRAP_ACTIVATION))
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;
SpellClassOptionsEntry const* spellClassOptions = procSpell->GetSpellClassOptions();
// Check (if set) for spellFamilyName
if (spellProcEvent->spellFamilyName && (!spellClassOptions || spellProcEvent->spellFamilyName != spellClassOptions->SpellFamilyName))
return false;
}
}
// Check for extra req (if none) and hit/crit
if (procEvent_procEx == PROC_EX_NONE)
{
// Don't allow proc from periodic heal if no extra requirement is defined
if (EventProcFlag & (PROC_FLAG_ON_DO_PERIODIC | PROC_FLAG_ON_TAKE_PERIODIC) && (procExtra & PROC_EX_PERIODIC_POSITIVE))
return false;
// No extra req, so can trigger for (damage/healing present) and cast end/hit/crit
if (procExtra & (PROC_EX_CAST_END | PROC_EX_NORMAL_HIT | PROC_EX_CRITICAL_HIT))
return true;
}
else // all spells hits here only if resist/reflect/immune/evade
{
// Exist req for PROC_EX_EX_TRIGGER_ALWAYS
if (procEvent_procEx & PROC_EX_EX_TRIGGER_ALWAYS)
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;
}
void SpellMgr::LoadSpellElixirs()
{
mSpellElixirs.clear(); // need for reload case
uint32 count = 0;
// 0 1
QueryResult* result = WorldDatabase.Query("SELECT entry, mask FROM spell_elixir");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded %u spell elixir definitions", count);
return;
}
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
uint32 entry = fields[0].GetUInt32();
uint8 mask = fields[1].GetUInt8();
SpellEntry const* spellInfo = sSpellStore.LookupEntry(entry);
if (!spellInfo)
{
sLog.outErrorDb("Spell %u listed in `spell_elixir` does not exist", entry);
continue;
}
mSpellElixirs[entry] = mask;
++count;
}
while (result->NextRow());
delete result;
sLog.outString();
sLog.outString(">> Loaded %u spell elixir definitions", count);
}
struct DoSpellThreat
{
DoSpellThreat(SpellThreatMap& _threatMap) : threatMap(_threatMap), count(0) {}
void operator()(uint32 spell_id)
{
SpellThreatEntry const& ste = state->second;
// add ranks only for not filled data (spells adding flat threat are usually different for ranks)
SpellThreatMap::const_iterator spellItr = threatMap.find(spell_id);
if (spellItr == threatMap.end())
threatMap[spell_id] = ste;
// just assert that entry is not redundant
else
{
SpellThreatEntry const& r_ste = spellItr->second;
if (ste.threat == r_ste.threat && ste.multiplier == r_ste.multiplier && ste.ap_bonus == r_ste.ap_bonus)
sLog.outErrorDb("Spell %u listed in `spell_threat` as custom rank has same data as Rank 1, so redundant", spell_id);
}
}
const char* TableName() { return "spell_threat"; }
bool IsValidCustomRank(SpellThreatEntry const& ste, uint32 entry, uint32 first_id)
{
if (!ste.threat)
{
sLog.outErrorDb("Spell %u listed in `spell_threat` is not first rank (%u) in chain and has no threat", entry, first_id);
// prevent loading unexpected data
return false;
}
return true;
}
void AddEntry(SpellThreatEntry const& ste, SpellEntry const* spell)
{
threatMap[spell->Id] = ste;
// flat threat bonus and attack power bonus currently only work properly when all
// effects have same targets, otherwise, we'd need to seperate it by effect index
if (ste.threat || ste.ap_bonus != 0.f)
{
SpellEffectEntry const* spellEffect0 = spell->GetSpellEffect(EFFECT_INDEX_0);
SpellEffectEntry const* spellEffect1 = spell->GetSpellEffect(EFFECT_INDEX_1);
SpellEffectEntry const* spellEffect2 = spell->GetSpellEffect(EFFECT_INDEX_2);
if ((spellEffect1 && spellEffect1->EffectImplicitTargetA && (!spellEffect0 || spellEffect1->EffectImplicitTargetA != spellEffect0->EffectImplicitTargetA)) ||
(spellEffect2 && spellEffect2->EffectImplicitTargetA && (!spellEffect0 || spellEffect2->EffectImplicitTargetA != spellEffect0->EffectImplicitTargetA)))
sLog.outErrorDb("Spell %u listed in `spell_threat` has effects with different targets, threat may be assigned incorrectly", spell->Id);
}
++count;
}
bool HasEntry(uint32 spellId) { return threatMap.count(spellId) > 0; }
bool SetStateToEntry(uint32 spellId) { return (state = threatMap.find(spellId)) != threatMap.end(); }
SpellThreatMap& threatMap;
SpellThreatMap::const_iterator state;
uint32 count;
};
void SpellMgr::LoadSpellThreats()
{
mSpellThreatMap.clear(); // need for reload case
// 0 1 2 3
QueryResult* result = WorldDatabase.Query("SELECT entry, Threat, multiplier, ap_bonus FROM spell_threat");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> No spell threat entries loaded.");
return;
}
SpellRankHelper<SpellThreatEntry, DoSpellThreat, SpellThreatMap> rankHelper(*this, mSpellThreatMap);
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
uint32 entry = fields[0].GetUInt32();
SpellThreatEntry ste;
ste.threat = fields[1].GetUInt16();
ste.multiplier = fields[2].GetFloat();
ste.ap_bonus = fields[3].GetFloat();
rankHelper.RecordRank(ste, entry);
}
while (result->NextRow());
rankHelper.FillHigherRanks();
delete result;
sLog.outString();
sLog.outString(">> Loaded %u spell threat entries", rankHelper.worker.count);
}
bool SpellMgr::IsRankSpellDueToSpell(SpellEntry const* spellInfo_1, uint32 spellId_2) const
{
SpellEntry const* spellInfo_2 = sSpellStore.LookupEntry(spellId_2);
if (!spellInfo_1 || !spellInfo_2) return false;
if (spellInfo_1->Id == spellId_2) return false;
return GetFirstSpellInChain(spellInfo_1->Id) == GetFirstSpellInChain(spellId_2);
}
bool SpellMgr::canStackSpellRanksInSpellBook(SpellEntry const* spellInfo) const
{
if (IsPassiveSpell(spellInfo)) // ranked passive spell
return false;
if (spellInfo->powerType != POWER_MANA && spellInfo->powerType != POWER_HEALTH)
return false;
if (IsProfessionOrRidingSpell(spellInfo->Id))
return false;
if (IsSkillBonusSpell(spellInfo->Id))
return false;
// All stance spells. if any better way, change it.
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
switch(spellInfo->GetSpellFamilyName())
{
case SPELLFAMILY_PALADIN:
{
// Paladin aura Spell
if (spellEffect->Effect == SPELL_EFFECT_APPLY_AREA_AURA_RAID)
return false;
// Seal of Righteousness, 2 version of same rank
SpellClassOptionsEntry const* classOptions = spellInfo->GetSpellClassOptions();
if (classOptions && (classOptions->SpellFamilyFlags & UI64LIT(0x0000000008000000)) && spellInfo->SpellIconID == 25)
return false;
}
break;
case SPELLFAMILY_DRUID:
// Druid form Spell
if (spellEffect->Effect == SPELL_EFFECT_APPLY_AURA &&
spellEffect->EffectApplyAuraName == SPELL_AURA_MOD_SHAPESHIFT)
return false;
break;
case SPELLFAMILY_ROGUE:
// Rogue Stealth
if (spellEffect->Effect == SPELL_EFFECT_APPLY_AURA &&
spellEffect->EffectApplyAuraName == SPELL_AURA_MOD_SHAPESHIFT)
return false;
break;
}
}
return true;
}
bool SpellMgr::IsNoStackSpellDueToSpell(uint32 spellId_1, uint32 spellId_2) const
{
SpellEntry const* spellInfo_1 = sSpellStore.LookupEntry(spellId_1);
SpellEntry const* spellInfo_2 = sSpellStore.LookupEntry(spellId_2);
if (!spellInfo_1 || !spellInfo_2)
return false;
SpellClassOptionsEntry const* classOptions1 = spellInfo_1->GetSpellClassOptions();
SpellClassOptionsEntry const* classOptions2 = spellInfo_2->GetSpellClassOptions();
// Resurrection sickness
if ((spellInfo_1->Id == SPELL_ID_PASSIVE_RESURRECTION_SICKNESS) != (spellInfo_2->Id == SPELL_ID_PASSIVE_RESURRECTION_SICKNESS))
return false;
// Allow stack passive and not passive spells
if (spellInfo_1->HasAttribute(SPELL_ATTR_PASSIVE) != spellInfo_2->HasAttribute(SPELL_ATTR_PASSIVE))
return false;
// Specific spell family spells
switch(spellInfo_1->GetSpellFamilyName())
{
case SPELLFAMILY_GENERIC:
switch(spellInfo_2->GetSpellFamilyName())
{
case SPELLFAMILY_GENERIC: // same family case
{
// Thunderfury
if ((spellInfo_1->Id == 21992 && spellInfo_2->Id == 27648) ||
(spellInfo_2->Id == 21992 && spellInfo_1->Id == 27648))
return false;
// Lightning Speed (Mongoose) and Fury of the Crashing Waves (Tsunami Talisman)
if ((spellInfo_1->Id == 28093 && spellInfo_2->Id == 42084) ||
(spellInfo_2->Id == 28093 && spellInfo_1->Id == 42084))
return false;
// Soulstone Resurrection and Twisting Nether (resurrector)
if (spellInfo_1->SpellIconID == 92 && spellInfo_2->SpellIconID == 92 && (
(spellInfo_1->SpellVisual[0] == 99 && spellInfo_2->SpellVisual[0] == 0) ||
(spellInfo_2->SpellVisual[0] == 99 && spellInfo_1->SpellVisual[0] == 0)))
return false;
// Heart of the Wild, Agility and various Idol Triggers
if (spellInfo_1->SpellIconID == 240 && spellInfo_2->SpellIconID == 240)
return false;
// Personalized Weather (thunder effect should overwrite rainy aura)
if (spellInfo_1->SpellIconID == 2606 && spellInfo_2->SpellIconID == 2606)
return false;
// Mirrored Soul (FoS - Devourer) - and other Boss spells
if (spellInfo_1->SpellIconID == 3176 && spellInfo_2->SpellIconID == 3176)
return false;
// Brood Affliction: Bronze
if ((spellInfo_1->Id == 23170 && spellInfo_2->Id == 23171) ||
(spellInfo_2->Id == 23170 && spellInfo_1->Id == 23171))
return false;
// Male Shadowy Disguise
if ((spellInfo_1->Id == 32756 && spellInfo_2->Id == 38080) ||
(spellInfo_2->Id == 32756 && spellInfo_1->Id == 38080))
return false;
// Female Shadowy Disguise
if ((spellInfo_1->Id == 32756 && spellInfo_2->Id == 38081) ||
(spellInfo_2->Id == 32756 && spellInfo_1->Id == 38081))
return false;
// Cool Down (See PeriodicAuraTick())
if ((spellInfo_1->Id == 52441 && spellInfo_2->Id == 52443) ||
(spellInfo_2->Id == 52441 && spellInfo_1->Id == 52443))
return false;
// See Chapel Invisibility and See Noth Invisibility
if ((spellInfo_1->Id == 52950 && spellInfo_2->Id == 52707) ||
(spellInfo_2->Id == 52950 && spellInfo_1->Id == 52707))
return false;
// Regular and Night Elf Ghost
if ((spellInfo_1->Id == 8326 && spellInfo_2->Id == 20584) ||
(spellInfo_2->Id == 8326 && spellInfo_1->Id == 20584))
return false;
// Aura of Despair auras
if ((spellInfo_1->Id == 64848 && spellInfo_2->Id == 62692) ||
(spellInfo_2->Id == 64848 && spellInfo_1->Id == 62692))
return false;
// Blood Fury and Rage of the Unraveller
if (spellInfo_1->SpellIconID == 1662 && spellInfo_2->SpellIconID == 1662)
return false;
// Kindred Spirits
if (spellInfo_1->SpellIconID == 3559 && spellInfo_2->SpellIconID == 3559)
return false;
// Vigilance and Damage Reduction (Vigilance triggered spell)
if (spellInfo_1->SpellIconID == 2834 && spellInfo_2->SpellIconID == 2834)
return false;
// Unstable Sphere Timer and Unstable Sphere Passive
if ((spellInfo_1->Id == 50758 && spellInfo_2->Id == 50756) ||
(spellInfo_2->Id == 50758 && spellInfo_1->Id == 50756))
return false;
// Arcane Beam Periodic and Arcane Beam Visual
if ((spellInfo_1->Id == 51019 && spellInfo_2->Id == 51024) ||
(spellInfo_2->Id == 51019 && spellInfo_1->Id == 51024))
return false;
// Crystal Spike Pre-visual and Crystal Spike aura
if ((spellInfo_1->Id == 50442 && spellInfo_2->Id == 47941) ||
(spellInfo_2->Id == 50442 && spellInfo_1->Id == 47941))
return false;
break;
}
case SPELLFAMILY_MAGE:
// Arcane Intellect and Insight
if (spellInfo_2->SpellIconID == 125 && spellInfo_1->Id == 18820)
return false;
break;
case SPELLFAMILY_WARRIOR:
{
// Scroll of Protection and Defensive Stance (multi-family check)
if (spellInfo_1->SpellIconID == 276 && spellInfo_1->SpellVisual[0] == 196 && spellInfo_2->Id == 71)
return false;
// Improved Hamstring -> Hamstring (multi-family check)
if (classOptions2 && (classOptions2->SpellFamilyFlags & UI64LIT(0x2)) && spellInfo_1->Id == 23694 )
return false;
break;
}
case SPELLFAMILY_DRUID:
{
// Scroll of Stamina and Leader of the Pack (multi-family check)
if (spellInfo_1->SpellIconID == 312 && spellInfo_1->SpellVisual[0] == 216 && spellInfo_2->Id == 24932)
return false;
// Dragonmaw Illusion (multi-family check)
if (spellId_1 == 40216 && spellId_2 == 42016)
return false;
break;
}
case SPELLFAMILY_ROGUE:
{
// Garrote-Silence -> Garrote (multi-family check)
if (spellInfo_1->SpellIconID == 498 && spellInfo_1->SpellVisual[0] == 0 && spellInfo_2->SpellIconID == 498)
return false;
break;
}
case SPELLFAMILY_HUNTER:
{
// Concussive Shot and Imp. Concussive Shot (multi-family check)
if (spellInfo_1->Id == 19410 && spellInfo_2->Id == 5116)
return false;
// Improved Wing Clip -> Wing Clip (multi-family check)
if (classOptions2 && (classOptions2->SpellFamilyFlags & UI64LIT(0x40)) && spellInfo_1->Id == 19229 )
return false;
break;
}
case SPELLFAMILY_PALADIN:
{
// Unstable Currents and other -> *Sanctity Aura (multi-family check)
if (spellInfo_2->SpellIconID == 502 && spellInfo_1->SpellIconID == 502 && spellInfo_1->SpellVisual[0] == 969)
return false;
// *Band of Eternal Champion and Seal of Command(multi-family check)
if (spellId_1 == 35081 && spellInfo_2->SpellIconID == 561 && spellInfo_2->SpellVisual[0] == 7992)
return false;
// Blessing of Sanctuary (multi-family check, some from 16 spell icon spells)
if (spellInfo_1->Id == 67480 && spellInfo_2->Id == 20911)
return false;
break;
}
}
// Dragonmaw Illusion, Blood Elf Illusion, Human Illusion, Illidari Agent Illusion, Scarlet Crusade Disguise
if (spellInfo_1->SpellIconID == 1691 && spellInfo_2->SpellIconID == 1691)
return false;
break;
case SPELLFAMILY_MAGE:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_MAGE )
{
// Blizzard & Chilled (and some other stacked with blizzard spells
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x80)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x100000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x80)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x100000)) )
return false;
// Blink & Improved Blink
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x0000000000010000)) && (spellInfo_2->SpellVisual[0] == 72 && spellInfo_2->SpellIconID == 1499) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x0000000000010000)) && (spellInfo_1->SpellVisual[0] == 72 && spellInfo_1->SpellIconID == 1499) )
return false;
// Fingers of Frost effects
if (spellInfo_1->SpellIconID == 2947 && spellInfo_2->SpellIconID == 2947)
return false;
// Living Bomb & Ignite (Dots)
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x2000000000000)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x8000000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x2000000000000)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x8000000)) )
return false;
// Fireball & Pyroblast (Dots)
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x1)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x400000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x1)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x400000)) )
return false;
}
// Detect Invisibility and Mana Shield (multi-family check)
if (spellInfo_2->Id == 132 && spellInfo_1->SpellIconID == 209 && spellInfo_1->SpellVisual[0] == 968)
return false;
// Combustion and Fire Protection Aura (multi-family check)
if (spellInfo_1->Id == 11129 && spellInfo_2->SpellIconID == 33 && spellInfo_2->SpellVisual[0] == 321)
return false;
// Arcane Intellect and Insight
if (spellInfo_1->SpellIconID == 125 && spellInfo_2->Id == 18820)
return false;
break;
case SPELLFAMILY_WARLOCK:
if( classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_WARLOCK )
{
// Siphon Life and Drain Life
if ((spellInfo_1->SpellIconID == 152 && spellInfo_2->SpellIconID == 546) ||
(spellInfo_2->SpellIconID == 152 && spellInfo_1->SpellIconID == 546))
return false;
// Corruption & Seed of corruption
if ((spellInfo_1->SpellIconID == 313 && spellInfo_2->SpellIconID == 1932) ||
(spellInfo_2->SpellIconID == 313 && spellInfo_1->SpellIconID == 1932))
if (spellInfo_1->SpellVisual[0] != 0 && spellInfo_2->SpellVisual[0] != 0)
return true; // can't be stacked
// Corruption and Unstable Affliction
if ((spellInfo_1->SpellIconID == 313 && spellInfo_2->SpellIconID == 2039) ||
(spellInfo_2->SpellIconID == 313 && spellInfo_1->SpellIconID == 2039))
return false;
// (Corruption or Unstable Affliction) and (Curse of Agony or Curse of Doom)
if (((spellInfo_1->SpellIconID == 313 || spellInfo_1->SpellIconID == 2039) && (spellInfo_2->SpellIconID == 544 || spellInfo_2->SpellIconID == 91)) ||
((spellInfo_2->SpellIconID == 313 || spellInfo_2->SpellIconID == 2039) && (spellInfo_1->SpellIconID == 544 || spellInfo_1->SpellIconID == 91)))
return false;
// Shadowflame and Curse of Agony
if ((spellInfo_1->SpellIconID == 544 && spellInfo_2->SpellIconID == 3317) ||
(spellInfo_2->SpellIconID == 544 && spellInfo_1->SpellIconID == 3317))
return false;
// Shadowflame and Curse of Doom
if ((spellInfo_1->SpellIconID == 91 && spellInfo_2->SpellIconID == 3317) ||
(spellInfo_2->SpellIconID == 91 && spellInfo_1->SpellIconID == 3317))
return false;
// Metamorphosis, diff effects
if (spellInfo_1->SpellIconID == 3314 && spellInfo_2->SpellIconID == 3314)
return false;
}
// Detect Invisibility and Mana Shield (multi-family check)
if (spellInfo_1->Id == 132 && spellInfo_2->SpellIconID == 209 && spellInfo_2->SpellVisual[0] == 968)
return false;
break;
case SPELLFAMILY_WARRIOR:
if (classOptions2 && classOptions1->SpellFamilyName == SPELLFAMILY_WARRIOR )
{
// Rend and Deep Wound
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x20)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x1000000000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x20)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x1000000000)) )
return false;
// Battle Shout and Rampage
if ((spellInfo_1->SpellIconID == 456 && spellInfo_2->SpellIconID == 2006) ||
(spellInfo_2->SpellIconID == 456 && spellInfo_1->SpellIconID == 2006))
return false;
// Glyph of Revenge (triggered), and Sword and Board (triggered)
if ((spellInfo_1->SpellIconID == 856 && spellInfo_2->SpellIconID == 2780) ||
(spellInfo_2->SpellIconID == 856 && spellInfo_1->SpellIconID == 2780))
return false;
// Defensive/Berserker/Battle stance aura can not stack (needed for dummy auras)
if (((classOptions1->SpellFamilyFlags & UI64LIT(0x800000)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x800000))) ||
((classOptions2->SpellFamilyFlags & UI64LIT(0x800000)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x800000))))
return true;
}
// Hamstring -> Improved Hamstring (multi-family check)
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x2)) && spellInfo_2->Id == 23694 )
return false;
// Defensive Stance and Scroll of Protection (multi-family check)
if (spellInfo_1->Id == 71 && spellInfo_2->SpellIconID == 276 && spellInfo_2->SpellVisual[0] == 196)
return false;
// Bloodlust and Bloodthirst (multi-family check)
if (spellInfo_2->Id == 2825 && spellInfo_1->SpellIconID == 38 && spellInfo_1->SpellVisual[0] == 0)
return false;
break;
case SPELLFAMILY_PRIEST:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_PRIEST )
{
//Devouring Plague and Shadow Vulnerability
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x2000000)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x800000000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x2000000)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x800000000)))
return false;
//StarShards and Shadow Word: Pain
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x200000)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x8000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x200000)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x8000)))
return false;
// Dispersion
if ((spellInfo_1->Id == 47585 && spellInfo_2->Id == 60069) ||
(spellInfo_2->Id == 47585 && spellInfo_1->Id == 60069))
return false;
}
break;
case SPELLFAMILY_DRUID:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_DRUID )
{
//Omen of Clarity and Blood Frenzy
if ((classOptions1 && (classOptions1->SpellFamilyFlags == UI64LIT(0x0) && spellInfo_1->SpellIconID == 108) && (classOptions2->SpellFamilyFlags & UI64LIT(0x20000000000000))) ||
((classOptions2->SpellFamilyFlags == UI64LIT(0x0) && spellInfo_2->SpellIconID == 108) && (classOptions1->SpellFamilyFlags & UI64LIT(0x20000000000000))))
return false;
// Tree of Life (Shapeshift) and 34123 Tree of Life (Passive)
if ((spellId_1 == 33891 && spellId_2 == 34123) ||
(spellId_2 == 33891 && spellId_1 == 34123))
return false;
// Lifebloom and Wild Growth
if ((spellInfo_1->SpellIconID == 2101 && spellInfo_2->SpellIconID == 2864) ||
(spellInfo_2->SpellIconID == 2101 && spellInfo_1->SpellIconID == 2864))
return false;
// Innervate and Glyph of Innervate and some other spells
if (spellInfo_1->SpellIconID == 62 && spellInfo_2->SpellIconID == 62)
return false;
// Wrath of Elune and Nature's Grace
if ((spellInfo_1->Id == 16886 && spellInfo_2->Id == 46833) ||
(spellInfo_2->Id == 16886 && spellInfo_1->Id == 46833))
return false;
// Bear Rage (Feral T4 (2)) and Omen of Clarity
if ((spellInfo_1->Id == 16864 && spellInfo_2->Id == 37306) ||
(spellInfo_2->Id == 16864 && spellInfo_1->Id == 37306))
return false;
// Cat Energy (Feral T4 (2)) and Omen of Clarity
if ((spellInfo_1->Id == 16864 && spellInfo_2->Id == 37311) ||
(spellInfo_2->Id == 16864 && spellInfo_1->Id == 37311))
return false;
// Survival Instincts and Survival Instincts
if ((spellInfo_1->Id == 61336 && spellInfo_2->Id == 50322) ||
(spellInfo_2->Id == 61336 && spellInfo_1->Id == 50322))
return false;
// Savage Roar and Savage Roar (triggered)
if (spellInfo_1->SpellIconID == 2865 && spellInfo_2->SpellIconID == 2865)
return false;
// Frenzied Regeneration and Savage Defense
if ((spellInfo_1->Id == 22842 && spellInfo_2->Id == 62606) ||
(spellInfo_2->Id == 22842 && spellInfo_1->Id == 62606))
return false;
}
// Leader of the Pack and Scroll of Stamina (multi-family check)
if (spellInfo_1->Id == 24932 && spellInfo_2->SpellIconID == 312 && spellInfo_2->SpellVisual[0] == 216)
return false;
// Dragonmaw Illusion (multi-family check)
if (spellId_1 == 42016 && spellId_2 == 40216)
return false;
break;
case SPELLFAMILY_ROGUE:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_ROGUE )
{
// Master of Subtlety
if ((spellId_1 == 31665 && spellId_2 == 31666) ||
(spellId_1 == 31666 && spellId_2 == 31665))
return false;
// Sprint & Sprint (waterwalk)
if (spellInfo_1->SpellIconID == 516 && spellInfo_2->SpellIconID == 516 &&
((spellInfo_1->GetCategory() == 44 && spellInfo_2->GetCategory() == 0) ||
(spellInfo_2->GetCategory() == 44 && spellInfo_1->GetCategory() == 0)))
return false;
}
// Overkill
if (spellInfo_1->SpellIconID == 2285 && spellInfo_2->SpellIconID == 2285)
return false;
// Garrote -> Garrote-Silence (multi-family check)
if (spellInfo_1->SpellIconID == 498 && spellInfo_2->SpellIconID == 498 && spellInfo_2->SpellVisual[0] == 0)
return false;
break;
case SPELLFAMILY_HUNTER:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_HUNTER )
{
// Rapid Fire & Quick Shots
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x20)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x20000000000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x20)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x20000000000)) )
return false;
// Serpent Sting & (Immolation/Explosive Trap Effect)
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x4)) && (classOptions2->SpellFamilyFlags & UI64LIT(0x00000004000)) ||
(classOptions2->SpellFamilyFlags & UI64LIT(0x4)) && (classOptions1->SpellFamilyFlags & UI64LIT(0x00000004000)) )
return false;
// Deterrence
if (spellInfo_1->SpellIconID == 83 && spellInfo_2->SpellIconID == 83)
return false;
// Bestial Wrath
if (spellInfo_1->SpellIconID == 1680 && spellInfo_2->SpellIconID == 1680)
return false;
// Aspect of the Viper & Vicious Viper
if (spellInfo_1->SpellIconID == 2227 && spellInfo_2->SpellIconID == 2227)
return false;
}
// Wing Clip -> Improved Wing Clip (multi-family check)
if (classOptions1 && (classOptions1->SpellFamilyFlags & UI64LIT(0x40)) && spellInfo_2->Id == 19229 )
return false;
// Concussive Shot and Imp. Concussive Shot (multi-family check)
if (spellInfo_2->Id == 19410 && spellInfo_1->Id == 5116)
return false;
break;
case SPELLFAMILY_PALADIN:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_PALADIN )
{
// Paladin Seals
if (IsSealSpell(spellInfo_1) && IsSealSpell(spellInfo_2))
return true;
// Swift Retribution / Improved Devotion Aura (talents) and Paladin Auras
if ((spellInfo_1->IsFitToFamilyMask(UI64LIT(0x0), 0x00000020) && (spellInfo_2->SpellIconID == 291 || spellInfo_2->SpellIconID == 3028)) ||
(spellInfo_2->IsFitToFamilyMask(UI64LIT(0x0), 0x00000020) && (spellInfo_1->SpellIconID == 291 || spellInfo_1->SpellIconID == 3028)))
return false;
// Beacon of Light and Light's Beacon
if ((spellInfo_1->SpellIconID == 3032) && (spellInfo_2->SpellIconID == 3032))
return false;
// Concentration Aura and Improved Concentration Aura and Aura Mastery
if ((spellInfo_1->SpellIconID == 1487) && (spellInfo_2->SpellIconID == 1487))
return false;
// Seal of Corruption (caster/target parts stacking allow, other stacking checked by spell specs)
if (spellInfo_1->SpellIconID == 2292 && spellInfo_2->SpellIconID == 2292)
return false;
// Divine Sacrifice and Divine Guardian
if (spellInfo_1->SpellIconID == 3837 && spellInfo_2->SpellIconID == 3837)
return false;
// Blood Corruption, Holy Vengeance, Righteous Vengeance
if ((spellInfo_1->SpellIconID == 2292 && spellInfo_2->SpellIconID == 3025) ||
(spellInfo_2->SpellIconID == 2292 && spellInfo_1->SpellIconID == 3025))
return false;
}
// Blessing of Sanctuary (multi-family check, some from 16 spell icon spells)
if (spellInfo_2->Id == 67480 && spellInfo_1->Id == 20911)
return false;
// Combustion and Fire Protection Aura (multi-family check)
if (spellInfo_2->Id == 11129 && spellInfo_1->SpellIconID == 33 && spellInfo_1->SpellVisual[0] == 321)
return false;
// *Sanctity Aura -> Unstable Currents and other (multi-family check)
if (spellInfo_1->SpellIconID==502 && classOptions2->SpellFamilyName == SPELLFAMILY_GENERIC && spellInfo_2->SpellIconID==502 && spellInfo_2->SpellVisual[0]==969 )
return false;
// *Seal of Command and Band of Eternal Champion (multi-family check)
if (spellInfo_1->SpellIconID == 561 && spellInfo_1->SpellVisual[0] == 7992 && spellId_2 == 35081)
return false;
break;
case SPELLFAMILY_SHAMAN:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_SHAMAN )
{
// Windfury weapon
if (spellInfo_1->SpellIconID==220 && spellInfo_2->SpellIconID==220 &&
!classOptions1->IsFitToFamilyMask(classOptions2->SpellFamilyFlags))
return false;
// Ghost Wolf
if (spellInfo_1->SpellIconID == 67 && spellInfo_2->SpellIconID == 67)
return false;
// Totem of Wrath (positive/negative), ranks checked early
if (spellInfo_1->SpellIconID == 2019 && spellInfo_2->SpellIconID == 2019)
return false;
}
// Bloodlust and Bloodthirst (multi-family check)
if (spellInfo_1->Id == 2825 && spellInfo_2->SpellIconID == 38 && spellInfo_2->SpellVisual[0] == 0)
return false;
break;
case SPELLFAMILY_DEATHKNIGHT:
if (classOptions2 && classOptions2->SpellFamilyName == SPELLFAMILY_DEATHKNIGHT)
{
// Lichborne and Lichborne (triggered)
if (spellInfo_1->SpellIconID == 61 && spellInfo_2->SpellIconID == 61)
return false;
// Frost Presence and Frost Presence (triggered)
if (spellInfo_1->SpellIconID == 2632 && spellInfo_2->SpellIconID == 2632)
return false;
// Unholy Presence and Unholy Presence (triggered)
if (spellInfo_1->SpellIconID == 2633 && spellInfo_2->SpellIconID == 2633)
return false;
// Blood Presence and Blood Presence (triggered)
if (spellInfo_1->SpellIconID == 2636 && spellInfo_2->SpellIconID == 2636)
return false;
}
break;
default:
break;
}
// more generic checks
if (spellInfo_1->SpellIconID == spellInfo_2->SpellIconID &&
spellInfo_1->SpellIconID != 0 && spellInfo_2->SpellIconID != 0)
{
bool isModifier = false;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect1 = spellInfo_1->GetSpellEffect(SpellEffectIndex(i));
SpellEffectEntry const* spellEffect2 = spellInfo_2->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect1 || !spellEffect2)
continue;
if (spellEffect1->EffectApplyAuraName == SPELL_AURA_ADD_FLAT_MODIFIER ||
spellEffect1->EffectApplyAuraName == SPELL_AURA_ADD_PCT_MODIFIER ||
spellEffect2->EffectApplyAuraName == SPELL_AURA_ADD_FLAT_MODIFIER ||
spellEffect2->EffectApplyAuraName == SPELL_AURA_ADD_PCT_MODIFIER )
isModifier = true;
}
if (!isModifier)
return true;
}
if (IsRankSpellDueToSpell(spellInfo_1, spellId_2))
return true;
if (!classOptions1 || classOptions1->SpellFamilyName == 0 || !classOptions2 || classOptions2->SpellFamilyName == 0)
return false;
if (classOptions1->SpellFamilyName != classOptions2->SpellFamilyName)
return false;
bool dummy_only = true;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect1 = spellInfo_1->GetSpellEffect(SpellEffectIndex(i));
SpellEffectEntry const* spellEffect2 = spellInfo_2->GetSpellEffect(SpellEffectIndex(i));
if (!spellEffect1 && !spellEffect2)
continue;
if (!spellEffect1 || !spellEffect2)
return false;
if (spellEffect1->Effect != spellEffect2->Effect ||
spellEffect1->EffectItemType != spellEffect2->EffectItemType ||
spellEffect1->EffectMiscValue != spellEffect2->EffectMiscValue ||
spellEffect1->EffectApplyAuraName != spellEffect2->EffectApplyAuraName)
return false;
// ignore dummy only spells
if (spellEffect1->Effect && spellEffect1->Effect != SPELL_EFFECT_DUMMY && spellEffect1->EffectApplyAuraName != SPELL_AURA_DUMMY)
dummy_only = false;
}
if (dummy_only)
return false;
return true;
}
bool SpellMgr::IsProfessionOrRidingSpell(uint32 spellId)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId);
if (!spellInfo)
return false;
if (spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_1) != SPELL_EFFECT_SKILL)
return false;
uint32 skill = spellInfo->GetEffectMiscValue(EFFECT_INDEX_1);
return IsProfessionOrRidingSkill(skill);
}
bool SpellMgr::IsProfessionSpell(uint32 spellId)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId);
if (!spellInfo)
return false;
if (spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_1) != SPELL_EFFECT_SKILL)
return false;
uint32 skill = spellInfo->GetEffectMiscValue(EFFECT_INDEX_1);
return IsProfessionSkill(skill);
}
bool SpellMgr::IsPrimaryProfessionSpell(uint32 spellId)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spellId);
if (!spellInfo)
return false;
if (spellInfo->GetSpellEffectIdByIndex(EFFECT_INDEX_1) != SPELL_EFFECT_SKILL)
return false;
uint32 skill = spellInfo->GetEffectMiscValue(EFFECT_INDEX_1);
return IsPrimaryProfessionSkill(skill);
}
uint32 SpellMgr::GetProfessionSpellMinLevel(uint32 spellId)
{
uint32 s2l[8][3] =
{
// 0 - gather 1 - non-gather 2 - fish
/*0*/ { 0, 5, 5 },
/*1*/ { 0, 5, 5 },
/*2*/ { 0, 10, 10 },
/*3*/ { 10, 20, 10 },
/*4*/ { 25, 35, 10 },
/*5*/ { 40, 50, 10 },
/*6*/ { 55, 65, 10 },
/*7*/ { 75, 75, 10 },
};
uint32 rank = GetSpellRank(spellId);
if (rank >= 8)
return 0;
SkillLineAbilityMapBounds bounds = GetSkillLineAbilityMapBounds(spellId);
if (bounds.first == bounds.second)
return 0;
switch (bounds.first->second->skillId)
{
case SKILL_FISHING:
return s2l[rank][2];
case SKILL_HERBALISM:
case SKILL_MINING:
case SKILL_SKINNING:
return s2l[rank][0];
default:
return s2l[rank][1];
}
}
bool SpellMgr::IsPrimaryProfessionFirstRankSpell(uint32 spellId) const
{
return IsPrimaryProfessionSpell(spellId) && GetSpellRank(spellId) == 1;
}
bool SpellMgr::IsSkillBonusSpell(uint32 spellId) const
{
SkillLineAbilityMapBounds bounds = GetSkillLineAbilityMapBounds(spellId);
for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
{
SkillLineAbilityEntry const* pAbility = _spell_idx->second;
if (!pAbility || pAbility->learnOnGetSkill != ABILITY_LEARNED_ON_GET_PROFESSION_SKILL)
continue;
if (pAbility->req_skill_value > 0)
return true;
}
return false;
}
SpellEntry const* SpellMgr::SelectAuraRankForLevel(SpellEntry const* spellInfo, uint32 level) const
{
// fast case
if (level + 10 >= spellInfo->GetSpellLevel())
return spellInfo;
// ignore selection for passive spells
if (IsPassiveSpell(spellInfo))
return spellInfo;
bool needRankSelection = false;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
// for simple aura in check apply to any non caster based targets, in rank search mode to any explicit targets
if (((spellEffect->Effect == SPELL_EFFECT_APPLY_AURA &&
(IsExplicitPositiveTarget(spellEffect->EffectImplicitTargetA) ||
IsAreaEffectPossitiveTarget(Targets(spellEffect->EffectImplicitTargetA)))) ||
spellEffect->Effect == SPELL_EFFECT_APPLY_AREA_AURA_PARTY ||
spellEffect->Effect == SPELL_EFFECT_APPLY_AREA_AURA_RAID) &&
IsPositiveEffect(spellInfo, SpellEffectIndex(i)))
{
needRankSelection = true;
break;
}
}
// not required (rank check more slow so check it here)
if (!needRankSelection || GetSpellRank(spellInfo->Id) == 0)
return spellInfo;
for (uint32 nextSpellId = spellInfo->Id; nextSpellId != 0; nextSpellId = GetPrevSpellInChain(nextSpellId))
{
SpellEntry const* nextSpellInfo = sSpellStore.LookupEntry(nextSpellId);
if (!nextSpellInfo)
break;
// if found appropriate level
if (level + 10 >= spellInfo->GetSpellLevel())
return nextSpellInfo;
// one rank less then
}
// not found
return NULL;
}
typedef UNORDERED_MAP<uint32, uint32> AbilitySpellPrevMap;
static void LoadSpellChains_AbilityHelper(SpellChainMap& chainMap, AbilitySpellPrevMap const& prevRanks, uint32 spell_id, uint32 prev_id, uint32 deep = 30)
{
// spell already listed in chains store
SpellChainMap::const_iterator chain_itr = chainMap.find(spell_id);
if (chain_itr != chainMap.end())
{
MANGOS_ASSERT(chain_itr->second.prev == prev_id && "LoadSpellChains_AbilityHelper: Conflicting data in talents or spell abilities dbc");
return;
}
// prev rank listed in main chain table (can fill correct data directly)
SpellChainMap::const_iterator prev_chain_itr = chainMap.find(prev_id);
if (prev_chain_itr != chainMap.end())
{
SpellChainNode node;
node.prev = prev_id;
node.first = prev_chain_itr->second.first;
node.rank = prev_chain_itr->second.rank + 1;
node.req = 0;
chainMap[spell_id] = node;
return;
}
// prev spell not listed in prev ranks store, so it first rank
AbilitySpellPrevMap::const_iterator prev_itr = prevRanks.find(prev_id);
if (prev_itr == prevRanks.end())
{
SpellChainNode prev_node;
prev_node.prev = 0;
prev_node.first = prev_id;
prev_node.rank = 1;
prev_node.req = 0;
chainMap[prev_id] = prev_node;
SpellChainNode node;
node.prev = prev_id;
node.first = prev_id;
node.rank = 2;
node.req = 0;
chainMap[spell_id] = node;
return;
}
if (deep == 0)
{
MANGOS_ASSERT(false && "LoadSpellChains_AbilityHelper: Infinity cycle in spell ability data");
return;
}
// prev rank listed, so process it first
LoadSpellChains_AbilityHelper(chainMap, prevRanks, prev_id, prev_itr->second, deep - 1);
// prev rank must be listed now
prev_chain_itr = chainMap.find(prev_id);
if (prev_chain_itr == chainMap.end())
return;
SpellChainNode node;
node.prev = prev_id;
node.first = prev_chain_itr->second.first;
node.rank = prev_chain_itr->second.rank + 1;
node.req = 0;
chainMap[spell_id] = node;
}
void SpellMgr::LoadSpellChains()
{
mSpellChains.clear(); // need for reload case
mSpellChainsNext.clear(); // need for reload case
// load known data for talents
for (unsigned int i = 0; i < sTalentStore.GetNumRows(); ++i)
{
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
if (!talentInfo)
continue;
// not add ranks for 1 ranks talents (if exist non ranks spells then it will included in table data)
if (!talentInfo->RankID[1])
continue;
for (int j = 0; j < MAX_TALENT_RANK; ++j)
{
uint32 spell_id = talentInfo->RankID[j];
if (!spell_id)
continue;
if (!sSpellStore.LookupEntry(spell_id))
{
// sLog.outErrorDb("Talent %u not exist as spell",spell_id);
continue;
}
SpellChainNode node;
node.prev = (j > 0) ? talentInfo->RankID[j - 1] : 0;
node.first = talentInfo->RankID[0];
node.rank = j + 1;
node.req = 0;
mSpellChains[spell_id] = node;
}
}
// load known data from spell abilities
{
// we can calculate ranks only after full data generation
AbilitySpellPrevMap prevRanks;
for (SkillLineAbilityMap::const_iterator ab_itr = mSkillLineAbilityMap.begin(); ab_itr != mSkillLineAbilityMap.end(); ++ab_itr)
{
uint32 spell_id = ab_itr->first;
// skip GM/test/internal spells.begin Its not have ranks anyway
if (ab_itr->second->skillId == SKILL_INTERNAL)
continue;
// some forward spells not exist and can be ignored (some outdated data)
SpellEntry const* spell_entry = sSpellStore.LookupEntry(spell_id);
if (!spell_entry) // no cases
continue;
// ignore spell without forwards (non ranked or missing info in skill abilities)
uint32 forward_id = ab_itr->second->forward_spellid;
if (!forward_id)
continue;
// some forward spells not exist and can be ignored (some outdated data)
SpellEntry const* forward_entry = sSpellStore.LookupEntry(forward_id);
if (!forward_entry)
continue;
// some forward spells still exist but excluded from real use as ranks and not listed in skill abilities now
SkillLineAbilityMapBounds bounds = mSkillLineAbilityMap.equal_range(forward_id);
if (bounds.first == bounds.second)
continue;
// spell already listed in chains store
SpellChainMap::const_iterator chain_itr = mSpellChains.find(forward_id);
if (chain_itr != mSpellChains.end())
{
MANGOS_ASSERT(chain_itr->second.prev == spell_id && "Conflicting data in talents or spell abilities dbc");
continue;
}
// spell already listed in prev ranks store
AbilitySpellPrevMap::const_iterator prev_itr = prevRanks.find(forward_id);
if (prev_itr != prevRanks.end())
{
MANGOS_ASSERT(prev_itr->second == spell_id && "Conflicting data in talents or spell abilities dbc");
continue;
}
// prev rank listed in main chain table (can fill correct data directly)
SpellChainMap::const_iterator prev_chain_itr = mSpellChains.find(spell_id);
if (prev_chain_itr != mSpellChains.end())
{
SpellChainNode node;
node.prev = spell_id;
node.first = prev_chain_itr->second.first;
node.rank = prev_chain_itr->second.rank + 1;
node.req = 0;
mSpellChains[forward_id] = node;
continue;
}
// need temporary store for later rank calculation
prevRanks[forward_id] = spell_id;
}
while (!prevRanks.empty())
{
uint32 spell_id = prevRanks.begin()->first;
uint32 prev_id = prevRanks.begin()->second;
prevRanks.erase(prevRanks.begin());
LoadSpellChains_AbilityHelper(mSpellChains, prevRanks, spell_id, prev_id);
}
}
// load custom case
QueryResult* result = WorldDatabase.Query("SELECT spell_id, prev_spell, first_spell, rank, req_spell FROM spell_chain");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded 0 spell chain records");
sLog.outErrorDb("`spell_chains` table is empty!");
return;
}
uint32 dbc_count = mSpellChains.size();
uint32 new_count = 0;
uint32 req_count = 0;
BarGoLink bar(result->GetRowCount());
do
{
bar.step();
Field* fields = result->Fetch();
uint32 spell_id = fields[0].GetUInt32();
SpellChainNode node;
node.prev = fields[1].GetUInt32();
node.first = fields[2].GetUInt32();
node.rank = fields[3].GetUInt8();
node.req = fields[4].GetUInt32();
if (!sSpellStore.LookupEntry(spell_id))
{
sLog.outErrorDb("Spell %u listed in `spell_chain` does not exist", spell_id);
continue;
}
SpellChainMap::iterator chain_itr = mSpellChains.find(spell_id);
if (chain_itr != mSpellChains.end())
{
if (chain_itr->second.rank != node.rank)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` expected rank %u by DBC data.",
spell_id, node.prev, node.first, node.rank, node.req, chain_itr->second.rank);
continue;
}
if (chain_itr->second.prev != node.prev)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` expected prev %u by DBC data.",
spell_id, node.prev, node.first, node.rank, node.req, chain_itr->second.prev);
continue;
}
if (chain_itr->second.first != node.first)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` expected first %u by DBC data.",
spell_id, node.prev, node.first, node.rank, node.req, chain_itr->second.first);
continue;
}
// update req field by table data
if (node.req)
{
chain_itr->second.req = node.req;
++req_count;
continue;
}
// in other case redundant
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) already added (talent or spell ability with forward) and non need in `spell_chain`",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
if (node.prev != 0 && !sSpellStore.LookupEntry(node.prev))
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has nonexistent previous rank spell.",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
if (!sSpellStore.LookupEntry(node.first))
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has not existing first rank spell.",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
// check basic spell chain data integrity (note: rank can be equal 0 or 1 for first/single spell)
if ((spell_id == node.first) != (node.rank <= 1) ||
(spell_id == node.first) != (node.prev == 0) ||
(node.rank <= 1) != (node.prev == 0))
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has not compatible chain data.",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
if (node.req != 0 && !sSpellStore.LookupEntry(node.req))
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has not existing required spell.",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
// talents not required data in spell chain for work, but must be checked if present for integrity
if (TalentSpellPos const* pos = GetTalentSpellPos(spell_id))
{
if (node.rank != pos->rank + 1)
{
sLog.outErrorDb("Talent %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has wrong rank.",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
if (TalentEntry const* talentEntry = sTalentStore.LookupEntry(pos->talent_id))
{
if (node.first != talentEntry->RankID[0])
{
sLog.outErrorDb("Talent %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has wrong first rank spell.",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
if (node.rank > 1 && node.prev != talentEntry->RankID[node.rank - 1 - 1])
{
sLog.outErrorDb("Talent %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has wrong prev rank spell.",
spell_id, node.prev, node.first, node.rank, node.req);
continue;
}
/*if(node.req!=talentEntry->DependsOnSpell)
{
sLog.outErrorDb("Talent %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has wrong required spell.",
spell_id,node.prev,node.first,node.rank,node.req);
continue;
}*/
}
}
// removed ranks often still listed as forward in skill abilities but not listed as spell in it
if (node.prev)
{
bool skip = false;
// some forward spells still exist but excluded from real use as ranks and not listed in skill abilities now
SkillLineAbilityMapBounds bounds = mSkillLineAbilityMap.equal_range(spell_id);
if (bounds.first == bounds.second)
{
SkillLineAbilityMapBounds prev_bounds = mSkillLineAbilityMap.equal_range(node.prev);
for (SkillLineAbilityMap::const_iterator ab_itr = prev_bounds.first; ab_itr != prev_bounds.second; ++ab_itr)
{
// spell listed as forward and not listed as ability
// this is marker for removed ranks
if (ab_itr->second->forward_spellid == spell_id)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` is removed rank by DBC data.",
spell_id, node.prev, node.first, node.rank, node.req);
skip = true;
break;
}
}
}
if (skip)
continue;
}
mSpellChains[spell_id] = node;
++new_count;
}
while (result->NextRow());
delete result;
// additional integrity checks
for (SpellChainMap::const_iterator i = mSpellChains.begin(); i != mSpellChains.end(); ++i)
{
if (i->second.prev)
{
SpellChainMap::const_iterator i_prev = mSpellChains.find(i->second.prev);
if (i_prev == mSpellChains.end())
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has not found previous rank spell in table.",
i->first, i->second.prev, i->second.first, i->second.rank, i->second.req);
}
else if (i_prev->second.first != i->second.first)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has different first spell in chain compared to previous rank spell (prev: %u, first: %u, rank: %d, req: %u).",
i->first, i->second.prev, i->second.first, i->second.rank, i->second.req,
i_prev->second.prev, i_prev->second.first, i_prev->second.rank, i_prev->second.req);
}
else if (i_prev->second.rank + 1 != i->second.rank)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has different rank compared to previous rank spell (prev: %u, first: %u, rank: %d, req: %u).",
i->first, i->second.prev, i->second.first, i->second.rank, i->second.req,
i_prev->second.prev, i_prev->second.first, i_prev->second.rank, i_prev->second.req);
}
}
if (i->second.req)
{
SpellChainMap::const_iterator i_req = mSpellChains.find(i->second.req);
if (i_req == mSpellChains.end())
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has not found required rank spell in table.",
i->first, i->second.prev, i->second.first, i->second.rank, i->second.req);
}
else if (i_req->second.first == i->second.first)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has required rank spell from same spell chain (prev: %u, first: %u, rank: %d, req: %u).",
i->first, i->second.prev, i->second.first, i->second.rank, i->second.req,
i_req->second.prev, i_req->second.first, i_req->second.rank, i_req->second.req);
}
else if (i_req->second.req)
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has required rank spell with required spell (prev: %u, first: %u, rank: %d, req: %u).",
i->first, i->second.prev, i->second.first, i->second.rank, i->second.req,
i_req->second.prev, i_req->second.first, i_req->second.rank, i_req->second.req);
}
}
}
// fill next rank cache
for (SpellChainMap::const_iterator i = mSpellChains.begin(); i != mSpellChains.end(); ++i)
{
uint32 spell_id = i->first;
SpellChainNode const& node = i->second;
if (node.prev)
mSpellChainsNext.insert(SpellChainMapNext::value_type(node.prev, spell_id));
if (node.req)
mSpellChainsNext.insert(SpellChainMapNext::value_type(node.req, spell_id));
}
// check single rank redundant cases (single rank talents/spell abilities not added by default so this can be only custom cases)
for (SpellChainMap::const_iterator i = mSpellChains.begin(); i != mSpellChains.end(); ++i)
{
// skip non-first ranks, and spells with additional reqs
if (i->second.rank > 1 || i->second.req)
continue;
if (mSpellChainsNext.find(i->first) == mSpellChainsNext.end())
{
sLog.outErrorDb("Spell %u (prev: %u, first: %u, rank: %d, req: %u) listed in `spell_chain` has single rank data, so redundant.",
i->first, i->second.prev, i->second.first, i->second.rank, i->second.req);
}
}
sLog.outString();
sLog.outString(">> Loaded %u spell chain records (%u from DBC data with %u req field updates, and %u loaded from table)", dbc_count + new_count, dbc_count, req_count, new_count);
}
void SpellMgr::LoadSpellLearnSkills()
{
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;
BarGoLink bar(sSpellStore.GetNumRows());
for (uint32 spell = 0; spell < sSpellStore.GetNumRows(); ++spell)
{
bar.step();
SpellEntry const* entry = sSpellStore.LookupEntry(spell);
if (!entry)
continue;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = entry->GetSpellEffect(SpellEffectIndex(i));
if (!spellEffect)
continue;
if (spellEffect->Effect == SPELL_EFFECT_SKILL)
{
SpellLearnSkillNode dbc_node;
dbc_node.skill = spellEffect->EffectMiscValue;
dbc_node.step = entry->CalculateSimpleValue(SpellEffectIndex(i));
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();
sLog.outString(">> Loaded %u Spell Learn Skills from DBC", dbc_count);
}
void SpellMgr::LoadSpellLearnSpells()
{
mSpellLearnSpells.clear(); // need for reload case
// 0 1 2
QueryResult* result = WorldDatabase.Query("SELECT entry, SpellID, Active FROM spell_learn_spell");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded 0 spell learn spells");
sLog.outErrorDb("`spell_learn_spell` table is empty!");
return;
}
uint32 count = 0;
BarGoLink bar(result->GetRowCount());
do
{
bar.step();
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 (!sSpellStore.LookupEntry(spell_id))
{
sLog.outErrorDb("Spell %u listed in `spell_learn_spell` does not exist", spell_id);
continue;
}
if (!sSpellStore.LookupEntry(node.spell))
{
sLog.outErrorDb("Spell %u listed in `spell_learn_spell` learning nonexistent 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());
delete result;
// 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 < sSpellStore.GetNumRows(); ++spell)
{
SpellEntry const* entry = sSpellStore.LookupEntry(spell);
if (!entry)
continue;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = entry->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
if(spellEffect->Effect == SPELL_EFFECT_LEARN_SPELL)
{
SpellLearnSpellNode dbc_node;
dbc_node.spell = spellEffect->EffectTriggerSpell;
dbc_node.active = true; // all dbc based learned spells is active (show in spell book or hide by client itself)
// ignore learning nonexistent spells (broken/outdated/or generic learning spell 483
if (!sSpellStore.LookupEntry(dbc_node.spell))
continue;
// talent or passive spells or skill-step spells auto-casted and not need dependent learning,
// pet teaching spells don't must be dependent learning (casted)
// other required explicit dependent learning
dbc_node.autoLearned = spellEffect->EffectImplicitTargetA==TARGET_PET || GetTalentSpellCost(spell) > 0 || IsPassiveSpell(entry) || IsSpellHaveEffect(entry,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();
sLog.outString(">> Loaded %u spell learn spells + %u found in DBC", count, dbc_count);
}
void SpellMgr::LoadSpellScriptTarget()
{
sSpellScriptTargetStorage.Load();
// Check content
for (SQLMultiStorage::SQLSIterator<SpellTargetEntry> itr = sSpellScriptTargetStorage.getDataBegin<SpellTargetEntry>(); itr < sSpellScriptTargetStorage.getDataEnd<SpellTargetEntry>(); ++itr)
{
SpellEntry const* spellProto = sSpellStore.LookupEntry(itr->spellId);
if (!spellProto)
{
sLog.outErrorDb("Table `spell_script_target`: spellId %u listed for TargetEntry %u does not exist.", itr->spellId, itr->targetEntry);
sSpellScriptTargetStorage.EraseEntry(itr->spellId);
continue;
}
bool targetfound = false;
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellProto->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
if( spellEffect->EffectImplicitTargetA == TARGET_SCRIPT ||
spellEffect->EffectImplicitTargetB == TARGET_SCRIPT ||
spellEffect->EffectImplicitTargetA == TARGET_SCRIPT_COORDINATES ||
spellEffect->EffectImplicitTargetB == TARGET_SCRIPT_COORDINATES ||
spellEffect->EffectImplicitTargetA == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT ||
spellEffect->EffectImplicitTargetB == TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT ||
spellEffect->EffectImplicitTargetA == TARGET_AREAEFFECT_INSTANT ||
spellEffect->EffectImplicitTargetB == TARGET_AREAEFFECT_INSTANT ||
spellEffect->EffectImplicitTargetA == TARGET_AREAEFFECT_CUSTOM ||
spellEffect->EffectImplicitTargetB == TARGET_AREAEFFECT_CUSTOM ||
spellEffect->EffectImplicitTargetA == TARGET_AREAEFFECT_GO_AROUND_SOURCE ||
spellEffect->EffectImplicitTargetB == TARGET_AREAEFFECT_GO_AROUND_SOURCE ||
spellEffect->EffectImplicitTargetA == TARGET_AREAEFFECT_GO_AROUND_DEST ||
spellEffect->EffectImplicitTargetB == TARGET_AREAEFFECT_GO_AROUND_DEST)
{
targetfound = true;
break;
}
}
if (!targetfound)
{
sLog.outErrorDb("Table `spell_script_target`: spellId %u listed for TargetEntry %u does not have any implicit target TARGET_SCRIPT(38) or TARGET_SCRIPT_COORDINATES (46) or TARGET_FOCUS_OR_SCRIPTED_GAMEOBJECT (40).", itr->spellId, itr->targetEntry);
sSpellScriptTargetStorage.EraseEntry(itr->spellId);
continue;
}
if (itr->type >= MAX_SPELL_TARGET_TYPE)
{
sLog.outErrorDb("Table `spell_script_target`: target type %u for TargetEntry %u is incorrect.", itr->type, itr->targetEntry);
sSpellScriptTargetStorage.EraseEntry(itr->spellId);
continue;
}
// Checks by target type
switch (itr->type)
{
case SPELL_TARGET_TYPE_GAMEOBJECT:
{
if (!itr->targetEntry)
break;
if (!sGOStorage.LookupEntry<GameObjectInfo>(itr->targetEntry))
{
sLog.outErrorDb("Table `spell_script_target`: gameobject template entry %u does not exist.", itr->targetEntry);
sSpellScriptTargetStorage.EraseEntry(itr->spellId);
continue;
}
break;
}
default:
if (!itr->targetEntry)
{
sLog.outErrorDb("Table `spell_script_target`: target entry == 0 for not GO target type (%u).", itr->type);
sSpellScriptTargetStorage.EraseEntry(itr->spellId);
continue;
}
if (const CreatureInfo* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(itr->targetEntry))
{
if (itr->spellId == 30427 && !cInfo->SkinLootId)
{
sLog.outErrorDb("Table `spell_script_target` has creature %u as a target of spellid 30427, but this creature has no skinlootid. Gas extraction will not work!", cInfo->Entry);
sSpellScriptTargetStorage.EraseEntry(itr->spellId);
continue;
}
}
else
{
sLog.outErrorDb("Table `spell_script_target`: creature template entry %u does not exist.", itr->targetEntry);
sSpellScriptTargetStorage.EraseEntry(itr->spellId);
continue;
}
break;
}
}
// Check all spells
if (!sLog.HasLogFilter(LOG_FILTER_DB_STRICTED_CHECK))
{
for (uint32 i = 1; i < sSpellStore.GetNumRows(); ++i)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(i);
if (!spellInfo)
continue;
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
for (int j = 0; j < MAX_EFFECT_INDEX; ++j)
{
if (spellEffect && (spellEffect->EffectImplicitTargetA == TARGET_SCRIPT ||
(spellEffect->EffectImplicitTargetA != TARGET_SELF && spellEffect->EffectImplicitTargetB == TARGET_SCRIPT)))
{
SQLMultiStorage::SQLMSIteratorBounds<SpellTargetEntry> bounds = sSpellScriptTargetStorage.getBounds<SpellTargetEntry>(i);
if (bounds.first == bounds.second)
{
sLog.outErrorDb("Spell (ID: %u) has effect EffectImplicitTargetA/EffectImplicitTargetB = %u (TARGET_SCRIPT), but does not have record in `spell_script_target`", spellInfo->Id, TARGET_SCRIPT);
break; // effects of spell
}
}
}
}
}
}
void SpellMgr::LoadSpellPetAuras()
{
mSpellPetAuraMap.clear(); // need for reload case
uint32 count = 0;
// 0 1 2 3
QueryResult* result = WorldDatabase.Query("SELECT spell, effectId, pet, aura FROM spell_pet_auras");
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded %u spell pet auras", count);
return;
}
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
uint32 spell = fields[0].GetUInt32();
SpellEffectIndex eff = SpellEffectIndex(fields[1].GetUInt32());
uint32 pet = fields[2].GetUInt32();
uint32 aura = fields[3].GetUInt32();
if (eff >= MAX_EFFECT_INDEX)
{
sLog.outErrorDb("Spell %u listed in `spell_pet_auras` with wrong spell effect index (%u)", spell, eff);
continue;
}
SpellPetAuraMap::iterator itr = mSpellPetAuraMap.find((spell << 8) + eff);
if (itr != mSpellPetAuraMap.end())
{
itr->second.AddAura(pet, aura);
}
else
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell);
if (!spellInfo)
{
sLog.outErrorDb("Spell %u listed in `spell_pet_auras` does not exist", spell);
continue;
}
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(eff);
if (!spellEffect || spellEffect->Effect != SPELL_EFFECT_DUMMY &&
(spellEffect->Effect != SPELL_EFFECT_APPLY_AURA ||
spellEffect->EffectApplyAuraName != SPELL_AURA_DUMMY))
{
sLog.outError("Spell %u listed in `spell_pet_auras` does not have dummy aura or dummy effect", spell);
continue;
}
SpellEntry const* spellInfo2 = sSpellStore.LookupEntry(aura);
if (!spellInfo2)
{
sLog.outErrorDb("Aura %u listed in `spell_pet_auras` does not exist", aura);
continue;
}
PetAura pa(pet, aura, spellEffect->EffectImplicitTargetA == TARGET_PET, spellEffect->CalculateSimpleValue());
mSpellPetAuraMap[(spell<<8) + eff] = pa;
}
++count;
}
while (result->NextRow());
delete result;
sLog.outString();
sLog.outString(">> Loaded %u spell pet auras", count);
}
void SpellMgr::LoadPetLevelupSpellMap()
{
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 (uint32 j = 0; j < sSkillLineAbilityStore.GetNumRows(); ++j)
{
SkillLineAbilityEntry const* skillLine = sSkillLineAbilityStore.LookupEntry(j);
if (!skillLine)
continue;
if (skillLine->skillId != creatureFamily->skillLine[0] &&
(!creatureFamily->skillLine[1] || skillLine->skillId != creatureFamily->skillLine[1]))
continue;
if (skillLine->learnOnGetSkill != ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL)
continue;
SpellEntry const* spell = sSpellStore.LookupEntry(skillLine->spellId);
if (!spell) // not exist
continue;
PetLevelupSpellSet& spellSet = mPetLevelupSpellMap[creatureFamily->ID];
if (spellSet.empty())
++family_count;
spellSet.insert(PetLevelupSpellSet::value_type(spell->GetSpellLevel(),spell->Id));
count++;
}
}
sLog.outString();
sLog.outString(">> Loaded %u pet levelup and default spells for %u families", count, family_count);
}
bool SpellMgr::LoadPetDefaultSpells_helper(CreatureInfo const* cInfo, PetDefaultSpellsEntry& petDefSpells)
{
// skip empty list;
bool have_spell = false;
for (int 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 ? GetPetLevelupSpellList(cInfo->family) : NULL)
{
for (int 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 (int j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
{
if (petDefSpells.spellid[j])
{
have_spell = true;
break;
}
}
return have_spell;
}
void SpellMgr::LoadPetDefaultSpells()
{
MANGOS_ASSERT(MAX_CREATURE_SPELL_DATA_SLOT <= CREATURE_MAX_SPELLS);
mPetDefaultSpellsMap.clear();
uint32 countCreature = 0;
uint32 countData = 0;
for (uint32 i = 0; i < sCreatureStorage.GetMaxEntry(); ++i)
{
CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(i);
if (!cInfo)
continue;
if (!cInfo->PetSpellDataId)
continue;
// for creature with PetSpellDataId get default pet spells from dbc
CreatureSpellDataEntry const* spellDataEntry = sCreatureSpellDataStore.LookupEntry(cInfo->PetSpellDataId);
if (!spellDataEntry)
continue;
int32 petSpellsId = -(int32)cInfo->PetSpellDataId;
PetDefaultSpellsEntry petDefSpells;
for (int j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
petDefSpells.spellid[j] = spellDataEntry->spellId[j];
if (LoadPetDefaultSpells_helper(cInfo, petDefSpells))
{
mPetDefaultSpellsMap[petSpellsId] = petDefSpells;
++countData;
}
}
// different summon spells
for (uint32 i = 0; i < sSpellStore.GetNumRows(); ++i)
{
SpellEntry const* spellEntry = sSpellStore.LookupEntry(i);
if (!spellEntry)
continue;
for (int k = 0; k < MAX_EFFECT_INDEX; ++k)
{
SpellEffectEntry const* spellEffect = spellEntry->GetSpellEffect(SpellEffectIndex(k));
if(!spellEffect)
continue;
if(spellEffect->Effect==SPELL_EFFECT_SUMMON || spellEffect->Effect==SPELL_EFFECT_SUMMON_PET)
{
uint32 creature_id = spellEffect->EffectMiscValue;
CreatureInfo const* cInfo = sCreatureStorage.LookupEntry<CreatureInfo>(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;
if (CreatureTemplateSpells const* templateSpells = sCreatureTemplateSpellsStorage.LookupEntry<CreatureTemplateSpells>(cInfo->Entry))
for (int j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
petDefSpells.spellid[j] = templateSpells->spells[j];
if (LoadPetDefaultSpells_helper(cInfo, petDefSpells))
{
mPetDefaultSpellsMap[petSpellsId] = petDefSpells;
++countCreature;
}
}
}
}
sLog.outString();
sLog.outString(">> Loaded addition spells for %u pet spell data entries and %u summonable creature templates", countData, countCreature);
}
/// Some checks for spells, to prevent adding deprecated/broken spells for trainers, spell book, etc
bool SpellMgr::IsSpellValid(SpellEntry const* spellInfo, Player* pl, bool msg)
{
// not exist
if (!spellInfo)
return false;
bool need_check_reagents = false;
// check effects
for (int i = 0; i < MAX_EFFECT_INDEX; ++i)
{
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(SpellEffectIndex(i));
if(!spellEffect)
continue;
switch(spellEffect->Effect)
{
case SPELL_EFFECT_NONE:
continue;
// craft spell for crafting nonexistent item (break client recipes list show)
case SPELL_EFFECT_CREATE_ITEM:
case SPELL_EFFECT_CREATE_ITEM_2:
{
if (spellEffect->EffectItemType == 0)
{
// skip auto-loot crafting spells, its not need explicit item info (but have special fake items sometime)
if (!IsLootCraftingSpell(spellInfo))
{
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 IsLootCraftingSpell case but fake item must exist anyway
else if (!ObjectMgr::GetItemPrototype( spellEffect->EffectItemType ))
{
if (msg)
{
if(pl)
ChatHandler(pl).PSendSysMessage("Craft spell %u create item (Entry: %u) but item does not exist in item_template.",spellInfo->Id,spellEffect->EffectItemType);
else
sLog.outErrorDb("Craft spell %u create item (Entry: %u) but item does not exist in item_template.",spellInfo->Id,spellEffect->EffectItemType);
}
return false;
}
need_check_reagents = true;
break;
}
case SPELL_EFFECT_LEARN_SPELL:
{
SpellEntry const* spellInfo2 = sSpellStore.LookupEntry(spellEffect->EffectTriggerSpell);
if( !IsSpellValid(spellInfo2,pl,msg) )
{
if (msg)
{
if(pl)
ChatHandler(pl).PSendSysMessage("Spell %u learn to broken spell %u, and then...",spellInfo->Id,spellEffect->EffectTriggerSpell);
else
sLog.outErrorDb("Spell %u learn to invalid spell %u, and then...",spellInfo->Id,spellEffect->EffectTriggerSpell);
}
return false;
}
break;
}
}
}
if (need_check_reagents)
{
SpellReagentsEntry const* spellReagents = spellInfo->GetSpellReagents();
if(spellReagents)
{
for(int j = 0; j < MAX_SPELL_REAGENTS; ++j)
{
if(spellReagents->Reagent[j] > 0 && !ObjectMgr::GetItemPrototype( spellReagents->Reagent[j] ))
{
if(msg)
{
if(pl)
ChatHandler(pl).PSendSysMessage("Craft spell %u requires reagent item (Entry: %u) but item does not exist in item_template.",spellInfo->Id,spellReagents->Reagent[j]);
else
sLog.outErrorDb("Craft spell %u requires reagent item (Entry: %u) but item does not exist in item_template.",spellInfo->Id,spellReagents->Reagent[j]);
}
return false;
}
}
}
}
return true;
}
void SpellMgr::LoadSpellAreas()
{
mSpellAreaMap.clear(); // need for reload case
mSpellAreaForQuestMap.clear();
mSpellAreaForActiveQuestMap.clear();
mSpellAreaForQuestEndMap.clear();
mSpellAreaForAuraMap.clear();
uint32 count = 0;
// 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)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outString(">> Loaded %u spell area requirements", count);
return;
}
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
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 (!sSpellStore.LookupEntry(spell))
{
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)
{
SpellEntry const* spellInfo = sSpellStore.LookupEntry(abs(spellArea.auraSpell));
if (!spellInfo)
{
sLog.outErrorDb("Spell %u listed in `spell_area` have wrong aura spell (%u) requirement", spell, abs(spellArea.auraSpell));
continue;
}
SpellEffectEntry const* spellEffect = spellInfo->GetSpellEffect(EFFECT_INDEX_0);
if (!spellEffect)
continue;
switch (spellEffect->EffectApplyAuraName)
{
case SPELL_AURA_DUMMY:
case SPELL_AURA_PHASE:
case SPELL_AURA_GHOST:
break;
default:
sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell requirement (%u) without dummy/phase/ghost aura in effect 0", 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());
delete result;
sLog.outString();
sLog.outString(">> Loaded %u spell area requirements", count);
}
SpellCastResult SpellMgr::GetSpellAllowedInLocationError(SpellEntry const* spellInfo, uint32 map_id, uint32 zone_id, uint32 area_id, Player const* player)
{
// normal case
int32 areaGroupId = spellInfo->GetAreaGroupId();
if (areaGroupId > 0)
{
bool found = false;
AreaGroupEntry const* groupEntry = sAreaGroupStore.LookupEntry(areaGroupId);
while (groupEntry)
{
for (uint32 i = 0; i < 6; ++i)
if (groupEntry->AreaId[i] == zone_id || groupEntry->AreaId[i] == area_id)
found = true;
if (found || !groupEntry->nextGroup)
break;
// Try search in next group
groupEntry = sAreaGroupStore.LookupEntry(groupEntry->nextGroup);
}
if (!found)
return SPELL_FAILED_INCORRECT_AREA;
}
// continent limitation (virtual continent), ignore for GM
if (spellInfo->HasAttribute(SPELL_ATTR_EX4_CAST_ONLY_IN_OUTLAND) && !(player && player->isGameMaster()))
{
uint32 v_map = GetVirtualMapForMapAndZone(map_id, zone_id);
MapEntry const* mapEntry = sMapStore.LookupEntry(v_map);
if (!mapEntry || mapEntry->addon < 1 || !mapEntry->IsContinent())
return SPELL_FAILED_INCORRECT_AREA;
}
// raid instance limitation
if (spellInfo->HasAttribute(SPELL_ATTR_EX6_NOT_IN_RAID_INSTANCE))
{
MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
if (!mapEntry || mapEntry->IsRaid())
return SPELL_FAILED_NOT_IN_RAID_INSTANCE;
}
// DB base check (if non empty then must fit at least single for allow)
SpellAreaMapBounds saBounds = GetSpellAreaMapBounds(spellInfo->Id);
if (saBounds.first != saBounds.second)
{
for (SpellAreaMap::const_iterator itr = saBounds.first; itr != saBounds.second; ++itr)
{
if (itr->second.IsFitToRequirements(player, zone_id, area_id))
return SPELL_CAST_OK;
}
return SPELL_FAILED_INCORRECT_AREA;
}
// bg spell checks
// do not allow spells to be cast in arenas
// - with SPELL_ATTR_EX4_NOT_USABLE_IN_ARENA flag
// - with greater than 10 min CD
if (spellInfo->HasAttribute(SPELL_ATTR_EX4_NOT_USABLE_IN_ARENA) ||
(GetSpellRecoveryTime(spellInfo) > 10 * MINUTE * IN_MILLISECONDS && !spellInfo->HasAttribute(SPELL_ATTR_EX4_USABLE_IN_ARENA)))
if (player && player->InArena())
return SPELL_FAILED_NOT_IN_ARENA;
// Spell casted only on battleground
if (spellInfo->HasAttribute(SPELL_ATTR_EX3_BATTLEGROUND))
if (!player || !player->InBattleGround())
return SPELL_FAILED_ONLY_BATTLEGROUNDS;
switch (spellInfo->Id)
{
// a trinket in alterac valley allows to teleport to the boss
case 22564: // recall
case 22563: // recall
{
if (!player)
return SPELL_FAILED_REQUIRES_AREA;
BattleGround* bg = player->GetBattleGround();
return map_id == 30 && bg
&& bg->GetStatus() != STATUS_WAIT_JOIN ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
}
case 23333: // Warsong Flag
case 23335: // Silverwing Flag
return map_id == 489 && player && player->InBattleGround() ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
case 34976: // Netherstorm Flag
return map_id == 566 && player && player->InBattleGround() ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
case 2584: // Waiting to Resurrect
case 42792: // Recently Dropped Flag
case 43681: // Inactive
{
return player && player->InBattleGround() ? SPELL_CAST_OK : SPELL_FAILED_ONLY_BATTLEGROUNDS;
}
case 22011: // Spirit Heal Channel
case 22012: // Spirit Heal
case 24171: // Resurrection Impact Visual
case 44535: // Spirit Heal (mana)
{
MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
if (!mapEntry)
return SPELL_FAILED_INCORRECT_AREA;
return mapEntry->IsBattleGround() ? SPELL_CAST_OK : SPELL_FAILED_ONLY_BATTLEGROUNDS;
}
case 44521: // Preparation
{
if (!player)
return SPELL_FAILED_REQUIRES_AREA;
BattleGround* bg = player->GetBattleGround();
return bg && bg->GetStatus() == STATUS_WAIT_JOIN ? SPELL_CAST_OK : SPELL_FAILED_ONLY_BATTLEGROUNDS;
}
case 32724: // Gold Team (Alliance)
case 32725: // Green Team (Alliance)
case 35774: // Gold Team (Horde)
case 35775: // Green Team (Horde)
{
return player && player->InArena() ? SPELL_CAST_OK : SPELL_FAILED_ONLY_IN_ARENA;
}
case 32727: // Arena Preparation
{
if (!player)
return SPELL_FAILED_REQUIRES_AREA;
if (!player->InArena())
return SPELL_FAILED_REQUIRES_AREA;
BattleGround* bg = player->GetBattleGround();
return bg && bg->GetStatus() == STATUS_WAIT_JOIN ? SPELL_CAST_OK : SPELL_FAILED_ONLY_IN_ARENA;
}
case 74410: // Arena - Dampening
return player && player->InArena() ? SPELL_CAST_OK : SPELL_FAILED_ONLY_IN_ARENA;
case 74411: // Battleground - Dampening
{
if (!player)
return SPELL_FAILED_ONLY_BATTLEGROUNDS;
BattleGround* bg = player->GetBattleGround();
return bg && !bg->isArena() ? SPELL_CAST_OK : SPELL_FAILED_ONLY_BATTLEGROUNDS;
}
}
return SPELL_CAST_OK;
}
void SpellMgr::LoadSkillLineAbilityMap()
{
mSkillLineAbilityMap.clear();
BarGoLink bar(sSkillLineAbilityStore.GetNumRows());
uint32 count = 0;
for (uint32 i = 0; i < sSkillLineAbilityStore.GetNumRows(); ++i)
{
bar.step();
SkillLineAbilityEntry const* SkillInfo = sSkillLineAbilityStore.LookupEntry(i);
if (!SkillInfo)
continue;
mSkillLineAbilityMap.insert(SkillLineAbilityMap::value_type(SkillInfo->spellId, SkillInfo));
++count;
}
sLog.outString();
sLog.outString(">> Loaded %u SkillLineAbility MultiMap Data", count);
}
void SpellMgr::LoadSkillRaceClassInfoMap()
{
mSkillRaceClassInfoMap.clear();
BarGoLink bar(sSkillRaceClassInfoStore.GetNumRows());
uint32 count = 0;
for (uint32 i = 0; i < sSkillRaceClassInfoStore.GetNumRows(); ++i)
{
bar.step();
SkillRaceClassInfoEntry const* skillRCInfo = sSkillRaceClassInfoStore.LookupEntry(i);
if (!skillRCInfo)
continue;
// not all skills really listed in ability skills list
if (!sSkillLineStore.LookupEntry(skillRCInfo->skillId))
continue;
mSkillRaceClassInfoMap.insert(SkillRaceClassInfoMap::value_type(skillRCInfo->skillId, skillRCInfo));
++count;
}
sLog.outString();
sLog.outString(">> Loaded %u SkillRaceClassInfo MultiMap Data", count);
}
void SpellMgr::CheckUsedSpells(char const* table)
{
uint32 countSpells = 0;
uint32 countMasks = 0;
// 0 1 2 3 4 5 6 7 8 9 10 11
QueryResult* result = WorldDatabase.PQuery("SELECT spellid,SpellFamilyName,SpellFamilyMaskA,SpellFamilyMaskB,SpellIcon,SpellVisual,SpellCategory,EffectType,EffectAura,EffectIdx,Name,Code FROM %s", table);
if (!result)
{
BarGoLink bar(1);
bar.step();
sLog.outString();
sLog.outErrorDb("`%s` table is empty!", table);
return;
}
BarGoLink bar(result->GetRowCount());
do
{
Field* fields = result->Fetch();
bar.step();
uint32 spell = fields[0].GetUInt32();
int32 family = fields[1].GetInt32();
uint64 familyMaskA = fields[2].GetUInt64();
uint32 familyMaskB = fields[3].GetUInt32();
int32 spellIcon = fields[4].GetInt32();
int32 spellVisual = fields[5].GetInt32();
int32 category = fields[6].GetInt32();
int32 effectType = fields[7].GetInt32();
int32 auraType = fields[8].GetInt32();
int32 effectIdx = fields[9].GetInt32();
std::string name = fields[10].GetCppString();
std::string code = fields[11].GetCppString();
// checks of correctness requirements itself
if (family < -1 || family > SPELLFAMILY_PET)
{
sLog.outError("Table '%s' for spell %u have wrong SpellFamily value(%u), skipped.", table, spell, family);
continue;
}
// TODO: spellIcon check need dbc loading
if (spellIcon < -1)
{
sLog.outError("Table '%s' for spell %u have wrong SpellIcon value(%u), skipped.", table, spell, spellIcon);
continue;
}
// TODO: spellVisual check need dbc loading
if (spellVisual < -1)
{
sLog.outError("Table '%s' for spell %u have wrong SpellVisual value(%u), skipped.", table, spell, spellVisual);
continue;
}
// TODO: for spellCategory better check need dbc loading
if (category < -1 || (category >= 0 && sSpellCategoryStore.find(category) == sSpellCategoryStore.end()))
{
sLog.outError("Table '%s' for spell %u have wrong SpellCategory value(%u), skipped.", table, spell, category);
continue;
}
if (effectType < -1 || effectType >= TOTAL_SPELL_EFFECTS)
{
sLog.outError("Table '%s' for spell %u have wrong SpellEffect type value(%u), skipped.", table, spell, effectType);
continue;
}
if (auraType < -1 || auraType >= TOTAL_AURAS)
{
sLog.outError("Table '%s' for spell %u have wrong SpellAura type value(%u), skipped.", table, spell, auraType);
continue;
}
if (effectIdx < -1 || effectIdx >= 3)
{
sLog.outError("Table '%s' for spell %u have wrong EffectIdx value(%u), skipped.", table, spell, effectIdx);
continue;
}
// now checks of requirements
if (spell)
{
++countSpells;
SpellEntry const* spellEntry = sSpellStore.LookupEntry(spell);
if (!spellEntry)
{
sLog.outError("Spell %u '%s' not exist but used in %s.", spell, name.c_str(), code.c_str());
continue;
}
SpellClassOptionsEntry const* classOptions = spellEntry->GetSpellClassOptions();
if(family >= 0 && classOptions && classOptions->SpellFamilyName != uint32(family))
{
sLog.outError("Spell %u '%s' family(%u) <> %u but used in %s.",spell,name.c_str(),classOptions->SpellFamilyName,family,code.c_str());
continue;
}
if (familyMaskA != UI64LIT(0xFFFFFFFFFFFFFFFF) || familyMaskB != 0xFFFFFFFF)
{
if (familyMaskA == UI64LIT(0x0000000000000000) && familyMaskB == 0x00000000)
{
if (classOptions && classOptions->SpellFamilyFlags)
{
sLog.outError("Spell %u '%s' not fit to (" I64FMT "," I32FMT ") but used in %s.",
spell, name.c_str(), familyMaskA, familyMaskB, code.c_str());
continue;
}
}
else
{
if (!spellEntry->IsFitToFamilyMask(familyMaskA, familyMaskB))
{
sLog.outError("Spell %u '%s' not fit to (" I64FMT "," I32FMT ") but used in %s.", spell, name.c_str(), familyMaskA, familyMaskB, code.c_str());
continue;
}
}
}
if (spellIcon >= 0 && spellEntry->SpellIconID != uint32(spellIcon))
{
sLog.outError("Spell %u '%s' icon(%u) <> %u but used in %s.", spell, name.c_str(), spellEntry->SpellIconID, spellIcon, code.c_str());
continue;
}
if (spellVisual >= 0 && spellEntry->SpellVisual[0] != uint32(spellVisual))
{
sLog.outError("Spell %u '%s' visual(%u) <> %u but used in %s.", spell, name.c_str(), spellEntry->SpellVisual[0], spellVisual, code.c_str());
continue;
}
if(category >= 0 && spellEntry->GetCategory() != uint32(category))
{
sLog.outError("Spell %u '%s' category(%u) <> %u but used in %s.",spell,name.c_str(),spellEntry->GetCategory(),category,code.c_str());
continue;
}
if (effectIdx >= EFFECT_INDEX_0)
{
SpellEffectEntry const* spellEffect = spellEntry->GetSpellEffect(SpellEffectIndex(effectIdx));
if(effectType >= 0 && spellEffect && spellEffect->Effect != uint32(effectType))
{
sLog.outError("Spell %u '%s' effect%d <> %u but used in %s.", spell, name.c_str(), effectIdx + 1, effectType, code.c_str());
continue;
}
if(auraType >= 0 && spellEffect && spellEffect->EffectApplyAuraName != uint32(auraType))
{
sLog.outError("Spell %u '%s' aura%d <> %u but used in %s.", spell, name.c_str(), effectIdx + 1, auraType, code.c_str());
continue;
}
}
else
{
if (effectType >= 0 && !IsSpellHaveEffect(spellEntry, SpellEffects(effectType)))
{
sLog.outError("Spell %u '%s' not have effect %u but used in %s.", spell, name.c_str(), effectType, code.c_str());
continue;
}
if (auraType >= 0 && !IsSpellHaveAura(spellEntry, AuraType(auraType)))
{
sLog.outError("Spell %u '%s' not have aura %u but used in %s.", spell, name.c_str(), auraType, code.c_str());
continue;
}
}
}
else
{
++countMasks;
bool found = false;
for (uint32 spellId = 1; spellId < sSpellStore.GetNumRows(); ++spellId)
{
SpellEntry const* spellEntry = sSpellStore.LookupEntry(spellId);
if (!spellEntry)
continue;
SpellClassOptionsEntry const* classOptions = spellEntry->GetSpellClassOptions();
if(family >=0 && classOptions && classOptions->SpellFamilyName != uint32(family))
continue;
if (familyMaskA != UI64LIT(0xFFFFFFFFFFFFFFFF) || familyMaskB != 0xFFFFFFFF)
{
if (familyMaskA == UI64LIT(0x0000000000000000) && familyMaskB == 0x00000000)
{
if (classOptions && classOptions->SpellFamilyFlags)
continue;
}
else
{
if (!spellEntry->IsFitToFamilyMask(familyMaskA, familyMaskB))
continue;
}
}
if (spellIcon >= 0 && spellEntry->SpellIconID != uint32(spellIcon))
continue;
if (spellVisual >= 0 && spellEntry->SpellVisual[0] != uint32(spellVisual))
continue;
if(category >= 0 && spellEntry->GetCategory() != uint32(category))
continue;
if (effectIdx >= 0)
{
SpellEffectEntry const* spellEffect = spellEntry->GetSpellEffect(SpellEffectIndex(effectIdx));
if(effectType >=0 && spellEffect && spellEffect->Effect != uint32(effectType))
continue;
if(auraType >=0 && spellEffect && spellEffect->EffectApplyAuraName != uint32(auraType))
continue;
}
else
{
if (effectType >= 0 && !IsSpellHaveEffect(spellEntry, SpellEffects(effectType)))
continue;
if (auraType >= 0 && !IsSpellHaveAura(spellEntry, AuraType(auraType)))
continue;
}
found = true;
break;
}
if (!found)
{
if (effectIdx >= 0)
sLog.outError("Spells '%s' not found for family %i (" I64FMT "," I32FMT ") icon(%i) visual(%i) category(%i) effect%d(%i) aura%d(%i) but used in %s",
name.c_str(), family, familyMaskA, familyMaskB, spellIcon, spellVisual, category, effectIdx + 1, effectType, effectIdx + 1, auraType, code.c_str());
else
sLog.outError("Spells '%s' not found for family %i (" I64FMT "," I32FMT ") icon(%i) visual(%i) category(%i) effect(%i) aura(%i) but used in %s",
name.c_str(), family, familyMaskA, familyMaskB, spellIcon, spellVisual, category, effectType, auraType, code.c_str());
continue;
}
}
}
while (result->NextRow());
delete result;
sLog.outString();
sLog.outString(">> Checked %u spells and %u spell masks", countSpells, countMasks);
}
DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellEntry const* spellproto, bool triggered)
{
// Explicit Diminishing Groups
SpellClassOptionsEntry const* classOptions = spellproto->GetSpellClassOptions();
switch(spellproto->GetSpellFamilyName())
{
case SPELLFAMILY_GENERIC:
// some generic arena related spells have by some strange reason MECHANIC_TURN
if (spellproto->GetMechanic() == MECHANIC_TURN)
return DIMINISHING_NONE;
break;
case SPELLFAMILY_MAGE:
// Dragon's Breath
if (spellproto->SpellIconID == 1548)
return DIMINISHING_DISORIENT;
break;
case SPELLFAMILY_ROGUE:
{
// Blind
if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00001000000))
return DIMINISHING_FEAR_CHARM_BLIND;
// Cheap Shot
else if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00000000400))
return DIMINISHING_CHEAPSHOT_POUNCE;
// Crippling poison - Limit to 10 seconds in PvP (No SpellFamilyFlags)
else if (spellproto->SpellIconID == 163)
return DIMINISHING_LIMITONLY;
break;
}
case SPELLFAMILY_HUNTER:
{
// Freezing Trap & Freezing Arrow & Wyvern Sting
if (spellproto->SpellIconID == 180 || spellproto->SpellIconID == 1721)
return DIMINISHING_DISORIENT;
break;
}
case SPELLFAMILY_WARLOCK:
{
// Curses/etc
if (spellproto->IsFitToFamilyMask(UI64LIT(0x00080000000)))
return DIMINISHING_LIMITONLY;
break;
}
case SPELLFAMILY_PALADIN:
{
// Judgement of Justice - Limit to 10 seconds in PvP
if (spellproto->IsFitToFamilyMask(UI64LIT(0x00000100000)))
if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00080000000))
return DIMINISHING_LIMITONLY;
break;
}
case SPELLFAMILY_DRUID:
{
// Cyclone
if (spellproto->IsFitToFamilyMask(UI64LIT(0x02000000000)))
return DIMINISHING_CYCLONE;
// Pounce
else if (spellproto->IsFitToFamilyMask(UI64LIT(0x00000020000)))
return DIMINISHING_CHEAPSHOT_POUNCE;
// Faerie Fire
else if (spellproto->IsFitToFamilyMask(UI64LIT(0x00000000400)))
return DIMINISHING_LIMITONLY;
break;
}
case SPELLFAMILY_WARRIOR:
{
// Hamstring - limit duration to 10s in PvP
if (spellproto->IsFitToFamilyMask(UI64LIT(0x00000000002)))
return DIMINISHING_LIMITONLY;
break;
}
case SPELLFAMILY_PRIEST:
{
// Shackle Undead
if (spellproto->SpellIconID == 27)
return DIMINISHING_DISORIENT;
break;
}
case SPELLFAMILY_DEATHKNIGHT:
{
// Hungering Cold (no flags)
if (spellproto->SpellIconID == 2797)
return DIMINISHING_DISORIENT;
break;
}
default:
break;
}
// Get by mechanic
uint32 mechanic = GetAllSpellMechanicMask(spellproto);
if (!mechanic)
return DIMINISHING_NONE;
if (mechanic & ((1 << (MECHANIC_STUN - 1)) | (1 << (MECHANIC_SHACKLE - 1))))
return triggered ? DIMINISHING_TRIGGER_STUN : DIMINISHING_CONTROL_STUN;
if (mechanic & ((1 << (MECHANIC_SLEEP - 1)) | (1 << (MECHANIC_FREEZE - 1))))
return DIMINISHING_FREEZE_SLEEP;
if (mechanic & ((1 << (MECHANIC_KNOCKOUT - 1)) | (1 << (MECHANIC_POLYMORPH - 1)) | (1 << (MECHANIC_SAPPED - 1))))
return DIMINISHING_DISORIENT;
if (mechanic & (1 << (MECHANIC_ROOT - 1)))
return triggered ? DIMINISHING_TRIGGER_ROOT : DIMINISHING_CONTROL_ROOT;
if (mechanic & ((1 << (MECHANIC_FEAR - 1)) | (1 << (MECHANIC_CHARM - 1)) | (1 << (MECHANIC_TURN - 1))))
return DIMINISHING_FEAR_CHARM_BLIND;
if (mechanic & ((1 << (MECHANIC_SILENCE - 1)) | (1 << (MECHANIC_INTERRUPT - 1))))
return DIMINISHING_SILENCE;
if (mechanic & (1 << (MECHANIC_DISARM - 1)))
return DIMINISHING_DISARM;
if (mechanic & (1 << (MECHANIC_BANISH - 1)))
return DIMINISHING_BANISH;
if (mechanic & (1 << (MECHANIC_HORROR - 1)))
return DIMINISHING_HORROR;
return DIMINISHING_NONE;
}
int32 GetDiminishingReturnsLimitDuration(DiminishingGroup group, SpellEntry const* spellproto)
{
if (!IsDiminishingReturnsGroupDurationLimited(group))
return 0;
SpellClassOptionsEntry const* classOptions = spellproto->GetSpellClassOptions();
// Explicit diminishing duration
switch(spellproto->GetSpellFamilyName())
{
case SPELLFAMILY_HUNTER:
{
// Wyvern Sting
if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x0000100000000000))
return 6000;
break;
}
case SPELLFAMILY_PALADIN:
{
// Repentance - limit to 6 seconds in PvP
if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00000000004))
return 6000;
break;
}
case SPELLFAMILY_DRUID:
{
// Faerie Fire - limit to 40 seconds in PvP (3.1)
if (classOptions && classOptions->SpellFamilyFlags & UI64LIT(0x00000000400))
return 40000;
break;
}
default:
break;
}
return 8000;
}
bool IsDiminishingReturnsGroupDurationLimited(DiminishingGroup group)
{
switch (group)
{
case DIMINISHING_CONTROL_STUN:
case DIMINISHING_TRIGGER_STUN:
case DIMINISHING_CONTROL_ROOT:
case DIMINISHING_TRIGGER_ROOT:
case DIMINISHING_FEAR_CHARM_BLIND:
case DIMINISHING_DISORIENT:
case DIMINISHING_CHEAPSHOT_POUNCE:
case DIMINISHING_FREEZE_SLEEP:
case DIMINISHING_CYCLONE:
case DIMINISHING_BANISH:
case DIMINISHING_LIMITONLY:
return true;
default:
return false;
}
return false;
}
DiminishingReturnsType GetDiminishingReturnsGroupType(DiminishingGroup group)
{
switch (group)
{
case DIMINISHING_CYCLONE:
case DIMINISHING_TRIGGER_STUN:
case DIMINISHING_CONTROL_STUN:
return DRTYPE_ALL;
case DIMINISHING_CONTROL_ROOT:
case DIMINISHING_TRIGGER_ROOT:
case DIMINISHING_FEAR_CHARM_BLIND:
case DIMINISHING_DISORIENT:
case DIMINISHING_SILENCE:
case DIMINISHING_DISARM:
case DIMINISHING_HORROR:
case DIMINISHING_FREEZE_SLEEP:
case DIMINISHING_BANISH:
case DIMINISHING_CHEAPSHOT_POUNCE:
return DRTYPE_PLAYER;
default:
break;
}
return DRTYPE_NONE;
}
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)
return false;
if (auraSpell > 0)
// have expected aura
return player->HasAura(auraSpell, EFFECT_INDEX_0);
else
// not have expected aura
return !player->HasAura(-auraSpell, EFFECT_INDEX_0);
}
return true;
}
SpellEntry const* GetSpellEntryByDifficulty(uint32 id, Difficulty difficulty, bool isRaid)
{
SpellDifficultyEntry const* spellDiff = sSpellDifficultyStore.LookupEntry(id);
if (!spellDiff)
return NULL;
for (Difficulty diff = difficulty; diff >= REGULAR_DIFFICULTY; diff = GetPrevDifficulty(diff, isRaid))
{
if (spellDiff->spellId[diff])
return sSpellStore.LookupEntry(spellDiff->spellId[diff]);
}
return NULL;
}
int32 GetMasteryCoefficient(SpellEntry const * spellProto)
{
if (!spellProto || !spellProto->HasAttribute(SPELL_ATTR_EX8_MASTERY))
return 0;
// Find mastery scaling coef
int32 coef = 0;
for (uint32 j = 0; j < MAX_EFFECT_INDEX; ++j)
{
SpellEffectEntry const * effectEntry = spellProto->GetSpellEffect(SpellEffectIndex(j));
if (!effectEntry)
continue;
// mastery scaling coef is stored in dummy aura, except 77215 (Potent Afflictions, zero effect)
// and 76808 (Executioner, not stored at all)
int32 bp = effectEntry->CalculateSimpleValue();
if (spellProto->Id == 76808)
bp = 250;
if (!bp)
continue;
coef = bp;
break;
}
return coef;
}