1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  5. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  6. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  7. The results are out! Check them out.
    Dismiss Notice
  8. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  9. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  10. 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

TimeWarp.w3x
Variables
TimeWarp Triggers
Retro
Retro Fade
Time Warp
Example
External Libraries
AutoIndex
AutoEvents
Vector
Projectile
Knockback
Group Utils
Cheats
cheats
strings
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.
//TESH.scrollpos=0
//TESH.alwaysfold=0
Name Type Is Array Initial Value
//TESH.scrollpos=193
//TESH.alwaysfold=0
library Retro requires AutoIndex, AutoEvents
//******************************************
//    ___  ___  ___  ___  ___
//   /__/ /__   /   /__/ /  /
//  /  | /__   /   /  \ /__/
//                          Created by Bribe
//******************************************
//  Original AutoIndex Version
//
/*  Requirements
 *  ¯¯¯¯¯¯¯¯¯¯¯¯
 *  AutoIndex  by grim001 ~ http://www.wc3c.net/showthread.php?t=105643
 *  AutoEvents by grim001 ~ http://www.wc3c.net/showthread.php?t=106250
 *  
 *  Description
 *  ¯¯¯¯¯¯¯¯¯¯¯
 *  Retro provides data retrieval and callback operators for use in simulating a time-travel effect for units.
 *  While there is no promise that this is an easy system to implement, I will try to label everything as easy
 *  to read and as simple as I can.  If you have any questions, just ask.
 *
 *  To Know
 *  ¯¯¯¯¯¯¯
 *  On its own, the retro struct indexes every unit that passes through AutoEvents. It records a unit's data
 *  during each lapse of RETRO_TIMEOUT and saves it all in a three-dimensional hashtable. Simple array values
 *  record the current-moment's data aspects: x, y, z and, if you desire, facing angle, HP and MP.
 *  
 *  To allocate your own struct and have access to Retro's excellent callback features, the struct(s) you use
 *  must extend array and implement the module named RETRO. This module uses allocate and deallocate as method
 *  names to regulate those events.  You are welcome to call allocate/deallocate freely as they have built-in
 *  foolproofs. <deallocate> is also automatically called when a unit is loaded into a transport or dies.
 *
 *  You can only instanciate for a given unit once per struct-type.  You can create numerous structs which all
 *  implement the RETRO module.
 *
 *  Events
 *  ¯¯¯¯¯¯
    method onLoop           -> called during each iteration of RETRO_TIMEOUT.
    method onLoopForward    -> called for only one struct per iteration, if it's in forward-mode.
    method onLoopReverse    -> called for only one struct per iteration, if it's in reverse-mode.
 
    method onStart  ->  if you specify a "capture" real value, this will be called after that time has expired.
    method onFinish ->  is called by the deallocate method. deallocate is called automatically if you specify a
                        real value for "capture" or "initiate". It's basically your "onDestroy" method.
 *
 *  Syntax Example
 *  ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    struct timeStruct extends array
   
        method onFinish takes nothing returns nothing
            call BJDebugMsg(GetUnitName(.subject)+"'s stats:\n")
            call BJDebugMsg("X-coordinate is "+R2S(.x))
            call BJDebugMsg("Y-coordinate is "+R2S(.y))
            call BJDebugMsg("Z-coordinate is "+R2S(.z))
            call BJDebugMsg("facing is "+R2S(.ang))
            call BJDebugMsg("hit points are "+R2S(.hp))
            call BJDebugMsg("mana points are "+R2S(.mp)+"\n")
        endmethod
   
        implement RETRO
       
        static method create takes unit newSubject returns thistype
            local thistype this = thistype.allocate(newSubject, true, false)
            return this
        endmethod
       
        static method onInit takes nothing returns nothing
            local thistype this = thistype.create(newSubject)
            call this.initiate(0.03)
        endmethod
       
    endstruct
 *
 *
 */

   
    globals
        // Values are in seconds;
        constant    real        RETRO_TIMEOUT       = 0.03125   /* This value is a tribute to Jesus4Lyf's helpful Timer32 */
        constant    real        RETRO_MEMORY_MAX    = 20.0      /* The hashtable will deallocate data past this threshold */
       
        // Static-if controllers;
        private     constant    boolean     BREAKS_CHANNEL      = true      /* <true> uses SetUnitPosition for smoother motion */
        private     constant    boolean     RECORD_FACING       = true      /* If you're an efficiency-freak and don't care, set to false */
        private     constant    boolean     RECORD_HP           = true      /* Can save the health-points of units */
        private     constant    boolean     RECORD_MP           = false     /* Can save the mana-points of units */
    endglobals
   
    private module Validate
        static method create takes unit whichSubject returns retro
       
            if GetUnitDefaultMoveSpeed(whichSubject) == 0.00 or GetUnitAbilityLevel(whichSubject, 'Aloc') > 0 or IsUnitType(whichSubject, UNIT_TYPE_STRUCTURE) then
                return 0
            else
                return GetUnitId(whichSubject)
            endif
           
        endmethod   // Anything that returns 0 will under no condition be added to the system.
    endmodule
   
    globals
        private     hashtable   retro_memory    = InitHashtable()  /* Serves as a three-dimensional array */
        private     trigger     retro_launch    = CreateTrigger()  /* So this system only needs one timer */
        private     trigger     retro_expire    = CreateTrigger()  /* RETRO uses 5 fewer handles this way */
        constant    integer     RETRO_ERASER    = R2I(RETRO_MEMORY_MAX/RETRO_TIMEOUT)
        private     keyword     RetroInitializer
    endglobals
   
    struct retro extends array
       
        private retro prev
        private retro next
       
        readonly unit subject
        readonly integer save
       
        real x
        real y
        real z
       
        static if RECORD_FACING then    // Static ifs for maximum efficiency.
            real ang
        endif
        static if RECORD_HP then
            real hp
        endif
        static if RECORD_MP then
            real mp
        endif
       
        implement Validate
       
        method destroy takes nothing returns nothing
            debug call BJDebugMsg("Error: You cannot manually destroy a retro struct.")
        endmethod
       
        private static integer tick = 0
       
        private static method periodic takes nothing returns nothing
            local retro this = retro(0).next
            set retro.tick = retro.tick + 1
            if (retro.tick == 4) then
                set retro.tick = 0
                loop
                    exitwhen this == 0
                    set .save = .save + 1
                   
                    set .x = GetUnitX(.subject)
                    set .y = GetUnitY(.subject)
                    set .z = GetUnitFlyHeight(.subject)
                    call SaveReal(retro_memory, this * 6 + 0, .save, .x)
                    call SaveReal(retro_memory, this * 6 + 1, .save, .y)
                    call SaveReal(retro_memory, this * 6 + 2, .save, .z)
                   
                    static if RECORD_FACING then
                        set .ang = GetUnitFacing(.subject)
                        call SaveReal(retro_memory, this * 6 + 3, .save, .ang)
                    endif
                    static if RECORD_HP then
                        set .hp = GetWidgetLife(.subject)
                        call SaveReal(retro_memory, this * 6 + 4, .save, .hp)
                    endif
                    static if RECORD_MP then
                        set .mp = GetUnitState(.subject, UNIT_STATE_MANA)
                        call SaveReal(retro_memory, this * 6 + 5, .save, .mp)
                    endif
                   
                    if (.save >= RETRO_ERASER) then
                        call RemoveSavedReal(retro_memory, this * 6 + 0, .save - RETRO_ERASER)
                        call RemoveSavedReal(retro_memory, this * 6 + 1, .save - RETRO_ERASER)
                        call RemoveSavedReal(retro_memory, this * 6 + 2, .save - RETRO_ERASER)
                        static if RECORD_FACING then
                            call RemoveSavedReal(retro_memory, this * 6 + 3, .save - RETRO_ERASER)
                        endif
                        static if RECORD_HP then
                            call RemoveSavedReal(retro_memory, this * 6 + 4, .save - RETRO_ERASER)
                        endif
                        static if RECORD_MP then
                            call RemoveSavedReal(retro_memory, this * 6 + 5, .save - RETRO_ERASER)
                        endif
                    endif
                    set this = .next
                endloop
            endif
           
            // Fire things;
            call TriggerEvaluate(retro_launch)
        endmethod
       
        private static method onInit takes nothing returns nothing
            call TimerStart(CreateTimer(), RETRO_TIMEOUT, true, function retro.periodic)
        endmethod
       
        static retro removing
       
        private static method remove takes unit exitSubject returns nothing
            local retro this = retro.create(exitSubject)
            if .subject == exitSubject then
           
                set .prev.next = .next
                set .next.prev = .prev
               
                call FlushChildHashtable(retro_memory, this * 6 + 0)
                call FlushChildHashtable(retro_memory, this * 6 + 1)
                call FlushChildHashtable(retro_memory, this * 6 + 2)
                static if RECORD_FACING then
                    call FlushChildHashtable(retro_memory, this * 6 + 3)
                endif
                static if RECORD_HP then
                    call FlushChildHashtable(retro_memory, this * 6 + 4)
                endif
                static if RECORD_MP then
                    call FlushChildHashtable(retro_memory, this * 6 + 5)
                endif
               
                // Fire things;
                set retro.removing = this
                call TriggerEvaluate(retro_expire)
               
                set .subject = null
            endif
        endmethod
       
        private static method add takes unit newSubject returns nothing
            local retro this = retro.create(newSubject)
            if this != 0 then
                set .subject = newSubject
                set .save = 0
                set retro(0).next.prev = this
                set this.next = retro(0).next
                set retro(0).next = this
                set this.prev = retro(0)
            endif
        endmethod
       
        implement RetroInitializer
         
    endstruct
   
    private module RetroInitializer
        private static method onInit takes nothing returns nothing
            call OnUnitIndexed(retro.add)
            call OnUnitDeindexed(retro.remove)
            call OnUnitLoad(retro.remove)
            call OnUnitUnload(retro.add)
            call OnUnitDeath(retro.remove)
            call OnUnitReincarnationFinish(retro.add)
        endmethod
    endmodule
   
    native UnitAlive takes unit id returns boolean
   
    module RETRO    /* May only be implemented in <extends array> structs */
       
        boolean noLoops     /* To toggle a method on/off that gets called <every single> loop */
        boolean noExtras    /* To toggle the extra methods on/off (called for one instance per loop) */
       
        private thistype prev
        private thistype next
     
        private boolean active
        private boolean reverse
        private boolean captured
       
        private real timeLeft
        private real timePend
        private integer iSave
       
        static if RECORD_FACING then
            method operator ang takes nothing returns real  // Delegate ang from retro
                return retro(this).ang
            endmethod
            method operator ang= takes real r returns nothing
                set retro(this).ang = r
            endmethod
        endif
        static if RECORD_HP then
       
            boolean restoreGoodHP
            boolean restoreBadHP
            boolean restoreAnyHP
           
            method operator hp takes nothing returns real   // Delegate hp from retro
                return retro(this).hp
            endmethod
            method operator hp= takes real r returns nothing
                set retro(this).hp = r
            endmethod
           
        endif
        static if RECORD_MP then
       
            boolean restoreGoodMP
            boolean restoreBadMP
            boolean restoreAnyMP
           
            method operator mp takes nothing returns real   // Delegate mp from retro
                return retro(this).mp
            endmethod
            method operator mp= takes real r returns nothing
                set retro(this).mp = r
            endmethod
           
        endif
       
        method operator x takes nothing returns real        // Delegate x
            return retro(this).x
        endmethod
        method operator x= takes real r returns nothing
            set retro(this).x = r
        endmethod
           
        method operator y takes nothing returns real        // Delegate y
            return retro(this).y
        endmethod
        method operator y= takes real r returns nothing
            set retro(this).y = r
        endmethod
           
        method operator z takes nothing returns real        // Delegate z
            return retro(this).z
        endmethod
        method operator z= takes real r returns nothing
            set retro(this).z = r
        endmethod
           
        method operator subject takes nothing returns unit  // Readonly subject
            return retro(this).subject
        endmethod
       
        method deallocate takes nothing returns nothing
            if .active then
                set .active = false
                set .reverse = false
                set .captured = false
                set .prev.next = .next
                set .next.prev = .prev
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, true)
                endif
                static if thistype.onFinish.exists then
                    call .onFinish()
                else
                    call BJDebugMsg("No onFinish found")
                endif
            endif
        endmethod
       
        method initiate takes real duration returns nothing
            set .iSave = retro(this).save
            set .timeLeft = .timeLeft + duration
            set .reverse = true
            static if BREAKS_CHANNEL then
                call SetUnitPathing(.subject, false)
            endif
        endmethod   /* <duration> is the time the unit will be in reverse */
       
        method capture takes real duration returns nothing
            set .timeLeft = .timeLeft + duration
            set .captured = true
            if .reverse then
                set .reverse = false
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, true)
                endif
            endif
        endmethod   /* <duration> is the time the unit will be in forward. Reverse is 1/4 of that */
       
        method collapse takes nothing returns nothing
            if not .reverse and .captured then
                set .timeLeft = (.timePend / 4)
                set .iSave = retro(this).save
                set .timePend = 0.00
                set .captured = false
                set .reverse = true
                static if thistype.onStart.exists then
                    call .onStart()
                endif
                static if BREAKS_CHANNEL then
                    call SetUnitPathing(.subject, false)
                endif
            endif
        endmethod
       
        private static method handler takes nothing returns boolean
            local thistype array choices
            local thistype this = thistype(0).next
            local thistype rand = 0
            local real r
            loop
                exitwhen (this == 0)
               
                if .reverse then
                   
                    set .x = LoadReal(retro_memory, this * 6 + 0, .iSave)
                    set .y = LoadReal(retro_memory, this * 6 + 1, .iSave)
                    set .z = LoadReal(retro_memory, this * 6 + 2, .iSave)
                   
                    static if BREAKS_CHANNEL then
                        call SetUnitPosition(.subject, .x, .y)
                    else
                        call SetUnitX(.subject, .x)
                        call SetUnitY(.subject, .y)
                    endif
                    call SetUnitFlyHeight(.subject, .z, 0.00)
                   
                    static if RECORD_FACING then
                        set .ang = LoadReal(retro_memory, this * 6 + 3, .iSave)
                        call SetUnitFacing(.subject, .ang)
                    endif
                    static if RECORD_HP then  
                        set r = GetWidgetLife(.subject)
                        set .hp = LoadReal(retro_memory, this * 6 + 4, .iSave)
                        if .restoreAnyHP or (.restoreGoodHP and r < .hp - 1) or (.restoreBadHP and r > .hp + 1) then
                            call SetWidgetLife(.subject, .hp)
                        endif
                    endif
                    static if RECORD_MP then
                        set r = GetUnitState(.subject, UNIT_STATE_MANA)
                        set .mp = LoadReal(retro_memory, this * 6 + 5, .iSave)
                        if .restoreAnyMP or (.restoreGoodMP and r < .mp + 1) or (.restoreBadMP and r > .mp + 1) then
                            call SetUnitState(.subject, UNIT_STATE_MANA, .mp)
                        endif
                    endif
                   
                    set .iSave = .iSave - 1
                    set .timeLeft = .timeLeft - RETRO_TIMEOUT
                    if (.timeLeft <= 0.00 or .iSave <= 0) then
                        call .deallocate()
                    endif
                   
                elseif .captured then
                    set .timePend = .timePend + RETRO_TIMEOUT
                    if (.timePend >= .timeLeft) then
                        call .collapse()
                    endif
                endif
               
                static if thistype.onLoop.exists then
                    if not .noLoops then
                        call .onLoop()
                    endif
                endif
                if not .noExtras then
                    set rand:choices = this
                    set rand = rand + 1
                endif
                set this = .next
            endloop
           
            if (rand > 0) then
                set rand = choices[GetRandomInt(0, rand - 1)]
                if rand.reverse then
                    static if thistype.onLoopReverse.exists then
                        call rand.onLoopReverse()
                    endif
                else
                    static if thistype.onLoopForward.exists then
                        call rand.onLoopForward()
                    endif
                endif
            endif
           
            return false
        endmethod
       
        static method allocate takes unit theSubject, boolean wantLoop, boolean wantLoopEx returns thistype
            local thistype this = retro.create(theSubject)
           
            if this == 0 or retro(this).save == 0 then
                return 0
            elseif .active then
                call .deallocate()
            endif
           
            set .active   = true
            set .noLoops  = not wantLoop
            set .noExtras = not wantLoopEx
            set .timeLeft = 0.00
            set .timePend = 0.00
           
            static if RECORD_HP then
                set .restoreGoodHP = false
                set .restoreBadHP = false
                set .restoreAnyHP = false
            endif
            static if RECORD_MP then
                set .restoreGoodMP = false
                set .restoreBadMP = false
                set .restoreAnyMP = false
            endif
           
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)
           
            return this
        endmethod
       
        private static method onRemoval takes nothing returns boolean
            if thistype(retro.removing).active then
                call thistype(retro.removing).deallocate()
            endif
            return false
        endmethod
       
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(retro_launch, Condition(function thistype.handler))    // Enables the handler event
            call TriggerAddCondition(retro_expire, Condition(function thistype.onRemoval))  // Enables the onRemoval event
        endmethod
       
    endmodule
   
    module RetroTimerModule
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(retro_launch, Condition(function thistype.handler))    // Basically for RetroFade
        endmethod
    endmodule
   
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
//! zinc

library RetroFade {
/*
 *  The current fade-functionality is very primitive at the moment.  If you have dynamic fog in your
 *  map that changes sometimes, I don't recommend this library for you.
 */

    constant real
   
        DURATION                = 2.00      ,
        MAX_DISTANCE            = 1000.0    ,   /* Players whose cameras are out of this range will not see the fog fade */
        FLICKER_MAGNITUDE       = 300.0     ;   /* Factors between Z-End + this & Z-End - this */
       
    constant boolean
   
        FLICKER                 = true      ;
   
    real fog[];
    integer units[];
    boolean paused = true, want = false, to = true;
    constant integer Fz = 18, FZ = 19, Fd = 20, Fr = 21, Fg = 22, Fb = 23;
   
    function onInit() {
        integer i;  /*    Your fog settings     Retro's fog settings
    Fog type                     |                       |                   Allowed values
    ¯¯¯¯¯¯¯¯                     V                       V                   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    |  Z-Start */
   fog[0]  = 3000.00   ;     fog[6]  = 1250.0    ; /*      0.00  to  ????
    ---------------------------------------------------------------------------------------
    |    Z-End */
   fog[1]  = 5000.00   ;     fog[7]  = 3250.00   ; /*      0.00  to  ????
    ---------------------------------------------------------------------------------------
    |  Density */
   fog[2]  = 0.50      ;     fog[8]  = 0.1       ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
    |      Red */
   fog[3]  = 1.00      ;     fog[9]  = 0.35      ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
    |    Green */
   fog[4]  = 1.00      ;     fog[10] = 0.35      ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
    |     Blue */
   fog[5]  = 1.00      ;     fog[11] = 0.00      ; /*      0.00  to  1.00
    ---------------------------------------------------------------------------------------
 */

        for (i = 0; i < 24; i+= 1)  i:units = 0;
        for (i = 0; i < 6;  i+= 1) {
       
            if (i:fog==fog[i + 6]) {
                if (i:fog < 0.999)  i:fog+= 0.001;
                else                i:fog-= 0.001;
            }
           
            fog[i + 12] = (i:fog - fog[i + 6])/(DURATION / RETRO_TIMEOUT);
            fog[i + 18] =  i:fog;
        }
    }
       
    public struct retrofade [] {
   
        private static method handler()-> boolean {
            integer i = 18;
           
            if (!want && paused)
                return false;
         
            static if (FLICKER) {
                if (GetRandomReal(0.0, 2.0)<=RETRO_TIMEOUT)
                    SetTerrainFogEx(0, Fz:fog, GetRandomReal(FZ:fog - FLICKER_MAGNITUDE, FZ:fog + FLICKER_MAGNITUDE), Fd:fog, Fr:fog, Fg:fog, Fb:fog);
            }
           
            if (paused)
                return false;
           
            if (to) while (i < 24) { /* Fade into retro-fog */
           
                if   ( i:fog <= fog[i - 12]) {
                       i:fog  = fog[i - 12];        paused = true;  break;
                } else i:fog -= fog[i -  6];                        i+= 1;
            }
            else while (i < 24) { /* Fade into normal-fog */
           
                if   ( i:fog >= fog[i - 18]) {
                       i:fog  = fog[i - 18];        paused = true;  break;
                } else i:fog += fog[i -  6];                        i+= 1;
            }
           
            SetTerrainFogEx (0, Fz:fog, FZ:fog, Fd:fog, Fr:fog, Fg:fog, Fb:fog);
           
            return false;
        }
   
        module RetroTimerModule;
       
        static method CamCheck (real centerX, real centerY) -> boolean {
         real   cam_x= GetCameraTargetPositionX(), cam_y= GetCameraTargetPositionY();
         return(cam_x >= centerX - MAX_DISTANCE && cam_y >= centerY - MAX_DISTANCE &&
                cam_x <= centerX + MAX_DISTANCE && cam_y <= centerY + MAX_DISTANCE );
        }
       
        static method yes (player whichPlayer, real x, real y) {
            integer id = GetPlayerId(whichPlayer);
            if (GetLocalPlayer() != whichPlayer || whichPlayer == null || id > 11)
                return;
               
            if (CamCheck(x, y)) {
                to = true;
                want = true;
                if (paused)
                    paused = false;
            }
           
            units[id]+= 1;
        }
       
        static method no (player whichPlayer) {
            integer id = GetPlayerId(whichPlayer);
            if (GetLocalPlayer() != whichPlayer || whichPlayer==null || id > 11)
                return;
               
            units[id]-= 1;
            if (units[id] <= 0) {
                units[id] = 0;
                to = false;
                want = false;
                if (paused)
                    paused = false;
            }
        }
       
    }
}
//! endzinc
library TimeWarp requires Retro, Projectile, optional Knockback, optional RetroFade
//*********************************************************************************
//   ___ ___ _   _  ___   _    _  _   ___  ___
//   /   /   /| /  /__    |   / /__/ /__/ /__/
//  /  _/_  / |/| /__     |/|/ /  / /  | /
//                                            Created by Bribe
//************************************************************
    globals
        private constant integer    SPELL_ID    = 'A003'    /* The rawcode for the Time Travel spell */
        private constant integer    EXTRA_ID    = 'A001'    /* An ability that collapses the caster's portals */
        private constant integer    DUMMY_ID    = 'n000'    /* This should use the DUMMY.mdx model from XE */
   
//* >> Knockback Values;
        private constant real   KB_DURATION         = 1.125     /* Duration is in seconds */
        private constant real   KB_MIN_DISTANCE     = 225.0     /* Value in WC3 gridspace */
        private constant real   KB_MAX_DISTANCE     = 525.0
                               
//* >> TimeWarp Values;        
        private constant real   BASE_DURATION       = 3.00      /* Base value at level 1 */
        private constant real   LVL_INC_DURATION    = 1.15      /* Level increment value */
                               
        private constant real   BASE_DAMAGE         = 10.0
        private constant real   LVL_INC_DAMAGE      = 4.00
                               
        private constant real   BASE_AOE            = 150.0
        private constant real   LVL_INC_AOE         = 40.0
       
        private   group array   casterGroup
    /*
     * If you have the xebasic library in your map, remove these two lines so they won't conflict:
     */
constant integer    XE_DUMMY_UNITID         = DUMMY_ID
        constant real       XE_MAX_COLLISION_SIZE   = 60.00
    endglobals

    static if LIBRARY_RetroFade then
        globals
            private sound callback = CreateMIDISound("FlashBack1Second", 12700, 12700)
        endglobals
    endif
   
    native UnitAlive takes unit whichUnit returns boolean
   
    static if LIBRARY_Knockback then
        struct retroKB extends knockback
           
            effect attach
            static constant string fx = "Abilities\\Spells\\Orc\\AncestralSpirit\\AncestralSpiritCaster.mdl"
           
            private method onDestroy takes nothing returns nothing
                call DestroyEffect(this.attach)
            endmethod
           
            private static method onInit takes nothing returns nothing
                call Preload(fx)
            endmethod
           
        endstruct
    endif
   
    struct retroproj extends projectile
       
        static constant string launchSkin = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
        static constant string attackSkin = "Abilities\\Spells\\Undead\\DarkSummoning\\DarkSummonMissile.mdl"
        static constant string simpleSkin = "Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl"
       
        static constant string simpImpact = "Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl"
        static constant string painImpact = "Abilities\\Spells\\Demon\\DemonBoltImpact\\DemonBoltImpact.mdl"
       
        private static method onInit takes nothing returns nothing
            call Preload(launchSkin)
            call Preload(attackSkin)
            call Preload(simpleSkin)
            call Preload(simpImpact)
            call Preload(painImpact)
        endmethod
       
        effect      skin
        group       units
       
        boolean     primary = true
       
        real        damage
        real        Time
       
        vector      carryVec
        timewarp    carryWarp
       
        private method onFinish takes nothing returns nothing
            if .primary then
                if UnitAlive(.target) then
                    call timewarp.Initiate(this)
                endif
                call .carryVec.destroy()
            else
                set .carryWarp.go = true
                if UnitAlive(.target) and not .carryWarp.to then
                    call DestroyEffect(AddSpecialEffectTarget(painImpact, .target, "overhead"))
                    call UnitDamageTarget(.carryWarp.caster, .target, .carryWarp.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                else
                    call DestroyEffect(AddSpecialEffect(simpImpact, .x, .y))
                endif
            endif
        endmethod
       
    endstruct
   
    struct timewarp extends array
       
        unit        caster
        unit        oriUnit
        group       units
       
        player      casterOwner
        player      victimOwner
       
        effect      attach
        effect      gloomy
        effect      spooky
       
        boolean     go
        boolean     to
        vector      oriVec
        real        damage
       
        static constant string finalFX      = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageDeathCaster.mdl"
        static constant string callbackFX1  = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
        static constant string callbackFX2  = "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl"
       
        static constant string forwardLoopFX  = "Abilities\\Weapons\\FlyingMachine\\FlyingMachineImpact.mdl"
        static constant string reverseLoopFX1 = "Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl"
        static constant string reverseLoopFX2 = "Abilities\\Spells\\Other\\StrongDrink\\BrewmasterMissile.mdl"
       
        static constant string caughtFX = "Abilities\\Spells\\Items\\AIso\\AIsoTarget.mdl"
        static constant string gloomyFX = "Abilities\\Spells\\Human\\Banish\\BanishTarget.mdl"
        static constant string spookyFX = "Abilities\\Spells\\NightElf\\shadowstrike\\shadowstrike.mdl"
        static constant string attachFX = "Abilities\\Spells\\Undead\\Possession\\PossessionTarget.mdl"
       
        static constant string epicenterFX  = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
        static constant string recallFXCast = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
        static constant string recallFXTarg = "Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
       
        private static method PreloadStrings takes nothing returns nothing
            call Preload(finalFX)
            call Preload(callbackFX1)
            call Preload(callbackFX2)
            call Preload(forwardLoopFX)
            call Preload(reverseLoopFX1)
            call Preload(reverseLoopFX2)
            call Preload(caughtFX)
            call Preload(gloomyFX)
            call Preload(spookyFX)
            call Preload(attachFX)
            call Preload(epicenterFX)
            call Preload(recallFXCast)
            call Preload(recallFXTarg)
        endmethod
       
        private method onFinish takes nothing returns nothing
            call DestroyEffect(AddSpecialEffect(finalFX, .x, .y))
            call DestroyEffect(.spooky)
            call BlzSetSpecialEffectZ(.gloomy, -50000.00) //The effect doesn't disappear immediately, so bury it
            call DestroyEffect(.gloomy)
            call DestroyEffect(.attach)
            call GroupRemoveUnit(.units, .subject)
            call SetUnitTimeScale(.subject, 1.00)
           
            if .oriVec > 0 then
                call .oriVec.destroy()
                set .oriVec = 0
            endif
            static if LIBRARY_RetroFade then
                call retrofade.no(.casterOwner)
                call retrofade.no(.victimOwner)
            endif
           
        endmethod
       
        private method onStart takes nothing returns nothing
            call DestroyEffect(AddSpecialEffectTarget(callbackFX1, .subject, "origin"))
            call DestroyEffect(AddSpecialEffectTarget(callbackFX2, .subject, "origin"))
            call SetUnitTimeScale(.subject, 4.00)
           
            set .restoreBadHP = true
            //set .restoreBadMP = true  <- would throw a syntax error because I have RESTORE_MP set to <false>
           
            static if LIBRARY_RetroFade then
                if retrofade.CamCheck(.x, .y) then  // Only plays the sound if a player's camera is near.
                    call StartSound(callback)
                endif
            endif
           
        endmethod
       
        private method onLoopForward takes nothing returns nothing
            call DestroyEffect(AddSpecialEffectTarget(forwardLoopFX, .subject, "overhead"))
        endmethod
        private method onLoopReverse takes nothing returns nothing
            call DestroyEffect(AddSpecialEffectTarget(reverseLoopFX1, .subject, "origin"))
            call DestroyEffect(AddSpecialEffectTarget(reverseLoopFX2, .subject, "origin"))
        endmethod
       
       
        static vector regVec    // This gets <vector.create> during map init.
       
        static method operator bigVec takes nothing returns vector
            return 8191
        endmethod
       
       
        private method onLoop takes nothing returns nothing
            local real x2
            local real y2
            local retroproj pro
            // Will not launch until the other missile has expired.
            // Check the origin vector to make sure it's not a dud.
            if .go and .oriVec!=0 then
                /* Check if the distance is far enough */
                set x2 = .x - .oriVec.x
                set y2 = .y - .oriVec.y
                if ((x2*x2 + y2*y2) > 0xF000) then
                    /* Yet another important check */
                    if UnitAlive(.subject) then
                        /* bigVec = Location(.x, .y) */
                        call bigVec.getTerrainPoint(.x, .y)
                        /* Add flyheight and offset to vector.Z */
                        set  bigVec.z = bigVec.z + this.z + 90.0
                        if .to then
                            /* If flying toward the subject unit */
                            set .to = false
                            set pro = retroproj.create(AddSpecialEffect(retroproj.attackSkin, .oriVec.x, .oriVec.y), Atan2(.y - .oriVec.y, .x - .oriVec.x))
                            set pro.target = .subject
                            /* Control the dimensions and launch-properties of the projectile */
                            call BlzSetSpecialEffectScale(pro.toUnit, 0.65)
                            call pro.doLaunch(.oriVec, bigVec, 900.0, 0.10)
                        else
                            /* If gliding towards the origin point */
                            set .to = true
                            set pro = retroproj.create(AddSpecialEffect(retroproj.simpleSkin, .x, .y), Atan2(.oriVec.y - .y, .oriVec.x - .x))
                            set pro.source = .oriUnit
                            /* Control the dimensions and launch-properties of the projectile */
                            call BlzSetSpecialEffectScale(pro.toUnit, 0.85)
                            call pro.doLaunch(bigVec, .oriVec, 300.0, 0.10)
                        endif
                       
                        set .go             = false
                        set pro.primary     = false
                        set pro.carryWarp   = this
                       
                    endif
                endif
            endif
        endmethod
       
        implement RETRO
       
        private static effect tempFX = null
        private method createFX takes string fx, real height returns effect
            set tempFX = AddSpecialEffect(fx, .oriVec.x, .oriVec.y)
            call BlzSetSpecialEffectYaw(tempFX, Atan2(.oriVec.y - .y, .oriVec.x - .x))
            call BlzSetSpecialEffectScale(tempFX, 0.80)
            call BlzSetSpecialEffectZ(tempFX, height + .z)
            return tempFX
        endmethod
       
        static method Initiate takes retroproj h returns nothing
            local real dist
            local real x
            local real y
            local thistype this = allocate(h.target, true, true)
           
            if this == 0 then   /* The allocate method will return 0 for stationary units (buildings, wards) */
                return
            endif
           
            set .caster = h.source
            set .units  = h.units
            set .damage = h.damage
           
            static if LIBRARY_RetroFade then
                set .casterOwner = GetOwningPlayer(.caster)
                set .victimOwner = GetOwningPlayer(.subject)
                call retrofade.yes(.casterOwner, .x, .y)
                call retrofade.yes(.victimOwner, .x, .y)
            endif
           
            if .oriVec == 0 then
                set .oriVec = vector.createTerrainPoint(.x, .y)
                set .oriVec.z = .oriVec.z + this.z + 60.00
            endif
           
            set .go = true
            set .to = false
            call GroupAddUnit(.units, .subject)
            call .capture(h.Time)
           
            call DestroyEffect(.createFX(caughtFX, 0.00))
            set .gloomy = .createFX(gloomyFX, 40.00)
            set .spooky = .createFX(spookyFX, 80.00)
            set .attach = .createFX(attachFX, 80.00)
           
            static if LIBRARY_Knockback then
                set x = .x - h.carryVec.x
                set y = .y - h.carryVec.y
                set dist = KB_MAX_DISTANCE - (SquareRoot(x*x + y*y) / 2.0)
                if dist <= KB_MIN_DISTANCE then
                    set dist = KB_MIN_DISTANCE
                endif
                set retroKB.create(.subject, Atan2(y, x), dist, KB_DURATION).attach = AddSpecialEffectTarget(retroKB.fx, h.target, "chest")
            endif
           
        endmethod
       
        static method enumAOE takes nothing returns boolean
            local retroproj p
            local effect e
            local unit u = GetFilterUnit()
            if UnitAlive(u) and IsUnitEnemy(u, thistype(0).casterOwner) and thistype(GetUnitId(u)).subject == u then
               
                call bigVec.getTerrainPoint(GetUnitX(u), GetUnitY(u))
                set bigVec.z    = bigVec.z + 60.0
                set e           = AddSpecialEffect(retroproj.launchSkin, regVec.x, regVec.y)
                set p           = retroproj.create(e, Atan2(bigVec.y - regVec.y, bigVec.x - regVec.x))
                set e = null
                set p.target    = u
                set p.source    = thistype(0).caster
                set p.units     = thistype(0).units
                set p.damage    = thistype(0).damage
                set p.Time      = retroproj(0).Time
                set p.carryVec  = vector.create(regVec.x, regVec.y, regVec.z)
                call p.doLaunch(regVec, bigVec, 900.0, 0.10)
            endif
            set u = null
            return false
        endmethod
       
        static method forceCollapse takes nothing returns boolean
            call thistype(GetUnitId(GetEnumUnit())).collapse()
            call DestroyEffect(AddSpecialEffectTarget(recallFXTarg, GetEnumUnit(), "origin"))
            return false
        endmethod
       
        static method onCast takes nothing returns boolean
            local real factor
            local integer id = GetSpellAbilityId()
           
            if (id == SPELL_ID) then
           
                set thistype(0).caster = GetTriggerUnit()
                set thistype(0).casterOwner = GetTriggerPlayer()
                set thistype(0).units = casterGroup[GetUnitId(thistype(0).caster)]
               
                call regVec.getTerrainPoint(GetSpellTargetX(), GetSpellTargetY())
                set regVec.z = regVec.z + 60.00
               
                call DestroyEffect(AddSpecialEffect(epicenterFX, regVec.x, regVec.y))
               
                set factor = GetUnitAbilityLevel(thistype(0).caster, SPELL_ID) - 1.00
                set thistype(0).damage = BASE_DAMAGE   + LVL_INC_DAMAGE   * factor
                set retroproj(0).Time  = BASE_DURATION + LVL_INC_DURATION * factor
               
                static if LIBRARY_GroupUtils then
                    if GetRandomInt(1, 10) == 1 then
                        call GroupRefresh(thistype(0).units)
                    endif
                    call GroupEnumUnitsInArea(ENUM_GROUP, regVec.x, regVec.y, BASE_AOE + LVL_INC_AOE * factor, Filter(function thistype.enumAOE))
                else
                    call GroupEnumUnitsInRange(bj_lastCreatedGroup, regVec.x, regVec.y, BASE_AOE + LVL_INC_AOE * factor, Filter(function thistype.enumAOE))
                endif
               
            elseif (id == EXTRA_ID) then
                call ForGroup(casterGroup[GetUnitId(GetTriggerUnit())], function thistype.forceCollapse)
                call DestroyEffect(AddSpecialEffectTarget(recallFXCast, GetTriggerUnit(), "origin"))
            endif
           
            return false
        endmethod
       
        static method learnSkill takes nothing returns boolean
            if GetLearnedSkill() == SPELL_ID and GetLearnedSkillLevel() == 1 then
                call UnitAddAbility(GetTriggerUnit(), EXTRA_ID)
               
                static if LIBRARY_Recycle then
                    set casterGroup[GetUnitId(GetTriggerUnit())] = Group.get()
                elseif LIBRARY_GroupUtils then
                    set casterGroup[GetUnitId(GetTriggerUnit())] = NewGroup()
                else
                    set casterGroup[GetUnitId(GetTriggerUnit())] = CreateGroup()
                endif
               
            endif
            return false
        endmethod
       
        static method cleanup takes unit caster returns nothing
            local integer id
            static if LIBRARY_AIDS then
                set id = AIDS_GetDecayingIndex()
            else
                set id = GetUnitId(caster)
            endif
            if casterGroup[id] != null then
                static if LIBRARY_Recycle then
                    call Group.release(casterGroup[id])
                elseif LIBRARY_GroupUtils then
                    call ReleaseGroup(casterGroup[id])
                else
                    call GroupClear(casterGroup[id])
                    call DestroyGroup(casterGroup[id])
                endif
                set casterGroup[id] = null
            endif
        endmethod
       
        static if LIBRARY_AIDS then
            private static method AIDS_OnDeallocate takes nothing returns boolean
                call cleanup(null)
                return false
            endmethod
        endif
       
        private static method onInit takes nothing returns nothing
            local trigger t
           
            set t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
            call TriggerAddCondition(t, Condition(function thistype.learnSkill))
           
            set t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.onCast))
           
            set regVec = vector.create(0.0, 0.0, 0.0)
            call PreloadStrings()
           
            static if LIBRARY_AIDS then
                call AIDS_RegisterOnDeallocate(Filter(function thistype.AIDS_OnDeallocate))
            else
                call OnUnitDeindexed(thistype.cleanup)
            endif
        endmethod
       
    endstruct
   
