• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] Projectile2D

Level 31
Joined
Jul 10, 2007
Messages
6,306
edit
Do not use this ; O. I am planning on a major revision for v2 as I think that this is designed wrong. It'll get rid of things like homing.

JASS:
library Projectile2D /* v1.0.3.3
*************************************************************************************
*
*   Simple 2D projectiles that are relatively flexible and very efficient.
*
*************************************************************************************
*
*   */uses/*
*
*       */ Position             /*      hiveworkshop.com/forums/jass-resources-412/snippet-position-184578/
*       */ PositionCollision    /*      
*       */ CTL                  /*      hiveworkshop.com/forums/jass-resources-412/snippet-constant-timer-loop-32-a-201381/
*       */ Particle             /*      hiveworkshop.com/forums/submissions-414/snippet-needs-work-particle-206279/
*       */ UnitInRangeEvent     /*      hiveworkshop.com/forums/jass-resources-412/snippet-unitinrangeevent-205036/
*
*************************************************************************************
*
*   struct Projectile2D extends array
*
*       Fields
*       -------------
*
*           readonly static Projectile2D eventProjectile
*
*           readonly Particle particle
*
*           Position target           
*           real angle
*           real turnRate
*           real speed
*           real acceleration
*           real accelerationAngle
*
*       Methods
*       -------------
*
*           static method start takes Particle particle, Position target, real turnRate, real speed, real angle, real lifespan, code onCollide, boolexpr onDestroy, boolexpr onResolve returns nothing
*
*               -   onCollide:  Runs when projectile collides with any non locust unit
*                               Does not do onCollide detection if this is null
*                               Does not destroy the projectile
*
*               -   onDestroy:  Runs when projectile is destroyed
*                               Must Return True
*
*               -   onResolve:  Runs when projectile runs into the target
*                               Does not do target collision detection if this is null
*                               Does not destroy the projectile, you must destroy it yourself if you want it destroyed
*                               Must Return True
*
*           method destroy takes nothing returns nothing
*
*************************************************************************************
*
*   module Projectile2D
*
*       Fields
*       -------------
*
*           readonly static integer count
*           static method operator [] takes integer index returns thistype
*
*       Methods
*       -------------
*
*           static method start takes Particle particle, Position target, real turnRate, real speed, real angle, real lifespan returns nothing
*           method destroy takes nothing returns nothing
*
*       Interface
*       -------------
*
*           private method onCollided takes unit whichUnit returns nothing      (optional)
*           private method onResolved takes nothing returns nothing             (optional)
*           private method onDestroyed takes nothing returns nothing            (optional)
*           private method onMove takes nothing returns nothing                 (optional)
*
*************************************************************************************/
    private struct Projectile2D_p extends array
        method operator particle takes nothing returns Particle
            return this
        endmethod
    
        static thistype eventProjectile = 0
    
        private static force evalForce = CreateForce()
        static Table table
        
        timer lifetimer
        Position target
        real angle
        real turnRate
        real speed
        real dx
        real dy
        real ox
        real oy
        real ax
        real ay
        real px
        real py
        integer rangeEvent
        real tcollision
        real pcollision
        real cturnRate
        real targetAngle
        real acceleration
        real accelerationAngle
        
        boolexpr onDestroy
        boolexpr onResolve
        
        static integer count = 0
        static integer array values
        static integer array indexes
        
        method destroy takes nothing returns nothing
            local integer index
            local integer last
            
            if (null != onDestroy) then
                set index = eventProjectile
                set eventProjectile = this
                call ForceEnumPlayersCounted(evalForce, onDestroy, 1)
                set eventProjectile = index
            endif
        
            call target.unlock()
            if (0 != rangeEvent) then
                call UnregisterUnitInRangeEvent(rangeEvent)
                set rangeEvent = 0
            endif
            
            call PauseTimer(lifetimer)
            
            set count = count - 1
            if (0 == count) then
                call stop()
            else
                set index = indexes[particle]
                set last = values[count]
                
                set values[index] = last
                set indexes[last] = index
            endif
            
            call Particle(this).destroy()
        endmethod
        
        implement CT32
            local thistype this
            local real x
            local real y
            local real tx
            local real ty
            local real xd
            local real yd
            local real direction
            local real angle
            local real distance
            local boolean hit
            local integer current = count
            local real collision
            
            loop
                exitwhen 0 == current
                set current = current - 1
                set this = values[current]
                
                /*
                *   Retrieve coordinates
                */
                set x = particle.x
                set y = particle.y
                set tx = target.x
                set ty = target.y
                    
                /*
                *   If can resolve, calculate for possible collision with target
                */
                if (null != onResolve) then
                    set xd = tx - x
                    set yd = ty - y
                    set distance = xd*xd + yd*yd
                    set collision = pcollision + tcollision
                    
                    set hit = distance < collision*collision
                    if (hit) then
                        set eventProjectile = this
                        call ForceEnumPlayersCounted(evalForce, onResolve, 1)
                        set eventProjectile = 0
                    endif
                else
                    set hit = false
                endif
                
                /*
                *   If didn't resolve, translate and possibly home
                */
                if (not hit) then
                    /*
                    *   If homing and the target moved, home in
                    */
                    if (0 != acceleration) then
                        set dx = dx + ax
                        set dy = dy + ay
                        set speed = SquareRoot(dx*dx + dy*dy)
                        set this.angle = Atan2(dy, dx)
                    endif
                    
                    if (0 != turnRate) then
                        set angle = this.angle
                        if (tx != ox or ty != oy or px != x or py != y) then
                            set targetAngle = Atan2(ty - y, tx - x)
                            set ox = tx
                            set oy = ty
                        elseif (0 != acceleration) then
                            set targetAngle = Atan2(ty - y, tx - x)
                        endif
                        if (angle != targetAngle) then
                            set direction = targetAngle
                            if (Cos(angle - direction) < cturnRate) then
                                if (0 < Sin(direction - angle)) then
                                    set angle = angle + turnRate
                                else
                                    set angle = angle - turnRate
                                endif
                                set particle.xyAngle = angle
                                set this.angle = angle
                                set dx = speed*Cos(angle)
                                set dy = speed*Sin(angle)
                            else
                                set particle.xyAngle = direction
                                set this.angle = direction
                                set dx = speed*Cos(direction)
                                set dy = speed*Sin(direction)
                            endif
                        endif
                        set x = x + dx
                        set y = y + dy
                        set px = x
                        set py = y
                    else
                        set x = x + dx
                        set y = y + dy
                    endif
                    
                    if (x > WorldBounds.maxX or x < WorldBounds.minX or y > WorldBounds.maxY or y < WorldBounds.minY) then
                        call destroy()
                    else
                        set particle.x = x
                        set particle.y = y
                    endif
                endif
            endloop
        implement CT32End
        
        static method expire takes nothing returns nothing
            call thistype(table[GetHandleId(GetExpiredTimer())]).destroy()
        endmethod
        
        private static method onInit takes nothing returns nothing
            set table = Table.create()
        endmethod
    endstruct
    
    struct Projectile2D extends array
        static method operator eventProjectile takes nothing returns Projectile2D
            return Projectile2D_p.eventProjectile
        endmethod
        method operator particle takes nothing returns Particle
            return this
        endmethod
        method operator target takes nothing returns Position
            return Projectile2D_p(this).target
        endmethod
        method operator target= takes Position target returns nothing
            call Projectile2D_p(this).target.unlock()
            set Projectile2D_p(this).target = target
            call target.lock()
        endmethod
        method operator angle takes nothing returns real
            return Projectile2D_p(this).angle
        endmethod
        method operator angle= takes real val returns nothing
            set Projectile2D_p(this).angle = val
            set Projectile2D_p(this).dx = Projectile2D_p(this).speed*Cos(val)
            set Projectile2D_p(this).dy = Projectile2D_p(this).speed*Sin(val)
        endmethod
        method operator turnRate takes nothing returns real
            return Projectile2D_p(this).turnRate*32
        endmethod
        method operator turnRate= takes real turnRate returns nothing
            set Projectile2D_p(this).turnRate = turnRate*.031250000
            set Projectile2D_p(this).cturnRate = Cos(Projectile2D_p(this).turnRate)
        endmethod
        method operator speed takes nothing returns real
            return Projectile2D_p(this).speed*32
        endmethod
        method operator speed= takes real value returns nothing
            set Projectile2D_p(this).speed = value*.031250000
            set Projectile2D_p(this).dx = Projectile2D_p(this).speed*Cos(Projectile2D_p(this).angle)
            set Projectile2D_p(this).dy = Projectile2D_p(this).speed*Sin(Projectile2D_p(this).angle)
        endmethod
        method operator acceleration takes nothing returns real
            return Projectile2D_p(this).acceleration*32
        endmethod
        method operator acceleration= takes real value returns nothing
            set Projectile2D_p(this).acceleration = value*.031250000
            set Projectile2D_p(this).ax = Projectile2D_p(this).acceleration*Cos(Projectile2D_p(this).accelerationAngle)
            set Projectile2D_p(this).ay = Projectile2D_p(this).acceleration*Sin(Projectile2D_p(this).accelerationAngle)
        endmethod
        method operator accelerationAngle takes nothing returns real
            return Projectile2D_p(this).accelerationAngle
        endmethod
        method operator accelerationAngle= takes real value returns nothing
            set Projectile2D_p(this).accelerationAngle = value
            set Projectile2D_p(this).ax = Projectile2D_p(this).acceleration*Cos(Projectile2D_p(this).accelerationAngle)
            set Projectile2D_p(this).ay = Projectile2D_p(this).acceleration*Sin(Projectile2D_p(this).accelerationAngle)
        endmethod
        static method start takes Particle particle, Position target, real turnRate, real speed, real angle, real lifespan, code onCollide, boolexpr onDestroy, boolexpr onResolve returns nothing
            if (0 == Projectile2D_p.count) then
                call Projectile2D_p.start()
            endif
            
            set Projectile2D_p.values[Projectile2D_p.count] = particle
            set Projectile2D_p.indexes[particle] = Projectile2D_p.count
            set Projectile2D_p.count = Projectile2D_p.count + 1
            
            call target.lock()
            
            set Projectile2D_p(particle).target = target
            set Projectile2D_p(particle).turnRate = turnRate*.031250000
            set Projectile2D_p(particle).cturnRate = Cos(Projectile2D_p(particle).turnRate)
            set Projectile2D_p(particle).speed = speed*.031250000
            set Projectile2D_p(particle).angle = angle
            set Projectile2D_p(particle).dx = Projectile2D_p(particle).speed*Cos(angle)
            set Projectile2D_p(particle).dy = Projectile2D_p(particle).speed*Sin(angle)
            set Projectile2D_p(particle).ox = target.x
            set Projectile2D_p(particle).oy = target.y
            set Projectile2D_p(particle).px = particle.x
            set Projectile2D_p(particle).py = particle.y
            set Projectile2D_p(particle).onResolve = onResolve
            set Projectile2D_p(particle).pcollision = particle.collision
            set Projectile2D_p(particle).tcollision = target.collision
            set Projectile2D_p(particle).targetAngle = angle
            set Projectile2D_p(particle).ax = 0
            set Projectile2D_p(particle).ay = 0
            set Projectile2D_p(particle).acceleration = 0
            set Projectile2D_p(particle).accelerationAngle = 0
            set particle.xyAngle = angle
            
            if (null != onCollide) then
                set Projectile2D_p(particle).rangeEvent = RegisterUnitInRangeEvent(onCollide, particle.unit, particle.collision + 16)
            endif
            
            set Projectile2D_p(particle).onDestroy = onDestroy
            
            if (0 < lifespan) then
                if (null == Projectile2D_p(particle).lifetimer) then
                    set Projectile2D_p(particle).lifetimer = CreateTimer()
                    set Projectile2D_p.table[GetHandleId(Projectile2D_p(particle).lifetimer)] = particle
                endif
                
                call TimerStart(Projectile2D_p(particle).lifetimer, lifespan, false, function Projectile2D_p.expire)
            endif
        endmethod
        method destroy takes nothing returns nothing
            call Projectile2D_p(this).destroy()
        endmethod
    endstruct
    
    module Projectile2D
        static if thistype.onMove.exists then
            private static TimerGroup32 timerGroup
        endif
        
        private static integer array values
        private static integer array indexes
        readonly static integer count = 0
        
        private static code onCollided_c = null
        private static boolexpr onResolved_c = null
        private static boolexpr onDestroyed_c
        
        static method operator [] takes integer index returns thistype
            return values[index]
        endmethod
        
        static if thistype.onCollided.exists then
            private static method onProjectileCollide_p takes nothing returns nothing
                call thistype(GetEventSourceUnitId()).onCollided(GetTriggerUnit())
            endmethod
        endif
        static if thistype.onResolved.exists then
            private static method onProjectileResolve_p takes nothing returns boolean
                call thistype(Projectile2D.eventProjectile).onResolved()
                return true
            endmethod
        endif
        private static method onProjectileDestroyed_p takes nothing returns boolean
            local thistype projectile = Projectile2D.eventProjectile
            local integer index
            local integer last
            
            set count = count - 1
            set index = indexes[projectile]
            set last = values[count]
            set values[index] = last
            set indexes[last] = index
            
            static if thistype.onMove.exists then
                if (0 == count) then
                    call timerGroup.stop()
                endif
            endif
            
            static if thistype.onDestroyed.exists then
                call projectile.onDestroyed()
            endif
            
            return true
        endmethod
        
        static if thistype.onMove.exists then
            private static method periodic_p takes nothing returns nothing
                local integer current = count
                loop
                    exitwhen 0 == current
                    set current = current - 1
                    call thistype(values[current]).onMove()
                endloop
            endmethod
        endif
    
        static method start takes Particle particle, Position target, real turnRate, real speed, real angle, real lifespan returns nothing
            call Projectile2D.start(particle, target, turnRate, speed, angle, lifespan, onCollided_c, onDestroyed_c, onResolved_c)
            
            static if thistype.onMove.exists then
                if (0 == count) then
                    call timerGroup.start()
                endif
            endif
            
            set values[count] = particle
            set indexes[particle] = count
            set count = count + 1
        endmethod
        method destroy takes nothing returns nothing
            call Projectile2D(this).destroy()
        endmethod
        
        private static method onInit takes nothing returns nothing
            static if thistype.onMove.exists then
                set timerGroup = TimerGroup32.create(function thistype.periodic_p)
            endif
            
            set onDestroyed_c = Condition(function thistype.onProjectileDestroyed_p)
            
            static if thistype.onCollided.exists then
                set onCollided_c = function thistype.onProjectileCollide_p
            endif
            
            static if thistype.onResolved.exists then
                set onResolved_c = Condition(function thistype.onProjectileResolve_p)
            endif
        endmethod
    endmodule
