From 9c276d10725bebc042c12aca013263838b59d098 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 11:40:31 -0700 Subject: [PATCH] feat: add encounter-level DPS/HPS tracking to DPS meter Track cumulative player damage/healing for the full combat encounter using the persistent CombatLog, shown alongside the existing 2.5s rolling window. The encounter row appears after 3s of combat and persists post-combat until the next engagement, giving a stable full-fight average rather than the spiky per-window reading. --- include/ui/game_screen.hpp | 3 ++ src/ui/game_screen.cpp | 62 +++++++++++++++++++++++++++++++++++--- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index cd102fa9..a21caf68 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -524,6 +524,9 @@ private: bool showDPSMeter_ = false; float dpsCombatAge_ = 0.0f; // seconds in current combat (for accurate early-combat DPS) bool dpsWasInCombat_ = false; + float dpsEncounterDamage_ = 0.0f; // total player damage this combat + float dpsEncounterHeal_ = 0.0f; // total player healing this combat + size_t dpsLogSeenCount_ = 0; // log entries already scanned public: void triggerDing(uint32_t newLevel); diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 45845e41..c841cd0d 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -7479,11 +7479,37 @@ void GameScreen::renderDPSMeter(game::GameHandler& gameHandler) { // Track combat duration for accurate DPS denominator in short fights bool inCombat = gameHandler.isInCombat(); + if (inCombat && !dpsWasInCombat_) { + // Just entered combat — reset encounter accumulators + dpsEncounterDamage_ = 0.0f; + dpsEncounterHeal_ = 0.0f; + dpsLogSeenCount_ = gameHandler.getCombatLog().size(); + dpsCombatAge_ = 0.0f; + } if (inCombat) { dpsCombatAge_ += dt; + // Scan any new log entries since last frame + const auto& log = gameHandler.getCombatLog(); + while (dpsLogSeenCount_ < log.size()) { + const auto& e = log[dpsLogSeenCount_++]; + if (!e.isPlayerSource) continue; + switch (e.type) { + case game::CombatTextEntry::MELEE_DAMAGE: + case game::CombatTextEntry::SPELL_DAMAGE: + case game::CombatTextEntry::CRIT_DAMAGE: + case game::CombatTextEntry::PERIODIC_DAMAGE: + dpsEncounterDamage_ += static_cast(e.amount); + break; + case game::CombatTextEntry::HEAL: + case game::CombatTextEntry::CRIT_HEAL: + case game::CombatTextEntry::PERIODIC_HEAL: + dpsEncounterHeal_ += static_cast(e.amount); + break; + default: break; + } + } } else if (dpsWasInCombat_) { - // Just left combat — let meter show last reading for LIFETIME then reset - dpsCombatAge_ = 0.0f; + // Just left combat — keep encounter totals but stop accumulating } dpsWasInCombat_ = inCombat; @@ -7507,8 +7533,9 @@ void GameScreen::renderDPSMeter(game::GameHandler& gameHandler) { } } - // Only show if there's something to report - if (totalDamage < 1.0f && totalHeal < 1.0f && !inCombat) return; + // Only show if there's something to report (rolling window or lingering encounter data) + if (totalDamage < 1.0f && totalHeal < 1.0f && !inCombat && + dpsEncounterDamage_ < 1.0f && dpsEncounterHeal_ < 1.0f) return; // DPS window = min(combat age, combat-text lifetime) to avoid under-counting // at the start of a fight and over-counting when entries expire. @@ -7534,8 +7561,22 @@ void GameScreen::renderDPSMeter(game::GameHandler& gameHandler) { float screenW = appWin ? static_cast(appWin->getWidth()) : 1280.0f; float screenH = appWin ? static_cast(appWin->getHeight()) : 720.0f; + // Show encounter row when fight has been going long enough (> 3s) + bool showEnc = (dpsCombatAge_ > 3.0f || (!inCombat && dpsEncounterDamage_ > 0.0f)); + float encDPS = (dpsCombatAge_ > 0.1f) ? dpsEncounterDamage_ / dpsCombatAge_ : 0.0f; + float encHPS = (dpsCombatAge_ > 0.1f) ? dpsEncounterHeal_ / dpsCombatAge_ : 0.0f; + + char encDpsBuf[16], encHpsBuf[16]; + fmtNum(encDPS, encDpsBuf, sizeof(encDpsBuf)); + fmtNum(encHPS, encHpsBuf, sizeof(encHpsBuf)); + constexpr float WIN_W = 90.0f; - constexpr float WIN_H = 36.0f; + // Extra rows for encounter DPS/HPS if active + int extraRows = 0; + if (showEnc && encDPS > 0.5f) ++extraRows; + if (showEnc && encHPS > 0.5f) ++extraRows; + float WIN_H = 18.0f + extraRows * 14.0f; + if (dps > 0.5f || hps > 0.5f) WIN_H = std::max(WIN_H, 36.0f); float wx = screenW * 0.5f + 160.0f; // right of cast bar float wy = screenH - 130.0f; // above action bar area @@ -7562,6 +7603,17 @@ void GameScreen::renderDPSMeter(game::GameHandler& gameHandler) { ImGui::SameLine(0, 2); ImGui::TextDisabled("hps"); } + // Encounter totals (full-fight average, shown when fight > 3s) + if (showEnc && encDPS > 0.5f) { + ImGui::TextColored(ImVec4(1.0f, 0.65f, 0.25f, 0.80f), "%s", encDpsBuf); + ImGui::SameLine(0, 2); + ImGui::TextDisabled("enc"); + } + if (showEnc && encHPS > 0.5f) { + ImGui::TextColored(ImVec4(0.50f, 1.0f, 0.50f, 0.80f), "%s", encHpsBuf); + ImGui::SameLine(0, 2); + ImGui::TextDisabled("enc"); + } } ImGui::End();