1. The long-awaited results for Concept Art Contest #11 have finally been released!
    Dismiss Notice
  2. Join Texturing Contest #30 now in a legendary battle of mythological creatures!
    Dismiss Notice
  3. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  4. Hivers united and created a bunch of 2v2 melee maps. Vote for the best in our Melee Mapping Contest #4 - Poll!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice

[vJASS] SpellEvent

Discussion in 'Submissions' started by AGD, Dec 29, 2017.

  1. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    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.

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


    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.

    Template

    Code (vJASS):

    library SpellEventTemplate uses SpellEvent
        /***********************************************************
        *
        *   Global Variables:
        *
        *       Unlike the default spell event responses provided
        *       by Jass, these variables are all recursion-safe.
        *
        *       readonly  integer  Spell.ABILITY_ID
        *       readonly  player   Spell.USER
        *       readonly  unit     Spell.CASTER
        *       readonly  unit     Spell.TARGET
        *       readonly  real     Spell.TARGET_X
        *       readonly  real     Spell.TARGET_Y
        *       readonly  integer  Spell.LEVEL
        *
        ***********************************************************/


        private struct YourSpellStruct 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

            /*
            *   > 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 nothing
                /*
                *   -> Put the spell's initial actions here <-
                *
                *   Runs once immediately after the spell event fires
                */

            endmethod

            private method onSpellPeriodic takes nothing returns boolean
                /*
                *   -> Put the spell's periodic actions here <-
                *
                *   Runs every <SPELL_PERIOD> seconds for
                *   <SPELL_DURATION> seconds after the spell
                *   event fires
                */

                return true //return false to stop the periodic actions
            endmethod

            private method onSpellEnd takes nothing returns nothing
                /*
                *   -> Put the spell's cleanup actions here <-
                *
                *   Runs once <SPELL_DURATION> seconds after
                *   the spell event fires
                */

            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
     


    Comparisons

    Code (vJASS):

    //=============================================================================
    /*
    *   Using SpellEvent
    */

    struct SpellUsingSpellEvent 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 real duration

        private method onSpellStart takes nothing returns nothing
            /*
            *   -> Put the spell's initial actions here <-
            */

            set this.duration = 10.00 + 0.00*Spell.LEVEL
        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_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_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_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_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
     



    Snippet

    Code (vJASS):

    library SpellEvent /* v1.01


        */
    uses /*

        */
    Table                             /*  https://www.hiveworkshop.com/threads/snippet-new-table.188084/
        */
    optional ErrorMessage             /*  https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
        */
    optional RegisterPlayerUnitEvent  /*  https://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
        */
    optional ResourcePreloader        /*  https://www.hiveworkshop.com/threads/snippet-resource-preloader.287358/


        */
    //! novjass

        /*
            Pros:
                - Minimizes handle creation when registering a spell event handler
                - Constant time complexity 'O(1)' when executing a spell event
                - Shortens spell registration code
                - Shortens and gives better structure and readability by making a template for
                  your spell struct through the use of modules

        */


        |=========|
        | Credits |
        |=========|
        /*
            - AGD (Author)
            - Bribe (SpellEffectEvent concept)

        */

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

          */
    struct Spell extends array/*

            Event Responses:
              */
    readonly static integer ABILITY_ID      /*
              */
    readonly static integer LEVEL           /*
              */
    readonly static player  USER            /*
              */
    readonly static unit    CASTER          /*
              */
    readonly static unit    TARGET          /*
              */
    readonly static real    TARGET_X        /*  Returns the x-coordinate of the caster if the spell is a 'No-target' ability
              */
    readonly static real    TARGET_Y        /*  Returns the y-coordinate of the caster if the spell is a 'No-target' ability

            Methods:

              */
    static method   operator []                 takes integer spellId                                           returns Spell/*
                    - Returns a Spell instance based on the given spellId for event handler
                      registrations

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


          */
    struct SpellEventType extends array/*

            Event Types:
              */
    static constant SpellEventType CAST      /*
              */
    static constant SpellEventType CHANNEL   /*
              */
    static constant SpellEventType EFFECT    /*
              */
    static constant SpellEventType ENDCAST   /*
              */
    static constant SpellEventType FINISH    /*


        */

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

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

        */

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

          */
    constant function   GetEventSpellId             takes nothing                                                   returns integer/*
          */
    constant function   GetEventSpellLevel          takes nothing                                                   returns integer/*
          */
    constant function   GetEventSpellUser           takes nothing                                                   returns player/*
          */
    constant function   GetEventSpellCaster         takes nothing                                                   returns unit/*
          */
    constant function   GetEventSpellTargetUnit     takes nothing                                                   returns unit/*
          */
    constant function   GetEventSpellTargetX        takes nothing                                                   returns real/*
          */
    constant function   GetEventSpellTargetY        takes nothing                                                   returns real/*

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

        */

        |=========|
        | Modules |
        |=========|
        /*
            Automates spell event registration at map init
            Implement either of these two modules at the bottom of your struct

          */
    module SpellEvent/*

                > Uses a single timer 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/*
                    - Spell instances links
                    - Readonly attribute is only effective outside the implementing struct, though
                      users are also not supposed to change these values

            Member interfaces:
                - Should be declared above the module implementation

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

              */
    method onSpellStart takes nothing returns nothing/*
                    - Runs right after the spell event fires
              */
    method onSpellPeriodic takes nothing returns boolean/*
                    - Runs periodically after the spell event fires until it returns false
              */
    method onSpellEnd takes nothing returns nothing/*
                    - Runs <SPELL_PERIOD> seconds after method onSpellPeriodic() returns false


          */
    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, and for spells where
                  you need to manually design your spell instance allocation/deallocation.

            Member interfaces:
                - Should be declared above the module implementation

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

              */
    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
              */
    method onSpellPeriodic takes nothing returns boolean/*
                    - Runs periodically after the spell event fires until it returns false
              */
    method onSpellEnd takes nothing returns nothing/*
                    - Runs <SPELL_PERIOD> seconds after method onSpellPeriodic() returns false
                    - User must manually deallocate the spell instance inside this method


        */
    //! endnovjass

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

        /*
        *   Arbitrary values are used so that users are forced to use the constant globals
        *   instead of manually typing the integers ;D
        */

        globals
            constant integer EVENT_SPELL_CAST      = 0x1234 + 0x123 * 1
            constant integer EVENT_SPELL_CHANNEL   = 0x1234 + 0x123 * 2
            constant integer EVENT_SPELL_EFFECT    = 0x1234 + 0x123 * 3
            constant integer EVENT_SPELL_ENDCAST   = 0x1234 + 0x123 * 4
            constant integer EVENT_SPELL_FINISH    = 0x1234 + 0x123 * 5
        endglobals

        struct SpellEventType extends array
            static constant integer CAST      = EVENT_SPELL_CAST
            static constant integer CHANNEL   = EVENT_SPELL_CHANNEL
            static constant integer EFFECT    = EVENT_SPELL_EFFECT
            static constant integer ENDCAST   = EVENT_SPELL_ENDCAST
            static constant integer FINISH    = EVENT_SPELL_FINISH
        endstruct

        globals
            private keyword Init
            private TableArray handlerTable
            private Table table
        endglobals

        private function GetEventIndex takes integer eventType returns integer
            return (eventType - 0x1234)/0x123
        endfunction

        struct Spell extends array

            readonly static integer ABILITY_ID      = 0
            readonly static integer LEVEL           = 0
            readonly static player  USER            = null
            readonly static unit    CASTER          = null
            readonly static unit    TARGET          = null
            readonly static real    TARGET_X        = 0.00
            readonly static real    TARGET_Y        = 0.00

            private static integer spellCount = 0
            private static trigger array handlerTrigger
            private static integer array handlerCount

            static method operator [] takes integer abilId returns thistype
                local thistype this = table[-abilId]
                if this == 0 then
                    static if LIBRARY_ErrorMessage then
                        debug call ThrowError(spellCount > 1000, "SpellEvent", "Spell[]", "thistype", 0, "Overflow")
                    endif
                    set spellCount = spellCount + 1
                    set table[-abilId] = spellCount
                    return spellCount
                endif
                return this
            endmethod

            method registerEventHandler takes SpellEventType eventType, code handler returns nothing
                local integer index = (this - 1)*5 + GetEventIndex(eventType)
                local boolexpr expr = Filter(handler)
                static if LIBRARY_ErrorMessage then
                    debug call ThrowError((this) < 1 or (this) > spellCount, "SpellEvent", "registerEventHandler()", "thistype", this, "Attempted to use an invalid Spell instance")
                    debug call ThrowError(GetEventIndex(eventType) < 1 or GetEventIndex(eventType) > 5, "SpellEvent", "registerEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                    debug call ThrowError(handlerTable[index].handle.has(GetHandleId(expr)), "SpellEvent", "registerEventHandler()", "thistype", this, "EventType(" + I2S(eventType) + "): Attempted to register an already registered code")
                endif
                if handlerTrigger[index] == null then
                    set handlerTrigger[index] = CreateTrigger()
                endif
                set handlerCount[index] = handlerCount[index] + 1
                set handlerTable[index].triggercondition[GetHandleId(expr)] = TriggerAddCondition(handlerTrigger[index], expr)
                set expr = null
            endmethod
            method unregisterEventHandler takes SpellEventType eventType, code handler returns nothing
                local integer index = (this - 1)*5 + GetEventIndex(eventType)
                local integer count = handlerCount[index] - 1
                local integer handleId = GetHandleId(Filter(handler))
                static if LIBRARY_ErrorMessage then
                    debug call ThrowError((this) < 1 or (this) > spellCount, "SpellEvent", "unregisterEventHandler()", "thistype", this, "Attempted to use an invalid Spell instance")
                    debug call ThrowError(GetEventIndex(eventType) < 1 or GetEventIndex(eventType) > 5, "SpellEvent", "unregisterEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                    debug call ThrowError(not handlerTable[index].handle.has(handleId), "SpellEvent", "registerEventHandler()", "thistype", this, "EventType(" + I2S(eventType) + "): Attempted to unregister an unregistered code")
                endif
                call TriggerRemoveCondition(handlerTrigger[index], handlerTable[index].triggercondition[handleId])
                call handlerTable[index].triggercondition.remove(handleId)
                set handlerCount[index] = handlerCount[index] - 1
                if handlerCount[index] == 0 then
                    call DestroyTrigger(handlerTrigger[index])
                    set handlerTrigger[index] = null
                endif
            endmethod
            method clearEventHandlers takes SpellEventType eventType returns nothing
                local integer index = (this - 1)*5 + GetEventIndex(eventType)
                static if LIBRARY_ErrorMessage then
                    debug call ThrowError((this) < 1 or (this) > spellCount, "SpellEvent", "clearEventHandlers()", "thistype", this, "Attempted to use an invalid Spell instance")
                    debug call ThrowError(GetEventIndex(eventType) < 1 or GetEventIndex(eventType) > 5, "SpellEvent", "clearEventHandlers()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
                endif
                call handlerTable[index].flush()
                call DestroyTrigger(handlerTrigger[index])
                set handlerTrigger[index] = null
                set handlerCount[index] = 0
            endmethod
            method clearHandlers takes nothing returns nothing
                static if LIBRARY_ErrorMessage then
                    debug call ThrowError((this) < 1 or (this) > spellCount, "SpellEvent", "clearHandlers()", "thistype", this, "Attempted to use an invalid Spell instance")
                endif
                call this.clearEventHandlers(SpellEventType.CAST)
                call this.clearEventHandlers(SpellEventType.CHANNEL)
                call this.clearEventHandlers(SpellEventType.EFFECT)
                call this.clearEventHandlers(SpellEventType.ENDCAST)
                call this.clearEventHandlers(SpellEventType.FINISH)
            endmethod

            private static method executeEventHandler takes trigger eventHandlerTrigger, integer currentId returns nothing

                local integer prevId    = ABILITY_ID
                local player prevUser   = USER
                local unit prevCaster   = CASTER
                local unit prevTarget   = TARGET
                local real prevTargetX  = TARGET_X
                local real prevTargetY  = TARGET_Y
                local integer prevLevel = LEVEL
                local location tempLoc

                set ABILITY_ID          = currentId
                set USER                = GetTriggerPlayer()
                set CASTER              = GetTriggerUnit()
                set TARGET              = GetSpellTargetUnit()
                set LEVEL               = GetUnitAbilityLevel(CASTER, ABILITY_ID)

                if TARGET != null then
                    set TARGET_X        = GetUnitX(TARGET)
                    set TARGET_Y        = GetUnitY(TARGET)
                else
                    set tempLoc = GetSpellTargetLoc()
                    if tempLoc == null then
                    /* Special Case (for some no-target spells) */
                        set TARGET_X    = GetUnitX(CASTER)
                        set TARGET_Y    = GetUnitY(CASTER)
                    else
                        set TARGET_X    = GetSpellTargetX()
                        set TARGET_Y    = GetSpellTargetY()
                        call RemoveLocation(tempLoc)
                        set tempLoc = null
                    endif
                endif

                call TriggerEvaluate(eventHandlerTrigger)

                set ABILITY_ID          = prevId
                set USER                = prevUser
                set CASTER              = prevCaster
                set TARGET              = prevTarget
                set TARGET_X            = prevTargetX
                set TARGET_Y            = prevTargetY
                set LEVEL               = prevLevel

                set prevUser            = null
                set prevCaster          = null
                set prevTarget          = null

            endmethod

            private static method onSpellEvent takes integer eventIndex returns nothing
                local integer id = GetSpellAbilityId()
                local trigger tempTrigger = handlerTrigger[(table[-id] - 1)*5 + eventIndex]
                if tempTrigger != null then
                    call executeEventHandler(tempTrigger, id)
                    set tempTrigger = null
                endif
            endmethod

            private static method onSpellCast takes nothing returns nothing
                call onSpellEvent(1)
            endmethod
            private static method onSpellChannel takes nothing returns nothing
                call onSpellEvent(2)
            endmethod
            private static method onSpellEffect takes nothing returns nothing
                call onSpellEvent(3)
            endmethod
            private static method onSpellEndcast takes nothing returns nothing
                call onSpellEvent(4)
            endmethod
            private static method onSpellFinish takes nothing returns nothing
                call onSpellEvent(5)
            endmethod

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

            private static method init takes nothing returns nothing
                set handlerTable = TableArray[1000*5]
                set table = handlerTable[0]
                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)
            endmethod
            implement Init

        endstruct

        private module Init
            private static method onInit takes nothing returns nothing
                call init()
            endmethod
        endmodule

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

        constant function GetEventSpellId takes nothing returns integer
            return Spell.ABILITY_ID
        endfunction
        constant function GetEventSpellLevel takes nothing returns integer
            return Spell.LEVEL
        endfunction
        constant function GetEventSpellUser takes nothing returns player
            return Spell.USER
        endfunction
        constant function GetEventSpellCaster takes nothing returns unit
            return Spell.CASTER
        endfunction
        constant function GetEventSpellTargetUnit takes nothing returns unit
            return Spell.TARGET
        endfunction
        constant function GetEventSpellTargetX takes nothing returns real
            return Spell.TARGET_X
        endfunction
        constant function GetEventSpellTargetY takes nothing returns real
            return Spell.TARGET_Y
        endfunction

        function SpellRegisterEventHandler takes integer abilId, SpellEventType eventType, code handler returns nothing
            static if LIBRARY_ErrorMessage then
                debug local integer eventIndex = GetEventIndex(eventType)
                debug call ThrowError(eventIndex < 1 or eventIndex > 5, "SpellEvent", "SpellRegisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
                debug call ThrowError(handlerTable[(Spell[abilId] - 1)*5 + eventIndex].handle.has(GetHandleId(Filter(handler))), "SpellEvent", "SpellRegisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "), EventType(" + I2S(eventType) + "): Attempted to register an already registered code")
            endif
            call Spell[abilId].registerEventHandler(eventType, handler)
        endfunction
        function SpellUnregisterEventHandler takes integer abilId, SpellEventType eventType, code handler returns nothing
            static if LIBRARY_ErrorMessage then
                debug local integer eventIndex = GetEventIndex(eventType)
                debug call ThrowError(eventIndex < 1 or eventIndex > 5, "SpellEvent", "SpellUnregisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
                debug call ThrowError(not handlerTable[(Spell[abilId] - 1)*5 + eventIndex].handle.has(GetHandleId(Filter(handler))), "SpellEvent", "SpellUnregisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "), EventType(" + I2S(eventType) + "): Attempted to unregister an unregistered code")
            endif
            call Spell[abilId].unregisterEventHandler(eventType, handler)
        endfunction
        function SpellClearEventHandlers takes integer abilId, SpellEventType eventType returns nothing
            static if LIBRARY_ErrorMessage then
                debug local integer eventIndex = GetEventIndex(eventType)
                debug call ThrowError(eventIndex < 1 or eventIndex > 5, "SpellEvent", "SpellClearEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
            endif
            call Spell[abilId].clearEventHandlers(eventType)
        endfunction
        function SpellClearHandlers takes integer abilId returns nothing
            call Spell[abilId].clearHandlers()
        endfunction

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

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

        module SpellEvent

            readonly thistype prev
            readonly thistype next
            private thistype recycler

            private static method onPeriodic takes nothing returns nothing
                local thistype node = thistype(0).next
                loop
                    exitwhen node == 0
                    if not node.onSpellPeriodic() then
                        call node.onSpellEnd()
                        set node.next.prev = node.prev
                        set node.prev.next = node.next
                        set node.recycler = thistype(0).recycler
                        set thistype(0).recycler = node
                        if thistype(0).next == 0 then
                            call DestroyTimerEx(GetExpiredTimer())
                        endif
                    endif
                    set node = node.next
                endloop
            endmethod

            private static method onSpellEvent takes nothing returns nothing
                local thistype last = thistype(0).prev
                local thistype node = thistype(0).recycler
                set thistype(0).recycler = node.recycler
                set thistype(0).prev = node
                set last.next = node
                set node.prev = last
                set node.next = 0
                call node.onSpellStart()
                if last == 0 then
                    call TimerStart(CreateTimer(), SPELL_PERIOD, true, function thistype.onPeriodic)
                endif
            endmethod

            private static method onInit takes nothing returns nothing
                local thistype node = 0
                loop
                    exitwhen node == 8190
                    set node.recycler = node + 1
                    set node = node + 1
                endloop
                set node.recycler = 0
                call Spell[SPELL_ID].registerEventHandler(SPELL_EVENT_TYPE, function thistype.onSpellEvent)
                static if LIBRARY_ResourcePreloader then
                    call PreloadAbility(SPELL_ID)
                endif
            endmethod

        endmodule

        module SpellEventEx

            private static method onPeriodic takes nothing returns nothing
                local timer expired = GetExpiredTimer()
                local integer handleId = GetHandleId(expired)
                local thistype node = table[handleId]
                if not node.onSpellPeriodic() then
                    call node.onSpellEnd()
                    call table.remove(handleId)
                    call DestroyTimerEx(expired)
                endif
                set expired = null
            endmethod

            private static method onSpellEvent takes nothing returns nothing
                local timer periodicTimer
                local thistype node = onSpellStart()
                if node > 0 then
                    set periodicTimer = CreateTimer()
                    set table[GetHandleId(periodicTimer)] = node
                    call TimerStart(periodicTimer, SPELL_PERIOD, true, function thistype.onPeriodic)
                    set periodicTimer = null
                endif
            endmethod

            private static method onInit takes nothing returns nothing
                call Spell[SPELL_ID].registerEventHandler(SPELL_EVENT_TYPE, function thistype.onSpellEvent)
                static if LIBRARY_ResourcePreloader then
                    call PreloadAbility(SPELL_ID)
                endif
            endmethod

        endmodule


    endlibrary
     
     
    Last edited: May 2, 2018
  2. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    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
     
  3. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    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:
    Code (vJASS):
    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