Collision Projectiles v1.00
In the spirit of Vexorian's classic Collision Missiles scripts, I present for review, a fully functional collision missiles system, one that I have been meaning to update and release for quite sometime now. Those that exist are generally rather dated (aside from Vexorians, which requires his Xe modules and isn't nearly as robust) or are way too complex to even bother with.
Benefits:
- Minimal requirements
- Robust: includes destructible detection, pitch/roll angle adjustments, 3 dimensional collisions, realistic missiles deflection and cliff prediction
- Easy to use, while still providing advanced functionalities
- Easy to implement
- Many different launching options
- Runs smooth and lagless
- Supports custom interfaces
Drawbacks:
- O(n) missile collision searches
- Constant z height adjustments don't seem to act quite how I had expected
- Minimal interface applicability, unless I just plum don't know what I'm doing
- Up for debate...
Requirements:
- GroupUtils
- DestructableLib
- UnitAlive native
Implementation:
Code:
Requirements:
GroupUtils: [url]http://www.wc3c.net/showthread.php?t=104464[/url]
DestructableLib: [url]http://www.wc3c.net/showthread.php?t=103927[/url]
UnitAlive native: custom script section of this map
Implementation:
Copy the aforementioned libraries to your map and setup accordingly
Copy the Collision Projectiles library to your map, configure and enjoy!
Simple JASS Use:
//==Struct
struct projectile extends events
// The destroy method can be used directly to immediately destroy a projectile
method destroy takes nothing returns nothing
//==Interface Methods
// Called every timer interval
method onPeriodic takes nothing returns nothing defaults nothing
// Called when the projectile is destroyed
method onEnd takes nothing returns nothing defaults nothing
// Called when the projectile collides with a target
// If this boolexpr returns true, the unit will be considered as having been hit
// If it returns false, the unit is assumed to have not been hit
method onCollision takes unit target returns boolean defaults false
//==Creation Methods
// Standard projectile creation
method create takes player owner, string sfx, real x, real y, real z, real velocity, real angle returns projectile
// Launch a parabolic projectile, z is the parabola apex
// Note, I use the dummy model to adjust the pitch/roll angles for accurate sfx display
method createParabolic takes player owner, string sfx, real x, real y, real z, real velocity, real angle, real distance returns projectile
// Launch a projectile which homes in on a unit
method createHoming takes player owner, unit target, string sfx, real x, real y, real z, real velocity returns projectile
// Launch a projectile with a timed life
method createTimed takes player owner, string sfx, real x, real y, real z, real velocity, real angle, real life returns projectile
// Launch a projectile a specific distance
method createDistance takes player owner, string sfx, real x, real y, real z, real velocity, real angle, real distance returns projectile
// Launch a projectile to a pair of coords (xDes/yDes)
method createToPoint takes player owner, unit target, string sfx, real x, real y, real z, real velocity, real angle, real xDes, real yDes returns projectile
//==Misc. Methods
// Update the size of a projectile
method setScale takes projectile this, real scale returns nothing
// Update the effect of a projectile
method setEffect takes projectile this, string sfx returns nothing
// Retrieve a projectiles dummy unit
method getDummy takes projectile this returns unit
// Forget the previously set target
method forgetTarget takes projectile this returns nothing
// Update collision radius
method setCollisionSize takes projectile this, real size returns nothing
// Set an acceleration, this can be both positive and negative
method accelerate takes projectile this, real rate returns nothing
//==Group Methods, These returned groups should be released when they are no longer needed
// Return the projectiles in range of x/y coordinates
method enumProjectilesInRange takes real x, real y, real range returns group
// Return the projectiles in a rect
method enumProjectilesInRect takes rect r returns group
// Return the projectiles owned by a player
method enumProjectilesOfPlayer takes player p returns group
Advanced JASS Use:
//==Constant globals
public constant real TIMEOUT = .04 // Periodic timer interval, lower=smoother, higher=more efficient
public constant real RADIUS = 125. // X/Y/Z collision range, as a default
public constant real MAXVEL = 2500. // Maximum projectile velocity, as a default
//==Struct Members
readonly unit dummy // Projectile unit
unit target=null // Target of projectile
readonly group g // Mark projectiles and targets collided with
readonly player owner // Owner of projectile
private effect sfx // Effect on projectile
private string sfxString // String of this effect
real velocity // Units/TIMEOUT
real angle // Angle of movement in Radians
real cos // Vector x component of velocity
real sin // Vector y component of velocity
real flyheight // Fly height of projectile
real angularRotation=45. // Homing rotation rate in Degrees/TIMEOUT
private real z // Starting location z height, or used to store parabolic apex height
private real absz // Absolute projectile z height when first spawned
//==For adjusting collisions
real radius=RADIUS // Collision radius, otherwise the default value is used
real sqRadius=SQRADIUS // Sqaure of the radius, reduces math overhead
// If radius is directly edited, you must also change sqRadius
//==For accelerations
real acceleration=0. // In the case of accelerated movement, velocity/TIMEOUT
real maxVelocity=MAXVEL/FRAMERATE // Maximum velocity of projectile, very large by default
private integer marker // Position in stack
//==Various un-related booleans
boolean end=false // Marked for destruction
boolean parabola=false // Parabolic projectile
boolean homing=false // Homing projectile
boolean oneOut=true // A single target collision will mark the projectile for destruction
boolean projectileCollision=true // Collide with other projectiles
//==When setting timed life or max distance
real life // Projectile max life
private real currLife = 0.
real distance // Projectile max distance
private real currDistance = 0.
//==When launching to coords
real xDes // X where the projectile is moving to
real yDes // Y where the projectile is moving to
// If either of these are directly edited, you would also need to update angle, cos and sin, and ticks
boolean toPoint=false // Moving to a set of coords
integer ticks=0 // Number of timer iterations before the projectile will reach it's destination
System Script:
JASS:
library CollisionProjectiles needs DestructableLib, GroupUtils
//**==Collision Missiles v1.00, by: emjlr3==**\\
//==CONFIGURABLES==\\
globals
private constant boolean KILLTREES = true // Projectiles destroy trees
private constant integer RAWCODE = 039;n000039; // Missile dummy rawcode, Ctrl+d in the object editor
public constant real TIMEOUT = .04 // Periodic timer interval, lower=smoother, higher=more efficient
public constant real RADIUS = 125. // X/Y/Z collision range, as a default
public constant real MAXVEL = 2500. // Maximum projectile velocity, as a default
endglobals
private function Filt takes unit target, projectile p returns boolean
return IsUnitEnemy(target,p.owner) // In the absence of an onCollision method, filters through targets
endfunction
//==NO TOUCHING==\\
globals
private unit TARG
private projectile TEMPD
private projectile array DATA
private integer COUNT = 0
public constant integer FRAMERATE = R2I(1./TIMEOUT) // Public
private constant real SQRADIUS = RADIUS*RADIUS
private timer TIMER = CreateTimer()
private boolexpr UNITFILTER
private boolexpr TREEFILTER
private rect RECT
private group GROUP = CreateGroup()
private location LOC = Location(0.,0.)
endglobals
private interface events
// Called every timer interval
method onPeriodic takes nothing returns nothing defaults nothing
// Called when the projectile is destroyed
method onEnd takes nothing returns nothing defaults nothing
// Called when the projectile collides with a target
// If this boolexpr returns true, the unit will be considered as having been hit
method onCollision takes unit target returns boolean defaults false
endinterface
struct projectile extends events
readonly unit dummy // Projectile unit
unit target=null // Target of projectile
readonly group g // Mark projectiles and targets collided with
readonly player owner // Owner of projectile
private effect sfx // Effect on projectile
private string sfxString // String of this effect
real velocity // Units/TIMEOUT
real angle // Angle of movement in Radians
real cos // Vector x component of velocity
real sin // Vector y component of velocity
real flyheight // Fly height of projectile
real angularRotation=45. // Homing rotation rate in Degrees/TIMEOUT
private real z // Starting location z height, or used to store parabolic apex height
private real absz // Absolute projectile z height when first spawned
//==For adjusting collisions
real radius=RADIUS // Collision radius, otherwise the default value is used
real sqRadius=SQRADIUS // Sqaure of the radius, reduces math overhead
//==For accelerations
real acceleration=0. // In the case of accelerated movement, velocity/TIMEOUT
real maxVelocity=MAXVEL/FRAMERATE // Maximum velocity of projectile in units/TIMEOUT, very large by default
private integer marker // Position in stack
//==Various un-related booleans
boolean end=false // Marked for destruction
boolean parabola=false // Parabolic projectile
boolean homing=false // Homing projectile
boolean oneOut=true // A single target collision will mark the projectile for destruction
boolean projectileCollision=true // Collide with other projectiles
//==When setting timed life or max distance
real life // Projectile max life
private real currLife = 0.
real distance // Projectile max distance
private real currDistance = 0.
//==When launching to coords
real xDes // X where the projectile is moving to
real yDes // Y where the projectile is moving to
boolean toPoint=false
private integer ticks=0 // Number of timer iterations before the projectile will reach it's destination
//==PRIVATE METHODS==\\
// This is accessable to users also
method destroy takes nothing returns nothing
// Execute custom onDestroy effects
call .onEnd()
// Reset our changeables to their defaults
set .life=0.
set .currLife=0.
set .distance=0.
set .currDistance=0.
set .target=null
set .end=false
set .acceleration=0.
set .parabola=false
set .toPoint=false
set .ticks=0
set .oneOut=true
set .maxVelocity=2000.
set .projectileCollision=true
// Update stack
set DATA[COUNT].marker=.marker
set DATA[.marker]=DATA[COUNT]
set COUNT=COUNT-1
// Clean up
call DestroyEffect(.sfx)
call KillUnit(.dummy)
call ReleaseGroup(.g)
call .deallocate()
endmethod
// RIP Acehart
private static method parabolaz takes real x,real d,real h returns real
return 4.*h*x*(d-x)/(d*d)
endmethod
private static method treefilter takes nothing returns boolean
// Filter trees for destruction
if IsDestructableTree(GetFilterDestructable()) then
call SetWidgetLife(GetFilterDestructable(),0.)
endif
return false
endmethod
private static method unitfilter takes nothing returns boolean
local projectile this=TEMPD
local real x
local real y
local real z
// Get target logistics
set TARG=GetFilterUnit()
set x=GetUnitX(TARG)
set y=GetUnitY(TARG)
call MoveLocation(LOC,x,y)
set z=GetLocationZ(LOC)+GetUnitFlyHeight(TARG)
// Unit is not dead and has not already been hit
if not UnitAlive(TARG) or IsUnitInGroup(TARG,.g) then
return false
endif
// Within z range
if .absz+.radius>z and .absz-.radius<z then
// Effects
if .onCollision.exists then
if .onCollision(TARG) then
call GroupAddUnit(.g,TARG)
return true
endif
elseif Filt(TARG,this) then
return true
endif
endif
return false
endmethod
private static method periodic takes nothing returns nothing
local projectile this
local projectile temp
local integer i=1
local integer j=1
local real x
local real tempx
local real y
local real tempy
local real z
local real r
loop
exitwhen i>COUNT
set this=DATA<i>
// Projectile is ready to be destroyed
if .end or not UnitAlive(.dummy) or (.life>0. and .currLife>=.life) or (.distance>0. and .currDistance>=.distance) then
call .destroy()
set i=i-1
else
// Not over
if .homing and not UnitAlive(.target) then
// Homing projectile target has perished
call .destroy()
else
// Execute custom periodic effects
call .onPeriodic()
// Not homing
if not .homing then
set x=GetUnitX(.dummy)+.cos
set y=GetUnitY(.dummy)+.sin
else
set x=GetUnitX(.dummy)
set y=GetUnitY(.dummy)
// Homing missile
set .angle=.angle*bj_RADTODEG
set r=Atan2(GetUnitY(.target)-y,GetUnitX(.target)-x)*bj_RADTODEG
// Solve for updated facing
if r>.angle then
set r=r-.angle
if r>.angularRotation then
set r=.angularRotation
endif
set .angle=(.angle+r)*bj_DEGTORAD
else
set r=.angle-r
if r>.angularRotation then
set r=.angularRotation
endif
set .angle=(.angle-r)*bj_DEGTORAD
endif
set .cos=.velocity*Cos(.angle)
set .sin=.velocity*Sin(.angle)
set x=x+.cos
set y=y+.sin
// Update projectile facing
call SetUnitFacing(.dummy,.angle*bj_RADTODEG)
endif
// Kill trees
static if KILLTREES then
call MoveRectTo(RECT,x,y)
call EnumDestructablesInRect(RECT,TREEFILTER,null)
endif
// Update various tangible values
set .currLife=.currLife+TIMEOUT
set .currDistance=.currDistance+.velocity
if .acceleration!=0. then
set .velocity=.velocity+.acceleration
if .velocity>.maxVelocity then
set .velocity=.maxVelocity
endif
set .cos=.velocity*Cos(.angle)
set .sin=.velocity*Sin(.angle)
endif
set .ticks=.ticks-1
// .ticks starts at 0, so it should only equal 0 if it has been otherwise set
if .ticks==0 then
set .end=true
endif
// Update coordinates
call SetUnitX(.dummy,x)
call SetUnitY(.dummy,y)
// Target collisions
// This will take care of manually set targets as well
set TEMPD=this
call GroupClear(GROUP)
call GroupEnumUnitsInRange(GROUP,x,y,.radius,UNITFILTER)
if FirstOfGroup(GROUP)!=null and .oneOut then
// Projectile has collided with a target
set end=true
endif
// Projectile collisions, O(n) ftl
loop
exitwhen j>COUNT
if j!=i then
set temp=DATA[j]
if temp.projectileCollision and .projectileCollision and not IsUnitInGroup(temp.dummy,.g) and not IsUnitInGroup(.dummy,temp.g) then
set tempx=GetUnitX(temp.dummy)
set tempy=GetUnitY(temp.dummy)
// Calculate distance
if Pow(tempx-x,2)+Pow(tempy-y,2)<=.sqRadius then
// Deflection for .dummy
set .angle=(.angle*bj_RADTODEG+180.+(2.*(bj_RADTODEG*Atan2(tempy-y,tempx-x)-.angle*bj_RADTODEG)))
call ShowUnit(.dummy,false)
call DestroyEffect(.sfx)
call RemoveUnit(.dummy)
set .dummy=CreateUnit(.owner,RAWCODE,x,y,.angle)
set .sfx=AddSpecialEffectTarget(.sfxString,.dummy,"origin")
set .angle=.angle*bj_DEGTORAD
set .cos=.velocity*Cos(.angle)
set .sin=.velocity*Sin(.angle)
// Deflection for temp.dummy
set temp.angle=(temp.angle*bj_RADTODEG+180.+(2.*(bj_RADTODEG*Atan2(y-tempy,x-tempx)-temp.angle*bj_RADTODEG)))
call ShowUnit(temp.dummy,false)
call DestroyEffect(temp.sfx)
call RemoveUnit(temp.dummy)
set temp.dummy=CreateUnit(temp.owner,RAWCODE,tempx,tempy,temp.angle)
set temp.sfx=AddSpecialEffectTarget(temp.sfxString,temp.dummy,"origin")
set temp.angle=temp.angle*bj_DEGTORAD
set temp.cos=temp.velocity*Cos(temp.angle)
set temp.sin=temp.velocity*Sin(temp.angle)
// Remove the posibility of an infinite collision loop
call GroupAddUnit(.g,temp.dummy)
call GroupAddUnit(temp.g,.dummy)
endif
endif
endif
set j=j+1
endloop
if .parabola then
call SetUnitFlyHeight(.dummy,projectile.parabolaz(.currDistance,.distance,.z),0.)
// iNfraNe's dummy model ftw!!
call SetUnitAnimationByIndex(.dummy,R2I((.currDistance/.distance)*181))
else
// Constant fly height adjustments and cliff detection
call MoveLocation(LOC,x,y)
set z=GetLocationZ(LOC)
if z>.z+.flyheight*1.5 then
// Cliff
call .destroy()
else
// Maintain static fly height
// The idea here was to maintain a constant flying z value, such that terrain height changes
// would not be reflected in its magnitude. In theory, it works great, in execution, not so much...
call SetUnitFlyHeight(.dummy,.flyheight+(.z-GetLocationZ(LOC)),0.)
endif
endif
endif
endif
set i=i+1
endloop
endmethod
//==USER METHODS==\\
// Standard creation
static method create takes player owner, string sfx, real x, real y, real z, real velocity, real angle returns projectile
local projectile this=projectile.allocate()
// Store the basics
set .dummy=CreateUnit(owner,RAWCODE,x,y,angle*bj_RADTODEG)
set .sfx=AddSpecialEffectTarget(sfx,.dummy,"origin")
set .sfxString=sfx
set .owner=owner
set .velocity=velocity/FRAMERATE
set .angle=angle
set .cos=.velocity*Cos(.angle)
set .sin=.velocity*Sin(.angle)
set .g=NewGroup()
set .radius=RADIUS
set .sqRadius=SQRADIUS
// The just incases...
call UnitAddAbility(.dummy,039;Amrf039;)
call UnitRemoveAbility(.dummy,039;Amrf039;)
call UnitAddAbility(.dummy,039;Aloc039;)
call SetUnitInvulnerable(.dummy,true)
call SetUnitPathing(.dummy,false)
if velocity<=0. then
set .velocity=.1
debug call BJDebugMsg("|c00FF0000Projectile created with invalid velocity.|r")
endif
if z<0. then
set .z=0.
debug call BJDebugMsg("|c00FF0000Projectile created with invalid fly height.|r")
endif
// Store relevant fly and z heights
call SetUnitFlyHeight(.dummy,z,0.)
set .flyheight=z
call MoveLocation(LOC,x,y)
set .z=GetLocationZ(LOC)
set .absz=.z+.flyheight
// Update stack
set COUNT=COUNT+1
set DATA[COUNT]=this
set .marker=COUNT
// Start timer
if COUNT==1 then
call TimerStart(TIMER,TIMEOUT,true,function projectile.periodic)
endif
return this
endmethod
// CREATION RELATED
// Launch a parabolic projectile, z is the parabola apex
static method createParabolic takes player owner, string sfx, real x, real y, real z, real velocity, real angle, real distance returns projectile
local projectile this=projectile.create(owner,sfx,x,y,40.,velocity,angle)
set .parabola=true
if distance<=0. then
set distance=50.
debug call BJDebugMsg("|c00FF0000Parabolic projectile created with invalid distance.|r")
endif
set .distance=distance
set .z=z
return this
endmethod
// Launch a projectile which homes in on a unit
static method createHoming takes player owner, unit target, string sfx, real x, real y, real z, real velocity returns projectile
local projectile this=projectile.create(owner,sfx,x,y,z,velocity,Atan2(GetUnitY(target)-y,GetUnitX(target)-x))
set .target=target
set .homing=true
return this
endmethod
// Launch a projectile with a timed life
static method createTimed takes player owner, string sfx, real x, real y, real z, real velocity, real angle, real life returns projectile
local projectile this=projectile.create(owner,sfx,x,y,z,velocity,angle)
if life<=0. then
set life=.1
debug call BJDebugMsg("|c00FF0000Projectile created with invalid timed life.|r")
endif
set .life=life
return this
endmethod
// Launch a projectile a specific distance
static method createDistance takes player owner, string sfx, real x, real y, real z, real velocity, real angle, real distance returns projectile
local projectile this=projectile.create(owner,sfx,x,y,z,velocity,angle)
if distance<=0. then
set distance=50.
debug call BJDebugMsg("|c00FF0000Projectile created with invalid distance.|r")
endif
set .distance=distance
return this
endmethod
// Launch a projectile to a pair of coords (xDes/yDes)
static method createToPoint takes player owner, unit target, string sfx, real x, real y, real z, real velocity, real angle, real xDes, real yDes returns projectile
local projectile this=projectile.create(owner,sfx,x,y,z,velocity,angle)
set .xDes=xDes
set .yDes=yDes
set .toPoint=true
set .ticks=R2I(SquareRoot(Pow(xDes-x,2)*Pow(yDes-y,2))/.velocity)
return this
endmethod
// MISC.
// Update the size of a projectile
static method setScale takes projectile this, real scale returns nothing
call SetUnitScale(.dummy,scale,scale,scale)
endmethod
// Update the effect of a projectile
static method setEffect takes projectile this, string sfx returns nothing
call DestroyEffect(.sfx)
set .sfxString=sfx
call AddSpecialEffectTarget(sfx,.dummy,"origin")
endmethod
// Retrieve a projectiles dummy unit
static method getDummy takes projectile this returns unit
return .dummy
endmethod
// Forget the previously set target
static method forgetTarget takes projectile this returns nothing
set .target=null
set .homing=false
endmethod
// Update collision radius
static method setCollisionSize takes projectile this, real size returns nothing
set .radius=size
set .sqRadius=size*size
endmethod
// Set an acceleration, this can be both positive and negative
static method accelerate takes projectile this, real rate returns nothing
if rate==0. then
debug call BJDebugMsg("|c00FF0000Projectile acceleration invalid.|r")
return
endif
set .acceleration=rate/(FRAMERATE*FRAMERATE)
endmethod
// GROUP RELATED
// These returned groups should be released when they are no longer needed
// Return the projectiles in range of x/y coordinates
static method enumProjectilesInRange takes real x, real y, real range returns group
local integer i=1
local group g=NewGroup()
set range=range*range
loop
exitwhen i>COUNT
if Pow(GetUnitX(DATA<i>.dummy)-x,2)+Pow(GetUnitY(DATA<i>.dummy)-y,2)<=range then
call GroupAddUnit(g,DATA<i>.dummy)
endif
set i=i+1
endloop
return g
endmethod
// Return the projectiles in a rect
static method enumProjectilesInRect takes rect r returns group
local integer i=1
local group g=NewGroup()
loop
exitwhen i>COUNT
if RectContainsCoords(r,GetUnitX(DATA<i>.dummy),GetUnitY(DATA<i>.dummy)) then
call GroupAddUnit(g,DATA<i>.dummy)
endif
set i=i+1
endloop
return g
endmethod
// Return the projectiles owned by a player
static method enumProjectilesOfPlayer takes player p returns group
local integer i=1
local group g=NewGroup()
loop
exitwhen i>COUNT
if DATA<i>.owner==p then
call GroupAddUnit(g,DATA<i>.dummy)
endif
set i=i+1
endloop
return g
endmethod
//==INITIALIZATION==\\
private static method onInit takes nothing returns nothing
set UNITFILTER=Condition(function projectile.unitfilter)
static if KILLTREES then
set RECT=Rect(-RADIUS,-RADIUS,RADIUS,RADIUS)
set TREEFILTER=Condition(function projectile.treefilter)
endif
endmethod
endstruct
endlibrary</i></i></i></i></i></i></i></i></i>
Demonstrations:
JASS:
scope Demo initializer Init
private struct demo extends projectile
unit caster // we can use this in our methods just like the members of the projectile struct
method onEnd takes nothing returns nothing
// not really needed, but is serves as an example for the onEnd interface
set caster=null
endmethod
method onCollision takes unit target returns boolean
// the checks for collision range, previously being hit and aliveness have already been done
// make sure you don't accidently use .target here, because that is the homing target
if IsUnitEnemy(target,.owner) then
// .dummy is the projectile dummy unit, just incase you need it
call UnitDamageTarget(.caster,target,10.,false,false,ATTACK_TYPE_MAGIC,DAMAGE_TYPE_FIRE,null)
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl",target, "origin") )
// returning true means we did hit the target
// since .oneOut=true, the missile will destruct, if it were false, the projectile could go on hitting more targets
return true
endif
// returning false means we did not actually hit the target
return false
endmethod
method onPeriodic takes nothing returns nothing
// create an effect on the projectile every so often
if GetRandomReal(0.,1.)<0.05 then
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl",.dummy,"origin"))
endif
endmethod
endstruct
private function Actions takes nothing returns nothing
local unit u=GetTriggerUnit()
local unit dum
local projectile p
local demo d
local real r=0.
if GetSpellTargetUnit()==null then
// create new projectile, in direction of casted ability, traveling with 40. fly height and at velocity 1000. units/s
// this will work with my interfaces
set d=demo.create(GetOwningPlayer(u),"Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl",GetUnitX(u),GetUnitY(u),40.,1000.,ABPXY(GetUnitX(u),GetUnitY(u),GetSpellTargetX(),GetSpellTargetY()))
// set maximum distance to 1250.
set d.distance=1250.
// set scale of projectile
call projectile.setScale(d,1.25)
// store caster to the demo struct, interfaces are nice!
set d.caster=GetTriggerUnit()
// store projectile dummy unit
set dum=d.getDummy(d)
// create a parabolic projectile, with max height of 500. at a velocity of 500. units/s which will land at the target location
// unfortunately this does not work with interfaces, the next one doesn't either <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite3" alt=":(" title="Frown :(" loading="lazy" data-shortname=":(" />
// strange then that I can create one of these projectiles using my demo struct...
set p=projectile.createParabolic(GetOwningPlayer(u),"Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl",GetUnitX(u),GetUnitY(u),500.,500.,ABPXY(GetUnitX(u),GetUnitY(u),GetSpellTargetX(),GetSpellTargetY()),DBPXY(GetUnitX(u),GetUnitY(u),GetSpellTargetX(),GetSpellTargetY()))
// alternatively, create a new projectile facing the coords, then set .z=apex height, .distance=maxdistance and .parabola=true
// that way you can use interfaces
// so our two new projectiles don't collide with one another
// either projectiles group will work
call GroupAddUnit(p.g,dum)
else
if IsUnitAlly(GetSpellTargetUnit(),GetOwningPlayer(u)) then
// create a projectile which will home in on the spell target, move it slowly so we can watch it chase its target
// used Player(12) as its owner so it will strike allied units (see Filt configurable)
set p=projectile.createHoming(Player(12),GetSpellTargetUnit(),"Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl",GetUnitX(u),GetUnitY(u),40.,200.)
// so it doesn't collide with our caster
call GroupAddUnit(p.g,u)
// store life as 7.5 seconds maximum, it can be outrun
set p.life=7.5
// alternatively, create a new projectile facing the target, then set .target=GetSpellTargetUnit, and set .homing=true
// that way you can use interfaces
// here we set up a custom homing projectile that can use our interfaces
else
loop
exitwhen r>360.
set d=demo.create(GetOwningPlayer(u),"Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl",GetUnitX(u),GetUnitY(u),40.,200.,r*bj_DEGTORAD)
// this projectile is homing
set d.homing=true
// on this target
set d.target=GetSpellTargetUnit()
// we can adjust the speed at which the facing of the projectile can update (degrees)
// try setting this to a higher value and see what happens (anything over 45. or so doesn't make a difference)
// the lower the value the slower the facing updates, so units can actually outrun the missiles even though their movement rate is faster
set d.angularRotation=5.
// remove collision for these projectiles, or they will all bounce off one another
set d.projectileCollision=false
// accelerate projectile at 100 units/s
call projectile.accelerate(d,100.)
set d.caster=u
set r=r+(360./12.)
endloop
endif
endif
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterUnitEvent(t,Hero,EVENT_UNIT_SPELL_EFFECT )
call TriggerAddAction( t,function Actions)
endfunction
endscope
Change Log:
- v1.00 - Initial release
Planned Updates:
- Bouncing missiles
- GUI wrapper functions, affording the casting of abilities on collision
- Bezier curves
- Revolving missiles