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
|
|
@ -514,6 +514,21 @@ public:
|
||||||
const std::vector<CombatTextEntry>& getCombatText() const { return combatText; }
|
const std::vector<CombatTextEntry>& getCombatText() const { return combatText; }
|
||||||
void updateCombatText(float deltaTime);
|
void updateCombatText(float deltaTime);
|
||||||
|
|
||||||
|
// Threat
|
||||||
|
struct ThreatEntry {
|
||||||
|
uint64_t victimGuid = 0;
|
||||||
|
uint32_t threat = 0;
|
||||||
|
};
|
||||||
|
// Returns the current threat list for a given unit GUID (from last SMSG_THREAT_UPDATE)
|
||||||
|
const std::vector<ThreatEntry>* getThreatList(uint64_t unitGuid) const {
|
||||||
|
auto it = threatLists_.find(unitGuid);
|
||||||
|
return (it != threatLists_.end()) ? &it->second : nullptr;
|
||||||
|
}
|
||||||
|
// Returns the threat list for the player's current target, or nullptr
|
||||||
|
const std::vector<ThreatEntry>* getTargetThreatList() const {
|
||||||
|
return targetGuid ? getThreatList(targetGuid) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Phase 3: Spells ----
|
// ---- Phase 3: Spells ----
|
||||||
void castSpell(uint32_t spellId, uint64_t targetGuid = 0);
|
void castSpell(uint32_t spellId, uint64_t targetGuid = 0);
|
||||||
void cancelCast();
|
void cancelCast();
|
||||||
|
|
@ -2047,6 +2062,8 @@ private:
|
||||||
float autoAttackFacingSyncTimer_ = 0.0f; // Periodic facing sync while meleeing
|
float autoAttackFacingSyncTimer_ = 0.0f; // Periodic facing sync while meleeing
|
||||||
std::unordered_set<uint64_t> hostileAttackers_;
|
std::unordered_set<uint64_t> hostileAttackers_;
|
||||||
std::vector<CombatTextEntry> combatText;
|
std::vector<CombatTextEntry> combatText;
|
||||||
|
// unitGuid → sorted threat list (descending by threat value)
|
||||||
|
std::unordered_map<uint64_t, std::vector<ThreatEntry>> threatLists_;
|
||||||
|
|
||||||
// ---- Phase 3: Spells ----
|
// ---- Phase 3: Spells ----
|
||||||
WorldEntryCallback worldEntryCallback_;
|
WorldEntryCallback worldEntryCallback_;
|
||||||
|
|
|
||||||
|
|
@ -368,6 +368,10 @@ private:
|
||||||
// Inspect window
|
// Inspect window
|
||||||
bool showInspectWindow_ = false;
|
bool showInspectWindow_ = false;
|
||||||
void renderInspectWindow(game::GameHandler& gameHandler);
|
void renderInspectWindow(game::GameHandler& gameHandler);
|
||||||
|
|
||||||
|
// Threat window
|
||||||
|
bool showThreatWindow_ = false;
|
||||||
|
void renderThreatWindow(game::GameHandler& gameHandler);
|
||||||
uint8_t lfgRoles_ = 0x08; // default: DPS (0x02=tank, 0x04=healer, 0x08=dps)
|
uint8_t lfgRoles_ = 0x08; // default: DPS (0x02=tank, 0x04=healer, 0x08=dps)
|
||||||
uint32_t lfgSelectedDungeon_ = 861; // default: random dungeon (entry 861 = Random Dungeon WotLK)
|
uint32_t lfgSelectedDungeon_ = 861; // default: random dungeon (entry 861 = Random Dungeon WotLK)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2331,24 +2331,51 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_THREAT_CLEAR:
|
case Opcode::SMSG_THREAT_CLEAR:
|
||||||
// All threat dropped on the local player (e.g. Vanish, Feign Death)
|
// 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");
|
LOG_DEBUG("SMSG_THREAT_CLEAR: threat wiped");
|
||||||
break;
|
break;
|
||||||
case Opcode::SMSG_THREAT_REMOVE: {
|
case Opcode::SMSG_THREAT_REMOVE: {
|
||||||
// packed_guid (unit) + packed_guid (victim whose threat was removed)
|
// packed_guid (unit) + packed_guid (victim whose threat was removed)
|
||||||
if (packet.getSize() - packet.getReadPos() >= 1) {
|
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||||
(void)UpdateObjectParser::readPackedGuid(packet);
|
uint64_t unitGuid = UpdateObjectParser::readPackedGuid(packet);
|
||||||
if (packet.getSize() - packet.getReadPos() >= 1) {
|
if (packet.getSize() - packet.getReadPos() < 1) break;
|
||||||
(void)UpdateObjectParser::readPackedGuid(packet);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case Opcode::SMSG_HIGHEST_THREAT_UPDATE: {
|
case Opcode::SMSG_HIGHEST_THREAT_UPDATE:
|
||||||
// packed_guid (tank) + packed_guid (new highest threat unit) + uint32 count
|
case Opcode::SMSG_THREAT_UPDATE: {
|
||||||
// + count × (packed_guid victim + uint32 threat)
|
// Both packets share the same format:
|
||||||
// Informational — no threat UI yet; consume to suppress warnings
|
// packed_guid (unit) + packed_guid (highest-threat target or target, unused here)
|
||||||
packet.setReadPos(packet.getSize());
|
// + 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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -5656,22 +5683,6 @@ void GameHandler::handlePacket(network::Packet& packet) {
|
||||||
break;
|
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: {
|
case Opcode::SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT: {
|
||||||
// uint32 slot + packed_guid unit (0 packed = clear slot)
|
// uint32 slot + packed_guid unit (0 packed = clear slot)
|
||||||
if (packet.getSize() - packet.getReadPos() < 5) {
|
if (packet.getSize() - packet.getReadPos() < 5) {
|
||||||
|
|
|
||||||
|
|
@ -500,6 +500,7 @@ void GameScreen::render(game::GameHandler& gameHandler) {
|
||||||
renderAchievementWindow(gameHandler);
|
renderAchievementWindow(gameHandler);
|
||||||
renderGmTicketWindow(gameHandler);
|
renderGmTicketWindow(gameHandler);
|
||||||
renderInspectWindow(gameHandler);
|
renderInspectWindow(gameHandler);
|
||||||
|
renderThreatWindow(gameHandler);
|
||||||
// renderQuestMarkers(gameHandler); // Disabled - using 3D billboard markers now
|
// renderQuestMarkers(gameHandler); // Disabled - using 3D billboard markers now
|
||||||
if (showMinimap_) {
|
if (showMinimap_) {
|
||||||
renderMinimapMarkers(gameHandler);
|
renderMinimapMarkers(gameHandler);
|
||||||
|
|
@ -2728,6 +2729,15 @@ void GameScreen::renderTargetFrame(game::GameHandler& gameHandler) {
|
||||||
float distance = std::sqrt(dx*dx + dy*dy + dz*dz);
|
float distance = std::sqrt(dx*dx + dy*dy + dz*dz);
|
||||||
ImGui::TextDisabled("%.1f yd", distance);
|
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)
|
// Target auras (buffs/debuffs)
|
||||||
const auto& targetAuras = gameHandler.getTargetAuras();
|
const auto& targetAuras = gameHandler.getTargetAuras();
|
||||||
int activeAuras = 0;
|
int activeAuras = 0;
|
||||||
|
|
@ -3152,6 +3162,13 @@ void GameScreen::sendChatMessage(game::GameHandler& gameHandler) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /threat command
|
||||||
|
if (cmdLower == "threat") {
|
||||||
|
showThreatWindow_ = !showThreatWindow_;
|
||||||
|
chatInputBuffer[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// /time command
|
// /time command
|
||||||
if (cmdLower == "time") {
|
if (cmdLower == "time") {
|
||||||
gameHandler.queryServerTime();
|
gameHandler.queryServerTime();
|
||||||
|
|
@ -14490,6 +14507,79 @@ void GameScreen::renderGmTicketWindow(game::GameHandler& gameHandler) {
|
||||||
ImGui::End();
|
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 ───────────────────────────────────────────────────────────
|
// ─── Inspect Window ───────────────────────────────────────────────────────────
|
||||||
void GameScreen::renderInspectWindow(game::GameHandler& gameHandler) {
|
void GameScreen::renderInspectWindow(game::GameHandler& gameHandler) {
|
||||||
if (!showInspectWindow_) return;
|
if (!showInspectWindow_) return;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue