From c30dfae7a6e3663d89774dfa7f3e0064c98084fa Mon Sep 17 00:00:00 2001 From: Brues <5278969+brues-code@users.noreply.github.com> Date: Fri, 22 May 2026 22:40:26 -0500 Subject: [PATCH] xpbar: track rested-XP gain rate + time to cap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The data frame now subscribes to UPDATE_EXHAUSTION and stores a sliding 5-minute window of (time, exhaustion) samples. The XP tooltip uses the first/last samples in the window to compute rest gain per hour, and projects time to reach UnitXPMax * 1.5 at the current rate. Useful for Turtle WoW's tent mechanic — when you stand under a tent you accumulate rested XP at an accelerated rate, and now you can see how long until you cap. Samples reset on PLAYER_ENTERING_WORLD (zone change can change the gain rate) and PLAYER_LEVEL_UP (Blizzard rescales exhaustion proportionally on level-up so prior samples no longer describe the same gain curve). The rate/time lines only appear when there are at least 2 samples in the window and the gain is positive — so they stay hidden when not actively gaining rested XP. --- modules/xpbar.lua | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/modules/xpbar.lua b/modules/xpbar.lua index 171c6643..fd5dd68a 100644 --- a/modules/xpbar.lua +++ b/modules/xpbar.lua @@ -2,18 +2,35 @@ pfUI:RegisterModule("xpbar", "vanilla:tbc", function () local rawborder, default_border = GetBorderSize() local parse_faction = SanitizePattern(FACTION_STANDING_INCREASED) + -- Rested-XP gain tracking constants + local REST_WINDOW = 300 -- seconds of sliding-window samples for rate calc + local REST_CAP_MUL = 1.5 -- target rested cap = UnitXPMax * 1.5 + local data = CreateFrame("Frame", "pfExperienceBarData", UIParent) + data.rest_samples = {} data:RegisterEvent("CHAT_MSG_COMBAT_FACTION_CHANGE") data:RegisterEvent("PLAYER_ENTERING_WORLD") data:RegisterEvent("PLAYER_LEVEL_UP") + data:RegisterEvent("UPDATE_EXHAUSTION") data:RegisterEvent("UPDATE_FACTION") data:SetScript("OnEvent", function() if event == "PLAYER_ENTERING_WORLD" then this.starttime = GetTime() this.startxp = UnitXP("player") or 0 + -- zone change can change resting location / rate; start fresh + this.rest_samples = {} elseif event == "PLAYER_LEVEL_UP" then -- add previously gained experience to the session this.startxp = this.startxp - UnitXPMax("player") + -- level-up rescales exhaustion proportionally; old samples no longer + -- describe the same gain trajectory + this.rest_samples = {} + elseif event == "UPDATE_EXHAUSTION" then + local now, exh = GetTime(), GetXPExhaustion() or 0 + table.insert(this.rest_samples, { now, exh }) + while this.rest_samples[1] and (now - this.rest_samples[1][1]) > REST_WINDOW do + table.remove(this.rest_samples, 1) + end elseif event == "CHAT_MSG_COMBAT_FACTION_CHANGE" then local _,_, faction, amount = string.find(arg1, parse_faction) this.faction = faction or this.faction @@ -73,6 +90,28 @@ local function OnEnter(self) table.insert(lines, { "|cffff8800" .. exh_perc .. "% is the current max cap." }) table.insert(lines, { "|cffff8800Report to TWoW if you want a proper display." }) end + + -- Rested gain rate + time to UnitXPMax * REST_CAP_MUL, when sampled + -- UPDATE_EXHAUSTION events span a meaningful interval (e.g., resting + -- under a Turtle WoW tent generates a fast continuous stream). + local samples = data.rest_samples + local n = table.getn(samples) + if n >= 2 then + local first, last = samples[1], samples[n] + local span = last[1] - first[1] + local gain = last[2] - first[2] + if span > 0 and gain > 0 then + local rate_per_sec = gain / span + local rate_per_hour = floor(rate_per_sec * 3600) + table.insert(lines, { T["Rested Gain"], "|cff5555ff+" .. rate_per_hour .. " / h" }) + + local cap = xpmax * REST_CAP_MUL + if exh < cap then + local sec = (cap - exh) / rate_per_sec + table.insert(lines, { T["Time to Rested Cap"], "|cffffffff" .. SecondsToTime(sec) }) + end + end + end end table.insert(lines, { "" }) table.insert(lines, { T["This Session"], "|cffffffff" .. session })