• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Solved] SetDestructableAnimationSpeed whoes !!! (Workaround found)

Status
Not open for further replies.
Level 12
Joined
Jan 30, 2020
Messages
875
Hello everyone !

For those who read my previous posts, it seems I can't get things to work as intended since I have tried to implement my "Torrefaction" custom spell.

Don't get me wrong, it is actually working, and when the Balls or Flying Balls are roasting, the countdown progress bar I added as a Destructable (Blizzard, why didn't you call that destructible ?) displays perfectly, but for some impossible reason, the timing seems to be off.

I use a 1 second stand animation in my countdown bar model, from 0 to 1000 in the MDL, and as for destructable animation speed, I use 1 divided by the duration, and it seemed to work properly in the quick GUI map I tested it with.

But for some reason, in my Lua map, the timer (started with the duration in question) expires before the countdown bar has finished its animation, and I don't understand why, because the timer is started right after I create the destructable and set the animation speed.

A couple of screens of the effect of the Torrefaction ability :

On a Ball :

WC3ScrnShot_052720_111808_001.png


And on a Flying Ball :

WC3ScrnShot_052720_112043_001.png


So when the timer expires, the Burning Ball is removed, the original Ball is shown again, updating its health if needed, but the change happens usually before the progress bar you see in these screenshots has finished animating. The bar animation makes it go from full to nothing in exactly 1 second.

I fail to understand why this happens, and it cannot be related to the moment the ability takes effect because the animation and the timer are started at the same time (unless a destructable does not play its initial animation right when created ???)

Here is the full trigger for Torrefaction, I have hidden the actions for the other spells for readability :
Lua:
function RoastDeathActions()
    local r=GetTriggerUnit()
    local bn,t=Roast[r][4],Roast[r][5]
    PlayCleanSFX(SFXCubes, GetUnitX(r), GetUnitY(r), math.random()*0.4+0.2, math.random()*Pi2, 0, 3.0)
    StoreBall(Roast[r][3], bn, math.floor((bn-1)/BallsPerLevel)+1)
    DestroyTrigger(Roast[r][1])
    RemoveDestructable(Roast[r][6])
    RemoveUnit(r)
    PauseTimer(t) -- If the Roasting Ball is killed, we don't need to let the Torrefaction timer run anymore
    DestroyTimer(t)
    DestroyTrigger(GetTriggeringTrigger())
    return false
end

