1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. The contestants were to create water structures for the 20th Terraining Contest. Choose one in the public poll!
    Dismiss Notice
  3. Join other hivers in a friendly concept-art contest. The contestants have to create a genie coming out of its container. We wish you the best of luck!
    Dismiss Notice
  4. The Melee Mapping Contest #4: 2v2 - Results are out! Step by to congratulate the winners!
    Dismiss Notice
  5. We're hosting the 15th Mini-Mapping Contest with YouTuber Abelhawk! The contestants are to create a custom map that uses the hidden content within Warcraft 3 or is inspired by any of the many secrets within the game.
    Dismiss Notice
  6. Check out the Staff job openings thread.
    Dismiss Notice

Trigger Viewer

Aeraia.w3x
Variables
GUI Spell System
Read Me
Spell System Config
Spell System
Sample Triggers
Spell System Sample Config
Spell System Sample Trigger
Spell System Sample Filter
Spell System All In One Template
Aeraia
HOW TO INSTALL
Aeraia Config
Aeraia Cast
Aeraia Loop
Testmap
Revive
Enter map-specific custom script code below. This text will be included in the map script after variables are declared and before any trigger code.
//TESH.scrollpos=0
//TESH.alwaysfold=0
function RegisterSpellSystem takes integer abilId, code onEffect, code onLoop returns nothing
    set udg_Spell__Ability = abilId
    if onEffect != null then
        set udg_Spell__Trigger_OnEffect = CreateTrigger()
        call TriggerAddCondition(udg_Spell__Trigger_OnEffect, Filter(onEffect))
    endif
    if onLoop != null then
        set udg_Spell__Trigger_OnLoop = CreateTrigger()
        call TriggerAddCondition(udg_Spell__Trigger_OnLoop, Filter(onLoop))
    endif
    if gg_trg_Spell_System == null then
        call ExecuteFunc("InitTrig_Spell_System")
    endif
    call TriggerExecute(gg_trg_Spell_System)
endfunction
Name Type Is Array Initial Value
Aeraia_Ability abilcode No
Aeraia_AbilityPropel abilcode No
Aeraia_AbilitySlow abilcode No
Aeraia_AttackType attacktype No
Aeraia_Damage real Yes
Aeraia_DamageDecay real Yes
Aeraia_DamageType damagetype No
Aeraia_Loop integer No
Aeraia_MaxDur real Yes
Aeraia_Point location No
Aeraia_PropelChance real Yes
Aeraia_TempUnit unit No
Aeraia_Tornado integer Yes
Aeraia_TornadoGroup group Yes
Aeraia_TornadoRangeEffect real Yes
Aeraia_TornadoRangeFromCenter real Yes
Aeraia_TornadoSFX string No
Aeraia_TornadoWander boolean No
Aeraia_TornadoWanderAbil abilcode No
Aeraia_Victim unit No
Dipper_Phases integer Yes
Spell__Ability abilcode No
Spell__Caster unit No
Spell__CasterOwner player No
Spell__CastPoint location No
Spell__Channeling boolean No
Spell__Completed boolean No
Spell__DummyOwner player No
Spell__DummyType unitcode No
Spell__Duration real No
Spell__DurationPerLevel real No
Spell__Expired boolean No
Spell__Filter_AllowAlly boolean No
Spell__Filter_AllowDead boolean No
Spell__Filter_AllowEnemy boolean No
Spell__Filter_AllowFlying boolean No
Spell__Filter_AllowHero boolean No
Spell__Filter_AllowLiving boolean No
Spell__Filter_AllowMagicImmune boolean No
Spell__Filter_AllowMechanical boolean No
Spell__Filter_AllowNonHero boolean No
Spell__Filter_AllowStructure boolean No
Spell__Hash hashtable No
Spell__Index integer No
Spell__InRange real No
Spell__InRangeCount integer No
Spell__InRangeGroup group No
Spell__InRangeMax integer No
Spell__InRangePoint location No
Spell__InRangeUnit unit No
Spell__InRangeUnits unit Yes
Spell__Interval real No
Spell__Level integer No
Spell__LevelMultiplier real No
Spell__Running boolean No
Spell__StartDuration boolean No
Spell__Target unit No
Spell__TargetGroup group No
Spell__TargetPoint location No
Spell__Time real No
Spell__Trigger_InRangeFilter trigger No
Spell__Trigger_OnCast trigger No
Spell__Trigger_OnChannel trigger No
Spell__Trigger_OnEffect trigger No
Spell__Trigger_OnFinish trigger No
Spell__Trigger_OnLoop trigger No
Spell__UseTargetGroup boolean No
Spell__WakeTargets boolean No
Spell_i_AllowAlly boolean Yes
Spell_i_AllowDead boolean Yes
Spell_i_AllowEnemy boolean Yes
Spell_i_AllowFlying boolean Yes
Spell_i_AllowHero boolean Yes
Spell_i_AllowLiving boolean Yes
Spell_i_AllowMagicImmune boolean Yes
Spell_i_AllowMechanical boolean Yes
Spell_i_AllowNonHero boolean Yes
Spell_i_AllowStructure boolean Yes
Spell_i_Caster unit Yes
Spell_i_Channeling boolean Yes
Spell_i_Completed boolean Yes
Spell_i_Duration real Yes
Spell_i_EventType integer Yes
Spell_i_GroupN integer No
Spell_i_GroupStack group Yes
Spell_i_Head integer Yes
Spell_i_InRangeFilter trigger Yes
Spell_i_Instances integer No
Spell_i_LastTime real Yes
Spell_i_Level integer Yes
Spell_i_Linked boolean Yes
Spell_i_OnCastStack trigger Yes
Spell_i_OnChannelStack trigger Yes
Spell_i_OnEffectStack trigger Yes
Spell_i_OnFinishStack trigger Yes
Spell_i_OnLoopStack trigger Yes
Spell_i_PreloadDummy unit No
Spell_i_Recycle integer No
Spell_i_RecycleList integer Yes
Spell_i_Stack integer Yes
Spell_i_StackN integer No
Spell_i_StackRef integer Yes
Spell_i_Target unit Yes
Spell_i_TargetGroup group Yes
Spell_i_TargetX real Yes
Spell_i_TargetY real Yes
Spell_i_Time real Yes
Spell_i_Timer timer No
Spell_i_UseTG boolean Yes
TempLoc location No
TempUnit unit No
GUI Spell System by Bribe from HiveWorkshop.com
version 1.6.1.0

The main goal of this system is to have organized, readable, intuitive spells that don't have to have the same components rewritten for each spell.

To set up a spell to work with this system, there are a few necessary things to keep in mind. You must have a configuration trigger with the event "map initialization" to set up the spell. You must set the ability you base your spell on to the variable Spell__Ability. You must set the trigger(s) you want to run during an event of the spell to a Spell__Trigger_OnEvent variable. Finally, you must run the trigger Spell System <gen> once all of your variables are set.

Each configuration for a spell is going to include a minimum of this (though you only need one of the four Spell__Trigger_OnEvent variables):