endlibrary

Demonstration
JASS:
struct Tester extends array
    private static unit u
    private static unit u2
    private static Projectile2D projectile
    
    private static method onResolve takes nothing returns boolean
        if (projectile.target.unit == u) then
            set projectile.target = Position[u2]
        else
            set projectile.target = Position[u]
        endif
    
        return true
    endmethod
    
    private static method onCollide takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Collided with "+GetUnitName(GetTriggerUnit()))
    endmethod
    
    private static method onInit takes nothing returns nothing
        set u = CreateUnit(Player(0), 'hfoo', GetStartLocationX(0) - 512, GetStartLocationY(0), 270)
        set u2 = CreateUnit(Player(0), 'hfoo', GetStartLocationX(0) + 512, GetStartLocationY(0), 270)
        set projectile = Particle.create(Player(0), GetUnitX(u), GetUnitY(u), 0, "units\\nightelf\\Wisp\\Wisp.mdl", 32)
        call Projectile2D.start(projectile, Position[u2], bj_PI, 128, 0, 0, function thistype.onCollide, null, Condition(function thistype.onResolve))
    endmethod
endstruct
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Well, i can't test the map now, but i'm sure that even if it's used properly (returns true to add an enumed player), the integer argument is 100 % useless, its value won't be taken in consideration, all EnumCounted functions are just a wrapper of their not Counted function equivalent.

