• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] SpellCloner

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
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
JASS:
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



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.
JASS:
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)
JASS:
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



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:
Top