NOTE: Initially I just wanted to fix some warnings, but noticed that there is no common method for percentage calculation and various formulas are used many time in the code making it difficult to read and understand what the code actually does. So, I introduced several template methods for calculating percent values and adding those values to the original base. I replaced all the raw calculations throughout the code where found, but I could have missed something or could have made a mistake. So, please report any strange behaviour after this commit. If you ask me why I did it: for the sake of consistency and exact understanding what code means. If you see CalculatePct method, you clearly understand, that it find the value of x percent of y. And you can easily express, for example, spell behviour "reduces smth by x%" by the means of a method instead of recalling school maths. --HG-- branch : trunk
4133 lines
146 KiB
C++
Executable File
4133 lines
146 KiB
C++
Executable File
/*
|
|
* Copyright (C) 2008-2010 TrinityCore <http://www.trinitycore.org/>
|
|
* Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
* option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "SpellMgr.h"
|
|
#include "ObjectMgr.h"
|
|
#include "SpellAuraDefines.h"
|
|
#include "ProgressBar.h"
|
|
#include "DBCStores.h"
|
|
#include "World.h"
|
|
#include "Chat.h"
|
|
#include "Spell.h"
|
|
#include "BattlegroundMgr.h"
|
|
#include "CreatureAI.h"
|
|
#include "MapManager.h"
|
|
|
|
bool IsAreaEffectTarget[TOTAL_SPELL_TARGETS];
|
|
SpellEffectTargetTypes EffectTargetType[TOTAL_SPELL_EFFECTS];
|
|
SpellSelectTargetTypes SpellTargetType[TOTAL_SPELL_TARGETS];
|
|
|
|
SpellMgr::SpellMgr()
|
|
{
|
|
for (int i = 0; i < TOTAL_SPELL_EFFECTS; ++i)
|
|
{
|
|
switch(i)
|
|
{
|
|
case SPELL_EFFECT_PERSISTENT_AREA_AURA: //27
|
|
case SPELL_EFFECT_SUMMON: //28
|
|
case SPELL_EFFECT_TRIGGER_MISSILE: //32
|
|
case SPELL_EFFECT_TRANS_DOOR: //50 summon object
|
|
case SPELL_EFFECT_SUMMON_PET: //56
|
|
case SPELL_EFFECT_ADD_FARSIGHT: //72
|
|
case SPELL_EFFECT_SUMMON_OBJECT_WILD: //76
|
|
//case SPELL_EFFECT_SUMMON_CRITTER: //97 not 303
|
|
case SPELL_EFFECT_SUMMON_OBJECT_SLOT1: //104
|
|
case SPELL_EFFECT_SUMMON_OBJECT_SLOT2: //105
|
|
case SPELL_EFFECT_SUMMON_OBJECT_SLOT3: //106
|
|
case SPELL_EFFECT_SUMMON_OBJECT_SLOT4: //107
|
|
case SPELL_EFFECT_SUMMON_DEAD_PET: //109
|
|
case SPELL_EFFECT_TRIGGER_SPELL_2: //151 ritual of summon
|
|
EffectTargetType[i] = SPELL_REQUIRE_DEST;
|
|
break;
|
|
case SPELL_EFFECT_PARRY: // 0
|
|
case SPELL_EFFECT_BLOCK: // 0
|
|
case SPELL_EFFECT_SKILL: // always with dummy 3 as A
|
|
//case SPELL_EFFECT_LEARN_SPELL: // 0 may be 5 pet
|
|
case SPELL_EFFECT_TRADE_SKILL: // 0 or 1
|
|
case SPELL_EFFECT_PROFICIENCY: // 0
|
|
EffectTargetType[i] = SPELL_REQUIRE_NONE;
|
|
break;
|
|
case SPELL_EFFECT_ENCHANT_ITEM:
|
|
case SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY:
|
|
case SPELL_EFFECT_DISENCHANT:
|
|
//in 243 this is 0, in 309 it is 1
|
|
//so both item target and unit target is pushed, and cause crash
|
|
//case SPELL_EFFECT_FEED_PET:
|
|
case SPELL_EFFECT_PROSPECTING:
|
|
case SPELL_EFFECT_MILLING:
|
|
case SPELL_EFFECT_ENCHANT_ITEM_PRISMATIC:
|
|
EffectTargetType[i] = SPELL_REQUIRE_ITEM;
|
|
break;
|
|
//caster must be pushed otherwise no sound
|
|
case SPELL_EFFECT_APPLY_AREA_AURA_PARTY:
|
|
case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
|
case SPELL_EFFECT_APPLY_AREA_AURA_ENEMY:
|
|
case SPELL_EFFECT_APPLY_AREA_AURA_PET:
|
|
case SPELL_EFFECT_APPLY_AREA_AURA_OWNER:
|
|
case SPELL_EFFECT_APPLY_AREA_AURA_RAID:
|
|
case SPELL_EFFECT_CHARGE:
|
|
case SPELL_EFFECT_CHARGE_DEST:
|
|
case SPELL_EFFECT_JUMP:
|
|
case SPELL_EFFECT_JUMP_DEST:
|
|
case SPELL_EFFECT_LEAP_BACK:
|
|
EffectTargetType[i] = SPELL_REQUIRE_CASTER;
|
|
break;
|
|
//case SPELL_EFFECT_WMO_DAMAGE:
|
|
//case SPELL_EFFECT_WMO_REPAIR:
|
|
//case SPELL_EFFECT_WMO_CHANGE:
|
|
// EffectTargetType[i] = SPELL_REQUIRE_GOBJECT;
|
|
// break;
|
|
default:
|
|
EffectTargetType[i] = SPELL_REQUIRE_UNIT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < TOTAL_SPELL_TARGETS; ++i)
|
|
{
|
|
switch(i)
|
|
{
|
|
case TARGET_UNIT_CASTER:
|
|
case TARGET_UNIT_CASTER_FISHING:
|
|
case TARGET_UNIT_MASTER:
|
|
case TARGET_UNIT_PET:
|
|
case TARGET_UNIT_PARTY_CASTER:
|
|
case TARGET_UNIT_RAID_CASTER:
|
|
case TARGET_UNIT_VEHICLE:
|
|
case TARGET_UNIT_PASSENGER_0:
|
|
case TARGET_UNIT_PASSENGER_1:
|
|
case TARGET_UNIT_PASSENGER_2:
|
|
case TARGET_UNIT_PASSENGER_3:
|
|
case TARGET_UNIT_PASSENGER_4:
|
|
case TARGET_UNIT_PASSENGER_5:
|
|
case TARGET_UNIT_PASSENGER_6:
|
|
case TARGET_UNIT_PASSENGER_7:
|
|
case TARGET_UNIT_SUMMONER:
|
|
SpellTargetType[i] = TARGET_TYPE_UNIT_CASTER;
|
|
break;
|
|
case TARGET_UNIT_TARGET_PUPPET:
|
|
case TARGET_UNIT_TARGET_ALLY:
|
|
case TARGET_UNIT_TARGET_RAID:
|
|
case TARGET_UNIT_TARGET_ANY:
|
|
case TARGET_UNIT_TARGET_ENEMY:
|
|
case TARGET_UNIT_TARGET_PARTY:
|
|
case TARGET_UNIT_PARTY_TARGET:
|
|
case TARGET_UNIT_CLASS_TARGET:
|
|
case TARGET_UNIT_CHAINHEAL:
|
|
SpellTargetType[i] = TARGET_TYPE_UNIT_TARGET;
|
|
break;
|
|
case TARGET_UNIT_NEARBY_ENEMY:
|
|
case TARGET_UNIT_NEARBY_ALLY:
|
|
case TARGET_UNIT_NEARBY_ALLY_UNK:
|
|
case TARGET_UNIT_NEARBY_ENTRY:
|
|
case TARGET_UNIT_NEARBY_RAID:
|
|
case TARGET_GAMEOBJECT_NEARBY_ENTRY:
|
|
SpellTargetType[i] = TARGET_TYPE_UNIT_NEARBY;
|
|
break;
|
|
case TARGET_UNIT_AREA_ENEMY_SRC:
|
|
case TARGET_UNIT_AREA_ALLY_SRC:
|
|
case TARGET_UNIT_AREA_ENTRY_SRC:
|
|
case TARGET_UNIT_AREA_PARTY_SRC:
|
|
case TARGET_GAMEOBJECT_AREA_SRC:
|
|
SpellTargetType[i] = TARGET_TYPE_AREA_SRC;
|
|
break;
|
|
case TARGET_UNIT_AREA_ENEMY_DST:
|
|
case TARGET_UNIT_AREA_ALLY_DST:
|
|
case TARGET_UNIT_AREA_ENTRY_DST:
|
|
case TARGET_UNIT_AREA_PARTY_DST:
|
|
case TARGET_GAMEOBJECT_AREA_DST:
|
|
SpellTargetType[i] = TARGET_TYPE_AREA_DST;
|
|
break;
|
|
case TARGET_UNIT_CONE_ENEMY:
|
|
case TARGET_UNIT_CONE_ALLY:
|
|
case TARGET_UNIT_CONE_ENTRY:
|
|
case TARGET_UNIT_CONE_ENEMY_UNKNOWN:
|
|
case TARGET_UNIT_AREA_PATH:
|
|
case TARGET_GAMEOBJECT_AREA_PATH:
|
|
SpellTargetType[i] = TARGET_TYPE_AREA_CONE;
|
|
break;
|
|
case TARGET_DST_CASTER:
|
|
case TARGET_SRC_CASTER:
|
|
case TARGET_MINION:
|
|
case TARGET_DEST_CASTER_FRONT_LEAP:
|
|
case TARGET_DEST_CASTER_FRONT:
|
|
case TARGET_DEST_CASTER_BACK:
|
|
case TARGET_DEST_CASTER_RIGHT:
|
|
case TARGET_DEST_CASTER_LEFT:
|
|
case TARGET_DEST_CASTER_FRONT_LEFT:
|
|
case TARGET_DEST_CASTER_BACK_LEFT:
|
|
case TARGET_DEST_CASTER_BACK_RIGHT:
|
|
case TARGET_DEST_CASTER_FRONT_RIGHT:
|
|
case TARGET_DEST_CASTER_RANDOM:
|
|
case TARGET_DEST_CASTER_RADIUS:
|
|
SpellTargetType[i] = TARGET_TYPE_DEST_CASTER;
|
|
break;
|
|
case TARGET_DST_TARGET_ENEMY:
|
|
case TARGET_DEST_TARGET_ANY:
|
|
case TARGET_DEST_TARGET_FRONT:
|
|
case TARGET_DEST_TARGET_BACK:
|
|
case TARGET_DEST_TARGET_RIGHT:
|
|
case TARGET_DEST_TARGET_LEFT:
|
|
case TARGET_DEST_TARGET_FRONT_LEFT:
|
|
case TARGET_DEST_TARGET_BACK_LEFT:
|
|
case TARGET_DEST_TARGET_BACK_RIGHT:
|
|
case TARGET_DEST_TARGET_FRONT_RIGHT:
|
|
case TARGET_DEST_TARGET_RANDOM:
|
|
case TARGET_DEST_TARGET_RADIUS:
|
|
SpellTargetType[i] = TARGET_TYPE_DEST_TARGET;
|
|
break;
|
|
case TARGET_DEST_DYNOBJ_ENEMY:
|
|
case TARGET_DEST_DYNOBJ_ALLY:
|
|
case TARGET_DEST_DYNOBJ_NONE:
|
|
case TARGET_DEST_DEST:
|
|
case TARGET_DEST_TRAJ:
|
|
case TARGET_DEST_DEST_FRONT_LEFT:
|
|
case TARGET_DEST_DEST_BACK_LEFT:
|
|
case TARGET_DEST_DEST_BACK_RIGHT:
|
|
case TARGET_DEST_DEST_FRONT_RIGHT:
|
|
case TARGET_DEST_DEST_FRONT:
|
|
case TARGET_DEST_DEST_BACK:
|
|
case TARGET_DEST_DEST_RIGHT:
|
|
case TARGET_DEST_DEST_LEFT:
|
|
case TARGET_DEST_DEST_RANDOM:
|
|
case TARGET_DEST_DEST_RANDOM_DIR_DIST:
|
|
SpellTargetType[i] = TARGET_TYPE_DEST_DEST;
|
|
break;
|
|
case TARGET_DST_DB:
|
|
case TARGET_DST_HOME:
|
|
case TARGET_DST_NEARBY_ENTRY:
|
|
SpellTargetType[i] = TARGET_TYPE_DEST_SPECIAL;
|
|
break;
|
|
case TARGET_UNIT_CHANNEL_TARGET:
|
|
case TARGET_DEST_CHANNEL_TARGET:
|
|
case TARGET_DEST_CHANNEL_CASTER:
|
|
SpellTargetType[i] = TARGET_TYPE_CHANNEL;
|
|
break;
|
|
default:
|
|
SpellTargetType[i] = TARGET_TYPE_DEFAULT;
|
|
}
|
|
}
|
|
|
|
for (int32 i = 0; i < TOTAL_SPELL_TARGETS; ++i)
|
|
{
|
|
switch(i)
|
|
{
|
|
case TARGET_UNIT_AREA_ENEMY_DST:
|
|
case TARGET_UNIT_AREA_ENEMY_SRC:
|
|
case TARGET_UNIT_AREA_ALLY_DST:
|
|
case TARGET_UNIT_AREA_ALLY_SRC:
|
|
case TARGET_UNIT_AREA_ENTRY_DST:
|
|
case TARGET_UNIT_AREA_ENTRY_SRC:
|
|
case TARGET_UNIT_AREA_PARTY_DST:
|
|
case TARGET_UNIT_AREA_PARTY_SRC:
|
|
case TARGET_UNIT_PARTY_TARGET:
|
|
case TARGET_UNIT_PARTY_CASTER:
|
|
case TARGET_UNIT_CONE_ENEMY:
|
|
case TARGET_UNIT_CONE_ALLY:
|
|
case TARGET_UNIT_CONE_ENEMY_UNKNOWN:
|
|
case TARGET_UNIT_AREA_PATH:
|
|
case TARGET_GAMEOBJECT_AREA_PATH:
|
|
case TARGET_UNIT_RAID_CASTER:
|
|
IsAreaEffectTarget[i] = true;
|
|
break;
|
|
default:
|
|
IsAreaEffectTarget[i] = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
SpellMgr::~SpellMgr()
|
|
{
|
|
}
|
|
|
|
SpellMgr& SpellMgr::Instance()
|
|
{
|
|
static SpellMgr spellMgr;
|
|
return spellMgr;
|
|
}
|
|
|
|
bool SpellMgr::IsSrcTargetSpell(SpellEntry const *spellInfo) const
|
|
{
|
|
for (uint8 i = 0; i< MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (SpellTargetType[spellInfo->EffectImplicitTargetA[i]] == TARGET_TYPE_AREA_SRC || SpellTargetType[spellInfo->EffectImplicitTargetB[i]] == TARGET_TYPE_AREA_SRC)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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]);
|
|
}
|
|
|
|
uint32 GetDispelChance(Unit* auraCaster, Unit* target, uint32 spellId, bool offensive, bool *result)
|
|
{
|
|
// we assume that aura dispel chance is 100% on start
|
|
// need formula for level difference based chance
|
|
int32 resist_chance = 0;
|
|
|
|
// Apply dispel mod from aura caster
|
|
if (auraCaster)
|
|
if (Player* modOwner = auraCaster->GetSpellModOwner())
|
|
modOwner->ApplySpellMod(spellId, SPELLMOD_RESIST_DISPEL_CHANCE, resist_chance);
|
|
|
|
// Dispel resistance from target SPELL_AURA_MOD_DISPEL_RESIST
|
|
// Only affects offensive dispels
|
|
if (offensive && target)
|
|
resist_chance += target->GetTotalAuraModifier(SPELL_AURA_MOD_DISPEL_RESIST);
|
|
|
|
// Try dispel
|
|
if (result)
|
|
*result = !roll_chance_i(resist_chance);
|
|
|
|
resist_chance = resist_chance < 0 ? 0 : resist_chance;
|
|
resist_chance = resist_chance > 100 ? 100 : resist_chance;
|
|
return 100 - resist_chance;
|
|
}
|
|
|
|
uint32 GetSpellCastTime(SpellEntry const* spellInfo, Spell * spell)
|
|
{
|
|
SpellCastTimesEntry const *spellCastTimeEntry = sSpellCastTimesStore.LookupEntry(spellInfo->CastingTimeIndex);
|
|
|
|
// not all spells have cast time index and this is all is pasiive abilities
|
|
if (!spellCastTimeEntry)
|
|
return 0;
|
|
|
|
int32 castTime = spellCastTimeEntry->CastTime;
|
|
|
|
if (spell && spell->GetCaster())
|
|
spell->GetCaster()->ModSpellCastTime(spellInfo, castTime, spell);
|
|
|
|
if (spellInfo->Attributes & SPELL_ATTR_REQ_AMMO && (!spell || !(spell->IsAutoRepeat())))
|
|
castTime += 500;
|
|
|
|
return (castTime > 0) ? uint32(castTime) : 0;
|
|
}
|
|
|
|
bool IsPassiveSpell(uint32 spellId)
|
|
{
|
|
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId);
|
|
if (!spellInfo)
|
|
return false;
|
|
return IsPassiveSpell(spellInfo);
|
|
}
|
|
|
|
bool IsPassiveSpell(SpellEntry const * spellInfo)
|
|
{
|
|
if (spellInfo->Attributes & SPELL_ATTR_PASSIVE)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool IsAutocastableSpell(uint32 spellId)
|
|
{
|
|
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId);
|
|
if (!spellInfo)
|
|
return false;
|
|
if (spellInfo->Attributes & SPELL_ATTR_PASSIVE)
|
|
return false;
|
|
if (spellInfo->AttributesEx & SPELL_ATTR_EX_UNAUTOCASTABLE_BY_PET)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool IsHigherHankOfSpell(uint32 spellId_1, uint32 spellId_2)
|
|
{
|
|
return sSpellMgr.GetSpellRank(spellId_1)<sSpellMgr.GetSpellRank(spellId_2);
|
|
}
|
|
|
|
uint32 CalculatePowerCost(SpellEntry const * spellInfo, Unit const * caster, SpellSchoolMask schoolMask)
|
|
{
|
|
// Spell drain all exist power on cast (Only paladin lay of Hands)
|
|
if (spellInfo->AttributesEx & SPELL_ATTR_EX_DRAIN_ALL_POWER)
|
|
{
|
|
// If power type - health drain all
|
|
if (spellInfo->powerType == POWER_HEALTH)
|
|
return caster->GetHealth();
|
|
// Else drain all power
|
|
if (spellInfo->powerType < MAX_POWERS)
|
|
return caster->GetPower(Powers(spellInfo->powerType));
|
|
sLog.outError("CalculateManaCost: Unknown power type '%d' in spell %d", spellInfo->powerType, spellInfo->Id);
|
|
return 0;
|
|
}
|
|
|
|
// Base powerCost
|
|
int32 powerCost = spellInfo->manaCost;
|
|
// PCT cost from total amount
|
|
if (spellInfo->ManaCostPercentage)
|
|
{
|
|
switch (spellInfo->powerType)
|
|
{
|
|
// health as power used
|
|
case POWER_HEALTH:
|
|
powerCost += int32(CalculatePctU(caster->GetCreateHealth(), spellInfo->ManaCostPercentage));
|
|
break;
|
|
case POWER_MANA:
|
|
powerCost += int32(CalculatePctU(caster->GetCreateMana(), spellInfo->ManaCostPercentage));
|
|
break;
|
|
case POWER_RAGE:
|
|
case POWER_FOCUS:
|
|
case POWER_ENERGY:
|
|
case POWER_HAPPINESS:
|
|
powerCost += int32(CalculatePctU(caster->GetMaxPower(Powers(spellInfo->powerType)), spellInfo->ManaCostPercentage));
|
|
break;
|
|
case POWER_RUNE:
|
|
case POWER_RUNIC_POWER:
|
|
sLog.outDebug("CalculateManaCost: Not implemented yet!");
|
|
break;
|
|
default:
|
|
sLog.outError("CalculateManaCost: Unknown power type '%d' in spell %d", spellInfo->powerType, spellInfo->Id);
|
|
return 0;
|
|
}
|
|
}
|
|
SpellSchools school = GetFirstSchoolInMask(schoolMask);
|
|
// Flat mod from caster auras by spell school
|
|
powerCost += caster->GetInt32Value(UNIT_FIELD_POWER_COST_MODIFIER + school);
|
|
// Shiv - costs 20 + weaponSpeed*10 energy (apply only to non-triggered spell with energy cost)
|
|
if (spellInfo->AttributesEx4 & SPELL_ATTR_EX4_SPELL_VS_EXTEND_COST)
|
|
powerCost += caster->GetAttackTime(OFF_ATTACK)/100;
|
|
// Apply cost mod by spell
|
|
if (Player* modOwner = caster->GetSpellModOwner())
|
|
modOwner->ApplySpellMod(spellInfo->Id, SPELLMOD_COST, powerCost);
|
|
|
|
if (spellInfo->Attributes & SPELL_ATTR_LEVEL_DAMAGE_CALCULATION)
|
|
powerCost = int32(powerCost/ (1.117f* spellInfo->spellLevel / caster->getLevel() -0.1327f));
|
|
|
|
// PCT mod from user auras by school
|
|
powerCost = int32(powerCost * (1.0f+caster->GetFloatValue(UNIT_FIELD_POWER_COST_MULTIPLIER+school)));
|
|
if (powerCost < 0)
|
|
powerCost = 0;
|
|
return powerCost;
|
|
}
|
|
|
|
Unit* GetTriggeredSpellCaster(SpellEntry const * spellInfo, Unit * caster, Unit * target)
|
|
{
|
|
for (uint8 i = 0 ; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (SpellTargetType[spellInfo->EffectImplicitTargetA[i]] == TARGET_TYPE_UNIT_TARGET
|
|
|| SpellTargetType[spellInfo->EffectImplicitTargetB[i]] == TARGET_TYPE_UNIT_TARGET
|
|
|| SpellTargetType[spellInfo->EffectImplicitTargetA[i]] == TARGET_TYPE_CHANNEL
|
|
|| SpellTargetType[spellInfo->EffectImplicitTargetB[i]] == TARGET_TYPE_CHANNEL
|
|
|| SpellTargetType[spellInfo->EffectImplicitTargetA[i]] == TARGET_TYPE_DEST_TARGET
|
|
|| SpellTargetType[spellInfo->EffectImplicitTargetB[i]] == TARGET_TYPE_DEST_TARGET)
|
|
return caster;
|
|
}
|
|
return target;
|
|
}
|
|
|
|
AuraState GetSpellAuraState(SpellEntry const * spellInfo)
|
|
{
|
|
// Seals
|
|
if (IsSealSpell(spellInfo))
|
|
return AURA_STATE_JUDGEMENT;
|
|
|
|
// Conflagrate aura state on Immolate and Shadowflame
|
|
if (spellInfo->SpellFamilyName == SPELLFAMILY_WARLOCK &&
|
|
// Immolate
|
|
((spellInfo->SpellFamilyFlags[0] & 4) ||
|
|
// Shadowflame
|
|
(spellInfo->SpellFamilyFlags[2] & 2)))
|
|
return AURA_STATE_CONFLAGRATE;
|
|
|
|
// Faerie Fire (druid versions)
|
|
if (spellInfo->SpellFamilyName == SPELLFAMILY_DRUID && spellInfo->SpellFamilyFlags[0] & 0x400)
|
|
return AURA_STATE_FAERIE_FIRE;
|
|
|
|
// Sting (hunter's pet ability)
|
|
if (spellInfo->Category == 1133)
|
|
return AURA_STATE_FAERIE_FIRE;
|
|
|
|
// Victorious
|
|
if (spellInfo->SpellFamilyName == SPELLFAMILY_WARRIOR && spellInfo->SpellFamilyFlags[1] & 0x00040000)
|
|
return AURA_STATE_WARRIOR_VICTORY_RUSH;
|
|
|
|
// Swiftmend state on Regrowth & Rejuvenation
|
|
if (spellInfo->SpellFamilyName == SPELLFAMILY_DRUID && spellInfo->SpellFamilyFlags[0] & 0x50)
|
|
return AURA_STATE_SWIFTMEND;
|
|
|
|
// Deadly poison aura state
|
|
if (spellInfo->SpellFamilyName == SPELLFAMILY_ROGUE && spellInfo->SpellFamilyFlags[0] & 0x10000)
|
|
return AURA_STATE_DEADLY_POISON;
|
|
|
|
// Enrage aura state
|
|
if (spellInfo->Dispel == DISPEL_ENRAGE)
|
|
return AURA_STATE_ENRAGE;
|
|
|
|
// Bleeding aura state
|
|
if (GetAllSpellMechanicMask(spellInfo) & 1<<MECHANIC_BLEED)
|
|
return AURA_STATE_BLEEDING;
|
|
|
|
if (GetSpellSchoolMask(spellInfo) & SPELL_SCHOOL_MASK_FROST)
|
|
{
|
|
for (uint8 i = 0; i<MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (spellInfo->EffectApplyAuraName[i] == SPELL_AURA_MOD_STUN
|
|
|| spellInfo->EffectApplyAuraName[i] == SPELL_AURA_MOD_ROOT)
|
|
return AURA_STATE_FROZEN;
|
|
}
|
|
}
|
|
return AURA_STATE_NONE;
|
|
}
|
|
|
|
SpellSpecific GetSpellSpecific(SpellEntry const * spellInfo)
|
|
{
|
|
switch(spellInfo->SpellFamilyName)
|
|
{
|
|
case SPELLFAMILY_GENERIC:
|
|
{
|
|
// Food / Drinks (mostly)
|
|
if (spellInfo->AuraInterruptFlags & AURA_INTERRUPT_FLAG_NOT_SEATED)
|
|
{
|
|
bool food = false;
|
|
bool drink = false;
|
|
for (int i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
switch(spellInfo->EffectApplyAuraName[i])
|
|
{
|
|
// 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_POWER:
|
|
drink = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (food && drink)
|
|
return SPELL_SPECIFIC_FOOD_AND_DRINK;
|
|
else if (food)
|
|
return SPELL_SPECIFIC_FOOD;
|
|
else if (drink)
|
|
return SPELL_SPECIFIC_DRINK;
|
|
}
|
|
// scrolls effects
|
|
else
|
|
{
|
|
uint32 firstSpell = sSpellMgr.GetFirstSpellInChain(spellInfo->Id);
|
|
switch (firstSpell)
|
|
{
|
|
case 8118: // Strength
|
|
case 8099: // Stamina
|
|
case 8112: // Spirit
|
|
case 8096: // Intellect
|
|
case 8115: // Agility
|
|
case 8091: // Armor
|
|
return SPELL_SPECIFIC_SCROLL;
|
|
case 12880: // Enrage (Enrage)
|
|
case 57518: // Enrage (Wrecking Crew)
|
|
return SPELL_SPECIFIC_WARRIOR_ENRAGE;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case SPELLFAMILY_MAGE:
|
|
{
|
|
// family flags 18(Molten), 25(Frost/Ice), 28(Mage)
|
|
if (spellInfo->SpellFamilyFlags[0] & 0x12040000)
|
|
return SPELL_SPECIFIC_MAGE_ARMOR;
|
|
|
|
// Arcane brillance and Arcane intelect (normal check fails because of flags difference)
|
|
if (spellInfo->SpellFamilyFlags[0] & 0x400)
|
|
return SPELL_SPECIFIC_MAGE_ARCANE_BRILLANCE;
|
|
|
|
if ((spellInfo->SpellFamilyFlags[0] & 0x1000000) && spellInfo->EffectApplyAuraName[0] == SPELL_AURA_MOD_CONFUSE)
|
|
return SPELL_SPECIFIC_MAGE_POLYMORPH;
|
|
|
|
break;
|
|
}
|
|
case SPELLFAMILY_WARRIOR:
|
|
{
|
|
if (spellInfo->Id == 12292) // Death Wish
|
|
return SPELL_SPECIFIC_WARRIOR_ENRAGE;
|
|
|
|
break;
|
|
}
|
|
case SPELLFAMILY_WARLOCK:
|
|
{
|
|
// only warlock curses have this
|
|
if (spellInfo->Dispel == DISPEL_CURSE)
|
|
return SPELL_SPECIFIC_CURSE;
|
|
|
|
// Warlock (Demon Armor | Demon Skin | Fel Armor)
|
|
if (spellInfo->SpellFamilyFlags[1] & 0x20000020 || spellInfo->SpellFamilyFlags[2] & 0x00000010)
|
|
return SPELL_SPECIFIC_WARLOCK_ARMOR;
|
|
|
|
//seed of corruption and corruption
|
|
if (spellInfo->SpellFamilyFlags[1] & 0x10 || spellInfo->SpellFamilyFlags[0] & 0x2)
|
|
return SPELL_SPECIFIC_WARLOCK_CORRUPTION;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_PRIEST:
|
|
{
|
|
// Divine Spirit and Prayer of Spirit
|
|
if (spellInfo->SpellFamilyFlags[0] & 0x20)
|
|
return SPELL_SPECIFIC_PRIEST_DIVINE_SPIRIT;
|
|
|
|
break;
|
|
}
|
|
case SPELLFAMILY_HUNTER:
|
|
{
|
|
// only hunter stings have this
|
|
if (spellInfo->Dispel == DISPEL_POISON)
|
|
return SPELL_SPECIFIC_STING;
|
|
|
|
// only hunter aspects have this (but not all aspects in hunter family)
|
|
if (spellInfo->SpellFamilyFlags.HasFlag(0x00380000, 0x00440000, 0x00001010))
|
|
return SPELL_SPECIFIC_ASPECT;
|
|
|
|
break;
|
|
}
|
|
case SPELLFAMILY_PALADIN:
|
|
{
|
|
if (IsSealSpell(spellInfo))
|
|
return SPELL_SPECIFIC_SEAL;
|
|
|
|
if (spellInfo->SpellFamilyFlags[0] & 0x00002190)
|
|
return SPELL_SPECIFIC_HAND;
|
|
|
|
// Judgement of Wisdom, Judgement of Light, Judgement of Justice
|
|
if (spellInfo->Id == 20184 || spellInfo->Id == 20185 || spellInfo->Id == 20186)
|
|
return SPELL_SPECIFIC_JUDGEMENT;
|
|
|
|
// only paladin auras have this (for palaldin class family)
|
|
if (spellInfo->SpellFamilyFlags[2] & 0x00000020)
|
|
return SPELL_SPECIFIC_AURA;
|
|
|
|
break;
|
|
}
|
|
case SPELLFAMILY_SHAMAN:
|
|
{
|
|
if (IsElementalShield(spellInfo))
|
|
return SPELL_SPECIFIC_ELEMENTAL_SHIELD;
|
|
|
|
break;
|
|
}
|
|
|
|
case SPELLFAMILY_DEATHKNIGHT:
|
|
if (spellInfo->Id == 48266 || spellInfo->Id == 48263 || spellInfo->Id == 48265)
|
|
//if (spellInfo->Category == 47)
|
|
return SPELL_SPECIFIC_PRESENCE;
|
|
break;
|
|
}
|
|
|
|
for (int i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AURA)
|
|
{
|
|
switch(spellInfo->EffectApplyAuraName[i])
|
|
{
|
|
case SPELL_AURA_MOD_CHARM:
|
|
case SPELL_AURA_MOD_POSSESS_PET:
|
|
case SPELL_AURA_MOD_POSSESS:
|
|
case SPELL_AURA_AOE_CHARM:
|
|
return SPELL_SPECIFIC_CHARM;
|
|
case SPELL_AURA_TRACK_CREATURES:
|
|
case SPELL_AURA_TRACK_RESOURCES:
|
|
case SPELL_AURA_TRACK_STEALTHED:
|
|
return SPELL_SPECIFIC_TRACKER;
|
|
case SPELL_AURA_PHASE:
|
|
return SPELL_SPECIFIC_PHASE;
|
|
}
|
|
}
|
|
}
|
|
|
|
return SPELL_SPECIFIC_NORMAL;
|
|
}
|
|
|
|
// target not allow have more one spell specific from same caster
|
|
bool IsSingleFromSpellSpecificPerCaster(SpellSpecific spellSpec1,SpellSpecific spellSpec2)
|
|
{
|
|
switch(spellSpec1)
|
|
{
|
|
case SPELL_SPECIFIC_SEAL:
|
|
case SPELL_SPECIFIC_HAND:
|
|
case SPELL_SPECIFIC_AURA:
|
|
case SPELL_SPECIFIC_STING:
|
|
case SPELL_SPECIFIC_CURSE:
|
|
case SPELL_SPECIFIC_ASPECT:
|
|
case SPELL_SPECIFIC_JUDGEMENT:
|
|
case SPELL_SPECIFIC_WARLOCK_CORRUPTION:
|
|
return spellSpec1 == spellSpec2;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsSingleFromSpellSpecificPerTarget(SpellSpecific spellSpec1, SpellSpecific spellSpec2)
|
|
{
|
|
switch(spellSpec1)
|
|
{
|
|
case SPELL_SPECIFIC_PHASE:
|
|
case SPELL_SPECIFIC_TRACKER:
|
|
case SPELL_SPECIFIC_WARLOCK_ARMOR:
|
|
case SPELL_SPECIFIC_MAGE_ARMOR:
|
|
case SPELL_SPECIFIC_ELEMENTAL_SHIELD:
|
|
case SPELL_SPECIFIC_MAGE_POLYMORPH:
|
|
case SPELL_SPECIFIC_PRESENCE:
|
|
case SPELL_SPECIFIC_CHARM:
|
|
case SPELL_SPECIFIC_SCROLL:
|
|
case SPELL_SPECIFIC_WARRIOR_ENRAGE:
|
|
case SPELL_SPECIFIC_MAGE_ARCANE_BRILLANCE:
|
|
case SPELL_SPECIFIC_PRIEST_DIVINE_SPIRIT:
|
|
return spellSpec1 == spellSpec2;
|
|
case SPELL_SPECIFIC_FOOD:
|
|
return spellSpec2 == SPELL_SPECIFIC_FOOD
|
|
|| spellSpec2 == SPELL_SPECIFIC_FOOD_AND_DRINK;
|
|
case SPELL_SPECIFIC_DRINK:
|
|
return spellSpec2 == SPELL_SPECIFIC_DRINK
|
|
|| spellSpec2 == SPELL_SPECIFIC_FOOD_AND_DRINK;
|
|
case SPELL_SPECIFIC_FOOD_AND_DRINK:
|
|
return spellSpec2 == SPELL_SPECIFIC_FOOD
|
|
|| spellSpec2 == SPELL_SPECIFIC_DRINK
|
|
|| spellSpec2 == SPELL_SPECIFIC_FOOD_AND_DRINK;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool IsPositiveTarget(uint32 targetA, uint32 targetB)
|
|
{
|
|
// non-positive targets
|
|
switch(targetA)
|
|
{
|
|
case TARGET_UNIT_NEARBY_ENEMY:
|
|
case TARGET_UNIT_TARGET_ENEMY:
|
|
case TARGET_UNIT_AREA_ENEMY_SRC:
|
|
case TARGET_UNIT_AREA_ENEMY_DST:
|
|
case TARGET_UNIT_CONE_ENEMY:
|
|
case TARGET_DEST_DYNOBJ_ENEMY:
|
|
case TARGET_DST_TARGET_ENEMY:
|
|
return false;
|
|
default:
|
|
break;
|
|
}
|
|
if (targetB)
|
|
return IsPositiveTarget(targetB, 0);
|
|
return true;
|
|
}
|
|
|
|
bool SpellMgr::_isPositiveEffect(uint32 spellId, uint32 effIndex, bool deep) const
|
|
{
|
|
SpellEntry const *spellproto = sSpellStore.LookupEntry(spellId);
|
|
if (!spellproto) return false;
|
|
|
|
// not found a single positive spell with this attribute
|
|
if (spellproto->Attributes & SPELL_ATTR_NEGATIVE_1)
|
|
return false;
|
|
|
|
switch (spellproto->SpellFamilyName)
|
|
{
|
|
case SPELLFAMILY_GENERIC:
|
|
switch (spellId)
|
|
{
|
|
case 34700: // Allergic Reaction
|
|
case 61987: // Avenging Wrath Marker
|
|
case 61988: // Divine Shield exclude aura
|
|
return false;
|
|
case 30877: // Tag Murloc
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SPELLFAMILY_MAGE:
|
|
// Amplify Magic, Dampen Magic
|
|
if (spellproto->SpellFamilyFlags[0] == 0x00002000)
|
|
return true;
|
|
break;
|
|
case SPELLFAMILY_PRIEST:
|
|
switch (spellId)
|
|
{
|
|
case 64844: // Divine Hymn
|
|
case 64904: // Hymn of Hope
|
|
case 47585: // Dispersion
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
case SPELLFAMILY_HUNTER:
|
|
// Aspect of the Viper
|
|
if (spellId == 34074)
|
|
return true;
|
|
break;
|
|
case SPELLFAMILY_SHAMAN:
|
|
if (spellId == 30708)
|
|
return false;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (spellproto->Mechanic)
|
|
{
|
|
case MECHANIC_IMMUNE_SHIELD:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Special case: effects which determine positivity of whole spell
|
|
for (uint8 i = 0; i<MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (spellproto->EffectApplyAuraName[i] == SPELL_AURA_MOD_STEALTH)
|
|
return true;
|
|
}
|
|
|
|
switch(spellproto->Effect[effIndex])
|
|
{
|
|
case SPELL_EFFECT_DUMMY:
|
|
// some explicitly required dummy effect sets
|
|
switch(spellId)
|
|
{
|
|
case 28441: return false; // AB Effect 000
|
|
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:
|
|
return true;
|
|
|
|
// non-positive aura use
|
|
case SPELL_EFFECT_APPLY_AURA:
|
|
case SPELL_EFFECT_APPLY_AREA_AURA_FRIEND:
|
|
{
|
|
switch(spellproto->EffectApplyAuraName[effIndex])
|
|
{
|
|
case SPELL_AURA_MOD_DAMAGE_DONE: // dependent from bas point sign (negative -> negative)
|
|
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:
|
|
case SPELL_AURA_MOD_DAMAGE_PERCENT_DONE:
|
|
if (SpellMgr::CalculateSpellEffectAmount(spellproto, effIndex) < 0)
|
|
return false;
|
|
break;
|
|
case SPELL_AURA_MOD_DAMAGE_TAKEN: // dependent from bas point sign (positive -> negative)
|
|
if (SpellMgr::CalculateSpellEffectAmount(spellproto, effIndex) > 0)
|
|
return false;
|
|
break;
|
|
case SPELL_AURA_MOD_CRIT_PCT:
|
|
case SPELL_AURA_MOD_SPELL_CRIT_CHANCE:
|
|
if (SpellMgr::CalculateSpellEffectAmount(spellproto, effIndex) > 0)
|
|
return true; // some expected positive spells have SPELL_ATTR_EX_NEGATIVE
|
|
break;
|
|
case SPELL_AURA_ADD_TARGET_TRIGGER:
|
|
return true;
|
|
case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE:
|
|
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
|
|
if (!deep)
|
|
{
|
|
uint32 spellTriggeredId = spellproto->EffectTriggerSpell[effIndex];
|
|
SpellEntry const *spellTriggeredProto = sSpellStore.LookupEntry(spellTriggeredId);
|
|
|
|
if (spellTriggeredProto)
|
|
{
|
|
// non-positive targets of main spell return early
|
|
for (int i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (!spellTriggeredProto->Effect[i])
|
|
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 (IsPositiveTarget(spellTriggeredProto->EffectImplicitTargetA[effIndex],spellTriggeredProto->EffectImplicitTargetB[effIndex]) && !_isPositiveEffect(spellTriggeredId,i, true))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
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 == 0 && spellproto->Effect[1] == 0 && spellproto->Effect[2] == 0)
|
|
return false; // but all single stun aura spells is negative
|
|
break;
|
|
case SPELL_AURA_MOD_PACIFY_SILENCE:
|
|
if (spellproto->Id == 24740) // Wisp Costume
|
|
return true;
|
|
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:
|
|
case SPELL_AURA_PREVENT_RESSURECTION:
|
|
return false;
|
|
case SPELL_AURA_PERIODIC_DAMAGE: // used in positive spells also.
|
|
// part of negative spell if casted at self (prevent cancel)
|
|
if (spellproto->EffectImplicitTargetA[effIndex] == TARGET_UNIT_CASTER)
|
|
return false;
|
|
break;
|
|
case SPELL_AURA_MOD_DECREASE_SPEED: // used in positive spells also
|
|
// part of positive spell if casted at self
|
|
if (spellproto->EffectImplicitTargetA[effIndex] != TARGET_UNIT_CASTER)
|
|
return false;
|
|
// but not this if this first effect (didn't find better check)
|
|
if (spellproto->Attributes & SPELL_ATTR_NEGATIVE_1 && effIndex == 0)
|
|
return false;
|
|
break;
|
|
case SPELL_AURA_MECHANIC_IMMUNITY:
|
|
{
|
|
// non-positive immunities
|
|
switch(spellproto->EffectMiscValue[effIndex])
|
|
{
|
|
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(spellproto->EffectMiscValue[effIndex])
|
|
{
|
|
case SPELLMOD_COST: // dependent from bas point sign (negative -> positive)
|
|
if (SpellMgr::CalculateSpellEffectAmount(spellproto, effIndex) > 0)
|
|
{
|
|
if (!deep)
|
|
{
|
|
bool negative = true;
|
|
for (uint8 i=0; i<MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (i != effIndex)
|
|
if (_isPositiveEffect(spellId, i, true))
|
|
{
|
|
negative = false;
|
|
break;
|
|
}
|
|
}
|
|
if (negative)
|
|
return false;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// non-positive targets
|
|
if (!IsPositiveTarget(spellproto->EffectImplicitTargetA[effIndex],spellproto->EffectImplicitTargetB[effIndex]))
|
|
return false;
|
|
|
|
// AttributesEx check
|
|
if (spellproto->AttributesEx & SPELL_ATTR_EX_NEGATIVE)
|
|
return false;
|
|
|
|
if (!deep && spellproto->EffectTriggerSpell[effIndex]
|
|
&& !spellproto->EffectApplyAuraName[effIndex]
|
|
&& IsPositiveTarget(spellproto->EffectImplicitTargetA[effIndex],spellproto->EffectImplicitTargetB[effIndex])
|
|
&& !_isPositiveSpell(spellproto->EffectTriggerSpell[effIndex], true))
|
|
return false;
|
|
|
|
// ok, positive
|
|
return true;
|
|
}
|
|
|
|
bool IsPositiveSpell(uint32 spellId)
|
|
{
|
|
if (!sSpellStore.LookupEntry(spellId)) // non-existing spells
|
|
return false;
|
|
return !(sSpellMgr.GetSpellCustomAttr(spellId) & SPELL_ATTR_CU_NEGATIVE);
|
|
}
|
|
|
|
bool IsPositiveEffect(uint32 spellId, uint32 effIndex)
|
|
{
|
|
if (!sSpellStore.LookupEntry(spellId))
|
|
return false;
|
|
switch(effIndex)
|
|
{
|
|
default:
|
|
case 0: return !(sSpellMgr.GetSpellCustomAttr(spellId) & SPELL_ATTR_CU_NEGATIVE_EFF0);
|
|
case 1: return !(sSpellMgr.GetSpellCustomAttr(spellId) & SPELL_ATTR_CU_NEGATIVE_EFF1);
|
|
case 2: return !(sSpellMgr.GetSpellCustomAttr(spellId) & SPELL_ATTR_CU_NEGATIVE_EFF2);
|
|
}
|
|
}
|
|
|
|
bool SpellMgr::_isPositiveSpell(uint32 spellId, bool deep) const
|
|
{
|
|
SpellEntry const *spellproto = sSpellStore.LookupEntry(spellId);
|
|
if (!spellproto) return false;
|
|
|
|
// 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_SPELL_EFFECTS; ++i)
|
|
if (!_isPositiveEffect(spellId, i, deep))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool IsSingleTargetSpell(SpellEntry const *spellInfo)
|
|
{
|
|
// all other single target spells have if it has AttributesEx5
|
|
if (spellInfo->AttributesEx5 & SPELL_ATTR_EX5_SINGLE_TARGET_SPELL)
|
|
return true;
|
|
|
|
switch(GetSpellSpecific(spellInfo))
|
|
{
|
|
case SPELL_SPECIFIC_JUDGEMENT:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsSingleTargetSpells(SpellEntry const *spellInfo1, SpellEntry const *spellInfo2)
|
|
{
|
|
// TODO - need better check
|
|
// Equal icon and spellfamily
|
|
if (spellInfo1->SpellFamilyName == spellInfo2->SpellFamilyName &&
|
|
spellInfo1->SpellIconID == spellInfo2->SpellIconID)
|
|
return true;
|
|
|
|
// TODO - need found Judgements rule
|
|
SpellSpecific spec1 = GetSpellSpecific(spellInfo1);
|
|
// spell with single target specific types
|
|
switch(spec1)
|
|
{
|
|
case SPELL_SPECIFIC_JUDGEMENT:
|
|
case SPELL_SPECIFIC_MAGE_POLYMORPH:
|
|
if (GetSpellSpecific(spellInfo2) == 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->Effect[0] == SPELL_EFFECT_LEARN_SPELL || spellInfo->Effect[1] == SPELL_EFFECT_LEARN_SPELL || spellInfo->Effect[2] == SPELL_EFFECT_LEARN_SPELL))
|
|
return SPELL_CAST_OK;
|
|
|
|
uint32 stanceMask = (form ? 1 << (form - 1) : 0);
|
|
|
|
if (stanceMask & spellInfo->StancesNot) // can explicitly not be casted in this stance
|
|
return SPELL_FAILED_NOT_SHAPESHIFT;
|
|
|
|
if (stanceMask & spellInfo->Stances) // can explicitly be casted in this stance
|
|
return SPELL_CAST_OK;
|
|
|
|
bool actAsShifted = false;
|
|
SpellShapeshiftEntry const *shapeInfo = NULL;
|
|
if (form > 0)
|
|
{
|
|
shapeInfo = sSpellShapeshiftStore.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->Attributes & SPELL_ATTR_NOT_SHAPESHIFT) // not while shapeshifted
|
|
return SPELL_FAILED_NOT_SHAPESHIFT;
|
|
else if (spellInfo->Stances != 0) // needs other shapeshift
|
|
return SPELL_FAILED_ONLY_SHAPESHIFT;
|
|
}
|
|
else
|
|
{
|
|
// needs shapeshift
|
|
if (!(spellInfo->AttributesEx2 & SPELL_ATTR_EX2_NOT_NEED_SHAPESHIFT) && spellInfo->Stances != 0)
|
|
return SPELL_FAILED_ONLY_SHAPESHIFT;
|
|
}
|
|
|
|
// Check if stance disables cast of not-stance spells
|
|
// Example: cannot cast any other spells in zombie or ghoul form
|
|
// TODO: Find a way to disable use of these spells clientside
|
|
if (shapeInfo && shapeInfo->flags1 & 0x400)
|
|
{
|
|
if (!(stanceMask & spellInfo->Stances))
|
|
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 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_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (spellInfo->EffectImplicitTargetA[i] == TARGET_DST_DB || spellInfo->EffectImplicitTargetB[i] == TARGET_DST_DB)
|
|
{
|
|
// additional requirements
|
|
if (spellInfo->Effect[i]==SPELL_EFFECT_BIND && spellInfo->EffectMiscValue[i])
|
|
{
|
|
uint32 area_id = sMapMgr.GetAreaId(st.target_mapId, st.target_X, st.target_Y, st.target_Z);
|
|
if (area_id != uint32(spellInfo->EffectMiscValue[i]))
|
|
{
|
|
sLog.outErrorDb("Spell (Id: %u) listed in `spell_target_position` expected point to zone %u bit point to zone %u.",Spell_ID, spellInfo->EffectMiscValue[i], area_id);
|
|
break;
|
|
}
|
|
}
|
|
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
sLog.outErrorDb("Spell (Id: %u) listed in `spell_target_position` does not have target TARGET_DST_DB (17).",Spell_ID);
|
|
continue;
|
|
}
|
|
|
|
mSpellTargetPositions[Spell_ID] = st;
|
|
++count;
|
|
|
|
} while (result->NextRow());
|
|
|
|
// Check all spells
|
|
for (uint32 i = 1; i < sSpellStore.GetNumRows(); ++i)
|
|
{
|
|
SpellEntry const * spellInfo = sSpellStore.LookupEntry(i);
|
|
if (!spellInfo)
|
|
continue;
|
|
|
|
bool found = false;
|
|
for (int j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
|
{
|
|
switch(spellInfo->EffectImplicitTargetA[j])
|
|
{
|
|
case TARGET_DST_DB:
|
|
found = true;
|
|
break;
|
|
}
|
|
if (found)
|
|
break;
|
|
switch(spellInfo->EffectImplicitTargetB[j])
|
|
{
|
|
case TARGET_DST_DB:
|
|
found = true;
|
|
break;
|
|
}
|
|
if (found)
|
|
break;
|
|
}
|
|
if (found)
|
|
{
|
|
// if (!sSpellMgr.GetSpellTargetPosition(i))
|
|
// sLog.outDebug("Spell (ID: %u) does not have record in `spell_target_position`", i);
|
|
}
|
|
}
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell teleport coordinates", count);
|
|
}
|
|
|
|
bool SpellMgr::IsAffectedByMod(SpellEntry const *spellInfo, SpellModifier *mod) const
|
|
{
|
|
// false for spellInfo == NULL
|
|
if (!spellInfo || !mod)
|
|
return false;
|
|
|
|
SpellEntry const *affect_spell = sSpellStore.LookupEntry(mod->spellId);
|
|
// False if affect_spell == NULL or spellFamily not equal
|
|
if (!affect_spell || affect_spell->SpellFamilyName != spellInfo->SpellFamilyName)
|
|
return false;
|
|
|
|
// true
|
|
if (mod->mask & spellInfo->SpellFamilyFlags)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void SpellMgr::LoadSpellProcEvents()
|
|
{
|
|
mSpellProcEventMap.clear(); // need for reload case
|
|
|
|
uint32 count = 0;
|
|
|
|
// 0 1 2 3 4 5 6 7 8 9 10
|
|
QueryResult result = WorldDatabase.Query("SELECT entry, SchoolMask, SpellFamilyName, SpellFamilyMask0, SpellFamilyMask1, SpellFamilyMask2, procFlags, procEx, ppmRate, CustomChance, Cooldown FROM spell_proc_event");
|
|
if (!result)
|
|
{
|
|
barGoLink bar(1);
|
|
bar.step();
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell proc event conditions", count);
|
|
return;
|
|
}
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
uint32 customProc = 0;
|
|
do
|
|
{
|
|
Field *fields = result->Fetch();
|
|
|
|
bar.step();
|
|
|
|
uint32 entry = fields[0].GetUInt32();
|
|
|
|
const SpellEntry *spell = sSpellStore.LookupEntry(entry);
|
|
if (!spell)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_proc_event` does not exist", entry);
|
|
continue;
|
|
}
|
|
|
|
SpellProcEventEntry spe;
|
|
|
|
spe.schoolMask = fields[1].GetUInt32();
|
|
spe.spellFamilyName = fields[2].GetUInt32();
|
|
spe.spellFamilyMask[0] = fields[3].GetUInt32();
|
|
spe.spellFamilyMask[1] = fields[4].GetUInt32();
|
|
spe.spellFamilyMask[2] = fields[5].GetUInt32();
|
|
spe.procFlags = fields[6].GetUInt32();
|
|
spe.procEx = fields[7].GetUInt32();
|
|
spe.ppmRate = fields[8].GetFloat();
|
|
spe.customChance = fields[9].GetFloat();
|
|
spe.cooldown = fields[10].GetUInt32();
|
|
|
|
mSpellProcEventMap[entry] = spe;
|
|
|
|
if (spell->procFlags == 0)
|
|
{
|
|
if (spe.procFlags == 0)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_proc_event` probally not triggered spell", entry);
|
|
continue;
|
|
}
|
|
customProc++;
|
|
}
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
if (customProc)
|
|
sLog.outString(">> Loaded %u extra spell proc event conditions + %u custom", count, customProc);
|
|
else
|
|
sLog.outString(">> Loaded %u extra spell proc event conditions", count);
|
|
}
|
|
|
|
void SpellMgr::LoadSpellBonusess()
|
|
{
|
|
mSpellBonusMap.clear(); // need for reload case
|
|
uint32 count = 0;
|
|
// 0 1 2 3 4
|
|
QueryResult result = WorldDatabase.Query("SELECT entry, direct_bonus, dot_bonus, ap_bonus, ap_dot_bonus FROM spell_bonus_data");
|
|
if (!result)
|
|
{
|
|
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();
|
|
|
|
const SpellEntry *spell = sSpellStore.LookupEntry(entry);
|
|
if (!spell)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_bonus_data` does not exist", entry);
|
|
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();
|
|
|
|
mSpellBonusMap[entry] = sbe;
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
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, bool active)
|
|
{
|
|
// No extra req need
|
|
uint32 procEvent_procEx = PROC_EX_NONE;
|
|
|
|
// check prockFlags for condition
|
|
if ((procFlags & EventProcFlag) == 0)
|
|
return false;
|
|
|
|
bool hasFamilyMask = false;
|
|
|
|
/* Check Periodic Auras
|
|
|
|
*Dots can trigger if spell has no PROC_FLAG_SUCCESSFUL_NEGATIVE_MAGIC_SPELL
|
|
nor PROC_FLAG_TAKEN_POSITIVE_MAGIC_SPELL
|
|
|
|
*Only Hots can trigger if spell has PROC_FLAG_TAKEN_POSITIVE_MAGIC_SPELL
|
|
|
|
*Only dots can trigger if spell has both positivity flags or PROC_FLAG_SUCCESSFUL_NEGATIVE_MAGIC_SPELL
|
|
|
|
*Aura has to have PROC_FLAG_TAKEN_POSITIVE_MAGIC_SPELL or spellfamily specified to trigger from Hot
|
|
|
|
*/
|
|
|
|
if (procFlags & PROC_FLAG_DONE_PERIODIC)
|
|
{
|
|
if (EventProcFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_NEG)
|
|
{
|
|
if (!(procExtra & PROC_EX_INTERNAL_DOT))
|
|
return false;
|
|
}
|
|
else if (procExtra & PROC_EX_INTERNAL_HOT)
|
|
procExtra |= PROC_EX_INTERNAL_REQ_FAMILY;
|
|
else if (EventProcFlag & PROC_FLAG_DONE_SPELL_MAGIC_DMG_CLASS_POS)
|
|
return false;
|
|
}
|
|
|
|
if (procFlags & PROC_FLAG_TAKEN_PERIODIC)
|
|
{
|
|
if (EventProcFlag & PROC_FLAG_TAKEN_SPELL_MAGIC_DMG_CLASS_POS)
|
|
{
|
|
if (!(procExtra & PROC_EX_INTERNAL_DOT))
|
|
return false;
|
|
}
|
|
else if (procExtra & PROC_EX_INTERNAL_HOT)
|
|
procExtra |= PROC_EX_INTERNAL_REQ_FAMILY;
|
|
else if (EventProcFlag & PROC_FLAG_TAKEN_SPELL_NONE_DMG_CLASS_POS)
|
|
return false;
|
|
}
|
|
// Trap casts are active by default
|
|
if (procFlags & PROC_FLAG_DONE_TRAP_ACTIVATION)
|
|
active = true;
|
|
|
|
// Always trigger for this
|
|
if (procFlags & (PROC_FLAG_KILLED | PROC_FLAG_KILL | PROC_FLAG_DEATH))
|
|
return true;
|
|
|
|
if (spellProcEvent) // Exist event data
|
|
{
|
|
// Store extra req
|
|
procEvent_procEx = spellProcEvent->procEx;
|
|
|
|
// For melee triggers
|
|
if (procSpell == NULL)
|
|
{
|
|
// Check (if set) for school (melee attack have Normal school)
|
|
if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & SPELL_SCHOOL_MASK_NORMAL) == 0)
|
|
return false;
|
|
}
|
|
else // For spells need check school/spell family/family mask
|
|
{
|
|
// Check (if set) for school
|
|
if (spellProcEvent->schoolMask && (spellProcEvent->schoolMask & procSpell->SchoolMask) == 0)
|
|
return false;
|
|
|
|
// Check (if set) for spellFamilyName
|
|
if (spellProcEvent->spellFamilyName && (spellProcEvent->spellFamilyName != procSpell->SpellFamilyName))
|
|
return false;
|
|
|
|
// spellFamilyName is Ok need check for spellFamilyMask if present
|
|
if (spellProcEvent->spellFamilyMask)
|
|
{
|
|
if ((spellProcEvent->spellFamilyMask & procSpell->SpellFamilyFlags) == 0)
|
|
return false;
|
|
hasFamilyMask = true;
|
|
// Some spells are not considered as active even with have spellfamilyflags
|
|
if (!(procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL))
|
|
active = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (procExtra & (PROC_EX_INTERNAL_REQ_FAMILY))
|
|
{
|
|
if (!hasFamilyMask)
|
|
return false;
|
|
}
|
|
|
|
// Check for extra req (if none) and hit/crit
|
|
if (procEvent_procEx == PROC_EX_NONE)
|
|
{
|
|
// No extra req, so can trigger only for hit/crit - spell has to be active
|
|
if ((procExtra & (PROC_EX_NORMAL_HIT|PROC_EX_CRITICAL_HIT)) && active)
|
|
return true;
|
|
}
|
|
else // Passive spells hits here only if resist/reflect/immune/evade
|
|
{
|
|
if (procExtra & AURA_SPELL_PROC_EX_MASK)
|
|
{
|
|
// if spell marked as procing only from not active spells
|
|
if (active && procEvent_procEx & PROC_EX_NOT_ACTIVE_SPELL)
|
|
return false;
|
|
// if spell marked as procing only from active spells
|
|
if (!active && procEvent_procEx & PROC_EX_ONLY_ACTIVE_SPELL)
|
|
return false;
|
|
// Exist req for PROC_EX_EX_TRIGGER_ALWAYS
|
|
if (procEvent_procEx & PROC_EX_EX_TRIGGER_ALWAYS)
|
|
return true;
|
|
// PROC_EX_NOT_ACTIVE_SPELL and PROC_EX_ONLY_ACTIVE_SPELL flags handle: if passed checks before
|
|
if ((procExtra & (PROC_EX_NORMAL_HIT|PROC_EX_CRITICAL_HIT)) && ((procEvent_procEx & (AURA_SPELL_PROC_EX_MASK)) == 0))
|
|
return true;
|
|
}
|
|
// Check Extra Requirement like (hit/crit/miss/resist/parry/dodge/block/immune/reflect/absorb and other)
|
|
if (procEvent_procEx & procExtra)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SpellMgr::LoadSpellGroups()
|
|
{
|
|
mSpellSpellGroup.clear(); // need for reload case
|
|
mSpellGroupSpell.clear();
|
|
|
|
uint32 count = 0;
|
|
|
|
// 0 1
|
|
QueryResult result = WorldDatabase.Query("SELECT id, spell_id FROM spell_group");
|
|
if (!result)
|
|
{
|
|
|
|
barGoLink bar(1);
|
|
|
|
bar.step();
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell group definitions", count);
|
|
return;
|
|
}
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
|
|
std::set<uint32> groups;
|
|
|
|
do
|
|
{
|
|
Field *fields = result->Fetch();
|
|
|
|
bar.step();
|
|
|
|
uint32 group_id = fields[0].GetUInt32();
|
|
if (group_id <= SPELL_GROUP_DB_RANGE_MIN && group_id >= SPELL_GROUP_CORE_RANGE_MAX)
|
|
{
|
|
sLog.outErrorDb("SpellGroup id %u listed in `spell_groups` is in core range, but is not defined in core!", group_id);
|
|
continue;
|
|
}
|
|
int32 spell_id = fields[1].GetInt32();
|
|
|
|
groups.insert(std::set<uint32>::value_type(group_id));
|
|
mSpellGroupSpell.insert(SpellGroupSpellMap::value_type((SpellGroup)group_id, spell_id));
|
|
|
|
} while (result->NextRow());
|
|
|
|
for (SpellGroupSpellMap::iterator itr = mSpellGroupSpell.begin(); itr!= mSpellGroupSpell.end() ;)
|
|
{
|
|
if (itr->second < 0)
|
|
{
|
|
if (groups.find(abs(itr->second)) == groups.end())
|
|
{
|
|
sLog.outErrorDb("SpellGroup id %u listed in `spell_groups` does not exist", abs(itr->second));
|
|
mSpellGroupSpell.erase(itr++);
|
|
}
|
|
else
|
|
++itr;
|
|
}
|
|
else
|
|
{
|
|
SpellEntry const* spellInfo = sSpellStore.LookupEntry(itr->second);
|
|
|
|
if (!spellInfo)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_group` does not exist", itr->second);
|
|
mSpellGroupSpell.erase(itr++);
|
|
}
|
|
else if (GetSpellRank(itr->second) > 1)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_group` is not first rank of spell", itr->second);
|
|
mSpellGroupSpell.erase(itr++);
|
|
}
|
|
else
|
|
++itr;
|
|
}
|
|
}
|
|
|
|
for (std::set<uint32>::iterator groupItr = groups.begin() ; groupItr != groups.end() ; ++groupItr)
|
|
{
|
|
std::set<uint32> spells;
|
|
GetSetOfSpellsInSpellGroup(SpellGroup(*groupItr), spells);
|
|
|
|
for (std::set<uint32>::iterator spellItr = spells.begin() ; spellItr != spells.end() ; ++spellItr)
|
|
{
|
|
++count;
|
|
mSpellSpellGroup.insert(SpellSpellGroupMap::value_type(*spellItr, SpellGroup(*groupItr)));
|
|
}
|
|
}
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell group definitions", count);
|
|
}
|
|
|
|
void SpellMgr::LoadSpellGroupStackRules()
|
|
{
|
|
mSpellGroupStack.clear(); // need for reload case
|
|
|
|
uint32 count = 0;
|
|
|
|
// 0 1
|
|
QueryResult result = WorldDatabase.Query("SELECT group_id, stack_rule FROM spell_group_stack_rules");
|
|
if (!result)
|
|
{
|
|
|
|
barGoLink bar(1);
|
|
|
|
bar.step();
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell group stack rules", count);
|
|
return;
|
|
}
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
|
|
do
|
|
{
|
|
Field *fields = result->Fetch();
|
|
|
|
bar.step();
|
|
|
|
uint32 group_id = fields[0].GetUInt32();
|
|
uint8 stack_rule = fields[1].GetUInt32();
|
|
if (stack_rule >= SPELL_GROUP_STACK_RULE_MAX)
|
|
{
|
|
sLog.outErrorDb("SpellGroupStackRule %u listed in `spell_group_stack_rules` does not exist", stack_rule);
|
|
continue;
|
|
}
|
|
|
|
SpellGroupSpellMapBounds spellGroup = GetSpellGroupSpellMapBounds((SpellGroup)group_id);
|
|
|
|
if (spellGroup.first == spellGroup.second)
|
|
{
|
|
sLog.outErrorDb("SpellGroup id %u listed in `spell_group_stack_rules` does not exist", group_id);
|
|
continue;
|
|
}
|
|
|
|
mSpellGroupStack[(SpellGroup)group_id] = (SpellGroupStackRule)stack_rule;
|
|
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell group stack rules", count);
|
|
}
|
|
|
|
void SpellMgr::LoadSpellThreats()
|
|
{
|
|
mSpellThreatMap.clear(); // need for reload case
|
|
|
|
uint32 count = 0;
|
|
|
|
// 0 1
|
|
QueryResult result = WorldDatabase.Query("SELECT entry, Threat FROM spell_threat");
|
|
if (!result)
|
|
{
|
|
|
|
barGoLink bar(1);
|
|
|
|
bar.step();
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u aggro generating spells", count);
|
|
return;
|
|
}
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
|
|
do
|
|
{
|
|
Field *fields = result->Fetch();
|
|
|
|
bar.step();
|
|
|
|
uint32 entry = fields[0].GetUInt32();
|
|
uint16 Threat = fields[1].GetUInt16();
|
|
|
|
if (!sSpellStore.LookupEntry(entry))
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_threat` does not exist", entry);
|
|
continue;
|
|
}
|
|
|
|
mSpellThreatMap[entry] = Threat;
|
|
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u aggro generating spells", 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::canStackSpellRanks(SpellEntry const *spellInfo)
|
|
{
|
|
if (IsPassiveSpell(spellInfo->Id)) // ranked passive spell
|
|
return false;
|
|
if (spellInfo->powerType != POWER_MANA && spellInfo->powerType != POWER_HEALTH)
|
|
return false;
|
|
if (IsProfessionOrRidingSpell(spellInfo->Id))
|
|
return false;
|
|
|
|
if (sSpellMgr.IsSkillBonusSpell(spellInfo->Id))
|
|
return false;
|
|
|
|
// All stance spells. if any better way, change it.
|
|
for (int i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
switch(spellInfo->SpellFamilyName)
|
|
{
|
|
case SPELLFAMILY_PALADIN:
|
|
// Paladin aura Spell
|
|
if (spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AREA_AURA_RAID)
|
|
return false;
|
|
break;
|
|
case SPELLFAMILY_DRUID:
|
|
// Druid form Spell
|
|
if (spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AURA &&
|
|
spellInfo->EffectApplyAuraName[i] == SPELL_AURA_MOD_SHAPESHIFT)
|
|
return false;
|
|
break;
|
|
case SPELLFAMILY_ROGUE:
|
|
// Rogue Stealth
|
|
if (spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AURA &&
|
|
spellInfo->EffectApplyAuraName[i] == SPELL_AURA_MOD_SHAPESHIFT)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SpellMgr::IsProfessionOrRidingSpell(uint32 spellId)
|
|
{
|
|
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId);
|
|
if (!spellInfo)
|
|
return false;
|
|
|
|
for (uint8 i = 0 ; i < MAX_SPELL_EFFECTS ; ++i)
|
|
{
|
|
if (spellInfo->Effect[i] == SPELL_EFFECT_SKILL)
|
|
{
|
|
uint32 skill = spellInfo->EffectMiscValue[i];
|
|
|
|
bool found = IsProfessionOrRidingSkill(skill);
|
|
if (found)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpellMgr::IsProfessionSpell(uint32 spellId)
|
|
{
|
|
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId);
|
|
if (!spellInfo)
|
|
return false;
|
|
|
|
for (uint8 i = 0 ; i < MAX_SPELL_EFFECTS ; ++i)
|
|
{
|
|
if (spellInfo->Effect[i] == SPELL_EFFECT_SKILL)
|
|
{
|
|
uint32 skill = spellInfo->EffectMiscValue[i];
|
|
|
|
bool found = IsProfessionSkill(skill);
|
|
if (found)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SpellMgr::IsPrimaryProfessionSpell(uint32 spellId)
|
|
{
|
|
SpellEntry const *spellInfo = sSpellStore.LookupEntry(spellId);
|
|
if (!spellInfo)
|
|
return false;
|
|
|
|
for (uint8 i = 0 ; i < MAX_SPELL_EFFECTS ; ++i)
|
|
{
|
|
if (spellInfo->Effect[i] == SPELL_EFFECT_SKILL)
|
|
{
|
|
uint32 skill = spellInfo->EffectMiscValue[i];
|
|
|
|
bool found = IsPrimaryProfessionSkill(skill);
|
|
if (found)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool SpellMgr::IsSkillTypeSpell(uint32 spellId, SkillType type) const
|
|
{
|
|
SkillLineAbilityMapBounds bounds = GetSkillLineAbilityMapBounds(spellId);
|
|
|
|
for (SkillLineAbilityMap::const_iterator _spell_idx = bounds.first; _spell_idx != bounds.second; ++_spell_idx)
|
|
if (_spell_idx->second->skillId == uint32(type))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// basepoints provided here have to be valid basepoints (use SpellMgr::CalculateSpellEffectBaseAmount)
|
|
int32 SpellMgr::CalculateSpellEffectAmount(SpellEntry const * spellEntry, uint8 effIndex, Unit const * caster, int32 const * effBasePoints, Unit const * /*target*/)
|
|
{
|
|
float basePointsPerLevel = spellEntry->EffectRealPointsPerLevel[effIndex];
|
|
int32 basePoints = effBasePoints ? *effBasePoints : spellEntry->EffectBasePoints[effIndex];
|
|
int32 randomPoints = int32(spellEntry->EffectDieSides[effIndex]);
|
|
|
|
// base amount modification based on spell lvl vs caster lvl
|
|
if (caster)
|
|
{
|
|
int32 level = int32(caster->getLevel());
|
|
if (level > int32(spellEntry->maxLevel) && spellEntry->maxLevel > 0)
|
|
level = int32(spellEntry->maxLevel);
|
|
else if (level < int32(spellEntry->baseLevel))
|
|
level = int32(spellEntry->baseLevel);
|
|
level -= int32(spellEntry->spellLevel);
|
|
basePoints += int32(level * basePointsPerLevel);
|
|
}
|
|
|
|
// roll in a range <1;EffectDieSides> as of patch 3.3.3
|
|
switch(randomPoints)
|
|
{
|
|
case 0: break;
|
|
case 1: basePoints += 1; break; // range 1..1
|
|
default:
|
|
// range can have positive (1..rand) and negative (rand..1) values, so order its for irand
|
|
int32 randvalue = (randomPoints >= 1)
|
|
? irand(1, randomPoints)
|
|
: irand(randomPoints, 1);
|
|
|
|
basePoints += randvalue;
|
|
break;
|
|
}
|
|
|
|
float value = float(basePoints);
|
|
|
|
// random damage
|
|
if (caster)
|
|
{
|
|
// bonus amount from combo points
|
|
if (caster->m_movedPlayer)
|
|
if (uint8 comboPoints = caster->m_movedPlayer->GetComboPoints())
|
|
if (float comboDamage = spellEntry->EffectPointsPerComboPoint[effIndex])
|
|
value += comboDamage * comboPoints;
|
|
|
|
value = caster->ApplyEffectModifiers(spellEntry, effIndex, value);
|
|
|
|
// amount multiplication based on caster's level
|
|
if (!basePointsPerLevel && (spellEntry->Attributes & SPELL_ATTR_LEVEL_DAMAGE_CALCULATION && spellEntry->spellLevel) &&
|
|
spellEntry->Effect[effIndex] != SPELL_EFFECT_WEAPON_PERCENT_DAMAGE &&
|
|
spellEntry->Effect[effIndex] != SPELL_EFFECT_KNOCK_BACK &&
|
|
spellEntry->EffectApplyAuraName[effIndex] != SPELL_AURA_MOD_SPEED_ALWAYS &&
|
|
spellEntry->EffectApplyAuraName[effIndex] != SPELL_AURA_MOD_SPEED_NOT_STACK &&
|
|
spellEntry->EffectApplyAuraName[effIndex] != SPELL_AURA_MOD_INCREASE_SPEED &&
|
|
spellEntry->EffectApplyAuraName[effIndex] != SPELL_AURA_MOD_DECREASE_SPEED)
|
|
//there are many more: slow speed, -healing pct
|
|
value *= 0.25f * exp(caster->getLevel() * (70 - spellEntry->spellLevel) / 1000.0f);
|
|
//value = int32(value * (int32)getLevel() / (int32)(spellProto->spellLevel ? spellProto->spellLevel : 1));
|
|
}
|
|
|
|
return int32(value);
|
|
}
|
|
|
|
int32 SpellMgr::CalculateSpellEffectBaseAmount(int32 value, SpellEntry const * spellEntry, uint8 effIndex)
|
|
{
|
|
if (spellEntry->EffectDieSides[effIndex] == 0)
|
|
return value;
|
|
else
|
|
return value - 1;
|
|
}
|
|
|
|
float SpellMgr::CalculateSpellEffectValueMultiplier(SpellEntry const * spellEntry, uint8 effIndex, Unit * caster, Spell * spell)
|
|
{
|
|
float multiplier = spellEntry->EffectValueMultiplier[effIndex];
|
|
|
|
if (caster)
|
|
if (Player * modOwner = caster->GetSpellModOwner())
|
|
modOwner->ApplySpellMod(spellEntry->Id, SPELLMOD_VALUE_MULTIPLIER, multiplier, spell);
|
|
return multiplier;
|
|
}
|
|
|
|
float SpellMgr::CalculateSpellEffectDamageMultiplier(SpellEntry const * spellEntry, uint8 effIndex, Unit * caster, Spell * spell)
|
|
{
|
|
float multiplier = spellEntry->EffectDamageMultiplier[effIndex];
|
|
|
|
if (caster)
|
|
if (Player * modOwner = caster->GetSpellModOwner())
|
|
modOwner->ApplySpellMod(spellEntry->Id, SPELLMOD_DAMAGE_MULTIPLIER, multiplier, spell);
|
|
return multiplier;
|
|
}
|
|
|
|
SpellEntry const* SpellMgr::SelectAuraRankForPlayerLevel(SpellEntry const* spellInfo, uint32 playerLevel) const
|
|
{
|
|
// ignore passive spells
|
|
if (IsPassiveSpell(spellInfo->Id))
|
|
return spellInfo;
|
|
|
|
bool needRankSelection = false;
|
|
for (int i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (IsPositiveEffect(spellInfo->Id, i) && (
|
|
spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AURA ||
|
|
spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AREA_AURA_PARTY ||
|
|
spellInfo->Effect[i] == SPELL_EFFECT_APPLY_AREA_AURA_RAID
|
|
))
|
|
{
|
|
needRankSelection = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// not required
|
|
if (!needRankSelection)
|
|
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 (playerLevel + 10 >= nextSpellInfo->spellLevel)
|
|
return nextSpellInfo;
|
|
|
|
// one rank less then
|
|
}
|
|
|
|
// not found
|
|
return NULL;
|
|
}
|
|
|
|
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 (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (entry->Effect[i] == SPELL_EFFECT_SKILL)
|
|
{
|
|
SpellLearnSkillNode dbc_node;
|
|
dbc_node.skill = entry->EffectMiscValue[i];
|
|
dbc_node.step = SpellMgr::CalculateSpellEffectAmount(entry, 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 not existed spell %u", spell_id, node.spell);
|
|
continue;
|
|
}
|
|
|
|
if (GetTalentSpellCost(node.spell))
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_learn_spell` attempt learning talent spell %u, skipped", spell_id, node.spell);
|
|
continue;
|
|
}
|
|
|
|
mSpellLearnSpells.insert(SpellLearnSpellMap::value_type(spell_id,node));
|
|
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
// search auto-learned spells and add its to map also for use in unlearn spells/talents
|
|
uint32 dbc_count = 0;
|
|
for (uint32 spell = 0; spell < sSpellStore.GetNumRows(); ++spell)
|
|
{
|
|
SpellEntry const* entry = sSpellStore.LookupEntry(spell);
|
|
|
|
if (!entry)
|
|
continue;
|
|
|
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (entry->Effect[i] == SPELL_EFFECT_LEARN_SPELL)
|
|
{
|
|
SpellLearnSpellNode dbc_node;
|
|
dbc_node.spell = entry->EffectTriggerSpell[i];
|
|
dbc_node.active = true; // all dbc based learned spells is active (show in spell book or hide by client itself)
|
|
|
|
// ignore learning not existed spells (broken/outdated/or generic learnig spell 483
|
|
if (!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 = entry->EffectImplicitTargetA[i] == TARGET_UNIT_PET || GetTalentSpellCost(spell) > 0 || IsPassiveSpell(spell) || 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::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();
|
|
uint8 eff = fields[1].GetUInt8();
|
|
uint32 pet = fields[2].GetUInt32();
|
|
uint32 aura = fields[3].GetUInt32();
|
|
|
|
SpellPetAuraMap::iterator itr = mSpellPetAuraMap.find((spell<<8) + eff);
|
|
if (itr != mSpellPetAuraMap.end())
|
|
itr->second.AddAura(pet, aura);
|
|
else
|
|
{
|
|
SpellEntry const* spellInfo = sSpellStore.LookupEntry(spell);
|
|
if (!spellInfo)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_pet_auras` does not exist", spell);
|
|
continue;
|
|
}
|
|
if (spellInfo->Effect[eff] != SPELL_EFFECT_DUMMY &&
|
|
(spellInfo->Effect[eff] != SPELL_EFFECT_APPLY_AURA ||
|
|
spellInfo->EffectApplyAuraName[eff] != 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, spellInfo->EffectImplicitTargetA[eff] == TARGET_UNIT_PET, SpellMgr::CalculateSpellEffectAmount(spellInfo, eff));
|
|
mSpellPetAuraMap[(spell<<8) + eff] = pa;
|
|
}
|
|
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell pet auras", count);
|
|
}
|
|
|
|
void SpellMgr::LoadPetLevelupSpellMap()
|
|
{
|
|
mPetLevelupSpellMap.clear(); // need for reload case
|
|
|
|
uint32 count = 0;
|
|
uint32 family_count = 0;
|
|
|
|
barGoLink bar(sCreatureFamilyStore.GetNumRows());
|
|
|
|
for (uint32 i = 0; i < sCreatureFamilyStore.GetNumRows(); ++i)
|
|
{
|
|
bar.step();
|
|
|
|
CreatureFamilyEntry const *creatureFamily = sCreatureFamilyStore.LookupEntry(i);
|
|
if (!creatureFamily) // not exist
|
|
continue;
|
|
|
|
for (uint8 j = 0; j < 2; ++j)
|
|
{
|
|
if (!creatureFamily->skillLine[j])
|
|
continue;
|
|
|
|
for (uint32 k = 0; k < sSkillLineAbilityStore.GetNumRows(); ++k)
|
|
{
|
|
SkillLineAbilityEntry const *skillLine = sSkillLineAbilityStore.LookupEntry(k);
|
|
if (!skillLine)
|
|
continue;
|
|
|
|
//if (skillLine->skillId != creatureFamily->skillLine[0] &&
|
|
// (!creatureFamily->skillLine[1] || skillLine->skillId != creatureFamily->skillLine[1]))
|
|
// continue;
|
|
|
|
if (skillLine->skillId != creatureFamily->skillLine[j])
|
|
continue;
|
|
|
|
if (skillLine->learnOnGetSkill != ABILITY_LEARNED_ON_GET_RACE_OR_CLASS_SKILL)
|
|
continue;
|
|
|
|
SpellEntry const *spell = sSpellStore.LookupEntry(skillLine->spellId);
|
|
if (!spell) // not exist or triggered or talent
|
|
continue;
|
|
|
|
if (!spell->spellLevel)
|
|
continue;
|
|
|
|
PetLevelupSpellSet& spellSet = mPetLevelupSpellMap[creatureFamily->ID];
|
|
if (spellSet.empty())
|
|
++family_count;
|
|
|
|
spellSet.insert(PetLevelupSpellSet::value_type(spell->spellLevel,spell->Id));
|
|
++count;
|
|
}
|
|
}
|
|
}
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u pet levelup and default spells for %u families", count, family_count);
|
|
}
|
|
|
|
bool LoadPetDefaultSpells_helper(CreatureInfo const* cInfo, PetDefaultSpellsEntry& petDefSpells)
|
|
{
|
|
// skip empty list;
|
|
bool have_spell = false;
|
|
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
|
{
|
|
if (petDefSpells.spellid[j])
|
|
{
|
|
have_spell = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!have_spell)
|
|
return false;
|
|
|
|
// remove duplicates with levelupSpells if any
|
|
if (PetLevelupSpellSet const *levelupSpells = cInfo->family ? sSpellMgr.GetPetLevelupSpellList(cInfo->family) : NULL)
|
|
{
|
|
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
|
{
|
|
if (!petDefSpells.spellid[j])
|
|
continue;
|
|
|
|
for (PetLevelupSpellSet::const_iterator itr = levelupSpells->begin(); itr != levelupSpells->end(); ++itr)
|
|
{
|
|
if (itr->second == petDefSpells.spellid[j])
|
|
{
|
|
petDefSpells.spellid[j] = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// skip empty list;
|
|
have_spell = false;
|
|
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
|
{
|
|
if (petDefSpells.spellid[j])
|
|
{
|
|
have_spell = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return have_spell;
|
|
}
|
|
|
|
void SpellMgr::LoadPetDefaultSpells()
|
|
{
|
|
mPetDefaultSpellsMap.clear();
|
|
|
|
uint32 countCreature = 0;
|
|
uint32 countData = 0;
|
|
|
|
barGoLink bar(sCreatureStorage.MaxEntry + sSpellStore.GetNumRows());
|
|
|
|
for (uint32 i = 0; i < sCreatureStorage.MaxEntry; ++i)
|
|
{
|
|
bar.step();
|
|
|
|
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 (uint8 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)
|
|
{
|
|
bar.step();
|
|
|
|
SpellEntry const* spellEntry = sSpellStore.LookupEntry(i);
|
|
if (!spellEntry)
|
|
continue;
|
|
|
|
for (uint8 k = 0; k < MAX_SPELL_EFFECTS; ++k)
|
|
{
|
|
if (spellEntry->Effect[k] == SPELL_EFFECT_SUMMON || spellEntry->Effect[k] == SPELL_EFFECT_SUMMON_PET)
|
|
{
|
|
uint32 creature_id = spellEntry->EffectMiscValue[k];
|
|
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;
|
|
for (uint8 j = 0; j < MAX_CREATURE_SPELL_DATA_SLOT; ++j)
|
|
petDefSpells.spellid[j] = cInfo->spells[j];
|
|
|
|
if (LoadPetDefaultSpells_helper(cInfo, petDefSpells))
|
|
{
|
|
mPetDefaultSpellsMap[petSpellsId] = petDefSpells;
|
|
++countCreature;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded addition spells for %u pet spell data entries.", countData);
|
|
sLog.outString(">> Loaded %u summonable creature templates.", 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 (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
switch (spellInfo->Effect[i])
|
|
{
|
|
case 0:
|
|
continue;
|
|
|
|
// craft spell for crafting non-existed item (break client recipes list show)
|
|
case SPELL_EFFECT_CREATE_ITEM:
|
|
case SPELL_EFFECT_CREATE_ITEM_2:
|
|
{
|
|
if (spellInfo->EffectItemType[i] == 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(spellInfo->EffectItemType[i]))
|
|
{
|
|
if (msg)
|
|
{
|
|
if (pl)
|
|
ChatHandler(pl).PSendSysMessage("Craft spell %u create not-exist in DB item (Entry: %u) and then...",spellInfo->Id,spellInfo->EffectItemType[i]);
|
|
else
|
|
sLog.outErrorDb("Craft spell %u create not-exist in DB item (Entry: %u) and then...",spellInfo->Id,spellInfo->EffectItemType[i]);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
need_check_reagents = true;
|
|
break;
|
|
}
|
|
case SPELL_EFFECT_LEARN_SPELL:
|
|
{
|
|
SpellEntry const *spellInfo2 = sSpellStore.LookupEntry(spellInfo->EffectTriggerSpell[i]);
|
|
if (!IsSpellValid(spellInfo2,pl,msg))
|
|
{
|
|
if (msg)
|
|
{
|
|
if (pl)
|
|
ChatHandler(pl).PSendSysMessage("Spell %u learn to broken spell %u, and then...",spellInfo->Id,spellInfo->EffectTriggerSpell[i]);
|
|
else
|
|
sLog.outErrorDb("Spell %u learn to invalid spell %u, and then...",spellInfo->Id,spellInfo->EffectTriggerSpell[i]);
|
|
}
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (need_check_reagents)
|
|
{
|
|
for (uint8 j = 0; j < MAX_SPELL_REAGENTS; ++j)
|
|
{
|
|
if (spellInfo->Reagent[j] > 0 && !ObjectMgr::GetItemPrototype(spellInfo->Reagent[j]))
|
|
{
|
|
if (msg)
|
|
{
|
|
if (pl)
|
|
ChatHandler(pl).PSendSysMessage("Craft spell %u have not-exist reagent in DB item (Entry: %u) and then...",spellInfo->Id,spellInfo->Reagent[j]);
|
|
else
|
|
sLog.outErrorDb("Craft spell %u have not-exist reagent in DB item (Entry: %u) and then...",spellInfo->Id,spellInfo->Reagent[j]);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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 (const SpellEntry* spellInfo = sSpellStore.LookupEntry(spell))
|
|
{
|
|
if (spellArea.autocast)
|
|
const_cast<SpellEntry*>(spellInfo)->Attributes |= SPELL_ATTR_CANT_CANCEL;
|
|
}
|
|
else
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` does not exist", spell);
|
|
continue;
|
|
}
|
|
|
|
{
|
|
bool ok = true;
|
|
SpellAreaMapBounds sa_bounds = GetSpellAreaMapBounds(spellArea.spellId);
|
|
for (SpellAreaMap::const_iterator itr = sa_bounds.first; itr != sa_bounds.second; ++itr)
|
|
{
|
|
if (spellArea.spellId != itr->second.spellId)
|
|
continue;
|
|
if (spellArea.areaId != itr->second.areaId)
|
|
continue;
|
|
if (spellArea.questStart != itr->second.questStart)
|
|
continue;
|
|
if (spellArea.auraSpell != itr->second.auraSpell)
|
|
continue;
|
|
if ((spellArea.raceMask & itr->second.raceMask) == 0)
|
|
continue;
|
|
if (spellArea.gender != itr->second.gender)
|
|
continue;
|
|
|
|
// duplicate by requirements
|
|
ok =false;
|
|
break;
|
|
}
|
|
|
|
if (!ok)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` already listed with similar requirements.", spell);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (spellArea.areaId && !GetAreaEntryByAreaID(spellArea.areaId))
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have wrong area (%u) requirement", spell,spellArea.areaId);
|
|
continue;
|
|
}
|
|
|
|
if (spellArea.questStart && !sObjectMgr.GetQuestTemplate(spellArea.questStart))
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have wrong start quest (%u) requirement", spell,spellArea.questStart);
|
|
continue;
|
|
}
|
|
|
|
if (spellArea.questEnd)
|
|
{
|
|
if (!sObjectMgr.GetQuestTemplate(spellArea.questEnd))
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have wrong end quest (%u) requirement", spell,spellArea.questEnd);
|
|
continue;
|
|
}
|
|
|
|
if (spellArea.questEnd == spellArea.questStart && !spellArea.questStartCanActive)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have quest (%u) requirement for start and end in same time", spell,spellArea.questEnd);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (spellArea.auraSpell)
|
|
{
|
|
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;
|
|
}
|
|
|
|
if (uint32(abs(spellArea.auraSpell)) == spellArea.spellId)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement for itself", spell,abs(spellArea.auraSpell));
|
|
continue;
|
|
}
|
|
|
|
// not allow autocast chains by auraSpell field (but allow use as alternative if not present)
|
|
if (spellArea.autocast && spellArea.auraSpell > 0)
|
|
{
|
|
bool chain = false;
|
|
SpellAreaForAuraMapBounds saBound = GetSpellAreaForAuraMapBounds(spellArea.spellId);
|
|
for (SpellAreaForAuraMap::const_iterator itr = saBound.first; itr != saBound.second; ++itr)
|
|
{
|
|
if (itr->second->autocast && itr->second->auraSpell > 0)
|
|
{
|
|
chain = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (chain)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement that itself autocast from aura", spell,spellArea.auraSpell);
|
|
continue;
|
|
}
|
|
|
|
SpellAreaMapBounds saBound2 = GetSpellAreaMapBounds(spellArea.auraSpell);
|
|
for (SpellAreaMap::const_iterator itr2 = saBound2.first; itr2 != saBound2.second; ++itr2)
|
|
{
|
|
if (itr2->second.autocast && itr2->second.auraSpell > 0)
|
|
{
|
|
chain = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (chain)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have aura spell (%u) requirement that itself autocast from aura", spell,spellArea.auraSpell);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (spellArea.raceMask && (spellArea.raceMask & RACEMASK_ALL_PLAYABLE) == 0)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have wrong race mask (%u) requirement", spell,spellArea.raceMask);
|
|
continue;
|
|
}
|
|
|
|
if (spellArea.gender != GENDER_NONE && spellArea.gender != GENDER_FEMALE && spellArea.gender != GENDER_MALE)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_area` have wrong gender (%u) requirement", spell, spellArea.gender);
|
|
continue;
|
|
}
|
|
|
|
SpellArea const* sa = &mSpellAreaMap.insert(SpellAreaMap::value_type(spell,spellArea))->second;
|
|
|
|
// for search by current zone/subzone at zone/subzone change
|
|
if (spellArea.areaId)
|
|
mSpellAreaForAreaMap.insert(SpellAreaForAreaMap::value_type(spellArea.areaId,sa));
|
|
|
|
// for search at quest start/reward
|
|
if (spellArea.questStart)
|
|
{
|
|
if (spellArea.questStartCanActive)
|
|
mSpellAreaForActiveQuestMap.insert(SpellAreaForQuestMap::value_type(spellArea.questStart,sa));
|
|
else
|
|
mSpellAreaForQuestMap.insert(SpellAreaForQuestMap::value_type(spellArea.questStart,sa));
|
|
}
|
|
|
|
// for search at quest start/reward
|
|
if (spellArea.questEnd)
|
|
mSpellAreaForQuestEndMap.insert(SpellAreaForQuestMap::value_type(spellArea.questEnd,sa));
|
|
|
|
// for search at aura apply
|
|
if (spellArea.auraSpell)
|
|
mSpellAreaForAuraMap.insert(SpellAreaForAuraMap::value_type(abs(spellArea.auraSpell),sa));
|
|
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
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
|
|
if (spellInfo->AreaGroupId > 0)
|
|
{
|
|
bool found = false;
|
|
AreaGroupEntry const* groupEntry = sAreaGroupStore.LookupEntry(spellInfo->AreaGroupId);
|
|
while (groupEntry)
|
|
{
|
|
for (uint8 i = 0; i < MAX_GROUP_AREA_IDS; ++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)
|
|
if (spellInfo->AttributesEx4 & SPELL_ATTR_EX4_CAST_ONLY_IN_OUTLAND)
|
|
{
|
|
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->AttributesEx6 & 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 = sSpellMgr.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
|
|
switch(spellInfo->Id)
|
|
{
|
|
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 22011: // Spirit Heal Channel
|
|
case 22012: // Spirit Heal
|
|
case 24171: // Resurrection Impact Visual
|
|
case 42792: // Recently Dropped Flag
|
|
case 43681: // Inactive
|
|
case 44535: // Spirit Heal (mana)
|
|
{
|
|
MapEntry const* mapEntry = sMapStore.LookupEntry(map_id);
|
|
if (!mapEntry)
|
|
return SPELL_FAILED_INCORRECT_AREA;
|
|
|
|
return zone_id == 4197 || (mapEntry->IsBattleground() && player && player->InBattleground()) ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
|
|
}
|
|
case 44521: // Preparation
|
|
{
|
|
if (!player)
|
|
return SPELL_FAILED_REQUIRES_AREA;
|
|
|
|
MapEntry const *mapEntry = sMapStore.LookupEntry(map_id);
|
|
if (!mapEntry)
|
|
return SPELL_FAILED_INCORRECT_AREA;
|
|
|
|
if (!mapEntry->IsBattleground())
|
|
return SPELL_FAILED_REQUIRES_AREA;
|
|
|
|
Battleground* bg = player->GetBattleground();
|
|
return bg && bg->GetStatus() == STATUS_WAIT_JOIN ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
|
|
}
|
|
case 32724: // Gold Team (Alliance)
|
|
case 32725: // Green Team (Alliance)
|
|
case 35774: // Gold Team (Horde)
|
|
case 35775: // Green Team (Horde)
|
|
{
|
|
MapEntry const *mapEntry = sMapStore.LookupEntry(map_id);
|
|
if (!mapEntry)
|
|
return SPELL_FAILED_INCORRECT_AREA;
|
|
|
|
return mapEntry->IsBattleArena() && player && player->InBattleground() ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
|
|
}
|
|
case 32727: // Arena Preparation
|
|
{
|
|
if (!player)
|
|
return SPELL_FAILED_REQUIRES_AREA;
|
|
|
|
MapEntry const *mapEntry = sMapStore.LookupEntry(map_id);
|
|
if (!mapEntry)
|
|
return SPELL_FAILED_INCORRECT_AREA;
|
|
|
|
if (!mapEntry->IsBattleArena())
|
|
return SPELL_FAILED_REQUIRES_AREA;
|
|
|
|
Battleground *bg = player->GetBattleground();
|
|
return bg && bg->GetStatus() == STATUS_WAIT_JOIN ? SPELL_CAST_OK : SPELL_FAILED_REQUIRES_AREA;
|
|
}
|
|
}
|
|
|
|
// aura limitations
|
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
switch (spellInfo->EffectApplyAuraName[i])
|
|
{
|
|
case SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED:
|
|
case SPELL_AURA_FLY:
|
|
{
|
|
if (player && !player->IsKnowHowFlyIn(map_id, zone_id))
|
|
return SPELL_FAILED_INCORRECT_AREA;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
DiminishingGroup GetDiminishingReturnsGroupForSpell(SpellEntry const* spellproto, bool triggered)
|
|
{
|
|
// Explicit Diminishing Groups
|
|
switch (spellproto->SpellFamilyName)
|
|
{
|
|
case SPELLFAMILY_GENERIC:
|
|
// some generic arena related spells have by some strange reason MECHANIC_TURN
|
|
if (spellproto->Mechanic == MECHANIC_TURN)
|
|
return DIMINISHING_NONE;
|
|
break;
|
|
case SPELLFAMILY_MAGE:
|
|
{
|
|
// Frostbite
|
|
if (spellproto->SpellFamilyFlags[1] & 0x80000000)
|
|
return DIMINISHING_TRIGGER_ROOT;
|
|
//Shattered Barrier: only flag SpellFamilyFlags[0] = 0x00080000 shared
|
|
//by most frost spells, using id instead
|
|
if (spellproto->Id == 55080)
|
|
return DIMINISHING_TRIGGER_ROOT;
|
|
// Frost Nova / Freeze (Water Elemental)
|
|
if (spellproto->SpellIconID == 193)
|
|
return DIMINISHING_CONTROL_ROOT;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_ROGUE:
|
|
{
|
|
// Sap 0x80 Gouge 0x8
|
|
if (spellproto->SpellFamilyFlags[0] & 0x88)
|
|
return DIMINISHING_POLYMORPH;
|
|
// Blind
|
|
else if (spellproto->SpellFamilyFlags[0] & 0x1000000)
|
|
return DIMINISHING_FEAR_BLIND;
|
|
// Cheap Shot
|
|
else if (spellproto->SpellFamilyFlags[0] & 0x400)
|
|
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_WARLOCK:
|
|
{
|
|
// Death Coil
|
|
if (spellproto->SpellFamilyFlags[0] & 0x80000)
|
|
return DIMINISHING_DEATHCOIL;
|
|
// Curses/etc
|
|
else if (spellproto->SpellFamilyFlags[0] & 0x80000000)
|
|
return DIMINISHING_LIMITONLY;
|
|
// Howl of Terror
|
|
else if (spellproto->SpellFamilyFlags[1] & 0x8)
|
|
return DIMINISHING_FEAR_BLIND;
|
|
// Seduction
|
|
else if (spellproto->SpellFamilyFlags[1] & 0x10000000)
|
|
return DIMINISHING_FEAR_BLIND;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_DRUID:
|
|
{
|
|
// Pounce
|
|
if (spellproto->SpellFamilyFlags[0] & 0x20000)
|
|
return DIMINISHING_CHEAPSHOT_POUNCE;
|
|
// Cyclone
|
|
else if (spellproto->SpellFamilyFlags[1] & 0x20)
|
|
return DIMINISHING_CYCLONE;
|
|
// Entangling Roots: to force natures grasp proc to be control root
|
|
else if (spellproto->SpellFamilyFlags[0] & 0x00000200)
|
|
return DIMINISHING_CONTROL_ROOT;
|
|
// Faerie Fire
|
|
else if (spellproto->SpellFamilyFlags[0] & 0x400)
|
|
return DIMINISHING_LIMITONLY;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_WARRIOR:
|
|
{
|
|
// Hamstring - limit duration to 10s in PvP
|
|
if (spellproto->SpellFamilyFlags[0] & 0x2)
|
|
return DIMINISHING_LIMITONLY;
|
|
// Intimidating Shout
|
|
else if (spellproto->SpellFamilyFlags[0] & 0x40000)
|
|
return DIMINISHING_FEAR_BLIND;
|
|
// Charge Stun
|
|
else if (spellproto->SpellFamilyFlags[0] & 0x01000000)
|
|
return DIMINISHING_NONE;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_PALADIN:
|
|
{
|
|
// Repentance
|
|
if (spellproto->SpellFamilyFlags[0] & 0x4)
|
|
return DIMINISHING_POLYMORPH;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_DEATHKNIGHT:
|
|
{
|
|
// Hungering Cold (no flags)
|
|
if (spellproto->SpellIconID == 2797)
|
|
return DIMINISHING_POLYMORPH;
|
|
// Mark of Blood
|
|
else if ((spellproto->SpellFamilyFlags[0] & 0x10000000)
|
|
&& spellproto->SpellIconID == 2285)
|
|
return DIMINISHING_LIMITONLY;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_HUNTER:
|
|
{
|
|
// Hunter's mark
|
|
if ((spellproto->SpellFamilyFlags[0] & 0x400) && spellproto->SpellIconID == 538)
|
|
return DIMINISHING_LIMITONLY;
|
|
// Scatter Shot
|
|
if ((spellproto->SpellFamilyFlags[0] & 0x40000) && spellproto->SpellIconID == 132)
|
|
return DIMINISHING_NONE;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Get by mechanic
|
|
uint32 mechanic = GetAllSpellMechanicMask(spellproto);
|
|
if (mechanic == MECHANIC_NONE) return DIMINISHING_NONE;
|
|
if (mechanic & ((1<<MECHANIC_STUN) |
|
|
(1<<MECHANIC_SHACKLE))) return triggered ? DIMINISHING_TRIGGER_STUN : DIMINISHING_CONTROL_STUN;
|
|
if (mechanic & ((1<<MECHANIC_SLEEP) |
|
|
(1<<MECHANIC_FREEZE))) return DIMINISHING_FREEZE_SLEEP;
|
|
if (mechanic & (1<<MECHANIC_POLYMORPH)) return DIMINISHING_POLYMORPH;
|
|
if (mechanic & (1<<MECHANIC_ROOT)) return triggered ? DIMINISHING_TRIGGER_ROOT : DIMINISHING_CONTROL_ROOT;
|
|
if (mechanic & ((1<<MECHANIC_FEAR) |
|
|
(1<<MECHANIC_TURN))) return DIMINISHING_FEAR_BLIND;
|
|
if (mechanic & (1<<MECHANIC_CHARM)) return DIMINISHING_CHARM;
|
|
if (mechanic & (1<<MECHANIC_SILENCE)) return DIMINISHING_SILENCE;
|
|
if (mechanic & (1<<MECHANIC_DISARM)) return DIMINISHING_DISARM;
|
|
if (mechanic & (1<<MECHANIC_FREEZE)) return DIMINISHING_FREEZE_SLEEP;
|
|
if (mechanic & ((1<<MECHANIC_KNOCKOUT) |
|
|
(1<<MECHANIC_SAPPED))) return DIMINISHING_KNOCKOUT;
|
|
if (mechanic & (1<<MECHANIC_BANISH)) return DIMINISHING_BANISH;
|
|
if (mechanic & (1<<MECHANIC_HORROR)) return DIMINISHING_DEATHCOIL;
|
|
|
|
// Get by effect
|
|
for (uint8 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
if (spellproto->EffectApplyAuraName[i] == SPELL_AURA_MOD_TAUNT)
|
|
return DIMINISHING_TAUNT;
|
|
}
|
|
return DIMINISHING_NONE;
|
|
}
|
|
|
|
int32 GetDiminishingReturnsLimitDuration(DiminishingGroup group, SpellEntry const* spellproto)
|
|
{
|
|
if (!IsDiminishingReturnsGroupDurationLimited(group))
|
|
return 0;
|
|
|
|
// Explicit diminishing duration
|
|
switch(spellproto->SpellFamilyName)
|
|
{
|
|
case SPELLFAMILY_HUNTER:
|
|
{
|
|
// Wyvern Sting
|
|
if (spellproto->SpellFamilyFlags[1] & 0x1000)
|
|
return 6 * IN_MILLISECONDS;
|
|
// Hunter's Mark
|
|
if (spellproto->SpellFamilyFlags[0] & 0x400)
|
|
return 120 * IN_MILLISECONDS;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_PALADIN:
|
|
{
|
|
// Repentance - limit to 6 seconds in PvP
|
|
if (spellproto->SpellFamilyFlags[0] & 0x4)
|
|
return 6 * IN_MILLISECONDS;
|
|
break;
|
|
}
|
|
case SPELLFAMILY_DRUID:
|
|
{
|
|
// Faerie Fire - limit to 40 seconds in PvP (3.1)
|
|
if (spellproto->SpellFamilyFlags[0] & 0x400)
|
|
return 40 * IN_MILLISECONDS;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 10 * IN_MILLISECONDS;
|
|
}
|
|
|
|
bool IsDiminishingReturnsGroupDurationLimited(DiminishingGroup group)
|
|
{
|
|
switch(group)
|
|
{
|
|
case DIMINISHING_CONTROL_STUN:
|
|
case DIMINISHING_TRIGGER_STUN:
|
|
case DIMINISHING_FREEZE_SLEEP:
|
|
case DIMINISHING_CONTROL_ROOT:
|
|
case DIMINISHING_TRIGGER_ROOT:
|
|
case DIMINISHING_FEAR_BLIND:
|
|
case DIMINISHING_CHARM:
|
|
case DIMINISHING_POLYMORPH:
|
|
case DIMINISHING_KNOCKOUT:
|
|
case DIMINISHING_CYCLONE:
|
|
case DIMINISHING_BANISH:
|
|
case DIMINISHING_LIMITONLY:
|
|
case DIMINISHING_CHEAPSHOT_POUNCE:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
DiminishingLevels GetDiminishingReturnsMaxLevel(DiminishingGroup group)
|
|
{
|
|
switch(group)
|
|
{
|
|
case DIMINISHING_TAUNT:
|
|
return DIMINISHING_LEVEL_TAUNT_IMMUNE;
|
|
default:
|
|
return DIMINISHING_LEVEL_IMMUNE;
|
|
}
|
|
}
|
|
|
|
DiminishingReturnsType GetDiminishingReturnsGroupType(DiminishingGroup group)
|
|
{
|
|
switch(group)
|
|
{
|
|
case DIMINISHING_TAUNT:
|
|
case DIMINISHING_CONTROL_STUN:
|
|
case DIMINISHING_TRIGGER_STUN:
|
|
case DIMINISHING_CHEAPSHOT_POUNCE:
|
|
case DIMINISHING_CYCLONE:
|
|
return DRTYPE_ALL;
|
|
case DIMINISHING_FEAR_BLIND:
|
|
case DIMINISHING_CONTROL_ROOT:
|
|
case DIMINISHING_TRIGGER_ROOT:
|
|
case DIMINISHING_CHARM:
|
|
case DIMINISHING_POLYMORPH:
|
|
case DIMINISHING_SILENCE:
|
|
case DIMINISHING_DISARM:
|
|
case DIMINISHING_DEATHCOIL:
|
|
case DIMINISHING_FREEZE_SLEEP:
|
|
case DIMINISHING_BANISH:
|
|
case DIMINISHING_KNOCKOUT:
|
|
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 || (auraSpell > 0 && !player->HasAura(auraSpell)) || (auraSpell < 0 && player->HasAura(-auraSpell)))
|
|
return false;
|
|
|
|
// Extra conditions -- leaving the possibility add extra conditions...
|
|
switch(spellId)
|
|
{
|
|
case 58600: // No fly Zone - Dalaran
|
|
if (!player)
|
|
return false;
|
|
|
|
AreaTableEntry const* pArea = GetAreaEntryByAreaID(player->GetAreaId());
|
|
if (!(pArea && pArea->flags & AREA_FLAG_NO_FLY_ZONE))
|
|
return false;
|
|
if (!player->HasAuraType(SPELL_AURA_MOD_INCREASE_MOUNTED_FLIGHT_SPEED) && !player->HasAuraType(SPELL_AURA_FLY))
|
|
return false;
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//-----------TRINITY-------------
|
|
|
|
bool SpellMgr::CanAurasStack(SpellEntry const *spellInfo_1, SpellEntry const *spellInfo_2, bool sameCaster) const
|
|
{
|
|
SpellSpecific spellSpec_1 = GetSpellSpecific(spellInfo_1);
|
|
SpellSpecific spellSpec_2 = GetSpellSpecific(spellInfo_2);
|
|
if (spellSpec_1 && spellSpec_2)
|
|
if (IsSingleFromSpellSpecificPerTarget(spellSpec_1, spellSpec_2)
|
|
|| (sameCaster && IsSingleFromSpellSpecificPerCaster(spellSpec_1, spellSpec_2)))
|
|
return false;
|
|
|
|
SpellGroupStackRule stackRule = CheckSpellGroupStackRules(spellInfo_1->Id, spellInfo_2->Id);
|
|
if (stackRule)
|
|
{
|
|
if (stackRule == SPELL_GROUP_STACK_RULE_EXCLUSIVE)
|
|
return false;
|
|
if (sameCaster && stackRule == SPELL_GROUP_STACK_RULE_EXCLUSIVE_FROM_SAME_CASTER)
|
|
return false;
|
|
}
|
|
|
|
if (spellInfo_1->SpellFamilyName != spellInfo_2->SpellFamilyName)
|
|
return true;
|
|
|
|
if (!sameCaster)
|
|
{
|
|
if (spellInfo_1->AttributesEx & SPELL_ATTR_EX_STACK_FOR_DIFF_CASTERS
|
|
|| spellInfo_1->AttributesEx3 & SPELL_ATTR_EX3_STACK_FOR_DIFF_CASTERS)
|
|
return true;
|
|
|
|
// check same periodic auras
|
|
for (uint32 i = 0; i < MAX_SPELL_EFFECTS; ++i)
|
|
{
|
|
switch(spellInfo_1->EffectApplyAuraName[i])
|
|
{
|
|
// DOT or HOT from different casters will stack
|
|
case SPELL_AURA_PERIODIC_DAMAGE:
|
|
case SPELL_AURA_PERIODIC_DUMMY:
|
|
case SPELL_AURA_PERIODIC_HEAL:
|
|
case SPELL_AURA_PERIODIC_TRIGGER_SPELL:
|
|
case SPELL_AURA_PERIODIC_ENERGIZE:
|
|
case SPELL_AURA_PERIODIC_MANA_LEECH:
|
|
case SPELL_AURA_PERIODIC_LEECH:
|
|
case SPELL_AURA_POWER_BURN_MANA:
|
|
case SPELL_AURA_OBS_MOD_POWER:
|
|
case SPELL_AURA_OBS_MOD_HEALTH:
|
|
case SPELL_AURA_PERIODIC_TRIGGER_SPELL_WITH_VALUE:
|
|
// periodic auras which target areas are not allowed to stack this way (replenishment for example)
|
|
if (IsAreaOfEffectSpellEffect(spellInfo_1, i) || IsAreaOfEffectSpellEffect(spellInfo_2, i))
|
|
break;
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 spellId_1 = GetLastSpellInChain(spellInfo_1->Id);
|
|
uint32 spellId_2 = GetLastSpellInChain(spellInfo_2->Id);
|
|
|
|
// same spell
|
|
if (spellId_1 == spellId_2)
|
|
{
|
|
// Hack for Incanter's Absorption
|
|
if (spellId_1 == 44413)
|
|
return true;
|
|
// same spell with same caster should not stack
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CanSpellDispelAura(SpellEntry const * dispelSpell, SpellEntry const * aura)
|
|
{
|
|
// These auras (like ressurection sickness) can't be dispelled
|
|
if (aura->Attributes & SPELL_ATTR_NEGATIVE_1)
|
|
return false;
|
|
|
|
// These spells (like Mass Dispel) can dispell all auras
|
|
if (dispelSpell->Attributes & SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY)
|
|
return true;
|
|
|
|
// These auras (like Divine Shield) can't be dispelled
|
|
if (aura->Attributes & SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY)
|
|
return false;
|
|
|
|
// These auras (Cyclone for example) are not dispelable
|
|
if (aura->AttributesEx & SPELL_ATTR_EX_UNAFFECTED_BY_SCHOOL_IMMUNE)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CanSpellPierceImmuneAura(SpellEntry const * pierceSpell, SpellEntry const * aura)
|
|
{
|
|
// these spells pierce all avalible spells (Resurrection Sickness for example)
|
|
if (pierceSpell->Attributes & SPELL_ATTR_UNAFFECTED_BY_INVULNERABILITY)
|
|
return true;
|
|
|
|
// these spells (Cyclone for example) can pierce all...
|
|
if ((pierceSpell->AttributesEx & SPELL_ATTR_EX_UNAFFECTED_BY_SCHOOL_IMMUNE)
|
|
// ...but not these (Divine shield for example)
|
|
&& !(aura && (aura->Mechanic == MECHANIC_IMMUNE_SHIELD || aura->Mechanic == MECHANIC_INVULNERABILITY)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void SpellMgr::LoadSpellEnchantProcData()
|
|
{
|
|
mSpellEnchantProcEventMap.clear(); // need for reload case
|
|
|
|
uint32 count = 0;
|
|
|
|
// 0 1 2 3
|
|
QueryResult result = WorldDatabase.Query("SELECT entry, customChance, PPMChance, procEx FROM spell_enchant_proc_data");
|
|
if (!result)
|
|
{
|
|
barGoLink bar(1);
|
|
|
|
bar.step();
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell enchant proc event conditions", count);
|
|
return;
|
|
}
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
do
|
|
{
|
|
Field *fields = result->Fetch();
|
|
|
|
bar.step();
|
|
|
|
uint32 enchantId = fields[0].GetUInt32();
|
|
|
|
SpellItemEnchantmentEntry const *ench = sSpellItemEnchantmentStore.LookupEntry(enchantId);
|
|
if (!ench)
|
|
{
|
|
sLog.outErrorDb("Enchancment %u listed in `spell_enchant_proc_data` does not exist", enchantId);
|
|
continue;
|
|
}
|
|
|
|
SpellEnchantProcEntry spe;
|
|
|
|
spe.customChance = fields[1].GetUInt32();
|
|
spe.PPMChance = fields[2].GetFloat();
|
|
spe.procEx = fields[3].GetUInt32();
|
|
|
|
mSpellEnchantProcEventMap[enchantId] = spe;
|
|
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u enchant proc data definitions", count);
|
|
}
|
|
|
|
void SpellMgr::LoadSpellRequired()
|
|
{
|
|
mSpellsReqSpell.clear(); // need for reload case
|
|
mSpellReq.clear(); // need for reload case
|
|
|
|
QueryResult result = WorldDatabase.Query("SELECT spell_id, req_spell from spell_required");
|
|
|
|
if (!result)
|
|
{
|
|
barGoLink bar(1);
|
|
bar.step();
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded 0 spell required records");
|
|
sLog.outErrorDb("`spell_required` table is empty!");
|
|
return;
|
|
}
|
|
uint32 rows = 0;
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
do
|
|
{
|
|
bar.step();
|
|
Field *fields = result->Fetch();
|
|
|
|
uint32 spell_id = fields[0].GetUInt32();
|
|
uint32 spell_req = fields[1].GetUInt32();
|
|
// check if chain is made with valid first spell
|
|
SpellEntry const * spell = sSpellStore.LookupEntry(spell_id);
|
|
if (!spell)
|
|
{
|
|
sLog.outErrorDb("spell_id %u in `spell_required` table is not found in dbcs, skipped", spell_id);
|
|
continue;
|
|
}
|
|
SpellEntry const * req_spell = sSpellStore.LookupEntry(spell_req);
|
|
if (!req_spell)
|
|
{
|
|
sLog.outErrorDb("req_spell %u in `spell_required` table is not found in dbcs, skipped", spell_req);
|
|
continue;
|
|
}
|
|
if (GetFirstSpellInChain(spell_id) == GetFirstSpellInChain(spell_req))
|
|
{
|
|
sLog.outErrorDb("req_spell %u and spell_id %u in `spell_required` table are ranks of the same spell, entry not needed, skipped", spell_req, spell_id);
|
|
continue;
|
|
}
|
|
if (IsSpellRequiringSpell(spell_id, spell_req))
|
|
{
|
|
sLog.outErrorDb("duplicated entry of req_spell %u and spell_id %u in `spell_required`, skipped", spell_req, spell_id);
|
|
continue;
|
|
}
|
|
|
|
mSpellReq.insert (std::pair<uint32, uint32>(spell_id, spell_req));
|
|
mSpellsReqSpell.insert (std::pair<uint32, uint32>(spell_req, spell_id));
|
|
++rows;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell required records", rows);
|
|
}
|
|
|
|
void SpellMgr::LoadSpellRanks()
|
|
{
|
|
mSpellChains.clear(); // need for reload case
|
|
|
|
QueryResult result = WorldDatabase.Query("SELECT first_spell_id, spell_id, rank from spell_ranks ORDER BY first_spell_id , rank");
|
|
|
|
if (!result)
|
|
{
|
|
barGoLink bar(1);
|
|
bar.step();
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded 0 spell rank records");
|
|
sLog.outErrorDb("`spell_ranks` table is empty!");
|
|
return;
|
|
}
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
|
|
uint32 rows = 0;
|
|
bool finished = false;
|
|
|
|
do
|
|
{
|
|
// spellid, rank
|
|
std::list < std::pair < int32, int32 > > rankChain;
|
|
int32 currentSpell = -1;
|
|
int32 lastSpell = -1;
|
|
|
|
// fill one chain
|
|
while (currentSpell == lastSpell && !finished)
|
|
{
|
|
Field *fields = result->Fetch();
|
|
|
|
currentSpell = fields[0].GetUInt32();
|
|
if (lastSpell == -1)
|
|
lastSpell = currentSpell;
|
|
uint32 spell_id = fields[1].GetUInt32();
|
|
uint32 rank = fields[2].GetUInt32();
|
|
|
|
// don't drop the row if we're moving to the next rank
|
|
if (currentSpell == lastSpell)
|
|
{
|
|
bar.step();
|
|
rankChain.push_back(std::make_pair(spell_id, rank));
|
|
if (!result->NextRow())
|
|
finished = true;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
// check if chain is made with valid first spell
|
|
SpellEntry const * first = sSpellStore.LookupEntry(lastSpell);
|
|
if (!first)
|
|
{
|
|
sLog.outErrorDb("Spell rank identifier(first_spell_id) %u listed in `spell_ranks` does not exist!", lastSpell);
|
|
continue;
|
|
}
|
|
// check if chain is long enough
|
|
if (rankChain.size() < 2)
|
|
{
|
|
sLog.outErrorDb("There is only 1 spell rank for identifier(first_spell_id) %u in `spell_ranks`, entry is not needed!", lastSpell);
|
|
continue;
|
|
}
|
|
int32 curRank = 0;
|
|
bool valid = true;
|
|
// check spells in chain
|
|
for (std::list<std::pair<int32, int32> >::iterator itr = rankChain.begin() ; itr!= rankChain.end(); ++itr)
|
|
{
|
|
SpellEntry const * spell = sSpellStore.LookupEntry(itr->first);
|
|
if (!spell)
|
|
{
|
|
sLog.outErrorDb("Spell %u (rank %u) listed in `spell_ranks` for chain %u does not exist!", itr->first, itr->second, lastSpell);
|
|
valid = false;
|
|
break;
|
|
}
|
|
++curRank;
|
|
if (itr->second != curRank)
|
|
{
|
|
sLog.outErrorDb("Spell %u (rank %u) listed in `spell_ranks` for chain %u does not have proper rank value(should be %u)!", itr->first, itr->second, lastSpell, curRank);
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!valid)
|
|
continue;
|
|
int32 prevRank = 0;
|
|
// insert the chain
|
|
std::list<std::pair<int32, int32> >::iterator itr = rankChain.begin();
|
|
do
|
|
{
|
|
++rows;
|
|
int32 addedSpell = itr->first;
|
|
mSpellChains[addedSpell].first = lastSpell;
|
|
mSpellChains[addedSpell].last = rankChain.back().first;
|
|
mSpellChains[addedSpell].rank = itr->second;
|
|
mSpellChains[addedSpell].prev = prevRank;
|
|
prevRank = addedSpell;
|
|
++itr;
|
|
if (itr == rankChain.end())
|
|
{
|
|
mSpellChains[addedSpell].next = 0;
|
|
break;
|
|
}
|
|
else
|
|
mSpellChains[addedSpell].next = itr->first;
|
|
}
|
|
while (true);
|
|
} while (!finished);
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u spell rank records", rows);
|
|
}
|
|
|
|
// set data in core for now
|
|
void SpellMgr::LoadSpellCustomAttr()
|
|
{
|
|
mSpellCustomAttr.resize(GetSpellStore()->GetNumRows());
|
|
|
|
barGoLink bar(GetSpellStore()->GetNumRows());
|
|
|
|
uint32 count = 0;
|
|
|
|
SpellEntry *spellInfo;
|
|
for (uint32 i = 0; i < GetSpellStore()->GetNumRows(); ++i)
|
|
{
|
|
bar.step();
|
|
|
|
mSpellCustomAttr[i] = 0;
|
|
spellInfo = (SpellEntry*)GetSpellStore()->LookupEntry(i);
|
|
if (!spellInfo)
|
|
continue;
|
|
|
|
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
|
{
|
|
switch (spellInfo->Effect[j])
|
|
{
|
|
case SPELL_EFFECT_SCHOOL_DAMAGE:
|
|
case SPELL_EFFECT_WEAPON_DAMAGE:
|
|
case SPELL_EFFECT_WEAPON_DAMAGE_NOSCHOOL:
|
|
case SPELL_EFFECT_NORMALIZED_WEAPON_DMG:
|
|
case SPELL_EFFECT_WEAPON_PERCENT_DAMAGE:
|
|
case SPELL_EFFECT_HEAL:
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_DIRECT_DAMAGE;
|
|
count++;
|
|
break;
|
|
case SPELL_EFFECT_CHARGE:
|
|
case SPELL_EFFECT_CHARGE_DEST:
|
|
case SPELL_EFFECT_JUMP:
|
|
case SPELL_EFFECT_JUMP_DEST:
|
|
case SPELL_EFFECT_LEAP_BACK:
|
|
if (!spellInfo->speed && !spellInfo->SpellFamilyName)
|
|
spellInfo->speed = SPEED_CHARGE;
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_CHARGE;
|
|
count++;
|
|
break;
|
|
case SPELL_EFFECT_PICKPOCKET:
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_PICKPOCKET;
|
|
break;
|
|
case SPELL_EFFECT_TRIGGER_SPELL:
|
|
if (IsPositionTarget(spellInfo->EffectImplicitTargetA[j]) ||
|
|
spellInfo->Targets & (TARGET_FLAG_SOURCE_LOCATION|TARGET_FLAG_DEST_LOCATION))
|
|
spellInfo->Effect[j] = SPELL_EFFECT_TRIGGER_MISSILE;
|
|
count++;
|
|
break;
|
|
}
|
|
|
|
switch (SpellTargetType[spellInfo->EffectImplicitTargetA[j]])
|
|
{
|
|
case TARGET_TYPE_UNIT_TARGET:
|
|
case TARGET_TYPE_DEST_TARGET:
|
|
spellInfo->Targets |= TARGET_FLAG_UNIT;
|
|
count++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uint8 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
|
{
|
|
switch (spellInfo->EffectApplyAuraName[j])
|
|
{
|
|
case SPELL_AURA_MOD_POSSESS:
|
|
case SPELL_AURA_MOD_CONFUSE:
|
|
case SPELL_AURA_MOD_CHARM:
|
|
case SPELL_AURA_AOE_CHARM:
|
|
case SPELL_AURA_MOD_FEAR:
|
|
case SPELL_AURA_MOD_STUN:
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_AURA_CC;
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!_isPositiveEffect(i, 0, false))
|
|
{
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_NEGATIVE_EFF0;
|
|
count++;
|
|
}
|
|
if (!_isPositiveEffect(i, 1, false))
|
|
{
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_NEGATIVE_EFF1;
|
|
count++;
|
|
}
|
|
if (!_isPositiveEffect(i, 2, false))
|
|
{
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_NEGATIVE_EFF2;
|
|
count++;
|
|
}
|
|
|
|
if (spellInfo->SpellVisual[0] == 3879)
|
|
{
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_CONE_BACK;
|
|
count++;
|
|
}
|
|
|
|
if (spellInfo->activeIconID == 2158) // flight
|
|
{
|
|
spellInfo->Attributes |= SPELL_ATTR_PASSIVE;
|
|
count++;
|
|
}
|
|
|
|
switch(i)
|
|
{
|
|
// Bind
|
|
case 3286:
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_TARGET_ENEMY;
|
|
spellInfo->EffectImplicitTargetA[1] = TARGET_UNIT_TARGET_ENEMY;
|
|
count++;
|
|
break;
|
|
// Heroism
|
|
case 32182:
|
|
spellInfo->excludeCasterAuraSpell = 57723; // Exhaustion
|
|
count++;
|
|
break;
|
|
// Blazing Harpoon
|
|
case 61588:
|
|
spellInfo->MaxAffectedTargets = 1;
|
|
count++;
|
|
break;
|
|
// Bloodlust
|
|
case 2825:
|
|
spellInfo->excludeCasterAuraSpell = 57724; // Sated
|
|
count++;
|
|
break;
|
|
// Heart of the Crusader
|
|
case 20335:
|
|
case 20336:
|
|
case 20337:
|
|
// Glyph of Life Tap
|
|
case 63320:
|
|
// Entries were not updated after spell effect change, we have to do that manually :/
|
|
spellInfo->AttributesEx3 |= SPELL_ATTR_EX3_CAN_PROC_TRIGGERED;
|
|
count++;
|
|
break;
|
|
case 16007: // Draco-Incarcinatrix 900
|
|
// was 46, but effect is aura effect
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_NEARBY_ENTRY;
|
|
spellInfo->EffectImplicitTargetB[0] = TARGET_DST_NEARBY_ENTRY;
|
|
count++;
|
|
break;
|
|
case 26029: // dark glare
|
|
case 37433: // spout
|
|
case 43140: case 43215: // flame breath
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_CONE_LINE;
|
|
count++;
|
|
break;
|
|
case 24340: case 26558: case 28884: // Meteor
|
|
case 36837: case 38903: case 41276: // Meteor
|
|
case 57467: // Meteor
|
|
case 26789: // Shard of the Fallen Star
|
|
case 31436: // Malevolent Cleave
|
|
case 35181: // Dive Bomb
|
|
case 40810: case 43267: case 43268: // Saber Lash
|
|
case 42384: // Brutal Swipe
|
|
case 45150: // Meteor Slash
|
|
case 64422: case 64688: // Sonic Screech
|
|
case 72373: // Shared Suffering
|
|
case 71904: // Chaos Bane
|
|
case 70492: case 72505: // Ooze Eruption
|
|
case 72624: case 72625: // Ooze Eruption
|
|
// ONLY SPELLS WITH SPELLFAMILY_GENERIC and EFFECT_SCHOOL_DAMAGE
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_SHARE_DAMAGE;
|
|
count++;
|
|
break;
|
|
case 59725: // Improved Spell Reflection - aoe aura
|
|
// Target entry seems to be wrong for this spell :/
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_PARTY_CASTER;
|
|
spellInfo->EffectRadiusIndex[0] = 45;
|
|
count++;
|
|
break;
|
|
case 27820: // Mana Detonation
|
|
//case 28062: case 39090: // Positive/Negative Charge
|
|
//case 28085: case 39093:
|
|
case 69782: case 69796: // Ooze Flood
|
|
case 69798: case 69801: // Ooze Flood
|
|
case 69538: case 69553: case 69610: // Ooze Combine
|
|
case 71447: case 71481: // Bloodbolt Splash
|
|
case 71482: case 71483: // Bloodbolt Splash
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_EXCLUDE_SELF;
|
|
count++;
|
|
break;
|
|
case 44978: case 45001: case 45002: // Wild Magic
|
|
case 45004: case 45006: case 45010: // Wild Magic
|
|
case 31347: // Doom
|
|
case 41635: // Prayer of Mending
|
|
case 44869: // Spectral Blast
|
|
case 45027: // Revitalize
|
|
case 45976: // Muru Portal Channel
|
|
case 39365: // Thundering Storm
|
|
case 41071: // Raise Dead (HACK)
|
|
case 52124: // Sky Darkener Assault
|
|
case 42442: // Vengeance Landing Cannonfire
|
|
case 45863: // Cosmetic - Incinerate to Random Target
|
|
case 25425: // Shoot
|
|
case 45761: // Shoot
|
|
case 42611: // Shoot
|
|
case 62374: // Pursued
|
|
spellInfo->MaxAffectedTargets = 1;
|
|
count++;
|
|
break;
|
|
case 52479: // Gift of the Harvester
|
|
spellInfo->MaxAffectedTargets = 1;
|
|
// a trap always has dst = src?
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_DST_CASTER;
|
|
spellInfo->EffectImplicitTargetA[1] = TARGET_DST_CASTER;
|
|
count++;
|
|
break;
|
|
case 41376: // Spite
|
|
case 39992: // Needle Spine
|
|
case 29576: // Multi-Shot
|
|
case 40816: // Saber Lash
|
|
case 37790: // Spread Shot
|
|
case 46771: // Flame Sear
|
|
case 45248: // Shadow Blades
|
|
case 41303: // Soul Drain
|
|
case 54172: // Divine Storm (heal)
|
|
case 29213: // Curse of the Plaguebringer - Noth
|
|
case 28542: // Life Drain - Sapphiron
|
|
case 66588: // Flaming Spear
|
|
case 54171: // Divine Storm
|
|
spellInfo->MaxAffectedTargets = 3;
|
|
count++;
|
|
break;
|
|
case 38310: // Multi-Shot
|
|
case 53385: // Divine Storm (Damage)
|
|
spellInfo->MaxAffectedTargets = 4;
|
|
count++;
|
|
break;
|
|
case 42005: // Bloodboil
|
|
case 38296: // Spitfire Totem
|
|
case 37676: // Insidious Whisper
|
|
case 46008: // Negative Energy
|
|
case 45641: // Fire Bloom
|
|
case 55665: // Life Drain - Sapphiron (H)
|
|
case 28796: // Poison Bolt Volly - Faerlina
|
|
spellInfo->MaxAffectedTargets = 5;
|
|
count++;
|
|
break;
|
|
case 40827: // Sinful Beam
|
|
case 40859: // Sinister Beam
|
|
case 40860: // Vile Beam
|
|
case 40861: // Wicked Beam
|
|
case 54835: // Curse of the Plaguebringer - Noth (H)
|
|
case 54098: // Poison Bolt Volly - Faerlina (H)
|
|
spellInfo->MaxAffectedTargets = 10;
|
|
count++;
|
|
break;
|
|
case 50312: // Unholy Frenzy
|
|
spellInfo->MaxAffectedTargets = 15;
|
|
count++;
|
|
break;
|
|
case 38794: case 33711: //Murmur's Touch
|
|
spellInfo->MaxAffectedTargets = 1;
|
|
spellInfo->EffectTriggerSpell[0] = 33760;
|
|
count++;
|
|
break;
|
|
case 17941: // Shadow Trance
|
|
case 22008: // Netherwind Focus
|
|
case 31834: // Light's Grace
|
|
case 34754: // Clearcasting
|
|
case 34936: // Backlash
|
|
case 48108: // Hot Streak
|
|
case 51124: // Killing Machine
|
|
case 54741: // Firestarter
|
|
case 57761: // Fireball!
|
|
case 39805: // Lightning Overload
|
|
case 64823: // Item - Druid T8 Balance 4P Bonus
|
|
case 44401:
|
|
spellInfo->procCharges = 1;
|
|
count++;
|
|
break;
|
|
case 53390: // Tidal Wave
|
|
spellInfo->procCharges = 2;
|
|
count++;
|
|
break;
|
|
case 44544: // Fingers of Frost
|
|
spellInfo->EffectSpellClassMask[0] = flag96(685904631, 1151048, 0);
|
|
count++;
|
|
break;
|
|
case 74396: // Fingers of Frost visual buff
|
|
spellInfo->procCharges = 2;
|
|
spellInfo->StackAmount = 0;
|
|
count++;
|
|
break;
|
|
case 28200: // Ascendance (Talisman of Ascendance trinket)
|
|
spellInfo->procCharges = 6;
|
|
count++;
|
|
break;
|
|
case 47201: // Everlasting Affliction
|
|
case 47202:
|
|
case 47203:
|
|
case 47204:
|
|
case 47205:
|
|
// add corruption to affected spells
|
|
spellInfo->EffectSpellClassMask[1][0] |= 2;
|
|
count++;
|
|
break;
|
|
case 51852: // The Eye of Acherus (no spawn in phase 2 in db)
|
|
spellInfo->EffectMiscValue[0] |= 1;
|
|
count++;
|
|
break;
|
|
case 52025: // Cleansing Totem Effect
|
|
spellInfo->EffectDieSides[1] = 1;
|
|
count++;
|
|
break;
|
|
case 51904: // Summon Ghouls On Scarlet Crusade (core does not know the triggered spell is summon spell)
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER;
|
|
count++;
|
|
break;
|
|
case 29809: // Desecration Arm - 36 instead of 37 - typo? :/
|
|
spellInfo->EffectRadiusIndex[0] = 37;
|
|
count++;
|
|
break;
|
|
// Master Shapeshifter: missing stance data for forms other than bear - bear version has correct data
|
|
// To prevent aura staying on target after talent unlearned
|
|
case 48420:
|
|
spellInfo->Stances = 1 << (FORM_CAT - 1);
|
|
count++;
|
|
break;
|
|
case 48421:
|
|
spellInfo->Stances = 1 << (FORM_MOONKIN - 1);
|
|
count++;
|
|
break;
|
|
case 48422:
|
|
spellInfo->Stances = 1 << (FORM_TREE - 1);
|
|
count++;
|
|
break;
|
|
case 30421: // Nether Portal - Perseverence
|
|
spellInfo->EffectBasePoints[2] += 30000;
|
|
count++;
|
|
break;
|
|
// some dummy spell only has dest, should push caster in this case
|
|
case 62324: // Throw Passenger
|
|
spellInfo->Targets |= TARGET_FLAG_UNIT_CASTER;
|
|
count++;
|
|
break;
|
|
case 16834: // Natural shapeshifter
|
|
case 16835:
|
|
spellInfo->DurationIndex = 21;
|
|
count++;
|
|
break;
|
|
case 51735: // Ebon Plague
|
|
case 51734:
|
|
case 51726:
|
|
spellInfo->AttributesEx3 |= SPELL_ATTR_EX3_STACK_FOR_DIFF_CASTERS;
|
|
spellInfo->SpellFamilyFlags[2] = 0x10;
|
|
count++;
|
|
break;
|
|
case 41013: // Parasitic Shadowfiend Passive
|
|
spellInfo->EffectApplyAuraName[0] = 4; // proc debuff, and summon infinite fiends
|
|
count++;
|
|
break;
|
|
case 27892: // To Anchor 1
|
|
case 27928: // To Anchor 1
|
|
case 27935: // To Anchor 1
|
|
case 27915: // Anchor to Skulls
|
|
case 27931: // Anchor to Skulls
|
|
case 27937: // Anchor to Skulls
|
|
spellInfo->rangeIndex = 13;
|
|
count++;
|
|
break;
|
|
case 48743: // Death Pact
|
|
spellInfo->AttributesEx &= ~SPELL_ATTR_EX_CANT_TARGET_SELF;
|
|
count++;
|
|
break;
|
|
// target allys instead of enemies, target A is src_caster, spells with effect like that have ally target
|
|
// this is the only known exception, probably just wrong data
|
|
case 29214: // Wrath of the Plaguebringer
|
|
case 54836: // Wrath of the Plaguebringer
|
|
spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_AREA_ALLY_SRC;
|
|
spellInfo->EffectImplicitTargetB[1] = TARGET_UNIT_AREA_ALLY_SRC;
|
|
count++;
|
|
break;
|
|
case 31687: // Summon Water Elemental
|
|
// 322-330 switch - effect changed to dummy, target entry not changed in client:(
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER;
|
|
count++;
|
|
break;
|
|
case 25771: // Forbearance - wrong mechanic immunity in DBC since 3.0.x
|
|
spellInfo->EffectMiscValue[0] = MECHANIC_IMMUNE_SHIELD;
|
|
count++;
|
|
break;
|
|
case 64321: // Potent Pheromones
|
|
// spell should dispel area aura, but doesn't have the attribute
|
|
// may be db data bug, or blizz may keep reapplying area auras every update with checking immunity
|
|
// that will be clear if we get more spells with problem like this
|
|
spellInfo->AttributesEx |= SPELL_ATTR_EX_DISPEL_AURAS_ON_IMMUNITY;
|
|
count++;
|
|
break;
|
|
case 69055: // Saber Lash
|
|
case 70814: // Saber Lash
|
|
spellInfo->EffectRadiusIndex[0] = 8;
|
|
count++;
|
|
break;
|
|
case 69075: // Bone Storm
|
|
case 70834: // Bone Storm
|
|
case 70835: // Bone Storm
|
|
case 70836: // Bone Storm
|
|
spellInfo->EffectRadiusIndex[0] = 12;
|
|
count++;
|
|
break;
|
|
case 18500: // Wing Buffet
|
|
case 33086: // Wild Bite
|
|
case 49749: // Piercing Blow
|
|
case 52890: // Penetrating Strike
|
|
case 53454: // Impale
|
|
case 59446: // Impale
|
|
case 62383: // Shatter
|
|
case 64777: // Machine Gun
|
|
case 65239: // Machine Gun
|
|
case 65919: // Impale
|
|
case 67858: // Impale
|
|
case 67859: // Impale
|
|
case 67860: // Impale
|
|
case 69293: // Wing Buffet
|
|
case 74439: // Machine Gun
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_IGNORE_ARMOR;
|
|
count++;
|
|
break;
|
|
// Strength of the Pack
|
|
case 64381:
|
|
spellInfo->StackAmount = 4;
|
|
count++;
|
|
break;
|
|
// THESE SPELLS ARE WORKING CORRECTLY EVEN WITHOUT THIS HACK
|
|
// THE ONLY REASON ITS HERE IS THAT CURRENT GRID SYSTEM
|
|
// DOES NOT ALLOW FAR OBJECT SELECTION (dist > 333)
|
|
case 70781: // Light's Hammer Teleport
|
|
case 70856: // Oratory of the Damned Teleport
|
|
case 70857: // Rampart of Skulls Teleport
|
|
case 70858: // Deathbringer's Rise Teleport
|
|
case 70859: // Upper Spire Teleport
|
|
case 70860: // Frozen Throne Teleport
|
|
case 70861: // Sindragosa's Lair Teleport
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_DST_DB;
|
|
count++;
|
|
break;
|
|
case 63675: // Improved Devouring Plague
|
|
spellInfo->AttributesEx3 |= SPELL_ATTR_EX3_NO_DONE_BONUS;
|
|
count++;
|
|
break;
|
|
case 53241: // Marked for Death (Rank 1)
|
|
case 53243: // Marked for Death (Rank 2)
|
|
case 53244: // Marked for Death (Rank 3)
|
|
case 53245: // Marked for Death (Rank 4)
|
|
case 53246: // Marked for Death (Rank 5)
|
|
spellInfo->EffectSpellClassMask[0] = flag96(423937, 276955137, 2049);
|
|
count++;
|
|
break;
|
|
// this is here until targetAuraSpell and alike support SpellDifficulty.dbc
|
|
case 70459: // Ooze Eruption Search Effect
|
|
spellInfo->targetAuraSpell = 0;
|
|
count++;
|
|
break;
|
|
case 70728: // Exploit Weakness
|
|
case 70840: // Devious Minds
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER;
|
|
spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_PET;
|
|
count++;
|
|
break;
|
|
case 70893: // Culling The Herd
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_UNIT_CASTER;
|
|
spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_MASTER;
|
|
count++;
|
|
break;
|
|
case 71413: // Green Ooze Summon
|
|
case 71414: // Orange Ooze Summon
|
|
spellInfo->EffectImplicitTargetA[0] = TARGET_DEST_DEST;
|
|
count++;
|
|
break;
|
|
// THIS IS HERE BECAUSE COOLDOWN ON CREATURE PROCS IS NOT IMPLEMENTED
|
|
case 71604: // Mutated Strength
|
|
case 72673: // Mutated Strength
|
|
case 72674: // Mutated Strength
|
|
case 72675: // Mutated Strength
|
|
spellInfo->Effect[1] = 0;
|
|
count++;
|
|
break;
|
|
case 70447: // Volatile Ooze Adhesive
|
|
case 72836: // Volatile Ooze Adhesive
|
|
case 72837: // Volatile Ooze Adhesive
|
|
case 72838: // Volatile Ooze Adhesive
|
|
case 70672: // Gaseous Bloat
|
|
case 72455: // Gaseous Bloat
|
|
case 72832: // Gaseous Bloat
|
|
case 72833: // Gaseous Bloat
|
|
spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_TARGET_ENEMY;
|
|
spellInfo->EffectImplicitTargetB[1] = TARGET_UNIT_TARGET_ENEMY;
|
|
spellInfo->EffectImplicitTargetB[2] = TARGET_UNIT_TARGET_ENEMY;
|
|
count++;
|
|
break;
|
|
case 70911: // Unbound Plague
|
|
case 72854: // Unbound Plague
|
|
case 72855: // Unbound Plague
|
|
case 72856: // Unbound Plague
|
|
spellInfo->EffectImplicitTargetB[0] = TARGET_UNIT_TARGET_ENEMY;
|
|
count++;
|
|
break;
|
|
case 71518: // Unholy Infusion Quest Credit
|
|
case 72934: // Blood Infusion Quest Credit
|
|
case 72289: // Frost Infusion Quest Credit
|
|
spellInfo->EffectRadiusIndex[0] = 28; // another missing radius
|
|
count++;
|
|
break;
|
|
case 71708: // Empowered Flare
|
|
case 72785: // Empowered Flare
|
|
case 72786: // Empowered Flare
|
|
case 72787: // Empowered Flare
|
|
spellInfo->AttributesEx3 |= SPELL_ATTR_EX3_NO_DONE_BONUS;
|
|
count++;
|
|
break;
|
|
case 71340: // Pact of the Darkfallen
|
|
spellInfo->DurationIndex = 21;
|
|
count++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch(spellInfo->SpellFamilyName)
|
|
{
|
|
case SPELLFAMILY_WARRIOR:
|
|
// Shout
|
|
if (spellInfo->SpellFamilyFlags[0] & 0x20000 || spellInfo->SpellFamilyFlags[1] & 0x20)
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_AURA_CC;
|
|
else
|
|
break;
|
|
count++;
|
|
break;
|
|
case SPELLFAMILY_DRUID:
|
|
// Starfall Target Selection
|
|
if (spellInfo->SpellFamilyFlags[2] & 0x100)
|
|
spellInfo->MaxAffectedTargets = 2;
|
|
// Starfall AOE Damage
|
|
else if (spellInfo->SpellFamilyFlags[2] & 0x800000)
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_EXCLUDE_SELF;
|
|
// Roar
|
|
else if (spellInfo->SpellFamilyFlags[0] & 0x8)
|
|
mSpellCustomAttr[i] |= SPELL_ATTR_CU_AURA_CC;
|
|
else
|
|
break;
|
|
count++;
|
|
break;
|
|
// Do not allow Deadly throw and Slice and Dice to proc twice
|
|
case SPELLFAMILY_ROGUE:
|
|
if (spellInfo->SpellFamilyFlags[1] & 0x1 || spellInfo->SpellFamilyFlags[0] & 0x40000)
|
|
spellInfo->AttributesEx4 |= SPELL_ATTR_EX4_CANT_PROC_FROM_SELFCAST;
|
|
else
|
|
break;
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
SummonPropertiesEntry *properties = const_cast<SummonPropertiesEntry*>(sSummonPropertiesStore.LookupEntry(121));
|
|
properties->Type = SUMMON_TYPE_TOTEM;
|
|
properties = const_cast<SummonPropertiesEntry*>(sSummonPropertiesStore.LookupEntry(647)); // 52893
|
|
properties->Type = SUMMON_TYPE_TOTEM;
|
|
|
|
CreatureAI::FillAISpellInfo();
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u custom spell attributes", count);
|
|
}
|
|
|
|
// Fill custom data about enchancments
|
|
void SpellMgr::LoadEnchantCustomAttr()
|
|
{
|
|
uint32 size = sSpellItemEnchantmentStore.GetNumRows();
|
|
mEnchantCustomAttr.resize(size);
|
|
|
|
barGoLink bar(GetSpellStore()->GetNumRows());
|
|
|
|
uint32 count = 0;
|
|
|
|
for (uint32 i = 0; i < size; ++i)
|
|
mEnchantCustomAttr[i] = 0;
|
|
|
|
for (uint32 i = 0; i < GetSpellStore()->GetNumRows(); ++i)
|
|
{
|
|
bar.step();
|
|
|
|
SpellEntry * spellInfo = (SpellEntry*)GetSpellStore()->LookupEntry(i);
|
|
if (!spellInfo)
|
|
continue;
|
|
|
|
// TODO: find a better check
|
|
if (!(spellInfo->AttributesEx2 & SPELL_ATTR_EX2_UNK13) || !(spellInfo->Attributes & SPELL_ATTR_NOT_SHAPESHIFT))
|
|
continue;
|
|
|
|
for (uint32 j = 0; j < MAX_SPELL_EFFECTS; ++j)
|
|
{
|
|
if (spellInfo->Effect[j] == SPELL_EFFECT_ENCHANT_ITEM_TEMPORARY)
|
|
{
|
|
uint32 enchId = spellInfo->EffectMiscValue[j];
|
|
SpellItemEnchantmentEntry const *ench = sSpellItemEnchantmentStore.LookupEntry(enchId);
|
|
if (!ench)
|
|
continue;
|
|
mEnchantCustomAttr[enchId] = true;
|
|
count++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u custom enchant attributes", count);
|
|
}
|
|
|
|
void SpellMgr::LoadSpellLinked()
|
|
{
|
|
mSpellLinkedMap.clear(); // need for reload case
|
|
uint32 count = 0;
|
|
|
|
// 0 1 2
|
|
QueryResult result = WorldDatabase.Query("SELECT spell_trigger, spell_effect, type FROM spell_linked_spell");
|
|
if (!result)
|
|
{
|
|
barGoLink bar(1);
|
|
bar.step();
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u linked spells", count);
|
|
return;
|
|
}
|
|
|
|
barGoLink bar(result->GetRowCount());
|
|
|
|
do
|
|
{
|
|
Field *fields = result->Fetch();
|
|
|
|
bar.step();
|
|
|
|
int32 trigger = fields[0].GetInt32();
|
|
int32 effect = fields[1].GetInt32();
|
|
int32 type = fields[2].GetInt32();
|
|
|
|
SpellEntry const* spellInfo = sSpellStore.LookupEntry(abs(trigger));
|
|
if (!spellInfo)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_linked_spell` does not exist", abs(trigger));
|
|
continue;
|
|
}
|
|
spellInfo = sSpellStore.LookupEntry(abs(effect));
|
|
if (!spellInfo)
|
|
{
|
|
sLog.outErrorDb("Spell %u listed in `spell_linked_spell` does not exist", abs(effect));
|
|
continue;
|
|
}
|
|
|
|
if (trigger > 0)
|
|
{
|
|
switch(type)
|
|
{
|
|
case 0: mSpellCustomAttr[trigger] |= SPELL_ATTR_CU_LINK_CAST; break;
|
|
case 1: mSpellCustomAttr[trigger] |= SPELL_ATTR_CU_LINK_HIT; break;
|
|
case 2: mSpellCustomAttr[trigger] |= SPELL_ATTR_CU_LINK_AURA; break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
mSpellCustomAttr[-trigger] |= SPELL_ATTR_CU_LINK_REMOVE;
|
|
}
|
|
|
|
if (type) //we will find a better way when more types are needed
|
|
{
|
|
if (trigger > 0)
|
|
trigger += SPELL_LINKED_MAX_SPELLS * type;
|
|
else
|
|
trigger -= SPELL_LINKED_MAX_SPELLS * type;
|
|
}
|
|
mSpellLinkedMap[trigger].push_back(effect);
|
|
|
|
++count;
|
|
} while (result->NextRow());
|
|
|
|
sLog.outString();
|
|
sLog.outString(">> Loaded %u linked spells", count);
|
|
}
|