• 🏆 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!

Spell Framework [vJASS + GUI] v2.1.0

Spell Framework
For fast & convenient custom spell development



Aside from easing spell creation, this framework also allows you to create spells mechanics and
spell modifications beyond what is previously doable (without messy workarounds). Examples of
these include multicast systems, single-target spell blocks, spell pool, random spell activation,
and much more!

The complete documentation can be found inside the map. Be sure to spare a time for reading.


How to Import
1. Copy the 'Spell Development Framework' trigger category into your map
2. Copy the 'Spell Development Template' trigger category into your map
so you can use the premade templates (OPTIONAL)

... and that's it!


Supported Patch
Any



Preview
full

The preview above shows two illusions seemingly casting an ability



.

Description

API & Documentation

Spell Templates

Changelogs

JASS:
library SpellFramework /* https://www.hiveworkshop.com/threads/325448/


    */uses /*


    [FRAMEWORK CORE]
    The core of the spell development framework contains three components namely, SpellEvent, SpellEventGUI,
    and SpellCloner. SpellEvent and SpellCloner go hand in hand in creating an organized template for spell
    development in vJass.
    Below are VERY brief overview of the purpose of each component. A more intensive description of each one can
    be found on the header of each listed library.

    */SpellEvent                /*  https://www.hiveworkshop.com/threads/301895/
        SpellEvent handles and automates common spell-related tasks such as spell indexing and event registration.
        It further provides useful advanced features such as manual event invocation, event parameters overriding,
        and event cancelling. It also provides event response variables/functions that are intuitive to use and
        recursion-safe.

    */SpellCloner               /*  https://www.hiveworkshop.com/threads/324157/
        SpellCloner provides a framework for ensuring the maintainability of custom spells. It divides the
        custom spell into two sections namely, the spell mechanics, and the spell configuration. This provides
        convenience in updating the code for the spell mechanics without touching the configuration section.
        The spell configuration section can also contain more than one set of spell configurations - which is
        cool.

    */optional SpellEventGUI    /*
        The SpellEventGUI in itself is a framework specifically made for GUI developers. It is built upon the
        SpellEvent library, from which it derives many of its functionalities. It also provides utilities for
        other spell-related tasks that are not so convenient in GUI such as filtered unit-group enumeration.
        It also automatically handles channeling abilities interruption/finish for the spell developer unlike
        in its vJass counterpart where freedom in usage is given more priority over total automation.


    [UTILITY COMPONENTS]
    These components are included as they are almost always used in spell making.

    */Alloc                     /*  https://www.hiveworkshop.com/threads/324937/
        Allocator module - compatible for recent patches with the updated JASS_MAX_ARRAY_SIZE
        You can also use any other Alloc if you prefer but be sure to update it to adapt to JASS_MAX_ARRAY_SIZE

    */LinkedList                /*  https://www.hiveworkshop.com/threads/325635/
        Library providing linked-list modules with different variants


    [List of External Library Requirements]
        Required:
            > Table
            https://www.hiveworkshop.com/threads/188084/
        Optional:
            > RegisterPlayerUnitEvent
            https://www.hiveworkshop.com/threads/250266/
            > ResourcePreloader
            https://www.hiveworkshop.com/threads/287358/
            > ErrorMessage
            https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j

*/
endlibrary

/*
*   An Alias
*/
library SpellDevFramework uses SpellFramework
endlibrary
JASS:
/*
    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.

*/
|=========|
| 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 your 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


*/
JASS:
|=====|
| 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)


*/
  • SpellEventGUI Documentation
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- /*----------------------------------------------------------------------------------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- Spell Event GUI Documentation --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- All system functionalities are stated in this trigger --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • -------- Features: --------
      • -------- - Easy to use event response variables --------
      • -------- - Spell-related events --------
      • -------- - Manual spell event triggering --------
      • -------- - Spell event response values overriding --------
      • -------- - Structured spell template --------
      • -------- */ --------
      • -------- --------
      • -------- System Configuration --------
      • Set SPELL__FRAME_RATE = 32.00
      • Set SPELL__DUMMY_PLAYER = Neutral Passive
      • -------- --------
      • Custom script: /*
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • -------- Events: --------
      • -------- (Runs only for all spells registered to the system) --------
      • -------- --------
      • -------- To catch a spell cast begin event, use --------
      • -------- - Game - SPELL__Event becomes Equal to 1.00 --------
      • -------- --------
      • -------- To catch a spell channel begin event, use --------
      • -------- - Game - SPELL__Event becomes Equal to 2.00 --------
      • -------- --------
      • -------- To catch a spell effect start event, use --------
      • -------- - Game - SPELL__Event becomes Equal to 3.00 --------
      • -------- --------
      • -------- To catch a spell cast stop event, use --------
      • -------- - Game - SPELL__Event becomes Equal to 4.00 --------
      • -------- --------
      • -------- To catch a spell cast finish event, use --------
      • -------- - Game - SPELL__Event becomes Equal to 5.00 --------
      • -------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • -------- Event Responses: --------
      • -------- These variables can be used in response to generic spell events as well as --------
      • -------- in your spell triggers (OnSpellStart, OnSpellPeriodic, and OnSpellEnd) --------
      • -------- - SPELL__Ability -> Ability being cast --------
      • -------- - SPELL__EventType -> Spell event triggering this trigger --------
      • -------- - SPELL__OrderType -> Order type of the ability being cast --------
      • -------- - SPELL__Level -> Level of the ability being cast (as integer) --------
      • -------- - SPELL__RealLevel -> Level of the ability being cast (as real) --------
      • -------- - SPELL__TriggerPlayer -> Triggering player --------
      • -------- - SPELL__TriggerUnit -> Triggering unit (caster) --------
      • -------- - SPELL__TargetPoint -> Target point of the ability being cast --------
      • -------- - SPELL__TargetUnit --------
      • -------- - SPELL__TargetItem --------
      • -------- - SPELL__TargetDest --------
      • -------- - SPELL__Node -> Unique integer to be used as index for your spell variables. Always 0 inside generic spell events --------
      • -------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • -------- Constants: --------
      • -------- --------
      • -------- Event type constants - use in conjunction with SPELL__EventType --------
      • -------- - SPELL__EVENT_CAST --------
      • -------- - SPELL__EVENT_CHANNEL --------
      • -------- - SPELL__EVENT_EFFECT --------
      • -------- - SPELL__EVENT_ENDCAST --------
      • -------- - SPELL__EVENT_FINISH --------
      • -------- --------
      • -------- Order type constants - use in conjunction with SPELL__OrderType --------
      • -------- - SPELL__ORDER_NO_TARGET --------
      • -------- - SPELL__ORDER_POINT_TARGET --------
      • -------- - SPELL__ORDER_SINGLE_TARGET --------
      • -------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • -------- Spell Registration: --------
      • -------- --------
      • -------- To register a spell, setup the neccesary data --------
      • -------- - SPELL__Ability -> Set to the activation ability --------
      • -------- - SPELL__EventType -> Set to the type of spell event --------
      • -------- - SPELL__OnSpellStart -> Set to the trigger that will run upon the spell event --------
      • -------- - SPELL__OnSpellPeriod -> Set to the spell's periodic trigger --------
      • -------- - SPELL__OnSpellEnd -> Set to the trigger that will run when the periodic trigger ends --------
      • -------- --------
      • -------- Then do --------
      • Trigger - Run SPELL__RegisterHandler (ignoring conditions)
      • -------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • -------- Manual Event Firing: --------
      • -------- --------
      • -------- To manually fire a no-target spell event, setup the neccessary data --------
      • -------- - SPELL__Ability -> Set to the triggering ability --------
      • -------- - SPELL__EventType -> Set to the event type to be fired --------
      • -------- - SPELL__Level --------
      • -------- - SPELL__TriggerUnit --------
      • -------- --------
      • -------- To manually fire a point-target spell event, setup the neccessary data --------
      • -------- - SPELL__Ability -> Set to the triggering ability --------
      • -------- - SPELL__EventType -> Set to the event type to be fired --------
      • -------- - SPELL__Level --------
      • -------- - SPELL__TriggerUnit --------
      • -------- - SPELL__TargetPoint --------
      • -------- --------
      • -------- To manually fire a single-target spell event, setup the neccessary data --------
      • -------- - SPELL__Ability -> Set to the triggering ability --------
      • -------- - SPELL__EventType -> Set to the event type to be fired --------
      • -------- - SPELL__Level --------
      • -------- - SPELL__TriggerUnit --------
      • -------- - SPELL__TargetUnit or SPELL__TargetItem or SPELL__TargetDest --------
      • -------- --------
      • -------- Then do --------
      • Trigger - Run SPELL__InvokeEvent (ignoring conditions)
      • -------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • -------- Event Parameters Overriding: --------
      • -------- --------
      • -------- You can only override event parameters in response to a generic spell event --------
      • -------- mentioned in the Events section above. --------
      • -------- There are three options in overriding event parameters. You can override --------
      • -------- the parameters of a No-Target, Point-Target, or a Single-Target event. --------
      • -------- --------
      • -------- (No-Target) --------
      • -------- --------
      • Set SPELL__OrderType = SPELL__ORDER_NO_TARGET
      • Set SPELL__Level = (SPELL__Level + 1)
      • Set SPELL__TriggerUnit = SPELL__TriggerUnit
      • Trigger - Run SPELL__OverrideParams (ignoring conditions)
      • -------- --------
      • -------- (Point-Target) --------
      • -------- --------
      • Set TempLoc = SPELL__TargetPoint
      • -------- --------
      • Set SPELL__OrderType = SPELL__ORDER_POINT_TARGET
      • Set SPELL__Level = (SPELL__Level + 1)
      • Set SPELL__TriggerUnit = SPELL__TriggerUnit
      • Set SPELL__TargetPoint = (SPELL__TargetPoint offset by 200.00 towards (Facing of SPELL__TriggerUnit) degrees)
      • Trigger - Run SPELL__OverrideParams (ignoring conditions)
      • -------- --------
      • -------- call RemoveLocation(udg_SPELL__TargetPoint) --------
      • Set SPELL__TargetPoint = TempLoc
      • -------- --------
      • -------- (Single-Target) --------
      • -------- --------
      • Set TempUnit = SPELL__TriggerUnit
      • -------- --------
      • Set SPELL__OrderType = SPELL__ORDER_NO_TARGET
      • Set SPELL__Level = (SPELL__Level + 1)
      • Set SPELL__TriggerUnit = SPELL__TargetUnit
      • Set SPELL__TargetUnit = TempUnit
      • -------- You could also use SPELL__TargetItem or SPELL__TargetDest ^ --------
      • Trigger - Run SPELL__OverrideParams (ignoring conditions)
      • -------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- End of Spell Event GUI Documentation --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- Below Are Just Variable Creators (Do Not Touch) --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- -------------------------------------------------------------------------------------- --------
      • -------- --------
      • Set SPELL__EVENT_CAST = 0
      • Set SPELL__EVENT_CHANNEL = 0
      • Set SPELL__EVENT_EFFECT = 0
      • Set SPELL__EVENT_ENDCAST = 0
      • Set SPELL__EVENT_FINISH = 0
      • Set SPELL__ORDER_NO_TARGET = 0
      • Set SPELL__ORDER_POINT_TARGET = 0
      • Set SPELL__ORDER_SINGLE_TARGET = 0
      • -------- --------
      • Set SPELL__Ability = SPELL__Ability
      • Set SPELL__EnumCount = 0
      • Set SPELL__EnumRange = 0.00
      • Set SPELL__EnumedTargets[0] = No unit
      • Set SPELL__EnumerateTargetsInRange = SPELL__EnumerateTargetsInRange
      • Set SPELL__Event = 0.00
      • Set SPELL__EventType = 0
      • Set SPELL__ExitPeriodic = False
      • Set SPELL__InitializationEvent = 0.00
      • Set SPELL__InvokeEvent = SPELL__InvokeEvent
      • Set SPELL__Level = 0
      • Set SPELL__Index = 0
      • Set SPELL__OnEndTrigger = SPELL__OnEndTrigger
      • Set SPELL__OnPeriodTrigger = SPELL__OnPeriodTrigger
      • Set SPELL__OnStartTrigger = SPELL__OnStartTrigger
      • Set SPELL__OverrideParams = SPELL__OverrideParams
      • Set SPELL__OrderType = 0
      • Set SPELL__PERIOD = 0.00
      • Set SPELL__RealLevel = 0.00
      • Set SPELL__RegisterHandler = SPELL__RegisterHandler
      • Set SPELL__TargetDest = No destructible
      • Set SPELL__TargetItem = No item
      • Set SPELL__TargetUnit = No unit
      • Set SPELL__TargetPoint = (Center of (Playable map area))
      • Set SPELL__TriggerPlayer = Player 1 (Red)
      • Set SPELL__TriggerUnit = No unit
      • Set SPELL__AllyFilterFlag = False
      • Set SPELL__DeadFilterFlag = False
      • Set SPELL__EnemyFilterFlag = False
      • Set SPELL__FlyingFilterFlag = False
      • Set SPELL__HeroFilterFlag = False
      • Set SPELL__LivingFilterFlag = False
      • Set SPELL__MagicImmuneFilterFlag = False
      • Set SPELL__MechanicalFilterFlag = False
      • Set SPELL__NonHeroFilterFlag = False
      • Set SPELL__StructureFilterFlag = False
      • Custom script: */
      • Custom script: call SpellEventGUI_Initialize()
