Upcoming Projectile System


library ProjectileStruct /* v1.0.0

    */  LinkedListModule    /*
    */  optional AutoFly    /*
    //  This sets the distance threshold for unit recognition in the path of
    //  the projectile
    private constant real       PROJECTILE_HIT_COLLISION    = 80.0
    //  This dummy must use Vexorian's dummy model and it's movement type
    //  should be "Hover" if you want to correctly move over water, otherwise
    //  use "None"
    private constant integer    DUMMY_ID                    = 'e000'
*   struct Projectile
*       -   Has a LinkedList incorporated of all active projectiles.
*       static method create takes real ox, real oy, real oz, real tx, real ty, real tz returns thistype
*           -   Projectiles have the following values to be set by you.
*               -   real speed
*               -   real height / arc
*               -   real collides
*               -   real turn
*               -   unit target
*               -   unit source
*       real currentX
*       real currentY
*       real currentZ
*       real targetX
*       real targetY
*       real targetZ
*       real distanceFromTarget
*       method deflect takes real x, real y returns nothing
*           -   Deflects the projectile from the target point, changing
*           -   it's course, doesn't work when the projectile has a
*           -   target.
*       method bounce takes real newHeight, real howFar returns nothing
*           -   Bounces the projectile from it's current Z position
*   module ProjectileStruct
*       static method launch takes Projectile toLaunch returns nothing
*       static method onImpact takes Projectile this, unit justHit returns boolean
*           -   Must be defined in structs that have this module, it
*           -   will run everytime the projectile hits a target, if it
*           -   returns true, the projectile ends it's course.

        private location GetZ = Location(0,0)
    struct Projectile extends array
        implement LinkedList
        static method operator first takes nothing returns thistype
            return base.next
        real currentX
        real currentY
        real currentZ
        real targetX
        real targetY
        real targetZ
        real distanceFromTarget
        real oX
        real oY
        real oZ
        real oD
        real angle
        real fZ
        effect  fx
        string  fP
        unit    dummy
        unit    target
        unit    source
        boolean collides
        real    speed
        real    height
        real    turn
        real    curve
        method operator model= takes string path returns nothing
            call DestroyEffect(fx)
            set fP=path
            set fx=AddSpecialEffectTarget(path,dummy,"origin")
        method operator model takes nothing returns string
            return fP
        method operator arc= takes real value returns nothing
            set height=Tan(value)*oD
        static method createEx takes unit whichUnit, real ox, real oy, real oz, real tx, real ty, real tz returns thistype
            local thistype this=allocate()
            set source=null
            set target=null
            set speed=0
            set height=0
            set turn=0
            set collides=false
            set fP=""
            set angle=Atan2(ty-oy,tx-ox)
            call MoveLocation(GetZ,ox,oy)
            set oX=ox
            set oY=oy
            set oZ=oz+GetLocationZ(GetZ)
            set oD=SquareRoot((tx-ox)*(tx-ox)+(ty-oy)*(ty-oy))
            set currentX=oX
            set currentY=oY
            set currentZ=oZ
            set distanceFromTarget=oD
            call MoveLocation(GetZ,tx,ty)
            set targetX=tx
            set targetY=ty
            set targetZ=tz+GetLocationZ(GetZ)
            set fZ=(targetZ-oZ)/oD
            set dummy=whichUnit
            static if not(LIBRARY_AutoFly) then
                call UnitAddAbility(dummy,'Amrf')
                call UnitRemoveAbility(dummy,'Amrf')
            call SetUnitFlyHeight(dummy,oz,0)
            set fx=AddSpecialEffectTarget(model,dummy,"origin")
            return this
        static method create takes real ox, real oy, real oz, real tx, real ty, real tz returns thistype
            return createEx(CreateUnit(Player(15),DUMMY_ID,ox,oy,Atan2(ty-oy,tx-ox)*bj_RADTODEG),ox,oy,oz,tx,ty,tz)
        method deflect takes real x, real y returns nothing
            set angle=2*Atan2(y-currentY,x-currentY)+bj_PI-angle
            set oX=currentX
            set oY=currentY
            set oZ=currentZ
            set targetX=currentX+distanceFromTarget*Cos(angle)
            set targetY=currentY+distanceFromTarget*Sin(angle)
            call SetUnitFacing(dummy,angle*bj_RADTODEG)
        method bounce takes real newHeight, real howFar returns nothing
            set oX=currentX
            set oY=currentY
            set oZ=currentZ
            set targetX=currentX+howFar*Cos(angle)
            set targetY=currentY+howFar*Sin(angle)
            set oD=SquareRoot((targetX-oX)*(targetX-oX)+(targetY-oY)*(targetY-oY))
            set distanceFromTarget=oD
            set fZ=(targetZ-oZ)/oD
            set height=newHeight
        private integer                     COUNT   =   0
        private integer                     ACTIVE  =   0
        private timer                       TIMER   =   CreateTimer()
        private trigger                     FIRE    =   CreateTrigger()
        private boolexpr            array   METHOD
        private integer             array   INSTANCES
        private triggercondition    array   COND
        private Projectile          array   NODE
    private function Execute takes nothing returns nothing
        call TriggerEvaluate(FIRE)
    private function StartPeriodic takes integer index, Projectile this returns nothing
        if INSTANCES[index]==0 then
            call Projectile.base.insertNode(this)
            set NODE[index]=this
            set COND[index]=TriggerAddCondition(FIRE,METHOD[index])
            call NODE[index].insertNode(this)
        if ACTIVE==0 then
            call TimerStart(TIMER,0.03125,true,function Execute)
        set ACTIVE=ACTIVE+1
        set INSTANCES[index]=INSTANCES[index]+1
    private function StopPeriodic takes integer index, Projectile this returns nothing
        set INSTANCES[index]=INSTANCES[index]-1
        if NODE[index]==this then
            set NODE[index]=NODE[index].next
        call this.removeNode()
        set ACTIVE=ACTIVE-1
        if INSTANCES[index]==0 then
            call TriggerRemoveCondition(FIRE,COND[index])
        if ACTIVE==0 then
            call PauseTimer(TIMER)
    module ProjectileStruct
        private static integer INDEX
        private static method terminate takes Projectile this, unit u returns nothing
            if thistype.onImpact(this,u) then
                call DestroyEffect(this.fx)
                call RemoveUnit(this.dummy)
                call StopPeriodic(INDEX,this)
        private static method onPeriod takes nothing returns boolean
            local unit u
            local real a
            local real x
            local real y
            local Projectile this=NODE[INDEX]
            local integer i=INSTANCES[INDEX]
                exitwhen i==0
                if this.target!=null and GetUnitTypeId(this.target)!=0 and GetWidgetLife(this.target)>=0.405 then
                    call MoveLocation(GetZ,this.targetX,this.targetY)
                    set this.targetX=GetUnitX(this.target)
                    set this.targetY=GetUnitY(this.target)
                    set this.targetZ=GetUnitFlyHeight(this.target)+GetLocationZ(GetZ)
                    set a=Atan2(this.targetY-this.currentY,this.targetX-this.currentX)
                    if this.turn!=0 then
                        if Sin(a-this.angle)>=0 then
                            set this.angle=this.angle+this.turn
                            set this.angle=this.angle-this.turn
                        set this.angle=a
                    set this.fZ=(this.targetZ-this.oZ)/this.oD
                    call SetUnitFacing(this.dummy,this.angle*bj_RADTODEG)
                    if this.distanceFromTarget<=PROJECTILE_HIT_COLLISION then
                        call terminate(this,this.target)
                    if this.distanceFromTarget<=PROJECTILE_HIT_COLLISION then
                        call terminate(this,null)
                set this.currentX=this.currentX+this.speed*Cos(this.angle)
                set this.currentY=this.currentY+this.speed*Sin(this.angle)
                if this.curve!=0 then
                    set a=4*this.curve*this.distanceFromTarget*(this.oD-this.distanceFromTarget)/(this.oD*this.oD)
                    set x=this.currentX+a*Cos(this.angle+bj_PI/2)
                    set y=this.currentY+a*Sin(this.angle+bj_PI/2)
                    call SetUnitFacing(this.dummy,(this.angle+Atan((8*this.curve*this.distanceFromTarget-4*this.oD*this.curve)/(this.oD*this.oD)))*bj_RADTODEG)
                    set x=this.currentX
                    set y=this.currentY
                set this.oD=SquareRoot((this.targetX-this.oX)*(this.targetX-this.oX)+(this.targetY-this.oY)*(this.targetY-this.oY))
                set this.distanceFromTarget=SquareRoot((this.currentX-this.targetX)*(this.currentX-this.targetX)+(this.currentY-this.targetY)*(this.currentY-this.targetY))
                if this.height!=0 or this.fZ!=0 then
                    set this.currentZ=4*this.height*this.distanceFromTarget*(this.oD-this.distanceFromTarget)/(this.oD*this.oD)+this.oZ+this.fZ*(this.oD-this.distanceFromTarget)
                    call MoveLocation(GetZ,x,y)
                    call SetUnitFlyHeight(this.dummy,this.currentZ-GetLocationZ(GetZ),0)
                    call SetUnitAnimationByIndex(this.dummy,R2I((Atan((8*this.height*this.distanceFromTarget-4*this.oD*this.height)/(this.oD*this.oD))+Atan(this.fZ))*bj_RADTODEG)+90)
                call SetUnitX(this.dummy,x)
                call SetUnitY(this.dummy,y)
                if this.collides then
                    call GroupEnumUnitsInRange(bj_lastCreatedGroup,x,y,PROJECTILE_HIT_COLLISION,null)
                        set u=FirstOfGroup(bj_lastCreatedGroup)
                        exitwhen u==null
                        call terminate(this,u)
                        call GroupRemoveUnit(bj_lastCreatedGroup,u)
                set this=this.next
                set i=i-1
            set u=null
            return false
        static method launch takes Projectile this returns nothing
            call StartPeriodic(INDEX,this)
        private static method onInit takes nothing returns nothing
            set INDEX=COUNT+1
            set COUNT=INDEX
            set METHOD[INDEX]=Filter(function thistype.onPeriod)
struct TESTER extends array

    static unit u
    static method onImpact takes Projectile this, unit u returns boolean
        if u==null then
            //Makes it bounce until its height is below 20
            if this.height>20 then
                call this.bounce(this.height*0.6,this.oD*0.6)
                set this.speed=this.speed*0.6
                return true
        elseif u!=this.source then
            //If it encounters a unit it bounces off it, it works better if you add the unit
            //to a group and prevent it from bouncing from the same unit twice
            call this.deflect(GetUnitX(u),GetUnitY(u))
        return false
    implement ProjectileStruct
    static method onPeriod takes nothing returns nothing
        local real a=GetRandomReal(0,bj_PI*2)
        local Projectile new = Projectile.create(GetUnitX(u),GetUnitY(u),65,GetUnitX(u)+1000*Cos(a),GetUnitY(u)+1000*Sin(a),0)
        set new.speed=20
        set new.model="Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl"
        set new.height=200
        set new.source=u
        set new.collides=true
        //set new.curve=500
        call thistype.launch(new)
    static method onInit takes nothing returns nothing
        set u=CreateUnit(Player(0),'hfoo',0,0,270)
        call TimerStart(CreateTimer(),0.5,true,function thistype.onPeriod)
Seems great for a WiP :)
But you have to make sure the API is user-friendly enough.
That's the most important thing in a projectile system (Since the user is required to give a lot of data)

Maybe if you:
- shorten the create function by removing the target parameters
- add these 2 parameters to the launch function

The API could be easier to work with : )


No i can't do that, the important thing about the create method is to trace the vectors within the 2 point's x y and z
This is even shorter than [ljass]MoveLightningEx[/ljass]
It's not even hard to remember
[ljass]call Projectile.create(x1,y1,z1,x2,y2,z2)[/ljass]
All the additional data is set by the user after the call if it needs it (must store the returned projectile in a variable)

If there's anything to add i think that would be some debug security mechanisms
Another thing i thought it would be cool to work on is to make the speed of the missile be accord to it's zAngle
That would be cool :D
You'd allow the user to give an initial speed and the speed would decrease over time ^_^
The zAngle would affect the speed's decrement rate.

Ofcourse, this wouldn't apply for homing missiles ;o
Speaking of homing missiles, why aren't you supporting them? :(


Seems great for a WiP :)
But you have to make sure the API is user-friendly enough.
That's the most important thing in a projectile system (Since the user is required to give a lot of data)
qft, interface = #1 priority.

Looks good so far, I recommend you look at both Berb's and Kenny's systems to see what you should include.

Is this going to be a basic projectile system (standard projectiles, unit collision support) or a bigger one like Kenny's? (eg: projectile bouncing, allowing the user to control the projectile themselves on periodic ticks, destructable collision/projectile collision)


  • Bouncing: no need, you can make the projectile bounce yourself manually, when onImpact runs and the unit is null means the projectile just ended it's route, but if returns false the projectile wont die, you just have to reset it to another target, maybe i'll add a "bounce" method, it would be nice
  • Periodic ticks, this is easy to implement, calling a method every period if the method exists using static ifs
  • I think that if the user really needs projectile collision or anything similar it can be done with periodic tick coding, and since the Projectile struct is a huge linked list the user can iterate through it to search if there's any projectile nearby, maybe i'll add two bounce methods instead, bounce off the floor and bounce off a widget
I've seen Berb's projectile system, doesn't suit my needs at all, much overhead and code bloat.
Haven't seen Kenny's, but i think it got graveyarded (??), I bet it was for a good reason.


Invasion in Duskwood
I don't support dummy recycling (or maybe Bribe's snippet might be a decent choice, but I haven't tested it). It looks ugly, because you can't instantly set the facing of units.

Well, one thing I'd like to see is XY arcing projectiles. This has a "turn" member now, but I think it doesn't work like XY arcing. With that you could easily recreate the "Arcane Missiles" WoW spell (I don't want to, of course) and create similar effects.

And well, graveyarded resource != bad resource. There're many reasons why a resource can get graveyarded (e.g. the owner being inactive, just like in this case). I'm using Kenny's projectile system ever since I first time created a custom missile based spell, and it works perfectly fine. It's the most powerful, user friendly projectile system IMO with tons of possibilities (the only thing it lacks is XY arcing)


Dummy Recycling? Maybe, i've tested dummy recycling and it bugs with effect creation...
XY curve angle? I just finished coding it

The great thing about this system is that it avoids trigger evaluations for projectile impacts.
Also that it supports dummy pitch angle
And that the onImpact method is incredibly useful\

EDIT: updated the main post with the new features:
  • curve
  • bounce
  • deflect


I've come up with the following issues on how to proceed to write this system:
-Projectiles in warcraft are based on the belief that most have homing capacities, but this doesn't apply to real physics in any way:
-How is supposed that a projectile with a given "arc" or "curve" chase a unit to wherever it goes despite of the forces acting on it? How would the arc, the curve be affected?
It makes no sense, leaving me with few choices on how to proceed...
How should the create method look like? Should it be based on a module? (real x, real y, real angle, real distance)
Or should it be as it is now? (real ox, real oy, real tx, real ty)
Should homing projectiles ignore arc or curve?
Because of the way warcraft projectiles must be a lot of overhead in the physics needed arises, making the system a lot slower (such as the use of the parabola function and it's derivative)


New Member
Should homing projectiles ignore arc or curve?
In my opinion yes projectiles should either be arching or homing not both.
Because of the way warcraft projectiles must be a lot of overhead in the physics needed arises, making the system a lot slower (such as the use of the parabola function and it's derivative)
Maybe it should resemble more of a real physics system that's an requirement for the projectile library? Check some of the code here as it might be useful: http://www.hiveworkshop.com/forums/spells-569/see-simple-entity-engine-2-4-a-142085/


Thanks for the feedback, i think i'll let the user decide if homing projectiles have arcs or curves, i've done my best to prevent bugs regarding the animation.
Anyways i've been working on this system and did a whole bunch of changes, it wont be long until it's finished.
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    Friday Yay!
  • mgarcia mgarcia:
    did you guys catch Carl's interview? https://www.youtube.com/watch?v=kuHiMXABkGs
  • mgarcia mgarcia:
    he's the one that informed me about the DVD's working!
  • mgarcia mgarcia:
    he also mentioned the progress on the controllers!
  • The Helper The Helper:
    I did actually it was cool to see the NUON mention
  • The Helper The Helper:
    https://discord.com/channels/985377399338332202/985377399950696481 you can still use this chat too we are two fisted now :)
  • thewrongvine thewrongvine:
    costs me $80 to fill gas tank sad face
  • Ghan Ghan:
  • The Helper The Helper:
    Yeah that gas is some expensive stuff :)
  • The Helper The Helper:
    ghan does not have to worry about it he has a tesla and I have a small tank so it does not cost me usually more than 50
  • The Helper The Helper:
    fyi Ghan we are getting an error trying to access stats or world editor tutorials Error 526 Ray ID: 72128c6bf99f6707 • 2022-06-26 02:35:15 UTCInvalid SSL certificate
  • Ghan Ghan:
    An artifact of switching to Cloudflare... the Let's Encrypt certs can't autorenew through Cloudflare.
  • Ghan Ghan:
    I got the forum updated before things expired but there were some other casualties.
  • Ghan Ghan:
    Everything should be fixed now....
  • The Helper The Helper:
    Thank you Ghan!
  • The Helper The Helper:
    Happy Monday!
  • The Helper The Helper:
    new NUON forum mod cubanral!
  • The Helper The Helper:
  • tom_mai78101 tom_mai78101:
    Started learning how to make tools-assisted speedruns, so I'm lately busy.
  • tom_mai78101 tom_mai78101:
    Here's my current project.
  • O Old Mountain Shadow:
    that was pretty good!
  • jonas jonas:
    really cool! I saw the game before but always thought it's just a half as good double dragon. Now I realize it actually has a lot of depth!
  • The Helper The Helper:
    I just saw a bunch of running and jumping past all the enemies look like it was scripted as he said I guess I should have watched the whole thing. I only got a couple of minutes in. You should post that video in the forum Tom
  • Darthfett Darthfett:
    "Hi in the chat!"
  • tom_mai78101 tom_mai78101:
    Currently busy with making the run even better, so I'll post that new one once I'm done. Right now, I had to modify the emulator itself, the tools that makes this video, and some RAM address disassembling / reverse-engineering to get the right values, and such.

    Staff online

    Members online


    Hive Workshop NUON Dome World Editor Tutorials

    Network Sponsors

    Apex Steel Pipe - Buys and sells Steel Pipe.