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