function SpellEffects()
    local spellEffects=CreateTrigger()
    for i=1, 4 do
        if Playing[i] then
            TriggerRegisterPlayerUnitEvent(spellEffects, Player(i-1), EVENT_PLAYER_UNIT_SPELL_EFFECT, nil)
        end
    end
    TriggerAddCondition(spellEffects, Condition(function ()
        local sId=GetSpellAbilityId()

----------- actions for other custom abilities

        elseif (sId==SpellID[15]) then -- Torrefaction
            local t,b,type,select=CreateTimer(),GetSpellTargetUnit(),BallType[5],__jarray(false)
            local bn,bx,by,bz,ba,life=GetUnitUserData(b),GetUnitX(b),GetUnitY(b),BlzGetUnitZ(b)+GetUnitFlyHeight(b),GetUnitFacing(b),GetWidgetLife(b)
            PlayCleanSFX(SFXTor, bx, by, 1.0, 0.0, 0, 1.5)
            if IsFlying then
                type=BallType[6]
            end
            for i=1, 4 do -- saving the selection state of the Ball for all players
                if IsUnitSelected(b, Player(i-1)) then
                    select[i]=true
                end
            end
            ShowUnit(b, false)
            PauseUnit(b, true)
            SetUnitInvulnerable(b, true)
            DisableBallTriggers(bn)
            local r,dur=CreateUnit(BallsMaster, type, bx, by, ba),10+2*GetUnitAbilityLevel(GetTriggerUnit(), sId)
            countdown=CreateDestructableZ(CountdownID,bx,by,bz+80.0, 270.00, 1.00, 0)
            SetDestructableAnimationSpeed(countdown, 1/dur)
            SetDestructableInvulnerable(countdown, true)
            SetUnitUserData(r, bn)
            BlzSetUnitMaxHP(r, CurrentMaxHP)
            SetWidgetLife(r, life)
            local size=life*ResizeFactor
            if (size<MinSize) then
                size=MinSize
            end
            SetUnitScale(r, size, size, size)
            for i=1, 4 do -- Selecting the Roasting Ball if the normal Ball was selected
                if select[i] and (i==LocalPn) then
                    SelectUnit(r, true)
                end
            end
            Roast[r]={CreateTrigger(), CreateTrigger(), b, bn, t, countdown} -- Initializing Roasting Ball data
            TriggerRegisterUnitEvent(Roast[r][1], r, EVENT_UNIT_DAMAGED)
            TriggerRegisterUnitEvent(Roast[r][2], r, EVENT_UNIT_DEATH)
            TriggerAddCondition(Roast[r][1], Condition(DamagedBallActions)) -- Roasting Balls can be damaged like normal Balls
            TriggerAddCondition(Roast[r][2], Condition(RoastDeathActions)) -- Roasting Balls require specific actions when killed
            TimerData[t]={b, r} -- Setting up the data for the timer expiring at the end of the duration of Torrefaction.
            TimerStart(t, dur, false, function()
                local t=GetExpiredTimer()
                local b,r=TimerData[t][1],TimerData[t][2]
                DestroyTimer(t)
                local life=GetWidgetLife(r)
                SetWidgetLife(b, life)
                local size,select=life*ResizeFactor,__jarray(false)
                if (size<MinSize) then
                    size=MinSize
                end
                SetUnitScale(b, size, size, size)
                for i=1, 4 do -- saving the selection state of the Roasting ball for all players
                    if IsUnitSelected(r, Player(i-1)) then
                        select[i]=true
                    end
                end
                PlayCleanSFX(SFXTor, GetUnitX(b), GetUnitY(b), 1.0, 0.0, 0, 1.5)
                DestroyTrigger(Roast[r][1])
                DestroyTrigger(Roast[r][2])
                RemoveDestructable(Roast[r][6])
                RemoveUnit(r)
                EnableBallTriggers(bn)
                SetUnitInvulnerable(b, false)
                PauseUnit(b, false)
                ShowUnit(b, true)
                for i=1, 4 do -- Selecting the Ball if the Roasting Ball was selected
                    if select[i] and (i==LocalPn) then
                        SelectUnit(b, true)
                    end
                end
                local d=Destination[GetUnitUserData(b)]
                IssueDelayedPointOrder(b, "move", WPx[d], WPy[d], 0.0)
            end)
        end
        return false
    end))
end

