1ps3 / Weak-auras

2 stars 2 forks source link

Bugs in "IpseFury" #1

Open Arcitec opened 4 years ago

Arcitec commented 4 years ago

Hey, I don't have (and don't want) a wago.io account, so I am messaging you here. Hopefully you discover this message. I've found multiple bugs in the https://wago.io/IpseFury aura.

  1. Trigger 2: immototal is being set to 70 when casting the Havoc Immolation Aura. The real amount is and has always been 80. Fixing this line fixes the prediction. You can verify by auto-attacking a training dummy, staying in combat (so that fury does not drain). Then hit immolation aura, place your mouse cursor on the predicted pixel of the bar, and then wait for it to fill with the whole immolation aura effect. You'll see that the bar actually fills 10 points more than what the WeakAura predicted, due to this bug. EDIT: Fixed in the code below.

  2. Problem 1 is actually complicated by things like this: https://www.wowhead.com/spell=211509/solitude (adds +10% fury/pain generation if nobody is nearby, meaning it will actually grant you 88 fury). The aura buff ID it grants (same in both havoc/vengeance) when nobody is nearby is https://www.wowhead.com/spell=211510/solitude. EDIT: I have fixed that bug now in the code below, although I have made a TODO note for further research about what happens if Solitude is lost while the aura is ticking (each tick may be lower then, and reach a lower total amount?).

  3. The immolation total calculation is pretty bad... the main problem is the bad use of SPELL_CAST_SUCCESS, which triggers AFTER the first energize event has already happened, so by triggering on the wrong event we MISS the FIRST TICK that has already happened. Should be using SPELL_AURA_APPLIED to detect it instead. EDIT: Fixed in the code below.

function(event, ...)
    local e = aura_env

    if (event == "UNIT_DISPLAYPOWER" and select(1, ...) == "player") or event == "PLAYER_ENTERING_WORLD" then

        e.prep = 0
        e.immototal = 0
        e.powerType = UnitPowerType("player")

    elseif event == "COMBAT_LOG_EVENT_UNFILTERED" then

        local timeStamp, subEvent, _, sourceGUID, _, _, _, destGUID, _, _, _, spellId, spellName = ...

        if sourceGUID == UnitGUID("player") then
            -- When casting Immolation Aura, the following sequence of events happens:
            -- SPELL_AURA_APPLIED (replaces any existing Immolation Aura, "killing" any remaining ticks)
            -- SPELL_ENERGIZE (1st tick happens immediately)
            -- SPELL_CAST_SUCCESS
            -- SPELL_ENERGIZE (multiple times, for all other ticks...)
            -- SPELL_AURA_REMOVED

            if subEvent == "SPELL_AURA_APPLIED" then
                --print(subEvent, spellId)
                if spellId == 203650 then
                    -- "Prepared" buff, lasts 10 sec, generating 8 fury per sec.
                    e.prep = GetTime() + 10
                elseif spellId == 258920 or spellId == 178740 then
                    -- Havoc or Vengeance Immolation Auras.

                    -- Check for "Solitude" world PvP buff, which grants +10% Fury/Pain generation.
                    local hasSolitude = false
                    for i=1,40 do
                        local buffId = select(10, UnitAura("player", i, "PLAYER HELPFUL"))
                        --print(buffId)
                        if buffId == 211510 then
                            hasSolitude = true
                        elseif buffId == nil then
                            break
                        end
                    end

                    -- Calculate the proper amount based on the +10% "Solitude" buff.
                    -- TODO: POTENTIAL BUG: What happens if solitude vanishes (friends arrive nearby) WHILE aura is counting?
                    if spellId == 258920 then
                        -- Havoc Immolation Aura, generates 80 fury over time.
                        e.immototal = hasSolitude and 88 or 80
                    else
                        -- Vengeance Immolation Aura, generates 20 pain over time.
                        e.immototal = hasSolitude and 22 or 20
                    end
                end
            elseif subEvent == "SPELL_ENERGIZE" then
                --print(subEvent, spellId)
                -- Look at the various Immolation Aura buff IDs.
                if (spellId == 258922 or spellId == 258920 or spellId == 178740 or spellId == 178741) then
                    local amount, overEnergize = select(15, ...)
                    local energy = amount + overEnergize

                    e.immototal = e.immototal - energy
                    if e.immototal < 0 then
                        e.immototal = 0
                    end

                    --print(subEvent, spellId, energy)
                end
            elseif subEvent == "UNIT_DIED" and destGUID == UnitGUID("player") then
                --print(subEvent)
                e.prep = 0
                e.immototal = 0
            elseif subEvent == "SPELL_AURA_REMOVED" then
                --print(subEvent, spellId)
                if spellId == 258920 or spellId == 178740 then
                    e.immototal = 0
                elseif spellId == 203650 then
                    e.prep = 0
                end
            end
        end
    end
end
  1. The Trigger 1: Overlay 1 and 2 are bugged. They set a "forward" bar amount if immototal (for example) is over 0. But if it's 0, they do not change the bar. This means that the final prediction "gets stuck" when there is nothing remaining. I.e. if the final prediction was "forward", 5, then that will stay forever when the bar has reached immototal 0. Because the bar "forward" amount is never being updated when the number is zero. Will need a separate fix, and is related to "problem 5" below. EDIT: Found out that it's a bug in WeakAuras2. If your overlay returns return (nothing/empty) and the bar has multiple overlays, the overlays all stay. They confirmed (in their repo) that it's a bug, which will be fixed by them... For now, we can just do return "forward", 0 to remove the overlay manually. EDIT2: Fixed in the code below for point 5.

  2. Trigger 1: Overlay 1 and 2 do not work nicely together since the latest update (which made predictions optional). Overlay 1 is always adding Prepared amount even when that option has been disabled. And Overlay 2 is totally ignoring the Immolation Aura amount, which means that the Prepared buff will NEVER render properly on the bars if Immolation Aura is active. The code of the two overlay calculations right now is a total spaghetti mess, they're wrong in many ways and need a complete rewrite... I'll work on that... EDIT: While working on this, I discovered that the Prepared talent from Legion has been deleted in Battle for Azeroth, so the entire "show Prepared prediction" and "overlay 2" code is pointless. But I am still gonna fix/refactor it to ensure that any future extra sources of ticking fury/pain can sit nicely together with the immolation aura. EDIT 2: I've cleaned up the Overlay code now. Code below:

Overlay 1:

function()
    local e = aura_env
    local c = e.config

    -- Handle "Immolation Aura" buff.
    -- NOTE: The return value is how many "Duration Info" units "past current position"
    -- that we want to point at. If that exceeds the bar max, it's automatically capped.
    if c.immoPred and e.immototal > 0 then
        return "forward", e.immototal
    else
        return "forward", 0
    end
end

Overlay 2:

function()
    local e = aura_env
    local c = e.config

    -- "Prepared" buff, generating 8 fury per sec for 10 seconds.
    -- NOTE: The return value is how many "Duration Info" units "past current position"
    -- that we want to point at. If that exceeds the bar max, it's automatically capped.
    if c.prepPred and e.prep > 0 then
        local prepAmount = (e.prep - GetTime()) * 8
        if prepAmount > 0 then
            -- Adjust by "Immolation Aura" offset, to ensure that the "Prepared" prediction
            -- sets beyond the right edge of the "Immolation Aura" prediction.
            -- NOTE: We use the literal "offset" (3rd) return value, otherwise the overlays
            -- would render on top of each other, causing ugly color blending.
            return "forward", prepAmount, (c.immoPred and e.immototal > 0) and e.immototal or nil
        end
    end

    return "forward", 0
