Core/Spells: Reimplemented automatic spell learning
* Fixed learning/unlearning talents
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
DELETE FROM `playercreateinfo_skills` WHERE `skill` IN (795,796,798,800,804,829,840,849,904,921,924);
|
||||
INSERT INTO `playercreateinfo_skills` (`raceMask`,`classMask`,`skill`,`rank`,`comment`) VALUES
|
||||
(0, 4, 795, 0, "Hunter skills."),
|
||||
(0, 32, 796, 0, "Death Knight skills."),
|
||||
(0, 1024, 798, 0, "Druid skills."),
|
||||
(0, 2, 800, 0, "Paladin skills."),
|
||||
(0, 16, 804, 0, "Priest skills."),
|
||||
(0, 512, 829, 0, "Monk skills."),
|
||||
(0, 1, 840, 0, "Warrior skills."),
|
||||
(0, 256, 849, 0, "Warlock skills."),
|
||||
(0, 128, 904, 0, "Mage skills."),
|
||||
(0, 8, 921, 0, "Rogue skills."),
|
||||
(0, 64, 924, 0, "Shaman skills.");
|
||||
|
||||
DELETE FROM `spell_ranks` WHERE `first_spell_id`=127650;
|
||||
INSERT INTO `spell_ranks` (`first_spell_id`,`spell_id`,`rank`) VALUES
|
||||
(127650,127650,1), -- Remove Talent: Vanishing Powder
|
||||
(127650,127649,2), -- Remove Talent: Dust of Disappearance
|
||||
(127650,113873,3); -- Remove Talent: Tome of the Clear Mind
|
||||
@@ -1078,7 +1078,7 @@ void Battleground::AddPlayer(Player* player)
|
||||
BattlegroundPlayer bp;
|
||||
bp.OfflineRemoveTime = 0;
|
||||
bp.Team = team;
|
||||
bp.ActiveSpec = player->GetActiveTalentSpec();
|
||||
bp.ActiveSpec = player->GetSpecId(player->GetActiveTalentGroup());
|
||||
|
||||
// Add to list/maps
|
||||
m_Players[player->GetGUID()] = bp;
|
||||
|
||||
@@ -76,7 +76,6 @@ DBCStorage <ChrRacesEntry> sChrRacesStore(ChrRacesEntryfmt);
|
||||
DBCStorage <ChrPowerTypesEntry> sChrPowerTypesStore(ChrClassesXPowerTypesfmt);
|
||||
DBCStorage <ChrSpecializationEntry> sChrSpecializationStore(ChrSpecializationEntryfmt);
|
||||
ChrSpecializationByIndexArray sChrSpecializationByIndexStore;
|
||||
SpecializationSpellsBySpecStore sSpecializationSpellsBySpecStore;
|
||||
DBCStorage <CinematicSequencesEntry> sCinematicSequencesStore(CinematicSequencesEntryfmt);
|
||||
DBCStorage <CreatureDisplayInfoEntry> sCreatureDisplayInfoStore(CreatureDisplayInfofmt);
|
||||
DBCStorage <CreatureDisplayInfoExtraEntry> sCreatureDisplayInfoExtraStore(CreatureDisplayInfoExtrafmt);
|
||||
@@ -199,6 +198,7 @@ DBCStorage <SkillTiersEntry> sSkillTiersStore(SkillTiersfmt);
|
||||
DBCStorage <SoundEntriesEntry> sSoundEntriesStore(SoundEntriesfmt);
|
||||
|
||||
DBCStorage <SpecializationSpellsEntry> sSpecializationSpellsStore(SpecializationSpellsEntryfmt);
|
||||
std::unordered_map<uint32, std::vector<SpecializationSpellsEntry const*>> sSpecializationSpellsBySpecStore;
|
||||
DBCStorage <SpellItemEnchantmentEntry> sSpellItemEnchantmentStore(SpellItemEnchantmentfmt);
|
||||
DBCStorage <SpellItemEnchantmentConditionEntry> sSpellItemEnchantmentConditionStore(SpellItemEnchantmentConditionfmt);
|
||||
DBCStorage <SpellEntry> sSpellStore(SpellEntryfmt);
|
||||
@@ -229,9 +229,7 @@ DBCStorage <SpellShapeshiftFormEntry> sSpellShapeshiftFormStore(SpellShapeshiftF
|
||||
DBCStorage <StableSlotPricesEntry> sStableSlotPricesStore(StableSlotPricesfmt);
|
||||
DBCStorage <SummonPropertiesEntry> sSummonPropertiesStore(SummonPropertiesfmt);
|
||||
DBCStorage <TalentEntry> sTalentStore(TalentEntryfmt);
|
||||
TalentBySpellIDMap sTalentBySpellIDMap;
|
||||
SpecializationSpellsMap sSpecializationSpellsMap;
|
||||
SpecializationOverrideSpellsMap sSpecializationOverrideSpellMap;
|
||||
TalentsByPosition sTalentByPos;
|
||||
|
||||
DBCStorage <TotemCategoryEntry> sTotemCategoryStore(TotemCategoryEntryfmt);
|
||||
DBCStorage <TransportAnimationEntry> sTransportAnimationStore(TransportAnimationfmt);
|
||||
@@ -546,16 +544,15 @@ void LoadDBCStores(const std::string& dataPath)
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sSkillTiersStore, dbcPath, "SkillTiers.dbc");
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sSoundEntriesStore, dbcPath, "SoundEntries.dbc");//15595
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sSpecializationSpellsStore, dbcPath, "SpecializationSpells.dbc");
|
||||
for (uint32 i = 1; i < sSpecializationSpellsStore.GetNumRows(); ++i)
|
||||
for (uint32 i = 0; i < sSpecializationSpellsStore.GetNumRows(); ++i)
|
||||
{
|
||||
SpecializationSpellsEntry const* specSpells = sSpecializationSpellsStore.LookupEntry(i);
|
||||
if (!specSpells)
|
||||
continue;
|
||||
sSpecializationSpellsBySpecStore[specSpells->SpecID].push_back(specSpells);
|
||||
|
||||
if (specSpells->OverridesSpellID)
|
||||
sSpecializationOverrideSpellMap[specSpells->SpecID][specSpells->OverridesSpellID] = specSpells->SpellID;
|
||||
sSpecializationSpellsBySpecStore[specSpells->SpecID].push_back(specSpells);
|
||||
}
|
||||
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sSpellStore, dbcPath, "Spell.dbc"/*, &CustomSpellEntryfmt, &CustomSpellEntryIndex*/);
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sSpellCategoriesStore, dbcPath, "SpellCategories.dbc");//15595
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sSpellCategoryStore, dbcPath, "SpellCategory.dbc");
|
||||
@@ -599,15 +596,17 @@ void LoadDBCStores(const std::string& dataPath)
|
||||
sSpellEffectScallingByEffectId.insert(std::make_pair(spellEffectScaling->SpellEffectID, j));
|
||||
}
|
||||
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sTalentStore, dbcPath, "Talent.dbc");//15595
|
||||
|
||||
for (unsigned int i = 0; i < sTalentStore.GetNumRows(); ++i)
|
||||
LoadDBC(availableDbcLocales, bad_dbc_files, sTalentStore, dbcPath, "Talent.dbc");//19342
|
||||
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
|
||||
if (!talentInfo)
|
||||
continue;
|
||||
|
||||
sTalentBySpellIDMap[talentInfo->SpellID] = talentInfo;
|
||||
if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(i))
|
||||
{
|
||||
if (talentInfo->ClassID < MAX_CLASSES && talentInfo->TierID < MAX_TALENT_TIERS && talentInfo->ColumnIndex < MAX_TALENT_COLUMNS)
|
||||
sTalentByPos[talentInfo->ClassID][talentInfo->TierID][talentInfo->ColumnIndex].push_back(talentInfo);
|
||||
else
|
||||
TC_LOG_ERROR("server.loading", "Value of class (found: %u, max allowed %u) or (found: %u, max allowed %u) tier or column (found: %u, max allowed %u) is invalid.",
|
||||
talentInfo->ClassID, MAX_CLASSES, talentInfo->TierID, MAX_TALENT_TIERS, talentInfo->ColumnIndex, MAX_TALENT_COLUMNS);
|
||||
}
|
||||
}
|
||||
|
||||
//LoadDBC(availableDbcLocales, bad_dbc_files, sTeamContributionPointsStore, dbcPath, "TeamContributionPoints.dbc");
|
||||
@@ -967,14 +966,6 @@ PvPDifficultyEntry const* GetBattlegroundBracketById(uint32 mapid, BattlegroundB
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TalentEntry const* GetTalentBySpellID(uint32 spellID)
|
||||
{
|
||||
auto itr = sTalentBySpellIDMap.find(spellID);
|
||||
if (itr != sTalentBySpellIDMap.end())
|
||||
return itr->second;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32 GetLiquidFlags(uint32 liquidType)
|
||||
{
|
||||
if (LiquidTypeEntry const* liq = sLiquidTypeStore.LookupEntry(liquidType))
|
||||
@@ -1044,14 +1035,6 @@ SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, u
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32 GetTalentSpellCost(uint32 spellId)
|
||||
{
|
||||
TalentBySpellIDMap::const_iterator itr = sTalentBySpellIDMap.find(spellId);
|
||||
if (itr == sTalentBySpellIDMap.end())
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32 GetQuestUniqueBitFlag(uint32 questId)
|
||||
{
|
||||
QuestV2Entry const* v2 = sQuestV2Store.LookupEntry(questId);
|
||||
@@ -1060,3 +1043,12 @@ uint32 GetQuestUniqueBitFlag(uint32 questId)
|
||||
|
||||
return v2->UniqueBitFlag;
|
||||
}
|
||||
|
||||
std::vector<SpecializationSpellsEntry const*> const* GetSpecializationSpells(uint32 specId)
|
||||
{
|
||||
auto itr = sSpecializationSpellsBySpecStore.find(specId);
|
||||
if (itr != sSpecializationSpellsBySpecStore.end())
|
||||
return &itr->second;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,6 @@ typedef std::list<uint32> SimpleFactionsList;
|
||||
SimpleFactionsList const* GetFactionTeamList(uint32 faction);
|
||||
|
||||
char const* GetPetName(uint32 petfamily, uint32 dbclang);
|
||||
uint32 GetTalentSpellCost(uint32 spellId);
|
||||
TalentEntry const* GetTalentBySpellID(uint32 spellID);
|
||||
|
||||
int32 GetAreaFlagByAreaID(uint32 area_id); // -1 if not found
|
||||
AreaTableEntry const* GetAreaEntryByAreaID(uint32 area_id);
|
||||
@@ -91,14 +89,10 @@ typedef std::unordered_multimap<uint32, SkillRaceClassInfoEntry const*> SkillRac
|
||||
typedef std::pair<SkillRaceClassInfoMap::iterator, SkillRaceClassInfoMap::iterator> SkillRaceClassInfoBounds;
|
||||
SkillRaceClassInfoEntry const* GetSkillRaceClassInfo(uint32 skill, uint8 race, uint8 class_);
|
||||
|
||||
typedef std::vector<SpecializationSpellsEntry const*> SpecializationSpellsBySpecEntry;
|
||||
typedef std::unordered_map<uint32, SpecializationSpellsBySpecEntry> SpecializationSpellsBySpecStore;
|
||||
typedef ChrSpecializationEntry const* ChrSpecializationByIndexArray[MAX_CLASSES][MAX_SPECIALIZATIONS];
|
||||
typedef std::unordered_map<uint32, TalentEntry const*> TalentBySpellIDMap;
|
||||
|
||||
typedef std::map<uint32, std::vector<uint32> > SpecializationSpellsMap;
|
||||
extern SpecializationSpellsMap sSpecializationSpellsMap;
|
||||
extern SpecializationOverrideSpellsMap sSpecializationOverrideSpellMap;
|
||||
std::vector<SpecializationSpellsEntry const*> const* GetSpecializationSpells(uint32 specId);
|
||||
typedef std::vector<TalentEntry const*> TalentsByPosition[MAX_CLASSES][MAX_TALENT_TIERS][MAX_TALENT_COLUMNS];
|
||||
extern TalentsByPosition sTalentByPos;
|
||||
|
||||
template<class T>
|
||||
class GameTable
|
||||
@@ -243,8 +237,6 @@ extern DBCStorage <SkillLineAbilityEntry> sSkillLineAbilityStore;
|
||||
extern DBCStorage <SkillTiersEntry> sSkillTiersStore;
|
||||
extern DBCStorage <SoundEntriesEntry> sSoundEntriesStore;
|
||||
extern SpellEffectScallingByEffectId sSpellEffectScallingByEffectId;
|
||||
extern DBCStorage <SpecializationSpellsEntry> sSpecializationSpellsStore;
|
||||
extern SpecializationSpellsBySpecStore sSpecializationSpellsBySpecStore;
|
||||
extern DBCStorage <SpellCastTimesEntry> sSpellCastTimesStore;
|
||||
extern DBCStorage <SpellCategoryEntry> sSpellCategoryStore;
|
||||
extern DBCStorage <SpellDurationEntry> sSpellDurationStore;
|
||||
@@ -271,7 +263,6 @@ extern DBCStorage <SpellTargetRestrictionsEntry> sSpellTargetRestrictionsStore;
|
||||
//extern DBCStorage <StableSlotPricesEntry> sStableSlotPricesStore;
|
||||
extern DBCStorage <SummonPropertiesEntry> sSummonPropertiesStore;
|
||||
extern DBCStorage <TalentEntry> sTalentStore;
|
||||
extern TalentBySpellIDMap sTalentBySpellIDMap;
|
||||
extern DBCStorage <TotemCategoryEntry> sTotemCategoryStore;
|
||||
extern DBCStorage <UnitPowerBarEntry> sUnitPowerBarStore;
|
||||
extern DBCStorage <VehicleEntry> sVehicleStore;
|
||||
|
||||
@@ -1788,6 +1788,7 @@ struct SummonPropertiesEntry
|
||||
};
|
||||
|
||||
#define MAX_TALENT_TIERS 7
|
||||
#define MAX_TALENT_COLUMNS 3
|
||||
|
||||
struct TalentEntry
|
||||
{
|
||||
|
||||
@@ -2998,13 +2998,14 @@ void Player::InitTalentForLevel()
|
||||
{
|
||||
uint8 level = getLevel();
|
||||
// talents base at level diff (talents = level - 9 but some can be used already)
|
||||
if (level < MIN_SPECIALIZATION_LEVEL)
|
||||
ResetTalentSpecialization();
|
||||
|
||||
uint32 talentTiers = CalculateTalentsTiers();
|
||||
if (level < 15)
|
||||
{
|
||||
// Remove all talent points
|
||||
if (GetUsedTalentCount() > 0) // Free any used talents
|
||||
{
|
||||
ResetTalents(true);
|
||||
}
|
||||
ResetTalents(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3013,9 +3014,15 @@ void Player::InitTalentForLevel()
|
||||
SetTalentGroupsCount(1);
|
||||
SetActiveTalentGroup(0);
|
||||
}
|
||||
|
||||
if (!GetSession()->HasPermission(rbac::RBAC_PERM_SKIP_CHECK_MORE_TALENTS_THAN_ALLOWED))
|
||||
for (uint32 t = talentTiers; t < MAX_TALENT_TIERS; ++t)
|
||||
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
|
||||
for (TalentEntry const* talent : sTalentByPos[getClass()][t][c])
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
|
||||
SetUInt32Value(PLAYER_FIELD_MAX_TALENT_TIERS, CalculateTalentsPoints());
|
||||
SetUInt32Value(PLAYER_FIELD_MAX_TALENT_TIERS, talentTiers);
|
||||
|
||||
if (!GetSession()->PlayerLoading())
|
||||
SendTalentsInfoData(); // update at client
|
||||
@@ -3294,29 +3301,20 @@ void DeleteSpellFromAllPlayers(uint32 spellId)
|
||||
}
|
||||
}
|
||||
|
||||
bool Player::AddTalent(uint32 talentId, uint8 spec, bool learning)
|
||||
bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
|
||||
{
|
||||
TalentEntry const* talentEntry = sTalentStore.LookupEntry(talentId);
|
||||
|
||||
// Check if talent exists in Talent.dbc
|
||||
if (!talentEntry)
|
||||
{
|
||||
TC_LOG_ERROR("spells", "Player::addTalent: Talent %u not found", talentId);
|
||||
return false;
|
||||
}
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentEntry->SpellID);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID);
|
||||
if (!spellInfo)
|
||||
{
|
||||
// do character spell book cleanup (all characters)
|
||||
if (!IsInWorld()) // spell load case
|
||||
if (!IsInWorld() && !learning) // spell load case
|
||||
{
|
||||
TC_LOG_ERROR("spells", "Player::addSpell: Non-existed in SpellStore spell #%u request, deleting for all characters in `character_spell`.", talentEntry->SpellID);
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Non-existed in SpellStore spell #%u request, deleting for all characters in `character_spell`.", talent->SpellID);
|
||||
|
||||
DeleteSpellFromAllPlayers(talentEntry->SpellID);
|
||||
DeleteSpellFromAllPlayers(talent->SpellID);
|
||||
}
|
||||
else
|
||||
TC_LOG_ERROR("spells", "Player::addSpell: Non-existed in SpellStore spell #%u request.", talentEntry->SpellID);
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Non-existed in SpellStore spell #%u request.", talent->SpellID);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -3324,40 +3322,58 @@ bool Player::AddTalent(uint32 talentId, uint8 spec, bool learning)
|
||||
if (!SpellMgr::IsSpellValid(spellInfo, this, false))
|
||||
{
|
||||
// do character spell book cleanup (all characters)
|
||||
if (!IsInWorld()) // spell load case
|
||||
if (!IsInWorld() && !learning) // spell load case
|
||||
{
|
||||
TC_LOG_ERROR("spells", "Player::addTalent: Broken spell #%u learning not allowed, deleting for all characters in `character_talent`.", talentEntry->SpellID);
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Broken spell #%u learning not allowed, deleting for all characters in `character_talent`.", talent->SpellID);
|
||||
|
||||
DeleteSpellFromAllPlayers(talentEntry->SpellID);
|
||||
DeleteSpellFromAllPlayers(talent->SpellID);
|
||||
}
|
||||
else
|
||||
TC_LOG_ERROR("spells", "Player::addTalent: Broken spell #%u learning not allowed.", talentEntry->SpellID);
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Broken spell #%u learning not allowed.", talent->SpellID);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talentId);
|
||||
if (itr == GetTalentMap(spec)->end())
|
||||
{
|
||||
//if (GetTalentBySpellID(talentEntry->SpellID))
|
||||
{
|
||||
PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
|
||||
PlayerTalent* newtalent = new PlayerTalent();
|
||||
if (talent->OverridesSpellID)
|
||||
AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
|
||||
|
||||
newtalent->state = state;
|
||||
newtalent->spec = spec;
|
||||
|
||||
(*GetTalentMap(spec))[talentId] = newtalent;
|
||||
|
||||
return true;
|
||||
}
|
||||
//else
|
||||
// TC_LOG_ERROR("spells", "Player::addTalent: Talent %u not found in talent store.", talentId);
|
||||
}
|
||||
else
|
||||
PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talent->ID);
|
||||
if (itr != GetTalentMap(spec)->end())
|
||||
itr->second->state = PLAYERSPELL_UNCHANGED;
|
||||
else
|
||||
{
|
||||
PlayerSpellState state = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
|
||||
PlayerTalent* newtalent = new PlayerTalent();
|
||||
|
||||
return false;
|
||||
newtalent->state = state;
|
||||
newtalent->spec = spec;
|
||||
|
||||
(*GetTalentMap(spec))[talent->ID] = newtalent;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::RemoveTalent(TalentEntry const* talent)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID);
|
||||
if (!spellInfo)
|
||||
return;
|
||||
|
||||
RemoveSpell(talent->SpellID, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
for (SpellEffectInfo const* effect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE))
|
||||
if (effect && effect->TriggerSpell > 0 && effect->Effect == SPELL_EFFECT_LEARN_SPELL)
|
||||
RemoveSpell(effect->TriggerSpell, true);
|
||||
|
||||
if (talent->OverridesSpellID)
|
||||
RemoveOverrideSpell(talent->OverridesSpellID, talent->SpellID);
|
||||
|
||||
// if this talent rank can be found in the PlayerTalentMap, mark the talent as removed so it gets deleted
|
||||
PlayerTalentMap::iterator plrTalent = GetTalentMap(GetActiveTalentGroup())->find(talent->ID);
|
||||
if (plrTalent != GetTalentMap(GetActiveTalentGroup())->end())
|
||||
plrTalent->second->state = PLAYERSPELL_REMOVED;
|
||||
}
|
||||
|
||||
bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent, bool disabled, bool loading /*= false*/, bool fromSkill /*= false*/)
|
||||
@@ -3453,7 +3469,7 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent
|
||||
if (active)
|
||||
{
|
||||
if (spellInfo->IsPassive() && IsNeedCastPassiveSpellAtLearn(spellInfo))
|
||||
CastSpell (this, spellId, true);
|
||||
CastSpell(this, spellId, true);
|
||||
}
|
||||
else if (IsInWorld())
|
||||
{
|
||||
@@ -3569,11 +3585,9 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 talentCost = GetTalentSpellCost(spellId);
|
||||
|
||||
// cast talents with SPELL_EFFECT_LEARN_SPELL (other dependent spells will learned later as not auto-learned)
|
||||
// note: all spells with SPELL_EFFECT_LEARN_SPELL isn't passive
|
||||
if (!loading && talentCost > 0 && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
|
||||
if (!loading && spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT) && spellInfo->HasEffect(SPELL_EFFECT_LEARN_SPELL))
|
||||
{
|
||||
// ignore stance requirement for talent learn spell (stance set for spell only for client spell description show)
|
||||
CastSpell(this, spellId, true);
|
||||
@@ -3590,72 +3604,45 @@ bool Player::AddSpell(uint32 spellId, bool active, bool learning, bool dependent
|
||||
return false;
|
||||
}
|
||||
|
||||
// update used talent points count
|
||||
SetUsedTalentCount(GetUsedTalentCount() + talentCost);
|
||||
|
||||
// update free primary prof.points (if any, can be none in case GM .learn prof. learning)
|
||||
if (uint32 freeProfs = GetFreePrimaryProfessionPoints())
|
||||
{
|
||||
if (spellInfo->IsPrimaryProfessionFirstRank())
|
||||
SetFreePrimaryProfessions(freeProfs-1);
|
||||
SetFreePrimaryProfessions(freeProfs - 1);
|
||||
}
|
||||
|
||||
// add dependent skills
|
||||
uint16 maxskill = GetMaxSkillValueForLevel();
|
||||
|
||||
SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spellId);
|
||||
|
||||
SkillLineAbilityMapBounds skill_bounds = sSpellMgr->GetSkillLineAbilityMapBounds(spellId);
|
||||
|
||||
if (spellLearnSkill)
|
||||
// add dependent skills if this spell is not learned from adding skill already
|
||||
if (!fromSkill)
|
||||
{
|
||||
uint32 skill_value = GetPureSkillValue(spellLearnSkill->skill);
|
||||
uint32 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill);
|
||||
|
||||
if (skill_value < spellLearnSkill->value)
|
||||
skill_value = spellLearnSkill->value;
|
||||
|
||||
uint32 new_skill_max_value = spellLearnSkill->maxvalue == 0 ? maxskill : spellLearnSkill->maxvalue;
|
||||
|
||||
if (skill_max_value < new_skill_max_value)
|
||||
skill_max_value = new_skill_max_value;
|
||||
|
||||
SetSkill(spellLearnSkill->skill, spellLearnSkill->step, skill_value, skill_max_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not ranked skills
|
||||
for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
|
||||
if (SpellLearnSkillNode const* spellLearnSkill = sSpellMgr->GetSpellLearnSkill(spellId))
|
||||
{
|
||||
SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->SkillLine);
|
||||
if (!pSkill)
|
||||
continue;
|
||||
uint32 skill_value = GetPureSkillValue(spellLearnSkill->skill);
|
||||
uint32 skill_max_value = GetPureMaxSkillValue(spellLearnSkill->skill);
|
||||
|
||||
if (HasSkill(pSkill->ID))
|
||||
continue;
|
||||
if (skill_value < spellLearnSkill->value)
|
||||
skill_value = spellLearnSkill->value;
|
||||
|
||||
SkillRaceClassInfoEntry const* rcEntry = GetSkillRaceClassInfo(pSkill->ID, getRace(), getClass());
|
||||
if (!rcEntry)
|
||||
continue;
|
||||
uint32 new_skill_max_value = spellLearnSkill->maxvalue == 0 ? GetMaxSkillValueForLevel() : spellLearnSkill->maxvalue;
|
||||
|
||||
if (_spell_idx->second->AquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN ||
|
||||
// lockpicking/runeforging special case, not have SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN
|
||||
((pSkill->ID == SKILL_LOCKPICKING || pSkill->ID == SKILL_RUNEFORGING) && (_spell_idx->second->TrivialSkillLineRankHigh == 0 || _spell_idx->second->TrivialSkillLineRankHigh == 1)))
|
||||
if (skill_max_value < new_skill_max_value)
|
||||
skill_max_value = new_skill_max_value;
|
||||
|
||||
SetSkill(spellLearnSkill->skill, spellLearnSkill->step, skill_value, skill_max_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not ranked skills
|
||||
for (SkillLineAbilityMap::const_iterator _spell_idx = skill_bounds.first; _spell_idx != skill_bounds.second; ++_spell_idx)
|
||||
{
|
||||
switch (GetSkillRangeType(rcEntry))
|
||||
{
|
||||
case SKILL_RANGE_LANGUAGE:
|
||||
SetSkill(pSkill->ID, GetSkillStep(pSkill->ID), 300, 300);
|
||||
break;
|
||||
case SKILL_RANGE_LEVEL:
|
||||
SetSkill(pSkill->ID, GetSkillStep(pSkill->ID), 1, GetMaxSkillValueForLevel());
|
||||
break;
|
||||
case SKILL_RANGE_MONO:
|
||||
SetSkill(pSkill->ID, GetSkillStep(pSkill->ID), 1, 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
SkillLineEntry const* pSkill = sSkillLineStore.LookupEntry(_spell_idx->second->SkillLine);
|
||||
if (!pSkill)
|
||||
continue;
|
||||
|
||||
///@todo: confirm if rogues start with lockpicking skill at level 1 but only receive the spell to use it at level 16
|
||||
if ((_spell_idx->second->AquireMethod == SKILL_LINE_ABILITY_LEARNED_ON_SKILL_LEARN && !HasSkill(pSkill->ID)) || (pSkill->ID == SKILL_LOCKPICKING && _spell_idx->second->TrivialSkillLineRankHigh == 0))
|
||||
LearnDefaultSkill(pSkill->ID, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3734,8 +3721,7 @@ bool Player::IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const
|
||||
|
||||
bool Player::IsCurrentSpecMasterySpell(SpellInfo const* spellInfo) const
|
||||
{
|
||||
|
||||
if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetActiveTalentSpec()))
|
||||
if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup())))
|
||||
return spellInfo->Id == chrSpec->MasterySpellID[0] || spellInfo->Id == chrSpec->MasterySpellID[1];
|
||||
|
||||
return false;
|
||||
@@ -3790,7 +3776,8 @@ void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank)
|
||||
// unlearn non talent higher ranks (recursive)
|
||||
if (uint32 nextSpell = sSpellMgr->GetNextSpellInChain(spell_id))
|
||||
{
|
||||
if (HasSpell(nextSpell) && !GetTalentBySpellID(nextSpell))
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(nextSpell);
|
||||
if (HasSpell(nextSpell) && !spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT))
|
||||
RemoveSpell(nextSpell, disabled, false);
|
||||
}
|
||||
//unlearn spells dependent from recently removed spells
|
||||
@@ -3916,6 +3903,8 @@ void Player::RemoveSpell(uint32 spell_id, bool disabled, bool learn_low_rank)
|
||||
}
|
||||
}
|
||||
|
||||
m_overrideSpells.erase(spell_id);
|
||||
|
||||
if (spell_id == 46917 && m_canTitanGrip)
|
||||
SetCanTitanGrip(false);
|
||||
|
||||
@@ -4118,11 +4107,8 @@ uint32 Player::GetNextResetTalentsCost() const
|
||||
}
|
||||
}
|
||||
|
||||
bool Player::ResetTalents(bool noCost, bool resetTalents, bool resetSpecialization)
|
||||
bool Player::ResetTalents(bool noCost)
|
||||
{
|
||||
if (!resetTalents && !resetSpecialization)
|
||||
return false;
|
||||
|
||||
sScriptMgr->OnPlayerTalentsReset(this, noCost);
|
||||
|
||||
// not need after this call
|
||||
@@ -4144,41 +4130,30 @@ bool Player::ResetTalents(bool noCost, bool resetTalents, bool resetSpecializati
|
||||
|
||||
RemovePet(NULL, PET_SAVE_NOT_IN_SLOT, true);
|
||||
|
||||
if (resetTalents)
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
{
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
if (!talentInfo)
|
||||
continue;
|
||||
|
||||
if (!talentInfo)
|
||||
continue;
|
||||
// unlearn only talents for character class
|
||||
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
|
||||
// to prevent unexpected lost normal learned spell skip another class talents
|
||||
if (talentInfo->ClassID != getClass())
|
||||
continue;
|
||||
|
||||
// unlearn only talents for character class
|
||||
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
|
||||
// to prevent unexpected lost normal learned spell skip another class talents
|
||||
if (talentInfo->ClassID != getClass())
|
||||
continue;
|
||||
// skip non-existant talent ranks
|
||||
if (talentInfo->SpellID == 0)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID);
|
||||
if (!spellEntry)
|
||||
continue;
|
||||
|
||||
RemoveSpell(spellEntry->Id, false);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them, 6.x remove?
|
||||
for (SpellEffectInfo const* effect : spellEntry->GetEffectsForDifficulty(DIFFICULTY_NONE))
|
||||
if (effect && effect->TriggerSpell > 0 && effect->Effect == SPELL_EFFECT_LEARN_SPELL)
|
||||
RemoveSpell(effect->TriggerSpell, true);
|
||||
|
||||
GetTalentMap(GetActiveTalentGroup())->erase(talentId);
|
||||
}
|
||||
RemoveTalent(talentInfo);
|
||||
}
|
||||
|
||||
if (resetSpecialization)
|
||||
{
|
||||
SetTalentSpec(GetActiveTalentGroup(), 0);
|
||||
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, 0);
|
||||
}
|
||||
// Remove spec specific spells
|
||||
RemoveSpecializationSpells();
|
||||
|
||||
SetSpecId(GetActiveTalentGroup(), 0);
|
||||
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, 0);
|
||||
|
||||
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
_SaveTalents(trans);
|
||||
@@ -4206,35 +4181,6 @@ bool Player::ResetTalents(bool noCost, bool resetTalents, bool resetSpecializati
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Player::RemoveTalent(uint32 talentId)
|
||||
{
|
||||
TalentEntry const* talent = sTalentStore.LookupEntry(talentId);
|
||||
if (!talent)
|
||||
return false;
|
||||
|
||||
uint32 spellId = talent->SpellID;
|
||||
|
||||
SpellInfo const* unlearnSpellProto = sSpellMgr->GetSpellInfo(spellId);
|
||||
|
||||
RemoveSpell(spellId, false);
|
||||
|
||||
// 6.x remove?
|
||||
for (SpellEffectInfo const* effect : unlearnSpellProto->GetEffectsForDifficulty(DIFFICULTY_NONE))
|
||||
if (effect && effect->TriggerSpell > 0 && effect->Effect == SPELL_EFFECT_LEARN_SPELL)
|
||||
RemoveSpell(effect->TriggerSpell, false);
|
||||
|
||||
GetTalentMap(GetActiveTalentSpec())->erase(talentId);
|
||||
|
||||
// Needs to be executed orthewise the talents will be screwedsx
|
||||
SQLTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
_SaveTalents(trans);
|
||||
_SaveSpells(trans);
|
||||
CharacterDatabase.CommitTransaction(trans);
|
||||
|
||||
SendTalentsInfoData();
|
||||
return true;
|
||||
}
|
||||
|
||||
Mail* Player::GetMail(uint32 id)
|
||||
{
|
||||
for (PlayerMails::iterator itr = m_mail.begin(); itr != m_mail.end(); ++itr)
|
||||
@@ -4299,10 +4245,10 @@ bool Player::HasSpell(uint32 spell) const
|
||||
!itr->second->disabled);
|
||||
}
|
||||
|
||||
bool Player::HasTalent(uint32 talentId, uint8 group)
|
||||
bool Player::HasTalent(uint32 talentId, uint8 group) const
|
||||
{
|
||||
PlayerTalentMap::const_iterator itr = GetTalentMap(group)->find(talentId);
|
||||
return (itr != GetTalentMap(group)->end());
|
||||
return (itr != GetTalentMap(group)->end() && itr->second->state != PLAYERSPELL_REMOVED);
|
||||
}
|
||||
|
||||
bool Player::HasActiveSpell(uint32 spell) const
|
||||
@@ -5951,26 +5897,28 @@ void Player::UpdateSkillsForLevel()
|
||||
if (!rcEntry)
|
||||
continue;
|
||||
|
||||
if (GetSkillRangeType(rcEntry) != SKILL_RANGE_LEVEL)
|
||||
continue;
|
||||
|
||||
if (IsWeaponSkill(rcEntry->SkillID))
|
||||
continue;
|
||||
|
||||
uint16 field = itr->second.pos / 2;
|
||||
uint8 offset = itr->second.pos & 1; // itr->second.pos % 2
|
||||
|
||||
//uint16 val = GetUInt16Value(PLAYER_SKILL_LINEID + SKILL_RANK_OFFSET + field, offset);
|
||||
uint16 max = GetUInt16Value(PLAYER_SKILL_LINEID + SKILL_MAX_RANK_OFFSET + field, offset);
|
||||
|
||||
/// update only level dependent max skill values
|
||||
if (max != 1)
|
||||
if (GetSkillRangeType(rcEntry) == SKILL_RANGE_LEVEL)
|
||||
{
|
||||
SetUInt16Value(PLAYER_SKILL_LINEID + SKILL_RANK_OFFSET + field, offset, maxSkill);
|
||||
SetUInt16Value(PLAYER_SKILL_LINEID + SKILL_MAX_RANK_OFFSET + field, offset, maxSkill);
|
||||
if (itr->second.uState != SKILL_NEW)
|
||||
itr->second.uState = SKILL_CHANGED;
|
||||
if (!IsWeaponSkill(rcEntry->SkillID))
|
||||
{
|
||||
uint16 max = GetUInt16Value(PLAYER_SKILL_LINEID + SKILL_MAX_RANK_OFFSET + field, offset);
|
||||
|
||||
/// update only level dependent max skill values
|
||||
if (max != 1)
|
||||
{
|
||||
SetUInt16Value(PLAYER_SKILL_LINEID + SKILL_RANK_OFFSET + field, offset, maxSkill);
|
||||
SetUInt16Value(PLAYER_SKILL_LINEID + SKILL_MAX_RANK_OFFSET + field, offset, maxSkill);
|
||||
if (itr->second.uState != SKILL_NEW)
|
||||
itr->second.uState = SKILL_CHANGED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update level dependent skillline spells
|
||||
LearnSkillRewardedSpells(rcEntry->SkillID, GetUInt16Value(PLAYER_SKILL_LINEID + SKILL_RANK_OFFSET + field, offset));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8415,7 +8363,7 @@ void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, uint8
|
||||
Spell* spell = new Spell(this, spellInfo, (count > 0) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
|
||||
spell->m_CastItem = item;
|
||||
spell->m_cast_count = cast_count; // set count of casts
|
||||
spell->m_glyphIndex = glyphIndex; // glyph index
|
||||
spell->m_misc.Data = glyphIndex; // glyph index
|
||||
spell->prepare(&targets);
|
||||
|
||||
++count;
|
||||
@@ -8443,7 +8391,7 @@ void Player::CastItemUseSpell(Item* item, SpellCastTargets const& targets, uint8
|
||||
Spell* spell = new Spell(this, spellInfo, (count > 0) ? TRIGGERED_FULL_MASK : TRIGGERED_NONE);
|
||||
spell->m_CastItem = item;
|
||||
spell->m_cast_count = cast_count; // set count of casts
|
||||
spell->m_glyphIndex = glyphIndex; // glyph index
|
||||
spell->m_misc.Data = glyphIndex; // glyph index
|
||||
spell->prepare(&targets);
|
||||
|
||||
++count;
|
||||
@@ -17193,17 +17141,19 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
|
||||
if (talentSpec)
|
||||
{
|
||||
if (sChrSpecializationStore.LookupEntry(talentSpec))
|
||||
SetTalentSpec(i, talentSpec);
|
||||
SetSpecId(i, talentSpec);
|
||||
else
|
||||
SetAtLoginFlag(AT_LOGIN_RESET_TALENTS);
|
||||
}
|
||||
}
|
||||
|
||||
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, GetActiveTalentSpec());
|
||||
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, GetSpecId(GetActiveTalentGroup()));
|
||||
|
||||
_LoadTalents(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS));
|
||||
_LoadSpells(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELLS));
|
||||
|
||||
LearnSpecializationSpells();
|
||||
|
||||
_LoadGlyphs(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_GLYPHS));
|
||||
_LoadAuras(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AURAS), holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_AURA_EFFECTS), time_diff);
|
||||
_LoadGlyphAuras();
|
||||
@@ -18912,7 +18862,7 @@ void Player::SaveToDB(bool create /*=false*/)
|
||||
|
||||
ss.str("");
|
||||
for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
|
||||
ss << GetTalentSpec(i) << " ";
|
||||
ss << GetSpecId(i) << " ";
|
||||
stmt->setString(index++, ss.str());
|
||||
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
|
||||
stmt->setUInt8(index++, m_stableSlots);
|
||||
@@ -19049,7 +18999,7 @@ void Player::SaveToDB(bool create /*=false*/)
|
||||
|
||||
ss.str("");
|
||||
for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
|
||||
ss << GetTalentSpec(i) << " ";
|
||||
ss << GetSpecId(i) << " ";
|
||||
stmt->setString(index++, ss.str());
|
||||
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
|
||||
stmt->setUInt8(index++, m_stableSlots);
|
||||
@@ -25404,43 +25354,60 @@ void Player::CompletedAchievement(AchievementEntry const* entry)
|
||||
bool Player::LearnTalent(uint32 talentId)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
|
||||
if (!talentInfo)
|
||||
return false;
|
||||
|
||||
uint32 maxTalentTier = GetUInt32Value(PLAYER_FIELD_MAX_TALENT_TIERS);
|
||||
if (talentInfo->SpecID && talentInfo->SpecID != GetSpecId(GetActiveTalentGroup()))
|
||||
return false;
|
||||
|
||||
// prevent learn talent for different class (cheating)
|
||||
if (talentInfo->ClassID != getClass())
|
||||
return false;
|
||||
|
||||
// check if we have enough talent points
|
||||
if (talentInfo->TierID > maxTalentTier)
|
||||
if (talentInfo->TierID >= GetUInt32Value(PLAYER_FIELD_MAX_TALENT_TIERS))
|
||||
return false;
|
||||
|
||||
// Check if player doesnt have any spell in selected collumn
|
||||
for (uint32 i = 0; i < sTalentStore.GetNumRows(); i++)
|
||||
// Check if there is a different talent for us to learn in selected slot
|
||||
// Example situation:
|
||||
// Warrior talent row 2 slot 0
|
||||
// Talent.dbc has an entry for each specialization
|
||||
// but only 2 out of 3 have SpecID != 0
|
||||
// We need to make sure that if player is in one of these defined specs he will not learn the other choice
|
||||
TalentEntry const* bestSlotMatch = nullptr;
|
||||
for (TalentEntry const* talent : sTalentByPos[getClass()][talentInfo->TierID][talentInfo->ColumnIndex])
|
||||
{
|
||||
if (TalentEntry const* talent = sTalentStore.LookupEntry(i))
|
||||
if (!talent->SpecID)
|
||||
bestSlotMatch = talent;
|
||||
else if (talent->SpecID == GetSpecId(GetActiveTalentGroup()))
|
||||
{
|
||||
if (talentInfo->TierID == talent->TierID && HasSpell(talent->SpellID))
|
||||
return false;
|
||||
bestSlotMatch = talent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (talentInfo != bestSlotMatch)
|
||||
return false;
|
||||
|
||||
// Check if player doesnt have any talent in current tier
|
||||
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
|
||||
for (TalentEntry const* talent : sTalentByPos[getClass()][talentInfo->TierID][c])
|
||||
if (HasTalent(talent->ID, GetActiveTalentGroup()))
|
||||
return false;
|
||||
|
||||
// spell not set in talent.dbc
|
||||
uint32 spellid = talentInfo->SpellID;
|
||||
if (spellid == 0)
|
||||
if (!spellid)
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Talent.dbc has no spellInfo for talent: %u (spell id = 0)", talentId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// already known
|
||||
if (HasSpell(spellid))
|
||||
if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid))
|
||||
return false;
|
||||
|
||||
if (!AddTalent(talentId, GetActiveTalentGroup(), true))
|
||||
if (!AddTalent(talentInfo, GetActiveTalentGroup(), true))
|
||||
return false;
|
||||
|
||||
LearnSpell(spellid, false);
|
||||
@@ -25452,49 +25419,42 @@ bool Player::LearnTalent(uint32 talentId)
|
||||
|
||||
void Player::LearnTalentSpecialization(uint32 talentSpec)
|
||||
{
|
||||
if (GetActiveTalentSpec())
|
||||
if (GetSpecId(GetActiveTalentGroup()))
|
||||
return;
|
||||
|
||||
SetTalentSpec(GetActiveTalentGroup(), talentSpec);
|
||||
|
||||
SetSpecId(GetActiveTalentGroup(), talentSpec);
|
||||
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, talentSpec);
|
||||
|
||||
PlayerTalentMap* talents = GetTalentMap(GetActiveTalentGroup());
|
||||
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
|
||||
if (!talentInfo || talentInfo->ClassID != getClass() || talentInfo->SpecID != talentSpec)
|
||||
continue;
|
||||
|
||||
for (PlayerTalentMap::iterator itr = talents->begin(); itr != talents->end();)
|
||||
{
|
||||
TalentEntry const* talent = sTalentStore.LookupEntry(itr->first);
|
||||
if (!talent || talent->TierID != talentInfo->TierID)
|
||||
{
|
||||
++itr;
|
||||
continue;
|
||||
}
|
||||
RemoveSpell(talent->SpellID, false);
|
||||
itr = talents->erase(itr);
|
||||
|
||||
TC_LOG_DEBUG("spells", "Player %s unlearning talent id: %u tier: %u due to specialization change", GetName().c_str(), talent->ID, talent->TierID);
|
||||
}
|
||||
}
|
||||
|
||||
SendTalentsInfoData();
|
||||
|
||||
SaveToDB();
|
||||
// Reset only talents that have different spells for each spec
|
||||
uint32 class_ = getClass();
|
||||
for (uint32 t = 0; t < MAX_TALENT_TIERS; ++t)
|
||||
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
|
||||
if (sTalentByPos[class_][t][c].size() > 1)
|
||||
for (TalentEntry const* talent : sTalentByPos[class_][t][c])
|
||||
RemoveTalent(talent);
|
||||
|
||||
LearnSpecializationSpells();
|
||||
SendTalentsInfoData();
|
||||
}
|
||||
|
||||
|
||||
void Player::AddKnownCurrency(uint32 itemId)
|
||||
void Player::ResetTalentSpecialization()
|
||||
{
|
||||
if (CurrencyTypesEntry const* ctEntry = sCurrencyTypesStore.LookupEntry(itemId))
|
||||
SetFlag64(0, (1LL << (ctEntry->ID-1)));
|
||||
if (!GetSpecId(GetActiveTalentGroup()))
|
||||
return;
|
||||
|
||||
SetSpecId(GetActiveTalentGroup(), 0);
|
||||
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, 0);
|
||||
|
||||
// Reset only talents that have different spells for each spec
|
||||
uint32 class_ = getClass();
|
||||
for (uint32 t = 0; t < MAX_TALENT_TIERS; ++t)
|
||||
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
|
||||
if (sTalentByPos[class_][t][c].size() > 1)
|
||||
for (TalentEntry const* talent : sTalentByPos[class_][t][c])
|
||||
RemoveTalent(talent);
|
||||
|
||||
RemoveSpecializationSpells();
|
||||
SendTalentsInfoData();
|
||||
}
|
||||
|
||||
void Player::UpdateFallInformationIfNeed(MovementInfo const& minfo, uint16 opcode)
|
||||
@@ -25577,12 +25537,14 @@ void Player::SendTalentsInfoData()
|
||||
{
|
||||
WorldPackets::Talent::TalentGroupInfo groupInfoPkt;
|
||||
|
||||
groupInfoPkt.SpecID = GetTalentSpec(i);
|
||||
|
||||
groupInfoPkt.SpecID = GetSpecId(i);
|
||||
groupInfoPkt.TalentIDs.reserve(GetTalentMap(i)->size());
|
||||
|
||||
for (PlayerTalentMap::const_iterator itr = GetTalentMap(i)->begin(); itr != GetTalentMap(i)->end(); ++itr)
|
||||
{
|
||||
if (itr->second->state == PLAYERSPELL_REMOVED)
|
||||
continue;
|
||||
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(itr->first);
|
||||
if (!talentInfo)
|
||||
{
|
||||
@@ -25600,9 +25562,6 @@ void Player::SendTalentsInfoData()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!HasTalent(itr->first, i))
|
||||
continue;
|
||||
|
||||
groupInfoPkt.TalentIDs.push_back(uint16(itr->first));
|
||||
}
|
||||
|
||||
@@ -25919,7 +25878,8 @@ void Player::_LoadTalents(PreparedQueryResult result)
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
AddTalent((*result)[0].GetUInt32(), (*result)[1].GetUInt8(), false);
|
||||
if (TalentEntry const* talent = sTalentStore.LookupEntry((*result)[0].GetUInt32()))
|
||||
AddTalent(talent, (*result)[1].GetUInt8(), false);
|
||||
while (result->NextRow());
|
||||
}
|
||||
}
|
||||
@@ -25930,15 +25890,25 @@ void Player::_SaveTalents(SQLTransaction& trans)
|
||||
stmt->setUInt64(0, GetGUID().GetCounter());
|
||||
trans->Append(stmt);
|
||||
|
||||
PlayerTalentMap* talents;
|
||||
for (uint8 group = 0; group < MAX_TALENT_GROUPS; ++group)
|
||||
{
|
||||
for (PlayerTalentMap::iterator itr = GetTalentMap(group)->begin(); itr != GetTalentMap(group)->end(); ++itr)
|
||||
talents = GetTalentMap(group);
|
||||
for (PlayerTalentMap::iterator itr = talents->begin(); itr != talents->end();)
|
||||
{
|
||||
if (itr->second->state == PLAYERSPELL_REMOVED)
|
||||
{
|
||||
delete itr->second;
|
||||
itr = talents->erase(itr);
|
||||
continue;
|
||||
}
|
||||
|
||||
stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_TALENT);
|
||||
stmt->setUInt64(0, GetGUID().GetCounter());
|
||||
stmt->setUInt32(1, itr->first);
|
||||
stmt->setUInt8(2, itr->second->spec);
|
||||
trans->Append(stmt);
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25989,12 +25959,12 @@ void Player::UpdateTalentGroupCount(uint8 count)
|
||||
SendTalentsInfoData();
|
||||
}
|
||||
|
||||
void Player::ActivateTalentGroup(uint8 group)
|
||||
void Player::ActivateTalentGroup(uint8 spec)
|
||||
{
|
||||
if (GetActiveTalentGroup() == group)
|
||||
if (GetActiveTalentGroup() == spec)
|
||||
return;
|
||||
|
||||
if (group > GetTalentGroupsCount())
|
||||
if (spec > GetTalentGroupsCount())
|
||||
return;
|
||||
|
||||
if (IsNonMeleeSpellCast(false))
|
||||
@@ -26026,57 +25996,76 @@ void Player::ActivateTalentGroup(uint8 group)
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
|
||||
if (!talentInfo)
|
||||
continue;
|
||||
|
||||
// unlearn only talents for character class
|
||||
// some spell learned by one class as normal spells or know at creation but another class learn it as talent,
|
||||
// to prevent unexpected lost normal learned spell skip another class talents
|
||||
if (getClass() != talentInfo->ClassID)
|
||||
if (talentInfo->ClassID != getClass())
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID);
|
||||
if (!spellEntry)
|
||||
if (talentInfo->SpellID == 0)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
RemoveSpell(talentInfo->SpellID, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
for (SpellEffectInfo const* effect : spellEntry->GetEffectsForDifficulty(DIFFICULTY_NONE))
|
||||
for (SpellEffectInfo const* effect : spellInfo->GetEffectsForDifficulty(DIFFICULTY_NONE))
|
||||
if (effect && effect->TriggerSpell > 0 && effect->Effect == SPELL_EFFECT_LEARN_SPELL)
|
||||
RemoveSpell(effect->TriggerSpell, true);
|
||||
|
||||
if (talentInfo->OverridesSpellID)
|
||||
RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
|
||||
}
|
||||
|
||||
// remove glyphs
|
||||
// Remove spec specific spells
|
||||
RemoveSpecializationSpells();
|
||||
|
||||
// set glyphs
|
||||
for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
|
||||
// remove secondary glyph
|
||||
if (uint32 oldglyph = GetGlyph(GetActiveTalentGroup(), slot))
|
||||
if (GlyphPropertiesEntry const* old_gp = sGlyphPropertiesStore.LookupEntry(oldglyph))
|
||||
RemoveAurasDueToSpell(old_gp->SpellID);
|
||||
|
||||
// Activate new group
|
||||
SetActiveTalentGroup(group);
|
||||
|
||||
uint32 spentTalents = 0;
|
||||
SetActiveTalentGroup(spec);
|
||||
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, GetSpecId(spec));
|
||||
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
|
||||
if (!talentInfo)
|
||||
continue;
|
||||
|
||||
// learn only talents for character class
|
||||
if (getClass() != talentInfo->ClassID)
|
||||
if (talentInfo->ClassID != getClass())
|
||||
continue;
|
||||
|
||||
++spentTalents;
|
||||
if (!talentInfo->SpellID)
|
||||
continue;
|
||||
|
||||
if (HasTalent(talentInfo->SpellID, group))
|
||||
LearnSpell(talentInfo->SpellID, false);
|
||||
// if the talent can be found in the newly activated PlayerTalentMap
|
||||
if (HasTalent(talentInfo->ID, GetActiveTalentGroup()))
|
||||
{
|
||||
LearnSpell(talentInfo->SpellID, false); // add the talent to the PlayerSpellMap
|
||||
if (talentInfo->OverridesSpellID)
|
||||
AddOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
|
||||
}
|
||||
}
|
||||
|
||||
LearnSpecializationSpells();
|
||||
|
||||
if (CanUseMastery())
|
||||
if (ChrSpecializationEntry const* specialization = sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup())))
|
||||
for (uint32 i = 0; i < MAX_MASTERY_SPELLS; ++i)
|
||||
if (uint32 mastery = specialization->MasterySpellID[i])
|
||||
LearnSpell(mastery, false);
|
||||
|
||||
// set glyphs
|
||||
for (uint8 slot = 0; slot < MAX_GLYPH_SLOT_INDEX; ++slot)
|
||||
{
|
||||
@@ -26090,7 +26079,6 @@ void Player::ActivateTalentGroup(uint8 group)
|
||||
SetGlyph(slot, glyph);
|
||||
}
|
||||
|
||||
SetUsedTalentCount(spentTalents);
|
||||
InitTalentForLevel();
|
||||
|
||||
{
|
||||
@@ -26109,10 +26097,8 @@ void Player::ActivateTalentGroup(uint8 group)
|
||||
|
||||
SetPower(pw, 0);
|
||||
|
||||
if (!sChrSpecializationStore.LookupEntry(GetActiveTalentSpec()))
|
||||
{
|
||||
if (!sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup())))
|
||||
ResetTalents(true);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::ResetTimeSync()
|
||||
@@ -27153,10 +27139,17 @@ void Player::SendSupercededSpell(uint32 oldSpell, uint32 newSpell)
|
||||
GetSession()->SendPacket(&data);
|
||||
}
|
||||
|
||||
uint32 Player::CalculateTalentsPoints() const
|
||||
uint32 Player::CalculateTalentsTiers() const
|
||||
{
|
||||
// 1 talent point for every 15 levels
|
||||
return getLevel() >= 100 ? 7 : uint32(floor(getLevel() / 15.f));
|
||||
static uint32 const DefaultTalentRowLevels[MAX_TALENT_TIERS] = { 15, 30, 45, 60, 75, 90, 100 };
|
||||
static uint32 const DKTalentRowLevels[MAX_TALENT_TIERS] = { 57, 58, 59, 60, 75, 90, 100 };
|
||||
|
||||
uint32 const* rowLevels = (getClass() != CLASS_DEATH_KNIGHT) ? DefaultTalentRowLevels : DKTalentRowLevels;
|
||||
for (uint32 i = MAX_TALENT_TIERS; i; --i)
|
||||
if (getLevel() >= rowLevels[i - 1])
|
||||
return i;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Difficulty Player::GetDifficultyID(MapEntry const* mapEntry) const
|
||||
@@ -27219,3 +27212,64 @@ Difficulty Player::CheckLoadedLegacyRaidDifficultyID(Difficulty difficulty)
|
||||
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
SpellInfo const* Player::GetCastSpellInfo(SpellInfo const* spellInfo) const
|
||||
{
|
||||
auto range = m_overrideSpells.equal_range(spellInfo->Id);
|
||||
for (auto itr = range.first; itr != range.second; ++itr)
|
||||
if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(itr->second))
|
||||
return Unit::GetCastSpellInfo(newInfo);
|
||||
|
||||
return Unit::GetCastSpellInfo(spellInfo);
|
||||
}
|
||||
|
||||
void Player::RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId)
|
||||
{
|
||||
auto range = m_overrideSpells.equal_range(overridenSpellId);
|
||||
for (auto itr = range.first; itr != range.second; ++itr)
|
||||
{
|
||||
if (itr->second == newSpellId)
|
||||
{
|
||||
m_overrideSpells.erase(itr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::LearnSpecializationSpells()
|
||||
{
|
||||
if (std::vector<SpecializationSpellsEntry const*> const* specSpells = GetSpecializationSpells(GetSpecId(GetActiveTalentGroup())))
|
||||
{
|
||||
for (size_t j = 0; j < specSpells->size(); ++j)
|
||||
{
|
||||
SpecializationSpellsEntry const* specSpell = specSpells->at(j);
|
||||
LearnSpell(specSpell->SpellID, false);
|
||||
if (specSpell->OverridesSpellID)
|
||||
AddOverrideSpell(specSpell->OverridesSpellID, specSpell->SpellID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Player::RemoveSpecializationSpells()
|
||||
{
|
||||
for (uint32 i = 0; i < MAX_SPECIALIZATIONS; ++i)
|
||||
{
|
||||
if (ChrSpecializationEntry const* specialization = sChrSpecializationByIndexStore[getClass()][i])
|
||||
{
|
||||
if (std::vector<SpecializationSpellsEntry const*> const* specSpells = GetSpecializationSpells(specialization->ID))
|
||||
{
|
||||
for (size_t j = 0; j < specSpells->size(); ++j)
|
||||
{
|
||||
SpecializationSpellsEntry const* specSpell = specSpells->at(j);
|
||||
RemoveSpell(specSpell->SpellID, true);
|
||||
if (specSpell->OverridesSpellID)
|
||||
RemoveOverrideSpell(specSpell->OverridesSpellID, specSpell->SpellID);
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32 j = 0; j < MAX_MASTERY_SPELLS; ++j)
|
||||
if (uint32 mastery = specialization->MasterySpellID[j])
|
||||
RemoveAurasDueToSpell(mastery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1247,13 +1247,15 @@ private:
|
||||
|
||||
struct PlayerTalentInfo
|
||||
{
|
||||
PlayerTalentInfo() : UsedTalentCount(0), ResetTalentsCost(0), ResetTalentsTime(0), ActiveGroup(0), GroupsCount(1)
|
||||
PlayerTalentInfo() :
|
||||
ResetTalentsCost(0), ResetTalentsTime(0),
|
||||
ActiveGroup(0), GroupsCount(1)
|
||||
{
|
||||
for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
|
||||
{
|
||||
GroupInfo[i].Talents = new PlayerTalentMap();
|
||||
memset(GroupInfo[i].Glyphs, 0, MAX_GLYPH_SLOT_INDEX * sizeof(uint32));
|
||||
GroupInfo[i].TalentTree = 0;
|
||||
GroupInfo[i].SpecId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1271,10 +1273,9 @@ struct PlayerTalentInfo
|
||||
{
|
||||
PlayerTalentMap* Talents;
|
||||
uint32 Glyphs[MAX_GLYPH_SLOT_INDEX];
|
||||
uint32 TalentTree;
|
||||
uint32 SpecId;
|
||||
} GroupInfo[MAX_TALENT_GROUPS];
|
||||
|
||||
uint32 UsedTalentCount;
|
||||
uint32 ResetTalentsCost;
|
||||
time_t ResetTalentsTime;
|
||||
uint8 ActiveGroup;
|
||||
@@ -1284,6 +1285,7 @@ private:
|
||||
PlayerTalentInfo(PlayerTalentInfo const&);
|
||||
};
|
||||
|
||||
|
||||
class Player : public Unit, public GridObject<Player>
|
||||
{
|
||||
friend class WorldSession;
|
||||
@@ -1820,6 +1822,7 @@ class Player : public Unit, public GridObject<Player>
|
||||
void SendRemoveControlBar();
|
||||
bool HasSpell(uint32 spell) const override;
|
||||
bool HasActiveSpell(uint32 spell) const; // show in spellbook
|
||||
SpellInfo const* GetCastSpellInfo(SpellInfo const* spellInfo) const override;
|
||||
TrainerSpellState GetTrainerSpellState(TrainerSpell const* trainer_spell) const;
|
||||
bool IsSpellFitByClassAndRace(uint32 spell_id) const;
|
||||
bool IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const;
|
||||
@@ -1839,41 +1842,37 @@ class Player : public Unit, public GridObject<Player>
|
||||
void LearnSpellHighestRank(uint32 spellid);
|
||||
void AddTemporarySpell(uint32 spellId);
|
||||
void RemoveTemporarySpell(uint32 spellId);
|
||||
void AddOverrideSpell(uint32 overridenSpellId, uint32 newSpellId) { m_overrideSpells.emplace(overridenSpellId, newSpellId); }
|
||||
void RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId);
|
||||
void LearnSpecializationSpells();
|
||||
void RemoveSpecializationSpells();
|
||||
void SetReputation(uint32 factionentry, uint32 value);
|
||||
uint32 GetReputation(uint32 factionentry) const;
|
||||
std::string GetGuildName();
|
||||
|
||||
// Talents
|
||||
uint32 GetUsedTalentCount() const { return _talentMgr->UsedTalentCount; }
|
||||
void SetUsedTalentCount(uint32 talents) { _talentMgr->UsedTalentCount = talents; }
|
||||
uint32 GetTalentResetCost() const { return _talentMgr->ResetTalentsCost; }
|
||||
void SetTalentResetCost(uint32 cost) { _talentMgr->ResetTalentsCost = cost; }
|
||||
uint32 GetTalentResetTime() const { return _talentMgr->ResetTalentsTime; }
|
||||
void SetTalentResetTime(time_t time_) { _talentMgr->ResetTalentsTime = time_; }
|
||||
|
||||
uint8 GetTalentGroupsCount() const { return _talentMgr->GroupsCount; }
|
||||
void SetTalentGroupsCount(uint8 count) { _talentMgr->GroupsCount = count; }
|
||||
uint32 GetSpecId(uint8 group) const { return _talentMgr->GroupInfo[group].SpecId; }
|
||||
void SetSpecId(uint8 group, uint32 tree) { _talentMgr->GroupInfo[group].SpecId = tree; }
|
||||
uint8 GetActiveTalentGroup() const { return _talentMgr->ActiveGroup; }
|
||||
void SetActiveTalentGroup(uint8 group){ _talentMgr->ActiveGroup = group; }
|
||||
uint8 GetTalentGroupsCount() const { return _talentMgr->GroupsCount; }
|
||||
void SetTalentGroupsCount(uint8 count) { _talentMgr->GroupsCount = count; }
|
||||
|
||||
uint32 GetTalentSpec(uint8 group) const { return _talentMgr->GroupInfo[group].TalentTree; }
|
||||
void SetTalentSpec(uint8 group, uint32 talentSpec) const { _talentMgr->GroupInfo[group].TalentTree = talentSpec; }
|
||||
uint32 GetActiveTalentSpec() const { return _talentMgr->GroupInfo[_talentMgr->ActiveGroup].TalentTree; }
|
||||
|
||||
|
||||
bool ResetTalents(bool noCost = false, bool resetTalents = true, bool resetSpecialization = true);
|
||||
bool RemoveTalent(uint32 talentId);
|
||||
|
||||
bool ResetTalents(bool noCost = false);
|
||||
uint32 GetNextResetTalentsCost() const;
|
||||
void InitTalentForLevel();
|
||||
void SendTalentsInfoData();
|
||||
bool LearnTalent(uint32 talentId);
|
||||
bool AddTalent(uint32 talentId, uint8 spec, bool learning);
|
||||
bool HasTalent(uint32 talentId, uint8 spec);
|
||||
uint32 CalculateTalentsPoints() const;
|
||||
|
||||
|
||||
bool AddTalent(TalentEntry const* talent, uint8 spec, bool learning);
|
||||
bool HasTalent(uint32 spell_id, uint8 spec) const;
|
||||
void RemoveTalent(TalentEntry const* talent);
|
||||
uint32 CalculateTalentsTiers() const;
|
||||
void LearnTalentSpecialization(uint32 talentSpec);
|
||||
void ResetTalentSpecialization();
|
||||
|
||||
// Dual Spec
|
||||
void UpdateTalentGroupCount(uint8 count);
|
||||
@@ -2801,6 +2800,7 @@ class Player : public Unit, public GridObject<Player>
|
||||
|
||||
PlayerMails m_mail;
|
||||
PlayerSpellMap m_spells;
|
||||
std::unordered_multimap<uint32 /*overridenSpellId*/, uint32 /*newSpellId*/> m_overrideSpells;
|
||||
uint32 m_lastPotionId; // last used health/mana potion in combat, that block next potion use
|
||||
|
||||
GlobalCooldownMgr m_GlobalCooldownMgr;
|
||||
@@ -2919,9 +2919,6 @@ class Player : public Unit, public GridObject<Player>
|
||||
void RefundItem(Item* item);
|
||||
void SendItemRefundResult(Item* item, ItemExtendedCostEntry const* iece, uint8 error);
|
||||
|
||||
// know currencies are not removed at any point (0 displayed)
|
||||
void AddKnownCurrency(uint32 itemId);
|
||||
|
||||
void AdjustQuestReqItemCount(Quest const* quest);
|
||||
|
||||
bool IsCanDelayTeleport() const { return m_bCanDelayTeleport; }
|
||||
|
||||
@@ -526,7 +526,7 @@ void Player::UpdateMastery()
|
||||
value += GetRatingBonusValue(CR_MASTERY);
|
||||
SetFloatValue(PLAYER_MASTERY, value);
|
||||
|
||||
ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetActiveTalentSpec());
|
||||
ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup()));
|
||||
if (!chrSpec)
|
||||
return;
|
||||
|
||||
|
||||
@@ -16569,3 +16569,18 @@ void Unit::Whisper(uint32 textId, Player* target, bool isBossWhisper /*= false*/
|
||||
packet.Initalize(isBossWhisper ? CHAT_MSG_RAID_BOSS_WHISPER : CHAT_MSG_MONSTER_WHISPER, LANG_UNIVERSAL, this, target, DB2Manager::GetBroadcastTextValue(bct, locale, getGender()), 0, "", locale);
|
||||
target->SendDirectMessage(packet.Write());
|
||||
}
|
||||
|
||||
SpellInfo const* Unit::GetCastSpellInfo(SpellInfo const* spellInfo) const
|
||||
{
|
||||
Unit::AuraEffectList swaps = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS);
|
||||
Unit::AuraEffectList const& swaps2 = GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_2);
|
||||
if (!swaps2.empty())
|
||||
swaps.insert(swaps.end(), swaps2.begin(), swaps2.end());
|
||||
|
||||
for (AuraEffect const* auraEffect : swaps)
|
||||
if (auraEffect->IsAffectingSpell(spellInfo))
|
||||
if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo(auraEffect->GetAmount()))
|
||||
return newInfo;
|
||||
|
||||
return spellInfo;
|
||||
}
|
||||
|
||||
@@ -1936,6 +1936,7 @@ class Unit : public WorldObject
|
||||
Spell* GetCurrentSpell(uint32 spellType) const { return m_currentSpells[spellType]; }
|
||||
Spell* FindCurrentSpellBySpellId(uint32 spell_id) const;
|
||||
int32 GetCurrentSpellCastTime(uint32 spell_id) const;
|
||||
virtual SpellInfo const* GetCastSpellInfo(SpellInfo const* spellInfo) const;
|
||||
|
||||
ObjectGuid m_SummonSlot[MAX_SUMMON_SLOT];
|
||||
ObjectGuid m_ObjectSlot[MAX_GAMEOBJECT_SLOT];
|
||||
|
||||
@@ -752,7 +752,7 @@ void WorldSession::HandlePetSpellAutocastOpcode(WorldPacket& recvPacket)
|
||||
charmInfo->SetSpellAutocast(spellInfo, state != 0);
|
||||
}
|
||||
|
||||
void WorldSession::HandlePetCastSpellOpcode(WorldPackets::Spells::SpellCastRequest& castRequest)
|
||||
void WorldSession::HandlePetCastSpellOpcode(WorldPackets::Spells::PetCastSpell& castRequest)
|
||||
{
|
||||
TC_LOG_DEBUG("network", "WORLD: CMSG_PET_CAST_SPELL");
|
||||
/*
|
||||
|
||||
@@ -32,10 +32,8 @@ void WorldSession::HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents&
|
||||
{
|
||||
bool anythingLearned = false;
|
||||
for (uint32 talentId : packet.Talents)
|
||||
{
|
||||
if (_player->LearnTalent(talentId))
|
||||
anythingLearned = true;
|
||||
}
|
||||
|
||||
if (anythingLearned)
|
||||
_player->SendTalentsInfoData();
|
||||
|
||||
@@ -320,109 +320,53 @@ void WorldSession::HandleGameobjectReportUse(WorldPackets::GameObject::GameObjec
|
||||
_player->UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_USE_GAMEOBJECT, go->GetEntry());
|
||||
}
|
||||
|
||||
void WorldSession::HandleCastSpellOpcode(WorldPackets::Spells::SpellCastRequest& castRequest)
|
||||
void WorldSession::HandleCastSpellOpcode(WorldPackets::Spells::CastSpell& cast)
|
||||
{
|
||||
// ignore for remote control state (for player case)
|
||||
Unit* mover = _player->m_mover;
|
||||
if (mover != _player && mover->GetTypeId() == TYPEID_PLAYER)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(castRequest.SpellID);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(cast.Cast.SpellID);
|
||||
if (!spellInfo)
|
||||
{
|
||||
TC_LOG_ERROR("network", "WORLD: unknown spell id %u", castRequest.SpellID);
|
||||
TC_LOG_ERROR("network", "WORLD: unknown spell id %u", cast.Cast.SpellID);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spellInfo->IsPassive())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Unit* caster = mover;
|
||||
if (caster->GetTypeId() == TYPEID_UNIT && !caster->ToCreature()->HasSpell(castRequest.SpellID))
|
||||
if (caster->GetTypeId() == TYPEID_UNIT && !caster->ToCreature()->HasSpell(spellInfo->Id))
|
||||
{
|
||||
// If the vehicle creature does not have the spell but it allows the passenger to cast own spells
|
||||
// change caster to player and let him cast
|
||||
if (!_player->IsOnVehicle(caster) || spellInfo->CheckVehicle(_player) != SPELL_CAST_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
caster = _player;
|
||||
}
|
||||
|
||||
if (caster->GetTypeId() == TYPEID_PLAYER && !caster->ToPlayer()->HasActiveSpell(castRequest.SpellID))
|
||||
{
|
||||
// not have spell in spellbook
|
||||
// check known spell
|
||||
if (caster->GetTypeId() == TYPEID_PLAYER && !caster->ToPlayer()->HasActiveSpell(spellInfo->Id))
|
||||
return;
|
||||
}
|
||||
|
||||
if (Player* plr = caster->ToPlayer())
|
||||
{
|
||||
uint32 specId = plr->GetActiveTalentSpec();
|
||||
if (specId)
|
||||
{
|
||||
if (sSpecializationOverrideSpellMap.find(specId) != sSpecializationOverrideSpellMap.end())
|
||||
{
|
||||
if (sSpecializationOverrideSpellMap[specId].find(castRequest.SpellID) != sSpecializationOverrideSpellMap[specId].end())
|
||||
{
|
||||
SpellInfo const* newSpellInfo = sSpellMgr->GetSpellInfo(sSpecializationOverrideSpellMap[specId][castRequest.SpellID]);
|
||||
if (newSpellInfo)
|
||||
{
|
||||
if (newSpellInfo->SpellLevel <= caster->getLevel())
|
||||
{
|
||||
spellInfo = newSpellInfo;
|
||||
castRequest.SpellID = newSpellInfo->Id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Unit::AuraEffectList swaps = mover->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS);
|
||||
Unit::AuraEffectList const& swaps2 = mover->GetAuraEffectsByType(SPELL_AURA_OVERRIDE_ACTIONBAR_SPELLS_2);
|
||||
if (!swaps2.empty())
|
||||
swaps.insert(swaps.end(), swaps2.begin(), swaps2.end());
|
||||
|
||||
if (!swaps.empty())
|
||||
{
|
||||
for (Unit::AuraEffectList::const_iterator itr = swaps.begin(); itr != swaps.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->IsAffectingSpell(spellInfo))
|
||||
{
|
||||
if (SpellInfo const* newInfo = sSpellMgr->GetSpellInfo((*itr)->GetAmount()))
|
||||
{
|
||||
spellInfo = newInfo;
|
||||
castRequest.SpellID = newInfo->Id;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check possible spell cast overrides
|
||||
spellInfo = caster->GetCastSpellInfo(spellInfo);
|
||||
|
||||
// Client is resending autoshot cast opcode when other spell is cast during shoot rotation
|
||||
// Skip it to prevent "interrupt" message
|
||||
if (spellInfo->IsAutoRepeatRangedSpell() && caster->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)
|
||||
&& caster->GetCurrentSpell(CURRENT_AUTOREPEAT_SPELL)->m_spellInfo == spellInfo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// can't use our own spells when we're in possession of another unit,
|
||||
if (_player->isPossessing())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// client provided targets
|
||||
SpellCastTargets targets(caster, castRequest.TargetFlags, castRequest.UnitGuid, castRequest.ItemGuid, castRequest.SrcTransportGuid, castRequest.DstTransportGuid, castRequest.SrcPos, castRequest.DstPos, castRequest.Pitch, castRequest.Speed, castRequest.Name);
|
||||
|
||||
|
||||
//HandleClientCastFlags(recvPacket, castFlags, targets);
|
||||
SpellCastTargets targets(caster, cast.Cast.Target);
|
||||
|
||||
// auto-selection buff level base at target level (in spellInfo)
|
||||
if (targets.GetUnitTarget())
|
||||
@@ -435,8 +379,8 @@ void WorldSession::HandleCastSpellOpcode(WorldPackets::Spells::SpellCastRequest&
|
||||
}
|
||||
|
||||
Spell* spell = new Spell(caster, spellInfo, TRIGGERED_NONE, ObjectGuid::Empty, false);
|
||||
spell->m_cast_count = castRequest.CastID; // set count of casts
|
||||
spell->m_glyphIndex = castRequest.Misc; // 6.x Misc is just a guess
|
||||
spell->m_cast_count = cast.Cast.CastID; // set count of casts
|
||||
spell->m_misc.Data = cast.Cast.Misc; // 6.x Misc is just a guess
|
||||
spell->prepare(&targets);
|
||||
}
|
||||
|
||||
|
||||
@@ -1188,7 +1188,7 @@ enum SpellEffectName
|
||||
SPELL_EFFECT_178 = 178, // Unused (4.3.4)
|
||||
SPELL_EFFECT_CREATE_AREATRIGGER = 179,
|
||||
SPELL_EFFECT_UPDATE_AREATRIGGER = 180, // NYI
|
||||
SPELL_EFFECT_REMOVE_TALENT = 181, // NYI
|
||||
SPELL_EFFECT_REMOVE_TALENT = 181,
|
||||
SPELL_EFFECT_182 = 182,
|
||||
SPELL_EFFECT_183 = 183,
|
||||
SPELL_EFFECT_REPUTATION_2 = 184, // NYI
|
||||
@@ -1517,7 +1517,7 @@ enum SpellCastResult // 19116
|
||||
SPELL_FAILED_BUILDING_ACTIVATE_NOT_READY = 257,
|
||||
SPELL_FAILED_NOT_SOULBOUND = 258,
|
||||
SPELL_FAILED_RIDING_VEHICLE = 259,
|
||||
SPELL_FAILED_UNKNOWN = 260, // custom value, default case
|
||||
SPELL_FAILED_UNKNOWN = 260,
|
||||
SPELL_CAST_OK = 0xFFFF // custom value, must not be sent to client
|
||||
};
|
||||
|
||||
|
||||
@@ -141,78 +141,91 @@ WorldPacket const* WorldPackets::Spells::AuraUpdate::Write()
|
||||
return &_worldPacket;
|
||||
}
|
||||
|
||||
void WorldPackets::Spells::SpellCastRequest::Read()
|
||||
ByteBuffer& operator>>(ByteBuffer& buffer, WorldPackets::Spells::TargetLocation& location)
|
||||
{
|
||||
if (_worldPacket.GetOpcode() == CMSG_PET_CAST_SPELL)
|
||||
_worldPacket >> PetGuid;
|
||||
buffer >> location.Transport;
|
||||
buffer >> location.Location.m_positionX;
|
||||
buffer >> location.Location.m_positionY;
|
||||
buffer >> location.Location.m_positionZ;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
_worldPacket >> CastID;
|
||||
_worldPacket >> SpellID;
|
||||
_worldPacket >> Misc;
|
||||
ByteBuffer& operator>>(ByteBuffer& buffer, WorldPackets::Spells::SpellTargetData& targetData)
|
||||
{
|
||||
buffer.ResetBitPos();
|
||||
|
||||
_worldPacket.ResetBitPos();
|
||||
targetData.Flags = buffer.ReadBits(21);
|
||||
targetData.SrcLocation.HasValue = buffer.ReadBit();
|
||||
targetData.DstLocation.HasValue = buffer.ReadBit();
|
||||
targetData.Orientation.HasValue = buffer.ReadBit();
|
||||
uint32 nameLength = buffer.ReadBits(7);
|
||||
|
||||
TargetFlags = _worldPacket.ReadBits(21);
|
||||
bool HasSrcLocation = _worldPacket.ReadBit();
|
||||
bool HasDstLocation = _worldPacket.ReadBit();
|
||||
bool HasOrientation = _worldPacket.ReadBit();
|
||||
uint32 NameLen = _worldPacket.ReadBits(7);
|
||||
buffer >> targetData.Unit;
|
||||
buffer >> targetData.Item;
|
||||
|
||||
_worldPacket >> UnitGuid;
|
||||
_worldPacket >> ItemGuid;
|
||||
if (targetData.SrcLocation.HasValue)
|
||||
buffer >> targetData.SrcLocation.Value;
|
||||
|
||||
if (HasSrcLocation)
|
||||
if (targetData.DstLocation.HasValue)
|
||||
buffer >> targetData.DstLocation.Value;
|
||||
|
||||
if (targetData.Orientation.HasValue)
|
||||
buffer >> targetData.Orientation.Value;
|
||||
|
||||
targetData.Name = buffer.ReadString(nameLength);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(ByteBuffer& buffer, WorldPackets::Spells::MissileTrajectoryRequest& trajectory)
|
||||
{
|
||||
buffer >> trajectory.Pitch;
|
||||
buffer >> trajectory.Speed;
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ByteBuffer& operator>>(ByteBuffer& buffer, WorldPackets::Spells::SpellCastRequest& request)
|
||||
{
|
||||
buffer >> request.CastID;
|
||||
buffer >> request.SpellID;
|
||||
buffer >> request.Misc;
|
||||
buffer >> request.Target;
|
||||
buffer >> request.MissileTrajectory;
|
||||
buffer >> request.Charmer;
|
||||
|
||||
buffer.ResetBitPos();
|
||||
request.SendCastFlags = buffer.ReadBits(5);
|
||||
request.MoveUpdate.HasValue = buffer.ReadBit();
|
||||
request.Weight.resize(buffer.ReadBits(2));
|
||||
|
||||
if (request.MoveUpdate.HasValue)
|
||||
buffer >> request.MoveUpdate.Value;
|
||||
|
||||
for (WorldPackets::Spells::SpellWeight& weight : request.Weight)
|
||||
{
|
||||
_worldPacket >> SrcTransportGuid;
|
||||
_worldPacket >> SrcPos.m_positionX;
|
||||
_worldPacket >> SrcPos.m_positionY;
|
||||
_worldPacket >> SrcPos.m_positionZ;
|
||||
buffer.ResetBitPos();
|
||||
weight.Type = buffer.ReadBits(2);
|
||||
buffer >> weight.ID;
|
||||
buffer >> weight.Quantity;
|
||||
}
|
||||
|
||||
if (HasDstLocation)
|
||||
{
|
||||
_worldPacket >> DstTransportGuid;
|
||||
_worldPacket >> DstPos.m_positionX;
|
||||
_worldPacket >> DstPos.m_positionY;
|
||||
_worldPacket >> DstPos.m_positionZ;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
if (HasOrientation)
|
||||
_worldPacket >> Orientation;
|
||||
void WorldPackets::Spells::CastSpell::Read()
|
||||
{
|
||||
_worldPacket >> Cast;
|
||||
}
|
||||
|
||||
Name = _worldPacket.ReadString(NameLen);
|
||||
|
||||
_worldPacket >> Pitch;
|
||||
_worldPacket >> Speed;
|
||||
|
||||
_worldPacket >> Guid;
|
||||
|
||||
_worldPacket.ResetBitPos();
|
||||
|
||||
SendCastFlags = _worldPacket.ReadBits(5);
|
||||
|
||||
bool HasMoveUpdate = _worldPacket.ReadBit();
|
||||
uint32 SpellWeightCount = _worldPacket.ReadBits(2);
|
||||
|
||||
if (HasMoveUpdate)
|
||||
{
|
||||
_worldPacket >> movementInfo;
|
||||
}
|
||||
|
||||
for (uint32 i = 0; i < SpellWeightCount; ++i)
|
||||
{
|
||||
_worldPacket.ResetBitPos();
|
||||
SpellWeight unused;
|
||||
unused.Type = _worldPacket.ReadBits(2);
|
||||
_worldPacket >> unused.ID;
|
||||
_worldPacket >> unused.Quantity;
|
||||
}
|
||||
void WorldPackets::Spells::PetCastSpell::Read()
|
||||
{
|
||||
_worldPacket >> PetGUID;
|
||||
_worldPacket >> Cast;
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::TargetLocation const& targetLocation)
|
||||
{
|
||||
data << targetLocation.Transport;
|
||||
// data << targetLocation.Location.PositionXYZStream();
|
||||
data << targetLocation.Location.m_positionX;
|
||||
data << targetLocation.Location.m_positionY;
|
||||
data << targetLocation.Location.m_positionZ;
|
||||
|
||||
@@ -155,46 +155,6 @@ namespace WorldPackets
|
||||
std::vector<AuraInfo> Auras;
|
||||
};
|
||||
|
||||
class SpellCastRequest final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
struct SpellWeight
|
||||
{
|
||||
uint32 Type = 0;
|
||||
int32 ID = 0;
|
||||
uint32 Quantity = 0;
|
||||
};
|
||||
|
||||
SpellCastRequest(WorldPacket&& packet) : ClientPacket(std::move(packet))
|
||||
{
|
||||
ASSERT(packet.GetOpcode() == CMSG_CAST_SPELL || packet.GetOpcode() == CMSG_PET_CAST_SPELL);
|
||||
}
|
||||
|
||||
void Read() override;
|
||||
|
||||
ObjectGuid PetGuid;
|
||||
uint8 CastID = 0;
|
||||
uint32 SpellID = 0;
|
||||
uint32 Misc = 0;
|
||||
uint32 TargetFlags = 0;
|
||||
ObjectGuid UnitGuid;
|
||||
ObjectGuid ItemGuid;
|
||||
|
||||
ObjectGuid SrcTransportGuid;
|
||||
ObjectGuid DstTransportGuid;
|
||||
Position SrcPos;
|
||||
Position DstPos;
|
||||
float Orientation = 0.0f;
|
||||
|
||||
std::string Name;
|
||||
float Pitch = 0.0f;
|
||||
float Speed = 0.0f;
|
||||
ObjectGuid Guid;
|
||||
uint32 SendCastFlags = 0;
|
||||
|
||||
MovementInfo movementInfo;
|
||||
};
|
||||
|
||||
struct TargetLocation
|
||||
{
|
||||
ObjectGuid Transport;
|
||||
@@ -208,10 +168,59 @@ namespace WorldPackets
|
||||
ObjectGuid Item;
|
||||
Optional<TargetLocation> SrcLocation;
|
||||
Optional<TargetLocation> DstLocation;
|
||||
Optional<float> Orientation; // Not found in JAM structures
|
||||
Optional<float> Orientation;
|
||||
std::string Name;
|
||||
};
|
||||
|
||||
struct MissileTrajectoryRequest
|
||||
{
|
||||
float Pitch = 0.0f;
|
||||
float Speed = 0.0f;
|
||||
};
|
||||
|
||||
struct SpellWeight
|
||||
{
|
||||
uint32 Type = 0;
|
||||
int32 ID = 0;
|
||||
uint32 Quantity = 0;
|
||||
};
|
||||
|
||||
struct SpellCastRequest
|
||||
{
|
||||
uint8 CastID = 0;
|
||||
uint32 SpellID = 0;
|
||||
uint32 Misc = 0;
|
||||
uint8 SendCastFlags = 0;
|
||||
SpellTargetData Target;
|
||||
MissileTrajectoryRequest MissileTrajectory;
|
||||
Optional<MovementInfo> MoveUpdate;
|
||||
std::vector<SpellWeight> Weight;
|
||||
ObjectGuid Charmer;
|
||||
};
|
||||
|
||||
class CastSpell final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
|
||||
CastSpell(WorldPacket&& packet) : ClientPacket(CMSG_CAST_SPELL, std::move(packet)) { }
|
||||
|
||||
void Read() override;
|
||||
|
||||
SpellCastRequest Cast;
|
||||
};
|
||||
|
||||
class PetCastSpell final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
|
||||
PetCastSpell(WorldPacket&& packet) : ClientPacket(CMSG_PET_CAST_SPELL, std::move(packet)) { }
|
||||
|
||||
void Read() override;
|
||||
|
||||
ObjectGuid PetGUID;
|
||||
SpellCastRequest Cast;
|
||||
};
|
||||
|
||||
struct SpellMissStatus
|
||||
{
|
||||
uint8 Reason = 0;
|
||||
@@ -392,18 +401,5 @@ namespace WorldPackets
|
||||
}
|
||||
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellCastLogData const& spellCastLogData);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::TargetLocation const& targetLocation);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellTargetData const& spellTargetData);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellMissStatus const& spellMissStatus);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellPowerData const& spellPowerData);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::RuneData const& runeData);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::MissileTrajectoryResult const& missileTrajectory);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellAmmo const& spellAmmo);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::ProjectileVisualData const& projectileVisual);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::CreatureImmunities const& immunities);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellHealPrediction const& spellPred);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellCastData const& spellCastData);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellModifierData const& spellModifierData);
|
||||
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Spells::SpellModifier const& spellModifier);
|
||||
|
||||
#endif // SpellPackets_h__
|
||||
|
||||
@@ -255,7 +255,7 @@ void OpcodeTable::Initialize()
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_CANCEL_TEMP_ENCHANTMENT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandleCancelTempEnchantmentOpcode);
|
||||
DEFINE_HANDLER(CMSG_CANCEL_TRADE, STATUS_LOGGEDIN_OR_RECENTLY_LOGGOUT, PROCESS_THREADUNSAFE, WorldPackets::Trade::CancelTrade, &WorldSession::HandleCancelTradeOpcode);
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_CAN_DUEL, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
|
||||
DEFINE_HANDLER(CMSG_CAST_SPELL, STATUS_LOGGEDIN, PROCESS_THREADSAFE, WorldPackets::Spells::SpellCastRequest, &WorldSession::HandleCastSpellOpcode);
|
||||
DEFINE_HANDLER(CMSG_CAST_SPELL, STATUS_LOGGEDIN, PROCESS_THREADSAFE, WorldPackets::Spells::CastSpell, &WorldSession::HandleCastSpellOpcode);
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_CHALLENGE_MODE_REQUEST_LEADERS, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_CHALLENGE_MODE_REQUEST_MAP_STATS, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_CHANGE_BAG_SLOT_FLAG, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
|
||||
@@ -650,7 +650,7 @@ void OpcodeTable::Initialize()
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_PET_BATTLE_REQUEST_WILD, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_PET_BATTLE_SCRIPT_ERROR_NOTIFY, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_PET_CANCEL_AURA, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandlePetCancelAuraOpcode );
|
||||
DEFINE_HANDLER(CMSG_PET_CAST_SPELL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Spells::SpellCastRequest, &WorldSession::HandlePetCastSpellOpcode);
|
||||
DEFINE_HANDLER(CMSG_PET_CAST_SPELL, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Spells::PetCastSpell, &WorldSession::HandlePetCastSpellOpcode);
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_PET_LEARN_TALENT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandlePetLearnTalent );
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_PET_NAME_CACHE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
|
||||
DEFINE_OPCODE_HANDLER_OLD(CMSG_PET_NAME_QUERY, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::HandlePetNameQuery );
|
||||
|
||||
@@ -247,7 +247,8 @@ namespace WorldPackets
|
||||
namespace Spells
|
||||
{
|
||||
class CancelAura;
|
||||
class SpellCastRequest;
|
||||
class CastSpell;
|
||||
class PetCastSpell;
|
||||
class SetActionButton;
|
||||
}
|
||||
|
||||
@@ -947,7 +948,7 @@ class WorldSession
|
||||
|
||||
void HandleUseItemOpcode(WorldPacket& recvPacket);
|
||||
void HandleOpenItemOpcode(WorldPacket& recvPacket);
|
||||
void HandleCastSpellOpcode(WorldPackets::Spells::SpellCastRequest& castRequest);
|
||||
void HandleCastSpellOpcode(WorldPackets::Spells::CastSpell& castRequest);
|
||||
void HandleCancelCastOpcode(WorldPacket& recvPacket);
|
||||
void HandleCancelAuraOpcode(WorldPackets::Spells::CancelAura& cancelAura);
|
||||
void HandleCancelGrowthAuraOpcode(WorldPacket& recvPacket);
|
||||
@@ -1030,7 +1031,7 @@ class WorldSession
|
||||
void HandlePetRename(WorldPacket& recvData);
|
||||
void HandlePetCancelAuraOpcode(WorldPacket& recvPacket);
|
||||
void HandlePetSpellAutocastOpcode(WorldPacket& recvPacket);
|
||||
void HandlePetCastSpellOpcode(WorldPackets::Spells::SpellCastRequest& castRequest);
|
||||
void HandlePetCastSpellOpcode(WorldPackets::Spells::PetCastSpell& castRequest);
|
||||
void HandlePetLearnTalent(WorldPacket& recvPacket);
|
||||
|
||||
void HandleSetActionBarToggles(WorldPacket& recvData);
|
||||
|
||||
@@ -109,34 +109,43 @@ void SpellDestination::RelocateOffset(Position const& offset)
|
||||
_position.RelocateOffset(offset);
|
||||
}
|
||||
|
||||
SpellCastTargets::SpellCastTargets() : m_elevation(0), m_speed(0), m_strTarget()
|
||||
SpellCastTargets::SpellCastTargets() : m_targetMask(0), m_objectTarget(nullptr), m_itemTarget(nullptr),
|
||||
m_itemTargetEntry(0), m_elevation(0.0f), m_speed(0.0f)
|
||||
{
|
||||
m_objectTarget = NULL;
|
||||
m_itemTarget = NULL;
|
||||
|
||||
m_itemTargetEntry = 0;
|
||||
|
||||
m_targetMask = 0;
|
||||
}
|
||||
|
||||
SpellCastTargets::SpellCastTargets(Unit* caster, uint32 targetMask, ObjectGuid targetGuid, ObjectGuid itemTargetGuid, ObjectGuid srcTransportGuid, ObjectGuid destTransportGuid, Position srcPos, Position destPos, float elevation, float missileSpeed, std::string targetString) :
|
||||
m_targetMask(targetMask), m_objectTargetGUID(targetGuid), m_itemTargetGUID(itemTargetGuid), m_elevation(elevation), m_speed(missileSpeed), m_strTarget(targetString)
|
||||
SpellCastTargets::SpellCastTargets(Unit* caster, WorldPackets::Spells::SpellTargetData const& spellTargetData) :
|
||||
m_targetMask(spellTargetData.Flags), m_objectTarget(nullptr), m_itemTarget(nullptr),
|
||||
m_objectTargetGUID(spellTargetData.Unit), m_itemTargetGUID(spellTargetData.Item),
|
||||
m_itemTargetEntry(0), m_elevation(0.0f), m_speed(0.0f), m_strTarget(spellTargetData.Name)
|
||||
{
|
||||
m_objectTarget = NULL;
|
||||
m_itemTarget = NULL;
|
||||
m_itemTargetEntry = 0;
|
||||
if (spellTargetData.SrcLocation.HasValue)
|
||||
{
|
||||
m_src._transportGUID = spellTargetData.SrcLocation.Value.Transport;
|
||||
Position* pos;
|
||||
if (!m_src._transportGUID.IsEmpty())
|
||||
pos = &m_src._transportOffset;
|
||||
else
|
||||
pos = &m_src._position;
|
||||
|
||||
m_src._transportGUID = srcTransportGuid;
|
||||
if (m_src._transportGUID != ObjectGuid::Empty)
|
||||
m_src._transportOffset.Relocate(srcPos);
|
||||
else
|
||||
m_src._position.Relocate(srcPos);
|
||||
pos->Relocate(spellTargetData.SrcLocation.Value.Location);
|
||||
if (spellTargetData.Orientation.HasValue)
|
||||
pos->SetOrientation(spellTargetData.Orientation.Value);
|
||||
}
|
||||
|
||||
m_dst._transportGUID = destTransportGuid;
|
||||
if (m_dst._transportGUID != ObjectGuid::Empty)
|
||||
m_dst._transportOffset.Relocate(destPos);
|
||||
else
|
||||
m_dst._position.Relocate(destPos);
|
||||
if (spellTargetData.DstLocation.HasValue)
|
||||
{
|
||||
m_dst._transportGUID = spellTargetData.DstLocation.Value.Transport;
|
||||
Position* pos;
|
||||
if (!m_dst._transportGUID.IsEmpty())
|
||||
pos = &m_dst._transportOffset;
|
||||
else
|
||||
pos = &m_dst._position;
|
||||
|
||||
pos->Relocate(spellTargetData.DstLocation.Value.Location);
|
||||
if (spellTargetData.Orientation.HasValue)
|
||||
pos->SetOrientation(spellTargetData.Orientation.Value);
|
||||
}
|
||||
|
||||
Update(caster);
|
||||
}
|
||||
@@ -230,39 +239,6 @@ void SpellCastTargets::Write(WorldPackets::Spells::SpellTargetData& data)
|
||||
|
||||
if (m_targetMask & TARGET_FLAG_STRING)
|
||||
data.Name = m_strTarget;
|
||||
/*data << uint32(m_targetMask);
|
||||
|
||||
if (m_targetMask & (TARGET_FLAG_UNIT | TARGET_FLAG_CORPSE_ALLY | TARGET_FLAG_GAMEOBJECT | TARGET_FLAG_CORPSE_ENEMY | TARGET_FLAG_UNIT_MINIPET))
|
||||
data << m_objectTargetGUID.WriteAsPacked();
|
||||
|
||||
if (m_targetMask & (TARGET_FLAG_ITEM | TARGET_FLAG_TRADE_ITEM))
|
||||
{
|
||||
if (m_itemTarget)
|
||||
data << m_itemTarget->GetPackGUID();
|
||||
else
|
||||
data << uint8(0);
|
||||
}
|
||||
|
||||
if (m_targetMask & TARGET_FLAG_SOURCE_LOCATION)
|
||||
{
|
||||
data << m_src._transportGUID.WriteAsPacked(); // relative position guid here - transport for example
|
||||
if (!m_src._transportGUID.IsEmpty())
|
||||
data << m_src._transportOffset.PositionXYZStream();
|
||||
else
|
||||
data << m_src._position.PositionXYZStream();
|
||||
}
|
||||
|
||||
if (m_targetMask & TARGET_FLAG_DEST_LOCATION)
|
||||
{
|
||||
data << m_dst._transportGUID.WriteAsPacked(); // relative position guid here - transport for example
|
||||
if (!m_dst._transportGUID.IsEmpty())
|
||||
data << m_dst._transportOffset.PositionXYZStream();
|
||||
else
|
||||
data << m_dst._position.PositionXYZStream();
|
||||
}
|
||||
|
||||
if (m_targetMask & TARGET_FLAG_STRING)
|
||||
data << m_strTarget;*/
|
||||
}
|
||||
|
||||
ObjectGuid SpellCastTargets::GetOrigUnitTargetGUID() const
|
||||
@@ -665,7 +641,7 @@ m_spellValue(new SpellValue(caster->GetMap()->GetDifficultyID(), m_spellInfo)),
|
||||
m_procEx = 0;
|
||||
focusObject = NULL;
|
||||
m_cast_count = 0;
|
||||
m_glyphIndex = 0;
|
||||
m_misc.Data = 0;
|
||||
m_preCastSpell = 0;
|
||||
m_triggeredByAuraSpell = NULL;
|
||||
m_spellAura = NULL;
|
||||
@@ -3744,7 +3720,7 @@ void Spell::SendCastResult(SpellCastResult result)
|
||||
if (m_caster->ToPlayer()->GetSession()->PlayerLoading()) // don't send cast results at loading time
|
||||
return;
|
||||
|
||||
SendCastResult(m_caster->ToPlayer(), m_spellInfo, m_cast_count, result, m_customError);
|
||||
SendCastResult(m_caster->ToPlayer(), m_spellInfo, m_cast_count, result, m_customError, SMSG_CAST_FAILED, m_misc.Data);
|
||||
}
|
||||
|
||||
void Spell::SendPetCastResult(SpellCastResult result)
|
||||
@@ -3756,10 +3732,10 @@ void Spell::SendPetCastResult(SpellCastResult result)
|
||||
if (!owner || owner->GetTypeId() != TYPEID_PLAYER)
|
||||
return;
|
||||
|
||||
SendCastResult(owner->ToPlayer(), m_spellInfo, m_cast_count, result, SPELL_CUSTOM_ERROR_NONE, SMSG_PET_CAST_FAILED);
|
||||
SendCastResult(owner->ToPlayer(), m_spellInfo, m_cast_count, result, SPELL_CUSTOM_ERROR_NONE, SMSG_PET_CAST_FAILED, m_misc.Data);
|
||||
}
|
||||
|
||||
void Spell::SendCastResult(Player* caster, SpellInfo const* spellInfo, uint8 cast_count, SpellCastResult result, SpellCustomErrors customError /*= SPELL_CUSTOM_ERROR_NONE*/, OpcodeServer opcode /*= SMSG_CAST_FAILED*/)
|
||||
void Spell::SendCastResult(Player* caster, SpellInfo const* spellInfo, uint8 cast_count, SpellCastResult result, SpellCustomErrors customError /*= SPELL_CUSTOM_ERROR_NONE*/, OpcodeServer opcode /*= SMSG_CAST_FAILED*/, uint32 misc /*= 0*/)
|
||||
{
|
||||
if (result == SPELL_CAST_OK)
|
||||
return;
|
||||
@@ -3870,6 +3846,12 @@ void Spell::SendCastResult(Player* caster, SpellInfo const* spellInfo, uint8 cas
|
||||
packet.FailedArg1 = missingItem; // first missing item
|
||||
break;
|
||||
}
|
||||
case SPELL_FAILED_CANT_UNTALENT:
|
||||
{
|
||||
if (TalentEntry const* talent = sTalentStore.LookupEntry(misc))
|
||||
packet.FailedArg1 = talent->SpellID;
|
||||
break;
|
||||
}
|
||||
// TODO: SPELL_FAILED_NOT_STANDING
|
||||
default:
|
||||
break;
|
||||
@@ -5606,12 +5588,25 @@ SpellCastResult Spell::CheckCast(bool strict)
|
||||
break;
|
||||
}
|
||||
case SPELL_EFFECT_TALENT_SPEC_SELECT:
|
||||
{
|
||||
// can't change during already started arena/battleground
|
||||
if (m_caster->GetTypeId() == TYPEID_PLAYER)
|
||||
if (Battleground const* bg = m_caster->ToPlayer()->GetBattleground())
|
||||
if (bg->GetStatus() == STATUS_IN_PROGRESS)
|
||||
return SPELL_FAILED_NOT_IN_BATTLEGROUND;
|
||||
break;
|
||||
}
|
||||
case SPELL_EFFECT_REMOVE_TALENT:
|
||||
{
|
||||
if (m_caster->GetTypeId() != TYPEID_PLAYER)
|
||||
return SPELL_FAILED_BAD_TARGETS;
|
||||
TalentEntry const* talent = sTalentStore.LookupEntry(m_misc.TalentId);
|
||||
if (!talent)
|
||||
return SPELL_FAILED_DONT_REPORT;
|
||||
if (m_caster->ToPlayer()->HasSpellCooldown(talent->SpellID))
|
||||
return SPELL_FAILED_CANT_UNTALENT;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class SpellCastTargets
|
||||
{
|
||||
public:
|
||||
SpellCastTargets();
|
||||
SpellCastTargets(Unit* caster, uint32 targetMask, ObjectGuid targetGuid, ObjectGuid itemTargetGuid, ObjectGuid srcTransportGuid, ObjectGuid destTransportGuid, Position srcPos, Position destPos, float elevation, float missileSpeed, std::string targetString);
|
||||
SpellCastTargets(Unit* caster, WorldPackets::Spells::SpellTargetData const& spellTargetData);
|
||||
~SpellCastTargets();
|
||||
|
||||
void Read(ByteBuffer& data, Unit* caster);
|
||||
@@ -364,6 +364,7 @@ class Spell
|
||||
void EffectGiveCurrency(SpellEffIndex effIndex);
|
||||
void EffectResurrectWithAura(SpellEffIndex effIndex);
|
||||
void EffectCreateAreaTrigger(SpellEffIndex effIndex);
|
||||
void EffectRemoveTalent(SpellEffIndex effIndex);
|
||||
|
||||
typedef std::set<Aura*> UsedSpellMods;
|
||||
|
||||
@@ -444,7 +445,7 @@ class Spell
|
||||
void CheckSrc() { if (!m_targets.HasSrc()) m_targets.SetSrc(*m_caster); }
|
||||
void CheckDst() { if (!m_targets.HasDst()) m_targets.SetDst(*m_caster); }
|
||||
|
||||
static void SendCastResult(Player* caster, SpellInfo const* spellInfo, uint8 cast_count, SpellCastResult result, SpellCustomErrors customError = SPELL_CUSTOM_ERROR_NONE, OpcodeServer opcode = SMSG_CAST_FAILED);
|
||||
static void SendCastResult(Player* caster, SpellInfo const* spellInfo, uint8 cast_count, SpellCastResult result, SpellCustomErrors customError = SPELL_CUSTOM_ERROR_NONE, OpcodeServer opcode = SMSG_CAST_FAILED, uint32 misc = 0);
|
||||
void SendCastResult(SpellCastResult result);
|
||||
void SendPetCastResult(SpellCastResult result);
|
||||
void SendSpellStart();
|
||||
@@ -475,7 +476,14 @@ class Spell
|
||||
ObjectGuid m_castItemGUID;
|
||||
uint32 m_castItemEntry;
|
||||
uint8 m_cast_count;
|
||||
uint32 m_glyphIndex;
|
||||
union
|
||||
{
|
||||
// Alternate names for this value
|
||||
uint32 TalentId;
|
||||
uint32 GlyphSlot;
|
||||
|
||||
uint32 Data;
|
||||
} m_misc;
|
||||
uint32 m_preCastSpell;
|
||||
SpellCastTargets m_targets;
|
||||
int8 m_comboPointGain;
|
||||
|
||||
@@ -250,7 +250,7 @@ pEffect SpellEffects[TOTAL_SPELL_EFFECTS]=
|
||||
&Spell::EffectUnused, //178 SPELL_EFFECT_178 unused
|
||||
&Spell::EffectCreateAreaTrigger, //179 SPELL_EFFECT_CREATE_AREATRIGGER
|
||||
&Spell::EffectNULL, //180 SPELL_EFFECT_UPDATE_AREATRIGGER
|
||||
&Spell::EffectNULL, //181 SPELL_EFFECT_REMOVE_TALENT
|
||||
&Spell::EffectRemoveTalent, //181 SPELL_EFFECT_REMOVE_TALENT
|
||||
&Spell::EffectNULL, //182 SPELL_EFFECT_182
|
||||
&Spell::EffectNULL, //183 SPELL_EFFECT_183
|
||||
&Spell::EffectNULL, //184 SPELL_EFFECT_REPUTATION
|
||||
@@ -3938,7 +3938,7 @@ void Spell::EffectStuck(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (!player->GetDeathTimer())
|
||||
player->RepopAtGraveyard();
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3948,7 +3948,7 @@ void Spell::EffectStuck(SpellEffIndex /*effIndex*/)
|
||||
player->Kill(player);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
player->TeleportTo(player->m_homebindMapId, player->m_homebindX, player->m_homebindY, player->m_homebindZ, player->GetOrientation(), TELE_TO_SPELL);
|
||||
|
||||
// Stuck spell trigger Hearthstone cooldown
|
||||
@@ -4005,7 +4005,7 @@ void Spell::EffectApplyGlyph(SpellEffIndex /*effIndex*/)
|
||||
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT)
|
||||
return;
|
||||
|
||||
if (m_glyphIndex >= MAX_GLYPH_SLOT_INDEX)
|
||||
if (m_misc.GlyphSlot >= MAX_GLYPH_SLOT_INDEX)
|
||||
return;
|
||||
|
||||
Player* player = m_caster->ToPlayer();
|
||||
@@ -4014,7 +4014,7 @@ void Spell::EffectApplyGlyph(SpellEffIndex /*effIndex*/)
|
||||
|
||||
// glyph sockets level requirement
|
||||
uint8 minLevel = 0;
|
||||
switch (m_glyphIndex)
|
||||
switch (m_misc.GlyphSlot)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
@@ -4038,7 +4038,7 @@ void Spell::EffectApplyGlyph(SpellEffIndex /*effIndex*/)
|
||||
{
|
||||
if (GlyphPropertiesEntry const* newGlyphProperties = sGlyphPropertiesStore.LookupEntry(newGlyph))
|
||||
{
|
||||
if (GlyphSlotEntry const* newGlyphSlot = sGlyphSlotStore.LookupEntry(player->GetGlyphSlot(m_glyphIndex)))
|
||||
if (GlyphSlotEntry const* newGlyphSlot = sGlyphSlotStore.LookupEntry(player->GetGlyphSlot(m_misc.GlyphSlot)))
|
||||
{
|
||||
if (newGlyphProperties->Type != newGlyphSlot->Type)
|
||||
{
|
||||
@@ -4048,26 +4048,26 @@ void Spell::EffectApplyGlyph(SpellEffIndex /*effIndex*/)
|
||||
}
|
||||
|
||||
// remove old glyph
|
||||
if (uint32 oldGlyph = player->GetGlyph(player->GetActiveTalentGroup(), m_glyphIndex))
|
||||
if (uint32 oldGlyph = player->GetGlyph(player->GetActiveTalentGroup(), m_misc.GlyphSlot))
|
||||
{
|
||||
if (GlyphPropertiesEntry const* oldGlyphProperties = sGlyphPropertiesStore.LookupEntry(oldGlyph))
|
||||
{
|
||||
player->RemoveAurasDueToSpell(oldGlyphProperties->SpellID);
|
||||
player->SetGlyph(m_glyphIndex, 0);
|
||||
player->SetGlyph(m_misc.GlyphSlot, 0);
|
||||
}
|
||||
}
|
||||
|
||||
player->CastSpell(m_caster, newGlyphProperties->SpellID, true);
|
||||
player->SetGlyph(m_glyphIndex, newGlyph);
|
||||
player->SetGlyph(m_misc.GlyphSlot, newGlyph);
|
||||
player->SendTalentsInfoData();
|
||||
}
|
||||
}
|
||||
else if (uint32 oldGlyph = player->GetGlyph(player->GetActiveTalentGroup(), m_glyphIndex)) // Removing the glyph, get the old one
|
||||
else if (uint32 oldGlyph = player->GetGlyph(player->GetActiveTalentGroup(), m_misc.GlyphSlot)) // Removing the glyph, get the old one
|
||||
{
|
||||
if (GlyphPropertiesEntry const* oldGlyphProperties = sGlyphPropertiesStore.LookupEntry(oldGlyph))
|
||||
{
|
||||
player->RemoveAurasDueToSpell(oldGlyphProperties->SpellID);
|
||||
player->SetGlyph(m_glyphIndex, 0);
|
||||
player->SetGlyph(m_misc.GlyphSlot, 0);
|
||||
player->SendTalentsInfoData();
|
||||
}
|
||||
}
|
||||
@@ -5768,3 +5768,20 @@ void Spell::EffectCreateAreaTrigger(SpellEffIndex /*effIndex*/)
|
||||
if (!areaTrigger->CreateAreaTrigger(sObjectMgr->GetGenerator<HighGuid::AreaTrigger>()->Generate(), triggerEntry, GetCaster(), GetSpellInfo(), pos))
|
||||
delete areaTrigger;
|
||||
}
|
||||
|
||||
void Spell::EffectRemoveTalent(SpellEffIndex effIndex)
|
||||
{
|
||||
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
|
||||
return;
|
||||
|
||||
TalentEntry const* talent = sTalentStore.LookupEntry(m_misc.TalentId);
|
||||
if (!talent)
|
||||
return;
|
||||
|
||||
Player* player = unitTarget ? unitTarget->ToPlayer() : nullptr;
|
||||
if (!player)
|
||||
return;
|
||||
|
||||
player->RemoveTalent(talent);
|
||||
player->SendTalentsInfoData();
|
||||
}
|
||||
|
||||
@@ -875,9 +875,71 @@ SpellEffectInfo::StaticData SpellEffectInfo::_data[TOTAL_SPELL_EFFECTS] =
|
||||
{EFFECT_IMPLICIT_TARGET_EXPLICIT, TARGET_OBJECT_TYPE_UNIT}, // 177 SPELL_EFFECT_177
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 178 SPELL_EFFECT_178
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_DEST}, // 179 SPELL_EFFECT_CREATE_AREATRIGGER
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 180 SPELL_EFFECT_180
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 181 SPELL_EFFECT_181
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_UNIT}, // 182 SPELL_EFFECT_182
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 180 SPELL_EFFECT_UPDATE_AREATRIGGER
|
||||
{EFFECT_IMPLICIT_TARGET_CASTER, TARGET_OBJECT_TYPE_UNIT}, // 181 SPELL_EFFECT_REMOVE_TALENT
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 182 SPELL_EFFECT_182
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 183 SPELL_EFFECT_183
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 184 SPELL_EFFECT_REPUTATION_2
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 185 SPELL_EFFECT_185
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 186 SPELL_EFFECT_186
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 187 SPELL_EFFECT_RANDOMIZE_ARCHAEOLOGY_DIGSITES
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 188 SPELL_EFFECT_188
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 189 SPELL_EFFECT_LOOT
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 190 SPELL_EFFECT_190
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 191 SPELL_EFFECT_TELEPORT_TO_DIGSITE
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 192 SPELL_EFFECT_192
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 193 SPELL_EFFECT_193
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 194 SPELL_EFFECT_194
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 195 SPELL_EFFECT_195
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 196 SPELL_EFFECT_196
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 197 SPELL_EFFECT_197
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 198 SPELL_EFFECT_198
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 199 SPELL_EFFECT_199
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 200 SPELL_EFFECT_HEAL_BATTLEPET_PCT
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 201 SPELL_EFFECT_ENABLE_BATTLE_PETS
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 202 SPELL_EFFECT_202
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 203 SPELL_EFFECT_203
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 204 SPELL_EFFECT_204
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 205 SPELL_EFFECT_205
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 206 SPELL_EFFECT_206
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 207 SPELL_EFFECT_207
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 208 SPELL_EFFECT_208
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 209 SPELL_EFFECT_209
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 210 SPELL_EFFECT_210
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 211 SPELL_EFFECT_211
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 212 SPELL_EFFECT_212
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 213 SPELL_EFFECT_213
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 214 SPELL_EFFECT_214
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 215 SPELL_EFFECT_215
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 216 SPELL_EFFECT_216
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 217 SPELL_EFFECT_217
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 218 SPELL_EFFECT_218
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 219 SPELL_EFFECT_219
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 220 SPELL_EFFECT_220
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 221 SPELL_EFFECT_221
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 222 SPELL_EFFECT_222
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 223 SPELL_EFFECT_223
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 224 SPELL_EFFECT_224
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 225 SPELL_EFFECT_225
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 226 SPELL_EFFECT_226
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 227 SPELL_EFFECT_227
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 228 SPELL_EFFECT_228
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 229 SPELL_EFFECT_229
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 230 SPELL_EFFECT_230
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 231 SPELL_EFFECT_231
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 232 SPELL_EFFECT_232
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 233 SPELL_EFFECT_233
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 234 SPELL_EFFECT_234
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 235 SPELL_EFFECT_235
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 236 SPELL_EFFECT_236
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 237 SPELL_EFFECT_237
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 238 SPELL_EFFECT_238
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 239 SPELL_EFFECT_239
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 240 SPELL_EFFECT_240
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 241 SPELL_EFFECT_241
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 242 SPELL_EFFECT_242
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 243 SPELL_EFFECT_243
|
||||
{EFFECT_IMPLICIT_TARGET_NONE, TARGET_OBJECT_TYPE_NONE}, // 244 SPELL_EFFECT_244
|
||||
};
|
||||
|
||||
SpellInfo::SpellInfo(SpellEntry const* spellEntry, SpellEffectEntryMap effects)
|
||||
|
||||
@@ -186,6 +186,7 @@ enum SpellCustomAttributes
|
||||
SPELL_ATTR0_CU_CONE_LINE = 0x00000004,
|
||||
SPELL_ATTR0_CU_SHARE_DAMAGE = 0x00000008,
|
||||
SPELL_ATTR0_CU_NO_INITIAL_THREAT = 0x00000010,
|
||||
SPELL_ATTR0_CU_IS_TALENT = 0x00000020,
|
||||
SPELL_ATTR0_CU_AURA_CC = 0x00000040,
|
||||
SPELL_ATTR0_CU_DIRECT_DAMAGE = 0x00000100,
|
||||
SPELL_ATTR0_CU_CHARGE = 0x00000200,
|
||||
|
||||
@@ -1251,73 +1251,8 @@ void SpellMgr::UnloadSpellInfoChains()
|
||||
mSpellChains.clear();
|
||||
}
|
||||
|
||||
void SpellMgr::LoadSpellTalentRanks()
|
||||
{
|
||||
/* TODO: 6.x remove this
|
||||
// cleanup core data before reload - remove reference to ChainNode from SpellInfo
|
||||
UnloadSpellInfoChains();
|
||||
|
||||
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(i);
|
||||
if (!talentInfo)
|
||||
continue;
|
||||
|
||||
SpellInfo const* lastSpell = NULL;
|
||||
for (uint8 rank = MAX_TALENT_RANK - 1; rank > 0; --rank)
|
||||
{
|
||||
if (talentInfo->RankID[rank])
|
||||
{
|
||||
lastSpell = GetSpellInfo(talentInfo->RankID[rank]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lastSpell)
|
||||
continue;
|
||||
|
||||
SpellInfo const* firstSpell = GetSpellInfo(talentInfo->RankID[0]);
|
||||
if (!firstSpell)
|
||||
{
|
||||
TC_LOG_ERROR("spells", "SpellMgr::LoadSpellTalentRanks: First Rank Spell %u for TalentEntry %u does not exist.", talentInfo->RankID[0], i);
|
||||
continue;
|
||||
}
|
||||
|
||||
SpellInfo const* prevSpell = NULL;
|
||||
for (uint8 rank = 0; rank < MAX_TALENT_RANK; ++rank)
|
||||
{
|
||||
uint32 spellId = talentInfo->RankID[rank];
|
||||
if (!spellId)
|
||||
break;
|
||||
|
||||
SpellInfo const* currentSpell = GetSpellInfo(spellId);
|
||||
if (!currentSpell)
|
||||
{
|
||||
TC_LOG_ERROR("spells", "SpellMgr::LoadSpellTalentRanks: Spell %u (Rank: %u) for TalentEntry %u does not exist.", spellId, rank + 1, i);
|
||||
break;
|
||||
}
|
||||
|
||||
SpellChainNode node;
|
||||
node.first = firstSpell;
|
||||
node.last = lastSpell;
|
||||
node.rank = rank + 1;
|
||||
|
||||
node.prev = prevSpell;
|
||||
node.next = node.rank < MAX_TALENT_RANK ? GetSpellInfo(talentInfo->RankID[node.rank]) : NULL;
|
||||
|
||||
mSpellChains[spellId] = node;
|
||||
mSpellInfoMap[spellId]->ChainEntry = &mSpellChains[spellId];
|
||||
|
||||
prevSpell = currentSpell;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
void SpellMgr::LoadSpellRanks()
|
||||
{
|
||||
// cleanup data and load spell ranks for talents from dbc
|
||||
LoadSpellTalentRanks();
|
||||
|
||||
uint32 oldMSTime = getMSTime();
|
||||
|
||||
// 0 1 2
|
||||
@@ -1553,7 +1488,8 @@ void SpellMgr::LoadSpellLearnSpells()
|
||||
node.active = fields[2].GetBool();
|
||||
node.autoLearned = false;
|
||||
|
||||
if (!GetSpellInfo(spell_id))
|
||||
SpellInfo const* spellInfo = GetSpellInfo(spell_id);
|
||||
if (!spellInfo)
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Spell %u listed in `spell_learn_spell` does not exist", spell_id);
|
||||
continue;
|
||||
@@ -1565,7 +1501,7 @@ void SpellMgr::LoadSpellLearnSpells()
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetTalentBySpellID(node.spell))
|
||||
if (spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT))
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Spell %u listed in `spell_learn_spell` attempt learning talent spell %u, skipped", spell_id, node.spell);
|
||||
continue;
|
||||
@@ -1603,7 +1539,7 @@ void SpellMgr::LoadSpellLearnSpells()
|
||||
// talent or passive spells or skill-step spells auto-cast and not need dependent learning,
|
||||
// pet teaching spells must not be dependent learning (cast)
|
||||
// other required explicit dependent learning
|
||||
dbc_node.autoLearned = effect->TargetA.GetTarget() == TARGET_UNIT_PET || GetTalentBySpellID(spell) || entry->IsPassive() || entry->HasEffect(SPELL_EFFECT_SKILL_STEP);
|
||||
dbc_node.autoLearned = effect->TargetA.GetTarget() == TARGET_UNIT_PET || entry->HasAttribute(SPELL_ATTR0_CU_IS_TALENT) || entry->IsPassive() || entry->HasEffect(SPELL_EFFECT_SKILL_STEP);
|
||||
|
||||
SpellLearnSpellMapBounds db_node_bounds = dbSpellLearnSpells.equal_range(spell);
|
||||
|
||||
@@ -1654,7 +1590,7 @@ void SpellMgr::LoadSpellLearnSpells()
|
||||
{
|
||||
if (itr->second.spell == mastery)
|
||||
{
|
||||
TC_LOG_ERROR("sql.sql", "Found redundant record (entry: %u, SpellID: %u) in `spell_learn_spell`, spell added automatically as mastery learned spell from TalentTab.dbc", masteryMainSpell, mastery);
|
||||
TC_LOG_ERROR("sql.sql", "Found redundant record (entry: %u, SpellID: %u) in `spell_learn_spell`, spell added automatically as mastery learned spell from ChrSpecialization.dbc", masteryMainSpell, mastery);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -2873,6 +2809,11 @@ void SpellMgr::LoadSpellInfoCustomAttributes()
|
||||
TC_LOG_INFO("server.loading", ">> Loaded %u spell custom attributes from DB in %u ms", count, GetMSTimeDiffToNow(oldMSTime2));
|
||||
}
|
||||
|
||||
std::set<uint32> talentSpells;
|
||||
for (uint32 i = 0; i < sTalentStore.GetNumRows(); ++i)
|
||||
if (TalentEntry const* talentInfo = sTalentStore.LookupEntry(i))
|
||||
talentSpells.insert(talentInfo->SpellID);
|
||||
|
||||
for (uint32 i = 0; i < GetSpellInfoStoreSize(); ++i)
|
||||
{
|
||||
spellInfo = mSpellInfoMap[i];
|
||||
@@ -2986,6 +2927,9 @@ void SpellMgr::LoadSpellInfoCustomAttributes()
|
||||
if (spellInfo->SpellVisual[0] == 3879)
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_CONE_BACK;
|
||||
|
||||
if (talentSpells.count(spellInfo->Id))
|
||||
spellInfo->AttributesCu |= SPELL_ATTR0_CU_IS_TALENT;
|
||||
|
||||
switch (spellInfo->SpellFamilyName)
|
||||
{
|
||||
case SPELLFAMILY_WARRIOR:
|
||||
|
||||
@@ -695,7 +695,6 @@ class SpellMgr
|
||||
|
||||
// Loading data at server startup
|
||||
void UnloadSpellInfoChains();
|
||||
void LoadSpellTalentRanks();
|
||||
void LoadSpellRanks();
|
||||
void LoadSpellRequired();
|
||||
void LoadSpellLearnSkills();
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "World.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "SpellMgr.h"
|
||||
#include "SpellInfo.h"
|
||||
#include "DBCStores.h"
|
||||
|
||||
void CharacterDatabaseCleaner::CleanDatabase()
|
||||
@@ -128,7 +129,8 @@ void CharacterDatabaseCleaner::CleanCharacterSkills()
|
||||
|
||||
bool CharacterDatabaseCleaner::SpellCheck(uint32 spell_id)
|
||||
{
|
||||
return sSpellMgr->GetSpellInfo(spell_id) && GetTalentBySpellID(spell_id) != nullptr;
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spell_id);
|
||||
return spellInfo && !spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT);
|
||||
}
|
||||
|
||||
void CharacterDatabaseCleaner::CleanCharacterSpell()
|
||||
|
||||
@@ -204,7 +204,7 @@ public:
|
||||
|
||||
// learn highest rank of talent and learn all non-talent spell ranks (recursive by tree)
|
||||
player->LearnSpellHighestRank(talentInfo->SpellID);
|
||||
player->AddTalent(talentInfo->SpellID, player->GetActiveTalentGroup(), true);
|
||||
player->AddTalent(talentInfo, player->GetActiveTalentGroup(), true);
|
||||
}
|
||||
|
||||
handler->SendSysMessage(LANG_COMMAND_LEARN_CLASS_TALENTS);
|
||||
|
||||
@@ -435,11 +435,11 @@ public:
|
||||
handler->PSendSysMessage(LANG_COMMAND_TARGET_LISTAURAS, auras.size());
|
||||
for (Unit::AuraApplicationMap::const_iterator itr = auras.begin(); itr != auras.end(); ++itr)
|
||||
{
|
||||
bool talent = (GetTalentBySpellID(itr->second->GetBase()->GetId()) != nullptr);
|
||||
|
||||
AuraApplication const* aurApp = itr->second;
|
||||
Aura const* aura = aurApp->GetBase();
|
||||
char const* name = aura->GetSpellInfo()->SpellName;
|
||||
bool talent = aura->GetSpellInfo()->HasAttribute(SPELL_ATTR0_CU_IS_TALENT);
|
||||
|
||||
std::ostringstream ss_name;
|
||||
ss_name << "|cffffffff|Hspell:" << aura->GetId() << "|h[" << name << "]|h|r";
|
||||
|
||||
@@ -814,7 +814,7 @@ public:
|
||||
|
||||
SpellInfo const* learnSpellInfo = effect ? sSpellMgr->GetSpellInfo(effect->TriggerSpell) : NULL;
|
||||
|
||||
bool talent = (GetTalentBySpellID(id) != nullptr);
|
||||
bool talent = spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT);
|
||||
bool passive = spellInfo->IsPassive();
|
||||
bool active = target && target->HasAura(id);
|
||||
|
||||
@@ -886,7 +886,7 @@ public:
|
||||
|
||||
SpellInfo const* learnSpellInfo = effect ? sSpellMgr->GetSpellInfo(effect->TriggerSpell) : NULL;
|
||||
|
||||
bool talent = (GetTalentBySpellID(id) != nullptr);
|
||||
bool talent = spellInfo->HasAttribute(SPELL_ATTR0_CU_IS_TALENT);
|
||||
bool passive = spellInfo->IsPassive();
|
||||
bool active = target && target->HasAura(id);
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ class spell_dru_eclipse_energize : public SpellScriptLoader
|
||||
Player* caster = GetCaster()->ToPlayer();
|
||||
|
||||
// No boomy, no deal.
|
||||
if (caster->GetActiveTalentSpec() != TALENT_SPEC_DRUID_BALANCE)
|
||||
if (caster->GetSpecId(caster->GetActiveTalentGroup()) != TALENT_SPEC_DRUID_BALANCE)
|
||||
return;
|
||||
|
||||
switch (GetSpellInfo()->Id)
|
||||
|
||||
Reference in New Issue
Block a user