1. Head to the 33rd Modeling Contest Poll and drink to your heart's desire.
    Dismiss Notice
  2. Choose your means of doom in the 17th Mini Mapping Contest Poll.
    Dismiss Notice
  3. A slave to two rhythms, the 22nd Terraining Contest is here.
    Dismiss Notice
  4. The heavens smile on the old faithful. The 16th Techtree Contest has begun.
    Dismiss Notice
  5. The die is cast - the 6th Melee Mapping Contest results have been announced. Onward to the Hive Cup!
    Dismiss Notice
  6. The glory of the 20th Icon Contest is yours for the taking!
    Dismiss Notice
  7. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] SpellCloner

Discussion in 'Submissions' started by AGD, Apr 19, 2020.

  1. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    The motivation behind this library is to simplify spell cloning.
    But what is spell cloning, you ask? Well the concept is explained in the rationale portion of this tutorial:
    Efficient Spell Cloning

    The method discussed in that tutorial however is outdated and it has its certain limitations. This library
    utilizes a better method and it also provides sufficient abstractions so that the spell developer can easily
    carry on his work without having to understand how the whole method works. Just a couple of module
    and function usage and following a certain template then he can proceed to the usual spellmaking process.


    SpellCloner Library
    Code (vJASS):
    library SpellCloner /* v2.2.0 https://www.hiveworkshop.com/threads/324157/


        */
    uses /*

        */
    SpellEvent            /*  https://www.hiveworkshop.com/threads/301895/
        */
    Table                 /*  https://www.hiveworkshop.com/threads/188084/


        */
    //! novjass

        /*
            CREDITS:
                - AGD (Author)
                - JAKEZINC (Feedbacks and suggestions, which helped bring the system into its current form)
        */

        |=====|
        | API |
        |=====|

            readonly static SpellCloner.configuredInstance/*
                    - Use this variable inside the configuration function to refer to the spell
                      instance being configured


          */
    module SpellClonerHeader/*
                - Implement this module at the top of your spell struct

              */
    static method hasActivationAbility takes integer abilId returns boolean/*
              */
    static method hasConfiguration takes integer configStructId returns boolean/*
                    - Only call this from inside a spell event handler (generic or specific)
                    - Could be useful for especially in generic handlers to see if the casted
                      ability activates a certain type of spell, as cloned spell can have many
                      activation abilities

              */
    method initSpellConfiguration takes integer abilId returns integer/*
                    - Call this method at the top of you onSpellStart() method to initialize the
                      correct local configuration of your spell instance based on the activation
                      ability id
                    - Returns the struct type id of the struct containing the configuration

              */
    method loadSpellConfiguration takes integer configStructId returns nothing/*
                    - Call this method with the value returned by initSpellConfiguration() as the
                      parameter
                    - Like initSpellConfiguration(), loads the correct local configuration of the
                      spell, but based on the typeid of the configuration struct


          */
    module SpellClonerFooter/*
                - Implement this module at the bottom of your spell struct, below your SpellEvent implementation

              */
    static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype/*
                    - Creates a new local configuration instance for the spell (Return value is obsolete)


          */
    module SpellCloner/*
                - A supplement to using both SpellClonerHeader and SpellClonerFotter. Implement this
                  module at the bottom of your spell struct, no need to implement the SpellEvent module

              */
    interface method    onClonedSpellStart      takes nothing   returns thistype/*
              */
    interface method    onClonedSpellPeriodic   takes nothing   returns boolean/*
              */
    interface method    onClonedSpellEnd        takes nothing   returns nothing/*
                    - Supplement to the onSpellStart(), onSpellPeriodic(), and onSpellEnd() methods from
                      SpellEvent module
                    - All these interface methods follow the same rules as their SpellEvent interface methods
                      counterpart
                    - You no longer need to call this.initSpellConfiguration(ABIL_ID) on onClonedSpellStart()
                      to run your configuration function, this is already done internally by the system.

              */
    static method hasActivationAbility takes integer abilId returns boolean/*
              */
    static method hasConfiguration takes integer configStructId returns boolean/*
                    - Already defined above (see SpellClonerHeader module)

              */
    static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype/*
                    - Creates a new local configuration instance for the spell (Return value is obsolete)


        */
    //! endnovjass

        globals
            private trigger evaluator = CreateTrigger()
            private integer array eventIndex
            private integer configuredSpellInstance = 0
            private TableArray table
            private TableArray configStructNode
        endglobals

        private module Init
            private static method onInit takes nothing returns nothing
                set table = TableArray[JASS_MAX_ARRAY_SIZE]
                set configStructNode = TableArray[JASS_MAX_ARRAY_SIZE]
                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
            endmethod
        endmodule

        struct SpellCloner extends array
            static method operator configuredInstance takes nothing returns thistype
                return configuredSpellInstance
            endmethod
            implement Init
        endstruct

        private struct SpellConfigList extends array
            thistype current
            readonly thistype prev
            readonly thistype next
            readonly integer structId
            readonly boolexpr configExpr

            private static thistype node = 0

            method evaluateExpr takes integer spellInstance returns nothing
                local integer prevInstance = configuredSpellInstance
                set configuredSpellInstance = spellInstance
                call TriggerAddCondition(evaluator, this.configExpr)
                call TriggerEvaluate(evaluator)
                call TriggerClearConditions(evaluator)
                set configuredSpellInstance = prevInstance
            endmethod

            method insert takes integer id, boolexpr expr returns thistype
                local thistype next = this.next
                set node = node + 1
                set node.structId = id
                set node.configExpr = expr
                set node.prev = this
                set node.next = next
                set next.prev = node
                set this.next = node
                return node
            endmethod

            static method create takes nothing returns thistype
                set node = node + 1
                set node.prev = node
                set node.next = node
                set node.current = node
                return node
            endmethod
        endstruct

        public function HasActivationAbility takes integer spellStructId, integer abilId returns boolean
            if GetEventSpellEventType() == 0 then
                return table[spellStructId*5 + 1].has(-abilId)
            endif
            return table[spellStructId*5 + eventIndex[GetEventSpellEventType()]].has(abilId)
        endfunction
        public function HasConfiguration takes integer spellStructId, integer configStructId returns boolean
            if GetEventSpellEventType() == 0 then
                return configStructNode[spellStructId*5 + 1].has(-configStructId)
            endif
            return configStructNode[spellStructId*5 + eventIndex[GetEventSpellEventType()]].has(configStructId)
        endfunction

        public function InitSpellConfiguration takes integer spellStructId, integer spellInstance, integer abilId returns integer
            local integer configStructId
            local SpellConfigList configList
            if GetEventSpellEventType() == 0 then
                set configList = table[spellStructId*5 + 1][-abilId]
            else
                set configList = table[spellStructId*5 + eventIndex[GetEventSpellEventType()]][abilId]
            endif
            set configList.current = configList.current.next
            set configStructId = configList.current.structId
            call configList.current.evaluateExpr(spellInstance)
            if configList.current.next == configList then
                set configList.current = configList
            endif
            return configStructId
        endfunction

        public function LoadSpellConfiguration takes integer spellStructId, integer spellInstance, integer configStructId returns nothing
            if GetEventSpellEventType() == 0 then
                call SpellConfigList(configStructNode[spellStructId*5 + 1][-configStructId]).evaluateExpr(spellInstance)
            else
                call SpellConfigList(configStructNode[spellStructId*5 + eventIndex[GetEventSpellEventType()]][configStructId]).evaluateExpr(spellInstance)
            endif
        endfunction

        public function CloneSpell takes integer spellStructId, integer configStructId, integer abilId, integer eventType, code configFunc returns nothing
            local SpellConfigList configList
            local integer eventId = 0x10
            local integer key
            if eventType == 0 then
                set key = spellStructId*5 + 1
                if configStructNode[key][-configStructId] == 0 then
                    set configList = table[key][-abilId]
                    if configList == 0 then
                        set configList = SpellConfigList.create()
                        set table[key][-abilId] = configList
                    endif
                    set configStructNode[key][-configStructId] = configList.prev.insert(configStructId, Filter(configFunc))
                endif
            else
                loop
                    exitwhen eventId == 0
                    if eventType >= eventId then
                        set eventType = eventType - eventId
                        set key = spellStructId*5 + eventIndex[eventId]
                        if configStructNode[key][configStructId] == 0 then
                            set configList = table[key][abilId]
                            if configList == 0 then
                                set configList = SpellConfigList.create()
                                set table[key][abilId] = configList
                            endif
                            set configStructNode[key][configStructId] = configList.prev.insert(configStructId, Filter(configFunc))
                        endif
                    endif
                    set eventId = eventId/2
                endloop
            endif
        endfunction

        private module SpellClonerCommonHeader
            static constant integer SPELL_ABILITY_ID = 0
            static constant integer SPELL_EVENT_TYPE = 0

            static method hasActivationAbility takes integer abilId returns boolean
                return HasActivationAbility(thistype.typeid, abilId)
            endmethod
            static method hasConfiguration takes integer configStructId returns boolean
                return HasConfiguration(thistype.typeid, configStructId)
            endmethod
        endmodule

        module SpellClonerHeader
            implement SpellClonerCommonHeader
            method initSpellConfiguration takes integer abilId returns integer
                return InitSpellConfiguration(thistype.typeid, this, abilId)
            endmethod
            method loadSpellConfiguration takes integer configStructId returns nothing
                call LoadSpellConfiguration(thistype.typeid, this, configStructId)
            endmethod
        endmodule

        module SpellClonerFooter
            static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype
                call CloneSpell(thistype.typeid, configStructId, abilId, spellEventType, configurationFunc)
                call registerSpellEvent(abilId, spellEventType)
                return 0
            endmethod
        endmodule

        module SpellCloner
            implement SpellClonerCommonHeader
            method onSpellStart takes nothing returns thistype
                local integer configStructId = InitSpellConfiguration(thistype.typeid, this, GetEventSpellAbilityId())
                local thistype node = this.onClonedSpellStart()
                if node > 0 then
                    if node != this then
                        call SpellConfigList(configStructNode[thistype.typeid][configStructId]).evaluateExpr(node)
                    endif
                    return node
                endif
                return 0
            endmethod
            method onSpellPeriodic takes nothing returns boolean
                return this.onClonedSpellPeriodic()
            endmethod
            method onSpellEnd takes nothing returns nothing
                call this.onClonedSpellEnd()
            endmethod
            implement SpellEvent
            implement SpellClonerFooter
        endmodule


    endlibrary



    Spell Making + Spell Cloning Template

    Core Spell Module
    (Should contain all spell mechanics)
    Note that this is also just an example, you can change/remove the unnecessary variables that this template might contain.
    Code (vJASS):
    library <SpellName> /*


        */
    uses /*

        */
    SpellEvent    /*
        */
    SpellCloner   /*

        */


        /*****************************************************************
        *                   GLOBAL SPELL CONFIGURATION                   *
        *****************************************************************/

        private module GlobalSpellConfiguration

            static constant real SPELL_PERIOD       = 1.00/32.00

            static constant real SFX_DEATH_TIME     = 1.50
            ...

        endmodule

        private function TargetFilter takes unit target, unit caster returns boolean
            return UnitAlive(target)
        endfunction
        ...
        /*****************************************************************
        *               END OF GLOBAL SPELL CONFIGURATION                *
        *****************************************************************/


        /*========================= SPELL CODE =========================*/
        native UnitAlive takes unit u returns boolean

        public struct <SpellName> extends array

            implement GlobalSpellConfiguration
            implement SpellClonerHeader

            string staticSfxModel
            string staticSfxAttachPoint
            real spellDuration
            ...

            private method onSpellStart takes nothing returns thistype
                call this.initSpellConfiguration(GetEventSpellAbilityId())

                if <InvalidCastCondition> then
                    return 0
                endif

                ...

                return this
            endmethod

            private method onSpellPeriodic takes nothing returns boolean

                ...

                set this.spellDuration = this.spellDuration - SPELL_PERIOD
                return this.spellDuration > 0.00
            endmethod

            private method onSpellEnd takes nothing returns nothing

                ...

            endmethod

            implement SpellEvent
            implement SpellClonerFooter

        endstruct

        /*
        *   This is the module that you will implement into the struct that you wish to
        *   contain the local spell configurations
        */

        module <SpellName>Configuration
            private static method configHandler takes nothing returns nothing
                local <SpellName> node = SpellCloner.configuredInstance
                set node.staticSfxModel               = STATIC_SFX_MODEL
                set node.staticSfxAttachPoint         = STATIC_SFX_ATTACHPOINT
                ...
                set node.spellDuration                = spellDuration(currentSpellInstance.level)
                ...
            endmethod

            private static method onInit takes nothing returns nothing
                call <SpellName>.create(thistype.typeid, SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.configHandler)
            endmethod
        endmodule


    endlibrary


    Configuration Module
    (Local configurations: you can divide these into multiple libraries if preferable)
    Code (vJASS):
    library <SpellName>ConfigurationModule uses <SpellName> SpellEvent


        public struct Configuration1 extends array

            static constant integer SPELL_ABILITY_ID        = 'A000'
            static constant integer SPELL_EVENT_TYPE        = EVENT_SPELL_EFFECT
            static constant string STATIC_SFX_MODEL         = ""
            static constant string STATIC_SFX_ATTACHPOINT   = "origin"
            ...

            static method spellDuration takes integer level returns real
                return 10.00 + 0.00*level
            endmethod
            ...

            implement <SpellName>Configuration
        endstruct

        public struct Configuration2 extends array
            /*
            *   Activation ability ids can even be similar, in which case a single cast would
            *   activate two (or more) 'spells'
            */

            static constant integer SPELL_ABILITY_ID        = 'A001'
            static constant integer SPELL_EVENT_TYPE        = EVENT_SPELL_EFFECT
            static constant string STATIC_SFX_MODEL         = ""
            static constant string STATIC_SFX_ATTACHPOINT   = "origin"
            ...

            static method spellDuration takes integer level returns real
                return 5.00 + 1.00*level
            endmethod
            ...

            implement <SpellName>Configuration
        endstruct

        ...


    endlibrary




    Changelogs
    v2.2.0
    - Added features for checking if a cloneable-spell struct has a certain configuration (check can be made by an ability ID or by a configuration struct type ID)

    v2.0.0
    - API changes
    - Added 'SpellCloner' module
    - Made Table a mandatory dependency

    v1.1.0
    - Improved the design

    v1.0.0
    - Initial release
     
    Last edited: Jun 21, 2020
  2. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,533
    Resources:
    9
    Models:
    1
    Icons:
    2
    Maps:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    9


    Odd thing I found about the library; in the class SpellConfigList, method remove appears to be unreachable. It doesn't get called on destroy.
     
  3. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    Right.. It was a leftover. Same thing with deallocate and destroy.

    EDIT: Changed
     
    Last edited: Apr 20, 2020
  4. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    Updated
    v1.1.0
    - Better design

    Now you only have to initialize the configurations once at onSpellStart().
     
  5. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    Updated to v2.0.0
    Note: Has API changes