• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Special Effect Animation by Index

Is there a way to use special effect by defining their index? I want to create a spell where the special effect accumulate stacks and when a new stack is acquired, the effect adjust accordingly. The effect in question is Ring Progress Indicator which works best with animation by index. This is not a timed spell, so the stack is not dependent on time which makes time and time scale out of equation for me.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
Special Effects use Sub-Animations which don't really work here. A Dummy unit would work nicely though.

That being said, I made this library to solve the Special Effect problem:
vJASS:
library RingProgressHelper

    globals
        private hashtable RingHash = InitHashtable()
    endglobals

    private function StopPlaying takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer id = GetHandleId(t)
        local effect sfx = LoadEffectHandle(RingHash, id, 0)
        call BlzSetSpecialEffectTimeScale(sfx, 0.0)
        call FlushChildHashtable(RingHash, id)
        call DestroyTimer(t)
        set t = null
    endfunction

    // This resets the Birth animation and jumps to the given "tick" or index.
    // This is less smooth than RingProgressPlay but fixes any potential issues.
    // I haven't tested RingProgressPlay enough to be sure if this is even necessary.
    function RingProgressSet takes effect sfx, integer tick returns nothing
        local timer t = CreateTimer()
        local real desiredDuration = 0.625 * tick
        local real actualDuration = 0.05 // Anything less than 0.05 won't work properly
        local real timeScale = desiredDuration / actualDuration
        call BlzPlaySpecialEffect(sfx, ANIM_TYPE_BIRTH)
        call BlzSetSpecialEffectTimeScale(sfx, timeScale)
        call TimerStart(t, actualDuration, false, function StopPlaying)
        call SaveEffectHandle(RingHash, GetHandleId(t), 0, sfx)
        set t = null
    endfunction

    // This assumes the Birth animation is already playing and
    // jumps forward by the number of given "ticks" or indexes.
    // This is smoother than RingProgressSet but may produce off results if you
    // call it on the same Special Effect multiple times in under 0.05 seconds.
    // I haven't tested it enough to be sure, so use with caution.
    function RingProgressPlay takes effect sfx, integer ticks returns nothing
        local timer t = CreateTimer()
        local real desiredDuration = 0.625 * ticks
        local real actualDuration = 0.05 // Anything less than 0.05 won't work properly
        local real timeScale = desiredDuration / actualDuration
        call BlzSetSpecialEffectTimeScale(sfx, timeScale)
        call TimerStart(t, actualDuration, false, function StopPlaying)
        call SaveEffectHandle(RingHash, GetHandleId(t), 0, sfx)
        set t = null
    endfunction
endlibrary
This code takes advantage of the Birth animation of the Ring Progress Indicator model, which plays through all 16 animations over 10 seconds.

What I did was divide the animation duration by 16 to get the time between each animation index: 10/16 = 0.625 seconds. With that value known, I am able to determine the amount of time that has to pass to reach a given animation index (or tick). To mimic the instant nature of SetUnitAnimationByIndex() I rely on a really fast Timescale to run that much animation time over 0.05 seconds. I tested other values and anything less than a 0.05 second timer didn't work.

Basically each time you call RingProgressPlay() you pass in your Special Effect and how many ticks you want it to progress, and it goes into super fast forward mode to get you to that next animation. This appears to be instant for the user.

Note that RingProgressPlay() may produce off results if you call it on the same Special Effect while it's already running, I'm not actually 100% sure. But that's a short time frame of 0.05 seconds so it may never happen, it all depends on your use case. Luckily, you can use RingProgressSet() instead which resets the Animation each time to ensure consistency. Again, I can't say for certain if it's an actual problem, I didn't test it enough to find out.

Also, this is how you could create the Special Effect and ensure that it plays nicely with the Ring functions:
  • Actions
    • Special Effect - Create a special effect at (Center of (Playable map area)) using CaptureProgress.mdx
    • Set VariableSet sfx = (Last created special effect)
    • Special Effect - Play Special Effect: sfx, Animation: Birth
    • Special Effect - Set Time Scale of sfx to 0.00
 

Attachments

  • Capture Progress Example.w3m
    23.3 KB · Views: 2
Last edited:
So, BlzSetSpecialEffectTimeScale does not work on unit attached effects. Well, guess that's interesting discovery of knowing many new effect native seems to be dysfunctional when attached to units. The solution proposed works for location-based special effects.

To be clear, I want to use this as an indicator for a 'stack' based ability to show how many stack of the ability owned by the unit to all players (or just the caster, but that's probably the easier part).
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,543
So, BlzSetSpecialEffectTimeScale does not work on unit attached effects. Well, guess that's interesting discovery of knowing many new effect native seems to be dysfunctional when attached to units. The solution proposed works for location-based special effects.

To be clear, I want to use this as an indicator for a 'stack' based ability to show how many stack of the ability owned by the unit to all players (or just the caster, but that's probably the easier part).
Two solutions come to mind:
1) Use a 0.01s timer that periodically moves the special effect to the position of it's source unit.
2) Create 16 separate versions of the model, each displaying a different tick as their stand animation, and create/destroy them as necessary.
 
Two solutions come to mind:
1) Use a 0.01s timer that periodically moves the special effect to the position of it's source unit.
2) Create 16 separate versions of the model, each displaying a different tick as their stand animation, and create/destroy them as necessary.
I'd stick with first solution since I have a resource that allows me to fiddle with Special Effect to a better degree than usual. Pretty much solved.
 
Top