[vJASS] SpellEvent

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
This purpose of this library is to simplify and expand the possibilities within the realm of spell development. This library provides a lot of features while using fairly minimal amount of game resources (just not in terms of the size of code :p). And although the library might seem complex, it is fairly easy and straightforward to use from the spellmakers' side.

Another purpose of this snippet is to save yourself some time from repeatedly typing the same spell logics over and over. It also gives your spell a better structure which results in a cleaner and much readable code.

More info and documentation can be found in the library header itself.


Additional Info:
This snippet minimizes handle use with regards to spell event responses. Credits goes to Bribe and the other people involved in creating the SpellEffectEvent library, from which the logic of the event registration in this snippet is based.
Credits also goes to Anitarf for his SpellEvent library which inspired a lot of spell-automation related snippets, including this one.


Below are template for creating a spell using this library and also a comparison between a spell that uses this lib and some other usual approach that doesn't use this lib.



JASS:
library SpellEventTemplate uses SpellEvent
    /***********************************************************
    *
    *   Global Variables:
    *
    *       Unlike some of the default spell event responses provided
    *       by Jass, these variables are all recursion-safe.
    *
    *       readonly static integer         abilityId
    *       readonly static integer         eventType
    *       readonly static integer         orderType
    *       readonly static integer         level
    *       readonly static player          triggerPlayer
    *       readonly static unit            triggerUnit
    *       readonly static unit            targetUnit
    *       readonly static item            targetItem
    *       readonly static destructable    targetDest
    *       readonly static real            targetX
    *       readonly static real            targetY
    *
    ***********************************************************/

    private struct YourSpellStruct extends array

        private static constant integer SPELL_ABILITY_ID = 'XXXX'

        private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT

        private static constant real SPELL_PERIOD = 1.00/32.00

        /*
        *   > The 'this' in the following methods refers to the spell instance
        *   > A spell instance is automatically created and added to the linked-list
        *     after the spell event fires and just before method onSpellStart() runs
        *   > A spell instance is automatically destroyed and removed from the linked-list
        *     after method onSpellEnd() runs
        *   > You can traverse the linked-list using the readonly variables 'prev' and 'next'
        */
        private method onSpellStart takes nothing returns thistype
            /*
            *   -> Put the spell's initial actions here <-
            *
            *   Runs after the spell event fires
            */
            return this
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            /*
            *   -> Put the spell's periodic actions here <-
            *
            *   Runs every <SPELL_PERIOD> seconds after onSpellStart()
            *   runs until this method returns false
            */
            return false//return true to stop the periodic actions
        endmethod

        private method onSpellEnd takes nothing returns nothing
            /*
            *   -> Put the spell's cleanup actions here <-
            *
            *   Runs after onSpellPeriodic() returns false
            */
        endmethod

        implement SpellEvent

        /*
        *   You no longer need to manually register the spell at initialization
        *   since the module already automates that for you
        */
        private static method onInit takes nothing returns nothing
            /*
            *   -> Put initialization actions here <-
            */
        endmethod

    endstruct


endlibrary


JASS:
//=============================================================================
/*
*   Using SpellEvent
*/
struct SpellUsingSpellEvent extends array

    private static constant integer SPELL_ABILITY_ID = 'XXXX'
    private static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
    private static constant real SPELL_PERIOD = 1.00/32.00

    private real duration

    private method onSpellStart takes nothing returns thistype
        /*
        *   -> Put the spell's initial actions here <-
        */
        set this.duration = 10.00 + 0.00*Spell.level
        return this
    endmethod

    private method onSpellPeriodic takes nothing returns boolean
        /*
        *   -> Put the spell's periodic actions here <-
        */
        set this.duration = this.duration - SPELL_PERIOD
        return this.duration <= 0.00
    endmethod

    private method onSpellEnd takes nothing returns nothing
        /*
        *   -> Put the spell's cleanup actions here <-
        */
    endmethod

    implement SpellEvent

endstruct

//=============================================================================
/*
*   Without SpellEvent: 1 timer for all spell instances
*/
struct TraditionalSpellStruct1

    private static constant integer SPELL_ABILITY_ID = 'XXXX'
    private static constant real SPELL_PERIOD = 1.00/32.00

    private thistype prev
    private thistype next
    private real duration

    private static method onSpellPeriodic takes nothing returns nothing
        local thistype this = thistype(0).next
        loop
            exitwhen this == 0
            set this.duration= this.duration - SPELL_PERIOD
            if this.duration > 0.00 then
                /*
                *   -> Put the spell's periodic actions here <-
                */
            else
                /*
                *   -> Put the spell's cleanup actions here <-
                */
                set this.next.prev = this.prev
                set this.prev.next = this.next
                call this.deallocate()
                if thistype(0).next == 0 then
                    call DestroyTimer(GetExpiredTimer())
                endif
            endif
            set this = this.next
        endloop
    endmethod

    private static method onSpellStart takes nothing returns boolean
        local thistype this = allocate()
        local thistype last = thistype(0).prev
        set thistype(0).prev = this
        set last.next = this
        set this.prev = last
        set this.next = 0
        set this.duration = 10.00
        /*
        *   -> Put the spell's initial actions here <-
        */
        if thistype(0).next == this then
            call TimerStart(CreateTimer(), SPELL_PERIOD, true, function thistype.onSpellPeriodic)
        endif
        return false
    endmethod

    private static method onCastCheck takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ABILITY_ID and onSpellStart()
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Filter(function thistype.onCastCheck))
        set t = null
    endmethod

endstruct

//=============================================================================
/*
*   Without SpellEvent: 1 timer per spell instance (Using TimerUtils)
*/
struct TraditionalSpellStruct2

    private static constant integer SPELL_ABILITY_ID = 'XXXX'
    private static constant real SPELL_PERIOD = 1.00/32.00

    private real duration

    private static method onSpellPeriodic takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        set this.duration = this.duration - SPELL_PERIOD
        if this.duration > 0.00 then
            /*
            *   -> Put the spell's periodic actions here <-
            */
        else
            /*
            *   -> Put the spell's cleanup actions here <-
            */
            call this.deallocate()
            call ReleaseTimer(GetExpiredTimer())
        endif
    endmethod

    private static method onSpellStart takes nothing returns boolean
        local thistype this = allocate()
        set this.duration = 10.00
        /*
        *   -> Put the spell's initial actions here <-
        */
        call TimerStart(NewTimerEx(this), SPELL_PERIOD, true, function thistype.onSpellPeriodic)
        return false
    endmethod

    private static method onCastCheck takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ABILITY_ID and onSpellStart()
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Filter(function thistype.onCastCheck))
        set t = null
    endmethod

endstruct




SpellEvent Library

