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

[vJASS] Projectile

Level 19
Joined
Mar 18, 2012
Messages
1,716
Projectile
Code

Library Projectile is a system that facilitates structured
projectile creation. Projectile is mainly used to configurate
projectile based attacks or spells, but also effect or unit movement.



~~~~~~~~~~~~~~~~~~~~~~~~~~
Color_balance.png
Import Instructions
  • Copy Library Projectile into your map.
  • For widget collision detection copy Library ProjectileCollision into your map.
  • Make sure you have a dummy unit, using Vexorian's "dummy.mdx".
  • Read over the global user settings inside library Projectile.



~~~~~~~~~~~~~~~~~~~~~~~~~~
Copy.png
Library Projectile

JASS:
library Projectile /* Version 1.0
*************************************************************************************
*
*   For your projectile needs.
*   
*************************************************************************************  
*   
*   Credits:
*   ¯¯¯¯¯¯¯¯
*       • Flux for library DummyRecycler.
*       • Bribe for library MissileRecycler.  
*       • Vexorian for the dummy.mdx and library xedummy.
*       • Nestharus for library ErrorMessage and library Dummy. 
*
************************************************************************************* 
*
*   Import instructions:
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       • Copy library Projectile into to your map. 
*       • Make sure you have a dummy unit, using Vexorian's "dummy.mdx".
*       • Read over the user settings below.
*
*************************************************************************************
*
*   */ requires /*
*
*       • Projectile requires nothing.
*
*************************************************************************************
*
*   Optional requirements:
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   a) For best debug results: ( Recommended )
*       */ optional ErrorMessage     /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage  
*
*   b) Dummy unit recycling: ( Very recommended ) 
*          - JassHelper compiles in priority as listed below. 
*       */ optional MissileRecycler  /* hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
*       */ optional DummyRecycler    /* hiveworkshop.com/forums/spells-569/dummy-recycler-v1-12-a-277659/
*       */ optional xedummy          /* wc3c.net/showthread.php?t=101150
*       */ optional Dummy            /* github.com/nestharus/JASS/blob/master/jass/Systems/Dummy/Dummy.w3x
*
*************************************************************************************/

// User settings:
// ==============
     
    globals
     
        // Set the data value of the dummy unit used for projectiles.
        //
        public constant integer DUMMY_UNIT_TYPE_ID = 'dumi'
        
        // Set the owner of all projectiles. 
        //     • Preferably a neutral player in your map.
        //
        public constant player  NEUTRAL_PLAYER     = Player(15)
        
        // Set the timer timeout for projectile motion and action.
        //     • An interval of 1/32 is very recommended.
        //
        public constant real    TIMER_TIMEOUT      = 0.03125
        
        // Set the maximum pathing collision size in your map.
        //     • A precise value can improve Projectile's performance,
        //       as smaller values enumerate less widgtes per loop per projectile.
        //
        public constant real    MAX_COLLISION_SIZE = 197.00
    
        // The following constants are available drivers for the motion phase.
        //     • A projectile can only use one driver during the same time.
        
        //     Projectile_MOTION_DRIVER_NONE ( Default motion driver )
        //         • The projectile travels an linear path in a specified direction.   
        // 
        public key MOTION_DRIVER_NONE          
        
        //    Projectile_MOTION_DRIVER_GUIDANCE
        //         • The projectiles moves after a target unit or target point in 3D space.
        //
        public key MOTION_DRIVER_GUIDANCE
        
        //    Projectile_MOTION_DRIVER_PARABOLA
        //         • Does so far nothing
        //
        public key MOTION_DRIVER_PARABOLA
        
        //    Projectile_MOTION_DRIVER_BALLISTIC
        //         • Fires a projectile through 3D space with a sepcified arc.
        //         • This driver is perfect to simulate classic unit attacks.
        //
        public key MOTION_DRIVER_BALLISTIC
        
        // The following constants are available projectile look at options.
        
        //     Projectile_LOOK_AT_3D ( Default look at )
        //         • The projectile always points towards the target in 3D space. 
        //
        public key LOOK_AT_3D
        
        //     Projectile_LOOK_AT_2D
        //         • The projectile only adjusts it's facing angle towards the target. 
        //
        public key LOOK_AT_2D
        
        //    Projectile_LOOK_AT_OFF
        //         • The projectile's angle and pitch rotations
        //           remain untouched during the motion phase.
        //
        public key LOOK_AT_OFF
    endglobals
     
//=============================================================================
// Functions, constants and variables used by Projectile. Make changes carefully.
//=============================================================================
     
    globals
        //======================================================
        // Constants
        //
        
        // Misc constants
        // If required set the mathematical constants to accurate values.
        private constant real          PI                      = 3.14159
        private constant real          TAU                     = 6.28318
        private constant real          RADTODEG                = 180.00/PI
        private constant real          DEGTORAD                = PI/180.00
        // Concerning projectile motion
        private constant real          MIN_PROJECTILE_SPEED    = 0.00
        private constant real          MAX_PROJECTILE_SPEED    = 999999.00
        private constant real          DEFAULT_GRAVITY         = 0.00
        // Others
        private constant real          TIMER_TIMEOUT_SQUARED   = Projectile_TIMER_TIMEOUT*Projectile_TIMER_TIMEOUT 
        private constant real          FX_TIMER_DELAY          = 0.00
        private constant integer       MAX_ITERATIONS_PER_LOOP = 150
        private constant integer       KEY_UNIT_TO_PROJECTILE  = -1 
        private constant integer       KEY_RECYCLE_DUMMY       = -2 
        private constant integer       KEY_BODY_SIZE           = -3 
        private constant integer       KEY_FX_TIMER            = -4 
        
        // Ability constants
        private constant integer       ABILITY_ID_LOCUST       = 'Aloc'
        private constant integer       ABILITY_ID_CROW_FORM    = 'Arav'
        
        // Utility constants
        private constant location      LOC                     = Location(0.00, 0.00)
        private constant rect          ENUM_RECT               = Rect(0.00, 0.00, 0.00, 0.00) 
        private constant group         FILTER_GROUP            = CreateGroup()
        
        // Constants for periodic projectile motion and action
        private constant timer         TIMER                   = CreateTimer()
        private constant trigger       ACTION                  = CreateTrigger()
        private constant trigger       MOTION                  = CreateTrigger()
        
        // Data storage constants
        private constant hashtable     HASH                    = InitHashtable()
        
        // Collision constants
        private constant real          TERRAIN_TILE_RADIUS     = 4.00  
        private constant real          ITEM_PATHING_RADIUS     = 8.00     
        private constant real          UNIT_PATHING_RADIUS     = 16.00
        private constant real          DEST_PATHING_RADIUS     = 16.00
        private constant real          UNIT_BODY_SIZE          = 100.00 
        
        //===================================
        // Variables
        //
        
        // Map boundary variables 
        private real                   maxX
        private real                   maxY
        private real                   minX
        private real                   minY
        
        // Structured projectile motion and action variables
        private integer                activeStructs = 0
        private integer array          recycleNext
        private integer array          collection
        private integer array          instances  
        private boolean array          recycling
        private boolean array          disabled
        private boolexpr array         expression 
        private triggercondition array condition  
        
        // Helper variables
        private real                   tempX
        private real                   tempY
        private real                   tempZ
        private real                   tempD
        private unit                   tempUnit
        private Projectile             loopIndex
    endglobals
    
