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

Trigger Viewer

Torrent Array v2.0.0.w3x
Variables
Torrent Array v2.0.0 by AGD
Torrent Array Configurations
Geysers
Meteor Shower
Eruption Helix
Earth Water and Fire
Torent Array Spell Core
TorrentArray
Dependencies
TorrentSystem
SpellEvent
SpellCloner
Table
WorldBounds
ResourcePreloader
Other Dependencies
DummyRecycler vJASS
UnitDex
AutoFly
Alloc
ErrorMessage
StunSystem
RegisterNativeEvent
RegisterPlayerUnitEvent
Enter map-specific custom script code below. This text will be included in the map script after variables are declared and before any trigger code.
scope Init initializer InitFunction

    private function RandomX takes nothing returns real
        return GetRandomReal(GetRectMinX(bj_mapInitialPlayableArea), GetRectMaxX(bj_mapInitialPlayableArea))
    endfunction
    private function RandomY takes nothing returns real
        return GetRandomReal(GetRectMinY(bj_mapInitialPlayableArea), GetRectMaxY(bj_mapInitialPlayableArea))
    endfunction

    private function A takes nothing returns nothing
        local unit u = GetTriggerUnit()
        call CreateUnit(GetOwningPlayer(u), GetUnitTypeId(u), RandomX(), RandomY(), GetRandomReal(0, 360))
        call TriggerSleepAction(10)
        call RemoveUnit(u)
        set u = null
    endfunction

    private function B takes nothing returns nothing
        call SetCameraField(CAMERA_FIELD_ZOFFSET, GetCameraField(CAMERA_FIELD_ZOFFSET) + 200, 0)
    endfunction

    private function InitFunction takes nothing returns nothing
        local unit u = CreateUnit(Player(0), 'H001', 0, 0, 0)
        local player p = Player(1)
        local real dist = 1300
        local integer creepcount = 20
        local integer campcount = 15
        local integer i = 0
        local integer f = 360
        local real x
        local real y
        local integer a
        local real facing
        local trigger t1 = CreateTrigger()
        local trigger t2 = CreateTrigger()
        call FogEnable(false)
        call FogMaskEnable(false)
        call SetPlayerState(p, PLAYER_STATE_GIVES_BOUNTY, 1)
        call SetHeroLevel(u, 10, false)
        call SelectUnit(u, true )
        loop
            exitwhen i == f
            set i = i + f/campcount
            set x = dist*Cos(i*bj_DEGTORAD)
            set y = dist*Sin(i*bj_DEGTORAD)
            set facing = bj_RADTODEG*Atan2(y, x)
            set a = 0
            loop
                set a = a + 1
                call CreateUnit(p, 'hpea', x, y, facing)
                exitwhen a == creepcount
            endloop
        endloop
        set u = null
        call TriggerRegisterAnyUnitEventBJ(t1, EVENT_PLAYER_UNIT_DEATH)
        call TriggerAddAction(t1, function A)
        call TriggerRegisterPlayerEvent(t2, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t2, function B)
        set t1 = null
        set t2 = null
    endfunction


endscope
Name Type Is Array Initial Value
library Geysers /*


    */
uses /*

    */
TorrentArray  /*  https://www.hiveworkshop.com/threads/291207/
    */
SpellEvent    /*  https://www.hiveworkshop.com/threads/301895/


    |---------------------------------|
    | Spell Configuration Description |
    |---------------------------------|

        Creates a linear array of Geysers, sending tall columns of hot water that toss
        units within the vicinity upwards, and damage them over a short duration.

    */

    public struct Linear extends array
        /*=================================================================*/
        /*                       Spell Configuration                       */
        /*=================================================================*/

        /*-----------------------------------------------------------------*/
        /*                      Static Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                       Basic Configuration                       */
        /*******************************************************************/
        /*
        *   Activation spell's rawcode
        */

        static constant integer SPELL_ABILITY_ID                = 'ATOR'
        /*
        *   Determines the type of spell event (See SpellEvent library for
        *   more options)
        */

        static constant integer SPELL_EVENT_TYPE                = EVENT_SPELL_EFFECT
        /*
        *   Determines if the tossed units are stunned or not (Normally,
        *   you should set this to true)
        */

        static constant boolean PAUSE_TOSSED_TARGETS            = true
        /*
        *   Determines if the toss unit feature is enabled or not
        */

        static constant boolean TOSS_TARGETS                    = true
        /*
        *   Determines if the torrent borders will be hidden from the
        *   enemies' vision
        */

        static constant boolean BLIND_ENEMIES                   = true
        /*
        *   If true, the target ground's location will serve as the basis
        *   for where the location of the first torrent will deviate from
        *   but if false, the caster's location will serve as the basis
        *   of deviation instead. This might be good to enable if you're
        *   configuring a circular of a spiral array of torrents so that
        *   it will have its center/origin at the target location instead
        *   of the caster's.
        */

        static constant boolean TARGET_IS_CENTER                = false
        /*
        *   Determines the attack type of the damage by the torrent
        */

        static constant attacktype ATTACK_TYPE                  = ATTACK_TYPE_HERO
        /*
        *   Determines the damage type of the damage by the torrent
        */

        static constant damagetype DAMAGE_TYPE                  = DAMAGE_TYPE_NORMAL
        /*
        *   Determines the weapon type of the damage by the torrent
        */

        static constant weapontype WEAPON_TYPE                  = null

        /*******************************************************************/
        /*                        Spell Aesthetics                         */
        /*******************************************************************/
        /*
        *   Determines the model of the SFX when a torrent is set up
        */

        static constant string SUMMON_SFX_MODEL                 = ""
        /*
        *   Determines the SFXModel used for the torrent
        */

        static constant string EXPLOSION_SFX_MODEL              = "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
        /*
        *   Determines the SFXModel used for the spell borders
        */

        static constant string BORDER_SFX_MODEL                 = "Abilities\\Weapons\\ZigguratMissile\\ZigguratMissile.mdl"
        /*
        *   Determines the SFXModel used to attach to the tossed targets
        */

        static constant string TOSSED_TARGET_SFX_MODEL          = ""
        /*
        *   Determines the SFXModel used to attach to the affected units
        *   upon landing
        */

        static constant string LANDED_TARGET_SFX_MODEL          = "Abilities\\Spells\\Items\\AIob\\AIobSpecialArt.mdl"
        /*
        *   Determines the attachment point for tossedTargetSfxModel
        */

        static constant string TOSSED_TARGET_SFX_ATTACH_POINT   = ""
        /*
        *   Determines the attachment point for landedTargetSfxModel
        */

        static constant string LANDED_TARGET_SFX_ATTACH_POINT   = "overhead"
        /*
        *   Determines the animation played by the affected units when
        *   being thrown on air (Default value is an empty string)
        */

        static constant string TOSSED_TARGET_ANIMATION          = ""

        /*-----------------------------------------------------------------*/
        /*                     Dynamic Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                        Spell Mechanics                          */
        /*******************************************************************/
        /*
        *   Determines the torrent count
        */

        static constant method torrentCount takes integer level returns integer
            return 10 + 0*level
        endmethod
        /*
        *   Determines the delay of the first torrent's explosion after
        *   its appearance
        */

        static constant method explosionDelay takes integer level returns real
            return 2.00 + 0.00*level
        endmethod
        /*
        *   Determines the time interval of the next torrent release
        */

        static constant method explosionInterval takes integer level returns real
            return 0.25 + 0.00*level
        endmethod
        /*
        *   Determines the distance between the torrents along the axis of the
        *   line between the caster and the target
        */

        static constant method torrentPositionOffset takes integer level returns real
            return 300.00 + 0.00*level
        endmethod
        /*
        *   Determines the angle deflection of the torrents' location
        *   with respect to the caster as the origin
        */

        static constant method torrentPositionAngleDeflection takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the duration of the torrent
        */

        static constant method tossDuration takes integer level returns real
            return 1.75 + 0.00*level
        endmethod
        /*
        *   Determines the radius area affected by torrent
        */

        static constant method torrentRadius takes integer level returns real
            return 200.00 + 0.00*level
        endmethod
        /*
        *   Determines the damage done by the torrent upon its explosion
        */

        static constant method explosionDamage takes integer level returns real
            return 75.00 + 25.00*level
        endmethod
        /*
        *   Determines the damage dealt per second to the affected units
        */

        static constant method damagePerSecond takes integer level returns real
            return 5.00 + 5.00*level
        endmethod
        /*
        *   Determines the maximum height of the thrown units
        */

        static constant method torrentHeight takes integer level returns real
            return 650.00 + 0.00*level
        endmethod
        /*
        *   Determines the distance of the border special effect from
        *   each other
        */

        static constant method borderPositionOffset takes integer level returns real
            return 60.00 + 0.00*level
        endmethod
        /*
        *   The code for filtering target units
        */

        static method targetFilter takes unit target, unit caster returns boolean
            return IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
                 */
not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
                 */
not IsUnitType(target, UNIT_TYPE_ETHEREAL)
        endmethod

        /*-----------------------------------------------------------------*/
        /*                    Extension Configuration                      */
        /*   (The following methods are optional, it's better to delete    */
        /*                  them if they're not needed)                    */
        /*-----------------------------------------------------------------*/
        /*
        *   The code that will run upon each torrent's explosion
        */

        static method onTorrentExplode takes Torrent this, integer level returns nothing
            /*
            *   ->  Put onExplode responses here
            */

        endmethod
        /*
        *   The code that will run when each torrent vanishes
        */

        static method onTorrentVanish takes Torrent this, integer level returns nothing
            /*
            *   ->  Put onVanish responses here
            */

        endmethod

        /*=================================================================*/
        /*                   End of Spell Configuration                    */
        /*=================================================================*/

        implement TorrentArrayConfiguration

    endstruct


endlibrary
library MeteorShower /*


    */
uses /*

    */
TorrentArray  /*  https://www.hiveworkshop.com/threads/291207/
    */
SpellEvent    /*  https://www.hiveworkshop.com/threads/301895/
    */
StunSystem    /*  https://www.hiveworkshop.com/threads/196749/


    |---------------------------------|
    | Spell Configuration Description |
    |---------------------------------|

        Calls down a circular array of meteors that damage and stun units within their
        area of impact. Units are also burned afterwards, taking damage over time.

    */

    public struct Circular extends array
        /*=================================================================*/
        /*                       Spell Configuration                       */
        /*=================================================================*/

        /*-----------------------------------------------------------------*/
        /*                      Static Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                       Basic Configuration                       */
        /*******************************************************************/
        /*
        *   Activation spell's rawcode
        */

        static constant integer SPELL_ABILITY_ID                = 'AMET'
        /*
        *   Determines the type of spell event (See SpellEvent library for
        *   more options)
        */

        static constant integer SPELL_EVENT_TYPE                = EVENT_SPELL_EFFECT
        /*
        *   Determines if the tossed units are stunned or not (Normally,
        *   you should set this to true)
        */

        static constant boolean PAUSE_TOSSED_TARGETS            = false
        /*
        *   Determines if the toss unit feature is enabled or not
        */

        static constant boolean TOSS_TARGETS                    = true
        /*
        *   Determines if the torrent borders will be hidden from the
        *   enemies' vision
        */

        static constant boolean BLIND_ENEMIES                   = false
        /*
        *   If true, the target ground's location will serve as the basis
        *   for where the location of the first torrent will deviate from
        *   but if false, the caster's location will serve as the basis
        *   of deviation instead. This might be good to enable if you're
        *   configuring a circular of a spiral array of torrents so that
        *   it will have its center/origin at the target location instead
        *   of the caster's.
        */

        static constant boolean TARGET_IS_CENTER                = true
        /*
        *   Determines the attack type of the damage by the torrent
        */

        static constant attacktype ATTACK_TYPE                  = ATTACK_TYPE_HERO
        /*
        *   Determines the damage type of the damage by the torrent
        */

        static constant damagetype DAMAGE_TYPE                  = DAMAGE_TYPE_NORMAL
        /*
        *   Determines the weapon type of the damage by the torrent
        */

        static constant weapontype WEAPON_TYPE                  = null

        /*******************************************************************/
        /*                        Spell Aesthetics                         */
        /*******************************************************************/
        /*
        *   Determines the model of the SFX when a torrent is set up
        */

        static constant string SUMMON_SFX_MODEL                 = "Units\\Demon\\Infernal\\InfernalBirth.mdl"
        /*
        *   Determines the SFXModel used for the torrent
        */

        static constant string EXPLOSION_SFX_MODEL              = ""
        /*
        *   Determines the SFXModel used for the spell borders
        */

        static constant string BORDER_SFX_MODEL                 = "Environment\\UndeadBuildingFire\\UndeadLargeBuildingFire2.mdl"
        /*
        *   Determines the SFXModel used to attach to the tossed targets
        */

        static constant string TOSSED_TARGET_SFX_MODEL          = "Environment\\LargeBuildingFire\\LargeBuildingFire1.mdl"
        /*
        *   Determines the SFXModel used to attach to the affected units
        *   upon landing
        */

        static constant string LANDED_TARGET_SFX_MODEL          = ""
        /*
        *   Determines the attachment point for tossedTargetSfxModel
        */

        static constant string TOSSED_TARGET_SFX_ATTACH_POINT   = "origin"
        /*
        *   Determines the attachment point for landedTargetSfxModel
        */

        static constant string LANDED_TARGET_SFX_ATTACH_POINT   = ""
        /*
        *   Determines the animation played by the affected units when
        *   being thrown on air (Default value is an empty string)
        */

        static constant string TOSSED_TARGET_ANIMATION          = ""

        /*-----------------------------------------------------------------*/
        /*                     Dynamic Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                        Spell Mechanics                          */
        /*******************************************************************/
        /*
        *   Determines the torrent count
        */

        static constant method torrentCount takes integer level returns integer
            return 10 + 0*level
        endmethod
        /*
        *   Determines the delay of the first torrent's explosion after
        *   its appearance
        */

        static constant method explosionDelay takes integer level returns real
            return 1.00 + 0.00*level
        endmethod
        /*
        *   Determines the time interval of the next torrent release
        */

        static constant method explosionInterval takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the distance between the torrents along the axis of the
        *   line between the caster and the target
        */

        static constant method torrentPositionOffset takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the angle deflection of the torrents' location
        *   with respect to the caster as the origin
        */

        static constant method torrentPositionAngleDeflection takes integer level returns real
            return 36.00 + 0.00*level
        endmethod
        /*
        *   Determines the duration of the torrent
        */

        static constant method tossDuration takes integer level returns real
            return 3.00 + 2.00*level
        endmethod
        /*
        *   Determines the radius area affected by torrent
        */

        static constant method torrentRadius takes integer level returns real
            return 175.00 + 0.00*level
        endmethod
        /*
        *   Determines the damage done by the torrent upon its explosion
        */

        static constant method explosionDamage takes integer level returns real
            return 75.00 + 25.00*level
        endmethod
        /*
        *   Determines the damage dealt per second to the affected units
        */

        static constant method damagePerSecond takes integer level returns real
            return 20.00 + 0.00*level
        endmethod
        /*
        *   Determines the maximum height of the thrown units
        */

        static constant method torrentHeight takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the distance of the border special effect from
        *   each other
        */

        static constant method borderPositionOffset takes integer level returns real
            return 60.00 + 0.00*level
        endmethod
        /*
        *   Determines the stun duration
        */

        static constant method stunDuration takes integer level returns real
            return 0.50 + 0.50*level
        endmethod
        /*
        *   The code for filtering target units
        */

        static method targetFilter takes unit target, unit caster returns boolean
            return UnitAlive(target) and /*
                 */
IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
                 */
not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
                 */
not IsUnitType(target, UNIT_TYPE_ETHEREAL)
        endmethod

        /*-----------------------------------------------------------------*/
        /*                    Extension Configuration                      */
        /*   (The following methods are optional, it's better to delete    */
        /*                  them if they're not needed)                    */
        /*-----------------------------------------------------------------*/
        /*
        *   The code that will run upon each torrent's explosion
        */

        static method onTorrentExplode takes Torrent this, integer level returns nothing
            local group targetGroup = this.targetGroup
            local unit u
            loop
                set u = FirstOfGroup(targetGroup)
                exitwhen u == null
                /* It's okay to empty this group (unless you have multiple
                   handlers) because it will be refilled later          */

                call GroupRemoveUnit(targetGroup, u)
                call Stun.apply(u, stunDuration(level), false)
            endloop
            set targetGroup = null
        endmethod
        /*
        *   The code that will run when each torrent vanishes
        */

        static method onTorrentVanish takes Torrent this, integer level returns nothing
            /*
            *   ->  Put onVanish responses here
            */

        endmethod

        /*=================================================================*/
        /*                   End of Spell Configuration                    */
        /*=================================================================*/

        implement TorrentArrayConfiguration

    endstruct


endlibrary
library EruptionHelix /*


    */
uses /*

    */
TorrentArray  /*  https://www.hiveworkshop.com/threads/291207/
    */
SpellEvent    /*  https://www.hiveworkshop.com/threads/301895/
    */
StunSystem    /*  https://www.hiveworkshop.com/threads/196749/


    |---------------------------------|
    | Spell Configuration Description |
    |---------------------------------|

        Creates a spiral chain of explosions that damage and stun units in their
        vicinity

    */

    public struct Spiral extends array
        /*=================================================================*/
        /*                       Spell Configuration                       */
        /*=================================================================*/

        /*-----------------------------------------------------------------*/
        /*                      Static Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                       Basic Configuration                       */
        /*******************************************************************/
        /*
        *   Activation spell's rawcode
        */

        static constant integer SPELL_ABILITY_ID                = 'AERP'
        /*
        *   Determines the type of spell event (See SpellEvent library for
        *   more options)
        */

        static constant integer SPELL_EVENT_TYPE                = EVENT_SPELL_EFFECT
        /*
        *   Determines if the tossed units are stunned or not (Normally,
        *   you should set this to true)
        */

        static constant boolean PAUSE_TOSSED_TARGETS            = false
        /*
        *   Determines if the toss unit feature is enabled or not
        */

        static constant boolean TOSS_TARGETS                    = false
        /*
        *   Determines if the torrent borders will be hidden from the
        *   enemies' vision
        */

        static constant boolean BLIND_ENEMIES                   = false
        /*
        *   If true, the target ground's location will serve as the basis
        *   for where the location of the first torrent will deviate from
        *   but if false, the caster's location will serve as the basis
        *   of deviation instead. This might be good to enable if you're
        *   configuring a circular of a spiral array of torrents so that
        *   it will have its center/origin at the target location instead
        *   of the caster's.
        */

        static constant boolean TARGET_IS_CENTER                = true
        /*
        *   Determines the attack type of the damage by the torrent
        */

        static constant attacktype ATTACK_TYPE                  = ATTACK_TYPE_HERO
        /*
        *   Determines the damage type of the damage by the torrent
        */

        static constant damagetype DAMAGE_TYPE                  = DAMAGE_TYPE_NORMAL
        /*
        *   Determines the weapon type of the damage by the torrent
        */

        static constant weapontype WEAPON_TYPE                  = null

        /*******************************************************************/
        /*                        Spell Aesthetics                         */
        /*******************************************************************/
        /*
        *   Determines the model of the SFX when a torrent is set up
        */

        static constant string SUMMON_SFX_MODEL                 = "Abilities\\Spells\\Human\\SpellSteal\\SpellStealMissile.mdl"
        /*
        *   Determines the SFXModel used for the torrent
        */

        static constant string EXPLOSION_SFX_MODEL              = "Abilities\\Spells\\Other\\Incinerate\\FireLordDeathExplode.mdl"
        /*
        *   Determines the SFXModel used for the spell borders
        */

        static constant string BORDER_SFX_MODEL                 = ""
        /*
        *   Determines the SFXModel used to attach to the tossed targets
        */

        static constant string TOSSED_TARGET_SFX_MODEL          = "Abilities\\Spells\\Items\\AIfb\\AIfbSpecialArt.mdl"
        /*
        *   Determines the SFXModel used to attach to the affected units
        *   upon landing
        */

        static constant string LANDED_TARGET_SFX_MODEL          = ""
        /*
        *   Determines the attachment point for tossedTargetSfxModel
        */

        static constant string TOSSED_TARGET_SFX_ATTACH_POINT   = "head"
        /*
        *   Determines the attachment point for landedTargetSfxModel
        */

        static constant string LANDED_TARGET_SFX_ATTACH_POINT   = ""
        /*
        *   Determines the animation played by the affected units when
        *   being thrown on air (Default value is an empty string)
        */

        static constant string TOSSED_TARGET_ANIMATION          = ""

        /*-----------------------------------------------------------------*/
        /*                     Dynamic Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                        Spell Mechanics                          */
        /*******************************************************************/
        /*
        *   Determines the torrent count
        */

        static constant method torrentCount takes integer level returns integer
            return 80 + 0*level
        endmethod
        /*
        *   Determines the delay of the first torrent's explosion after
        *   its appearance
        */

        static constant method explosionDelay takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the time interval of the next torrent release
        */

        static constant method explosionInterval takes integer level returns real
            return 0.02 + 0.00*level
        endmethod
        /*
        *   Determines the distance between the torrents along the axis of the
        *   line between the caster and the target
        */

        static constant method torrentPositionOffset takes integer level returns real
            return 10.00 + 0.00*level
        endmethod
        /*
        *   Determines the angle deflection of the torrents' location
        *   with respect to the caster as the origin
        */

        static constant method torrentPositionAngleDeflection takes integer level returns real
            return -18.00 + 0.00*level
        endmethod
        /*
        *   Determines the duration of the torrent
        */

        static constant method tossDuration takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the radius area affected by torrent
        */

        static constant method torrentRadius takes integer level returns real
            return 100.00 + 0.00*level
        endmethod
        /*
        *   Determines the damage done by the torrent upon its explosion
        */

        static constant method explosionDamage takes integer level returns real
            return 150.00 + 50.00*level
        endmethod
        /*
        *   Determines the damage dealt per second to the affected units
        */

        static constant method damagePerSecond takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the maximum height of the thrown units
        */

        static constant method torrentHeight takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   Determines the distance of the border special effect from
        *   each other
        */

        static constant method borderPositionOffset takes integer level returns real
            return 60.00 + 0.00*level
        endmethod
        /*
        *   Determines the stun duration
        */

        static constant method stunDuration takes integer level returns real
            return 0.50 + 0.50*level
        endmethod
        /*
        *   The code for filtering target units
        */

        static method targetFilter takes unit target, unit caster returns boolean
            return UnitAlive(target) and /*
                 */
IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
                 */
not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /*
                 */
not IsUnitType(target, UNIT_TYPE_ETHEREAL)
        endmethod

        /*-----------------------------------------------------------------*/
        /*                    Extension Configuration                      */
        /*   (The following methods are optional, it's better to delete    */
        /*                  them if they're not needed)                    */
        /*-----------------------------------------------------------------*/
        /*
        *   The code that will run upon each torrent's explosion
        */

        static method onTorrentExplode takes Torrent this, integer level returns nothing
            local group targetGroup = this.targetGroup
            local unit u
            loop
                set u = FirstOfGroup(targetGroup)
                exitwhen u == null
                /* It's okay to empty this group because it will be
                   refilled later                                       */

                call GroupRemoveUnit(targetGroup, u)
                call Stun.apply(u, stunDuration(level), false)
            endloop
            set targetGroup = null
        endmethod
        /*
        *   The code that will run when each torrent vanishes
        */

        static method onTorrentVanish takes Torrent this, integer level returns nothing
            /*
            *   ->  Put onVanish responses here
            */

        endmethod

        /*=================================================================*/
        /*                   End of Spell Configuration                    */
        /*=================================================================*/

        implement TorrentArrayConfiguration

    endstruct


endlibrary
library EarthWaterAndFire /*


    */
uses /*

    */
TorrentArray  /*  https://www.hiveworkshop.com/threads/291207/
    */
SpellEvent    /*  https://www.hiveworkshop.com/threads/301895/
    */
SpellCloner   /*  https://www.hiveworkshop.com/threads/324157/
    */
StunSystem    /*  https://www.hiveworkshop.com/threads/196749/

    */
MeteorShower  /*
    */
Geysers       /*
    */
EruptionHelix /*


    |---------------------------------|
    | Spell Configuration Description |
    |---------------------------------|

        A combination of the three elemental spells
    */


    /*=====================================================================*/
    /*                         Spell Configuration                         */
    /*=====================================================================*/
    /*
    *   Activation spell's rawcode
    */

    globals
        private constant integer ABILITY_ID                         = 'AEWF'
    endglobals

    /*
    *   Radius of the spell
    */

    private function SpellRadius takes integer level returns real
        return 400.00 + 0.00*level
    endfunction

    public struct Earth extends array
        /*-----------------------------------------------------------------*/
        /*                      Static Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                       Basic Configuration                       */
        /*******************************************************************/
        static constant integer SPELL_ABILITY_ID                = ABILITY_ID
        /*
        *   Determines the type of spell event (See SpellEvent library for
        *   more options)
        */

        static constant integer SPELL_EVENT_TYPE                = EVENT_SPELL_EFFECT
        /*
        *   Determines if the tossed units are stunned or not (Normally,
        *   you should set this to true)
        */

        static constant boolean PAUSE_TOSSED_TARGETS            = MeteorShower_Circular.PAUSE_TOSSED_TARGETS
        /*
        *   Determines if the toss unit feature is enabled or not
        */

        static constant boolean TOSS_TARGETS                    = MeteorShower_Circular.TOSS_TARGETS
        /*
        *   Determines if the torrent borders will be hidden from the
        *   enemies' vision
        */

        static constant boolean BLIND_ENEMIES                   = MeteorShower_Circular.BLIND_ENEMIES
        /*
        *   If true, the target ground's location will serve as the basis
        *   for where the location of the first torrent will deviate from
        *   but if false, the caster's location will serve as the basis
        *   of deviation instead. This might be good to enable if you're
        *   configuring a circular of a spiral array of torrents so that
        *   it will have its center/origin at the target location instead
        *   of the caster's.
        */

        static constant boolean TARGET_IS_CENTER                = false
        /*
        *   Determines the attack type of the damage by the torrent
        */

        static constant attacktype ATTACK_TYPE                  = MeteorShower_Circular.ATTACK_TYPE
        /*
        *   Determines the damage type of the damage by the torrent
        */

        static constant damagetype DAMAGE_TYPE                  = MeteorShower_Circular.DAMAGE_TYPE
        /*
        *   Determines the weapon type of the damage by the torrent
        */

        static constant weapontype WEAPON_TYPE                  = MeteorShower_Circular.WEAPON_TYPE

        /*******************************************************************/
        /*                        Spell Aesthetics                         */
        /*******************************************************************/
        /*
        *   Determines the model of the SFX when a torrent is set up
        */

        static constant string SUMMON_SFX_MODEL                 = MeteorShower_Circular.SUMMON_SFX_MODEL
        /*
        *   Determines the SFXModel used for the torrent
        */

        static constant string EXPLOSION_SFX_MODEL              = MeteorShower_Circular.EXPLOSION_SFX_MODEL
        /*
        *   Determines the SFXModel used for the spell borders
        */

        static constant string BORDER_SFX_MODEL                 = MeteorShower_Circular.BORDER_SFX_MODEL
        /*
        *   Determines the SFXModel used to attach to the tossed targets
        */

        static constant string TOSSED_TARGET_SFX_MODEL          = MeteorShower_Circular.TOSSED_TARGET_SFX_MODEL
        /*
        *   Determines the SFXModel used to attach to the affected units
        *   upon landing
        */

        static constant string LANDED_TARGET_SFX_MODEL          = MeteorShower_Circular.LANDED_TARGET_SFX_MODEL
        /*
        *   Determines the attachment point for tossedTargetSfxModel
        */

        static constant string TOSSED_TARGET_SFX_ATTACH_POINT   = MeteorShower_Circular.TOSSED_TARGET_SFX_ATTACH_POINT
        /*
        *   Determines the attachment point for landedTargetSfxModel
        */

        static constant string LANDED_TARGET_SFX_ATTACH_POINT   = MeteorShower_Circular.LANDED_TARGET_SFX_ATTACH_POINT
        /*
        *   Determines the animation played by the affected units when
        *   being thrown on air (Default value is an empty string)
        */

        static constant string TOSSED_TARGET_ANIMATION          = MeteorShower_Circular.TOSSED_TARGET_ANIMATION

        /*-----------------------------------------------------------------*/
        /*                     Dynamic Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                        Spell Mechanics                          */
        /*******************************************************************/
        /*
        *   Determines the torrent count
        */

        static constant method torrentCount takes integer level returns integer
            return MeteorShower_Circular.torrentCount(level)
        endmethod
        /*
        *   Determines the delay of the first torrent's explosion after
        *   its appearance
        */

        static constant method explosionDelay takes integer level returns real
            return MeteorShower_Circular.explosionDelay(level)
        endmethod
        /*
        *   Determines the time interval of the next torrent release
        */

        static constant method explosionInterval takes integer level returns real
            return MeteorShower_Circular.explosionInterval(level)
        endmethod
        /*
        *   Determines the distance between the torrents along the axis of the
        *   line between the caster and the target
        */

        static constant method torrentPositionOffset takes integer level returns real
            return MeteorShower_Circular.torrentPositionOffset(level)
        endmethod
        /*
        *   Determines the angle deflection of the torrents' location
        *   with respect to the caster as the origin
        */

        static constant method torrentPositionAngleDeflection takes integer level returns real
            return MeteorShower_Circular.torrentPositionAngleDeflection(level)
        endmethod
        /*
        *   Determines the duration of the torrent
        */

        static constant method tossDuration takes integer level returns real
            return MeteorShower_Circular.tossDuration(level)
        endmethod
        /*
        *   Determines the radius area affected by torrent
        */

        static constant method torrentRadius takes integer level returns real
            return MeteorShower_Circular.torrentRadius(level)
        endmethod
        /*
        *   Determines the damage done by the torrent upon its explosion
        */

        static constant method explosionDamage takes integer level returns real
            return MeteorShower_Circular.explosionDamage(level)
        endmethod
        /*
        *   Determines the damage dealt per second to the affected units
        */

        static constant method damagePerSecond takes integer level returns real
            return MeteorShower_Circular.damagePerSecond(level)
        endmethod
        /*
        *   Determines the maximum height of the thrown units
        */

        static constant method torrentHeight takes integer level returns real
            return MeteorShower_Circular.torrentHeight(level)
        endmethod
        /*
        *   Determines the distance of the border special effect from
        *   each other
        */

        static constant method borderPositionOffset takes integer level returns real
            return MeteorShower_Circular.borderPositionOffset(level)
        endmethod
        /*
        *   The code for filtering target units
        */

        static method targetFilter takes unit target, unit caster returns boolean
            return MeteorShower_Circular.targetFilter(target, caster)
        endmethod

        /*-----------------------------------------------------------------*/
        /*                    Extension Configuration                      */
        /*   (The following methods are optional, it's better to delete    */
        /*                  them if they're not needed)                    */
        /*-----------------------------------------------------------------*/
        /*
        *   The code that will run upon each torrent's explosion
        */

        static method onTorrentExplode takes Torrent this, integer level returns nothing
            call MeteorShower_Circular.onTorrentExplode(this, level)
        endmethod
        /*
        *   The code that will run when each torrent vanishes
        */

        static method onTorrentVanish takes Torrent this, integer level returns nothing
            call MeteorShower_Circular.onTorrentVanish(this, level)
        endmethod

        implement TorrentArrayConfiguration

    endstruct


    public struct Water extends array
        /*-----------------------------------------------------------------*/
        /*                      Static Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                       Basic Configuration                       */
        /*******************************************************************/
        static constant integer SPELL_ABILITY_ID                = ABILITY_ID
        /*
        *   Determines the type of spell event (See SpellEvent library for
        *   more options)
        */

        static constant integer SPELL_EVENT_TYPE                = EVENT_SPELL_EFFECT
        /*
        *   Determines if the tossed units are stunned or not (Normally,
        *   you should set this to true)
        */

        static constant boolean PAUSE_TOSSED_TARGETS            = Geysers_Linear.PAUSE_TOSSED_TARGETS
        /*
        *   Determines if the toss unit feature is enabled or not
        */

        static constant boolean TOSS_TARGETS                    = Geysers_Linear.TOSS_TARGETS
        /*
        *   Determines if the torrent borders will be hidden from the
        *   enemies' vision
        */

        static constant boolean BLIND_ENEMIES                   = Geysers_Linear.BLIND_ENEMIES
        /*
        *   If true, the target ground's location will serve as the basis
        *   for where the location of the first torrent will deviate from
        *   but if false, the caster's location will serve as the basis
        *   of deviation instead. This might be good to enable if you're
        *   configuring a circular of a spiral array of torrents so that
        *   it will have its center/origin at the target location instead
        *   of the caster's.
        */

        static constant boolean TARGET_IS_CENTER                = false
        /*
        *   Determines the attack type of the damage by the torrent
        */

        static constant attacktype ATTACK_TYPE                  = Geysers_Linear.ATTACK_TYPE
        /*
        *   Determines the damage type of the damage by the torrent
        */

        static constant damagetype DAMAGE_TYPE                  = Geysers_Linear.DAMAGE_TYPE
        /*
        *   Determines the weapon type of the damage by the torrent
        */

        static constant weapontype WEAPON_TYPE                  = Geysers_Linear.WEAPON_TYPE

        /*******************************************************************/
        /*                        Spell Aesthetics                         */
        /*******************************************************************/
        /*
        *   Determines the model of the SFX when a torrent is set up
        */

        static constant string SUMMON_SFX_MODEL                 = Geysers_Linear.SUMMON_SFX_MODEL
        /*
        *   Determines the SFXModel used for the torrent
        */

        static constant string EXPLOSION_SFX_MODEL              = Geysers_Linear.EXPLOSION_SFX_MODEL
        /*
        *   Determines the SFXModel used for the spell borders
        */

        static constant string BORDER_SFX_MODEL                 = Geysers_Linear.BORDER_SFX_MODEL
        /*
        *   Determines the SFXModel used to attach to the tossed targets
        */

        static constant string TOSSED_TARGET_SFX_MODEL          = Geysers_Linear.TOSSED_TARGET_SFX_MODEL
        /*
        *   Determines the SFXModel used to attach to the affected units
        *   upon landing
        */

        static constant string LANDED_TARGET_SFX_MODEL          = Geysers_Linear.LANDED_TARGET_SFX_MODEL
        /*
        *   Determines the attachment point for tossedTargetSfxModel
        */

        static constant string TOSSED_TARGET_SFX_ATTACH_POINT   = Geysers_Linear.TOSSED_TARGET_SFX_ATTACH_POINT
        /*
        *   Determines the attachment point for landedTargetSfxModel
        */

        static constant string LANDED_TARGET_SFX_ATTACH_POINT   = Geysers_Linear.LANDED_TARGET_SFX_ATTACH_POINT
        /*
        *   Determines the animation played by the affected units when
        *   being thrown on air (Default value is an empty string)
        */

        static constant string TOSSED_TARGET_ANIMATION          = Geysers_Linear.TOSSED_TARGET_ANIMATION

        /*-----------------------------------------------------------------*/
        /*                     Dynamic Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                        Spell Mechanics                          */
        /*******************************************************************/
        /*
        *   Determines the torrent count
        */

        static constant method torrentCount takes integer level returns integer
            return 1 + MeteorShower_Circular.torrentCount(level)/2
        endmethod
        /*
        *   Determines the delay of the first torrent's explosion after
        *   its appearance
        */

        static constant method explosionDelay takes integer level returns real
            return 1.25 + 0.00*level
        endmethod
        /*
        *   Determines the time interval of the next torrent release
        */

        static constant method explosionInterval takes integer level returns real
            return 0.20 + 0.00*level
        endmethod
        /*
        *   Determines the distance between the torrents along the axis of the
        *   line between the caster and the target
        */

        static constant method torrentPositionOffset takes integer level returns real
            return MeteorShower_Circular.torrentPositionOffset(level)
        endmethod
        /*
        *   Determines the angle deflection of the torrents' location
        *   with respect to the caster as the origin
        */

        static constant method torrentPositionAngleDeflection takes integer level returns real
            return MeteorShower_Circular.torrentPositionAngleDeflection(level)
        endmethod
        /*
        *   Determines the duration of the torrent
        */

        static constant method tossDuration takes integer level returns real
            return Geysers_Linear.tossDuration(level)
        endmethod
        /*
        *   Determines the radius area affected by torrent
        */

        static constant method torrentRadius takes integer level returns real
            return MeteorShower_Circular.torrentRadius(level)
        endmethod
        /*
        *   Determines the damage done by the torrent upon its explosion
        */

        static constant method explosionDamage takes integer level returns real
            return Geysers_Linear.explosionDamage(level)
        endmethod
        /*
        *   Determines the damage dealt per second to the affected units
        */

        static constant method damagePerSecond takes integer level returns real
            return Geysers_Linear.damagePerSecond(level)
        endmethod
        /*
        *   Determines the maximum height of the thrown units
        */

        static constant method torrentHeight takes integer level returns real
            return Geysers_Linear.torrentHeight(level)
        endmethod
        /*
        *   Determines the distance of the border special effect from
        *   each other
        */

        static constant method borderPositionOffset takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   The code for filtering target units
        */

        static method targetFilter takes unit target, unit caster returns boolean
            return Geysers_Linear.targetFilter(target, caster)
        endmethod

        /*-----------------------------------------------------------------*/
        /*                    Extension Configuration                      */
        /*   (The following methods are optional, it's better to delete    */
        /*                  them if they're not needed)                    */
        /*-----------------------------------------------------------------*/
        /*
        *   The code that will run upon each torrent's explosion
        */

        static method onTorrentExplode takes Torrent this, integer level returns nothing
            call Geysers_Linear.onTorrentExplode(this, level)
        endmethod
        /*
        *   The code that will run when each torrent vanishes
        */

        static method onTorrentVanish takes Torrent this, integer level returns nothing
            call Geysers_Linear.onTorrentVanish(this, level)
        endmethod

        implement TorrentArrayConfiguration

    endstruct


    public struct Fire extends array
        /*-----------------------------------------------------------------*/
        /*                      Static Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                       Basic Configuration                       */
        /*******************************************************************/
        static constant integer SPELL_ABILITY_ID                = ABILITY_ID
        /*
        *   Determines the type of spell event (See SpellEvent library for
        *   more options)
        */

        static constant integer SPELL_EVENT_TYPE                = EVENT_SPELL_EFFECT
        /*
        *   Determines if the tossed units are stunned or not (Normally,
        *   you should set this to true)
        */

        static constant boolean PAUSE_TOSSED_TARGETS            = EruptionHelix_Spiral.PAUSE_TOSSED_TARGETS
        /*
        *   Determines if the toss unit feature is enabled or not
        */

        static constant boolean TOSS_TARGETS                    = EruptionHelix_Spiral.TOSS_TARGETS
        /*
        *   Determines if the torrent borders will be hidden from the
        *   enemies' vision
        */

        static constant boolean BLIND_ENEMIES                   = EruptionHelix_Spiral.BLIND_ENEMIES
        /*
        *   If true, the target ground's location will serve as the basis
        *   for where the location of the first torrent will deviate from
        *   but if false, the caster's location will serve as the basis
        *   of deviation instead. This might be good to enable if you're
        *   configuring a circular of a spiral array of torrents so that
        *   it will have its center/origin at the target location instead
        *   of the caster's.
        */

        static constant boolean TARGET_IS_CENTER                = false
        /*
        *   Determines the attack type of the damage by the torrent
        */

        static constant attacktype ATTACK_TYPE                  = EruptionHelix_Spiral.ATTACK_TYPE
        /*
        *   Determines the damage type of the damage by the torrent
        */

        static constant damagetype DAMAGE_TYPE                  = EruptionHelix_Spiral.DAMAGE_TYPE
        /*
        *   Determines the weapon type of the damage by the torrent
        */

        static constant weapontype WEAPON_TYPE                  = EruptionHelix_Spiral.WEAPON_TYPE

        /*******************************************************************/
        /*                        Spell Aesthetics                         */
        /*******************************************************************/
        /*
        *   Determines the model of the SFX when a torrent is set up
        */

        static constant string SUMMON_SFX_MODEL                 = EruptionHelix_Spiral.SUMMON_SFX_MODEL
        /*
        *   Determines the SFXModel used for the torrent
        */

        static constant string EXPLOSION_SFX_MODEL              = EruptionHelix_Spiral.EXPLOSION_SFX_MODEL
        /*
        *   Determines the SFXModel used for the spell borders
        */

        static constant string BORDER_SFX_MODEL                 = EruptionHelix_Spiral.BORDER_SFX_MODEL
        /*
        *   Determines the SFXModel used to attach to the tossed targets
        */

        static constant string TOSSED_TARGET_SFX_MODEL          = EruptionHelix_Spiral.TOSSED_TARGET_SFX_MODEL
        /*
        *   Determines the SFXModel used to attach to the affected units
        *   upon landing
        */

        static constant string LANDED_TARGET_SFX_MODEL          = EruptionHelix_Spiral.LANDED_TARGET_SFX_MODEL
        /*
        *   Determines the attachment point for tossedTargetSfxModel
        */

        static constant string TOSSED_TARGET_SFX_ATTACH_POINT   = EruptionHelix_Spiral.TOSSED_TARGET_SFX_MODEL
        /*
        *   Determines the attachment point for landedTargetSfxModel
        */

        static constant string LANDED_TARGET_SFX_ATTACH_POINT   = EruptionHelix_Spiral.LANDED_TARGET_SFX_ATTACH_POINT
        /*
        *   Determines the animation played by the affected units when
        *   being thrown on air (Default value is an empty string)
        */

        static constant string TOSSED_TARGET_ANIMATION          = EruptionHelix_Spiral.TOSSED_TARGET_ANIMATION

        /*-----------------------------------------------------------------*/
        /*                     Dynamic Configuration                       */
        /*-----------------------------------------------------------------*/
        /*******************************************************************/
        /*                        Spell Mechanics                          */
        /*******************************************************************/
        /*
        *   Determines the torrent count
        */

        static constant method torrentCount takes integer level returns integer
            return Water.torrentCount(level)
        endmethod
        /*
        *   Determines the delay of the first torrent's explosion after
        *   its appearance
        */

        static constant method explosionDelay takes integer level returns real
            return Water.explosionDelay(level)
        endmethod
        /*
        *   Determines the time interval of the next torrent release
        */

        static constant method explosionInterval takes integer level returns real
            return Water.explosionInterval(level)
        endmethod
        /*
        *   Determines the distance between the torrents along the axis of the
        *   line between the caster and the target
        */

        static constant method torrentPositionOffset takes integer level returns real
            return Water.torrentPositionOffset(level)
        endmethod
        /*
        *   Determines the angle deflection of the torrents' location
        *   with respect to the caster as the origin
        */

        static constant method torrentPositionAngleDeflection takes integer level returns real
            return -Water.torrentPositionAngleDeflection(level)
        endmethod
        /*
        *   Determines the duration of the torrent
        */

        static constant method tossDuration takes integer level returns real
            return EruptionHelix_Spiral.tossDuration(level)
        endmethod
        /*
        *   Determines the radius area affected by torrent
        */

        static constant method torrentRadius takes integer level returns real
            return EruptionHelix_Spiral.torrentRadius(level)
        endmethod
        /*
        *   Determines the damage done by the torrent upon its explosion
        */

        static constant method explosionDamage takes integer level returns real
            return EruptionHelix_Spiral.explosionDamage(level)
        endmethod
        /*
        *   Determines the damage dealt per second to the affected units
        */

        static constant method damagePerSecond takes integer level returns real
            return EruptionHelix_Spiral.damagePerSecond(level)
        endmethod
        /*
        *   Determines the maximum height of the thrown units
        */

        static constant method torrentHeight takes integer level returns real
            return EruptionHelix_Spiral.torrentHeight(level)
        endmethod
        /*
        *   Determines the distance of the border special effect from
        *   each other
        */

        static constant method borderPositionOffset takes integer level returns real
            return 0.00 + 0.00*level
        endmethod
        /*
        *   The code for filtering target units
        */

        static method targetFilter takes unit target, unit caster returns boolean
            return EruptionHelix_Spiral.targetFilter(target, caster)
        endmethod

        /*-----------------------------------------------------------------*/
        /*                    Extension Configuration                      */
        /*   (The following methods are optional, it's better to delete    */
        /*                  them if they're not needed)                    */
        /*-----------------------------------------------------------------*/
        /*
        *   The code that will run upon each torrent's explosion
        */

        static method onTorrentExplode takes Torrent this, integer level returns nothing
            call EruptionHelix_Spiral.onTorrentExplode(this, level)
        endmethod
        /*
        *   The code that will run when each torrent vanishes
        */

        static method onTorrentVanish takes Torrent this, integer level returns nothing
            call EruptionHelix_Spiral.onTorrentVanish(this, level)
        endmethod

        implement TorrentArrayConfiguration

    endstruct
    /*=====================================================================*/
    /*                     End of Spell Configuration                      */
    /*=====================================================================*/

    /*
    *   Just a simple spell modifier:
    *   Sets the target point to <spellRadius()> units in front of the caster
    */

    public struct SpellModifier extends array
        private static method onSpellEvent takes nothing returns nothing
            local real angle
            local real radius
            if GetEventSpellAbilityId() == ABILITY_ID then
                set angle = GetUnitFacing(GetEventSpellCaster())*bj_DEGTORAD
                set radius = SpellRadius(GetEventSpellLevel())
                call SpellOverridePointTargetParams(GetEventSpellLevel(), GetEventSpellCaster(), GetEventSpellTargetX() + radius*Cos(angle), GetEventSpellTargetY() + radius*Sin(angle))
            endif
        endmethod
        implement SpellEventGeneric
    endstruct


