- 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.
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.
~~~~~~~~~~~~~~~~~~~~~~~~~~

- 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~

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

- In order to compile Projectile you'll need The Jass NewGen Pack.
- A basic knowledge about structs and how to use them.
- 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.
~~~~~~~~~~~~~~~~~~~~~~~~~~

- -
~~~~~~~~~~~~~~~~~~~~~~~~~~

- soon
~~~~~~~~~~~~~~~~~~~~~~~~~~

- soon
Last edited: