Core/Player: allow players to learn a primary specialization without having to select a talent

* converted CMSG_LEARN_PREVIEW_TALENTS to packet class
This commit is contained in:
Ovahlord committed 2025-06-16 20:32:28 +02:00
1 parent 53df12ae73
commit c092c6ad00
7 files changed
+147 -59

No files matched your search

+39 -25
View File
@@ -25474,21 +25474,18 @@ void Player::CompletedAchievement(AchievementEntry const* entry)
bool Player::LearnTalent(uint32 talentId, uint32 talentRank)
{
uint32 CurTalentPoints = GetFreeTalentPoints();
if (CurTalentPoints == 0)
if (talentId == 0 || talentRank >= MAX_TALENT_RANK)
return false;
if (talentRank >= MAX_TALENT_RANK)
uint32 freeTalentPoints = GetFreeTalentPoints();
if (freeTalentPoints == 0)
return false;
TalentEntry const* talentInfo = sTalentStore.LookupEntry(talentId);
if (!talentInfo)
return false;
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentInfo->TabID);
if (!talentTabInfo)
return false;
@@ -25512,7 +25509,7 @@ bool Player::LearnTalent(uint32 talentId, uint32 talentRank)
return false;
// check if we have enough talent points
if (CurTalentPoints < (talentRank - curtalent_maxrank + 1))
if (freeTalentPoints < (talentRank - curtalent_maxrank + 1))
return false;
// Check if it requires another talent
@@ -25587,24 +25584,8 @@ bool Player::LearnTalent(uint32 talentId, uint32 talentRank)
TC_LOG_DEBUG("entities.player", "TalentID: %u Rank: %u Spell: %u Spec: %u\n", talentId, talentRank, spellid, GetActiveSpec());
// set talent tree for player
if (!GetPrimaryTalentTree(GetActiveSpec()))
{
SetPrimaryTalentTree(GetActiveSpec(), talentInfo->TabID);
std::vector<uint32> const* specSpells = sDBCManager.GetTalentTreePrimarySpells(talentInfo->TabID);
if (specSpells)
for (size_t i = 0; i < specSpells->size(); ++i)
LearnSpell(specSpells->at(i), false);
if (CanUseMastery())
for (uint32 i = 0; i < MAX_MASTERY_SPELLS; ++i)
if (SpellInfo const* masterySpell = sSpellMgr->GetSpellInfo(talentTabInfo->MasterySpellID[i]))
if (masterySpell->IsPassive() && HandlePassiveSpellLearn(masterySpell))
CastSpell(this, masterySpell->Id, true);
}
// update free talent points
SetFreeTalentPoints(CurTalentPoints - (talentRank - curtalent_maxrank + 1));
SetFreeTalentPoints(freeTalentPoints - (talentRank - curtalent_maxrank + 1));
return true;
}
@@ -25743,6 +25724,38 @@ void Player::LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRa
pet->SetFreeTalentPoints(CurTalentPoints - (talentRank - curtalent_maxrank + 1));
}
bool Player::LearnPrimaryTalentSpecialization(uint8 talentTabIndex)
{
if (talentTabIndex >= MAX_TALENT_TABS)
return false;
if (GetPrimaryTalentTree(GetActiveSpec()) != 0)
return false;
uint32 const* talentTabs = sDBCManager.GetTalentTabPages(getClass());
if (!talentTabs)
return false;
TalentTabEntry const* talentTabInfo = sTalentTabStore.LookupEntry(talentTabs[talentTabIndex]);
if (!talentTabInfo)
return false;
SetPrimaryTalentTree(GetActiveSpec(), talentTabInfo->ID);
// Learn specialization spells and mastery
if (std::vector<uint32> const* specSpells = sDBCManager.GetTalentTreePrimarySpells(talentTabInfo->ID))
for (uint32 spellId : *specSpells)
LearnSpell(spellId, false);
if (CanUseMastery())
for (uint8 i = 0; i < MAX_MASTERY_SPELLS; ++i)
if (SpellInfo const* masterySpell = sSpellMgr->GetSpellInfo(talentTabInfo->MasterySpellID[i]))
if (masterySpell->IsPassive() && HandlePassiveSpellLearn(masterySpell))
CastSpell(this, masterySpell->Id, true);
return true;
}
void Player::AddKnownCurrency(uint32 itemId)
{
if (CurrencyTypesEntry const* ctEntry = sCurrencyTypesStore.LookupEntry(itemId))
@@ -27793,7 +27806,8 @@ uint32 Player::GetPrimaryTalentTree(uint8 spec) const
void Player::SetPrimaryTalentTree(uint8 spec, uint32 tree)
{
_talentMgr->SpecInfo[spec].TalentTree = tree; UpdateArmorSpecialization();
_talentMgr->SpecInfo[spec].TalentTree = tree;
UpdateArmorSpecialization();
}
uint8 Player::GetActiveSpec() const
+1
View File
@@ -1583,6 +1583,7 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
void SendTalentsInfoData(bool pet);
bool LearnTalent(uint32 talentId, uint32 talentRank);
void LearnPetTalent(ObjectGuid petGuid, uint32 talentId, uint32 talentRank);
bool LearnPrimaryTalentSpecialization(uint8 talentTabIndex);
bool AddTalent(uint32 spellId, uint8 spec, bool learning);
bool HasTalent(uint32 spell_id, uint8 spec) const;
uint32 CalculateTalentsPoints() const;
+11 -33
View File
@@ -22,6 +22,7 @@
#include "ObjectAccessor.h"
#include "Pet.h"
#include "Player.h"
#include "TalentPackets.h"
#include "WorldPacket.h"
void WorldSession::HandleLearnTalentOpcode(WorldPacket& recvData)
@@ -33,48 +34,25 @@ void WorldSession::HandleLearnTalentOpcode(WorldPacket& recvData)
_player->SendTalentsInfoData(false);
}
void WorldSession::HandleLearnPreviewTalents(WorldPacket& recvPacket)
void WorldSession::HandleLearnPreviewTalents(WorldPackets::Talent::LearnPreviewTalents& packet)
{
TC_LOG_DEBUG("network", "CMSG_LEARN_PREVIEW_TALENTS");
int32 tabPage;
uint32 talentsCount;
recvPacket >> tabPage; // talent tree
// Player is attempting to set his primary specialization for his active spec.
// This field is doing the same as CMSG_SET_PRIMARY_TALENT_TREE
if (packet.TalentTab >= 0)
if (!_player->LearnPrimaryTalentSpecialization(packet.TalentTab))
return;
// prevent cheating (selecting new tree with points already in another)
if (tabPage >= 0) // -1 if player already has specialization
{
if (packet.TalentTab >= 0) // -1 if player already has specialization
if (TalentTabEntry const* talentTabEntry = sTalentTabStore.LookupEntry(_player->GetPrimaryTalentTree(_player->GetActiveSpec())))
{
if (talentTabEntry->OrderIndex != uint32(tabPage))
{
recvPacket.rfinish();
if (talentTabEntry->OrderIndex != static_cast<uint32>(packet.TalentTab))
return;
}
}
}
recvPacket >> talentsCount;
uint32 talentId, talentRank;
// Client has max 21 talents for tree for 3 trees, rounded up : 70
uint32 const MaxTalentsCount = 70;
for (uint32 i = 0; i < talentsCount && i < MaxTalentsCount; ++i)
{
recvPacket >> talentId >> talentRank;
if (!_player->LearnTalent(talentId, talentRank))
{
recvPacket.rfinish();
for (WorldPackets::Talent::TalentInfo const& talent : packet.Talents)
if (!_player->LearnTalent(talent.TalentID, talent.Rank))
break;
}
}
_player->SendTalentsInfoData(false);
recvPacket.rfinish();
}
void WorldSession::HandleTalentWipeConfirmOpcode(WorldPacket& recvData)
@@ -42,6 +42,7 @@
#include "ReforgePackets.h"
#include "SpellPackets.h"
#include "SystemPackets.h"
#include "TalentPackets.h"
#include "TicketPackets.h"
#include "TotemPackets.h"
#include "TradePackets.h"
@@ -0,0 +1,41 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "TalentPackets.h"
ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Talent::TalentInfo const& talent)
{
data << uint32(talent.TalentID);
data << uint32(talent.Rank);
return data;
}
ByteBuffer& operator>>(ByteBuffer& data, WorldPackets::Talent::TalentInfo& talent)
{
data >> talent.TalentID;
data >> talent.Rank;
return data;
}
void WorldPackets::Talent::LearnPreviewTalents::Read()
{
_worldPacket >> TalentTab;
Talents.resize(_worldPacket.read<uint32>());
for (TalentInfo& talent : Talents)
_worldPacket >> talent;
}
@@ -0,0 +1,49 @@
/*
* This file is part of the TrinityCore Project. See AUTHORS file for Copyright information
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TalentPackets_h__
#define TalentPackets_h__
#include "Packet.h"
#include "DBCEnums.h"
#include "ObjectGuid.h"
#include "PacketUtilities.h"
namespace WorldPackets
{
namespace Talent
{
struct TalentInfo
{
uint32 TalentID = 0;
uint32 Rank = 0;
};
class LearnPreviewTalents final : public ClientPacket
{
public:
LearnPreviewTalents(WorldPacket&& packet) : ClientPacket(CMSG_LEARN_PREVIEW_TALENTS, std::move(packet)) { }
void Read() override;
int32 TalentTab = 0;
Array<TalentInfo, 70> Talents;
};
}
}
#endif // TalentPackets_h__
+5 -1
View File
@@ -235,6 +235,10 @@ namespace WorldPackets
class UpdateMissileTrajectory;
}
namespace Talent
{
class LearnPreviewTalents;
}
namespace Ticket
{
class Complaint;
@@ -988,7 +992,7 @@ class TC_GAME_API WorldSession
void HandleCancelAutoRepeatSpellOpcode(WorldPacket& recvPacket);
void HandleLearnTalentOpcode(WorldPacket& recvPacket);
void HandleLearnPreviewTalents(WorldPacket& recvPacket);
void HandleLearnPreviewTalents(WorldPackets::Talent::LearnPreviewTalents& packet);
void HandleTalentWipeConfirmOpcode(WorldPacket& recvPacket);
void HandleUnlearnSkillOpcode(WorldPacket& recvPacket);