To test it, you need it at least 2 human players, as ForceEnum takes in consideration only human playing players.

So your pseudo code is wrong, plus in jass, safety is common for native functions (negative integer argument you know), so it would be count > 0
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Ok... you guys don't know how to use the native... I taught mag how it works, so he knows how to use it now..

count is how many players you want to be in the force... obviously if you return false, the force will never reach that count, so it'll enumerate over all players. If you use a null force, it'll never reach that count as well obviously... Are you guys stupid or what to not figure that out? I figured that out after using it one time when I returned false.


Now stop with this really stupid argument before somebody gets shot.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Well, I figured it out almost immediately and you guys didn't... so that's why I say that... plus you guys are arguing about a native when I'm obviously using it without problems in a map that contains computers and 1 human.

Plus you guys are saying that it only works on humans when I got it to run on computers. Either my wc3 is magical or you guys are totally wrong..
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I can attest that this works. If player 12 (brown) is a computer, ForceEnumPlayers will call the boolexpr twice, however ForceEnumPlayersCounted will call the boolexpr ONCE.

I have tested it with GroupEnumUnitsOfTypeCounted and I can confirm it still bugs the same (no matter true or false, it doesn't stop at 1).

Edit: I can also confirm that ForceEnumPlayersCounted is roughly the same speed as a TriggerEvaluate. I think we can soon say good-bye to trigger evaluations.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Well, first all other Enum...Counted functions are bugged as you can test them by yourself :

JASS:
scope Test initializer init

    globals
        private force F = CreateForce()
    endglobals
    
    private function Test takes nothing returns boolean
        call BJDebugMsg("enum")
        return true
    endfunction

    private function init takes nothing returns nothing
        call CreateUnit(Player(0),'hfoo',0,0,0)
        call CreateUnit(Player(0),'hfoo',0,0,0)
        call GroupEnumUnitsInRangeCounted(CreateGroup(),0,0,1000,Filter(function Test),1)
        call BJDebugMsg(" ")
        call GroupEnumUnitsInRangeOfLocCounted(CreateGroup(),Location(0,0),1000,Filter(function Test),1)
        call BJDebugMsg(" ")
        call GroupEnumUnitsInRectCounted(CreateGroup(),GetWorldBounds(),Filter(function Test),1)
        call BJDebugMsg(" ")
        call GroupEnumUnitsOfTypeCounted(CreateGroup(),UnitId2String('hfoo'),Filter(function Test),1)

    endfunction
    
endscope

That's why i assumed ForceEnumPlayersCounted didn't work as well.
I said it should be tested, then watermelon_1234 and Bribe did a test.
They both said that it didn't work.
My error was also to read Mag's code too quickly.
Unfortunately they didn't shared their test code (i rarely can open a map).
In fact i just assumed they did it right, because as you said, it's obvious that the integer argument is meant for the players added, not enumed.

I've myself reported many bugs, and was never wrong, but i did a mistake here about the players enumed.
Indeed if you don't choice the option "choice fixed player position" (i don't really know the english name) in the window "player settings" of the editor, then all reserved slots are not used, then it obviously doesn't enum all players.