//***************************************************************************
//*
//*  Debugging Functions ( Require library ErrorMessage )
//*
//***************************************************************************
    
    private function DebugError takes boolean condition, string functionName, string objectName, integer objectInstance, string description returns nothing
        static if LIBRARY_ErrorMessage then
            debug call ThrowError(condition, "Projectile", functionName, objectName, objectInstance, description)
        endif
    endfunction
    
    private function DebugWarning takes boolean condition, string functionName, string objectName, integer objectInstance, string description returns nothing
        static if LIBRARY_ErrorMessage then
            debug call ThrowWarning(condition, "Projectile", functionName, objectName, objectInstance, description)
        endif
    endfunction
    
//***************************************************************************
//*
//*  Terrain Utility Functions
//*
//***************************************************************************
    
    public function GetTerrainZ takes real x, real y returns real
        call MoveLocation(LOC, x, y)
        return GetLocationZ(LOC)
    endfunction

    private function GetTerrainNormal takes real x, real y, real radius returns nothing
        call MoveLocation(LOC, x - radius, y)
        set tempX = GetLocationZ(LOC) 
        call MoveLocation(LOC, x + radius, y)
        set tempX = 2.00*radius*(tempX - GetLocationZ(LOC))
        call MoveLocation(LOC, x, y - radius)
        set tempY = GetLocationZ(LOC) 
        call MoveLocation(LOC, x, y + radius)
        set tempY = 2.00*radius*(tempY - GetLocationZ(LOC))
        set tempZ = 4.00*radius*radius
    endfunction
    
//***************************************************************************
//*
//*  Map Utility Functions
//*
//***************************************************************************
    
    private function GetMapSafeX takes real x returns real
        if x > maxX then
            return maxX 
        elseif x < minX then
            return minX
        endif
        return x
    endfunction
        
    private function GetMapSafeY takes real y returns real
        if y > maxY then
            return maxY
        elseif y < minY then
            return minY
        endif
        return y        
    endfunction

//***************************************************************************
//*
//*  Unit Utility Functions
//*
//***************************************************************************
    
    private function UnitAddAbilityToFly takes unit whichUnit returns boolean
        return UnitAddAbility(whichUnit, ABILITY_ID_CROW_FORM) and UnitRemoveAbility(whichUnit, ABILITY_ID_CROW_FORM)
    endfunction
    
    // Vexorian's dummy.mdx enables pitch manipulation from -pi/2 to pi/2.
    // Therefore you won't be able to have upside down units.
    private function SetUnitAnimationByPitch takes unit whichUnit, real pitch returns nothing
        local integer index
    
        // The following operation works only for Vexorian's dummy.mdx.
        if GetUnitTypeId(whichUnit) != Projectile_DUMMY_UNIT_TYPE_ID then
            return
        endif
        
        set index = R2I(90.50 + pitch*RADTODEG)
        if index > 179 then
            set index = 179
        elseif index < 0 then
            set index = 0
        endif
        call SetUnitAnimationByIndex(whichUnit, index)
    endfunction
    
    private function GetDummyUnit takes real x, real y, real z, real face returns unit    
        static if LIBRARY_ProjectileRecycler then
            return GetRecycledMissile(x, y, z, face)
            
        elseif LIBRARY_DummyRecycler then
            return GetRecycledDummy(x, y, z, face) 
        
        elseif LIBRARY_xedummy and xedummy.new.exists then
            set tempUnit = xedummy.new(Projectile_NEUTRAL_PLAYER, x, y, face)
       
       elseif LIBRARY_Dummy and Dummy.create.exists then
            set tempUnit = Dummy.create(x, y, face).unit
        
        else
            set tempUnit = CreateUnit(Projectile_NEUTRAL_PLAYER, Projectile_DUMMY_UNIT_TYPE_ID , x, y, face)
            call SetUnitX(tempUnit, x)
            call SetUnitY(tempUnit, y)
            call UnitAddAbility(tempUnit, ABILITY_ID_LOCUST)
            call PauseUnit(tempUnit, true)
        endif
        call UnitAddAbilityToFly(tempUnit)
        call SetUnitFlyHeight(tempUnit, z, 0.00)
        return tempUnit
    endfunction
    
    private function DummyTimerExpires takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local integer id = GetHandleId(t)
        
        static if LIBRARY_MissileRecycler then
            call RecycleMissile(LoadUnitHandle(HASH, KEY_RECYCLE_DUMMY, id))

        elseif LIBRARY_Dummy and Dummy.create.exists then
            call Dummy[LoadUnitHandle(HASH, KEY_RECYCLE_DUMMY, id)].destroy()

        elseif LIBRARY_xedummy and xedummy.release.exists then
            call xedummy.release(LoadUnitHandle(HASH, KEY_RECYCLE_DUMMY, id))
        
        else
            call RemoveUnit(LoadUnitHandle(HASH, KEY_RECYCLE_DUMMY, id))
        endif
        call RemoveSavedHandle(HASH, KEY_RECYCLE_DUMMY, id)
        call DestroyTimer(t)
        
        set t = null
    endfunction
    
    private function RecycleDummyTimed takes unit dummy, real time returns nothing
        static if LIBRARY_DummyRecycler and not LIBRARY_MissileRecycler then
            call DummyAddRecycleTimer(dummy, time)
        else
            local timer t = CreateTimer()
            
            call SaveUnitHandle(HASH, KEY_RECYCLE_DUMMY, GetHandleId(t), dummy)
            call TimerStart(t, time, false, function DummyTimerExpires)
            
            set t = null
        endif
    endfunction
    
    //! runtextmacro optional PROJECTILE_COLLISION_UTILTIY_CODE()
    
//***************************************************************************
//*
//*  Math Utility Functions
//*
//***************************************************************************

    private function GetAngleRotation takes real currentAngle, real targetAngle, real turnRate returns real
        if Cos(currentAngle - targetAngle) < Cos(turnRate) then
            if Sin(currentAngle - targetAngle) > 0.00 then
                return -turnRate 
            else
                return turnRate
            endif
        endif
        return targetAngle - currentAngle
    endfunction

    // Returns an angle between -pi and pi.  
    private function ModuloAngle takes real angle returns real
        set angle = angle + PI - I2R(R2I(angle/TAU))*TAU
        if angle < 0.00 then
            return angle + TAU - PI
        endif
        return angle - PI
    endfunction
    
    // Computes the difference in radians between angle alpha and beta.
    private function GetAngleDifference takes real alpha, real beta returns real
        return Acos(Cos(alpha - beta))
    endfunction
    
    // Computes the flight time given an initial speed, acceleration and distance
    private function GetFlyTime takes real speed, real acceleration, real distance returns real
        local real disc = speed*speed + 2*distance*acceleration
        
        if acceleration == 0.00 then
            if speed == 0.00 then
                return -1.00
            endif
            return distance/speed
        elseif disc < 0.00 then
            return -1.00
        endif
    
        return (-speed + SquareRoot(disc))/acceleration
    endfunction
        
