From 74d5984ee285f247d6b44b2bad8e695724326722 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 23:46:38 -0700 Subject: [PATCH] feat: parse arena header in MSG_PVP_LOG_DATA and show arena scoreboard Previously the arena path in handlePvpLogData consumed the packet and returned early with no data. Now the two-team header is parsed (rating change, new rating, team name), followed by the same player list and winner fields as battlegrounds. The BgScoreboardData struct gains ArenaTeamScore fields (teamName, ratingChange, newRating) populated when isArena=true. The BG scoreboard UI is updated to: - Use "Arena Score" window title for arenas - Show each team's name and rating delta at the top - Identify the winner by team name instead of faction label --- include/game/game_handler.hpp | 7 ++++++ src/game/game_handler.cpp | 32 ++++++++++++++++++++------- src/ui/game_screen.cpp | 41 ++++++++++++++++++++++++++++++----- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/include/game/game_handler.hpp b/include/game/game_handler.hpp index 413cdb70..32af0860 100644 --- a/include/game/game_handler.hpp +++ b/include/game/game_handler.hpp @@ -433,11 +433,18 @@ public: uint32_t bonusHonor = 0; std::vector> bgStats; // BG-specific fields }; + struct ArenaTeamScore { + std::string teamName; + uint32_t ratingChange = 0; // signed delta packed as uint32 + uint32_t newRating = 0; + }; struct BgScoreboardData { std::vector players; bool hasWinner = false; uint8_t winner = 0; // 0=Horde, 1=Alliance bool isArena = false; + // Arena-only fields (valid when isArena=true) + ArenaTeamScore arenaTeams[2]; // team 0 = first, team 1 = second }; void requestPvpLog(); const BgScoreboardData* getBgScoreboard() const { diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 97de0b71..7233b0ae 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -14992,12 +14992,19 @@ void GameHandler::handlePvpLogData(network::Packet& packet) { bgScoreboard_.isArena = (packet.readUInt8() != 0); if (bgScoreboard_.isArena) { - // Skip arena-specific header (two teams × (rating change uint32 + name string + 5×uint32)) - // Rather than hardcoding arena parse we skip gracefully up to playerCount - // Each arena team block: uint32 + string + uint32*5 — variable length due to string. - // Skip by scanning for the uint32 playerCount heuristically; simply consume rest. - packet.setReadPos(packet.getSize()); - return; + // WotLK 3.3.5a MSG_PVP_LOG_DATA arena header: + // two team blocks × (uint32 ratingChange + uint32 newRating + uint32 unk1 + uint32 unk2 + uint32 unk3 + CString teamName) + // After both team blocks: same player list and winner fields as battleground. + for (int t = 0; t < 2; ++t) { + if (remaining() < 20) { packet.setReadPos(packet.getSize()); return; } + bgScoreboard_.arenaTeams[t].ratingChange = packet.readUInt32(); + bgScoreboard_.arenaTeams[t].newRating = packet.readUInt32(); + packet.readUInt32(); // unk1 + packet.readUInt32(); // unk2 + packet.readUInt32(); // unk3 + bgScoreboard_.arenaTeams[t].teamName = remaining() > 0 ? packet.readString() : ""; + } + // Fall through to parse player list and winner fields below (same layout as BG) } if (remaining() < 4) return; @@ -15046,8 +15053,17 @@ void GameHandler::handlePvpLogData(network::Packet& packet) { bgScoreboard_.winner = packet.readUInt8(); } - LOG_INFO("PvP log: ", bgScoreboard_.players.size(), " players, hasWinner=", - bgScoreboard_.hasWinner, " winner=", (int)bgScoreboard_.winner); + if (bgScoreboard_.isArena) { + LOG_INFO("Arena log: ", bgScoreboard_.players.size(), " players, hasWinner=", + bgScoreboard_.hasWinner, " winner=", (int)bgScoreboard_.winner, + " team0='", bgScoreboard_.arenaTeams[0].teamName, + "' ratingChange=", (int32_t)bgScoreboard_.arenaTeams[0].ratingChange, + " team1='", bgScoreboard_.arenaTeams[1].teamName, + "' ratingChange=", (int32_t)bgScoreboard_.arenaTeams[1].ratingChange); + } else { + LOG_INFO("PvP log: ", bgScoreboard_.players.size(), " players, hasWinner=", + bgScoreboard_.hasWinner, " winner=", (int)bgScoreboard_.winner); + } } void GameHandler::handleOtherPlayerMovement(network::Packet& packet) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 4c9a0417..3a0194bc 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -20400,7 +20400,8 @@ void GameScreen::renderBgScoreboard(game::GameHandler& gameHandler) { ImGui::SetNextWindowSize(ImVec2(600, 400), ImGuiCond_FirstUseEver); ImGui::SetNextWindowPos(ImVec2(150, 100), ImGuiCond_FirstUseEver); - const char* title = "Battleground Score###BgScore"; + const char* title = data && data->isArena ? "Arena Score###BgScore" + : "Battleground Score###BgScore"; if (!ImGui::Begin(title, &showBgScoreboard_, ImGuiWindowFlags_NoCollapse)) { ImGui::End(); return; @@ -20408,16 +20409,46 @@ void GameScreen::renderBgScoreboard(game::GameHandler& gameHandler) { if (!data) { ImGui::TextDisabled("No score data yet."); - ImGui::TextDisabled("Use /score to request the scoreboard while in a battleground."); + ImGui::TextDisabled("Use /score to request the scoreboard while in a battleground or arena."); ImGui::End(); return; } + // Arena team rating banner (shown only for arenas) + if (data->isArena) { + for (int t = 0; t < 2; ++t) { + const auto& at = data->arenaTeams[t]; + if (at.teamName.empty()) continue; + int32_t ratingDelta = static_cast(at.ratingChange); + ImVec4 teamCol = (t == 0) ? ImVec4(1.0f, 0.35f, 0.35f, 1.0f) // team 0: red + : ImVec4(0.4f, 0.6f, 1.0f, 1.0f); // team 1: blue + ImGui::TextColored(teamCol, "%s", at.teamName.c_str()); + ImGui::SameLine(); + char ratingBuf[32]; + if (ratingDelta >= 0) + std::snprintf(ratingBuf, sizeof(ratingBuf), "Rating: %u (+%d)", at.newRating, ratingDelta); + else + std::snprintf(ratingBuf, sizeof(ratingBuf), "Rating: %u (%d)", at.newRating, ratingDelta); + ImGui::TextDisabled("%s", ratingBuf); + } + ImGui::Separator(); + } + // Winner banner if (data->hasWinner) { - const char* winnerStr = (data->winner == 1) ? "Alliance" : "Horde"; - ImVec4 winnerColor = (data->winner == 1) ? ImVec4(0.4f, 0.6f, 1.0f, 1.0f) - : ImVec4(1.0f, 0.35f, 0.35f, 1.0f); + const char* winnerStr; + ImVec4 winnerColor; + if (data->isArena) { + // For arenas, winner byte 0/1 refers to team index in arenaTeams[] + const auto& winTeam = data->arenaTeams[data->winner & 1]; + winnerStr = winTeam.teamName.empty() ? "Team 1" : winTeam.teamName.c_str(); + winnerColor = (data->winner == 0) ? ImVec4(1.0f, 0.35f, 0.35f, 1.0f) + : ImVec4(0.4f, 0.6f, 1.0f, 1.0f); + } else { + winnerStr = (data->winner == 1) ? "Alliance" : "Horde"; + winnerColor = (data->winner == 1) ? ImVec4(0.4f, 0.6f, 1.0f, 1.0f) + : ImVec4(1.0f, 0.35f, 0.35f, 1.0f); + } float textW = ImGui::CalcTextSize(winnerStr).x + ImGui::CalcTextSize(" Victory!").x; ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - textW) * 0.5f); ImGui::TextColored(winnerColor, "%s", winnerStr);