Advanced Ability Animations

A simple system that enables you to give any ability a unique Art - Animation - Cast Point as well as any Animation of your choosing.

Cast Point is the value that determines how long it takes to cast a spell. Normally, you define this value once on your Unit and it applies to ALL of their abilities. With this system, you can bypass that limitation and assign this value on a per ability basis. You can also choose to play a specific Animation by relying on the SetUnitAnimationByIndex() function which uses Integers instead of Strings to give you exact control. This is useful since the Art - Animation Names field that Abilities use is limited in what it can play. Furthermore, the system will adjust your casting Unit's timescale (Animation speed) in order to play the entire Animation within the given time frame.

However, do note that it comes with some requirements:
1) Your Unit needs to have it's Art - Animation - Cast Point set to 0.00 for it to work smoothly.
2) Your Ability needs to have it's Casting Time set to it's new Cast Point value. This may not work with every single ability.
3) Your Ability needs to have it's Art - Animation Names set to stand for it to work smoothly.

(Technically you could tweak #1 and #3 and it'd still work, just with different results than what I was going for here)

This system is GUI friendly but you could easily use it in Jass. It's designed to use GUI to register new abilities to the system:
  • AAA Demo
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • -------- How to use: --------
      • -------- --------
      • -------- 1) Set your casting unit's Art - Animation - Cast Point to 0.00. --------
      • -------- 2) Set your abilities Casting Time to whatever you want it's new Cast Point to be. This is how long it takes to cast! --------
      • -------- 3) Set your abilities Art - Animation Names to stand. --------
      • -------- 4) Set the four AAA__ variables below. --------
      • -------- - Abil_Type is the ability that you want to register to the system. --------
      • -------- - Anim_Index is the Integer id of the animation you want to play. You can find this value in a Model Editor or on Hiveworkshop's 3D Model Viewer. --------
      • -------- - Anim_Duration is the duration in seconds of your animation. (You can tweak this to make the animation play faster/slower) --------
      • -------- - Anim_Cast_Time is the duration in seconds it takes to cast the ability. This should match the Casting Time of your ability in the Object Editor! --------
      • -------- 5) Call the AAA_Register_Ability() function to add this ability to the system. --------
      • -------- --------
      • -------- In this example I am changing Holy Light to use the "stand victory" animation and take 1.0 second to cast. --------
      • Set VariableSet AAA__Abil_Type = Holy Light
      • Set VariableSet AAA__Anim_Index = 7
      • Set VariableSet AAA__Anim_Duration = 2.66
      • Set VariableSet AAA__Cast_Time = 1.00
      • Custom script: call AAA_Register_Ability()
Notes:
It can be difficult to find the correct Animation Index since Animations aren't ordered in a logical way in the World Editor.
What I do is go into the Hiveworkshop Model section and find a similar model that uses the same set of Animations.
Then click the View In 3D button to view the model. You should see all of it's Animations listed out in the proper order on the right side.
If it's still playing the wrong animation then try subtracting 1 from Anim_Index or adding 1 to it. It can be inconsistent.

Also, if all of the Units using this system have their Art - Animation - Cast Backswing set to 0.00 then you can set this variable to false to improve performance:
vJASS:
library AdvancedAbilityAnimations initializer Init
    // Created by Uncle

    globals
        private constant boolean DOES_USE_CAST_BACKSWING = true

        // Define the above DOES_USE_CAST_BACKSWING boolean.
        // If any unit has an Art - Cast Backswing > 0.00 then
        // keep this set to true. Otherwise, set it to false to
        // make the system slightly more efficient.