Spell Configuration
Events
Map initialization
Conditions
Actions
Set Spell__Ability = <Your Ability>

Set Spell__Trigger_OnChannel = <When a unit starts channeling an ability>
Set Spell__Trigger_OnCast = <When a unit starts casting an ability>
Set Spell__Trigger_OnEffect = <When a unit starts the effect of an ability>
Set Spell__Trigger_OnFinish = <When a unit stops casting an ability>

Trigger - Run Spell System <gen> (ignoring conditions)

Unlike the configuration trigger, an event trigger isn't required to do anything specific with its variables. Here is what you need for an OnEvent trigger:

OnEvent Trigger
Events
Conditions
Actions

For a full list of configurable variables and functional variables, review the two triggers under "Sample Triggers" in the GUI Spell System trigger category on the left sidebar. I've commented the code wherever I've felt it was needed, though I can add more if requested. For those savvy in JASS, there are a load of comments in the all-JASS Spell System trigger.
Spell System Config
  Events
  Conditions
  Actions
    -------- Only one dummy unit type is needed as you can attach an effect to it of any kind --------
    -------- --------
    Set Spell__DummyType = Dummy (Vexorian, Anitarf, Infrane)
    Set Spell__DummyOwner = Neutral Extra
    Set Spell__Interval = (1 / 32.00)
    -------- --------
    -------- Configure default values for the unit filter: --------
    -------- --------
    Set Spell__Filter_AllowEnemy = True
    Set Spell__Filter_AllowLiving = True
    Set Spell__Filter_AllowHero = True
    Set Spell__Filter_AllowNonHero = True
    Set Spell__Filter_AllowAlly = False
    Set Spell__Filter_AllowDead = False
    Set Spell__Filter_AllowFlying = False
    Set Spell__Filter_AllowMechanical = False
    Set Spell__Filter_AllowStructure = False
    -------- --------
    -------- Magic immunity is a great thing to block, as it also discludes invulnerable units from being picked --------
    -------- --------
    Set Spell__Filter_AllowMagicImmune = False
    -------- --------
    -------- Normal WC3 abilities, like Channel, wake sleeping creeps - even if they don't deal damage or apply buffs. --------
    -------- Because of this, I provided an option to wake up creeps when they are enumerated by an InRange command. --------
    -------- --------
    Set Spell__WakeTargets = True
    -------- --------
    -------- Do not enable the following lines as they are variable declarations which make copying this system easier --------
    -------- --------
    Set Spell_i_Level[(Integer(Spell__LevelMultiplier))] = (Spell__Level + (Player number of Spell__CasterOwner))
    Set Spell_i_TargetX[(Integer(Spell_i_TargetY[(Integer(Spell_i_Time[(Integer(Spell__Time))]))]))] = ((X of Spell__CastPoint) + (X of Spell__TargetPoint))
    Set Spell__Caster = Spell_i_Caster[(Custom value of Spell_i_Target[(Custom value of Spell__Target)])]
    Unit Group - Add all units of Spell_i_TargetGroup[0] to Spell__TargetGroup
    Set Spell__Running = Spell__UseTargetGroup
    Set Spell__Completed = Spell_i_UseTG[Spell_i_Level[(Integer((Elapsed time for Spell_i_Timer)))]]
    Set Spell_i_Completed[0] = Spell_i_Linked[Spell_i_EventType[0]]
    Set Spell__Trigger_OnLoop = Spell_i_OnLoopStack[(Load 0 of 0 from Spell__Hash)]
    Set Spell__Trigger_OnFinish = Spell_i_OnFinishStack[Spell__Index]
    Set Spell__Trigger_OnEffect = Spell_i_OnEffectStack[0]
    Set Spell__Trigger_OnChannel = Spell_i_OnChannelStack[0]
    Set Spell__Trigger_OnCast = Spell_i_OnCastStack[Spell_i_Recycle]
    Set Spell_i_Instances = Spell_i_Head[Spell_i_RecycleList[Spell_i_Stack[Spell_i_StackRef[Spell_i_StackN]]]]
    Set Spell_i_PreloadDummy = No unit
    Set Spell__Ability = Spell__Ability
    Set Spell__InRangeGroup = (Units within Spell__InRange of Spell__InRangePoint)
    Set Spell_i_AllowAlly[0] = Spell__Filter_AllowAlly
    Set Spell_i_AllowDead[0] = Spell__Filter_AllowDead
    Set Spell_i_AllowEnemy[0] = Spell__Filter_AllowEnemy
    Set Spell_i_AllowFlying[0] = Spell__Filter_AllowFlying
    Set Spell_i_AllowHero[0] = Spell__Filter_AllowHero
    Set Spell_i_AllowMagicImmune[0] = Spell__Filter_AllowMagicImmune
    Set Spell_i_AllowMechanical[0] = Spell__Filter_AllowMechanical
    Set Spell_i_AllowNonHero[0] = Spell__Filter_AllowNonHero
    Set Spell_i_AllowStructure[0] = Spell__Filter_AllowStructure
    Set Spell_i_AllowLiving[0] = Spell__Filter_AllowLiving
    Set Spell__Channeling = Spell_i_Channeling[0]
    Set Spell_i_GroupN = (Number of units in Spell_i_GroupStack[0])
    Set Spell__InRangeUnits[Spell__InRangeCount] = No unit
    Set Spell__Duration = (Spell_i_Duration[(Integer(Spell__DurationPerLevel))] + Spell_i_LastTime[0])
    Set Spell__Expired = Spell__StartDuration
    Set Spell__InRangeMax = ((Supply used by Spell__InRangeUnit) + ((Evaluation count of Spell__Trigger_InRangeFilter) + (Evaluation count of Spell_i_InRangeFilter[0])))
//TESH.scrollpos=0
//TESH.alwaysfold=0
function SpellIndexGetVars takes integer i returns nothing
    set udg_Spell__Index = i
    set udg_Spell__Caster = udg_Spell_i_Caster[i]
    set udg_Spell__CasterOwner = GetOwningPlayer(udg_Spell__Caster)
    set udg_Spell__Level = udg_Spell_i_Level[i]
    set udg_Spell__LevelMultiplier = udg_Spell__Level //Spell__LevelMultiplier is a real variable.
    set udg_Spell__Target = udg_Spell_i_Target[i]
   
    //Assign the saved coordinates to the static locations
    call MoveLocation(udg_Spell__CastPoint, GetUnitX(udg_Spell__Caster), GetUnitY(udg_Spell__Caster))
    if udg_Spell__Target == null then
        call MoveLocation(udg_Spell__TargetPoint, udg_Spell_i_TargetX[i], udg_Spell_i_TargetY[i])
    else
        call MoveLocation(udg_Spell__TargetPoint, GetUnitX(udg_Spell__Target), GetUnitY(udg_Spell__Target))
    endif
    set udg_Spell__TargetGroup = udg_Spell_i_TargetGroup[i]
    set udg_Spell__Completed = udg_Spell_i_Completed[i]
    set udg_Spell__Channeling = udg_Spell_i_Channeling[i]
endfunction

