From c092c6ad00bfa3384e04ea2128761491e4d5925e Mon Sep 17 00:00:00 2001 From: Ovahlord Date: Mon, 16 Jun 2025 20:32:28 +0200 Subject: [PATCH] Core/Player: allow players to learn a primary specialization without having to select a talent * converted CMSG_LEARN_PREVIEW_TALENTS to packet class --- src/server/game/Entities/Player/Player.cpp | 64 +++++++++++-------- src/server/game/Entities/Player/Player.h | 1 + src/server/game/Handlers/SkillHandler.cpp | 44 ++++--------- src/server/game/Server/Packets/AllPackets.h | 1 + .../game/Server/Packets/TalentPackets.cpp | 41 ++++++++++++ .../game/Server/Packets/TalentPackets.h | 49 ++++++++++++++ src/server/game/Server/WorldSession.h | 6 +- 7 files changed, 147 insertions(+), 59 deletions(-) create mode 100644 src/server/game/Server/Packets/TalentPackets.cpp create mode 100644 src/server/game/Server/Packets/TalentPackets.h diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index c2652d90f2..f7d391dced 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -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 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 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 diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 7ff17d61ae..68b16e97b8 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -1583,6 +1583,7 @@ class TC_GAME_API Player : public Unit, public GridObject 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; diff --git a/src/server/game/Handlers/SkillHandler.cpp b/src/server/game/Handlers/SkillHandler.cpp index 43b1e0aafa..c55d537efb 100644 --- a/src/server/game/Handlers/SkillHandler.cpp +++ b/src/server/game/Handlers/SkillHandler.cpp @@ -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(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) diff --git a/src/server/game/Server/Packets/AllPackets.h b/src/server/game/Server/Packets/AllPackets.h index af6b2ae3bc..2b9736fe63 100644 --- a/src/server/game/Server/Packets/AllPackets.h +++ b/src/server/game/Server/Packets/AllPackets.h @@ -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" diff --git a/src/server/game/Server/Packets/TalentPackets.cpp b/src/server/game/Server/Packets/TalentPackets.cpp new file mode 100644 index 0000000000..a87ce3a4cf --- /dev/null +++ b/src/server/game/Server/Packets/TalentPackets.cpp @@ -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 . + */ + +#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()); + + for (TalentInfo& talent : Talents) + _worldPacket >> talent; +} diff --git a/src/server/game/Server/Packets/TalentPackets.h b/src/server/game/Server/Packets/TalentPackets.h new file mode 100644 index 0000000000..dfe34395cd --- /dev/null +++ b/src/server/game/Server/Packets/TalentPackets.h @@ -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 . + */ + +#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 Talents; + }; + } +} + +#endif // TalentPackets_h__ diff --git a/src/server/game/Server/WorldSession.h b/src/server/game/Server/WorldSession.h index ea100648d8..dcea02a615 100644 --- a/src/server/game/Server/WorldSession.h +++ b/src/server/game/Server/WorldSession.h @@ -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);