mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
Add threat list window showing live aggro data for current target
Store SMSG_THREAT_UPDATE/SMSG_HIGHEST_THREAT_UPDATE in a per-unit map (sorted descending by threat) and clear on SMSG_THREAT_REMOVE/CLEAR. Show a threat window (/threat or via target frame button) with a progress bar per player and gold highlight for the tank, red if local player has aggro.
This commit is contained in:
parent
43de2be1f2
commit
920950dfbd
4 changed files with 149 additions and 27 deletions
|
|
@ -2331,24 +2331,51 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
break;
|
||||
case Opcode::SMSG_THREAT_CLEAR:
|
||||
// All threat dropped on the local player (e.g. Vanish, Feign Death)
|
||||
// No local state to clear — informational
|
||||
threatLists_.clear();
|
||||
LOG_DEBUG("SMSG_THREAT_CLEAR: threat wiped");
|
||||
break;
|
||||
case Opcode::SMSG_THREAT_REMOVE: {
|
||||
// packed_guid (unit) + packed_guid (victim whose threat was removed)
|
||||
if (packet.getSize() - packet.getReadPos() >= 1) {
|
||||
(void)UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() >= 1) {
|
||||
(void)UpdateObjectParser::readPackedGuid(packet);
|
||||
}
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
uint64_t unitGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
uint64_t victimGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
auto it = threatLists_.find(unitGuid);
|
||||
if (it != threatLists_.end()) {
|
||||
auto& list = it->second;
|
||||
list.erase(std::remove_if(list.begin(), list.end(),
|
||||
[victimGuid](const ThreatEntry& e){ return e.victimGuid == victimGuid; }),
|
||||
list.end());
|
||||
if (list.empty()) threatLists_.erase(it);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_HIGHEST_THREAT_UPDATE: {
|
||||
// packed_guid (tank) + packed_guid (new highest threat unit) + uint32 count
|
||||
// + count × (packed_guid victim + uint32 threat)
|
||||
// Informational — no threat UI yet; consume to suppress warnings
|
||||
packet.setReadPos(packet.getSize());
|
||||
case Opcode::SMSG_HIGHEST_THREAT_UPDATE:
|
||||
case Opcode::SMSG_THREAT_UPDATE: {
|
||||
// Both packets share the same format:
|
||||
// packed_guid (unit) + packed_guid (highest-threat target or target, unused here)
|
||||
// + uint32 count + count × (packed_guid victim + uint32 threat)
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
uint64_t unitGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
(void)UpdateObjectParser::readPackedGuid(packet); // highest-threat / current target
|
||||
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||
uint32_t cnt = packet.readUInt32();
|
||||
if (cnt > 100) { packet.setReadPos(packet.getSize()); break; } // sanity
|
||||
std::vector<ThreatEntry> list;
|
||||
list.reserve(cnt);
|
||||
for (uint32_t i = 0; i < cnt; ++i) {
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
ThreatEntry entry;
|
||||
entry.victimGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||
entry.threat = packet.readUInt32();
|
||||
list.push_back(entry);
|
||||
}
|
||||
// Sort descending by threat so highest is first
|
||||
std::sort(list.begin(), list.end(),
|
||||
[](const ThreatEntry& a, const ThreatEntry& b){ return a.threat > b.threat; });
|
||||
threatLists_[unitGuid] = std::move(list);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -5656,22 +5683,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
|||
break;
|
||||
}
|
||||
|
||||
case Opcode::SMSG_THREAT_UPDATE: {
|
||||
// packed_guid (unit) + packed_guid (target) + uint32 count
|
||||
// + count × (packed_guid victim + uint32 threat) — consume to suppress warnings
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
(void)UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||
(void)UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() < 4) break;
|
||||
uint32_t cnt = packet.readUInt32();
|
||||
for (uint32_t i = 0; i < cnt && packet.getSize() - packet.getReadPos() >= 1; ++i) {
|
||||
(void)UpdateObjectParser::readPackedGuid(packet);
|
||||
if (packet.getSize() - packet.getReadPos() >= 4)
|
||||
packet.readUInt32();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Opcode::SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT: {
|
||||
// uint32 slot + packed_guid unit (0 packed = clear slot)
|
||||
if (packet.getSize() - packet.getReadPos() < 5) {
|
||||
|
|
|
|||
|
|
@ -500,6 +500,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
|||
renderAchievementWindow(gameHandler);
|
||||
renderGmTicketWindow(gameHandler);
|
||||
renderInspectWindow(gameHandler);
|
||||
renderThreatWindow(gameHandler);
|
||||
// renderQuestMarkers(gameHandler); // Disabled - using 3D billboard markers now
|
||||
if (showMinimap_) {
|
||||
renderMinimapMarkers(gameHandler);
|
||||
|
|
@ -2728,6 +2729,15 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
|
|||
float distance = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||||
ImGui::TextDisabled("%.1f yd", distance);
|
||||
|
||||
// Threat button (shown when in combat and threat data is available)
|
||||
if (gameHandler.getTargetThreatList()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.1f, 0.1f, 0.8f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.7f, 0.2f, 0.2f, 0.9f));
|
||||
if (ImGui::SmallButton("Threat")) showThreatWindow_ = !showThreatWindow_;
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
// Target auras (buffs/debuffs)
|
||||
const auto& targetAuras = gameHandler.getTargetAuras();
|
||||
int activeAuras = 0;
|
||||
|
|
@ -3152,6 +3162,13 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
|||
return;
|
||||
}
|
||||
|
||||
// /threat command
|
||||
if (cmdLower == "threat") {
|
||||
showThreatWindow_ = !showThreatWindow_;
|
||||
chatInputBuffer[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
// /time command
|
||||
if (cmdLower == "time") {
|
||||
gameHandler.queryServerTime();
|
||||
|
|
@ -14490,6 +14507,79 @@ void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
|||
ImGui::End();
|
||||
}
|
||||
|
||||
// ─── Threat Window ────────────────────────────────────────────────────────────
|
||||
void GameScreen::renderThreatWindow(game::GameHandler& gameHandler) {
|
||||
if (!showThreatWindow_) return;
|
||||
|
||||
const auto* list = gameHandler.getTargetThreatList();
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(280, 220), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowPos(ImVec2(10, 300), ImGuiCond_FirstUseEver);
|
||||
ImGui::SetNextWindowBgAlpha(0.85f);
|
||||
|
||||
if (!ImGui::Begin("Threat###ThreatWin", &showThreatWindow_,
|
||||
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!list || list->empty()) {
|
||||
ImGui::TextDisabled("No threat data for current target.");
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t maxThreat = list->front().threat;
|
||||
|
||||
ImGui::TextDisabled("%-19s Threat", "Player");
|
||||
ImGui::Separator();
|
||||
|
||||
uint64_t playerGuid = gameHandler.getPlayerGuid();
|
||||
int rank = 0;
|
||||
for (const auto& entry : *list) {
|
||||
++rank;
|
||||
bool isPlayer = (entry.victimGuid == playerGuid);
|
||||
|
||||
// Resolve name
|
||||
std::string victimName;
|
||||
auto entity = gameHandler.getEntityManager().getEntity(entry.victimGuid);
|
||||
if (entity) {
|
||||
if (entity->getType() == game::ObjectType::PLAYER) {
|
||||
auto p = std::static_pointer_cast<game::Player>(entity);
|
||||
victimName = p->getName().empty() ? "Player" : p->getName();
|
||||
} else if (entity->getType() == game::ObjectType::UNIT) {
|
||||
auto u = std::static_pointer_cast<game::Unit>(entity);
|
||||
victimName = u->getName().empty() ? "NPC" : u->getName();
|
||||
}
|
||||
}
|
||||
if (victimName.empty())
|
||||
victimName = "0x" + [&](){
|
||||
char buf[20]; snprintf(buf, sizeof(buf), "%llX",
|
||||
static_cast<unsigned long long>(entry.victimGuid)); return std::string(buf); }();
|
||||
|
||||
// Colour: gold for #1 (tank), red if player is highest, white otherwise
|
||||
ImVec4 col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
if (rank == 1) col = ImVec4(1.0f, 0.82f, 0.0f, 1.0f); // gold
|
||||
if (isPlayer && rank == 1) col = ImVec4(1.0f, 0.3f, 0.3f, 1.0f); // red — you have aggro
|
||||
|
||||
// Threat bar
|
||||
float pct = (maxThreat > 0) ? (float)entry.threat / (float)maxThreat : 0.0f;
|
||||
ImGui::PushStyleColor(ImGuiCol_PlotHistogram,
|
||||
isPlayer ? ImVec4(0.8f, 0.2f, 0.2f, 0.7f) : ImVec4(0.2f, 0.5f, 0.8f, 0.5f));
|
||||
char barLabel[48];
|
||||
snprintf(barLabel, sizeof(barLabel), "%.0f%%", pct * 100.0f);
|
||||
ImGui::ProgressBar(pct, ImVec2(60, 14), barLabel);
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::TextColored(col, "%-18s %u", victimName.c_str(), entry.threat);
|
||||
|
||||
if (rank >= 10) break; // cap display at 10 entries
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
// ─── Inspect Window ───────────────────────────────────────────────────────────
|
||||
void GameScreen::renderInspectWindow(game::GameHandler& gameHandler) {
|
||||
if (!showInspectWindow_) return;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue