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

Projectile

Status
Not open for further replies.
Level 19
Joined
Mar 18, 2012
Messages
1,716
I started to make a new projectile system.
I was thinking of going with a gravity effect.

Some input would be very nice.

This is what I've got for projectile motion so far.

JASS:
library Missile /* Version 4.0 by BPower
*************************************************************************************
*
*   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 Missile into to your map. 
*       • Make sure you have a dummy unit, using Vexorian's "dummy.mdx".
*       • Read over the user settings below.
*
*************************************************************************************
*
*   */ requires /*
*
*       • Missile requires nothing.
*
*************************************************************************************
*
*   Optional requirements:
*   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*       • Reduce the generated code.
*       • Add useful debug options.
*       • Decrease the overall overhead.
*       • Optimize handle management.  
*
*   It's recommended to use one per block listed below.
*
*   a) For best debug results: ( Useful )
*       */ optional ErrorMessage     /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage  
*
*   b) Dumy 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
*
*   c) Accuracy: 
*       */ optional GetUnitCollision /* github.com/nestharus/JASS/tree/master/jass/Systems/GetUnitCollision
*
*************************************************************************************/

// User settings:
// ==============
     
    globals
     
        // Set the data value of the dummy unit used for missiles.
        //
        public constant integer DUMMY_UNIT_TYPE_ID = 'dumi'
        
        // Set the owner of all missiles. 
        //     • Preferably a neutral player in your map.
        //
        public constant player  NEUTRAL_PLAYER     = Player(14)
        
        // Set the timer timeout for missile 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 Missile's performance,
        //       as smaller values enumerate less widgtes per loop per missile.
        //
        public constant real    MAX_COLLISION_SIZE = 197.00
    
        // The following constants are available drivers for the motion phase.
        //     • A missile can only use one driver during the same time.
        //
        //     Missile_MOTION_DRIVER_NONE ( Default motion driver )
        //         • The missile travels an linear path in a specified direction.   
        // 
        public key MOTION_DRIVER_NONE          
        //
        //    Missile_MOTION_DRIVER_GUIDANCE
        //         • The missiles moves after a target unit or target point in 3D space.
        //
        public key MOTION_DRIVER_GUIDANCE
        //
        //    Missile_MOTION_DRIVER_PARABOLA
        //         • Does so far nothing
        //
        public key MOTION_DRIVER_PARABOLA
        //
        //    Missile_MOTION_DRIVER_BALLISTIC
        //         • Fires a missile 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 missile look at options.
        //
        //     Missile_LOOK_AT_3D ( Default look at )
        //         • The missiles always points towards the target in 3D space. 
        //
        public key LOOK_AT_3D
        //
        //     Missile_LOOK_AT_2D
        //         • The missile only adjusts it's facing angle towards the target. 
        //
        public key LOOK_AT_2D
        //
        //    Missile_LOOK_AT_OFF
        //         • The missile's angle and pitch rotations
        //           remain untouched during the motion phase.
        //
        public key LOOK_AT_OFF
    endglobals
     
//=============================================================================
// Functions, constants and variables used by Missile. 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 missile motion
        private constant real          MIN_MISSILE_SPEED       = 0.00
        private constant real          MAX_MISSILE_SPEED       = 999999.00
        private constant real          DEFAULT_GRAVITY         = 0.00
        // Others
        private constant real          TIMER_TIMEOUT_SQUARED   = Missile_TIMER_TIMEOUT*Missile_TIMER_TIMEOUT     
        private constant integer       MAX_ITERATIONS_PER_LOOP = 150
    
        // 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         MISSILE_TIMER           = CreateTimer()
        private constant trigger       MISSILE_ACTION          = CreateTrigger()
        private constant trigger       MISSILE_MOTION          = CreateTrigger()
        
        // Data storage constants
        private constant hashtable     HASH                    = InitHashtable()
        
        // Collision constants
        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_PATHING_DIAMETER   = 32.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 Missile                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, "Missile", 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, "Missile", functionName, objectName, objectInstance, description)
        endif
    endfunction
    
