• 🏆 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!

Follow Through Cancelling and Mana Refund - Channel-Based Spells

Level 12
Joined
Jan 10, 2023
Messages
191
Hello all,

I'm wondering where the best place to post this is, but I think this is the right place to start, in case there is already a library that contains this or if there is a library this would fit well in.

I've recently come across a niche-scenario that concerns any/all Channel-based abilities in regard to Refunding Mana, as well as a secondary niche-scenario which may occur depending on a channel-based ability's "Follow Through Time", which requires some special treatment when it comes to triggering said ability.

The issue with Refunding Mana for a Channel-based ability is a well known one, described well here in this thread from five years ago.
If there is a better solution than the one I am about to share, I would like to know it, if you know a better way to handle this please share, there are a few ways I have found of dealing with it, some are better than others, depending on some finer details.

Some education:
  • When an Ability "Starts the Effect" for a Casting Unit, the Mana is charged to the Casting Unit immediately AFTER the Event fires. (additional source: This thread from ten years ago).
  • If the Casting Unit's Mana is at its Maximum, you need to use a timer or TriggerSleepAction' ("call TriggerSleepAction( 0.00 )" works)
  • Timers are the superior solution because TriggerSleepAction is much slower, a TSA argument of 0.00 does not wait 0.00 seconds
  • A timer with a runtime of 0.00 will work!

The 'issue' is that when you try to refund the mana, if the unit is at maximum mana, the unit will not be refunded unless you use either a timer or TriggerSleepAction to ensure that the mana has actually been lost.
The problem is that immediately as the EVENT_PLAYER_UNIT_SPELL_EFFECT Event fires, the Caster has not yet been charged the Mana, so the Mana is "added", but without the desired effect, and then the cost comes.
This may easily go unnoticed if the Casting Unit is not at its Maximum Mana, because it happens so fast it is often, if not always too fast to see.

Again, this is a well-known and long-solved situation and I don't pretend to have made an advancement here, but I do have a tried solution that I share here for the sake of spreading the knowledge, and because it relates to the secondary niche Ability property of a Channel-based ability with a "Follow Through Time", and how to cancel that Ability early and effectively ending the Unit's Follow Through Time.

It may be that the following concepts regarding cancelling an ability follow through apply to abilities other than Channel-based abilities, and perhaps for abilities that use a Duration in place of a Follow Through Time, but I have not tested for this.

Here is a copy of the snippets I used, followed by instructions for best practices for usage, as determined by my testing:
(Explaination is within the Script as comments)
JASS:
library CAS // Common Ability Systems
    globals
        private constant timer CAS_MR_T = CreateTimer()
        private integer        CAS_MR_N = 0
        private integer  array CAS_MR_A
        private unit     array CAS_MR_U
    endglobals

    private function CAS_MR_TF takes nothing returns nothing
        local integer i = 0
        loop
            call SetUnitState( CAS_MR_U[i], UNIT_STATE_MANA, GetUnitState( CAS_MR_U[i], UNIT_STATE_MANA ) + CAS_MR_A[i] )
            set CAS_MR_U[i] = null
            set i = i + 1
            exitwhen i > CAS_MR_N
        endloop
        set CAS_MR_N = 0
    endfunction

    private function CAS_MR_AddRefundUnit takes unit trgt, integer amnt returns nothing
        set CAS_MR_U[CAS_MR_N] = trgt
        set CAS_MR_A[CAS_MR_N] = amnt
        set CAS_MR_N = CAS_MR_N + 1
        set trgt = null
        call TimerStart( CAS_MR_T, 0.00, false, function CAS_MR_TF )
    endfunction

    // CAS_MR_RefundActions should be called before CAS_FT_UnitStopAbility
    // otherwise the mana cost will return 0 for BlzGetUnitAbilityManaCost
    // seemingly because GetSpellAbilityId will be lost, I didn't test it.
    // This must be called as an action to get the abilcode & TriggerUnit.
    // If it can refund Unit's Mana immediately, we will avoid the hoopla.
    function CAS_MR_RefundActions takes nothing returns nothing
        local integer cost = BlzGetUnitAbilityManaCost( GetTriggerUnit(), GetSpellAbilityId(), GetUnitAbilityLevel( GetTriggerUnit(), GetSpellAbilityId() ) - 1 )
        local real maxMana = GetUnitState( GetTriggerUnit(), UNIT_STATE_MAX_MANA )
        local real mana = GetUnitState( GetTriggerUnit(), UNIT_STATE_MANA ) + cost
        if mana < maxMana then
            call SetUnitState( GetTriggerUnit(), UNIT_STATE_MANA, mana )
        else
            call CAS_MR_AddRefundUnit( GetTriggerUnit(), cost )
        endif
    endfunction

    // For channel based abilities, this will only interupt the Follow Through
    // Time if it is called on or after the Spell's Effect Event. If called on
    // before that, it is too early and a Unit will be uneffected. This relies
    // on the Spell being removed on or after the Spell's Effect Event firing.
    // It would seem that if the Unit gets its Ability "Stopped" at an earlier
    // time it will continue with Follow Through like nothing happened at all.
    function CAS_FT_UnitStopAbility takes unit u, integer abilcode returns boolean
        local integer abillvl = GetUnitAbilityLevel( u, abilcode )
        if UnitRemoveAbility( u, abilcode ) and UnitAddAbility( u, abilcode ) then
            if SetUnitAbilityLevel( u, abilcode, abillvl ) == abillvl then
                set u = null
                return true
            endif
        endif
        set u = null
        return false
    endfunction

    // This function is meant to ensure one function can simultaneously refund
    // and stop an ability, to avoid the chance of wrongly ordered functions.
    // This must be called as an action to get the abilcode & TriggerUnit.
    function CAS_FT_CancelAbilityActions takes nothing returns nothing
        call CAS_MR_RefundActions()
        call CAS_FT_UnitStopAbility( GetTriggerUnit(), GetSpellAbilityId() )
    endfunction

