From 52c1fed6ab622b5562cda0f2461223448bb32159 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 22:49:23 -0700 Subject: [PATCH] game: implement dual-spec switch via CMSG_SET_ACTIVE_TALENT_GROUP (0x4C3) switchTalentSpec() was only updating local state without notifying the server, leaving the server out of sync with the client's active talent group. Now sends CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE (WotLK wire opcode 0x4C3) with the target group index (0=primary, 1=secondary), prompting the server to apply the spec swap and respond with a fresh SMSG_TALENTS_INFO for the newly active group. Also adds ActivateTalentGroupPacket::build() to world_packets for the packet construction. --- include/game/world_packets.hpp | 7 +++++++ src/game/game_handler.cpp | 13 ++++++++++--- src/game/world_packets.cpp | 8 ++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index 42f64bc9..e4a796e1 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -2266,6 +2266,13 @@ public: static network::Packet build(bool accept); }; +/** CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE (0x4C3) — switch dual-spec talent group */ +class ActivateTalentGroupPacket { +public: + /** @param group 0 = primary spec, 1 = secondary spec */ + static network::Packet build(uint32_t group); +}; + // ============================================================ // Taxi / Flight Paths // ============================================================ diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index ec1709d3..52ad0e29 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -12832,9 +12832,16 @@ void GameHandler::switchTalentSpec(uint8_t newSpec) { return; } - // For now, just switch locally. In a real implementation, we'd send - // MSG_TALENT_WIPE_CONFIRM to the server to trigger a spec switch. - // The server would respond with new SMSG_TALENTS_INFO for the new spec. + // Send CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE (0x4C3) to the server. + // The server will validate the swap, apply the new spec's spells/auras, + // and respond with SMSG_TALENTS_INFO for the newly active group. + // We optimistically update the local state so the UI reflects the change + // immediately; the server response will correct us if needed. + if (state == WorldState::IN_WORLD && socket) { + auto pkt = ActivateTalentGroupPacket::build(static_cast(newSpec)); + socket->send(pkt); + LOG_INFO("Sent CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE: group=", (int)newSpec); + } activeTalentSpec_ = newSpec; LOG_INFO("Switched to talent spec ", (int)newSpec, diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index c83563f0..b71c2834 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -3935,6 +3935,14 @@ network::Packet TalentWipeConfirmPacket::build(bool accept) { return packet; } +network::Packet ActivateTalentGroupPacket::build(uint32_t group) { + // CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE (0x4C3 in WotLK 3.3.5a) + // Payload: uint32 group (0 = primary, 1 = secondary) + network::Packet packet(wireOpcode(Opcode::CMSG_SET_ACTIVE_TALENT_GROUP_OBSOLETE)); + packet.writeUInt32(group); + return packet; +} + // ============================================================ // Death/Respawn // ============================================================