JASS:
library SpellEvent /* v3.1.1 https://www.hiveworkshop.com/threads/301895/


    */uses /*

    */Table                             /*  https://www.hiveworkshop.com/threads/188084/
    */LinkedList                        /*  https://www.hiveworkshop.com/threads/325635/

    */optional Alloc                    /*  https://www.hiveworkshop.com/threads/324937/
    */optional RegisterPlayerUnitEvent  /*  https://www.hiveworkshop.com/threads/250266/
    */optional ResourcePreloader        /*  https://www.hiveworkshop.com/threads/287358/
    */optional ErrorMessage             /*  https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j


    *///! novjass

    /*
        A library that eases and expands the possibilities of custom spells development.

        Core Features:
            1. Two-phase spell event handlers
            2. Manual spell event invocation
            3. Event parameters overriding and event cancelling
            4. Spell development template

            1.) Spell event handlers are grouped into two: generic handlers that runs for every spell, and ability-specific
            handlers. All generic handlers run first. Within them, you can do things such as changing the event parameters
            (caster, target, etc.) as well as preventing the ability-specific handlers from running. The second phase are
            the specific handlers which are for the spell developers to define the mechanics of the spell.

            Note: Generic handlers only run for spells that have existing ability-specific handlers. This is because
            generic handlers are intended as custom-spell modifier, not an ability handler. If you want to catch an event
            that runs for just any ability (including normal OE abilities), you can easily use (in fact, you should) the
            blizzard native events instead.

            2.) You can invoke a spell event to run and define the parameters manually. This removes the need for dummy
            casters in most cases.

            3.) As mentioned in no. 1, within the generic event handlers, you can override the event parameters. The change
            will only affect the ability-specific handlers. You can also stop the ability-specific handlers from running
            (known as event cancelling).

            Note: Event cancelling is currently incompatible with Reforged (Crashes the game).

            4.) This library provides a framework for the flow of spell through the use of modules. This removes from the
            spell developers the additional work of manual spell event registration, spell instance allocation, and other
            minute tasks such as storing and looping through each active spell instance.

    */

    |=========|
    | Credits |
    |=========|
    /*
        - AGD (Author)
        - Bribe, Nestharus (SpellEffectEvent concept)
        - Anitarf (Original SpellEvent Idea)

    */
    |=========|
    | Structs |
    |=========|
    /*

      */struct Spell extends array/*

          */static constant thistype        GENERIC         /*  You can also use this like 'Spell.GENERIC.registerEventHandlers()'

        Event Responses:
          */readonly static integer         abilityId       /*
          */readonly static integer         eventType       /*
          */readonly static integer         orderType       /*
          */readonly static integer         level           /*
          */readonly static player          triggerPlayer   /*
          */readonly static unit            triggerUnit     /*
          */readonly static unit            targetUnit      /*  Fixed a mistake in the native event responses where target is set to caster for 'No-target' abilities based on channel (Is set to null instead)
          */readonly static item            targetItem      /*
          */readonly static destructable    targetDest      /*
          */readonly static widget          target          /*
          */readonly static real            targetX         /*  Returns the x-coordinate of the caster if the spell is a 'No-target' ability
          */readonly static real            targetY         /*  Returns the y-coordinate of the caster if the spell is a 'No-target' ability

        Fields:
          */readonly integer abilId/*
                - Rawcode of the activation ability for the spell

          */boolean handlersDisabled/*
                - (Incompatible with Reforged versions - crashes the game)
                - Value is always reset to false before running the generic spell handlers
                - Set to <true> to increment the internal disabler counter or <false> to decrement counter
                - If counter > 0, the ability-specific handlers won't run

        Methods:

          */static method   operator []                     takes integer abilId                                                           returns Spell/*
                - Returns a Spell instance based on the given activation-ability rawcode which can be used for event handler registrations

          */method          setEventFlag                    takes integer eventType, boolean flag                                           returns nothing/*
          */method          getEventFlag                    takes integer eventType                                                         returns boolean/*
                - Disables/Enables certain event types from running for a Spell (These flags are <true> by default)

          */method          invokeNoTargetEvent             takes integer eventType, integer level, unit caster                             returns nothing/*
          */method          invokePointTargetEvent          takes integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
          */method          invokeSingleTargetEvent         takes integer eventType, integer level, unit caster, widget target              returns nothing/*
                - Manually invokes a spell event

          */static method   overrideNoTargetParams          takes integer level, unit caster                                                returns nothing/*
          */static method   overridePointTargetParams       takes integer level, unit caster, real targetX, real targetY                    returns nothing/*
          */static method   overrideSingleTargetParams      takes integer level, unit caster, widget target                                 returns nothing/*
                - Overrides the values of the event response variables (Only effective when called inside a generic event handler)
                - The values are only overriden in the ability-specific spell event handlers

          */method          registerEventHandler            takes integer eventType, code handler                                           returns nothing/*
          */method          unregisterEventHandler          takes integer eventType, code handler                                           returns nothing/*
          */method          clearEventHandlers              takes integer eventType                                                         returns nothing/*
          */method          clearHandlers                   takes nothing                                                                   returns nothing/*
                - Manages ability-specific spell event handlers

          */static method   registerGenericEventHandler     takes integer eventType, code handler                                           returns nothing/*
          */static method   unregisterGenericEventHandler   takes integer eventType, code handler                                           returns nothing/*
          */static method   clearGenericEventHandlers       takes integer eventType                                                         returns nothing/*
          */static method   clearGenericHandlers            takes nothing                                                                   returns nothing/*
                - Manages generic spell event handlers

    */
    |===========|
    | Variables |
    |===========|
    /*
        Spell Event Types

      */constant integer EVENT_SPELL_CAST/*
      */constant integer EVENT_SPELL_CHANNEL/*
      */constant integer EVENT_SPELL_EFFECT/*
      */constant integer EVENT_SPELL_ENDCAST/*
      */constant integer EVENT_SPELL_FINISH/*

        Spell Order Types

      */constant integer SPELL_ORDER_TYPE_SINGLE_TARGET/*
      */constant integer SPELL_ORDER_TYPE_POINT_TARGET/*
      */constant integer SPELL_ORDER_TYPE_NO_TARGET/*

    */
    |===========|
    | Functions |
    |===========|
    /*
        Equivalent functions for the methods above

        (Event Responses)
      */constant function GetEventSpellAbilityId    takes nothing                                                   returns integer/*
      */constant function GetEventSpellEventType    takes nothing                                                   returns integer/*
      */constant function GetEventSpellOrderType    takes nothing                                                   returns integer/*
      */constant function GetEventSpellLevel        takes nothing                                                   returns integer/*
      */constant function GetEventSpellPlayer       takes nothing                                                   returns player/*
      */constant function GetEventSpellCaster       takes nothing                                                   returns unit/*
      */constant function GetEventSpellTargetUnit   takes nothing                                                   returns unit/*
      */constant function GetEventSpellTargetItem   takes nothing                                                   returns item/*
      */constant function GetEventSpellTargetDest   takes nothing                                                   returns destructable/*
      */constant function GetEventSpellTarget       takes nothing                                                   returns widget/*
      */constant function GetEventSpellTargetX      takes nothing                                                   returns real/*
      */constant function GetEventSpellTargetY      takes nothing                                                   returns real/*

      */function SetSpellEventFlag                  takes integer abilId, integer eventType, boolean flag           returns nothing/*
      */function GetSpellEventFlag                  takes integer abilId, integer eventType                         returns boolean/*

      */function SpellCancelEventHandlers           takes boolean cancel                                            returns nothing/*
            - This function is imcompatible with Reforged versions

      */function SpellInvokeNoTargetEvent           takes integer abilId, integer eventType, integer level, unit caster                                returns nothing/*
      */function SpellInvokePointTargetEvent        takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY    returns nothing/*
      */function SpellInvokeSingleTargetEvent       takes integer abilId, integer eventType, integer level, unit caster, widget target                 returns nothing/*

      */function SpellOverrideNoTargetParams        takes integer level, unit caster                                returns nothing/*
      */function SpellOverridePointTargetParams     takes integer level, unit caster, real targetX, real targetY    returns nothing/*
      */function SpellOverrideSingleTargetParams    takes integer level, unit caster, widget target                 returns nothing/*

      */function SpellRegisterEventHandler          takes integer abilId, integer eventType, code handler           returns nothing/*
      */function SpellUnregisterEventHandler        takes integer abilId, integer eventType, code handler           returns nothing/*
      */function SpellClearEventHandlers            takes integer abilId, integer eventType                         returns nothing/*
      */function SpellClearHandlers                 takes integer abilId                                            returns nothing/*

      */function SpellRegisterGenericEventHandler   takes integer eventType, code handler                           returns nothing/*
      */function SpellUnregisterGenericEventHandler takes integer eventType, code handler                           returns nothing/*
      */function SpellClearGenericEventHandlers     takes integer eventType                                         returns nothing/*
      */function SpellClearGenericHandlers          takes nothing                                                   returns nothing/*

    */
    |=========|
    | Modules |
    |=========|
    /*
        Automates spell event handler registration at map initialization
        Modules <SpellEvent> and <SpellEventEx> cannot both be implemented in the same struct

      */module SpellEvent extends LinkedListLite/*

            > Uses a single timer (per struct) for all active spell instances. Standard module designed for
              periodic spells with high-frequency timeout (<= 0.5 seconds)

        Fields:

          */readonly thistype prev/*
          */readonly thistype next/*
                - 'Inherited' from LinkedListLite module
                - Spell instances links
                - Readonly attribute is only effective outside the implementing struct, though users are
                also not supposed to change these values from inside the struct.

        Public methods:
          */static method registerSpellEvent takes integer abilId, integer eventType returns nothing/*
                - Manually registers an ability rawcode to trigger spell events
                - Can be used for spells that involve more than one activation ability IDs

        Member interfaces:
            - Should be declared above the module implementation

          */interface static integer    SPELL_ABILITY_ID    /*  Ability rawcode
          */interface static integer    SPELL_EVENT_TYPE    /*  Spell event type
          */interface static real       SPELL_PERIOD        /*  Spell periodic actions execution period

          */interface method            onSpellStart    takes nothing   returns thistype/*
                - Runs right after the spell event fires
                - Returning zero or a negative value will not run the periodic operations for that instance
                - Returning a negative value will try to find that value's positive node equivalent and
                  calls onSpellEnd() for that node and removes it from the list.
                - You can return a different value or transmute 'this', provided that all your nodes/values
                  comes from the same node/value stack. The important thing to remember is that all nodes in
                  the list should be unique. Also remember to always deallocate what you manually allocated.
                - The value returned will be added to the list of instances that will run onSpellPeriodic().
          */optional interface method   onSpellPeriodic takes nothing   returns boolean/*
                - Runs periodically after the spell event fires until it returns true
          */optional interface method   onSpellEnd      takes nothing   returns nothing/*
                - Runs after method onSpellPeriodic() returns true
                - If onSpellPeriodic() is not present, this will be called after onSpellStart() returns a valid instance


      */module SpellEventEx/*

            > Uses 1 timer for each active spell instance. A module specifically designed for
              periodic spells with low-frequency timeout (> 0.5 seconds) as it does not affect
              the accuracy of the first 'tick' of the periodic operations. Here, you always
              need to manually allocate/deallocate you spell instances.

        Public methods:
          */static method registerSpellEvent takes integer abilId, integer eventType returns nothing/*
                - Manually registers a spell rawcode to trigger spell events
                - Can be used for spells that involves more than one abilityId

        Member interfaces:
            - Should be declared above the module implementation

          */interface static integer    SPELL_ABILITY_ID    /*  Ability rawcode
          */interface static integer    SPELL_EVENT_TYPE    /*  Spell event type
          */interface static real       SPELL_PERIOD        /*  Spell periodic actions execution period

          */interface static method     onSpellStart    takes nothing   returns thistype/*
                - Runs right after the spell event fires
                - User should manually allocate the spell instance and use it as a return value of this method
                - Returning zero or a negative value will not run the periodic operations for that instance
                - Returning a negative value will try to find that value's positive node equivalent and calls
                  onSpellEnd() for that node.
          */optional interface method   onSpellPeriodic takes nothing   returns boolean/*
                - Runs periodically after the spell event fires until it returns true
          */optional interface method   onSpellEnd      takes nothing   returns nothing/*
                - Runs after method onSpellPeriodic() returns true
                - If onSpellPeriodic() is not present, this will be called after onSpellStart() returns a valid instance
                - User must manually deallocate the spell instance inside this method


      */module SpellEventGeneric/*

        Member interfaces:
            - Should be declared above the module implementation

          */optional interface static method    onSpellEvent    takes nothing   returns nothing/*
                - Runs on any generic spell event

          */optional interface static method    onSpellCast     takes nothing   returns nothing/*
          */optional interface static method    onSpellChannel  takes nothing   returns nothing/*
          */optional interface static method    onSpellEffect   takes nothing   returns nothing/*
          */optional interface static method    onSpellEndcast  takes nothing   returns nothing/*
          */optional interface static method    onSpellFinish   takes nothing   returns nothing/*
                - Runs on certain generic spell events


    *///! endnovjass

    /*=================================== SYSTEM CODE ===================================*/

    globals
        constant integer EVENT_SPELL_CAST               = 0x1
        constant integer EVENT_SPELL_CHANNEL            = 0x2
        constant integer EVENT_SPELL_EFFECT             = 0x4
        constant integer EVENT_SPELL_ENDCAST            = 0x8
        constant integer EVENT_SPELL_FINISH             = 0x10

        constant integer SPELL_ORDER_TYPE_SINGLE_TARGET = 0x12
        constant integer SPELL_ORDER_TYPE_POINT_TARGET  = 0x123
        constant integer SPELL_ORDER_TYPE_NO_TARGET     = 0x1234
    endglobals

    globals
        private integer         eventAbilityId          = 0
        private integer         eventEventType          = 0
        private integer         eventOrderType          = 0
        private integer         eventLevel              = 0
        private player          eventTriggerPlayer      = null
        private unit            eventTriggerUnit        = null
        private unit            eventTargetUnit         = null
        private item            eventTargetItem         = null
        private destructable    eventTargetDest         = null
        private real            eventTargetX            = 0.00
        private real            eventTargetY            = 0.00

        private integer         tempOrderType           = 0
        private integer         tempLevel               = 0
        private player          tempTriggerPlayer       = null
        private unit            tempTriggerUnit         = null
        private widget          tempTarget              = null
        private real            tempTargetX             = 0.00
        private real            tempTargetY             = 0.00

        private boolexpr bridgeExpr
        private TableArray table
        private integer array eventTypeId
        private integer array eventIndex
    endglobals

    private keyword Init

    static if DEBUG_MODE then
        private function IsValidEventType takes integer eventType returns boolean
            return eventType > 0 and eventType <= (EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
        endfunction

        private function IsEventSingleFlag takes integer eventType returns boolean
            return eventType == EVENT_SPELL_CAST    or/*
                */ eventType == EVENT_SPELL_CHANNEL or/*
                */ eventType == EVENT_SPELL_EFFECT  or/*
                */ eventType == EVENT_SPELL_ENDCAST or/*
                */ eventType == EVENT_SPELL_FINISH
        endfunction

        private function AssertError takes boolean condition, string methodName, string structName, integer instance, string message returns nothing
            static if LIBRARY_ErrorMessage then
                call ThrowError(condition, SCOPE_PREFIX, methodName, structName, instance, message)
            else
                if condition then
                    call BJDebugMsg("|cffff0000[ERROR]|r [Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(instance) + "] : |cffff0000" + message + "|r")
                    call PauseGame(true)
                endif
            endif
        endfunction
    endif

    /*===================================================================================*/

    private function OnOverrideParams takes nothing returns nothing
        set eventOrderType          = tempOrderType
        set eventLevel              = tempLevel
        set eventTriggerPlayer      = GetOwningPlayer(tempTriggerUnit)
        set eventTriggerUnit        = tempTriggerUnit
        set eventTargetX            = tempTargetX
        set eventTargetY            = tempTargetY

        if tempTarget == null then
            set eventTargetUnit     = null
            set eventTargetItem     = null
            set eventTargetDest     = null
        else
            set table[0].widget[0]  = tempTarget
            set eventTargetUnit     = table[0].unit[0]
            set eventTargetItem     = table[0].item[0]
            set eventTargetDest     = table[0].destructable[0]
            call table[0].handle.remove(0)
        endif

        set tempOrderType           = 0
        set tempLevel               = 0
        set tempTriggerUnit         = null
        set tempTargetX             = 0.00
        set tempTargetY             = 0.00
        set tempTarget              = null
    endfunction

    /*===================================================================================*/

    /*
    *   One Allocator for the whole library. Yes, it would be unlikely for this system to
    *   reach JASS_MAX_ARRAY_SIZE instances of allocated nodes at a single time.
    *
    *   Need to use custom Alloc because of the updated value for JASS_MAX_ARRAY_SIZE.
    *   Credits to MyPad for the allocation algorithm
    */
    private struct Node extends array
        static if LIBRARY_Alloc then
            implement optional Alloc
        else
            private static thistype array stack
            static method allocate takes nothing returns thistype
                local thistype node = stack[0]
                if stack[node] == 0 then
                    debug call AssertError(node == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", "thistype", node, "Overflow")
                    set node = node + 1
                    set stack[0] = node
                else
                    set stack[0] = stack[node]
                    set stack[node] = 0
                endif
                return node
            endmethod
            method deallocate takes nothing returns nothing
                debug call AssertError(this == 0, "deallocate()", "thistype", 0, "Null node")
                debug call AssertError(stack[this] > 0, "deallocate()", "thistype", this, "Double-free")
                set stack[this] = stack[0]
                set stack[0] = this
            endmethod
        endif
    endstruct

    private struct ConditionList extends array
        triggercondition handle

        private static method onRemove takes thistype node returns nothing
            set node.handle = null
            call Node(node).deallocate()
        endmethod

        implement List
    endstruct

    private struct ExprList extends array
        boolexpr handle

        method operator conditionList takes nothing returns ConditionList
            return this
        endmethod

        private static method onRemove takes thistype node returns nothing
            set node.handle = null
            call Node(node).deallocate()
        endmethod

        implement List

        method insertExpr takes boolexpr expr returns thistype
            local thistype node = Node.allocate()
            set node.handle = expr
            call insert(this, node)
            return node
        endmethod
    endstruct

    private struct Handler extends array

        readonly trigger trigger

        boolean overrideParams
        integer disablerCounter
        private integer index
        private static ExprList array genericList

        private method operator exprList takes nothing returns ExprList
            return this
        endmethod

        /*
        *   You might think that the process of registering handlers are expensive in performance
        *   due to constant rebuilding of triggerconditions each time, but setting up proper spell
        *   handlers are seldom done (often only once per spell) and a large part of them are done
        *   at map initialization.
        */
        method updateHandlers takes nothing returns nothing
            local ExprList exprNode = genericList[this.index].next
            local ConditionList conditionNode
            call TriggerClearConditions(this.trigger)
            if exprNode != genericList[this.index].prev then
                loop
                    exitwhen exprNode == genericList[this.index]
                    set conditionNode = exprNode.conditionList.next
                    loop
                        exitwhen conditionNode == exprNode.conditionList
                        call TriggerAddCondition(this.trigger, exprNode.handle)
                        set conditionNode = conditionNode.next
                    endloop
                    set exprNode = exprNode.next
                endloop
            endif
            set exprNode = this.exprList.next
            loop
                exitwhen exprNode == this.exprList
                set conditionNode = exprNode.conditionList.next
                loop
                    exitwhen conditionNode == exprNode.conditionList
                    set conditionNode.handle = TriggerAddCondition(this.trigger, exprNode.handle)
                    set conditionNode = conditionNode.next
                endloop
                set exprNode = exprNode.next
            endloop
        endmethod
        /*
        *   This method is registered in position after all the generic handlers and before the
        *   ability-specific handlers. Its position allows it to unlink all the ability-specific
        *   handlers positioned after it or to change the event parameters, should the user choose to
        */
        private static method bridge takes nothing returns nothing
            local integer triggerId = GetHandleId(GetTriggeringTrigger())
            local thistype node = table[0][triggerId]
            local trigger tempTrig
            if node.disablerCounter > 0 then
                if node.exprList.next != node.exprList then
                    set tempTrig = node.trigger
                    set node.trigger = CreateTrigger()
                    set table[0][GetHandleId(node.trigger)] = node
                    call node.updateHandlers()
                    call table[0].remove(triggerId)
                    call TriggerClearConditions(tempTrig)
                    call DestroyTrigger(tempTrig)
                    set tempTrig = null
                endif
                return
            endif
            if node.overrideParams and node.exprList.next != node.exprList then
                call OnOverrideParams()
            endif
        endmethod

        static method registerGeneric takes integer eventIndex, boolexpr expr returns nothing
            local integer exprId = GetHandleId(expr)
            local ExprList exprNode = table[genericList[eventIndex]][exprId]
            if exprNode == 0 then
                set exprNode = genericList[eventIndex].prev.prev.insertExpr(expr)
                call ConditionList.makeHead(exprNode.conditionList)
                set table[genericList[eventIndex]][exprId] = exprNode
            endif
            call exprNode.conditionList.pushBack(Node.allocate())
        endmethod
        static method unregisterGeneric takes integer eventIndex, integer exprId returns nothing
            local ExprList exprNode = table[genericList[eventIndex]][exprId]
            local ConditionList conditionNode = exprNode.conditionList.next
            loop
                exitwhen conditionNode == exprNode.conditionList
                call ConditionList.remove(conditionNode)
                set conditionNode = conditionNode.next
            endloop
            call table[genericList[eventIndex]].remove(exprId)
            call exprList.remove(exprNode)
        endmethod
        static method clearGeneric takes integer eventIndex returns nothing
            local ExprList exprNode = genericList[eventIndex].next
            loop
                exitwhen exprNode == genericList[eventIndex].prev
                call unregisterGeneric(eventIndex, GetHandleId(exprNode.handle))
                set exprNode = exprNode.next
            endloop
        endmethod

        method register takes boolexpr expr returns nothing
            local integer exprId = GetHandleId(expr)
            local ExprList exprNode = table[this][exprId]
            local ConditionList conditionNode = Node.allocate()
            if this.exprList.empty then
                set this.trigger = CreateTrigger()
                set table[0][GetHandleId(this.trigger)] = this
            endif
            if exprNode == 0 then
                set exprNode = this.exprList.prev.insertExpr(expr)
                call ConditionList.makeHead(exprNode.conditionList)
                set table[this][exprId] = exprNode
                call exprNode.conditionList.pushBack(conditionNode)
                call this.updateHandlers()
            else
                call exprNode.conditionList.pushBack(conditionNode)
                if exprNode.next == this.exprList then
                    set conditionNode.handle = TriggerAddCondition(this.trigger, expr)
                else
                    call this.updateHandlers()
                endif
            endif
        endmethod
        method unregister takes integer exprId returns nothing
            local ExprList exprNode = table[this][exprId]
            local ConditionList conditionNode = exprNode.conditionList.next
            loop
                exitwhen conditionNode == exprNode.conditionList
                call TriggerRemoveCondition(this.trigger, conditionNode.handle)
                call ConditionList.remove(conditionNode)
                set conditionNode = conditionNode.next
            endloop
            call ExprList.remove(exprNode)
            call table[this].remove(exprId)
            if this.exprList.empty then
                call table[0].remove(GetHandleId(this.trigger))
                call DestroyTrigger(this.trigger)
                set this.trigger = null
            endif
        endmethod
        method clear takes nothing returns nothing
            local ExprList exprNode = this.exprList.next
            loop
                exitwhen exprNode == this.exprList
                call this.unregister(GetHandleId(exprNode.handle))
                set exprNode = exprNode.next
            endloop
        endmethod

        static method create takes integer eventIndex returns thistype
            local thistype node = Node.allocate()
            call ExprList.makeHead(node)
            set node.index = eventIndex
            set node.disablerCounter = 0
            return node
        endmethod
        method destroy takes nothing returns nothing
            call this.clear()
            call Node(this).deallocate()
        endmethod

        debug static method hasGenericExpr takes integer eventIndex, boolexpr expr returns boolean
            debug return table[genericList[eventIndex]][GetHandleId(expr)] != 0
        debug endmethod
        debug method hasExpr takes boolexpr expr returns boolean
            debug return table[this][GetHandleId(expr)] != 0
        debug endmethod

        method operator enabled= takes boolean flag returns nothing
            if flag then
                call EnableTrigger(this.trigger)
            else
                call DisableTrigger(this.trigger)
            endif
        endmethod
        method operator enabled takes nothing returns boolean
            return IsTriggerEnabled(this.trigger)
        endmethod

        private static method initGenericList takes integer eventIndex returns nothing
            local ExprList list = create(eventIndex)
            local ExprList exprNode = list.insertExpr(bridgeExpr)
            call ConditionList.makeHead(exprNode.conditionList)
            call exprNode.conditionList.pushBack(Node.allocate())
            set genericList[eventIndex] = list
        endmethod

        static method init takes nothing returns nothing
            /*
            *   This bridge boolexpr executes after all the generic spell handlers
            *   before transitioning into the ability-specific spell handlers.
            *   This boolexpr is responsible for disabling the ability-specific handlers
            *   (if requested) as well as implementing the overriding of the event
            *   parameters.
            */
            local code bridgeFunc = function thistype.bridge
            set bridgeExpr = Filter(bridgeFunc)

            call initGenericList(eventIndex[EVENT_SPELL_CAST])
            call initGenericList(eventIndex[EVENT_SPELL_CHANNEL])
            call initGenericList(eventIndex[EVENT_SPELL_EFFECT])
            call initGenericList(eventIndex[EVENT_SPELL_ENDCAST])
            call initGenericList(eventIndex[EVENT_SPELL_FINISH])
        endmethod

    endstruct

    /*===================================================================================*/

    struct Spell extends array

        readonly integer abilId

        private static integer spellCount = 0
        private static Node spellKey
        private static Handler array eventHandler

        static if not LIBRARY_ResourcePreloader then
            private static unit preloadDummy
        endif

        static constant method operator abilityId takes nothing returns integer
            return eventAbilityId
        endmethod
        static constant method operator eventType takes nothing returns integer
            return eventEventType
        endmethod
        static constant method operator orderType takes nothing returns integer
            return eventOrderType
        endmethod
        static constant method operator level takes nothing returns integer
            return eventLevel
        endmethod
        static constant method operator triggerPlayer takes nothing returns player
            return eventTriggerPlayer
        endmethod
        static constant method operator triggerUnit takes nothing returns unit
            return eventTriggerUnit
        endmethod
        static constant method operator targetUnit takes nothing returns unit
            return eventTargetUnit
        endmethod
        static constant method operator targetItem takes nothing returns item
            return eventTargetItem
        endmethod
        static constant method operator targetDest takes nothing returns destructable
            return eventTargetDest
        endmethod
        static constant method operator target takes nothing returns widget
            if eventTargetUnit != null then
                return eventTargetUnit
            elseif eventTargetItem != null then
                return eventTargetItem
            elseif eventTargetDest != null then
                return eventTargetDest
            endif
            return null
        endmethod
        static constant method operator targetX takes nothing returns real
            return eventTargetX
        endmethod
        static constant method operator targetY takes nothing returns real
            return eventTargetY
        endmethod

        static method operator [] takes integer abilId returns thistype
            local thistype this = table[spellKey][abilId]
            local integer offset
            if this == 0 then
                debug call AssertError(spellCount > R2I(JASS_MAX_ARRAY_SIZE/5), "Spell[]", "thistype", 0, "Overflow")
                static if LIBRARY_ResourcePreloader then
                    call PreloadAbility(abilId)
                else
                    if UnitAddAbility(preloadDummy, abilId) then
                        call UnitRemoveAbility(preloadDummy, abilId)
                    endif
                endif
                set spellCount = spellCount + 1
                set thistype(spellCount).abilId = abilId
                set table[spellKey][abilId] = spellCount
                set offset = (spellCount - 1)*5
                set eventHandler[offset + eventIndex[EVENT_SPELL_CAST]]     = Handler.create(eventIndex[EVENT_SPELL_CAST])
                set eventHandler[offset + eventIndex[EVENT_SPELL_CHANNEL]]  = Handler.create(eventIndex[EVENT_SPELL_CHANNEL])
                set eventHandler[offset + eventIndex[EVENT_SPELL_EFFECT]]   = Handler.create(eventIndex[EVENT_SPELL_EFFECT])
                set eventHandler[offset + eventIndex[EVENT_SPELL_ENDCAST]]  = Handler.create(eventIndex[EVENT_SPELL_ENDCAST])
                set eventHandler[offset + eventIndex[EVENT_SPELL_FINISH]]   = Handler.create(eventIndex[EVENT_SPELL_FINISH])
                return spellCount
            endif
            return this
        endmethod

        static method operator GENERIC takes nothing returns thistype
            return thistype[0]
        endmethod

        static method registerGenericEventHandler takes integer eventType, code handler returns nothing
            local boolexpr expr = Filter(handler)
            local integer eventId = 0x10
            local integer node
            if eventType != 0 then
                debug call AssertError(not IsValidEventType(eventType), "registerGenericEventHandler()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                loop
                    exitwhen eventId == 0
                    if eventType >= eventId then
                        set eventType = eventType - eventId
                        call Handler.registerGeneric(eventIndex[eventId], expr)
                        set node = spellCount
                        loop
                            exitwhen node == 0
                            set node = node - 1
                            call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
                        endloop
                    endif
                    set eventId = eventId/2
                endloop
            endif
            set expr = null
        endmethod
        static method unregisterGenericEventHandler takes integer eventType, code handler returns nothing
            local boolexpr expr = Filter(handler)
            local integer eventId = 0x10
            local integer node
            if eventType != 0 then
                debug call AssertError(not IsValidEventType(eventType), "unregisterGenericEventHandler()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                loop
                    exitwhen eventId == 0
                    if eventType >= eventId then
                        set eventType = eventType - eventId
                        debug call AssertError(not Handler.hasGenericExpr(eventIndex[eventId], expr), "unregisterGenericEventHandler()", "thistype", 0, "EventType(" + I2S(eventType) + "): Code is not registered")
                        call Handler.unregisterGeneric(eventIndex[eventId], GetHandleId(expr))
                        set node = spellCount
                        loop
                            exitwhen node == 0
                            set node = node - 1
                            call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
                        endloop
                    endif
                    set eventId = eventId/2
                endloop
            endif
            set expr = null
        endmethod
        static method clearGenericEventHandlers takes integer eventType returns nothing
            local integer eventId = 0x10
            local integer node
            if eventType != 0 then
                debug call AssertError(not IsValidEventType(eventType), "clearGenericEventHandlers()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                loop
                    exitwhen eventId == 0
                    if eventType >= eventId then
                        set eventType = eventType - eventId
                        call Handler.clearGeneric(eventIndex[eventId])
                        set node = spellCount
                        loop
                            exitwhen node == 0
                            set node = node - 1
                            call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
                        endloop
                    endif
                    set eventId = eventId/2
                endloop
            endif
        endmethod
        static method clearGenericHandlers takes nothing returns nothing
            call clearGenericEventHandlers(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
        endmethod

        method registerEventHandler takes integer eventType, code handler returns nothing
            local boolexpr expr = Filter(handler)
            local integer offset = (this - 1)*5
            local integer eventId = 0x10
            if eventType != 0 then
                debug call AssertError((this) < 1 or (this) > spellCount, "registerEventHandler()", "thistype", this, "Invalid Spell instance")
                debug call AssertError(not IsValidEventType(eventType), "registerEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                if this == GENERIC then
                    call registerGenericEventHandler(eventType, handler)
                else
                    loop
                        exitwhen eventId == 0
                        if eventType >= eventId then
                            set eventType = eventType - eventId
                            call eventHandler[offset + eventIndex[eventId]].register(expr)
                        endif
                        set eventId = eventId/2
                    endloop
                endif
            endif
            set expr = null
        endmethod
        method unregisterEventHandler takes integer eventType, code handler returns nothing
            local boolexpr expr = Filter(handler)
            local integer offset = (this - 1)*5
            local integer eventId = 0x10
            if eventType != 0 then
                debug call AssertError((this) < 1 or (this) > spellCount, "unregisterEventHandler()", "thistype", this, "Invalid Spell instance")
                debug call AssertError(not IsValidEventType(eventType), "unregisterEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                if this == GENERIC then
                    call unregisterGenericEventHandler(eventType, handler)
                else
                    loop
                        exitwhen eventId == 0
                        if eventType >= eventId then
                            set eventType = eventType - eventId
                            debug call AssertError(not eventHandler[offset + eventIndex[eventId]].hasExpr(expr), "registerEventHandler()", "thistype", this, "EventType(" + I2S(eventType) + "): Code is already unregistered")
                            call eventHandler[offset + eventIndex[eventId]].unregister(GetHandleId(expr))
                        endif
                        set eventId = eventId/2
                    endloop
                endif
            endif
            set expr = null
        endmethod
        method clearEventHandlers takes integer eventType returns nothing
            local integer offset = (this - 1)*5
            local integer eventId = 0x10
            if eventType != 0 then
                debug call AssertError((this) < 1 or (this) > spellCount, "SpellEvent", "clearEventHandlers()", this, "Invalid Spell instance")
                debug call AssertError(not IsValidEventType(eventType), "SpellEvent", "clearEventHandlers()", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                if this == GENERIC then
                    call clearGenericEventHandlers(eventType)
                else
                    loop
                        exitwhen eventId == 0
                        if eventType >= eventId then
                            set eventType = eventType - eventId
                            call eventHandler[offset + eventIndex[eventId]].clear()
                        endif
                        set eventId = eventId/2
                    endloop
                endif
            endif
        endmethod
        method clearHandlers takes nothing returns nothing
            debug call AssertError((this) < 1 or (this) > spellCount, "clearHandlers()", "thistype", this, "Invalid Spell instance")
            if this == GENERIC then
                call this.clearGenericHandlers()
            else
                call this.clearEventHandlers(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
            endif
        endmethod

        method setEventFlag takes integer eventType, boolean flag returns nothing
            debug call AssertError(not IsEventSingleFlag(eventType), "setEventFlag()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
            set eventHandler[(this - 1)*5 + eventIndex[eventType]].enabled = flag
        endmethod
        method getEventFlag takes integer eventType returns boolean
            debug call AssertError(not IsEventSingleFlag(eventType), "getEventFlag()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
            return eventHandler[(this - 1)*5 + eventIndex[eventType]].enabled
        endmethod

        method operator handlersDisabled= takes boolean disabled returns nothing
            local Handler handler
            if eventAbilityId == this.abilId then
                set handler = eventHandler[(this - 1)*5 + eventIndex[eventEventType]]
                if disabled then
                    set handler.disablerCounter = handler.disablerCounter + 1
                else
                    set handler.disablerCounter = handler.disablerCounter - 1
                endif
            endif
        endmethod
        method operator handlersDisabled takes nothing returns boolean
            if eventAbilityId != this.abilId then
                return false
            endif
            return eventHandler[(this - 1)*5 + eventIndex[eventEventType]].disablerCounter > 0
        endmethod

        private static method overrideParams takes integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
            if eventAbilityId != 0 then
                set Handler(table[0][GetHandleId(GetTriggeringTrigger())]).overrideParams = true

                set tempOrderType           = orderType
                set tempLevel               = level
                set tempTriggerPlayer       = GetOwningPlayer(triggerUnit)
                set tempTriggerUnit         = triggerUnit
                set tempTargetX             = targetX
                set tempTargetY             = targetY
                set tempTarget              = target
            endif
        endmethod

        static method overrideNoTargetParams takes integer level, unit triggerUnit returns nothing
            call overrideParams(SPELL_ORDER_TYPE_NO_TARGET, level, triggerUnit, null, GetUnitX(triggerUnit), GetUnitY(triggerUnit))
        endmethod
        static method overridePointTargetParams takes integer level, unit triggerUnit, real targetX, real targetY returns nothing
            call overrideParams(SPELL_ORDER_TYPE_POINT_TARGET, level, triggerUnit, null, targetX, targetY)
        endmethod
        static method overrideSingleTargetParams takes integer level, unit triggerUnit, widget target returns nothing
            call overrideParams(SPELL_ORDER_TYPE_SINGLE_TARGET, level, triggerUnit, target, GetWidgetX(target), GetWidgetY(target))
        endmethod

        private static method setTargetData takes real x, real y, integer orderType returns nothing
            set eventTargetX    = x
            set eventTargetY    = y
            set eventOrderType  = orderType
        endmethod

        private static method executeEventHandler takes Handler eventHandler, integer currentId, boolean manualInvoke, integer eventFlag, integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing

            local integer disablerCounter       = eventHandler.disablerCounter
            local boolean overrideParams        = eventHandler.overrideParams
            local integer prevAbilityId         = eventAbilityId
            local integer prevEventType         = eventEventType
            local integer prevOrderType         = eventOrderType
            local integer prevLevel             = eventLevel
            local player prevTriggerPlayer      = eventTriggerPlayer
            local unit prevTriggerUnit          = eventTriggerUnit
            local real prevTargetX              = eventTargetX
            local real prevTargetY              = eventTargetY
            local unit prevTargetUnit           = eventTargetUnit
            local item prevTargetItem           = eventTargetItem
            local destructable prevTargetDest   = eventTargetDest
            local location tempLoc

            set eventAbilityId                  = currentId

            if manualInvoke then
                call setTargetData(targetX, targetY, orderType)

                set eventEventType              = eventFlag
                set eventLevel                  = level
                set eventTriggerPlayer          = GetOwningPlayer(triggerUnit)
                set eventTriggerUnit            = triggerUnit

                set table[0].widget[0]          = target
                set eventTargetUnit             = table[0].unit[0]
                set eventTargetItem             = table[0].item[0]
                set eventTargetDest             = table[0].destructable[0]
            else
                set eventEventType              = eventTypeId[GetHandleId(GetTriggerEventId())]
                set eventTriggerPlayer          = GetTriggerPlayer()
                set eventTriggerUnit            = GetTriggerUnit()
                set eventLevel                  = GetUnitAbilityLevel(eventTriggerUnit, eventAbilityId)
                set eventTargetUnit             = GetSpellTargetUnit()
                set eventTargetItem             = GetSpellTargetItem()
                set eventTargetDest             = GetSpellTargetDestructable()

                if eventTargetUnit != null then
                    if eventTargetUnit == eventTriggerUnit              and/*
                    */ not (GetSpellTargetX() != 0.)                    and/*
                    */ not (GetSpellTargetY() != 0.)                    then
                    /* Special Case (for no-target spells based on channel) */
                        call setTargetData(GetUnitX(eventTriggerUnit), GetUnitY(eventTriggerUnit), SPELL_ORDER_TYPE_NO_TARGET)
                        set eventTargetUnit     = null
                    else
                        call setTargetData(GetUnitX(eventTargetUnit), GetUnitY(eventTargetUnit), SPELL_ORDER_TYPE_SINGLE_TARGET)
                    endif
                elseif eventTargetItem != null then
                    call setTargetData(GetWidgetX(eventTargetItem), GetWidgetY(eventTargetItem), SPELL_ORDER_TYPE_SINGLE_TARGET)
                elseif eventTargetDest != null then
                    call setTargetData(GetWidgetX(eventTargetDest), GetWidgetY(eventTargetDest), SPELL_ORDER_TYPE_SINGLE_TARGET)
                else
                    set tempLoc = GetSpellTargetLoc()
                    if tempLoc == null then
                    /* Special Case (for some no-target spells) */
                        call setTargetData(GetUnitX(eventTriggerUnit), GetUnitY(eventTriggerUnit), SPELL_ORDER_TYPE_NO_TARGET)
                    else
                        call RemoveLocation(tempLoc)
                        set tempLoc = null
                        call setTargetData(GetSpellTargetX(), GetSpellTargetY(), SPELL_ORDER_TYPE_POINT_TARGET)
                    endif
                endif
            endif

            set eventHandler.disablerCounter    = 0
            set eventHandler.overrideParams     = false
            call TriggerEvaluate(eventHandler.trigger)
            set eventHandler.disablerCounter    = disablerCounter
            set eventHandler.overrideParams     = overrideParams

            call setTargetData(prevTargetX, prevTargetY, prevOrderType)

            set eventAbilityId                  = prevAbilityId
            set eventEventType                  = prevEventType
            set eventLevel                      = prevLevel
            set eventTriggerPlayer              = prevTriggerPlayer
            set eventTriggerUnit                = prevTriggerUnit
            set eventTargetUnit                 = prevTargetUnit
            set eventTargetItem                 = prevTargetItem
            set eventTargetDest                 = prevTargetDest

            set prevTriggerPlayer               = null
            set prevTriggerUnit                 = null
            set prevTargetUnit                  = null
            set prevTargetItem                  = null
            set prevTargetDest                  = null

        endmethod

        private method invokeEvent takes integer eventType, integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
            local Handler handler = eventHandler[(this - 1)*5 + eventIndex[eventType]]
            if handler != 0 and handler.enabled then
                call executeEventHandler(handler, this.abilId, true, eventType, orderType, level, triggerUnit, target, targetX, targetY)
            endif
        endmethod

        method invokeNoTargetEvent takes integer eventType, integer level, unit triggerUnit returns nothing
            debug call AssertError(not IsEventSingleFlag(eventType), "executeNoTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
            call this.invokeEvent(eventType, SPELL_ORDER_TYPE_NO_TARGET, level, triggerUnit, null, GetUnitX(triggerUnit), GetUnitY(triggerUnit))
        endmethod
        method invokePointTargetEvent takes integer eventType, integer level, unit triggerUnit, real targetX, real targetY returns nothing
            debug call AssertError(not IsEventSingleFlag(eventType), "executePointTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
            call this.invokeEvent(eventType, SPELL_ORDER_TYPE_POINT_TARGET, level, triggerUnit, null, targetX, targetY)
        endmethod
        method invokeSingleTargetEvent takes integer eventType, integer level, unit triggerUnit, widget target returns nothing
            debug call AssertError(not IsEventSingleFlag(eventType), "executeSingleTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
            call this.invokeEvent(eventType, SPELL_ORDER_TYPE_SINGLE_TARGET, level, triggerUnit, target, GetWidgetX(target), GetWidgetY(target))
        endmethod

        private static method onSpellEvent takes integer eventIndex returns nothing
            local integer id = GetSpellAbilityId()
            local Handler handler = eventHandler[(table[spellKey][id] - 1)*5 + eventIndex]
            if handler != 0 and handler.enabled then
                call executeEventHandler(handler, id, false, 0, 0, 0, null, null, 0.00, 0.00)
            endif
        endmethod

        private static method onSpellCast takes nothing returns nothing
            call onSpellEvent(eventIndex[EVENT_SPELL_CAST])
        endmethod
        private static method onSpellChannel takes nothing returns nothing
            call onSpellEvent(eventIndex[EVENT_SPELL_CHANNEL])
        endmethod
        private static method onSpellEffect takes nothing returns nothing
            call onSpellEvent(eventIndex[EVENT_SPELL_EFFECT])
        endmethod
        private static method onSpellEndcast takes nothing returns nothing
            call onSpellEvent(eventIndex[EVENT_SPELL_ENDCAST])
        endmethod
        private static method onSpellFinish takes nothing returns nothing
            call onSpellEvent(eventIndex[EVENT_SPELL_FINISH])
        endmethod

        private static method registerEvent takes playerunitevent whichEvent, code handler returns nothing
            static if LIBRARY_RegisterPlayerUnitEvent then
                call RegisterAnyPlayerUnitEvent(whichEvent, handler)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, whichEvent)
                call TriggerAddCondition(t, Filter(handler))
                set t = null
            endif
        endmethod

        static if not LIBRARY_ResourcePreloader then
            private static method initPreloadDummy takes nothing returns nothing
                local rect world = GetWorldBounds()
                set preloadDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'hpea', 0.00, 0.00, 0.00)
                call SetUnitY(preloadDummy, GetRectMaxY(world) + 1000.00)
                call UnitAddAbility(preloadDummy, 'AInv')
                call UnitAddAbility(preloadDummy, 'Avul')
                call UnitRemoveAbility(preloadDummy, 'Amov')
                call RemoveRect(world)
                set world = null
            endmethod
        endif

        private static method init takes nothing returns nothing
            set spellKey = Node.allocate()
            set spellCount = spellCount + 1
            set table[spellKey][0] = spellCount

            set eventIndex[EVENT_SPELL_CAST]    = 1
            set eventIndex[EVENT_SPELL_CHANNEL] = 2
            set eventIndex[EVENT_SPELL_EFFECT]  = 3
            set eventIndex[EVENT_SPELL_ENDCAST] = 4
            set eventIndex[EVENT_SPELL_FINISH]  = 5
            set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CAST)]        = EVENT_SPELL_CAST
            set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CHANNEL)]    = EVENT_SPELL_CHANNEL
            set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_EFFECT)]    = EVENT_SPELL_EFFECT
            set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_ENDCAST)]    = EVENT_SPELL_ENDCAST
            set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_FINISH)]    = EVENT_SPELL_FINISH
            call registerEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.onSpellCast)
            call registerEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function thistype.onSpellChannel)
            call registerEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onSpellEffect)
            call registerEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onSpellEndcast)
            call registerEvent(EVENT_PLAYER_UNIT_SPELL_FINISH, function thistype.onSpellFinish)

            static if not LIBRARY_ResourcePreloader then
                call initPreloadDummy()
            endif
        endmethod
        implement Init

    endstruct

    private module Init
        private static method onInit takes nothing returns nothing
            set table = TableArray[JASS_MAX_ARRAY_SIZE]
            call init()
            call Handler.init()
        endmethod
    endmodule

    /*===================================================================================*/

    constant function GetEventSpellAbilityId takes nothing returns integer
        return Spell.abilityId
    endfunction
    constant function GetEventSpellEventType takes nothing returns integer
        return Spell.eventType
    endfunction
    constant function GetEventSpellOrderType takes nothing returns integer
        return Spell.orderType
    endfunction
    constant function GetEventSpellLevel takes nothing returns integer
        return Spell.level
    endfunction
    constant function GetEventSpellPlayer takes nothing returns player
        return Spell.triggerPlayer
    endfunction
    constant function GetEventSpellCaster takes nothing returns unit
        return Spell.triggerUnit
    endfunction
    constant function GetEventSpellTargetUnit takes nothing returns unit
        return Spell.targetUnit
    endfunction
    constant function GetEventSpellTargetItem takes nothing returns item
        return Spell.targetItem
    endfunction
    constant function GetEventSpellTargetDest takes nothing returns destructable
        return Spell.targetDest
    endfunction
    constant function GetEventSpellTarget takes nothing returns widget
        return Spell.target
    endfunction
    constant function GetEventSpellTargetX takes nothing returns real
        return Spell.targetX
    endfunction
    constant function GetEventSpellTargetY takes nothing returns real
        return Spell.targetY
    endfunction

    function SetSpellEventFlag takes integer abilId, integer eventType, boolean flag returns nothing
        debug call AssertError(not IsEventSingleFlag(eventType), "SetSpellEventFlag()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
        call Spell[abilId].setEventFlag(eventType, flag)
    endfunction
    function GetSpellEventFlag takes integer abilId, integer eventType returns boolean
        debug call AssertError(not IsEventSingleFlag(eventType), "GetSpellEventFlag()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
        return Spell[abilId].getEventFlag(eventType)
    endfunction

    function SpellCancelEventHandlers takes boolean cancel returns nothing
        set Spell[GetEventSpellAbilityId()].handlersDisabled = cancel
    endfunction

    function SpellInvokeNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing
        call Spell[abilId].invokeNoTargetEvent(eventType, level, caster)
    endfunction
    function SpellInvokePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing
        call Spell[abilId].invokePointTargetEvent(eventType, level, caster, targetX, targetY)
    endfunction
    function SpellInvokeSingleTargetEvent takes integer abilId, integer eventType, integer level, unit caster, widget target returns nothing
        call Spell[abilId].invokeSingleTargetEvent(eventType, level, caster, target)
    endfunction

    function SpellOverrideNoTargetParams takes integer level, unit caster returns nothing
        call Spell.overrideNoTargetParams(level, caster)
    endfunction
    function SpellOverridePointTargetParams takes integer level, unit caster, real targetX, real targetY returns nothing
        call Spell.overridePointTargetParams(level, caster, targetX, targetY)
    endfunction
    function SpellOverrideSingleTargetParams takes integer level, unit caster, widget target returns nothing
        call Spell.overrideSingleTargetParams(level, caster, target)
    endfunction

    function SpellRegisterEventHandler takes integer abilId, integer eventType, code handler returns nothing
        debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellRegisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
        call Spell[abilId].registerEventHandler(eventType, handler)
    endfunction
    function SpellUnregisterEventHandler takes integer abilId, integer eventType, code handler returns nothing
        debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellUnregisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
        call Spell[abilId].unregisterEventHandler(eventType, handler)
    endfunction
    function SpellClearEventHandlers takes integer abilId, integer eventType returns nothing
        debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellClearEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
        call Spell[abilId].clearEventHandlers(eventType)
    endfunction
    function SpellClearHandlers takes integer abilId returns nothing
        call Spell[abilId].clearHandlers()
    endfunction

    function SpellRegisterGenericEventHandler takes integer eventType, code handler returns nothing
        debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellRegisterGenericEventHandler()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
        call Spell.registerGenericEventHandler(eventType, handler)
    endfunction
    function SpellUnregisterGenericEventHandler takes integer eventType, code handler returns nothing
        debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellUnregisterGenericEventHandler()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
        call Spell.unregisterGenericEventHandler(eventType, handler)
    endfunction
    function SpellClearGenericEventHandlers takes integer eventType returns nothing
        debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellClearGenericEventHandlers()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
        call Spell.clearGenericEventHandlers(eventType)
    endfunction
    function SpellClearGenericHandlers takes nothing returns nothing
        call Spell.clearGenericHandlers()
    endfunction

    /*===================================================================================*/

    private function DestroyTimerEx takes timer whichTimer returns nothing
        call PauseTimer(whichTimer)
        call DestroyTimer(whichTimer)
    endfunction

    private function TimerStartEx takes integer node, real period, code callback returns integer
        local timer t
        if node > 0 then
            set t = CreateTimer()
            set table[0].timer[node] = t
            set table[0][GetHandleId(t)] = node
            call TimerStart(t, period, true, callback)
            set t = null
        elseif node < 0 then
            set node = -node
            if table[0].handle.has(node) then
                set t = table[0].timer[node]
                call table[0].handle.remove(node)
                call table[0].remove(GetHandleId(t))
                call DestroyTimerEx(t)
                set t = null
                return node
            endif
        endif
        return 0
    endfunction

    private function RegisterSpell takes integer abilId, integer eventType, code onSpellEvent returns nothing
        if abilId != 0 then
            call SpellRegisterEventHandler(abilId, eventType, onSpellEvent)
        endif
    endfunction

    module SpellEvent
        static if thistype.onSpellPeriodic.exists then
            implement LinkedListLite
            private boolean internal
            private static timer periodicTimer

            private method deleteNode takes nothing returns nothing
                call remove(this)
                static if thistype.onSpellEnd.exists then
                    call this.onSpellEnd()
                endif
                if this.internal then
                    set this.internal = false
                    call Node(this).deallocate()
                endif
                if thistype(0).next == 0 then
                    call DestroyTimerEx(periodicTimer)
                endif
            endmethod

            private static method onPeriodic takes nothing returns nothing
                local thistype node = thistype(0).next
                local thistype next
                debug local thistype prev
                debug local boolean array traversed
                /*
                *   Additional safety check
                */
                if node == 0 then
                    call DestroyTimerEx(periodicTimer)
                    return
                endif
                loop
                    exitwhen node == 0
                    debug call AssertError(traversed[node], "[Internal] onSpellPeriodic()", "thistype", node, "[Node Links Bugged] : A node in the list was traversed twice in one traversal")
                    debug set traversed[node] = true
                    debug set prev = node.prev
                    set next = node.next
                    if node.onSpellPeriodic() then
                        debug call AssertError(node.next.prev != node, "[Internal onSpellPeriodic()", "thistype", node, "[Node Links Bugged] : Node links are messed up!")
                        call node.deleteNode()
                        debug set traversed[node] = false
                    debug elseif next.prev == prev then
                        debug set traversed[node] = false
                    endif
                    set node = next
                endloop
            endmethod

            private static method onSpellEvent takes nothing returns nothing
                local thistype node = Node.allocate()
                local boolean prevEmpty = thistype(0).next == 0
                local thistype used = node.onSpellStart()
                /*
                *   Add the new node into the list
                */
                if used == node then
                    debug call AssertError(used.next.prev == used, "[Internal] onSpellStart()", "thistype", used, "[Node Already Exists] : Make sure your nodes are all from the same stack..")
                    set used.internal = true
                    call insert(thistype(0).prev, used)
                /*
                *   If the user returned a different node than the one he was given,
                *   deallocate the earlier node and replace it with the new node
                *   from the user.
                */
                else
                    call Node(node).deallocate()
                    if used > 0 then
                        debug call AssertError(used.next.prev == used, "[Internal] onSpellStart()", "thistype", used, "[Node Already Exists] : Make sure your nodes are all from the same stack..")
                        call insert(thistype(0).prev, used)
                    elseif used < 0 then
                        set used = -used
                        if used.next.prev == used then
                            call used.deleteNode()
                        endif
                    endif
                endif
                /*
                *   We need to use this kind of check in case the user returned 0
                *   but manually added some node in the list inside onSpellStart()
                */
                if prevEmpty and thistype(0).next != 0 then
                    set periodicTimer = CreateTimer()
                    call TimerStart(periodicTimer, SPELL_PERIOD, true, function thistype.onPeriodic)
                endif
            endmethod
        else
        /*
        *   If no periodic operations
        */
            private static method onSpellEvent takes nothing returns nothing
                local thistype node = Node.allocate()
                static if thistype.onSpellEnd.exists then
                    local thistype used = node.onSpellStart()
                    if used > 0 then
                        call used.onSpellEnd()
                    endif
                else
                    call node.onSpellStart()
                endif
                call Node(node).deallocate()
            endmethod
        endif

        private static method onInit takes nothing returns nothing
            call RegisterSpell(SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
            static if thistype.onSpellEventModuleInit.exists then
                call onSpellEventModuleInit()
            endif
        endmethod

        static method registerSpellEvent takes integer abilId, integer eventType returns nothing
            call SpellRegisterEventHandler(abilId, eventType, function thistype.onSpellEvent)
        endmethod
    endmodule

    module SpellEventEx
        static if thistype.onSpellPeriodic.exists then
            private static method onPeriodic takes nothing returns nothing
                local thistype node = table[0][GetHandleId(GetExpiredTimer())]
                if node.onSpellPeriodic() then
                    static if thistype.onSpellEnd.exists then
                        call node.onSpellEnd()
                    endif
                    call TimerStartEx(-node, 0.00, null)
                endif
            endmethod
        endif

        private static method onSpellEvent takes nothing returns nothing
            static if thistype.onSpellPeriodic.exists then
                static if thistype.onSpellEnd.exists then
                    local thistype node = TimerStartEx(onSpellStart(), SPELL_PERIOD, function thistype.onPeriodic)
                    if node > 0 then
                        call node.onSpellEnd()
                    endif
                else
                    call TimerStartEx(onSpellStart(), SPELL_PERIOD, function thistype.onPeriodic)
                endif
            elseif thistype.onSpellEnd.exists then
                local thistype node = onSpellStart()
                if node > 0 then
                    call node.onSpellEnd()
                endif
            else
                call onSpellStart()
            endif
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterSpell(SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
            static if thistype.onSpellEventModuleInit.exists then
                call onSpellEventModuleInit()
            endif
        endmethod

        static method registerSpellEvent takes integer abilId, integer eventType returns nothing
            call SpellRegisterEventHandler(abilId, eventType, function thistype.onSpellEvent)
        endmethod
    endmodule

    module SpellEventGeneric
        private static method onSpellResponse takes nothing returns nothing
            static if thistype.onSpellEvent.exists then
                call onSpellEvent()
            endif
            static if thistype.onSpellCast.exists then
                if GetEventSpellEventType() == EVENT_SPELL_CAST then
                    call onSpellCast()
                endif
            endif
            static if thistype.onSpellChannel.exists then
                if GetEventSpellEventType() == EVENT_SPELL_CHANNEL then
                    call onSpellChannel()
                endif
            endif
            static if thistype.onSpellEffect.exists then
                if GetEventSpellEventType() == EVENT_SPELL_EFFECT then
                    call onSpellEffect()
                endif
            endif
            static if thistype.onSpellEndcast.exists then
                if GetEventSpellEventType() == EVENT_SPELL_ENDCAST then
                    call onSpellEndcast()
                endif
            endif
            static if thistype.onSpellFinish.exists then
                if GetEventSpellEventType() == EVENT_SPELL_FINISH then
                    call onSpellFinish()
                endif
            endif
        endmethod
        private static method onInit takes nothing returns nothing
            call SpellRegisterGenericEventHandler(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH, function thistype.onSpellResponse)
            static if thistype.onSpellEventGenericModuleInit.exists then
                call onSpellEventGenericModuleInit()
            endif
        endmethod
    endmodule


endlibrary


Other possible uses:


Chance to block a single-target spell
JASS:
library SpellBlock /*


    */uses /*

    */SpellEvent    /*
    */UnitDex        /*
    */Table


    private struct ConditionList extends array

        boolexpr expr
        readonly thistype prev
        readonly thistype next
        private static thistype array stack

        private static trigger evaluator = CreateTrigger()

        method evaluate takes nothing returns boolean
            call TriggerClearConditions(evaluator)
            call TriggerAddCondition(evaluator, this.expr)
            return TriggerEvaluate(evaluator)
        endmethod

        static method allocate takes nothing returns thistype
            local thistype node = stack[0]
            if stack[node] == 0 then
                set node = node + 1
                set stack[0] = node
            else
                set stack[0] = stack[node]
                set stack[node] = 0
            endif
            return node
        endmethod
        method deallocate takes nothing returns nothing
            set stack[this] = stack[0]
            set stack[0] = this
        endmethod

        method insert takes thistype node returns nothing
            local thistype next = this.next
            set node.prev = this
            set node.next = next
            set next.prev = node
            set this.next = node
        endmethod
        method remove takes nothing returns nothing
            set this.next.prev = this.prev
            set this.prev.next = this.next
        endmethod

        static method create takes nothing returns thistype
            local thistype node = allocate()
            set node.prev = node
            set node.next = node
            return node
        endmethod
        method destroy takes nothing returns nothing
            local thistype node = this.next
            loop
                exitwhen node == this
                set node.expr = null
                call node.remove()
                call node.deallocate()
                set node = node.next
            endloop
            call this.deallocate()
        endmethod

    endstruct

    struct SpellBlock extends array

        readonly ConditionList conditions

        static method operator [] takes unit u returns thistype
            local integer unitId = GetHandleId(u)
            local thistype node = table[0][unitId]
            if node != 0 then
                set table[0][unitId] = ConditionList.create()
            endif
            return node
        endmethod

        method add takes boolexpr expr returns nothing
            local ConditionList node = ConditionList.allocate()
            set node.expr = expr
            set table[this][GetHandleId(expr)] = node
            call this.prev.insert(node)
        endmethod
        method remove takes boolexpr expr returns nothing
            local integer exprId = GetHandleId(expr)
            local ConditionList node = table[this][exprId]
            set node.expr = null
            call table[this].remove(exprId)
            call node.deallocate()
        endmethod

        private static method onSpellEffect takes nothing returns nothing
            local ConditionList list
            local ConditionList node
            local boolean result
            if GetEventSpellTargetUnit() != null then
                set list = table[0][GetHandleId(GetEventSpellTargetUnit())]
                set node = list.next
                loop
                    exitwhen node == list
                    if node.evaluate() then
                        set Spell[GetEventSpellAbilityId()].handlersDisabled = true    // Block the spell's effects
                        exitwhen true
                    endif
                    set node = node.next
                endloop
            endif
        endmethod

        private static method onDeindex takes nothing returns nothing
            local integer unitId = GetHandleId(GetIndexedUnit())
            local ConditionList list = table[0][unitId]
            if list != 0 then
                call list.destroy()
                call table[0].remove(unitId)
            endif
        endmethod

        private static method onInit takes nothing returns nothing
            call OnUnitDeindex(function thistype.onDeindex)
            call SpellRegisterGenericEventHandler(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT, function thistype.onSpellEffect)
        endmethod

    endstruct


endlibrary


A passive ability that implements spell-block
JASS:
library LinkenSphere initializer OnInit uses SpellBlock SpellEvent

    globals
        private integer ABIL_ID                 = 'Als0'
        private string TARGET_SFX_MODEL         = ""
        private string TARGET_SFX_ATTACHPOINT     = "chest"
    endglobals

    private constant function BlockChance takes integer level returns real
        return 0.25 + 0.05*level
    endfunction

    /*=============================================================*/

    private function OnBlockCheck takes nothing returns boolean
        if GetEventSpellOrderType() == SPELL_ORDER_TYPE_TARGET and GetRandomReal(0.00, 1.00) <= BlockChance(GetUnitAbilityLevel(GetEventSpellTargetUnit(), ABIL_ID)) then
            call DestroyEffect(AddSpecialEffectTarget(TARGET_SFX_MODEL, GetEventSpellTargetUnit(), TARGET_SFX_ATTACHPOINT))
            return true // Block the spell
        endif
        return false
    endfunction

    private function OnLearn takes nothing returns nothing
        local unit u = GetTriggerUnit()
        local integer level = GetUnitAbilityLevel(u, ABIL_ID)
        if level == 1 then
            call SpellBlock[u].add(Filter(function OnBlockCheck))
        endif
        set u = null
    endfunction

    private function OnInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL) //Skill learn event
        call TriggerAddCondition(t, Filter(function OnLearn))
    endfunction

endlibrary




v3.1.1
- Yet another enhancement for the security of SpellEvent module

v3.1.0
- Added a new meaning to the return value of onSpellStart()
> If returned value is negative, the system will search for its positive equivalent in the nodes running onSpellPeriodic. If present, it will be removed and onSpellEnd() will be called for that node
- Better error situation handling

v3.0.0
- Added LinkedList library as a mandatory requirement
- Swapped the meaning of the boolean return values of onSpellPeriodic()
> return true now ends the periodic phase and runs onSpellEnd()
- Added a lot more debug messages in the SpellEvent module
- Updated documentation and other changes

v2.0.5
- Stability updates and bug fixes

v2.0.3
- Major API naming changes
- Made Table a mandatory dependency
- Marked one feature as incompatible with Reforged (Spell<>.handlersDisabled)
- .handlersDisabled = true/false now increments/decrements an internal 'disabled' counter
- Made the spell event modules even more fool-proof and flexible
- Made an internal ability preloader if library ResourcePreloader is not present
- Some code refactors and bug fixes

v1.4.5
- Shortened code and removed code leftovers

v1.4.4
- Made the framework of the SpellEvent module more error-proof
- Added debug messages to the SpellEvent module

v1.4.3
- Shortened and removed some code leftovers
- Fixed a little mistake with the order of triggerconditions upon registration

v1.4.2
- Fixed a bug that prevents registration of the same handler code more than once

v1.4.1
- Fixed a bug in patches 1.30+

v1.4.0
- Added 'SpellEventGeneric' module

v1.3.2
- Fixed bug regarding handlers being added twice upon registration

v1.3.1
- Changed some API naming
> Spell.USER -> Spell.TRIGGER_PLAYER
> Spell.CASTER -> Spell.TRIGGER_UNIT
- Updated documentation
- Fixed bugs regarding the generic event handlers
- Other improvements

v1.3.0 (Major Update)
- Added a lot more functionalities

v1.1.0 - v1.2.X
- Undocumented

v1.0
- Initial release
 
Last edited:
I would recommend comparing this to SpellEvent by Anitarf, as these 2 are effectively similar. He has features that were easy to overlook, but most which I was able to implement into GUI Spell System (of course Spell System does much more, because I tried to simplify as much of the spellmaking process as possible).

SpellEvent - Wc3C.net
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I took a look at SpellEvent by Anitarf and this is my comparison

Anitarf's

Features:
- API for event registrations
- Event response variables (Writable, which is useful if all your spells are using his library since you could make some cool effects such as target-redirection, evasion (for target-spells), and reflection)
- Event responses are recursion-safe and are instance-specific
- You can register an any-spell event by using 0 as the abilityId upon registration

Implementation:
- An Event handler is limited to 1 per event for a specific spell
- Event responses are instance-specific since they are set only at the earliest stage of the event (channel phase)
- Uses function interfaces for event handler registration (Consequently, limits the registered handlers to 1 per event for a specific spell + use of function interfaces is not so preferable imo)
- Generic spell handlers uses a single trigger per registered handler (since it uses function interfaces)


Mine

Features:
- API for event registration/uregistration
- Modules which further eases spell writing
- Event responses are recursion-safe, static, and readonly
- Debug messages

Implementation:
- Able to register more than 1 handler code per event for a specific spell
- Uses codes instead of function interfaces
- trigger creation/destruction is dynamic (only creates triggers if there are handlers codes registered for an event per spell)
- TARGET_X and TARGET_Y returns coordinates of the caster for immediate-cast spells


What I personally like in this version most is the modules it provides which results into a shorter and cleaner code. For a usual spell, the format will be:
JASS:
library SpellLib uses SpellEvent

    private struct SpellStruct extends array

        private static constant integer SPELL_ID = 'XXXX'

        private static constant integer SPELL_EVENT_TYPE = SpellEventType.EFFECT

        private static constant real SPELL_PERIOD = 1.00/32.00

        private method onSpellStart takes nothing returns nothing
            //...
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            //...
            return this.duration > 0.00
        endmethod

        private method onSpellEnd takes nothing returns nothing
            //...
        endmethod

        implement SpellEvent

    endstruct

endlibrary
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Updated to v1.3.0

The library now offers a lot more features. In addition to previous benefits, this now allows you to do cool spell-related systems such as Multicast, custom spell-block, spell rebound/redirection, among other things.


Update

v1.3.1

- Changed some API naming
> Spell.USER -> Spell.TRIGGER_PLAYER
> Spell.CASTER -> Spell.TRIGGER_UNIT
- Updated documentation
- Fixed bugs regarding the generic event handlers
- Other improvements
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Updated to v3.0.0
- Added LinkedList library as a mandatory requirement
- Swapped the meaning of the boolean return values of onSpellPeriodic()
> return true now ends the periodic phase and runs onSpellEnd()
- Added a lot more debug messages in the SpellEvent module
- Updated documentation and other changes

v3.1.0
- Added a new meaning to the return value of onSpellStart()
> If returned value is negative, the system will search for its positive equivalent in the nodes running onSpellPeriodic. If present, it will be removed and onSpellEnd() will be called for that node
- Better error situation handling
 
Last edited:
Top