If anyone has an idea about what is wrong, I would be immensely grateful, as a perfectionist, I can't really leave the progress bar like that :'(
 
Level 12
Joined
Jan 30, 2020
Messages
875
Sorry for the early bump, but If I keep tearing my hair out further on this, I'll end up Bald.

I am really stuck here.
This makes no sense.

The duration of the countdown animation works perfectly in a messy test map I made with a chat message "+cd x.xx", and the countdown animation works fine.
But with my Lua Trigger, the Timer (supposed to last the exact same time as the progress bar) keeps expiring before the bar has finished its animation.
How the hell can this be ?!

Furthermore, there is virtually nothing available on the web about destructable animation speeds.
It seems I am really unlucky, or maybe I try too hard to make uncommon things, but the fact is I am often able to help other people, and very few are able to help me.

If any expert around ever finds a minute to try to find an explanation, it would seriously save my ass before I have to suffer a glitched map.

I don't know, @Dr Super Good ? @TriggerHappy ? Or anyone else ? I would love to k now what is wrong with destructable animation speeds.

Sorry to be a pain, I am desperate and can't seem to be able to focus on anything else since this issue.
I even wasted a few hours trying to make the Balls actually summon their Roasting versions with a custom Water Elemental ability to get the expiring blue progress bar - and adding similar actions to those in the code posted above. And the triggers themselves worked quite nicely - until I realized you can not see a summoned unit timeout on an enemy unit. Ok that was dumb.

So here am I, stuck with no better idea, just for a silly progress bar that my perfectionist sickness refuses to ignore because a temporary unit without a timeout bar is annoying...

Help ?

EDIT : attached my custom progress bar model
 

Attachments

  • Countdown.mdx
    2.9 KB · Views: 23
  • Countdown.mdl
    3.5 KB · Views: 23
Last edited:
Level 12
Joined
Jan 30, 2020
Messages
875
UPDATE !

Well I could not get the Destructible animation duration to be perfectly synchronized with the spell duration.
BlzPlaySpecialEffectWithTimeScale doesn't seem to work properly as it does not affect the special effect animation speed, whether it is attached or not to a unit.

As both these methods seemed to be dead ends, I gave up on the countdown progress bar with a custom model.

This said, I ended up choosing a cleaner and more elaborate method, using 2 UI Bars (the spell can be applied on up to 2 units in the same cooldown time) for each player.
With quite complex intricate timers, triggers to take every case of selection event, and updating the bars values with a separate timer linked to the "Roasting" Ball, I ended up making the system work perfectly in all circumstances.

I cannot post code as it implies quite a few different triggers throughout the map, but here is a screenshot to give you an idea of what the result looks like :

WC3ScrnShot_053020_070945_001.png


I would have preferred a less script-intensive solution, but the good side of things is I now have an elaborate countdown progress bar that can work for other things, and that allows showing a timed life progress bar for enemy units when needed.

If anyone is reading this in the future and found a way to get SetDestructableAnimationSpeed to synchronize with a SPELL_EFFECT event or even managed to get BlzPlaySpecialEffectWithTimeScale to work with non-attached special effects, don't hesitate to reply to this thread so that it could help people facing a similar issue in the future. Thanks !
 
Level 12
Joined
Jan 30, 2020
Messages
875
As you confirmed it was working for you, it pushed me to check my old code with the sfx version of the progress bar timer, and there was a variable name syntax mistake in my Initialization function (thanks again damn Lua).

Although I will definitely stick to my new system as it is much more seamless, I am grateful because at least I know I can use this native to scale the animation speed.

Thanks a lot for this !!!

EDIT :
Not a very useful addition, but I wanted to double check, and as expected it does not work on attached sfx. Thats sad because of attached sfx advantages, but nothing that cannot be worked around :)
 
Last edited:
Level 20
Joined
Aug 13, 2013
Messages
1,696
Yeah, those new special effect natives don't work if attached to a unit.
Maybe, they're just characterized to the unit's properties such as color, size, even position.
So if you're unit is having an attached model and you set its size to 5 or something,
The attached model will only be then gets its size scaled to 5.
 
Level 12
Joined
Jan 30, 2020
Messages
875
Yes thats right, I must admit thats how we usually take these attachment into consideration.
I think you ill agree that it was not a good choice from Blizzard as it makes a lot of people's modding life less easy.

Especially when it comes to giving the proper scale to weapons, auras, ability target sfx that suddenly become messed up when you need to change the Scaling Value of a unit, and that it does not scale accordingly.

Fun enough, on sfx with a team color, it seems that we can alter the property.

I have to use these to get the proper TeamColor on a Glow model because attaching it to the hero did not make the job :
Lua:
function SetupHero(pn, h)
    -- Adding Glow to the Hero, then changing its color to the player color as a workaround to the team color issues
    Glow[pn]=AddSpecialEffectTarget(GlowModel, h, "origin")
    BlzSetSpecialEffectColorByPlayer(Glow[pn], Player(pn-1))
    -- Setup Hero abilities
...

But I suppose, like we have always done, we have to live with it and find our own workaround, until Blizzard decides to do something about it :D
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,208
Animations are not synchronized with timers. Where as a timer will pause when the game is paused, for example as the result of players lagging or explicitly pausing, animations will not and will keep playing while paused. As such one cannot use them to represent a timer or any other game state that.

A possible work around would be to emulate a single animated model using multiple simple ones that are updated with game state. This will be more resource intensive but the scale of the models could be adjusted in time with the timer, such as every 0.03 seconds or so.
 
Level 12
Joined
Jan 30, 2020
Messages
875
Now thats the knowledge I was really expecting to obtain, thank you so much @Dr Super Good !

This also explains why the equivalent progress bars I discovered in the site resources were using repeating timers.
You have no idea how I feel relieved to understand why I could not get my single animation in sync.

I am still pleased with the workaround I used with a custom UI Bar, but that information is very valuable and will save me (and future readers) a huge amount of time.

Nothing replaces experience.
 
Status
Not open for further replies.
Top