System Collision Missiles

emjlr3

Change can be a good thing
Reaction score
395
Collision Projectiles v1.00
cm.jpg

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:
  1. GroupUtils
  2. DestructableLib
  3. 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     = 'n000' // 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&gt;0. and .currLife&gt;=.life) or (.distance&gt;0. and .currDistance&gt;=.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&gt;.angle then
                            set r=r-.angle
                            if r&gt;.angularRotation then
                                set r=.angularRotation
                            endif
                            set .angle=(.angle+r)*bj_DEGTORAD
                        else
                            set r=.angle-r
                            if r&gt;.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&gt;.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&gt;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)&lt;=.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,&quot;origin&quot;)
                                    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,&quot;origin&quot;)
                                    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&#039;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&gt;.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,&quot;origin&quot;)
        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;Amrf&#039;)
        call UnitRemoveAbility(.dummy,&#039;Amrf&#039;)
        call UnitAddAbility(.dummy,&#039;Aloc&#039;)
        call SetUnitInvulnerable(.dummy,true)
        call SetUnitPathing(.dummy,false)
        if velocity&lt;=0. then
            set .velocity=.1
            debug call BJDebugMsg(&quot;|c00FF0000Projectile created with invalid velocity.|r&quot;)
        endif
        if z&lt;0. then
            set .z=0.
            debug call BJDebugMsg(&quot;|c00FF0000Projectile created with invalid fly height.|r&quot;)
        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&lt;=0. then
            set distance=50.
            debug call BJDebugMsg(&quot;|c00FF0000Parabolic projectile created with invalid distance.|r&quot;)
        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&lt;=0. then
            set life=.1
            debug call BJDebugMsg(&quot;|c00FF0000Projectile created with invalid timed life.|r&quot;)
        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&lt;=0. then
            set distance=50.
            debug call BJDebugMsg(&quot;|c00FF0000Projectile created with invalid distance.|r&quot;)
        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,&quot;origin&quot;)
    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(&quot;|c00FF0000Projectile acceleration invalid.|r&quot;)
            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&gt;COUNT
            if Pow(GetUnitX(DATA<i>.dummy)-x,2)+Pow(GetUnitY(DATA<i>.dummy)-y,2)&lt;=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&gt;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&gt;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&#039;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(&quot;Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl&quot;,target, &quot;origin&quot;) ) 
            // 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.)&lt;0.05 then 
            call DestroyEffect(AddSpecialEffectTarget(&quot;Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl&quot;,.dummy,&quot;origin&quot;))
        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),&quot;Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl&quot;,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&#039;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),&quot;Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl&quot;,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&#039;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(),&quot;Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl&quot;,GetUnitX(u),GetUnitY(u),40.,200.)
            // so it doesn&#039;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&gt;360.                
                set d=demo.create(GetOwningPlayer(u),&quot;Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl&quot;,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&#039;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
 

Attachments

  • emjlr3 - CollisionMissiles.w3x
    95.5 KB · Views: 436
  • cm.jpg
    cm.jpg
    126.8 KB · Views: 450

emjlr3

Change can be a good thing
Reaction score
395
can't believe no one has a single comment...
 

tooltiperror

Super Moderator
Reaction score
231
This has a fair share of uses. Even without bezier curves.

Approved.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top