end
  1. Actions: Custom Init code can be improved. The MarkAdjustment function calculates an offset as local offset = pixels * cost, but this creates in-between pixel values like "13.423", which in turn makes WoW draw non-pixel perfect bars, such as extra wide or extra thin bars. Basically it screws up the marker bar width. So I added a line of code after that, where I say: offset = round(offset) -- Lower accuracy (up to half a pixel off), but no smeared half-pixels. This makes the bars look great in more cases, and even though I call it "lower accuracy" it isn't really lower in any significant way (it's, at most, off by half a pixel due to the rounding); the rendering looks perfect.
                or 0
                local offset = round(pixels * cost)  -- round() lowers accuracy (up to half a pixel off), but no smeared half-pixels.
                b[v]:SetColorTexture(color[1],color[2],color[3],color[4])
                b[v]:SetWidth(c.width)
  1. Even after improving MarkAdjustment above, WoW's width rendering can still be "off" due to uiScale, but I dunno how to solve that, perhaps by multiplying each bar's width by the UI scale... I know that ElvUI has a "pixel perfect" feature where it draws its UI outlines perfectly, and I am guessing it does such UI scale calculations. I'd have to research how they do it, as well as whether the fix in "point 6" (above) would need rewriting too... EDIT: This is fixed by rescaling the entire bar's scale to make the bar pixel-perfect on the user's screen. There is no other way to fix this. We MUST have a pixel-perfect bar, and within that bar we MUST have pixel-perfect marks. Solved by adding a new function to the On Init code, and calling it on every MarkAdjustment (that may be overkill though, but I don't know if we can listen for "UI scale changed" events to only call it when needed, meh). Also note that we MUST still do the round() code in the point above too, to ensure we put our pixel-perfect mark at a pixel-perfect offset.
e.SetPixelPerfectScale = function()
    -- First calculate the virtual scale needed to get 1:1 pixel rendering. It is well-known
    -- that WoW divides "768 / screen height" to get its initial scale, and then further adjusts
    -- that number via the user's chosen UI Scale. The formula below is also well-known.
    local uiScale = UIParent:GetScale() -- The user's active UI scale setting.
    if aura_env.lastUIScale == uiScale then
        -- We'll only recalculate the scale if the master UI scale changes. Avoids doing the
        -- costly resolution calculations below each time. Won't react if the user changes
        -- desktop resolution, but so what?!
        return
    end
    local screenHeight = tonumber(string.match(GetCVar("gxWindowedResolution"), "%d+x(%d+)"), 10) -- More accurate than GetScreenHeight().
    local virtualPixelScale = (768 / screenHeight) / uiScale

    -- Now set the bar region's scale, so that all elements within it become pixel perfect.
    -- This ensures that our vertical "mark" bars are completely perfect and sharp and exactly
    -- as wide as the user requested, rather than being smeared.
    -- NOTE: If the user has manually set a non-1.0 scale for the master "WeakAuras" container,
    -- this fails (since our frame will be scaled by our parent's scale), but that's not our problem.
    aura_env.region:SetScale(virtualPixelScale)

    -- Remember that we have processed this UI scale.
    aura_env.lastUIScale = uiScale
end

e.MarkAdjustment = function()
    local e = aura_env
    local c = e.config
    local b = e.region.bar

    e.SetPixelPerfectScale() -- Ensure marks will be perfectly rendered.

    ....
  1. The "Spender Animation" is bugged. If you set its duration to 2 seconds and spam Chaos Strike twice, only the first one animates. That's because WoW ignores :Start() when an animation is already playing. The fix is very simple in Trigger 1 Duration Info:
function()
    local e = aura_env
    local r = e.region
    local c = e.config
    local b = r.bar

    e.prevPower = e.power
    e.power = UnitPower("player", e.powerType)

    if e.prevPower > e.power + 2 and b.spendBar and c.spend then
        local pixels = b:GetWidth()/e.powerMax
        local cost = e.prevPower - e.power
        local size = cost * pixels
        local color = c.spendColor
        b.spendBar.anim:Stop() -- Instantly stop ongoing anims, else Play() does nothing.
        b.spendBar:SetColorTexture(color[1],color[2],color[3],color[4])
        b.spendBar:SetWidth(size)
        b.spendBar:SetHeight(b:GetHeight())
        b.spendBar:SetPoint("TOPLEFT", e.power*pixels, 0)
        b.spendBar.anim:Play()
    end
    e.powerMax = UnitPowerMax("player", e.powerType)

    return e.power, e.powerMax, true
end
  1. The if not ... then statement in the Trigger 2 code is nonsense. It kills any logic, because it triggers anytime an event has zero arguments. Examples of events with zero arguments: OPTIONS, PLAYER_ENTERING_WORLD, UNIT_DISPLAYPOWER, etc. So because that's the first statement, all following elseif event == "PLAYER_ENTERING_WORLD" etc never fire. And we also needlessly clear all data (back to zero) at random intervals due to this bug. You should completely remove the if not ... then part. EDIT: Fixed above.

  2. Furthermore, PLAYER_REGEN_ENABLED is a pointless check since the frame is not registered for that event. And UNIT_DISPLAYPOWER needs an extra check to make sure the event is targeting the player (not a party member). EDIT: Fixed above.

  3. Whenever you gain Metamorphosis, the Immolation Aura prediction vanishes (is reset to 0) due to SPELL_AURA_REMOVED being fake-triggered for Immolation Aura by the Metamorphosis buff. Fixed in discussion below...

Anyway I hope this helps you!

Arcitec commented 4 years ago

Hehe, in case it isn't obvious yet from all edits, I am working on solving each bug above. I'm taking a break but will resume the bugfixing soon.

Arcitec commented 4 years ago

This is a work in progress. The only unfixed bug now is problem 7, and the footnote for problem 2. But I've worked on all of this for almost 4 hours, so I'll take a break for today @1ps3. If you're in the mood for fixing 7, or researching 2 (what happens when Solitude fades while aura is ticking), please let me know.

Arcitec commented 4 years ago

I have just had a brief look at researching point 7 and it brought me to this: https://www.reddit.com/r/WowUI/comments/95o7qc/other_how_to_pixel_perfect_ui_xpost_rwow/

Arcitec commented 4 years ago

I've solved point 7 now... so all that remains is researching point 2...

1ps3 commented 4 years ago

You don't need a wago account to comment.

Fury and the like is mostly borken anyway. I don't play anymore nor do I really have much interest in adding something for solitude. This was made a while ago and I literally have had no chance to look at it at all. I dropped this because of meta breaking auras basically and I couldn't be bothered working around it but if you want to update this feel free.

As for pixel perfect, wa already sets the snapping off so everything else is just the games issue. there's nothing you can do after setting whole values and setting your UI scale correctly.

1ps3 commented 4 years ago

That being said, if you want to update it feel free. I'll take a look at it and upload it.

Arcitec commented 4 years ago

Ahh, you've quit WoW? Fun fact: Between the time of making this ticket, and today, I came to the same conclusion, hehe. I'm quitting before the next billing in a few days. The game is too grindy for me.

I dropped this because of meta breaking auras basically and I couldn't be bothered working around it but if you want to update this feel free.

What does this mean? Meta breaking auras?

As for pixel perfect, wa already sets the snapping off so everything else is just the games issue. there's nothing you can do after setting whole values and setting your UI scale correctly.

Yeah, I managed to do it though. It involved calculating the inverse of how WoW scales on-screen pixels, and applying that as the scale for the bar's frame. As well as round() on the calculated marker offsets to ensure they sit at whole pixels. With the changes, the marks now render perfectly at any width and any scale.

That being said, if you want to update it feel free. I'll take a look at it and upload it.

Yeah I can do the update of your aura with the changes described above and post it here. Will do so in a moment!

Most of this code is actually contributed from my private fork of the aura, where I've redesigned everything and added features. :O But now that I'm quitting I dunno what to do about that. ;-)

1ps3 commented 4 years ago

Okay so when I was making this thing like a year ago, metamorphosis getting applied to the player caused every buff to be reapplied. That's why I didn't use the applied check to start counting. Obviously you can check for it but I never got around to it.

Arcitec commented 4 years ago

Ahh, woah that's interesting, I am gonna investigate that. In the meantime here's your aura with all fixes above! :-)

edit: removed incomplete aura

Arcitec commented 4 years ago