JASS:
/*
    A allocator module using a single global indexed stack. Allocated values are
    within the JASS_MAX_ARRAY_SIZE. No more need to worry about code bloats behind
    the module implementation as it generates the least code possible (6 lines of
    code in non-DEBUG_MODE), nor does it use an initialization function. This system
    also only uses ONE variable (for the whole map) for the hashtable.
*/
|-----|
| API |
|-----|
/*
  */module GlobalAlloc/*
        - Uses a single stack globally
  */module Alloc/*
        - Uses a unique stack per struct

      */debug readonly boolean allocated/* Is node allocated?

      */static method allocate takes nothing returns thistype/*
      */method deallocate takes nothing returns nothing/*

*/
API List
JASS:
/*
    Pros:
        - Feature-rich (Can be)
        - Modular
        - Safety-oriented (On DEBUG_MODE, but not 100% fool-proof ofcourse)
        - Flexible (Does not enforce a built-in allocator - allows user to choose between a custom Alloc
          or the default vjass allocator, or neither)
        - Extensible (Provides interfaces)

    Note:
        If you are using using Dirac's 'LinkedListModule' library, you need to replace its contents with
        the compatibility lib provided alongside this library for all to work seamlessly.

*/
|-----|
| API |
|-----|
/*
Note: All the fields except from 'prev' and 'next' are actually operators, so you might want to
      avoid using them from the interface methods that would be declared above them.
=====================================================================================================
List Fields Modules (For those who want to write or inline the core linked-list operations themselves)

  */module LinkedListFields/*

      */readonly thistype prev/*
      */readonly thistype next/*


  */module StaticListFields extends LinkedListFields/*

      */readonly static constant thistype sentinel/*
      */readonly static thistype front/*
      */readonly static thistype back/*
      */readonly static boolean empty/*


  */module ListFields extends LinkedListFields/*

      */readonly thistype front/*
      */readonly thistype back/*
      */readonly boolean empty/*

=====================================================================================================
Lite List Modules (Should be enough for most cases)

  */module LinkedListLite extends LinkedListFields/*

      */optional interface static method onInsert takes thistype node returns nothing/*
      */optional interface static method onRemove takes thistype node returns nothing/*
      */optional interface method onTraverse takes thistype node returns boolean/*

      */static method insert takes thistype prev, thistype node returns nothing/*
      */static method remove takes thistype node returns nothing/*

      */method traverseForwards takes nothing returns nothing/*
      */method traverseBackwards takes nothing returns nothing/*
            - Only present if onTraverse() is also present


  */module StaticListLite extends StaticListFields, LinkedListLite/*

      */static method pushFront takes thistype node returns nothing/*
      */static method popFront takes nothing returns thistype/*

      */static method pushBack takes thistype node returns nothing/*
      */static method popBack takes nothing returns thistype/*


  */module ListLite extends ListFields, LinkedListLite/*

      */method pushFront takes thistype node returns nothing/*
      */method popFront takes nothing returns thistype/*

      */method pushBack takes thistype node returns nothing/*
      */method popBack takes nothing returns thistype/*


  */module InstantiatedListLite extends ListLite/*

      */interface static method allocate takes nothing returns thistype/*
      */interface method deallocate takes nothing returns nothing/*
      */optional interface method onConstruct takes nothing returns nothing/*
      */optional interface method onDestruct takes nothing returns nothing/*

      */static method create takes nothing returns thistype/*
      */method destroy takes nothing returns nothing/*

=====================================================================================================
Standard List Modules

  */module LinkedList extends LinkedListLite/*

      */static method isLinked takes thistype node returns boolean/*


  */module StaticList extends StaticListLite, LinkedList/*

      */static method clear takes nothing returns nothing/*
      */static method flush takes nothing returns nothing/*


  */module List extends ListLite, LinkedList/*

      */static method makeHead takes thistype node returns nothing/*
      */method clear takes nothing returns nothing/*
      */method flush takes nothing returns nothing/*


  */module InstantiatedList extends InstantiatedListLite, List/*

=====================================================================================================
Feature-rich List Modules (For those who somehow need exotic linked-list operations)

  */module LinkedListEx extends LinkedList/*

      */static method move takes thistype prev, thistype node returns nothing/*
      */static method swap takes thistype nodeA, thistype nodeB returns nothing/*


  */module StaticListEx extends StaticList, LinkedListEx/*

      */static method contains takes thistype node returns boolean/*
      */static method getSize takes nothing returns integer/*
      */static method rotateLeft takes nothing returns nothing/*
      */static method rotateRight takes nothing returns nothing/*


  */module ListEx extends List, LinkedListEx/*

      */method contains takes thistype node returns boolean/*
      */method getSize takes nothing returns integer/*
      */method rotateLeft takes nothing returns nothing/*
      */method rotateRight takes nothing returns nothing/*


  */module InstantiatedListEx extends InstantiatedList, ListEx/*


*/
API Description
JASS:
|-------------------|
| API Documentation |
|-------------------|
/*
    v1.3.0
    LinkedList library 

        If you are looking for a comprehensive listing of API available for each module, see the header of the
        library itself. What you can find here is the description of ALL avaiable API.

        The library supports 4 different implementations of linked-list:
            - Free lists, where you have freedom in linking and dealing with nodes
              without being restricted by the concept of a 'head node' or a 'container node'
            - Static Lists, which turns a struct into a single list, with the 'head node'
              representing the list itself
            - Non-static Lists, which allows you to have multiple lists within a struct
            - Instantiated Lists, similar to non-static lists but comes with its own methods
              for creating and destroying a list (requires allocate() and deallocate() in the
              implementing struct)

        Note: In all these kind of lists, all nodes should be unique together with the head nodes.
              Lists can also be circular or linear.


    |-----------------|
    | STATIC LIST API |
    |-----------------|

        Interfaces:

          */optional interface static method onInsert takes thistype node returns nothing/*
          */optional interface static method onRemove takes thistype node returns nothing/*
                - onInsert() is called after a node is inserted everytime insert(), pushFront(),
                  or pushBack() is called
                - onRemove() is called before a node is removed everytime remove(), popFront(),
                  or popBack() is called
                - <node> is the node being inserted/removed

          */optional interface method onTraverse takes thistype node returns boolean/*
                - Runs in response to traverseForwards()/traverseBackwards() calls
                - <this> is the head node
                - <node> is the currently traversed node
                - Returning <true> removes <node> from the list <this>


        Fields:

          */readonly thistype prev/*
          */readonly thistype next/*
          */readonly static thistype front/*
          */readonly static thistype back/*
          */static constant thistype head/*
          */readonly static boolean empty/*
                - <front>, <back>, <head>, and <empty> are method operators


        Methods:

          */static method isLinked takes thistype node returns boolean/*
                - Checks if a node is currently belongs to a list

          */static method move takes thistype prev, thistype node returns nothing/*
                - Moves <node> next to <prev>
                - Only works if <node> is already linked
          */static method swap takes thistype nodeA, thistype nodeB returns nothing/*
                - Swaps the placement of two nodes

          */static method contains takes thistype node returns boolean/*
                - Checks if <head> contains the given node
          */static method getSize takes nothing returns integer/*
                - Gets the size of the list <head>
                - Time complexity: O(n)

          */static method traverseForwards takes nothing returns nothing/*
          */static method traverseBackwards takes nothing returns nothing/*
                - Traverses a list forwards/backwards and calls onTraverse() for
                  each node in the list

          */static method rotateLeft takes nothing returns nothing/*
          */static method rotateRight takes nothing returns nothing/*

          */static method insert takes thistype prev, thistype node returns nothing/*
          */static method remove takes thistype node returns nothing/*

          */static method pushFront takes thistype node returns nothing/*
                - Inlines to insert() if not on DEBUG_MODE
          */static method popFront takes nothing returns thistype/*

          */static method pushBack takes thistype node returns nothing/*
                - Inlines to insert() if not on DEBUG_MODE
          */static method popBack takes nothing returns thistype/*

          */static method clear takes nothing returns nothing/*
                - Does not call remove() for any node, but only unlinks them from the list
          */static method flush takes nothing returns nothing/*
                - Calls remove() for each node on the list starting from the front to the back node


    |---------------------|
    | NON-STATIC LIST API |
    |---------------------|

          */optional interface static method onInsert takes thistype node returns nothing/*
          */optional interface static method onRemove takes thistype node returns nothing/*
                - onInsert() is called after a node is inserted everytime insert(), pushFront(),
                  or pushBack() is called
                - onRemove() is called before a node is removed everytime remove(), popFront(),
                  or popBack() is called
                - <node> is the node being inserted/removed

          */optional interface method onConstruct takes nothing returns nothing/*
          */optional interface method onDestruct takes nothing returns nothing/*
                - This methods will be called when calling create()/destroy()
                - <this> refers to the list to be created/destroyed

          */interface static method allocate takes nothing returns thistype/*
                - The value returned by this method will be the value returned by create()
          */interface method deallocate takes nothing returns nothing/*
                - This method will be called when calling destroy()

          */optional interface method onTraverse takes thistype node returns boolean/*
                - Runs in response to traverseForwards()/traverseBackwards() calls
                - <this> is the head node
                - <node> is the currently traversed node
                - Returning <true> removes <node> from the list <this>


        Fields:

          */readonly thistype prev/*
          */readonly thistype next/*
          */readonly thistype front/*
          */readonly thistype back/*
          */readonly boolean empty/*
                - <front>, <back>, and <empty> are method operators


        Methods:

          */static method isLinked takes thistype node returns boolean/*
                - Checks if a node is currently belongs to a list

          */static method move takes thistype prev, thistype node returns nothing/*
                - Moves <node> next to <prev>
                - Only works if <node> is already linked
          */static method swap takes thistype nodeA, thistype nodeB returns nothing/*
                - Swaps the placement of two nodes

          */method contains takes thistype node returns boolean/*
                - Checks if <head> contains the given node
          */method getSize takes nothing returns integer/*
                - Gets the size of the list <head>
                - Time complexity: O(n)

          */method traverseForwards takes nothing returns nothing/*
          */method traverseBackwards takes nothing returns nothing/*
                - traverses a list forwards/backwards and calls onTraverse() for
                  each node in the list

          */method rotateLeft takes nothing returns nothing/*
          */method rotateRight takes nothing returns nothing/*

          */static method makeHead takes thistype node returns nothing/*
                - Turns a node into a list

          */static method insert takes thistype prev, thistype node returns nothing/*
          */static method remove takes thistype node returns nothing/*

          */method pushFront takes thistype node returns nothing/*
                - Inlines to insert() if not on DEBUG_MODE
          */method popFront takes nothing returns thistype/*

          */method pushBack takes thistype node returns nothing/*
                - Inlines to insert() if not on DEBUG_MODE
          */method popBack takes nothing returns thistype/*

          */method clear takes nothing returns nothing/*
                - Does not call remove() for any node, but only unlinks them from the list
          */method flush takes nothing returns nothing/*
                - Calls remove() for each node on the list starting from the front to the back node

          */static method create takes nothing returns thistype/*
                - Creates a new list
          */method destroy takes nothing returns nothing/*
                - Destroys the list (Also calls flush internally for InstantiatedList and InstantiatedListEx)


*/
JASS:
/*
    This folder contains templates for spell-making using the various components of the SpellFramework Library.
    Multiple templates are given to accomodate the varying design categories of spells.

    |==================================================================================|
    | These are six (6) templates provided for making spells using the SpellEvent ONLY |
    |==================================================================================|

    Spell Template A:
        - In this template, we used the simplest form of spell as an example. The spell has no periodic operations
        and immediately performs its effects after cast.

    Spell Template B:
        - Uses the most common form of spell that we usually see in public spell resource submissions. The spell
        has a setup section (onSpellStart), periodic section (onSpellPeriodic), and a cleanup section (onSpellEnd).
        The spell has no child components and can easily be described using a single node that is created  after a
        spell is cast.

    Spell Template C:
        - Similar to Spell Template B but is more flexible. It automatically detects if the spell must be 'channeling'
        based on the value of <SPELL_EVENT_TYPE> in the configuration by checking if it is equal to <EVENT_SPELL_CHANNEL
        + EVENT_SPELL_ENDCAST>. Any other event combination will turn the spell into non-channeling.
        The template shows how the framework easily allows the developer to make the method of invoking the spell
        (in this case, whether the spell is channeled or not) configurable by the end users.

    Spell Template D:
        - The featured spell is another common format seen in public submissions. The spell has child components
        (could be one or more levels) that together, make up the overall characteristic of the spell. When the
        primary node (the spell instance) expires, all existing/remaining children nodes are destroyed alongside
        it.

    Spell Template E:
        - The featured spell is a bit similar to the one in Spell Template D. However, there are times when we want
        the children nodes to outlive the primary node. We can usually see this in spells that involve summoning
        a separate entity that in turn summons sub-entities over time. For example, a Turret summoning spell. The
        summoned Turret will periodically launch missiles towards enemy units within a certain radius. When the
        Turret expires, we don't want the remaining missiles still travelling to disappear also. Therefore, the
        missiles themselves need to be totally independent from their parent/primary node.

    Spell Template F:
        - The featured spell is not so common but still warrants a template :D. It usually has no significant
        operations concerning its primary node except that it creates multiple child components at the time of cast.
        Its components are usually the ones that provide the overall effect of the spell themselves. Due to this,
        we can easily disregard the primary node (not include it to the list for which the periodic operations run)
        and let its children do all the work.

    |================================================================================================|
    | These are three (3) templates provided for making spells using BOTH SpellEvent AND SpellCloner |
    |================================================================================================|

    Cloneable Spell Template A:
        - Here it feature a spell similar to that in Spell Template B, but using SpellCloner. The method of cloning
        here can also be applied to other spells featured in Spell Templates A, D, & E.

    Cloneable Spell Template B:
        - Featured spell is similar to that used in Spell Template F, but using SpellCloner.

    Cloneable Spell Template C:
        - Featured spell is similar to that used in Cloneable Spell Template A, but only uses the 'SpellCloner'
        module unlike the usual 'SpellClonerHeader' + 'SpellClonerFooter' + 'SpellEvent' combination. This method
        also provides better automation when it comes to running the spells configuration setup.

*/
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'XXXX'

        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT

        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 SPELL CONFIGURATION                   *
    *****************************************************************/

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

    public struct <SpellName> extends array

        implement SpellConfiguration

        effect spellSfx
        ...

        private static method onSpellStart takes nothing returns thistype
            local thistype node

            if <InvalidCastCondition> then
                return 0 // Does not run onSpellEnd()
            endif

            set node            = ...
            set node.spellSfx   = AddSpecialEffectTarget(...)

            ...

            return node
        endmethod

        private method onSpellEnd takes nothing returns nothing
            call DestroyEffect(this.spellSfx)

            ...

            set this.spellSfx = null
        endmethod

        implement SpellEventEx

    endstruct


