Hrrmph... proof of concept.
Tried to think as a caster
<<Center>> = "Rest"
<< Left = "How fast to OOM at current mana expenditure" (the more left the slider the faster we're going oom)
Right >> = "How fast to mana-full at current mana-gain" (the more right, the faster we're gaining full mana)
This makes it so we don't need a static MPS range, rather the user can set a "warning time" before the slider will start moving.
In example code warning time = 30, 10 segments for each side, slider moves 1 notch at 30sec to OOM, another notch at 27sec to OOM and so on until full left at 3sec to OOM.
Similar for right side with slider closer to center for slow mana-regen, further right for mana-tide/innervate etc.
Code:
local frame = CreateFrame("Frame")
frame:SetScript("OnEvent",
function(self,event,...)
return self[event] and self[event](...)
end)
frame:RegisterEvent("PLAYER_LOGIN")
-- These would be options
local averageOver = 20 -- delta segments to average
local polling = 0.5 -- changing this changes the averaging timeframe (10*1sec segments = 10sec, 10*2sec segments = 20sec to get rolling average etc)
local time_to_either_end = 30 -- middle of bar to either end in seconds (leftmost = oom, rightmost = full mana)
local bar_segments = {10,1,10} -- 10 negative, 1 steady, 10 positive
-- function upvalues
local tinsert, tremove, ipairs, floor, abs, max = tinsert, tremove, ipairs, floor, abs, max
local UnitPower, UnitPowerMax = UnitPower, UnitPowerMax
local mDeltas = {} -- temp table
local lastMana
local function getRollingMPS()
local mps_per_sec
local mana = UnitPower("player",SPELL_POWER_MANA)
local now = GetTime()
local delta = {mana-lastMana, now}
lastMana = mana
table.insert(mDeltas, 1, delta) -- always insert at first position
if mDeltas[averageOver+1] then mDeltas[averageOver+1] = nil end -- remove the tail to keep the size down
local sum, startt, endt = 0
for i,delta in ipairs(mDeltas) do
if i==1 then
endt = delta[2]
else
startt = delta[2]
end
sum = sum + delta[1]
if i==averageOver then break end
end
mps_per_sec = (startt and endt and startt~=endt) and sum/(endt-startt) or 0
return mps_per_sec
end
local function Update(self, elapsed)
self.lastTime = self.lastTime + elapsed
if self.lastTime >= polling then
local barsegment, time_per_segment -- -10.....-1,0,1.....10
local rollingMPS = getRollingMPS()
local manaMax = UnitPowerMax("player",SPELL_POWER_MANA)
local mana = UnitPower("player",SPELL_POWER_MANA)
-- edge cases first: oom, full mana, 0 mps
if rollingMPS == 0 then
if mana==manaMax then
barsegment = 0
elseif mana == 0 then
barsegment = -bar_segments[1]
end
else
local mana_to_full = manaMax - mana
if rollingMPS > 0 then -- gaining mana
if mana_to_full > 0 then
local time_to_full = mana_to_full/rollingMPS
time_per_segment = time_to_either_end/bar_segments[3]
barsegment = floor((bar_segments[3]+1)-(min(max(time_to_full,time_per_segment),time_to_either_end)/time_per_segment)+0.5)
else
barsegment = 0
end
else -- losing mana
if mana == 0 then
barsegment = -bar_segments[1]
else
local time_to_empty = mana/abs(rollingMPS)
time_per_segment = time_to_either_end/bar_segments[1]
barsegment = -floor((bar_segments[1]+1)-(min(max(time_to_empty,time_per_segment),time_to_either_end)/time_per_segment)+0.5)
end
end
end
barsegment = barsegment or 0
frame.slider:SetValue(barsegment)
_G[frame.slider:GetName().."Text"]:SetText(barsegment)
self.lastTime = 0
end
end
local updateEngine = CreateFrame("Frame")
function frame.PLAYER_LOGIN()
local slider = CreateFrame("Slider","TestRollingManaSlider",UIParent,"OptionsSliderTemplate")
slider:SetPoint("TOP",0,-50)
slider:SetSize(200,30)
slider:SetMinMaxValues(-10,10)
slider:SetValueStep(1)
slider:SetValue(0)
slider:EnableMouse(false)
_G[slider:GetName().."Low"]:SetText("-10")
_G[slider:GetName().."Text"]:SetText("0")
_G[slider:GetName().."High"]:SetText("10")
frame.slider = slider
updateEngine.lastTime = 0
lastMana = UnitPower("player",SPELL_POWER_MANA)
updateEngine:SetScript("OnUpdate", Update)
end