feat: parse MSG_INSPECT_ARENA_TEAMS and display in inspect window

Implements MSG_INSPECT_ARENA_TEAMS (WotLK): reads the inspected player's
arena team data (2v2/3v3/5v5 bracket, team name, personal rating,
week/season W-L) and stores it in InspectResult.arenaTeams.

The inspect window now shows an "Arena Teams" section below the gear list
when arena team data is available, displaying bracket, team name, rating,
and win/loss record.

Also implement SMSG_COMPLAIN_RESULT with user-visible feedback for
report-player results.
This commit is contained in:
Kelsi 2026-03-12 21:27:02 -07:00
parent a1edddd1f0
commit 758ca76bd3
3 changed files with 76 additions and 4 deletions

View file

@ -339,6 +339,16 @@ public:
// Inspection // Inspection
void inspectTarget(); void inspectTarget();
struct InspectArenaTeam {
uint32_t teamId = 0;
uint8_t type = 0; // bracket size: 2, 3, or 5
uint32_t weekGames = 0;
uint32_t weekWins = 0;
uint32_t seasonGames = 0;
uint32_t seasonWins = 0;
std::string name;
uint32_t personalRating = 0;
};
struct InspectResult { struct InspectResult {
uint64_t guid = 0; uint64_t guid = 0;
std::string playerName; std::string playerName;
@ -348,6 +358,7 @@ public:
uint8_t activeTalentGroup = 0; uint8_t activeTalentGroup = 0;
std::array<uint32_t, 19> itemEntries{}; // 0=head…18=ranged std::array<uint32_t, 19> itemEntries{}; // 0=head…18=ranged
std::array<uint16_t, 19> enchantIds{}; // permanent enchant per slot (0 = none) std::array<uint16_t, 19> enchantIds{}; // permanent enchant per slot (0 = none)
std::vector<InspectArenaTeam> arenaTeams; // from MSG_INSPECT_ARENA_TEAMS (WotLK)
}; };
const InspectResult* getInspectResult() const { const InspectResult* getInspectResult() const {
return inspectResult_.guid ? &inspectResult_ : nullptr; return inspectResult_.guid ? &inspectResult_ : nullptr;

View file

@ -5166,9 +5166,37 @@ void GameHandler::handlePacket(network::Packet& packet) {
case Opcode::MSG_PVP_LOG_DATA: case Opcode::MSG_PVP_LOG_DATA:
handlePvpLogData(packet); handlePvpLogData(packet);
break; break;
case Opcode::MSG_INSPECT_ARENA_TEAMS: case Opcode::MSG_INSPECT_ARENA_TEAMS: {
LOG_INFO("Received MSG_INSPECT_ARENA_TEAMS"); // WotLK: uint64 playerGuid + uint8 teamCount + per-team fields
if (packet.getSize() - packet.getReadPos() < 9) {
packet.setReadPos(packet.getSize());
break;
}
uint64_t inspGuid = packet.readUInt64();
uint8_t teamCount = packet.readUInt8();
if (teamCount > 3) teamCount = 3; // 2v2, 3v3, 5v5
if (inspGuid == inspectResult_.guid || inspectResult_.guid == 0) {
inspectResult_.guid = inspGuid;
inspectResult_.arenaTeams.clear();
for (uint8_t t = 0; t < teamCount; ++t) {
if (packet.getSize() - packet.getReadPos() < 21) break;
InspectArenaTeam team;
team.teamId = packet.readUInt32();
team.type = packet.readUInt8();
team.weekGames = packet.readUInt32();
team.weekWins = packet.readUInt32();
team.seasonGames = packet.readUInt32();
team.seasonWins = packet.readUInt32();
team.name = packet.readString();
if (packet.getSize() - packet.getReadPos() < 4) break;
team.personalRating = packet.readUInt32();
inspectResult_.arenaTeams.push_back(std::move(team));
}
}
LOG_DEBUG("MSG_INSPECT_ARENA_TEAMS: guid=0x", std::hex, inspGuid, std::dec,
" teams=", (int)teamCount);
break; break;
}
case Opcode::MSG_TALENT_WIPE_CONFIRM: { case Opcode::MSG_TALENT_WIPE_CONFIRM: {
// Server sends: uint64 npcGuid + uint32 cost // Server sends: uint64 npcGuid + uint32 cost
// Client must respond with the same opcode containing uint64 npcGuid to confirm. // Client must respond with the same opcode containing uint64 npcGuid to confirm.
@ -5869,10 +5897,21 @@ void GameHandler::handlePacket(network::Packet& packet) {
LOG_DEBUG("SMSG_ITEM_ENCHANT_TIME_UPDATE: slot=", enchSlot, " dur=", durationSec, "s"); LOG_DEBUG("SMSG_ITEM_ENCHANT_TIME_UPDATE: slot=", enchSlot, " dur=", durationSec, "s");
break; break;
} }
case Opcode::SMSG_COMPLAIN_RESULT: case Opcode::SMSG_COMPLAIN_RESULT: {
// uint8 result: 0=success, 1=failed, 2=disabled
if (packet.getSize() - packet.getReadPos() >= 1) {
uint8_t result = packet.readUInt8();
if (result == 0)
addSystemChatMessage("Your complaint has been submitted.");
else if (result == 2)
addUIError("Report a Player is currently disabled.");
}
packet.setReadPos(packet.getSize());
break;
}
case Opcode::SMSG_ITEM_REFUND_INFO_RESPONSE: case Opcode::SMSG_ITEM_REFUND_INFO_RESPONSE:
case Opcode::SMSG_LOOT_LIST: case Opcode::SMSG_LOOT_LIST:
// Consume — not yet processed // Consume silently — informational, no UI action needed
packet.setReadPos(packet.getSize()); packet.setReadPos(packet.getSize());
break; break;

View file

@ -20697,6 +20697,28 @@ void GameScreen::renderInspectWindow(game::GameHandler& gameHandler) {
ImGui::EndChild(); ImGui::EndChild();
} }
// Arena teams (WotLK — from MSG_INSPECT_ARENA_TEAMS)
if (!result->arenaTeams.empty()) {
ImGui::Separator();
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), "Arena Teams");
ImGui::Spacing();
for (const auto& team : result->arenaTeams) {
const char* bracket = (team.type == 2) ? "2v2"
: (team.type == 3) ? "3v3"
: (team.type == 5) ? "5v5" : "?v?";
ImGui::TextColored(ImVec4(0.9f, 0.9f, 0.9f, 1.0f),
"[%s] %s", bracket, team.name.c_str());
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.4f, 0.85f, 1.0f, 1.0f),
" Rating: %u", team.personalRating);
if (team.weekGames > 0 || team.seasonGames > 0) {
ImGui::TextDisabled(" Week: %u/%u Season: %u/%u",
team.weekWins, team.weekGames,
team.seasonWins, team.seasonGames);
}
}
}
ImGui::End(); ImGui::End();
} }