1. 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
  2. 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
  3. 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
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. 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.

random desync problem

Discussion in 'World Editor Help Zone' started by coolyoshi1, Aug 8, 2012.

  1. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    I have a map ive been updating for a long time but i still cant figure out wats causing it to desync at completely random times and random number of players (usually 1-3 players desyncs in a game if the desyncs does happen).
    I looked at some past replays and all of the desyncs happened at completely random times but they happened when pretty much nothing happened and no triggers were running their actions. Im really confused how desyncs work. do sources of desyncs desync players immediately/shortly after, or do they slowly build up desyncing between players overtime until its at its max and explodes?
    I was looking up desync problems on hiveworkshop and came up with a small list of things that might cause it:

    -bj functions / GetLocalPlayer()
    -wait/polled waits
    -Pan camera as necessary / using current camera view
    -spells/triggers that use Terrain deformations
    -custom script that involve player(id) ( JASS start from player(0), GUI start from player(1))
    -Long file names
    -units moving to a target point but are blocked by other units so they cant get to their target point
    -bad imported models

    I dont use any of the 1st three things in the list

    I use some terrain deformations from shockwave and earthquake spells in my map but I dont think theyre causing it.

    Can anyone help explain this cause of desync? I just copy/pasted it:
    -"custom script that involve player(id) ( JASS start from player(0), GUI start from player(1))"

    and are these causes of desync true?
    -Long file names
    -units moving to a target point but are blocked by other units

    desyncs happen sometimes a long time after all the imported models have entered the game so im not sure if this is it.


    Can someone help answer some of my questions? +rep!
     
  2. Ralle

    Ralle

    Owner

    Joined:
    Oct 6, 2004
    Messages:
    11,235
    Resources:
    22
    Tools:
    3
    Maps:
    5
    Tutorials:
    14
    Resources:
    22
    I have little experience with this, but desync means that something happens to one person's game, which does not happen to somebody elses, so they are not in an equal state anymore.
    Often times I have found it to be occur when using the
    GetLocalPlayer()
    because you can make it do something only on one person's game.

    I think you should try to disable as many systems as you can in your map. For example ALL spells. Then play and see if this still occurs. Try to isolate what trigger does it.
     
  3. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    so does it take some time for a source of a desync to actually desync the players?
    like if a spell is cast, can that spell cause a desync 3 minutes after the spell is cast or does it desync players immediately?

    I dont think isolating will work well because sometimes i dont even see any desyncs for 10 games straight, and other times i might see desyncs all the time.
     
  4. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,426
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    If you use a player number that is outside of the range 0-15 (or 1-16 in GUI) then it will cause an instant crash. However, this is not a desync.

    Probably not. As far as I know, long file names might make the map not appear in the warcraft 3 map selection window but it shouldn't cause desyncs.

    It should not cause a desync unless the pathing is locally changed. (which will not happen without GetLocalPlayer())

    Ralle described it well. By not being in equal states on different computers, the game will be out of sync and may end up causing a disconnection. Some of those things that you've listed are potential causes of desyncs, but a few of them are either crashes or random errors.

    The first thing to do to figure out when something desyncs is to test a lot and figure out when the desync occurs. Watch the replay and see what was happening when the desync happened. If you know when it occurred/what situation, then you can rule out a good amount of triggers (such as the initialization ones) and then just narrow down the rest. If it is truly random and you have no clue what caused it, make sure that the players had a stable internet connection because that too can cause a desync.

    Good luck.

    EDIT:
    In most cases it is instant. There are rare cases where the map can survive for a while with things being out of sync (such as when a unit is locally moved or locally hidden) but upon interaction they may cause the desync.

    It is very annoying to debug. If you have replays, just try to analyze the point of desync and see what was going on as best as you can.
     
  5. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    thanks for the answers!
    i guess ill just keep looking at more replays then.
    its good to know that desyncs happen instantly
     
  6. WaterKnight

    WaterKnight

    Joined:
    Aug 18, 2009
    Messages:
    4,033
    Resources:
    5
    Maps:
    1
    Tutorials:
    4
    Resources:
    5
    And upload map, so we can take a look.
     
  7. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    k uploaded.
    might be the messiest map you've ever seen
     

    Attached Files:

  8. WaterKnight

    WaterKnight

    Joined:
    Aug 18, 2009
    Messages:
    4,033
    Resources:
    5
    Maps:
    1
    Tutorials:
    4
    Resources:
    5
    In some places, you use
    GetLocationZ
    in a sync manner although the function itself can be async in special cases. See the imported jass libraries. I would suggest to hook (build a wrapper) around the
    GetLocationZ
    native and output the values in an external file using the preload natives.

    Code (vJASS):
    function GetLocationZ_Print takes location l returns real
        local real r = GetLocationZ(l)

        call Preload(I2S(GetHandleId(GetTriggeringTrigger())) + " --> " + R2S(r))

        call PreloadGenEnd("LocationZResults.txt")

        return r
    endfunction


    This file (LocationZResults.txt) will be created in your Wc3's Logs folder and you shall compare them with the versions of the players you desynced from.

    However, depending on the frequency of calls, this function can slow down your map significantly. Also tell players to delete the file afterwards to free memory again. Do not do this with publics please.
     
  9. Ralle

    Ralle

    Owner

    Joined:
    Oct 6, 2004
    Messages:
    11,235
    Resources:
    22
    Tools:
    3
    Maps:
    5
    Tutorials:
    14
    Resources:
    22
    from my experience a desync happens almost instantly. So no need to find out what ran the last x minutes.
     
  10. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    Im not sure how to do this

    I couldnt find where function GetLocationZ_Print was. Is it just the jass jump spell that uses this? Would it be better to just delete it and use a GUI version of jump?

    I had no idea jass could slow down maps like this..

    edit: cant find LocationZResults.txt in my logs folder. I tried check hidden files too
     
  11. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,426
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    He meant to add this code in your map.
    Code (vJASS):
    function GetLocationZ_Print takes location l returns real
        local real r = GetLocationZ(l)

        call Preload(I2S(GetHandleId(GetTriggeringTrigger())) + " --> " + R2S(r))

        call PreloadGenEnd("LocationZResults.txt")

        return r
    endfunction

    hook GetLocationZ GetLocationZ_Print
     
  12. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    oh i didnt read it properly
    ill just look out for the getlocationZ spells during replays
     
    Last edited: Aug 10, 2012
  13. WaterKnight

    WaterKnight

    Joined:
    Aug 18, 2009
    Messages:
    4,033
    Resources:
    5
    Maps:
    1
    Tutorials:
    4
    Resources:
    5
    Have not worked with hooks before but tested it and it seems like it would replace

    Code (vJASS):
    call GetLocationZ


    by

    Code (vJASS):
    call GetLocationZ_Print
    call GetLocationZ


    and even the occurence in the target function, which forms an infinite loop.

    Well, else, substitute all instances yourself.
     
  14. PurgeandFire

    PurgeandFire

    Code Moderator

    Joined:
    Nov 11, 2006
    Messages:
    7,426
    Resources:
    18
    Icons:
    1
    Spells:
    4
    Tutorials:
    9
    JASS:
    4
    Resources:
    18
    Ah, that makes sense. Then coolyoshi should just manually write the debug messages/message logs.
     
  15. WaterKnight

    WaterKnight

    Joined:
    Aug 18, 2009
    Messages:
    4,033
    Resources:
    5
    Maps:
    1
    Tutorials:
    4
    Resources:
    5
    That is actually pretty shitty and it makes no sense to replace occurences within the target function.
     
  16. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    so is this jump spell unfixable? I'd only use it if it were 0% chance of desyncs

    Spell:

    Code (vJASS):
    scope TestProjectiles initializer init

    globals
        private location loc = Location(0, 0)
       
    endglobals

    public function jumpfilter takes nothing returns boolean
        return GetSpellAbilityId()=='A0H9'
    endfunction
    public function jumpmain takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        local real xA = GetUnitX(caster)
        local real yA = GetUnitY(caster)
        local real xB = GetSpellTargetX()
        local real yB = GetSpellTargetY()
        local vector vA
        local vector vB
        local projectile p
       
        call IssuePointOrder(caster, "move", xB, yB)
       
        call MoveLocation(loc, xA, yA)
        set vA = vector.create(xA, yA, GetLocationZ(loc)+GetUnitFlyHeight(caster))
        call MoveLocation(loc, xB, yB)
        set vB = vector.create(xB, yB, GetLocationZ(loc))
       
        set p = projectile.create(caster)
        call p.launch(vA, vB, 600, 0.4)
        set p.activePitch           = false
        set p.activeRotation        = true
        set p.activeUnitCollision   = false
        set p.toKill                = false
       
        call vA.destroy()
        call vB.destroy()
        set caster = null
    endfunction


    public function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local trigger jump = CreateTrigger()
        local timer h = NewTimer()

        call TriggerRegisterPlayerUnitEvent(jump, Player(0), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(1), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(2), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(3), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(4), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(5), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(6), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(7), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(8), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(9), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(10), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(jump, Player(11), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerAddCondition(jump, Filter(function jumpfilter))
        call TriggerAddAction(jump, function jumpmain)

    endfunction

    endscope
     
  17. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    170
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    Libraries:

    TimerUtilis:
    Code (vJASS):

    library TimerUtils initializer init
    //*********************************************************************
    //* TimerUtils (red+blue+orange flavors for 1.24b+)
    //* ----------
    //*
    //*  To implement it , create a custom text trigger called TimerUtils
    //* and paste the contents of this script there.
    //*
    //*  To copy from a map to another, copy the trigger holding this
    //* library to your map.
    //*
    //* (requires vJass)   More scripts: htt://www.wc3c.net
    //*
    //* For your timer needs:
    //*  * Attaching
    //*  * Recycling (with double-free protection)
    //*
    //* set t=NewTimer()      : Get a timer (alternative to CreateTimer)
    //* ReleaseTimer(t)       : Relese a timer (alt to DestroyTimer)
    //* SetTimerData(t,2)     : Attach value 2 to timer
    //* GetTimerData(t)       : Get the timer's value.
    //*                         You can assume a timer's value is 0
    //*                         after NewTimer.
    //*
    //* Multi-flavor:
    //*    Set USE_HASH_TABLE to true if you don't want to complicate your life.
    //*
    //* If you like speed and giberish try learning about the other flavors.
    //*
    //********************************************************************

    //================================================================
        globals
            //How to tweak timer utils:
            // USE_HASH_TABLE = true  (new blue)
            //  * SAFEST
            //  * SLOWEST (though hash tables are kind of fast)
            //
            // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true  (orange)
            //  * kinda safe (except there is a limit in the number of timers)
            //  * ALMOST FAST
            //
            // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
            //  * THE FASTEST (though is only  faster than the previous method
            //                  after using the optimizer on the map)
            //  * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
            //                     work)
            //
            private constant boolean USE_HASH_TABLE      = true
            private constant boolean USE_FLEXIBLE_OFFSET = false

            private constant integer OFFSET     = 0x100000
            private          integer VOFFSET    = OFFSET
                 
            //Timers to preload at map init:
            private constant integer QUANTITY   = 256
           
            //Changing this  to something big will allow you to keep recycling
            // timers even when there are already AN INCREDIBLE AMOUNT of timers in
            // the stack. But it will make things far slower so that's probably a bad idea...
            private constant integer ARRAY_SIZE = 8190

        endglobals

        //==================================================================================================
        globals
            private integer array data[ARRAY_SIZE]
            private hashtable     ht
        endglobals

        //It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
        function SetTimerData takes timer t, integer value returns nothing
            static if(USE_HASH_TABLE) then
                // new blue
                call SaveInteger(ht,0,GetHandleId(t), value)
               
            elseif (USE_FLEXIBLE_OFFSET) then
                // orange
                static if (DEBUG_MODE) then
                    if(GetHandleId(t)-VOFFSET<0) then
                        call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                    endif
                endif
                set data[GetHandleId(t)-VOFFSET]=value
            else
                // new red
                static if (DEBUG_MODE) then
                    if(GetHandleId(t)-OFFSET<0) then
                        call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                    endif
                endif
                set data[GetHandleId(t)-OFFSET]=value
            endif        
        endfunction

        function GetTimerData takes timer t returns integer
            static if(USE_HASH_TABLE) then
                // new blue
                return LoadInteger(ht,0,GetHandleId(t) )
               
            elseif (USE_FLEXIBLE_OFFSET) then
                // orange
                static if (DEBUG_MODE) then
                    if(GetHandleId(t)-VOFFSET<0) then
                        call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                    endif
                endif
                return data[GetHandleId(t)-VOFFSET]
            else
                // new red
                static if (DEBUG_MODE) then
                    if(GetHandleId(t)-OFFSET<0) then
                        call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                    endif
                endif
                return data[GetHandleId(t)-OFFSET]
            endif        
        endfunction

        //==========================================================================================
        globals
            private timer array tT[ARRAY_SIZE]
            private integer tN = 0
            private constant integer HELD=0x28829022
            //use a totally random number here, the more improbable someone uses it, the better.
        endglobals

        //==========================================================================================
        function NewTimer takes nothing returns timer
            if (tN==0) then
                //If this happens then the QUANTITY rule has already been broken, try to fix the
                // issue, else fail.
                debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
                static if( not USE_HASH_TABLE) then
                    debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
                    set tT[0]=CreateTimer()
                    static if( USE_FLEXIBLE_OFFSET) then
                        if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    else
                        if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    endif
                endif
            else
                set tN=tN-1
            endif
            call SetTimerData(tT[tN],0)
         return tT[tN]
        endfunction

        //==========================================================================================
        function ReleaseTimer takes timer t returns nothing
            if(t==null) then
                debug call BJDebugMsg("Warning: attempt to release a null timer")
                return
            endif
            if (tN==ARRAY_SIZE) then
                debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

                //stack is full, the map already has much more troubles than the chance of bug
                call DestroyTimer(t)
            else
                call PauseTimer(t)
                if(GetTimerData(t)==HELD) then
                    debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
                    return
                endif
                call SetTimerData(t,HELD)
                set tT[tN]=t
                set tN=tN+1
            endif    
        endfunction

        private function init takes nothing returns nothing
         local integer i=0
         local integer o=-1
         local boolean oops = false
         
            static if( USE_HASH_TABLE ) then
                set ht = InitHashtable()
                loop
                    exitwhen(i==QUANTITY)
                    set tT[i]=CreateTimer()
                    call SetTimerData(tT[i], HELD)
                    set i=i+1
                endloop
                set tN = QUANTITY
            else
                loop
                    set i=0
                    loop
                        exitwhen (i==QUANTITY)
                        set tT[i] = CreateTimer()
                        if(i==0) then
                            set VOFFSET = GetHandleId(tT[i])
                            static if(USE_FLEXIBLE_OFFSET) then
                                set o=VOFFSET
                            else
                                set o=OFFSET
                            endif
                        endif
                        if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
                            exitwhen true
                        endif
                        if (GetHandleId(tT[i])-o>=0)  then
                            set i=i+1
                        endif
                    endloop
                    set tN = i
                    exitwhen(tN == QUANTITY)
                    set oops = true
                    exitwhen not USE_FLEXIBLE_OFFSET
                    debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")              
                endloop
               
                if(oops) then
                    static if ( USE_FLEXIBLE_OFFSET) then
                        debug call BJDebugMsg("The problem has been fixed.")
                        //If this message doesn't appear then there is so much
                        //handle id fragmentation that it was impossible to preload
                        //so many timers and the thread crashed! Therefore this
                        //debug message is useful.
                    elseif(DEBUG_MODE) then
                        call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
                        call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
                        call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
                        call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
                    endif
                endif
            endif

        endfunction

    endlibrary

     


    Projectile:
    Code (vJASS):

    library Projectile requires Vector, optional ProjectileArt
    //##########################################################################################################    
    //#
    //#     P R O J E C T I L E                                                                        
    //#                         by Berb
    //#
    //##########################################################################################################

    //**********************************************************************************************************
    //*
    //*     WarCraft III: Custom Projectiles Library
    //*     ____________________________________________________________________________________________________
    //*     Author:     Berb / TheKid
    //*     Version:    2.2.0
    //*
    //*     Requirements:
    //*     ____________________________________________________________________________________________________
    //*         _ JassHelper 0.A.2.B
    //*         _ Vector by Anitarf
    //*
    //*     To Know:
    //*     ____________________________________________________________________________________________________
    //*         _ Pitch variance requires the dummy.mdx model that is included in "xebasic".
    //*         _ Projectile groups are limited by the number of projectiles that they can contain.
    //*
    //*     Description:
    //*     ____________________________________________________________________________________________________
    //*         The purpose of this library is to provide users with an interface by which they can easily
    //*         manage their projectiles. It is recommended that the user read through the API to discover
    //*         which control features he/she has access to.
    //*
    //*     Recent Changes:
    //*     ____________________________________________________________________________________________________
    //*         2.1.1   - Removed the "FLAG__onFinish" state flag which disallowed projectiles from being
    //*                   launched properly in succession. It isn't actually very useful, and it doesn't provide
    //*                   any bonus functionality - I'm not sure why I put it in there in the first place.
    //*
    //*                 - If necessary, projectiles can be launched in their "onFinish" method response as they
    //*                   are deactivated before that response is executed. By deactivating the "toDestroy"
    //*                   member users will more easily be able to make projectiles "bounce" from one target
    //*                   to another.
    //*
    //*         2.1.2   - Optimized the "enumNearby" method as corrected by Bribe.
    //*
    //*         2.1.3   - Fixed a very minor issue with projectiles being destroyed prematurely by adding a member
    //*                   "destroyed" to the projectile members.
    //*
    //*                     > Instead of checking "if (proj == 0) then" to detect if a projectile has been
    //*                       destroyed, the user can now do: "if (proj.destroyed) then". This simply removes
    //*                       the "double-free" message from a projectile being destroyed twice.
    //*
    //*                     > The value "destroyed" is defaulted to false, and set to true in the "onDestroy"
    //*                       method.
    //*
    //*                 - Added method operator to the "projectilegroup" struct to reference members of the
    //*                   group with array syntax, rather than using the array member "indexOf[]".
    //*
    //*                 - Changed the members "DATA__stack" and "DATA__size" to readonly so that users can
    //*                   perform iterations on the entire stack of projectiles.
    //*
    //*         2.2.0   - Removed the indexOf[] array member from the projectile-group struct. In substitution,
    //*                   the array syntax operator [] is available (since 2.1.3).
    //*
    //*                 - Removed one of the hashtable requirements for projectile group referencing. Instead,
    //*                   only one hashtable is required for this (and another for projectile groups).
    //*
    //*                 - Removed the limit on the amount of projectiles that can be stored in a projectile
    //*                   group by further utilizing the hashtable that was already used for the very quick
    //*                   "inGroup" method.
    //*
    //*
    //*
    //**********************************************************************************************************


    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.
    //*
    //* Fly Height Modifier Ability
    //* ________________________________________________________________________________________________________
        public      constant integer            ID__flyHeightModifier           = 'Amrf'          
    //*          
    //* Settings
    //* ________________________________________________________________________________________________________
        public      constant real               COLLISION__default              = 70.00         // The maximum and default settings for projectile collision.
        public      constant real               COLLISION__sizeMax              = 200.00
    //*
    //* Default Features Config
    //* ________________________________________________________________________________________________________
        private     constant boolean            DEFAULT__targetFollow           = true          // These features enable certain elements of each projectile, giving the user
        private     constant boolean            DEFAULT__unitCollision          = false         // the option to customize what properties the projectile displays - like whether
        private     constant boolean            DEFAULT__destCollision          = false         // it can collide with units/destructables to whether or not it is a target                                                                                                            
        private     constant boolean            DEFAULT__faceRotation           = true          // "seeking" or following target.
        private     constant boolean            DEFAULT__facePitch              = true            
        private     constant boolean            DEFAULT__toExpire               = true      
        private     constant boolean            DEFAULT__toKill                 = true    
        private     constant boolean            DEFAULT__toRemove               = false  
    //*
    //*
        public      constant real               SETTING__timeout                = 0.03          // This identifies the amount of time (in seconds) that lapses between each
    //*                                                                                         // iteration that the projectiles are "updated".
    //*
    //*
    //**********************************************************************************************************
    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.
    //*
    //* Event Response - State
    //* ________________________________________________________________________________________________________
        method  onStart             takes nothing returns nothing           defaults nothing
        method  onFinish            takes nothing returns nothing           defaults nothing    // Projectile completes trajectory.
        method  onGround            takes nothing returns nothing           defaults nothing    // Impact with terran.
    //*
    //* Event Response - Collision
    //* ________________________________________________________________________________________________________
        method  onUnitCollision     takes unit u returns nothing            defaults nothing
        method  onDestCollision     takes destructable d returns nothing    defaults nothing
    //*
    //* Filters
    //* ________________________________________________________________________________________________________
        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().
    //*
    //* References
    //* ________________________________________________________________________________________________________
        private     projectile          projRef
        private     location            loc                     = Location(0, 0)
        private     group               grp                     = CreateGroup()
        private     rect                rct                    
    //*
        private     hashtable           projGroupTable          = InitHashtable( )
    //*
    //* Misc
    //* ________________________________________________________________________________________________________
        private     integer             totalProjectiles        = 0
    //*
    //*
    //**********************************************************************************************************
    endglobals




    //**************************
    //* Total Projectile Count
    //* ________________________________________________________________________________________________________
    //*     The amount of projectiles that are currently in operation. The value that is returned is added to
    //*     and subtracted from whenever a projectile is created/destroyed.
    //*
    function GetTotalProjectiles takes nothing returns integer
        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.
    //*
    //* API:
    //* ________________________________________________________________________________________________________
    //*     @[]                     - Method operator for referencing members of the group with array syntax.
    //*     @size                   - The amount of projectiles that are currently stored in the group.
    //*
    //*     @add( )                 - Adds a specified projectile to the group if it hasn't been already.
    //*     @remove( )              - Removes a specified projectile from the group if it has been added.
    //*
    //*     @inGroup( )             - Returns true if the designated projectile is included in the group.
    //*
    //* Components
    //* ________________________________________________________________________________________________________
        readonly        integer             size            = 0
        private static  hashtable           table           = InitHashtable( )
    //*
    //* Operators
    //* ________________________________________________________________________________________________________
        method operator [] takes integer index returns projectile
            return LoadInteger(table, this+8092, index)
        endmethod
    //*
    //* Utility Methods
    //* ________________________________________________________________________________________________________
        method inGroup takes projectile p returns boolean               // This will return true if the
            return HaveSavedInteger(thistype.table, this, p)            // specified projectile is included in
                                                                        // "this" group.
        endmethod
    //*
    //* Group Operators
    //* ________________________________________________________________________________________________________
        method add takes projectile p returns boolean    
            local integer indexOfLast
            if not inGroup(p) then          
                call SaveInteger(thistype.table, this, p, size)  
                call SaveInteger(thistype.table, this+8092, size, p)  
                set size = size + 1    
       
                set indexOfLast = 0
                if HaveSavedInteger(projGroupTable, p, -1) then
                    set indexOfLast = LoadInteger(projGroupTable, p, -1)
                endif
                call SaveInteger(projGroupTable, p, indexOfLast, this)
                call SaveInteger(projGroupTable, p+8092, this, indexOfLast)
                call SaveInteger(projGroupTable, p, -1, indexOfLast + 1)
               
                return true                                          
            endif                                                      
            return false
        endmethod
        method remove takes projectile p returns boolean
            local integer indexOfLast
            local integer indexOfThis
            if inGroup(p) then          
                set size = size - 1
                set indexOfThis = LoadInteger(table, this, p)
                call SaveInteger(table, this+8092, indexOfThis, LoadInteger(table, this+8092, size))
                call SaveInteger(table, this, LoadInteger(table, this+8092, indexOfThis), indexOfThis)
                call RemoveSavedInteger(table, this+8092, size)
                call RemoveSavedInteger(table, this, p)
           
                set indexOfLast = LoadInteger(projGroupTable, p, -1) - 1
                set indexOfThis = LoadInteger(projGroupTable, p+8092, this)
                call SaveInteger(projGroupTable, p, -1, indexOfLast)
                call SaveInteger(projGroupTable, p, indexOfThis, LoadInteger(projGroupTable, p, indexOfLast))
                call SaveInteger(projGroupTable, p+8092, LoadInteger(projGroupTable, p, indexOfThis), indexOfThis)
               
                if indexOfLast < 1 then
                    call FlushChildHashtable(projGroupTable, p)
                    call FlushChildHashtable(projGroupTable, p+8092)
                   
                endif
                return true
            endif
            return false
        endmethod
    //*
    //* Destructor & Clear
    //* ________________________________________________________________________________________________________
    //*     Both the .clear( ) and .destroy( ) methods will perform the exact same actions, however when
    //*     .destroy( ) is used it will recycle the group struct. If .clear( ) is used it will simply ensure
    //*     there are no projectiles included in the projectilegroup.
    //*
        method clear takes nothing returns nothing            
            local integer i = size - 1                        
            loop                                                
                exitwhen i < 0
                call remove(LoadInteger(table, this+8092, i))
                set i = i - 1
            endloop
        endmethod
        method onDestroy takes nothing returns nothing        
            local integer i = size - 1
            loop
                exitwhen i < 0
                call remove(LoadInteger(table, this+8092, i))
                set i = i - 1
            endloop
        endmethod
    //*
    //*
    //*********************************************************************************************************
    endstruct



    struct projectile extends projectileinterface
    //*******************************************
    //* Projectile
    //* ________________________________________________________________________________________________________
    //*     The projectile struct controls all aspects of the projectile motion, from creating it with a unit
    //*     handle to launching it through the air and destroying it. It has an extensive API as control can
    //*     get a little complex in areas.
    //*
    //* API: members
    //* ________________________________________________________________________________________________________
    //*     @toUnit                     - The unit handle that represents the projectile in-game.
    //*     @speed                      - The speed at which the projectile is moving in a 2D perspective.
    //*     @arc                        - The arc at which the projectile is moving.
    //*
    //*     @activeTargetFollow         - Enables/disables projectile homing.
    //*     @activePitch                - Enables/disables projectile pitch rotation (cosmetic).
    //*     @activeRotation             - Enables/disables projectile rotation (cosmetic).
    //*     @activeUnitCollision        - Enables/disables projectile-unit collision.
    //*     @activeDestCollision        - Enables/disables projectile-destructable collision.
    //*
    //*     @destroyed                  - Value of 'true' if the projectile has been destroyed.
    //*     @toDestroy                  - Allows the projectile struct to be destroyed once it finishes.
    //*     @toRemove (optimal)         - Allows "toUnit" to be removed upon projectile destruction.
    //*     @toKill                     - Allows "toUnit" to be killed upon projectile destruction.
    //*
    //*     @x                          - "x" coordinate of the projectile object.
    //*     @y                          - "y" coordinate of the projectile object.
    //*     @z                          - "z" coordinate of the projectile object.
    //*     @collision                  - The collision-size of the projectile object.
    //*     @timescale                  - The execution time-scale of the projectile. Setting this to 0 will
    //*                                   freeze the projectile in position until it is "unpaused".
    //*
    //*     @source                     - The source unit of the projectile being launched.
    //*     @target                     - The target unit of the projectile being launched.
    //*     @targetX                    - "x" coordinate of the projectile target.
    //*     @targetY                    - "y" coordinate of the projectile target.
    //*     @targetZ                    - "z" coordinate of the projectile target.
    //*     @targetZOffset              - "z" coordinate offset of the projectile target.
    //*
    //* API: instance methods
    //* ________________________________________________________________________________________________________
    //*     @setTargetPos               - Updates a projectile's target with 3 coordinate input parameters.
    //*
    //*     @launch                     - Launches a projectile from one point to another with given input.
    //*                                 @start      - The initial position vector.
    //*                                 @finish     - The final position vector.
    //*                                 @speed      - The velocity of the projectile in a 2D perspective.
    //*                                 @arc        - The arc that the projectile will be launched at.
    //*
    //*     @destroy                    - Destroys a projectile, cleaning all of its members and deallocating
    //*                                 it from the projectile engine in addition to removing the projectile
    //*                                 from any "projectilegroup" it was included in.
    //*
    //* API: static methods
    //* ________________________________________________________________________________________________________    
    //*     @create                     - Creates a projectile object from a unit handle.
    //*
    //*     @enumNearby                 - Gathers nearby projectiles in a specified "projectilegroup".
    //*
    //*
    //* ########################################################################################################
    //*
        readonly        unit            toUnit                      = null
        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         unit            PROP__target                = null                      // Both .priv_target and .priv_source can be referenced publicly by using the
        private         unit            PROP__source                = null                      // method operators without the "priv_" prefix.
        private         real            PROP__timescale             = 1.00
        private         real            PROP__collision             = COLLISION__default
        private         real            PROP__targetHeight          = 0
    //*
    //*
        public          boolean         activeTargetFollow          = DEFAULT__targetFollow     // These settings are all defaulted to the configuration constants that are
        public          boolean         activePitch                 = DEFAULT__facePitch        // declared above when the projectile is created. If the user wants to customize
        public          boolean         activeRotation              = DEFAULT__faceRotation     // these settings beyond their defaults (or change them mid-flight) then they can
        public          boolean         activeUnitCollision         = DEFAULT__unitCollision    // be referenced publicly and flagged on/off (true/false).
        public          boolean         activeDestCollision         = DEFAULT__destCollision
    //*
    //*
        public          boolean         destroyed                   = false
        public          boolean         toDestroy                   = DEFAULT__toExpire         // These flags are specific to the end-phase of the projectile flight. Flags that
        public          boolean         toKill                      = DEFAULT__toKill           // control whether the unit handle is killed/removed upon projectile destruction,
        public          boolean         toRemove                    = DEFAULT__toRemove         // and whether or not a projectile is destroyed upon its "finish" event.
    //*
    //*
        private         boolean         FLAG__onGround              = true                    
        private         boolean         FLAG__active                = false  
        private         boolean         FLAG__targetFollow          = false
        private         boolean         FLAG__followUnit            = false
        private         boolean         FLAG__isPaused              = false
    //*
    //*
        private         vector          VECTOR__position                                // Each projectile allocates 5 vectors that control its motion. These vectors
        private         vector          VECTOR__velocity                                // are mainly initiated to 0-vectors except for the "position vector", which
        private         vector          VECTOR__acceleration                            // immediately occupies with the projectile's in-game coordinates.
        private         vector          VECTOR__target
        private         vector          VECTOR__start                                   // The starting-position is necessary to reference the "total" distance that
    //*                                                                                 // the projectile has traveled from where it began (cosmetic calculations).
    //*
        private         integer         DATA__index                                    
        readonly static thistype array  DATA__stack                                    
        readonly static integer         DATA__size                  = 0
        private static  timer           DATA__loop                  = CreateTimer()
        private static  constant real   DATA__loopRef               = SETTING__timeout
        private         real            DATA__timeLeft    
    //*
    //*  
        private static  boolexpr        FUNC__enumUnits             = null
        private static  boolexpr        FUNC__enumDests             = null
    //*
    //*
    //*
    //* ########################################################################################################
    //* - MODULE IMPLEMENTATION -
        implement optional              ProjectileArt
    //*
    //* ########################################################################################################
    //*
    //* Position Interface
    //* ________________________________________________________________________________________________________
        method operator x takes nothing returns real
            return VECTOR__position.x
        endmethod
        method operator y takes nothing returns real
            return VECTOR__position.y
        endmethod
        method operator z takes nothing returns real
            return VECTOR__position.z
        endmethod  
    //*
    //*
    //* Timescale Interface
    //* ________________________________________________________________________________________________________
        method operator timescale takes nothing returns real
            if FLAG__isPaused then
                return 0.00
            endif
            return PROP__timescale
        endmethod
        method operator timescale= takes real r returns nothing    
            if (r == 0) then                                      
                set FLAG__isPaused = true                          
            else
                set FLAG__isPaused = false
                set PROP__timescale = r
            endif
        endmethod
    //*
    //*
    //* Collision Interface
    //* ________________________________________________________________________________________________________
        method operator collision takes nothing returns real
            return PROP__collision
        endmethod
        method operator collision= takes real r returns nothing
            if r > COLLISION__sizeMax then
                set r = COLLISION__sizeMax
            elseif r < 0 then
                set r = 0
            endif
            set PROP__collision = r
        endmethod
    //*
    //*
    //* Source Interface
    //* ________________________________________________________________________________________________________
        method operator source takes nothing returns unit
            return PROP__source
        endmethod
        method operator source= takes unit who returns nothing
            set PROP__source = who
        endmethod
    //*
    //*
    //* Target Interface
    //* ________________________________________________________________________________________________________
    //* ========================================================================================================
        method operator target takes nothing returns unit           //* The target object (unit-only) can be
            return PROP__target                                     //* referenced at any time through the
                                                                    //* projectile.
        endmethod
        method operator target= takes unit who returns nothing      //* The target can also be changed at any
            if GetUnitTypeId(who) != 0 then                         //* time; setting the target to "null" or
                set PROP__target = who                              //* even a removed unit will reset the
                set FLAG__followUnit = true                         //* effects of modifying the target.
            else
                set PROP__target = null                             //* If a target is selected then the system
                set FLAG__followUnit = false                        //* will automatically follow the unit's
            endif                                                   //* position as the target vector.
        endmethod
    //*
    //* ========================================================================================================
        method operator targetX takes nothing returns real          
            return VECTOR__target.x
        endmethod
        method operator targetY takes nothing returns real
            return VECTOR__target.y
        endmethod
        method operator targetZ takes nothing returns real          
            return VECTOR__target.z                                
        endmethod    
    //*
    //* ========================================================================================================
        method operator targetZOffset takes nothing returns real    
            return PROP__targetHeight
        endmethod
        method operator targetZOffset= takes real r returns nothing
            if r < 0 then                                          
                set r = 0                                        
            endif                    
            set PROP__targetHeight = r
        endmethod
    //*
    //* ========================================================================================================
        method setTargetPos takes real x1, real y1, real z1 returns boolean  
            local real xA
            local real yA  
            local vector v = VECTOR__velocity
            if (x1 != VECTOR__target.x) or (y1 != VECTOR__target.y) or (z1 != VECTOR__target.z) then
                set VECTOR__target.x    = x1
                set VECTOR__target.y    = y1
                set VECTOR__target.z    = z1 + PROP__targetHeight
                set xA                  = VECTOR__position.x
                set yA                  = VECTOR__position.y
               
                set xA                  = (x1-xA)*(x1-xA) + (y1-yA)*(y1-yA)
                set yA                  = (v.x*v.x + v.y*v.y)/(thistype.DATA__loopRef*thistype.DATA__loopRef)
                set DATA__timeLeft      = SquareRoot(xA/yA)
               
                set FLAG__targetFollow = true       // The "targetFollow" flag will be toggled to notify the
                return true                         // engine that the target has been changed from its
                                                    // original position.
            endif
            return false
        endmethod  
    //*
    //*
    //* Destructor
    //* ________________________________________________________________________________________________________
        method onDestroy takes nothing returns nothing
            local integer i    
            if not (destroyed) then
                set thistype.DATA__size = thistype.DATA__size - 1
                set thistype.DATA__stack[DATA__index] = thistype.DATA__stack[thistype.DATA__size]
                set thistype.DATA__stack[DATA__index].DATA__index = DATA__index
                if (thistype.DATA__size == 0) then
                    call PauseTimer(thistype.DATA__loop)
                   
                endif
                set totalProjectiles = totalProjectiles - 1        
               
                if LIBRARY_ProjectileArt then       // If the ProjectileArt module was implemented, this will execute
                    call killModel( )               // a command to remove the model art.
                endif
                if toRemove then                    // The projectile unit handle can either be removed or killed,
                    call RemoveUnit(toUnit)         // with removal in priority if they are both flagged.
                elseif toKill then
                    call KillUnit(toUnit)
                endif
               
                call VECTOR__position.destroy( )
                call VECTOR__velocity.destroy( )
                call VECTOR__acceleration.destroy( )
                call VECTOR__target.destroy( )
                call VECTOR__start.destroy( )
               
                if HaveSavedInteger(projGroupTable, this, -1) then
                    set i = LoadInteger(projGroupTable, this, -1) - 1
                    loop
                        exitwhen i < 0
                        call projectilegroup(LoadInteger(projGroupTable, this, i)).remove(this)
                        set i = i - 1
                    endloop
                    call FlushChildHashtable(projGroupTable, this)
                    call FlushChildHashtable(projGroupTable, this+8092)
                endif
                set destroyed = true
            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
            local real r2 = radius*radius
            local thistype p
            local vector v
            loop
                exitwhen i == DATA__size
                set p  = DATA__stack[i]
                set v  = p.VECTOR__position
                set x2 = v.x
                set y2 = v.y
                set z2 = v.z
                if ((x2-x)*(x2-x)+(y2-y)*(y2-y)+(z2-z)*(z2-z)) <= (r2) then
                    call g.add(DATA__stack[i])
                endif
                set i=i+1
            endloop
        endmethod
    //*
    //*
    //* Launch Projectile
    //* ________________________________________________________________________________________________________
        method launch takes vector start, vector finish, real speed, real arc returns boolean
            local real d
            local real a
            local boolean instant = false
            if (start != 0) and (finish != 0) and not (FLAG__active) then
                set VECTOR__position.x      = start.x      
                set VECTOR__position.y      = start.y      
                set VECTOR__position.z      = start.z
                set VECTOR__target.x        = finish.x
                set VECTOR__target.y        = finish.y
                set VECTOR__target.z        = finish.z + PROP__targetHeight
                set VECTOR__start.x         = start.x
                set VECTOR__start.y         = start.y
                set VECTOR__start.z         = start.z
                set this.speed              = speed
                set this.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) or (speed == 0) then
                    set VECTOR__position.x      = VECTOR__target.x      // If the projectile speed is 0 or the distance to the target
                    set VECTOR__position.y      = VECTOR__target.y      // is 0 then the projectile will be considered "instant", and
                    set VECTOR__position.z      = VECTOR__target.z      // launch to its destination immediately.
                    set DATA__timeLeft          = 0.00
                    set instant                 = true
                else
                    set DATA__timeLeft          = d/speed
                    set VECTOR__acceleration.z  = (-8*arc*speed*speed/d)
                    set VECTOR__velocity.x      = speed*Cos(a)  *thistype.DATA__loopRef
                    set VECTOR__velocity.y      = speed*Sin(a)  *thistype.DATA__loopRef
                    set VECTOR__velocity.z      = (-VECTOR__acceleration.z * (d/speed)/2 + (VECTOR__target.z-start.z)/(d/speed)) /*
                                                            */
     *thistype.DATA__loopRef
                    set VECTOR__acceleration.z  = VECTOR__acceleration.z *thistype.DATA__loopRef*thistype.DATA__loopRef
                       
                endif
               
                call SetUnitX(toUnit, VECTOR__position.x)
                call SetUnitY(toUnit, VECTOR__position.y)
                call MoveLocation(loc, VECTOR__position.x, VECTOR__position.y)
                call SetUnitFlyHeight(toUnit, VECTOR__position.z-GetLocationZ(loc), 0)
               
                call onStart( )
                set FLAG__active = true
                if instant then
                    set FLAG__active = false
                    call onFinish( )
                    if (toDestroy and not destroyed) then
                        call destroy( )
                    endif
                endif
                return true
            endif
            return false    
        endmethod
    //*
    //* Filter Enumeration Methods
    //* ________________________________________________________________________________________________________
    //*     In order to efficiently cycle through nearby units and destructables it is necessary to use a
    //*     filterfunc callback which is automatically executed by the enumeration natives. These two filters
    //*     have global "boolexpr" references for efficient reference without having to use the Filter() native.
    //*
        private static method doLoopDestFilter takes nothing returns boolean
            local destructable d = GetFilterDestructable()
            local real x         = GetDestructableX(d)
            local real y         = GetDestructableY(d)
            local real xB        = projRef.VECTOR__position.x
            local real yB        = projRef.VECTOR__position.y
            if ((xB-x)*(xB-x) + (yB-y)*(yB-y)) <= projRef.PROP__collision*projRef.PROP__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.VECTOR__position.x
            local real yB   = projRef.VECTOR__position.y
            if (((xA-xB)*(xA-xB) + (yA-yB)*(yA-yB)) <= projRef.PROP__collision*projRef.PROP__collision) then
                if (projRef.isValidTargetUnit(filt)) then
                    call projRef.onUnitCollision(filt)
                endif
            endif
            set filt = null
            return false
        endmethod
    //*
    //* Control Loop Iterator
    //* ________________________________________________________________________________________________________
    //*     The loop will run through each member in the stack of projectiles and use it's acceleration and
    //*     velocity to calculate its motion path. This control loop will detect when the projectile collides
    //*     with units, destructables, or even the terrain; in addition to throwing a few interface methods
    //*     that allow the user to customize the actions to his/her preference.
    //*
        private static method doLoop takes nothing returns nothing
            local integer i = DATA__size - 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 unit u
            loop
                exitwhen (i < 0)
                set dat = DATA__stack[i]
                if (dat != 0) then
                    if dat.FLAG__active then
                        set e       = dat.timescale
                        set vA      = dat.VECTOR__velocity
                        set vB      = dat.VECTOR__acceleration
                        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.VECTOR__position
                        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.VECTOR__velocity
                        set vB      = dat.VECTOR__position
                    endif
                   
                    set u = dat.toUnit
                    set x = vB.x
                    set y = vB.y
                    set z = vB.z
                    call SetUnitX(u, x)
                    call SetUnitY(u, y)
                    call MoveLocation(loc, x, y)
                    call SetUnitFlyHeight(u, z-GetLocationZ(loc), 0)
                   
                    set x2 = vA.x
                    set y2 = vA.y
                    set z2 = vA.z
                    if (dat.FLAG__active) and (x2 != 0 or y2 != 0 or z2 != 0) then
                        if dat.activePitch then
                            call SetUnitAnimationByIndex(u, R2I(bj_RADTODEG*Atan2(z2, dat.speed*DATA__loopRef)+0.5)+90)
                            // 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 SetUnitFacingTimed(u, Atan2(y2, x2)*bj_RADTODEG, 0)
                        endif
                    endif
                   
                    if dat != 0 then
                        if (z <= GetLocationZ(loc)) then                  
                            if not (dat.FLAG__onGround) then            // The ".onGround( )" method callback will only be executed
                                set dat.FLAG__onGround = true           // once upon hitting the ground, and not repeatedly if the
                                call dat.onGround( )                    // projectile remains under the terrain level.
                            endif                              
                        else
                            if (dat.FLAG__onGround) then
                                set dat.FLAG__onGround = false
                            endif
                        endif
                    endif
                   
                    if (dat.activeUnitCollision) and dat != 0 then // Unit Collision
                        set projRef = dat
                        call GroupEnumUnitsInRange(grp, x, y, dat.PROP__collision, FUNC__enumUnits)
                    endif
                    if (dat.activeDestCollision) and dat != 0 then // Destructable Collision
                        set projRef = dat
                        call SetRect(rct, 0, 0, dat.PROP__collision, dat.PROP__collision)
                        call MoveRectTo(rct, x, y)
                        call EnumDestructablesInRect(rct, FUNC__enumDests, null)
                    endif
                   
                    if (dat != 0) and (dat.FLAG__active) and (dat.activeTargetFollow) then
                        if (dat.FLAG__followUnit) and GetUnitTypeId(dat.target) != 0 then
                            set x = GetUnitX(dat.target)
                            set y = GetUnitY(dat.target)
                            call MoveLocation(loc, x, y)
                            set z = GetLocationZ(loc)+GetUnitFlyHeight(dat.target)
                           
                            call dat.setTargetPos(x, y, z)
                        endif
                        if (dat.FLAG__targetFollow) then
                            set vC           = dat.VECTOR__target
                            set x            = Atan2(vC.y-vB.y, vC.x-vB.x)                                      // Direction To Target (radians)
                            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) * DATA__loopRef
                            set vA.y         = dat.speed * Sin(x) * DATA__loopRef
                            set vB           = dat.VECTOR__start
                            set x            = SquareRoot((vB.x-vC.x)*(vB.x-vC.x) + (vB.y-vC.y)*(vB.y-vC.y))    // Total Distance

                            set dat.VECTOR__acceleration.z  = -8*dat.arc*dat.speed*dat.speed/x                                                  
                            set vA.z                        = -dat.VECTOR__acceleration.z * y/(dat.speed*2) + dat.speed*z2/y
                           
                            set dat.VECTOR__acceleration.z  = dat.VECTOR__acceleration.z   *DATA__loopRef*DATA__loopRef
                            set vA.z                        = vA.z                         *DATA__loopRef
                        endif
                    endif
                       
                    if dat.FLAG__active then
                        if not dat.FLAG__isPaused then
                            set dat.DATA__timeLeft = dat.DATA__timeLeft - (DATA__loopRef*dat.timescale)
                        endif
                        if dat.DATA__timeLeft <= 0.00  then
                            set dat.FLAG__active = false
                            call dat.onFinish( )
                            if (dat.toDestroy and not dat.destroyed) then
                                call dat.destroy( )
                            endif
                        endif
                    endif
                endif
                set i = i - 1
            endloop
            set u = null
        endmethod
    //*
    //*
    //* Constructor
    //* ________________________________________________________________________________________________________
        static method create takes unit u returns thistype
            local thistype dat  = thistype.allocate()
            local real x        = GetUnitX(u)
            local real y        = GetUnitY(u)
               
            call UnitAddAbility(u, ID__flyHeightModifier)
            call UnitRemoveAbility(u, ID__flyHeightModifier)
            if DATA__size == 0 then
                call TimerStart(DATA__loop, DATA__loopRef, true, function thistype.doLoop)
               
            endif
            call MoveLocation(loc, x, y)
            set totalProjectiles = totalProjectiles + 1
           
            set dat.toUnit                  = u
            set dat.DATA__index             = DATA__size
            set dat.VECTOR__position        = vector.create(x, y, GetLocationZ(loc)+GetUnitFlyHeight(u))
            set dat.VECTOR__target          = vector.create(0, 0, 0)
            set dat.VECTOR__velocity        = vector.create(0, 0, 0)
            set dat.VECTOR__acceleration    = vector.create(0, 0, 0)
            set dat.VECTOR__start           = vector.create(0, 0, 0)
            set DATA__stack[DATA__size]     = dat
            set DATA__size                  = DATA__size + 1
               
            return dat
        endmethod
    //*
    //*
    //* Setup & Initialization
    //* ________________________________________________________________________________________________________
        private static method onInit takes nothing returns nothing
            set rct = Rect(0, 0, COLLISION__sizeMax, COLLISION__sizeMax)
           
            set FUNC__enumUnits = Filter(function thistype.doLoopEnum)
            set FUNC__enumDests = Filter(function thistype.doLoopDestFilter)
           
        endmethod
    //*
    //*
    //*********************************************************************************************************
    endstruct




    endlibrary
     

    Vector:
    Code (vJASS):

    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 null
                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 null
                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
     


    ProjectileArt:
    Code (vJASS):
    library ProjectileArt
    //######################################################################################################
    //#
    //#     P R O J E C T I L E   A R T
    //#                                 by Berb
    //#
    //######################################################################################################

    module ProjectileArt
    //**********************************
    //* Projectile Art Module
    //* ____________________________________________________________________________________________________
    //*     The projectile-art module offers the Projectile library "improved" functionality, allowing the
    //*     user to designate an effect model that they would like to be attached to the origin of the
    //*     model. This is particularly useful when dealing with the dummy.mdx model.
    //*
    //*     This module is designed for the Projectile library version 1.8+; versions prior to 1.8 will
    //*     not have the necessary support ("implement optional" command).
    //*
        private     string      ART__path    
        private     effect      ART__model      = null
    //*
    //* ====================================================================================================
    //* method killModel( )         -> This will delete a projectile's model art by destroying the attached
    //*                             effect if it is present. It will return false if it has not.
    //*                  
        method killModel takes nothing returns boolean  
            if ART__model != null then
                call DestroyEffect(ART__model)
                return true
            endif
            return false
        endmethod
    //*
    //* ====================================================================================================
    //* method setModel( )
    //*         @param "filepath"   -> The filepath of the model that is going to be attached to the origin
    //*                             of the projectile unit.
    //*        
        method setModel takes string filepath returns nothing
            call killModel( )
            set ART__model = AddSpecialEffectTarget(filepath, toUnit, "origin")
            set ART__path  = filepath
        endmethod
    //*
    //*
    //******************************************************************************************************
    endmodule

    endlibrary