endlibrary
   
library_once xebasic
/* AutoIndex and GroupUtils will interpret this to set some default values */
endlibrary

 
library RetroExample requires Retro
   
    private struct example extends array
    // Retro structs must <extend array>
       
   
        effect attach   // This effect gets attached to the unit for the duration of the spell.
       
        method onFinish takes nothing returns nothing
            call DestroyEffect(.attach)
        endmethod   // <onFinish> is the <onDestroy> method. You don't need to call <deallocate> from it.
       
    /*
     *  <onLoopReverse> or <onLoopForward> methods are called only once per cycle of RETRO_TIMEOUT,
     *  so they help to moderate high numbers of special effects to keep the frames-per-second low.
     */

        method onLoopReverse takes nothing returns nothing
            call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\StrongDrink\\BrewmasterMissile.mdl", .x, .y))
            call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl", .x, .y))
        endmethod
       
       
        implement RETRO // The module is below <onFinish> and <onLoopReverse> while above <allocate>
       
       
        static method Selection takes nothing returns boolean
            local thistype this
           
            // <allocate> and <deallocate> are provided by the RETRO module.
            set this = allocate(GetFilterUnit(), false, true)
           
            // <allocate> will return <0> if a unit is invalid; I advise checking that before any continuation;
            if this > 0 and GetHeroProperName(.subject) != "Master of Time" then
           
            /*
             *  <initiate> immediately launches a unit back through time for the
             *  specified duration.  This won't do anything for units created in
             *  the same instant; they haven't had time to record some movement.
             */

                call .initiate(2.00)
             
                // The unit you're manipulating is referenced as <.subject>
                set .attach = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl", .subject, "overhead")
               
                // <.restoreGoodHP> will revive the unit if its former HP was greater.
                set .restoreGoodHP = true
               
            /*
             *  <.restoreGoodMP> will do the same, but for MP. Un-commenting the line will provide a syntax
             *  error, as I have the Retro constant boolean RECORD_MP set to <false>, preventing the variable
             *  from even being compiled.
             */

                //set .restoreGoodMP = true
               
                call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl", .subject, "origin"))
            endif
            return false
        endmethod
       
       
        static method onInit takes nothing returns nothing
            local integer i = 0
            local trigger t = CreateTrigger()
            loop
                call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SELECTED, Filter(function thistype.Selection))
                exitwhen i == 11
                set i = i + 1
            endloop
        endmethod   // A normal initialization method.
       
        /*
         *  Note the <onLoop>, <onLoopForward> and <onStart> methods are nowhere in sight. They are optional,
         *  and so are <onLoopReverse> and <onFinish>
         *  
         *  Final Notes
         *  ¯¯¯¯¯¯¯¯¯¯¯
         *  ~ You should only implement an event-method if you have a use for it.
         *  ~ You don't have to call <destroy> on a RETRO-implemented struct unless you wish to call your onFinish method.
         */

         
    endstruct
   
endlibrary
//TESH.scrollpos=646
//TESH.alwaysfold=0
library AutoIndex
//===========================================================================
// Information:
//==============
//
//     AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)
// to get get the unique value assigned to a particular unit. The GetUnitId
// function is extremely fast because it inlines directly to a GetUnitUserData
// call. AutoIndex automatically assigns an ID to each unit as it enters the
// map, and instantly frees that ID as the unit leaves the map. Detection of
// leaving units is accomplished in constant time without a periodic scan.
//
//     AutoIndex uses UnitUserData by default. If something else in your map
// would conflict with that, you can set the UseUnitUserData configuration
// constant to false, and a hashtable will be used instead. Note that hash-
// tables are about 60% slower.
//
//     If you turn on debug mode, AutoIndex will be able to display several
// helpful error messages. The following issues will be detected:
//   -Passing a removed or decayed unit to GetUnitId
//   -Code outside of AutoIndex has overwritten a unit's UserData value.
//   -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
//     AutoIndex provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game. Also
// included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
// you to fully utilize AutoIndex's enter/leave detection capabilities in
// conjunction with your structs.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
//     ability for you. Close and re-open the map. After that, disable the macro
//     to prevent the delay while saving.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
//     So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
/*
    globals
        integer array IntegerData
        real array RealData
        SomeStruct array SomeStructData
    englobals

    function Example takes nothing returns nothing
        local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
        local integer id = GetUnitId(u)
            //You now have a unique index for the unit, so you can
            //attach or retrieve data about the unit using arrays.
            set IntegerData[id] = 5
            set RealData[id] = 25.0
            set SomeStructData[id] = SomeStruct.create()
            //If you have access to the same unit in another function, you can
            //retrieve the data by using GetUnitId() and reading the arrays.
    endfunction
*/

//     The UnitFilter function in the configuration section is provided so that
// you can make AutoIndex completely ignore certain unit-types. Ignored units
// won't be indexed or fire indexed/deindexed events. You may want to filter out
// dummy casters or system-private units, especially ones that use UnitUserData
// internally. xe dummy units are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
//     AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
/*
    function UnitEntersMap takes unit u returns nothing
        call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")
    endfunction //Using GetUnitId() during Indexed events works fine...

    function UnitLeavesMap takes unit u returns nothing
        call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")
    endfunction  //So does using GetUnitId() during Deindexed events.

    function Init takes nothing returns nothing
        call OnUnitIndexed(UnitEntersMap)
        call OnUnitDeindexed(UnitLeavesMap)
    endfunction
*/

//     If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from the need
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
//     OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
//   This function returns a unique ID in the range of 1-8190 for the
//   specified unit. Returns 0 if a null unit was passed. This function
//   inlines directly to GetUnitUserData or LoadInteger if debug mode
//   is disabled. If debug mode is enabled, this function will print
//   an error message when passed a decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
//   This function returns a boolean indicating whether the specified
//   unit has been indexed. The only time this will return false is
//   for units you have filtered using the UnitFilter function, or
//   for xe dummy units. You can use this function to easily detect
//   dummy units and avoid performing certain actions on them.
//
// OnUnitIndexed(IndexFunc)
//   This function accepts an IndexFunc, which must take a unit and
//   return nothing. The IndexFunc will be fired instantly whenever
//   a unit enters the map. You may use GetUnitId on the unit. When
//   you call this function during map initialization, every existing
//   unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
//   Same as above, but runs whenever a unit is leaving the map. When
//   this event runs, the unit still exists, but it will cease to exist
//   as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// How to use AutoData:
//======================
//
//     The AutoData module allows you to associate one or more instances
// of the implementing struct with units, as well as iterate through all
// of the instances associated with each unit.
//
//     This association is accomplished through the "me" instance member,
// which the module will place in the implementing struct. Whichever unit
// you assign to "me" becomes the owner of that instance. You may change
// ownership by reassigning "me" to another unit at any time, or you may
// make the instance unowned by assigning "me" to null.
//
//     AutoData implements the static method operator [] in your struct
// to allow you to access instances from their owning units. For example,
// you may type: local StructName s = StructName[u]. If u has been set
// to own an instance of StructName, s will be set to that instance.
//
//     So, what happens if you assign the same owning unit to multiple
// instances? You may use 2D array syntax to access instances assigned to
// the same unit: local StructName s = StructName[u][n], where u is the
// owning unit, and n is the index beginning with 0 for each unit. You
// can access the size of a unit's instance list (i.e. the number of
// instances belonging to the unit) by using the .size instance member.
/*
    struct Example
        implement AutoData
        static method create takes unit u returns Example
            local Example this = allocate()
                set me = u //Assigning the "me" member from AutoData.
            return this
        endmethod
    endstruct
    function Test takes nothing returns nothing
        local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
        local Example e1 = Example.create(u)
        local Example e2 = Example.create(u)
        local Example e3 = Example.create(u)
        local Example e
            call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.
            set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.
            set e = Example[u]  //This is the fastest way to iterate the instances belonging
            loop                //to a specific unit, starting with the first instance.
                exitwhen e == 0 //e will be assigned to 0 when no instances remain.
                call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.
                set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.
            endloop                    //Thus, index + 1 is next, and index - 1 is previous.
    endfunction                        //This trick allows you to avoid a local counter.
*/

//   AutoData restrictions:
//   -You may not implement AutoData in any struct which has already
//    declared static or non-static method operator [].
//   -AutoData will conflict with anything named "me", "size", or
//    "index" in the implementing struct.
//   -AutoData may not be implemented in structs that extend array.
//   -You may not declare your own destroy method. (This restriction
//    can be dropped as soon as JassHelper supports module onDestroy).
//
//   AutoData information:
//   -You do not need to null the "me" member when destroying an
//    instance. That is done for you automatically during destroy().
//    (But if you use deallocate(), you must null "me" manually.)
//   -StructName[u] and StructName[u][0] refer to the same instance,
//    which is the first instance that was associated with unit u.
//   -StructName[u][StructName[u].size - 1] refers to the instance that
//    was most recently associated with unit u.
//   -Instances keep their relative order in the list when one is removed.
//
//===========================================================================
// How to use AutoCreate:
//=======================
//
//     The AutoCreate module allows you to automatically create instances
// of the implementing struct for units as they enter the game. AutoCreate
// automatically implements AutoData into your struct. Any time an instance
// is automatically created for a unit, that instance's "me" member will be
// assigned to the entering unit.
//
//   AutoCreate restrictions:
//   -All of the same restrictions as AutoData.
//   -If your struct's allocate() method takes parameters (i.e. the parent
//    type's create method takes parameters), you must declare a create
//    method and pass those extra parameters to allocate yourself.
//
//   AutoCreate information:
//   -You may optionally declare the createFilter method, which specifies
//    which units should recieve an instance as they enter the game. If
//    you do not declare it, all entering units will recieve an instance.
//   -You may optionally declare the onCreate method, which will run when
//    AutoCreate automatically creates an instance. (This is just a stand-
//    in until JassHelper supports the onCreate method.)
//   -You may declare your own create method, but it must take a single
//    unit parameter (the entering unit) if you do so.
/*
    struct Example
        private static method createFilter takes unit u returns boolean
            return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
        endmethod
        private method onCreate takes nothing returns nothing
            call BJDebugMsg(GetUnitName(me)+" entered the game!")
        endmethod
        implement AutoCreate
    endstruct
*/

//===========================================================================
// How to use AutoDestroy:
//=========================
//
//     The AutoDestroy module allows you to automatically destroy instances
// of the implementing struct when their "me" unit leaves the game. AutoDestroy
// automatically implements AutoData into your struct. You must assign a unit
// to the "me" member of an instance for this module to have any effect.
//
//   AutoDestroy restrictions:
//   -All of the same restrictions as AutoData.
//
//   AutoDestroy information:
//   -If you also implement AutoCreate in the same struct, remember that it
//    assigns the "me" unit automatically. That means you can have fully
//    automatic creation and destruction.
/*
    struct Example
        static method create takes unit u returns Example
            local Example this = allocate()
                set me = u //You should assign a unit to "me",
            return this    //otherwise AutoDestroy does nothing.
        endmethod          //Not necessary if using AutoCreate.
        private method onDestroy takes nothing returns nothing
            call BJDebugMsg(GetUnitName(me)+" left the game!")
        endmethod
        implement AutoDestroy
    endstruct
*/

//===========================================================================
// Configuration:
//================

// ! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.

globals
    private constant integer LeaveDetectAbilityID = 'lvdt'
    //This rawcode must match the parameter after "Adef" in the
    //ObjectMerger macro above. You may change both if you want.
   
    private constant boolean UseUnitUserData = true
    //If this is set to true, UnitUserData will be used. You should only set
    //this to false if something else in your map already uses UnitUserData.
    //A hashtable will be used instead, but it is about 60% slower.
   
    private constant boolean SafeMode = true
    //This is set to true by default so that GetUnitId() will ALWAYS work.
    //If if this is set to false, GetUnitId() may fail to work in a very
    //rare circumstance: creating a unit that has a default-on autocast
    //ability, and using GetUnitId() on that unit as it enters the game,
    //within a trigger that detects any order. Set this to false for a
    //performance boost only if you think you can avoid this issue.
   
    private constant boolean AutoDataFastMode = true
    //If this is set to true, AutoData will utilize one hashtable per time
    //it is implemented. If this is set to false, all AutoDatas will share
    //a single hashtable, but iterating through the instances belonging to
    //a unit will become about 12.5% slower. Your map will break if you
    //use more than 255 hashtables simultaneously. Only set this to false
    //if you suspect you will run out of hashtable instances.
endglobals

private function UnitFilter takes unit u returns boolean
    return GetUnitAbilityLevel(u, 'Aloc') == 0
endfunction
//Make this function return false for any unit-types you want to ignore.
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
//events. The unit parameter "u" to refers to the unit being filtered.
//Do not filter out xe dummy units; they are automatically filtered.

//===========================================================================
// AutoData / AutoCreate / AutoDestroy modules:
//==============================================

function interface AutoCreator takes unit u returns nothing
function interface AutoDestroyer takes unit u returns nothing

globals                
    hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
endglobals                    //initialized and shared between all AutoData implementations.