endlibrary
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'XXXX'

        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT

        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 SPELL CONFIGURATION                   *
    *****************************************************************/

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

    public struct <SpellName> extends array

        implement SpellConfiguration

        unit dummy
        effect spellSfx
        ...

        private method onSpellStart takes nothing returns thistype
            if <InvalidCastCondition> then
                return 0
            endif

            set this.dummy          = CreateUnit(...)
            set this.spellSfx       = AddSpecialEffectTarget(..., this.dummy, ...)
            ...

            return this
        endmethod

        private method onSpellPeriodic takes nothing returns boolean

            ...

            return <EndPeriodicCondition> // Return true to end the periodic process and proceed to onSpellEnd()
        endmethod

        private method onSpellEnd takes nothing returns nothing
            call DestroyEffect(this.spellSfx)
            call UnitApplyTimedLife(this.dummy, 'BTLF', SFX_DEATH_TIME)

            ...

            set this.spellSfx = null
            set this.dummy = null
        endmethod

        implement SpellEvent

    endstruct


endlibrary
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*
    */UnitDex           /*

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'XXXX'

        /*
        *   Any other event combination will make the spell non-channeling
        */
        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_CHANNEL + EVENT_SPELL_ENDCAST

        static constant real SPELL_PERIOD           = 1.00/32.00

        static constant real SFX_DEATH_TIME         = 1.50
        ...

    endmodule

    /*
    *   Note: For seamless manipulation of channeling ability, I suggest using
    *   an ability based on CHANNEL then be sure to set its 'Follow through
    *   time' field into a really high value such as <99999> and set the
    *   'Disable other abilities' to false.
    *   This allows you to configure the channeling (follow through time) duration
    *   in the code instead of using the Object Editor.
    */
    private constant function FollowThroughTime takes integer level returns real
        return 10.00 + 0.00*level
    endfunction

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

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

    private constant function IsSpellChanneling takes nothing returns boolean
        return SPELL_EVENT_TYPE == EVENT_SPELL_CHANNEL + EVENT_SPELL_ENDCAST
    endfunction

    public struct <SpellName> extends array

        implement SpellConfiguration

        unit dummy
        effect spellSfx
        real followThroughTime
        integer unitId
        ...

        private static thistype array channelingInstance

        private method onSpellStart takes nothing returns thistype
            local integer unitId

            if <InvalidCastCondition> then
                return 0
            endif

            if IsSpellChanneling() then
                set unitId = GetUnitId(Spell.triggerUnit)
                /*
                *   We do this to limit the channeling instance to only one per unit
                */
                if channelingInstance[unitId] == 0 then
                    set channelingInstance[unitId] = this
                    set this.unitId = unitId
                else
                    set this = channelingInstance[unitId]
                endif

                if Spell.eventType == EVENT_SPELL_ENDCAST then
                    /*
                    *   This block executes when a channeling spell ends, whether because
                    *   it was finished or was interrupted
                    *
                    *   Returning a negative value will remove the positive equivalent node
                    *   and call onSpellEnd() for that node if it exists in the list
                    */
                    return -this
                endif
            endif

            set this.dummy              = CreateUnit(...)
            set this.spellSfx           = AddSpecialEffectTarget(..., this.dummy, ...)
            set this.followThroughTime  = FollowThroughTime(Spell.level)
            ...

            return this
        endmethod

        /*
        *   Notice that onSpellPeriodic() stops executing when the caster stops channeling,
        *   either when the channeling is finished or is interrupted
        */
        private method onSpellPeriodic takes nothing returns boolean

            ...

            if IsSpellChanneling() then
            /*
            *   Spell is channeling
            */
                set this.followThroughTime = this.followThroughTime - SPELL_PERIOD
                if not (this.followThroughTime > 0.00 and <ContinueChannelingCondition>) then
                /*
                *   If it is time to end the spell, instead of manually returning true, we
                *   order the caster to "stop" to trigger onSpellStart() which will in turn
                *   stop the periodic proccess after detecting that the event is EVENT_SPELL_ENDCAST
                */
                    call IssueImmediateOrderById(GetUnitById(this.unitId), STOP_ORDER_ID)
                endif

                return false
            endif
            /*
            *   Spell is non-channeling
            */
            return <EndPeriodicCondition> // Return true to end the periodic process and proceed to onSpellEnd()
        endmethod

        private method onSpellEnd takes nothing returns nothing
            local boolean finished = IsSpellChanneling() and this.followThroughTime <= 0.00

            if finished then
            /*
            *   The channel was finished
            */
                ...

            else
            /*
            *   The channel was interrupted
            */
                ...

            endif

            call DestroyEffect(this.spellSfx)
            call UnitApplyTimedLife(this.dummy, 'BTLF', SFX_DEATH_TIME)

            ...

            set this.spellSfx = null
            set this.dummy = null
            ...

            set channelingInstance[this.unitId] = 0
        endmethod

        implement SpellEvent

    endstruct


