feat: parse MSG_BATTLEGROUND_PLAYER_POSITIONS and show flag carriers on minimap

Replaces the silent consume with full packet parsing: reads two lists of
(guid, x, y) positions (typically ally and horde flag carriers) and stores
them in bgPlayerPositions_. Renders each as a colored diamond on the minimap
(blue=group0, red=group1) with a "Flag carrier" tooltip showing the player's
name when available.
This commit is contained in:
Kelsi 2026-03-17 20:54:59 -07:00
parent 48cb7df4b4
commit 113be66314
3 changed files with 76 additions and 3 deletions

View file

@ -497,6 +497,15 @@ public:
return bgScoreboard_.players.empty() ? nullptr : &bgScoreboard_; return bgScoreboard_.players.empty() ? nullptr : &bgScoreboard_;
} }
// BG flag carrier / important player positions (MSG_BATTLEGROUND_PLAYER_POSITIONS)
struct BgPlayerPosition {
uint64_t guid = 0;
float wowX = 0.0f; // canonical WoW X (north)
float wowY = 0.0f; // canonical WoW Y (west)
int group = 0; // 0 = first list (usually ally flag carriers), 1 = second list
};
const std::vector<BgPlayerPosition>& getBgPlayerPositions() const { return bgPlayerPositions_; }
// Network latency (milliseconds, updated each PONG response) // Network latency (milliseconds, updated each PONG response)
uint32_t getLatencyMs() const { return lastLatency; } uint32_t getLatencyMs() const { return lastLatency; }
@ -2780,6 +2789,9 @@ private:
// BG scoreboard (MSG_PVP_LOG_DATA) // BG scoreboard (MSG_PVP_LOG_DATA)
BgScoreboardData bgScoreboard_; BgScoreboardData bgScoreboard_;
// BG flag carrier / player positions (MSG_BATTLEGROUND_PLAYER_POSITIONS)
std::vector<BgPlayerPosition> bgPlayerPositions_;
// Instance encounter boss units (slots 0-4 from SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT) // Instance encounter boss units (slots 0-4 from SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT)
std::array<uint64_t, kMaxEncounterSlots> encounterUnitGuids_ = {}; // 0 = empty slot std::array<uint64_t, kMaxEncounterSlots> encounterUnitGuids_ = {}; // 0 = empty slot

View file

@ -5587,10 +5587,22 @@ void GameHandler::handlePacket(network::Packet& packet) {
addUIError("Battlefield port denied."); addUIError("Battlefield port denied.");
addSystemChatMessage("Battlefield port denied."); addSystemChatMessage("Battlefield port denied.");
break; break;
case Opcode::MSG_BATTLEGROUND_PLAYER_POSITIONS: case Opcode::MSG_BATTLEGROUND_PLAYER_POSITIONS: {
// Optional map position updates for BG objectives/players. bgPlayerPositions_.clear();
packet.setReadPos(packet.getSize()); for (int grp = 0; grp < 2; ++grp) {
if (packet.getSize() - packet.getReadPos() < 4) break;
uint32_t count = packet.readUInt32();
for (uint32_t i = 0; i < count && packet.getSize() - packet.getReadPos() >= 16; ++i) {
BgPlayerPosition pos;
pos.guid = packet.readUInt64();
pos.wowX = packet.readFloat();
pos.wowY = packet.readFloat();
pos.group = grp;
bgPlayerPositions_.push_back(pos);
}
}
break; break;
}
case Opcode::SMSG_REMOVED_FROM_PVP_QUEUE: case Opcode::SMSG_REMOVED_FROM_PVP_QUEUE:
addSystemChatMessage("You have been removed from the PvP queue."); addSystemChatMessage("You have been removed from the PvP queue.");
break; break;

View file

@ -17391,6 +17391,55 @@ void GameScreen::renderMinimapMarkers(game::GameHandler& gameHandler) {
} }
} }
// BG flag carrier / important player positions (MSG_BATTLEGROUND_PLAYER_POSITIONS)
{
const auto& bgPositions = gameHandler.getBgPlayerPositions();
if (!bgPositions.empty()) {
ImVec2 mouse = ImGui::GetMousePos();
// group 0 = typically ally-held flag / first list; group 1 = enemy
static const ImU32 kBgGroupColors[2] = {
IM_COL32( 80, 180, 255, 240), // group 0: blue (alliance)
IM_COL32(220, 50, 50, 240), // group 1: red (horde)
};
for (const auto& bp : bgPositions) {
// Packet coords: wowX=canonical X (north), wowY=canonical Y (west)
glm::vec3 bpRender = core::coords::canonicalToRender(glm::vec3(bp.wowX, bp.wowY, 0.0f));
float sx = 0.0f, sy = 0.0f;
if (!projectToMinimap(bpRender, sx, sy)) continue;
ImU32 col = kBgGroupColors[bp.group & 1];
// Draw a flag-like diamond icon
const float r = 5.0f;
ImVec2 top (sx, sy - r);
ImVec2 right(sx + r, sy );
ImVec2 bot (sx, sy + r);
ImVec2 left (sx - r, sy );
drawList->AddQuadFilled(top, right, bot, left, col);
drawList->AddQuad(top, right, bot, left, IM_COL32(255, 255, 255, 180), 1.0f);
float mdx = mouse.x - sx, mdy = mouse.y - sy;
if (mdx * mdx + mdy * mdy < 64.0f) {
// Show entity name if available, otherwise guid
auto ent = gameHandler.getEntityManager().getEntity(bp.guid);
if (ent) {
std::string nm;
if (ent->getType() == game::ObjectType::PLAYER) {
auto pl = std::static_pointer_cast<game::Unit>(ent);
nm = pl ? pl->getName() : "";
}
if (!nm.empty())
ImGui::SetTooltip("Flag carrier: %s", nm.c_str());
else
ImGui::SetTooltip("Flag carrier");
} else {
ImGui::SetTooltip("Flag carrier");
}
}
}
}
}
// Corpse direction indicator — shown when player is a ghost // Corpse direction indicator — shown when player is a ghost
if (gameHandler.isPlayerGhost()) { if (gameHandler.isPlayerGhost()) {
float corpseCanX = 0.0f, corpseCanY = 0.0f; float corpseCanX = 0.0f, corpseCanY = 0.0f;