endlibrary
library TorrentArray /* v2.0.0


    */
requires /*

    */
TorrentSystem                 /*  https://www.hiveworkshop.com/threads/torrentsystem-v2-0b.305301/
    */
SpellEvent                    /*  https://www.hiveworkshop.com/threads/spellevent.301895/
    */
SpellCloner                   /*  https://www.hiveworkshop.com/threads/324157/
    */
Table                         /*  https://www.hiveworkshop.com/threads/snippet-new-table.188084/

    */
optional WorldBounds          /*  https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
    */
optional ResourcePreloader    /*  https://www.hiveworkshop.com/threads/snippet-resource-preloader.287358/


    Resource Link: https://www.hiveworkshop.com/threads/291207/

    |---------|
    | Credits |
    |---------|

        - AGD (Author; TorrentSystem, SpellEvent, SpellCloner, ResourcePreloader)
        - Bribe (Table)
        - Nestharus (WorldBounds)


    |-----------|
    | Importing |
    |-----------|

        - Import the dummy.mdx model to your map
        - Copy the dummy unit and the Torrent ability in Object Editor
        - Copy the trigger category containing this script and its dependencies
        - Configure the spell's data in the default configuration library or alternatively,
          make your own one


    |--------------------|
    | Configuration Tips |
    |--------------------|

        The configuration allows you to do many possible tricks. One of these is that it
        allows you to configure the shape of the torrent array.

        1. Linear Torrent Array

            This is the default setup of the configuration in the Geysers sample library. If
            (TARGET_IS_CENTER) is true, the location of the first torrent is twice the
            distance between the caster and the target ground, else, the location is exactly
            the target ground.

            Conditions:

            - torrentCount(level) > 1
            - torrentPositionOffset(level) != 0.00
            - torrentPositionAngleDeflection(level) == 0.00

        2. Circular Torrent Array

            If (TARGET_IS_CENTER) is true, the center of the circle is the target ground,
            else, the caster's position.

            Conditions:

            - torrentCount(level) > 1
            - torrentPositionOffset(level) == 0.00
            - torrentPositionAngleDeflection(level) == 360.00/torrentCount(level)

        3. Spiral Torrent Array

            If (TARGET_IS_CENTER) is true, the center of the spiral is the target ground,
            else, the caster's position.

            Conditions:

            - torrentCount(level) > 1
            - torrentPositionOffset(level) != 0.00
            - torrentPositionAngleDeflection(level) != 0.00


    |-------------------|
    | Configuration API |
    |-------------------|

        - Each new configuration must be inside a struct
        - If you use a private struct inside a library, make sure to make that library
          require this library to ensure proper initialization order

        struct YourSpellConfiguration extends array
            /*
            *   Activation spell's rawcode
            */

            static constant integer SPELL_ID
            /*
            *   Determines the type of spell event (See SpellEvent library for
            *   more options)
            */

            static constant integer SPELL_EVENT_TYPE
            /*
            *   Determines if the tossed units are stunned or not (Normally,
            *   you should set this to true)
            */

            static constant boolean PAUSE_TOSSED_TARGETS
            /*
            *   Determines if the toss unit feature is enabled or not
            */

            static constant boolean TOSS_TARGETS
            /*
            *   Determines if the torrent borders will be hidden from the
            *   enemies' vision
            */

            static constant boolean BLIND_ENEMIES
            /*
            *   If true, the target ground's location will serve as the basis
            *   for where the location of the first torrent will deviate from
            *   but if false, the caster's location will serve as the basis
            *   of deviation instead. This might be good to enable if you're
            *   configuring a circular of a spiral array of torrents so that
            *   it will have its center/origin at the target location instead
            *   of the caster's.
            */

            static constant boolean TARGET_IS_CENTER
            /*
            *   Determines the attack type of the damage by the torrent
            */

            static constant attacktype ATTACK_TYPE
            /*
            *   Determines the damage type of the damage by the torrent
            */

            static constant damagetype DAMAGE_TYPE
            /*
            *   Determines the weapon type of the damage by the torrent
            */

            static constant weapontype WEAPON_TYPE
            /*
            *   Determines the model of the SFX when a torrent is set up
            */

            static constant string SUMMON_SFX_MODEL
            /*
            *   Determines the SFXModel used for the torrent
            */

            static constant string EXPLOSION_SFX_MODEL
            /*
            *   Determines the SFXModel used for the spell borders
            */

            static constant string BORDER_SFX_MODEL
            /*
            *   Determines the SFXModel used to attach to the tossed targets
            */

            static constant string TOSSED_TARGET_SFX_MODEL
            /*
            *   Determines the SFXModel used to attach to the affected units
            *   upon landing
            */

            static constant string LANDED_TARGET_SFX_MODEL
            /*
            *   Determines the attachment point for tossedTargetSfxModel
            */

            static constant string TOSSED_TARGET_SFX_ATTACH_POINT
            /*
            *   Determines the attachment point for landedTargetSfxModel
            */

            static constant string LANDED_TARGET_SFX_ATTACH_POINT
            /*
            *   Determines the animation played by the affected units when
            *   being thrown on air (Default value is an empty string)
            */

            static constant string TOSSED_TARGET_ANIMATION
            /*
            *   Determines the torrent count
            */

            static constant method torrentCount takes integer level returns integer
            /*
            *   Determines the delay of the first torrent's explosion after
            *   its appearance
            */

            static constant method explosionDelay takes integer level returns real
            /*
            *   Determines the time interval of the next torrent release
            */

            static constant method explosionInterval takes integer level returns real
            /*
            *   Determines the distance between the torrents along the axis of the
            *   line between the caster and the target
            */

            static constant method torrentPositionOffset takes integer level returns real
            /*
            *   Determines the angle deflection of the torrents' location
            *   with respect to the caster as the origin
            */

            static constant method torrentPositionAngleDeflection takes integer level returns real
            /*
            *   Determines the duration of the torrent
            */

            static constant method tossDuration takes integer level returns real
            /*
            *   Determines the radius area affected by torrent
            */

            static constant method torrentRadius takes integer level returns real
            /*
            *   Determines the damage done by the torrent upon its appearance
            */

            static constant method explosionDamage takes integer level returns real
            /*
            *   Determines the damage dealt per second to the affected units
            */

            static constant method damagePerSecond takes integer level returns real
            /*
            *   Determines the maximum height of the thrown units
            */

            static constant method torrentHeight takes integer level returns real
            /*
            *   Determines the distance of the border special effect from
            *   each other
            */

            static constant method borderPositionOffset takes integer level returns real
            /*
            *   The code for filtering target units
            */

            static method targetFilter takes unit target, unit caster returns boolean
            /*-----------------------------------------------------------------*/
            /*                 Optional Configuration Methods                  */
            /*-----------------------------------------------------------------*/
            /*
            *   The code that will run upon each torrent's explosion
            */

            static method onTorrentExplode takes Torrent this, integer level returns nothing
            /*
            *   The code that will run when each torrent vanishes
            */

            static method onTorrentVanish takes Torrent this, integer level returns nothing

            implement TorrentArrayConfiguration

        endstruct


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

    private keyword Init

    private struct TorrentList extends array
        readonly Torrent torrent
        readonly thistype prev
        readonly thistype next
        private static thistype array stack

        private static method allocate takes nothing returns thistype
            local thistype node = stack[0]
            if stack[node] == 0 then
                debug if node == JASS_MAX_ARRAY_SIZE - 2 then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, "|cffff0000ERROR:|R TorrentList.allocate(): Overflow")
                    debug return 0
                debug endif
                set node = node + 1
                set stack[0] = node
            else
                set stack[0] = stack[node]
                set stack[node] = 0
            endif
            return node
        endmethod
        private method deallocate takes nothing returns nothing
            set stack[this] = stack[0]
            set stack[0] = stack[this]
        endmethod

        method insert takes Torrent torrent returns nothing
            local thistype node = allocate()
            local thistype next = this.next
            set node.prev = this
            set node.next = next
            set next.prev = node
            set this.next = node
            set node.torrent = torrent
        endmethod
        method remove takes nothing returns Torrent
            set this.next.prev = this.prev
            set this.prev.next = this.next
            call this.deallocate()
            return this.torrent
        endmethod

        static method create takes nothing returns thistype
            local thistype node = allocate()
            set node.prev = node
            set node.next = node
            return node
        endmethod
        method destroy takes nothing returns nothing
            set this.prev = 0
            set this.next = 0
            call this.deallocate()
        endmethod
    endstruct

    private struct TorrentArray extends array

        implement SpellClonerHeader

        private static constant real SPELL_PERIOD = 1.00 // Not used

        integer torrentCount
        boolean pauseTossedTargets
        boolean tossTargets
        boolean blindEnemies
        boolean targetIsCenter
        real explosionDelay
        real explosionInterval
        real torrentPositionOffset
        real torrentPositionAngleDeflection
        real tossDuration
        real torrentRadius
        real explosionDamage
        real damagePerSecond
        real torrentHeight
        real borderPositionOffset
        attacktype attackType
        damagetype damageType
        weapontype weaponType
        boolexpr filterExpr
        string summonSfxModel
        string explosionSfxModel
        string borderSfxModel
        string tossedTargetSfxModel
        string landedTargetSfxModel
        string tossedTargetSfxAttachPoint
        string landedTargetSfxAttachPoint
        string tossedTargetAnimation
        boolexpr torrentExplosionHandler
        boolexpr torrentVanishHandler

        static if not LIBRARY_WorldBounds then
            private static real minX
            private static real maxX
            private static real minY
            private static real maxY
        endif

        private static key table
        private static trigger evaluator = CreateTrigger()
        readonly static thistype triggerInstance = 0
        readonly integer level

        private static method getBoundedValue takes real value, real min, real max returns real
            if value < min then
                return min
            elseif value > max then
                return max
            endif
            return value
        endmethod

        private static method onPeriod takes nothing returns nothing
            local timer expired = GetExpiredTimer()
            local TorrentList node = Table(table)[-GetHandleId(expired)]
            set triggerInstance = node
            call Torrent(node.next.remove()).explode(thistype(node).tossTargets)
            set triggerInstance = 0
            if node.next == node then
                /* If the last Torrent in the list has exploded, destroy
                   this spell instance.                                  */

                call Table(table).remove(-GetHandleId(expired))
                call node.destroy()
                call PauseTimer(expired)
                call DestroyTimer(expired)
            endif
            set expired = null
        endmethod

        private static method beginChainExplosion takes nothing returns nothing
            local timer expired = GetExpiredTimer()
            call TimerStart(expired, thistype(Table(table)[-GetHandleId(expired)]).explosionInterval, true, function thistype.onPeriod)
            set expired = null
        endmethod

        private method onSpellEffect takes nothing returns nothing
            local timer spellTimer      = CreateTimer()
            local real casterX          = GetUnitX(GetEventSpellCaster())
            local real casterY          = GetUnitY(GetEventSpellCaster())
            local real dx               = GetEventSpellTargetX() - casterX
            local real dy               = GetEventSpellTargetY() - casterY
            local real ds               = SquareRoot(dx*dx + dy*dy)
            local real angle            = 0
            local real arcTan
            local real distance
            local real centerX
            local real centerY
            local real x
            local real y
            local integer i
            local integer borders
            local Torrent torrent
            set this.level = GetEventSpellLevel()
            if this.borderPositionOffset > 0.00 then
                set borders = R2I(2.00*this.torrentRadius*bj_PI/this.borderPositionOffset)
            else
                set borders = 0
            endif
            if this.blindEnemies and IsUnitEnemy(GetEventSpellCaster(), GetLocalPlayer()) then
                /* Hide the torrent borders from enemies' vision */
                set this.borderSfxModel = ""
            endif
            if ds == 0.00 then
                set arcTan = GetUnitFacing(GetEventSpellCaster())*bj_DEGTORAD
            else
                set arcTan = Atan2(dy, dx)
            endif
            /* Setup torrents in their designated location */
            loop
                exitwhen this.torrentCount == 0
                set this.torrentCount = this.torrentCount - 1
                set angle = arcTan + (this.torrentPositionAngleDeflection*this.torrentCount)*bj_DEGTORAD
                set distance = ds + this.torrentPositionOffset*this.torrentCount
                if this.targetIsCenter then
                    set centerX = GetEventSpellTargetX() + distance*Cos(angle)
                    set centerY = GetEventSpellTargetY() + distance*Sin(angle)
                else
                    set centerX = casterX + distance*Cos(angle)
                    set centerY = casterY + distance*Sin(angle)
                endif
                /* Implement map bounds */
                static if LIBRARY_WorldBounds then
                    set centerX = getBoundedValue(centerX, WorldBounds.minX, WorldBounds.maxX)
                    set centerY = getBoundedValue(centerY, WorldBounds.minY, WorldBounds.maxY)
                else
                    set centerX = getBoundedValue(centerX, minX, maxX)
                    set centerY = getBoundedValue(centerY, minY, maxY)
                endif
                /* Create the torrent and setup the neccessary data */
                set torrent                             = Torrent.create(centerX, centerY, this.torrentRadius, borders, this.explosionSfxModel, this.borderSfxModel, this.summonSfxModel)
                set torrent.caster                      = GetEventSpellCaster()
                set torrent.explosionDamage             = this.explosionDamage
                set torrent.damagePerSecond             = this.damagePerSecond
                set torrent.height                      = this.torrentHeight
                set torrent.duration                    = this.tossDuration
                set torrent.attackType                  = this.attackType
                set torrent.damageType                  = this.damageType
                set torrent.weaponType                  = this.weaponType
                set torrent.filterExpr                  = this.filterExpr
                set torrent.pauseTargets                = this.pauseTossedTargets
                set torrent.tossedTargetSfxModel        = this.tossedTargetSfxModel
                set torrent.landedTargetSfxModel        = this.landedTargetSfxModel
                set torrent.tossedTargetSfxAttachPoint  = this.tossedTargetSfxAttachPoint
                set torrent.landedTargetSfxAttachPoint  = this.landedTargetSfxAttachPoint
                set torrent.tossedTargetAnimation       = this.tossedTargetAnimation
                call torrent.registerExplosionHandler(this.torrentExplosionHandler)
                call torrent.registerVanishHandler(this.torrentVanishHandler)
                call TorrentList(this).insert(torrent)
            endloop
            set Table(table)[-GetHandleId(spellTimer)] = this
            call TimerStart(spellTimer, RMaxBJ(this.explosionDelay - this.explosionInterval, 0.00), false, function thistype.beginChainExplosion)
            set spellTimer = null
        endmethod

        /* After casting, determine the ability casted and then loop
           through all its configuration sets                       */

        private static method onSpellStart takes nothing returns thistype
            local thistype node = TorrentList.create()
            call node.initSpellConfiguration(GetEventSpellAbilityId())
            /* Proceed if the torrent count is 1 or more */
            if node.torrentCount > 0 then
                call node.onSpellEffect()
            else
                call TorrentList(node).destroy()
            endif
            return 0
        endmethod
        private method onSpellPeriodic takes nothing returns boolean
            return false
        endmethod
        private method onSpellEnd takes nothing returns nothing
        endmethod

        implement SpellEventEx
        implement SpellClonerFooter

        static if not LIBRARY_WorldBounds then
            private static method init takes nothing returns nothing
            /* Setup Map Bounds for the spell */
                local rect bounds = GetWorldBounds()
                set minX = GetRectMinX(bounds)
                set minY = GetRectMinY(bounds)
                set maxX = GetRectMaxX(bounds)
                set maxY = GetRectMaxY(bounds)
                call RemoveRect(bounds)
                set bounds = null
            endmethod
            implement Init
        endif

    endstruct

    static if not LIBRARY_WorldBounds then
        private module Init
            private static method onInit takes nothing returns nothing
                call init()
            endmethod
        endmodule
    endif

    private function InitConfiguration takes integer torrentCount, boolean pauseTossedTargets, boolean tossTargets, boolean blindEnemies, boolean targetIsCenter/*
    */