The problem is not that you're right, it's even really good here, but you're just a douchebag when you are.
I was several times right against you and never i acted like you.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Oh sry, I didn't know about the GroupEnumCounted bugs as I've never used them ;o.


Only counted native I've used thus far was the ForceEnumPlayersCounted ; )


I can see why you guys would automatically assume that it's broken ;p. What I'm thinking is that the only broken counted natives are the group ones. I believe that all of the force counted natives work ;o. Don't take my word on that because I haven't tested them all.


It's just really irritating when someone tells me that something works when I obviously have it working w/o bugs... lol

Just an example (a stupd one at that).

Imagine I submit a resource that sets a real to an integer. Someone then replies, "you can't set a real to an integer fool." I then say, well, I obviously can... they then say, "no you can't." That sort of stuff is just really aggravating, and that's what happened in this thread.

Keep this in mind... before I submit a resource, I make sure to test every situation that I am unsure of. Now with the enum, I obviously didn't go out of my way to test it, I just happened to run into it while making DamageModificationEffect (multiple players in the map), and I fixed it accordingly.

Before I submitted Projectile2D, I made sure that the demonstration code underneath it worked. Sure it doesn't test things like recycling, but I don't need to test those... I just needed to make sure that the homing worked.

Now, there is a problem with the new version. Acceleration changes the angle, meaning that if a projectile has acceleration, I have to recalculate its angle accordingly. I am not doing that atm, so the angles are becoming messed up ;o.

