From 21764e52931a8e7a988a4ae06402f9d685dfae23 Mon Sep 17 00:00:00 2001 From: careyourcurroundings <70723278+careyourcurroundings@users.noreply.github.com> Date: Wed, 17 Jun 2026 10:59:38 -0400 Subject: [PATCH 1/4] init --- GWToolboxdll/Widgets/PartyDamage.cpp | 30 ++++++++++++++++++++++++++++ GWToolboxdll/Widgets/PartyDamage.h | 1 + 2 files changed, 31 insertions(+) diff --git a/GWToolboxdll/Widgets/PartyDamage.cpp b/GWToolboxdll/Widgets/PartyDamage.cpp index 85b8d90f4..0c87f87f5 100644 --- a/GWToolboxdll/Widgets/PartyDamage.cpp +++ b/GWToolboxdll/Widgets/PartyDamage.cpp @@ -31,6 +31,10 @@ namespace { uint32_t total = 0; uint32_t total_healing = 0; + clock_t first_packet_time = 0; + clock_t last_packet_time = 0; + clock_t accumulated_combat_time_ms = 0; + std::map hp_map_nm{}; std::map hp_map_hm{}; const std::pair*> section_maps[] = { @@ -388,6 +392,20 @@ void PartyDamage::DamagePacketCallback(GW::HookStatus*, const GW::Packet::StoC:: } } + const clock_t now = TIMER_INIT(); + if (first_packet_time == 0) { + first_packet_time = now; + last_packet_time = now; + } + else { + const clock_t elapsed = now - last_packet_time; + if (elapsed > 5000 && first_packet_time != 0) { + accumulated_combat_time_ms += (last_packet_time - first_packet_time); + first_packet_time = now; + } + last_packet_time = now; + } + if (is_damage) { entry->damage += amount; total += amount; @@ -411,6 +429,9 @@ void PartyDamage::ResetDamage() } departed_damage.clear(); prev_party_agent_ids.clear(); + first_packet_time = 0; + last_packet_time = 0; + accumulated_combat_time_ms = 0; } void PartyDamage::WriteOwnDamage() @@ -709,6 +730,13 @@ void PartyDamage::Draw(IDirect3DDevice9*) } } + if (settings.show_dps && settings.show_damage && entry->damage > 0) { + const uint32_t dps = accumulated_combat_time_ms == 0 ? 0 : static_cast(std::llround(static_cast(entry->damage) * 1000.0 / static_cast(accumulated_combat_time_ms))); + snprintf(buffer, buffer_size, "%d/s", dps); + const float dps_text_x = x + width * 0.75f; + draw_list->AddText(ImVec2(dps_text_x, text_y), IM_COL32(255, 255, 255, 255), buffer); + } + if (settings.show_healing) { // Healing text const float healing_float = static_cast(entry->healing); @@ -834,6 +862,8 @@ void PartyDamage::DrawSettingsInternal() ImGui::Checkbox("Show damage", &settings.show_damage); ImGui::NextSpacedElement(); ImGui::Checkbox("Show healing", &settings.show_healing); + ImGui::NextSpacedElement(); + ImGui::Checkbox("Show DPS", &settings.show_dps); ImGui::StartSpacedElements(292.f); ImGui::NextSpacedElement(); diff --git a/GWToolboxdll/Widgets/PartyDamage.h b/GWToolboxdll/Widgets/PartyDamage.h index c3dbc2126..145353656 100644 --- a/GWToolboxdll/Widgets/PartyDamage.h +++ b/GWToolboxdll/Widgets/PartyDamage.h @@ -58,6 +58,7 @@ class PartyDamage : public SnapsToPartyWindow { bool overlay_party_window = false; bool show_damage = true; bool show_healing = false; + bool show_dps = false; // Distance away from the party window on the x axis; used with snap to party window int user_offset = 0; }; From 4a53d68615ab44b59286dd178b21322d3c7e866f Mon Sep 17 00:00:00 2001 From: careyourcurroundings <70723278+careyourcurroundings@users.noreply.github.com> Date: Wed, 17 Jun 2026 11:26:11 -0400 Subject: [PATCH 2/4] Move packet tracking to correct location --- GWToolboxdll/Widgets/PartyDamage.cpp | 29 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/GWToolboxdll/Widgets/PartyDamage.cpp b/GWToolboxdll/Widgets/PartyDamage.cpp index 0c87f87f5..194981a91 100644 --- a/GWToolboxdll/Widgets/PartyDamage.cpp +++ b/GWToolboxdll/Widgets/PartyDamage.cpp @@ -392,25 +392,24 @@ void PartyDamage::DamagePacketCallback(GW::HookStatus*, const GW::Packet::StoC:: } } - const clock_t now = TIMER_INIT(); - if (first_packet_time == 0) { - first_packet_time = now; - last_packet_time = now; - } - else { - const clock_t elapsed = now - last_packet_time; - if (elapsed > 5000 && first_packet_time != 0) { - accumulated_combat_time_ms += (last_packet_time - first_packet_time); - first_packet_time = now; - } - last_packet_time = now; - } - if (is_damage) { + const clock_t now = TIMER_INIT(); entry->damage += amount; total += amount; entry->recent_damage += amount; - entry->last_damage = TIMER_INIT(); + entry->last_damage = now; + if (first_packet_time == 0) { + first_packet_time = now; + last_packet_time = now; + } + else { + const clock_t elapsed = now - last_packet_time; + if (elapsed > 5000 && first_packet_time != 0) { + accumulated_combat_time_ms += (last_packet_time - first_packet_time); + first_packet_time = now; + } + last_packet_time = now; + } } else { entry->healing += amount; From d573c0edf584dc8d138b5ddbb09ee474264065f2 Mon Sep 17 00:00:00 2001 From: careyourcurroundings <70723278+careyourcurroundings@users.noreply.github.com> Date: Wed, 17 Jun 2026 14:03:19 -0400 Subject: [PATCH 3/4] Add condition dps --- GWToolboxdll/Widgets/PartyDamage.cpp | 157 ++++++++++++++++++++++++++- GWToolboxdll/Widgets/PartyDamage.h | 4 +- 2 files changed, 158 insertions(+), 3 deletions(-) diff --git a/GWToolboxdll/Widgets/PartyDamage.cpp b/GWToolboxdll/Widgets/PartyDamage.cpp index 194981a91..7e95596ce 100644 --- a/GWToolboxdll/Widgets/PartyDamage.cpp +++ b/GWToolboxdll/Widgets/PartyDamage.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,8 @@ #include #include +#include + constexpr const wchar_t* INI_FILENAME = L"healthlog.ini"; constexpr const wchar_t* JSON_FILENAME = L"healthlog.json"; @@ -35,6 +38,63 @@ namespace { clock_t last_packet_time = 0; clock_t accumulated_combat_time_ms = 0; + // Condition DPS tracking + enum class ConditionType : uint8_t { Bleeding, Poison, Disease, Burning, Count }; + constexpr uint32_t CONDITION_DPS_RATES[] = { 6, 8, 8, 14 }; + struct CondTracker { + uint32_t agent_id = 0; + clock_t apply_time[4] = {}; // bleeding, poison, disease, burning + }; + CondTracker cond_trackers[512] = {}; + uint32_t cond_tracker_count = 0; + double cond_damage[4] = {}; // accumulated damage per condition + + int CondIdxFromEffectId(uint32_t effect_id) { + switch (effect_id) { + case 23: return 0; // bleeding + case 27: return 1; // poison + case 26: return 2; // disease + case 25: return 3; // burning + default: return -1; + } + } + + // Finalize damage for all active conditions on a tracker, then remove it + void FinalizeAndRemoveTracker(uint32_t ti) { + if (ti >= cond_tracker_count) return; + const clock_t now = TIMER_INIT(); + for (int ci = 0; ci < 4; ci++) { + if (cond_trackers[ti].apply_time[ci] != 0) { + const double elapsed = static_cast(now - cond_trackers[ti].apply_time[ci]) / 1000.0; + cond_damage[ci] += static_cast(CONDITION_DPS_RATES[ci]) * elapsed; + } + } + cond_tracker_count--; + cond_trackers[ti] = cond_trackers[cond_tracker_count]; + } + + GW::HookEntry AgentRemove_Entry; + GW::HookEntry AgentState_Entry; + + void ConditionAgentRemoveCallback(GW::HookStatus*, const GW::Packet::StoC::AgentRemove* packet) { + for (uint32_t ti = 0; ti < cond_tracker_count; ti++) { + if (cond_trackers[ti].agent_id == packet->agent_id) { + FinalizeAndRemoveTracker(ti); + return; + } + } + } + + void ConditionAgentStateCallback(GW::HookStatus*, const GW::Packet::StoC::AgentState* packet) { + if ((packet->state & 16) == 0) return; + for (uint32_t ti = 0; ti < cond_tracker_count; ti++) { + if (cond_trackers[ti].agent_id == packet->agent_id) { + FinalizeAndRemoveTracker(ti); + return; + } + } + } + std::map hp_map_nm{}; std::map hp_map_hm{}; const std::pair*> section_maps[] = { @@ -50,6 +110,7 @@ namespace { PartyDamage::Settings settings; GW::HookEntry GenericModifier_Entry; + GW::HookEntry GenericValue_Entry; GW::HookEntry MapLoaded_Entry; float GetPartOfTotal(uint32_t dmg) @@ -304,6 +365,51 @@ void PartyDamage::MapLoadedCallback(GW::HookStatus*, const GW::Packet::StoC::Map } } +void PartyDamage::ConditionValueCallback(GW::HookStatus*, const GW::Packet::StoC::GenericValue* packet) +{ + if (packet->value_id == GW::Packet::StoC::GenericValueID::add_effect) { + const int ci = CondIdxFromEffectId(packet->value); + if (ci < 0) return; + const auto target = static_cast(GW::Agents::GetAgentByID(packet->agent_id)); + if (!target || target->allegiance != GW::Constants::Allegiance::Enemy) return; + uint32_t ti = 0; + for (; ti < cond_tracker_count; ti++) { + if (cond_trackers[ti].agent_id == packet->agent_id) + break; + } + if (ti == cond_tracker_count) { + if (ti >= 512) return; + cond_trackers[ti].agent_id = packet->agent_id; + cond_tracker_count++; + } + cond_trackers[ti].apply_time[ci] = TIMER_INIT(); + return; + } + if (packet->value_id == GW::Packet::StoC::GenericValueID::remove_effect) { + for (uint32_t ti = 0; ti < cond_tracker_count; ti++) { + if (cond_trackers[ti].agent_id != packet->agent_id) continue; + const clock_t now = TIMER_INIT(); + for (int ci = 0; ci < 4; ci++) { + if (cond_trackers[ti].apply_time[ci] == 0) continue; + const double elapsed = static_cast(now - cond_trackers[ti].apply_time[ci]) / 1000.0; + cond_damage[ci] += static_cast(CONDITION_DPS_RATES[ci]) * elapsed; + cond_trackers[ti].apply_time[ci] = 0; // clear it + } + // If all conditions cleared, remove tracker + bool all_cleared = true; + for (int ci = 0; ci < 4; ci++) { + if (cond_trackers[ti].apply_time[ci] != 0) { all_cleared = false; break; } + } + if (all_cleared) { + cond_tracker_count--; + cond_trackers[ti] = cond_trackers[cond_tracker_count]; + } + return; + } + return; + } +} + void PartyDamage::DamagePacketCallback(GW::HookStatus*, const GW::Packet::StoC::GenericModifier* packet) { // ignore non-damage/heal packets @@ -431,6 +537,11 @@ void PartyDamage::ResetDamage() first_packet_time = 0; last_packet_time = 0; accumulated_combat_time_ms = 0; + + cond_tracker_count = 0; + for (auto& d : cond_damage) { + d = 0.0; + } } void PartyDamage::WriteOwnDamage() @@ -491,6 +602,9 @@ void PartyDamage::Initialize() GW::StoC::RegisterPacketCallback(&GenericModifier_Entry, DamagePacketCallback, 0x8000); GW::StoC::RegisterPacketCallback(&MapLoaded_Entry, MapLoadedCallback, 0x8000); + GW::StoC::RegisterPacketCallback(&GenericValue_Entry, ConditionValueCallback, 0x8000); + GW::StoC::RegisterPacketCallback(&AgentRemove_Entry, ConditionAgentRemoveCallback, 0x8000); + GW::StoC::RegisterPacketCallback(&AgentState_Entry, ConditionAgentStateCallback, 0x8000); GW::Chat::CreateCommand(&ChatCmd_HookEntry, L"dmg", CmdDamage); GW::Chat::CreateCommand(&ChatCmd_HookEntry, L"damage", CmdDamage); @@ -501,7 +615,10 @@ void PartyDamage::Terminate() { SnapsToPartyWindow::Terminate(); GW::StoC::RemoveCallbacks(&GenericModifier_Entry); + GW::StoC::RemoveCallbacks(&GenericValue_Entry); GW::StoC::RemoveCallbacks(&MapLoaded_Entry); + GW::StoC::RemoveCallbacks(&AgentRemove_Entry); + GW::StoC::RemoveCallbacks(&AgentState_Entry); GW::Chat::DeleteCommand(&ChatCmd_HookEntry); party_names_by_index.clear(); @@ -630,9 +747,11 @@ void PartyDamage::Draw(IDirect3DDevice9*) } } + const float cond_h = settings.show_condition_dps ? ImGui::GetTextLineHeight() * 2.0f + 6.0f : 0.0f; + // Add a window to capture mouse clicks. - ImGui::SetNextWindowPos({window_x, party_health_bars_position.top_left.y}); - ImGui::SetNextWindowSize({width, party_health_bars_position.bottom_right.y - party_health_bars_position.top_left.y}); + ImGui::SetNextWindowPos({window_x, party_health_bars_position.top_left.y - cond_h}); + ImGui::SetNextWindowSize({width, party_health_bars_position.bottom_right.y - party_health_bars_position.top_left.y + cond_h}); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(10.0f, 10.0f)); @@ -642,6 +761,38 @@ void PartyDamage::Draw(IDirect3DDevice9*) constexpr size_t buffer_size = 16; char buffer[buffer_size]; + // Condition DPS header row + if (settings.show_condition_dps) { + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(200, 50, 50, 255)); + ImGui::Text(ICON_FA_TINT); + ImGui::PopStyleColor(); + ImGui::SameLine(); + const uint32_t bleed_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[0] * 1000.0 / static_cast(accumulated_combat_time_ms))); + ImGui::Text("%d/s", bleed_dps); + ImGui::SameLine(); + + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 120, 0, 255)); + ImGui::Text(ICON_FA_FIRE); + ImGui::PopStyleColor(); + ImGui::SameLine(); + const uint32_t burn_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[3] * 1000.0 / static_cast(accumulated_combat_time_ms))); + ImGui::Text("%d/s", burn_dps); + + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 200, 80, 255)); + ImGui::Text(ICON_FA_TINT); + ImGui::PopStyleColor(); + ImGui::SameLine(); + const uint32_t poison_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[1] * 1000.0 / static_cast(accumulated_combat_time_ms))); + ImGui::Text("%d/s", poison_dps); + ImGui::SameLine(); + + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(200, 100, 200, 255)); + ImGui::Text(ICON_FA_SKULL); + ImGui::PopStyleColor(); + ImGui::SameLine(); + const uint32_t disease_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[2] * 1000.0 / static_cast(accumulated_combat_time_ms))); + ImGui::Text("%d/s", disease_dps); + } for (auto& [agent_id, party_slot] : party_indeces_by_agent_id) { uint32_t this_agent_party_index = 0; @@ -863,6 +1014,8 @@ void PartyDamage::DrawSettingsInternal() ImGui::Checkbox("Show healing", &settings.show_healing); ImGui::NextSpacedElement(); ImGui::Checkbox("Show DPS", &settings.show_dps); + ImGui::NextSpacedElement(); + ImGui::Checkbox("Show Condition DPS", &settings.show_condition_dps); ImGui::StartSpacedElements(292.f); ImGui::NextSpacedElement(); diff --git a/GWToolboxdll/Widgets/PartyDamage.h b/GWToolboxdll/Widgets/PartyDamage.h index 145353656..b1bed1292 100644 --- a/GWToolboxdll/Widgets/PartyDamage.h +++ b/GWToolboxdll/Widgets/PartyDamage.h @@ -34,6 +34,7 @@ class PartyDamage : public SnapsToPartyWindow { static void MapLoadedCallback(GW::HookStatus*, const GW::Packet::StoC::MapLoaded*); static void DamagePacketCallback(GW::HookStatus*, const GW::Packet::StoC::GenericModifier*); + static void ConditionValueCallback(GW::HookStatus*, const GW::Packet::StoC::GenericValue*); public: static PartyDamage& Instance() @@ -59,6 +60,7 @@ class PartyDamage : public SnapsToPartyWindow { bool show_damage = true; bool show_healing = false; bool show_dps = false; + bool show_condition_dps = false; // Distance away from the party window on the x axis; used with snap to party window int user_offset = 0; }; @@ -77,4 +79,4 @@ class PartyDamage : public SnapsToPartyWindow { void SaveSettings(SettingsDoc& doc) override; void DrawSettingsInternal() override; -}; +}; \ No newline at end of file From ff97a3aed51e50f824efa3adb0d9666fef3584f5 Mon Sep 17 00:00:00 2001 From: careyourcurroundings <70723278+careyourcurroundings@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:06:12 -0400 Subject: [PATCH 4/4] Increased Accuracy --- GWToolboxdll/Widgets/PartyDamage.cpp | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/GWToolboxdll/Widgets/PartyDamage.cpp b/GWToolboxdll/Widgets/PartyDamage.cpp index 7e95596ce..f42d7ca9e 100644 --- a/GWToolboxdll/Widgets/PartyDamage.cpp +++ b/GWToolboxdll/Widgets/PartyDamage.cpp @@ -38,6 +38,14 @@ namespace { clock_t last_packet_time = 0; clock_t accumulated_combat_time_ms = 0; + // Returns total combat time including the current battle segment, + // up to the last damage packet time (not wall clock). + clock_t GetEffectiveCombatTime() { + if (first_packet_time == 0) + return accumulated_combat_time_ms; + return accumulated_combat_time_ms + (last_packet_time - first_packet_time); + } + // Condition DPS tracking enum class ConditionType : uint8_t { Bleeding, Poison, Disease, Burning, Count }; constexpr uint32_t CONDITION_DPS_RATES[] = { 6, 8, 8, 14 }; @@ -45,7 +53,7 @@ namespace { uint32_t agent_id = 0; clock_t apply_time[4] = {}; // bleeding, poison, disease, burning }; - CondTracker cond_trackers[512] = {}; + CondTracker cond_trackers[64] = {}; uint32_t cond_tracker_count = 0; double cond_damage[4] = {}; // accumulated damage per condition @@ -378,7 +386,7 @@ void PartyDamage::ConditionValueCallback(GW::HookStatus*, const GW::Packet::StoC break; } if (ti == cond_tracker_count) { - if (ti >= 512) return; + if (ti >= 64) return; cond_trackers[ti].agent_id = packet->agent_id; cond_tracker_count++; } @@ -603,7 +611,6 @@ void PartyDamage::Initialize() GW::StoC::RegisterPacketCallback(&GenericModifier_Entry, DamagePacketCallback, 0x8000); GW::StoC::RegisterPacketCallback(&MapLoaded_Entry, MapLoadedCallback, 0x8000); GW::StoC::RegisterPacketCallback(&GenericValue_Entry, ConditionValueCallback, 0x8000); - GW::StoC::RegisterPacketCallback(&AgentRemove_Entry, ConditionAgentRemoveCallback, 0x8000); GW::StoC::RegisterPacketCallback(&AgentState_Entry, ConditionAgentStateCallback, 0x8000); GW::Chat::CreateCommand(&ChatCmd_HookEntry, L"dmg", CmdDamage); @@ -617,7 +624,6 @@ void PartyDamage::Terminate() GW::StoC::RemoveCallbacks(&GenericModifier_Entry); GW::StoC::RemoveCallbacks(&GenericValue_Entry); GW::StoC::RemoveCallbacks(&MapLoaded_Entry); - GW::StoC::RemoveCallbacks(&AgentRemove_Entry); GW::StoC::RemoveCallbacks(&AgentState_Entry); GW::Chat::DeleteCommand(&ChatCmd_HookEntry); @@ -697,6 +703,8 @@ void PartyDamage::Draw(IDirect3DDevice9*) } // @Cleanup: Only call when the party window has been moved or updated + const clock_t combat_time = GetEffectiveCombatTime(); + if (party_agent_ids_by_index.empty() || !RecalculatePartyPositions()) { return; } @@ -767,7 +775,7 @@ void PartyDamage::Draw(IDirect3DDevice9*) ImGui::Text(ICON_FA_TINT); ImGui::PopStyleColor(); ImGui::SameLine(); - const uint32_t bleed_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[0] * 1000.0 / static_cast(accumulated_combat_time_ms))); + const uint32_t bleed_dps = (combat_time == 0) ? 0 : static_cast(std::llround(cond_damage[0] * 1000.0 / static_cast(combat_time))); ImGui::Text("%d/s", bleed_dps); ImGui::SameLine(); @@ -775,14 +783,14 @@ void PartyDamage::Draw(IDirect3DDevice9*) ImGui::Text(ICON_FA_FIRE); ImGui::PopStyleColor(); ImGui::SameLine(); - const uint32_t burn_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[3] * 1000.0 / static_cast(accumulated_combat_time_ms))); + const uint32_t burn_dps = (combat_time == 0) ? 0 : static_cast(std::llround(cond_damage[3] * 1000.0 / static_cast(combat_time))); ImGui::Text("%d/s", burn_dps); ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(120, 200, 80, 255)); ImGui::Text(ICON_FA_TINT); ImGui::PopStyleColor(); ImGui::SameLine(); - const uint32_t poison_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[1] * 1000.0 / static_cast(accumulated_combat_time_ms))); + const uint32_t poison_dps = (combat_time == 0) ? 0 : static_cast(std::llround(cond_damage[1] * 1000.0 / static_cast(combat_time))); ImGui::Text("%d/s", poison_dps); ImGui::SameLine(); @@ -790,7 +798,7 @@ void PartyDamage::Draw(IDirect3DDevice9*) ImGui::Text(ICON_FA_SKULL); ImGui::PopStyleColor(); ImGui::SameLine(); - const uint32_t disease_dps = (accumulated_combat_time_ms == 0) ? 0 : static_cast(std::llround(cond_damage[2] * 1000.0 / static_cast(accumulated_combat_time_ms))); + const uint32_t disease_dps = (combat_time == 0) ? 0 : static_cast(std::llround(cond_damage[2] * 1000.0 / static_cast(combat_time))); ImGui::Text("%d/s", disease_dps); } @@ -881,7 +889,7 @@ void PartyDamage::Draw(IDirect3DDevice9*) } if (settings.show_dps && settings.show_damage && entry->damage > 0) { - const uint32_t dps = accumulated_combat_time_ms == 0 ? 0 : static_cast(std::llround(static_cast(entry->damage) * 1000.0 / static_cast(accumulated_combat_time_ms))); + const uint32_t dps = combat_time == 0 ? 0 : static_cast(std::llround(static_cast(entry->damage) * 1000.0 / static_cast(combat_time))); snprintf(buffer, buffer_size, "%d/s", dps); const float dps_text_x = x + width * 0.75f; draw_list->AddText(ImVec2(dps_text_x, text_y), IM_COL32(255, 255, 255, 255), buffer);