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);