, attacktype attackType, damagetype damageType, weapontype weaponType, string summonSfxModel, string explosionSfxModel, string borderSfxModel, string tossedTargetSfxModel/*
    */
, string landedTargetSfxModel, string tossedTargetSfxAttachPoint, string landedTargetSfxAttachPoint, string tossedTargetAnimation, real explosionDelay, real explosionInterval/*
    */
, real torrentPositionOffset, real torrentPositionAngleDeflection, real tossDuration, real torrentRadius, real explosionDamage, real damagePerSecond/*
    */
, real torrentHeight, real borderPositionOffset, code targetFilterFunc, code torrentExplosionHandler, code torrentVanishHandler returns nothing
        local TorrentArray node = TorrentArray.configuredInstance
        set node.torrentCount                       = IMaxBJ(0, torrentCount)
        set node.pauseTossedTargets                 = pauseTossedTargets
        set node.tossTargets                        = tossTargets
        set node.blindEnemies                       = blindEnemies
        set node.targetIsCenter                     = targetIsCenter
        set node.attackType                         = attackType
        set node.damageType                         = damageType
        set node.weaponType                         = weaponType
        set node.summonSfxModel                     = summonSfxModel
        set node.explosionSfxModel                  = explosionSfxModel
        set node.borderSfxModel                     = borderSfxModel
        set node.tossedTargetSfxModel               = tossedTargetSfxModel
        set node.landedTargetSfxModel               = landedTargetSfxModel
        set node.tossedTargetSfxAttachPoint         = tossedTargetSfxAttachPoint
        set node.landedTargetSfxAttachPoint         = landedTargetSfxAttachPoint
        set node.tossedTargetAnimation              = tossedTargetAnimation
        set node.explosionDelay                     = explosionDelay
        set node.explosionInterval                  = explosionInterval
        set node.torrentPositionOffset              = torrentPositionOffset
        set node.torrentPositionAngleDeflection     = torrentPositionAngleDeflection
        set node.tossDuration                       = tossDuration
        set node.torrentRadius                      = torrentRadius
        set node.explosionDamage                    = explosionDamage
        set node.damagePerSecond                    = damagePerSecond
        set node.torrentHeight                      = torrentHeight
        set node.borderPositionOffset               = borderPositionOffset
        set node.filterExpr                         = Filter(targetFilterFunc)
        set node.torrentExplosionHandler            = Filter(torrentExplosionHandler)
        set node.torrentVanishHandler               = Filter(torrentVanishHandler)
    endfunction

    /*
    *   Configuration Module
    *   - Transforms a struct into a TorrentArray configuration
    */

    module TorrentArrayConfiguration

        private static method targetFilterFunc takes nothing returns boolean
            return targetFilter(GetFilterUnit(), Torrent.triggerInstance.caster)
        endmethod

        private static method explosionHandler takes nothing returns nothing
            static if thistype.onTorrentExplode.exists then
                call onTorrentExplode(Torrent.triggerInstance, TorrentArray.triggerInstance.level)
            endif
        endmethod

        private static method vanishHandler takes nothing returns nothing
            static if thistype.onTorrentVanish.exists then
                call onTorrentVanish(Torrent.triggerInstance, TorrentArray.triggerInstance.level)
            endif
        endmethod

        private static method configurationFunction takes nothing returns nothing
            local integer level = GetEventSpellLevel()
            call InitConfiguration(/*
                */
torrentCount(level)                   , PAUSE_TOSSED_TARGETS              , TOSS_TARGETS,/*
                */
BLIND_ENEMIES                         , TARGET_IS_CENTER                  , ATTACK_TYPE, DAMAGE_TYPE,/*
                */
WEAPON_TYPE                           , SUMMON_SFX_MODEL                  , EXPLOSION_SFX_MODEL,/*
                */
BORDER_SFX_MODEL                      , TOSSED_TARGET_SFX_MODEL           , LANDED_TARGET_SFX_MODEL,/*
                */
TOSSED_TARGET_SFX_ATTACH_POINT        , LANDED_TARGET_SFX_ATTACH_POINT    , TOSSED_TARGET_ANIMATION,/*
                */
explosionDelay(level)                 , explosionInterval(level)          , torrentPositionOffset(level),/*
                */
torrentPositionAngleDeflection(level) , tossDuration(level)               , torrentRadius(level),/*
                */
explosionDamage(level)                , damagePerSecond(level)            , torrentHeight(level),/*
                */
borderPositionOffset(level)           , function thistype.targetFilterFunc, function thistype.explosionHandler, function thistype.vanishHandler)
        endmethod

        private static method onInit takes nothing returns nothing
            call TorrentArray.create(thistype.typeid, SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.configurationFunction)
            static if LIBRARY_ResourcePreloader then
                call PreloadEffect(SUMMON_SFX_MODEL)
                call PreloadEffect(EXPLOSION_SFX_MODEL)
                call PreloadEffect(BORDER_SFX_MODEL)
                call PreloadEffect(TOSSED_TARGET_SFX_MODEL)
                call PreloadEffect(LANDED_TARGET_SFX_MODEL)
            endif
        endmethod

    endmodule


endlibrary
TorrentArray library requirements
library TorrentSystem /* v2.1.0


    */
uses /*

    */
StunSystem               /*  https://www.hiveworkshop.com/threads/196749/
    */
Table                    /*  https://www.hiveworkshop.com/threads/188084/
    */
UnitDex                  /*  https://www.hiveworkshop.com/threads/248209/

    */
optional DummyRecycler   /*  https://www.hiveworkshop.com/threads/277659/
    */
optional AutoFly         /*  https://www.hiveworkshop.com/threads/249422/
    */
optional WorldBounds     /*  https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j


    Resource Link: https://www.hiveworkshop.com/threads/305301/

    */
//! novjass

    |=========|
    | Credits |
    |=========|
    /*
        Author:
            - AGD
        Dependendies:
            - iAyanami (StunSystem)
            - Nestharus (WorldBounds)
            - Bribe (Table)
            - Flux (DummyRecycler)
            - TriggerHappy (UnitDex, AutoFly)
        Other:
            - Aniki (Finding bugs)
            - Vexorian (dummy.mdx)
            - IceFrog (Original Concept)

    */

    |===========|
    | Importing |
    |===========|
    /*
        1. Import the dummy.mdx model into your map.
        2. Copy the "TorrentSystem" library into your map.
        3. Configure the necessary data in the system configuration below.

        Note:
            Torrent instances is based on the index of dummy units. You should make sure that dummy units used
            by this system are not excluded from unit indexers.

    */

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

        struct Torrent/*

          */
readonly static Torrent triggerInstance /*  The exploding/vanishing torrent instance (Only use inside a torrent event handler)
          */
readonly group targetGroup              /*  The group containing the targets of the <triggerInstance> (Only use inside a torrent event handler)
          */
unit caster                             /*  The unit considered as the owner of the Torrent
          */
real height                             /*  Height of the tossed targets
          */
real duration                           /*  Duration of the Torrent's explosion
          */
real explosionDamage                    /*  Initial damage caused by the Torrent
          */
real damagePerSecond                    /*  Damage dealt per second to the tossed targets
          */
attacktype attackType                   /*  AttackType of the damage from the Torrent
          */
damagetype damageType                   /*  DamageType of the damage from the Torrent
          */
weapontype weaponType                   /*  WeaponType of the damage from the Torrent
          */
boolexpr filterExpr                     /*  The boolexpr for filtering target units
          */
boolean pauseTargets                    /*  Determines if tossed targets are paused (Normally, you should set this to true)
          */
string targetStaticSfxModel             /*  Sfx model attached to the targets while being tossed
          */
string targetStaticSfxAttachPoint       /*  Sfx model attachment point for <targetStaticSfxModel>
          */
string landedTargetSfxModel             /*  Sfx model attached to the landed targets
          */
string landedTargetSfxAttachPoint       /*  Sfx model attachment point for <landedTargetSfxModel>
          */
string tossedTargetAnimation            /*  Animation played by the tossed units

          */
static method create takes real centerX, real centerY, real radius, integer borderCount, string model, string borderSfxModel, string onSummonSfxModel returns thistype/*
            - Creates a new Torrent instance
            - Arguments:
                * centerX - The x-coordinate of the Torrent's center
                * centerY - The y-coordinate of the Torrent's center
                * radius - The radius of the Torrent
                * borderCount - number of special effects created in the Torrent's circumference
                * model - Sfx model for the Torrent's explosion
                * borderSfxModel - Sfx model for the Torrent's borders
                * onSummonSfxModel - Sfx model for the Torrent's appearance/creation

          */
method destroy takes nothing returns nothing/*
            - Destroys a Torrent instance

          */
method explode takes boolean tossUnits returns nothing/*
            - Explodes a Torrent instance, dealing initial damage to the units within its area, and tossing them while damaging them over time if <tossUnits>
              is true
            - The Torrent instance is then destroyed

          */
method explodeTimed takes real delay, boolean tossUnits returns nothing/*
            - Explodes a torrent after a certain delay

          */
method registerExplosionHandler takes boolexpr expr returns nothing/*
            - Registers a boolexpr that will run upon the Torrent's explosion (when explode() is called)

          */
method registerVanishHandler takes boolexpr expr returns nothing/*
            - Registers a boolexpr that will run upon the Torrent's disappearance, after tossed units are landed
            - This will not run when <tossUnits> is false

          */
method registerExplosionHandlerFunc takes code handler returns nothing/*
          */
method registerVanishHandlerFunc takes code handler returns nothing/*
            - Same to the methods above but takes code instead

          */
method clearExplosionHandlers takes nothing returns nothing/*
          */
method clearVanishHandlers takes nothing returns nothing/*


    */