So the "todo list" is simple:

  1. Checking Solitude (argh I don't know people in the game well enough to help me with that, but eh it doesn't matter much since we reset immototal to 0 whenever the aura drops off, so even if we estimate that we should get 88 and we only get like 84 because solitude was lost during the immolation ticks (if its bonus is checked per-tick by the game), well, so what, it still resets to 0 when immolation ends, so any remaining "wrong" prediction will be cleared at the end).

  2. Gotta check that metamorphosis bug, ouch.

1ps3 commented 4 years ago

IIRC solitude was the pvp talent which is why I never bothered. You can't get the distance of something unless you have a Unit and can't guarentee that at all so I didn't even bother putting it in.

Arcitec commented 4 years ago

@1ps3 Yep but the Solitude talent gives an aura (Solitude) when nobody is nearby, which is how I solved it. The only question is what happens if someone gets near you (making you lose Solitude) or leaves (making you gain Solitude) while immolation aura was already underway. But if the game changes the immolation amount dynamically on each tick based on that, then there's nothing we can do, too complicated. ;-) Either way, it'll reset to 0 remaining when the immolation aura vanishes. So any wrong math due to gaining/losing solitude is gonna be no issue in real use. The prediction would be off between 0% and 10% of the total amount (based on how many ticks are covered by Solitude) in thta case, and heck people would be to obusy fighting and spending power to even notice that. And it all fixes itself to 0 after immolation aura is gone.

But the metamorphosis problem sounds bad, I am gonna test it soon. And if it's still an issue, I'll make it remember the "last tick value" (the tick that happens before spell success) and subtract that from the total on SPELL_CAST_SUCCESS.

1ps3 commented 4 years ago

Ah okay, I'm pretty sure it never gave the buff back in when I made this which is why it was missing. That's nice to see that it's a buff now, but this wasn't made to be future proofed. I might be a nice project to parse tooltips to see if power gain is increased.

Arcitec commented 4 years ago

@1ps3 Okay it's the next day now, and I'm resuming this work. I have just confirmed the Metamorphosis aura bug you mentioned (that all auras are re-added when metamorphosis happens). That's ugly, but I have a pretty clean fix in mind. Will work on that now...

EDIT: Works perfectly, except that Metamorphosis first forces SPELL_AURA_REMOVED which sets our remaining immolation to 0. I'll have to fix that now...

Arcitec commented 4 years ago

Okay, so I see that the sequence of events when you cast Metamorphosis is:

AURA_APPLIED: Metamorphosis AURA_REMOVED: Immolation Aura AURA_APPLIED: Immolation Aura

All three events happen immediately after each other. The "SPELL_CAST_SUCCESS" fix took care of the AURA_APPLIED problem, but not AURA_REMOVED. And on AURA_REMOVED we (both your aura bar and mine) set immototal to 0. So in fact, despite the SPELL_CAST_SUCCESS code, the immolation prediction is erased to 0 when metamorphosis happens.

I'm gonna try a different solution... when Metamorphosis is applied, we save the timestamp (which has millisecond precision) of the combat log event, and then on aura_applied/aura_removed, we ignore the event if it happened within like 0.1 second of metamorphosis applied.

Edit: Okay, on average, the fake AURA_REMOVED and AURA_APPLIED after Metamorphosis happen ~0.15 seconds after AURA_APPLIED of Metamorphosis. I've seen 0.162 too. So I guess 0.25 seconds will be the cutoff to be used. Now I just gotta test this and also check the Vengeance version of metamorphosis (there's some talent for that).

Edit: Success. The timestamp-based method works perfectly and fixes the issue completely. Even your SPELL_CAST_SUCCESS workaround wasn't handling the AURA_REMOVED problem, so I guess that was a recently added problem. But this new solution handles all cases well... Just gotta research the Vengeance version, and then I'll post the new aura...

Arcitec commented 4 years ago

@1ps3 Alright after a bunch of research, the new Metamorphosis bug workaround is complete and works perfectly in both Havoc and Vengeance. I tested it with all talents, verifying that they all use the same Metamorphosis buffs and that they all have the bug. The result is completely reliable now.

Here's the new aura with all 11 bugs fixed:

edit: further improvements in a later post below

1ps3 commented 4 years ago

Thanks, I'll take a look.

Arcitec commented 4 years ago

@1ps3 You're welcome. That's the final aura with all fixes. And excuse my habit of writing long comments in the code whenever there's some confusing concept. You can delete them if you prefer that, but on the other hand I always like comments because when I come back to a project a year later, it tells me wtf is going on. ;-) Like for example, the Metamorphosis bug... since it wasn't documented in your aura, I just assumed the SPELL_CAST_SUCCESS technique was a bug, rather than actually being a bug workaround! ;-)

