Upcoming Projectile System

Dirac

22710180
JASS:
library ProjectileStruct /* v1.0.0

*/uses/* 
    */  LinkedListModule    /*
    */  optional AutoFly    /*
    
**********************************************************************/
globals
    //***********************************************************************
    //  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'
endglobals
/**********************************************************************
*
*   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.
*
**********************************************************************/

    globals
        private location GetZ = Location(0,0)
    endglobals
    
    struct Projectile extends array
        implement LinkedList
        
        static method operator first takes nothing returns thistype
            return base.next
        endmethod
        
        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")
        endmethod
        
        method operator model takes nothing returns string
            return fP
        endmethod
        
        method operator arc= takes real value returns nothing
            set height=Tan(value)*oD
        endmethod
        
        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')
            endif
            
            call SetUnitFlyHeight(dummy,oz,0)
            set fx=AddSpecialEffectTarget(model,dummy,"origin")
            return this
        endmethod
        
        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)
        endmethod
        
        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)
        endmethod
        
        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
        endmethod
        
    endstruct
    
    globals
        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
    endglobals
    
    private function Execute takes nothing returns nothing
        call TriggerEvaluate(FIRE)
    endfunction
    
    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])
        else
            call NODE[index].insertNode(this)
        endif
        if ACTIVE==0 then
            call TimerStart(TIMER,0.03125,true,function Execute)
        endif
        set ACTIVE=ACTIVE+1
        set INSTANCES[index]=INSTANCES[index]+1
    endfunction
    
    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
        endif
        call this.removeNode()
        set ACTIVE=ACTIVE-1
        if INSTANCES[index]==0 then
            call TriggerRemoveCondition(FIRE,COND[index])
        endif
        if ACTIVE==0 then
            call PauseTimer(TIMER)
        endif
    endfunction
    
    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)
            endif
        endmethod
        
        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]
            loop
                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
                        else
                            set this.angle=this.angle-this.turn
                        endif
                    else
                        set this.angle=a
                    endif
                    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)
                    endif
                else
                    if this.distanceFromTarget<=PROJECTILE_HIT_COLLISION then
                        call terminate(this,null)
                    endif
                endif            
                
                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)
                else
                    set x=this.currentX
                    set y=this.currentY
                endif
                
                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)
                endif
                
                call SetUnitX(this.dummy,x)
                call SetUnitY(this.dummy,y)
                
                if this.collides then
                    call GroupEnumUnitsInRange(bj_lastCreatedGroup,x,y,PROJECTILE_HIT_COLLISION,null)
                    loop
                        set u=FirstOfGroup(bj_lastCreatedGroup)
                        exitwhen u==null
                        call terminate(this,u)
                        call GroupRemoveUnit(bj_lastCreatedGroup,u)
                    endloop
                endif
                
                set this=this.next
                set i=i-1
                
            endloop
            
            set u=null
            return false
        endmethod
        
        static method launch takes Projectile this returns nothing
            call StartPeriodic(INDEX,this)
        endmethod
        
        private static method onInit takes nothing returns nothing
            set INDEX=COUNT+1
            set COUNT=INDEX
            set METHOD[INDEX]=Filter(function thistype.onPeriod)
        endmethod
        
    endmodule
    
endlibrary
JASS:
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
            else
                return true
            endif
        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))
        endif
        return false
    endmethod
    
    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)
    endmethod
    
    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)
    endmethod
    
endstruct
 
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 : )
 

Dirac

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

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

PurgeandFire

zxcvmkgdfg
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)
 

Dirac

22710180
  • 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.
 

luorax

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)
 

Dirac

22710180
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
 

Dirac

22710180
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)
 

Switch33

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/
 

Dirac

22710180
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.
  • C Cherry.Grove:
    My boyfriend is super into Bethesda modding so I'm probably going to join him in that for a bit, then move on to making mobile/browser games.
  • C Cherry.Grove:
    I mostly just want to look at my old projects because I know I posted uploads on here somewhere.
  • C Cherry.Grove:
    My one game was basically Among Us but you sabotaged an entire medieval city simulated drastically inefficiently with dynamic NPCs :S
  • C Cherry.Grove:
    of course I never finished it x D
  • jonas jonas:
    xD
  • jonas jonas:
    I think you can still run the original game, just there's a chance your map won't work with the newer patches
  • jonas jonas:
    what development framework have you been looking at for mobile development?
  • jonas jonas:
    I'm currently creating (as a hobby) a space invaders like game for mobile using Xamarin, after starting a few larger projects that I also may not have finished :rolleyes::p
  • The Helper The Helper:
    Awesome! you should look at the Atari VCS platform it does not have a huge user base but the one it has is super active at around 11k and they have no games in there store
  • The Helper The Helper:
    basically it is linux
  • The Helper The Helper:
    OMG it is BanLord as Blackveiled on the forum
  • The Helper The Helper:
    what is up buddy
  • jonas jonas:
    thanks for the tip, that sounds cool. Never heard about the VCS before but seems like it should be a good match
  • tom_mai78101 tom_mai78101:
    The winter vacation left a toll on me. I missed staying up late and sleeping in late.
  • C Cherry.Grove:
    i haven't looked into it yet
  • C Cherry.Grove:
    If I could use something Python compatible it would save me some learning
  • C Cherry.Grove:
    But Unity might be easiest
  • C Cherry.Grove:
    looking at old posts here makes me happy. Makes me be all like "wow I was pretty smart in 2009"
  • C Cherry.Grove:
    mostly failing at life through adulthood really demotivated me for a while
  • C Cherry.Grove:
    ....what I don't get is why the fuck they would force us to permanently convert to a version of the game everyone fucking hated : \
  • C Cherry.Grove:
    Raid Shadow Legends texture pack smh
  • jonas jonas:
    Most people fail at life throughout their adulthood. Sometimes we get to learn from it. Usually it just feels like crap.
    +1
  • The Helper The Helper:
    What does not kill you makes you stronger is what they say
  • C Cherry.Grove:
    for real! :cool:

    Staff online

    Members online

    Affiliates

    Hive Workshop NUON Dome
    Top