//***************************************************************************
//*
//*  Terrain Utility Functions
//*
//***************************************************************************
    
    private 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
        set tempX = (GetTerrainZ(x - radius, y) - GetTerrainZ(x + radius, y))*radius*2
        set tempX = (GetTerrainZ(x, y - radius) - GetTerrainZ(x, y + radius))*radius*2
        set tempZ = 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) != Missile_DUMMY_UNIT_TYPE_ID then
            return
        endif
        
        set index = R2I(90.5 + 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_MissileRecycler then
            return GetRecycledMissile(x, y, z, face) // Inlines.
        elseif LIBRARY_DummyRecycler then
            set tempUnit = GetRecycedUnit(x, y, false, face)
        elseif LIBRARY_xedummy and xedummy.new.exists then
            set tempUnit = xedummy.new(Missile_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(Missile_NEUTRAL_PLAYER, Missile_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.)
        return tempUnit
    endfunction
    
    // An avarage finalizer should be able to inline this function.
    private constant function GetUnitPathing takes unit whichUnit returns real
        static if LIBRARY_GetUnitCollision then
            return GetUnitCollision(whichUnit)
        else
            return UNIT_PATHING_DIAMETER
        endif
    endfunction
    
//***************************************************************************
//*
//*  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 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. 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 InitMissileGlobals takes nothing returns nothing
        local rect map = GetWorldBounds()
            
        set maxX = GetRectMaxX(map) - Missile_MAX_COLLISION_SIZE
        set maxY = GetRectMaxY(map) - Missile_MAX_COLLISION_SIZE
        set minX = GetRectMinX(map) + Missile_MAX_COLLISION_SIZE
        set minY = GetRectMinY(map) + Missile_MAX_COLLISION_SIZE
        
        call RemoveRect(map)
        set map = null
    endfunction
    
//***************************************************************************
//*
//*  Structured Missiles
//*
//***************************************************************************
        
    // Returns only true for ids using the MissileStruct module.
    private function IsMissileStructId takes integer id returns boolean
        return id < 8192 and collection[id] != 0
    endfunction
        
    // Directive to a module which I placed at bottom of the library.
    private keyword MISSILE_TYPE_DATA_STRUCTURE
    
    struct MissileType extends array      
        
        // Implements a linked list data structure
        implement MISSILE_TYPE_DATA_STRUCTURE
    
        static method operator [] takes integer id returns thistype
            debug call DebugError(not IsMissileStructId(id), "MissileType[]", "thistype", id, "Attempt to access invalid list!")
            
            return collection[id]
        endmethod
    endstruct 
    
    private function StructAddMissile takes integer structId, integer missile returns nothing
        call MissileType[structId].push(missile)
        
        if instances[structId] == 0 or recycling[structId] then
            if recycling[structId] then
                set disabled[structId] = false
            else
                set condition[structId] = TriggerAddCondition(MISSILE_ACTION, expression[structId])
                set activeStructs = activeStructs + 1
            endif
        endif
        set instances[structId] = instances[structId] + 1
    endfunction
    
    private function StructRemoveMissile takes integer structId, integer missile returns nothing
        call MissileType(missile).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(MISSILE_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(MISSILE_ACTION)
        endif
    endfunction
    
    private function CreateMissileStruct takes integer id, code func returns nothing
        set collection[id] = MissileType.allocateList()
        set expression[id] = Condition(func)
    endfunction
    
//***************************************************************************
//*
//*  Core Missile Code
//*
//***************************************************************************
    
    struct Missile 
    
        //===================================================================
        // Safety
        
        readonly boolean allocated = true
        method operator exists takes nothing returns boolean
            return allocated
        endmethod
    
        //===================================================================
        // Static unique list holding all flying missiles.
    
        readonly thistype next
        readonly thistype prev
        static constant method operator first takes nothing returns thistype
            return thistype(0).next
        endmethod
        // What a disgrace that this actually compiles.
        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 missile instance
        
        static method operator [] takes unit missile returns thistype
            return LoadInteger(HASH, 0, GetHandleId(missile))
        endmethod

        //===================================================================
        // Missile unit and typeid
        
        readonly unit    dummy  
        readonly integer typeId = 0
        
        //===================================================================
        // Position members
        
        // Position of the current missile game state.
        readonly real    x
        readonly real    y
        readonly real    z
        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.)
            
            set x = newX
            set y = newY
            set z = newZ
        endmethod
        
        // Position of the previous missile 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
        private method setTargetPos takes real x, real y, real z returns nothing
            set targetX = x
            set targetY = y
            set targetZ = z + GetTerrainZ(x, y)
        endmethod
        
        // Position when launching the missile.
        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   real    timeScale     = 1.00
        public   real    zOffset       = 0.00
        public   boolean homing        = false
        public   real    pitchTurnRate = 0.00
        public   real    angleTurnRate = 0.00       

        //===================================================================       
        // Others
        
        public   integer lookAt    = Missile_LOOK_AT_3D
        public   real    collision = 0.00
        public   unit    source    = null
        public   real    damage    = 0.00
        public   player  owner     = null
        public   integer data      = 0
        
        //===================================================================
        // Collision code ( Optional )
        
        //! runtextmacro optional MISSILE_COLLISION_CODE()
        
        //===================================================================
        // Missile special effect
        //     • For multiple effects access "readonly unit dummy" directily.
        
        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
        
        //===================================================================
        // Missile 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

        //===================================================================
        // Missile yaw 
        //     • Method operator takes and returns radians!
        
        private real angleXY
        method operator angle takes nothing returns real
            return angleXY 
        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 == Missile_LOOK_AT_2D or lookAt == Missile_LOOK_AT_3D then
                call SetUnitFacing(dummy, angleXY*RADTODEG)
            endif
        endmethod
        
        //=======================================================
        // Missile 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 theta 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(theta)
            set velY = vel*Sin(angle)*Cos(theta)
            set velZ = vel*Sin(theta)
            set accX = acc*Cos(angle)*Cos(theta)
            set accY = acc*Sin(angle)*Cos(theta)
            set accZ = acc*Sin(theta)
            
            set angleZ = ModuloAngle(theta)    
            if lookAt == Missile_LOOK_AT_3D then
                call SetUnitAnimationByPitch(dummy, angleZ)
            endif
        endmethod
        
        //===================================================================
        // Missile 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 minSpeed = MIN_MISSILE_SPEED
        public real maxSpeed = MAX_MISSILE_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)
        endmethod
        
        method getSpeed takes nothing returns real
            return speed/Missile_TIMER_TIMEOUT
        endmethod
        
        method setSpeed takes real value returns nothing
            set speed = value*Missile_TIMER_TIMEOUT
        endmethod
        
        //===================================================================
        // Missile 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 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

        //=======================================================
        // Missile arc 
        
        readonly real arc      = 0.00
        readonly real arcSpeed = 0.00
        method setArcBySpeed takes real tX, real tY, real tZ, real arcValue, real velocity returns nothing
            local real time 
            
            call setTargetPos(tX, tY, tZ)
            
            // Compute the vector magnitude between missile and target position.
            set tempX = targetX - x
            set tempY = targetY - y
            set tempZ = targetZ - z - GetTerrainZ(x, y)
            
            // Enable the correct motion driver and cache the arc argument.
            set arc = arcValue
            set arcSpeed = velocity
            if arcValue == 0.00 then
                set driver = Missile_MOTION_DRIVER_NONE
                set speed  = velocity
                set angle  = Atan2(tempY, tempX)
                set pitch  = Atan2(tempZ, SquareRoot(tempX*tempX + tempY*tempY))
                return 
            endif
            set driver = Missile_MOTION_DRIVER_BALLISTIC
            set tempD = SquareRoot(tempX*tempX + tempY*tempY + tempZ*tempZ)
            
            // Compute the total flight time.
            set time = GetFlyTime(velocity, SquareRoot(accX*accX + accY*accY), tempD)*Missile_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*velocity - accX*time/2
            set velY = tempY/tempD*velocity - accY*time/2
            set velZ = ((tempD*arcValue)/(time/4) + tempZ/time)*Missile_TIMER_TIMEOUT
            set accZ = 2*(tempZ/time/time*TIMER_TIMEOUT_SQUARED - (velZ*Missile_TIMER_TIMEOUT)/time)
            // Add an expiration time to the missile.
            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 == Missile_LOOK_AT_2D then
                call SetUnitFacing(dummy, angleXY*RADTODEG)
            elseif lookAt == Missile_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
            set tempZ = tZ + GetTerrainZ(tX, tY) - z - GetTerrainZ(x, y)
            call setArcBySpeed(tX, tY, tZ, arc, SquareRoot(tempX*tempX + tempY*tempY + tempZ*tempZ)/time*Missile_TIMER_TIMEOUT)
        endmethod
        
        //=======================================================
        // Motion drivers
        //     • Available motion drivers can be read out of the globals!
    
        public integer driver = Missile_MOTION_DRIVER_NONE
        
        private method driverTypeBallistic takes nothing returns nothing
            local real time
        
            set arcSpeed = arcSpeed + 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 - GetTerrainZ(x, y)
                    set tempD = SquareRoot(tempX*tempX + tempY*tempY + tempZ*tempZ)            
                    // Compute the total flight time.
                    set time = GetFlyTime(arcSpeed, SquareRoot(accX*accX + accY*accY), tempD)*Missile_TIMER_TIMEOUT
                    if time <= 0.00 or tempD == 0. then
                        return
                    endif
                    set maxDuration = duration + time
                    
                    set velX = tempX/tempD*arcSpeed - accX*time/2
                    set velY = tempY/tempD*arcSpeed - accY*time/2
                    set accZ = 2.00*(tempZ/time/time*TIMER_TIMEOUT_SQUARED - (velZ*Missile_TIMER_TIMEOUT)/time)
                    set angleXY = Atan2(tempY, tempX)
                    if lookAt == Missile_LOOK_AT_2D then
                        call SetUnitFacing(dummy, angleXY*RADTODEG)
                    endif
                endif
            endif
            set angleZ = Atan2(velZ, SquareRoot(velX*velX + velY*velY))
            if lookAt == Missile_LOOK_AT_3D then
                call SetUnitAnimationByPitch(dummy, angleZ)
            endif
        endmethod
        
        private method driverTypeGuidance takes nothing returns nothing
            local real tempAngle
        
            if target != null then
                if GetUnitTypeId(target) == 0 then
                    set target = null
                else 
                    set targetX = GetUnitX(target)
                    set targetY = GetUnitY(target)
                    set targetZ = GetUnitFlyHeight(target) + GetTerrainZ(targetX, targetY) + zOffset
                endif
            endif
            
            set tempX = targetX - prevX
            set tempY = targetY - prevY
            set tempZ = targetZ - prevZ
            
            // Check if the missile is within target point range.
            set expires = tempX*tempX + tempY*tempY + tempZ*tempZ < velX*velX + velY*velY + velZ*velZ
            
            if angleTurnRate == 0. then
                set angle = Atan2(tempY, tempX)
            else
                set tempAngle = Atan2(tempY, tempX)
                set expires = expires and GetAngleDifference(angle, tempAngle) <= angleTurnRate
                set angle = angle + GetAngleRotation(angle, tempAngle, angleTurnRate)
            endif
            
            if pitchTurnRate == 0. then
                set pitch = Atan2(tempZ, SquareRoot(tempX*tempX + tempY*tempY))
            else
                set tempAngle = Atan2(tempZ, SquareRoot(tempX*tempX + tempY*tempY))
                set expires = expires and GetAngleDifference(pitch, tempAngle) <= pitchTurnRate
                set pitch = pitch + GetAngleRotation(pitch, tempAngle, pitchTurnRate)
            endif
            
            // Make the missile end directly on the target.
            if expires then
                set velX = tempX - accX/2
                set velY = tempY - accY/2
                set velZ = tempZ - accZ/2 - gravity/2
            endif
        endmethod
        
        private method driverTypeParabola takes nothing returns nothing
            // No content
        endmethod
        
        //=======================================================
        // Periodic update
        
        private static method motion takes nothing returns boolean
            local thistype this = loopIndex
            local integer  iter = 0
            local real     velocity
            
            loop
                exitwhen this == 0 or iter == MAX_ITERATIONS_PER_LOOP
                set loopIndex = next

                // Cache the position of the current missile game state.
                if updatePos then
                    set x = GetUnitX(dummy)
                    set y = GetUnitY(dummy)
                    set z = GetUnitFlyHeight(dummy)
                endif
                set prevX = x
                set prevY = y
                set prevZ = z + GetTerrainZ(x, y)
                
                // Check for specified driver types.
                if driver == Missile_MOTION_DRIVER_GUIDANCE then
                    call driverTypeGuidance()
                elseif driver == Missile_MOTION_DRIVER_BALLISTIC then
                    call driverTypeBallistic()
                elseif driver == Missile_MOTION_DRIVER_PARABOLA then
                    call driverTypeParabola()
                endif
                
                // Compute missile's limits.
                set distance = distance + speed
                set duration = duration + Missile_TIMER_TIMEOUT
                if maxDistance > 0. and distance >= maxDistance then
                    set expires = true
                elseif maxDuration > 0. and duration >= maxDuration then
                    set expires = true
                endif
                
                // Calculate the next missile position, then move it.
                set tempX = prevX + velX + accX/2
                set tempY = prevY + 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 tempZ = prevZ + velZ + accZ/2 + gravity/2 - GetLocationZ(LOC)
                
                if tempZ < minFlyHeight then
                    set tempZ = minFlyHeight
                elseif tempZ < 0. then
                    set tempZ = 0.
                endif
                
                // Cache the current missile position for the user.
                set x = tempX
                set y = tempY
                set z = tempZ
                
                call SetUnitX(dummy, tempX)
                call SetUnitY(dummy, tempY)
                call SetUnitFlyHeight(dummy, tempZ, 0.)
                
                // Add the gravity effect to the z velocity.
                if gravity != 0. and prevZ > 0. then
                    set velZ = velZ + gravity
                endif
                
                // Update the velocity vector components.
                set velX = velX + accX
                set velY = velY + accY
                set velZ = velZ + accZ
                
                set velocity = speed
                if velocity > maxSpeed then
                    set speed = maxSpeed
                elseif velocity < 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(MISSILE_MOTION)
            endloop
            
            if activeStructs > 0 then
                call RunStructActions()
            endif
        endmethod
        
        
        //=======================================================
        // Launcher
        //     • Launches a missile 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 missile!")
                debug call DebugWarning(launched,   "launch", "launched", this, "Attempt to double launch a missile!")
            
                return this
            endif
            
            set launched = true
            set typeId   = id
            set launchX  = x
            set launchY  = y
            set launchZ  = z
            
            if driver != Missile_MOTION_DRIVER_BALLISTIC then
                set angle = angleXY
                set pitch = angleZ
            endif
                
            call enqueue()
            if prev == 0 then
                call TimerStart(MISSILE_TIMER, Missile_TIMER_TIMEOUT, true, function thistype.onPeriodic)
            endif
            
            if IsMissileStructId(id) then
                call StructAddMissile(id, this)
            endif
            
            return this// For fancy struct syntax.
        endmethod
        
        //=======================================================
        // Creators and destructor
    
        static method createEx takes unit missile, real angle, real pitch returns thistype
            local thistype this = allocate()
            
            set dummy     = missile
            set angleXY   = ModuloAngle(angle)
            set angleZ    = ModuloAngle(pitch)
            set x         = GetUnitX(missile)
            set y         = GetUnitY(missile)
            set z         = GetUnitFlyHeight(missile)
            set updatePos = not IsUnitPaused(missile) and GetUnitTypeId(missile) != Missile_DUMMY_UNIT_TYPE_ID
            
            call UnitAddAbilityToFly(missile)
            call SetUnitFacing(missile, angle*RADTODEG)
            call SetUnitAnimationByPitch(missile, pitch*RADTODEG)
            
            // Create a refernce to this dummy unit.
            call SaveInteger(HASH, 0, GetHandleId(dummy), this)
            
            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, 0, GetHandleId(dummy))
            call FlushChildHashtable(HASH, this)
            
            if launched then
                if IsMissileStructId(typeId) then
                    call StructRemoveMissile(typeId, this)
                endif
                call remove()
            endif
            
            set launched  = false
            set allocated = false
            set model     = null
            set dummy     = null
            set source    = null
            set target    = null
            set owner     = null
            
            call deallocate()
        endmethod

        private static method onInit takes nothing returns nothing
            debug local unit dummy = CreateUnit(Missile_NEUTRAL_PLAYER, Missile_DUMMY_UNIT_TYPE_ID, 0., 0., 0.)
            debug call DebugError(GetUnitTypeId(dummy) != Missile_DUMMY_UNIT_TYPE_ID, "onInit", "unitTypeId", 0, "Global setup for public integer DUMMY_UNIT_TYPE_ID is incorrect! This map currently can't use Missile!")
            debug call RemoveUnit(dummy)
            debug set dummy = null
        
            //! runtextmacro optional MISSILE_COLLISION_ON_INIT()
        
            call TriggerAddCondition(MISSILE_MOTION, Condition(function thistype.motion))
            call InitMissileGlobals()
        endmethod
    
    endstruct
    