endlibrary
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'XXXX'

        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT

        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 SPELL CONFIGURATION                   *
    *****************************************************************/

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

    private struct Node extends array
        implement Alloc
    endstruct

    private struct SpellComponent extends array

        ...

        static method create takes ... returns thistype
            local thistype node = Node.allocate()
            ...
            return node
        endmethod
        method destroy takes nothing returns nothing
            ...
            call Node(this).deallocate()
        endmethod

    endstruct

    private struct SpellComponentList extends array
        method operator component takes nothing returns SpellComponent
            return this
        endmethod

        private static method onRemove takes thistype node returns nothing
            call node.component.destroy()
        endmethod

        private static method allocate takes nothing returns thistype
            return Node.allocate()
        endmethod
        private method deallocate takes nothing returns nothing
            call Node(this).deallocate()
        endmethod

        implement InstantiatedList  // Refer to the LinkedList library's documentation to know the above methods
        // Or you can use your own linked list
    endstruct

    public struct <SpellName> extends array

        implement SpellConfiguration

        unit dummy
        effect spellSfx
        SpellComponentList componentList
        ...

        private method onSpellStart takes nothing returns thistype
            if <InvalidCastCondition> then
                return 0
            endif

            set this.dummy          = CreateUnit(...)
            set this.spellSfx       = AddSpecialEffectTarget(..., this.dummy, ...)

            ...

            set this.componentList  = SpellComponentList.create()

            return this
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local thistype node

            ...

            if <CreateComponentCondition> then
                set node = SpellComponent.create(...)

                ...

                call this.componentList.pushBack(node)
            endif

            set node = this.componentList.next
            loop
                exitwhen node == this.componentList

                ...

                if <DestroyComponentCondition> then
                    call node.remove()
                endif

                set node = node.next
            endloop

            ...

            return <EndPeriodicCondition>
        endmethod

        private method onSpellEnd takes nothing returns nothing
            // Destroy remaining component nodes
            call this.componentList.destroy() // Calls flush() which in turn calls remove() for each node on the list

            call DestroyEffect(this.spellSfx)
            call UnitApplyTimedLife(this.dummy, 'BTLF', SFX_DEATH_TIME)

            ...

            set this.spellSfx = null
            set this.dummy = null
        endmethod

        implement SpellEvent

    endstruct