//===========================================================================
// Init

    private function InitProjectileGlobals takes nothing returns nothing
        local rect map = GetWorldBounds()
            
        set maxX = GetRectMaxX(map) - Projectile_MAX_COLLISION_SIZE
        set maxY = GetRectMaxY(map) - Projectile_MAX_COLLISION_SIZE
        set minX = GetRectMinX(map) + Projectile_MAX_COLLISION_SIZE
        set minY = GetRectMinY(map) + Projectile_MAX_COLLISION_SIZE
        
        call RemoveRect(map)
        
        set map = null
    endfunction
    
//***************************************************************************
//*
//*  Structured Projectiles
//*
//***************************************************************************
        
    // Returns only true for ids using the ProjectileStruct module.
    private function IsProjectileStructId takes integer id returns boolean
        return id < JASS_MAX_ARRAY_SIZE and collection[id] != 0
    endfunction
        
    // Directive to a module which I placed at bottom of the library.
    private keyword PROJECTILE_TYPE_DATA_STRUCTURE
    
    struct ProjectileType extends array      
        
        // Implements a linked list data structure
        implement PROJECTILE_TYPE_DATA_STRUCTURE
    
        static method operator [] takes integer id returns thistype
            debug call DebugError(not IsProjectileStructId(id), "ProjectileType[]", "thistype", id, "Attempt to access invalid list!")
            
            return collection[id]
        endmethod
    endstruct 
    
    private function StructAddProjectile takes integer structId, integer projectile returns nothing
        call ProjectileType[structId].push(projectile)
        
        if instances[structId] == 0 or recycling[structId] then
            if recycling[structId] then
                set disabled[structId] = false
            else
                set condition[structId] = TriggerAddCondition(ACTION, expression[structId])
                set activeStructs = activeStructs + 1
            endif
        endif
        set instances[structId] = instances[structId] + 1
    endfunction
    
    private function StructRemoveProjectile takes integer structId, integer projectile returns nothing
        call ProjectileType(projectile).remove()
        
        set instances[structId] = instances[structId] - 1
        if instances[structId] == 0 then
            set disabled[structId] = true
            if not recycling[structId] then
                set recycling[structId] = true
                set recycleNext[structId] = recycleNext[0]
                set recycleNext[0] = structId
            endif
        endif
    endfunction
    
    private function RunStructActions takes nothing returns nothing
        local integer id = recycleNext[0]

        set recycleNext[0] = 0        
        loop
            exitwhen id == 0
            // Remove inactive structs from the trigger.
            if disabled[id] then
                call TriggerRemoveCondition(ACTION, condition[id])
                set condition[id] = null
                set disabled[id] = false
                set activeStructs = activeStructs - 1
            endif
            set recycling[id] = false
            set id = recycleNext[id]
        endloop
        
        if activeStructs > 0 then
            call TriggerEvaluate(ACTION)
        endif
    endfunction
    
    private function CreateProjectileStruct takes integer id, code func returns nothing
        set collection[id] = ProjectileType.allocateList()
        set expression[id] = Condition(func)
    endfunction
    
