From 4272491d56fda1c80ea005b987d382eb809b409c Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 13 Mar 2026 04:25:05 -0700 Subject: [PATCH] feat: send CMSG_SET_ACTION_BUTTON to server when action bar slot changes Action bar changes (dragging spells/items) were only saved locally. Now notifies the server via CMSG_SET_ACTION_BUTTON so the layout persists across relogs. Supports Classic (5-byte) and TBC/WotLK (packed uint32) wire formats. --- include/game/world_packets.hpp | 15 ++++++++++++++ src/game/game_handler.cpp | 10 ++++++++++ src/game/world_packets.cpp | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) diff --git a/include/game/world_packets.hpp b/include/game/world_packets.hpp index e5c7e63c..7e0d9a41 100644 --- a/include/game/world_packets.hpp +++ b/include/game/world_packets.hpp @@ -947,6 +947,21 @@ public: static network::Packet build(uint8_t state); }; +// ============================================================ +// Action Bar +// ============================================================ + +/** CMSG_SET_ACTION_BUTTON packet builder */ +class SetActionButtonPacket { +public: + // button: 0-based slot index + // type: ActionBarSlot::Type (SPELL=0, ITEM=1, MACRO=2, EMPTY=0) + // id: spellId, itemId, or macroId (0 to clear) + // isClassic: true for Vanilla/Turtle format (5-byte payload), + // false for TBC/WotLK (5-byte packed uint32) + static network::Packet build(uint8_t button, uint8_t type, uint32_t id, bool isClassic); +}; + // ============================================================ // Display Toggles // ============================================================ diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 90fa6a12..3046c4c6 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -16101,6 +16101,16 @@ void GameHandler::setActionBarSlot(int slot, ActionBarSlot::Type type, uint32_t queryItemInfo(id, 0); } saveCharacterConfig(); + // Notify the server so the action bar persists across relogs. + if (state == WorldState::IN_WORLD && socket) { + const bool classic = isClassicLikeExpansion(); + auto pkt = SetActionButtonPacket::build( + static_cast(slot), + static_cast(type), + id, + classic); + socket->send(pkt); + } } float GameHandler::getSpellCooldown(uint32_t spellId) const { diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 14dc7a20..090ead75 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -1905,6 +1905,42 @@ network::Packet StandStateChangePacket::build(uint8_t state) { return packet; } +// ============================================================ +// Action Bar +// ============================================================ + +network::Packet SetActionButtonPacket::build(uint8_t button, uint8_t type, uint32_t id, bool isClassic) { + // Classic/Turtle (1.12): uint8 button + uint16 id + uint8 type + uint8 misc(0) + // type encoding: 0=spell, 1=item, 64=macro + // TBC/WotLK: uint8 button + uint32 packed (type<<24 | id) + // type encoding: 0x00=spell, 0x80=item, 0x40=macro + // packed=0 means clear the slot + network::Packet packet(wireOpcode(Opcode::CMSG_SET_ACTION_BUTTON)); + packet.writeUInt8(button); + if (isClassic) { + // Classic: 16-bit id, 8-bit type code, 8-bit misc + // Map ActionBarSlot::Type (0=EMPTY,1=SPELL,2=ITEM,3=MACRO) → classic type byte + uint8_t classicType = 0; // 0 = spell + if (type == 2 /* ITEM */) classicType = 1; + if (type == 3 /* MACRO */) classicType = 64; + packet.writeUInt16(static_cast(id)); + packet.writeUInt8(classicType); + packet.writeUInt8(0); // misc + LOG_DEBUG("Built CMSG_SET_ACTION_BUTTON (Classic): button=", (int)button, + " id=", id, " type=", (int)classicType); + } else { + // TBC/WotLK: type in bits 24–31, id in bits 0–23; packed=0 clears slot + uint8_t packedType = 0x00; // spell + if (type == 2 /* ITEM */) packedType = 0x80; + if (type == 3 /* MACRO */) packedType = 0x40; + uint32_t packed = (id == 0) ? 0 : (static_cast(packedType) << 24) | (id & 0x00FFFFFF); + packet.writeUInt32(packed); + LOG_DEBUG("Built CMSG_SET_ACTION_BUTTON (TBC/WotLK): button=", (int)button, + " packed=0x", std::hex, packed, std::dec); + } + return packet; +} + // ============================================================ // Display Toggles // ============================================================