Anyway... shorter summary of all fixed bugs:

  1. Immolation Aura prediction now predicts the correct "final amount". (Fixes bug 1 and 3)
  2. Solitude buff, which grants extra Fury/Pain from Immolation Aura, is now correctly taken into account.
  3. The overlays now correctly vanish (this fixes the WeakAuras bug at https://github.com/WeakAuras/WeakAuras2/issues/1803).
  4. The two overlays now work properly together (the 2nd overlay sits to the right of the 1st overlay, which wasn't the case earlier... and the overlays now do the right maths to calculate their amounts).
  5. The MarkAdjustment code now calculates pixel-perfect offsets.
  6. The entire aura is now pixel-perfect via a "scaling" technique, ensuring that the marks are never ugly and smeared (different widths) due to WoW's virtual pixels anymore.
  7. The "spender" animation has been fixed so that it properly plays if another spender animation wants to begin while one is already ongoing.
  8. Removed the incorrect if not ... then line which killed any event handling wherever the events lacked parameters.
  9. Removed pointless PLAYER_REGEN_ENABLED check since that event wasn't even registered.
  10. Fixed the Metamorphosis bug in both Havoc and Vengeance by ignoring any aura-removed/applied events that happen within 0.25 seconds of gaining the Metamorphosis aura. This fixes the problem you mentioned once and for all.

So... you've quit WoW? I'm gonna quit in the coming days... since I'm not having fun anymore. Either way, I am glad to contribute these code fixes. Enjoy!

1ps3 commented 4 years ago

Yea thanks so much, I don't actually ever get a chance to update much simply because I really don't care at all. The update recently was simply because I had forgot to put it up really and someone reminded me. Also for solitude it seems like it'd just increase the amount you get while people are around, so the value should just adjust accordingly. As for everything else it seems fine. I'm not sure how I feel about the scaling thing you put in there but I'll take a look at it too. Also you probably want to change the round in oninit to Round other than that it seems fine so far ^.^

1ps3 commented 4 years ago

I quit wow a long time ago, halfway through Uldir prog when ever that was. It's been ages. I quit during legion too, I don't like playing it anymore and I've moved over to classic but I haven't been playing it much at the moment. I really just can't be bothered, the game is garbage and the gameplay is garbage too. I haven't been able to find a class I enjoy for the last 2 years so there's literally no point and it doesn't seem like there's going to be any changes in shadowlands so yea.

Arcitec commented 4 years ago

Ahh, I see. I had thought the recent update was because you were active. ;-)

Also for solitude it seems like it'd just increase the amount you get while people are around, so the value should just adjust accordingly.

It'd need testing, because sometimes WoW does something like this:

Or, it may be doing this:

It would need testing to find out which. But I am guessing it may be the prior, not the latter, because how would you add 10% to an energy tick of 7 or 8 points? That wouldn't really work, math-wise, in the client... The result would be 7.7 or 8.8, which aren't valid energy amounts.

If it does the latter, it'd have to keep some kinda running total of the non-integer amounts and apply them to the next tick or something... it'd be complicated for them to do that...

But like I said, I wasn't able to test what happens if you lose or gain Solitude while the aura is ticking... I have an idea: Adding a debug print() that shows each energy tick. Then starting aura near another player, then running away and seeing if the ticks get bigger after gaining Solitude...

Let's say the ticks to get bigger; then what? Our math to estimate the new total would be very hard... Would we take remaining immototal and add 10% to it? And then what happens if we lose solitude again? We'd have to remove the added 10%... Hmm, meh.

I'm not sure how I feel about the scaling thing you put in there but I'll take a look at it too.

Yeah... the code itself is correct, and it can only break if the user has manually set a different scale to WeakAura's parent frame (which is the parent of the aura)... which pretty much nobody does (they have to use a custom macro for that), and even if they've done that, it just means the pixel-perfection doesn't happen on their screen... there's no other downside if they've done that. Anyway, what the code achieves is that you can finally set something like "Width: 2" in the aura settings and actually see 2 pixel wide marks, rather than 1, 2 and 3 pixel wide ones (smeared all over) as it was before the fix. :-/ Stupid Blizzard scaling...

Also you probably want to change the round in oninit to Round other than that it seems fine so far ^.^

What, why the capital R? round() is a built-in WoW function which rounds a floating point number. Is Round() some WeakAuras function?

I quit wow a long time ago, halfway through Uldir prog when ever that was. It's been ages. I quit during legion too, I don't like playing it anymore and I've moved over to classic but I haven't been playing it much at the moment. I really just can't be bothered, the game is garbage and the gameplay is garbage too. I haven't been able to find a class I enjoy for the last 2 years so there's literally no point and it doesn't seem like there's going to be any changes in shadowlands so yea.

Haha, you sound exactly like my situation. I stuck with the game from 2006 to 2012. Played TBC on a private server in early 2019. Then got the urge to check out retail in August this year. Had fun to begin with but the game just wore me down... tried Classic and was fun for a while, but felt like it was total garbage after a while. The game is just so dead... you stare at the screen and run sloooowly to each objective and then grind forever to do the tiniest things. Meanwhile, I could be playing Red Dead Redemption 2, GTA 5, Divinity: Original Sin 2, Path of Exile, etc, and be having insane amounts of fun with so much action and story flying right left and center at me! WoW on the other hand is like watching paint dry... it's terribly slow and everything is either time-gated (painfully having to wait to continue what you were doing), or time-expiring (neurotically having to do some stuff before the event expires). It's just... a bad game. I'm gonna let the account sit, but probably won't even try Shadowlands... we already know what kinda atmosphere this game has and it's not gonna get better just because they add yet another new world. Meh. :-P

1ps3 commented 4 years ago

The only reason I don't want to touch pixel perfect is because I don't want to have anything touching it at all really. I don't want the aura to have something that people would end up having to look for if they happen to want to change something or the like. As for the power it probably does need testing, but I guess I can just ask the demon hunters.

The wow function is Round with a capital r https://www.townlong-yak.com/framexml/live/Util.lua#880

1ps3 commented 4 years ago

Thanks for updating it though. I'll take a look later today when I get a chance. It seems quite well done and I'm pretty happy with it ^.^ it seems like once you get into it the only thing actually worth it in this game is the API haha.

Arcitec commented 4 years ago

@1ps3 Oh damn, you're right, the actual WoW function is Round. Turns out round was provided by the addon Rarity which wrote directly to the global namespace, lol. The weird thing, though, is that I used to write code for TBC and it had round builtin. I'm gonna fix that now, and you can fix that in the aura I shared above (edit: actually, I updated the import string above to a fixed version).

As for pixel perfection, I agree, I do feel sorta the same way... on the other hand, it doesn't hurt users, because the aura width and height is still fully controlled by them via the Display tab of the aura or by pulling on the aura handles of the WeakAuras options screen to resize it. So they don't notice the code. Perhaps you wanna keep it but add an off-by-default config Toggle that controls whether the SetPixelPerfectScale is called or not? Without it, the marks can still smear and become +/- 1 extra pixel wide, which looks ugly.

As for testing the Solitude, I think I can do the test in a moment... via the debug print() idea.

Thanks for updating it though. I'll take a look later today when I get a chance. It seems quite well done and I'm pretty happy with it

You're welcome. It's not my finest work but I'm a very experienced programmer in all kinds of languages for like two decades. ;-)

^.^ it seems like once you get into it the only thing actually worth it in this game is the API haha.

Dude... truer words have not been spoken. In 2009 I already started getting bored with the game (before quitting in 2012), and spent most of the time writing addons and not playing. It's way more fun than playing this garbage game. ;)

Arcitec commented 4 years ago

Just FYI I am doing the Solitude testing now. It's pretty difficult because you need to stay in combat the whole time, otherwise your Fury decreases over time naturally. And you need some other friendly player to be near you or go away from you mid-aura. I'm gonna have to find a person to help me test that. For now here's the tests of 100% solitude and 100% without solitude:

Immolation Aura with permanent Solitude the whole time: 89 Fury (interesting, 1 more than 1.1*80)... And the tooltip for Immolation Aura itself says 91 Fury which is totally wrong.

[11:42:00] SPELL_ENERGIZE 258920 11 
[11:42:01] SPELL_ENERGIZE 258922 8 
[11:42:02] SPELL_ENERGIZE 258922 8 
[11:42:03] SPELL_ENERGIZE 258922 8 
[11:42:04] SPELL_ENERGIZE 258922 8 
[11:42:05] SPELL_ENERGIZE 258922 7 
[11:42:06] SPELL_ENERGIZE 258922 8 
[11:42:07] SPELL_ENERGIZE 258922 8 
[11:42:08] SPELL_ENERGIZE 258922 8 
[11:42:09] SPELL_ENERGIZE 258922 7 
[11:42:10] SPELL_ENERGIZE 258922 8

Tested again; the individual numbers differed (five 8s in a row instead of four, at the start, this time), but reached the same 89 Fury (with same ticks as before, 1*11, 8*8, 2*7, just spread out differently):

[11:48:45] SPELL_ENERGIZE 258920 11 
[11:48:46] SPELL_ENERGIZE 258922 8 
[11:48:47] SPELL_ENERGIZE 258922 8 
[11:48:48] SPELL_ENERGIZE 258922 8 
[11:48:49] SPELL_ENERGIZE 258922 8 
[11:48:50] SPELL_ENERGIZE 258922 8 
[11:48:51] SPELL_ENERGIZE 258922 7 
[11:48:52] SPELL_ENERGIZE 258922 8 
[11:48:53] SPELL_ENERGIZE 258922 7 
[11:48:54] SPELL_ENERGIZE 258922 8 
[11:48:55] SPELL_ENERGIZE 258922 8

And again, very different tick order, but always the same overall amount, and as we can see it always reaches 89, so the number in the updated aura script will need to be fixed to 89 no matter what:

[11:54:35] SPELL_ENERGIZE 258920 11 
[11:54:36] SPELL_ENERGIZE 258922 7 
[11:54:37] SPELL_ENERGIZE 258922 8 
[11:54:38] SPELL_ENERGIZE 258922 8 
[11:54:39] SPELL_ENERGIZE 258922 8 
[11:54:40] SPELL_ENERGIZE 258922 8 
[11:54:41] SPELL_ENERGIZE 258922 8 
[11:54:42] SPELL_ENERGIZE 258922 8 
[11:54:43] SPELL_ENERGIZE 258922 7 
[11:54:44] SPELL_ENERGIZE 258922 8 
[11:54:45] SPELL_ENERGIZE 258922 8

*EDIT: UHH?! `111, 58, 57` = 86 Fury. WTF. The game seems to possibly keep a running total of overflow/underflow and correct itself over time. So we cannot assume that the aura will give 89 each time, or 88, or 86. Wtf.**

[11:57:56] SPELL_ENERGIZE 258920 11 
[11:57:57] SPELL_ENERGIZE 258922 8 
[11:57:58] SPELL_ENERGIZE 258922 7 
[11:57:59] SPELL_ENERGIZE 258922 7 
[11:58:00] SPELL_ENERGIZE 258922 7 
[11:58:01] SPELL_ENERGIZE 258922 7 
[11:58:02] SPELL_ENERGIZE 258922 8 
[11:58:03] SPELL_ENERGIZE 258922 8 
[11:58:04] SPELL_ENERGIZE 258922 8 
[11:58:05] SPELL_ENERGIZE 258922 7 
[11:58:06] SPELL_ENERGIZE 258922 8

EDIT: Same again... Seems like the game originally aimed at giving 88 fury above, ended up giving 89 a few times (above), so now it's compensating downwards by only granting 86 two times in a row... this complicates things...

[12:01:12] SPELL_ENERGIZE 258920 11 
[12:01:13] SPELL_ENERGIZE 258922 7 
[12:01:14] SPELL_ENERGIZE 258922 8 
[12:01:15] SPELL_ENERGIZE 258922 8 
[12:01:16] SPELL_ENERGIZE 258922 7 
[12:01:17] SPELL_ENERGIZE 258922 7 
[12:01:18] SPELL_ENERGIZE 258922 7 
[12:01:19] SPELL_ENERGIZE 258922 8 
[12:01:20] SPELL_ENERGIZE 258922 7 
[12:01:21] SPELL_ENERGIZE 258922 8 
[12:01:22] SPELL_ENERGIZE 258922 8

Immolation Aura without any Solitude at all: 80 Fury

[11:44:00] SPELL_ENERGIZE 258920 10 
[11:44:01] SPELL_ENERGIZE 258922 7 
[11:44:02] SPELL_ENERGIZE 258922 7 
[11:44:03] SPELL_ENERGIZE 258922 7 
[11:44:04] SPELL_ENERGIZE 258922 7 
[11:44:05] SPELL_ENERGIZE 258922 7 
[11:44:06] SPELL_ENERGIZE 258922 7 
[11:44:07] SPELL_ENERGIZE 258922 7 
[11:44:08] SPELL_ENERGIZE 258922 7 
[11:44:09] SPELL_ENERGIZE 258922 7 
[11:44:10] SPELL_ENERGIZE 258922 7
1ps3 commented 4 years ago

Yea I started "learning" lua at the end of legion and it's been a rough ride. I completely new to all of this but it actualy kept me interested because I could always mess around and make random shit. So I just lurk in the wa discord and help with lua stuff. It's the only language I know other than python which I only just started to learn because of school so yea. Though Lua is so much fucking fun because of how ncie it is to work with.

1ps3 commented 4 years ago

I'm not surprised, it's probably because of the rounding, iirc when it comes to power the demicals are kept until they become whole numbers and it's added to the next tick. The same thing happened with energy.

Arcitec commented 4 years ago

82 total... Yep, Solitude took effect mid-ticks. So it is per-tick.

[12:04:25] [Kíusa-Stormreaver]: Ok 
-- here I start the aura WITHOUT Solitude, since kiusa stands next to me --
[12:04:25] SPELL_ENERGIZE 258920 10 
[12:04:26] SPELL_ENERGIZE 258922 7 
[12:04:27] [Bloodyblood]: ok go 
-- now the person begins running away and I GAIN solitude three sec or so after I said ok go --
[12:04:27] SPELL_ENERGIZE 258922 7 
[12:04:28] SPELL_ENERGIZE 258922 7 
[12:04:29] SPELL_ENERGIZE 258922 7 
[12:04:30] SPELL_ENERGIZE 258922 7 
[12:04:30] [Bloodyblood]: as far away as possible
[12:04:31] SPELL_ENERGIZE 258922 8 
[12:04:32] SPELL_ENERGIZE 258922 7 
[12:04:33] SPELL_ENERGIZE 258922 8 
[12:04:34] SPELL_ENERGIZE 258922 7 
[12:04:35] SPELL_ENERGIZE 258922 7 
[12:04:44] [Bloodyblood]: thanks a lot, now i got the numbers, gonna check them :) 
[12:04:51] [Kíusa-Stormreaver]: Yw 