//***************************************************************************
//*
//*  Core Projectile Code
//*
//***************************************************************************
    
    struct Projectile 

        //===================================================================
        // Number of allocated instance.
    
        readonly static integer counter = 0
    
        //===================================================================
        // Safety
        
        readonly boolean allocated = true
        method operator exists takes nothing returns boolean
            return allocated
        endmethod

        //===================================================================
        // Singly linked list of deallocating nodes.
        
        private thistype destroying
        
        //===================================================================
        // Static unique list holding all flying projectiles.
    
        readonly thistype next
        readonly thistype prev
        static constant method operator first takes nothing returns thistype
            return thistype(0).next
        endmethod

        static constant method operator last takes nothing returns thistype
            return thistype(0).prev
        endmethod
        
        private method enqueue takes nothing returns nothing
            set next = 0
            set prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
        endmethod
        
        private method remove takes nothing returns nothing
            set prev.next = next
			set next.prev = prev
        endmethod

        //===================================================================
        // Typecast unit to projectiles instance
        
        static method operator [] takes unit projectile returns thistype
            return LoadInteger(HASH, KEY_UNIT_TO_PROJECTILE, GetHandleId(projectile))
        endmethod

        //===================================================================
        // Projectile unit and typeid
        
        readonly unit    dummy  
        readonly integer typeId = 0 // Id of the struct the projectile was launched from.
        
        //===================================================================
        // Position members
        
        // Position of the current projectile game state.
        readonly real    x
        readonly real    y
        readonly real    z
        readonly real    terrainZ
        public   boolean updatePos // Indicates if a periodic position update is required.
        method setPos takes real newX, real newY, real newZ returns nothing
            call SetUnitX(dummy, newX)
            call SetUnitY(dummy, newY)
            call SetUnitFlyHeight(dummy, newZ, 0.00)
            
            set x = newX
            set y = newY
            set z = newZ
            call MoveLocation(LOC, newX, newY)
            set terrainZ = GetLocationZ(LOC)
        endmethod
        
        // Position of the previous projectile game state.
        readonly real    prevX
        readonly real    prevY
        readonly real    prevZ
        
        // Target unit and target position.
        public unit      target = null
        public real      targetX
        public real      targetY
        public real      targetZ
        method setTargetPos takes real newX, real newY, real newZ returns nothing
            set targetX = newX
            set targetY = newY
            call MoveLocation(LOC, newX, newY)
            set targetZ = newZ + GetLocationZ(LOC)
        endmethod
        
        // Position when launching the projectile.
        readonly real    launchX
        readonly real    launchY
        readonly real    launchZ
        
        //===================================================================       
        // Informative flight members 
        
        readonly boolean expires  = false
        readonly real    distance = 0.00  // Total distance traveled.
        readonly real    duration = 0.00  // Total flight time.
    
        // Flight limitations
        public   real    maxDuration  = 0.00
        public   real    maxDistance  = 0.00 
        public   real    minFlyHeight = 0.00
        
        // Flight parameters
        public   boolean homing        = false
        public   real    timeScale     = 1.00
        public   real    zOffset       = 0.00
        public   real    pitchTurnRate = 0.00
        public   real    angleTurnRate = 0.00       

        //===================================================================       
        // Others
        
        public   integer lookAt    = Projectile_LOOK_AT_3D
        public   unit    source    = null
        public   real    damage    = 0.00
        public   player  owner     = null
        public   integer data      = 0
        public   real    deathTime = 0.00 // How long the dummy should be hold back before recycling.  
        
        //===================================================================
        // Collision code ( Optional )
        
        public   real    collision = 0.00
        
        //! runtextmacro optional PROJECTILE_COLLISION_CODE()
        
        //===================================================================
        // Projectile special effect
        //     • For multiple effects access "readonly unit dummy" directly.
        
        private effect fx
        private string fxPath = null
        method operator model takes nothing returns string
            return fxPath
        endmethod
        
        method operator model= takes string modelName returns nothing
            if fx != null then
                call DestroyEffect(fx)
                set fx = null
            endif
            if StringLength(modelName) > 0 then
                set fx = AddSpecialEffectTarget(modelName, dummy, "origin")
            endif
            set fxPath = modelName
        endmethod
        
        //===================================================================
        // Projectile scale
        
        private real scaling = 1.00
        method operator scale takes nothing returns real
            return scaling
        endmethod
        
        method operator scale= takes real value returns nothing
            set scaling = value
            call SetUnitScale(dummy, value, 0.00, 0.00)
        endmethod

        //===================================================================
        // Projectile yaw 
        //     • Method operator takes and returns radians!
        
        private real angleXY
        method operator angle takes nothing returns real
            return angleXY// Atan2(velY, velX), GetUnitFacing(dummy)*DEGTORAD 
        endmethod
        
        method operator angle= takes real theta returns nothing
            local real vxy = SquareRoot(velX*velX + velY*velY)
            local real axy = SquareRoot(accX*accX + accY*accY)

            set velX = vxy*Cos(theta)
            set velY = vxy*Sin(theta)
            set accX = axy*Cos(theta)
            set accY = axy*Sin(theta)
            
            set angleXY = ModuloAngle(theta)
            
            if lookAt == Projectile_LOOK_AT_2D or lookAt == Projectile_LOOK_AT_3D then
                call SetUnitFacing(dummy, angleXY*RADTODEG)
            endif
        endmethod
        
        //=======================================================
        // Projectile pitch
        //     • Method operator takes and returns radians!
        
        private real angleZ
        method operator pitch takes nothing returns real
            return angleZ
        endmethod
        
        method operator pitch= takes real phi returns nothing
            local real vel = SquareRoot(velX*velX + velY*velY + velZ*velZ)
            local real acc = SquareRoot(accX*accX + accY*accY + accZ*accZ)
            
            set velX = vel*Cos(angle)*Cos(phi)
            set velY = vel*Sin(angle)*Cos(phi)
            set velZ = vel*Sin(phi)
            set accX = acc*Cos(angle)*Cos(phi)
            set accY = acc*Sin(angle)*Cos(phi)
            set accZ = acc*Sin(phi)
            
            set angleZ = ModuloAngle(phi)    
            if lookAt == Projectile_LOOK_AT_3D then
                call SetUnitAnimationByPitch(dummy, angleZ)
            endif
        endmethod
        
        //===================================================================
        // Projectile speed 
        //     • Method operators take and return per timer interval values.
        //     • Methods take and return per second values.
        
        public real velX     = 0.00
        public real velY     = 0.00
        public real velZ     = 0.00
        public real velXYZ   = 0.00
        public real minSpeed = MIN_PROJECTILE_SPEED
        public real maxSpeed = MAX_PROJECTILE_SPEED
        method operator speed takes nothing returns real
            return SquareRoot(velX*velX + velY*velY + velZ*velZ)
        endmethod

        method operator speed= takes real value returns nothing
            set velX = value*Cos(angleXY)*Cos(angleZ)
            set velY = value*Sin(angleXY)*Cos(angleZ)
            set velZ = value*Sin(angleZ)
            set velXYZ = value
        endmethod
        
        method getSpeed takes nothing returns real
            return speed/Projectile_TIMER_TIMEOUT
        endmethod
        
        method setSpeed takes real value returns nothing
            set speed = value*Projectile_TIMER_TIMEOUT
        endmethod
        
        //===================================================================
        // Projectile acceleration 
        //     • Method operators take and return per timer interval values.
        //     • Methods take and return per second values.
        
        public real accX = 0.00
        public real accY = 0.00
        public real accZ = 0.00
        method operator acceleration takes nothing returns real
            return SquareRoot(accX*accX + accY*accY + accZ*accZ)
        endmethod

        method operator acceleration= takes real value returns nothing
            set accX = value*Cos(angleXY)*Cos(angleZ)
            set accY = value*Sin(angleXY)*Cos(angleZ)
            set accZ = value*Sin(angleZ)
        endmethod
        
        method getAcceleration takes nothing returns real
            return acceleration/TIMER_TIMEOUT_SQUARED
        endmethod
        
        method setAcceleration takes real value returns nothing
            set acceleration = value*TIMER_TIMEOUT_SQUARED
        endmethod
        
        //=======================================================
        // Gravity effect 
        //     • Methods take and return per second squared values.
        
        public real gravity = DEFAULT_GRAVITY
        method getGravity takes nothing returns real
            return -gravity/TIMER_TIMEOUT_SQUARED
        endmethod
        
        method setGravity takes real value returns nothing
            set gravity = -value*TIMER_TIMEOUT_SQUARED
        endmethod

        //=======================================================
        // Projectile arc 
        
        readonly real arc = 0.00
        method setArcBySpeed takes real tX, real tY, real tZ, real arcValue, real newSpeed returns nothing
            local real time 
            
            call setTargetPos(tX, tY, tZ)
            
            // Compute the vector magnitude between projectile and target position.
            set tempX = targetX - x
            set tempY = targetY - y
            set tempZ = targetZ - z - terrainZ
            
            // Enable the correct motion driver and cache the arc argument.
            set arc = arcValue
            set velXYZ = newSpeed
            if arcValue == 0.00 then
                set driver = Projectile_MOTION_DRIVER_NONE
                set speed  = newSpeed
                set angle  = Atan2(tempY, tempX)
                set pitch  = Atan2(tempZ, SquareRoot(tempX*tempX + tempY*tempY))
                return 
            endif
            set driver = Projectile_MOTION_DRIVER_BALLISTIC
            set tempD = SquareRoot(tempX*tempX + tempY*tempY + tempZ*tempZ)
            
            // Compute the total flight time.
            set time = GetFlyTime(newSpeed, SquareRoot(accX*accX + accY*accY), tempD)*Projectile_TIMER_TIMEOUT
            // For uncomputable arguments GetFlyTime returns -1.00
            if time <= 0.00 or tempD == 0.00 then
                return
            endif
            
            // Compute the new velocity components.
            set velX = tempX/tempD*newSpeed - accX*time/2
            set velY = tempY/tempD*newSpeed - accY*time/2
            set velZ = ((tempD*arcValue)/(time/4) + tempZ/time)*Projectile_TIMER_TIMEOUT
            set accZ = 2*(tempZ/time/time*TIMER_TIMEOUT_SQUARED - (velZ*Projectile_TIMER_TIMEOUT)/time)
            // Add an expiration time to the projectile.
            set maxDuration = duration + time
            
            // Compute the rotation and set the correct look at angle.
            set angleZ = Atan2(velZ, SquareRoot(velX*velX + velY*velY))
            set angleXY = Atan2(tempY, tempX)
            if lookAt == Projectile_LOOK_AT_2D then
                call SetUnitFacing(dummy, angleXY*RADTODEG)
            elseif lookAt == Projectile_LOOK_AT_3D then
                call SetUnitFacing(dummy, angleXY*RADTODEG)
                call SetUnitAnimationByPitch(dummy, pitch)
            endif
        endmethod
        
        method setArcByTime takes real tX, real tY, real tZ, real arc, real time returns nothing
            if time <= 0.00 then
            
                debug call DebugWarning(time <= 0.00, "setArcByTime", "time", this, "Time argument must be greater than zero.")
            
                return
            endif
            
            set tempX = tY - x
            set tempY = tX - y
            call MoveLocation(LOC, x, y)
            set tempZ = tZ + GetLocationZ(LOC) - z - terrainZ
            call setArcBySpeed(tX, tY, tZ, arc, SquareRoot(tempX*tempX + tempY*tempY + tempZ*tempZ)/time*Projectile_TIMER_TIMEOUT)
        endmethod

        //=======================================================
        // Projectile parabola

        //=======================================================
        // Projectile bounce and deflect.
        
        method bounce takes nothing returns nothing
            call GetTerrainNormal(x, y, TERRAIN_TILE_RADIUS)
            set tempD = tempX*tempX + tempY*tempY + tempZ*tempZ
            if tempD <= 0.00 then 
                return 
            endif
            set tempD = (velX*tempX + velY*tempY + velZ*tempZ)/tempD
            set velX = velX - 2.00*tempX*tempD
            set velY = velY - 2.00*tempY*tempD
            set velZ = velZ - 2.00*tempZ*tempD
        endmethod
        
        method deflect2D takes real posX, real posY returns nothing
            set angle = 2.00*Atan2(posY - y, posX - x) + PI - angleXY
        endmethod
        
        method deflect3D takes real posX, real posY, real posZ returns nothing
            set tempX = posX - x
            set tempY = posY - y
            call MoveLocation(LOC, posX, posY)
            set tempZ = posZ + GetLocationZ(LOC) - z - terrainZ
            set angle = 2.00*Atan2(tempY, tempX) + PI - angleXY
            set pitch = 2.00*Atan2(tempZ, SquareRoot(tempX*tempX + tempY*tempY)) + PI - angleZ
        endmethod
        
        //=======================================================
        // Motion drivers
        //     • Available motion drivers can be read out of the globals!
    
        public integer driver = Projectile_MOTION_DRIVER_NONE
        
        private method driverTypeBallistic takes nothing returns nothing
            local real time
            
            set velXYZ = velXYZ + SquareRoot(accX*accX + accY*accY)
        
            if target != null then
                if GetUnitTypeId(target) == 0 then
                    set target = null
                elseif homing then
                    
                    call setTargetPos(GetUnitX(target), GetUnitY(target), GetUnitFlyHeight(target) + zOffset)
                    set tempX = targetX - x
                    set tempY = targetY - y
                    set tempZ = targetZ - z - terrainZ
                    set tempD = SquareRoot(tempX*tempX + tempY*tempY + tempZ*tempZ)            
                    // Compute the total flight time.
                    set time = GetFlyTime(velXYZ, SquareRoot(accX*accX + accY*accY), tempD)*Projectile_TIMER_TIMEOUT
                    if time <= 0.00 or tempD == 0.00 then
                        return
                    endif
                    set maxDuration = duration + time
                    
                    set velX = tempX/tempD*velXYZ - accX*time/2
                    set velY = tempY/tempD*velXYZ - accY*time/2
                    set accZ = 2*(tempZ/time/time*TIMER_TIMEOUT_SQUARED - (velZ*Projectile_TIMER_TIMEOUT)/time)
                    set angleXY = Atan2(tempY, tempX)
                    if lookAt == Projectile_LOOK_AT_2D then
                        call SetUnitFacing(dummy, angleXY*RADTODEG)
                    endif
                endif
            endif
            set angleZ = Atan2(velZ, SquareRoot(velX*velX + velY*velY))
            if lookAt == Projectile_LOOK_AT_3D then
                call SetUnitAnimationByPitch(dummy, angleZ)
            endif
        endmethod
        
        private method driverTypeGuidance takes nothing returns nothing
            local real tA
        
            if target != null then
                if GetUnitTypeId(target) == 0 then
                    set target = null
                else
                    set tempZ = GetUnitFlyHeight(target) + zOffset
                    set targetX = GetUnitX(target)
                    set targetY = GetUnitY(target)
                    call MoveLocation(LOC, targetX, targetY)
                    if minFlyHeight > tempZ then
                        set targetZ = GetLocationZ(LOC) + minFlyHeight 
                    else
                        set targetZ = GetLocationZ(LOC) + tempZ 
                    endif
                endif
            endif
            
            // Compute the vector difference.
            set tempX = targetX - x
            set tempY = targetY - y
            set tempZ = targetZ - z - terrainZ
            
            // Evaluate if the projectile is within target point range.
            set expires = tempX*tempX + tempY*tempY + tempZ*tempZ < velX*velX + velY*velY + velZ*velZ + (accX*accX + accY*accY + accZ*accZ)/2
            
            if angleTurnRate == 0.00 then
                set angleXY = Atan2(tempY, tempX)
            else
                set tA = Atan2(tempY, tempX)
                set expires = expires and Acos(Cos(angleXY - tA)) <= angleTurnRate
                set angle = angleXY + GetAngleRotation(angleXY, tA, angleTurnRate)
            endif
            
            if pitchTurnRate == 0.00 then
                set pitch = Atan2(tempZ, SquareRoot(tempX*tempX + tempY*tempY))
            else
                set tA = Atan2(tempZ, SquareRoot(tempX*tempX + tempY*tempY))
                set expires = expires and Acos(Cos(angleZ - tA)) <= pitchTurnRate
                set pitch = angleZ + GetAngleRotation(angleZ, tA, pitchTurnRate)
            endif
            
            // Adjust x, y, z and terrainZ that the projectile ends directly on the target.
            if expires then
                set x = targetX - velX - accX/2
                set y = targetY - velY - accY/2
                call MoveLocation(LOC, targetX, targetY)
                set z = targetZ - GetLocationZ(LOC) - velZ - accZ/2 - gravity/2 
                call MoveLocation(LOC, x, y)
                set terrainZ = GetLocationZ(LOC)
            endif
        endmethod
        
        private method driverTypeParabola takes nothing returns nothing
            // No content yet
        endmethod
        
        //=======================================================
        // Periodic projectile motion
        
        private static method motion takes nothing returns boolean
            local thistype this = loopIndex
            local integer  iter = 0
            
            loop
                exitwhen this == 0 or iter == MAX_ITERATIONS_PER_LOOP
                set loopIndex = next

                // Cache the position of the current projectile game state.
                if updatePos then
                    set x = GetUnitX(dummy)
                    set y = GetUnitY(dummy)
                    set z = GetUnitFlyHeight(dummy)
                    call MoveLocation(LOC, x, y)
                    set terrainZ = GetLocationZ(LOC)
                endif
                set prevX = x
                set prevY = y
                set prevZ = z + terrainZ
                
                // Run specified driver types.
                if driver == Projectile_MOTION_DRIVER_GUIDANCE then
                    call driverTypeGuidance()
                elseif driver == Projectile_MOTION_DRIVER_BALLISTIC then
                    call driverTypeBallistic()
                elseif driver == Projectile_MOTION_DRIVER_PARABOLA then
                    call driverTypeParabola()
                else
                    set expires = false
                endif
                
                // Compute projectile's limits.
                set distance = distance + SquareRoot(velX*velX + velY*velY + velZ*velZ)
                set duration = duration + Projectile_TIMER_TIMEOUT
                if maxDistance > 0.00 and distance >= maxDistance then
                    set expires = true
                elseif maxDuration > 0.00 and duration >= maxDuration then
                    set expires = true
                endif
                
                // Compute the next projectile position.
                set tempX = x + velX + accX/2
                set tempY = y + velY + accY/2
                if tempX > maxX or tempX < minX or tempY > maxY or tempY < minY then
                    set tempX = GetMapSafeX(tempX)
                    set tempY = GetMapSafeY(tempY)
                endif
                call MoveLocation(LOC, tempX, tempY)
                set tempD = GetLocationZ(LOC)
                set tempZ = z + velZ + accZ/2 + gravity/2 + terrainZ - tempD
                set terrainZ = tempD
                
                // Add the gravity effect to the z velocity.
                if gravity != 0.00 and tempZ > minFlyHeight then
                    set velZ = velZ + gravity
                    if driver == Projectile_MOTION_DRIVER_NONE then
                        set angleZ = Atan2(velZ, SquareRoot(velX*velX + velY*velY))
                        if lookAt == Projectile_LOOK_AT_3D then
                            call SetUnitAnimationByPitch(dummy, angleZ)
                        endif
                    endif
                endif
                
                // Cache the current projectile position for the user.
                set x = tempX
                set y = tempY
                set z = tempZ
                
                if tempZ < minFlyHeight then
                    set tempZ = minFlyHeight
                endif
                
                // Move the projectile.
                call SetUnitX(dummy, tempX)
                call SetUnitY(dummy, tempY)
                call SetUnitFlyHeight(dummy, tempZ, 0.00)
                
                // Update the velocity vector components.
                set velX = velX + accX
                set velY = velY + accY
                set velZ = velZ + accZ
                
                // Check for speed limits.
                set tempD = speed
                if tempD > maxSpeed then
                    set speed = maxSpeed
                elseif tempD < minSpeed then
                    set speed = minSpeed
                endif
                
                set iter = iter + 1
                set this = loopIndex
            endloop
            
            return this == 0
        endmethod

        //=======================================================
        // Periodic event handler
        
        private static method onPeriodic takes nothing returns nothing
            set loopIndex = thistype(0).next
            loop
                exitwhen TriggerEvaluate(MOTION)
            endloop
            
            if activeStructs > 0 then
                call RunStructActions()
                
                // Deallocated queued nodes.
                set loopIndex = thistype(0).destroying
                set thistype(0).destroying = 0
                loop
                    exitwhen loopIndex == 0
                    call StructRemoveProjectile(loopIndex.typeId, loopIndex)
                    call loopIndex.deallocate()
                    set loopIndex = loopIndex.destroying
                endloop
            endif
        endmethod
        
        
        //=======================================================
        // Launcher
        //     • Launches a projectile instance from a specific struct.
        
        readonly boolean launched = false
        method launch takes integer id returns thistype
            if launched or not exists then
            
                debug call DebugWarning(not exists, "launch", "exists",   this, "Attempt to launch a null projectile!")
                debug call DebugWarning(launched,   "launch", "launched", this, "Attempt to double launch a projectile!")
            
                return this
            endif
            
            set launched = true
            set typeId   = id
            set launchX  = x
            set launchY  = y
            set launchZ  = z
            
            if driver != Projectile_MOTION_DRIVER_BALLISTIC and driver != Projectile_MOTION_DRIVER_PARABOLA then
                set angle = angleXY
                set pitch = angleZ
            endif
                
            call enqueue()
            if prev == 0 then
                call TimerStart(TIMER, Projectile_TIMER_TIMEOUT, true, function thistype.onPeriodic)
            endif
            
            if IsProjectileStructId(id) then
                call StructAddProjectile(id, this)
            endif
            
            return this// For fancy struct syntax.
        endmethod
        
        //=======================================================
        // Creators and destructor
    
        static method createEx takes unit projectile, real angle, real pitch returns thistype
            local thistype this = allocate()
            
            set counter     = counter + 1           
            set dummy       = projectile
            set angleXY     = ModuloAngle(angle)
            set angleZ      = ModuloAngle(pitch)
            set x           = GetUnitX(projectile)
            set y           = GetUnitY(projectile)
            set z           = GetUnitFlyHeight(projectile)
            set updatePos   = IsUnitPaused(projectile)
            
            call UnitAddAbilityToFly(projectile)
            call SetUnitFacing(projectile, angle*RADTODEG)
            call SetUnitAnimationByPitch(projectile, pitch*RADTODEG)
            
            // Create a refernce to this dummy unit.
            call SaveInteger(HASH, KEY_UNIT_TO_PROJECTILE, GetHandleId(dummy), this)
            call SaveUnitHandle(HASH, this, GetHandleId(dummy), dummy)
            
            return this
        endmethod
    
        static method create takes real x, real y, real z, real angle, real pitch returns thistype
            return createEx(GetDummyUnit(x, y, z, angle*RADTODEG), angle, pitch)
        endmethod
    
        method destroy takes nothing returns nothing
            if not exists then
                return
            endif
            
            call RemoveSavedInteger(HASH, KEY_UNIT_TO_PROJECTILE, GetHandleId(dummy))
            call FlushChildHashtable(HASH, this)
            
            // Recycle the dummy unit.
            if GetUnitTypeId(dummy) == Projectile_DUMMY_UNIT_TYPE_ID then
                if deathTime > 0.00 then
                    call RecycleDummyTimed(dummy, deathTime)
                else
                    static if LIBRARY_MissileRecycler then
                        call RecycleMissile(dummy)
                    elseif LIBRARY_DummyRecycler then
                        call RecycleDummy(dummy)
                    elseif LIBRARY_Dummy and Dummy.create.exists then
                        call Dummy[dummy].destroy()
                    elseif LIBRARY_xedummy and xedummy.release.exists then
                        call xedummy.release(dummy)
                    else
                        call RemoveUnit(dummy)
                    endif
                endif
            endif
            
            if launched then
                if IsProjectileStructId(typeId) then
                    set destroying = thistype(0).destroying
                    set thistype(0).destroying = this
                else
                    call deallocate()
                endif
                call remove()
            endif

            set allocated = false
            set launched  = false
            set model     = null
            set dummy     = null
            set source    = null
            set target    = null
            set owner     = null

            set counter   = counter - 1
            if thistype(0).next == 0 then
                call PauseTimer(TIMER)
            endif
        endmethod

        private static method onInit takes nothing returns nothing
            debug local unit dummy = CreateUnit(Projectile_NEUTRAL_PLAYER, Projectile_DUMMY_UNIT_TYPE_ID, 0., 0., 0.)
            debug call DebugError(GetUnitTypeId(dummy) != Projectile_DUMMY_UNIT_TYPE_ID, "onInit", "unitTypeId", 0, "Global setup for public integer DUMMY_UNIT_TYPE_ID is incorrect! This map currently can't use Projectile!")
            debug call RemoveUnit(dummy)
            debug set dummy = null
        
            //! runtextmacro optional PROJECTILE_COLLISION_ON_INIT()
        
            call TriggerAddCondition(MOTION, Condition(function thistype.motion))
            call InitProjectileGlobals()
        endmethod
    
    endstruct
    