endlibrary
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'XXXX'

        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT

        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 SPELL CONFIGURATION                   *
    *****************************************************************/

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

    private struct Node extends array
        implement Alloc
    endstruct

    private struct SpellComponent extends array

        ...

        static method create takes ... returns thistype
            local thistype node = Node.allocate()
            ...
            return node
        endmethod
        method destroy takes nothing returns nothing
            ...
            call Node(this).deallocate()
        endmethod

    endstruct

    public struct <SpellName> extends array

        implement SpellConfiguration

        unit dummy
        effect spellSfx
        boolean primaryNode
        ...

        private method operator component takes nothing returns SpellComponent
            return this
        endmethod

        private method onSpellStart takes nothing returns thistype
            if <InvalidCastCondition> then
                return 0
            endif

            set this                = Node.allocate()
            set this.dummy          = CreateUnit(...)
            set this.spellSfx       = AddSpecialEffectTarget(..., this.dummy, ...)
            ...
            set this.primaryNode    = true

            return this
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local thistype node

            ...

            if this.primaryNode then
                if <CreateComponentCondition> then
                    set node = SpellComponent.create(...)
                    set node.primaryNode = false

                    set node.next = 0
                    set node.prev = thistype(0).prev
                    set thistype(0).prev.next = node
                    set thistype(0).prev = node
                endif

                ...

                return <EndPeriodicCondition>
            else

                ...

                /*
                *   No longer need to manually remove component node from list - it
                *   will automatically be removed upon returning true
                */
                return <DestroyComponentCondition>
            endif

        endmethod

        private method onSpellEnd takes nothing returns nothing
            if this.primaryNode then
                set this.primaryNode = false

                call DestroyEffect(this.spellSfx)
                call UnitApplyTimedLife(this.dummy, 'BTLF', SFX_DEATH_TIME)

                ...

                set this.spellSfx = null
                set this.dummy = null

                call Node(this).deallocate()
            else

                call this.component.destroy()
            endif
        endmethod

        implement SpellEvent

    endstruct


