1. The mythological era has spawned some interesting characters around. Check them out and be sure to vote for them in the 30th Poll of the Texturing Contest.
    Dismiss Notice
  2. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  3. Hivers united and created a bunch of 2v2 melee maps. Vote for the best in our Melee Mapping Contest #4 - Poll!
    Dismiss Notice
  4. Check out the Staff job openings thread.
    Dismiss Notice

[System] Missile

Discussion in 'Graveyard' started by Dirac, Nov 29, 2011.

  1. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    Code (vJASS):
    library Missile /* v2.0.1

    */
    uses/*
    */
     CTL                         /*  hiveworkshop.com/forums/jass-resources-412/snippet-constant-timer-loop-32-a-201381/
    */
     AdvLoc                      /*  hiveworkshop.com/forums/submissions-414/snippet-lacking-loc-209322/
    */
     optional WorldBounds        /*  hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
    */
     optional MissileRecycler    /*  hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/

    Credits:
        -   emjlr3 for the deflect angle equation
        -   AceHart for the parabola equation
       
    **********************************************************************/

    globals
        //***********************************************************************
        //  This dummy must use Vexorian's dummy model and it's movement type
        //  should be "Hover" if you want to correctly move over water, otherwise
        //  use "None". The dummy must also have Crow Form. If you don't have a
        //  dummy unit in your map then use the one MissileRecycler provides.
        private constant integer DUMMY_RAW_CODE = 'e000'
    endglobals
    /**********************************************************************
    *
    *   struct Missile
    *
    *       static method create takes real originX, real originY, real originZ...
    *                               ...real angle, real distance, real targetZ returns Missile
    *           -   The angle is in radians.
    *           -   Missiles have the following values to be set by you.
    *               -   real speed
    *               -   real turn
    *               -   real open / curve
    *               -   real height / arc
    *               -   real acceleration
    *               -   real collision
    *               -   unit target
    *               -   unit source
    *               -   string model
    *               -   integer data
    *
    *       real x
    *       real y
    *       real z
    *           -   The missile's position
    *
    *       real slide
    *           -   The amount of distance it has covered.
    *       AdvLoc origin
    *           -   The missile's origin Loc
    *       AdvLoc impact
    *           -   Where the missile will aim to.
    *
    *       method deflect takes real x, real y returns nothing
    *           -   Deflects the missile from the target point, changing
    *           -   it's course.
    *
    *       method bounce takes nothing returns nothing
    *           -   Bounces the missile from it's current Z position.
    *           -   Useful when assigning targets to missiles that were
    *           -   already active, it fixes the rough Z position change.
    *
    ***********************************************************************
    *
    *   (optional)
    *   static method onCollide takes Missile this, unit justHit returns boolean
    *       -   Runs every time the missile collides with something.
    *       -   If returns true the missile is destroyed.
    *  
    *   (optional)
    *   static method onPeriod takes Missile this returns boolean
    *       -   Runs every period.
    *       -   If returns true the missile is destroyed.
    *
    *   (optional)
    *   static method onFinish takes Missile this returns boolean
    *       -   Runs whenever the missile finishes it's course.
    *       -   If returns true the missile is destroyed.
    *
    *   (optional)
    *   static method onMissile takes Missile this, Missile hit, real range returns boolean
    *       -   Runs whenever the missile encounters another missile.
    *       -   If returns true the missile is destroyed.
    *
    *   (optional)
    *   static method onRemove takes Missile this returns nothing
    *       -   Runs whenever the missile is deallocated.
    *
    *   module MissileStruct
    *
    *       static method launch takes Missile toLaunch returns nothing
    *
    ***********************************************************************
    *
    *   DISCLAIMER:
    *
    *       -   Missile's default owner is Player(15) and this is meant to
    *       -   not be modifiable.
    *
    *       -   For curved missiles the "x" and "y" coordinates are only
    *       -   correct in the moment of launch or impact, but during it's
    *       -   air time they ignore the curve it has.
    *
    *       -   This system can support up to 600 proyectiles with complex
    *       -   mathematics (curve, arc, collision) before it lags.
    *
    **********************************************************************/

       
        struct MissileList extends array
            implement LinkedList
        endstruct
       
        struct Missile extends array
       
            //***********************************************************************
            //  Why LinkedList? In order to reach a high level of dynamism i'm basing
            //  this system on iterating through multiple linked lists, each struct
            //  has one and each struct iterates through it.
            implement LinkedList
           
            AdvLoc impact
            AdvLoc origin
           
            real slide
           
            readonly real x
            readonly real y
            readonly real z
           
            private real    cA  //current angle
           
            private effect  fx  //effect
            private string  fP  //model
           
            private real    dS
           
            readonly unit   dummy
            readonly group  unitsHit
           
            boolean recycle
            boolean wantDestroy
           
            unit    target
            unit    source
            real    collision
            real    height
            real    turn
            real    open
            real    damage
            real    speed
            real    acceleration
            integer data
           
            method operator model= takes string path returns nothing
                call DestroyEffect(fx)
                set fP=path
                set fx=AddSpecialEffectTarget(path,dummy,"origin")
            endmethod
           
            method operator model takes nothing returns string
                return fP
            endmethod
           
            method operator curve= takes real value returns nothing
                set open=Tan(value)*origin.distance
            endmethod
           
            method operator curve takes nothing returns real
                return Atan(open/origin.distance)
            endmethod
           
            method operator arc= takes real value returns nothing
                set height=Tan(value)*origin.distance/4
            endmethod
           
            method operator arc takes nothing returns real
                return Atan(4*height/origin.distance)
            endmethod
           
            method operator scale= takes real v returns nothing
                call SetUnitScale(dummy,v,v,v)
                set dS=v
            endmethod
           
            method operator scale takes nothing returns real
                return dS
            endmethod
           
            static method createEx takes unit whichUnit, AdvLoc o, AdvLoc i returns thistype
                local thistype this=allocate()
               
                set source = null
                set target = null
                set acceleration = 0
                set height = 0
                set turn = 0
                set open = 0
                set unitsHit = CreateGroup()
                set collision = 0
                set recycle = false
                set wantDestroy = false
                set fP = ""
               
                set x = o.x
                set y = o.y
                set z = o.z
                set origin = o
                set impact = i
               
                set cA = origin.angle
                set slide=0
               
                set dummy=whichUnit
                call MoveLocation(Loc.global,o.x,o.y)
                call SetUnitFlyHeight(dummy,o.z-GetLocationZ(Loc.global),0)
               
                call MissileList.base.insertNode(this)
               
                return this
            endmethod
           
            static method createLoc takes AdvLoc o, AdvLoc i returns thistype
                call AdvLoc.link(o,i)
                static if LIBRARY_MissileRecycler then
                    return createEx(GetRecycledMissile(o.x,o.y,o.z,o.angle*bj_RADTODEG),o,i)
                else
                    return createEx(CreateUnit(Player(15),DUMMY_RAW_CODE,o.x,o.y,o.angle*bj_RADTODEG),o,i)
                endif
            endmethod
           
            static method create takes real ox, real oy, real oz, real a, real d, real iz returns thistype
                local AdvLoc o = AdvLoc.create(ox,oy,oz)
                local AdvLoc i = AdvLoc.create(ox+d*Cos(a),oy+d*Sin(a),iz)
                call AdvLoc.link(o,i)
                static if LIBRARY_MissileRecycler then
                    return createEx(GetRecycledMissile(o.x,o.y,o.z,o.angle*bj_RADTODEG),o,i)
                else
                    return createEx(CreateUnit(Player(15),DUMMY_RAW_CODE,o.x,o.y,o.angle*bj_RADTODEG),o,i)
                endif
            endmethod
           
            method bounce takes nothing returns nothing
                call origin.move(x,y,z)
                set slide=0
            endmethod
           
            method deflect takes real tx, real ty returns nothing
                local real a = 2*Atan2(ty-y,tx-x)+bj_PI-cA
                call impact.move(x+(origin.distance-slide)*Cos(a),y+(origin.distance-slide)*Sin(a),impact.z)
                call this.bounce()
            endmethod
           
            method destroy takes nothing returns nothing
                set wantDestroy=true
            endmethod
           
            method terminate takes nothing returns nothing
                call DestroyEffect(fx)
                call DestroyGroup(unitsHit)
                set recycle=false
                set fx=null
               
                static if LIBRARY_MissileRecycler then
                    call RecycleMissile(dummy)
                else
                    call RemoveUnit(dummy)
                endif
               
                call impact.unlock()
                call origin.unlock()
               
                call MissileList(this).removeNode()
               
                call this.removeNode()
                call this.deallocate()
            endmethod
           
            method move takes nothing returns nothing
           
                local real a
                local real d
                local real s
                local real h
                local real tx
                local real ty
                local real ox
                local real oy
                local AdvLoc o
               
                loop
                    exitwhen head
                    set o = origin
                    set ox = o.x
                    set oy = o.y
                    set h = height
                   
                    if target!=null and GetUnitTypeId(target)!=0 then
                        call impact.move(GetUnitX(target),GetUnitY(target),GetUnitFlyHeight(target))
                        set a = Atan2(impact.y-y,impact.x-x)
                        set slide = origin.distance-SquareRoot((impact.x-x)*(impact.x-x)+(impact.y-y)*(impact.y-y))
                    else
                        set a = o.angle
                        set target = null
                    endif
                   
                    if turn!=0 and not(Cos(cA-a)>=Cos(turn)) then
                        if Sin(a-cA)>=0 then
                            set cA = cA+turn
                        else
                            set cA = cA-turn
                        endif
                    else
                        set cA = a
                    endif
                   
                    set d = o.distance
                    set s = slide+speed
                    set slide = s
                    call SetUnitFacing(dummy,cA*bj_RADTODEG)
                   
                    set tx = x+speed*Cos(cA)
                    set ty = y+speed*Sin(cA)
                    set speed = speed + acceleration
                    set x = tx
                    set y = ty

                    if h!=0 or o.slope!=0 then
                        call MoveLocation(Loc.global,tx,ty)
                        set z = 4*h*s*(d-s)/(d*d)+o.slope*s+o.z
                        call SetUnitFlyHeight(dummy,z-GetLocationZ(Loc.global),0)
                        call SetUnitAnimationByIndex(dummy,R2I(Atan(origin.slope)-Atan((8*h*s-4*d*h)/(d*d))*bj_RADTODEG)+90)
                    endif
                   
                    if open!=0 then
                        set a = 4*open*s*(d-s)/(d*d)
                        set tx = tx+a*Cos(cA+1.57)
                        set ty = ty+a*Sin(cA+1.57)
                        call SetUnitFacing(dummy,(cA+Atan(-(8*open*s-4*d*open)/(d*d)))*bj_RADTODEG)
                    endif
                   
                    static if LIBRARY_WorldBounds then
                        if tx>WorldBounds.maxX or tx<WorldBounds.minX or ty>WorldBounds.maxY or ty<WorldBounds.minY then
                            call destroy()
                        else
                            call SetUnitX(dummy,tx)
                            call SetUnitY(dummy,ty)
                        endif
                    else
                        call SetUnitX(dummy,tx)
                        call SetUnitY(dummy,ty)
                    endif
                   
                   
                    if s>=d then
                        set recycle = true
                    endif
                   
                    set this = next
                   
                endloop
            endmethod
       
        endstruct
       
        globals
            private integer                     ACTIVE  =   0
            private integer                     SIZE    =   0
            private trigger                     FIRE    =   CreateTrigger()
            private integer             array   STACK
            private integer             array   INSTANCES
            private TimerGroup32        array   TIMER
            private Missile             array   NODE
        endglobals
       
        //***********************************************************************
        //  This function runs periodically. Can you see the trigger evaluation
        //  at the end? If you've read T32 then you know exactly what it does.
        //  The loop above is for cleaning up, the SIZE variable keeps track of
        //  how many instances have been deallocated by the user, if higher than
        //  0 then some of them need to be removed. STACK[SIZE] stores the value
        //  of the deallocated instances.
        private function Execute takes nothing returns nothing
            loop
                exitwhen SIZE==0
                set ACTIVE=ACTIVE-1
                set SIZE=SIZE-1
                set INSTANCES[STACK[SIZE]]=INSTANCES[STACK[SIZE]]-1
                if INSTANCES[STACK[SIZE]]==0 then
                    call TIMER[STACK[SIZE]].stop()
                    if ACTIVE==0 then
                        return
                    endif
                endif
            endloop
            call TriggerEvaluate(FIRE)
        endfunction
       
        //***********************************************************************
        //  Adds a new instance to the given struct index (This system attaches
        //  indexes to every struct you implement MissileStruct to) If the amount
        //  of INSTANCES[index] was 0 then it adds the struct's iterate method to
        //  the FIRE trigger for it's evaluation. ACTIVE keeps track of all
        //  allocated instances, if it was 0 that means the timer isn't even
        //  running yet, it needs to be started.
        private function StartPeriodic takes integer index returns nothing
            if INSTANCES[index]==0 then
                call TIMER[index].start()
            endif
            set ACTIVE=ACTIVE+1
            set INSTANCES[index]=INSTANCES[index]+1
        endfunction
       
        //***********************************************************************
        //  Adds the struct's index to the stack to clear it in the Execute
        //  function above.
        private function StopPeriodic takes integer index returns nothing
            set STACK[SIZE]=index
            set SIZE=SIZE+1
        endfunction
       
        module MissileStruct
           
            private static method missileTerminate takes Missile this returns nothing
                static if thistype.onRemove.exists then
                    call onRemove(this)
                endif
                call this.terminate()
                call StopPeriodic(thistype.typeid)
            endmethod
           
            static method unpin takes Missile this returns nothing
                call this.removeNode()
                call StopPeriodic(thistype.typeid)
            endmethod

            static if thistype.onMissile.exists then
                private static method onMissileLoop takes Missile this returns nothing
                    local Missile node = MissileList.base
                    local real ox = this.x
                    local real oy = this.y
                    loop
                        set node = node.next
                        exitwhen MissileList(node).head
                        loop
                            exitwhen node.head
                            if node!=this and onMissile(this,node,SquareRoot((ox-node.x)*(ox-node.x)+(oy-node.y)*(oy-node.y))) then
                                call missileTerminate(this)
                            endif
                            set node = MissileList(node).next
                        endloop
                    endloop
                endmethod
            endif

            //***********************************************************************
            //  First it takes the struct's first instance, which is the next to the
            //  head (NODE[thistype.typeid].next) and starts iterating through it until it hits
            //  the head again. Then checks if the missile is marked for recycling,
            //  if so it runs the methods and destroys the missile.
            private static method missileIterate takes nothing returns nothing
                local unit u
                local Missile this = NODE[thistype.typeid].next
                call this.move()
                loop
                    exitwhen this.head
                   
                    if this.wantDestroy then
                        static if thistype.onFinish.exists then
                            call thistype.onFinish(this)
                        endif
                        call missileTerminate(this)
                    else
                        if this.recycle then
                            static if thistype.onCollide.exists then
                                static if thistype.onFinish.exists then
                                    if this.target==null then
                                        if thistype.onFinish(this) then
                                            call missileTerminate(this)
                                        else
                                            set this.recycle = false
                                        endif
                                    elseif thistype.onCollide(this,this.target) then
                                        call missileTerminate(this)
                                    else
                                        set this.recycle = false
                                    endif
                                else
                                    if this.target==null then
                                        call missileTerminate(this)
                                    elseif thistype.onCollide(this,this.target) then
                                        call missileTerminate(this)
                                    else
                                        set this.recycle = false
                                    endif
                                endif
                            else
                                static if thistype.onFinish.exists then
                                    if thistype.onFinish(this) then
                                        call missileTerminate(this)
                                    else
                                        set this.recycle = false
                                    endif
                                else
                                    call missileTerminate(this)
                                endif
                            endif
                        else
                            static if thistype.onCollide.exists then
                                if this.collision!=0 then
                                    call GroupEnumUnitsInRange(bj_lastCreatedGroup,this.x,this.y,this.collision,null)
                                    loop
                                        set u = FirstOfGroup(bj_lastCreatedGroup)
                                        exitwhen u==null
                                        if not(IsUnitInGroup(u,this.unitsHit)) and u!=this.target and thistype.onCollide(this,u) then
                                            call missileTerminate(this)
                                        endif
                                        call GroupAddUnit(this.unitsHit,u)
                                        call GroupRemoveUnit(bj_lastCreatedGroup,u)
                                    endloop
                                endif
                            endif
                        endif
                    endif
                   
                    static if thistype.onMissile.exists then
                        call onMissileLoop(this)
                    endif
                   
                    static if thistype.onPeriod.exists then
                        if thistype.onPeriod(this) then
                            call missileTerminate(this)
                        endif
                    endif
                   
                    set this = this.next
                endloop
               
                set u=null
            endmethod
           
            static method launch takes Missile this returns nothing
                call StartPeriodic(thistype.typeid)
                call NODE[thistype.typeid].insertNode(this)
            endmethod
           
            private static method onInit takes nothing returns nothing
                set NODE[thistype.typeid] = Missile.createNode()
                set TIMER[thistype.typeid] = TimerGroup32.create(function thistype.missileIterate)
            endmethod
           
        endmodule
       
    endlibrary
    Demo
    Code (vJASS):
    struct TESTER extends array

        static integer COUNT = 0
        static unit FOOTMAN = null
        static timer t = CreateTimer()
        static boolean b = false
           
        implement MissileStruct
       
        static method onExpire takes nothing returns nothing
            local real x = GetUnitX(FOOTMAN)
            local real y = GetUnitY(FOOTMAN)
            local real a = GetRandomReal(-bj_PI,bj_PI)
            local Missile new = Missile.create(x,y,65,x+1000*Cos(a),y+1000*Sin(a),0)
            set new.speed=10
            set new.model="abilities\\weapons\\WyvernSpear\\WyvernSpearMissile.mdl"
            set new.height=100
            set new.source=FOOTMAN
            set new.collision=80
            call launch(new)
        endmethod
       
        static method onEsc takes nothing returns nothing
            if b then
                call PauseTimer(t)
            else
                call TimerStart(t,0.005,true,function thistype.onExpire)
            endif
            set b = not b
        endmethod
       
        static method onInit takes nothing returns nothing
            local trigger trig=CreateTrigger()
            call TriggerRegisterPlayerEvent(trig,Player(0),EVENT_PLAYER_END_CINEMATIC)
            call TriggerAddAction(trig,function thistype.onEsc)
            set FOOTMAN=CreateUnit(Player(0),'hfoo',0,0,270)
        endmethod
       
    endstruct

    Upcoming features:
    -onDestructable method
    -Improve onMissile's efficiency
    -Math improvements
     
    Last edited by a moderator: Jul 14, 2013
  2. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    A lot of variables should be readonly instead of public. The source unit can remain public, for example, but most should be readonly, especially the dummy unit.

    GetWidgetLife(this.target)>=0.405 should have the ">=" changed to ">" but even then it needs to be replaced entirely with IsUnitType, UNIT_TYPE_DEAD. The GetUnitTypeId check should remain of course.

    bj_PI/2 should be stored into a constant to avoid computing it dynamically.

    While I like that you made it a module much of it could be centralized (as much of it is uniform and isn't module-specific). This would keep the code way way shorter.
     
  3. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    Updated the system
     
  4. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    The unit should be readonly, for example if you wanted to add an additional special effect to the missile. You could also centralize the OnInit method and make it take "code" as an argument, meaning you could also just return nothing from that module and make it slightly shorter than it already is.

    I really like this resource and I'm planning to use it for my TimeWarp spell when I update it soon.
     
  5. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    Updated to v1.0.5.
    -Some minor changes that increase the FPS by 4
    -Reduced the amount of code in the module
    -Fixed an issue in which missiles that lost their targets because it died cause the missile to never end
     
  6. Laiev

    Laiev

    Joined:
    Oct 11, 2008
    Messages:
    273
    Resources:
    1
    Template:
    1
    Resources:
    1
    -Fixed an issue in which missiles that lost their targets because it died cause the missile to never end

    Thank you, serious :D
    I show'd you that thing in your demo and you fix.

    I'm planning use this, actually is better then other projectiles system (IMO).
     
  7. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Dirac, if LinkedListModule recycled using the "prev" variable, you could iterate forwards without causing a disruption when nodes get removed (eliminating need for the "safe" variable). I did that method in that one demo I showed you. This would improve performance by a tiny bit but it makes LinkedListModule a lot easier to work with at the same time.

    For other improvements I would look for ways to save repeat-referencing an array variable. For example, this.distance should be stored into a local. A local declaration is about the same speed as an array lookup. So even if you save just 1 array lookup you are saving a lot of performance. Which really matters with projectile systems.
     
  8. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    You're right in both suggestions.
    For the first time i get what you meant with the "prev" recycling.
    Updated to 1.0.6 to apply both of your suggestions, LinkedListModule will be updated too
     
  9. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    The .move method in the Missile struct does affect performance because the locals need to be created each time. What I advise is to turn that into a static method, iterate all instance through it (for example make it a simple function call by the "Execute" private function) and let each struct iterate themselves as well. This way the locals only get created once per 0.03125 instead of once per instance per 0.03125.

    I still see some areas that you could chomp out repeat-array references using locals, for example this.target and this.x/y are used a lot.

    Also adding some double-free protection could be useful to the destroy method (and don't make it debug-only, performance doesn't matter so much here).

    "model" should just be a readonly variable instead of using both the operator and "fP".

    The following variables you should just set from the "destroy" method, instead of the "create" method, because arrays are already initialized to those values by default. Nulling handles at the end of the destroy method will also keep the handle ID stack as low as possible so they aren't consumed by recycled instances.

    Code (vJASS):

                set source=null
                set target=null
                set acceleration=0
                set height=0
                set turn=0
                set open=0
                set collides=false
                set recycle=false
                set fP=""
               
                set sM=0
                set sX=0
                set sY=0
                set slide=0
     
     
    Last edited: Dec 1, 2011
  10. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    Ok there are two options now:
    -Revert the module to the way it was before (more text, all the "move" method inlined in the module) to gain that bonus speed with local variables. There is no way to call an "onImpact" or "onPeriod" method out of an stack that isn't inside the module.
    -Leave it like it is now
    Also in the next version i'll fix an issue that caused missiles that chase targets with flying height to have wrong Z values.

    EDIT: You're not going to believe this, but moving the whole "nulling" process to the destroy method causes the fps to drop BY A HUGE AMOUNT
    Nulling at allocate = 17 fps with 700 projs
    Nulling at deallocate = crashes with 700 projs

    EDIT2: Just tested by adding the move method to the module and it gained 7 fps (24)
     
    Last edited: Dec 1, 2011
  11. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Dirac you missed what I said. I think you understand programming better than you understand my explainations. Which is fine because my mind works quite similar.

    Instead of this:

    Code (vJASS):

                        if this.move() and thistype.onImpact(this,this.target) then
                            call this.destroy()
                        endif
     


    Do this:

    Code (vJASS):

                        if thistype.onImpact(this,this.target) then
                            call this.destroy()
                        endif
     


    Then, change your .move method to this:

    Code (vJASS):

            private static method move takes nothing returns nothing
                local real a
                local real tx
                local real ty
                local real d
                local real ox
                local real oy
                local unit u
                local thistype this=NODE[INDEX].next
                loop
                    exitwhen this.head
                    set ox = this.oX
                    set oy = this.oY
                    set u = this.target
                    if u!=null and not(IsUnitType(u,UNIT_TYPE_DEAD)) and GetUnitTypeId(u)!=0 then
                        set tx=GetUnitX(u)
                        set ty=GetUnitY(u)
                        set a=Atan2(ty-this.y,tx-this.x)
                        call MoveLocation(GetZ,tx,ty)
                        if this.turn!=0 then
                            if Sin(a-this.cA)>=0 then
                                set this.angle=this.cA+this.turn
                            else
                                set this.angle=this.cA-this.turn
                            endif
                        else
                            set this.angle=a
                        endif
                        set d=SquareRoot((tx-ox)*(tx-ox)+(ty-oy)*(ty-oy))
                        set this.distance=d
                        set this.slide=SquareRoot((this.x-ox)*(this.x-ox)+(this.y-oy)*(this.y-oy))
                        set this.fZ=(GetLocationZ(GetZ)+this.oZ-GetUnitFlyHeight(u))/d
                        call SetUnitFacing(this.dummy,this.cA*bj_RADTODEG)
                    else
                        set d=this.distance
                        set this.target=null
                        set this.slide=this.slide+this.sM
                    endif
                    set this.x=this.x+this.sX
                    set this.y=this.y+this.sY
                   
                    if this.open!=0 then
                        set a=4*this.open*this.slide*(d-this.slide)/(d*d)
                        set tx=this.x+a*Cos(this.cA+HALF_PI)
                        set ty=this.y+a*Sin(this.cA+HALF_PI)
                        call SetUnitFacing(this.dummy,(this.cA+Atan(-(8*this.open*this.slide-4*d*this.open)/(d*d)))*bj_RADTODEG)
                    else
                        set tx=this.x
                        set ty=this.y
                    endif
               
                    if this.height!=0 or this.fZ!=0 then
                        set this.z=4*this.height*this.slide*(d-this.slide)/(d*d)+this.fZ*(d-this.slide)+this.tZ
                        call MoveLocation(GetZ,tx,ty)
                        call SetUnitFlyHeight(this.dummy,this.z-GetLocationZ(GetZ),0)
                        call SetUnitAnimationByIndex(this.dummy,R2I((Atan(-(8*this.height*this.slide-4*d*this.height)/(d*d))-Atan(this.fZ))*bj_RADTODEG)+90)
                    endif
                   
                    set this.sX=this.sX+this.aX
                    set this.sY=this.sY+this.aY
                   
                    call SetUnitX(this.dummy,tx)
                    call SetUnitY(this.dummy,ty)
                   
                    if this.slide>=d then
                        call this.destroy()
                    endif
                    set this=this.next
                endloop
                set u = null
            endmethod
     


    And simply call .move from the "Expire" function.

    I can hardly believe that nulling the variables from the destroy method is causing that. I would have to see some evidence (like your demo code you're using to test).

    Also, the dummy unit should just have Amrf added to it from object editor. No reason to require the JASS script for it. It doesn't need to be removed from the dummy either. I have an object merger line in MissileRecycler which you can use to create the perfect projectile for this system as well.
     
  12. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    How are you supposed to call onImpact? remember that it's called for every instance and only when the missile either hits a target or ends it's course. If i call the move method on the Expire method how do i know which missiles hit a target? It really makes no sense to me.
    Also to benchmark i'm using the demo code on the main post but instead of 0.05 timeout i'm using 0.005 (which generates almost 700 active projectiles)
    Also i couldn't believe it either but it does, go ahead and test it yourself
     
  13. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    You could set a boolean "this.toDestroy = true" and the module will check if it's supposed to be destroyed or not. So you'd change the comparison to "if this.toDestroy and this.onImpact() then"

    Remember also that modules can access private members of both the library the module is declared in as well as the library it's implemented in, so this "toDestroy" thing could be a private keyword.
     
  14. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    basically i would have to iterate two times between all the nodes of the struct then, one to move it and other one to call onImpact, sounds like it's going to be slow as hell
     
  15. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    One array lookup is about the same-speed as a local declaration, and the "move" method would end up having around 10 local declarations (once you optimize it). Doing it my way would add maybe 2 or 3 array lookups and saves on the function call as well. There is nothing wrong with iterating twice, because iterating is not where the bottleneck is.
     
  16. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    Finished applying your suggestions (v1.0.7), it seems that it's working fine after all, it's slightly faster and saves up module code size.
    I would make MissileRecycler non-optional if it wasn't because recycled missiles display the previous effect's destroy animation.
    Regarding the nulling of the array variables, i have no idea what on earth is causing this issue, it seems that array values are barely initialized, if the user doesn't assign a proper value before using them reding them is slower (and according to my results a LOT slower)

    v1.0.7 changelog
    -Increased efficiency and reduced module code size
    -Fixed an issue homing missiles had when chasing units with flying height.
    -Removed Autofly as a requirement, the dummy is supposed to have crow form set with the object editor
     
  17. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    You can optimize your "move" method even further to this:

    Code (vJASS):

            static method move takes nothing returns nothing
                local real a
                local real d
                local real s
                local real x
                local real y
                local real tx
                local real ty
                local real ox
                local real oy
                local unit u
                local unit m
                loop
                    exitwhen this.head
                    set x = this.x
                    set y = this.y
                    set ox=this.oX
                    set oy=this.oY
                    set m = this.dummy
                    set u = this.target
                    if u!=null and not(IsUnitType(u,UNIT_TYPE_DEAD)) and GetUnitTypeId(u)!=0 then
                        set tx=GetUnitX(u)
                        set ty=GetUnitY(u)
                        set a=Atan2(ty-y,tx-x)
                        call MoveLocation(GetZ,tx,ty)
                        if this.turn!=0 and not(Cos(this.cA-a)>=Cos(this.turn)) then
                            if Sin(a-this.cA)>=0 then
                                set this.angle=this.cA+this.turn
                            else
                                set this.angle=this.cA-this.turn
                            endif
                        else
                            set this.angle=a
                        endif
                        set d=SquareRoot((tx-ox)*(tx-ox)+(ty-oy)*(ty-oy))
                        set s=SquareRoot((x-ox)*(x-ox)+(y-oy)*(y-oy))
                        set this.distance=d
                        set this.slide=s
                        set this.fZ=(GetLocationZ(GetZ)+GetUnitFlyHeight(u))/d
                        call SetUnitFacing(m,this.cA*bj_RADTODEG)
                    else
                        set this.target=null
                        set this.slide=this.slide+this.sM
                        set d=this.distance
                        set s=this.slide
                    endif
                   
                    set this.x=x+this.sX
                    set this.y=y+this.sY
                   
                    if this.open!=0 then
                        set a=4*this.open*s*(d-s)/(d*d)
                        set x=x+a*Cos(this.cA+HALF_PI)
                        set y=y+a*Sin(this.cA+HALF_PI)
                        call SetUnitFacing(m,(this.cA+Atan(-(8*this.open*s-4*d*this.open)/(d*d)))*bj_RADTODEG)
                    endif
                   
                    if this.height!=0 or this.fZ!=0 then
                        set this.z=4*this.height*s*(d-s)/(d*d)+this.oZ+this.fZ*s
                        call MoveLocation(GetZ,x,y)
                        call SetUnitFlyHeight(m,this.z-GetLocationZ(GetZ),0)
                        call SetUnitAnimationByIndex(m,R2I((Atan(-(8*this.height*s-4*d*this.height)/(d*d))-Atan(-this.fZ))*bj_RADTODEG + 90.5))
                    endif
                   
                    set this.sX=this.sX+this.aX
                    set this.sY=this.sY+this.aY
                   
                    call SetUnitX(m,x)
                    call SetUnitY(m,y)
                   
                    if s>=d then
                        set this.recycle=true
                    endif
                   
                    set this=this.next
                endloop
                set u = null
                set m = null
            endmethod
     


    I have cached a lot of your repeat-references into locals so it should give a good speed boost. Also don't forget you need to round up as well, so notice how I changed the +90 outside of the R2I to a +90.5 inside the R2I.

    You can also do most of the SetUnitAnimationByIndex math from the create method or from one of the arc/height operators. Look at how Berb does it in his Custom Projectiles library, it's very simple math for him but way more complicated for you.
     
  18. Dirac

    Dirac

    Joined:
    Jun 20, 2011
    Messages:
    249
    Resources:
    3
    JASS:
    3
    Resources:
    3
    That's a bold statement, i've been thinking already in another way of doing it and i'll soon check berb's code.
     
  19. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,773
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    I recommend looking at XE Missile by Anitarf (it is found in the XE library now). The math it uses is almost as simple as it gets, and I built my Missile library off of an even further optimized version using some of his tricks and some of Berb's tricks. Tonight I will link you to that system (I have to upload it first) and then the combination of yours + mine will be the best ever.
     
  20. Magtheridon96

    Magtheridon96

    Joined:
    Dec 12, 2008
    Messages:
    6,007
    Resources:
    26
    Maps:
    1
    Spells:
    8
    Tutorials:
    7
    JASS:
    10
    Resources:
    26
    This looks very decent :)
    I like it. This is the first projectile system I stumble upon that's the kind I'd use ;)
    Kenny's has way too much overhead, Berb's is slightly better, but it has some overhead too, Bribe's old projectile system was bad, etc...

    One problem: Why aren't you caching the Cos(value)/Sin(value) reals in those operators into locals? Sin and Cos are pretty slow functions :/ (They should be faster than SquareRoot and Pow though)

    And over here:

    return createEx(CreateUnit(Player(15),DUMMY_ID,ox,oy,a*bj_RADTODEG),ox,oy,oz,a,d,z)


    The player you're creating it for should be configurable.

    I'm going to do some benchmarks for this.