endlibrary

In short there are Three useful functions for certain situations.
NONE of these functions will work properly if called before the Spell's Effect starts.
(Use these only after EVENT_PLAYER_UNIT_SPELL_EFFECT)

Details for each Function:
  1. CAS_MR_RefundActions() - no arguments, uses Event Responses
    • This must be called as an Event Response (as a Trigger Action).
    • This is intentional, because it is not necessary whatsoever unless as a response to EVENT_PLAYER_UNIT_SPELL_EFFECT.
    • This may work (I think it is most likely) in a Trigger Condition - I have not tested by see no reason why it should not.
  2. CAS_FT_UnitStopAbility( u, abilcode ) - arguments: unit u, integer abilcode
    • This may be called at any time during the Follow Through Time of an Ability, as long as you know the Unit and the Ability's abilcode.
    • This functionality is to allow an Ability to be stopped midway in its Follow Through Time, long after the Spell's Effect Event fires.
      • This may be desired without a Mana Refund, if the Caster was able to use its Ability sufficiently.
    • This function will temporarily cause CAS_MR_RefundActions() to be unable to Get the Ability's Mana Cost.
      • This function should only be used in the same function as CAS_MR_RefundActions() if it is read AFTER CAS_MR_RefundActions()!
  3. CAS_FT_UnitCancelAbility() - no arguments, uses Event Responses
    • This must be called as an Event Response (as a Trigger Action).
    • This is intentional, because it is not necessary whatsoever unless as a response to EVENT_PLAYER_UNIT_SPELL_EFFECT.
    • This may work (I think it is most likely) in a Trigger Condition - I have not tested by see no reason why it should not.
    • This function exists solely for the purpose of calling CAS_RM_RefundActions() and CAS_FT_UnitStopAbility(u,abilcode) in that order
    • This is not an essential function if the system is used considerately.

Edit - I noticed something I had to fix: I had to update because the original snippet I shared was giving the Caster maximum mana when the Caster's pre-cast mana amount was ( Max - X ), where X is the mana cost to be refunded.

EDIT: Some forgotten mentions:
  • Cancelling/Stopping an Ability this way does not interrupt the Casting Unit's Order Queue.
  • Cancelling/Stopping an Ability at the "Begins to Cast" or "Begins Channeling" event may have varying results:
    • The Unit becomes somewhat locked, it would be maybe a good way to suspend a unit if done right.
      • A Unit will be in the semi-locked state for the duration of its Follow Through - it is unable to move if given a move, patrol, or attack order, nor can it take any other orders and it will not stop or hold-position, although these two icons alone will glow if the Unit is so ordered.
      • If given an order to stop or to hold position, the Unit will become "semi-unlocked"
      • From the semi-unlocked state, if a Unit is given a move, patrol, or attack order, or any other order other than a stop or hold position order, it will follow that new order and stop following through the cancelled Ability.
      • If a semi-unlocked Unit is given a stop or hold-position order, it will change nothing; the Unit will remain in its semi-unlocked state, still following through until given a non-stop/hold-position order.
  • When testing and Cancelling/Stopping Big Bad Voodoo at the typical Start of Effect Event, the Player had the capability of initiating an uncancellable Big Bad Voodoo "Aura" that would remain effective even while the Unit was moving and attacking and being given orders: if the Player Queued an Order other than stop/hold-position to be carried out after the cancelled Big Bad Voodoo (using the [SHIFT]+CLICK Order Queue), the Big Bad Voodoo would remain active in this "uncancellable" state - I didn't test the duration of the uncancelled Big Bad Voodoo.
  • I expect strange results for other Abilities that require a sustained channel effect.
  • The Mana Refund is safe in all cases (when used at the Start of Effect Event - before this point is too early).
  • The Cancelling and Stopping of Abilities can only be guaranteed to be safe if it is used with Channel-based Trigger Abilities that are being handled properly and do not otherwise interfere with this system in their process.
  • Non-Channel-based Abilities that are Cancelled or Stopped may have strange effects, some might be interesting to mess with, like that Big Bad Voodoo one, others might have adverse effects, like that Big Bad Voodoo one. Be warned!

Thanks for reading, I appreciate any feedback.
 
Last edited:
Top