function SpellSetFilters takes integer i returns nothing
    set udg_Spell_i_AllowEnemy[i]       = udg_Spell__Filter_AllowEnemy
    set udg_Spell_i_AllowAlly[i]        = udg_Spell__Filter_AllowAlly
    set udg_Spell_i_AllowDead[i]        = udg_Spell__Filter_AllowDead
    set udg_Spell_i_AllowLiving[i]      = udg_Spell__Filter_AllowLiving
    set udg_Spell_i_AllowMagicImmune[i] = udg_Spell__Filter_AllowMagicImmune
    set udg_Spell_i_AllowMechanical[i]  = udg_Spell__Filter_AllowMechanical
    set udg_Spell_i_AllowStructure[i]   = udg_Spell__Filter_AllowStructure
    set udg_Spell_i_AllowFlying[i]      = udg_Spell__Filter_AllowFlying
    set udg_Spell_i_AllowHero[i]        = udg_Spell__Filter_AllowHero
    set udg_Spell_i_AllowNonHero[i]     = udg_Spell__Filter_AllowNonHero
endfunction

function SpellIndexDestroy takes integer i returns nothing
    local integer indexOf
    local integer index
    if udg_Spell_i_RecycleList[i] >= 0 then
        return
    endif
    //If the caster is still channeling on the spell, don't destroy until it's finished:
    if not udg_Spell_i_Channeling[i] then
        set index = udg_Spell_i_Head[i]
        set udg_Spell_i_RecycleList[i] = udg_Spell_i_Recycle
        set udg_Spell_i_Recycle = i
       
        //Reset things to defaults:
        set udg_Spell_i_Time[i] = 0.00
        set udg_Spell_i_LastTime[i] = 0.00
        set udg_Spell_i_Duration[i] = 0.00
        set udg_Spell_i_Completed[i] = false
        set udg_Spell_i_Caster[i] = null
        set udg_Spell_i_Target[i] = null
        set udg_Spell_i_OnLoopStack[i] = null
       
        //Recycle any applicable target unit group.
        if udg_Spell_i_TargetGroup[i] != null then
            call GroupClear(udg_Spell_i_TargetGroup[i])
            set udg_Spell_i_GroupStack[udg_Spell_i_GroupN] = udg_Spell_i_TargetGroup[i]
            set udg_Spell_i_GroupN = udg_Spell_i_GroupN + 1
            set udg_Spell_i_TargetGroup[i] = null
        endif
       
        //Clear any user-specified data in the hashtable:
        call FlushChildHashtable(udg_Spell__Hash, i)
        //call BJDebugMsg("Destroying index: " + I2S(i))
    endif
   
    set indexOf = udg_Spell_i_StackRef[i]
    if indexOf >= 0 then
        set index = udg_Spell_i_StackN - 1
        set udg_Spell_i_StackN = index
       
        set udg_Spell_i_StackRef[udg_Spell_i_Stack[index]] = indexOf
        set udg_Spell_i_Stack[indexOf] = udg_Spell_i_Stack[index]
        if index == 0 then
            //If no more spells require the timer, pause it.
            call PauseTimer(udg_Spell_i_Timer)
        endif
        set udg_Spell_i_StackRef[i] = -1
    endif
endfunction

function SpellTriggerExecute takes integer i, trigger t returns real
    local real d = udg_Spell_i_Duration[i]
    local boolean b = false
    set udg_Spell__Duration = d
    set udg_Spell__Time = 0.00
    if t != null then
        set udg_Spell__Trigger_OnLoop = null
        set udg_Spell__Expired = d <= 0.00 //If the duration is <= 0, the spell has expired.
        call SpellIndexGetVars(i)
        if TriggerEvaluate(t) then
            call TriggerExecute(t)
        endif
        if udg_Spell__Trigger_OnLoop != null then
            set udg_Spell_i_OnLoopStack[i] = udg_Spell__Trigger_OnLoop
        endif
        //The remaining lines in this function process the duration specified by the user.
        if udg_Spell__StartDuration then
            set udg_Spell__StartDuration = false
            set udg_Spell__Duration = udg_Spell_i_Duration[udg_Spell_i_Head[i]] + udg_Spell_i_LastTime[udg_Spell_i_Head[i]]*udg_Spell__LevelMultiplier
        elseif (udg_Spell__Expired and d > 0.00) or (udg_Spell__Duration <= 0.00) then
            set udg_Spell__Duration = 0.00
            return udg_Spell__Time
            //The user manually expired the spell or the spell duration ended on its own.
        endif
        if d != udg_Spell__Duration then
            //A new duration has been assigned
            set d = udg_Spell__Duration
            set b = true
        endif
        set udg_Spell__Duration = 0.00
        if udg_Spell__Time == 0.00 then
            if udg_Spell_i_LastTime[i] == 0.00 then
                if udg_Spell_i_Time[udg_Spell_i_Head[i]] > 0.00 then
                    //The user specified a default interval to follow:
                    set udg_Spell__Time = udg_Spell_i_Time[udg_Spell_i_Head[i]]
                else
                    //Set the spell time to the minimum.
                    set udg_Spell__Time = udg_Spell__Interval
                endif
            else
                //Otherwise, set it to what it was before.
                set udg_Spell__Time = udg_Spell_i_LastTime[i]
            endif
        //else, the user is specifying a new time for the spell.
        endif
        set udg_Spell_i_LastTime[i] = udg_Spell__Time //Whatever the case, remember this time for next time.
        if b then
            //The duration was just assigned
            set udg_Spell_i_Duration[i] = d
        else
            //The duration has been ongoing
            set udg_Spell_i_Duration[i] = d - udg_Spell__Time
        endif
    endif
    return udg_Spell__Time
endfunction

//===========================================================================
// Runs every Spell__Interval seconds and handles all of the timed events.
//
function SpellTimerLoop takes nothing returns nothing
    local integer i = udg_Spell_i_StackN
    local integer node
    local real time
    set udg_Spell__Running = true
   
    //Run stack top to bottom to avoid skipping slots when destroying.
    loop
        set i = i - 1
        exitwhen i < 0
        set node = udg_Spell_i_Stack[i]
        set time = udg_Spell_i_Time[node] - udg_Spell__Interval
        if time <= 0.00 then
            set time = SpellTriggerExecute(node, udg_Spell_i_OnLoopStack[node])
        endif
        if time <= 0.00 then
            call SpellIndexDestroy(node)
        else
            set udg_Spell_i_Time[node] = time
        endif
    endloop
    set udg_Spell__Running = false
endfunction