module AutoData
    private static hashtable ht
    private static thistype array data
    private static integer array listsize
    private static key typeid //Good thing keys exist to identify each implementing struct.
    private unit meunit
    private integer id
   
    readonly integer index //The user can avoid using a local counter because this is accessable.
   
    static method operator [] takes unit u returns thistype
        return data[GetUnitId(u)]
    endmethod //This is as fast as retrieving an instance from a unit gets.
   
    method operator [] takes integer index returns thistype
        static if AutoDataFastMode then //If fast mode is enabled...
            return LoadInteger(ht, id, index)
        else //Each instance has its own hashtable to associate unit and index.
            return LoadInteger(AutoData, id, index*8190+typeid)
        endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
    endmethod //Somehow, this version is 12.5% slower just because of the math.
   
    private method setIndex takes integer index, thistype data returns nothing
        static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
            call SaveInteger(ht, id, index, data)
        else
            call SaveInteger(AutoData, id, index*8190+typeid, data)
        endif
    endmethod
   
    private method remove takes nothing returns nothing
        if meunit == null then //If the struct doesn't have an owner...
            return             //Nothing needs to be done.
        endif
        loop
            exitwhen index == listsize[id]        //The last value gets overwritten by 0.
            call setIndex(index, this[index + 1]) //Shift each element down by one.
            set this[index].index = index         //Update the shifted instance's index.
            set index = index + 1
        endloop
        set listsize[id] = listsize[id] - 1
        set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
        set meunit = null
    endmethod
   
    private method add takes unit u returns nothing
        if meunit != null then     //If the struct has an owner...
            call remove()          //remove it first.
        endif
        set meunit = u
        set id = GetUnitId(u)      //Cache GetUnitId for slight performance boost.
        if data[id] == 0 then      //If this is the first instance for this unit...
            set data[id] = this    //Update the value that thistype[u] returns.
        endif
        set index = listsize[id]   //Remember the index for removal.
        call setIndex(index, this) //Add to the array.
        set listsize[id] = index + 1
    endmethod
   
    method operator me takes nothing returns unit
        return meunit
    endmethod
   
    method operator me= takes unit u returns nothing
        if u != null then //If assigning "me" a non-null value...
            call add(u)   //Add this instance to that unit's array.
        else              //If assigning "me" a null value...
            call remove() //Remove this instance from that unit's array.
        endif
    endmethod
   
    method operator size takes nothing returns integer
        return listsize[id]
    endmethod
   
    method destroy takes nothing returns nothing
        call deallocate()
        call remove() //This makes removal automatic when an instance is destroyed.
    endmethod
   
    private static method onInit takes nothing returns nothing
        static if AutoDataFastMode then        //If fast mode is enabled...
            set ht = InitHashtable()           //Initialize one hashtable per instance.
        else                                   //If fast mode is disabled...
            if AutoData == null then           //If the hashtable hasn't been initialized yet...
                set AutoData = InitHashtable() //Initialize the shared hashtable.
            endif
        endif
    endmethod
endmodule

module AutoCreate
    implement AutoData //AutoData is necessary for AutoCreate.

    private static method creator takes unit u returns nothing
        local thistype this
        local boolean b = true                          //Assume that the instance will be created.
            static if thistype.createFilter.exists then //If createFilter exists...
                set b = createFilter(u)                 //evaluate it and update b.
            endif
            if b then                                   //If the instance should be created...
                static if thistype.create.exists then   //If the create method exists...
                    set this = create(u)                //Create the instance, passing the entering unit.
                else                                    //If the create method doesn't exist...
                    set this = allocate()               //Just allocate the instance.
                endif
                set me = u                              //Assign the instance's owner as the entering unit.
                static if thistype.onCreate.exists then //If onCreate exists...
                    call onCreate()                     //Call it, because JassHelper should do this anyway.
                endif
            endif
    endmethod

    private static method onInit takes nothing returns nothing
        call AutoIndex.addAutoCreate(thistype.creator)
    endmethod //During module initialization, pass the creator function to AutoIndex.
endmodule

module AutoDestroy
    implement AutoData //AutoData is necessary for AutoDestroy.
   
    static method destroyer takes unit u returns nothing
        loop
            exitwhen thistype[u] == 0
            call thistype[u].destroy()
        endloop
    endmethod //Destroy each instance owned by the unit until none are left.

    private static method onInit takes nothing returns nothing
        call AutoIndex.addAutoDestroy(thistype.destroyer)
    endmethod //During module initialization, pass the destroyer function to AutoIndex.
endmodule

//===========================================================================
// AutoIndex struct:
//===================

function interface IndexFunc takes unit u returns nothing

hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData

private keyword getIndex
private keyword getIndexDebug
private keyword isUnitIndexed
private keyword onUnitIndexed
private keyword onUnitDeindexed

struct AutoIndex
    private static trigger   enter      = CreateTrigger()
    private static trigger   order      = CreateTrigger()
    private static trigger   creepdeath = CreateTrigger()
    private static group     preplaced  = CreateGroup()
    private static timer     allowdecay = CreateTimer()
    private static hashtable ht

    private static boolean array dead
    private static boolean array summoned
    private static boolean array animated
    private static boolean array nodecay
    private static boolean array removing
   
    private static IndexFunc array indexfuncs
    private static integer indexfuncs_n = -1
    private static IndexFunc array deindexfuncs
    private static integer deindexfuncs_n = -1
    private static IndexFunc indexfunc
   
    private static AutoCreator array creators
    private static integer creators_n = -1
    private static AutoDestroyer array destroyers
    private static integer destroyers_n = -1
   
    private static unit array allowdecayunit
    private static integer allowdecay_n = -1
   
    private static boolean duringinit = true
    private static boolean array altered
    private static unit array idunit
   
    //===========================================================================

    static method getIndex takes unit u returns integer
        static if UseUnitUserData then
            return GetUnitUserData(u)
        else
            return LoadInteger(ht, 0, GetHandleId(u))
        endif
    endmethod //Resolves to an inlinable one-liner after the static if.
   
    static method getIndexDebug takes unit u returns integer
            if u == null then
                return 0
            elseif GetUnitTypeId(u) == 0 then
                call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
            elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
                call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
            endif
        return getIndex(u)
    endmethod //If debug mode is enabled, use the getIndex method that shows errors.
   
    private static method setIndex takes unit u, integer index returns nothing
        static if UseUnitUserData then
            call SetUnitUserData(u, index)
        else
            call SaveInteger(ht, 0, GetHandleId(u), index)
        endif
    endmethod //Resolves to an inlinable one-liner after the static if.
   
    static method isUnitIndexed takes unit u returns boolean
        return u != null and idunit[getIndex(u)] == u
    endmethod
   
    static method isUnitAnimateDead takes unit u returns boolean
        return animated[getIndex(u)]
    endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
   
    //===========================================================================
   
    private static method onUnitIndexed_sub takes nothing returns nothing
        call indexfunc.evaluate(GetEnumUnit())
    endmethod
    static method onUnitIndexed takes IndexFunc func returns nothing
        set indexfuncs_n = indexfuncs_n + 1
        set indexfuncs[indexfuncs_n] = func
        if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
            set indexfunc = func
            call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
        endif
    endmethod
   
    static method onUnitDeindexed takes IndexFunc func returns nothing
        set deindexfuncs_n = deindexfuncs_n + 1
        set deindexfuncs[deindexfuncs_n] = func
    endmethod
   
    static method addAutoCreate takes AutoCreator func returns nothing
        set creators_n = creators_n + 1
        set creators[creators_n] = func
    endmethod
   
    static method addAutoDestroy takes AutoDestroyer func returns nothing
        set destroyers_n = destroyers_n + 1
        set destroyers[destroyers_n] = func
    endmethod
   
    //===========================================================================
   
    private static method hook_RemoveUnit takes unit whichUnit returns nothing
        set removing[getIndex(whichUnit)] = true
    endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
    private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
        set removing[getIndex(whichUnit)] = true
    endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
   
    private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
        static if UseUnitUserData then
            if idunit[getIndex(whichUnit)] == whichUnit then
                if getIndex(whichUnit) == data then
                    call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
                else
                    call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
                    if idunit[data] != null then
                        call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
                    endif
                    set altered[data] = true
                endif
            endif
        endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
    endmethod //Displays an error message if outside code tries to alter a unit's index.
   
    //===========================================================================
   
    private static method allowDecay takes nothing returns nothing
        local integer n = allowdecay_n
            loop
                exitwhen n < 0
                set nodecay[getIndex(allowdecayunit[n])] = false
                set allowdecayunit[n] = null
                set n = n - 1
            endloop
            set allowdecay_n = -1
    endmethod //Iterate through all the units in the stack and allow them to decay again.
   
    private static method detectStatus takes nothing returns boolean
        local unit u = GetTriggerUnit()
        local integer index = getIndex(u)
        local integer n
           
            if idunit[index] == u then //Ignore non-indexed units.
                if not IsUnitType(u, UNIT_TYPE_DEAD) then
               
                    if dead[index] then         //The unit was dead, but now it's alive.
                        set dead[index] = false //The unit has been resurrected.
                        //! runtextmacro optional RunAutoEvent("Resurrect")
                        //If AutoEvents is in the map, run the resurrection events.
                       
                        if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
                            set summoned[index] = true //If the unit gained the summoned flag,
                            set animated[index] = true //it's been raised with Animate Dead.
                            //! runtextmacro optional RunAutoEvent("AnimateDead")
                            //If AutoEvents is in the map, run the Animate Dead events.
                        endif
                    endif
                else
               
                    if not removing[index] and not dead[index] and not animated[index] then
                        set dead[index] = true               //The unit was alive, but now it's dead.
                        set nodecay[index] = true            //A dead unit can't decay for at least 0. seconds.
                        set allowdecay_n = allowdecay_n + 1  //Add the unit to a stack. After the timer
                        set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
                        call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
                        //! runtextmacro optional RunAutoEvent("Death")
                        //If AutoEvents is in the map, run the Death events.
                       
                    elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
                        //If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
                        //If .animated was true and the unit is dead, the unit died and exploded.
                        //If .removing was true, the unit is being removed or replaced.
                        set n = deindexfuncs_n
                        loop //Run the OnUnitDeindexed events.
                            exitwhen n < 0
                            call deindexfuncs[n].evaluate(u)
                            set n = n - 1
                        endloop
                        set n = destroyers_n
                        loop //Destroy AutoDestroy structs for the leaving unit.
                            exitwhen n < 0
                            call destroyers[n].evaluate(u)
                            set n = n - 1
                        endloop
                        call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
                        set idunit[index] = null        //Null this unit reference to prevent a leak.
                    endif
                endif
            endif
        set u = null
        return false
    endmethod

    //===========================================================================
   
    private static method unitEntersMap takes unit u returns nothing
        local integer index
        local integer n = 0
            if getIndex(u) != 0 then
                return //Don't index a unit that already has an ID.
            endif
            static if LIBRARY_xebasic then
                if GetUnitTypeId(u) == XE_DUMMY_UNITID then
                    return //Don't index xe dummy units.
                endif
            endif
            if not UnitFilter(u) then
                return //Don't index units that fail the unit filter.
            endif
            set index = create()
            call setIndex(u, index) //Assign an index to the entering unit.
           
            call UnitAddAbility(u, LeaveDetectAbilityID)                 //Add the leave detect ability to the entering unit.
            call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
            set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD)              //Reset all of the flags for the entering unit.
            set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED)      //Each of these flags are necessary to detect
            set animated[index] = false                                  //when a unit leaves the map.
            set nodecay[index] = false
            set removing[index] = false
            debug set altered[index] = false    //In debug mode, this flag tracks wheter a unit's index was altered.
            set idunit[index] = u               //Attach the unit that is supposed to have this index to the index.
           
            if duringinit then                  //If a unit enters the map during initialization...
                call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
            endif                               //all units are noticed by OnUnitIndexed during initialization.
            loop //Create AutoCreate structs for the entering unit.
                exitwhen n > creators_n
                call creators[n].evaluate(u)
                set n = n + 1
            endloop
            set n = 0
            loop //Run the OnUnitIndexed events.
                exitwhen n > indexfuncs_n
                call indexfuncs[n].evaluate(u)
                set n = n + 1
            endloop
    endmethod
   
    private static method onIssuedOrder takes nothing returns boolean
            static if SafeMode then     //If SafeMode is enabled, perform this extra check.
                if getIndex(GetTriggerUnit()) == 0 then  //If the unit doesn't already have
                    call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
                endif
            endif
        return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
    endmethod
   
    private static method initEnteringUnit takes nothing returns boolean
            call unitEntersMap(GetFilterUnit())
        return false
    endmethod
   
    //===========================================================================
   
    private static method afterInit takes nothing returns nothing
        set duringinit = false               //Initialization is over; set a flag.
        call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
        call GroupClear(preplaced)           //The preplaced units group is
        call DestroyGroup(preplaced)         //no longer needed, so clean it.
        set preplaced = null
    endmethod
   
    private static method onInit takes nothing returns nothing
        local region maparea = CreateRegion()
        local rect bounds = GetWorldBounds()
        local group g = CreateGroup()
        local integer i = bj_MAX_PLAYER_SLOTS
            static if not UseUnitUserData then
                set ht = InitHashtable() //Only create a hashtable if it will be used.
            endif
            loop
                exitwhen i < 0
                call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
                //Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
                call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
                //Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
                call GroupEnumUnitsOfPlayer(g, Player(i), function AutoIndex.initEnteringUnit)
                //Enum every non-filtered unit on the map during initialization and assign it a unique
                //index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
                set i = i - 1
            endloop
            call TriggerAddCondition(order, And(function AutoIndex.onIssuedOrder, function AutoIndex.detectStatus))
            //The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
            //And() is used here to avoid using a trigger action, which starts a new thread and is slower.
            call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
            call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
            //The detectStatus method must also fire when a neutral hostile creep dies, in case it was
            //sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
            call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
            call TriggerRegisterEnterRegion(enter, maparea, function AutoIndex.initEnteringUnit)
            //The filter function of an EnterRegion trigger runs instantly when a unit is created.
            call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
            //After any time elapses, perform after-initialization actions.
        call GroupClear(g)
        call DestroyGroup(g)
        call RemoveRect(bounds)
        set g = null
        set bounds = null
    endmethod
   
endstruct

//===========================================================================
// User functions:
//=================

function GetUnitId takes unit u returns integer
    static if DEBUG_MODE then             //If debug mode is enabled...
        return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
    else                                  //If debug mode is disabled...
        return AutoIndex.getIndex(u)      //call the normal, inlinable version.
    endif
endfunction

function IsUnitIndexed takes unit u returns boolean
    return AutoIndex.isUnitIndexed(u)
endfunction

function OnUnitIndexed takes IndexFunc func returns nothing
    call AutoIndex.onUnitIndexed(func)
endfunction

function OnUnitDeindexed takes IndexFunc func returns nothing
    call AutoIndex.onUnitDeindexed(func)
endfunction

endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AutoEvents requires AutoIndex
//===========================================================================
// Information:
//==============
//
//     AutoEvents is an add-on library for AutoIndex. It gives you events that
// detect the following things: when units resurrect, when units are raised with
// Animate Dead, when units begin reincarnating, when units finish reincarnating,
// when transports load units, and when transports unload units. It also provides
// other useful functions: you can check if a unit is currently raised with Ani-
// mate Dead, get the transport carrying a unit, get the number of a units in a
// transport, get the passenger in a specific slot of a transport, and enumerate
// through all of the units in a transport.
//
//===========================================================================
// How to use AutoEvents:
//========================
//
//     You can use the events in this library to run specific functions when
// an event occurs. Unit-related events require a function matching the Stat-
// usEvent function interface, and transport-related events require a function
// matching the TransportEvent function interface:
//
// function interface AutoEvent takes unit u returns nothing
// function interface TransportEvent takes unit transport, unit passenger returns nothing
//
//     The following examples use the AutoEvent function interface:
/*
    function UnitDies takes unit u returns nothing
        call BJDebugMsg(GetUnitName(u)+" has died.")
    endfunction

    function UnitResurrects takes unit u returns nothing
        call BJDebugMsg(GetUnitName(u)+" has been resurrected.")
    endfunction

    function Init takes nothing returns nothing
        call OnUnitDeath(UnitDies)
        call OnUnitResurrect(UnitResurrects)
    endfunction
*/

//     And the following examples use the TransportEvents function interface:
/*
    function UnitLoads takes unit transport, unit passenger returns nothing
        call BJDebugMsg(GetUnitName(transport)+" loaded "+GetUnitName(passenger))
    endfunction

    function UnitUnloads takes unit transport, unit passenger returns nothing
        call BJDebugMsg(GetUnitName(transport)+" unloaded "+GetUnitName(passenger))
    endfunction

    function Init takes nothing returns nothing
        call OnUnitLoad(UnitLoads)
        call OnUnitUnload(UnitUnloads)
    endfunction
*/

//     Here is an example of using ForPassengers to enumerate each unit in
// a transport and heal them for 100 life:
/*
    function HealPassenger takes unit transport, unit passenger returns nothing
        call SetWidgetLife(passenger, GetWidgetLife(passenger) + 100.)
    endfunction
    function HealAllPassengers takes unit transport returns nothing
        call ForPassengers(transport, HealPassenger)
    endfunction
*/

//     GetPassengerBySlot provides an alternative way to enumerate the
// units within a transport. (The following example would heal a unit
// that occupies multiple slots in the transport only one time, since
// GetPassengerBySlot assumes that each unit occupies only one slot.)
/*
    function HealAllPassengers takes unit transport returns nothing
        local integer slot = 1 //Start at slot 1.
        local unit passenger
            loop
                set passenger = GetPassengerBySlot(transport, slot)
                exitwhen passenger == null
                call SetWidgetLife(passenger, GetWidgetLife(passenger) + 100.)
                set slot = slot + 1
            endloop
    endfunction
*/