//=============================================
// Public modules. 
//=============================================
    
module ProjectileDebug 
endmodule
    
module ProjectileLaunch 
        static method launch takes Projectile this returns Projectile
            return this.launch(thistype.typeid)
        endmethod
endmodule
    
module ProjectileStruct
        implement ProjectileDebug
        implement ProjectileLaunch
        
        private static method terminateM takes Projectile this returns nothing
            static if thistype.onRemove.exists then
                if this.exists and thistype.onRemove(this) then
                    call this.destroy()
                endif
            else
                call this.destroy() 
            endif
        endmethod

    static if thistype.onProjectile.exists then
        private static method onProjectileM takes nothing returns nothing
            local Projectile projectile = Projectile[GetEnumUnit()]
            local integer id = GetHandleId(projectile.dummy)
            
            if loopIndex.exists and projectile.exists then
                call SaveUnitHandle(HASH, loopIndex, id, projectile.dummy) 
                if thistype.onProjectile(loopIndex, projectile) then
                    call terminateM(loopIndex)
                endif
            endif
        endmethod
    endif
    
    static if thistype.onItem.exists then
        private static method onItemM takes nothing returns nothing
            if loopIndex.exists then
                call SaveItemHandle(HASH, loopIndex, GetHandleId(GetEnumItem()), GetEnumItem()) 
                if thistype.onItem(loopIndex, GetEnumItem()) then
                    call terminateM(loopIndex)
                endif
            endif
        endmethod
    endif
        
    static if thistype.onDestructable.exists then
        private static method onDestructableM takes nothing returns nothing
            if loopIndex.exists then
                call SaveDestructableHandle(HASH, loopIndex, GetHandleId(GetEnumDestructable()), GetEnumDestructable()) 
                if thistype.onDestructable(loopIndex, GetEnumDestructable()) then
                    call terminateM(loopIndex)
                endif
            endif
        endmethod
    endif
    
        private static method iterateM takes nothing returns boolean
            local Projectile this = ProjectileType[thistype.typeid].first
            local Projectile next