//===========================================================================
// This is the meat of the system as it handles the event responses.
//
function RunSpellEvent takes nothing returns boolean
    local boolean b
    local integer aid = GetSpellAbilityId()
    local integer head = LoadInteger(udg_Spell__Hash, 0, aid)
    local integer i
    local integer id
    local trigger t
    local playerunitevent eid
    if head == 0 then
        //Nothing for this ability has been registered. Skip the sequence.
        return false
    endif
    set eid = ConvertPlayerUnitEvent(GetHandleId(GetTriggerEventId()))
    set udg_Spell__Caster = GetTriggerUnit()
    set id = GetHandleId(udg_Spell__Caster)
    set i = LoadInteger(udg_Spell__Hash, aid, id)
    if i == 0 then
        //This block will almost always happen with the OnChannel event. In the
        //case of Charge Gold and Lumber, only an OnEffect event will run.
        set i = udg_Spell_i_Recycle
        if i == 0 then
            //Create a new, unique index
            set i = udg_Spell_i_Instances + 1
            set udg_Spell_i_Instances = i
        else
            //Repurpose an existing one
            set udg_Spell_i_Recycle = udg_Spell_i_RecycleList[i]
        endif
        //call BJDebugMsg("Creating index: " + I2S(i))
        set udg_Spell_i_RecycleList[i] = -1
        set udg_Spell_i_StackRef[i] = -1
        set udg_Spell_i_Head[i] = head
       
        if eid == EVENT_PLAYER_UNIT_SPELL_CHANNEL then
            set udg_Spell_i_Channeling[i] = true
            call SaveInteger(udg_Spell__Hash, aid, id, i)
            set t = udg_Spell_i_OnChannelStack[head]
        else //eid == EVENT_PLAYER_UNIT_SPELL_EFFECT
            set t = udg_Spell_i_OnEffectStack[head]
        endif
        set udg_Spell_i_Caster[i] = udg_Spell__Caster
        set udg_Spell_i_Level[i] = GetUnitAbilityLevel(udg_Spell__Caster, aid)
        set udg_Spell_i_Target[i] = GetSpellTargetUnit()
        set udg_Spell_i_TargetX[i] = GetSpellTargetX()
        set udg_Spell_i_TargetY[i] = GetSpellTargetY()
       
        set udg_Spell_i_OnLoopStack[i] = udg_Spell_i_OnLoopStack[head]
        if udg_Spell_i_UseTG[head] then
            //Get a recycled unit group or create a new one.
            set id = udg_Spell_i_GroupN - 1
            if id >= 0 then
                set udg_Spell_i_GroupN = id
                set udg_Spell_i_TargetGroup[i] = udg_Spell_i_GroupStack[id]
            else
                set udg_Spell_i_TargetGroup[i] = CreateGroup()
            endif
        endif
    elseif eid == EVENT_PLAYER_UNIT_SPELL_CAST then
        set t = udg_Spell_i_OnCastStack[head]
    elseif eid == EVENT_PLAYER_UNIT_SPELL_EFFECT then
        set t = udg_Spell_i_OnEffectStack[head]
    elseif eid == EVENT_PLAYER_UNIT_SPELL_FINISH then
        set udg_Spell_i_Completed[i] = true
        return true
    else //eid == EVENT_PLAYER_UNIT_SPELL_ENDCAST
        set udg_Spell_i_Channeling[i] = false
        call RemoveSavedInteger(udg_Spell__Hash, aid, id)
        set t = udg_Spell_i_OnFinishStack[head]
    endif
    if SpellTriggerExecute(i, t) > 0.00 then
        //Set the spell time to the user-specified one.
        set udg_Spell_i_Time[i] = udg_Spell__Time
        if udg_Spell_i_StackRef[i] < 0 then
            //Allocate the spell index onto the loop stack.
            set aid = udg_Spell_i_StackN
            set udg_Spell_i_Stack[aid] = i
            set udg_Spell_i_StackRef[i] = aid
            set udg_Spell_i_StackN = aid + 1
            if aid == 0 then
                //If this is the first spell index using the timer, start it up:
                call TimerStart(udg_Spell_i_Timer, udg_Spell__Interval, true, function SpellTimerLoop)
            endif
        endif
    elseif (not udg_Spell_i_Channeling[i]) and (t != null or udg_Spell_i_Time[i] <= 0.00) then
        call SpellIndexDestroy(i)
    endif
    set t = null
    return true
endfunction

//This function is invoked if an event was launched recursively by another event's callback.
function RunPreSpellEvent takes nothing returns nothing
    local integer i = udg_Spell__Index
    local real time = udg_Spell__Time
    local real d = udg_Spell__Duration
    local boolean expired = udg_Spell__Expired
    if udg_Spell__Trigger_OnLoop != null then
        set udg_Spell_i_OnLoopStack[i] = udg_Spell__Trigger_OnLoop
    endif
    if RunSpellEvent() then
        set udg_Spell__Time = time
        set udg_Spell__Duration = d
        set udg_Spell__Expired = expired
        call SpellIndexGetVars(i)
    endif
endfunction

//===========================================================================
// Base function of the system: runs when an ability event does something.
//
function SpellSystemEvent takes nothing returns boolean
    if udg_Spell__Running then
        call RunPreSpellEvent()
    else
        set udg_Spell__Running = true
        call RunSpellEvent()
        set udg_Spell__Running = false
    endif
    return false
endfunction

//===========================================================================
// Set Spell__Ability to your spell's ability
// Set Spell__Trigger_OnChannel/Cast/Effect/Finish/Loop to any trigger(s) you
// want to automatically run.
//
// GUI-friendly: Run Spell System <gen> (ignoring conditions)
//
function SpellSystemRegister takes nothing returns nothing
    local integer aid = udg_Spell__Ability
    local integer head = udg_Spell_i_Instances + 1
   
    if HaveSavedInteger(udg_Spell__Hash, 0, aid) or aid == 0 then
        //The system rejects duplicate or unassigned abilities.
        return
    endif
    set udg_Spell_i_Instances = head
   
    //Preload the ability on dummy unit to help prevent first-instance lag
    call UnitAddAbility(udg_Spell_i_PreloadDummy, aid)
   
    //Save head index to the spell ability so it be referenced later.
    call SaveInteger(udg_Spell__Hash, 0, aid, head)
   
    //Set any applicable event triggers.
    set udg_Spell_i_OnChannelStack[head]= udg_Spell__Trigger_OnChannel
    set udg_Spell_i_OnCastStack[head]   = udg_Spell__Trigger_OnCast
    set udg_Spell_i_OnEffectStack[head] = udg_Spell__Trigger_OnEffect
    set udg_Spell_i_OnFinishStack[head] = udg_Spell__Trigger_OnFinish
    set udg_Spell_i_OnLoopStack[head]   = udg_Spell__Trigger_OnLoop
    set udg_Spell_i_InRangeFilter[head] = udg_Spell__Trigger_InRangeFilter
   
    //Set any customized filter variables:
    call SpellSetFilters(head)
   
    //Tell the system to automatically create target groups, if needed
    set udg_Spell_i_UseTG[head] = udg_Spell__UseTargetGroup
   
    //Set the default time sequences if a duration is used:
    set udg_Spell_i_Time[head]     = udg_Spell__Time
    set udg_Spell_i_Duration[head] = udg_Spell__Duration
    set udg_Spell_i_LastTime[head] = udg_Spell__DurationPerLevel
   
    //Set variables back to their defaults:
    set udg_Spell__Trigger_OnChannel    = null
    set udg_Spell__Trigger_OnCast       = null
    set udg_Spell__Trigger_OnEffect     = null
    set udg_Spell__Trigger_OnFinish     = null
    set udg_Spell__Trigger_OnLoop       = null
    set udg_Spell__Trigger_InRangeFilter= null
    set udg_Spell__UseTargetGroup       = false
    set udg_Spell__Time                 = 0.00
    set udg_Spell__Duration             = 0.00
    set udg_Spell__DurationPerLevel     = 0.00
   
    set udg_Spell__Filter_AllowEnemy        = udg_Spell_i_AllowEnemy[0]
    set udg_Spell__Filter_AllowAlly         = udg_Spell_i_AllowAlly[0]
    set udg_Spell__Filter_AllowDead         = udg_Spell_i_AllowDead[0]
    set udg_Spell__Filter_AllowMagicImmune  = udg_Spell_i_AllowMagicImmune[0]
    set udg_Spell__Filter_AllowMechanical   = udg_Spell_i_AllowMechanical[0]
    set udg_Spell__Filter_AllowStructure    = udg_Spell_i_AllowStructure[0]
    set udg_Spell__Filter_AllowFlying       = udg_Spell_i_AllowFlying[0]
    set udg_Spell__Filter_AllowHero         = udg_Spell_i_AllowHero[0]
    set udg_Spell__Filter_AllowNonHero      = udg_Spell_i_AllowNonHero[0]
    set udg_Spell__Filter_AllowLiving       = udg_Spell_i_AllowLiving[0]
