From f8f57411f254e89f7bb3b5131e523317349081e2 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 21:54:48 -0700 Subject: [PATCH] feat: implement SMSG_BATTLEFIELD_LIST handler Parse the battleground availability list sent by the server when the player opens the BG finder. Handles all three expansion wire formats: - Classic: bgTypeId + isRegistered + count + instanceIds - TBC: adds isHoliday byte - WotLK: adds minLevel/maxLevel for bracket display Stores results in availableBgs_ (public via getAvailableBgs()) so the UI can show available battlegrounds and running instance counts without an additional server round-trip. --- include/game/game_handler.hpp | 15 +++++++ src/game/game_handler.cpp | 76 ++++++++++++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 42403a32..4a1ad26a 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -405,11 +405,22 @@ public: std::chrono::steady_clock::time_point inviteReceivedTime{}; }; + // Available BG list (populated by SMSG_BATTLEFIELD_LIST) + struct AvailableBgInfo { + uint32_t bgTypeId = 0; + bool isRegistered = false; + bool isHoliday = false; + uint32_t minLevel = 0; + uint32_t maxLevel = 0; + std::vector instanceIds; + }; + // Battleground bool hasPendingBgInvite() const; void acceptBattlefield(uint32_t queueSlot = 0xFFFFFFFF); void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF); const std::array& getBgQueues() const { return bgQueues_; } + const std::vector& getAvailableBgs() const { return availableBgs_; } // BG scoreboard (MSG_PVP_LOG_DATA) struct BgPlayerScore { @@ -2475,6 +2486,10 @@ private: // ---- Battleground queue state ---- std::array bgQueues_{}; + // ---- Available battleground list (SMSG_BATTLEFIELD_LIST) ---- + std::vector availableBgs_; + void handleBattlefieldList(network::Packet& packet); + // Instance difficulty uint32_t instanceDifficulty_ = 0; bool instanceIsHeroic_ = false; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 17b39b50..aa834bfe 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -5001,7 +5001,7 @@ void GameHandler::handlePacket(network::Packet& packet) { handleBattlefieldStatus(packet); break; case Opcode::SMSG_BATTLEFIELD_LIST: - LOG_INFO("Received SMSG_BATTLEFIELD_LIST"); + handleBattlefieldList(packet); break; case Opcode::SMSG_BATTLEFIELD_PORT_DENIED: addSystemChatMessage("Battlefield port denied."); @@ -13220,6 +13220,80 @@ void GameHandler::handleBattlefieldStatus(network::Packet& packet) { } } +void GameHandler::handleBattlefieldList(network::Packet& packet) { + // SMSG_BATTLEFIELD_LIST wire format by expansion: + // + // Classic 1.12 (vmangos/cmangos): + // bgTypeId(4) isRegistered(1) count(4) [instanceId(4)...] + // + // TBC 2.4.3: + // bgTypeId(4) isRegistered(1) isHoliday(1) count(4) [instanceId(4)...] + // + // WotLK 3.3.5a: + // bgTypeId(4) isRegistered(1) isHoliday(1) minLevel(4) maxLevel(4) count(4) [instanceId(4)...] + + if (packet.getSize() - packet.getReadPos() < 5) return; + + AvailableBgInfo info; + info.bgTypeId = packet.readUInt32(); + info.isRegistered = packet.readUInt8() != 0; + + const bool isWotlk = isActiveExpansion("wotlk"); + const bool isTbc = isActiveExpansion("tbc"); + + if (isTbc || isWotlk) { + if (packet.getSize() - packet.getReadPos() < 1) return; + info.isHoliday = packet.readUInt8() != 0; + } + + if (isWotlk) { + if (packet.getSize() - packet.getReadPos() < 8) return; + info.minLevel = packet.readUInt32(); + info.maxLevel = packet.readUInt32(); + } + + if (packet.getSize() - packet.getReadPos() < 4) return; + uint32_t count = packet.readUInt32(); + + // Sanity cap to avoid OOM from malformed packets + constexpr uint32_t kMaxInstances = 256; + count = std::min(count, kMaxInstances); + info.instanceIds.reserve(count); + + for (uint32_t i = 0; i < count; ++i) { + if (packet.getSize() - packet.getReadPos() < 4) break; + info.instanceIds.push_back(packet.readUInt32()); + } + + // Update or append the entry for this BG type + bool updated = false; + for (auto& existing : availableBgs_) { + if (existing.bgTypeId == info.bgTypeId) { + existing = std::move(info); + updated = true; + break; + } + } + if (!updated) { + availableBgs_.push_back(std::move(info)); + } + + const auto& stored = availableBgs_.back(); + static const std::unordered_map kBgNames = { + {1, "Alterac Valley"}, {2, "Warsong Gulch"}, {3, "Arathi Basin"}, + {4, "Nagrand Arena"}, {5, "Blade's Edge Arena"}, {6, "All Arenas"}, + {7, "Eye of the Storm"}, {8, "Ruins of Lordaeron"}, + {9, "Strand of the Ancients"}, {10, "Dalaran Sewers"}, + {11, "The Ring of Valor"}, {30, "Isle of Conquest"}, + }; + auto nameIt = kBgNames.find(stored.bgTypeId); + const char* bgName = (nameIt != kBgNames.end()) ? nameIt->second : "Unknown Battleground"; + + LOG_INFO("SMSG_BATTLEFIELD_LIST: ", bgName, " bgType=", stored.bgTypeId, + " registered=", stored.isRegistered ? "yes" : "no", + " instances=", stored.instanceIds.size()); +} + void GameHandler::declineBattlefield(uint32_t queueSlot) { if (state != WorldState::IN_WORLD) return; if (!socket) return;