System Projectile

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
Hmm, could we have projectiles that home in on another projectile? Like interception.
 

Vestras

Retired
Reaction score
248
> Who made the time slow dome model? Is it available for use? (It's pretty cool.)

I made the original. It's somewhere in the Diablo III thread on WC3C.
 

Executor

I see you
Reaction score
57
Isn't it possible to create an arc interface like this:

JASS:
Projectile.projectArcingPoint(real sx, real sy, real speed, real archeight, real arcangle, real tx, real ty)


An arcangle of 0° would be the 'normal' arced projectile (like mortar).
An arcangle of 90° would be an arc on the ground (like rexxars axes of dota)
An arcangle of 270° would be the inverted 'normal' projectile.

JASS:
Projectile.projectArcingTarget(real sx, real sy, real speed, real archeight, real arcangle, widget target)
// same like the one with "Point" but targeting and following a target

Projectile.projectLinePoint(real sx, real sy, real speed, real tx, real ty)

Projectile.projectLineTarget(real sx, real sy, real speed, widget target)
// follows the target if it moves


I think this would comprise the most aspects.
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
How do we control collision size of units and projectiles?
 

Kenny

Back for now.
Reaction score
202
Hmm, could we have projectiles that home in on another projectile? Like interception.

Again, I thought that would be something that a user would make from this system.

Just make a projectile, group projectiles in range, set the first projectile in the group to the .target of the projectile you made and set homing to true. You have a projectile that can intercept other projectiles.

@ Executor:

As I said, I would only add horizontal bezier curves if I could find a simple way to do it that didn't lead to excessive math and function calls in the periodic method.

How do we control collision size of units and projectiles?

Not quite sure I understand.. Don't you just mean the:

JASS:
.unitCollision // Collision detection radius for units.
.projCollision // Collision detection radius for projectiles.


Struct members?

If you mean retrieving and manipulating the collision size of all units on the map, that is something you should make using a hashtable + unit raw codes, or something.

And thanks for your interest in the System Narks. Gives me a reason to get updates out sooner (first one is coming soon, but not with a unit recycler as I don't have a foolproof method for it yet).
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
It seems you use dmgGroup like a way to link a boolean with an unit.
Wouldn't it be better to simply use a global boolean array, since you already use an unit indexer ?
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
JASS:

.unitCollision // Collision detection radius for units.
.projCollision // Collision detection radius for projectiles.


But this doesn't make sense - if I call .unitCollision on my projectile, what does that do? Does that set the "size" of all units on the map? I am assuming .projCollision sets the size of the projectile.
 

quraji

zap
Reaction score
144
JASS:

.unitCollision // Collision detection radius for units.
.projCollision // Collision detection radius for projectiles.


But this doesn't make sense - if I call .unitCollision on my projectile, what does that do? Does that set the "size" of all units on the map? I am assuming .projCollision sets the size of the projectile.

Probably the projectile's collision radius when colliding with regular units, and then other projectiles, respectively.
 

D.V.D

Make a wish
Reaction score
73
Shouldn't .unitcollision and .projcollision be like conditions that return true if the projectile has collided?

EDIT: I mean only proj collision. But then again, having conditions to check that would make this system much better since u can add effects on collision and stuff.
 

Kenny

Back for now.
Reaction score
202
It seems you use dmgGroup like a way to link a boolean with an unit.
Wouldn't it be better to simply use a global boolean array, since you already use an unit indexer ?

One global boolean to replace dynamic group usage? Wouldn't really work, unless I use a 2D array or something.

Shouldn't .unitcollision and .projcollision be like conditions that return true if the projectile has collided?

Those members are just reals that represent a collision radius for the projectile... How would I go about adding booleans for them? I don't think you understand how that part of the system works.

Users can set up an onProjectileCollision/onUnitCollision function to do effects on collision and stuff.
 

cleeezzz

The Undead Ranger.
Reaction score
268
very cool, i was trying to think how you kept the unit damage with the projectiles but you explained it earlier, didn't think of that

ive actually created something like this but i can't code as efficient and i haven't had much time (or im just lazy) to learn jesus' systems =/ (oh and linked lists)

oh and, was time warp inspired by me by any chance? :D
(same name/effect/and icon)

im guessing the Red arrow on the archers was double damage? i thought it was a bug at first (iono it looks weird)
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
Wait, what?

Correct me if I'm wrong, but I'm currently getting this picture:

If you set .unitCollision to 64, the projectile will collide with any unit whose origin is within 64 units of the projectile?

Or does this system use the actual collision size of the unit?
 

Kenny

Back for now.
Reaction score
202
oh and, was time warp inspired by me by any chance?

Haha, yes it was. :D Check the first post under test map. You will see I got the idea off Archer Wars: Legacy.

im guessing the Red arrow on the archers was double damage? i thought it was a bug at first (iono it looks weird)

Yeah, the red arrow is double damage. I couldn't think of a good way to show it, so I just did that (it looks pretty crap).

If you set .unitCollision to 64, the projectile will collide with any unit whose origin is within 64 units of the projectile?

The system uses [ljass]TriggerRegisterUnitInRange()[/ljass], which I thought took unit collision radii into account. Maybe I am wrong though, but from tests it seems that way.

Or does this system use the actual collision size of the unit?

No it doesn't. Again, that would be something for an attack system that a user would make off this.
 

Narks

Vastly intelligent whale-like being from the stars
Reaction score
90
The system uses [ljass]TriggerRegisterUnitInRange()[/ljass], which I thought took unit collision radii into account. Maybe I am wrong though, but from tests it seems that way.
Oh! In that case, I'll take your word for it. You might want to mention this on the documentation though.

No it doesn't. Again, that would be something for an attack system that a user would make off this.
Oh, I was talking about how the projectile collides, not the default unitCollision value.
 

Kenny

Back for now.
Reaction score
202
@ Narks:

[ljass]TriggerRegisterUnitInRange()[/ljass] does take the collision size of units into account (did some proper testing).

And projectiles do not have a collision radius, so for them, once their origin is with the .projCollision radius, they will be considered for collision.

Which kind of makes sense because projectiles aren't meant to be huge, and it does increase accuracy at bit (looks much better to have two projectiles colliding when they are right next to each other instead of ~60 units away from each other).

Edit:

I've been testing the projectile recycler a bit. It seems to save about 3-4 FPS with about 35-40 projectiles on the map at any given time (would probably be better if there was no set delay when recycling them). So this definitely does seem useful for those who wont be using arrow-like projectiles.

I think I will just keep the OnRecycle event. If users want a recycler, they will have to code carefully, as there is no way for the recycler to circumvent all situations.

Edit:

Even though people haven't answered my questions before, I will ask another:

Would people rather have the projectile recycler built into the system and have a boolean for using it, or would you rather it in a separate library and use static if's + LIBRARY_Something.

Edit:

Current script (for me to look at on my other laptop):
JASS:
library Projectile initializer OnInit requires AIDS, T32, Event, Vector, AutoFly, GroupUtils, ListModule

    globals
        public  constant integer DUMMY_ID            = 'proj'
        public  constant player  OWNER_ID            = Player(0) // Player(PLAYER_NEUTRAL_PASSIVE)
        public  constant boolean RECYCLE             = true

        private constant real    DEFAULT_UNIT_COLL   = 64.00
        private constant real    DEFAULT_PROJ_COLL   = 32.00
        private constant real    REMOVAL_DELAY       = 2.00
        
        private constant boolean PRELOAD_PROJECTILES = true
        private constant integer PRELOAD_AMOUNT      = 50
    endglobals
    
    //------------------------------------------------------------------------\\
    //                                                                        \\
    //   DO NOT TOUCH PAST THIS POINT UNLESS YOU KNOW WHAT YOUR ARE DOING!!   \\
    //                                                                        \\
    //------------------------------------------------------------------------\\
    
    globals    
        private Event      OnRecycleEvent = 0
        private Event      CollisionEvent = 0    
        private Projectile FirstProjData  = 0
        private Projectile SecondProjData = 0
        private Projectile ProjectileList = 0
        private integer    RecycledCount  = 0
        private real       WorldMaxX      = 0.00
        private real       WorldMaxY      = 0.00
        private real       WorldMinX      = 0.00
        private real       WorldMinY      = 0.00
        private unit       RecycledProj   = null
        private hashtable  TriggerHash    = null
        private unit array RecycledUnits
    endglobals
    
    //------------------------------------------------------------------------\\
    //  Function interfaces: Bonus functions available for greater control.   
    function interface OnStart  takes Projectile instance returns nothing
    function interface OnLoop   takes Projectile instance returns nothing
    function interface OnFinish takes Projectile instance returns nothing
    
    function interface OnLand   takes Projectile instance returns nothing
    function interface OnUnit   takes Projectile instance, unit whichUnit returns nothing
    function interface OnProj   takes Projectile instance, Projectile whichProj returns nothing
    
    function interface ForGroupCallback takes Projectile whichProj, integer whichInst returns nothing
    
    //------------------------------------------------------------------------\\
    //  Projectile recycling: For more efficient projectile creation.
    private function NewProjectile takes real xPos, real yPos, real facing returns unit
        if RecycledCount == 0 then
            set RecycledUnits[0] = CreateUnit(OWNER_ID,DUMMY_ID,xPos,yPos,facing * bj_RADTODEG)
        else
            set RecycledCount = RecycledCount - 1
            call PauseUnit(RecycledUnits[RecycledCount],false)
            call SetUnitFacing(RecycledUnits[RecycledCount],facing * bj_RADTODEG)
        endif
        return RecycledUnits[RecycledCount]
    endfunction
    
    private function ReleaseProjectile takes unit whichUnit returns boolean
        call SetUnitX(whichUnit,WorldMaxX + 164.00)
        call SetUnitY(whichUnit,WorldMaxY + 164.00)
        call PauseUnit(whichUnit,true)
        set RecycledUnits[RecycledCount] = whichUnit
        set RecycledCount = RecycledCount + 1
        set RecycledProj = whichUnit
        call OnRecycleEvent.fire()
        return true
    endfunction
    
    //------------------------------------------------------------------------\\
    //  ProjData: Stores Projectile structs allocated to dummy units.    
    private struct ProjData extends array
        //! runtextmacro AIDS()
        
        Projectile data
        
        private static method AIDS_filter takes unit whichUnit returns boolean
            return GetUnitTypeId(whichUnit) == DUMMY_ID
        endmethod
        
        private method AIDS_onCreate takes nothing returns nothing
            set this.data = 0
        endmethod
        
        private method AIDS_onDestroy takes nothing returns nothing
            set this.data = 0
        endmethod
        
    endstruct
    
    //------------------------------------------------------------------------\\
    //  ForGroupStack: For recursion safety for ForProjectileGroup().
    private struct ForGroupStack extends array
    
        group   forGroup
        integer instance
        integer callback
        
        static thistype top = 0
        
        static method increment takes nothing returns nothing
            set thistype.top = thistype(thistype.top + 1)
        endmethod
        
        static method decrement takes nothing returns nothing
            set thistype.top = thistype(thistype.top - 1)
        endmethod
        
    endstruct
    
    //------------------------------------------------------------------------\\
    //  Unit recycler: To show the death effect of projectiles.
    private struct DelayedRemoval
    
        unit temp = null
        real tick = 0.00
        
        private method destroy takes nothing returns nothing
            static if RECYCLE then
                call ReleaseProjectile(this.temp)
            else
                call RemoveUnit(this.temp)
            endif
            set this.temp = null
            call this.deallocate()
        endmethod
        
        private method periodic takes nothing returns nothing
            if this.tick == T32_Tick then
                call this.stopPeriodic()
                call this.destroy()
            endif
        endmethod
        
        implement T32x
        
        static method add takes unit whichUnit returns nothing
            local thistype this = thistype.allocate()
            
            set this.temp = whichUnit
            set this.tick = T32_Tick + R2I(REMOVAL_DELAY / T32_PERIOD)
            
            call this.startPeriodic()
        endmethod
        
    endstruct
    
    //------------------------------------------------------------------------\\
    //  Projectile: The main struct interface for the system.
    struct Projectile
        implement LinkedList                            // Need linked list for GroupEnum function.
        
        unit     caster            = null               // Casting unit of the projectile.
        unit     target            = null               // Target unit of the projectile (if there is one).
        player   owner             = null               // Normally the owning unit of the caster.
        real     unitDamage        = 0.00               // Easier to pass damage through projectile.
        real     unitCollision     = DEFAULT_UNIT_COLL  // Collision radius for unit/proj collisions.
        real     projCollision     = DEFAULT_PROJ_COLL  // Collision radius for proj/proj collisions.
        boolean  pauseProj         = false              // If the projectile has been paused or not.
        boolean  collideable       = false              // If the projectile can collide with other projectiles.
        boolean  enableHoming      = false              // If the projectile is homing (must have a target unit to work).
        boolean  showDeathFx       = true               // Whether or not to show the projectiles death effect.
        
        OnStart  onStart           = 0                  // Function that is executed when the projectile is launched.
        OnLoop   onLoop            = 0                  // Function that is executed each iteration or the periodic timer.
        OnFinish onFinish          = 0                  // Function that is executed when the projectile ends.
        OnLand   onLand            = 0                  // Function that is executed when the projectile hits the ground.
        OnUnit   onUnit            = 0                  // Function that is executed when the projectile hits a unit.
        OnProj   onProj            = 0                  // Function that is executed when the projectile hits another projectile.

        readonly unit     proj     = null               // The actual projectile unit.
        readonly real     angle    = 0.00               // The angle at which the projectile is facing.
        readonly real     pitch    = 0.00               // The pitch at which the projectile is facing.
        readonly vector   pos      = 0                  // A vector struct holding the XYZ coordinates of the projectiles position.
        readonly vector   vel      = 0                  // A vector struct holding the XYZ coordinates of the projectiles velocity.
        readonly vector   start    = 0                  // A vector struct holidng the XYZ coordinates of the projectiles starting location.
        readonly vector   targ     = 0                  // A vector struct holding the XYZ coordinates of the projectiles target location.
        
        private  trigger  unitTrig = null               // Trigger needed for the unit comes in range event.
        private  group    dmgGroup = null               // Group needed so units aren't hit more than once.
        private  region   projReg  = null               // Region for the unit enters region event.
        private  rect     projRect = null               // Rect that is added and removed from the region periodically.
        private  trigger  projTrig = null               // Trigger needed for the unit enters region event.
        private  real     height   = 0.00               // Maximum height of an arcing projectile.
        private  real     maxDist  = 0.00               // Maximum distance of an arcing projectile.
        private  real     distDone = 0.00               // Distance travelled by an arcing projectile.
        private  real     tick     = 0.00               // Ticks to count projectile life span.
        private  boolean  arcing   = false              // If the projectile should be arcing or not.
        private  effect   sfx      = null               // The effect attached to the projectile.
        private  string   path     = ""                 // The path of the above effect.
        private  boolean  stop     = false              // If the projectile instance has been stopped (destroyed).
        private  real     speed    = 0.00               // The current speed of the projectile.
        private  real     oriSpeed = 0.00               // The previous speed of the projectile.
        private  real     oldSpeed = 0.00               // The original speed of the projectile.

        private  static boolexpr unitFilt = null        // The condition used for unit collision.
        private  static boolexpr projFilt = null        // The condition used for projectile collision.
        private  static vector   tempVect = 0           // The helper vector needed for z height.
        
        static method operator[] takes unit whichUnit returns thistype
            return ProjData[whichUnit].data
        endmethod
        
        method operator scaleSize= takes real value returns nothing
            call SetUnitScale(this.proj,value,0.00,0.00)
        endmethod
        
        method operator timedLife= takes real value returns nothing
            set this.tick = T32_Tick + R2I(value / T32_PERIOD)
        endmethod
        
        method operator effectPath= takes string whichPath returns nothing
           if this.sfx != null then
               call DestroyEffect(this.sfx)
               set this.path = ""
           endif
           
           if whichPath == "" then
               set this.sfx  = null
               set this.path = ""
           else
               set this.sfx  = AddSpecialEffectTarget(whichPath,this.proj,"origin")
               set this.path = whichPath
           endif
        endmethod
        
        method operator effectPath takes nothing returns string
            return this.path
        endmethod
        
        method operator currentSpeed takes nothing returns real
            return this.speed / T32_PERIOD
        endmethod
        
        method operator previousSpeed takes nothing returns real
            return this.oldSpeed / T32_PERIOD
        endmethod
        
        method operator originalSpeed takes nothing returns real
            return this.oriSpeed / T32_PERIOD
        endmethod
        
        method operator isTerminated takes nothing returns boolean
            return this.stop
        endmethod
        
        method terminate takes nothing returns nothing
            set this.effectPath = ""
            set this.stop = true
        endmethod
        
        method isUnitInHeightRange takes unit whichUnit returns boolean
            local real height = GetUnitFlyHeight(whichUnit)
            call thistype.tempVect.getTerrainPoint(GetUnitX(whichUnit),GetUnitY(whichUnit))
            set height = height - thistype.tempVect.z
            return height <= this.pos.z + this.unitCollision and height >= (this.pos.z - this.unitCollision) - 128.00
        endmethod

        method modifyTargeting takes real xPos, real yPos, real zPos returns nothing
            set this.angle  = Atan2((yPos - this.pos.y),(xPos - this.pos.x))
            set this.pitch  = Atan2(SquareRoot((xPos - this.pos.x) * (xPos - this.pos.x) + (yPos - this.pos.y) * (yPos - this.pos.y)),(zPos - this.pos.z))
            set this.vel.x  = Sin(this.pitch) * Cos(this.angle) * speed
            set this.vel.y  = Sin(this.pitch) * Sin(this.angle) * speed
            set this.vel.z  = Cos(this.pitch) * speed
            set this.targ.x = xPos
            set this.targ.y = yPos
            set this.targ.z = zPos
            
            if not this.arcing then
                call SetUnitAnimationByIndex(this.proj,R2I(bj_RADTODEG * Atan2(this.vel.z,SquareRoot(this.vel.x * this.vel.x + this.vel.y * this.vel.y)) + 0.50) + 90)
            endif
            
            call SetUnitFacing(this.proj,this.angle * bj_RADTODEG)
        endmethod
        
        method modifyPositioning takes real xPos, real yPos, real zPos returns nothing
            set this.pos.x = xPos
            set this.pos.y = yPos
            set this.pos.z = zPos
        endmethod
        
        method modifySpeed takes real value returns nothing
            set this.oldSpeed = this.speed
            set this.speed    = value * T32_PERIOD
            set this.vel.x    = Sin(this.pitch) * Cos(this.angle) * this.speed
            set this.vel.y    = Sin(this.pitch) * Sin(this.angle) * this.speed
            set this.vel.z    = Cos(this.pitch) * this.speed
        endmethod
        
        private method destroy takes nothing returns nothing
            call this.onFinish.execute(this)
            call this.pos.destroy()
            call this.vel.destroy()
            call this.targ.destroy()
            call this.start.destroy()
            
            if this.sfx != null then
                call DestroyEffect(this.sfx)
            endif
            
            if this.showDeathFx then
                call DelayedRemoval.add(this.proj)
            else
                static if RECYCLE then
                    call ReleaseProjectile(this.proj)
                else
                    call RemoveUnit(this.proj)
                endif
            endif
            
            call TriggerClearConditions(this.unitTrig)
            call DisableTrigger(this.unitTrig)
            call DestroyTrigger(this.unitTrig)
            
            call TriggerClearConditions(this.projTrig)
            call DisableTrigger(this.projTrig)
            call DestroyTrigger(this.projTrig)
            
            call RemoveRegion(this.projReg)
            call RemoveRect(this.projRect)
            call ReleaseGroup(this.dmgGroup)
            
            set ProjData[this.proj].data = 0
            
            set this.projTrig = null
            set this.projRect = null
            set this.projReg  = null
            set this.unitTrig = null
            set this.dmgGroup = null
            set this.caster   = null
            set this.proj     = null
            set this.sfx      = null
            set this.path     = ""
            
            call this.deallocate()
        endmethod
        
        private static method projectileFilter takes nothing returns boolean
            local thistype this = LoadInteger(TriggerHash,GetHandleId(GetTriggeringTrigger()),0)
            local unit     filt = GetEnteringUnit()
            local thistype that = Projectile[filt]

            if that != 0 and this != 0 and this != that then
                if that.collideable and this.collideable then
                    if (this.pos.z - that.pos.z) * (this.pos.z - that.pos.z) < this.projCollision * this.projCollision or /*
                    */ (that.pos.z - this.pos.z) * (that.pos.z - this.pos.z) < that.projCollision * that.projCollision then
                        call this.onProj.execute(this,that)
                        call that.onProj.execute(that,this)
                        set FirstProjData  = this
                        set SecondProjData = that
                        call CollisionEvent.fire()
                    endif
                endif
            endif
            
            set filt = null
            
            return false
        endmethod
        
        private static method collisionFilter takes nothing returns boolean
            local thistype this = LoadInteger(TriggerHash,GetHandleId(GetTriggeringTrigger()),0)
            local unit     filt = GetFilterUnit()

            if not IsUnitInGroup(filt,this.dmgGroup) then
                call GroupAddUnit(this.dmgGroup,filt)
                call this.onUnit.execute(this,filt)
            endif
            
            set filt = null
            
            return false
        endmethod
        
        private method periodic takes nothing returns nothing   
            local real tempX = 0.00
            local real tempY = 0.00
            local real tempZ = 0.00
            
            if this.stop or this.tick == T32_Tick then
                call this.stopPeriodic()
                call this.destroyThis()
            else
                if this.onLoop != 0 then
                    call this.onLoop.execute(this)
                endif
                
                if not this.pauseProj then
                    call RegionClearRect(this.projReg,this.projRect)
                    call SetRect(this.projRect,this.pos.x  - this.projCollision,this.pos.y - this.projCollision,this.pos.x + this.projCollision,this.pos.y + this.projCollision)
                    call RegionAddRect(this.projReg,this.projRect)
                    
                    if this.target != null and this.enableHoming then
                        set tempX = GetUnitX(this.target)
                        set tempY = GetUnitY(this.target)
                        if this.targ.x != tempX and this.targ.y != tempY then
                            if this.arcing then
                                set this.arcing = false
                            endif
                            call this.modifyTargeting(tempX,tempY,this.targ.z)
                        endif
                    endif
                    
                    call this.pos.add(this.vel)
                    call thistype.tempVect.getTerrainPoint(this.pos.x,this.pos.y)

                    if this.arcing then
                        set tempZ         = this.pos.z
                        set this.pos.z    = this.start.z + (4.00 * this.height / this.maxDist) * (this.maxDist - this.distDone) * (this.distDone / this.maxDist)
                        set this.distDone = this.distDone + this.speed
                        call SetUnitAnimationByIndex(this.proj,R2I(bj_RADTODEG * Atan2((this.pos.z - tempZ) + this.vel.z,SquareRoot(this.vel.x * this.vel.x + this.vel.y * this.vel.y)) + 0.50) + 90)
                    endif
                    
                    call SetUnitX(this.proj,this.pos.x)
                    call SetUnitY(this.proj,this.pos.y)
                    call SetUnitFlyHeight(this.proj,this.pos.z - thistype.tempVect.z,0.00)
                    
                    if this.pos.z <= thistype.tempVect.z then
                        call this.onLand.execute(this)
                        set this.stop = true
                    endif
                endif
            endif
        endmethod
        
        implement T32x
        
        private method setCommon takes real xPos, real yPos, real zPos, real speed returns nothing
            local real tempX  = xPos - this.pos.x
            local real tempY  = yPos - this.pos.y
            local real tempZ  = zPos - this.pos.z
            
            set this.angle    = Atan2(tempY,tempX)
            set this.pitch    = Atan2(SquareRoot(tempX * tempX + tempY * tempY),tempZ)
            set this.speed    = speed * T32_PERIOD
            set this.vel      = vector.create(Sin(this.pitch) * Cos(this.angle) * this.speed,Sin(this.pitch) * Sin(this.angle) * this.speed,Cos(this.pitch) * this.speed)
            set this.targ     = vector.create(xPos,yPos,zPos)
            set this.oriSpeed = this.speed
            set this.oldSpeed = this.speed
            
            call SetUnitAnimationByIndex(this.proj,R2I(bj_RADTODEG * Atan2(this.vel.z,SquareRoot(this.vel.x * this.vel.x + this.vel.y * this.vel.y)) + 0.50) + 90)
            
            set this.unitTrig = CreateTrigger()
            call SaveInteger(TriggerHash,GetHandleId(this.unitTrig),0,this)
            call TriggerRegisterUnitInRange(this.unitTrig,this.proj,this.unitCollision,null)
            call TriggerAddCondition(this.unitTrig,thistype.unitFilt)
            
            set this.projTrig = CreateTrigger()
            set this.projReg  = CreateRegion()
            set this.projRect = Rect(xPos - this.projCollision,yPos - this.projCollision,xPos + this.projCollision,yPos + this.projCollision)
            
            call SaveInteger(TriggerHash,GetHandleId(this.projTrig),0,this)
            call RegionAddRect(this.projReg,this.projRect)
            call TriggerRegisterEnterRegion(this.projTrig,this.projReg,null)
            call TriggerAddCondition(this.projTrig,thistype.projFilt)
        endmethod
        
        method projectNormal takes real xPos, real yPos, real zPos, real speed returns nothing
            call thistype.tempVect.getTerrainPoint(xPos,yPos)
            if zPos <= thistype.tempVect.z then
                set zPos = zPos + thistype.tempVect.z + 1.00
                call this.setCommon(xPos,yPos,zPos,speed)
            else
                call this.setCommon(xPos,yPos,zPos,speed)
            endif
            call this.onStart.execute(this)
            call this.startPeriodic()
        endmethod
        
        method projectArcing takes real xPos, real yPos, real zPos, real speed, real maxHeight returns nothing
            call thistype.tempVect.getTerrainPoint(xPos,yPos)
            if (zPos + thistype.tempVect.z > maxHeight - this.start.z) or (this.start.z > maxHeight - this.start.z) then   
                call this.projectNormal(xPos,yPos,zPos + thistype.tempVect.z,speed)
            else
                set this.maxDist = SquareRoot((xPos - this.pos.x) * (xPos - this.pos.x) + (yPos - this.pos.y) * (yPos - this.pos.y) + (zPos - this.start.z) * (zPos - this.start.z)) - this.start.z
                set this.height  = maxHeight - this.start.z
                set this.arcing  = true
                call this.projectNormal(xPos,yPos,zPos,speed)
            endif
        endmethod
        
        static method create takes real xPos, real yPos, real zPos, real facing returns thistype
            local thistype this = thistype.allocate()
            
            static if RECYCLE then
                set this.proj = NewProjectile(xPos,yPos,facing)
            else
                set this.proj = CreateUnit(OWNER_ID,DUMMY_ID,xPos,yPos,facing * bj_RADTODEG)
            endif
            
            call thistype.tempVect.getTerrainPoint(xPos,yPos)
            
            if zPos <= thistype.tempVect.z then
                set zPos = zPos + thistype.tempVect.z + 1.00
            endif
            
            call SetUnitX(this.proj,xPos)
            call SetUnitY(this.proj,yPos)
            call SetUnitFlyHeight(this.proj,zPos - thistype.tempVect.z,0.00)
            
            set this.pos      = vector.create(xPos,yPos,zPos)
            set this.start    = vector.create(xPos,yPos,zPos)
            set this.dmgGroup = NewGroup()
            set ProjData[this.proj].data = ProjectileList.addToStart(this)
            
            return this
        endmethod

        private static method onInit takes nothing returns nothing
            set thistype.tempVect = vector.create(0.00,0.00,0.00)
            set thistype.unitFilt = Condition(function thistype.collisionFilter)
            set thistype.projFilt = Condition(function thistype.projectileFilter)
        endmethod
        
    endstruct
    
    //------------------------------------------------------------------------\\
    //  GroupEnum function: Groups all projectiles in range, very useful.  
    function GroupProjectilesInRange takes group whichGroup, real x, real y, real z, real radius returns nothing
        local Projectile p = ProjectileList.head
        local vector     v = vector.create(x,y,z)
        
        call GroupClear(whichGroup)
        
        loop
            exitwhen p == 0
            if p.pos.isInSphere(v,radius) then
                call GroupAddUnit(whichGroup,p.proj)
            endif
            set p = p.next
        endloop
        
        call v.destroy()
    endfunction
    
    //------------------------------------------------------------------------\\
    //  ForProjectileGroup: For those who want a cleaner interface. Not recommended.
    private function ForProjectileGroupCallback takes nothing returns nothing
        call ForGroupCallback(ForGroupStack.top.callback).execute(ProjData[GetEnumUnit()].data,ForGroupStack.top.instance) // Don't kill me for this.
    endfunction
    
    function ForProjectileGroup takes group whichGroup, ForGroupCallback whichCallback, integer whichInst returns nothing
        call ForGroupStack.increment()
        set ForGroupStack.top.forGroup = whichGroup
        set ForGroupStack.top.instance = whichInst
        set ForGroupStack.top.callback = whichCallback
        call ForGroup(whichGroup,function ForProjectileGroupCallback)
        call ForGroupStack.decrement()
    endfunction
    
    //------------------------------------------------------------------------\\
    //  Global collision event: Useful for a variety of spells. Has event responses.
    function RegisterProjectileCollisionEvent takes trigger whichTrigger returns EventReg
        return CollisionEvent.register(whichTrigger)
    endfunction
    
    function GetFirstCollisionProj takes nothing returns Projectile
        return FirstProjData
    endfunction
    
    function GetSecondCollisionProj takes nothing returns Projectile
        return SecondProjData
    endfunction
    
    //------------------------------------------------------------------------\\
    //  On recycle event: Simulates an on projectile 'removal' event for users.
    function RegisterProjectileRecycledEvent takes trigger whichTrigger returns EventReg
        return OnRecycleEvent.register(whichTrigger)
    endfunction
    
    function GetRecycledProjectile takes nothing returns unit
        return RecycledProj
    endfunction
    
    //------------------------------------------------------------------------\\
    //  WC3 style wrappers: For people who like normal WC3 style functions.
    function IsUnitProjectile takes unit whichUnit returns boolean
        return Projectile[whichUnit] != 0
    endfunction
    
    function GetUnitProjectileData takes unit whichUnit returns Projectile
        return Projectile[whichUnit]
    endfunction
    
    //------------------------------------------------------------------------\\
    //  Bounds detection: Projectiles are destroyed when they try to leave the map.
    private function OutsideMapRemoval takes nothing returns boolean
        local unit       u = GetLeavingUnit()
        local real       x = GetUnitX(u)
        local real       y = GetUnitY(u)
        local Projectile p = Projectile<u>
        
        if p != 0 then
            call p.terminate()
        else
            if(x &gt; WorldMaxX) then
                set x = WorldMaxX
            elseif(x &lt; WorldMinX) then
                set x = WorldMinX
            endif
            
            if(y &gt; WorldMaxY) then
                set y = WorldMaxY
            elseif(y &lt; WorldMinY) then
                set y = WorldMinY
            endif
            
            call SetUnitX(u,x)
            call SetUnitY(u,y)
        endif

        set u=null
        
        return false
    endfunction
    
    //------------------------------------------------------------------------\\
    //  Initialisation: Collision event, linked list, hashtable.
    private function OnInit takes nothing returns nothing
        local trigger trig = CreateTrigger()
        local region  reg  = CreateRegion()
        local rect    rec  = null
        
        static if RECYCLE then
            set OnRecycleEvent = Event.create()
        endif
        
        set CollisionEvent = Event.create()
        set ProjectileList = Projectile.createList()
        set TriggerHash    = InitHashtable()
        set WorldMaxX      = GetRectMaxX(bj_mapInitialPlayableArea) - 64.00
        set WorldMaxY      = GetRectMaxY(bj_mapInitialPlayableArea) - 64.00
        set WorldMinX      = GetRectMinX(bj_mapInitialPlayableArea) + 64.00
        set WorldMinY      = GetRectMinY(bj_mapInitialPlayableArea) + 64.00
        set rec            = Rect(WorldMinX,WorldMinY,WorldMaxX,WorldMaxY)
        
        static if PRELOAD_PROJECTILES and RECYCLE then
            loop
                exitwhen RecycledCount == PRELOAD_AMOUNT
                set RecycledUnits[RecycledCount] = CreateUnit(OWNER_ID,DUMMY_ID,WorldMaxX + 164.00,WorldMaxY + 164.00,0.00)
                set RecycledCount = RecycledCount + 1
            endloop
            set RecycledCount = PRELOAD_AMOUNT
        endif
        
        call RegionAddRect(reg,rec)
        call TriggerRegisterLeaveRegion(trig,reg,null)
        call TriggerAddCondition(trig,Condition(function OutsideMapRemoval))
        
        call RemoveRect(rec)
        set trig = null
        set rec  = null
        set reg  = null
    endfunction
    
endlibrary 
</u>


Done for next update:

- Implemented recycling.
- Changed how time life works. Can now no longer retrieve the current timed life of a projectile (subject to change, but I can't find a need for it to change).
- Updated how projectiles work with terrain height changes (Thanks PurgeAndFire).
- Added user safety to the .project() methods so users don't screw themselves over.
- Now destroys the start and targ vectors (completely missed that).
- Minor scripting changes.

To do before next update:

- Remove those excess function interfaces (use one for onStart, onLoop, onFinish and onLand).
- Make function interfaces private.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Kenny said:
One global boolean to replace dynamic group usage? Wouldn't really work, unless I use a 2D array or something.
True, but you could simply use the boolean of GroupAddUnit though.
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
JASS:
if not IsUnitInGroup(filt,this.dmgGroup) then
   call GroupAddUnit(this.dmgGroup,filt)
   call this.onUnit.execute(this,filt)
endif


JASS:
if GroupAddUnit(this.dmgGroup,filt)
   call this.onUnit.execute(this,filt)
endif


GroupAddUnit return true only if the unit is added to the group (valid unit and not already inside the group)
 

Kenny

Back for now.
Reaction score
202
Ohhhh... Very clever. thank you for that! I will add it right away. :D

Wait a sec...

Are you sure that works? Doesn't [ljass]GroupAddUnit()[/ljass] return nothing?
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Wait a sec...

Are you sure that works? Doesn't [ljass]GroupAddUnit()[/ljass] return nothing?

It returns a valid boolean, you just have never use it like that, since in jass2 you're able to ignore the return of a function and act as if the function returns nothing.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top