endfunction

function SpellFilterCompare takes boolean is, boolean yes, boolean no returns boolean
    return (is and yes) or ((not is) and no)
endfunction

//===========================================================================
// Before calling this function, set Spell__InRangePoint to whatever point
// you need, THEN set Spell__InRange to the radius you need. The system will
// enumerate the units matching the configured filter and fill them into
// Spell_InRangeGroup.
//
function SpellGroupUnitsInRange takes nothing returns boolean
    local integer i = udg_Spell_i_Head[udg_Spell__Index]
    local integer j = 0
    local unit u
    local real padding = 64.00
    if udg_Spell_i_AllowStructure[i] then
        //A normal unit can only have up to size 64.00 collision, but if the
        //user needs to check for structures we need a padding big enough for
        //the "fattest" ones: Tier 3 town halls.
        set padding = 197.00
    endif
    call GroupEnumUnitsInRangeOfLoc(udg_Spell__InRangeGroup, udg_Spell__InRangePoint, udg_Spell__InRange + padding, null)
    loop
        set u = FirstOfGroup(udg_Spell__InRangeGroup)
        exitwhen u == null
        call GroupRemoveUnit(udg_Spell__InRangeGroup, u)
        loop
            exitwhen not IsUnitInRangeLoc(u, udg_Spell__InRangePoint, udg_Spell__InRange)
            exitwhen not SpellFilterCompare(IsUnitType(u, UNIT_TYPE_DEAD), udg_Spell_i_AllowDead[i], udg_Spell_i_AllowLiving[i])
            exitwhen not SpellFilterCompare(IsUnitAlly(u, udg_Spell__CasterOwner), udg_Spell_i_AllowAlly[i], udg_Spell_i_AllowEnemy[i])
            exitwhen not SpellFilterCompare(IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_RESISTANT), udg_Spell_i_AllowHero[i], udg_Spell_i_AllowNonHero[i])
            exitwhen IsUnitType(u, UNIT_TYPE_STRUCTURE) and not udg_Spell_i_AllowStructure[i]
            exitwhen IsUnitType(u, UNIT_TYPE_FLYING) and not udg_Spell_i_AllowFlying[i]
            exitwhen IsUnitType(u, UNIT_TYPE_MECHANICAL) and not udg_Spell_i_AllowMechanical[i]
            exitwhen IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not udg_Spell_i_AllowMagicImmune[i]
            set udg_Spell__InRangeUnit = u
            //Run the user's designated filter, if one exists.
            exitwhen udg_Spell_i_InRangeFilter[i] != null and not TriggerEvaluate(udg_Spell_i_InRangeFilter[i])
            set j = j + 1
            set udg_Spell__InRangeUnits[j] = u
            exitwhen true
        endloop
    endloop
    if j > udg_Spell__InRangeMax and udg_Spell__InRangeMax > 0 then
        //The user has defined a maximum number of units allowed in the group.
        //Remove a random unit until the total does not exceed capacity.
        loop
            set i = GetRandomInt(1, j)
            set udg_Spell__InRangeUnits[i] = udg_Spell__InRangeUnits[j]
            set j = j - 1
            exitwhen j == udg_Spell__InRangeMax
        endloop
    endif
    set udg_Spell__InRangeCount = j
    set udg_Spell__InRangeMax = 0
    set udg_Spell__InRange = 0.00
    loop
        exitwhen j == 0
        call GroupAddUnit(udg_Spell__InRangeGroup, udg_Spell__InRangeUnits[j])
        if udg_Spell__WakeTargets and UnitIsSleeping(u) then
            call UnitWakeUp(u)
        endif
        set j = j - 1
    endloop
    return false
endfunction

//===========================================================================
function InitTrig_Spell_System takes nothing returns nothing
    local integer i = 16
    local player p
    local trigger t
   
    if gg_trg_Spell_System != null then
        //A JASS function call already initialized the system.
        return
    endif
   
    //This runs before map init events so the hashtable is ready before then.
    set udg_Spell__Hash = InitHashtable()
   
    //Initialize these two locations which will never get removed
    set udg_Spell__CastPoint = Location(0, 0)
    set udg_Spell__TargetPoint = Location(0, 0)
   
    //Recycle existing unit groups into the recycle stack to avoid needing to destroy any extras.
    set udg_Spell_i_GroupStack[2] = udg_Spell__TargetGroup
    set udg_Spell_i_GroupStack[3] = udg_Spell_i_TargetGroup[0]
    set udg_Spell_i_GroupStack[4] = udg_Spell_i_TargetGroup[1]
    set udg_Spell_i_GroupN = 5 //There are already five valid unit groups thanks to Variable Editor.
   
    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_Spell__InRange", GREATER_THAN, 0.00)
    call TriggerAddCondition(t, Filter(function SpellGroupUnitsInRange))
   
    set t = CreateTrigger()
    call TriggerAddCondition(t, Filter(function SpellSystemEvent))
    loop
        set i = i - 1
        set p = Player(i)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CHANNEL, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CAST, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_FINISH, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
        exitwhen i == 0
    endloop
    set p = null
    set t = null
   
    //Run the configuration trigger so its variables are ready before the
    //map initialization events run.
    call TriggerExecute(gg_trg_Spell_System_Config)
    call SpellSetFilters(0)
   
    //Create this trigger so it's GUI-friendly.
    set gg_trg_Spell_System = CreateTrigger()
    call TriggerAddAction(gg_trg_Spell_System, function SpellSystemRegister)
    set gg_trg_Spell_System_Config = gg_trg_Spell_System //In case the user accidentally picks this one
   
    //Create a dummy unit for preloading abilities.
    set udg_Spell_i_PreloadDummy = CreateUnit(udg_Spell__DummyOwner, udg_Spell__DummyType, 0, 0, 0)
    call UnitApplyTimedLife(udg_Spell_i_PreloadDummy, 'BTLF', 0.01)
