System Projectile

Tom_Kazansky

--- wraith it ! ---
Reaction score
157
I tested the map, my fps is about 3 or 4 and I can't see a thing :banghead:
maybe I will try that on my desktop later

@Kenny, can you explain for me the... purpose of these systems (that you're using)
  • T32 <--- for timer, right ?
  • Event
  • Vector
  • Linked List Module
sorry, I just can't get the document of those systems :banghead:
purpose = what do they do
 

Kenny

Back for now.
Reaction score
202
T32 is for the periodic timer.

Event is for the global projectile collision event.

Vector is for moving the projectiles and for the GroupProjectilesInRange() function.

The linked list module is to keep track of the projectiles for the GroupEnumUnitsInRange() function (this could be removed if Jesus4Lyf updates T32 with public next and prev members).

Oh and Nestharus, it seems the cJass installer is considered a keylogger virus. -____-
 

Nestharus

o-o
Reaction score
84
Add an exception for it.. I run Kapersky and do anti-virus all the time and it never even pops up for me -.-.



By the way, do you think it might be Horus? Horus is the syntax highlighter for it, and yea, it does track your keys : P.


Also my laptop is dual core 4 gigs of ram. To me that is garbage, heh... my desktop is like 4x or 8x the power.


Oh, and sevion's old machine could run 850 or so. His new one would probably do 1300.
 

Weep

Godspeed to the sound of the pounding
Reaction score
400
and here's a quick demo using 650 projectiles
http://www.filefront.com/15833723/ProjectileSystem~1.w3x

Keep in mind that computers vary in power, so that might lag you a little to a lot (that was edge on my laptop), or it might be absolutely 0 lag. Depends on where you computer lies in relation to my laptop ; P
What the heck kind of laptop do you have? :nuts: I get ~8 FPS on a 2.8GHz Xeon E5462 with 650 projectiles, ~80 FPS with 450 projectiles (but stuttery, unenjoyable performance when scrolling despite the claimed frame rate), and ~2 FPS with 450 projectiles when I ordered my Footman to take a walk. For comparison, ~200 FPS on a blank map.

Gratz for not leaking, though: memory usage held steady around 100MB. :p
 

Kenny

Back for now.
Reaction score
202
Just finished installing cJass and testing Nestharus' system.

From the tests I've done on my laptop, this is what I found:

- Nestharus' system: 100 projectiles without projectile on projectile collision or terrain detection reduced FPS to around 25-30. When I move the footman FPS drops to around 12-15, then FPS averages out to around 20.

- My system: 100 projectiles with both of the above features reduced FPS to around 50-55. When I move the footman FPS drops to around 30-35, then FPS averages out to around 40.

Average FPS for warcraft on my laptop: 80.

Interesting stuff... I am looking into it some more.

@ Nestharus:

Have you stress tested my system on your laptop?
 

Nestharus

o-o
Reaction score
84
keep in mind that mine has a turn rate, which means it has to calculate the angle far more often than yours as it has to update every period until it is aligned again ^^. yes, I finally looked through your code : D. I just saw some of those systems and went ><.

So those results are to be expected, but they should be exactly like simple projectiles once they are aligned -.-.


Anyways, I guess that turn rate is very expensive : ).
 

Deaod

Member
Reaction score
6
JASS:
constant native GetTriggeringRegion takes nothing returns region
Maybe use this one in combination with a static trigger.
Recycle as much as you can (like the region).
And please catch multiple consecutive calls to projectNormal, so that you dont overwrite the trigger and so on.

Use Table instead of your own hashtable.
There are other ways to not use T32 besides using a timer for each instance.
 

Kenny

Back for now.
Reaction score
202
Maybe use this one in combination with a static trigger.
Recycle as much as you can (like the region).
And please catch multiple consecutive calls to projectNormal, so that you dont overwrite the trigger and so on.

Hmm.. I'll look into the static trigger + [ljass]GetTriggeringRegion()[/ljass]. However, that will only get rid of 1 dynamic trigger per instance.

Region recycling is implemented in a current build of mine.

Safety for the dynamic triggers has also been added (about 75% added) into my current build for the next update.

I will probably switch to using Table.

I still don't see a more efficient way to do the periodic effects than T32 though. It is the best there is for stuff like this.

Oh and thanks for your criticisms and comments, they are a bit more thorough then most comments concerning the inner workings of the system so far.
 

Viikuna

No Marlo no game.
Reaction score
265
Can you post the part of the code where you use SetUnitLookAt and do those arcing thingies?

