strange implementations in health prediction
cjhwang0222 opened this issue ยท 16 comments
oUF/elements/healthprediction.lua
Lines 108 to 112 in 5a6cf97
Replacing the value of healAbsorb with that of Health at the line would bias the calculations after the line. healAbsorb Bar's max value should not be over health bar's length but shouldn't it be done later?
https://github.com/tomrus88/BlizzardInterfaceCode/blob/37e971677afa43e2d2a6cd39dff2e34aeaedf180/Interface/FrameXML/UnitFrame.lua#L273-L279 reference to stock ui code
The healAbsorb is placed above the the health bar because healing someone with a healabsorb would not change their actual health value but deplete the healabsorb first. This is the default ui behavior and it seems logical to me.
Yet, I'm a bit out of context right now, so feel free to object.
first of all, I'm not English speaker, so please consider it :)
Let's say an example, a unit whose current health is 1000 and got a debuff of heal absorb 2000, and there is incoming heal as 2400 which can erase the debuff and would give the unit some health.
so the math should be like this; the incoming heal would covers the heal absorb(2400 - 2000) and the remained 400 incoming heal should be shown. BUT, the code overrides the heal absorb (2000 -> 1000) before considering incoming heal, finally, the incoming heal (2400 - 1000 = 1400) would be shown, which isn't right. After that, if the remained incoming heal exceed the max health then it should be capped based no the max overflow.
another example
If the unit got 400 incoming heal, then the expected heal absorb should be 1600. In this point, as you mentioned the heal absorb should not be over health bar, it should be overrided as 1000, the unit's current health.
BUT, the code overrides the heal absorb FIRST, the incoming heal covers 400 of 1000 heal absorb so the 600 heal absorb would be shown.
Am I right?
p.s. Happy New Year!
If you want to test your assumptions, run the following code on https://www.lua.org/cgi-bin/demo and adjust the values accordingly.
This is basically the body of the update function of the healthprediction element. overflow
is a substitution for element.maxOverflow
local overflow = 1
local myIncomingHeal = 2400
local allIncomingHeal = 2400
local absorb = 0
local healAbsorb = 2000
local health, maxHealth = 1000, 5000
local hasOverHealAbsorb = false
if(health < healAbsorb) then
hasOverHealAbsorb = true
healAbsorb = health
end
if(health - healAbsorb + allIncomingHeal > maxHealth * overflow) then
allIncomingHeal = maxHealth * overflow - health + healAbsorb
end
local otherIncomingHeal = 0
if(allIncomingHeal < myIncomingHeal) then
myIncomingHeal = allIncomingHeal
else
otherIncomingHeal = allIncomingHeal - myIncomingHeal
end
local hasOverAbsorb = false
if(health - healAbsorb + allIncomingHeal + absorb >= maxHealth or health + absorb >= maxHealth) then
if(absorb > 0) then
hasOverAbsorb = true
end
if(allIncomingHeal > healAbsorb) then
absorb = math.max(0, maxHealth - (health - healAbsorb + allIncomingHeal))
else
absorb = math.max(0, maxHealth - health)
end
end
if(healAbsorb > allIncomingHeal) then
healAbsorb = healAbsorb - allIncomingHeal
else
healAbsorb = 0
end
print("mine:", myIncomingHeal)
print("others:", otherIncomingHeal)
print("absorb:", absorb)
print("healAbsorb:", healAbsorb)
print("overAbsorb:", hasOverAbsorb)
print("overHealAbsorb:", hasOverHealAbsorb)
So, yes it seems the returned values are not the expected ones. I'll evaluate this against Blizzard's implementation.
Points of interest here:
- Does
UnitGetIncomingHeals
return raw values or does it account for healAbsorbs?
Judging by https://github.com/tomrus88/BlizzardInterfaceCode/blob/37e971677afa43e2d2a6cd39dff2e34aeaedf180/Interface/FrameXML/UnitFrame.lua#L325 it should return raw values - If we need to subtract
healAbsorb
from incoming heals, how do we calculatemyIncomingHeals
after that (how to split the reduced healing amount between the player's and others' heals)?
There must be some sort of dummy vars for graphics and ones for actual math.
Ah. and thank you for letting me know such a site to test some Lua code
Use the version I posted above. I have stripped the part where the bar values are set and substituted with the prints.
I've tried my own version(?).
local overflow = 1
local allIncomingHeal = 2400
local absorb = 0
local healAbsorb = 2000
local health, maxHealth = 1000, 5000
-- for math
if (healAbsorb > allIncomingHeal) then
healAbsorb = healAbsorb - allIncomingHeal
allIncomingHeal = 0
else
allIncomingHeal = allIncomingHeal - healAbsorb
healAbsorb = 0
end
-- for graphic
local hasOverAbsorb = false
local remaining_heal = math.max(allIncomingHeal - healAbsorb, 0)
if(health + remaining_heal + absorb >= maxHealth * overflow) then
hasOverAbsorb = true
if(health + remaining_heal > maxHealth * overflow) then
absorb = 0
else
absorb = math.min(maxHealth * overflow - (health + remaining_heal), absorb)
end
end
local hasOverHealAbsorb = false
if(health < healAbsorb) then
hasOverHealAbsorb = true
healAbsorb = health
end
print("allHeal:", allIncomingHeal)
print("overAbsorb:", hasOverAbsorb)
print("absorb:", absorb)
print("overHealAbsorb:", hasOverHealAbsorb)
print("healAbsorb:", healAbsorb)
I don't know the priority of heal consumption (mine first? other's first?) so I gave it up to split them. except that, I think this one works as theory.
Do you know where/how I can get a heal absorb debuff? I just want to verify the UnitGetIncomingHeals
returns.
well... mainly some boss mobs in raid?
I just googled.
https://us.battle.net/forums/en/wow/topic/15140594556
Full(?) list of heal absorb spells. Harmful Strike is used by some mobs in Tanaan Jungle. UnitGetIncomingHeals
returns raw (and sometimes incorrect) values, so it does not account for heal absorbs.
@p3lim
Why is this needed? The healthprediction element should handle those.
@selfaddicted
My current quick and dirty solution would be something like that:
local overflow = 1
local myIncomingHeal = 2400
local allIncomingHeal = 2400
local absorb = 3600
local healAbsorb = 2000
local health, maxHealth = 1000, 5000
local hasOverAbsorb = false
local hasOverHealAbsorb = false
local otherIncomingHeal = 0
if(allIncomingHeal < myIncomingHeal) then
myIncomingHeal = allIncomingHeal
else
otherIncomingHeal = allIncomingHeal - myIncomingHeal
end
if(healAbsorb >= allIncomingHeal) then
healAbsorb = healAbsorb - allIncomingHeal
allIncomingHeal = 0
myIncomingHeal = 0
otherIncomingHeal = 0
else
local reduced = allIncomingHeal - healAbsorb
local ratio = reduced / allIncomingHeal
myIncomingHeal = myIncomingHeal * ratio
otherIncomingHeal = otherIncomingHeal * ratio
allIncomingHeal = reduced
healAbsorb = 0
end
if(health - healAbsorb + allIncomingHeal > maxHealth * overflow) then
allIncomingHeal = maxHealth * overflow - health + healAbsorb
end
if(health - healAbsorb + allIncomingHeal + absorb >= maxHealth or health + absorb >= maxHealth) then
if(absorb > 0) then
hasOverAbsorb = true
end
if(allIncomingHeal > healAbsorb) then
absorb = math.max(0, maxHealth - (health - healAbsorb + allIncomingHeal))
else
absorb = math.max(0, maxHealth - health)
end
end
if(health < healAbsorb) then
hasOverHealAbsorb = true
healAbsorb = health
end
print("mine:", myIncomingHeal)
print("others:", otherIncomingHeal)
print("all:", allIncomingHeal)
print("absorb:", absorb)
print("healAbsorb:", healAbsorb)
print("overAbsorb:", hasOverAbsorb)
print("overHealAbsorb:", hasOverHealAbsorb)
If I get the idea right, we should have either healAbsorb
or allIncomingHeal
, whichever is bigger will negate the other. This distributes the healing reduced by heal absorbs between the player's and others' heals. It could be cleaned up a bit maybe, will test a bit more first.
Ah, Blizzard displays the incoming heals on top of the healAbsorb texture. It makes sense because this way it will show how much of the heal absorb will be "eaten" by the heal. The example in oUF's healthprediction element suggests to anchor the element.myBar
to the texture of the health bar instead of the texture of the healAbsorbBar. The question is now is whether to fix the anchoring suggestion (the amount of incoming heals will need to be adjusted by the amount by which healAbsorb is reduced to accommodate the current health) and inform layout authors to adjust, or apply something like above.
Either way I take responsibility for it, you can beat me now :(
I pushed a branch in which I mimic the default UI exactly and fix the edge cases where an overhealabsorb is shown (healAbsorb = health). See master...patch-healthprediction for details. What layouts would need to change is the anchoring of the self.HealthPrediction.myBar
. Technically it will not break existing layouts (if they don't adapt, they won't get Lua errors) and, because the element returns wrong values currently, it won't hurt to merge it either way.
You can use this for testing out-of-game:
local maxOverflow = 1
local myIncomingHeal = 2400
local allIncomingHeal = 2400
local absorb = 0
local healAbsorb = 2000
local health, maxHealth = 1000, 5000
local hasOverHealAbsorb = false
if(health < healAbsorb) then
hasOverHealAbsorb = true
local reduction = healAbsorb - health
if(allIncomingHeal > reduction) then
myIncomingHeal = myIncomingHeal * (allIncomingHeal - reduction) / allIncomingHeal
allIncomingHeal = allIncomingHeal - reduction
else
allIncomingHeal = 0
myIncomingHeal = 0
end
healAbsorb = health
end
if(health - healAbsorb + allIncomingHeal > maxHealth * maxOverflow) then
allIncomingHeal = maxHealth * maxOverflow - health + healAbsorb
end
local otherIncomingHeal = 0
if(allIncomingHeal < myIncomingHeal) then
myIncomingHeal = allIncomingHeal
else
otherIncomingHeal = allIncomingHeal - myIncomingHeal
end
local hasOverAbsorb = false
if(health - healAbsorb + allIncomingHeal + absorb >= maxHealth or health + absorb >= maxHealth) then
if(absorb > 0) then
hasOverAbsorb = true
end
if(allIncomingHeal > healAbsorb) then
absorb = math.max(0, maxHealth - (health - healAbsorb + allIncomingHeal))
else
absorb = math.max(0, maxHealth - health)
end
end
print('mine:', myIncomingHeal)
print('others:', otherIncomingHeal)
print('all:', allIncomingHeal)
print('absorb:', absorb)
print('healAbsorb:', healAbsorb)
print('overAbsorb:', hasOverAbsorb)
print('overHealAbsorb:', hasOverHealAbsorb)
@ls- will take a look at it tomorrow, but feel free to give it a go.
After a lengthy discussion with @ls- we figured the above solution won't work without dynamic re-anchoring of the absorb bar (depending on whether incoming heals exceed the healAbsorb or not). Since we want to stay away from dynamic anchoring (layouts will have to use PostUpdate or we'll have to account for possible statusbar orientations and stuff in update), we'll stick to a solution similar to the one in #409 (comment) (Val will punch it pretty first ๐ ๐ซ)
Wuuuut? @Rainrider, you want to stay away from dynamic reanchoring, I'm pro-dynamic-anchoring :D
Thing is, current implementation is almost correct, inc heal values need slight adjustment, but other than that it's okay. Thing is, we try to keep things as Blizzlike as possible. So you can attach 3 out of 4 bars permanently to each others' bar textures (statusbar:GetStatusBarTexture()
), health > reversed heal absorb > my inc heals > others' inc heals, but damage absorb bar has to be anchored dynamically to the rightmost bar.
For instance, if inc heals overheal heal absorb, absorb bar should be attached to others' inc heals bar, but if heal absorb is still present, damage absorb bar should be attached to our health bar.
That's how Blizz UI works.
You see everything at once.
However, there's another non-Blizzlike solution that recalculates all values, primarily inc heals and heal absorbs which was discussed in this thread earlier, kinda. This way people will be able to attach reversed heal absorb bar to the health bar, and other 3 bars can be appended to each other w/o any need to reattach them later.
I have a working non-Blizzlike solution, @Rainrider already tested it, but I want to adjust maths for our Blizzlike approach and update docs accordingly by adding PostUpdate func example.
That's what I'll do later when I get back home from work.
Done. See #415 for more info.