oUF

99.9k Downloads

healprediction: Support 5.2 absorb prediction

Haleth opened this issue ยท 17 comments

commented

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

commented

I'd strongly prefer this to be a separate element from HealPrediction.

commented

Phanx, what are your reasons for this to be a separate element from HealPrediction?

commented

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.

commented

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)
commented

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?

commented

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

commented

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).

commented

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.

commented

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.

commented

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.

commented

Rainrider@ef52f03

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?

commented

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.

commented

As long as absorbs can be disabled, I have no objections to the elements being merged.

commented

Should be solved with freebaser's push request.

commented

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.

commented

You could also get the health values by using self.Health:GetValue() and self:Health:GetMinMaxValues()

commented

Right but you have to recalculate anyway, and eventually reanchor the textures (depending on your layout), each time these values change.