static if LIBRARY_ProjectileCollision and thistype.onCollide.exists then
            local unit u
endif
            loop
                exitwhen this == 0 
                set next = ProjectileType(this).next
                set loopIndex = this
            
                static if thistype.onPeriodic.exists then
                    if this.exists then
                        static if LIBRARY_ProjectileCollision then
                            call this.cacheUnitPos()
                        endif
                        if thistype.onPeriodic(this) then
                            call thistype.terminateM(this)
                        endif
                    endif
                endif

static if LIBRARY_ProjectileCollision then
                static if not thistype.onCollide.exists and not thistype.onItem.exists and not thistype.onDestructable.exists then 
                else
                    call this.prepareCollision()
                endif
                
                static if thistype.onCollide.exists then
                    if this.exists and this.collision > 0.00 then
                        call GroupEnumUnitsInRect(FILTER_GROUP, ENUM_RECT, Projectile.unitFilter)
                        loop
                            set u = FirstOfGroup(FILTER_GROUP)
                            exitwhen u == null
                            call GroupRemoveUnit(FILTER_GROUP, u)
                            
                            call SaveUnitHandle(HASH, this, GetHandleId(u), u)
                            if thistype.onCollide(this, u) then
                                call thistype.terminateM(this)
                                set u = null
                                exitwhen true
                            endif
                        endloop
                    endif
                endif

                static if thistype.onDestructable.exists then
                    if this.exists and this.collision > 0.00 then
                        call EnumDestructablesInRect(ENUM_RECT, Projectile.destFilter, function thistype.onDestructableM)
                    endif
                endif
                
                static if thistype.onItem.exists then
                    if this.exists and this.collision > 0.00 then
                        call EnumItemsInRect(ENUM_RECT, Projectile.itemFilter, function thistype.onItemM)
                    endif
                endif
                
                static if thistype.onProjectile.exists then
                    if this.exists and this.collision > 0.00 then
                        call this.enumForOnProjectile(function thistype.onProjectileM)
                    endif
                endif