//=============================================
// Public modules. 
//=============================================
    
module MissileDebug 
endmodule
    
module MissileLaunch 

        static method launch takes Missile this returns Missile
            return this.launch(thistype.typeid)
        endmethod

endmodule
    
module MissileTerminate
        
        static method terminateM takes Missile 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
        
endmodule
    
module MissileAction
    
        static method iterateM takes nothing returns boolean
            local Missile this = MissileType[thistype.typeid].first
            local Missile next
            local unit    u
            
            loop
                exitwhen this == 0 
                set next = MissileType(this).next
            
                static if thistype.onPeriodic.exists then
                    if this.exists and thistype.onPeriodic(this) then
                        call thistype.terminateM(this)
                    endif
                endif

static if LIBRARY_MissileCollision 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. then
                        call GroupEnumUnitsInRect(FILTER_GROUP, ENUM_RECT, Missile.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. then
                        call EnumDestructablesInRect(ENUM_RECT, Missile.destFilter, function thistype.onDestructableM)
                    endif
                endif
                
                static if thistype.onItem.exists then
                    if this.exists and this.collision > 0. then
                        call EnumDestructablesInRect(ENUM_RECT, Missile.itemFilter, function thistype.onItemM)
                    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 thistype.onTerrain(this) then
                        call thistype.terminateM(this)
                    endif
                endif
                
                set this = next
            endloop
            
            return false
        endmethod
    endmodule
    
    module MissileStruct
        implement MissileDebug
        implement MissileLaunch
        implement MissileTerminate
        implement MissileAction
        
        private static method onInit takes nothing returns nothing
            call CreateMissileStruct(thistype.typeid, function thistype.iterateM)
        endmethod
    endmodule
    
    private module MISSILE_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 Missile
            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 Missile
            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 Missile
            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 Missile
            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
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
ZLibary - No because the location is neither public nor readonly.
There are cases where I want to inline GetTerrainZ for speed reasons.

MapBounds / WorldBounds - No because moving units to the absolut world border is
also not 100% safe. Also neither MapContainsX nor MapContainsY inlines.
 
Status
Not open for further replies.
Top