I'm actually going to be fixing it right now ; ).


I will not be able to test the update since I forgot to upload the map with all of the req'd resources online and I don't want to create a new test map (I'm on my laptop atm and it doesn't have the map).

edit
acceleration should now work properly. Haven't tested, but will test later when I get home.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
All right, here is my current work towards a good projectile system. I haven't tested or run it at all yet and I'm not sure about the efficiency atm, but here we go ;o.

The implementation is semi slow because I am allowing people to pass in positions for both the projectiles and the targets, which is pretty cool ^_^.

Only translation and acceleration are in the core. Everything is a separate resource ^_^.

Things like the gravity of a planet would just apply a z acceleration to the position. I probably need to make sure that the position can't go lower than 0 for the z.
Things like gravity between two points would apply an acceleration to the position based on the distance between the position and the source of gravity

You can also do homing forces, meaning that a projectile could be homing in on 2 targets ^_^.

I split up external velocity and the unit's own velocity. I know it's not realistic to do this, but it allows people to have proper homing projectiles without lots of complications. The internal speed of the projectile is never changed. I should probably make propulsion modify the internal speed rather than the external speed. It's just weird with an internal speed right now ^_^.

I'm just putting this up because I wanted to show people the design =o.

Projectile
JASS:
library Projectile uses Position
    struct Projectile extends array
        private method operator projectile takes nothing returns Position
            return this
        endmethod
        
        private static location zloc = Location(0, 0)
    
        static thistype eventProjectile = 0
    
        private static force evalForce = CreateForce()
        
        Position target
        
        /*
        *   change in position
        */
        real dx
        real dy
        real dz
        
        real x
        real y
        real z
        real theta
        real phi
        
        /*
        *   unit speed and propulsion
        *   based on unit facing and phi
        */
        real speed
        real propulsion
        
        real targetCollision
        real projectileCollision
        
        boolexpr onDestroy
        boolexpr onResolve
        
        static integer unitCount = 0
        static integer array unitValues
        static integer array unitIndexes
        
        static integer itemCount = 0
        static integer array itemValues
        static integer array itemIndexes
        
        method deallocateUnit takes nothing returns nothing
            set unitValues[unitIndexes[this]] = unitValues[unitCount]
            set unitIndexes[unitValues[unitCount]] = unitIndexes[this]
            set unitCount = unitCount - 1
        endmethod
        method deallocateItem takes nothing returns nothing
            set itemValues[itemIndexes[this]] = itemValues[itemCount]
            set itemIndexes[itemValues[itemCount]] = itemIndexes[this]
            set itemCount = itemCount - 1
        endmethod
        method destroy takes nothing returns nothing
            local integer prevProjectile
            
            if (null != onDestroy) then
                set prevProjectile = eventProjectile
                call ForceEnumPlayersCounted(evalForce, onDestroy, 1)
                set eventProjectile = prevProjectile
            endif
            
            if (projectile.isItem) then
                call deallocateItem()
            else
                call deallocateUnit()
            endif
            
            if (0 == itemCount + unitCount) then
                call stop()
            endif
            
            call target.unlock()
            call projectile.unlock()
        endmethod
        
        implement CT32
            /*
            *   Projectile Coordinates
            */
            local real x
            local real y
            local real z
            
            /*
            *   Target coordinates
            */
            local real targetX
            local real targetY
            local real targetZ
            
            local thistype this
            local Position target
            local integer targetType
            
            local real sinPhi
            local real sinTheta
            local real cosTheta
            
            local real distanceX
            local real distanceY
            local real distanceZ
            local real distance2
            
            local integer current
            
            local boolean translate
            
            local real fspeed
            local real facceleration
            local real radius
            
            local thistype fTarget
            local Position fSource
            
            local real rate
            
            local real theta
            local real phi
            
            /*
            *   Iterate over all unit positions
            */
            set current = unitCount
            loop
                exitwhen 0 == current
                set this = unitValues[current]
                set current = current - 1
                
                set target = this.target
                set targetType = target.type
                
                if (targetType > Position.ITEM) then
                    if (targetType > Position.LOC) then
                        set targetX = target.posX
                        set targetY = target.posY
                        set targetZ = target.posZ
                    else
                        set targetX = GetLocationX(target.location)
                        set targetY = GetLocationY(target.location)
                        set targetZ = 0
                    endif
                    
                    call MoveLocation(zloc, targetX, targetY)
                    set targetZ = targetZ + GetLocationZ(zloc)
                    
                    set translate = true
                elseif (targetType < Position.ITEM) then
                    if (targetType == Position.UNIT) then
                        if (0 != GetUnitTypeId(target.unit)) then
                            set targetX = GetUnitX(target.unit)
                            set targetY = GetUnitY(target.unit)
                            
                            call MoveLocation(zloc, targetX, targetY)
                            set targetZ = GetUnitFlyHeight(target.unit) + GetLocationZ(zloc)
                            
                            set translate = true
                        else
                            set translate = false
                        endif
                    else
                        if (0 != GetDestructableTypeId(target.destructable)) then
                            set targetX = GetDestructableX(target.destructable)
                            set targetY = GetDestructableY(target.destructable)
                            
                            call MoveLocation(zloc, targetX, targetY)
                            set targetZ = GetLocationZ(zloc)
                            
                            set translate = true
                        else
                            set translate = false
                        endif
                    endif
                else
                    if (0 != GetItemTypeId(target.item)) then
                        set targetX = ItemGetX(target.item)
                        set targetY = ItemGetY(target.item)
                        set targetZ = ItemGetZ(target.item)
                        
                        set translate = true
                    else
                        set translate = false
                    endif
                endif
                
                if (translate) then
                    set x = GetUnitX(projectile.unit)
                    set y = GetUnitY(projectile.unit)
                    
                    call MoveLocation(zloc, x, y)
                    set z = GetUnitFlyHeight(projectile.unit) + GetLocationZ(zloc)
                    
                    /*
                    *   Update stored coordinate data for use with forces
                    */
                    set this.x = x*x
                    set this.y = y*y
                    set this.z = z*z
                    set this.theta = GetUnitFacing(projectile.unit)
                    set this.phi = projectile.facingZ
                    
                    if (null != onResolve) then
                        /*
                        *   Calculate squared distance between target and projectile
                        */
                        set distanceX = targetX - x
                        set distanceY = targetY - y
                        set distanceZ = targetZ - z
                        set distance2 = distanceX*distanceX + distanceY*distanceY + distanceZ*distanceZ - targetCollision*targetCollision - projectileCollision*projectileCollision
                    
                        if (distance2 < .1) then
                            call ForceEnumPlayersCounted(evalForce, onResolve, 1)
                        endif
                    endif
                    
                    /*
                    *   Translate projectile
                    */
                    if (0 != speed) then
                        set sinPhi = Sin(this.phi)

                        call SetUnitX(projectile.unit, x + dx + speed*Cos(this.theta)*sinPhi)
                        call SetUnitY(projectile.unit, y + dy + speed*Sin(this.theta)*sinPhi)
                        call SetUnitFlyHeight(projectile.unit, z + dz + speed*sinPhi, 0)
                    else
                        call SetUnitX(projectile.unit, x + dx)
                        call SetUnitY(projectile.unit, y + dy)
                        call SetUnitFlyHeight(projectile.unit, z + dz, 0)
                    endif
                    
                    if (0 != propulsion) then
                        set speed = speed + propulsion
                    endif
                else
                    call destroy()
                endif
            endloop
            
            /*
            *   Iterate over all item positions
            */
            set current = itemCount
            loop
                exitwhen 0 == current
                set this = itemValues[current]
                set current = current - 1
                
                set target = this.target
                set targetType = target.type
                
                if (targetType > Position.ITEM) then
                    if (targetType > Position.LOC) then
                        set targetX = target.posX
                        set targetY = target.posY
                        set targetZ = target.posZ
                    else
                        set targetX = GetLocationX(target.location)
                        set targetY = GetLocationY(target.location)
                        set targetZ = 0
                    endif
                    
                    call MoveLocation(zloc, targetX, targetY)
                    set targetZ = targetZ + GetLocationZ(zloc)
                    
                    set translate = true
                elseif (targetType < Position.ITEM) then
                    if (targetType == Position.UNIT) then
                        if (0 != GetUnitTypeId(target.unit)) then
                            set targetX = GetUnitX(target.unit)
                            set targetY = GetUnitY(target.unit)
                            
                            call MoveLocation(zloc, targetX, targetY)
                            set targetZ = GetUnitFlyHeight(target.unit) + GetLocationZ(zloc)
                            
                            set translate = true
                        else
                            set translate = false
                        endif
                    else
                        if (0 != GetDestructableTypeId(target.destructable)) then
                            set targetX = GetDestructableX(target.destructable)
                            set targetY = GetDestructableY(target.destructable)
                            
                            call MoveLocation(zloc, targetX, targetY)
                            set targetZ = GetLocationZ(zloc)
                            
                            set translate = true
                        else
                            set translate = false
                        endif
                    endif
                else
                    if (0 != GetItemTypeId(target.item)) then
                        set targetX = ItemGetX(target.item)
                        set targetY = ItemGetY(target.item)
                        set targetZ = ItemGetZ(target.item)
                        
                        set translate = true
                    else
                        set translate = false
                    endif
                endif
                
                if (translate) then
                    set x = ItemGetX(projectile.item)
                    set y = ItemGetY(projectile.item)
                    set z = ItemGetZ(projectile.item)
                    
                    /*
                    *   Update stored coordinate data for use with forces
                    */
                    set this.x = x*x
                    set this.y = y*y
                    set this.z = z*z
                    set this.theta = projectile.facing
                    set this.phi = projectile.facingZ
                    
                    if (null != onResolve) then
                        /*
                        *   Calculate squared distance between target and projectile
                        */
                        set distanceX = targetX - x
                        set distanceY = targetY - y
                        set distanceZ = targetZ - z
                        set distance2 = distanceX*distanceX + distanceY*distanceY + distanceZ*distanceZ - targetCollision*targetCollision - projectileCollision*projectileCollision
                    
                        if (distance2 < .1) then
                            call ForceEnumPlayersCounted(evalForce, onResolve, 1)
                        endif
                    endif
                    
                    /*
                    *   Translate projectile
                    */
                    if (0 != speed) then
                        set sinPhi = Sin(this.phi)
                        
                        call ItemSetPosZ(projectile.item, x + dx + speed*Cos(this.theta)*sinPhi, y + dy + speed*Sin(this.theta)*sinPhi, z + dz + speed*sinPhi, true)
                    else
                        call ItemSetPosZ(projectile.item, x + dx, y + dy, z + dx, true)
                    endif
                    
                    if (0 != propulsion) then
                        set speed = speed + propulsion
                    endif
                else
                    call destroy()
                endif
            endloop
        implement CT32End
        
        static method allocateUnit takes thistype projectile, Position target returns boolean
            local thistype this
            
            if (Position(projectile).lock()) then
                if (target.lock()) then
                    set this = unitCount + 1
                    set unitCount = this
                    
                    set unitIndexes[projectile] = this
                    set unitValues[this] = projectile
                    
                    set projectile.target = target
                    set projectile.targetCollision = target.collision
                    set projectile.projectileCollision = projectile.projectile.collision
                    
                    return true
                else
                    call Position(projectile).unlock()
                endif
            endif
            
            return false
        endmethod
        static method allocateItem takes thistype projectile, Position target returns boolean
            local thistype this
            
            if (Position(projectile).lock()) then
                if (target.lock()) then
                    set this = itemCount + 1
                    set itemCount = this
                    
                    set itemIndexes[projectile] = this
                    set itemValues[this] = projectile
                    
                    set projectile.target = target
                    set projectile.targetCollision = target.collision
                    set projectile.projectileCollision = projectile.projectile.collision
                    
                    return true
                else
                    call Position(projectile).unlock()
                endif
            endif
            
            return false
        endmethod
        
        static method allocate takes Position projectile, Position target returns nothing
            if (projectile.isUnit) then
                if (not allocateUnit(projectile, target)) then
                    return
                endif
            elseif (projectile.isItem) then
                if (not allocateItem(projectile, target)) then
                    return
                endif
            else
                return
            endif
            
            if (1 == itemCount + unitCount) then
                call start()
            endif
        endmethod
    endstruct