endlibrary
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                       SPELL CONFIGURATION                      *
    *****************************************************************/
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID    = 'XXXX'

        static constant integer SPELL_EVENT_TYPE    = EVENT_SPELL_EFFECT

        static constant real SPELL_PERIOD           = 1.00/32.00

        static constant real SFX_DEATH_TIME         = 1.50
        ...

    endmodule

    private constant function SummonCount takes integer level returns integer
        return 5 + 5*level
    endfunction

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

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

    private struct Node extends array
        implement Alloc
    endstruct

    private struct SpellComponent extends array

        ...

        static method create takes ... returns thistype
            local thistype node = Node.allocate()
            ...
            return node
        endmethod
        method destroy takes nothing returns nothing
            ...
            call Node(this).deallocate()
        endmethod

    endstruct

    public struct <SpellName> extends array

        implement SpellConfiguration

        ...

        private method operator component takes nothing returns SpellComponent
            return this
        endmethod

        private method onSpellStart takes nothing returns thistype
            local integer count
            local thistype node

            if not <InvalidCastCondition> then
                set count = SummonCount(GetEventSpellLevel())
                if count > 0 then
                    loop
                        exitwhen count == 0

                        set node = SpellComponent.create(...)

                        ...

                        set node.next = 0
                        set node.prev = thistype(0).prev
                        set thistype(0).prev.next = node
                        set thistype(0).prev = node

                        set count = count - 1
                    endloop

                    ...
                endif
            endif

            return 0
        endmethod

        private method onSpellPeriodic takes nothing returns boolean

            ...

            /*
            *   Again, no need to manually remove the component node, it will
            *   automatically be removed from the list when returning true.
            */
            return <EndPeriodicCondition>
        endmethod

        private method onSpellEnd takes nothing returns nothing

            ...

            call this.component.destroy()
        endmethod

        implement SpellEvent

    endstruct


endlibrary
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                   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 or <OtherEndPeriodicCondition>
        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
            /*
            *   This configuration setup is run whenever <node>.initSpellConfiguration(ABIL_ID) or
            *   <node>.loadSpellConfiguration(CONFIG_ID)
            */
            local <SpellName> node = SpellCloner.configuredInstance
            set node.staticSfxModel               = STATIC_SFX_MODEL
            set node.staticSfxAttachPoint         = STATIC_SFX_ATTACHPOINT
            ...
            set node.spellDuration                = spellDuration(GetEventSpellLevel())
            ...
        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
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                   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 constant function SummonCount takes integer level returns integer
        return 5 + 5*level
    endfunction

    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

    private struct Node extends array
        implement Alloc
    endstruct

    private struct SpellComponent extends array

        ...

        static method create takes ... returns thistype
            local thistype node = Node.allocate()
            ...
            return node
        endmethod
        method destroy takes nothing returns nothing
            ...
            call Node(this).deallocate()
        endmethod

    endstruct

    public struct <SpellName> extends array

        implement GlobalSpellConfiguration
        implement SpellClonerHeader

        real summonedNodeDuration
        ...

        private method operator component takes nothing returns SpellComponent
            return this
        endmethod

        private method onSpellStart takes nothing returns thistype
            /*
            *   It is important that you only call initSpellConfiguration() once per cast because
            *   sometimes, it is possible that a similar activation abilityId is assigned to multiple
            *   configuration struct, in which case, whenever the ability is cast, onSpellStart() will
            *   run for each configuration struct. Calling initSpellConfiguration(ABIL_ID) will move
            *   its internal configuration struct iterator to the next one in the list of configurations
            *   belonging to <ABIL_ID>. Therefore, we just have to save current configuration struct ID
            *   (The return value) so we can load it in the loop later below.
            *
            *   Note: The 'this' below is useless. You can use an arbitrary instance or even 'thistype(0)'.
            */
            local integer configurationId = this.initSpellConfiguration(GetEventSpellAbilityId())
            local integer count
            local thistype node

            if not <InvalidCastCondition> then
                set count = SummonCount(GetEventSpellLevel())
                if count > 0 then
                    loop
                        exitwhen count == 0

                        set node = SpellComponent.create(...)
                        call node.loadSpellConfiguration(configurationId)

                        set node.next = 0
                        set node.prev = thistype(0).prev
                        set thistype(0).prev.next = node
                        set thistype(0).prev = node

                        ...

                        set count = count - 1
                    endloop

                    ...
                endif
            endif

            return 0
        endmethod

        private method onSpellPeriodic takes nothing returns boolean

            ...

            set this.summonedNodeDuration = this.summonedNodeDuration - SPELL_PERIOD
            return this.summonedNodeDuration <= 0.00 or <OtherEndPeriodicCondition>
        endmethod

        private method onSpellEnd takes nothing returns nothing

            ...

            call this.component.destroy()
        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
            /*
            *   This configuration setup is run whenever <node>.initSpellConfiguration(ABIL_ID) or
            *   <node>.loadSpellConfiguration(CONFIG_ID)
            */
            local <SpellName> node = SpellCloner.configuredInstance
            ...
            set node.summonedNodeDuration           = summonDuration(GetEventSpellLevel())
            ...
        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
JASS:
library <SpellName> /*


    */uses /*

    */SpellFramework    /*

    */

    /*****************************************************************
    *                   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

        string staticSfxModel
        string staticSfxAttachPoint
        real spellDuration
        ...

        private method onClonedSpellStart takes nothing returns thistype
            if <InvalidCastCondition> then
                return 0
            endif

            ...

            return this
        endmethod

        private method onClonedSpellPeriodic takes nothing returns boolean

            ...

            set this.spellDuration = this.spellDuration - SPELL_PERIOD
            return this.spellDuration <= 0.00 or <OtherEndPeriodicCondition>
        endmethod

        private method onClonedSpellEnd takes nothing returns nothing

            ...

        endmethod

        implement SpellCloner

    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
            /*
            *   This configuration setup is run automatically by the system before calling
            *   onClonedSpellStart().
            */
            local <SpellName> node = SpellCloner.configuredInstance
            set node.staticSfxModel               = STATIC_SFX_MODEL
            set node.staticSfxAttachPoint         = STATIC_SFX_ATTACHPOINT
            ...
            set node.spellDuration                = spellDuration(GetEventSpellLevel())
            ...
        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