The summary is as follows:

Hmm, the prior thing could sorta be implemented as follows:

What do you think, should I implement that? But I was disheartened to see that the game client doesn't reliably grant 88 Fury; 89 89 89 86 86 is unpredictable as hell. Hehe. The game seems to be aiming for 88 total, but grants 89 due to the rounding errors, so after a while it granted 86 to compensate. Annoying...

Arcitec commented 4 years ago

Oh I just saw your other replies!

Yea I started "learning" lua at the end of legion and it's been a rough ride. I completely new to all of this but it actualy kept me interested because I could always mess around and make random shit. So I just lurk in the wa discord and help with lua stuff. It's the only language I know other than python which I only just started to learn because of school so yea. Though Lua is so much fucking fun because of how ncie it is to work with.

I completely agree, Lua and Python are fantastic languages. You would probably love the https://mpv.io media player! It's scriptable entirely in Lua or JavaScript and I've written tons of famous tools for it! https://github.com/VideoPlayerCode/mpv-tools ;-) (That's the most starred mpv script repo anywhere. But I didn't intend them to become famous so I never put up any nice screenshots or usage instructions though, cough cough.)

I'm not surprised, it's probably because of the rounding, iirc when it comes to power the demicals are kept until they become whole numbers and it's added to the next tick. The same thing happened with energy.

Yep that's how I assumed it worked above before seeing your post. Makes sense. But damn that complicates things. I guess I'll start implementing the idea from my previous comment now... it's the best we can do... 1. Assume that the aura will give 88 if Solitude is active (real number will be 86 or 89, totally randomly), 2. Multiply the remaining immototal anytime the player gains or loses Solitude.

Need to think about this now... ^_^


Edit: Here's the theory behind it:

THIS IS WHAT HAPPENED IN THE SOLITUDE TEST ABOVE:

[0] 80
[-10] 70
[-7] 63
[-7] 56
[-7] 49
[-7] 42
[-7] 35

<GAINED SOLITUDE AURA HERE>
<we did not correct our "35 remaining" estimate>

[-8] 27
[-7] 20
[-8] 12
[-7] 5
[-7] -2

Total gained: 82 (instead of 80) thanks
to gaining Solitude while ticks were ongoing.

So the prediction was off by -2 (we gained 2 more than we predicted).

If we instead correct the immototal remaining when we gain the aura, we'd get this:

[0] 80
[-10] 70
[-7] 63
[-7] 56
[-7] 49
[-7] 42
[-7] 35

<GAINED SOLITUDE AURA HERE>
<correcting: 35 * 1.1 = 38.5>

[-8] 30.5 (due to 38.5 -8)
[-7] 23.5
[-8] 15.5
[-7] 8.5
[-7] 1.5

So the prediction is off by +1.5 (we would predict 1.5 more than what we really gained).

This is an improvement... and it would be a larger improvement the earlier we gain (or lose) solitude during ticks. Let's say we gain Immolation Aura without Solitude, prediction is set to 80, then we gain Solitude immediately after the 1st tick. Our prediction would be "70 remaining" but in reality, Solitude would be granting us about 77 more. So the correction code above would change our prediction to 77 and we'd be pretty damn close to the real number...

I think this is the best we can do... time to implement it...

1ps3 commented 4 years ago

You should just multiply the remaining value by 1.1 and not worry about the extra 1 or lack of 1 fury. No one will care, you lose and gain fury so quickly and demon's bite is random so it doesn't really matter too much at all.

Arcitec commented 4 years ago

Yeah. That was my original reasoning behind not even checking this factor at all. ^^

Well, I'm trying to implement it now but so far I can't find any way to detect the aura at all. Solitude aura is not in combat log events, so it may need an entirely different event to detect... researching... Would be disgusting if the only way to find it is via https://wow.gamepedia.com/UNIT_AURA, since that event is very unsuitable. It doesn't provide info on what was gained or lost, so we'd have to loop over all player buffs every time we see that, ugh.

1ps3 commented 4 years ago

Yup, that's right. WA has a built in function you can call WA_GetUnitBuff(unit, spellId)

Arcitec commented 4 years ago

Ugh, okay... the only event that can detect the loss or gain of Solitude is UNIT_AURA, target==player, which triggers anytime you gain or lose a buff or debuff, upon which we have to loop through every friendly aura (same as what my hasSolitude check does).

The inefficiency of constantly scanning the auras would be pretty heavy, wouldn't it? Even WA_GetUnitBuff has the same problem. All just to fix the scenario where the user gains or loses Solitude mid-Immolation Aura. Let's say they gain or lose it at the half-way point, the prediction would be off by 4 (either 4 too much or 4 too little). All while the user is busy spamming attacks and would be too busy to see the 4 error.