endif                
                if this.exists and this.expires then
                    static if thistype.onFinish.exists then
                        if thistype.onFinish(this) then
                            call thistype.terminateM(this)
                        endif
                    else
                        call thistype.terminateM(this)
                    endif
                endif
                
                static if thistype.onTerrain.exists then
                    if this.exists and this.z <= this.minFlyHeight then
                        call GetTerrainNormal(this.x, this.y, TERRAIN_TILE_RADIUS)
                        // Compute the dot product of the terrain vector and projectile velocity.
                        if tempX*this.velX + tempY*this.velY + tempZ*this.velZ < 0.00 and thistype.onTerrain(this) then
                            call thistype.terminateM(this)
                        endif
                    endif
                endif
                
                set this = next
            endloop
            
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            call CreateProjectileStruct(thistype.typeid, function thistype.iterateM)
        endmethod
    endmodule
    
    private module PROJECTILE_TYPE_DATA_STRUCTURE
        private static thistype listCount = 0
        
        debug private boolean isNode
        debug private boolean isList
        
        private thistype _list
        method operator list takes nothing returns thistype
            debug call DebugError(this == 0,  "list", "thistype", this, "Attempted To Read Null Node.")
            debug call DebugError(not isNode, "list", "thistype", this, "Attempted To Read Invalid Node.")

            return _list
        endmethod
        
        private thistype _next
        method operator next takes nothing returns Projectile
            debug call DebugError(this == 0,  "next", "thistype", this, "Attempted To Go Out Of Bounds.")
            debug call DebugError(not isNode, "next", "thistype", this, "Attempted To Read Invalid Node.")

            return _next
        endmethod
        
        private thistype _prev
        method operator prev takes nothing returns Projectile
            debug call DebugError(this == 0,  "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
            debug call DebugError(not isNode, "prev", "thistype", this, "Attempted To Read Invalid Node.")
            
            return _prev
        endmethod
        
        private thistype _first
        method operator first takes nothing returns Projectile
            debug call DebugError(this == 0,  "first", "thistype", this, "Attempted To Read Null List.")
            debug call DebugError(not isList, "first", "thistype", this, "Attempted To Read Invalid List.")

            return _first
        endmethod
        
        private thistype _last
        method operator last takes nothing returns Projectile
            debug call DebugError(this == 0,  "last", "thistype", this, "Attempted To Read Null List.")
            debug call DebugError(not isList, "last", "thistype", this, "Attempted To Read Invalid List.")

            return _last
        endmethod
        
        static method allocateList takes nothing returns thistype
            local thistype this = thistype(0)._first
            
            if this == 0 then
            
                debug call DebugError(listCount == 8191, "allocateList", "thistype", 0, "Overflow.")
                
                set this = listCount + 1
                set listCount = this
            else
                set thistype(0)._first = _first
            endif
            
            debug set isList = true
            
            set _first = 0
            
            return this
        endmethod
        
        method push takes thistype node returns nothing
            debug call DebugError(this == 0,  "push", "thistype", this, "Attempted to push on to null list.")
            debug call DebugError(not isList, "push", "thistype", this, "Attempted to push on to invalid list.")
            debug set node.isNode = true
            
            set node._list = this
        
            if _first == 0 then
                set _first = node
                set _last = node
                set node._next = 0
            else
                set _first._prev = node
                set node._next = _first
                set _first = node
            endif
            
            set node._prev = 0
        endmethod
      
        method remove takes nothing returns nothing
            local thistype node = this
            set this = node._list
            
            debug call DebugError(node == 0,       "remove", "thistype", this, "Attempted to remove null node.")
            debug call DebugError(not node.isNode, "remove", "thistype", this, "Attempted to remove invalid node (" + I2S(node) + ").")
            debug set node.isNode = false
            
            set node._list = 0
        
            if 0 == node._prev then
                set _first = node._next
            else
                set node._prev._next = node._next
            endif
            if 0 == node._next then
                set _last = node._prev
            else
                set node._next._prev = node._prev
            endif
            
            set node._next = thistype(0)._next
            set thistype(0)._next = node
        endmethod
    endmodule
endlibrary



~~~~~~~~~~~~~~~~~~~~~~~~~~
Flow_block.png
Requirements
  • Projectile can draw back on the following optional requirements:
    ____
    • Bribe's MissileRecycler to recycle dummies. ( Highly recommended )
    • Flux's DummyRecycler to recycle dummies. ( Highly recommended )
    • Nestharus's Dummy to recycle dummies. ( Suffers unsolved bugs, useful )
    • Vexorian's Xe dummy to recycle dummies ( Only use if already have xe, useful )
    • Nestharus's ErrorMessage for best debug results.


~~~~~~~~~~~~~~~~~~~~~~~~~~
Scenario.png
Changelog
  • -



~~~~~~~~~~~~~~~~~~~~~~~~~~
Forward.png
Demo Code
  • soon


~~~~~~~~~~~~~~~~~~~~~~~~~~
Help.png
Tutorial
  • soon
 
Last edited:
Okay :ogre_datass:

Are Projectile Types include homing, etc ....

JASS:
// The following constants are available drivers for the motion phase.
        //     • A projectile can only use one driver during the same time.
        
        //     Projectile_MOTION_DRIVER_NONE ( Default motion driver )
        //         • The projectile travels an linear path in a specified direction.   
        // 
        public key MOTION_DRIVER_NONE          
        
        //    Projectile_MOTION_DRIVER_GUIDANCE
        //         • The projectiles moves after a target unit or target point in 3D space.
        //
        public key MOTION_DRIVER_GUIDANCE
        
        //    Projectile_MOTION_DRIVER_PARABOLA
        //         • Does so far nothing
        //
        public key MOTION_DRIVER_PARABOLA
        
        //    Projectile_MOTION_DRIVER_BALLISTIC
        //         • Fires a projectile through 3D space with a sepcified arc.
        //         • This driver is perfect to simulate classic unit attacks.
        //
        public key MOTION_DRIVER_BALLISTIC
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Are there more features compared to that system : [vJass] (system) Missile ?
In the end yes. It looks similar but is very different. This resource is much more advanced than Missile
However it's not working yet. There are some unsolved bugs.

Edit:

I'm currently not working on this system, actually I think it already works.
Also I saw that there is already a library called Projectile, so maybe this one should be called library Mover.
 
Last edited:
There needs to be demo code, or API list for how to use the system.

@everyone, BPower is not really active at the moment, and if you want to help, I guess you also could write a good demo or so, to help, and we can have a look on it together.

Though, the description currently is:

For your projectile needs

And the description from [vJASS] - Missile is:

Creating custom projectiles in Warcraft III.

It's for an outstanding like me hard to understand if we really need both. I've read posts that they can co-exist, though I've not analysed code to see it.
I guess that Missile has also focus also on special curves, while Projectile's focus is on targets.
We should have a good definition of what is exactly the difference between a missile and a projectile, or if there even is a basic difference to validate a seperation.
Maybe there also can be a basic missile system, and just one addon, or extension. I just have a bit of an ungood feeling at the moment.

We might also have a good talk in chat about it again, once you find the time! : )
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Hi everyone, yes I'm not very active a the moment so some of my resources rust away.

This one is also such a resource. I wrote Projectile because I was not satisfied with a few
aspects of my "Missile" library and making required changes would be very radical, impossible to maintain the current API.
Finally Projectile has a better data structure and is easier to modify or extend by extra features than the Missile library.

I will make an update ( quite big one ) on Projectile including a proper demo map and docs.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
I've been using this on my FPS thing. All's working well except on collision part. I don't know how to explain it but there is certainly something wrong there (the vid tries hard to conceal the bug :p, but you can see I missed an easy shot that should've hit a footman). I'm sure it's related to unit's height and missile z checking. Hope you can fix it soon. For now I have to modify the library as necessarily to fit it to the FPS system. I also recommend adding some sort of raycasting for collision check, in case the user uses extreme missile speed.

I can see why this deserves a spot next to the Missile library. Nice work :)

EDIT:
Oh, why this one doesn't provide "onPeriodic" interface? Your missile library provides one iirc. It's very useful imo.

EDIT 2:
Oh, almost forgot, missiles are never disposed when reaching map edges.
 
Last edited:
Top