endfunction
Spell System Sample Config
  Events
    Map initialization
  Conditions
  Actions
    -------- --------
    -------- I have created this trigger as a means to show users what can (or must) be configured for each spell --------
    -------- --------
    -------- You must assign an ability ID to let the system know which spell you're using: --------
    -------- --------
    Set Spell__Ability = (Ability being cast)
    -------- --------
    -------- Next, you must pick at least one of the following types of triggers to run at a phase of the spell: --------
    -------- --------
    Set Spell__Trigger_OnChannel = (This trigger)
    Set Spell__Trigger_OnCast = (This trigger)
    Set Spell__Trigger_OnEffect = (This trigger)
    Set Spell__Trigger_OnFinish = (This trigger)
    -------- --------
    -------- Next, you can specify a trigger to run periodically, if you want that type of behavior. --------
    -------- --------
    Set Spell__Trigger_OnLoop = (This trigger)
    -------- --------
    -------- Added 28 Feb 2016: You can now specify Spell__Time, Spell__Duration and Spell__DurationPerLevel from the Config trigger. --------
    -------- NOTE: With these settings, the OnLoop trigger will run every 1 second for 4/5/6 seconds --------
    -------- --------
    Set Spell__Time = 1.00
    Set Spell__Duration = 3.00
    Set Spell__DurationPerLevel = 1.00
    -------- --------
    -------- The next ones are for the InRange filter, if you use it. You don't have to include any of these for many typical spells. --------
    -------- TIPS: --------
    -------- > For healing spells, you'll want to set AllowAllies to True and AllowEnemies to False. --------
    -------- > If you are creating a "kill unit" spell, you'll want to set AllowHero to False. --------
    -------- > If you are making a custom "Resurrect", you'll want to set AllowDead to True and AllowLiving to False. --------
    -------- > AllowMagicImmune as False also prevents invulnerable units from being picked, which is a plus in my opinion. --------
    -------- --------
    Set Spell__Filter_AllowEnemy = True
    Set Spell__Filter_AllowLiving = True
    Set Spell__Filter_AllowHero = True
    Set Spell__Filter_AllowNonHero = True
    Set Spell__Filter_AllowAlly = False
    Set Spell__Filter_AllowDead = False
    Set Spell__Filter_AllowFlying = False
    Set Spell__Filter_AllowMechanical = False
    Set Spell__Filter_AllowStructure = False
    Set Spell__Filter_AllowMagicImmune = False
    -------- --------
    -------- NEW: Added 17 May 2016 - you can specify your own trigger conditions to add to the InRange check --------
    -------- The trigger conditions reference the unit "Spell__InRangeUnit" --------
    -------- --------
    Set Spell__Trigger_InRangeFilter = Spell_System_Sample_Filter <gen>
    -------- --------
    -------- If you want to keep track of which units have already been hit by the spell, set UseTargetGroup to True --------
    -------- TIPS: --------
    -------- > The group is called Spell__TargetGroup. It starts empty and will destroy itself when the spell is finished --------
    -------- > After using a Spell__InRange check, you can use "Add all units in Spell__InRangeGroup to Spell__TargetGroup" --------
    -------- --------
    Set Spell__UseTargetGroup = True
    -------- --------
    -------- When everything is set the way you want it, run Spell System <gen>: --------
    -------- --------
    Trigger - Run Spell_System <gen> (ignoring conditions)
    -------- --------
    -------- To recap, you MUST set Spell__Ability and you MUST have an OnChannel/Cast/Effect or Finish trigger --------
    -------- --------