I vote for not doing this, to save CPU on something pretty meaningless. What do you think? But I will definitely tweak my hasSolitude code to use the function you described, since it does the same thing my code did and cuts down the code length a bit!

1ps3 commented 4 years ago

It's not really that expensive, the play doesn't have that many buffs at all. Like it doesn't take long to iterate every time. I mean realistically this is how it's done. It's better than it used to be that's for sure at least. The old UnitAura functions did a worse version of this and that's why they were removed and you need an id now instead of just being able to check the buff, and a lot of people using those were just doing it all the time. Really you could just make some kind of ticker and check before the ticks goes off but who cares honestly it's not really as bad as it seems. This is why I dropped this, it was just too much work for what it was worth. It's a pvp talent and like no one really care about the extra like 8 fury you'll gain.

Arcitec commented 4 years ago

Yeah, I guess so... You just gave me an idea... I'll implement UNIT_AURA with a simple check: if immototal > 0, to avoid scanning all auras when there is no immolation at all. :-)

Arcitec commented 4 years ago

@1ps3 Okay the algorithm was a success. I was able to find a big group of players standing still near a training dummy and tried running in and out of range (to get and lose Solitude) MULTIPLE times per immolation aura and the new algorithm always adjusts the remaining prediction in real time and hits the correct final number +/- 1 or so. :-) Fantastic. And it's as efficient as it can be, by only scanning auras when we have an active immolation.

The end... this is done now. All you have to do is think about what you wanna do with the pixel-perfect "marks". Perhaps you wanna make it an optional (off by default) toggle, so people have a choice.

edit: improved aura in next comment

Arcitec commented 4 years ago

Good news. I agreed with you about not wanting to set scale for the whole bar itself. It felt icky. So I reworked it into a technique that successfully affects only the marks, without scaling the frame... and I've tested it with tons of really messed up scales on the aura_env.region and the main WA Main Frame, and it perfectly calculated pixel-perfect marks each time! ;-) Here's the new, final code:

!L3td3Pnss(B54923aZyJr4VW5Um3dWyBY6G9zWjZSzdocrdO1cjojb2o7E53(vF0TulKeiCYS79E3KjePwD3v1vxD9vxQ1qJHdg2F(W(JX)hUmyHP)JF0EC4SH9mQnS)ip)Xc)wMwpo23BXW(98Cfd7BUmCMN)nlcT9CdGM1J6MqXZHd73Y0)BFPTNJh8pQA0p8LfqZglcS8TPYg2FINByF7Vcf7y6pf(NNyOwFyi(ZavJSWUAy)hfVmSVXPxCzufng231CouJlw6)IeKd7VmqCoaMHTqWnXCPtiJF9Qv90gnQF4X1p7idJtp5yeiqH1pwR0th27qSWZoRUXXN04K6NE0Xqtpc7Gqg)h2VVi8BFjCM4BFzH3tcyuAXdwVjF7l3AA7w9BFP8vMR8SQGJKdZBK0k3rcsTN67T0DCrgvgznQQDAJJo04WtRF8rNvRUCuvpQeJZozddRrAWp9qeXryiMyqEuodYgznDH0OTpWmKJKtWH1rNwJNM4rYRzg5dc3PctxlbHWhNhc3kxeE3MvQVgAtJfd5Sex(2glBEAi7X1jXJl3LZhjsIKZnFgWKAWY6GqXcAegpwNB7IOdtiia1EMP7ur8yRxJArKegFKlZBpZ1CkGXdM5lcM55mwny4EiA8epcEAMaiPMJ8wbpWekw4hsuz5aDLPZsrvCeDA2ZuHkqrOqqgtAChfHsPM0gLhh3rhMC1FwZsiM1igZaPMwW)SY032CKJGLFce8zc7PZcjSca7v8DJskQ7SyrN9xiCCGPwfXnayfEpipw89ikfKIhHNHEtN6iKKW2a)a23PPDW0MxW3(s)qF7hfik8OiBU9wk6rhxCClNOxR5w04GHfnLAyK9CAB1DBfFysYwxfstOgBBDM2IQIH(1ttqz8SLJ5yOfNJlhJiBXdgAq26CfvVW0ZKDBA8jTkMSrNeuTuOLvXeiR0LuC652q)JYH)StR84p78c2TcZ57oVPwttJj5OvOtRC4ltGhfKNS6PO2xL(5d1iQgfNKUXrrb0bySzva8OtknkPyiw0wSkbf)ZtQYtj2NEIgUhWDlJXbmkF6RuuAlnShF22ejpQG9tszYUJXrEtx75MS82D2A28ebhGDEg8vso2SG9250du6BDS)6xn9hVVi4)Ejt45EZmQ3qCZ47wngrZQtsfd8Me(Ejxe3N(SneJSN2Ny4aRGugIO4bPI05CuJGXl9j8uJ7zcjlXBzO2WqzobnaTDNkTIaSokdwA0AQMXTekrmwJFUw1JLJcjkgppDUeBsmhKGeKJT(uRBNTiK0yuXmm2iBrgjPFRl2inhasJqm)OFmlCQFC8chPvm36lgBBHW6n48Xgx6mhQ6Y5RT25KCw7ypFUh25PPPDHN4iPMXGFZlDgqDnq40BCtygprxG4ZP5Gpl8flYgFGsbVQfJ3k2mkf2KOPle(QLTa91AzqO38beTEYsxQxlx5BVdDnd)JJNLjiNfM2FlmHdJKheURs9yF6XIQ(IP4q(D(IWL(UyjSL4VdzpF32)ZW(VCZKjbcGcTVXjgvpY4KdRxRrnqr3W(GM)zE(36z7c4A7o9g05UH9bt)9DmFjk8bS2XJKUNYkitzahuHA0)D4XNc2NFMkwb62VNOhoAZnougXdg3)nsubvWDEHmTgljMuF)IXMHWuQyLahll9DGfgHHlcEZbh8K5uVQ2Eh0DrGaDs(aulRPLkgjGWotFCrmoFz7AhmJVgUelvceq)r2ZB5mFjl2swSLN7e7PrfpIk2Vk66fmjjQIlAcHHMd9GAyrZmd675yhUCmdYjMobWv7V)3(cIAGOdVOcD9qf3HHcYfoZWQyh4yge(ErO5Cp)fZ8cSdI7BCnH2Dih1ayLdv09qNFRQKYLwa8cc)svIQyYkfvH9I4nX2vrcLv3UJnbVdeXNSfqbBSr2plC6dewEayuLgvH2wpYwhnG1Adp6VhVoJ4MA3NkUKYLlO3twHwNZvq5DqQk0PfxbLrXyf(FqWtMt1D8wHTbYXFuoW1OrJJo7SCGPXznQzCidVV9UjOgLh3BfYDakAnT9dkNIiub0D7f3B2tqwhGvA0Nw9zsxKB8dxtGebzcUJEtBFbSAdx3T0xu212bMuk18UbF8M7(ZLQKUlO2(gWS5Z9nF6ACASCu1HME0MAIckJEZLX3uztTG0mw2QkjUVY)oVK5J2OXTJewEZXWrHCmW)i8NiScjdAi7twIIrORavnOLk5dfwrlJwYRRaalNg02ry630XHK1guoF0FM3tz9uEc6TQPHONtAbKQcevzPGxTUGJDxju8viTR9mHfm1tCyLuDDjqsJNpg)MBxDlGGlNmzpu4JTf4uXuW6YqqEZVyu7pXrM8aoyDtfUsLLA020Y6iMs6Np28bGaJscqL(6sbQByCSrnKZ72RB(7DU7FCvNRV9I7VMevLKICloDFlpzhlN47MUCbSgdqrOfwlr(fP1CRS9dxslzyq5cwYIwkGYRNIUoA8gJiwqFYYpGLdS4Rl8muk9tGKJ9F017j3KWleKSdueVpcmS2RShJgIu60tA8TVCacmFbASnBoyjnWzJZeOkd7iKAp0uZXYv8aHWhUan)C8Fdu2fKfuzpwXbNPCyc2j5)tqpdMqeG9Y9DbR2XoVkgDpbXVmhOl4QohVN4HgOUYtFawnfPFPD8m09DVfmYYnexJrLctveonqhbqv5y4kreqsYbBgcjAAcrDSkX77QaWB1Gw2Y(ijhIFcfD4568coDT(STeI2kB5NdGajuAyJfB8pqxAUYZECakeMeYGnityIbpGbxGNZs2OxfGrZwIOPctC5wO9CKQ)rp3FkeBKjktlcJq6uesKn8awPhd9wKeG4Y9fe9F)z2WQwFRzVihtivFcmmFcCJnypC1paqAMfyw(p)3WyGeG8N7LdjTjG5(MbroefTsdylFbhnzI6yaPBtwJPc7kJKGT7HXUwnwKHyBP5ZjLBM9Q6bZqg1qH1mxBY)C8wWgkenTSqFnjSZeaoXwIl7ukaasrGnfonEvJfwlLP7UWeNa()088bADcX4h6XR3khesYgaB7SMvgax7pyc6oN(8hTDhd2an(UO5RsiWl9Ng)lpxg(PcDRrTkRjjpprvsPrsgwsspqSN4BsAljMaue2yBybaXzIsxulptpGK97TjnmRCszv84TcvIADycSTPRPZlFLcRGpTnf7lris2fnNZOgi1lMbTS4zlXIWy0dPei6owesk7H5H9rBeLSYvtc0E3mOZBuCb4FDfwIGatCdaj6cT8Ix(6XCO7ZUiaUkpM7u6PuLjQmYU)Es93fm(dd4l99wUiinLtoct4lHQkGYvsuLpYvQBhNUqovhOj(KxLqQtLpnBPDrgBadXht)4KRzsT2r9NSN7ZO0dKOJU09c0)XuOOwZdt9MVH139q5MbMRuRbkL0yWsOtvwHE(XwZqRIhXIJGzytuEWsNq7fo2O4jI3cmSvUkAFXZK8xc72i3fimamMnqjSG4YbNVbB2roHsOdfLi(Eu0NpjCB(chGlgxagz7kTyiyMP)c51e8HQKaWisdgel4R0eU6laHCGkRXW4fKTr2bWs3gji(4G5cmshjSxBX2MCZH2FNyUGTJGhYpbTFgnvSW3dxMr2jreNi9NjaCsf4AYnIS4d92PjzhZCI1)1zOxoUVV2JhLyHQ0B(ud8CTcvznthIpGd9onnXUQenhZwcG2jMyAi(QxJVF6Ym2K7F61dxOUqyb2rA)vtjfLK0uxteJvb6mD3AEZvaBzMl9v0pmehBTRuZyyOv)GjhYHvVjy5OYgOZcv2TEGAU1Nu9w1QLOll95c3ns))LCi6rd4tpw8EHwOfiD7(sLVTvoqlsjfFCfWlh4zr0xuOhWDrQSe7QKL421)djIj)U4tgF(htVuLX3T3v4KvTctf8Oaks0H7Wu4OSIc)ZmfsUKKsVdK72bXTaLPFwO9flxWY8Nz6mHmtsQab66kkBHXaYjfCYvCFgovl4AI(GPEidNkGhe3hqv2JVOU6IdvxC0NRu8UoresOHUUiDjjGUw62kRylsLJSDKyhLxySnD7W4RObqjV2tHsPCjoS1yabgHgaFDNlgG3WZZWf1kmDjNqVSrZFsuyCWyIdSwvAVDOCVlPiXyXeYhVtrwt1SIfun9AV14PPx5TnhPx3MolMzwUww9u(r)sN(8wnQHMsU8iOvXnlBDQAQh)w9YKK3On1JmcpdRcYOPu3TE7bcooOlvjxeGGaBJb2NkZuiLUzwZnoCO6G06l89MZ0BJkBV2d8sp3KxDvBMkiDiXMRwGM2FUNhy7M70YLU5(brHHB)9)0NsmbohCr35ISCej5ZaIgtVP7lx6I7A((oiRnVSyuLSBij70XErqBWDPXGDtLrhMYPY8LPH1TuygFp(qniQ1Wn2Fik8NfIfu7VXLmWrIeejzD0nEPXOQtMwz9kqcM(8Nrsz2WIGt5p9js9zWbZ8a3M9CHku)ZOAH9ZRDzlcn(2AQyiKtpOKys7Z4yVhKBvwlEd8c9TNofuEgNhYCb4LJJ2ZEJH9PGcBBHD3nqjb0)2KIQhVvTb8oYnwGrzEaTfVYG9gTT(YnPBPRChaXGing6RLJOcU1xmX(5H97FBNRVoU6z5zGkCnKB2VJYdezFELW0btMgU13W7tQX3ZU8MN)fj9A6kWAj0FNsRVh4L44UNNpMI4bdL3MC8mMH()o30fmLP05rjYrx3jEq3T0LcRsPfMuKTx6ZXpyHxGnfKPScnm6e3tMUYDKCbYtbd5qmG2tu1bJjJyCGEgNo3859WO4qHWDjqqnjhFrNDSmxSiHRoO0dRQQmlq5NR(UM(ROrG541HIiucmybJyjVhIXn(DB2tJSAET0A9v8fXPJYpUD))14wQF((JMCJzJsPaTkSl7ABAiWbBxdk)QwN(lK7I0mOU2FQaPsNTSvKVgNIw3jOeO3(zqew3on7VYZzyp8Zsm4DB3fYQXjzug7Q3AMx0p0BHkOaDDdcHfv4YHak08EUt5DnaRkg0DIB9lOMQYKV9yKKCznWv3eS(b5CrYoKNdqQ0wRAMgvUTgjvvn4MBvw4lNXKZf5yP)6uyj1k74e(Qs)G1LsiF6E6D3EAAt0tnhL2P(GqCu70dTB2FWd9haMXtzNg6lSu3hlB5bzQGfAgUmake3y24KEbtyL(mEkleQZ996o4HBV5JDU7HlURZ)19GY910Gv)pEnyLu5lvPODkwTtWihDdApi5GAIBENL03sJA0DEUJd()l64uzRwKooovD2K6nLurOInN7Tug0ZYQMUpfOLbG9q4Ym0z)gzhfq9oytWlXw0qrAfgTVKT5jrUHJKnHmoNm5Id(UgJXIOKWBZWe6qAdqFXtQPayb4DotmgZU5O0Qmlekgiv3muKCyFKduUShDSdbMwm488ad6WYh6Jbolbtig)bms6pzh1svw1TzG(K3smJk8LPdkI3SS)jQT6L639q2NLbCkRmL4MynoJC4mTDdJTSSMjEMhUP82n0QswpGcaSTtrIrsAHe5yJfAwfjztZ1bLT7Ki0W4xkX4AK0LGSLCYfod8mlUM54SWgDpjPq3DwGUMOxQHWWUA1QVMesbxbZ9bhs(sKG)Z72hZogs(Fj1EfjCewHuyXjyXvpkp)4TuxRJ40R5bYRWU9U8HpEZDxFEPCLoKE(pr6gM(PPY9Xm6GILHIPByQmIAZ7XiAsfz8660XM3FxZDyiZ71vY0WGrtmAWG0RPM2U46lKw7q2KAYAgpGDYLNRRMDplfn1evQGPaXcWzx5gYGBAPE(Qeq7DjPmnobRKOKV4NK9GkdASJLxMjGnP8Uos49CV50U)l3oZ5SMGOeEzmdPNKPaLogqiKNAZgbbwb8wxXVkblrRsRMTsQmC6B7m1BttwSg0vRN8CjZnT8doCb50smssK1BF7F82mWGITnAY0ucNcf2mvnndwCNkZJG0zuVDu2ub0qoHE4ucRyOaRkwmhan)2EeRqNmqGt1hPfgSraL0gQGcaQzEUhaAxlfPn0CcVRd2HvlgEKyt2n12ly8EqpmTDQo2CM5sYmXT0a3rd526l0BwG0S0UByXrMMgrK0j(y2kfaaHSBiE4gLutuNJRSQOYkVzK9P8ct1UoHwa8lhaDRH08ZcqlYGlRy8t5jBoX9)mMP1g8y9sj3wGeqVHt9ZcGJfBZDlkcv2GsReaVKErFnX6RHw7tzKAzGl1Jt7aXkBVLbOSsZXuAAYz)rLIG7zMqmfE7IkM6N238(wnh8W13C5dD(aOd(H77Dr3RbvXD2fDWsjCyurbtGMVatuTLJ6in54b8wVL(wIlVV75Ycu)Dm0ImkwAnJ6QEQq2JsokacHdZyqkZxjqVoENMk9c4cYhPCy1cCMJKESUuT9uAv8CC8EIZOfmLxOS0ZtrSrFgrhYCdEZMHgztizoWdnV92R725CIBcqylkdfrxofpBNpYu6rBhmbXkjRSM8smHncQue43PxN7US7FPdYXJm4ylJgbKECqYRjMOqfQ7yRuVVD7o97VJGpwIjXBTNk5bjtqKk4XHvsRA3cL9UoV)Mpau2nw)Ralaxr(cfBldLhrGy95kb6R9(VezJXuID1YXMnGP5ekNDZyYDJOqebNgZjGLITJbHkXNdabaeAqX2djwM4RYUA8YI)J4RKCMBefIP2Vq5pMKMJ6tcIZGn0gm28m7PUuQSwokpvfVqzxMk1HDuWTczyNxGqILBenulImDEcDYvszaZZQAC4(WpNefihzMdNEQjYQ4nj5DJwuPKOjLCME2SuXnOAHpgVrnzKr5itHu7gN9p8R4t9toITfl5dACAJ6NURAL5TeALNvkAyzgsKHee)gNWIOTxTazoJ5xfdaEMoIczbHoiJoqtYaS04ilWEnfJnmlL9vPQ4Loldd9CJqJGQ7I()SFP2I0RvmdosndvV2HNCCTxZergr2erXGOGxUTqDUJZcjTTfwLFatf5fqSznJvMDQjQtUCV1fn3jYDKN7rHqK2ahJAVwc9XnoRETSwkCAJtpQ23XsbSh1yrxxZ7UWLr(hhXrX7oug8D)kgTIQ1pw9A0q7AJu8klxxjvKNns08DaD2uc8dQ7y)hKEvnYKw65P0ZXVaw6V0wRTHXVkEpr6UmY5XLQWBlr0XeR((Sfk8M8PsNAPpQVkCj2VwvwXREpsymbHh)wv13tLsX4F5WvtQkJELOkumH3OREx3)MKKhErhfQhs5mGlOQp0Etk1cjzb2Us)7SjdNiItkNYFvieB8JrnCYp25VLl4xTMPGd(ue0dFIELXACIe4noJ1qeB4a96OHVvfpjIOtq9A8kiu7yiskS2vLiLDtYXgKKKHr7sj4ivRrnLeCmK(Q39QxnCt7rBc)15PLgm)EJAVcQ(o5F9w07VzkdojSGEnx(NgLPUmXZR)AOmU7GONcv5nhpaL6WSmsv5q1)CmqLdmI3JAHYfpvr8wM5XSblDV75bQ3RZi5MAY0b8G9Qw6mAqXWc(D)yS9KjcEtyLhcra02dFvwvUOG(uhIit0ZXaOjq4WVjKVPyWRKsybq6VIhSWWCb(I8GrYGEA9KpDamA2l2(e4rFiDdPNAK8PydlvWO0vUygjPWVCTEk7hyuzxLoQYdw1EcIlO7al0NQYcgv42po1gwvWUgfBm9fExTK2U8lRbNIVI(ht8d3pcR2jlft0h)hfyFZlkYv7hS4PDNCLVuM9u0Qk)XiwuU5LKl7KagvuiFTrkmVHurW)nU)LFhZIBuLGEGW(xuCl(b7S2)h1TQ)Ox29dmGd7iN4332teNxRQujBdBfbWOPLOz3F75nh0rvMEoiaLLz6eOQlY1VEETPLb4RLi2zLq4RLUiHXN1Z7ucISgKYj1quug62(p0(QM9UKig8AyMo8W99B266o5n03scMKKwSXemjvkLKuZSk7rw7vKTCIZiaw(r63mp(ykk6fZRsIuEwMhnCo3NXeq)X2b)ncPWrKP7l85V1kXaUA47VG84YZ2YZ9b(q6BJhREawk8DnD(GWpGooWQF0W(phFuNvfppXoXO2HhBC8jhIavD2kQDIFf9AiOM85quhDkDINP6H4PIOTBQxAbchVtDS7jr5wRZB9mDoLLhymXxlh(KmJYh3en9L4h0KadD5LXx2k(YlaIR28(cFVP(Ia0gAFmJiMI)mc)Xe)XVowg(Zi8hZ6zMe3)OY)ZmETA1sONQvlDXL6VwT6vV1Lz2Gw52G2YguDTJH68syQiOWbMIrX4RXUlBj0YmCpk72TQQDYCVnX66VW4vhnftVzGnou8mHSLLifMq2ryiMu2X3CO(nB8n)uLxDYHJwF2wVpBR3NTX(mdLdBmsc)RAmP1JA9NwVL9yjHIUmhyBza5B8ZvRV3u63r0VMgvY)Dhj1YWSpcV29)mSVpEIik4tir8v29fsSaD5ZsHLrhRHftGh0IrYpSeDDVW3ZLobmX8xgfWb0J2AYNXVeaQdL9AN1i7Zn5amMaVe9MXa9gD6ya6Kndnz8v)WDK0h0V59dUzy)vkb80zJ8Yr3rZkAVuzeMJYNae7b4VXzeQ45WhcMzo27PFtPyWi60uD5i(4qLQfF5FYsEBb0)q1dpHuh2)sVWz4j(CptFFmVZB5yIAS1aFc6L(X19A9hx7FpwlMQCHZe5P0jtuIGoD(SIhT0CrRSdSh5ii9q49joGp72Rhy8H6y(KFueSQjBbAvG9KxIpoqz2l(4mT3n96OA5VTElr0H1G1JzJyZU4pujp4fpbiljGr9O75UL4VwBAimQowBNqkRPgLqwcMC4GMBWkm8fgrJpGFoZ3RomDRBWMIGSQoEM0NGfEJ2iwlkRhug3GQ)Xx7qFSYCLsvhlWrh(Wg12LoAAph8SR3v3Z04evf8tWktyW9rl8OB2sKQglwTihypHpRttvUVNt6UbJfNTfuYlPEeG54R6BQYPjY1kuD(T2ctiD5Y5ZppwUs7nUcdpSNd5dG4lCmNcYNU5(bx3fz(0wkO4q1oOy)D2ClrayepgvACgHTmbrq13XGn(viG(u28d7ltd9Lwzx(WPqFPt(o(yLeDAhpk(8XFxoH7tEIAZh726F0rOV1iAhC2B2iD1rbT(XrD8xAHwXFEd2YNYa6RYZpUpSoR)rkzx(oJWhn8T0(mD0s7tIqlQk0lc)TsJWXzcy5l7WfEsqINd7qzS5fd0vFPKvP062A9wA(m2sYhPaYLR76E5vaNTn4k25xf9fkPf2vS8u691Mbs)231PtVOd4zlh7fkffl9Xt3ENv4HUeO(Y9rCH2CuKwFJQgvpuUiBGQQDrVVa5kI)ABoV7ai(xVV7(X3TFFSbY2rcg9SIuL3Ow9AhlF2v2JhlaxQ615d4I54pUojnYWOEowzaZY10NWybi40G07Q4VKc1psnqkaV7sKMAC7ORA0D1S(wDq5F4aGKPqDY5SygQapFmnVKEhE1n319VCtVbnVMK(m2wDutRKkkpK27DiSw6REGZEmcdY5Qx14yCj3k87VrDerg()o