JASS:
library <SpellName>ConfigurationModule /*


    */uses /*

    */SpellFramework    /*
    */<SpellName>


    /*
    *   Configuration struct and its members are better to be public (Just use public keyword
    *   on struct for less collision). This would allow spell modifier systems running on
    *   generic spell events to be able to modify spells freely.
    */
    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.1.0
- Spell Template C changed from being specifically a channeling spell template into a flexible one that automatically detects whether the spell is channeling or non-channeling based on the value of <SPELL_EVENT_TYPE> in the configuration
- Some fixes for Spell Template E, Spell Template F, & Cloneable Spell Template B related to their data structures
- Fixed an instance leak in sample spell 'Teeth' along with minor improvements
- Minor improvements for sample spell 'Reflection'
- Added tooltips for the sample spells
- Updated the libraries used
- Other minor changes

v2.0.0
- Renamed the resource from 'Spell Development Framework' into 'Spell Framework'
- Major update to SpellEvent resulting to backwards incompatibility in API
- Significant updates to SpellCloner library
- Significant updates to LinkedList library
- Updated all the spell templates to match with the new Framework API
- Other important updates

v1.0.1
- Updated documentations across the libraries
- Updated the utility lib 'LinkedList'

v1.0.0
- Initial release




Spell Resources Using this Framework

Archimonde Spellpack
Jaina Spellpack
Kael'thas Spellpack
Torrent Array



CREDITS
JAKEZINC
(Beta tester, bug reports, & suggestions)

Bribe
(GUI SpellSystem, some features of which I adopted into SpellEventGUI)

Devalut
(Letting me modify his Teeth - Ver. Gangrenous Gillyweed to be used as a sample spell)

Contents

Spell Framework v2.1.0 (Map)

Reviews
MyPad
The sample spells used work seamlessly with the system, and the system appears to be robust and relatively error-free. Granted, the sample spells do cost a lot of memory when spammed (dummy unit creation), so a word of caution for those who would like...
Level 20
Joined
Aug 13, 2013
Messages
1,696
Yeah, I'm advocating the use of this. Now it comes even with a GUI version.
It should be highly recommended from those who'll incorporate their maps with a bunch of custom spells/abilities.

The more spell/ability code registered to the SpellFramework the more it becomes powerful.
Yeah this is obviously true, the system debunks most of the undoable things for the spell-makers in the past.
(I just hoped you made it recognized earlier: where spell-makers aren't that gone, plus a lively spell section)

Although, it shouldn't be a problem anymore as you have my back for that. ;)

The only thing I'm waiting for is the moderator on this section to criticize this as It deserves to be proven very useful.
 
Currently wrapping my head around this system. Merciful God, there are a lot of abstractions in this system.

In the test map, if you create too many fireballs, the game crashes. I have yet to investigate the reason behind this, so I can only point this out.

EDIT:
Further testing revealed that the creation of fireballs wasn't a major cause of the game crash. The likelihood of the game crashing increased the more interactions the fireballs had with the environment (albeit destructive).
 
Last edited:
The sample spells used work seamlessly with the system, and the system appears to be robust and relatively error-free. Granted, the sample spells do cost a lot of memory when spammed (dummy unit creation), so a word of caution for those who would like to import these sample spells-as-is.

This system has a lot of dependencies, which makes it a bit more complex to import than most systems, but offers in exchange feature-rich, easy spellmaking templates that I would encourage a lot of vJASSers/GUIers to use in their own projects.

The Tooth spell appears to leak a bit, with the index slowly growing bigger. (Debug message is printed onSpellStart, reading instance this).
upload_2020-8-25_19-59-17.png

Rating: 4.7/5.0
Status: Approved
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Why not spawn 1,000 dummies at map init to solve the lag caused by unit making? If one isn't using them for unit capabilities then could use corpse units as well though make sure they'd be set to not raiseable and also not based on a tauren unit.
I did not do it since I didn't experience any lags related to casting spells. It's rare to experience lag due to creating dummy units even in bunches. What affects performance more in such cases are probably the attached special effects.

The sample spells used work seamlessly with the system, and the system appears to be robust and relatively error-free. Granted, the sample spells do cost a lot of memory when spammed (dummy unit creation), so a word of caution for those who would like to import these sample spells-as-is.

This system has a lot of dependencies, which makes it a bit more complex to import than most systems, but offers in exchange feature-rich, easy spellmaking templates that I would encourage a lot of vJASSers/GUIers to use in their own projects.

The Tooth spell appears to leak a bit, with the index slowly growing bigger. (Debug message is printed onSpellStart, reading instance this).
View attachment 362680

Rating: 4.7/5.0
Status: Approved
Thanks, I'm gonna check out that leak. Gonna update soon.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Updated to v2.1.0
- Spell Template C changed from being specifically a channeling spell template into a flexible one that automatically detects whether the spell is channeling or non-channeling based on the value of <SPELL_EVENT_TYPE> in the configuration
- Some fixes for Spell Template E, Spell Template F, & Cloneable Spell Template B related to their data structures
- Fixed an instance leak in sample spell 'Teeth' along with minor improvements
- Minor improvements for sample spell 'Reflection'
- Added tooltips for the sample spells
- Updated the libraries used
- Other minor changes

I believe the leak is fixed now
 
Level 7
Joined
Feb 9, 2021
Messages
301
@maxodors I'm not sure if AdicHelper can recognize //! novjass? because that code is just a template and is purposely wrapped with //! novjass and //! endnovjass so that it won't be included in the compilation process of the map.
So, does it mean I have to delete all novjass and comment out these sections?
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
So, does it mean I have to delete all novjass and comment out these sections?
Yup, if indeed AdicHelper cant recognize //!novjass

Also, why do you use DamageEvent instead of DDS?
Some examples used here are recycled old codes and I simply didn't bother to port them since they already work good as examples. But yeah I suggest using Bribe's DamageEngine instead.
 
Top