mirror of
https://github.com/TrinityCore/TrinityCore.git
synced 2026-01-20 09:17:36 +01:00
Core/Players: re-implement talents
*todo: class restriction and talent tier checks and remove the now useless learn all talents command
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE `character_talent`
|
||||
ADD COLUMN `talentRank` TINYINT UNSIGNED DEFAULT 0 NOT NULL AFTER `talentId`;
|
||||
@@ -150,7 +150,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
"appearance17, appearance18, mainHandEnchant, offHandEnchant FROM character_transmog_outfits WHERE guid = ? ORDER BY setindex", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_BGDATA, "SELECT instanceId, team, joinX, joinY, joinZ, joinO, joinMapId, taxiStart, taxiEnd, mountSpell, queueId FROM character_battleground_data WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_GLYPHS, "SELECT talentGroup, glyphId FROM character_glyphs WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_TALENTS, "SELECT talentId, talentRank, talentGroup FROM character_talent WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_SKILLS, "SELECT skill, value, max, professionSlot FROM character_skills WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_RANDOMBG, "SELECT guid FROM character_battleground_random WHERE guid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_SEL_CHARACTER_BANNED, "SELECT guid FROM character_banned WHERE guid = ? AND active = 1", CONNECTION_ASYNC);
|
||||
@@ -658,7 +658,7 @@ void CharacterDatabaseConnection::DoPrepareStatements()
|
||||
PrepareStatement(CHAR_DEL_PETITION_BY_OWNER, "DELETE FROM petition WHERE ownerguid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_PETITION_SIGNATURE_BY_OWNER, "DELETE FROM petition_sign WHERE ownerguid = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_CHAR_GLYPHS, "INSERT INTO character_glyphs VALUES(?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentGroup) VALUES (?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_CHAR_TALENT, "INSERT INTO character_talent (guid, talentId, talentRank, talentGroup) VALUES (?, ?, ?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_UPD_CHAR_LIST_SLOT, "UPDATE characters SET slot = ? WHERE guid = ? AND account = ?", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_INS_CHAR_FISHINGSTEPS, "INSERT INTO character_fishingsteps (guid, fishingSteps) VALUES (?, ?)", CONNECTION_ASYNC);
|
||||
PrepareStatement(CHAR_DEL_CHAR_FISHINGSTEPS, "DELETE FROM character_fishingsteps WHERE guid = ?", CONNECTION_ASYNC);
|
||||
|
||||
@@ -2129,6 +2129,7 @@ DEFINE_ENUM_FLAG(SummonPropertiesFlags);
|
||||
|
||||
#define MAX_TALENT_TIERS 11
|
||||
#define MAX_TALENT_COLUMNS 4
|
||||
#define MAX_TALENT_RANK 9
|
||||
|
||||
enum class TaxiNodeFlags : int32
|
||||
{
|
||||
|
||||
@@ -2414,8 +2414,9 @@ bool Player::IsMaxLevel() const
|
||||
void Player::InitTalentForLevel()
|
||||
{
|
||||
uint8 level = GetLevel();
|
||||
int32 talentPoints = CalculateTalentsPoints();
|
||||
if (level < MIN_SPECIALIZATION_LEVEL)
|
||||
uint32 talentPoints = CalculateTalentsPoints();
|
||||
uint32 spentTalentPoints = GetSpentTalentPointsCount();
|
||||
if (spentTalentPoints && level < MIN_SPECIALIZATION_LEVEL)
|
||||
{
|
||||
// Remove all talent points
|
||||
ResetTalents(true);
|
||||
@@ -2424,7 +2425,8 @@ void Player::InitTalentForLevel()
|
||||
if (GetSpentTalentPointsCount() > talentPoints)
|
||||
ResetTalents(true);
|
||||
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), talentPoints);
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::MaxTalentTiers), static_cast<int32>(talentPoints));
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), static_cast<int32>(talentPoints - spentTalentPoints));
|
||||
|
||||
if (!GetSession()->PlayerLoading())
|
||||
SendTalentsInfoData(); // update at client
|
||||
@@ -2709,33 +2711,56 @@ void DeleteSpellFromAllPlayers(uint32 spellId)
|
||||
CharacterDatabase.Execute(stmt);
|
||||
}
|
||||
|
||||
bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
|
||||
bool Player::AddTalent(TalentEntry const* talent, uint16 rank, uint8 talentGroupId, bool learning)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellRank[rank], DIFFICULTY_NONE);
|
||||
if (!spellInfo)
|
||||
{
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist.", talent->SpellID);
|
||||
// do character spell book cleanup (all characters)
|
||||
if (!IsInWorld() && !learning) // spell load case
|
||||
{
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist. Deleting for all characters in `character_spell` and `character_talent`.", talent->SpellRank[rank]);
|
||||
DeleteSpellFromAllPlayers(talent->SpellRank[rank]);
|
||||
}
|
||||
else
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) does not exist", talent->SpellRank[rank]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SpellMgr::IsSpellValid(spellInfo, this, false))
|
||||
{
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellID);
|
||||
// do character spell book cleanup (all characters)
|
||||
if (!IsInWorld() && !learning) // spell load case
|
||||
{
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid. Deleting for all characters in `character_spell` and `character_talent`.", talent->SpellRank[rank]);
|
||||
DeleteSpellFromAllPlayers(talent->SpellRank[rank]);
|
||||
}
|
||||
else
|
||||
TC_LOG_ERROR("spells", "Player::AddTalent: Spell (ID: {}) is invalid", talent->SpellRank[rank]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerTalentMap::iterator itr = GetTalentMap(spec)->find(talent->ID);
|
||||
if (itr != GetTalentMap(spec)->end())
|
||||
itr->second = PLAYERSPELL_UNCHANGED;
|
||||
else
|
||||
(*GetTalentMap(spec))[talent->ID] = learning ? PLAYERSPELL_NEW : PLAYERSPELL_UNCHANGED;
|
||||
|
||||
if (spec == GetActiveTalentGroup())
|
||||
PlayerTalentMap& talentMap = GetPlayerTalentMap(talentGroupId);
|
||||
auto itr = talentMap.find(talent->ID);
|
||||
if (itr != talentMap.end())
|
||||
{
|
||||
LearnSpell(talent->SpellID, true);
|
||||
if (talent->OverridesSpellID)
|
||||
AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
|
||||
|
||||
|
||||
itr->second.State = PLAYERSPELL_UNCHANGED;
|
||||
itr->second.Rank = static_cast<uint8>(rank);
|
||||
}
|
||||
else
|
||||
talentMap[talent->ID] = { PLAYERSPELL_UNCHANGED, static_cast<uint8>(rank) };
|
||||
|
||||
// Inactive talent groups will only be initialized
|
||||
if (GetActiveTalentGroup() != talentGroupId)
|
||||
return true;
|
||||
|
||||
LearnSpell(spellInfo->Id, true);
|
||||
if (talent->OverridesSpellID)
|
||||
AddOverrideSpell(talent->OverridesSpellID, talent->SpellID);
|
||||
|
||||
if (learning)
|
||||
RemoveAurasWithInterruptFlags(SpellAuraInterruptFlags2::ChangeTalent);
|
||||
@@ -2745,11 +2770,20 @@ bool Player::AddTalent(TalentEntry const* talent, uint8 spec, bool learning)
|
||||
|
||||
void Player::RemoveTalent(TalentEntry const* talent)
|
||||
{
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talent->SpellID, DIFFICULTY_NONE);
|
||||
PlayerTalentMap& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
|
||||
auto itr = talentMap.find(talent->ID);
|
||||
if (itr == talentMap.end())
|
||||
return;
|
||||
|
||||
uint32 spellId = talent->SpellRank[itr->second.Rank];
|
||||
if (!spellId)
|
||||
return;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(spellId, DIFFICULTY_NONE);
|
||||
if (!spellInfo)
|
||||
return;
|
||||
|
||||
RemoveSpell(talent->SpellID, true);
|
||||
RemoveSpell(spellId, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
|
||||
@@ -2759,10 +2793,9 @@ void Player::RemoveTalent(TalentEntry const* talent)
|
||||
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 = PLAYERSPELL_REMOVED;
|
||||
// Mark the talent as deleted to it will be deleted upon the next save cycle
|
||||
itr->second.State = PLAYERSPELL_REMOVED;
|
||||
itr->second.Rank = 0;
|
||||
}
|
||||
|
||||
void Player::AddStoredAuraTeleportLocation(uint32 spellId)
|
||||
@@ -3573,23 +3606,25 @@ bool Player::ResetTalents(bool noCost)
|
||||
|
||||
RemovePet(nullptr, PET_SAVE_NOT_IN_SLOT, true);
|
||||
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
|
||||
for (auto const& pair : talentMap)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
if (!talentInfo)
|
||||
TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first);
|
||||
if (!talentEntry)
|
||||
continue;
|
||||
|
||||
/*
|
||||
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID);
|
||||
if (!talentTabInfo)
|
||||
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())
|
||||
if ((GetClassMask() & talentTabInfo->ClassMask) == 0)
|
||||
continue;
|
||||
*/
|
||||
|
||||
// skip non-existent talent ranks
|
||||
if (talentInfo->SpellID == 0)
|
||||
continue;
|
||||
|
||||
RemoveTalent(talentInfo);
|
||||
RemoveTalent(talentEntry);
|
||||
}
|
||||
|
||||
CharacterDatabaseTransaction trans = CharacterDatabase.BeginTransaction();
|
||||
@@ -3615,6 +3650,8 @@ bool Player::ResetTalents(bool noCost)
|
||||
}
|
||||
*/
|
||||
|
||||
InitTalentForLevel();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3812,12 +3849,6 @@ bool Player::HasSpell(uint32 spell) const
|
||||
!itr->second.disabled);
|
||||
}
|
||||
|
||||
bool Player::HasTalent(uint32 talentId, uint8 group) const
|
||||
{
|
||||
PlayerTalentMap::const_iterator itr = GetTalentMap(group)->find(talentId);
|
||||
return (itr != GetTalentMap(group)->end() && itr->second != PLAYERSPELL_REMOVED);
|
||||
}
|
||||
|
||||
bool Player::HasActiveSpell(uint32 spell) const
|
||||
{
|
||||
PlayerSpellMap::const_iterator itr = m_spells.find(spell);
|
||||
@@ -26086,94 +26117,98 @@ bool Player::ModifierTreeSatisfied(uint32 modifierTreeId) const
|
||||
return m_achievementMgr->ModifierTreeSatisfied(modifierTreeId);
|
||||
}
|
||||
|
||||
TalentLearnResult Player::LearnTalent(uint32 talentId, int32* spellOnCooldown)
|
||||
static constexpr uint8 NEEDED_TALENT_POINT_PER_TIER = 5;
|
||||
|
||||
bool Player::LearnTalent(uint32 talentId, uint16 requestedRank)
|
||||
{
|
||||
if (IsInCombat())
|
||||
return TALENT_FAILED_AFFECTING_COMBAT;
|
||||
// No talent points left to spend, skip learn request
|
||||
if (!m_activePlayerData->CharacterPoints)
|
||||
return false;
|
||||
|
||||
if (isDead())
|
||||
return TALENT_FAILED_CANT_DO_THAT_RIGHT_NOW;
|
||||
|
||||
if (GetPrimarySpecialization() == ChrSpecialization::None)
|
||||
return TALENT_FAILED_NO_PRIMARY_TREE_SELECTED;
|
||||
if (requestedRank >= MAX_TALENT_RANK)
|
||||
return false;
|
||||
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
if (!talentInfo)
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
return false;
|
||||
|
||||
if (talentInfo->SpecID && ChrSpecialization(talentInfo->SpecID) != GetPrimarySpecialization())
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
/*
|
||||
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID);
|
||||
if (!talentTabInfo)
|
||||
return;
|
||||
|
||||
// prevent learn talent for different class (cheating)
|
||||
if (talentInfo->ClassID != GetClass())
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
if (!(GetClassMask() & talentTabInfo->ClassMask))
|
||||
return;
|
||||
*/
|
||||
|
||||
// check if we have enough talent points
|
||||
if (talentInfo->TierID >= m_activePlayerData->MaxTalentTiers)
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
// Check for existing talents
|
||||
|
||||
// TODO: prevent changing talents that are on cooldown
|
||||
|
||||
// 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 : sDB2Manager.GetTalentsByPosition(GetClass(), talentInfo->TierID, talentInfo->ColumnIndex))
|
||||
uint32 neededTalentPoints = 0;
|
||||
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
|
||||
auto itr = talentMap.find(talentId);
|
||||
if (itr != talentMap.end())
|
||||
{
|
||||
if (!talent->SpecID)
|
||||
bestSlotMatch = talent;
|
||||
else if (ChrSpecialization(talent->SpecID) == GetPrimarySpecialization())
|
||||
{
|
||||
bestSlotMatch = talent;
|
||||
break;
|
||||
}
|
||||
// We already know this or a higher rank of this talent
|
||||
if (itr->second.Rank >= requestedRank)
|
||||
return false;
|
||||
|
||||
neededTalentPoints = (itr->second.Rank - requestedRank) + 1;
|
||||
}
|
||||
else
|
||||
neededTalentPoints = requestedRank + 1;
|
||||
|
||||
// Not enough talent points to learn the talent at this rank
|
||||
if (neededTalentPoints > static_cast<uint32>(m_activePlayerData->CharacterPoints))
|
||||
return false;
|
||||
|
||||
// Check talent dependencies
|
||||
for (size_t i = 0; i < talentInfo->PrereqRank.size(); ++i)
|
||||
{
|
||||
if (!talentInfo->PrereqTalent[i])
|
||||
continue;
|
||||
|
||||
itr = talentMap.find(talentInfo->PrereqTalent[i]);
|
||||
if (itr == talentMap.end() || itr->second.Rank < talentInfo->PrereqRank[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
if (talentInfo != bestSlotMatch)
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
// Find out how many points we have in this field
|
||||
/*
|
||||
uint32 spentPoints = 0;
|
||||
uint32 tTab = talentInfo->TabID;
|
||||
if (talentInfo->TierID > 0)
|
||||
for (TalentEntry const* tmpTalent : sTalentStore) // the way talents are tracked
|
||||
if (tmpTalent->TabID == tTab)
|
||||
for (uint8 rank = 0; rank < MAX_TALENT_RANK; rank++)
|
||||
if (tmpTalent->SpellRank[rank] != 0)
|
||||
if (HasSpell(tmpTalent->SpellRank[rank]))
|
||||
spentPoints += (rank + 1);
|
||||
|
||||
// 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()))
|
||||
continue;
|
||||
|
||||
if (!HasPlayerFlag(PLAYER_FLAGS_RESTING) && !HasUnitFlag2(UNIT_FLAG2_ALLOW_CHANGING_TALENTS))
|
||||
return TALENT_FAILED_REST_AREA;
|
||||
|
||||
if (GetSpellHistory()->HasCooldown(talent->SpellID))
|
||||
{
|
||||
*spellOnCooldown = talent->SpellID;
|
||||
return TALENT_FAILED_CANT_REMOVE_TALENT;
|
||||
}
|
||||
|
||||
RemoveTalent(talent);
|
||||
}
|
||||
}
|
||||
// not have required min points spent in talent tree
|
||||
if (spentPoints < (talentInfo->TierID * NEEDED_TALENT_POINT_PER_TIER))
|
||||
return;
|
||||
>*/
|
||||
|
||||
// spell not set in talent.dbc
|
||||
uint32 spellid = talentInfo->SpellID;
|
||||
if (!spellid)
|
||||
uint32 spellId = talentInfo->SpellRank[requestedRank];
|
||||
if (spellId == 0)
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Player::LearnTalent: Talent.dbc has no spellInfo for talent: {} (spell id = 0)", talentId);
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
return false;
|
||||
}
|
||||
|
||||
// already known
|
||||
if (HasTalent(talentId, GetActiveTalentGroup()) || HasSpell(spellid))
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
if (HasSpell(spellId))
|
||||
return false;
|
||||
|
||||
if (!AddTalent(talentInfo, GetActiveTalentGroup(), true))
|
||||
return TALENT_FAILED_UNKNOWN;
|
||||
AddTalent(talentInfo, requestedRank, GetActiveTalentGroup(), true);
|
||||
|
||||
TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: {} Spell: {} Group: {}\n", talentId, spellid, GetActiveTalentGroup());
|
||||
TC_LOG_DEBUG("misc", "Player::LearnTalent: TalentID: {} Spell: {} Group: {}\n", talentId, spellId, uint32(GetActiveTalentGroup()));
|
||||
|
||||
return TALENT_LEARN_OK;
|
||||
// update free talent points
|
||||
SetUpdateFieldValue(m_values.ModifyValue(&Player::m_activePlayerData).ModifyValue(&UF::ActivePlayerData::CharacterPoints), static_cast<int32>(CalculateTalentsPoints() - GetSpentTalentPointsCount()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void Player::EnablePvpRules(bool dueToCombat /*= false*/)
|
||||
@@ -26367,39 +26402,37 @@ void Player::SendTalentsInfoData()
|
||||
|
||||
uint8 activeGroup = GetActiveTalentGroup();
|
||||
packet.ActiveGroup = activeGroup;
|
||||
packet.UnspentTalentPoints = CalculateTalentsPoints() - GetSpentTalentPointsCount();
|
||||
packet.UnspentTalentPoints = m_activePlayerData->CharacterPoints;
|
||||
|
||||
for (uint8 i = 0; i < (1 + GetBonusTalentGroupCount()); ++i)
|
||||
{
|
||||
WorldPackets::Talent::TalentGroupInfo& groupInfo = packet.TalentGroupInfos.emplace_back();
|
||||
groupInfo.SpecID = MAX_SPECIALIZATIONS;
|
||||
if (PlayerTalentMap const* talentMap = GetTalentMap(i))
|
||||
PlayerTalentMap const& talentMap = GetPlayerTalentMap(i);
|
||||
groupInfo.Talents.reserve(talentMap.size());
|
||||
|
||||
for (auto const& pair : talentMap)
|
||||
{
|
||||
groupInfo.Talents.reserve(talentMap->size());
|
||||
if (pair.second.State == PLAYERSPELL_REMOVED)
|
||||
continue;
|
||||
|
||||
for (auto const& pair : *talentMap)
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(pair.first);
|
||||
if (!talentInfo)
|
||||
{
|
||||
if (pair.second == PLAYERSPELL_REMOVED)
|
||||
continue;
|
||||
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(pair.first);
|
||||
if (!talentInfo)
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent id: {}",
|
||||
GetName(), GetGUID().ToString(), pair.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
|
||||
if (!spellEntry)
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent spell: {}",
|
||||
GetName(), GetGUID().ToString(), talentInfo->SpellID);
|
||||
continue;
|
||||
}
|
||||
|
||||
groupInfo.Talents.push_back(uint16(pair.first));
|
||||
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent id: {}",
|
||||
GetName(), GetGUID().ToString(), pair.first);
|
||||
continue;
|
||||
}
|
||||
|
||||
SpellInfo const* spellEntry = sSpellMgr->GetSpellInfo(talentInfo->SpellRank[pair.second.Rank], DIFFICULTY_NONE);
|
||||
if (!spellEntry)
|
||||
{
|
||||
TC_LOG_ERROR("entities.player", "Player::SendTalentsInfoData: Player '{}' ({}) has unknown talent spell: {}",
|
||||
GetName(), GetGUID().ToString(), talentInfo->SpellID);
|
||||
continue;
|
||||
}
|
||||
|
||||
groupInfo.Talents.push_back({ pair.first, pair.second.Rank });
|
||||
}
|
||||
|
||||
std::vector<uint32> glyphs = GetGlyphs(activeGroup);
|
||||
@@ -26676,12 +26709,12 @@ void Player::_SaveGlyphs(CharacterDatabaseTransaction trans) const
|
||||
|
||||
void Player::_LoadTalents(PreparedQueryResult result)
|
||||
{
|
||||
// "SELECT talentId, talentGroup FROM character_talent WHERE guid = ?"
|
||||
// "SELECT talentId, talentRank, talentGroup FROM character_talent WHERE guid = ?"
|
||||
if (result)
|
||||
{
|
||||
do
|
||||
if (TalentEntry const* talent = sTalentStore.LookupEntry((*result)[0].GetUInt32()))
|
||||
AddTalent(talent, (*result)[1].GetUInt8(), false);
|
||||
AddTalent(talent, (*result)[1].GetUInt8(), (*result)[2].GetUInt8(), false);
|
||||
while (result->NextRow());
|
||||
}
|
||||
}
|
||||
@@ -26834,19 +26867,20 @@ void Player::_SaveTalents(CharacterDatabaseTransaction trans)
|
||||
|
||||
for (uint8 group = 0; group < MAX_SPECIALIZATIONS; ++group)
|
||||
{
|
||||
PlayerTalentMap* talents = GetTalentMap(group);
|
||||
for (auto itr = talents->begin(); itr != talents->end();)
|
||||
PlayerTalentMap& talents = _specializationInfo.Talents[group];
|
||||
for (auto itr = talents.begin(); itr != talents.end();)
|
||||
{
|
||||
if (itr->second == PLAYERSPELL_REMOVED)
|
||||
if (itr->second.State == PLAYERSPELL_REMOVED)
|
||||
{
|
||||
itr = talents->erase(itr);
|
||||
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, group);
|
||||
stmt->setUInt8(2, itr->second.Rank);
|
||||
stmt->setUInt8(3, group);
|
||||
trans->Append(stmt);
|
||||
++itr;
|
||||
}
|
||||
@@ -26989,37 +27023,31 @@ void Player::ActivateTalentGroup(uint8 talentGroup)
|
||||
|
||||
// Let client clear his current Actions
|
||||
SendActionButtons(2);
|
||||
// m_actionButtons.clear() is called in the next _LoadActionButtons
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
|
||||
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
|
||||
for (auto const& pair : talentMap)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
if (!talentInfo)
|
||||
TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first);
|
||||
if (!talentEntry)
|
||||
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;
|
||||
|
||||
if (talentInfo->SpellID == 0)
|
||||
continue;
|
||||
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentInfo->SpellID, DIFFICULTY_NONE);
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentEntry->SpellRank[pair.second.Rank], DIFFICULTY_NONE);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
RemoveSpell(talentInfo->SpellID, true);
|
||||
RemoveSpell(spellInfo->Id, true);
|
||||
|
||||
// search for spells that the talent teaches and unlearn them
|
||||
for (SpellEffectInfo const& spellEffectInfo : spellInfo->GetEffects())
|
||||
if (spellEffectInfo.IsEffect(SPELL_EFFECT_LEARN_SPELL) && spellEffectInfo.TriggerSpell > 0)
|
||||
RemoveSpell(spellEffectInfo.TriggerSpell, true);
|
||||
|
||||
if (talentInfo->OverridesSpellID)
|
||||
RemoveOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
|
||||
if (talentEntry->OverridesSpellID)
|
||||
RemoveOverrideSpell(talentEntry->OverridesSpellID, talentEntry->SpellID);
|
||||
}
|
||||
|
||||
// m_actionButtons.clear() is called in the next _LoadActionButtons
|
||||
|
||||
ApplyTraitConfig(m_activePlayerData->ActiveCombatTraitConfigID, false);
|
||||
|
||||
for (uint32 glyphId : GetGlyphs(GetActiveTalentGroup()))
|
||||
@@ -27028,26 +27056,21 @@ void Player::ActivateTalentGroup(uint8 talentGroup)
|
||||
SetActiveTalentGroup(talentGroup);
|
||||
SetPrimarySpecialization(0);
|
||||
|
||||
for (uint32 talentId = 0; talentId < sTalentStore.GetNumRows(); ++talentId)
|
||||
// if the talent can be found in the newly activated PlayerTalentMap
|
||||
PlayerTalentMap const& activatedTalentMap = GetPlayerTalentMap(GetActiveTalentGroup());
|
||||
for (auto const& pair : activatedTalentMap)
|
||||
{
|
||||
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
|
||||
if (!talentInfo)
|
||||
TalentEntry const* talentEntry = sTalentStore.LookupEntry(pair.first);
|
||||
if (!talentEntry)
|
||||
continue;
|
||||
|
||||
// learn only talents for character class
|
||||
if (talentInfo->ClassID != GetClass())
|
||||
SpellInfo const* spellInfo = sSpellMgr->GetSpellInfo(talentEntry->SpellRank[pair.second.Rank], DIFFICULTY_NONE);
|
||||
if (!spellInfo)
|
||||
continue;
|
||||
|
||||
if (!talentInfo->SpellID)
|
||||
continue;
|
||||
|
||||
// if the talent can be found in the newly activated PlayerTalentMap
|
||||
if (HasTalent(talentInfo->ID, GetActiveTalentGroup()))
|
||||
{
|
||||
LearnSpell(talentInfo->SpellID, true); // add the talent to the PlayerSpellMap
|
||||
if (talentInfo->OverridesSpellID)
|
||||
AddOverrideSpell(talentInfo->OverridesSpellID, talentInfo->SpellID);
|
||||
}
|
||||
LearnSpell(spellInfo->Id, true); // add the talent to the PlayerSpellMap
|
||||
if (talentEntry->OverridesSpellID)
|
||||
AddOverrideSpell(talentEntry->OverridesSpellID, talentEntry->SpellID);
|
||||
}
|
||||
|
||||
InitTalentForLevel();
|
||||
@@ -28732,8 +28755,13 @@ void Player::SetBonusTalentGroupCount(uint8 amount)
|
||||
|
||||
uint32 Player::GetSpentTalentPointsCount() const
|
||||
{
|
||||
PlayerTalentMap const& talentMap = _specializationInfo.Talents[GetActiveTalentGroup()];
|
||||
return std::count_if(talentMap.begin(), talentMap.end(), [](auto const& pair) { return (pair.second != PLAYERSPELL_REMOVED); });
|
||||
PlayerTalentMap const& talentMap = GetPlayerTalentMap(GetActiveTalentGroup());
|
||||
uint32 count = 0;
|
||||
for (auto const& pair : talentMap)
|
||||
if (pair.second.State != PLAYERSPELL_REMOVED)
|
||||
count += pair.second.Rank + 1;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
ChrSpecializationEntry const* Player::GetPrimarySpecializationEntry() const
|
||||
|
||||
@@ -264,7 +264,13 @@ struct PlayerCurrency
|
||||
CurrencyDbFlags Flags;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<uint32, PlayerSpellState> PlayerTalentMap;
|
||||
struct PlayerTalentState
|
||||
{
|
||||
PlayerSpellState State = PLAYERSPELL_UNCHANGED;
|
||||
uint8 Rank = 0;
|
||||
};
|
||||
|
||||
typedef std::unordered_map<uint32, PlayerTalentState> PlayerTalentMap;
|
||||
typedef std::unordered_map<uint32, PlayerSpell> PlayerSpellMap;
|
||||
typedef std::unordered_set<SpellModifier*> SpellModContainer;
|
||||
typedef std::unordered_map<uint32, PlayerCurrency> PlayerCurrenciesMap;
|
||||
@@ -1047,24 +1053,8 @@ struct GroupUpdateCounter
|
||||
int32 UpdateSequenceNumber;
|
||||
};
|
||||
|
||||
enum TalentLearnResult : int32
|
||||
{
|
||||
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,
|
||||
TALENT_FAILED_UNSPENT_TALENT_POINTS = 9,
|
||||
TALENT_FAILED_IN_PVP_MATCH = 10
|
||||
};
|
||||
|
||||
struct TC_GAME_API SpecializationInfo
|
||||
{
|
||||
|
||||
SpecializationInfo() : ResetTalentsCost(0), ResetTalentsTime(0), ActiveGroup(0), BonusGroups(0) { }
|
||||
|
||||
PlayerTalentMap Talents[MAX_SPECIALIZATIONS];
|
||||
@@ -1818,6 +1808,9 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
|
||||
void SetTalentResetCost(uint32 cost) { _specializationInfo.ResetTalentsCost = cost; }
|
||||
time_t GetTalentResetTime() const { return _specializationInfo.ResetTalentsTime; }
|
||||
void SetTalentResetTime(time_t time_) { _specializationInfo.ResetTalentsTime = time_; }
|
||||
PlayerTalentMap const& GetPlayerTalentMap(uint8 talentGroupId) const { return _specializationInfo.Talents[talentGroupId]; }
|
||||
PlayerTalentMap& GetPlayerTalentMap(uint8 talentGroupId) { return _specializationInfo.Talents[talentGroupId]; }
|
||||
|
||||
ChrSpecialization GetPrimarySpecialization() const { return ChrSpecialization(*m_playerData->CurrentSpecID); }
|
||||
void SetPrimarySpecialization(uint32 spec) { SetUpdateFieldValue(m_values.ModifyValue(&Player::m_playerData).ModifyValue(&UF::PlayerData::CurrentSpecID), spec); }
|
||||
uint8 GetActiveTalentGroup() const { return _specializationInfo.ActiveGroup; }
|
||||
@@ -1833,9 +1826,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
|
||||
void InitTalentForLevel();
|
||||
void SendTalentsInfoData();
|
||||
uint32 CalculateTalentsPoints() const;
|
||||
TalentLearnResult LearnTalent(uint32 talentId, int32* spellOnCooldown);
|
||||
bool AddTalent(TalentEntry const* talent, uint8 spec, bool learning);
|
||||
bool HasTalent(uint32 spell_id, uint8 spec) const;
|
||||
bool LearnTalent(uint32 talentId, uint16 requestedRank);
|
||||
bool AddTalent(TalentEntry const* talent, uint16 rank, uint8 talentGroupId, bool learning);
|
||||
void RemoveTalent(TalentEntry const* talent);
|
||||
|
||||
void EnablePvpRules(bool dueToCombat = false);
|
||||
@@ -1847,8 +1839,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
|
||||
// Dual Spec
|
||||
void ActivateTalentGroup(uint8 talentGroup);
|
||||
|
||||
PlayerTalentMap const* GetTalentMap(uint8 spec) const { return &_specializationInfo.Talents[spec]; }
|
||||
PlayerTalentMap* GetTalentMap(uint8 spec) { return &_specializationInfo.Talents[spec]; }
|
||||
std::vector<uint32> const& GetGlyphs(uint8 spec) const { return _specializationInfo.Glyphs[spec]; }
|
||||
std::vector<uint32>& GetGlyphs(uint8 spec) { return _specializationInfo.Glyphs[spec]; }
|
||||
ActionButtonList const& GetActionButtons() const { return m_actionButtons; }
|
||||
|
||||
@@ -47,10 +47,12 @@ void WorldSession::HandleInspectOpcode(WorldPackets::Inspect::Inspect& inspect)
|
||||
|
||||
if (GetPlayer()->CanBeGameMaster() || sWorld->getIntConfig(CONFIG_TALENTS_INSPECTING) + (GetPlayer()->GetEffectiveTeam() == player->GetEffectiveTeam()) > 1)
|
||||
{
|
||||
/*
|
||||
PlayerTalentMap const* talents = player->GetTalentMap(player->GetActiveTalentGroup());
|
||||
for (PlayerTalentMap::value_type const& v : *talents)
|
||||
if (v.second != PLAYERSPELL_REMOVED)
|
||||
inspectResult.Talents.push_back(v.first);
|
||||
*/
|
||||
|
||||
inspectResult.TalentTraits.Level = player->GetLevel();
|
||||
inspectResult.TalentTraits.ChrSpecializationID = AsUnderlyingType(player->GetPrimarySpecialization());
|
||||
|
||||
@@ -28,25 +28,11 @@
|
||||
|
||||
void WorldSession::HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet)
|
||||
{
|
||||
WorldPackets::Talent::LearnTalentFailed learnTalentFailed;
|
||||
bool anythingLearned = false;
|
||||
for (uint32 talentId : packet.Talents)
|
||||
{
|
||||
if (TalentLearnResult result = _player->LearnTalent(talentId, &learnTalentFailed.SpellID))
|
||||
{
|
||||
if (!learnTalentFailed.Reason)
|
||||
learnTalentFailed.Reason = result;
|
||||
}
|
||||
|
||||
learnTalentFailed.Talents.push_back(talentId);
|
||||
}
|
||||
else
|
||||
anythingLearned = true;
|
||||
}
|
||||
|
||||
if (learnTalentFailed.Reason)
|
||||
SendPacket(learnTalentFailed.Write());
|
||||
|
||||
if (anythingLearned)
|
||||
void WorldSession::HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& packet)
|
||||
{
|
||||
if (_player->LearnTalent(packet.TalentID, packet.RequestedRank))
|
||||
_player->SendTalentsInfoData();
|
||||
}
|
||||
|
||||
|
||||
@@ -25,120 +25,6 @@
|
||||
|
||||
void WorldSession::HandleTraitsCommitConfig(WorldPackets::Traits::TraitsCommitConfig const& traitsCommitConfig)
|
||||
{
|
||||
int32 configId = traitsCommitConfig.Config.ID;
|
||||
UF::TraitConfig const* existingConfig = _player->GetTraitConfig(configId);
|
||||
if (!existingConfig)
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_player->IsInCombat())
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_AFFECTING_COMBAT).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (_player->GetBattleground() && _player->GetBattleground()->GetStatus() == STATUS_IN_PROGRESS)
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_IN_PVP_MATCH).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
auto findEntry = [](WorldPackets::Traits::TraitConfig& config, int32 traitNodeId, int32 traitNodeEntryId) -> WorldPackets::Traits::TraitEntry*
|
||||
{
|
||||
auto entryItr = std::find_if(config.Entries.begin(), config.Entries.end(), [=](WorldPackets::Traits::TraitEntry const& traitEntry)
|
||||
{
|
||||
return traitEntry.TraitNodeID == traitNodeId && traitEntry.TraitNodeEntryID == traitNodeEntryId;
|
||||
});
|
||||
return entryItr != config.Entries.end() ? &*entryItr : nullptr;
|
||||
};
|
||||
|
||||
bool hasRemovedEntries = false;
|
||||
WorldPackets::Traits::TraitConfig newConfigState(*existingConfig);
|
||||
for (WorldPackets::Traits::TraitEntry const& newEntry : traitsCommitConfig.Config.Entries)
|
||||
{
|
||||
WorldPackets::Traits::TraitEntry* traitEntry = findEntry(newConfigState, newEntry.TraitNodeID, newEntry.TraitNodeEntryID);
|
||||
if (traitEntry && traitEntry->Rank > newEntry.Rank)
|
||||
{
|
||||
TraitNodeEntry const* traitNode = sTraitNodeStore.LookupEntry(newEntry.TraitNodeID);
|
||||
if (!traitNode)
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
TraitTreeEntry const* traitTree = sTraitTreeStore.LookupEntry(traitNode->TraitTreeID);
|
||||
if (!traitTree)
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (traitTree->GetFlags().HasFlag(TraitTreeFlag::CannotRefund))
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
TraitNodeEntryEntry const* traitNodeEntry = sTraitNodeEntryStore.LookupEntry(newEntry.TraitNodeEntryID);
|
||||
if (!traitNodeEntry)
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
TraitDefinitionEntry const* traitDefinition = sTraitDefinitionStore.LookupEntry(traitNodeEntry->TraitDefinitionID);
|
||||
if (!traitDefinition)
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, TALENT_FAILED_UNKNOWN).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (traitDefinition->SpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->SpellID))
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->SpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
if (traitDefinition->VisibleSpellID && _player->GetSpellHistory()->HasCooldown(traitDefinition->VisibleSpellID))
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, traitDefinition->VisibleSpellID, TALENT_FAILED_CANT_REMOVE_TALENT).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
hasRemovedEntries = true;
|
||||
}
|
||||
|
||||
if (traitEntry)
|
||||
{
|
||||
if (newEntry.Rank)
|
||||
traitEntry->Rank = newEntry.Rank;
|
||||
else
|
||||
newConfigState.Entries.erase(std::remove_if(newConfigState.Entries.begin(), newConfigState.Entries.end(), [&newEntry](WorldPackets::Traits::TraitEntry const& traitEntry)
|
||||
{
|
||||
return traitEntry.TraitNodeID == newEntry.TraitNodeID && traitEntry.TraitNodeEntryID == newEntry.TraitNodeEntryID;
|
||||
}), newConfigState.Entries.end());
|
||||
}
|
||||
else
|
||||
newConfigState.Entries.emplace_back() = newEntry;
|
||||
}
|
||||
|
||||
TraitMgr::LearnResult validationResult = TraitMgr::ValidateConfig(newConfigState, _player, true);
|
||||
if (validationResult != TraitMgr::LearnResult::Ok)
|
||||
{
|
||||
SendPacket(WorldPackets::Traits::TraitConfigCommitFailed(configId, 0, AsUnderlyingType(validationResult)).Write());
|
||||
return;
|
||||
}
|
||||
|
||||
bool needsCastTime = newConfigState.Type == TraitConfigType::Combat && hasRemovedEntries;
|
||||
|
||||
if (traitsCommitConfig.SavedLocalIdentifier)
|
||||
newConfigState.LocalIdentifier = traitsCommitConfig.SavedLocalIdentifier;
|
||||
else if (UF::TraitConfig const* savedConfig = _player->GetTraitConfig(traitsCommitConfig.SavedLocalIdentifier))
|
||||
newConfigState.LocalIdentifier = savedConfig->LocalIdentifier;
|
||||
|
||||
_player->UpdateTraitConfig(std::move(newConfigState), traitsCommitConfig.SavedConfigID, needsCastTime);
|
||||
}
|
||||
|
||||
void WorldSession::HandleClassTalentsRequestNewConfig(WorldPackets::Traits::ClassTalentsRequestNewConfig& classTalentsRequestNewConfig)
|
||||
|
||||
@@ -78,6 +78,12 @@ void WorldPackets::Talent::LearnTalents::Read()
|
||||
_worldPacket >> Talents[i];
|
||||
}
|
||||
|
||||
void WorldPackets::Talent::LearnTalent::Read()
|
||||
{
|
||||
_worldPacket >> TalentID;
|
||||
_worldPacket >> RequestedRank;
|
||||
}
|
||||
|
||||
WorldPacket const* WorldPackets::Talent::RespecWipeConfirm::Write()
|
||||
{
|
||||
_worldPacket << int8(RespecType);
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace WorldPackets
|
||||
struct TalentGroupInfo
|
||||
{
|
||||
int32 SpecID = 0;
|
||||
std::vector<uint16> Talents;
|
||||
std::vector<TalentInfo> Talents;
|
||||
std::vector<uint16> GlyphIDs;
|
||||
};
|
||||
|
||||
@@ -143,6 +143,17 @@ namespace WorldPackets
|
||||
int32 SpellID = 0;
|
||||
std::vector<PvPTalent> Talents;
|
||||
};
|
||||
|
||||
class LearnTalent final : public ClientPacket
|
||||
{
|
||||
public:
|
||||
LearnTalent(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_TALENT, std::move(packet)) { }
|
||||
|
||||
void Read() override;
|
||||
|
||||
int32 TalentID = 0;
|
||||
uint16 RequestedRank = 0;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -557,7 +557,7 @@ void OpcodeTable::Initialize()
|
||||
DEFINE_HANDLER(CMSG_LEARN_TALENTS, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleLearnTalentsOpcode);
|
||||
DEFINE_HANDLER(CMSG_LEARN_PREVIEW_TALENTS, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_LEARN_PREVIEW_TALENTS_PET, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_LEARN_TALENT, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_LEARN_TALENT, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLearnTalentOpcode);
|
||||
DEFINE_HANDLER(CMSG_LEAVE_GROUP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleLeaveGroupOpcode);
|
||||
DEFINE_HANDLER(CMSG_LEAVE_PET_BATTLE_QUEUE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL);
|
||||
DEFINE_HANDLER(CMSG_LFG_LIST_APPLY_TO_GROUP, STATUS_UNHANDLED, PROCESS_THREADUNSAFE, &WorldSession::Handle_NULL);
|
||||
|
||||
@@ -733,6 +733,7 @@ namespace WorldPackets
|
||||
|
||||
namespace Talent
|
||||
{
|
||||
class LearnTalent;
|
||||
class LearnTalents;
|
||||
class LearnPvpTalents;
|
||||
class ConfirmRespecWipe;
|
||||
@@ -1514,6 +1515,7 @@ class TC_GAME_API WorldSession
|
||||
|
||||
void HandleLearnPvpTalentsOpcode(WorldPackets::Talent::LearnPvpTalents& packet);
|
||||
void HandleLearnTalentsOpcode(WorldPackets::Talent::LearnTalents& packet);
|
||||
void HandleLearnTalentOpcode(WorldPackets::Talent::LearnTalent& packet);
|
||||
void HandleConfirmRespecWipeOpcode(WorldPackets::Talent::ConfirmRespecWipe& confirmRespecWipe);
|
||||
void HandleUnlearnSkillOpcode(WorldPackets::Spells::UnlearnSkill& packet);
|
||||
void HandleTradeSkillSetFavorite(WorldPackets::Spells::TradeSkillSetFavorite const& tradeSkillSetFavorite);
|
||||
|
||||
@@ -183,6 +183,7 @@ public:
|
||||
|
||||
static bool HandleLearnAllTalentsCommand(ChatHandler* handler)
|
||||
{
|
||||
/*
|
||||
Player* player = handler->GetSession()->GetPlayer();
|
||||
uint32 playerClass = player->GetClass();
|
||||
|
||||
@@ -209,6 +210,7 @@ public:
|
||||
player->SendTalentsInfoData();
|
||||
|
||||
handler->SendSysMessage(LANG_COMMAND_LEARN_CLASS_TALENTS);
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user