vJASS:
library AdvancedAbilityAnimations initializer Init
    // Created by Uncle

    globals
        private constant boolean DOES_USE_CAST_BACKSWING = true

        // Define the above DOES_USE_CAST_BACKSWING boolean.
        // If any unit has an Art - Cast Backswing > 0.00 then
        // keep this set to true. Otherwise, set it to false to
        // make the system slightly more efficient.



        // Do not modify anything else below this line
        private hashtable hash = InitHashtable()
        private unit caster
        private integer abilityId
        private integer timerHandle
        private timer abilityTimer

        // References to your AAA_ variables
        private integer castAnim // animation index
        private real castDuration // animation standard duration
        private real castTime // animation desired duration

        // KEYS in the Hashtable
        private constant integer KEY_UNIT = 0
        private constant integer KEY_ABIL = 1

        private constant integer KEY_ANIM = 0
        private constant integer KEY_DURATION = 1
        private constant integer KEY_TIME = 2
        private constant integer KEY_ANIM_ALT = 3
        private constant integer KEY_DURATION_ALT = 4
        private constant integer KEY_TIME_ALT = 5
    endglobals

    private function StopCasting takes nothing returns nothing
        set abilityId = GetSpellAbilityId()
        set castTime = LoadReal(hash, abilityId, KEY_TIME)
        if castTime == 0.0 then
            return
        endif

        set caster = GetTriggerUnit()
        call SetUnitTimeScale(caster, 1.0)

        // Seems unnecessary, commented out for now
        //call ResetUnitAnimation(caster)
      
        set abilityTimer = LoadTimerHandle(hash, GetHandleId(caster), 0)
        if abilityTimer != null then
            call FlushChildHashtable(hash, GetHandleId(abilityTimer))
            call FlushChildHashtable(hash, GetHandleId(caster))
            call DestroyTimer(abilityTimer)
        endif

    endfunction
  
    private function BeginCastingCallback takes nothing returns nothing
        set abilityTimer = GetExpiredTimer()
        set timerHandle = GetHandleId(abilityTimer)
        set caster = LoadUnitHandle(hash, timerHandle, KEY_UNIT)

        if IsUnitType(caster, UNIT_TYPE_DEAD) or GetUnitTypeId(caster) == 0 then
            return
        endif

        set abilityId = LoadInteger(hash, timerHandle, KEY_ABIL)
      
        // Variables used to play the special cast animation
        if GetUnitAbilityLevel(caster, udg_AAA__Alt_Ability) > 0 then
            // It has the alternate animation tag
            set castAnim = LoadInteger(hash, abilityId, KEY_ANIM_ALT)
            set castDuration = LoadReal(hash, abilityId, KEY_DURATION_ALT)
            set castTime = LoadReal(hash, abilityId, KEY_TIME_ALT)
        else
            // It does not have an alternate animation tag
            set castAnim = LoadInteger(hash, abilityId, KEY_ANIM)
            set castDuration = LoadReal(hash, abilityId, KEY_DURATION)
            set castTime = LoadReal(hash, abilityId, KEY_TIME)
        endif

        call SetUnitAnimationByIndex(caster, castAnim)
        call SetUnitTimeScale(caster, castDuration / castTime)

        call FlushChildHashtable(hash, timerHandle)
        call FlushChildHashtable(hash, GetHandleId(caster))

        call DestroyTimer(abilityTimer)
    endfunction

    private function BeginCasting takes nothing returns nothing
        set abilityId = GetSpellAbilityId()
        set castTime = LoadReal(hash, abilityId, KEY_TIME)
        if castTime == 0.0 then
            return
        endif

        set caster = GetTriggerUnit()
        set abilityTimer = CreateTimer()
        set timerHandle = GetHandleId(abilityTimer)
        call SaveUnitHandle(hash, timerHandle, KEY_UNIT, caster)
        call SaveInteger(hash, timerHandle, KEY_ABIL, abilityId)
        call SaveTimerHandle(hash, GetHandleId(caster), 0, abilityTimer)

        call TimerStart(abilityTimer, 0, false, function BeginCastingCallback)
    endfunction

    // Call this after setting up the AAA_ variables
    function AAA_Register_Ability takes nothing returns nothing
        local integer id = udg_AAA__Abil_Type
        if udg_AAA__Anim_Duration <= 0 or udg_AAA__Cast_Time <= 0 then
            call DisplayTextToPlayer(Player(0), 0, 0, "[AAA Error] Ability " + GetAbilityName(id) + " was not registered! Anim Duration and Cast Time must be > 0.")
            return
        endif
        call SaveInteger(hash, id, KEY_ANIM, udg_AAA__Anim_Index)
        call SaveReal(hash, id, KEY_DURATION, udg_AAA__Anim_Duration)
        call SaveReal(hash, id, KEY_TIME, udg_AAA__Cast_Time)

        // A value of -1 represents unused
        if udg_AAA__Anim_Index_Alt > -1 then
            call SaveInteger(hash, id, KEY_ANIM_ALT, udg_AAA__Anim_Index_Alt)
            call SaveReal(hash, id, KEY_DURATION_ALT, udg_AAA__Anim_Duration_Alt)
            call SaveReal(hash, id, KEY_TIME_ALT, udg_AAA__Cast_Time_Alt)
        endif
        set udg_AAA__Anim_Index_Alt = -1
    endfunction
  
    private function Init takes nothing returns nothing
        local trigger t1 = CreateTrigger()
        local trigger t2 = CreateTrigger()

        // Define the ability used to determine alternate animations
        set udg_AAA__Alt_Ability = 'AAAT'

        // A value of -1 represents unused
        set udg_AAA__Anim_Index_Alt = -1

        call TriggerRegisterAnyUnitEventBJ( t1, EVENT_PLAYER_UNIT_SPELL_CHANNEL )
        call TriggerRegisterAnyUnitEventBJ( t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST )

        // Art - Cast Backswing may need to be accounted for (if > 0.00)
        if DOES_USE_CAST_BACKSWING then
            call TriggerRegisterAnyUnitEventBJ( t2, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        endif

        call TriggerAddAction( t1, function BeginCasting )
        call TriggerAddAction( t2, function StopCasting )

        set t1 = null
        set t2 = null
    endfunction
  
endlibrary
v1.0: Released.

v1.1: Added DOES_USE_CAST_BACKSWING boolean to improve performance when Art - Animation - Cast Backswing is set to 0.00 for your Units that use this system, added more If condition checks to prevent unnecessary code from running, fixed unit handle memory leak.

v1.2: Adjusted code syntax and added an error message for when an ability fails to register. Renamed map to have spaces in title.

v.1.3: Added _Alt variables for Index, Duration, and Cast Time which can be registered along with the standard variables. These will be used instead if the caster has the new AAA__Alt_Ability ability - which must now be imported into your map with the rawcode 'AAAT'. If this rawcode is already taken then you'll need to go into the code and modify the line that assigns this variable. This ability is meant to be paired with the "alternate" animation tag, although it's not actually necessary, which could serve useful to someone who wants an alternate animation for other reasons.
Previews
Contents

Advanced Ability Animations v.1.3 (Map)

Reviews
Antares
An option to pass the animation type either by string or by index would be great. While index gives you more control, it is also more cumbersome in a case where a string would suffice. I expected the mana to get deducted when the cast sequence...
This would probably make more sense as something that inserts into Spell System, to avoid having to configure two separate things for spells. If you want, I could add some //! runtextmacro optional statements inside of Spell System, like I did with Damage Engine, to allow your system to be optionally supported and integrated.
 

Uncle

Warcraft Moderator
Level 69
Joined
Aug 10, 2018
Messages
7,166
This would probably make more sense as something that inserts into Spell System, to avoid having to configure two separate things for spells. If you want, I could add some //! runtextmacro optional statements inside of Spell System, like I did with Damage Engine, to allow your system to be optionally supported and integrated.
Sounds good to me, I don't really have much attachment to this, it's quite simple after all.
 
An option to pass the animation type either by string or by index would be great. While index gives you more control, it is also more cumbersome in a case where a string would suffice.

I expected the mana to get deducted when the cast sequence starts, since that's the case with Flamestrike. Adding back the mana cost to the Holy Light in the test map would clarify that. It might also be worth noting in the documentation that a spell cast this way cannot be aborted with a movement command until it is finished.

Since the AAA__Cast_Time variable should match the Casting Time field of the ability, you should be able to remove it and retrieve the casting time with BlzGetAbilityRealLevelField(abi, ABILITY_RLF_CASTING_TIME, GetUnitAbilityLevel(caster, abilityId) - 1).

A pure JASS register function would be nice to have.


A nice light-weight system to give more control over spell animations and cast duration.

Approved.
 
Level 33
Joined
Aug 6, 2015
Messages
680
Noticed a little bug.
- If adding an "Alternate" tag to the unit. Then casting a spell tied to the system -> it will move the unit in his non-alternate form after the cast.

Can be fixed by ordering the unit to move to his own position after finishing the cast (this way the alternate animation will re-cast itself after the spell is finished - maybe something like this could be implemented into the system where you can give the creator an option to set a variable is the spell is using alternate animations - to order the unit to move to his own position after cast - or something in this lines.

I also don't understand how to make use of the spell efficiently in my case.
So i have a heal - it is casted instantly by default - but i want to play a custom animation when casting it - and i don't really understand how to do it.
 

Attachments

  • Advanced Ability Animations v.1.2.w3m
    22.8 KB · Views: 1
Last edited:

Uncle

Warcraft Moderator
Level 69
Joined
Aug 10, 2018
Messages
7,166
Noticed a little bug.
- If adding an "Alternate" tag to the unit. Then casting a spell tied to the system -> it will move the unit in his non-alternate form after the cast.

Can be fixed by ordering the unit to move to his own position after finishing the cast (this way the alternate animation will re-cast itself after the spell is finished - maybe something like this could be implemented into the system where you can give the creator an option to set a variable is the spell is using alternate animations - to order the unit to move to his own position after cast - or something in this lines.

I also don't understand how to make use of the spell efficiently in my case.
So i have a heal - it is casted instantly by default - but i want to play a custom animation when casting it - and i don't really understand how to do it.
Thanks for pointing this out, I just fixed it in the latest update (1.3). I've added 4 new variables which are meant to address the problem.

The first 3 variables are alternate versions of the animation index, duration, and cast time variables:
AAA__Anim_Index_Alt
AAA__Anim_Duration_Alt
AAA__Cast_Time_Alt


These are optional and can be registered along with the original variables. When your unit has the "alternate" tag it will use the values provided by these instead, allowing you to have two sets of animations per Ability (standard and alternate).

The last variable is an Ability Code called AAA__Alt_Ability which must be added/removed along with the "alternate" animation tag. For example:
  • AAA Demo Add Tag To Demon Hunter
    • Events
      • Player - Player 1 (Red) types a chat message containing add as An exact match
    • Conditions
    • Actions
      • -------- Tell the AAA system that this Unit has the "alternate" tag: --------
      • Animation - Add the alternate animation tag to Demon Hunter 0006 <gen>
      • Unit - Add AAA__Alt_Ability to Demon Hunter 0006 <gen>
  • AAA Demo Remove Tag From Demon Hunter
    • Events
      • Player - Player 1 (Red) types a chat message containing remove as An exact match
    • Conditions
    • Actions
      • -------- Tell the AAA system that this Unit no longer has the "alternate" tag: --------
      • Animation - Remove the alternate animation tag to Demon Hunter 0006 <gen>
      • Unit - Remove AAA__Alt_Ability from Demon Hunter 0006 <gen>
I rely on this custom ability to detect that the unit has the "alternate" tag. Unfortunately, there's no native way to do this or I would have simply done something like this in my code: If unit has "alternate" tag Equal to True.

Although, I suppose this approach is more flexible since the "alternate" tag isn't even necessary to get access to the alternate animation.
 
Last edited:
Top