//! endnovjass


    native UnitAlive takes unit u returns boolean

    /*
    This serves as the default target filter flags whenever a new
    Torrent instance is created. You can delete this module if
    it's not needed. By default, all units except the caster are
    allowed as targets.                                         */

    private module DefaultTargetFilters
        private static method targetFilter takes nothing returns boolean
            return GetFilterUnit() != Torrent.triggerInstance.caster
        endmethod
    endmodule

    private module TorrentModule

        /*================ SYSTEM CONFIGURATION ================*/
        /*
        The static timer timeout in real seconds
        Default value: 1.00/32.00 (32 FPS)                      */

        static constant real TIMEOUT = 0.031250000
        /*
        The delay before dummy units are removed
        Make sure there's enough time for the special
        effects to play its death animation
        Default value: 2.00                                     */

        static constant real DUMMY_DURATION = 2.00
        /*
        The owner of the dummy units
        Default value: Player(14)                               */

        static constant player DUMMY_OWNER = Player(14)
        /*
        The rawcode of the dummy units                          */

        static constant integer DUMMY_ID = 'h000'
        /*================ END OF CONFIGURATION ================*/

        static if not LIBRARY_WorldBounds then
            private static real mapMinX
            private static real mapMaxX
            private static real mapMinY
            private static real mapMaxY
        endif

        private static TableArray table
        private static timer staticTimer = CreateTimer()
        private static group tempGroup = CreateGroup()
        private static group refreshGroup
        private static boolean clearGroup
        private trigger onExplodeTrigger
        private trigger onVanishTrigger
        private integer borderCount
        private real radius
        private real centerX
        private real centerY
        private real elapsed
        private real damagePerInterval
        private group tossedGroup
        private string model
        private string borderModel
        private thistype prev
        private thistype next
        readonly static thistype triggerInstance = 0
        readonly group targetGroup
        boolexpr filterExpr
        boolean pauseTargets
        real height
        real duration
        real explosionDamage
        attacktype attackType
        damagetype damageType
        weapontype weaponType
        string tossedTargetSfxModel
        string landedTargetSfxModel
        string tossedTargetSfxAttachPoint
        string landedTargetSfxAttachPoint
        string tossedTargetAnimation
        unit caster

        private static method getBoundedValue takes real value, real min, real max returns real
            if value < min then
                return min
            elseif value > max then
                return max
            endif
            return value
        endmethod

        private method evaluate takes trigger whichTrigger returns nothing
            local thistype prevInstance = triggerInstance
            set triggerInstance = this
            call TriggerEvaluate(whichTrigger)
            set triggerInstance = prevInstance
        endmethod

        private method destroyBorders takes nothing returns nothing
            local integer count
            if .borderModel != null then
                set .borderModel = null
                /* Iterate through all border special effects and destroy
                   each one                                               */

                set count = .borderCount
                loop
                    exitwhen count == 0
                    call DestroyEffect(table[this].effect[count])
                    set count = count - 1
                endloop
            endif
            call DestroyEffect(table[this].effect[0])
            set .borderCount = 0
        endmethod

        method destroy takes nothing returns nothing
            /* Check if this torrent tosses its targets
               If yes, make sure it has finished its business
               before destroying                                */

            if .tossedGroup == null or .elapsed >= .duration then
                /* If the torrent borders haven't been destroyed yet, do so now */
                call .destroyBorders()
                call table[this].flush()
                static if LIBRARY_DummyRecycler then
                    call DummyAddRecycleTimer(GetUnitById(this), DUMMY_DURATION)
                else
                    call UnitApplyTimedLife(GetUnitById(this), 'BTLF', DUMMY_DURATION)
                endif
                call DestroyTrigger(.onExplodeTrigger)
                call DestroyTrigger(.onVanishTrigger)
                call DestroyGroup(.targetGroup)
                call DestroyGroup(.tossedGroup)
                set .pauseTargets = false
                set .caster = null
                set .centerX = 0.00
                set .centerY = 0.00
                set .radius = 0.00
                set .height = 0.00
                set .duration = 0.00
                set .explosionDamage = 0.00
                set .damagePerSecond = 0.00
                set .attackType = null
                set .damageType = null
                set .weaponType = null
                set .model = null
                set .tossedTargetSfxModel = null
                set .landedTargetSfxModel = null
                set .tossedTargetSfxAttachPoint = null
                set .landedTargetSfxAttachPoint = null
                set .filterExpr = null
                set .onExplodeTrigger = null
                set .onVanishTrigger = null
                set .targetGroup = null
                if .tossedGroup != null then
                    call DestroyGroup(.tossedGroup)
                    set .tossedGroup = null
                    set .elapsed = 0.00
                    set .next.prev = .prev
                    set .prev.next = .next
                    if thistype(0).next == 0 then
                        call PauseTimer(staticTimer)
                    endif
                endif
            endif
        endmethod

        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next
            local integer id
            local effect sfx
            local group g
            local unit u
            local unit dummy
            local real time
            local real speed
            local player owner
            local boolean recreate
            local boolean staticSfx
            /* Iterate through all currently exploding torrents and
               do actions                                           */

            loop
                exitwhen this == 0
                set .elapsed = .elapsed + TIMEOUT
                set owner = GetOwningPlayer(.caster)
                if .elapsed < .duration then
                    set time = 2.00*.elapsed/.duration
                    set speed = 2.00*.height
                    set dummy = GetUnitById(this)
                    /* Adjust units' fly height and deal damage over time */
                    loop
                        set u = FirstOfGroup(.tossedGroup)
                        exitwhen u == null
                        call GroupRemoveUnit(.tossedGroup, u)
                        call GroupAddUnit(tempGroup, u)
                        if speed > 0.00 then
                            call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u) + speed*time - 0.5*speed*time*time, 0)
                        endif
                        if .caster == null then
                            call UnitDamageTarget(dummy, u, .damagePerInterval, true, false, .attackType, .damageType, .weaponType)
                        else
                            call UnitDamageTarget(.caster, u, .damagePerInterval, true, false, .attackType, .damageType, .weaponType)
                        endif
                        if .tossedTargetSfxModel != null and not UnitAlive(u) then
                            set id = GetHandleId(u)
                            set sfx = table[this].effect[id]
                            if sfx != null then
                                call DestroyEffect(sfx)
                                call table[this].effect.remove(id)
                            endif
                        endif
                        if .pauseTargets then
                            call Stun.apply(u, 0.01, false)
                        endif
                    endloop
                    set g = tempGroup
                    set tempGroup = .tossedGroup
                    set .tossedGroup = g
                    set g = null
                    set dummy = null
                else
                    set recreate = .onVanishTrigger != null and .targetGroup == null
                    if recreate then
                        set .targetGroup = CreateGroup()
                    endif
                    loop
                        set u = FirstOfGroup(.tossedGroup)
                        exitwhen u == null
                        call GroupRemoveUnit(.tossedGroup, u)
                        if recreate then
                            call GroupAddUnit(.targetGroup, u)
                        endif
                        if .landedTargetSfxModel != null then
                            call DestroyEffect(AddSpecialEffectTarget(.landedTargetSfxModel, u, .landedTargetSfxAttachPoint))
                        endif
                        if .tossedTargetSfxModel != null then
                            set sfx = table[this].effect[GetHandleId(u)]
                            if sfx != null then
                                call DestroyEffect(sfx)
                                set sfx = null
                            endif
                        endif
                        /* Reset tossed units' fly height to default upon landing */
                        call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0.00)
                    endloop
                    if recreate then
                        /* Fire torrent disappearance handler */
                        call .evaluate(.onVanishTrigger)
                    endif
                    call .destroy()
                endif
                set this = .next
            endloop
        endmethod

        method explode takes boolean tossUnits returns nothing
            local unit dummy = GetUnitById(this)
            local player owner = GetOwningPlayer(.caster)
            local boolean evaluateExplosion
            local thistype prevInstance
            local unit u
            call .destroyBorders()
            set tossUnits = tossUnits and .duration >= 2.00*TIMEOUT
            if tossUnits then
                /* Add this torrent instance to the periodic loop actions */
                set .next = 0
                set .prev = thistype(0).prev
                set thistype(0).prev = this
                set .prev.next = this
                set .tossedGroup = CreateGroup()
                if .prev == 0 then
                    call TimerStart(staticTimer, TIMEOUT, true, function thistype.periodic)
                endif
            endif
            if .model != null then
                /* Creating torrent explosion SFX */
                call DestroyEffect(AddSpecialEffectTarget(.model, dummy, "origin"))
            endif
            set evaluateExplosion = .onExplodeTrigger != null
            if evaluateExplosion then
                set .targetGroup = CreateGroup()
            endif
            set prevInstance = triggerInstance
            set triggerInstance = this
            call GroupEnumUnitsInRange(tempGroup, .centerX, .centerY, .radius, .filterExpr)
            set triggerInstance = prevInstance
            loop
                set u = FirstOfGroup(tempGroup)
                exitwhen u == null
                call GroupRemoveUnit(tempGroup, u)
                if evaluateExplosion then
                    call GroupAddUnit(.targetGroup, u)
                endif
                if tossUnits then
                    call GroupAddUnit(.tossedGroup, u)
                    static if not LIBRARY_AutoFly then
                        if UnitAddAbility(u, 'Arav') and UnitRemoveAbility(u, 'Arav') then
                        endif
                    endif
                    call SetUnitAnimation(u, .tossedTargetAnimation)
                    if .tossedTargetSfxModel != null then
                        set table[this].effect[GetHandleId(u)] = AddSpecialEffectTarget(.tossedTargetSfxModel, u, .tossedTargetSfxAttachPoint)
                    endif
                elseif .tossedTargetSfxModel != null then
                    call DestroyEffect(AddSpecialEffectTarget(.tossedTargetSfxModel, u, .tossedTargetSfxAttachPoint))
                endif
                if .caster == null then
                    call UnitDamageTarget(dummy, u, .explosionDamage, true, false, .attackType, .damageType, .weaponType)
                else
                    call UnitDamageTarget(.caster, u, .explosionDamage, true, false, .attackType, .damageType, .weaponType)
                endif
            endloop
            if evaluateExplosion then
                /* Fire the torrent's explosion handler */
                call .evaluate(.onExplodeTrigger)
                call GroupClear(.targetGroup)
            endif
            if not tossUnits then
                /* If <tossUnits> is false, destroy instantly */
                call .destroy()
            endif
            set owner = null
            set dummy = null
        endmethod

        private static method onExplodeTimerExpire takes nothing returns nothing
            local timer expired = GetExpiredTimer()
            local integer timerId = GetHandleId(expired)
            local thistype node = table[0][timerId]
            if node > 0 then
                call node.explode(true)
            else
                call thistype(-node).explode(false)
            endif
            call table[0].remove(timerId)
            call DestroyTimer(expired)
            set expired = null
        endmethod

        method explodeTimed takes real delay, boolean tossUnits returns nothing
            local timer t = CreateTimer()
            if tossUnits then
                set table[0][GetHandleId(t)] = this
            else
                set table[0][GetHandleId(t)] = -this
            endif
            call TimerStart(t, delay, false, function thistype.onExplodeTimerExpire)
            set t = null
        endmethod

        static method create takes real centerX, real centerY, real radius, integer borderCount, string model, string borderSfxModel, string onSummonSfxModel returns thistype
            static if LIBRARY_DummyRecycler then
                local unit dummy = GetRecycledDummyAnyAngle(centerX, centerY, 0)
            else
                local unit dummy = CreateUnit(DUMMY_OWNER, DUMMY_ID, centerX, centerY, 0)
            endif
            local thistype this = GetUnitId(dummy)
            local real deltaAngle
            local real angle
            /* Initialize default target filters */
            static if thistype.targetFilter.exists then
                set .filterExpr = Filter(function thistype.targetFilter)
            endif
            set .pauseTargets   = true
            set .centerX        = centerX
            set .centerY        = centerY
            set .borderCount    = borderCount
            set .radius         = radius
            set .model          = model
            set .borderModel    = borderSfxModel
            if borderSfxModel != null and borderCount > 0 then
                /* Adjust the radius a bit for the border effects' location
                   to fit with the radius                                   */

                set radius = radius - 20
                set deltaAngle = 2.00*bj_PI/borderCount
                loop
                    exitwhen borderCount == 0
                    set angle = deltaAngle*borderCount
                    /* Implement map bounds */
                    static if LIBRARY_WorldBounds then
                        set table[this].effect[borderCount] = AddSpecialEffect(borderSfxModel, getBoundedValue(centerX + radius*Cos(angle), WorldBounds.minX, WorldBounds.maxX), getBoundedValue(centerY + radius*Sin(angle), WorldBounds.minY, WorldBounds.maxY))
                    else
                        set table[this].effect[borderCount] = AddSpecialEffect(borderSfxModel, getBoundedValue(centerX + radius*Cos(angle), minX, maxX), getBoundedValue(centerY + radius*Sin(angle), minY, maxY))
                    endif
                    set borderCount = borderCount - 1
                endloop
            endif
            call SetUnitScale(dummy, radius*0.01 , 0, 0)
            set table[this].effect[0] = AddSpecialEffectTarget(onSummonSfxModel, dummy, "origin")
            set .caster = dummy
            set dummy = null
            return this
        endmethod

        method registerExplosionHandler takes boolexpr expr returns nothing
            if expr != null then
                if .onExplodeTrigger == null then
                    set .onExplodeTrigger = CreateTrigger()
                endif
                call TriggerAddCondition(.onExplodeTrigger, expr)
            endif
        endmethod

        method registerVanishHandler takes boolexpr expr returns nothing
            if expr != null then
                if .onVanishTrigger == null then
                    set .onVanishTrigger = CreateTrigger()
                endif
                call TriggerAddCondition(.onVanishTrigger, expr)
            endif
        endmethod

        method registerExplosionHandlerFunc takes code handler returns nothing
            call .registerExplosionHandler(Filter(handler))
            return
        endmethod

        method registerVanishHandlerFunc takes code handler returns nothing
            call .registerVanishHandler(Filter(handler))
            return
        endmethod

        method clearExplosionHandlers takes nothing returns nothing
            call DestroyTrigger(.onExplodeTrigger)
            set .onExplodeTrigger = null
        endmethod

        method clearVanishHandlers takes nothing returns nothing
            call DestroyTrigger(.onVanishTrigger)
            set .onVanishTrigger = null
        endmethod

        method operator damagePerSecond= takes real dps returns nothing
            set .damagePerInterval = dps*TIMEOUT
        endmethod

        method operator damagePerSecond takes nothing returns real
            return .damagePerInterval/TIMEOUT
        endmethod

        /* Clean groups from removed units which is more efficient than
           regularly cleaning groups per tossing period                    */

        private static method onDeindex takes nothing returns boolean
            local thistype this = thistype(0).next
            local unit deindexed = GetIndexedUnit()
            loop
                exitwhen this == 0
                call GroupRemoveUnit(.tossedGroup, deindexed)
                set this = .next
            endloop
            set deindexed = null
            return false
        endmethod

        private static method onInit takes nothing returns nothing
            /* Setup map bounds */
            static if not LIBRARY_WorldBounds then
                local rect bounds = GetWorldBounds()
                set mapMinX = GetRectMinX(bounds)
                set mapMinY = GetRectMinY(bounds)
                set mapMaxX = GetRectMaxX(bounds)
                set mapMaxY = GetRectMaxY(bounds)
                call RemoveRect(bounds)
                set bounds = null
            endif
            set table = TableArray[JASS_MAX_ARRAY_SIZE]
            call OnUnitDeindex(function thistype.onDeindex)
        endmethod

    endmodule

    struct Torrent extends array
        implement TorrentModule
    endstruct


endlibrary
library SpellEvent /* v1.4.3 https://www.hiveworkshop.com/threads/301895/


    */
uses /*

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


    */
//! novjass

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

        Core Features:
            1. Two-phase spell event handlers
            2. Ability to manually trigger a spell event
            3. Event response values overriding
            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.) Within the generic event handlers, you can override the event parameters. The change will only affect the
            ability-specific handlers.

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

    */


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

    */

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

      */
struct Spell extends array/*

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

        Event Responses:
          */
readonly static integer         ABILITY_ID      /*
          */
readonly static integer         EVENT_TYPE      /*
          */
readonly static integer         ORDER_TYPE      /*
          */
readonly static integer         LEVEL           /*
          */
readonly static player          TRIGGER_PLAYER  /*
          */
readonly static unit            TRIGGER_UNIT    /*
          */
readonly static unit            TARGET_UNIT     /*
          */
readonly static item            TARGET_ITEM     /*
          */
readonly static destructable    TARGET_DEST     /*
          */
readonly static real            TARGET_X        /*  Returns the x-coordinate of the caster if the spell is a 'No-target' ability
          */
readonly static real            TARGET_Y        /*  Returns the y-coordinate of the caster if the spell is a 'No-target' ability

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

        Methods:

          */
static method   operator []                     takes integer spellId                                                           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          executeNoTargetEvent            takes integer eventType, integer level, unit caster                             returns nothing/*
          */
method          executePointTargetEvent         takes integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
          */
method          executeSingleTargetEvent        takes integer eventType, integer level, unit caster, widget target              returns nothing/*
                - Manually triggers 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_TARGET/*
      */
constant integer SPELL_ORDER_TYPE_POINT/*
      */
constant integer SPELL_ORDER_TYPE_IMMEDIATE/*

    */

    |===========|
    | 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 GetEventSpellUser         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 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 SpellExecuteNoTargetEvent          takes integer abilId, integer eventType, integer level, unit caster                                returns nothing/*
      */
function SpellExecutePointTargetEvent       takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY    returns nothing/*
      */
function SpellExecuteSingleTargetEvent      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 spellId, integer eventType, code handler          returns nothing/*
      */
function SpellUnregisterEventHandler        takes integer spellId, integer eventType, code handler          returns nothing/*
      */
function SpellClearEventHandlers            takes integer spellId, integer eventType                        returns nothing/*
      */
function SpellClearHandlers                 takes integer spellId                                           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/*

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

        Fields:

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

        Public methods:
          */
static method registerSpellEvent takes integer spellId, 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

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

          */
method onSpellStart     takes nothing   returns thistype/*
                - Runs right after the spell event fires
                - Returning zero or a negative value will not run the periodic operations for that instance
                - You can return a value different from the original value of 'this'
                - The value returned will be added to the list of instances that will run onSpellPeriodic().
          */
method onSpellPeriodic  takes nothing   returns boolean/*
                - Runs periodically after the spell event fires until it returns false
          */
method onSpellEnd       takes nothing   returns nothing/*
                - Runs after method onSpellPeriodic() returns false


      */
module SpellEventEx/*

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

        Public methods:
          */
static method registerSpellEvent takes integer spellId, 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

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

          */
static method   onSpellStart        takes nothing   returns thistype/*
                - Runs right after the spell event fires
                - User should manually allocate the spell instance and use it as a return value of this method
                - Returning zero or a negative value will not run the periodic operations for that instance
          */
method          onSpellPeriodic     takes nothing   returns boolean/*
                - Runs periodically after the spell event fires until it returns false
          */
method          onSpellEnd          takes nothing   returns nothing/*
                - Runs after method onSpellPeriodic() returns false
                - User must manually deallocate the spell instance inside this method


      */
module SpellEventGeneric/*

        Member interfaces (All optional):
            - Should be declared above the module implementation

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

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


    */