Spell System Sample Trigger
  Events
  Conditions
  Actions
    -------- --------
    -------- I have created this sample to show what you can do from Spell__Trigger_OnEffect/Loop/Channel/Cast/Finish. --------
    -------- --------
    -------- The following variables are set so you can see what they represent, but in a real spell they will already be set to these values --------
    -------- TIPS: --------
    -------- > Spell__CastPoint and Spell__TargetPoint should never be removed as they do not leak --------
    -------- > Spell__CasterOwner will change if the caster changes ownership during the spell due to "Charm" or "Possession" --------
    -------- > Spell__Level will stay the same even if the ability was leveled up during the spell --------
    -------- > Unlike in normal WC3 events, Spell__Target and Spell__TargetPoint actually work from an OnFinish event. --------
    -------- --------
    Set Spell__Caster = (Casting unit)
    Set Spell__CastPoint = (Position of Spell__Caster)
    Set Spell__CasterOwner = (Owner of Spell__Caster)
    Set Spell__Level = (Level of (Ability being cast) for Spell__Caster)
    Set Spell__LevelMultiplier = (Real(Spell__Level))
    Set Spell__Target = (Target unit of ability being cast)
    Set Spell__TargetPoint = (Target point of ability being cast)
    -------- --------
    -------- I present to you the unique integer index for this particular cast of the spell: Spell__Index --------
    -------- TIPS: --------
    -------- > Spell__Index is used as an array index so that you can store your spell data using it --------
    -------- > This number is unique per-spell, making everything which uses this fully-MUI. --------
    -------- > This number is automatically recycled when the spell is finished or the Spell__Time has expired. --------
    -------- --------
    Set Dipper_Phases[Spell__Index] = 0
    -------- --------
    -------- Specify how long to wait before the OnLoop trigger runs (or runs again, if reset from an OnLoop trigger) --------
    -------- TIPS: --------
    -------- > This is only required if you use the OnLoop trigger --------
    -------- > If you want a spell index to not be destroyed when the OnFinish event runs, you will need to set a Spell__Time to an above-0 value --------
    -------- > If you don't use an OnLoop trigger but want Spell__Index around longer than the channel time, set Spell__Time to how long you'll need that index --------
    -------- --------
    Set Spell__Time = 2.00
    -------- --------
    -------- Alternatively (or additionally), you can set the Spell__Duration to how long the OnLoop trigger should repeat --------
    -------- TIPS: --------
    -------- > If you don't specify a Spell__Time, the OnLoop trigger will run every Spell__Interval (0.03125) seconds --------
    -------- > From the OnLoop trigger, you can check if the spell is about to end via the condition "Spell__Expired Equal to True". --------
    -------- --------
    Set Spell__Duration = 10.00
    -------- --------
    -------- Alternatively, if you set the Spell__Duration (or at least Spell__DurationPerLevel) from the Config trigger, --------
    -------- you can tell the system to start the duration with the Spell__StartDuration boolean: --------
    -------- --------
    Set Spell__StartDuration = True
    -------- --------
    -------- You can change (or even add, if it wasn't yet assigned) your periodic trigger during any phase of the spell, like so: --------
    -------- --------
    Set Spell__Trigger_OnLoop = (This trigger)
    -------- --------
    -------- If you set a Spell__Duration and want to know if it's about to expire, check if Spell__Duration is less than or equal to 0.00 --------
    -------- --------
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        Spell__Duration Less than or equal to 0.00
      Then - Actions
        -------- The loop trigger will no longer run at this point, unless you specify a new spell time --------
      Else - Actions
        -------- The loop trigger will keep going, as the duration hasn't yet ended --------
    -------- --------
    -------- You can even force the duration to come to a premature end by setting Spell__Duration to 0.00, yourself. --------
    -------- --------
    Set Spell__Duration = 0.00
    -------- --------
    -------- Spell__Completed lets you know if the spell was interrupted (False) or the unit completed channeling it (True) --------
    -------- Spell__Channeling lets you know if the spell is still being channelled (True) or the caster has stopped (False). --------
    -------- NOTE: These booleans should be used only from Spell__Trigger_OnFinish or Spell__Trigger_OnLoop. --------
    -------- --------
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        Spell__Channeling Equal to False
      Then - Actions
        -------- The unit is no longer channeling the spell --------
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            Spell__Completed Equal to True
          Then - Actions
            -------- The spell finished normally --------
          Else - Actions
            -------- The spell was interrupted --------
      Else - Actions
        -------- The unit is still working on it --------
    -------- --------
    -------- Spell__TargetGroup is a nice one to have if you're keeping track of different units hit by this spell instance --------
    -------- NOTE: This group is unique per spell instance, but you must first Set Spell__UseTargetGroup = True from your config trigger --------
    -------- --------
    Unit Group - Add Spell__Target to Spell__TargetGroup
    -------- --------
    -------- The Spell__InRange feature picks all units in range and adds them to Spell__InRangeGroup --------
    -------- NOTE: Spell__InRangePoint must be set first, then the Spell__InRange real value. --------
    -------- Additionally, Spell__InRangeGroup does not leak and should never be destroyed. --------
    -------- --------
    -------- NEW: Added 17 May 2016, you can also set Spell__InRangeMax to limit the group to that many units --------
    -------- - Extra units are removed from the group at random --------
    -------- - Make sure to still set Spell__InRange after setting the first two. --------
    -------- --------
    Set Spell__InRangePoint = Spell__CastPoint
    Set Spell__InRangeMax = 5
    Set Spell__InRange = 300.00
    Unit Group - Add all units of Spell__InRangeGroup to Spell__TargetGroup
    -------- --------
    -------- Just to show how much time that saves, those four lines are a replacement to the following tedious stuff: --------
    -------- --------
    Custom script: set bj_wantDestroyGroup = true
    Unit Group - Pick every unit in (Units within 300.00 of Spell__CastPoint) and do (Actions)
      Loop - Actions
        -------- --------
        -------- The following conditions should have the same output as the default filter that Spell System uses. --------
        -------- However, (Unit is Alive) does not have guaranteed accuracy in GUI as a corpse's HP can be above 0. --------
        -------- Additionally, these conditions are buried within the code instead of made available in the configuration trigger. --------
        -------- --------
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            ((Picked unit) is A structure) Equal to False
            ((Picked unit) is A ground unit) Equal to True
            ((Picked unit) is Mechanical) Equal to False
            ((Picked unit) is Magic Immune) Equal to False
            ((Picked unit) is alive) Equal to True
            ((Picked unit) belongs to an enemy of Spell__CasterOwner) Equal to True
          Then - Actions
            Unit Group - Add (Picked unit) to Spell__InRangeGroup
          Else - Actions
    Custom script: set bj_wantDestroyGroup = true
    Unit Group - Pick every unit in (Random 5 units from Spell__InRangeGroup) and do (Actions)
      Loop - Actions
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            ((Picked unit) is sleeping) Equal to True
          Then - Actions
            -------- This is something I added to Spell System as units targeted by object editor spells are also awoken. --------
            Unit - Wake up (Picked unit)
          Else - Actions
        Unit Group - Add (Picked unit) to Spell__TargetGroup
    -------- --------
    -------- Alternatively, instead of (or in addition to) using Spell__InRangeGroup, --------
    -------- you have access to Spell__InRangeUnits and Spell__InRangeCount. --------
    -------- --------
    -------- To loop through the unit list instead of picking them, loop from 1 to (Spell__InRangeCount) --------
    -------- NOTE: I skip index 0 on purpose to make it easier to loop through it in GUI --------
    -------- --------
    For each (Integer A) from 1 to Spell__InRangeCount, do (Actions)
      Loop - Actions
        -------- --------
        Game - Display to (All players) the text: (Name of Spell__InRangeUnits[(Integer A)])
        -------- --------
    -------- --------
    -------- Having it in array format, you can easily pick a random unit, like so: --------
    -------- --------
    Unit - Kill Spell__InRangeUnits[(Random integer number between 1 and Spell__InRangeCount)]
    -------- --------
    -------- And, finally, I've made it so you have access to Spell__Hash --------
    -------- NOTE: Only save as (anything you need) of (Spell__Index) into Spell__Hash. --------
    -------- Additionally, the child hashtable of Spell__Index will be flushed when the spell is over, so you don't have to worry about that, either --------
    -------- --------
    Hashtable - Save 9000.10 as 1 of Spell__Index in Spell__Hash
    Hashtable - Save Pi as 2 of Spell__Index in Spell__Hash
    -------- --------
    -------- When the unit is finished channeling the spell and you haven't set a Spell__Time nor Spell__Duration, --------
    -------- the trigger will no longer be run. This means all the indexing and deindexing is done behind the scenes. --------
    -------- --------
Spell System Sample Filter
  Events
  Conditions
    (Spell__InRangeUnit is in Spell__TargetGroup) Equal to False
  Actions
    -------- The above condition makes it so only units who weren't already added to Spell__TargetGroup are picked --------
    -------- The unit being filtered is Spell__InRangeUnit --------
Spell System All In One Template
  Events
    Map initialization
  Conditions
  Actions
    -------- --------
    -------- Making configuration and OnEffect in one trigger for simplicity --------
    -------- This is intended for very simple spells which don't require an OnLoop trigger. --------
    -------- I don't recommend this way if you have a lot of configurables, as they are easier to track in a separate trigger --------
    -------- --------
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        Spell__Running Equal to False
      Then - Actions
        -------- --------
        -------- If Spell__Running is true, the spell has been cast. --------
        -------- If it is false, then the original event - map initialization - for this trigger has run --------
        -------- Spell__Trigger_OnEffect will point to (This trigger) --------
        -------- --------
        Set Spell__Ability = Acid Bomb
        Set Spell__Trigger_OnEffect = (This trigger)
        Trigger - Run Spell_System <gen> (ignoring conditions)
        -------- Don't do anything else --------
        Skip remaining actions
      Else - Actions
    -------- --------
    -------- The spell has been cast normally; do what you need to do from this point on. --------
    -------- --------
1. Make sure to tick "Create unknown variables when pasting trigger data in Preference
2. Install GUI Spell System from the official source
3. Copy and Paste all object editor related abilities and buffs
4. Copy "Aeraia" folder to your map
5. Configure all related stats at Aeraia Config
6. Configure "Aeraia" main ability to your desired unit. ENJOY!
Aeraia Config
  Events
    Map initialization
  Conditions
  Actions
    -------- Ability --------
    Set Aeraia_Ability = Aeraia
    Set Spell__Ability = Aeraia_Ability
    -------- Dummy Abilities --------
    Set Aeraia_AbilityPropel = Aeraia (Dummy Propel)
    Set Aeraia_AbilitySlow = Wind Blow (Dummy Slow)
    -------- Tornado SFX --------
    Set Aeraia_TornadoSFX = Abilities\Spells\Other\Tornado\TornadoElemental.mdx
    -------- Triggers --------
    Set Spell__Trigger_OnCast = Aeraia_Cast <gen>
    Set Spell__Trigger_OnLoop = Aeraia_Loop <gen>
    -------- Filter --------
    Set Spell__Filter_AllowMechanical = True
    Set Spell__Filter_AllowStructure = True
    Set Spell__Filter_AllowEnemy = True
    Set Spell__Filter_AllowLiving = True
    Set Spell__Filter_AllowHero = True
    Set Spell__Filter_AllowNonHero = True
    -------- Total duration for the spell (follow through time must be equal to this) --------
    Set Spell__Duration = 10.00
    Set Spell__DurationPerLevel = 0.00
    -------- Initial Damage --------
    Set Aeraia_Damage[1] = 100.00
    Set Aeraia_Damage[2] = 200.00
    Set Aeraia_Damage[3] = 300.00
    -------- Damage Decay --------
    Set Aeraia_DamageDecay[1] = 10.00
    Set Aeraia_DamageDecay[2] = 20.00
    Set Aeraia_DamageDecay[3] = 30.00
    -------- Propel Chance --------
    Set Aeraia_PropelChance[1] = 10.00
    Set Aeraia_PropelChance[2] = 20.00
    Set Aeraia_PropelChance[3] = 30.00
    -------- Tornado Amount --------
    Set Aeraia_Tornado[1] = 4
    Set Aeraia_Tornado[2] = 6
    Set Aeraia_Tornado[3] = 8
    -------- Tornado range from center --------
    Set Aeraia_TornadoRangeFromCenter[1] = 300.00
    Set Aeraia_TornadoRangeFromCenter[2] = 300.00
    Set Aeraia_TornadoRangeFromCenter[3] = 300.00
    -------- Tornado effect range --------
    Set Aeraia_TornadoRangeEffect[1] = 600.00
    Set Aeraia_TornadoRangeEffect[2] = 600.00
    Set Aeraia_TornadoRangeEffect[3] = 600.00
    -------- Attack Type --------
    Set Aeraia_AttackType = Spells
    -------- Damage Type --------
    Set Aeraia_DamageType = Normal
    -------- Tornado wanders? --------
    Set Aeraia_TornadoWander = True
    -------- END CONFIG --------
    -------- Ignore the TornadoWanderAbil below! --------
    Set Aeraia_TornadoWanderAbil = Wander (Neutral)
    -------- Damage per second --------
    Set Spell__Time = 1.00
    Trigger - Run Spell_System <gen> (ignoring conditions)
Aeraia Cast
  Events
  Conditions
  Actions
    Set Spell__StartDuration = True
    -------- Saving maximum duration --------
    Set Aeraia_MaxDur[Spell__Index] = Spell__Duration
    -------- Preparing the tornadoes --------
    Custom script: set udg_Aeraia_TornadoGroup[udg_Spell__Index] = CreateGroup()
    For each (Integer Aeraia_Loop) from 1 to Aeraia_Tornado[Spell__Level], do (Actions)
      Loop - Actions
        Set Aeraia_Point = (Spell__TargetPoint offset by Aeraia_TornadoRangeFromCenter[Spell__Level] towards (Random angle) degrees)
        Unit - Create 1 Spell__DummyType for Spell__CasterOwner at Aeraia_Point facing Default building facing degrees
        Set Aeraia_TempUnit = (Last created unit)
        Unit - Add Aeraia_AbilitySlow to Aeraia_TempUnit
        Unit - Add Aeraia_AbilityPropel to Aeraia_TempUnit
        If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          If - Conditions
            Aeraia_TornadoWander Equal to True
          Then - Actions
            Unit - Add Aeraia_TornadoWanderAbil to Aeraia_TempUnit
          Else - Actions
        Unit - Set level of Aeraia_AbilityPropel for Aeraia_TempUnit to Spell__Level
        Unit - Set level of Aeraia_AbilitySlow for Aeraia_TempUnit to Spell__Level
        Special Effect - Create a special effect attached to the origin of Aeraia_TempUnit using Aeraia_TornadoSFX
        Unit Group - Add Aeraia_TempUnit to Aeraia_TornadoGroup[Spell__Index]
        Unit - Add a Spell__Duration second Generic expiration timer to Aeraia_TempUnit
        Custom script: call RemoveLocation(udg_Aeraia_Point)
Aeraia Loop
  Events
  Conditions
  Actions
    -------- Tornado damage --------
    If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      If - Conditions
        Spell__Channeling Equal to True
      Then - Actions
        Unit Group - Pick every unit in Aeraia_TornadoGroup[Spell__Index] and do (Actions)
          Loop - Actions
            Set Aeraia_TempUnit = (Picked unit)
            Set Spell__InRangePoint = (Position of Aeraia_TempUnit)
            Set Spell__InRange = Aeraia_TornadoRangeEffect[Spell__Level]
            Unit Group - Pick every unit in Spell__InRangeGroup and do (Actions)
              Loop - Actions
                -------- Damage algorithm : base damage - ((maxdur-dur)*decay damage) --------
                Set Aeraia_Victim = (Picked unit)
                If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                  If - Conditions
                    (Random percentage) Less than or equal to Aeraia_PropelChance[Spell__Level]
                  Then - Actions
                    Unit - Order Aeraia_TempUnit to Night Elf Druid Of The Talon - Cyclone Aeraia_Victim
                  Else - Actions
                    Unit - Cause Spell__Caster to damage Aeraia_Victim, dealing (Aeraia_Damage[Spell__Level] - ((Aeraia_MaxDur[Spell__Index] - Spell__Duration) x Aeraia_DamageDecay[Spell__Level])) damage of attack type Aeraia_AttackType and damage type Aeraia_DamageType
      Else - Actions
        Unit Group - Pick every unit in Aeraia_TornadoGroup[Spell__Index] and do (Actions)
          Loop - Actions
            Set Aeraia_TempUnit = (Picked unit)
            Unit - Kill Aeraia_TempUnit
        Custom script: call DestroyGroup(udg_Aeraia_TornadoGroup[udg_Spell__Index])
Revive
  Events
    Unit - A unit owned by Player 2 (Blue) Dies
  Conditions
  Actions
    Set TempUnit = (Triggering unit)
    Set TempLoc = (Position of TempUnit)
    Unit - Create 1 (Unit-type of (Triggering unit)) for Player 2 (Blue) at TempLoc facing Default building facing degrees
    Custom script: call RemoveLocation(udg_TempLoc)