endlibrary

And two libs that use it
JASS:
library HomingForce uses Projectile
    struct HomingForce extends array
        private static integer instanceCount = 0
        private static integer array recycler
        private Position source
        private Position target
        
        private thistype prev
        private thistype next
        
        private static integer count = 0
        
        real rate
        
        private static method allocate takes nothing returns thistype
            local thistype this = recycler[0]
            if (0 == this) then
                set this = instanceCount + 1
                set instanceCount = this
            else
                set recycler[0] = recycler[this]
            endif
            
            set next = thistype(0).next
            set prev = 0
            set thistype(0).next.prev = this
            set thistype(0).next = this
            
            if (0 == count) then
                call start()
            endif
            set count = count + 1
            
            return this
        endmethod
        
        private method deallocate takes nothing returns nothing
            set prev.next = next
            set next.prev = prev
        
            set recycler[this] = recycler[0]
            set recycler[0] = this
            
            set count = count - 1
            if (0 == count) then
                call stop()
            endif
        endmethod
        
        static method create takes Position source, Position target, real rate returns thistype
            local thistype this = allocate()
            
            set this.rate = rate
            set this.source = source
            set this.target = target
            
            call source.lock()
            call target.lock()
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            call source.unlock()
            call target.unlock()
            
            call deallocate()
        endmethod
        
        implement CT32
            local thistype this
        
            local Projectile target
            local Position source
            
            local real rate
            
            local real distanceX
            local real distanceY
            local real distanceZ
            
            local real distance2
            
            local real theta
            local real phi
        
            /*
            *   Iterate over homing forces
            */
            set this = thistype(0).next
            loop
                exitwhen 0 == this
                
                set target = this.target
                set source = this.source
                
                if (source.valid) then
                    if (Position(target).valid) then
                        set rate = this.rate
                        
                        set distanceX = target.x - source.x
                        set distanceY = target.y - source.y
                        set distanceZ = target.z - source.z
                        set distance2 = distanceX*distanceX + distanceY*distanceY + distanceZ*distanceZ
                        
                        set theta = Atan2(distanceY, distanceX)
                        set phi = distance2/(distanceZ*distanceZ)
                        
                        if (Cos(target.theta - theta) < rate) then
                            if (0 < Sin(theta - target.theta)) then
                                set target.theta = target.theta + rate
                            else
                                set target.theta = target.theta - rate
                            endif
                        endif
                        
                        set Position(target).facing = target.theta
                        
                        if (Cos(target.phi - phi) < rate) then
                            if (0 < Sin(phi - target.phi)) then
                                set target.phi = target.phi + rate
                            else
                                set target.phi = target.phi - rate
                            endif
                        endif
                        
                        set Position(target).facingZ = target.phi
                    else
                        call deallocate()
                        call source.unlock()
                    endif
                else
                    call deallocate()
                    call Position(target).unlock()
                endif
                
                set this = next
            endloop
        implement CT32End
    endstruct