//! endnovjass

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

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

        constant integer SPELL_ORDER_TYPE_TARGET        = 0x12
        constant integer SPELL_ORDER_TYPE_POINT         = 0x123
        constant integer SPELL_ORDER_TYPE_IMMEDIATE     = 0x1234
    endglobals

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

        private integer array eventType
        private integer array eventIndex
    endglobals

    private keyword Init

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

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

        private function AssertError takes boolean condition, string methodName, string structName, integer instance, string message returns nothing
            static if LIBRARY_ErrorMessage then
                call ThrowError(condition, SCOPE_PREFIX, methodName, structName, instance, message)
            endif
        endfunction
    endif

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

    private struct Hashtable extends array

        static if LIBRARY_Table then
            readonly static TableArray table
            static method save takes integer index, integer key, integer value returns nothing
                set table[index][key] = value
            endmethod
            static method load takes integer index, integer key returns integer
                return table[index][key]
            endmethod
            static method remove takes integer index, integer key returns nothing
                call table[index].remove(key)
            endmethod
            static method flushChild takes integer index returns nothing    
                call table[index].flush()
            endmethod
            static method init takes nothing returns nothing
                set table = TableArray[JASS_MAX_ARRAY_SIZE]
            endmethod
        else
            readonly static hashtable table = InitHashtable()
            static method save takes integer index, integer key, integer value returns nothing
                call SaveInteger(table, index, key, value)
            endmethod
            static method load takes integer index, integer key returns integer
                return LoadInteger(table, index, key)
            endmethod
            static method remove takes integer index, integer key returns nothing
                call RemoveSavedInteger(table, index, key)
            endmethod
            static method flushChild takes integer index returns nothing
                call FlushChildHashtable(table, index)
            endmethod
        endif

    endstruct

    private module List
        readonly thistype prev
        readonly thistype next

        method operator empty takes nothing returns boolean
            return this.next == this
        endmethod

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

    /*
    *   One Allocator for the whole library. Yes, it would be unlikely for this system to
    *   reach JASS_MAX_ARRAY_SIZE instances of allocated nodes at a single time.
    *
    *   Need to use custom Alloc because of the updated value for JASS_MAX_ARRAY_SIZE
    *   Credits to MyPad for the allocation algorithm
    */

    private struct Node extends array
        private static thistype array stack
        static method allocate takes nothing returns thistype
            local thistype node = stack[0]
            if stack[node] == 0 then
                debug call AssertError(node == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", "thistype", node, "Overflow")
                set node = node + 1
                set stack[0] = node
            else
                set stack[0] = stack[node]
                set stack[node] = 0
            endif
            return node
        endmethod
        method deallocate takes nothing returns nothing
            debug call AssertError(this == 0, "deallocate()", "thistype", 0, "Null node")
            debug call AssertError(stack[this] > 0, "deallocate()", "thistype", this, "Double-free")
            set stack[this] = stack[0]
            set stack[0] = this
        endmethod
    endstruct

    private struct ConditionList extends array
        triggercondition handle

        implement List

        method makeHead takes nothing returns nothing
            set this.prev = this
            set this.next = this
        endmethod
    endstruct

    private struct ExprList extends array
        boolexpr handle

        method operator conditionList takes nothing returns ConditionList
            return this
        endmethod

        implement List

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

    private struct Handler extends array

        readonly trigger trigger

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

        private method operator exprList takes nothing returns ExprList
            return this
        endmethod

        /*
        *   You might think that the process of registering handlers are expensive in performance
        *   due to constant rebuilding of triggerconditions each time, but setting up proper spell
        *   handlers are seldom done (often only once per spell) and a large part of them are done
        *   at map initialization.
        */

        method updateHandlers takes nothing returns nothing
            local ExprList exprNode = genericList[this.index].next
            local ConditionList conditionNode
            call TriggerClearConditions(this.trigger)
            if exprNode != genericList[this.index].prev then
                loop
                    exitwhen exprNode == genericList[this.index]
                    set conditionNode = exprNode.conditionList.next
                    loop
                        exitwhen conditionNode == exprNode.conditionList
                        call TriggerAddCondition(this.trigger, exprNode.handle)
                        set conditionNode = conditionNode.next
                    endloop
                    set exprNode = exprNode.next
                endloop
            endif
            set exprNode = this.exprList.next
            loop
                exitwhen exprNode == this.exprList
                set conditionNode = exprNode.conditionList.next
                loop
                    exitwhen conditionNode == exprNode.conditionList
                    set conditionNode.handle = TriggerAddCondition(this.trigger, exprNode.handle)
                    set conditionNode = conditionNode.next
                endloop
                set exprNode = exprNode.next
            endloop
        endmethod

        static method bridge takes nothing returns boolean
            local integer triggerId = GetHandleId(GetTriggeringTrigger())
            local thistype node = Hashtable.load(0, triggerId)
            local trigger tempTrig
            if node.disableBackExpr then
                if node.exprList.next != node.exprList then
                    set tempTrig = node.trigger
                    set node.trigger = CreateTrigger()
                    call Hashtable.save(0, GetHandleId(node.trigger), node)
                    call node.updateHandlers()
                    call TriggerClearConditions(tempTrig)
                    call DestroyTrigger(tempTrig)
                    call Hashtable.remove(0, triggerId)
                    set tempTrig = null
                endif
                return false
            endif
            return node.exprList.next != node.exprList and node.overrideParams
        endmethod

        static method create takes integer eventIndex returns thistype
            local thistype node = ExprList.create()
            set node.index = eventIndex
            return node
        endmethod
        method destroy takes nothing returns nothing
            if not this.exprList.empty then
                call Hashtable.remove(0, GetHandleId(this.trigger))
                call DestroyTrigger(this.trigger)
                set this.trigger = null
            endif
            set this.index = 0
            call Hashtable.flushChild(this)
            call this.exprList.destroy()
        endmethod

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

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

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

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

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

        static method init takes nothing returns nothing
            call initGenericList(eventIndex[EVENT_SPELL_CAST])
            call initGenericList(eventIndex[EVENT_SPELL_CHANNEL])
            call initGenericList(eventIndex[EVENT_SPELL_EFFECT])
            call initGenericList(eventIndex[EVENT_SPELL_ENDCAST])
            call initGenericList(eventIndex[EVENT_SPELL_FINISH])
        endmethod

    endstruct

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

    struct Spell extends array

        readonly static integer         ABILITY_ID      = 0
        readonly static integer         EVENT_TYPE      = 0
        readonly static integer         ORDER_TYPE      = 0
        readonly static integer         LEVEL           = 0
        readonly static player          TRIGGER_PLAYER  = null
        readonly static unit            TRIGGER_UNIT    = null
        readonly static unit            TARGET_UNIT     = null
        readonly static item            TARGET_ITEM     = null
        readonly static destructable    TARGET_DEST     = null
        readonly static real            TARGET_X        = 0.00
        readonly static real            TARGET_Y        = 0.00

        readonly integer abilityId

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

        static method operator GENERIC takes nothing returns thistype
            return Hashtable.load(spellKey, 0)
        endmethod

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

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

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

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

        method operator handlersDisabled= takes boolean disabled returns nothing
            if Spell.ABILITY_ID != 0 then
                set eventHandler[(this - 1)*5 + eventIndex[Spell.EVENT_TYPE]].disableBackExpr = disabled
            endif
        endmethod
        method operator handlersDisabled takes nothing returns boolean
            if Spell.ABILITY_ID == 0 then
                return false
            endif
            return eventHandler[(this - 1)*5 + eventIndex[Spell.EVENT_TYPE]].disableBackExpr
        endmethod

        private static method onOverrideParams takes nothing returns nothing
            if Handler.bridge() then
                set ORDER_TYPE      = tempOrderType
                set LEVEL           = tempLevel
                set TRIGGER_PLAYER  = GetOwningPlayer(tempTriggerUnit)
                set TRIGGER_UNIT    = tempTriggerUnit
                set TARGET_X        = tempTargetX
                set TARGET_Y        = tempTargetY

                static if LIBRARY_Table then
                    set Hashtable.table[0].widget[0] = tempTarget
                    set TARGET_UNIT = Hashtable.table[0].unit[0]
                    set TARGET_ITEM = Hashtable.table[0].item[0]
                    set TARGET_DEST = Hashtable.table[0].destructable[0]
                else
                    call SaveWidgetHandle(Hashtable.table, 0, 0, tempTarget)
                    set TARGET_UNIT = LoadUnitHandle(Hashtable.table, 0, 0)
                    set TARGET_ITEM = LoadItemHandle(Hashtable.table, 0, 0)
                    set TARGET_DEST = LoadDestructableHandle(Hashtable.table, 0, 0)
                endif

                set tempOrderType   = 0
                set tempLevel       = 0
                set tempTriggerUnit = null
                set tempTargetX     = 0.00
                set tempTargetY     = 0.00
                set tempTarget      = null
            endif
        endmethod

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

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

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

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

            local boolean disableBackExpr       = eventHandler.disableBackExpr
            local boolean overrideParams        = eventHandler.overrideParams
            local integer prevId                = ABILITY_ID
            local integer prevEventType         = EVENT_TYPE
            local integer prevOrderType         = ORDER_TYPE
            local integer prevLevel             = LEVEL
            local player prevTriggerPlayer      = TRIGGER_PLAYER
            local unit prevTriggerUnit          = TRIGGER_UNIT
            local real prevTargetX              = TARGET_X
            local real prevTargetY              = TARGET_Y
            local unit prevTargetUnit           = TARGET_UNIT
            local item prevTargetItem           = TARGET_ITEM
            local destructable prevTargetDest   = TARGET_DEST
            local location tempLoc

            set ABILITY_ID                      = currentId

            if manualExecute then
                set EVENT_TYPE                  = eventFlag
                set ORDER_TYPE                  = orderType
                set LEVEL                       = level
                set TRIGGER_PLAYER              = GetOwningPlayer(triggerUnit)
                set TRIGGER_UNIT                = triggerUnit
                set TARGET_X                    = targetX
                set TARGET_Y                    = targetY

                static if LIBRARY_Table then
                    set Hashtable.table[0].widget[0] = target
                    set TARGET_UNIT             = Hashtable.table[0].unit[0]
                    set TARGET_ITEM             = Hashtable.table[0].item[0]
                    set TARGET_DEST             = Hashtable.table[0].destructable[0]
                else
                    call SaveWidgetHandle(Hashtable.table, 0, 0, target)
                    set TARGET_UNIT             = LoadUnitHandle(Hashtable.table, 0, 0)
                    set TARGET_ITEM             = LoadItemHandle(Hashtable.table, 0, 0)
                    set TARGET_DEST             = LoadDestructableHandle(Hashtable.table, 0, 0)
                endif
            else
                set EVENT_TYPE                  = eventType[GetHandleId(GetTriggerEventId())]
                set TRIGGER_PLAYER              = GetTriggerPlayer()
                set TRIGGER_UNIT                = GetTriggerUnit()
                set LEVEL                       = GetUnitAbilityLevel(TRIGGER_UNIT, ABILITY_ID)
                set TARGET_UNIT                 = GetSpellTargetUnit()
                set TARGET_ITEM                 = GetSpellTargetItem()
                set TARGET_DEST                 = GetSpellTargetDestructable()

                if TARGET_UNIT != null then
                    set TARGET_X                = GetUnitX(TARGET_UNIT)
                    set TARGET_Y                = GetUnitY(TARGET_UNIT)
                    set ORDER_TYPE              = SPELL_ORDER_TYPE_TARGET
                elseif TARGET_ITEM != null then
                    set TARGET_X                = GetItemX(TARGET_ITEM)
                    set TARGET_Y                = GetItemY(TARGET_ITEM)
                    set ORDER_TYPE              = SPELL_ORDER_TYPE_TARGET
                elseif TARGET_DEST != null then
                    set TARGET_X                = GetWidgetX(TARGET_DEST)
                    set TARGET_Y                = GetWidgetY(TARGET_DEST)
                    set ORDER_TYPE              = SPELL_ORDER_TYPE_TARGET
                else
                    set tempLoc = GetSpellTargetLoc()
                    if tempLoc == null then
                    /* Special Case (for some no-target spells) */
                        set TARGET_X            = GetUnitX(TRIGGER_UNIT)
                        set TARGET_Y            = GetUnitY(TRIGGER_UNIT)
                        set ORDER_TYPE          = SPELL_ORDER_TYPE_IMMEDIATE
                    else
                        call RemoveLocation(tempLoc)
                        set tempLoc = null
                        set TARGET_X            = GetSpellTargetX()
                        set TARGET_Y            = GetSpellTargetY()
                        set ORDER_TYPE          = SPELL_ORDER_TYPE_POINT
                    endif
                endif
            endif

            set eventHandler.disableBackExpr = false
            set eventHandler.overrideParams = false
            call TriggerEvaluate(eventHandler.trigger)
            set eventHandler.disableBackExpr = disableBackExpr
            set eventHandler.overrideParams = overrideParams

            set ABILITY_ID                      = prevId
            set EVENT_TYPE                      = prevEventType
            set ORDER_TYPE                      = prevOrderType
            set LEVEL                           = prevLevel
            set TRIGGER_PLAYER                  = prevTriggerPlayer
            set TRIGGER_UNIT                    = prevTriggerUnit
            set TARGET_X                        = prevTargetX
            set TARGET_Y                        = prevTargetY
            set TARGET_UNIT                     = prevTargetUnit
            set TARGET_ITEM                     = prevTargetItem
            set TARGET_DEST                     = prevTargetDest

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

        endmethod

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

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

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

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

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

        private static method init takes nothing returns nothing
            /*
            *   This bridge boolexpr executes in after all the generic spell handlers
            *   before transitioning into the ability-specific spell handlers.
            *   This boolexpr is responsible for disabling the ability-specific handlers
            *   (if requested) as well as implementing the change/overriding of the
            *   event parameters.
            */

            local code bridgeFunc = function thistype.onOverrideParams
            set bridgeExpr = Filter(bridgeFunc)

            set spellKey = Node.allocate()
            set spellCount = spellCount + 1
            call Hashtable.save(spellKey, 0, spellCount)

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

    endstruct

    private module Init
        private static method onInit takes nothing returns nothing
            static if LIBRARY_Table then
                call Hashtable.init()
            endif
            call init()
            call Handler.init()
        endmethod
    endmodule

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

    constant function GetEventSpellAbilityId takes nothing returns integer
        return Spell.ABILITY_ID
    endfunction
    constant function GetEventSpellEventType takes nothing returns integer
        return Spell.EVENT_TYPE
    endfunction
    constant function GetEventSpellOrderType takes nothing returns integer
        return Spell.ORDER_TYPE
    endfunction
    constant function GetEventSpellLevel takes nothing returns integer
        return Spell.LEVEL
    endfunction
    constant function GetEventSpellUser takes nothing returns player
        return Spell.TRIGGER_PLAYER
    endfunction
    constant function GetEventSpellCaster takes nothing returns unit
        return Spell.TRIGGER_UNIT
    endfunction
    constant function GetEventSpellTargetUnit takes nothing returns unit
        return Spell.TARGET_UNIT
    endfunction
    constant function GetEventSpellTargetItem takes nothing returns item
        return Spell.TARGET_ITEM
    endfunction
    constant function GetEventSpellTargetDest takes nothing returns destructable
        return Spell.TARGET_DEST
    endfunction
    constant function GetEventSpellTargetX takes nothing returns real
        return Spell.TARGET_X
    endfunction
    constant function GetEventSpellTargetY takes nothing returns real
        return Spell.TARGET_Y
    endfunction

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

    function SpellExecuteNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing
        call Spell[abilId].executeNoTargetEvent(eventType, level, caster)
    endfunction
    function SpellExecutePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing
        call Spell[abilId].executePointTargetEvent(eventType, level, caster, targetX, targetY)
    endfunction
    function SpellExecuteSingleTargetEvent takes integer abilId, integer eventType, integer level, unit caster, widget target returns nothing
        call Spell[abilId].executeSingleTargetEvent(eventType, level, caster, target)
    endfunction

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

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

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

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

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

    private function OnSpellEventEx takes integer node, real period, code callback returns nothing
        local timer periodicTimer
        if node > 0 then
            set periodicTimer = CreateTimer()
            call Hashtable.save(0, GetHandleId(periodicTimer), node)
            call TimerStart(periodicTimer, period, true, callback)
            set periodicTimer = null
        endif
    endfunction

    private function RegisterSpell takes integer abilId, integer eventType, code onSpellEvent returns nothing
        if abilId != 0 then
            call Spell[abilId].registerEventHandler(eventType, onSpellEvent)
        endif
    endfunction

    module SpellEvent

        readonly thistype prev
        readonly thistype next
        private boolean replacement

        private static method onPeriodic takes nothing returns nothing
            local thistype node = thistype(0).next
            if node == 0 then
            /*
            *   For some reason, some guy tried to manually remove his node from the supposed
            *   readonly linked-list, without realizing that he ALMOST messed up the system..
            */

                call DestroyTimerEx(GetExpiredTimer())
                return
            endif
            loop
                exitwhen node == 0
                if not node.onSpellPeriodic() then
                    call node.onSpellEnd()
                    set node.next.prev = node.prev
                    set node.prev.next = node.next
                    if node.replacement then
                        set node.replacement = false
                    else
                        call Node(node).deallocate()
                    endif
                    if thistype(0).next == 0 then
                        call DestroyTimerEx(GetExpiredTimer())
                    endif
                endif
                set node = node.next
            endloop
        endmethod

        private static method onSpellEvent takes nothing returns nothing
            local thistype node = Node.allocate()
            local thistype last = thistype(0).prev
            local boolean prevEmpty = thistype(0).next == 0
            /*
            *   Add the new node into the list
            */

            set thistype(0).prev = node
            set last.next = node
            set node.prev = last
            set node.next = 0
            set last = node.onSpellStart()
            if last != node then
            /*
            *   If the user returned a different node than the one he was given,
            *   remove and deallocate the earlier node and replace it with the
            *   new node from the user.
            */

                set node.next.prev = node.prev
                set node.prev.next = node.next
                call Node(node).deallocate()
                if last > 0 then
                    set last.replacement = true
                    set node = thistype(0).prev
                    set thistype(0).prev = last
                    set node.next = last
                    set last.prev = node
                    set last.next = 0
                endif
            endif
            /*
            *   We need to use this kind of check in case the user returned 0
            *   but manually added some node in the list inside onSpellStart()
            */

            if prevEmpty and thistype(0).next != 0 then
                call TimerStart(CreateTimer(), SPELL_PERIOD, true, function thistype.onPeriodic)
            endif
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterSpell(SPELL_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
        endmethod

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

    endmodule

    module SpellEventEx

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

        private static method onSpellEvent takes nothing returns nothing
            call OnSpellEventEx(onSpellStart(), SPELL_PERIOD, function thistype.onPeriodic)
        endmethod

        private static method onInit takes nothing returns nothing
            call RegisterSpell(SPELL_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
        endmethod

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

    endmodule

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


endlibrary
library SpellCloner /* v1.1.0 https://www.hiveworkshop.com/threads/324157/


    */
uses /*

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

    */
//! novjass

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

    |-----|
    | API |
    |-----|

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

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

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


    */
//! endnovjass

    private keyword SpellConfigList

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

    private module Init
        static if LIBRARY_Table then
            readonly static TableArray table
        else
            static constant hashtable table = InitHashtable()
        endif
        private static method onInit takes nothing returns nothing
            static if LIBRARY_Table then
                set table = TableArray[JASS_MAX_ARRAY_SIZE]
            endif
            set eventIndex[EVENT_SPELL_CAST]    = 1
            set eventIndex[EVENT_SPELL_CHANNEL] = 2
            set eventIndex[EVENT_SPELL_EFFECT]  = 3
            set eventIndex[EVENT_SPELL_ENDCAST] = 4
            set eventIndex[EVENT_SPELL_FINISH]  = 5
        endmethod
    endmodule

    private struct S extends array
        implement Init
    endstruct

    private function SaveInt takes integer index, integer key, integer value returns nothing
        static if LIBRARY_Table then
            set S.table[index][key] = value
        else
            call SaveInteger(S.table, index, key, value)
        endif
    endfunction
    private function LoadInt takes integer index, integer key returns integer
        static if LIBRARY_Table then
            return S.table[index][key]
        else
            return LoadInteger(S.table, index, key)
        endif
    endfunction

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

        private static thistype node = 0

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

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

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

    private function InitSpellConfiguration takes integer spellStructId, integer spellInstance, integer abilId returns integer
        local SpellConfigList configList = LoadInt(spellStructId*5 + eventIndex[Spell.EVENT_TYPE], abilId)
        local integer configStructId
        set configList.current = configList.current.next
        set configStructId = configList.current.structId
        call configList.current.evaluateExpr(spellInstance)
        if configList.current.next == configList then
            set configList.current = configList
        endif
        return configStructId
    endfunction

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

    module SpellClonerHeader
        static constant integer SPELL_ID = 0
        static constant integer SPELL_EVENT_TYPE = 0

        method initSpellConfiguration takes integer abilId returns integer
            return InitSpellConfiguration(thistype.typeid, this, abilId)
        endmethod
        method loadSpellConfiguration takes integer configStructId returns nothing
            call SpellConfigList(configStructNode[configStructId]).evaluateExpr(this)
        endmethod
        static method operator configuredInstance takes nothing returns thistype
            return configuredSpellInstance
        endmethod
    endmodule

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


endlibrary
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.

    One map, one hashtable. Welcome to NewTable 4.1.0.1

    This newest iteration of Table introduces the new HashTable struct.
    You can now instantiate HashTables which enables the use of large
    parent and large child keys, just like a standard hashtable. Previously,
    the user would have to instantiate a Table to do this on their own which -
    while doable - is something the user should not have to do if I can add it
    to this resource myself (especially if they are inexperienced).

    This library was originally called NewTable so it didn't conflict with
    the API of Table by Vexorian. However, the damage is done and it's too
    late to change the library name now. To help with damage control, I
    have provided an extension library called TableBC, which bridges all
    the functionality of Vexorian's Table except for 2-D string arrays &
    the ".flush(integer)" method. I use ".flush()" to flush a child hash-
    table, because I wanted the API in NewTable to reflect the API of real
    hashtables (I thought this would be more intuitive).

    API

    ------------
    struct Table
    | static method create takes nothing returns Table
    |     create a new Table
    |
    | method destroy takes nothing returns nothing
    |     destroy it
    |
    | method flush takes nothing returns nothing
    |     flush all stored values inside of it
    |
    | method remove takes integer key returns nothing
    |     remove the value at index "key"
    |
    | method operator []= takes integer key, $TYPE$ value returns nothing
    |     assign "value" to index "key"
    |
    | method operator [] takes integer key returns $TYPE$
    |     load the value at index "key"
    |
    | method has takes integer key returns boolean
    |     whether or not the key was assigned
    |
    ----------------
    struct TableArray
    | static method operator [] takes integer array_size returns TableArray
    |     create a new array of Tables of size "array_size"
    |
    | method destroy takes nothing returns nothing
    |     destroy it
    |
    | method flush takes nothing returns nothing
    |     flush and destroy it
    |
    | method operator size takes nothing returns integer
    |     returns the size of the TableArray
    |
    | method operator [] takes integer key returns Table
    |     returns a Table accessible exclusively to index "key"
*/

   
globals
    private integer less = 0    //Index generation for TableArrays (below 0).
    private integer more = 8190 //Index generation for Tables.
    //Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
   
    private hashtable ht = InitHashtable()
    private key sizeK
    private key listK
endglobals

private struct dex extends array
    static method operator size takes nothing returns Table
        return sizeK
    endmethod
    static method operator list takes nothing returns Table
        return listK
    endmethod
endstruct

private struct handles extends array
    method has takes integer key returns boolean
        return HaveSavedHandle(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSavedHandle(ht, this, key)
    endmethod
endstruct

private struct agents extends array
    method operator []= takes integer key, agent value returns nothing
        call SaveAgentHandle(ht, this, key, value)
    endmethod
endstruct

//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$(ht, this, key)
    endmethod
    method operator []= takes integer key, $TYPE$ value returns nothing
        call Save$FUNC$(ht, this, key, value)
    endmethod
    method has takes integer key returns boolean
        return HaveSaved$SUPER$(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSaved$SUPER$(ht, this, key)
    endmethod
endstruct
private module $TYPE$m
    method operator $TYPE$ takes nothing returns $TYPE$s
        return this
    endmethod
endmodule
//! endtextmacro
   
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$Handle(ht, this, key)
    endmethod
    method operator []= takes integer key, $TYPE$ value returns nothing
        call Save$FUNC$Handle(ht, this, key, value)
    endmethod
    method has takes integer key returns boolean
        return HaveSavedHandle(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSavedHandle(ht, this, key)
    endmethod
endstruct
private module $TYPE$m
    method operator $TYPE$ takes nothing returns $TYPE$s
        return this
    endmethod
endmodule
//! endtextmacro

//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
   
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
   
struct Table extends array
   
    // Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
    implement realm
    implement integerm
    implement booleanm
    implement stringm
    implement playerm
    implement widgetm
    implement destructablem
    implement itemm
    implement unitm
    implement abilitym
    implement timerm
    implement triggerm
    implement triggerconditionm
    implement triggeractionm
    implement eventm
    implement forcem
    implement groupm
    implement locationm
    implement rectm
    implement boolexprm
    implement soundm
    implement effectm
    implement unitpoolm
    implement itempoolm
    implement questm
    implement questitemm
    implement defeatconditionm
    implement timerdialogm
    implement leaderboardm
    implement multiboardm
    implement multiboarditemm
    implement trackablem
    implement dialogm
    implement buttonm
    implement texttagm
    implement lightningm
    implement imagem
    implement ubersplatm
    implement regionm
    implement fogstatem
    implement fogmodifierm
    implement hashtablem

    method operator handle takes nothing returns handles
        return this
    endmethod

    method operator agent takes nothing returns agents
        return this
    endmethod

    //set this = tb[GetSpellAbilityId()]
    method operator [] takes integer key returns Table
        return LoadInteger(ht, this, key) //return this.integer[key]
    endmethod

    //set tb[389034] = 8192
    method operator []= takes integer key, Table tb returns nothing
        call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
    endmethod

    //set b = tb.has(2493223)
    method has takes integer key returns boolean
        return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
    endmethod

    //call tb.remove(294080)
    method remove takes integer key returns nothing
        call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
    endmethod
   
    //Remove all data from a Table instance
    method flush takes nothing returns nothing
        call FlushChildHashtable(ht, this)
    endmethod

    //local Table tb = Table.create()
    static method create takes nothing returns Table
        local Table this = dex.list[0]

        if this == 0 then
            set this = more + 1
            set more = this
        else
            set dex.list[0] = dex.list[this]
            call dex.list.remove(this) //Clear hashed memory
        endif

        debug set dex.list[this] = -1
        return this
    endmethod

    // Removes all data from a Table instance and recycles its index.
    //
    //     call tb.destroy()
    //
    method destroy takes nothing returns nothing
        debug if dex.list[this] != -1 then
            debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
            debug return
        debug endif
       
        call this.flush()
       
        set dex.list[this] = dex.list[0]
        set dex.list[0] = this
    endmethod
   
    //! runtextmacro optional TABLE_BC_METHODS()
endstruct
   
//! runtextmacro optional TABLE_BC_STRUCTS()
   
struct TableArray extends array
   
    //Returns a new TableArray to do your bidding. Simply use:
    //
    //    local TableArray ta = TableArray[array_size]
    //
    static method operator [] takes integer array_size returns TableArray
        local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
        local TableArray this = tb[0]         //The last-destroyed TableArray that had this array size
       
        debug if array_size <= 0 then
            debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
            debug return 0
        debug endif
       
        if this == 0 then
            set this = less - array_size
            set less = this
        else
            set tb[0] = tb[this]  //Set the last destroyed to the last-last destroyed
            call tb.remove(this)  //Clear hashed memory
        endif
       
        set dex.size[this] = array_size //This remembers the array size
        return this
    endmethod
   
    //Returns the size of the TableArray
    method operator size takes nothing returns integer
        return dex.size[this]
    endmethod
   
    //This magic method enables two-dimensional[array][syntax] for Tables,
    //similar to the two-dimensional utility provided by hashtables them-
    //selves.
    //
    //ta[integer a].unit[integer b] = unit u
    //ta[integer a][integer c] = integer d
    //
    //Inline-friendly when not running in debug mode
    //
    method operator [] takes integer key returns Table
        static if DEBUG_MODE then
            local integer i = this.size
            if i == 0 then
                call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
                return 0
            elseif key < 0 or key >= i then
                call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
                return 0
            endif
        endif
        return this + key
    endmethod
   
    //Destroys a TableArray without flushing it; I assume you call .flush()
    //if you want it flushed too. This is a public method so that you don't
    //have to loop through all TableArray indices to flush them if you don't
    //need to (ie. if you were flushing all child-keys as you used them).
    //
    method destroy takes nothing returns nothing
        local Table tb = dex.size[this.size]
       
        debug if this.size == 0 then
            debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
            debug return
        debug endif
       
        if tb == 0 then
            //Create a Table to index recycled instances with their array size
            set tb = Table.create()
            set dex.size[this.size] = tb
        endif
       
        call dex.size.remove(this) //Clear the array size from hash memory
       
        set tb[this] = tb[0]
        set tb[0] = this
    endmethod
   
    private static Table tempTable
    private static integer tempEnd
   
    //Avoids hitting the op limit
    private static method clean takes nothing returns nothing
        local Table tb = .tempTable
        local integer end = tb + 0x1000
        if end < .tempEnd then
            set .tempTable = end
            call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
        else
            set end = .tempEnd
        endif
        loop
            call tb.flush()
            set tb = tb + 1
            exitwhen tb == end
        endloop
    endmethod
   
    //Flushes the TableArray and also destroys it. Doesn't get any more
    //similar to the FlushParentHashtable native than this.
    //
    method flush takes nothing returns nothing
        debug if this.size == 0 then
            debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
            debug return
        debug endif
        set .tempTable = this
        set .tempEnd = this + this.size
        call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
        call this.destroy()
    endmethod
   
endstruct
   
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array

    //Enables myHash[parentKey][childKey] syntax.
    //Basically, it creates a Table in the place of the parent key if
    //it didn't already get created earlier.
    method operator [] takes integer index returns Table
        local Table t = Table(this)[index]
        if t == 0 then
            set t = Table.create()
            set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
        endif
        return t
    endmethod

    //You need to call this on each parent key that you used if you
    //intend to destroy the HashTable or simply no longer need that key.
    method remove takes integer index returns nothing
        local Table t = Table(this)[index]
        if t != 0 then
            call t.destroy()
            call Table(this).remove(index)
        endif
    endmethod

    //Added in version 4.1
    method has takes integer index returns boolean
        return Table(this).has(index)
    endmethod

    //HashTables are just fancy Table indices.
    method destroy takes nothing returns nothing
        call Table(this).destroy()
    endmethod

    //Like I said above...
    static method create takes nothing returns thistype
        return Table.create()
    endmethod

endstruct

endlibrary
library WorldBounds /* v2.0.0.0
************************************************************************************
*
*   struct WorldBounds extends array
*
*       Fields
*       -------------------------
*
*           readonly static integer maxX
*           readonly static integer maxY
*           readonly static integer minX
*           readonly static integer minY
*
*           readonly static integer centerX
*           readonly static integer centerY
*
*           readonly static rect world
*           readonly static region worldRegion
*
************************************************************************************/

    private module WorldBoundInit
        private static method onInit takes nothing returns nothing
            set world=GetWorldBounds()

            set maxX = R2I(GetRectMaxX(world))
            set maxY = R2I(GetRectMaxY(world))
            set minX = R2I(GetRectMinX(world))
            set minY = R2I(GetRectMinY(world))
           
            set centerX = R2I((maxX + minX)/2)
            set centerY = R2I((minY + maxY)/2)
           
            set worldRegion = CreateRegion()
           
            call RegionAddRect(worldRegion, world)
        endmethod
    endmodule
   
    struct WorldBounds extends array
        readonly static integer maxX
        readonly static integer maxY
        readonly static integer minX
        readonly static integer minY
       
        readonly static integer centerX
        readonly static integer centerY
       
        readonly static rect world
       
        readonly static region worldRegion
       
        implement WorldBoundInit
    endstruct
endlibrary
library ResourcePreloader /* v1.4c


    */
uses /*

    */
optional Table        /*   https://www.hiveworkshop.com/threads/snippet-new-table.188084/
    */
optional BJObjectId   /*   https://www.hiveworkshop.com/threads/bjobjectid.287128/
    */
optional UnitRecycler /*   https://www.hiveworkshop.com/threads/snippet-unit-recycler.286701/


    */
//! novjass

    |================|
    | Written by AGD |
    |================|

        [CREDITS]
    /*      IcemanBo - for suggesting further improvements
            Silvenon - for the sound preloading method                            */



        |-----|
        | API |
        |-----|

            function PreloadUnit takes integer rawcode returns nothing/*
                - Assigns a certain type of unit to be preloaded

          */
function PreloadItem takes integer rawcode returns nothing/*
                - Assigns a certain type of item to be preloaded

          */
function PreloadAbility takes integer rawcode returns nothing/*
                - Assigns a certain type of ability to be preloaded

          */
function PreloadEffect takes string modelPath returns nothing/*
                - Assigns a certain type of effect to be preloaded

          */
function PreloadSound takes string soundPath returns nothing/*
                - Assigns a certain type of sound to be preloaded


          The following functions requires the BJObjectId library:

          */
function PreloadUnitEx takes integer start, integer end returns nothing/*
                - Assigns a range of units to be preloaded

          */
function PreloadItemEx takes integer start, integer end returns nothing/*
                - Assigns a range of items to be preloaded

          */
function PreloadAbilityEx takes integer start, integer end returns nothing/*
                - Assigns a range of abilities to be preloaded


    */
//! endnovjass

    /*========================================================================================================*/
    /*            Do not try to change below this line if you're not so sure on what you're doing.            */
    /*========================================================================================================*/

    private keyword S

    /*============================================== TextMacros ==============================================*/

    //! textmacro PRELOAD_TYPE takes NAME, ARG, TYPE, INDEX, I
    function Preload$NAME$ takes $ARG$ what returns nothing
        static if LIBRARY_Table then
            if S.tb[$I$].boolean[$INDEX$] then
                return
            endif
            set S.tb[$I$].boolean[$INDEX$] = true
            call Do$NAME$Preload(what)
        else
            if LoadBoolean(S.tb, $I$, $INDEX$) then
                return
            endif
            call SaveBoolean(S.tb, $I$, $INDEX$, true)
            call Do$NAME$Preload(what)
        endif
    endfunction
    //! endtextmacro

    //! textmacro RANGED_PRELOAD_TYPE takes NAME
    function Preload$NAME$Ex takes integer start, integer end returns nothing
        local boolean mode = start < end
        loop
            call Preload$NAME$(start)
            exitwhen start == end
            if mode then
                set start = BJObjectId(start).plus_1()
                exitwhen start > end
            else
                set start = BJObjectId(start).minus_1()
                exitwhen start < end
            endif
        endloop
    endfunction
    //! endtextmacro

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

    private function DoUnitPreload takes integer id returns nothing
        static if LIBRARY_UnitRecycler then
            call RecycleUnitEx(CreateUnit(Player(15), id, 0, 0, 270))
        else
            call RemoveUnit(CreateUnit(Player(15), id, 0, 0, 0))
        endif
    endfunction

    private function DoItemPreload takes integer id returns nothing
        call RemoveItem(UnitAddItemById(S.dummy, id))
    endfunction

    private function DoAbilityPreload takes integer id returns boolean
        return UnitAddAbility(S.dummy, id) and UnitRemoveAbility(S.dummy, id)
    endfunction

    private function DoEffectPreload takes string path returns nothing
        call DestroyEffect(AddSpecialEffectTarget(path, S.dummy, "origin"))
    endfunction

    private function DoSoundPreload takes string path returns nothing
        local sound s = CreateSound(path, false, false, false, 10, 10, "")
        call SetSoundVolume(s, 0)
        call StartSound(s)
        call KillSoundWhenDone(s)
        set s = null
    endfunction

    //! runtextmacro PRELOAD_TYPE("Unit", "integer", "unit", "what", "0")
    //! runtextmacro PRELOAD_TYPE("Item", "integer", "item", "what", "1")
    //! runtextmacro PRELOAD_TYPE("Ability", "integer", "ability", "what", "2")
    //! runtextmacro PRELOAD_TYPE("Effect", "string", "effect", "StringHash(what)", "3")
    //! runtextmacro PRELOAD_TYPE("Sound", "string", "sound", "StringHash(what)", "4")

    static if LIBRARY_BJObjectId then
    //! runtextmacro RANGED_PRELOAD_TYPE("Unit")
    //! runtextmacro RANGED_PRELOAD_TYPE("Item")
    //! runtextmacro RANGED_PRELOAD_TYPE("Ability")
    endif

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

    private module Init
        private static method onInit takes nothing returns nothing
            local rect world = GetWorldBounds()
            static if LIBRARY_Table then
                set tb = TableArray[5]
            endif
            set dummy = CreateUnit(Player(15), 'hpea', 0, 0, 0)
            call SetUnitY(dummy, GetRectMaxY(world) + 1000)
            call UnitAddAbility(dummy, 'AInv')
            call UnitAddAbility(dummy, 'Avul')
            call UnitRemoveAbility(dummy, 'Amov')
            call RemoveRect(world)
            set world = null
        endmethod
    endmodule

    private struct S extends array
        static if LIBRARY_Table then
            static TableArray tb
        else
            static hashtable tb = InitHashtable()
        endif
        static unit dummy
        implement Init
    endstruct


endlibrary
Requirements of the requirements of the TorrentArray library
library DummyRecycler /*
 
//                      DummyRecycler v1.25
//                          by Flux
//
//  A system that recycles dummy units while considering their facing angle.
//  It can be used as attachment dummies for visual effects or as dummy caster.
//
//  Why is recycling a unit important?
//      Because creating a unit is is one of the slowest function in the game
//      and there are reports that will always leave a permanent tiny bit of
//      memory (0.04 KB).
//      On average, retrieving a pending Dummy is approximately 4x faster compared
//      to creating a new one and recycling a Dummy compared to removing it is
//      approximately 1.3x faster.
//      Furthermore, if you're using a lot of "Unit has entered map" events,
//      using this system will even result to even more better performance
//      because retrieving Dummy units does not cause that event to run.


    */
requires /*
       nothing

    */
optional Table/*
        if not found, this system will use a hashtable. Hashtables are limited to
        255 per map.
     
    */
optional WorldBounds /*
        if not found, this system will initialize its own Map Boundaries.
//
//
//  Features:
//
//    -- Dummy Sharing
//        When a Dummy List gets low on unit count, it will borrow Dummy Units
//        from the Dummy List with the highest unit count. The transfer is not
//        instant because the shared Dummy Unit has to turn to the appropriate
//        angle of its new Dummy List before it can be recycled.
//        See BORROW_REQUEST.
//
//    -- Self-balancing recycling algorithm
//        Recycled Dummy Units will be thrown to the List having the least number
//        of Dummy Units.
//
//    -- Recycling least used
//        Allows recycling a Dummy from the Dummy List with the highest
//        unit count. It is useful when the facing angle of the Dummy Unit
//        does not matter.
//        See GetRecycledDummyAnyAngle.
//
//    -- Self-adaptation
//        When there are no free Dummy Units from a Dummy List, it will end up creating
//        a new unit instead but that unit will be permanently added as a Dummy
//        Unit to be recycled increasing the overall total Dummy Unit count.
//
//    -- Count control
//        Allows limiting the overall number of Dummy Units.
//        See MAX_DUMMY_COUNT.
//
//    -- Delayed Recycle
//        Allows recycling Dummy Units after some delay to allocate time for the
//        death animation of Special Effects to be seen.
//        See DummyAddRecycleTimer.
//
// ******************************************************************
// ***************************** API: *******************************
// ******************************************************************
//
//  function GetRecycledDummy takes real x, real y, real z, real facing returns unit
//      - Retrieve an unused Dummy Unit from the List.
//      - The equivalent of CreateUnit.
//      - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
//  function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
//      - Use this function if the facing angle of the Dummy doesn't matter to you.
//      - It will return a unit from the list having the highest number of unused Dummy Units.
//      - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
//  function RecycleDummy takes unit u returns nothing
//      - Recycle the Dummy unit for it to be used again later.
//      - The equivalent of RemoveUnit.
//
//  function DummyAddRecycleTimer takes unit u, real time returns nothing
//      - Recycle the Dummy unit after a certain time.
//      - Use this to allocate time for the the death animation of an effect attached to the
//        Dummy Unit to finish..
//      - The equivalent of UnitApplyTimedLife.
//
//  function ShowDummy takes unit u, boolean flag returns nothing
//      - Shows/hides Dummy Unit without conflicting with the Locust ability.
//
//--------------------
//      CREDITS
//--------------------
//  Bribe - for the MissileRecycler (vJASS) where I got this concept from
//       http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
//        - for the optional Table
//       http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
//  Vexorian - for the Attachable and Pitch Animation Model (dummy.mdx)
//       http://www.wc3c.net/showthread.php?t=101150
//  Maker and IcemanBo - for the unit permanent 0.04 KB memory leak of units.
//       http://www.hiveworkshop.com/forums/trigger-gui-editor-tutorials-279/memory-leaks-263410/
//  Nestharus - for the data structure
//       http://www.hiveworkshop.com/forums/2809461-post7.html
//            - for the optional WorldBounds
//       http://githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j

// =============================================================== //
// ====================== CONFIGURATION ========================== //
// =============================================================== */


    globals
        //The rawcode of the Dummy Unit
        private constant integer DUMMY_ID = 'h000'
     
        //The owner of the Dummy Unit
        private constant player OWNER = Player(14)
     
        //The number of indexed angle. The higher the value the:
        // - Lesser the turning time for the Dummy Units.
        // - Higher the total number of Dummy Units created at Map Initialization.
        //          Recommended Value: 10 (Max difference of 18 degrees)
        private constant integer ANGLES_COUNT = 10
     
        //The number of Dummy units per ANGLES_COUNT. The higher the value the:
        // - Higher the number of units that can be recycled per angle, when
        //   no more units are in queue, the system will resort to use CreateUnit.
        // - Higher the total number of Dummy Units created at Map Initialization.
        //    Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
        private constant integer STORED_UNIT_COUNT = 3
     
        //The maximum number of Dummy units that can exist. When the system resort
        //to using CreateUnit, the unit will be permanently added to the Dummy
        //List. To avoid spamming Dummy Units and having too much free Dummy
        //Units to allocate, the maximum number of Dummy Units is capped.
        //               Recommended Value: 80 to 120
        private constant integer MAX_DUMMY_COUNT = 100
     
        //When a certain angle have less than BORROW_REQUEST units in its list,
        //it will start to borrow Dummy Units from the list with the highest
        //Dummy Unit count.
        //      Recommended Value: Half of maximum STORED_UNIT_COUNT
        private constant integer BORROW_REQUEST = 5
     
        //It will only return a Dummy if the current dummy is close
        //to it's appropriate facing angle. This is to avoid returning
        //a Dummy which is still turning to face it's list angle.
        private constant real ANGLE_TOLERANCE = 10.0
     
        //An additional option to automatically hide recycled dummy units in the
        //corner of the map camera bounds
        private constant boolean HIDE_ON_MAP_CORNER = true
    endglobals
 
    //Every time a new dummy unit is retrieved, it will apply this resets
    //If it is redundant/you dont need it, remove it.
    //! textmacro DUMMY_UNIT_RESET
        call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
        call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
        call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
        call ShowDummy(bj_lastCreatedUnit, true)
    //! endtextmacro
// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //
 
 
    globals
        private integer dummyCount = ANGLES_COUNT*STORED_UNIT_COUNT
        private real array angle
        private integer array count
        private integer array countHead
        private integer array countNext
        private integer array countPrev
        private integer array next
        private integer array prev
        private unit array dummy
        private integer upper
        private integer lower
        private integer lastInstance
        private constant real FACING_OFFSET = 180.0/ANGLES_COUNT
    endglobals
 
    static if HIDE_ON_MAP_CORNER and not LIBRARY_WorldBounds then
        private module BoundsInit
     
            readonly static real x
            readonly static real y
         
            private static method onInit takes nothing returns nothing
                local rect map = GetWorldBounds()
                set thistype.x = GetRectMaxX(map)
                set thistype.y = GetRectMaxY(map)
                call RemoveRect(map)
                set map = null
            endmethod
         
        endmodule
     
        private struct Bounds extends array
            implement BoundsInit
        endstruct
    endif
 
    private module M
     
        static if LIBRARY_Table then
            static Table tb
        else
            static hashtable hash = InitHashtable()
        endif
     
        private static method onInit takes nothing returns nothing
            local real add = 360.0/ANGLES_COUNT
            local real a = 0
            local integer this = ANGLES_COUNT
            local integer head = 0
            local integer cHead = JASS_MAX_ARRAY_SIZE - 1   //avoid allocation collision
            local integer i = R2I(MAX_DUMMY_COUNT/ANGLES_COUNT + 0.5)
            set upper = STORED_UNIT_COUNT
            set lower = STORED_UNIT_COUNT
            static if LIBRARY_Table then
                set tb = Table.create()
            endif
            //Initialize countHeads
            loop
                exitwhen i < 0
                set countNext[cHead] = cHead
                set countPrev[cHead] = cHead
                set countHead[i] = cHead
                set cHead = cHead - 1
                set i = i - 1
            endloop
            set cHead = countHead[STORED_UNIT_COUNT]  //All heads will be inserted here initially
            //Create the Dummy units
            loop
                exitwhen a >= 360
                //Initialize head
                set next[head] = head
                set prev[head] = head
                set count[head] = STORED_UNIT_COUNT
                set angle[head] = a
                //Insert head in the Count List
                set countNext[head] = cHead
                set countPrev[head] = countPrev[cHead]
                set countNext[countPrev[head]] = head
                set countPrev[countNext[head]] = head
                set i = 0
                loop
                    exitwhen i >= STORED_UNIT_COUNT
                    //Queued Linked List
                    set next[this] = head
                    set prev[this] = prev[head]
                    set next[prev[this]] = this
                    set prev[next[this]] = this
                    static if HIDE_ON_MAP_CORNER then
                        static if LIBRARY_WorldBounds then
                            set dummy[this] = CreateUnit(OWNER, DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, a)
                        else
                            set dummy[this] = CreateUnit(OWNER, DUMMY_ID, Bounds.x, Bounds.y, a)
                        endif
                    else
                        set dummy[this] = CreateUnit(OWNER, DUMMY_ID, 0, 0, a)
                    endif
                    call PauseUnit(dummy[this], true)
                    static if LIBRARY_Table then
                        set tb[GetHandleId(dummy[this])] = this
                    else
                        call SaveInteger(hash, GetHandleId(dummy[this]), 0, this)
                    endif
                    set this = this + 1
                    set i = i + 1
                endloop
                set head = head + 1
                set a = a + add
            endloop
            set lastInstance = this
        endmethod
     
    endmodule
 
    private struct S extends array
        implement M
    endstruct
 
    private function GetHead takes integer facing returns integer
        if facing < 0 or facing >= 360 then
            set facing = facing - (facing/360)*360
            if facing < 0 then
                set facing = facing + 360
            endif
        endif
        return R2I((facing*ANGLES_COUNT/360.0))
    endfunction
 
    function ShowDummy takes unit u, boolean flag returns nothing
        if IsUnitHidden(u) == flag then
            call ShowUnit(u, flag)
            if flag and GetUnitTypeId(u) == DUMMY_ID then
                call UnitRemoveAbility(u, 'Aloc')
                call UnitAddAbility(u, 'Aloc')
            endif
        endif
    endfunction
 
    function GetRecycledDummy takes real x, real y, real z, real facing returns unit
        local integer head = GetHead(R2I(facing + FACING_OFFSET))
        local integer this = next[head]
        local integer cHead
     
        //If there are Dummy Units in the Queue List already facing close to the appropriate angle
        if this != head and RAbsBJ(GetUnitFacing(dummy[this]) - angle[head]) <= ANGLE_TOLERANCE then
            //Remove from the Queue List
            set next[prev[this]] = next[this]
            set prev[next[this]] = prev[this]
            //For double free protection
            set next[this] = -1
            //Unit Properties
            set bj_lastCreatedUnit = dummy[this]
            call SetUnitX(bj_lastCreatedUnit, x)
            call SetUnitY(bj_lastCreatedUnit, y)
            call SetUnitFacing(bj_lastCreatedUnit, facing)
            call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
            //! runtextmacro DUMMY_UNIT_RESET()
            //Update Count and Bounds
            set count[head] = count[head] - 1
         
            //------------------------------------------------
            //                 Unit Sharing
            //------------------------------------------------
            if count[head] < BORROW_REQUEST and count[countNext[countHead[upper]]] > count[head] then
                set count[head] = count[head] + 1
                set this = next[countNext[countHead[upper]]]
                call SetUnitFacing(dummy[this], angle[head])
                //Remove
                set next[prev[this]] = next[this]
                set prev[next[this]] = prev[this]
                //Add to the Current List
                set next[this] = head
                set prev[this] = prev[head]
                set next[prev[this]] = this
                set prev[next[this]] = this
                set head = countNext[countHead[upper]]
                set count[head] = count[head] - 1
            endif
         
            //---------------------------
            //Update Count Lists
            //---------------------------
            //Remove from the current Count List
            set countNext[countPrev[head]] = countNext[head]
            set countPrev[countNext[head]] = countPrev[head]
            //Add to the new Count List
            set cHead = countHead[count[head]]
            set countNext[head] = cHead
            set countPrev[head] = countPrev[cHead]
            set countNext[countPrev[head]] = head
            set countPrev[countNext[head]] = head
         
            //---------------------------
            //  Update Bounds
            //---------------------------
            set cHead = countHead[upper]
            if countNext[cHead] == cHead then
                set upper = upper - 1
            endif
            if count[head] < lower then
                set lower = count[head]
            endif
        else
            set bj_lastCreatedUnit = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
            call PauseUnit(bj_lastCreatedUnit, true)
            call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
            if dummyCount < MAX_DUMMY_COUNT then
                set this = lastInstance
                //For double free protection
                set next[this] = -1
                set dummy[this] = bj_lastCreatedUnit
                static if LIBRARY_Table then
                    set S.tb[GetHandleId(bj_lastCreatedUnit)] = this
                else
                    call SaveInteger(S.hash, GetHandleId(bj_lastCreatedUnit), 0, this)
                endif
                set lastInstance = lastInstance + 1
            endif
            set dummyCount = dummyCount + 1
        endif

        return bj_lastCreatedUnit
    endfunction
 
    function RecycleDummy takes unit u returns nothing
        static if LIBRARY_Table then
            local integer this = S.tb[GetHandleId(u)]
        else
            local integer this = LoadInteger(S.hash, GetHandleId(u), 0)
        endif
        local integer head
        local integer cHead
     
        //If the unit is a legit Dummy Unit
        if this > 0 and next[this] == -1 then
            //Find where to insert based on the list having the least number of units
            set head = countNext[countHead[lower]]
            set next[this] = head
            set prev[this] = prev[head]
            set next[prev[this]] = this
            set prev[next[this]] = this
            //Update Status
            call SetUnitFacing(u, angle[head])
            call PauseUnit(u, true)
            call SetUnitOwner(u, OWNER, false)
            static if HIDE_ON_MAP_CORNER then
                static if LIBRARY_WorldBounds then
                    call SetUnitX(u, WorldBounds.maxX)
                    call SetUnitY(u, WorldBounds.maxY)
                else
                    call SetUnitX(u, Bounds.x)
                    call SetUnitY(u, Bounds.y)
                endif
            else
                call SetUnitScale(u, 0, 0, 0)
                call SetUnitVertexColor(u, 0, 0, 0, 0)
            endif
            set count[head] = count[head] + 1
         
            //---------------------------
            //    Update Count Lists
            //---------------------------
            //Remove
            set countNext[countPrev[head]] = countNext[head]
            set countPrev[countNext[head]] = countPrev[head]
            //Add to the new Count List
            set cHead = countHead[count[head]]
            set countNext[head] = cHead
            set countPrev[head] = countPrev[cHead]
            set countNext[countPrev[head]] = head
            set countPrev[countNext[head]] = head
         
            //---------------------------
            //  Update Bounds
            //---------------------------
            set cHead = countHead[lower]
            if countNext[cHead] == cHead then
                set lower = lower + 1
            endif
            if count[head] > upper then
                set upper = count[head]
            endif
        elseif this == 0 then
            call RemoveUnit(u)
        debug elseif next[this] != -1 then
            debug call BJDebugMsg("|cffffcc00[DummyRecycler]:|r Attempted to recycle a pending/free Dummy Unit.")
        endif
     
    endfunction
 
    private function Expires takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer id = GetHandleId(t)
        static if LIBRARY_Table then
            call RecycleDummy(S.tb.unit[id])
            call S.tb.unit.remove(id)
        else
            call RecycleDummy(LoadUnitHandle(S.hash, id, 0))
            call FlushChildHashtable(S.hash, id)
        endif
        call DestroyTimer(t)
        set t = null
    endfunction

    function DummyAddRecycleTimer takes unit u, real time returns nothing
        local timer t = CreateTimer()
        static if LIBRARY_Table then
            set S.tb.unit[GetHandleId(t)] = u
        else
            call SaveUnitHandle(S.hash, GetHandleId(t), 0, u)
        endif
        call TimerStart(t, time, false, function Expires)
        set t = null
    endfunction
 
    function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
        return GetRecycledDummy(x, y, z, angle[countNext[countHead[upper]]])
    endfunction
 
    // runtextmacro DUMMY_DEBUG_TOOLS()
 
endlibrary
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
*   v1.2.1, by TriggerHappy
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   UnitDex assigns every unit an unique integer. It attempts to make that number within the
*   maximum array limit so you can associate it with one.
*   _________________________________________________________________________
*   1. Installation
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Copy the script to your map, save it, then restart the editor and comment out the line below.
*/

    // external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/*  ________________________________________________________________________
*   2. Configuration
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
 
    private module UnitDexConfig

        // The raw code of the leave detection ability.
        static constant integer DETECT_LEAVE_ABILITY = 'uDex'
       
        // Allow debug messages (debug mode must also be on)
        static constant boolean ALLOW_DEBUGGING      = true
       
        // Uncomment the lines below to define a filter for units being indexed
       
        /*static method onFilter takes unit u returns boolean
            return true
        endmethod*/

       
    endmodule
/*  _________________________________________________________________________
*   3. Function API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   Every function inlines except for UnitDexRemove
*
*       function GetUnitId takes unit whichUnit returns integer
*       function GetUnitById takes integer index returns unit
*    
*       function UnitDexEnable takes boolean flag returns nothing
*       function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
*       function IsUnitIndexed takes unit u returns boolean
*       function IsIndexingEnabled takes nothing returns boolean
*
*       function GetIndexedUnit takes nothing returns unit
*       function GetIndexedUnitId takes nothing returns integer
*
*       function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
*       function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
*       function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
*       function OnUnitIndex takes code func returns triggercondition
*       function OnUnitDeidex takes code func returns triggercondition
*   _________________________________________________________________________
*   4. Struct API
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       UnitDex.Enabled = false // toggle the indexer
*       UnitDex.Initialized     // returns true if the preload timer has finished
*       UnitDex.Count           // returns the amount of units indexed
*       UnitDex.Unit[0]         // access the UnitDex array directly
*       UnitDex.Group           // a unit group containing every indexed unit (for enumeration)
*       UnitDex.LastIndex       // returns the last indexed unit's id
*   _________________________________________________________________________
*   5. Public Variables
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       These are to be used with the "eventtype" argument of the event API:
*
*           constant integer EVENT_UNIT_INDEX     = 0
*           constant integer EVENT_UNIT_DEINDEX   = 1
*   _________________________________________________________________________
*   6. Examples
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Associate a unit with a variable
*
*           set UnitFlag[GetUnitId(yourUnit)] = true
*
*       2. Allocate a struct instance using a units assigned ID
*
*           local somestruct data = GetUnitId(yourUnit)
*  
*       3. Detect when a unit leaves the map
*
*           function Exit takes nothing returns nothing
*               call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
*           endfunction
*
*           call OnUnitDeindex(function Exit)
*           // or
*           call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
*   _________________________________________________________________________
*   7. How it works
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       1. Enumerate all preplaced units
*       2. TriggerRegisterEnterRegion native to detect when a unit enters the map
*       3. Assign each unit that enters the map a unique integer
*       4. Give every unit an ability based off of defend. There is no native to accurately
*          detect when a unit leaves the map but when a unit dies or is removed from the game
*          they are issued the "undefend" order
*       5. Catch the "undefend" order and remove unit from the queue
*   _________________________________________________________________________
*   8. Notes
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
*       - The object merger line should be commented out after saving and restarting.
*       - All public functions are inlined except UnitDexRemove.
*
*   -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/

   
    globals
        // Event types
        constant integer EVENT_UNIT_INDEX     = 0
        constant integer EVENT_UNIT_DEINDEX   = 1
       
        // System variables
        private trigger array IndexTrig
        private integer Index = 0
        private real E=-1
        private boolexpr FilterEnter
    endglobals
   
    function GetUnitId takes unit whichUnit returns integer
        return GetUnitUserData(whichUnit)
    endfunction
   
    function GetUnitById takes integer index returns unit
        return UnitDex.Unit[index]
    endfunction
   
    function GetIndexedUnit takes nothing returns unit
        return UnitDex.Unit[UnitDex.LastIndex]
    endfunction
   
    function GetIndexedUnitId takes nothing returns integer
        return UnitDex.LastIndex
    endfunction
   
    function IsUnitIndexed takes unit u returns boolean
        return (GetUnitById(GetUnitId(u)) != null)
    endfunction
   
    function UnitDexEnable takes boolean flag returns nothing
        set UnitDex.Enabled = flag
    endfunction
   
    function IsIndexingEnabled takes nothing returns boolean
        return UnitDex.Enabled
    endfunction
   
    function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
        return TriggerAddCondition(IndexTrig[eventtype], func)
    endfunction
   
    function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
        call TriggerRemoveCondition(IndexTrig[eventtype], c)
    endfunction
   
    function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
        call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
    endfunction
   
    function OnUnitIndex takes code func returns triggercondition
        return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
    endfunction

    function OnUnitDeindex takes code func returns triggercondition
        return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
    endfunction
   
    function UnitDexRemove takes unit u, boolean runEvents returns boolean
        return UnitDex.Remove(u, runEvents)
    endfunction
   
    /****************************************************************/
   
    private keyword UnitDexCore
   
    struct UnitDex extends array
        static boolean Enabled = true
       
        readonly static integer LastIndex