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.
This commit is contained in:
Kelsi 2026-03-12 21:54:48 -07:00
parent 793c2b5611
commit f8f57411f2
2 changed files with 90 additions and 1 deletions

View file

@ -405,11 +405,22 @@ public:
std::chrono::steady_clock::time_point inviteReceivedTime{}; 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<uint32_t> instanceIds;
};
// Battleground // Battleground
bool hasPendingBgInvite() const; bool hasPendingBgInvite() const;
void acceptBattlefield(uint32_t queueSlot = 0xFFFFFFFF); void acceptBattlefield(uint32_t queueSlot = 0xFFFFFFFF);
void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF); void declineBattlefield(uint32_t queueSlot = 0xFFFFFFFF);
const std::array<BgQueueSlot, 3>& getBgQueues() const { return bgQueues_; } const std::array<BgQueueSlot, 3>& getBgQueues() const { return bgQueues_; }
const std::vector<AvailableBgInfo>& getAvailableBgs() const { return availableBgs_; }
// BG scoreboard (MSG_PVP_LOG_DATA) // BG scoreboard (MSG_PVP_LOG_DATA)
struct BgPlayerScore { struct BgPlayerScore {
@ -2475,6 +2486,10 @@ private:
// ---- Battleground queue state ---- // ---- Battleground queue state ----
std::array<BgQueueSlot, 3> bgQueues_{}; std::array<BgQueueSlot, 3> bgQueues_{};
// ---- Available battleground list (SMSG_BATTLEFIELD_LIST) ----
std::vector<AvailableBgInfo> availableBgs_;
void handleBattlefieldList(network::Packet& packet);
// Instance difficulty // Instance difficulty
uint32_t instanceDifficulty_ = 0; uint32_t instanceDifficulty_ = 0;
bool instanceIsHeroic_ = false; bool instanceIsHeroic_ = false;

View file

@ -5001,7 +5001,7 @@ void GameHandler::handlePacket(network::Packet& packet) {
handleBattlefieldStatus(packet); handleBattlefieldStatus(packet);
break; break;
case Opcode::SMSG_BATTLEFIELD_LIST: case Opcode::SMSG_BATTLEFIELD_LIST:
LOG_INFO("Received SMSG_BATTLEFIELD_LIST"); handleBattlefieldList(packet);
break; break;
case Opcode::SMSG_BATTLEFIELD_PORT_DENIED: case Opcode::SMSG_BATTLEFIELD_PORT_DENIED:
addSystemChatMessage("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<uint32_t, const char*> 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) { void GameHandler::declineBattlefield(uint32_t queueSlot) {
if (state != WorldState::IN_WORLD) return; if (state != WorldState::IN_WORLD) return;
if (!socket) return; if (!socket) return;