//===========================================================================
// AutoEvents API:
//=================
//
// OnUnitDeath(AutoEvent)
//   This event runs when any unit dies. It fires after the unit is dead, but
//   before any death triggers fire.
//
// OnUnitResurrect(AutoEvent)
//   This event runs when any unit is resurrected. It also fires when units
//   are raised with Animate Dead or Reincarnation, as those are forms of
//   resurrection as well.
//
// OnUnitAnimateDead(AutoEvent)
//   This event runs when any unit is raised with Animate Dead. It fires after
//   the resurrection event.
//
// IsUnitAnimateDead(unit) -> boolean
//   This function returns a boolean that indicates if the specified unit
//   has been raised with Animate Dead.
//
// OnUnitReincarnateStart(AutoEvent)
//   This event runs when any unit begins reincarnating. The OnUnitDeath event
//   will run first.
//
// OnUnitReincarnateEnd(AutoEvent)
//   This event runs when any unit finishes reincarnating. The OnUnitResurrect
//   event will occur immediately after.
//
// OnUnitLoad(TransportEvent)
//   This event runs when any transport loads a passenger.        
//
// OnUnitUnload(TransportEvent)
//   This event runs when any transport unloads a passenger.
//
// GetUnitTransport(unit)
//   Returns the transport that a unit is loaded in. Returns null if the
//   unit is not riding in any transport.
//
// CountPassengers(transport) -> integer
//   Returns the number of passengers in the specified transport.
//
// GetPassengerBySlot(transport, slot) -> unit
//   Returns the passenger in the given slot of the specified transport.
//   However, if a unit takes  more than one transport slot, it will only be
//   treated as occupying one transport slot.
//
// ForPassengers(transport, TransportEvent)
//   This function runs a TransportEvent immediately for each passenger in
//   the specified transport.
//
//===========================================================================

function interface AutoEvent takes unit u returns nothing

//! textmacro RunAutoEvent takes EVENT
    set n = 0
    loop
        exitwhen n > $EVENT$funcs_n
        call $EVENT$funcs[n].evaluate(u)
        set n = n + 1
    endloop
//! endtextmacro

//Injecting this textmacro into AutoIndex will cause the events to actually run.

//! textmacro AutoEvent takes EVENT, EVENTTYPE
    globals
        $EVENTTYPE$ array $EVENT$funcs
        integer $EVENT$funcs_n = -1
    endglobals
    function OnUnit$EVENT$ takes $EVENTTYPE$ func returns nothing
        set $EVENT$funcs_n = $EVENT$funcs_n + 1
        set $EVENT$funcs[$EVENT$funcs_n] = func
    endfunction
//! endtextmacro

//Instantiate the function to register events of each type.
//! runtextmacro AutoEvent("Death", "AutoEvent")
//! runtextmacro AutoEvent("Resurrect", "AutoEvent")
//! runtextmacro AutoEvent("AnimateDead", "AutoEvent")

//===========================================================================
//The code below this point adds Reincarnation support to AutoEvents.
//Credit to ToukoAozaki for the idea behind this detection method.

//! runtextmacro AutoEvent("ReincarnationStart", "AutoEvent")
//! runtextmacro AutoEvent("ReincarnationFinish", "AutoEvent")
//Create registration functions for reincarnation start and stop events.

globals
    private timer ReincarnateTimer = CreateTimer()
    private boolean array Reincarnated
    private unit array Reincarnating
    private integer Reincarnating_N = -1
endglobals

private function OnResurrect takes unit u returns nothing
    local integer index = GetUnitId(u)
    local integer n
        if Reincarnated[index] then
            set Reincarnated[index] = false
            //If a resurrecting unit is flagged as reincarnating,
            //it's time to run the ReincarnationFinish event.
            //! runtextmacro RunAutoEvent("ReincarnationFinish")
        endif
endfunction

private function ReincarnateCheck takes nothing returns nothing
    local integer n = Reincarnating_N
    local unit u
        loop
            exitwhen n < 0
            set u = Reincarnating[n]
            if GetUnitTypeId(u) != 0 and Reincarnated[GetUnitId(u)] then
                //If the unit is still flagged as reincarnating, it means DeathDetect didn't run.
                //The unit is actually reincarnating, so run the ReincarnationStart event.
                //! runtextmacro RunAutoEvent("ReincarnationStart")
            endif
            set Reincarnating[n] = null
            set n = n - 1
        endloop
        set Reincarnating_N = -1
    set u = null
endfunction

private function OnDeath takes unit u returns nothing
    set Reincarnated[GetUnitId(u)] = true
    //Assume any unit that dies is going to reincarnate, unless this
    //flag is set to false later by the DeathDetect function.
    set Reincarnating_N = Reincarnating_N + 1 //Add the dying unit to a stack and
    set Reincarnating[Reincarnating_N] = u    //check the flag 0. seconds later.
    call TimerStart(ReincarnateTimer, 0., false, function ReincarnateCheck)
endfunction
   
private function DeathDetect takes nothing returns boolean
        set Reincarnated[GetUnitId(GetTriggerUnit())] = false
    return false //Set the Reincarnated flag to false if the unit will not reincarnate.
endfunction

private function OnEnter takes unit u returns nothing
    set Reincarnated[GetUnitId(u)] = false
    //When a unit enters the map, initialize its Reincarnated flag to false.
endfunction
   
