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

random desync problem

Status
Not open for further replies.
Level 8
Joined
Jul 25, 2009
Messages
194
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!
 

Ralle

Owner
Level 77
Joined
Oct 6, 2004
Messages
10,100
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.
 
Level 8
Joined
Jul 25, 2009
Messages
194
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.
 
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))"

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.

and are these causes of desync true?
-Long file names

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.

-units moving to a target point but are blocked by other units

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:
so does it take some time for a source of a desync to actually desync the players?

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.

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.

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.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
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.

JASS:
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.
 
Level 8
Joined
Jul 25, 2009
Messages
194
I would suggest to hook (build a wrapper) around the GetLocationZ native and output the values in an external file using the preload natives.
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
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
He meant to add this code in your map.
JASS:
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

Have not worked with hooks before but tested it and it seems like it would replace

JASS:
call GetLocationZ

by

JASS:
call GetLocationZ_Print
call GetLocationZ

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

Well, else, substitute all instances yourself.
 
Level 8
Joined
Jul 25, 2009
Messages
194
so is this jump spell unfixable? I'd only use it if it were 0% chance of desyncs

Spell:

JASS:
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
 
Level 8
Joined
Jul 25, 2009
Messages
194
Libraries:

TimerUtilis:
JASS:
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:
JASS:
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:
JASS:
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:
JASS:
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
 
Status
Not open for further replies.
Top