aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2016-06-09 21:33:18 +0200
committerShauren <shauren.trinity@gmail.com>2016-06-09 21:33:18 +0200
commitf14c66b5e4c157fa3ba4d18532a274f651d6c741 (patch)
treeb907507c91c43b74d7e84289447a567451bd0e77 /src
parent6226f04cafbcecb5b936cdb320b5bc05db3e4369 (diff)
Core/Players: Updated talent specializations
Diffstat (limited to 'src')
-rw-r--r--src/server/database/Database/Implementation/CharacterDatabase.cpp14
-rw-r--r--src/server/game/AI/PlayerAI/PlayerAI.cpp14
-rw-r--r--src/server/game/Battlegrounds/Battleground.cpp2
-rw-r--r--src/server/game/Conditions/ConditionMgr.cpp2
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp4
-rw-r--r--src/server/game/Entities/Item/ItemTemplate.cpp2
-rw-r--r--src/server/game/Entities/Player/Player.cpp255
-rw-r--r--src/server/game/Entities/Player/Player.h56
-rw-r--r--src/server/game/Entities/Unit/StatSystem.cpp2
-rw-r--r--src/server/game/Handlers/InspectHandler.cpp2
-rw-r--r--src/server/game/Handlers/SkillHandler.cpp47
-rw-r--r--src/server/game/Server/Packets/TalentPackets.cpp43
-rw-r--r--src/server/game/Server/Packets/TalentPackets.h36
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp9
-rw-r--r--src/server/game/Server/Protocol/Opcodes.h5
-rw-r--r--src/server/game/Server/WorldSession.h2
-rw-r--r--src/server/game/Spells/Spell.cpp4
-rw-r--r--src/server/game/Spells/Spell.h4
-rw-r--r--src/server/game/Spells/SpellEffects.cpp15
-rw-r--r--src/server/game/Spells/SpellInfo.cpp2
-rw-r--r--src/server/scripts/Spells/spell_druid.cpp2
21 files changed, 201 insertions, 321 deletions
diff --git a/src/server/database/Database/Implementation/CharacterDatabase.cpp b/src/server/database/Database/Implementation/CharacterDatabase.cpp
index 240337afac8..7d8023fa272 100644
--- a/src/server/database/Database/Implementation/CharacterDatabase.cpp
+++ b/src/server/database/Database/Implementation/CharacterDatabase.cpp
@@ -77,9 +77,9 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_SEL_CHARACTER, "SELECT guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, customDisplay1, customDisplay2, customDisplay3, bankSlots, restState, playerFlags, "
"position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, "
- "resettalents_time, talentTree, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeonDifficulty, "
+ "resettalents_time, primarySpecialization, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeonDifficulty, "
"totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, "
- "health, power1, power2, power3, power4, power5, power6, instance_id, talentGroupsCount, activeTalentGroup, lootSpecId, exploredZones, equipmentCache, knownTitles, actionBars, grantableLevels, raidDifficulty, legacyRaidDifficulty "
+ "health, power1, power2, power3, power4, power5, power6, instance_id, activeTalentGroup, lootSpecId, exploredZones, knownTitles, actionBars, grantableLevels, raidDifficulty, legacyRaidDifficulty "
"FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(CHAR_SEL_GROUP_MEMBER, "SELECT guid FROM group_member WHERE memberGuid = ?", CONNECTION_BOTH);
@@ -391,17 +391,17 @@ void CharacterDatabaseConnection::DoPrepareStatements()
PrepareStatement(CHAR_INS_CHARACTER, "INSERT INTO characters (guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, customDisplay1, customDisplay2, customDisplay3, bankSlots, restState, playerFlags, "
"map, instance_id, dungeonDifficulty, raidDifficulty, legacyRaidDifficulty, position_x, position_y, position_z, orientation, trans_x, trans_y, trans_z, trans_o, transguid, "
"taximask, cinematic, "
- "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, talentTree, "
+ "totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, resettalents_time, primarySpecialization, "
"extra_flags, stable_slots, at_login, zone, "
"death_expire_time, taxi_path, totalKills, "
"todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, health, power1, power2, power3, "
- "power4, power5, power6, latency, talentGroupsCount, activeTalentGroup, lootSpecId, exploredZones, equipmentCache, knownTitles, actionBars, grantableLevels) VALUES "
- "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", CONNECTION_ASYNC);
+ "power4, power5, power6, latency, activeTalentGroup, lootSpecId, exploredZones, equipmentCache, knownTitles, actionBars, grantableLevels) VALUES "
+ "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_CHARACTER, "UPDATE characters SET name=?,race=?,class=?,gender=?,level=?,xp=?,money=?,skin=?,face=?,hairStyle=?,hairColor=?,facialStyle=?,customDisplay1=?,customDisplay2=?,customDisplay3=?,bankSlots=?,restState=?,playerFlags=?,"
"map=?,instance_id=?,dungeonDifficulty=?,raidDifficulty=?,legacyRaidDifficulty=?,position_x=?,position_y=?,position_z=?,orientation=?,trans_x=?,trans_y=?,trans_z=?,trans_o=?,transguid=?,taximask=?,cinematic=?,totaltime=?,leveltime=?,rest_bonus=?,"
- "logout_time=?,is_logout_resting=?,resettalents_cost=?,resettalents_time=?,talentTree=?,extra_flags=?,stable_slots=?,at_login=?,zone=?,death_expire_time=?,taxi_path=?,"
+ "logout_time=?,is_logout_resting=?,resettalents_cost=?,resettalents_time=?,primarySpecialization=?,extra_flags=?,stable_slots=?,at_login=?,zone=?,death_expire_time=?,taxi_path=?,"
"totalKills=?,todayKills=?,yesterdayKills=?,chosenTitle=?,"
- "watchedFaction=?,drunk=?,health=?,power1=?,power2=?,power3=?,power4=?,power5=?,power6=?,latency=?,talentGroupsCount=?,activeTalentGroup=?,lootSpecId=?,exploredZones=?,"
+ "watchedFaction=?,drunk=?,health=?,power1=?,power2=?,power3=?,power4=?,power5=?,power6=?,latency=?,activeTalentGroup=?,lootSpecId=?,exploredZones=?,"
"equipmentCache=?,knownTitles=?,actionBars=?,grantableLevels=?,online=? WHERE guid=?", CONNECTION_ASYNC);
PrepareStatement(CHAR_UPD_ADD_AT_LOGIN_FLAG, "UPDATE characters SET at_login = at_login | ? WHERE guid = ?", CONNECTION_ASYNC);
diff --git a/src/server/game/AI/PlayerAI/PlayerAI.cpp b/src/server/game/AI/PlayerAI/PlayerAI.cpp
index 006cbd07116..dffc49949a3 100644
--- a/src/server/game/AI/PlayerAI/PlayerAI.cpp
+++ b/src/server/game/AI/PlayerAI/PlayerAI.cpp
@@ -41,13 +41,13 @@ bool PlayerAI::IsPlayerHealer(Player const* who)
default:
return false;
case CLASS_PALADIN:
- return who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_PALADIN_HOLY;
+ return who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_PALADIN_HOLY;
case CLASS_PRIEST:
- return who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_PRIEST_DISCIPLINE || who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_PRIEST_HOLY;
+ return who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_PRIEST_DISCIPLINE || who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_PRIEST_HOLY;
case CLASS_SHAMAN:
- return who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_SHAMAN_RESTORATION;
+ return who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_SHAMAN_RESTORATION;
case CLASS_DRUID:
- return who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_DRUID_RESTORATION;
+ return who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_DRUID_RESTORATION;
}
}
@@ -74,11 +74,11 @@ bool PlayerAI::IsPlayerRangedAttacker(Player const* who)
return false;
}
case CLASS_PRIEST:
- return who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_PRIEST_SHADOW;
+ return who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_PRIEST_SHADOW;
case CLASS_SHAMAN:
- return who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_SHAMAN_ELEMENTAL;
+ return who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_SHAMAN_ELEMENTAL;
case CLASS_DRUID:
- return who->GetSpecId(who->GetActiveTalentGroup()) == TALENT_SPEC_DRUID_BALANCE;
+ return who->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == TALENT_SPEC_DRUID_BALANCE;
}
}
diff --git a/src/server/game/Battlegrounds/Battleground.cpp b/src/server/game/Battlegrounds/Battleground.cpp
index 8a5cc02bcf6..825a56820f2 100644
--- a/src/server/game/Battlegrounds/Battleground.cpp
+++ b/src/server/game/Battlegrounds/Battleground.cpp
@@ -1089,7 +1089,7 @@ void Battleground::AddPlayer(Player* player)
BattlegroundPlayer bp;
bp.OfflineRemoveTime = 0;
bp.Team = team;
- bp.ActiveSpec = player->GetSpecId(player->GetActiveTalentGroup());
+ bp.ActiveSpec = player->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID);
// Add to list/maps
m_Players[player->GetGUID()] = bp;
diff --git a/src/server/game/Conditions/ConditionMgr.cpp b/src/server/game/Conditions/ConditionMgr.cpp
index 79d05ecb2bb..a052d4d44d4 100644
--- a/src/server/game/Conditions/ConditionMgr.cpp
+++ b/src/server/game/Conditions/ConditionMgr.cpp
@@ -2397,7 +2397,7 @@ bool ConditionMgr::IsPlayerMeetingCondition(Player* player, PlayerConditionEntry
if (condition->ChrSpecializationIndex >= 0 || condition->ChrSpecializationRole >= 0)
{
- if (ChrSpecializationEntry const* spec = sChrSpecializationStore.LookupEntry(player->GetSpecId(player->GetActiveTalentGroup())))
+ if (ChrSpecializationEntry const* spec = sChrSpecializationStore.LookupEntry(player->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID)))
{
if (condition->ChrSpecializationIndex >= 0 && spec->OrderIndex != uint32(condition->ChrSpecializationIndex))
return false;
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index 2ef4a85c679..ff6fb779650 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -711,8 +711,8 @@ void DB2Manager::LoadStores(std::string const& dataPath, uint32 defaultLocale)
for (TalentEntry const* talentInfo : sTalentStore)
{
ASSERT(talentInfo->ClassID < MAX_CLASSES);
- ASSERT(talentInfo->TierID < MAX_TALENT_TIERS, "MAX_TALENT_TIERS must be at least %u", MAX_TALENT_TIERS);
- ASSERT(talentInfo->ColumnIndex < MAX_TALENT_COLUMNS, "MAX_TALENT_COLUMNS must be at least %u", MAX_TALENT_COLUMNS);
+ ASSERT(talentInfo->TierID < MAX_TALENT_TIERS, "MAX_TALENT_TIERS must be at least %u", talentInfo->TierID);
+ ASSERT(talentInfo->ColumnIndex < MAX_TALENT_COLUMNS, "MAX_TALENT_COLUMNS must be at least %u", talentInfo->ColumnIndex);
_talentsByPosition[talentInfo->ClassID][talentInfo->TierID][talentInfo->ColumnIndex].push_back(talentInfo);
}
diff --git a/src/server/game/Entities/Item/ItemTemplate.cpp b/src/server/game/Entities/Item/ItemTemplate.cpp
index 67f683849a9..e33bea37e54 100644
--- a/src/server/game/Entities/Item/ItemTemplate.cpp
+++ b/src/server/game/Entities/Item/ItemTemplate.cpp
@@ -218,7 +218,7 @@ bool ItemTemplate::CanWinForPlayer(Player const* player) const
if (specs.empty())
return true;
- uint32 spec = player->GetSpecId(player->GetActiveTalentGroup());
+ uint32 spec = player->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID);
if (!spec)
spec = player->GetDefaultSpecId();
diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp
index 2f626fc39ea..fd96b65b2cf 100644
--- a/src/server/game/Entities/Player/Player.cpp
+++ b/src/server/game/Entities/Player/Player.cpp
@@ -2510,12 +2510,6 @@ void Player::InitTalentForLevel()
}
else
{
- if (level < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL) || GetTalentGroupsCount() == 0)
- {
- 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)
@@ -3227,7 +3221,7 @@ bool Player::IsNeedCastPassiveSpellAtLearn(SpellInfo const* spellInfo) const
bool Player::IsCurrentSpecMasterySpell(SpellInfo const* spellInfo) const
{
- if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup())))
+ if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID)))
return spellInfo->Id == chrSpec->MasterySpellID[0] || spellInfo->Id == chrSpec->MasterySpellID[1];
return false;
@@ -13303,8 +13297,7 @@ void Player::PrepareGossipMenu(WorldObject* source, uint32 menuId /*= 0*/, bool
break;
}
case GOSSIP_OPTION_LEARNDUALSPEC:
- if (!(GetTalentGroupsCount() == 1 && creature->isCanTrainingAndResetTalentsOf(this) && !(getLevel() < sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))))
- canTalk = false;
+ canTalk = false;
break;
case GOSSIP_OPTION_UNLEARNTALENTS:
if (!creature->isCanTrainingAndResetTalentsOf(this))
@@ -13508,16 +13501,6 @@ void Player::OnGossipSelect(WorldObject* source, uint32 gossipListId, uint32 men
GetSession()->SendTrainerList(guid);
break;
case GOSSIP_OPTION_LEARNDUALSPEC:
- if (GetTalentGroupsCount() == 1 && getLevel() >= sWorld->getIntConfig(CONFIG_MIN_DUALSPEC_LEVEL))
- {
- // Cast spells that teach dual spec
- // Both are also ImplicitTarget self and must be cast by player
- CastSpell(this, 63680, true, nullptr, nullptr, GetGUID());
- CastSpell(this, 63624, true, nullptr, nullptr, GetGUID());
-
- // Should show another Gossip text with "Congratulations..."
- PlayerTalkClass->SendCloseGossip();
- }
break;
case GOSSIP_OPTION_UNLEARNTALENTS:
PlayerTalkClass->SendCloseGossip();
@@ -16285,12 +16268,12 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
//"SELECT guid, account, name, race, class, gender, level, xp, money, skin, face, hairStyle, hairColor, facialStyle, customDisplay1, customDisplay2, customDisplay3, bankSlots, restState, playerFlags, "
// 20 21 22 23 24 25 26 27 28 29 30 31 32
//"position_x, position_y, position_z, map, orientation, taximask, cinematic, totaltime, leveltime, rest_bonus, logout_time, is_logout_resting, resettalents_cost, "
- // 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
- //"resettalents_time, talentTree, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeonDifficulty, "
+ // 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
+ //"resettalents_time, primarySpecialization, trans_x, trans_y, trans_z, trans_o, transguid, extra_flags, stable_slots, at_login, zone, online, death_expire_time, taxi_path, dungeonDifficulty, "
// 48 49 50 51 52 53
//"totalKills, todayKills, yesterdayKills, chosenTitle, watchedFaction, drunk, "
- // 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
- //"health, power1, power2, power3, power4, power5, power6, instance_id, talentGroupsCount, activeTalentGroup, lootSpecId, exploredZones, equipmentCache, knownTitles, actionBars, grantableLevels, raidDifficulty, legacyRaidDifficulty "
+ // 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
+ //"health, power1, power2, power3, power4, power5, power6, instance_id, activeTalentGroup, lootSpecId, exploredZones, knownTitles, actionBars, grantableLevels, raidDifficulty, legacyRaidDifficulty "
//
//"FROM characters WHERE guid = ?", CONNECTION_ASYNC);
PreparedQueryResult result = holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_FROM);
@@ -16360,8 +16343,8 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
SetUInt32Value(UNIT_FIELD_LEVEL, fields[6].GetUInt8());
SetUInt32Value(PLAYER_XP, fields[7].GetUInt32());
- _LoadIntoDataField(fields[65].GetString(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE);
- _LoadIntoDataField(fields[67].GetString(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE * 2);
+ _LoadIntoDataField(fields[64].GetString(), PLAYER_EXPLORED_ZONES_1, PLAYER_EXPLORED_ZONES_SIZE);
+ _LoadIntoDataField(fields[65].GetString(), PLAYER__FIELD_KNOWN_TITLES, KNOWN_TITLES_SIZE * 2);
SetObjectScale(1.0f);
SetFloatValue(UNIT_FIELD_HOVERHEIGHT, 1.0f);
@@ -16403,7 +16386,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
}
// set which actionbars the client has active - DO NOT REMOVE EVER AGAIN (can be changed though, if it does change fieldwise)
- SetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES, fields[68].GetUInt8());
+ SetByteValue(PLAYER_FIELD_BYTES, PLAYER_FIELD_BYTES_OFFSET_ACTION_BAR_TOGGLES, fields[66].GetUInt8());
InitDisplayIds();
@@ -16439,8 +16422,8 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
uint32 instanceId = fields[61].GetUInt32();
SetDungeonDifficultyID(CheckLoadedDungeonDifficultyID(Difficulty(fields[47].GetUInt8())));
- SetRaidDifficultyID(CheckLoadedRaidDifficultyID(Difficulty(fields[70].GetUInt8())));
- SetLegacyRaidDifficultyID(CheckLoadedLegacyRaidDifficultyID(Difficulty(fields[71].GetUInt8())));
+ SetRaidDifficultyID(CheckLoadedRaidDifficultyID(Difficulty(fields[68].GetUInt8())));
+ SetLegacyRaidDifficultyID(CheckLoadedLegacyRaidDifficultyID(Difficulty(fields[69].GetUInt8())));
std::string taxi_nodes = fields[46].GetString();
@@ -16751,6 +16734,10 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
SetTalentResetCost(fields[32].GetUInt32());
SetTalentResetTime(time_t(fields[33].GetUInt32()));
+ SetPrimarySpecialization(fields[34].GetUInt32());
+ ChrSpecializationEntry const* primarySpec = sChrSpecializationStore.LookupEntry(GetPrimarySpecialization());
+ if (!primarySpec || primarySpec->ClassID != getClass())
+ SetPrimarySpecialization(0);
m_taxi.LoadTaxiMask(fields[25].GetString()); // must be before InitTaxiNodesForLevel
@@ -16833,42 +16820,29 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
_LoadSkills(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SKILLS));
UpdateSkillsForLevel(); //update skills after load, to make sure they are correctly update at player load
- SetTalentGroupsCount(fields[62].GetUInt8());
- SetActiveTalentGroup(fields[63].GetUInt8());
+ SetActiveTalentGroup(fields[62].GetUInt8());
- uint32 lootSpecId = fields[64].GetUInt32();
+ uint32 lootSpecId = fields[63].GetUInt32();
if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(lootSpecId))
- {
if (chrSpec->ClassID == getClass())
SetLootSpecId(lootSpecId);
- }
// sanity check
- if (GetTalentGroupsCount() > MAX_TALENT_GROUPS || GetActiveTalentGroup() > MAX_TALENT_GROUP || GetTalentGroupsCount() < MIN_TALENT_GROUPS)
+ if (GetActiveTalentGroup() >= MAX_SPECIALIZATIONS)
{
- TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player %s (%s) has invalid SpecCount = %u and/or invalid ActiveSpec = %u.",
- GetName().c_str(), GetGUID().ToString().c_str(), GetTalentGroupsCount(), GetActiveTalentGroup());
+ TC_LOG_ERROR("entities.player", "Player::LoadFromDB: Player %s (%s) has invalid invalid ActiveSpec = %u.",
+ GetName().c_str(), GetGUID().ToString().c_str(), GetActiveTalentGroup());
SetActiveTalentGroup(0);
}
- // Only load selected specializations, learning mastery spells requires this
- Tokenizer talentSpecs(fields[34].GetString(), ' ', MAX_TALENT_GROUPS);
- for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
+ if (ChrSpecializationEntry const* spec = sDB2Manager.GetChrSpecializationByIndex(getClass(), GetActiveTalentGroup()))
{
- if (i >= talentSpecs.size())
- break;
-
- uint32 talentSpec = atoul(talentSpecs[i]);
- if (talentSpec)
- {
- if (sChrSpecializationStore.LookupEntry(talentSpec))
- SetSpecId(i, talentSpec);
- else
- SetAtLoginFlag(AT_LOGIN_RESET_TALENTS);
- }
+ SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, spec->ID);
+ if (!GetPrimarySpecialization())
+ SetPrimarySpecialization(spec->ID);
}
-
- SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, GetSpecId(GetActiveTalentGroup()));
+ else
+ ResetTalentSpecialization();
_LoadTalents(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_TALENTS));
_LoadSpells(holder->GetPreparedResult(PLAYER_LOGIN_QUERY_LOAD_SPELLS));
@@ -17012,7 +16986,7 @@ bool Player::LoadFromDB(ObjectGuid guid, SQLQueryHolder *holder)
}
// RaF stuff.
- m_grantableLevels = fields[69].GetUInt8();
+ m_grantableLevels = fields[67].GetUInt8();
if (GetSession()->IsARecruiter() || (GetSession()->GetRecruiterId() != 0))
SetFlag(OBJECT_DYNAMIC_FLAGS, UNIT_DYNFLAG_REFER_A_FRIEND);
@@ -18598,11 +18572,7 @@ void Player::SaveToDB(bool create /*=false*/)
//save, but in tavern/city
stmt->setUInt32(index++, GetTalentResetCost());
stmt->setUInt32(index++, GetTalentResetTime());
-
- ss.str("");
- for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
- ss << GetSpecId(i) << " ";
- stmt->setString(index++, ss.str());
+ stmt->setUInt32(index++, GetPrimarySpecialization());
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
stmt->setUInt8(index++, m_stableSlots);
stmt->setUInt16(index++, (uint16)m_atLoginFlags);
@@ -18637,7 +18607,6 @@ void Player::SaveToDB(bool create /*=false*/)
stmt->setUInt32(index++, GetSession()->GetLatency());
- stmt->setUInt8(index++, GetTalentGroupsCount());
stmt->setUInt8(index++, GetActiveTalentGroup());
stmt->setUInt32(index++, GetLootSpecId());
@@ -18744,11 +18713,7 @@ void Player::SaveToDB(bool create /*=false*/)
//save, but in tavern/city
stmt->setUInt32(index++, GetTalentResetCost());
stmt->setUInt32(index++, GetTalentResetTime());
-
- ss.str("");
- for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
- ss << GetSpecId(i) << " ";
- stmt->setString(index++, ss.str());
+ stmt->setUInt32(index++, GetPrimarySpecialization());
stmt->setUInt16(index++, (uint16)m_ExtraFlags);
stmt->setUInt8(index++, m_stableSlots);
stmt->setUInt16(index++, (uint16)m_atLoginFlags);
@@ -18783,7 +18748,6 @@ void Player::SaveToDB(bool create /*=false*/)
stmt->setUInt32(index++, GetSession()->GetLatency());
- stmt->setUInt8(index++, GetTalentGroupsCount());
stmt->setUInt8(index++, GetActiveTalentGroup());
stmt->setUInt32(index++, GetLootSpecId());
@@ -24614,22 +24578,33 @@ bool Player::ModifierTreeSatisfied(uint32 modifierTreeId) const
return m_achievementMgr->ModifierTreeSatisfied(modifierTreeId);
}
-bool Player::LearnTalent(uint32 talentId)
+TalentLearnResult Player::LearnTalent(uint32 talentId, int32* spellOnCooldown)
{
+ if (IsInCombat())
+ return TALENT_FAILED_AFFECTING_COMBAT;
+
+ if (isDead() || GetMap()->IsBattlegroundOrArena())
+ return TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW;
+
+ if (!GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID))
+ return TALENT_FAILED_NO_PRIMARY_TREE_SELECTED;
+
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
- return false;
+ return TALENT_FAILED_UNKNOWN;
- if (talentInfo->SpecID && talentInfo->SpecID != GetSpecId(GetActiveTalentGroup()))
- return false;
+ if (talentInfo->SpecID && talentInfo->SpecID != GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID))
+ return TALENT_FAILED_UNKNOWN;
// prevent learn talent for different class (cheating)
if (talentInfo->ClassID != getClass())
- return false;
+ return TALENT_FAILED_UNKNOWN;
// check if we have enough talent points
if (talentInfo->TierID >= GetUInt32Value(PLAYER_FIELD_MAX_TALENT_TIERS))
- return false;
+ return TALENT_FAILED_UNKNOWN;
+
+ // TODO: prevent changing talents that are on cooldown
// Check if there is a different talent for us to learn in selected slot
// Example situation:
@@ -24642,7 +24617,7 @@ bool Player::LearnTalent(uint32 talentId)
{
if (!talent->SpecID)
bestSlotMatch = talent;
- else if (talent->SpecID == GetSpecId(GetActiveTalentGroup()))
+ else if (talent->SpecID == GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID))
{
bestSlotMatch = talent;
break;
@@ -24650,63 +24625,53 @@ bool Player::LearnTalent(uint32 talentId)
}
if (talentInfo != bestSlotMatch)
- return false;
+ return TALENT_FAILED_UNKNOWN;
// Check if player doesn't have any talent in current tier
for (uint32 c = 0; c < MAX_TALENT_COLUMNS; ++c)
+ {
for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(getClass(), talentInfo->TierID, c))
- if (HasTalent(talent->ID, GetActiveTalentGroup()))
- return false;
+ {
+ if (HasTalent(talent->ID, GetActiveTalentGroup()) && !HasFlag(PLAYER_FLAGS, PLAYER_FLAGS_RESTING) && HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_IMMUNE_TO_NPC))
+ return TALENT_FAILED_REST_AREA;
+
+ if (GetSpellHistory()->HasCooldown(talent->SpellID))
+ {
+ *spellOnCooldown = talent->SpellID;
+ return TALENT_FAILED_CANT_REMOVE_TALENT;
+ }
+
+ RemoveTalent(talent);
+ }
+ }
// spell not set in talent.dbc
uint32 spellid = talentInfo->SpellID;
if (!spellid)
{
TC_LOG_ERROR("entities.player", "Player::LearnTalent: Talent.dbc has no spellInfo for talent: %u (spell id = 0)", talentId);
- return false;
+ return TALENT_FAILED_UNKNOWN;
}
// already known
if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid))
- return false;
+ return TALENT_FAILED_UNKNOWN;
if (!AddTalent(talentInfo, GetActiveTalentGroup(), true))
- return false;
+ return TALENT_FAILED_UNKNOWN;
LearnSpell(spellid, false);
TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: %u Spell: %u Group: %u\n", talentId, spellid, GetActiveTalentGroup());
- return true;
-}
-
-void Player::LearnTalentSpecialization(uint32 talentSpec)
-{
- if (GetSpecId(GetActiveTalentGroup()))
- return;
-
- SetSpecId(GetActiveTalentGroup(), talentSpec);
- SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, talentSpec);
-
- // 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 (sDB2Manager.GetTalentsByPosition(class_, t, c).size() > 1)
- for (TalentEntry const* talent : sDB2Manager.GetTalentsByPosition(class_, t, c))
- RemoveTalent(talent);
-
- LearnSpecializationSpells();
- SendTalentsInfoData();
- UpdateItemSetAuras(false);
+ return TALENT_LEARN_OK;
}
void Player::ResetTalentSpecialization()
{
- if (!GetSpecId(GetActiveTalentGroup()))
+ if (!GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID))
return;
- SetSpecId(GetActiveTalentGroup(), 0);
SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, 0);
// Reset only talents that have different spells for each spec
@@ -24791,16 +24756,17 @@ bool Player::CanSeeSpellClickOn(Creature const* c) const
void Player::SendTalentsInfoData()
{
WorldPackets::Talent::UpdateTalentData packet;
-
+ packet.Info.PrimarySpecialization = GetPrimarySpecialization();
packet.Info.ActiveGroup = GetActiveTalentGroup();
- uint8 groupsCount = GetTalentGroupsCount();
-
- for (uint8 i = 0; i < groupsCount; ++i)
+ for (uint8 i = 0; i < MAX_SPECIALIZATIONS; ++i)
{
- WorldPackets::Talent::TalentGroupInfo groupInfoPkt;
+ ChrSpecializationEntry const* spec = sDB2Manager.GetChrSpecializationByIndex(getClass(), i);
+ if (!spec)
+ continue;
- groupInfoPkt.SpecID = GetSpecId(i);
+ WorldPackets::Talent::TalentGroupInfo groupInfoPkt;
+ groupInfoPkt.SpecID = spec->ID;
groupInfoPkt.TalentIDs.reserve(GetTalentMap(i)->size());
for (PlayerTalentMap::const_iterator itr = GetTalentMap(i)->begin(); itr != GetTalentMap(i)->end(); ++itr)
@@ -25048,58 +25014,9 @@ void Player::_SaveTalents(SQLTransaction& trans)
}
}
-void Player::UpdateTalentGroupCount(uint8 count)
+void Player::ActivateTalentGroup(ChrSpecializationEntry const* spec)
{
- uint32 curCount = GetTalentGroupsCount();
- if (curCount == count)
- return;
-
- if (GetActiveTalentGroup() >= count)
- ActivateTalentGroup(0);
-
- SQLTransaction trans = CharacterDatabase.BeginTransaction();
- PreparedStatement* stmt;
-
- // Copy spec data
- if (count > curCount)
- {
- _SaveActions(trans); // make sure the button list is cleaned up
- for (ActionButtonList::iterator itr = m_actionButtons.begin(); itr != m_actionButtons.end(); ++itr)
- {
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_CHAR_ACTION);
- stmt->setUInt64(0, GetGUID().GetCounter());
- stmt->setUInt8(1, 1);
- stmt->setUInt8(2, itr->first);
- stmt->setUInt32(3, itr->second.GetAction());
- stmt->setUInt8(4, uint8(itr->second.GetType()));
- trans->Append(stmt);
- }
- }
- // Delete spec data for removed spec.
- else if (count < curCount)
- {
- _SaveActions(trans);
-
- stmt = CharacterDatabase.GetPreparedStatement(CHAR_DEL_CHAR_ACTION_EXCEPT_SPEC);
- stmt->setUInt8(0, GetActiveTalentGroup());
- stmt->setUInt64(1, GetGUID().GetCounter());
- trans->Append(stmt);
-
- }
-
- CharacterDatabase.CommitTransaction(trans);
-
- SetTalentGroupsCount(count);
-
- SendTalentsInfoData();
-}
-
-void Player::ActivateTalentGroup(uint8 spec)
-{
- if (GetActiveTalentGroup() == spec)
- return;
-
- if (spec > GetTalentGroupsCount())
+ if (GetActiveTalentGroup() == spec->OrderIndex)
return;
if (IsNonMeleeSpellCast(false))
@@ -25160,8 +25077,10 @@ void Player::ActivateTalentGroup(uint8 spec)
// Remove spec specific spells
RemoveSpecializationSpells();
- SetActiveTalentGroup(spec);
- SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, GetSpecId(spec));
+ SetActiveTalentGroup(spec->OrderIndex);
+ SetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID, spec->ID);
+ if (!GetPrimarySpecialization())
+ SetPrimarySpecialization(spec->ID);
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
{
@@ -25188,10 +25107,9 @@ void Player::ActivateTalentGroup(uint8 spec)
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);
+ for (uint32 i = 0; i < MAX_MASTERY_SPELLS; ++i)
+ if (uint32 mastery = spec->MasterySpellID[i])
+ LearnSpell(mastery, false);
InitTalentForLevel();
@@ -25211,9 +25129,6 @@ void Player::ActivateTalentGroup(uint8 spec)
SetPower(pw, 0);
UpdateItemSetAuras(false);
-
- if (!sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup())))
- ResetTalents(true);
}
void Player::ResetTimeSync()
@@ -25850,7 +25765,7 @@ Pet* Player::SummonPet(uint32 entry, float x, float y, float z, float ang, PetTy
bool Player::CanUseMastery() const
{
- if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup())))
+ if (ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID)))
return HasSpell(chrSpec->MasterySpellID[0]) || HasSpell(chrSpec->MasterySpellID[1]);
return false;
@@ -26068,7 +25983,7 @@ void Player::RemoveOverrideSpell(uint32 overridenSpellId, uint32 newSpellId)
void Player::LearnSpecializationSpells()
{
- if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(GetSpecId(GetActiveTalentGroup())))
+ if (std::vector<SpecializationSpellsEntry const*> const* specSpells = sDB2Manager.GetSpecializationSpells(GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID)))
{
for (size_t j = 0; j < specSpells->size(); ++j)
{
diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h
index 5bee6c6c117..e46712d0580 100644
--- a/src/server/game/Entities/Player/Player.h
+++ b/src/server/game/Entities/Player/Player.h
@@ -1136,42 +1136,38 @@ struct ResurrectionData
uint32 Aura;
};
+enum TalentLearnResult
+{
+ TALENT_LEARN_OK = 0,
+ TALENT_FAILED_UNKNOWN = 1,
+ TALENT_FAILED_NOT_ENOUGH_TALENTS_IN_PRIMARY_TREE = 2,
+ TALENT_FAILED_NO_PRIMARY_TREE_SELECTED = 3,
+ TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW = 4,
+ TALENT_FAILED_AFFECTING_COMBAT = 5,
+ TALENT_FAILED_CANT_REMOVE_TALENT = 6,
+ TALENT_FAILED_CANT_DO_THAT_CHALLENGE_MODE_ACTIVE = 7,
+ TALENT_FAILED_REST_AREA = 8
+};
+
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 };
static uint32 const DHTalentRowLevels[MAX_TALENT_TIERS] = { 99, 100, 102, 104, 106, 108, 110 };
struct TC_GAME_API PlayerTalentInfo
{
- PlayerTalentInfo() :
- ResetTalentsCost(0), ResetTalentsTime(0),
- ActiveGroup(0), GroupsCount(1)
- {
- for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
- {
- GroupInfo[i].Talents = new PlayerTalentMap();
- GroupInfo[i].SpecId = 0;
- }
- }
-
- ~PlayerTalentInfo()
+ PlayerTalentInfo() : ResetTalentsCost(0), ResetTalentsTime(0), PrimarySpecialization(0), ActiveGroup(0)
{
- for (uint8 i = 0; i < MAX_TALENT_GROUPS; ++i)
- delete GroupInfo[i].Talents;
}
- struct TalentGroupInfo
- {
- PlayerTalentMap* Talents;
- uint32 SpecId;
- } GroupInfo[MAX_TALENT_GROUPS];
-
+ PlayerTalentMap Talents[MAX_SPECIALIZATIONS];
uint32 ResetTalentsCost;
time_t ResetTalentsTime;
+ uint32 PrimarySpecialization;
uint8 ActiveGroup;
- uint8 GroupsCount;
private:
- PlayerTalentInfo(PlayerTalentInfo const&);
+ PlayerTalentInfo(PlayerTalentInfo const&) = delete;
+ PlayerTalentInfo& operator=(PlayerTalentInfo const&) = delete;
};
class TC_GAME_API Player : public Unit, public GridObject<Player>
@@ -1753,32 +1749,28 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SetTalentResetCost(uint32 cost) { _talentMgr->ResetTalentsCost = cost; }
time_t GetTalentResetTime() const { return _talentMgr->ResetTalentsTime; }
void SetTalentResetTime(time_t time_) { _talentMgr->ResetTalentsTime = time_; }
- uint32 GetSpecId(uint8 group) const { return _talentMgr->GroupInfo[group].SpecId; }
- void SetSpecId(uint8 group, uint32 tree) { _talentMgr->GroupInfo[group].SpecId = tree; }
+ uint32 GetPrimarySpecialization() const { return _talentMgr->PrimarySpecialization; }
+ void SetPrimarySpecialization(uint32 spec) { _talentMgr->PrimarySpecialization = spec; }
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 GetDefaultSpecId() const;
bool ResetTalents(bool noCost = false);
uint32 GetNextResetTalentsCost() const;
void InitTalentForLevel();
void SendTalentsInfoData();
- bool LearnTalent(uint32 talentId);
+ TalentLearnResult LearnTalent(uint32 talentId, int32* spellOnCooldown);
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);
- void ActivateTalentGroup(uint8 group);
+ void ActivateTalentGroup(ChrSpecializationEntry const* spec);
- PlayerTalentMap const* GetTalentMap(uint8 spec) const { return _talentMgr->GroupInfo[spec].Talents; }
- PlayerTalentMap* GetTalentMap(uint8 spec) { return _talentMgr->GroupInfo[spec].Talents; }
+ PlayerTalentMap const* GetTalentMap(uint8 spec) const { return &_talentMgr->Talents[spec]; }
+ PlayerTalentMap* GetTalentMap(uint8 spec) { return &_talentMgr->Talents[spec]; }
ActionButtonList const& GetActionButtons() const { return m_actionButtons; }
uint32 GetFreePrimaryProfessionPoints() const { return GetUInt32Value(PLAYER_CHARACTER_POINTS); }
diff --git a/src/server/game/Entities/Unit/StatSystem.cpp b/src/server/game/Entities/Unit/StatSystem.cpp
index 447169a28d0..c3b8ef8e07e 100644
--- a/src/server/game/Entities/Unit/StatSystem.cpp
+++ b/src/server/game/Entities/Unit/StatSystem.cpp
@@ -534,7 +534,7 @@ void Player::UpdateMastery()
value += GetRatingBonusValue(CR_MASTERY);
SetFloatValue(PLAYER_MASTERY, value);
- ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetSpecId(GetActiveTalentGroup()));
+ ChrSpecializationEntry const* chrSpec = sChrSpecializationStore.LookupEntry(GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID));
if (!chrSpec)
return;
diff --git a/src/server/game/Handlers/InspectHandler.cpp b/src/server/game/Handlers/InspectHandler.cpp
index 3ac39c2d500..02746109348 100644
--- a/src/server/game/Handlers/InspectHandler.cpp
+++ b/src/server/game/Handlers/InspectHandler.cpp
@@ -69,7 +69,7 @@ void WorldSession::HandleInspectOpcode(WorldPackets::Inspect::Inspect& inspect)
}
inspectResult.InspecteeGUID = inspect.Target;
- inspectResult.SpecializationID = player->GetSpecId(player->GetActiveTalentGroup());
+ inspectResult.SpecializationID = player->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID);
SendPacket(inspectResult.Write());
}
diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp
index 3596eee68f6..e9fafac5d1d 100644
--- a/src/server/game/Handlers/SkillHandler.cpp
+++ b/src/server/game/Handlers/SkillHandler.cpp
@@ -28,10 +28,23 @@
void WorldSession::HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet)
{
+ WorldPackets::Talent::LearnTalentsFailed learnTalentsFailed;
bool anythingLearned = false;
for (uint32 talentId : packet.Talents)
- if (_player->LearnTalent(talentId))
+ {
+ if (TalentLearnResult result = _player->LearnTalent(talentId, &learnTalentsFailed.SpellID))
+ {
+ if (!learnTalentsFailed.Reason)
+ learnTalentsFailed.Reason = result;
+
+ learnTalentsFailed.Talents.push_back(talentId);
+ }
+ else
anythingLearned = true;
+ }
+
+ if (learnTalentsFailed.Reason)
+ SendPacket(learnTalentsFailed.Write());
if (anythingLearned)
_player->SendTalentsInfoData();
@@ -77,35 +90,3 @@ void WorldSession::HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill&
GetPlayer()->SetSkill(packet.SkillLine, 0, 0, 0);
}
-
-void WorldSession::HandleSetSpecializationOpcode(WorldPackets::Talent::SetSpecialization& packet)
-{
- Player* player = GetPlayer();
-
- if (packet.SpecGroupIndex >= MAX_SPECIALIZATIONS)
- {
- TC_LOG_DEBUG("network", "WORLD: HandleSetSpecializationOpcode - specialization index %u out of range", packet.SpecGroupIndex);
- return;
- }
-
- ChrSpecializationEntry const* chrSpec = sDB2Manager.GetChrSpecializationByIndex(player->getClass(), packet.SpecGroupIndex);
- if (!chrSpec)
- {
- TC_LOG_DEBUG("network", "WORLD: HandleSetSpecializationOpcode - specialization index %u not found", packet.SpecGroupIndex);
- return;
- }
-
- if (chrSpec->ClassID != player->getClass())
- {
- TC_LOG_DEBUG("network", "WORLD: HandleSetSpecializationOpcode - specialization %u does not belong to class %u", chrSpec->ID, player->getClass());
- return;
- }
-
- if (player->getLevel() < MIN_SPECIALIZATION_LEVEL)
- {
- TC_LOG_DEBUG("network", "WORLD: HandleSetSpecializationOpcode - player level too low for specializations");
- return;
- }
-
- player->LearnTalentSpecialization(chrSpec->ID);
-}
diff --git a/src/server/game/Server/Packets/TalentPackets.cpp b/src/server/game/Server/Packets/TalentPackets.cpp
index bc7b6aec20c..1069b8a89f7 100644
--- a/src/server/game/Server/Packets/TalentPackets.cpp
+++ b/src/server/game/Server/Packets/TalentPackets.cpp
@@ -19,41 +19,31 @@
WorldPacket const* WorldPackets::Talent::UpdateTalentData::Write()
{
- _worldPacket << Info.ActiveGroup;
+ _worldPacket << uint8(Info.ActiveGroup);
+ _worldPacket << uint32(Info.PrimarySpecialization);
_worldPacket << uint32(Info.TalentGroups.size());
for (auto& talentGroupInfo : Info.TalentGroups)
{
- _worldPacket << talentGroupInfo.SpecID;
+ _worldPacket << uint32(talentGroupInfo.SpecID);
_worldPacket << uint32(talentGroupInfo.TalentIDs.size());
-
- //for (uint32 i = 0; i < MAX_GLYPH_SLOT_INDEX; ++i)
- // _worldPacket << talentGroupInfo.GlyphIDs[i];
+ _worldPacket << uint32(talentGroupInfo.PvPTalentIDs.size());
for (uint16 talentID : talentGroupInfo.TalentIDs)
- _worldPacket << talentID;
+ _worldPacket << uint16(talentID);
+
+ for (uint16 talentID : talentGroupInfo.PvPTalentIDs)
+ _worldPacket << uint16(talentID);
}
return &_worldPacket;
}
-void WorldPackets::Talent::SetSpecialization::Read()
-{
- _worldPacket >> SpecGroupIndex;
-}
-
-
void WorldPackets::Talent::LearnTalents::Read()
{
- uint32 count;
- _worldPacket >> count;
-
- for (uint32 i = 0; i < count; ++i)
- {
- uint16 talent;
- _worldPacket >> talent;
- Talents.push_back(talent);
- }
+ Talents.resize(_worldPacket.ReadBits(6));
+ for (uint32 i = 0; i < Talents.size(); ++i)
+ _worldPacket >> Talents[i];
}
WorldPacket const* WorldPackets::Talent::RespecWipeConfirm::Write()
@@ -69,3 +59,14 @@ void WorldPackets::Talent::ConfirmRespecWipe::Read()
_worldPacket >> RespecMaster;
_worldPacket >> RespecType;
}
+
+WorldPacket const* WorldPackets::Talent::LearnTalentsFailed::Write()
+{
+ _worldPacket.WriteBits(Reason, 4);
+ _worldPacket << int32(SpellID);
+ _worldPacket << uint32(Talents.size());
+ if (!Talents.empty())
+ _worldPacket.append(Talents.data(), Talents.size());
+
+ return &_worldPacket;
+}
diff --git a/src/server/game/Server/Packets/TalentPackets.h b/src/server/game/Server/Packets/TalentPackets.h
index 9e09605b362..57893bb94a4 100644
--- a/src/server/game/Server/Packets/TalentPackets.h
+++ b/src/server/game/Server/Packets/TalentPackets.h
@@ -19,6 +19,7 @@
#define TalentPackets_h__
#include "Packet.h"
+#include "PacketUtilities.h"
#include "Player.h"
namespace WorldPackets
@@ -27,14 +28,15 @@ namespace WorldPackets
{
struct TalentGroupInfo
{
- uint32 SpecID;
+ uint32 SpecID = 0;
std::vector<uint16> TalentIDs;
- //uint16 GlyphIDs[MAX_GLYPH_SLOT_INDEX];
+ std::vector<uint16> PvPTalentIDs;
};
struct TalentInfoUpdate
{
- uint8 ActiveGroup;
+ uint8 ActiveGroup = 0;
+ uint32 PrimarySpecialization = 0;
std::vector<TalentGroupInfo> TalentGroups;
};
@@ -48,26 +50,13 @@ namespace WorldPackets
TalentInfoUpdate Info;
};
- class SetSpecialization final : public ClientPacket
- {
- public:
- SetSpecialization(WorldPacket&& packet) : ClientPacket(CMSG_SET_SPECIALIZATION, std::move(packet)) { }
-
- void Read() override;
-
- uint32 SpecGroupIndex = 0;
- };
-
class LearnTalents final : public ClientPacket
{
public:
- LearnTalents(WorldPacket&& packet) : ClientPacket(std::move(packet))
- {
- ASSERT(packet.GetOpcode() == CMSG_LEARN_TALENTS);
- }
+ LearnTalents(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_TALENTS, std::move(packet)) { }
void Read() override;
- std::vector<uint16> Talents;
+ Array<uint16, MAX_TALENT_TIERS> Talents;
};
class RespecWipeConfirm final : public ServerPacket
@@ -93,6 +82,17 @@ namespace WorldPackets
uint8 RespecType = 0;
};
+ class LearnTalentsFailed final : public ServerPacket
+ {
+ public:
+ LearnTalentsFailed() : ServerPacket(SMSG_LEARN_TALENTS_FAILED, 1 + 4 + 4 + 2 * MAX_TALENT_TIERS) { }
+
+ WorldPacket const* Write() override;
+
+ uint32 Reason = 0;
+ int32 SpellID = 0;
+ std::vector<uint16> Talents;
+ };
}
}
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index ca6f86e8a18..8cd8dd7cd84 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -428,7 +428,7 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_JOIN_RATED_BATTLEGROUND, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_KEEP_ALIVE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPacket, &WorldSession::Handle_EarlyProccess);
DEFINE_HANDLER(CMSG_KEYBOUND_OVERRIDE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_LEARN_TALENTS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, WorldPackets::Talent::LearnTalents, &WorldSession::HandleLearnTalentsOpcode);
+ DEFINE_HANDLER(CMSG_LEARN_TALENTS, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Talent::LearnTalents, &WorldSession::HandleLearnTalentsOpcode);
DEFINE_HANDLER(CMSG_LEAVE_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Party::LeaveGroup, &WorldSession::HandleLeaveGroupOpcode);
DEFINE_HANDLER(CMSG_LEAVE_PET_BATTLE_QUEUE, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
DEFINE_HANDLER(CMSG_LFG_LIST_APPLY_TO_GROUP, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
@@ -701,7 +701,6 @@ void OpcodeTable::Initialize()
DEFINE_HANDLER(CMSG_SET_SELECTION, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Misc::SetSelection, &WorldSession::HandleSetSelectionOpcode);
DEFINE_HANDLER(CMSG_SET_SHEATHED, STATUS_LOGGEDIN, PROCESS_INPLACE, WorldPackets::Combat::SetSheathed, &WorldSession::HandleSetSheathedOpcode);
DEFINE_HANDLER(CMSG_SET_SORT_BAGS_RIGHT_TO_LEFT, STATUS_UNHANDLED, PROCESS_INPLACE, WorldPackets::Null, &WorldSession::Handle_NULL);
- DEFINE_HANDLER(CMSG_SET_SPECIALIZATION, STATUS_LOGGEDIN, PROCESS_INPLACE, WorldPackets::Talent::SetSpecialization, &WorldSession::HandleSetSpecializationOpcode);
DEFINE_HANDLER(CMSG_SET_TAXI_BENCHMARK_MODE, STATUS_LOGGEDIN, PROCESS_INPLACE, WorldPackets::Misc::SetTaxiBenchmarkMode, &WorldSession::HandleSetTaxiBenchmark);
DEFINE_HANDLER(CMSG_SET_TITLE, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Character::SetTitle, &WorldSession::HandleSetTitleOpcode);
DEFINE_HANDLER(CMSG_SET_TRADE_CURRENCY, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, WorldPackets::Trade::SetTradeCurrency, &WorldSession::HandleSetTradeCurrencyOpcode);
@@ -1234,8 +1233,8 @@ void OpcodeTable::Initialize()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_ITEM_TIME_UPDATE, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_KICK_REASON, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARNED_SPELLS, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_PVP_TALENT_FAILED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_TALENT_FAILED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_PVP_TALENTS_FAILED, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEARN_TALENTS_FAILED, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEVEL_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LEVEL_UP_INFO, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_LFG_BOOT_PLAYER, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
@@ -1696,7 +1695,7 @@ void OpcodeTable::Initialize()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_INSTANCE_OWNERSHIP, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_LAST_INSTANCE, STATUS_NEVER, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_OBJECT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_TALENT_DATA, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_TALENT_DATA, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_TASK_PROGRESS, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_WEEKLY_SPELL_USAGE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_UPDATE_WORLD_STATE, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
diff --git a/src/server/game/Server/Protocol/Opcodes.h b/src/server/game/Server/Protocol/Opcodes.h
index 571a1473aa1..d6ce4971a15 100644
--- a/src/server/game/Server/Protocol/Opcodes.h
+++ b/src/server/game/Server/Protocol/Opcodes.h
@@ -736,7 +736,6 @@ enum OpcodeClient : uint32
CMSG_BF_MGR_ENTRY_INVITE_RESPONSE = 0xBADD,
CMSG_BF_MGR_QUEUE_INVITE_RESPONSE = 0xBADD,
CMSG_BF_MGR_QUEUE_EXIT_REQUEST = 0xBADD,
- CMSG_SET_SPECIALIZATION = 0xBADD,
};
enum OpcodeServer : uint32
@@ -1174,8 +1173,8 @@ enum OpcodeServer : uint32
SMSG_ITEM_TIME_UPDATE = 0x2793,
SMSG_KICK_REASON = 0x2821,
SMSG_LEARNED_SPELLS = 0x2C4C,
- SMSG_LEARN_PVP_TALENT_FAILED = 0x25E7,
- SMSG_LEARN_TALENT_FAILED = 0x25E6,
+ SMSG_LEARN_PVP_TALENTS_FAILED = 0x25E7,
+ SMSG_LEARN_TALENTS_FAILED = 0x25E6,
SMSG_LEVEL_UPDATE = 0x2587,
SMSG_LEVEL_UP_INFO = 0x271C,
SMSG_LFG_BOOT_PLAYER = 0x2A36,
diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h
index 53119742c4c..5b06c9ce03f 100644
--- a/src/server/game/Server/WorldSession.h
+++ b/src/server/game/Server/WorldSession.h
@@ -613,7 +613,6 @@ namespace WorldPackets
namespace Talent
{
- class SetSpecialization;
class LearnTalents;
class ConfirmRespecWipe;
}
@@ -1433,7 +1432,6 @@ class TC_GAME_API WorldSession
void HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet);
void HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe);
void HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill& packet);
- void HandleSetSpecializationOpcode(WorldPackets::Talent::SetSpecialization& packet);
void HandleQuestgiverStatusQueryOpcode(WorldPackets::Quest::QuestGiverStatusQuery& packet);
void HandleQuestgiverStatusMultipleQuery(WorldPackets::Quest::QuestGiverStatusMultipleQuery& packet);
diff --git a/src/server/game/Spells/Spell.cpp b/src/server/game/Spells/Spell.cpp
index c0746016ff1..8aa8bfc404d 100644
--- a/src/server/game/Spells/Spell.cpp
+++ b/src/server/game/Spells/Spell.cpp
@@ -5404,6 +5404,10 @@ SpellCastResult Spell::CheckCast(bool strict)
}
case SPELL_EFFECT_TALENT_SPEC_SELECT:
{
+ ChrSpecializationEntry const* spec = sChrSpecializationStore.LookupEntry(m_misc.SpecializationId);
+ if (!spec || spec->ClassID != m_caster->getClass())
+ return SPELL_FAILED_NO_SPEC;
+
// can't change during already started arena/battleground
if (m_caster->GetTypeId() == TYPEID_PLAYER)
if (Battleground const* bg = m_caster->ToPlayer()->GetBattleground())
diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h
index 523796eb56d..5917ed512c3 100644
--- a/src/server/game/Spells/Spell.h
+++ b/src/server/game/Spells/Spell.h
@@ -430,7 +430,6 @@ class TC_GAME_API Spell
void EffectTitanGrip(SpellEffIndex effIndex);
void EffectEnchantItemPrismatic(SpellEffIndex effIndex);
void EffectPlayMusic(SpellEffIndex effIndex);
- void EffectSpecCount(SpellEffIndex effIndex);
void EffectActivateSpec(SpellEffIndex effIndex);
void EffectPlaySound(SpellEffIndex effIndex);
void EffectRemoveAura(SpellEffIndex effIndex);
@@ -572,6 +571,9 @@ class TC_GAME_API Spell
uint32 TalentId;
uint32 GlyphSlot;
+ // SPELL_EFFECT_TALENT_SPEC_SELECT
+ uint32 SpecializationId;
+
// SPELL_EFFECT_SET_FOLLOWER_QUALITY
// SPELL_EFFECT_INCREASE_FOLLOWER_ITEM_LEVEL
// SPELL_EFFECT_INCREASE_FOLLOWER_EXPERIENCE
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index d7aa6769b8b..3e9dec9f485 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -226,7 +226,7 @@ pEffect SpellEffects[TOTAL_SPELL_EFFECTS]=
&Spell::EffectMilling, //158 SPELL_EFFECT_MILLING milling
&Spell::EffectRenamePet, //159 SPELL_EFFECT_ALLOW_RENAME_PET allow rename pet once again
&Spell::EffectForceCast, //160 SPELL_EFFECT_FORCE_CAST_2
- &Spell::EffectSpecCount, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert)
+ &Spell::EffectNULL, //161 SPELL_EFFECT_TALENT_SPEC_COUNT second talent spec (learn/revert)
&Spell::EffectActivateSpec, //162 SPELL_EFFECT_TALENT_SPEC_SELECT activate primary/secondary spec
&Spell::EffectUnused, //163 SPELL_EFFECT_163 unused
&Spell::EffectRemoveAura, //164 SPELL_EFFECT_REMOVE_AURA
@@ -5357,17 +5357,6 @@ void Spell::EffectPlayMusic(SpellEffIndex /*effIndex*/)
unitTarget->ToPlayer()->GetSession()->SendPacket(WorldPackets::Misc::PlayMusic(soundid).Write());
}
-void Spell::EffectSpecCount(SpellEffIndex /*effIndex*/)
-{
- if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
- return;
-
- if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER)
- return;
-
- unitTarget->ToPlayer()->UpdateTalentGroupCount(damage);
-}
-
void Spell::EffectActivateSpec(SpellEffIndex /*effIndex*/)
{
if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
@@ -5376,7 +5365,7 @@ void Spell::EffectActivateSpec(SpellEffIndex /*effIndex*/)
if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER)
return;
- unitTarget->ToPlayer()->ActivateTalentGroup(damage-1); // damage is 1 or 2, spec is 0 or 1
+ unitTarget->ToPlayer()->ActivateTalentGroup(sChrSpecializationStore.AssertEntry(m_misc.SpecializationId));
}
void Spell::EffectPlaySound(SpellEffIndex /*effIndex*/)
diff --git a/src/server/game/Spells/SpellInfo.cpp b/src/server/game/Spells/SpellInfo.cpp
index 743237ada6e..6e8db741a3d 100644
--- a/src/server/game/Spells/SpellInfo.cpp
+++ b/src/server/game/Spells/SpellInfo.cpp
@@ -2843,7 +2843,7 @@ float SpellInfo::CalcProcPPM(Unit* caster, int32 itemLevel) const
case SPELL_PPM_MOD_SPEC:
{
if (Player* plrCaster = caster->ToPlayer())
- if (plrCaster->GetSpecId(plrCaster->GetActiveTalentGroup()) == mod->Param)
+ if (plrCaster->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) == mod->Param)
ppm *= 1.0f + mod->Coeff;
break;
}
diff --git a/src/server/scripts/Spells/spell_druid.cpp b/src/server/scripts/Spells/spell_druid.cpp
index a4665973009..2c941a4f757 100644
--- a/src/server/scripts/Spells/spell_druid.cpp
+++ b/src/server/scripts/Spells/spell_druid.cpp
@@ -162,7 +162,7 @@ class spell_dru_eclipse_energize : public SpellScriptLoader
Player* caster = GetCaster()->ToPlayer();
// No boomy, no deal.
- if (caster->GetSpecId(caster->GetActiveTalentGroup()) != TALENT_SPEC_DRUID_BALANCE)
+ if (caster->GetUInt32Value(PLAYER_FIELD_CURRENT_SPEC_ID) != TALENT_SPEC_DRUID_BALANCE)
return;
switch (GetSpellInfo()->Id)