private struct ReincarnationInit extends array
    private static method onInit takes nothing returns nothing
        local trigger deathdetect = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(deathdetect, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(deathdetect, function DeathDetect)
            //This trigger runs 0. seconds after OnUnitDeath events,
            //but does not fire if the unit is going to Reincarnate.
            call OnUnitIndexed(OnEnter)
            call OnUnitDeath(OnDeath)
            call OnUnitResurrect(OnResurrect)
    endmethod
endstruct

//===========================================================================
// All of the remaining code deals with transports.

function interface TransportEvent takes unit transport, unit passenger returns nothing

//! runtextmacro AutoEvent("Load", "TransportEvent")
//! runtextmacro AutoEvent("Unload", "TransportEvent")
//Create registration functions for load and unload events.

//! textmacro RunTransportEvent takes EVENT
    set n = 0
    loop
        exitwhen n > $EVENT$funcs_n
        call $EVENT$funcs[n].evaluate(transport, passenger)
        set n = n + 1
    endloop
//! endtextmacro
//The above textmacro is used to run the Load/Unload events in the Transport struct below.

//===========================================================================
//A transport struct is created and attached to any unit detected loading another unit.
//It keeps track of the units within a transport and updates when they load or unload.

private keyword getUnitTransport
private keyword countPassengers
private keyword getPassengerBySlot
private keyword forPassengers

struct Transport
    private static unit array loadedin
    private static Transport array transports
    private static integer array loadedindex
    private static group array groups
    private static integer groups_n = -1
    private static real MaxX
    private static real MaxY
   
    private unit array loaded[10] //Transports can only carry 10 units.
    private integer loaded_n = -1
   
    //===========================================================================
   
    static method getUnitTransport takes unit u returns unit
        return loadedin[GetUnitId(u)]
    endmethod
   
    static method countPassengers takes unit transport returns integer
        return transports[GetUnitId(transport)].loaded_n + 1
    endmethod
   
    static method getPassengerBySlot takes unit transport, integer slot returns unit
            if slot < 1 or slot > 10 then
                return null
            endif
        return transports[GetUnitId(transport)].loaded[slot - 1]
    endmethod
   
    static method forPassengers takes unit transport, TransportEvent func returns nothing
        local Transport this = transports[GetUnitId(transport)]
        local integer n = 0
            if loaded_n == -1 then
                return //Return if transport has no units loaded inside.
            endif
            loop
                exitwhen n > loaded_n
                call func.evaluate(transport, loaded[n])
                //Loop through each passenger and call the TransportEvent func on it.
                set n = n + 1
            endloop
    endmethod
   
    //===========================================================================

    static method loadUnit takes nothing returns boolean
        local unit transport = GetTransportUnit()
        local unit passenger = GetTriggerUnit()
        local Transport this = transports[GetUnitId(transport)]
        local integer n
            if this == 0 then        //If this is the first unit loaded by this transport...
                set this = allocate()                       //allocate a Transport struct,
                set transports[GetUnitId(transport)] = this //and attach it to the transport.
            endif
            set loaded_n = loaded_n + 1      //Increment the passenger counter.
            set loaded[loaded_n] = passenger //Put the passenger in the unit array.
            set loadedindex[GetUnitId(passenger)] = loaded_n //Attach the index to the passenger.
            set loadedin[GetUnitId(passenger)] = transport   //Attach the transport struct to the transport.
            //! runtextmacro RunTransportEvent("Load") //Run the OnUnitLoad events.
            call SetUnitX(passenger, MaxX) //Move the passenger to the edge of the map so that
            call SetUnitY(passenger, MaxY) //unloading will trigger a "unit enters region" event.
        set transport = null
        set passenger = null
        return false
    endmethod
   
    static method unloadUnit takes unit passenger returns nothing
        local unit transport = getUnitTransport(passenger)      //Get the transport unit.
        local Transport this = transports[GetUnitId(transport)] //Get the transport struct.
        local integer n = loadedindex[GetUnitId(passenger)]     //Get the passenger's index.
            loop
                exitwhen n == loaded_n
                set loaded[n] = loaded[n + 1]
                set loadedindex[GetUnitId(loaded[n])] = n
                set n = n + 1 //Starting from the position of the removed unit,
            endloop           //shift everything down by one and update the index.
            set loaded[n] = null
            set loaded_n = loaded_n - 1                  //Decrement the passenger counter.
            set loadedin[GetUnitId(passenger)] = null    //Null the unloaded unit's transport.
            //! runtextmacro RunTransportEvent("Unload") //Run the OnUnitUnload events.
            if loaded_n == -1 then                       //If the transport is now empty...
                call destroy()                           //Destroy the transport struct.
                set transports[GetUnitId(transport)] = 0 //Disassociate it from the unit.
            endif
        set transport = null
    endmethod
   
    //===========================================================================
   
    private static method unitEntersMap takes nothing returns boolean
            if getUnitTransport(GetFilterUnit()) != null then //If the entering unit is in a transport...
                call unloadUnit(GetFilterUnit())              //The unit was unloaded.
            endif
        return false
    endmethod
   
    private static method unitDies takes nothing returns boolean
            if getUnitTransport(GetTriggerUnit()) != null then //If the dying unit is in a transport...
                call unloadUnit(GetTriggerUnit())              //Unload the unit from its transport.
            endif
        return false
    endmethod
   
    private static method onInit takes nothing returns nothing
        local region maparea = CreateRegion()
        local rect bounds = GetWorldBounds()
        local trigger unload = CreateTrigger()
        local trigger load = CreateTrigger()
        local trigger death = CreateTrigger()
            call RegionAddRect(maparea, bounds)
            call TriggerRegisterEnterRegion(unload, maparea, function Transport.unitEntersMap) //When a unit enters the map area,
            call TriggerRegisterAnyUnitEventBJ(load, EVENT_PLAYER_UNIT_LOADED)                 //it may have been unloaded.
            call TriggerAddCondition(load, function Transport.loadUnit) //When a unit loads a unit, run the loadUnit method.
            call TriggerRegisterAnyUnitEventBJ(death, EVENT_PLAYER_UNIT_DEATH)
            call TriggerAddCondition(death, function Transport.unitDies) //Detect when a unit dies in order to unload it.
            call OnUnitDeindexed(Transport.unloadUnit) //When a unit leaves the game, unload it from its transport.
            set Transport(0).loaded_n = -1 //Initialize this to -1 to make CountUnitsInTransport work properly.
            set MaxX = GetRectMaxX(bounds) //Record the coordinates of a corner of the map so
            set MaxY = GetRectMaxY(bounds) //that loaded units can be moved to that location.
        call RemoveRect(bounds)
        set bounds = null
    endmethod
   
endstruct

//===========================================================================
// User functions:
//=================

function IsUnitAnimateDead takes unit u returns boolean
    return AutoIndex.isUnitAnimateDead(u)
endfunction

function GetUnitTransport takes unit u returns unit
    return Transport.getUnitTransport(u)
endfunction

function CountPassengers takes unit transport returns integer
    return Transport.countPassengers(transport)
endfunction

function GetPassengerBySlot takes unit transport, integer slot returns unit
    return Transport.getPassengerBySlot(transport, slot)
endfunction

function ForPassengers takes unit transport, TransportEvent func returns nothing
    call Transport.forPassengers(transport, func)
endfunction

endlibrary
library Vector

//*****************************************************************
//*  VECTOR LIBRARY
//*
//*  written by: Anitarf
//*
//*  The library contains a struct named vector, which represents a
//*  point in 3D space. As such, it has three real members, one for
//*  each coordinate: x, y, z. It also has the following methods:
//*
//*        static method create takes real x, real y, real z returns vector
//*  Creates a new vector with the given coordinates.
//*
//*        method getLength takes nothing returns real
//*  Returns the length of the vector it is called on.
//*
//*        static method sum takes vector augend, vector addend returns vector
//*  Returns the sum of two vectors as a new vector.
//*
//*        method add takes vector addend returns nothing
//*  Similar to sum, except that it doesn't create a new vector for the result,
//*  but changes the vector it is called on by adding the "added" to it.
//*
//*        static method difference takes vector minuend, vector subtrahend returns vector
//*  Returns the difference between two vectors as a new vector.
//*
//*        method subtract takes vector subtrahend returns nothing
//*  Similar to difference, except that it doesn't create a new vector for the result,
//*  but changes the vector it is called on by subtracting the "subtrahend" from it.
//*
//*        method scale takes real factor returns nothing
//*  Scales the vector it is called on by the given factor.
//*
//*        method setLength takes real length returns nothing
//*  Sets the length of the vector it is called on to the given value, maintaining it's orientation.
//*
//*        static method dotProduct takes vector a, vector b returns real
//*  Calculates the dot product (also called scalar product) of two vectors.
//*
//*        static method crossProduct takes vector a, vector b returns vector
//*  Calculates the cross product (also called vector product) of two vectors
//*  and returns it as a new vector.
//*
//*        static method tripleProductScalar takes vector a, vector b, vector c returns real
//*  Calculates the triple scalar product of three vectors.
//*
//*        static method tripleProductVector takes vector a, vector b, vector c returns vector
//*  Calculates the triple vector product of three vectors and returns it as a new vector.
//*
//*
//*        static method projectionVector takes vector projected, vector direction returns vector
//*  Calculates the projection of the vector "projected" onto the vector "direction"
//*  and returns it as a new vector.
//*  Returns null if the vector direction has a length of 0.
//*
//*        method projectVector takes vector direction returns nothing
//*  Projects the vector it is called on onto the vector "direction".
//*  Does nothing if the vector "direction" has a length of 0.
//*
//*        static method projectionPlane takes vector projected, vector normal returns vector
//*  Calculates the projection of the vector projected onto a plane defined by
//*  it's normal vector and returns it as a new vector.
//*  Returns null if the vector "normal" has a length of 0.
//*
//*        method projectPlane takes vector normal returns nothing
//*  Projects the vector it is called on onto a plane defined by it's normal vector.
//*  Does nothing if the vector "normal" has a length of 0.
//*
//*        static method getAngle takes vector a, vector b returns real
//*  Returns the angle between two vectors, in radians, returns a value between 0 and pi.
//*  Returns 0.0 if any of the vectors are 0 units long.
//*
//*        method rotate takes vector axis, real angle returns nothing
//*  Rotates the vector it is called on around the axis defined by the vector "axis"
//*  by the given angle, which should be input in radians.
//*  Does nothing if axis is 0 units long.
//*
//*
//*        static method createTerrainPoint takes real x, real y returns vector
//*  Creates a vector to the given terrain coordinate, taking it's z height into account.
//*
//*        method getTerrainPoint takes real x, real y returns nothing
//*  Sets the vector it is called on to the given terrain coordinate, taking it's z height into account.
//*
//*        static method createTerrainNormal takes real x, real y, real sampleRadius returns vector
//*  Creates the normal vector of the terrain at given coordinates. "sampleRadius" defines
//*  how far apart the reference points will be, if they are further apart, the result will
//*  be an impression of smoother terrain; normaly the value should be between 0 and 128.
//*
//*        method getTerrainNormal takes real x, real y, real sampleRadius returns nothing
//*  Sets the vector it is called on to the normal of the terrain at given coordinates.
//*
//*
//*        method isInCylinder takes vector cylinderOrigin, vector cylinderHeight, real cylinderRadius returns boolean
//*  Determines if a point is within a given cylinder. The cylinder's origin vector points
//*  to the center of one of the two paralel circular sides, and the height vector points
//*  from the origin point to the center of the other of the two paralel circular sides.
//*  Returns false if the point is not in the cylinder or if the vector cylinderHeight is 0 units long.
//*
//*        method isInCone takes vector coneOrigin, vector coneHeight, real coneRadius returns boolean
//*  Determines if a point is within a given cone. The cone's origin vector points to the
//*  center of the circular side, and the height vector points from the origin point to
//*  the tip of the cone.
//*  Returns false if the point is not in the cylinder or if the vector coneHeight is 0 units long.
//*
//*        method isInSphere takes vector sphereOrigin, real sphereRadius returns boolean
//*  Determines if a point is within a give sphere. The sphere's origin vector points to the
//*  center of the sphere.
//*  Returns false if the point is not in the sphere.
//*****************************************************************

    struct vector
        real x
        real y
        real z
       
        static method create takes real x, real y, real z returns vector
            local vector v = vector.allocate()
            set v.x=x
            set v.y=y
            set v.z=z
            return v
        endmethod
       
        method getLength takes nothing returns real
          return SquareRoot(.x*.x + .y*.y + .z*.z)
        endmethod
       
        static method sum takes vector augend, vector addend returns vector
            local vector v = vector.allocate()
            set v.x = augend.x+addend.x
            set v.y = augend.y+addend.y
            set v.z = augend.z+addend.z
            return v
        endmethod
        method add takes vector addend returns nothing
            set this.x=this.x+addend.x
            set this.y=this.y+addend.y
            set this.z=this.z+addend.z
        endmethod
       
        static method difference takes vector minuend, vector subtrahend returns vector
            local vector v = vector.allocate()
            set v.x = minuend.x-subtrahend.x
            set v.y = minuend.y-subtrahend.y
            set v.z = minuend.z-subtrahend.z
            return v
        endmethod
        method subtract takes vector subtrahend returns nothing
            set this.x=this.x-subtrahend.x
            set this.y=this.y-subtrahend.y
            set this.z=this.z-subtrahend.z
        endmethod
       
        method scale takes real factor returns nothing
            set this.x=this.x*factor
            set this.y=this.y*factor
            set this.z=this.z*factor
        endmethod
       
        method setLength takes real length returns nothing
            local real l = SquareRoot(.x*.x + .y*.y + .z*.z)
            if l == 0.0 then
                debug call BJDebugMsg("Attempted to set the length of a vector with no length!")
                return
            endif
            set l = length/l
            set this.x = this.x*l
            set this.y = this.y*l
            set this.z = this.z*l
        endmethod
       
        static method dotProduct takes vector a, vector b returns real
            return (a.x*b.x+a.y*b.y+a.z*b.z)
        endmethod
       
        static method crossProduct takes vector a, vector b returns vector
            local vector v = vector.allocate()
            set v.x = a.y*b.z - a.z*b.y
            set v.y = a.z*b.x - a.x*b.z
            set v.z = a.x*b.y - a.y*b.x
            return v
        endmethod

        static method tripleProductScalar takes vector a, vector b, vector c returns real
            return ((a.y*b.z - a.z*b.y)*c.x+(a.z*b.x - a.x*b.z)*c.y+(a.x*b.y - a.y*b.x)*c.z)
        endmethod

        static method tripleProductVector takes vector a, vector b, vector c returns vector
            local vector v = vector.allocate()
            local real n = a.x*c.x+a.y*c.y+a.z*c.z
            local real m = a.x*b.x+a.y*b.y+a.z*b.z
            set v.x = b.x*n-c.x*m
            set v.y = b.y*n-c.y*m
            set v.z = b.z*n-c.z*m
            return v
        endmethod

// ================================================================

        static method projectionVector takes vector projected, vector direction returns vector
            local vector v = vector.allocate()
            local real l = direction.x*direction.x+direction.y*direction.y+direction.z*direction.z
            if l == 0.0 then
                call v.destroy()
                debug call BJDebugMsg("Attempted to project onto a vector with no length!")
                return vector(0)
            endif
            set l = (projected.x*direction.x+projected.y*direction.y+projected.z*direction.z) / l
            set v.x = direction.x*l
            set v.y = direction.y*l
            set v.z = direction.z*l
            return v
        endmethod
        method projectVector takes vector direction returns nothing
            local real l = direction.x*direction.x+direction.y*direction.y+direction.z*direction.z
            if l == 0.0 then
                debug call BJDebugMsg("Attempted to project onto a vector with no length!")
                return
            endif
            set l = (this.x*direction.x+this.y*direction.y+this.z*direction.z) / l
            set this.x = direction.x*l
            set this.y = direction.y*l
            set this.z = direction.z*l
        endmethod

        static method projectionPlane takes vector projected, vector normal returns vector
            local vector v = vector.allocate()
            local real l = normal.x*normal.x+normal.y*normal.y+normal.z*normal.z
            if l == 0.0 then
                call v.destroy()
                debug call BJDebugMsg("Attempted to project onto an undefined plane!")
                return vector(0)
            endif
            set l = (projected.x*normal.x+projected.y*normal.y+projected.z*normal.z) / l
            set v.x = projected.x - normal.x*l
            set v.y = projected.y - normal.y*l
            set v.z = projected.z - normal.z*l
            return v
        endmethod
        method projectPlane takes vector normal returns nothing
            local real l = normal.x*normal.x+normal.y*normal.y+normal.z*normal.z
            if l == 0.0 then
                debug call BJDebugMsg("Attempted to project onto an undefined plane!")
                return
            endif
            set l = (this.x*normal.x+this.y*normal.y+this.z*normal.z) / l
            set this.x = this.x - normal.x*l
            set this.y = this.y - normal.y*l
            set this.z = this.z - normal.z*l
        endmethod

        static method getAngle takes vector a, vector b returns real
            local real l = SquareRoot(a.x*a.x + a.y*a.y + a.z*a.z)*SquareRoot(b.x*b.x + b.y*b.y + b.z*b.z)
            if l == 0 then
                debug call BJDebugMsg("Attempted to get angle between vectors with no length!")
                return 0.0
            endif
            return Acos((a.x*b.x+a.y*b.y+a.z*b.z)/l) //angle is returned in radians
        endmethod
       
        method rotate takes vector axis, real angle returns nothing //angle is taken in radians
            local real xx
            local real xy
            local real xz
            local real yx
            local real yy
            local real yz
            local real zx
            local real zy
            local real zz
            local real al = axis.x*axis.x+axis.y*axis.y+axis.z*axis.z //axis length^2
            local real f
            local real c = Cos(angle)
            local real s = Sin(angle)
            if al == 0.0 then
                debug call BJDebugMsg("Attempted to project onto a vector with no length!")
                return
            endif
            set f = (this.x*axis.x+this.y*axis.y+this.z*axis.z) / al
            set zx = axis.x*f
            set zy = axis.y*f
            set zz = axis.z*f //axis component of rotated vector
            set xx = this.x-zx
            set xy = this.y-zy
            set xz = this.z-zz //component of vector perpendicular to axis
            set al = SquareRoot(al)
            set yx = (axis.y*xz - axis.z*xy)/al
            set yy = (axis.z*xx - axis.x*xz)/al //y same length as x by using cross product and dividing with axis length
            set yz = (axis.x*xy - axis.y*xx)/al //x,y - coordinate system in which we rotate
            set this.x=xx*c+yx*s+zx
            set this.y=xy*c+yy*s+zy
            set this.z=xz*c+yz*s+zz
        endmethod
       
// ================================================================

        private static location loc = Location(0.0,0.0)

        static method createTerrainPoint takes real x, real y returns vector
            local vector v = vector.allocate()
            call MoveLocation(vector.loc,x,y)
            set v.x=x
            set v.y=y
            set v.z=GetLocationZ(loc)
            return v
        endmethod
        method getTerrainPoint takes real x, real y returns nothing
            call MoveLocation(vector.loc,x,y)
            set this.x=x
            set this.y=y
            set this.z=GetLocationZ(loc)
        endmethod

        static method createTerrainNormal takes real x, real y, real sampleRadius returns vector
            local vector v = vector.allocate()
            local real zx
            local real zy
            call MoveLocation(vector.loc, x-sampleRadius, y)
            set zx=GetLocationZ(vector.loc)
            call MoveLocation(vector.loc, x+sampleRadius, y)
            set zx=zx-GetLocationZ(vector.loc)
            call MoveLocation(vector.loc, x, y-sampleRadius)
            set zy=GetLocationZ(vector.loc)
            call MoveLocation(vector.loc, x, y+sampleRadius)
            set zy=zy-GetLocationZ(vector.loc)
            set sampleRadius=2*sampleRadius
            set v.x = zx*sampleRadius
            set v.y = zy*sampleRadius
            set v.z = sampleRadius*sampleRadius
            return v
        endmethod
        method getTerrainNormal takes real x, real y, real sampleRadius returns nothing
            local real zx
            local real zy
            call MoveLocation(vector.loc, x-sampleRadius, y)
            set zx=GetLocationZ(vector.loc)
            call MoveLocation(vector.loc, x+sampleRadius, y)
            set zx=zx-GetLocationZ(vector.loc)
            call MoveLocation(vector.loc, x, y-sampleRadius)
            set zy=GetLocationZ(vector.loc)
            call MoveLocation(vector.loc, x, y+sampleRadius)
            set zy=zy-GetLocationZ(vector.loc)
            set sampleRadius=2*sampleRadius
            set this.x = zx*sampleRadius
            set this.y = zy*sampleRadius
            set this.z = sampleRadius*sampleRadius
        endmethod

// ================================================================

        method isInCylinder takes vector cylinderOrigin, vector cylinderHeight, real cylinderRadius returns boolean
            local real l

            local real x = this.x-cylinderOrigin.x
            local real y = this.y-cylinderOrigin.y
            local real z = this.z-cylinderOrigin.z
            if x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z < 0.0 then //point below cylinder
                return false
            endif
           
            set x = x-cylinderHeight.x
            set y = y-cylinderHeight.y
            set z = z-cylinderHeight.z
            if x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z > 0.0 then //point above cylinder
                return false
            endif
           
            set l = cylinderHeight.x*cylinderHeight.x+cylinderHeight.y*cylinderHeight.y+cylinderHeight.z*cylinderHeight.z
            if l == 0.0 then
                debug call BJDebugMsg("Cylinder with no height!")
                return false
            endif
            set l = (x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z) / l
            set x = x - cylinderHeight.x*l
            set y = y - cylinderHeight.y*l
            set z = z - cylinderHeight.z*l
            if x*x+y*y+z*z > cylinderRadius*cylinderRadius then //point outside cylinder
                return false
            endif
           
            return true
        endmethod

        method isInCone takes vector coneOrigin, vector coneHeight, real coneRadius returns boolean
            local real l

            local real x = this.x-coneOrigin.x
            local real y = this.y-coneOrigin.y
            local real z = this.z-coneOrigin.z
            if x*coneHeight.x+y*coneHeight.y+z*coneHeight.z < 0.0 then //point below cone
                return false
            endif
           
            set l = coneHeight.x*coneHeight.x+coneHeight.y*coneHeight.y+coneHeight.z*coneHeight.z
            if l == 0.0 then
                debug call BJDebugMsg("cone with no height!")
                return false
            endif
            set l = (x*coneHeight.x+y*coneHeight.y+z*coneHeight.z) / l
            set x = x - coneHeight.x*l
            set y = y - coneHeight.y*l
            set z = z - coneHeight.z*l
            if SquareRoot(x*x+y*y+z*z) > coneRadius*(1.0-l) then //point outside cone
                return false
            endif
           
            return true
        endmethod

        method isInSphere takes vector sphereOrigin, real sphereRadius returns boolean
            if sphereRadius*sphereRadius < ((this.x-sphereOrigin.x)*(this.x-sphereOrigin.x)+(this.y-sphereOrigin.y)*(this.y-sphereOrigin.y)+(this.z-sphereOrigin.z)*(this.z-sphereOrigin.z)) then
                return false
            endif
            return true
        endmethod
    endstruct

endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Projectile requires Vector
//*********************************************************************************************************
//*
//*     WarCraft III: Custom Projectiles Library
//*     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//*     Author:     Berbanog / TheKid
//*     Version:    1.7
//*
//*     Requirements:
//*         • JassHelper 0.A.2.B
//*         • Vector library by Anitarf
//*
//*     To Know:
//*         • Pitch rotation only works with the dummy model that is included in the imports to this
//*         map. It is Vexorian/Infrane's with many animations for different pitch angles.
//*         • Projectile groups are limited by the number of projectiles that they can contain.
//*
//*     Description:
//*         • Duplicates the cosmetics of an in-game projectile and includes functionality dedicated
//*         to the manipulation of projectiles.
//*
//*     Changelog:
//*         • 1.2   a)  Added pause functionality to timescale (zero timescale means frozen or paused).
//*                 b)  Fixed test-map problem.
//*
//*         • 1.3   a)  Added functionality for .toRemove, which acts like .toKill except instead of
//*                     having the projectile-unit killed it is removed. They can both be used simultaneously.
//*                 b)  Added function GetTotalProjectiles which is basically just a debugging tool.
//*
//*         • 1.4   a)  Added more functionality to projectile groups. They now include the .add and
//*                     .remove methods as well as the .inGroup boolean which takes a projectile parameter.
//*                 b)  Added the isValidTargetUnit interface method that is used to filter the unit
//*                     that is collected by the .onUnitCollision interface method.
//*                 c)  The name of the library has been changed from Projectiles to Projectile. I can't
//*                     seem to remember whether this update was made in 1.3 or 1.4, but it is for my own
//*                     programming grammar standards.
//*
//*         • 1.5   a)  Fixed more issues with timescale. It should now be working 100% correctly.
//*                 b)  Added the .removeIndex to the method API of projectile groups. This makes removing
//*                     projectiles faster in certain situations.
//*                 c)  Added operators for projectile position; .x, .y, and .z.
//*                 d)  Added a hashtable to dynamic storage for use with projectile groups, hopefully
//*                     this second method of reference will improve the functionality of these groups.
//*                 e)  Added destructable collision, as well as a .isValidTargetDest method filter for
//*                     destructables that trigger the collision.
//*
//*         • 1.6   a)  Fixed the z-arc when the projectile is following a target so that the projectile
//*                     does not simply move directly towards the target once it moves (ugly).
//*                 b)  Fixed a timescale issue on projectiles that have moving targets that would cause
//*                     the projectile to be destroyed earlier if it's time-scale was manipulated.
//*
//*         • 1.6b  a)  Fixed test-map references to imported code so that they actually contain the
//*                     reqiored cpde for testing.
//*                 b)  Optimized the destructible collision by removing the code and executing all
//*                     necessary actions in the boolexpr filter.
//*
//*         • 1.6c  a)  Optimized code further. Eliminated unnecessary function calls.
//*                         - Replaced repeated Filter call with a static reference.
//*                         - Eliminated a square root call in setTargetPos() method.
//*                         - Eliminated a square root call in loop iteration on target seeking projectiles.
//*
//*         • 1.6d  a)  Included method operators for retrieving the coordinates of the target vector,
//*                     which will allow some more accurate enumeration of nearby units for something like
//*                     an area of effect spell.
//*                 b)  Fixed a logic error that caused the rotation functionality to cease when it should
//*                     not: (x2 != 0 and y2 != 0 and z2 != 0) to (x2 != 0 or  y2 != 0 or  z2 != 0)
//*                 c)  Fixed a logic error that doesn't seem to affect any functionality associated with
//*                     the pitch animation calculation.
//*
//*         • 1.7   a)  Removed the height check when executing a unit-collision response to allow more
//*                     exact duplication of Warcraft III mechanics.
//*                 b)  Replaced a "SquareRoot" call with another mathematical derivation of the same value.
//*                 c)  Cleaned up a few unnecessary libraries, such as "LinkedList" from the test-map.
//*
//*                     If the user needs to detect spherical collision (as was done before) they can
//*                     compare the projectile/unit Z values. This only improves functionality, it will
//*                     not restrict the user in any way.
//*
//*
//*
//*********************************************************************************************************

globals
//************************
//* Configuration
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Includes default definitions of projectile settings in addition to other values that are used
//* within the system but rely on a value in order to function.
//*
//* >> Medihv's crow-form ability which allows non-flying units to have their fly-height modified;
    public      constant integer            FLY_ABIL_ID                     = 'Amrf'          
//*
//* >> Projectile enumeration constants;
    public      constant integer            PROJ_ENUM_COUNT_MAX             = 64
//*
//* >> Default constants; these can be changed per instance of each projectile.
    public      constant real               PROJ_COLLISION_DEFAULT          = 70.00
    public      constant real               PROJ_COLLISION_MAX              = 200.00
//*
    public      constant real               PROJ_MOTION_REF                 = 0.03              //* This identifies the amount of time (in seconds) that lapses
                                                                                                //* between each iteration that the projectiles are "updated";
//* >> Active feature defaults;
    private     constant boolean            default__targetFollow           = true
    private     constant boolean            default__unitCollision          = true              //* The unit-collision feature of projectiles is on by default.
    private     constant boolean            default__destCollision          = true              //* The destructible-collision feature of the projectile is on
                                                                                                //* by default.
    private     constant boolean            default__faceRotation           = true              //* Whether or not the unit's facing will be updated to match the
                                                                                                //* direction of the velocity vector.
    private     constant boolean            default__facePitch              = true              //* Whether or not a unit will have its pitch-rotation adjusted
                                                                                                //* when the projectile is updated. Only guaranteed to work with
                                                                                                //* the dummy.mdx included in the imports (by Vexorian).
    private     constant boolean            default__toExpire               = true              //* By default, projectiles are flagged to be destroyed after their
                                                                                                //* determined path unless otherwise specified. If it is necessary
                                                                                                //* to manipulate projectiles after their determined lifespan, then
                                                                                                //* this should be set to false.
    private     constant boolean            default__toKill                 = true              //* Once the projectile expires, the unit associated with the
                                                                                                //* projectile will in turn be killed.
    private     constant boolean            default__toRemove               = false             //* Once the projectile expires, the unit associated with the
                                                                                                //* projectile will in turn be removed.
//*
//*
//*********************************************************************************************************
endglobals

interface projectileinterface
//***************************
//* Projectile Interface
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Includes the names for methods (and their parameters) that users can declare in child-structures to
//* acquire reference to a projectile on different occurances that prompt a response.
//*
//* >> Responses associated with time-line "events";
    method  onStart             takes nothing returns nothing           defaults nothing
    method  onFinish            takes nothing returns nothing           defaults nothing
//*
//* >> Responses associated with physical surrounding occurances;
    method  onGround            takes nothing returns nothing           defaults nothing
//*
//* >> Responses associated with widget collision;
    method  onUnitCollision     takes unit u returns nothing            defaults nothing
    method  onDestCollision     takes destructable d returns nothing    defaults nothing
//*
//* >> The unit collision event is used to filter units that trigger
//*    the onUnitCollision method event response.
    method  isValidTargetUnit   takes unit u returns boolean            defaults true
    method  isValidTargetDest   takes destructable d returns boolean    defaults true
//*
//*
//*********************************************************************************************************
endinterface

globals
//********************
//* Dynamic Storage
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* In order to optimize the Projectiles efficiency, certain global variables are required to make special
//* calculations and operations; such as GetLocationZ().
//*
    private     projectile          projRef
    private     location            loc                     = Location(0, 0)
    private     group               grp                     = CreateGroup()
    private     rect                rct                    
//*
//* Hashtable Storage
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    private     hashtable           table                   = InitHashtable()
//*
//* -------------------------------------------------------------------------------------------------------
    private     integer             totalProjectiles        = 0
//* -------------------------------------------------------------------------------------------------------
//*
//*********************************************************************************************************
endglobals


function GetTotalProjectiles takes nothing returns integer
// ----
// This function is for debugging purposes. It returns the total
// amount of projectiles created that have not yet been destroyed.
//
    return totalProjectiles
endfunction


struct projectilegroup
//************************
//* Projectile Group
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Enumeration is a big part of WarCraft III, and it would be hard to really utilize projectiles properly
//* without having some method of enumeration. At this point this structure is pretty basic, including
//* only a method for adding a projectile to the group (can only be done this way for security).
//*
//*     Update 1.5              >> Some values are easier to retrieve if the projectiles are added to a
//*                             hashtable, so they are added to both a linear array as well as a hashtable.
//*
    readonly    projectile array    at      [PROJ_ENUM_COUNT_MAX]
    readonly    integer             max     = 0
//*
    method onDestroy takes nothing returns nothing
        call FlushChildHashtable(table, this)
    endmethod
//* ----
//* Group Methods & Operators
//* =======================================================================================================
//*
    method add takes projectile p returns boolean
        if .max < PROJ_ENUM_COUNT_MAX then
            set .at[.max] = p
            set .max = .max + 1
            call SaveBoolean(table, this, integer(p), true)
            return true
        endif
        return false
    endmethod
    method remove takes projectile p returns boolean
        local integer i=.max-1
        call RemoveSavedBoolean(table, this, integer(p))
        loop
            exitwhen(i < 0)
            if (.at[i]==p) then
                set .max   = .max-1
                set .at[i] = .at[.max]
                return true
            endif
            set i=i-1
        endloop
        return false        // If the projectile was not in the group, it will return false;
    endmethod
    method removeIndex takes integer index returns boolean
        call RemoveSavedBoolean(table, this, .at[index])
        if (index < .max) then                      // Remove index can be used on an index of the
            set .max        = .max - 1              // projectile group to save the trouble of
            set .at[index]  = .at[.max]             // looping through the group until the indexed
            return true                             // projectile is found.
        endif
        return false
    endmethod               // If the index is within the bounds of the group it will return true;
//*
//*
    method inGroup takes projectile p returns boolean
        return HaveSavedBoolean(table, this, p)
    endmethod
//*
//*
//*********************************************************************************************************
endstruct

struct projectile extends projectileinterface
//*******************************************
//* Projectile
//* ¯¯¯¯¯¯¯¯¯¯
//* Primary data-structure that encapsullates the projectile functionality. Contains methods for creating,
//* destroying, and manipulating certain aspects of the projectile motion.
//*
//* #######################################################################################################
//*
    readonly    effect          toUnit              = null
    private     unit            priv_target         = null              //* Both .priv_target and .priv_source can be referenced publicly by using the
    private     unit            priv_source         = null              //* method operators without the "priv_" prefix.
//*
    readonly    real            speed                                   //* In order to launch a projectile, a "speed" and "arc" input is necessary. These
    readonly    real            arc                                     //* values are stored in readonly-scope variables so that they can be referenced
                                                                        //* externally.
    private     real            priv_timescale      = 1.00
    private     real            priv_collision      = PROJ_COLLISION_DEFAULT
//*
//* These are used to maintain necessary projectile checks. They are also used to determine the features
//* that should be included in each specific projectile, such as "activeUnitCollision".
//*
    private     boolean         active              = false                         //* This is automatically updated depending on whether the projectile
                                                                                    //* has been launched.
    public      boolean         activeTargetFollow  = default__targetFollow    
    public      boolean         activePitch         = default__facePitch      
    public      boolean         activeRotation      = default__faceRotation         //* Whether the unit's facing will be updated to match the 2D direction
                                                                                    //* of the projectile's velocity vector.
    public      boolean         activeUnitCollision = default__unitCollision
    public      boolean         activeDestCollision = default__destCollision
//*
//* In order to prevent multiple responses of certain projectile events, the "state" of the projectile is
//* stored in a private-scope variable. The events that are included in this are:
//*     • method onGround
//*     • method onFinish
//*
    private     boolean         priv_ground         = false
    private     boolean         priv_finish         = false
//*
//* Other private status checks:
    private     boolean         priv_follow         = false
    private     boolean         priv_unitfollow     = false
    private     boolean         priv_paused         = false
//*
//* Projectile motion is dictated by 4 vectors: position, velocity, acceleration, and target-position. If
//* these values are manipulated they can be used to achieve certain effects.
//*
    private     vector          posVec      // These are all created when the projectile is created. All
    private     vector          velVec      // values are 0 until the projectile has been launched except
    private     vector          accVec      // for the position (posVec) of the projectile.
    private     vector          tarVec
    private     vector          strVec      // The starting point of a projectile motion is given when the
//*                                         // projectile is launched, and used for arc-motion reference.
//*
//* Projectiles are updated in a stack; and require certain data on-hand in order to properly update and
//* manage the stack.
//*
    private         integer         priv_index            
    private static  integer         priv_stackDex       = 0                     //* The amount of projectiles currently in the stack.
//*
    private static  thistype array  priv_stack
    private static  timer           priv_stackLoop      = CreateTimer()
    private static  constant real   priv_stackLoopRef   = PROJ_MOTION_REF
//*
//* To make the enumeration process slightly faster, use a reference variable to store the filter so it
//* doesn't have to be referenced via function.
    private static  boolexpr        priv_filterFunc     = null
//*
    private         real            priv_timeleft    
    private         boolean         priv_killnext       = false
//*
    public          boolean         toDestroy           = default__toExpire     //* Once the projectile finishes it's launch process, these
    public          boolean         toKill              = default__toKill       //* flags will determine whether special procedure is taken,
    public          boolean         toRemove            = default__toRemove     //* such as destroying the projectile once it completes its
//*                                                                             //* path, or killing the projectile-unit once it is destroyed.
//*
//* #######################################################################################################
//*
//* Position
//* =======================================================================================================
    method operator x takes nothing returns real
        return .posVec.x
    endmethod
    method operator y takes nothing returns real
        return .posVec.y
    endmethod
    method operator z takes nothing returns real
        return .posVec.z
    endmethod
//*
//* Collision
//* =======================================================================================================
    method operator collision takes nothing returns real
        return .priv_collision
    endmethod
    method operator collision= takes real r returns nothing
        if (r>PROJ_COLLISION_MAX) then
            set r=PROJ_COLLISION_MAX
        elseif (r<0) then
            set r=0
        endif
        set .priv_collision=r
    endmethod
//*
//* Source
//* =======================================================================================================
    method operator source takes nothing returns unit
        return .priv_source
    endmethod
    method operator source= takes unit who returns nothing
        if (.priv_source==null) then
            set .priv_source=who
        endif
    endmethod
//*
//* Target
//* =======================================================================================================
    method operator target takes nothing returns unit
        return .priv_target
    endmethod
    method operator target= takes unit who returns nothing    
        if (.priv_target==null) then
            set .priv_target=who
            set .priv_unitfollow=true
        endif
    endmethod
//*
    method operator targetX takes nothing returns real
        return tarVec.x
    endmethod
    method operator targetY takes nothing returns real
        return tarVec.y
    endmethod
    method operator targetZ takes nothing returns real
        return tarVec.z
    endmethod
//*
//* Timescale
//* =======================================================================================================
    method operator timescale takes nothing returns real
        if (.priv_paused) then
            return 0.00
        endif
        return .priv_timescale
    endmethod
    method operator timescale= takes real r returns nothing     // Time left = 10s     Time scale = 1.0
        if (r == 0) then                                        // Time left = 20s     Time scale = 0.5     (1.0 - 0.5) = 0.5
            set .priv_paused = true                             // Time left = 20s     Time scale = 1.0     (0.5 - 0.5) = 0.0
        else
            set .priv_paused    = false
            set .priv_timescale = r
        endif
    endmethod
//*
//* Target Position
//* =======================================================================================================
    method setTargetPos takes real x, real y, real z returns boolean      //* The projectile-target-pos can be manipulated many times.
        local real xA
        local real yA  
        local vector v = .velVec
        if (.tarVec.x==x and .tarVec.y==y and .tarVec.z==z) then              
            return false
        endif
        set .tarVec.x       = x
        set .tarVec.y       = y
        set .tarVec.z       = z      
        set xA              = .posVec.x
        set yA              = .posVec.y
       
        set xA              = (x-xA)*(x-xA) + (y-yA)*(y-yA)
        set yA              = (v.x*v.x + v.y*v.y)/(thistype.priv_stackLoopRef*thistype.priv_stackLoopRef)
        set .priv_timeleft  = SquareRoot(xA/yA)
       
        set .priv_follow    = true      // This must be flagged to notify the "projectile"
        return true                     // that the target has been changed.
    endmethod
//*
//*
    method kill takes nothing returns nothing
        set .priv_killnext = true
    endmethod
    private method onDestroy takes nothing returns nothing
        set thistype.priv_stackDex = thistype.priv_stackDex - 1
        set thistype.priv_stack[.priv_index] = thistype.priv_stack[thistype.priv_stackDex]
        set thistype.priv_stack[.priv_index].priv_index = .priv_index
        set totalProjectiles = totalProjectiles - 1
        call .posVec.destroy()
        call .velVec.destroy()
        call .accVec.destroy()
        call .tarVec.destroy()
        call .strVec.destroy()
        call DestroyEffect(.toUnit)
        if (thistype.priv_stackDex == 0) then
            call PauseTimer(thistype.priv_stackLoop)
        endif
    endmethod
//*
//* Projectile Enumeration
//* =======================================================================================================
    static method enumNearby takes projectilegroup g, real x, real y, real z, real radius returns nothing
        local integer i=0
        local real x2
        local real y2
        local real z2
        loop
            exitwhen (i==.priv_stackDex)
            set x2  = .priv_stack[i].posVec.x
            set y2  = .priv_stack[i].posVec.y
            set z2  = .priv_stack[i].posVec.z
            if ((x2-x)*(x2-x)+(y2-y)*(y2-y)+(z2-z)*(z2-z)) <= (radius*radius) then
                call g.add(.priv_stack[i])
            endif
            set i=i+1
        endloop
    endmethod
//*
//* Launch Projectile **
//* =======================================================================================================
    method doLaunch takes vector start, vector finish, real speed, real arc returns boolean
        local real d
        local real a
        local real r
        local unit u
        if (start == 0) or (finish == 0) or (.active) then
            return false
            //There are rules to launching a projectile. If any of these rules are broken, the method will
            //return false. A projectile cannot be launched if it has already been launched.
        endif
       
        set .posVec.x   = start.x    
        set .posVec.y   = start.y      
        set .posVec.z   = start.z
        set .tarVec.x   = finish.x
        set .tarVec.y   = finish.y
        set .tarVec.z   = finish.z
        set .strVec.x   = start.x
        set .strVec.y   = start.y
        set .strVec.z   = start.z
        set .speed      = speed
        set .arc        = arc
       
        set d = SquareRoot((finish.x-start.x)*(finish.x-start.x) + (finish.y-start.y)*(finish.y-start.y))
        set a = Atan2(finish.y-start.y, finish.x-start.x)
       
        if (d == 0.00) or (speed == 0.00) then
            set .posVec.x       = .tarVec.x
            set .posVec.y       = .tarVec.y
            set .posVec.z       = .tarVec.z
            set .priv_timeleft  = 0.00
        else
            set .priv_timeleft  = d/speed
            set r               = thistype.priv_stackLoopRef
            set .accVec.z       = (-8*arc*speed*speed/d)
            set .velVec.x       = speed*Cos(a)
            set .velVec.y       = speed*Sin(a)
            set .velVec.z       = (-.accVec.z * (d/speed)/2 + (finish.z-start.z)/(d/speed))
            set .accVec.z       = .accVec.z *r*r
            set .velVec.x       = .velVec.x *r
            set .velVec.y       = .velVec.y *r
            set .velVec.z       = .velVec.z *r
        endif
       
        call MoveLocation(loc, posVec.x, posVec.y)
        call BlzSetSpecialEffectPosition(.toUnit, posVec.x, posVec.y, posVec.z - GetLocationZ(loc))
       
        call onStart()
        set active = true
        set priv_finish = false
        return true
    endmethod
//*
//*
    private static method doLoopDestFilter takes nothing returns boolean
        local destructable d = GetFilterDestructable()
        local real x         = GetWidgetX(d)
        local real y         = GetWidgetY(d)
        if ((projRef.x-x)*(projRef.x-x) + (projRef.y-y)*(projRef.y-y)) <= projRef.priv_collision*projRef.priv_collision then
            if projRef.isValidTargetDest(d) then
                call projRef.onDestCollision(d)
            endif
        endif               //destructibles are picked if their x/y coordinates fall within a
        set d=null          //circular area around the projectile.
        return false        
    endmethod
//*
    private static method doLoopEnum takes nothing returns boolean
        local unit filt = GetFilterUnit()
        local real xA   = GetUnitX(filt)
        local real yA   = GetUnitY(filt)
        local real xB   = projRef.posVec.x
        local real yB   = projRef.posVec.y
       
        if (((xA-xB)*(xA-xB) + (yA-yB)*(yA-yB)) <= projRef.collision*projRef.collision) then
            // Unit collision is filtered using the interface method 'isValidTargetUnit';
            if (projRef.isValidTargetUnit(filt)) then
                call projRef.onUnitCollision(filt)
            endif
        endif
        set filt = null
        return false
    endmethod
    private static method doLoop takes nothing returns nothing
        local integer i = .priv_stackDex - 1
        local thistype dat
        local vector vA
        local vector vB
        local vector vC
        local real x
        local real y
        local real z
        local real x2
        local real y2
        local real z2
        local real e
        local effect f
        loop
            exitwhen (i < 0)
            set dat = .priv_stack[i]
            if (dat != 0) then
                //-----------------------------------------------------------------------------------------------------------
                // Vector Updates
                // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                //
                if (dat.active) then
                    set e       = dat.priv_timescale
                    // If .priv_paused then set e = 0.00;
                    if (dat.priv_paused) then
                        set e   = 0.00
                    endif
                    set vA      = dat.velVec
                    set vB      = dat.accVec
                    set vA.x    = vA.x + vB.x *e
                    set vA.y    = vA.y + vB.y *e
                    set vA.z    = vA.z + vB.z *e
                    set vB      = dat.posVec
                    set vB.x    = vB.x + vA.x *e
                    set vB.y    = vB.y + vA.y *e
                    set vB.z    = vB.z + vA.z *e
                else
                    set vA      = dat.velVec
                    set vB      = dat.posVec
                endif
                set f = dat.toUnit
                set x = vB.x
                set y = vB.y
                set z = vB.z
                call MoveLocation(loc, x, y)
                call BlzSetSpecialEffectPosition(f, dat.posVec.x, dat.posVec.y, dat.posVec.z - GetLocationZ(loc))
                //-----------------------------------------------------------------------------------------------------------
                // Projectile Rotation
                // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                //  Projectiles also have the feature of pitch and rotation values, if those features are enabled for
                //  the specific projectile. While pitch-rotation has specific requirements, rotation does not.
                //
                set x2 = vA.x
                set y2 = vA.y
                set z2 = vA.z
                if (dat.active) and (x2!=0 or y2!=0 or z2!=0) then
                    if dat.activePitch then
                        call BlzSetSpecialEffectPitch(f, Atan2(z2, dat.speed*priv_stackLoopRef) + bj_PI*0.50)
                        // This requires the dummy.mdx file that is included in xepreload. I should really make
                        // xepreload a requirement (perhaps optional requirement) of this system.
                    endif
                    if dat.activeRotation then
                        call BlzSetSpecialEffectYaw(f, Atan2(y2, x2))
                        // There are no special requirements for units to have their rotation updated to match their
                        // velocity, only that rotation be enabled.
                    endif
                endif
                //-----------------------------------------------------------------------------------------------------------
                // Terrain Collision
                // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                //
                if (z <= GetLocationZ(loc)) then        //This detects whether or not a projectile's trajectory has
                    if not (dat.priv_ground) then       //taken it beneath the terrain. The "onGround" method will
                        set dat.priv_ground = true      //only fire once when the projectile flies underground, and
                        call dat.onGround()             //will not repeat on every iteration after until the projectile
                    endif                               //has re-surfaced above the terrain.
                else
                    if (dat.priv_ground) then
                        set dat.priv_ground = false

                    endif
                endif
                //-----------------------------------------------------------------------------------------------------------
                // Projectile Collision Events
                // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                //
                if (dat.activeUnitCollision) then       // -- Unit Collision --
                    set projRef = dat
                    call GroupEnumUnitsInRange(grp, x, y, dat.collision, priv_filterFunc)
                    //priv_filterFunc always returns false so there is no need to monitor/clear this group at any time,
                    //it should always be empty.
                endif
                if (dat.activeDestCollision) then       // -- Destructable Collision --
                    set projRef = dat
                    call SetRect(rct, 0, 0, dat.priv_collision, dat.priv_collision)
                    call MoveRectTo(rct, x, y)
                    call EnumDestructablesInRect(rct, Filter(function thistype.doLoopDestFilter), null)
                endif
                //-----------------------------------------------------------------------------------------------------------
                // Target Tracking
                // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                //  Target tracking (specifically, units) requires an "active" boolean config value in order for each
                //  specific projectile to allow tracking, that is, following a moving target.
                //
                //  There are two variations to this, typically when you want to track a unit (which is common) and when
                //  you simply want to track the vector target (which could possibly be moved using setTargetPos().
                //
                if (dat.active) and (dat.activeTargetFollow) then
                    if (dat.priv_unitfollow) and (dat.target!=null) then
                        //Unit-follow means the target-vector will be updated to match the 3D position of the given
                        //unit-target.
                        set x = GetUnitX(dat.target)
                        set y = GetUnitY(dat.target)
                        set z = BlzGetUnitZ(dat.target)
                        call dat.setTargetPos(x, y, z)
                    endif
                    if (dat.priv_follow) then
                        //If this runs, then the target has moved; in which case certain components need to be adjusted
                        //to compensate for the change.
                        set vC           = dat.tarVec
                        set x            = Atan2(vC.y-vB.y, vC.x-vB.x)                                      //Angle to target
                        set y            = SquareRoot((vC.x-vB.x)*(vC.x-vB.x) + (vC.y-vB.y)*(vC.y-vB.y))    //Distance to target
                        set z2           = vC.z-vB.z                                                        //Height difference
                       
                        set vA.x         = dat.speed * Cos(x) * priv_stackLoopRef
                        set vA.y         = dat.speed * Sin(x) * priv_stackLoopRef
                        set vB           = dat.strVec
                        set x            = SquareRoot((vB.x-vC.x)*(vB.x-vC.x) + (vB.y-vC.y)*(vB.y-vC.y))    //Total distance

                        set dat.accVec.z = -8*dat.arc*dat.speed*dat.speed/x                                                  
                        set vA.z         = -dat.accVec.z * y/(dat.speed*2) + dat.speed*z2/y
                       
                        set dat.accVec.z = dat.accVec.z *priv_stackLoopRef*priv_stackLoopRef
                        set vA.z         = vA.z         *priv_stackLoopRef
                    endif
                endif
                //-----------------------------------------------------------------------------------------------------------
                // Recycling
                // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
                //  If the projectile's life-span expires then the system will automatically destroy the projectile based
                //  on whether or not the expire constant is on/off (can be changed per projectile).
                //
                if (dat.active) then    
                    if not (dat.priv_paused) then
                        set dat.priv_timeleft = dat.priv_timeleft - priv_stackLoopRef*dat.priv_timescale
                       
                    endif
                    if (dat.priv_timeleft <= 0.00) and not (dat.priv_finish) then
                        set dat.priv_finish = true
                        call dat.onFinish()

                        if (dat.toDestroy) and (dat!=0) then    //It is rare, but possible that the projectile will be
                            call dat.destroy()                  //destroyed twice, so we should check to make sure that
                        endif                                   //it has not already been destroyed.
                    else
                        if (dat.priv_killnext) then             //This will be flagged if the user calls the "kill"
                            call dat.destroy()                  //method.
                           
                        endif
                    endif
                endif
            endif
            set i = i - 1
        endloop
        set f = null
    endmethod
//*
//*
    static method create takes effect f, real facing returns thistype
        local thistype dat  = thistype.allocate()
       
        if (.priv_stackDex == 0) then
            call TimerStart(.priv_stackLoop, .priv_stackLoopRef, true, function thistype.doLoop)
        endif
        set totalProjectiles = totalProjectiles + 1
       
        call BlzSetSpecialEffectYaw(f, facing)
        set dat.toUnit                  = f
        set dat.priv_index              = .priv_stackDex
        set dat.posVec                  = vector.create(BlzGetLocalSpecialEffectX(f), BlzGetLocalSpecialEffectY(f), BlzGetLocalSpecialEffectZ(f))
        set dat.tarVec                  = vector.create(0, 0, 0)
        set dat.velVec                  = vector.create(0, 0, 0)
        set dat.accVec                  = vector.create(0, 0, 0)
        set dat.strVec                  = vector.create(0, 0, 0)
        set .priv_stack[.priv_stackDex] = dat
        set .priv_stackDex              = .priv_stackDex + 1
        return dat
    endmethod
//*
//*
//* Initialization Setup
//* =======================================================================================================
    private static method onInit takes nothing returns nothing
        // Variables that can not be initialized with their declaration are to
        // be defined in the initialization method.
        set rct = Rect(0, 0, PROJ_COLLISION_MAX, PROJ_COLLISION_MAX)
        // Initialize the filter function reference
        set priv_filterFunc = Filter(function thistype.doLoopEnum)
    endmethod
//*
//*
//*********************************************************************************************************
endstruct

endlibrary
//TESH.scrollpos=29
//TESH.alwaysfold=0
library Knockback


globals
//*********************
//* Configuration
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯
//*
    private constant real       LOOP_REF        = 0.03              
//*
//*
//*****************************************************************************
endglobals


struct knockback
   
    readonly    unit    subject         = null
    readonly    real    velocity
    readonly    real    rotation
    private     real    rotationCos
    private     real    rotationSin
   
    readonly    real    friction        
   
    private static  real            checkItemX          = GetRectMaxX(bj_mapInitialPlayableArea)
    private static  real            checkItemY          = GetRectMaxY(bj_mapInitialPlayableArea)
    private static  item            checkItem        
   
    private static  timer           priv_stackLoop      = CreateTimer()
    private static  constant real   priv_stackLoopRef   = LOOP_REF
    private         real            priv_time          
    private         boolean         priv_stopped        = false

    private         integer         priv_index
    private static  thistype array  priv_stack
    private static  integer         priv_stackN         = 0

   
    method stop takes nothing returns nothing
        // Instead of directly destroying an instance from knockback, the user can tell the system
        // that he/she wants the knockback to be stopped. If the knockback is to be stopped, it will
        // be on the next iteration.
        set .priv_stopped = true
    endmethod
    private method onDestroy takes nothing returns nothing
        set thistype.priv_stackN = thistype.priv_stackN-1
        set thistype.priv_stack[.priv_index] = thistype.priv_stack[thistype.priv_stackN]
        set thistype.priv_stack[.priv_index].priv_index = .priv_index
        if (.priv_stackN == 0) then
            call PauseTimer(thistype.priv_stackLoop)
        endif
    endmethod
   
    private static method onRef takes nothing returns nothing
        local integer i = .priv_stackN-1
        local thistype kb
        local real x
        local real y
        local real iX
        local real iY
        loop
            exitwhen(i < 0)
            set kb = .priv_stack[i]
            if (kb!=0) then
                set x  = GetWidgetX(kb.subject) + kb.velocity * kb.rotationCos
                set y  = GetWidgetY(kb.subject) + kb.velocity * kb.rotationSin
               
                //use an item to check the pathability of the new coordinate and
                //move the unit if the point is available.
                call SetItemPosition(checkItem, x, y)
                set iX = GetWidgetX(checkItem)
                set iY = GetWidgetY(checkItem)
                if ((iX-x)*(iX-x) + (iY-y)*(iY-y)) <= 1024.0 then
                    call SetUnitX(kb.subject, x)
                    call SetUnitY(kb.subject, y)
                endif
               
                set kb.velocity = kb.velocity + kb.friction
                set kb.priv_time = kb.priv_time - .priv_stackLoopRef
                if (kb.priv_time <= 0.00) or kb.priv_stopped then
                    call kb.destroy()
                endif
            endif
            set i=i-1
        endloop
        call SetItemPosition(checkItem, checkItemX, checkItemY)
        call SetItemVisible(checkItem, false)
    endmethod
   
    static method create takes unit u, real angle, real distance, real time returns thistype
        local thistype kb   = .allocate()
        set kb.subject      = u
        set kb.velocity     = 2*distance/time
        set kb.rotation     = angle
        set kb.rotationCos  = Cos(angle)
        set kb.rotationSin  = Sin(angle)
        set kb.friction     = -kb.velocity/time
        set kb.priv_index   = .priv_stackN
        set kb.priv_time    = time
       
        set kb.velocity = kb.velocity * .priv_stackLoopRef
        set kb.friction = kb.friction * .priv_stackLoopRef * .priv_stackLoopRef
        if (.priv_stackN == 0) then
            call TimerStart(.priv_stackLoop, .priv_stackLoopRef, true, function thistype.onRef)
        endif
        set .priv_stack[.priv_stackN] = kb
        set .priv_stackN = .priv_stackN + 1
        return kb
    endmethod
   
    static method onInit takes nothing returns nothing
        //the item will be have maintained invisibility as to not interfere with
        //in-game actions, such as a unit picking the item up.
        set checkItem=CreateItem('afac', checkItemX, checkItemY)
        call SetItemVisible(checkItem, false)
    endmethod
   
endstruct

endlibrary
//TESH.scrollpos=87
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//*     [group]    ENUM_GROUP      As you might expect, this group is good for
//*                                when you need a group just for enumeration.
//*     [boolexpr] BOOLEXPR_TRUE   This is a true boolexpr, which is important
//*                                because a 'null' boolexpr in enumeration
//*                                calls results in a leak. Use this instead.
//*     [boolexpr] BOOLEXPR_FALSE  This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//*     local group MyGroup = NewGroup()
//*     call GroupRefresh(MyGroup)
//*     call ReleaseGroup(MyGroup)
//*     call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//*     call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
    //If you don't have xebasic in your map, this value will be used instead.
    //This value corresponds to the max collision size of a unit in your map.
    private constant real    MAX_COLLISION_SIZE = 197.
    //If you are insane and don't care about any of the protection involved in
    //this library, but want this script to be really fast, set this to true.
    private constant boolean LESS_SAFETY        = false
endglobals

globals
    //* Constants that are available to the user
    group    ENUM_GROUP     = CreateGroup()
    boolexpr BOOLEXPR_TRUE  = null
    boolexpr BOOLEXPR_FALSE = null
endglobals

globals
    //* Hashtable for debug purposes
    private hashtable     ht     = InitHashtable()
    //* Temporary references for GroupRefresh
    private boolean       Flag   = false
    private group         Refr   = null
    //* Arrays and counter for the group stack
    private group   array Groups
    private integer       Count  = 0
    //* Variables for use with the GroupUnitsInArea function
    private real          X      = 0.
    private real          Y      = 0.
    private real          R      = 0.
    private hashtable     H      = InitHashtable()
endglobals

private function HookDestroyGroup takes group g returns nothing
    if g == ENUM_GROUP then
        call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
    endif
endfunction

debug hook DestroyGroup HookDestroyGroup

private function AddEx takes nothing returns nothing
    if Flag then
        call GroupClear(Refr)
        set Flag = false
    endif
    call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
    set Flag = true
    set Refr = g
    call ForGroup(Refr, function AddEx)
    if Flag then
        call GroupClear(g)
    endif
endfunction

function NewGroup takes nothing returns group
    if Count == 0 then
        set Groups[0] = CreateGroup()
    else
        set Count = Count - 1
    endif
    static if not LESS_SAFETY then
        call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
    endif
    return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
    local integer id = GetHandleId(g)
    static if LESS_SAFETY then
        if g == null then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
            return false
        elseif Count == 8191 then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
            call DestroyGroup(g)
            return false
        endif
    else
        if g == null then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
            return false
        elseif not HaveSavedInteger(ht, 0, id) then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
            return false
        elseif LoadInteger(ht, 0, id) == 2 then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
            return false
        elseif Count == 8191 then
            debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
            call DestroyGroup(g)
            return false
        endif
        call SaveInteger(ht, 0, id, 2)
    endif
    call GroupClear(g)
    set Groups[Count] = g
    set Count         = Count + 1
    return true
endfunction

private function Filter takes nothing returns boolean
    return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction

private function HookDestroyBoolExpr takes boolexpr b returns nothing
    local integer bid = GetHandleId(b)
    if HaveSavedHandle(H, 0, bid) then
        //Clear the saved boolexpr
        call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
        call RemoveSavedHandle(H, 0, bid)
    endif
endfunction

hook DestroyBoolExpr HookDestroyBoolExpr

private constant function GetRadius takes real radius returns real
    static if LIBRARY_xebasic then
        return radius+XE_MAX_COLLISION_SIZE
    else
        return radius+MAX_COLLISION_SIZE
    endif
endfunction

function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
    local real    prevX = X
    local real    prevY = Y
    local real    prevR = R
    local integer bid   = 0
   
    //Set variables to new values
    set X = x
    set Y = y
    set R = radius
    if filter == null then
        //Adjusts for null boolexprs passed to the function
        set filter = Condition(function Filter)
    else
        //Check for a saved boolexpr
        set bid = GetHandleId(filter)
        if HaveSavedHandle(H, 0, bid) then
            //Set the filter to use to the saved one
            set filter = LoadBooleanExprHandle(H, 0, bid)
        else
            //Create a new And() boolexpr for this filter
            set filter = And(Condition(function Filter), filter)
            call SaveBooleanExprHandle(H, 0, bid, filter)
        endif
    endif
    //Enumerate, if they want to use the boolexpr, this lets them
    call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
    //Give back original settings so nested enumerations work
    set X = prevX
    set Y = prevY
    set R = prevR
endfunction

function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
    local real prevX = X
    local real prevY = Y
    local real prevR = R
   
    //Set variables to new values
    set X = x
    set Y = y
    set R = radius
    //Enumerate
    call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
    //Give back original settings so nested enumerations work
    set X = prevX
    set Y = prevY
    set R = prevR
endfunction

private function True takes nothing returns boolean
    return true
endfunction
private function False takes nothing returns boolean
    return false
endfunction
private function Init takes nothing returns nothing
    set BOOLEXPR_TRUE  = Condition(function True)
    set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
 
//TESH.scrollpos=32
//TESH.alwaysfold=0
//! zinc
library cheat requires Retro
{
    unit guy;
    real mx;
    real my;
    unitpool pool = CreateUnitPool();

    function Move()-> boolean
    {
        unit u = GetFilterUnit();
        if (!IsUnit(u,guy)&&UnitAlive(u))
        {
            IssuePointOrder(u,"move",mx,my);
            DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl",u,"origin"));
        }
        u=null;
        return false;
    }

    function Conditions()-> boolean
    {
        if (GetSpellAbilityId() == 'A002')
        {
            mx = GetSpellTargetX();
            my = GetSpellTargetY();
            GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(guy), GetUnitY(guy), 1200., Filter(function Move));
        }
        return false;
    }
   
    function Cheating()-> boolean {
   
        PlaceRandomUnit(pool, GetTriggerPlayer(), 0.0, 0.0, 0.0);
        SetHeroLevel(guy, GetHeroLevel(guy) + 1, true);
        return false;
    }

    function onInit()
    {
        trigger t = CreateTrigger();
        TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT);
        TriggerAddCondition(t, Condition(function Conditions));
       
        DisplayTimedTextToPlayer(GetLocalPlayer(), 0., 0., 12., "|cffddff66Press Esc to add a hero level.\n\nSelect any enemy to send it back through time.");
        UseTimeOfDayBJ(false);
        SetTimeOfDay(24.);
       
        guy = CreateUnit(Player(0),'H000',0.,0.,270.);
        SetUnitVertexColor(guy, 45, 45, 45, 160);
       
        t=CreateTrigger();
        TriggerRegisterPlayerEvent(t,GetLocalPlayer(), EVENT_PLAYER_END_CINEMATIC);
        TriggerAddCondition(t, Filter(function Cheating));
       
        FogModifierStart(CreateFogModifierRect(GetLocalPlayer(),FOG_OF_WAR_VISIBLE,bj_mapInitialPlayableArea,true,false));
       
        UnitPoolAddUnitType(pool, 'hfoo', 0.25);
        UnitPoolAddUnitType(pool, 'ufro', 0.05);
        UnitPoolAddUnitType(pool, 'oshm', 0.10);
        UnitPoolAddUnitType(pool, 'edot', 0.10);
        UnitPoolAddUnitType(pool, 'hsor', 0.12);
        UnitPoolAddUnitType(pool, 'edry', 0.12);
    }
}
//! endzinc
strings
  Events
  Conditions
  Actions
    Special Effect - Create a special effect attached to the overhead of (Triggering unit) using Abilities\Spells\Undead\AnimateDead\AnimateDeadTarget.mdl
    Sound - Set pitch of (Last played sound) to 0.00