endlibrary

JASS:
library GravityForce uses Projectile
    struct GravityForce extends array
        private static integer instanceCount = 0
        private static integer array recycler
        private Position source
        private Position target
        
        private thistype prev
        private thistype next
        
        private static integer count = 0
        
        real acceleration
        real radius
        real speed
        
        real dx
        real dy
        real dz
        
        boolean relative
        
        private method deallocate takes nothing returns nothing
            set prev.next = next
            set next.prev = prev
        
            set recycler[this] = recycler[0]
            set recycler[0] = this
            
            set count = count - 1
            if (0 == count) then
                call stop()
            endif
        endmethod
        
        implement CT32
            local thistype this
            
            local Position source
            local Projectile target
            
            local real radius
            local real acceleration
            local real speed
            
            local real targetX
            local real targetY
            local real targetZ
            
            local real x
            local real y
            local real z
            
            local real distanceX
            local real distanceY
            local real distanceZ
            local real distance2
            
            local real theta
            local real phi
            
            local real sinPhi
            local real sinTheta
            local real cosTheta
        
            /*
            *   Iterate over forces
            */
            set this = thistype(0).next
            loop
                exitwhen 0 == this
                
                set target = this.target
                set source = this.source
                
                if (source.valid) then
                    if (Position(target).valid) then
                        set radius = this.radius
                        set acceleration = this.acceleration
                        set speed = this.speed
                        
                        set targetX = target.x
                        set targetY = target.y
                        set targetZ = target.z
                        
                        set x = source.x
                        set y = source.y
                        set z = source.z
                        
                        set distanceX = targetX - x
                        set distanceY = targetY - y
                        set distanceZ = targetZ - z
                        
                        set distance2 = distanceX*distanceX + distanceY*distanceY + distanceZ*distanceZ - radius
                        if (1 > distance2) then
                            if (this.relative) then
                                set theta = source.facing
                                set phi = source.facingZ
                            else
                                set theta = Atan2(distanceY, distanceX)
                                set phi = (distance2 + radius)/(distanceZ*distanceZ)
                            endif
                            
                            set distance2 = 1
                        else
                            if (this.relative) then
                                set theta = Atan2(distanceY, distanceX) + source.facing
                                set phi = distance2/(distanceZ*distanceZ) + source.facingZ
                            else
                                set theta = Atan2(distanceY, distanceX)
                                set phi = distance2/(distanceZ*distanceZ)
                            endif
                        endif
                        
                        set acceleration = acceleration/distance2
                        
                        set sinPhi = Sin(phi)
                        set sinTheta = Sin(theta)*sinPhi
                        set cosTheta = Cos(theta)*sinPhi
                        
                        set target.dx = target.dx - this.dx + speed*cosTheta + acceleration*cosTheta
                        set target.dy = target.dy - this.dy + speed*sinTheta + acceleration*sinTheta
                        set target.dz = target.dz - this.dz + speed*sinPhi + acceleration*sinPhi
                        
                        set this.dx = speed*cosTheta
                        set this.dy = speed*sinTheta
                        set this.dz = speed*sinPhi
                    else
                        call deallocate()
                        call source.unlock()
                    endif
                else
                    call deallocate()
                    call Position(target).unlock()
                endif
                set this = next
            endloop
        implement CT32End
    
        static method create takes Position source, Position target, real radius, real speed, real acceleration, boolean relative returns thistype
            local thistype this = allocate()
            
            set dx = 0
            set dy = 0
            set dz = 0
            
            set this.target = target
            set this.source = source
            set this.acceleration = acceleration
            set this.radius = radius
            set this.speed = speed
            set this.relative = relative
            
            set next = thistype(0).next
            set prev = 0
            set thistype(0).next.prev = this
            set thistype(0).next = this
            
            call source.lock()
            call target.lock()
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            call source.unlock()
            call target.unlock()
            
            call deallocate()
        endmethod
        
        private static method allocate takes nothing returns thistype
            local thistype this = recycler[0]
            if (0 == this) then
                set this = instanceCount + 1
                set instanceCount = this
            else
                set recycler[0] = recycler[this]
            endif
            
            set next = thistype(0).next
            set prev = 0
            set thistype(0).next.prev = this
            set thistype(0).next = this
            
            if (0 == count) then
                call start()
            endif
            set count = count + 1
            
            return this
        endmethod
    endstruct
endlibrary
 
Top