healprediction: Support 5.2 absorb prediction
Haleth opened this issue ยท 17 comments
5.2 adds support for absorb amounts on health bars similar to the current healing prediction bars. There's also a glow texture that shows on overabsorbs.
https://github.com/Ketho/wow-ui-source/blob/ptr/FrameXML/UnitFrame.lua
https://github.com/Ketho/wow-ui-source/blob/ptr/FrameXML/UnitFrame.xml
Phanx, what are your reasons for this to be a separate element from HealPrediction?
I don't care about absorbs on any class I play, and don't want to see them. Also, absorbs are not the same thing as heals.
I used Haleth's code after failing at the statusbar solution as he did. Here is a slightly reworked version of it that hopefully would make Phanx happy.
--[[ Element: Heal Prediction Bar
Handle updating and visibility of the heal prediction and total absorb bars.
Widget
HealPrediction - A table containing `myBar`, `otherBar`, `absorbBar`
and `overAbsorbGlow`
Sub-Widgets
myBar - A Texture used to represent your incoming heals.
otherBar - A Texture used to represent other peoples incoming heals.
absorbBar - A Texture used to represent total absorbs.
overAbsorbGlow - A Texture used to represent overabsorbs.
Notes
A default texture will be applied if the UI widget doesn't have a texture defined.
Options
.maxOverflow - Defines the maximum amount of overflow past the end of the
health bar.
Examples
local health = self.Health
local myBar = health:CreateTexture(nil, 'OVERLAY')
myBar:SetPoint('TOP')
myBar:SetPoint('BOTTOM')
local otherBar = health:CreateTexture(nil, 'OVERLAY')
otherBar:SetPoint('TOP')
otherBar:SetPoint('BOTTOM')
local absorbBar = health:CreateTexture(nil, 'OVERLAY')
absorbBar:SetPoint('TOP')
absorbBar:SetPoint('BOTTOM')
local overAbsorbGlow = health:CreateTexture(nil, 'OVERLAY')
overAbsorbGlow:SetPoint('TOP')
overAbsorbGlow:SetPoint('BOTTOM')
overAbsorbGlow:SetPoint('LEFT', health, 'RIGHT', -7, 0)
-- Register with oUF
self.HealPrediction = {
myBar = myBar,
otherBar = otherBar,
absorbBar = absorbBar,
overAbsorbGlow = overAbsorbGlow,
maxOverflow = 1.05,
}
Hooks
Override(self) - Used to completely override the internal update function.
Removing the table key entry will make the element fall-back
to its internal function again.
]]
local _, ns = ...
local oUF = ns.oUF
local UpdateFillBar = function(frame, previousTexture, bar, amount, totalMax)
if(amount == 0) then
bar:Hide()
return previousTexture
end
bar:SetPoint('TOPLEFT', previousTexture, 'TOPRIGHT', 0, 0)
bar:SetPoint('BOTTOMLEFT', previousTexture, 'BOTTOMRIGHT', 0, 0)
local totalWidth = frame.Health:GetWidth()
local barWidth = (amount / totalMax) * totalWidth
bar:SetWidth(barWidth)
bar:Show()
return bar
end
local Update = function(self, event, unit)
if(self.unit ~= unit) then return end
local hp = self.HealPrediction
if(hp.PreUpdate) then hp:PreUpdate(unit) end
local myIncomingHeal = UnitGetIncomingHeals(unit, 'player') or 0
local allIncomingHeal = UnitGetIncomingHeals(unit) or 0
local totalAbsorb, overAbsorb
local health, maxHealth = UnitHealth(unit), UnitHealthMax(unit)
if(health + allIncomingHeal > maxHealth * hp.maxOverflow) then
allIncomingHeal = maxHealth * hp.maxOverflow - health
end
if(allIncomingHeal < myIncomingHeal) then
myIncomingHeal = allIncomingHeal
allIncomingHeal = 0
else
allIncomingHeal = allIncomingHeal - myIncomingHeal
end
if(hp.absorbBar) then
totalAbsorb = UnitGetTotalAbsorbs(unit) or 0
if(health + myIncomingHeal + allIncomingHeal + totalAbsorb >= maxHealth) then
if(totalAbsorb > 0) then
overAbsorb = true
end
totalAbsorb = max(0, maxHealth - (health + myIncomingHeal + allIncomingHeal))
end
if(hp.overAbsorbGlow) then
if(overAbsorb) then
hp.overAbsorbGlow:Show()
else
hp.overAbsorbGlow:Hide()
end
end
end
local previousTexture = self.Health:GetStatusBarTexture()
if(hp.myBar) then
previousTexture = UpdateFillBar(self, previousTexture, hp.myBar, myIncomingHeal, maxHealth)
end
if(hp.otherBar) then
previousTexture = UpdateFillBar(self, previousTexture, hp.otherBar, allIncomingHeal, maxHealth)
end
if(hp.absorbBar) then
previousTexture = UpdateFillBar(self, previousTexture, hp.absorbBar, totalAbsorb, maxHealth)
end
if(hp.PostUpdate) then
return hp:PostUpdate(unit)
end
end
local Path = function(self, ...)
return (self.HealPrediction.Override or Update) (self, ...)
end
local ForceUpdate = function(element)
return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end
local Enable = function(self)
local hp = self.HealPrediction
if(hp) then
hp.__owner = self
hp.ForceUpdate = ForceUpdate
self:RegisterEvent('UNIT_HEAL_PREDICTION', Path)
self:RegisterEvent('UNIT_MAXHEALTH', Path)
self:RegisterEvent('UNIT_HEALTH', Path)
if(not hp.maxOverflow) then
hp.maxOverflow = 1.05
end
if(hp.myBar and hp.myBar:IsObjectType'Texture' and not hp.myBar:GetTexture()) then
hp.myBar:SetTexture([[Interface\TargetingFrame\UI-StatusBar]])
end
if(hp.otherBar and hp.otherBar:IsObjectType'Texture' and not hp.otherBar:GetTexture()) then
hp.otherBar:SetTexture([[Interface\TargetingFrame\UI-StatusBar]])
end
if(hp.absorbBar) then
self:RegisterEvent('UNIT_ABSORB_AMOUNT_CHANGED', Path)
if(hp.absorbBar:IsObjectType'Texture' and not hp.absorbBar:GetTexture()) then
hp.absorbBar:SetTexture([[Interface\RaidFrame\Shield-Fill]])
end
if(hp.overAbsorbGlow) then
if(hp.overAbsorbGlow:IsObjectType'Texture' and not hp.overAbsorbGlow:GetTexture()) then
hp.overAbsorbGlow:SetTexture([[Interface\RaidFrame\Shield-Overshield]])
end
if(not hp.overAbsorbGlow:GetBlendMode()) then
hp.overAbsorbGlow:SetBlendMode('ADD')
end
end
end
return true
end
end
local Disable = function(self)
local hp = self.HealPrediction
if(hp) then
self:UnregisterEvent('UNIT_HEAL_PREDICTION', Path)
self:UnregisterEvent('UNIT_MAXHEALTH', Path)
self:UnregisterEvent('UNIT_HEALTH', Path)
if(hp.absorbBar) then
self:UnregisterEvent("UNIT_ABSORB_AMOUNT_CHANGED", Path)
end
end
end
oUF:AddElement('HealPrediction', Path, Enable, Disable)
Blizzard anchors the absorbbar to the heal prediction bars if present. If we want to mimic the default behavior, we have to make the absorbbar check for heal prediction too, meaning duplicate code from the heal prediction element. Wouldn't it be better to extend the heal prediction element to support absorbs and add a boolean like self.HealPrediction.showAbsorbs to allow layouts to disable their display? Do you have other ideas about this?
Here is the working code I'm using now, I switched to blizzard's method of using textures because adding the absorb as a status bar didn't work for me (only after reloads). Obviously this needs a few more boolean checks if this were to be added but you get the idea.
https://github.com/Haleth/FreeUI/blob/master/FreeUI/oUF/elements/healprediction.lua
Looks good. You don't need to call SetPoint in your layout though, only for the absorb glow (and you can embed that in the Enable function as well).
I wouldn't embed the absorb glow positioning in Enable so that layouts can position it themselves. I use SetPoint to define the height of the elements as height is not changed in Update, you are right that we don't need to anchor them as UpdateFillBar does this. Should we also pass myIncomingHeal, allIncomingHeal, totalAbsorb and overAbsorb to PostUpdate? Phanx, is this implementation ok with you? If you don't define absorbBar, the element won't register the events for it and won't take care of it.
We could still check if the absorb glow is positioned already, and if not still position it. Passing those values to PostUpdate sounds like a good idea as well.
Blizzard's heal prediction/absorb code doesn't use UNIT_HEALTH_FREQUENT. I assume that registering this won't make any difference and the amounts are only updated through the already registered events. The positioning happens automatically because it's anchored to the health, so we don't need to register anything for that.
I'm not too savvy on GitHub, I'm not exactly sure how to properly issue a pull request. You can do it, if you like. It's not my code anyway, mostly Blizzard's.
I removed all the SetPoint calls from the example (height does get set in UpdateFillBar). Also refactored how the overAbsorbGlow is handled. Please tell me if you have further remarks. If not, Haleth could issue a pull request as it was his code I used.
The last point I have is whether we should check for self.Health.frequentUpdates and register UNIT_HEALTH_FREQUENT as this is done in the health element?
Blizzard uses an OnUpdate script (or UNIT_HEALTH if predictedHealth is not set by the user) for the health bar and pulls the values for health and maxhealth from the health bar values. We use UnitHealth() and UnitHealthMax() instead. UnitHealthMax will be correct as we register UNIT_MAXHEALTH, but UnitHealth will probably lag behind. It would be anchored corretly, but the values of allIncomingHeal and myIncomingHeal might be incorrect.
As long as absorbs can be disabled, I have no objections to the elements being merged.
Just discovered I could anchor to the fill texture of the health bar. Thanks for the tip. About frequent updates, as the values are restricted by the current value of health, the heal prediction should be updated as often as the health bar (be it frequent or not), otherwise they will go out of the health bar. That should be possible with the couple latest PR.
You could also get the health values by using self.Health:GetValue() and self:Health:GetMinMaxValues()