Also, are you sure that your dummy model is all right?
 

Furby

Current occupation: News poster
Reaction score
144
I added some debug messages to function Actions in trigger Attack. Here is what I did:

JASS:
private function Actions takes nothing returns boolean
        local unit       att = GetEventDamageSource()
        local unit       trg = GetTriggerUnit()
        local real       dmg = GetEventDamage()
        local real       ax  = GetUnitX(att)
        local real       ay  = GetUnitY(att)
        local real       tx  = GetUnitX(trg)
        local real       ty  = GetUnitY(trg)
        local real       ang = Atan2((ty - ay),(tx - ax))
        local AttackData ad  = AttackData[Hash(GetUnitTypeId(att))]
        local Projectile p   = 0
        if Damage_IsAttack() and ad.reg then
            call Damage_BlockAll()
            
            call BJDebugMsg(&quot;BLOCKED: &quot;+R2S(dmg))
            set p = Projectile.create(ax,ay,ad.hgt,ang)
            
            set p.caster        = att
            set p.target        = trg
            set p.owner         = GetOwningPlayer(att)
            set p.effectPath    = ad.sfx
            set p.scaleSize     = ad.scl
            set p.unitDamage    = dmg
            set p.unitCollision = ad.col
            set p.enableHoming  = true
            
            set p.onUnit        = OnUnitImpact
            
            if ad.nrm then
                call p.projectNormal(tx,ty,GetUnitFlyHeight(trg) + ad.hgt,ad.spd)
            else
                call p.projectArcing(tx,ty,GetUnitFlyHeight(trg) + ad.hgt,ad.spd,ad.arc)
            endif
            
            set SourceUnit  = att
            set TargetUnit  = trg
            set DamageDealt = dmg
            set AttackDataX = ad
            call OnAttackEvent.fire()
        else
            call BJDebugMsg(&quot;REAL: &quot;+R2S(dmg))
        endif
        
        set att = null
        set trg = null
        
        return false
    endfunction


Here's result:
28hd9hg.jpg


The first damage is higher than damage which target really takes. Caused by armor.

You shoot, instant "dummy" shot, which goes through armor then it's saved and blocked.
After missile hits unit, target is damaged by saved value, meaning it goes through armor again, so it's lower than the first value.

I hope I helped. :)
 

Kenny

Back for now.
Reaction score
202
@ Viikuna:

JASS:
library Projectile initializer OnInit requires AIDS, T32, Event, Vector, AutoFly, GroupUtils, ListModule
     
    //------------------------------------------------------------------------\\
    //  Configurables:                                                        \\
    // ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯                                                        \\
    //    - DUMMY_ID             --&gt; The raw code of the dummy unit used for  \\
    //                               the projectiles in this system.          \\
    //    - OWNER_ID             --&gt; The owning player of the projectiles     \\
    //                               used in this system.                     \\
    //    - RECYCLE              --&gt; Whether or not to recycle projectiles.   \\
    //                               Does not work well for arrow-types.      \\
    //    - DEFAULT_UNIT_COLL    --&gt; The default unit collision radius for    \\
    //                               projectiles.                             \\
    //    - DEFAULT_PROJ_COLL    --&gt; The default projectile collision radius  \\
    //                               for projectiles.                         \\
    //    - REMOVAL_DELAY        --&gt; How long it takes for a projectile to be \\
    //                               removed. Allows death effects.           \\
    //    - PRELOAD_PROJECTILES  --&gt; Whether or not to preload projectiles to \\
    //                               minimise the effects of unit creation.   \\
    //    - PRELOAD_AMOUNT       --&gt; How many projectile dummy units should   \\
    //                               be preloaded for use.                    \\
    //                                                                        \\
    //------------------------------------------------------------------------\\
    
    globals
        public  constant integer DUMMY_ID            = &#039;proj&#039;
        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.   
    private function interface UserEvents takes Projectile instance returns nothing
    private function interface UnitImpact takes Projectile instance, unit whichUnit returns nothing
    private function interface ProjImpact takes Projectile instance, Projectile whichProj returns nothing
    
    private 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 SetUnitLookAt(RecycledUnits[RecycledCount],&quot;Bone_Head&quot;,RecycledUnits[RecycledCount],Cos(facing) * 1000000.00,Sin(facing) * 1000000.00,0.00)
        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
    
    //------------------------------------------------------------------------\\
    //  Delayed remover: 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       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    showDeathFx     = true               // Whether or not to show the projectiles death effect.
        
        UserEvents onStart         = 0                  // Function that is executed when the projectile is launched.
        UserEvents onLoop          = 0                  // Function that is executed each iteration or the periodic timer.
        UserEvents onFinish        = 0                  // Function that is executed when the projectile ends.
        UserEvents onLand          = 0                  // Function that is executed when the projectile hits the ground.
        UnitImpact onUnit          = 0                  // Function that is executed when the projectile hits a unit.
        ProjImpact 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&#039;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  real     uColl    = DEFAULT_UNIT_COLL  // Collision radius for unit/proj collisions.
        private  boolean  arcing   = false              // If the projectile should be arcing or not.
        private  boolean  homing   = false              // If the projectile is homing (must have a target unit to work).
        private  effect   sfx      = null               // The effect attached to the projectile.
        private  string   path     = &quot;&quot;                 // 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 whichValue returns nothing
            call SetUnitScale(this.proj,whichValue,0.00,0.00)
        endmethod
        
        method operator timedLife= takes real whichValue returns nothing
            set this.tick = T32_Tick + R2I(whichValue / T32_PERIOD)
        endmethod
        
        method operator enableHoming= takes boolean whichFlag returns nothing
            if whichFlag then
                set this.homing = true
                call SetUnitLookAt(this.proj,&quot;Bone_Head&quot;,this.target,0.00,0.00,150.00)
            else
                set this.homing = false
                call SetUnitLookAt(this.proj,&quot;Bone_Head&quot;,this.proj,Cos(this.angle) * 1000000.00,Sin(this.angle) * 1000000.00,0.00)
            endif
        endmethod
        
        method operator enableHoming takes nothing returns boolean
            return this.homing
        endmethod
        
        method operator effectPath= takes string whichPath returns nothing
           if this.sfx != null then
               call DestroyEffect(this.sfx)
               set this.path = &quot;&quot;
           endif
           
           if whichPath == &quot;&quot; then
               set this.sfx  = null
               set this.path = &quot;&quot;
           else
               set this.sfx  = AddSpecialEffectTarget(whichPath,this.proj,&quot;origin&quot;)
               set this.path = whichPath
           endif
        endmethod
        
        method operator effectPath takes nothing returns string
            return this.path
        endmethod
        
        method operator unitCollision= takes real whichValue returns nothing
            if this.unitTrig != null and this.uColl != whichValue and whichValue != 0.00 then
                call TriggerClearConditions(this.unitTrig)
                call DisableTrigger(this.unitTrig)
                call DestroyTrigger(this.unitTrig)
                set this.unitTrig = null
            endif
            if whichValue != 0.00 then
                set this.uColl    = whichValue
                set this.unitTrig = CreateTrigger()
                call SaveInteger(TriggerHash,GetHandleId(this.unitTrig),0,this)
                call TriggerRegisterUnitInRange(this.unitTrig,this.proj,this.uColl,null)
                call TriggerAddCondition(this.unitTrig,thistype.unitFilt)
            endif
        endmethod
        
        method operator unitCollision takes nothing returns real
            return this.uColl
        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 = &quot;&quot;
            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 &lt;= this.pos.z + this.unitCollision and height &gt;= (this.pos.z - this.unitCollision) - 128.00
        endmethod

        method modifyTargeting takes real xPos, real yPos, real zPos, boolean adjustFacing 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
            
            if adjustFacing then
                call SetUnitLookAt(this.proj,&quot;Bone_Head&quot;,this.proj,Cos(this.angle) * 1000000.00,Sin(this.angle) * 1000000.00,0.00)
            endif
        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()
            
            set this.collideable = false
            set this.homing      = false
            
            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
            
            if this.unitTrig != null then
                call TriggerClearConditions(this.unitTrig)
                call DisableTrigger(this.unitTrig)
                call DestroyTrigger(this.unitTrig)
            endif
            
            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     = &quot;&quot;
            
            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) &lt; this.projCollision * this.projCollision or /*
                    */ (that.pos.z - this.pos.z) * (that.pos.z - this.pos.z) &lt; 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.homing 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,false)
                        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 &lt;= thistype.tempVect.z then
                        call this.onLand.execute(this)
                    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
            local real tempD  = SquareRoot(tempX * tempX + tempY * tempY + tempZ * tempZ)
            
            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(tempX / tempD * this.speed, tempY / tempD * this.speed, tempZ / tempD * this.speed)
            // 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)
            
            if this.unitTrig == null and this.unitCollision != 0.00 then
                set this.unitTrig = CreateTrigger()
                call SaveInteger(TriggerHash,GetHandleId(this.unitTrig),0,this)
                call TriggerRegisterUnitInRange(this.unitTrig,this.proj,this.uColl,null)
                call TriggerAddCondition(this.unitTrig,thistype.unitFilt)
            endif
            
            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 &lt;= 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 &gt; maxHeight - this.start.z) or (this.start.z &gt; 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 &lt;= 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&#039;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 &#039;removal&#039; 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>


All I do is use [ljass]SetUnitLookAt()[/ljass] to make the projectile homing, and then the arcing projectiles will just look weird.

Oh, sorry, I forgot to say that arcing projectiles work, but arcing projectiles that have a target unit (homing, but before the unit moves anywhere) don't look good.

@ Furby:

That is only a demonstration library. You wouldn't use it in an actual game, you would need some library to get the units base damage or something.

But thanks for finding that.
 

Furby

Current occupation: News poster
Reaction score
144
I think you should mention that in the first post or as comment in Attack trigger, so people know it, because this thing isn't that easy to notice.
 

Nestharus

o-o
Reaction score
84
So I'm guessing your projectiles still have no turn rate? You can rip the code from my code if you want, but like I said, turn rate is somewhat important for projectiles >.<
 

Sevion

The DIY Ninja
Reaction score
413
Haha. I remember that old thing. You've updated it. ATM, I wish I had WC3 installed. Haha.
 

Viikuna

No Marlo no game.
Reaction score
265
You just gotta add turn rate and not use SetUntiLookAt for homing like that. Just use animations and SetUnitFacing. SetUnitLookAt is only really needed for recycling and some rare cases when you want instantly turn your missile. ( Like some reflection thingy )


@ Furby. I posted that armor problem and solution to it earlier in this thread. You simply just use UnitProperties and some damage detection system to get all needed data, like armor, armor type and damage type, and then you calculate the real damage before armor and magic resistances.
 

Furby

Current occupation: News poster
Reaction score
144
Ah, sorry then. I haven't bothered myself reading the whole thread. :p
 

Kenny

Back for now.
Reaction score
202
@ Viikuna:

I think I stated somewhere in this thread that once I use [ljass]SetUnitLookAt()[/ljass] on a projectile, using [ljass]SetUnitFacing()[/ljass] will no longer work to update the projectiles facing angle (it is like [ljass]SetUnitFacing()[/ljass] is constantly overridden by [ljass]SetUnitLookAt()[/ljass], making it not function correctly).

That is why I use [ljass]SetUnitLookAt()[/ljass] for homing as well.

Edit:

I am currently working on getting [LJASS].unitCollision[/LJASS] and [LJASS].projCollision[/LJASS] to work properly when people want to change them multiple times throughout a projectiles life span.

I am also working on: safety for the [LJASS].projectNormal()[/LJASS] and [LJASS].projectArcing()[/LJASS] methods (so users cannot call them more than once per projectile), as well as region recycling and switching to one global trigger for projectile on projectile collision + [ljass]GetTriggeringRegion[/ljass]. I also implemented Table as a required library.

There are a lot of changes going on that affect how the system works. I currently have about 10 different builds of the system to make sure I get the best possible results when implementing the changes. It may take some time to get them all right.

Edit 2:

Hmm... I am actually thinking about using Rectwraps by Azlier... It would make things nice and easy.

Comments?
 

Nestharus

o-o
Reaction score
84
What about turn rate... and SetUnitFacing has no problems in other projectile systems, so I think the issue is with you. You should use it. Just because you can't get it to work doesn't mean it doesn't work, it's faster than SetUnitLookat -.-.


No excuses, get cracking =p. You can rip my code if you have to, it has as few extra operations as I could possibly do and has gone through 2 drafts = ).
 

Kenny

Back for now.
Reaction score
202
Have you tested [ljass]SetUnitLookAt()[/ljass] in conjunction with [ljass]SetUnitFacing()[/ljass]?

I will attach a demo map so you can see the problem. Make one blood mage cast hurl boulder on the other, and then move the targeted blood mage around and watch the facing angle of the projectile.

I do not have issues with [ljass]SetUnitFacing()[/ljass] itself (I use it all the time and it works fine), however it seems that [ljass]SetUnitLookAt()[/ljass] overrides it and doesn't allow the unit to face any other angle then what was given in the initial [ljass]SetUnitLookAt()[/ljass] call.
 

Attachments

  • Projectile [v1.1.0].w3x
    168.8 KB · Views: 272
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top