1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still haven't received your rank award? Then please contact the administration.
    Dismiss Notice
  4. Let your favorite entries duke it out in the 15th Techtree Contest Poll.
    Dismiss Notice
  5. Weave light to take you to your highest hopes - the 6th Special Effect Contest is here!
    Dismiss Notice
  6. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Relativistic Missiles

Submitted by chopinski
This bundle is marked as approved. It works and satisfies the submission rules.
Relativistic Missiles
v1.3 by Chopinski
Introduction
First of all, Credits and thanks to BPower, Dirac, and Vexorian for their missile systems at which I based this system upon, and to AGD for the help with effect orientation methods.

This is a Missile system that I put together initially to fulfill my resources needs. Very often while I was creating system, spells and maps I found myself in need of a such systems, but all the available options usually have a little flaw that forced me to used something else.

For example, let's say I wanted a missile that can arc and homing to a target, well there goes BPower's system. It can arc/curve but can't do homing with arc/curve natively. So the options left are Vexorian xemissile which can arc and do homing but cant curve, and Dirac's Missile system. Then again more problems with those, because Vexorian system do not have a lot of the useful events like Dirac's and BPower's systems. Dirac's system can arc/curve and do homing but it's T32 timer is never stopped and it do not have some useful events that the BPower system has. So I decided to get all three system together and build my own system with the stuff I think is useful and with the syntax I think is the most friendly, because I'm not a genius of vJASS myself.

What's different in this system? (read if you dare, muahhaaa)
Ok, so why it's called "Relativistic Missiles"? Well because mid development I realize that the way missiles were being processed could be optimized and the Einstein postulates about Time Dilation by Gravity gave the idea behind the implementation. Yeah, I know, WTH?

When it comes to Missile systems, performance is everything, and all missiles systems do basically the same thing when processing missiles instances, they loop every set period, which is usually 1/32, through all instances and do their calculations and event calls. The problem with it, is that the more instances existing at a given moment the greater will be the tanking of the game frame rate because the game will lower its fps so all that's need to be done before the next frame gets drawn have time to do so, and that's where the Einstein postulates gave me an idea.

See, when we study Relativity, we learn that gravity dilates time, the greater the gravitational potential (more or less mass) the slower time passes relative to an outside point of reference. Gravity in this analogy would be the amount of missiles instances currently existing, so instead of letting the game frame rate drops drastically to move all those instances, I make the missile instances have their time dilated.

Ok, but how?? Simple, first we need to find what I'll be calling the system Sweet Spot, which is the amount of missiles the game can handle without dropping to much performance per given period, which I found out to be between 150 and 200 missiles. Then we limit the amount of missiles processed per period to this sweet spot, and move the next missile in the line to be processed and all other behind it to the front, In terms of data structures, think of a circular list with a head and tail, and we move the head and tail to change the initial and final positions. I did it with arrays because they are a bit faster and because I'm lazy :p.

But that's not all, because by limiting the amount of missiles processed, we delay their next process call by (total amount)/(sweet spot), so now their speed needs to be corrected and relativity comes full circle now, because time dilation has it's relation with Length Contraction, so we increment the missiles speed by this factor.

Also, something very important is that this system is using only effects as missiles, thanks to the new natives introduced in 1.31, so no dummy units, no CreateUnit() and SetUnitFacing() blasphemy. I've included in the test map a version of this system that do use the dummy method for those that do not have the newer versions of the game. I Also included a version of this system that uses the usual process method, so processing all missiles in a single blow.

Results
I've taken the liberty to test this system against the king of Missile systems for some time, BPower's System. Below, there are 3 videos I make for stress test. The first from the left is BPower's Missile System reworked by AGD. Amazing job btw. You will be able to notice what I was talking about earlier about the sweet spot, when there are more than 150 or so missiles being processed, the fps goes basically to 0 and it freezes until the missiles are finished.

The video in the middle is my system in its normal version (not relativistic). It performs a little better (I've build it a little differently), but in the same way, when the missile count near 250, the fps starts to go down hill real quick.

The video in the right is the relativistic version, notice the missile count and fps compared to the 2 other system. At about 600 missiles fps get really low, but not so much because of the amount of missiles being processed but because of the amount of visual effects in the screen. If the screen was moved to the side where not so many stuff is visible, fps goes up. In my internal tests I was reaching more than 1200 missiles with about 16 fps but with no screen freezes.


Enough of your blabbering, Hand over the codes!
Code (vJASS):

Coordinates impact -> the impact coordiantes (x, y, z, anngle, slope, alpha, distance)
Coordinates origin -> same, but for origin
Effect         effect -> The missile (only in version 1.31+)
//-------------------------------------------------------
readonly real    x -> current x position of the missile
readonly real    y -> current y position of the missile
readonly real    z -> current z position of the missile
readonly real    prevX -> last x position of the missile
readonly real    prevY -> last y position of the missile
readonly real    prevZ -> last z position of the missile
readonly real    turn -> the turn rate of the missile
readonly real    veloc -> the missile speed (change it using the .speed or .duration operator)
readonly real    travel -> distance travelled
readonly unit    dummy -> the dummy unit (only in version 1.30.x -)
readonly boolean launched -> true if the missile was already launched
readonly boolean allocated -> true if the missile can still perform its movement operations
//-------------------------------------------------------
unit    source -> the source unit (optional)
unit    target -> the target unit (optional and if set to a valid unit the missile will be homing)
player  owner -> the owner of the missile (optional)
real    collision -> the collision size of the missile (optional. The onHit and onDestructable events only works if this is greater than 0)
real    damage -> stores the damage you want the missile to deal (optional)
real    acceleration -> if different than 0 then the missile will change its speed with time (optional)
integer data -> just in case you want to pass some information to the missile to retrieve it later (optional)
 

Code (vJASS):

/* -------------------------- Model of the missile -------------------------- */
method operator model= takes string path returns nothing
method operator model takes nothing returns string

/* ----------------------------- Curved movement ---------------------------- */
method operator curve= takes real value returns nothing
method operator curve takes nothing returns real

/* ----------------------------- Arced Movement ----------------------------- */
method operator arc= takes real value returns nothing
method operator arc takes nothing returns real

/* ------------------------------ Effect scale ------------------------------ */
method operator scale= takes real v returns nothing
method operator scale takes nothing returns real

/* ------------------------------ Missile Speed ----------------------------- */
method operator speed= takes real newspeed returns nothing
method operator speed takes nothing returns real

/* ------------------------------- Flight Time ------------------------------ */
method operator duration= takes real flightTime returns nothing
 

Code (vJASS):

(call)
method deflect takes real x, real y returns nothing
    -   Deflects the missile from the target point, changing
    -   its course to the new x and y.
    -   If target is a valid unit, deflect will null the target
    -   and then deflects the missile

(call)
method deflectZ takes real x, real y, real z returns nothing
    -   Deflects the missile from the target point, changing
    -   its course to the new x, y and z.
    -   If target is a valid unit, deflect will null the target
    -   and then deflects the missile
 
(call)
method bounce takes nothing returns nothing
    -   Bounces the missile from its current Z position.
    -   Useful when assigning targets to missiles that were
    -   already active, it fixes the rough Z position change.

(call)
method flush takes widget w returns nothing
    -   call this method to allow the missile to be able to hit
    -   a unit or destructable again

(call)
method flushAll nothing returns nothing
    -   flushes the hit table for the missile
 
(call)
method hitted takes widget w returns boolean
    -   returns true if the missile has hitted the widget

(call)
method attach takes string model, real dx, real dy, real dz, real scale returns effect
    -   attach another effect to the main missile.
    -   dx, dy and dz are offsets from the main misisle position

(call)
method dettach takes effect attachment returns nothing
    -   dettahes the effect from the missile, destrying it

 

Code (vJASS):

(optional)
method onHit takes unit hit returns boolean
    -   Runs every time the missile collides with a unit.
    -   If returns true the missile is destroyed.

(optional)
method onDestructable takes destructable dest returns boolean
    -   Runs every time the missile collides with a destructable.
    -   If returns true the missile is destroyed.

(optional)
method onItem takes item i returns boolean
    -   Runs every time the missile collides with an item.
    -   If returns true the missile is destroyed.

(optional)
method onMissile takes Missiles missile returns boolean
    -   Runs every time the missile collides with another missile.
    -   By default, missiles collide only once
    -   If returns true the missile is destroyed.
    -   Please, be aware that this method can be very performance
    -   intensive, so careful!
 
(optional)
method onPeriod takes nothing returns boolean
    -   Runs every period.
    -   If returns true the missile is destroyed.

(optional)
method onFinish takes nothing returns boolean
    -   Runs whenever the missile finishes its course.
    -   If returns true the missile is destroyed.

(optional)
method onTerrain takes nothing returns boolean
    -   Runs whenever the missile collides with terrain height
    -   greater then the missile current z
    -   If returns true the missile is destroyed.

(optional)
method onRemove takes Missile this returns nothing
    -   Runs whenever the missile is deallocated.
 

Code (vJASS):

library Missiles requires optional WorldBounds, optional DummyRecycler
    /* ----------------------- Missiles v1.3 by Chopinski ----------------------- */
    // Thanks and Full Credits to BPower, Dirac and Vexorian for the Missile Library's at which i based
    // this Missiles library. Credits to Vexorian for the dummy model.
   
    // How to Import:
    //     1 -First copy the Missile dummy unit into your map and then import the dummy.mdx
    //     model, setting the missile dummy model path to imported dummy.mdx model.
    //     Dummy model: https://www.hiveworkshop.com/threads/vexorians-dummy-model.149230/
    //     WorldBounds: https://raw.githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
    //     DummyRecycler: https://www.hiveworkshop.com/threads/dummy-recycler-v1-25.277659/ (Highly Recommended)
   
    //     2 - Copy this library into your map and set the
    //         DUMMY_RAW_CODE to the raw code of the missile dummy (ctrl + d) and you
    //         are done
   
    // How to Use:
    //         This system works almost identicaly to the Missile library by BPower but
    //         with a more user friendly interface in my opinion. Also this system
    //         allows you to create Arc/Curved Homing missiles like Dirac's system.
   
    //         Differently than both BPower and Dirac systems, you are not required
    //         to use the ImplementStruct at the end of your struct. To make your struct
    //         behave like a Missile simply make it extends Missiles and you are done.
   
    //         After that you will have access to the events in the MissileEvents Interface.
    //         Simply declare the event you want for your strcut to have and the system
    //         takes care of the rest. You can access the members and functions using
    //         the "this" or "." syntax which is a plus. to terminate a missile, simply
    //         return true or call terminate() within the method. Example:
   
    //         struct MySpell extends Missiles
    //             method onPeriod takes nothing returns boolean
    //                 // will display the missile current position
    //                 call ClearTextMessages()
    //                 call BJDebugMsg(I2S(.x))
    //                 call BJDebugMsg(I2S(.y))
    //                 call BJDebugMsg(I2S(.z))
   
    //                 return false
    //             endmethod
    //         endstruct
               
    //         function Spell takes nothing returns nothing
    //             // the create method takes the initial and the final coordinates
    //             // if the target member is set, the missile home
    //             local MySpell spell = MySpell.create(fromX, fromY, fromZ, toX, toY, toZ)
   
    //             set spell.source   = GetTriggerUnit()
    //             set spell.target   = GetSpellTargetUnit() -> when a target is specified the missile will be homing
    //             set spell.speed    = 1000 -> warcraft 3 speed unit
    //             set spell.duration = 1.5 -> will set the speed to match the time passed
    //             set spell.model    = "Some Model.dmx"
    //             set spell.scale    = 1.3
    //             set spell.arc      = 30 (degrees converted to radians internally)
    //             set spell.curve    = GetRandomReal(-40, 40) (degrees converted to radians internally
   
    //             call spell.launch()
    //         endfunction
   
    //         Available members and methods:
    //             Coordinates impact               -> the impact coordiantes (x, y, z, anngle, slope, alpha, distance)
    //             Coordinates origin               -> same, but for origin
    //             //-------------------------------------------------------
    //             readonly real    x               -> current x position of the missile
    //             readonly real    y               -> current y position of the missile
    //             readonly real    z               -> current z position of the missile  
    //             readonly real    prevX           -> last x position of the missile
    //             readonly real    prevY           -> last y position of the missile
    //             readonly real    prevZ           -> last z position of the missile
    //             readonly real    height          -> the arc of the missile (change it using the .arc operator)
    //             readonly real    turn            -> the turn rate of the missile
    //             readonly real    open            -> the curvature of the missile (change it using the .curve operator)
    //             readonly real    veloc           -> the missile speed (change it using the .speed or .duration operator)
    //             readonly real    travel          -> distance travelled
    //             readonly unit    dummy           -> the dummy unit
    //             readonly boolean launched        -> true if the missile was already launched
    //             readonly boolean allocated       -> true if the missile can still perform its movement operations
    //             //-------------------------------------------------------
    //             unit    source                   -> the source unit (optional)
    //             unit    target                   -> the target unit (optional and if set to a valid unit the missile will be homing)
    //             player  owner                    -> the owner of the missile (optional)
    //             boolean collideZ                 -> set to true if you want the missile to consider z in collisions
    //             real    collision                -> the collision size of the missile (optional. The onHit and onDestructable events only works if this is greater than 0)
    //             real    damage                   -> stores the damage you want the missile to deal (optional)
    //             real    acceleration             -> if different than 0 then the missile will change its speed with time (optional)
    //             integer data                     -> just in case you want to pass some information to the missile to retrieve it later (optional)
   
    //             (call)
    //             method deflect takes real x, real y returns nothing
    //                 -   Deflects the missile from the target point, changing
    //                 -   it's course to the new x and y.
    //                 -   If target is a valid unit, deflect will null the target
    //                 -   and then deflects the missile
   
    //             (call)
    //             method deflectZ takes real x, real y, real z returns nothing
    //                 -   Deflects the missile from the target point, changing
    //                 -   it's course to the new x, y and z.
    //                 -   If target is a valid unit, deflect will null the target
    //                 -   and then deflects the missile
                 
    //             (call)
    //             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.
   
    //             (call)
    //             method flush takes widget w returns nothing
    //                 -   call this method to allow the missile to be able to hit
    //                 -   a unit or destructable again
   
    //             (call)
    //             method flushAll nothing returns nothing
    //                 -   flushes the hit table for the missile
                 
    //             (call)
    //             method hitted takes widget w returns boolean
    //                 -   returns true if the missile has hitted the widget
   
                   
    //             (optional)
    //             method onHit takes unit hit returns boolean
    //                 -   Runs every time the missile collides with a unit.
    //                 -   If returns true the missile is destroyed.
   
    //             (optional)
    //             method onDestructable takes destructable dest returns boolean
    //                 -   Runs every time the missile collides with a destructable.
    //                 -   If returns true the missile is destroyed.

    //             (optional)
    //             method onItem takes item i returns boolean
    //                 -   Runs every time the missile collides with an item.
    //                 -   If returns true the missile is destroyed.

    //             (optional)
    //             method onMissile takes Missiles missile returns boolean
    //                 -   Runs every time the missile collides with another missile.
    //                 -   By default, missiles collide only once
    //                 -   If returns true the missile is destroyed.
    //                 -   Please, be aware that this method can be very performance
    //                 -   intensive, so careful!
                     
    //             (optional)
    //             method onPeriod takes nothing returns boolean
    //                 -   Runs every period.
    //                 -   If returns true the missile is destroyed.
           
    //             (optional)
    //             method onFinish takes nothing returns boolean
    //                 -   Runs whenever the missile finishes it's course.
    //                 -   If returns true the missile is destroyed.
           
    //             (optional)
    //             method onTerrain takes nothing returns boolean
    //                 -   Runs whenever the missile collides with terrain height
    //                 -   greater then the missile current z
    //                 -   If returns true the missile is destroyed.
           
    //             (optional)
    //             method onRemove takes Missile this returns nothing
    //                 -   Runs whenever the missile is deallocated.
    // /* ----------------------------------- END ---------------------------------- */
   
    /* --------------------------------- System --------------------------------- */
    globals
        // The raw code of the dummy unit. Match it with the
        // Object Editor id
        private constant integer DUMMY_RAW_CODE = 'dumi'
        // The update period of the system
        public  constant real    PERIOD             = 1./40.
        // The max amount of Missiles processed in a PERIOD
        // You can play around with both these values to find
        // your sweet spot. If equal to 0, the system will
        // process all missiles at once every period.
        public  constant real    SWEET_SPOT         = 150
        // the avarage collision size compensation when detecting
        // collisions
        private constant real    COLLISION_SIZE     = 128.
        // item size used in z collision
        private constant real    ITEM_SIZE          = 16.
        // uint size used in z collision
        private constant real    BODY_SIZE          = 64.
        // How long takes for the missile to be removed.
        // This is necessary so the death animation of the
        // effect can play through
        private constant real    RECYCLE_TIME       = 2.
        // Needed, dont touch. Seriously, dont touch!
        private location         LOC                = Location(0., 0.)
        private rect             RECT               = Rect(0., 0., 0., 0.)
        private hashtable        table              = InitHashtable()
    endglobals

    // The available methods the user can implement in their structs
    private interface MissileEvents
        method onHit takes unit hit returns boolean defaults false
        method onItem takes item i returns boolean defaults false
        method onMissile takes Missiles missile returns boolean defaults false
        method onTerrain takes nothing returns boolean defaults false
        method onPeriod takes nothing returns boolean defaults false
        method onFinish takes nothing returns boolean defaults false
        method onDestructable takes destructable dest returns boolean defaults false
        method onRemove takes nothing returns nothing defaults nothing
    endinterface

    function GetLocZ takes real x, real y returns real
        call MoveLocation(LOC, x, y)
        return GetLocationZ(LOC)
    endfunction

    //Little snippet to remove the dummy unit after a delay
    //I'm not using MissileRecycler because of a leaking problem i discover
    //This will only be used if you dont have DummyRecycler
    private struct Timed
        timer t
        unit  u

        static method onPeriod takes nothing returns nothing
            local thistype this = LoadInteger(table, GetHandleId(GetExpiredTimer()), 0)

            call FlushChildHashtable(table, GetHandleId(.t))
            call PauseTimer(.t)
            call DestroyTimer(.t)
            call RemoveUnit(.u)

            set .u = null
            set .t = null

            call .deallocate()
        endmethod

        static method Recycle takes unit u, real timeout returns nothing
            local thistype this = thistype.allocate()

            set .u = u
            set .t = CreateTimer()
            call SaveInteger(table, GetHandleId(.t), 0, this)
       
            call TimerStart(.t, timeout, false, function thistype.onPeriod)
        endmethod
    endstruct

    //Credits to Dirac for AdvLoc and BPower for fixing an error in it
    private struct Coordinates
        readonly real x
        readonly real y
        readonly real z
        readonly real angle
        readonly real distance
        readonly real square
        readonly real slope
        readonly real alpha

        // Creates an origin - impact link.
        private thistype ref

        private static method math takes thistype a, thistype b returns nothing
            local real dx
            local real dy
            loop
                set dx = b.x - a.x
                set dy = b.y - a.y
                set dx = dx*dx + dy*dy
                set dy = SquareRoot(dx)
                exitwhen dx != 0. and dy != 0.
                set b.x = b.x + .01
                set b.z = b.z - GetLocZ(b.x -.01, b.y) + GetLocZ(b.x, b.y)
            endloop

            set a.square   = dx
            set a.distance = dy
            set a.angle    = Atan2(b.y - a.y, b.x - a.x)
            set a.slope    = (b.z - a.z)/dy
            set a.alpha    = Atan(a.slope)*bj_RADTODEG
            // Set b.
            if b.ref == a then
                set b.angle     = a.angle + bj_PI
                set b.distance  = dy
                set b.slope     = -a.slope
                set b.alpha     = -a.alpha
                set b.square    = dx
            endif
        endmethod

        static method link takes thistype a, thistype b returns nothing
            set a.ref = b
            set b.ref = a
            call math(a, b)
        endmethod

        method move takes real toX, real toY, real toZ returns nothing
            set x = toX
            set y = toY
            set z = toZ + GetLocZ(toX, toY)
            if ref != this then
                call math(this, ref)
            endif
        endmethod

        method destroy takes nothing returns nothing
            call .deallocate()
        endmethod

        static method create takes real x, real y, real z returns Coordinates
            local thistype this = thistype.allocate()
            set ref = this
            call move(x, y, z)
            return this
        endmethod
    endstruct
   
    /* ------------------------------ Missile Sytem ----------------------------- */
    struct Missiles extends MissileEvents
        private static thistype array missiles
        private static integer        didx      = -1
        private static integer        last      = 0
        private static real           dilation
        private static timer          t         = CreateTimer()
        private static group          hitGroup  = CreateGroup()
        private static thistype       temp      = 0
        //-------------------------------------------------------
        private real     cA     // current angle
        private effect   sfx    // effect
        private string   fxpath // model
        private real     size   // scale
        private real     height // Arcs
        private real     open   // Curves
        private real     toZ    // Necessary to fix a bug for homming missiles
        //-------------------------------------------------------
        Coordinates      impact
        Coordinates      origin
        //-------------------------------------------------------
        readonly real    x
        readonly real    y
        readonly real    z
        readonly real    prevX
        readonly real    prevY
        readonly real    prevZ
        readonly real    turn
        readonly real    veloc
        readonly real    travel
        readonly unit    dummy
        readonly boolean launched
        readonly boolean allocated
        //-------------------------------------------------------
        unit             source
        unit             target
        player           owner
        boolean          collideZ
        real             collision
        real             damage
        real             acceleration
        integer          data
        //-------------------------------------------------------
   
        /* -------------------------- Model of the missile -------------------------- */
        method operator model= takes string path returns nothing
            call DestroyEffect(sfx)
            set fxpath = path
            set sfx  = AddSpecialEffectTarget(path, dummy, "origin")
        endmethod
   
        method operator model takes nothing returns string
            return fxpath
        endmethod
   
        /* ----------------------------- Curved movement ---------------------------- */
        method operator curve= takes real value returns nothing
            set open = Tan(value*bj_DEGTORAD)*origin.distance
        endmethod
   
        method operator curve takes nothing returns real
            return Atan(open/origin.distance)
        endmethod
   
        /* ----------------------------- Arced Movement ----------------------------- */
        method operator arc= takes real value returns nothing
            set height = Tan(value*bj_DEGTORAD)*origin.distance/4
        endmethod
   
        method operator arc takes nothing returns real
            return Atan(4*height/origin.distance)
        endmethod
   
        /* ------------------------------ Effect scale ------------------------------ */
        method operator scale= takes real v returns nothing
            call SetUnitScale(dummy, v, v, v)
            set size = v
        endmethod
   
        method operator scale takes nothing returns real
            return size
        endmethod

        /* ------------------------------ Missile Speed ----------------------------- */
        method operator speed= takes real newspeed returns nothing
            set veloc = newspeed*PERIOD
        endmethod

        method operator speed takes nothing returns real
            return veloc
        endmethod

        /* ------------------------------- Flight Time ------------------------------ */
        method operator duration= takes real flightTime returns nothing
            set veloc = RMaxBJ(0.00000001, (origin.distance - travel)*PERIOD/RMaxBJ(0.00000001, flightTime))
        endmethod

        /* ---------------------------- Bound and Deflect --------------------------- */
        method bounce takes nothing returns nothing
            local real locZ = GetLocZ(x, y)

            // This is here just to avoid an infinite loop
            // with a deflect being called within an onTerrain
            // event
            if z < locZ then
                set z = locZ
                call impact.move(impact.x, impact.y, origin.z - GetLocZ(impact.x, impact.y))
            endif
            call origin.move(x, y, origin.z - GetLocZ(origin.x, origin.y))
            set travel = 0
        endmethod
   
        method deflect takes real tx, real ty returns nothing
            if target != null then
                set target = null
            endif

            call impact.move(tx, ty, impact.z - GetLocZ(impact.x, impact.y))
            call bounce()
        endmethod

        method deflectZ takes real tx, real ty, real tz returns nothing
            call impact.move(impact.x, impact.y, tz)
            call deflect(tx, ty)
        endmethod

        /* ---------------------------- Flush hit targets --------------------------- */
        method flushAll takes nothing returns nothing
            call FlushChildHashtable(table, this)
        endmethod

        method flush takes widget w returns nothing
            if w != null then
                call RemoveSavedBoolean(table, this, GetHandleId(w))
            endif
        endmethod

        method hitted takes widget w returns boolean
            return HaveSavedBoolean(table, this, GetHandleId(w))
        endmethod

        /* ------------------------- Destructable hit method ------------------------ */
        private static method onDest takes nothing returns nothing
            local thistype this  = temp
            local destructable d = GetEnumDestructable()
            local real dz
            local real tz

            if not HaveSavedBoolean(table, this, GetHandleId(d)) then
                if collideZ then
                    set dz = GetLocZ(GetWidgetX(d), GetWidgetY(d)) - GetLocZ(x, y)
                    set tz = GetDestructableOccluderHeight(d)
                    if dz + tz >= z - collision and dz <= z + collision then
                        call SaveBoolean(table, this, GetHandleId(d), true)
                        if allocated and .onDestructable(d) then
                            set d = null
                            call terminate()
                            return
                        endif
                    endif
                else
                    call SaveBoolean(table, this, GetHandleId(d), true)
                    if allocated and .onDestructable(d) then
                        set d = null
                        call terminate()
                        return
                    endif
                endif
            endif

            set d = null
        endmethod

        /* -------------------------- Item collision method ------------------------- */
        private static method onItems takes nothing returns nothing
            local thistype this  = temp
            local item i = GetEnumItem()
            local real dz

            if not HaveSavedBoolean(table, this, GetHandleId(i)) then
                if collideZ then
                    set dz = GetLocZ(GetItemX(i), GetItemY(i)) - GetLocZ(x, y)
                    if dz + ITEM_SIZE >= z - collision and dz <= z + collision then
                        call SaveBoolean(table, this, GetHandleId(i), true)
                        if allocated and .onItem(i) then
                            set i = null
                            call terminate()
                            return
                        endif
                    endif
                else
                    call SaveBoolean(table, this, GetHandleId(i), true)
                    if allocated and .onItem(i) then
                        set i = null
                        call terminate()
                        return
                    endif
                endif
            endif

            set i = null
        endmethod

        /* ------------------------------ Reset members ----------------------------- */
        private method reset takes nothing returns nothing
            set launched     = false
            set collideZ     = false
            set source       = null
            set target       = null
            set owner        = null
            set sfx          = null
            set dummy        = null
            set fxpath       = ""
            set open         = 0.
            set height       = 0.
            set veloc        = 0.
            set acceleration = 0.
            set collision    = 0.
            set damage       = 0.
            set travel       = 0.
            set turn         = 0.
            set size         = 0.
            set data         = 0
        endmethod

        /* -------------------------------- Terminate ------------------------------- */
        method terminate takes nothing returns nothing
            if allocated then
                set allocated = false
                // onRemove event
                if .onRemove.exists then
                    call .onRemove()
                endif
                call FlushChildHashtable(table, this)
            endif
        endmethod
        /* -------------------------- Destroys the missile -------------------------- */
        method remove takes integer i returns integer
            call terminate()
            call DestroyEffect(sfx)
            call origin.destroy()
            call impact.destroy()

            static if LIBRARY_DummyRecycler then
                call DummyAddRecycleTimer(dummy, RECYCLE_TIME)
            else
                call Timed.Recycle(dummy, RECYCLE_TIME)
            endif
            call reset()
            set missiles[i] = missiles[didx]
            set didx        = didx - 1

            //Compensation for time dilation
            if didx + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
                set dilation = (didx + 1)/SWEET_SPOT
            else
                set dilation = 1
            endif

            if didx == -1 then
                call PauseTimer(t)
            endif

            call deallocate()

            return i - 1
        endmethod

        /* --------------------------- Launch the Missile --------------------------- */
        method launch takes nothing returns nothing
            if not launched and allocated then
                set launched       = true
                set didx           = didx + 1
                set missiles[didx] = this

                //Compensation for time dilation
                if didx + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
                    set dilation = (didx + 1)/SWEET_SPOT
                else
                    set dilation = 1.
                endif

                if didx == 0 then
                    call TimerStart(t, PERIOD, true, function thistype.move)
                endif
            endif
        endmethod
   
        /* ---------------------------- Missiles movement --------------------------- */
        static method move takes nothing returns nothing
            local integer     j = 0
            local integer     i
            local integer     k
            local unit        u
            local real        a
            local real        d
            local real        s
            local real        h
            local real        c
            local real        dx
            local real        dy
            local real        vel
            local real        locZ
            local real        yaw
            local real        pitch
            local Missiles    missile
            local Coordinates o
            local thistype    this

            // SWEET_SPOOT used to enable or disable
            // relativistic processing
            if SWEET_SPOT > 0 then
                set i = last
            else
                set i = 0
            endif
       
            loop
                exitwhen ((j >= SWEET_SPOT and SWEET_SPOT > 0) or j > didx)
                set this = missiles[i]
                    set temp = this
                    if allocated then
                        set o    = origin
                        set h    = height
                        set c    = open
                        set d    = o.distance
                        set locZ = GetLocZ(x, y)
                       
                        //onPeriod Event
                        if .onPeriod.exists then
                            if allocated and .onPeriod() then
                                call terminate()
                            endif
                        endif

                        // onHit Event
                        if .onHit.exists then
                            if allocated and collision > 0 then
                                call GroupEnumUnitsInRange(hitGroup, x, y, collision + COLLISION_SIZE, null)
                                loop
                                    set u = FirstOfGroup(hitGroup)
                                    exitwhen u == null
                                        if not HaveSavedBoolean(table, this, GetHandleId(u)) then
                                            if IsUnitInRangeXY(u, x, y, collision) then
                                                if collideZ then
                                                    set dx = GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u) - locZ
                                                    set dy = BODY_SIZE
                                                    if dx + dy >= z - collision and dx <= z + collision then
                                                        call SaveBoolean(table, this, GetHandleId(u), true)
                                                        if allocated and .onHit(u) then
                                                            call terminate()
                                                            exitwhen true
                                                        endif
                                                    endif
                                                else
                                                    call SaveBoolean(table, this, GetHandleId(u), true)
                                                    if allocated and .onHit(u) then
                                                        call terminate()
                                                        exitwhen true
                                                    endif
                                                endif
                                            endif
                                        endif
                                    call GroupRemoveUnit(hitGroup, u)
                                endloop
                            endif
                        endif

                        // onMissile Event
                        if .onMissile.exists then
                            if allocated and collision > 0 then
                                set k = 0
                                loop
                                    exitwhen k > didx
                                        set missile = missiles[k]
                                        if missile != this then
                                            if not HaveSavedBoolean(table, this, missile) then
                                                set dx = missile.x - x
                                                set dy = missile.y - y
                                                if SquareRoot(dx*dx + dy*dy) <= collision then
                                                    call SaveBoolean(table, this, missile, true)
                                                    if allocated and .onMissile(missile) then
                                                        call terminate()
                                                        exitwhen true
                                                    endif
                                                endif
                                            endif
                                        endif
                                    set k = k + 1
                                endloop
                            endif
                        endif

                        // onDestructable Event
                        if .onDestructable.exists then
                            if allocated and collision > 0 then
                                set dx = collision
                                call SetRect(RECT, x - dx, y - dx, x + dx, y + dx)
                                call EnumDestructablesInRect(RECT, null, function thistype.onDest)
                            endif
                        endif

                        // onItem Event
                        if .onItem.exists then
                            if allocated and collision > 0 then
                                set dx = collision
                                call SetRect(RECT, x - dx, y - dx, x + dx, y + dx)
                                call EnumItemsInRect(RECT, null, function thistype.onItems)
                            endif
                        endif

                        // Homing or not
                        set u = target
                        if u != null and GetUnitTypeId(u) != 0 then
                            call impact.move(GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u) + toZ)
                            set dx     = impact.x - prevX
                            set dy     = impact.y - prevY
                            set a      = Atan2(dy, dx)
                            set travel = o.distance - SquareRoot(dx*dx + dy*dy)
                        else
                            set a = o.angle
                            set target = null
                        endif
                       
                        // turn rate
                        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 vel    = veloc*dilation
                        set x      = prevX + vel*Cos(cA)
                        set y      = prevY + vel*Sin(cA)
                        set s      = travel + vel
                        set prevX  = x
                        set prevY  = y
                        set prevZ  = z
                        set travel = s
                        set veloc  = veloc + acceleration
                        set pitch  = origin.alpha
                        set yaw    = cA*bj_RADTODEG


                        // arc calculation
                        if h != 0 or o.slope != 0 then
                            set z     = 4*h*s*(d-s)/(d*d) + o.slope*s + o.z
                            set pitch = pitch - Atan(((4*h)*(2*s - d))/(d*d))*bj_RADTODEG
                            call SetUnitFlyHeight(dummy, z - GetLocZ(x, y), 0)
                        endif
                   
                        // curve calculation
                        if c != 0 then
                            set dx  = 4*c*s*(d-s)/(d*d)
                            set a   = cA + bj_PI/2
                            set x   = x + dx*Cos(a)
                            set y   = y + dx*Sin(a)
                            set yaw = (cA + Atan(-((4*c)*(2*s - d))/(d*d)))*bj_RADTODEG
                        endif

                        // onTerrain event
                        if .onTerrain.exists then
                            if GetLocZ(x, y) > z then
                                if allocated and .onTerrain() then
                                    call terminate()
                                endif
                            endif
                        endif

                        if s >= d then
                            // onFinish event
                            if .onFinish.exists then
                                if allocated and .onFinish() then
                                    call terminate()
                                else
                                    // deflected onFinish
                                    if travel > 0 then
                                        call terminate()
                                    endif
                                endif
                            else
                                call terminate()
                            endif
                        endif

                        // missile orientation and positioning
                        call SetUnitAnimationByIndex(dummy, R2I(pitch + 90.5))
                        call SetUnitFacing(dummy, yaw)
                   
                        static if LIBRARY_WorldBounds then
                            if not (x > WorldBounds.maxX or x < WorldBounds.minX or y > WorldBounds.maxY or y < WorldBounds.minY) then
                                call SetUnitX(dummy, x)
                                call SetUnitY(dummy, y)
                            endif
                        else
                            if RectContainsCoords(bj_mapInitialPlayableArea, x, y) then
                                call SetUnitX(dummy, x)
                                call SetUnitY(dummy, y)
                            endif
                        endif
                    else
                        set i = remove(i)
                        set j = j - 1
                    endif
                set i = i + 1
                set j = j + 1

                if i > didx and SWEET_SPOT > 0 then
                    set i = 0
                endif
            endloop
            set last = i

            set u = null
        endmethod

        /* --------------------------- Main Creator method -------------------------- */
        static method create takes real x, real y, real z, real toX, real toY, real toZ returns thistype
            local thistype this = thistype.allocate()
            local real angle    = Atan2(toY - y, toX - x)*bj_RADTODEG

            call .reset()
            set .origin    = Coordinates.create(x, y, z)
            set .impact    = Coordinates.create(toX, toY, toZ)
            set .allocated = true
            set .x         = x
            set .y         = y
            set .z         = z
            set .prevX     = x
            set .prevY     = y
            set .prevZ     = z
            set .toZ       = toZ

            static if LIBRARY_DummyRecycler then
                set .dummy = GetRecycledDummy(x, y, z - GetLocZ(x, y), angle)
            else
                set .dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_RAW_CODE, x, y, angle)
                call SetUnitFlyHeight(.dummy, z - GetLocZ(x, y), 0)
            endif
            call Coordinates.link(origin, impact)
            set .cA = origin.angle

            return this
        endmethod
    endstruct
endlibrary
 

Code (vJASS):

library MissileEffect
/* -------------------- Missile Effect v1.0 by Chopinski -------------------- */
// This is a simple helper library for the Relativistic Missiles system.
// Credits:
//     Sevion for the Alloc module
//         - www.hiveworkshop.com/threads/snippet-alloc.192348/
//     Nestharus for World Bounds Algorithm
/* ----------------------------------- END ---------------------------------- */

    // Alloc ~~ By Sevion ~~ Version 1.09
    module Alloc
        private static integer instanceCount = 0
        private thistype recycle
 
        static method allocate takes nothing returns thistype
            local thistype this
 
            if (thistype(0).recycle == 0) then
                debug if (instanceCount == 8191) then
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to allocate too many instances!")
                    debug return 0
                debug endif
                set instanceCount = instanceCount + 1
                set this = instanceCount
            else
                set this = thistype(0).recycle
                set thistype(0).recycle = thistype(0).recycle.recycle
            endif

            debug set this.recycle = -1
 
            return this
        endmethod
 
        method deallocate takes nothing returns nothing
            debug if (this.recycle != -1) then
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to deallocate an invalid instance at [" + I2S(this) + "]!")
                debug return
            debug endif

            set this.recycle = thistype(0).recycle
            set thistype(0).recycle = this
        endmethod
    endmodule

    // Simple linked list to cycle through
    module LinkedList
        readonly thistype next
        readonly thistype prev

        method init takes nothing returns thistype
            set next = this
            set prev = this

            return this
        endmethod

        method pushBack takes thistype node returns thistype
            set node.prev = prev
            set node.next = this
            set prev.next = node
            set prev = node

            return node
        endmethod

        method pushFront takes thistype node returns thistype
            set node.prev = this
            set node.next = next
            set next.prev = node
            set next = node

            return node
        endmethod

        method pop takes nothing returns nothing
            set prev.next = next
            set next.prev = prev
        endmethod
    endmodule

    // Effect attachments helper
    private struct Effect extends array
        implement LinkedList
        implement Alloc

        real    x
        real    y
        real    z
        real    size
        real    yaw
        real    pitch
        real    roll
        string  path
        effect  effect

        method remove takes nothing returns nothing
            call DestroyEffect(effect)
            call pop()
            call deallocate()
            set effect = null
        endmethod

        method insert takes string fxpath, real x, real y, real z, real scale returns thistype
            local thistype node = pushBack(allocate())

            set node.x      = x
            set node.y      = y
            set node.z      = z
            set node.yaw    = 0.
            set node.pitch  = 0.
            set node.roll   = 0.
            set node.path   = fxpath
            set node.size   = scale
            set node.effect = AddSpecialEffect(fxpath, x, y)
            call BlzSetSpecialEffectZ(node.effect, z)
            call BlzSetSpecialEffectScale(node.effect, scale)

            return node
        endmethod

        static method create takes nothing returns thistype
            return thistype(allocate()).init()
        endmethod
    endstruct

    // The Main Missile effect
    struct MissileEffect
        // world bounds
        readonly static integer maxX
        readonly static integer maxY
        readonly static integer minX
        readonly static integer minY
        // attachment members
        real    size
        real    yaw
        real    pitch
        real    roll
        string  path
        effect  effect
        Effect  attachments

        method scale takes effect sfx, real scale returns nothing
            set size = scale
            call BlzSetSpecialEffectScale(sfx, scale)
        endmethod

        // Must be set in this order
        method orient takes real yaw, real pitch, real roll returns nothing
            local Effect node = attachments.next

            // Main missile orientation
            set .yaw   = yaw
            set .pitch = pitch
            set .roll  = roll
            call BlzSetSpecialEffectYaw(effect, yaw)
            call BlzSetSpecialEffectPitch(effect, pitch)
            call BlzSetSpecialEffectRoll(effect, roll)

            // Attachments orientation
            loop
                exitwhen node == attachments
                    set node.yaw   = yaw
                    set node.pitch = pitch
                    set node.roll  = roll
                    call BlzSetSpecialEffectYaw(node.effect, yaw)
                    call BlzSetSpecialEffectPitch(node.effect, pitch)
                    call BlzSetSpecialEffectRoll(node.effect, roll)
                set node = node.next
            endloop
        endmethod

        method move takes real x, real y, real z returns nothing
            local Effect node = attachments.next

            if not (x > maxX or x < minX or y > maxY or y < minY) then
                call BlzSetSpecialEffectPosition(effect, x, y, z)
                loop
                    exitwhen node == attachments
                        call BlzSetSpecialEffectPosition(node.effect, x - node.x, y - node.y, z - node.z)
                    set node = node.next
                endloop
            endif
        endmethod

        method attach takes string fxpath, real dx, real dy, real dz, real scale returns effect
            local Effect node = attachments.insert(fxpath, dx, dy, dz, scale)

            call BlzSetSpecialEffectPosition(node.effect, BlzGetLocalSpecialEffectX(effect) - dx, BlzGetLocalSpecialEffectY(effect) - dy, BlzGetLocalSpecialEffectZ(effect) - dz)

            return node.effect
        endmethod

        method dettach takes effect sfx returns nothing
            local Effect node = attachments.next

            loop
                exitwhen node == attachments
                    if GetHandleId(node.effect) == GetHandleId(sfx) then
                        call node.remove()
                        exitwhen true
                    endif
                set node = node.next
            endloop
        endmethod

        /* -------------------------- Contructor/Destructor ------------------------- */
        method destroy takes nothing returns nothing
            local Effect node = attachments.next

            loop
                exitwhen node == attachments
                    call DestroyEffect(node.effect)
                    set node.effect = null
                    call node.deallocate()
                set node = node.next
            endloop
            call DestroyEffect(effect)
            set effect = null
            set path   = null
            set size   = 1.
            call deallocate()
        endmethod

        static method create takes real x, real y, real z returns thistype
            local thistype this = thistype.allocate()

            set effect = AddSpecialEffect("", x, y)
            set path   = ""
            set size   = 1
            set attachments = Effect.create()
            call BlzSetSpecialEffectZ(effect, z)

            return this
        endmethod

        //Credits to Nestharus for WorldBounds idea.
        static method onInit takes nothing returns nothing
            local rect world = GetWorldBounds()

            set maxX = R2I(GetRectMaxX(world))
            set maxY = R2I(GetRectMaxY(world))
            set minX = R2I(GetRectMinX(world))
            set minY = R2I(GetRectMinY(world))

            call RemoveRect(world)
            set world = null
        endmethod
    endstruct
endlibrary
 

Code (vJASS):

library Missiles requires MissileEffect
    /* ----------------------- Missiles v1.3 by Chopinski ----------------------- */
    // Thanks and Full Credits to BPower, Dirac and Vexorian for the Missile Library's at which i based
    // this Missiles library. Credits and thanks to AGD and for the effect orientation ideas.
    // This version of Missiles requires patch 1.31+
   
    // How to Import:
    //     1 - Copy this and MissileEffect librarys into your map
   
    // How to Use:
    //         This system works almost identicaly to the Missile library by BPower but
    //         with a more user friendly interface in my opinion. Also this system
    //         allows you to create Arc/Curved Homing missiles like Dirac's system.
   
    //         Differently than both BPower and Dirac systems, you are not required
    //         to use the ImplementStruct at the end of your struct. To make your struct
    //         behave like a Missile simply make it extends Missiles and you are done.
   
    //         After that you will have access to the events in the MissileEvents Interface.
    //         Simply declare the event you want for your strcut to have and the system
    //         takes care of the rest. You can access the members and functions using
    //         the "this" or "." syntax which is a plus. to terminate a missile, simply
    //         return true or call terminate() within the method. Example:
   
    //         struct MySpell extends Missiles
    //             method onPeriod takes nothing returns boolean
    //                 // will display the missile current position
    //                 call ClearTextMessages()
    //                 call BJDebugMsg(I2S(.x))
    //                 call BJDebugMsg(I2S(.y))
    //                 call BJDebugMsg(I2S(.z))
   
    //                 return false
    //             endmethod
    //         endstruct
               
    //         function Spell takes nothing returns nothing
    //             // the create method takes the initial and the final coordinates
    //             // if the target member is set, the missile home
    //             local MySpell spell = MySpell.create(fromX, fromY, fromZ, toX, toY, toZ)
   
    //             set spell.source   = GetTriggerUnit()
    //             set spell.target   = GetSpellTargetUnit() -> when a target is specified the missile will be homing
    //             set spell.speed    = 1000 -> warcraft 3 speed unit
    //             set spell.duration = 1.5 -> will set the speed to match the time passed
    //             set spell.model    = "Some Model.dmx"
    //             set spell.scale    = 1.3
    //             set spell.arc      = 30 (degrees converted to radians internally)
    //             set spell.curve    = GetRandomReal(-40, 40) (degrees converted to radians internally
   
    //             call spell.launch()
    //         endfunction
   
    //         Available members and methods:
    //             Coordinates impact               -> the impact coordiantes (x, y, z, anngle, slope, alpha, distance)
    //             Coordinates origin               -> same, but for origin
    //             //-------------------------------------------------------
    //             readonly real    x               -> current x position of the missile
    //             readonly real    y               -> current y position of the missile
    //             readonly real    z               -> current z position of the missile  
    //             readonly real    prevX           -> last x position of the missile
    //             readonly real    prevY           -> last y position of the missile
    //             readonly real    prevZ           -> last z position of the missile
    //             readonly real    height          -> the arc of the missile (change it using the .arc operator)
    //             readonly real    turn            -> the turn rate of the missile
    //             readonly real    open            -> the curvature of the missile (change it using the .curve operator)
    //             readonly real    veloc           -> the missile speed (change it using the .speed or .duration operator)
    //             readonly real    travel          -> distance travelled
    //             readonly boolean launched        -> true if the missile was already launched
    //             readonly boolean allocated       -> true if the missile can still perform its movement operations
    //             //-------------------------------------------------------
    //             unit    source                   -> the source unit (optional)
    //             unit    target                   -> the target unit (optional and if set to a valid unit the missile will be homing)
    //             player  owner                    -> the owner of the missile (optional)
    //             boolean collideZ                 -> set to true if you want the missile to consider z in collisions
    //             real    collision                -> the collision size of the missile (optional. The onHit and onDestructable events only works if this is greater than 0)
    //             real    damage                   -> stores the damage you want the missile to deal (optional)
    //             real    acceleration             -> if different than 0 then the missile will change its speed with time (optional)
    //             integer data                     -> just in case you want to pass some information to the missile to retrieve it later (optional)
   
    //             (call)
    //             method deflect takes real x, real y returns nothing
    //                 -   Deflects the missile from the target point, changing
    //                 -   it's course to the new x and y.
    //                 -   If target is a valid unit, deflect will null the target
    //                 -   and then deflects the missile
   
    //             (call)
    //             method deflectZ takes real x, real y, real z returns nothing
    //                 -   Deflects the missile from the target point, changing
    //                 -   it's course to the new x, y and z.
    //                 -   If target is a valid unit, deflect will null the target
    //                 -   and then deflects the missile
                 
    //             (call)
    //             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.
   
    //             (call)
    //             method flush takes widget w returns nothing
    //                 -   call this method to allow the missile to be able to hit
    //                 -   a unit or destructable again
   
    //             (call)
    //             method flushAll nothing returns nothing
    //                 -   flushes the hit table for the missile
                 
    //             (call)
    //             method hitted takes widget w returns boolean
    //                 -   returns true if the missile has hitted the widget

    //             (call)
    //             method attach takes string model, real dx, real dy, real dz, real scale returns effect
    //                 -   attach another effect to the main missile.
    //                 -   dx, dy and dz are offsets from the main misisle position

    //             (call)
    //             method dettach takes effect attachment returns nothing
    //                 -   dettahes the effect from the missile, destrying it
   
    //             (call)
    //             method hitted takes widget w returns boolean
    //                 -   returns true if the missile has hitted the widget
   
                   
    //             (optional)
    //             method onHit takes unit hit returns boolean
    //                 -   Runs every time the missile collides with a unit.
    //                 -   If returns true the missile is destroyed.
   
    //             (optional)
    //             method onDestructable takes destructable dest returns boolean
    //                 -   Runs every time the missile collides with a destructable.
    //                 -   If returns true the missile is destroyed.

    //             (optional)
    //             method onItem takes item i returns boolean
    //                 -   Runs every time the missile collides with an item.
    //                 -   If returns true the missile is destroyed.

    //             (optional)
    //             method onMissile takes Missiles missile returns boolean
    //                 -   Runs every time the missile collides with another missile.
    //                 -   By default, missiles collide only once
    //                 -   If returns true the missile is destroyed.
    //                 -   Please, be aware that this method can be very performance
    //                 -   intensive, so careful!
                     
    //             (optional)
    //             method onPeriod takes nothing returns boolean
    //                 -   Runs every period.
    //                 -   If returns true the missile is destroyed.
           
    //             (optional)
    //             method onFinish takes nothing returns boolean
    //                 -   Runs whenever the missile finishes it's course.
    //                 -   If returns true the missile is destroyed.
           
    //             (optional)
    //             method onTerrain takes nothing returns boolean
    //                 -   Runs whenever the missile collides with terrain height
    //                 -   greater then the missile current z
    //                 -   If returns true the missile is destroyed.
           
    //             (optional)
    //             method onRemove takes Missile this returns nothing
    //                 -   Runs whenever the missile is deallocated.
    // /* ----------------------------------- END ---------------------------------- */
   
    /* --------------------------------- System --------------------------------- */
    globals
        // The update period of the system
        public  constant real    PERIOD             = 1./40.
        // The max amount of Missiles processed in a PERIOD
        // You can play around with both these values to find
        // your sweet spot. If equal to 0, the system will
        // process all missiles at once every period.
        public  constant real    SWEET_SPOT         = 150
        // the avarage collision size compensation when detecting
        // collisions
        private constant real    COLLISION_SIZE     = 128.
        // item size used in z collision
        private constant real    ITEM_SIZE          = 16.
        // set this to true if you want the system to orient
        // the missile roll. Roll can look really fishy for
        // some users and it was never possible to be set
        // until recent patches, so use it if you want.
        private constant boolean ROLL               = false
        // Needed, dont touch. Seriously, dont touch!
        private location         LOC                = Location(0., 0.)
        private rect             RECT               = Rect(0., 0., 0., 0.)
        private hashtable        table              = InitHashtable()
    endglobals

    // The available Events the user can implement in their structs
    private interface MissileEvents
        method onHit takes unit hit returns boolean defaults false
        method onItem takes item i returns boolean defaults false
        method onMissile takes Missiles missile returns boolean defaults false
        method onTerrain takes nothing returns boolean defaults false
        method onPeriod takes nothing returns boolean defaults false
        method onFinish takes nothing returns boolean defaults false
        method onDestructable takes destructable dest returns boolean defaults false
        method onRemove takes nothing returns nothing defaults nothing
    endinterface

    function GetLocZ takes real x, real y returns real
        call MoveLocation(LOC, x, y)
        return GetLocationZ(LOC)
    endfunction

    //Credits to Dirac for AdvLoc and BPower for fixing an error in it
    private struct Coordinates
        readonly real x
        readonly real y
        readonly real z
        readonly real angle
        readonly real distance
        readonly real square
        readonly real slope
        readonly real alpha

        // Creates an origin - impact link.
        private thistype ref

        private static method math takes thistype a, thistype b returns nothing
            local real dx
            local real dy
            loop
                set dx = b.x - a.x
                set dy = b.y - a.y
                set dx = dx*dx + dy*dy
                set dy = SquareRoot(dx)
                exitwhen dx != 0. and dy != 0.
                set b.x = b.x + .01
                set b.z = b.z - GetLocZ(b.x -.01, b.y) + GetLocZ(b.x, b.y)
            endloop

            set a.square   = dx
            set a.distance = dy
            set a.angle    = Atan2(b.y - a.y, b.x - a.x)
            set a.slope    = (b.z - a.z)/dy
            set a.alpha    = Atan(a.slope)
            // Set b.
            if b.ref == a then
                set b.angle     = a.angle + bj_PI
                set b.distance  = dy
                set b.slope     = -a.slope
                set b.alpha     = -a.alpha
                set b.square    = dx
            endif
        endmethod

        static method link takes thistype a, thistype b returns nothing
            set a.ref = b
            set b.ref = a
            call math(a, b)
        endmethod

        method move takes real toX, real toY, real toZ returns nothing
            set x = toX
            set y = toY
            set z = toZ + GetLocZ(toX, toY)
            if ref != this then
                call math(this, ref)
            endif
        endmethod

        method destroy takes nothing returns nothing
            call .deallocate()
        endmethod

        static method create takes real x, real y, real z returns Coordinates
            local thistype this = thistype.allocate()
            set ref = this
            call move(x, y, z)
            return this
        endmethod
    endstruct
       
    /* ------------------------------ Missile Sytem ----------------------------- */
    struct Missiles extends MissileEvents
        private static timer          t         = CreateTimer()
        private static group          hitGroup  = CreateGroup()
        private static integer        didx      = -1
        private static integer        last      = 0
        private static real           dilation
        private static thistype       temp      = 0
        private static thistype array missiles
        //-------------------------------------------------------
        private real     cA     // Current Angle
        private real     height // Arcs
        private real     open   // Curves
        private real     toZ    // Necessary to fix a bug for homming missiles
        //-------------------------------------------------------
        Coordinates      impact
        Coordinates      origin
        MissileEffect    effect
        //-------------------------------------------------------
        readonly real    x
        readonly real    y
        readonly real    z
        readonly real    prevX
        readonly real    prevY
        readonly real    prevZ
        readonly real    turn
        readonly real    veloc
        readonly real    travel
        readonly boolean launched
        readonly boolean allocated
        //-------------------------------------------------------
        unit             source
        unit             target
        player           owner
        boolean          collideZ
        real             collision
        real             damage
        real             acceleration
        integer          data
        //-------------------------------------------------------
       
        /* -------------------------- Model of the missile -------------------------- */
        method operator model= takes string fx returns nothing
            call DestroyEffect(effect.effect)
            set effect.path = fx
            set effect.effect = AddSpecialEffect(fx, origin.x, origin.y)
            call BlzSetSpecialEffectZ(effect.effect, origin.z)
        endmethod

        method operator model takes nothing returns string
            return effect.path
        endmethod
       
        /* ----------------------------- Curved movement ---------------------------- */
        method operator curve= takes real value returns nothing
            set open = Tan(value*bj_DEGTORAD)*origin.distance
        endmethod
       
        method operator curve takes nothing returns real
            return Atan(open/origin.distance)
        endmethod
       
        /* ----------------------------- Arced Movement ----------------------------- */
        method operator arc= takes real value returns nothing
            set height = Tan(value*bj_DEGTORAD)*origin.distance/4
        endmethod
       
        method operator arc takes nothing returns real
            return Atan(4*height/origin.distance)
        endmethod
       
        /* ------------------------------ Effect scale ------------------------------ */
        method operator scale= takes real value returns nothing
            set effect.size = value
            call effect.scale(effect.effect, value)
        endmethod

        method operator scale takes nothing returns real
            return effect.size
        endmethod

        /* ------------------------------ Missile Speed ----------------------------- */
        method operator speed= takes real newspeed returns nothing
            set veloc = newspeed*PERIOD
        endmethod

        method operator speed takes nothing returns real
            return veloc
        endmethod

        /* ------------------------------- Flight Time ------------------------------ */
        method operator duration= takes real flightTime returns nothing
            set veloc = RMaxBJ(0.00000001, (origin.distance - travel)*PERIOD/RMaxBJ(0.00000001, flightTime))
        endmethod

        /* ---------------------------- Bound and Deflect --------------------------- */
        method bounce takes nothing returns nothing
            local real locZ = GetLocZ(x, y)

            // This is here just to avoid an infinite loop
            // with a deflect being called within an onTerrain
            // event
            if z < locZ then
                set z = locZ
                call impact.move(impact.x, impact.y, origin.z - GetLocZ(impact.x, impact.y))
            endif
            call origin.move(x, y, origin.z - GetLocZ(origin.x, origin.y))
            set travel = 0
        endmethod
       
        method deflect takes real tx, real ty returns nothing
            if target != null then
                set target = null
            endif

            call impact.move(tx, ty, impact.z - GetLocZ(impact.x, impact.y))
            call bounce()
        endmethod

        method deflectZ takes real tx, real ty, real tz returns nothing
            call impact.move(impact.x, impact.y, tz)
            call deflect(tx, ty)
        endmethod

        /* ---------------------------- Flush hit targets --------------------------- */
        method flushAll takes nothing returns nothing
            call FlushChildHashtable(table, this)
        endmethod

        method flush takes widget w returns nothing
            if w != null then
                call RemoveSavedBoolean(table, this, GetHandleId(w))
            endif
        endmethod

        method hitted takes widget w returns boolean
            return HaveSavedBoolean(table, this, GetHandleId(w))
        endmethod

        /* ----------------------- Missile attachment methods ----------------------- */
        // dx, dy, and dz are offsets from the main missile coordinates
        method attach takes string model, real dx, real dy, real dz, real scale returns effect
            return effect.attach(model, dx, dy, dz, scale)
        endmethod

        method dettach takes effect attachment returns nothing
            if attachment != null then
                call effect.dettach(attachment)
            endif
        endmethod

        /* ---------------------- Destructable collision method --------------------- */
        private static method onDest takes nothing returns nothing
            local thistype this  = temp
            local destructable d = GetEnumDestructable()
            local real dz
            local real tz

            if not HaveSavedBoolean(table, this, GetHandleId(d)) then
                if collideZ then
                    set dz = GetLocZ(GetWidgetX(d), GetWidgetY(d)) - GetLocZ(x, y)
                    set tz = GetDestructableOccluderHeight(d)
                    if dz + tz >= z - collision and dz <= z + collision then
                        call SaveBoolean(table, this, GetHandleId(d), true)
                        if allocated and .onDestructable(d) then
                            set d = null
                            call terminate()
                            return
                        endif
                    endif
                else
                    call SaveBoolean(table, this, GetHandleId(d), true)
                    if allocated and .onDestructable(d) then
                        set d = null
                        call terminate()
                        return
                    endif
                endif
            endif

            set d = null
        endmethod

        /* -------------------------- Item collision method ------------------------- */
        private static method onItems takes nothing returns nothing
            local thistype this  = temp
            local item i = GetEnumItem()
            local real dz

            if not HaveSavedBoolean(table, this, GetHandleId(i)) then
                if collideZ then
                    set dz = GetLocZ(GetItemX(i), GetItemY(i)) - GetLocZ(x, y)
                    if dz + ITEM_SIZE >= z - collision and dz <= z + collision then
                        call SaveBoolean(table, this, GetHandleId(i), true)
                        if allocated and .onItem(i) then
                            set i = null
                            call terminate()
                            return
                        endif
                    endif
                else
                    call SaveBoolean(table, this, GetHandleId(i), true)
                    if allocated and .onItem(i) then
                        set i = null
                        call terminate()
                        return
                    endif
                endif
            endif

            set i = null
        endmethod

        /* ------------------------------ Reset members ----------------------------- */
        private method reset takes nothing returns nothing
            set launched     = false
            set collideZ     = false
            set source       = null
            set target       = null
            set owner        = null
            set open         = 0.
            set height       = 0.
            set veloc        = 0.
            set acceleration = 0.
            set collision    = 0.
            set damage       = 0.
            set travel       = 0.
            set turn         = 0.
            set data         = 0
        endmethod

        /* -------------------------------- Terminate ------------------------------- */
        method terminate takes nothing returns nothing
            if allocated then
                set allocated = false
                // onRemove event
                if .onRemove.exists then
                    call .onRemove()
                endif
                call FlushChildHashtable(table, this)
            endif
        endmethod
        /* -------------------------- Destroys the missile -------------------------- */
        method remove takes integer i returns integer
            call terminate()
            call origin.destroy()
            call impact.destroy()
            call effect.destroy()
            call reset()
            set missiles[i] = missiles[didx]
            set didx        = didx - 1

            //Compensation for time dilation
            if didx + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
                set dilation = (didx + 1)/SWEET_SPOT
            else
                set dilation = 1
            endif

            if didx == -1 then
                call PauseTimer(t)
            endif

            call deallocate()

            return i - 1
        endmethod

        /* --------------------------- Launch the Missile --------------------------- */
        method launch takes nothing returns nothing
            if not launched and allocated then
                set launched       = true
                set didx           = didx + 1
                set missiles[didx] = this

                //Compensation for time dilation
                if didx + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
                    set dilation = (didx + 1)/SWEET_SPOT
                else
                    set dilation = 1.
                endif

                if didx == 0 then
                    call TimerStart(t, PERIOD, true, function thistype.move)
                endif
            endif
        endmethod
       
        /* ---------------------------- Missiles movement --------------------------- */
        static method move takes nothing returns nothing
            local integer     j = 0
            local integer     i
            local integer     k
            local unit        u
            local real        a
            local real        d
            local real        s
            local real        h
            local real        c
            local real        dx
            local real        dy
            local real        vel
            local real        yaw
            local real        pitch
            local real        locZ
            local Missiles    missile
            local Coordinates o
            local thistype    this

            // SWEET_SPOOT used to enable or disable
            // relativistic processing
            if SWEET_SPOT > 0 then
                set i = last
            else
                set i = 0
            endif
           
            loop
                exitwhen ((j >= SWEET_SPOT and SWEET_SPOT > 0) or j > didx)
                    set this = missiles[i]
                    set temp = this
                    if allocated then
                        set o    = origin
                        set h    = height
                        set c    = open
                        set d    = o.distance
                        set locZ = GetLocZ(x, y)
                       
                        //onPeriod Event
                        if .onPeriod.exists then
                            if allocated and .onPeriod() then
                                call terminate()
                            endif
                        endif

                        // onHit Event
                        if .onHit.exists then
                            if allocated and collision > 0 then
                                call GroupEnumUnitsInRange(hitGroup, x, y, collision + COLLISION_SIZE, null)
                                loop
                                    set u = FirstOfGroup(hitGroup)
                                    exitwhen u == null
                                        if not HaveSavedBoolean(table, this, GetHandleId(u)) then
                                            if IsUnitInRangeXY(u, x, y, collision) then
                                                if collideZ then
                                                    set dx = GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u) - locZ
                                                    set dy = BlzGetUnitCollisionSize(u)
                                                    if dx + dy >= z - collision and dx <= z + collision then
                                                        call SaveBoolean(table, this, GetHandleId(u), true)
                                                        if allocated and .onHit(u) then
                                                            call terminate()
                                                            exitwhen true
                                                        endif
                                                    endif
                                                else
                                                    call SaveBoolean(table, this, GetHandleId(u), true)
                                                    if allocated and .onHit(u) then
                                                        call terminate()
                                                        exitwhen true
                                                    endif
                                                endif
                                            endif
                                        endif
                                    call GroupRemoveUnit(hitGroup, u)
                                endloop
                            endif
                        endif

                        // onMissile Event
                        if .onMissile.exists then
                            if allocated and collision > 0 then
                                set k = 0
                                loop
                                    exitwhen k > didx
                                        set missile = missiles[k]
                                        if missile != this then
                                            if not HaveSavedBoolean(table, this, missile) then
                                                set dx = missile.x - x
                                                set dy = missile.y - y
                                                if SquareRoot(dx*dx + dy*dy) <= collision then
                                                    call SaveBoolean(table, this, missile, true)
                                                    if allocated and .onMissile(missile) then
                                                        call terminate()
                                                        exitwhen true
                                                    endif
                                                endif
                                            endif
                                        endif
                                    set k = k + 1
                                endloop
                            endif
                        endif

                        // onDestructable Event
                        if .onDestructable.exists then
                            if allocated and collision > 0 then
                                set dx = collision
                                call SetRect(RECT, x - dx, y - dx, x + dx, y + dx)
                                call EnumDestructablesInRect(RECT, null, function thistype.onDest)
                            endif
                        endif

                        // onItem Event
                        if .onItem.exists then
                            if allocated and collision > 0 then
                                set dx = collision
                                call SetRect(RECT, x - dx, y - dx, x + dx, y + dx)
                                call EnumItemsInRect(RECT, null, function thistype.onItems)
                            endif
                        endif

                        // Homing or not
                        set u = target
                        if u != null and GetUnitTypeId(u) != 0 then
                            call impact.move(GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u) + toZ)
                            set dx     = impact.x - prevX
                            set dy     = impact.y - prevY
                            set a      = Atan2(dy, dx)
                            set travel = o.distance - SquareRoot(dx*dx + dy*dy)
                        else
                            set a = o.angle
                            set target = null
                        endif
                       
                        // turn rate
                        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 vel    = veloc*dilation
                        set yaw    = cA
                        set pitch  = o.alpha
                        set x      = prevX + vel*Cos(yaw)
                        set y      = prevY + vel*Sin(yaw)
                        set s      = travel + vel
                        set prevX  = x
                        set prevY  = y
                        set prevZ  = z
                        set veloc  = veloc + acceleration
                        set travel = s

                        // arc calculation
                        if h != 0 or o.slope != 0 then
                            set z = 4*h*s*(d-s)/(d*d) + o.slope*s + o.z
                            set pitch = pitch - Atan(((4*h)*(2*s - d))/(d*d))
                        endif
                       
                        // curve calculation
                        if c != 0 then
                            set dx  = 4*c*s*(d-s)/(d*d)
                            set a   = yaw + bj_PI/2
                            set x   = x + dx*Cos(a)
                            set y   = y + dx*Sin(a)
                            set yaw = yaw + Atan(-((4*c)*(2*s - d))/(d*d))
                        endif

                        // onTerrain event
                        if .onTerrain.exists then
                            if GetLocZ(x, y) > z then
                                if allocated and .onTerrain() then
                                    call terminate()
                                endif
                            endif
                        endif

                        if s >= d - 0.0001 then
                            // onFinish event
                            if .onFinish.exists then
                                if allocated and .onFinish() then
                                    call terminate()
                                else
                                    // deflected onFinish
                                    if travel > 0 then
                                        call terminate()
                                    endif
                                endif
                            else
                                call terminate()
                            endif
                        else
                            static if ROLL then
                                call effect.orient(yaw, -pitch, Atan2(c, h))
                            else
                                call effect.orient(yaw, -pitch, 0)
                            endif
                        endif
                        call effect.move(x, y, z)
                    else
                        set i = remove(i)
                        set j = j - 1
                    endif
                set i = i + 1
                set j = j + 1

                if i > didx and SWEET_SPOT > 0 then
                    set i = 0
                endif
            endloop
            set last = i

            set u = null
        endmethod

        /* --------------------------- Main Creator method -------------------------- */
        static method create takes real x, real y, real z, real toX, real toY, real toZ returns thistype
            local thistype this = thistype.allocate()

            call .reset()
            set .origin    = Coordinates.create(x, y, z)
            set .impact    = Coordinates.create(toX, toY, toZ)
            set .effect    = MissileEffect.create(x, y, z)
            set .allocated = true
            set .x         = x
            set .y         = y
            set .z         = z
            set .prevX     = x
            set .prevY     = y
            set .prevZ     = z
            set .toZ       = toZ
            call Coordinates.link(origin, impact)
            set .cA = origin.angle

            return this
        endmethod
    endstruct
endlibrary
 

Code (vJASS):

scope MissilesQ
    private struct Misisle extends Missiles
        unit caster
        // integer i = 0
        //effect attachment

        // method onPeriod takes nothing returns boolean
        //     set i = i + 1

        //     if i == 100 then
        //         call dettach(attachment)
        //     endif

        //     return false
        // endmethod

        method onHit takes unit hit returns boolean
            if hit != .source and UnitAlive(hit) then
                call BJDebugMsg("onHit(" + (I2S(this)) + "): " + GetUnitName(hit))
                call KillUnit(hit)
            endif

            return false
        endmethod

        method onMissile takes Missiles missile returns boolean
            call BJDebugMsg("onMissile(" + (I2S(this)) + "): Collided with Missile(" + (I2S(missile)) + ")")

            return false
        endmethod

        method onItem takes item i returns boolean
            call BJDebugMsg("onItem(" + (I2S(this)) + "): Removing")
            call RemoveItem(i)

            return false
        endmethod

        method onDestructable takes destructable dest returns boolean
            call BJDebugMsg("onDestructable(" + (I2S(this)) + "): Killing")
            call KillDestructable(dest)

            return false
        endmethod

        method onTerrain takes nothing returns boolean
            call BJDebugMsg("onTerrain(" + (I2S(this)) + "): Deflecting")
            call deflect(GetUnitX(.caster), GetUnitY(.caster))

            return false
        endmethod

        method onFinish takes nothing returns boolean
            call BJDebugMsg("onFinish(" + (I2S(this)) + "): Exploding")
            call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl", .x, .y))

            return false
        endmethod

        method onRemove takes nothing returns nothing
            call BJDebugMsg("onRemove(" + (I2S(this)) + "): Cleaning")
 
            set .caster = null
        endmethod
    endstruct

    private struct Test
        static method onCast takes nothing returns nothing
            local unit c     = GetTriggerUnit()
            local unit t     = GetSpellTargetUnit()
            local real fromX = GetUnitX(c)
            local real fromY = GetUnitY(c)
            local real fromZ = GetUnitFlyHeight(c) + 50
            local real toX   = GetSpellTargetX()
            local real toY   = GetSpellTargetY()
            local real toZ   = fromZ
            local Misisle missile = Misisle.create(fromX, fromY, fromZ, toX, toY, 50)

            set missile.model      = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
            set missile.speed      = 500
            set missile.target     = t
            set missile.source     = c
            set missile.collision  = 75
            set missile.caster     = c

            //set missile.arc        = GetRandomReal(10, 40)
            //set missile.curve      = GetRandomReal(-25, 25)
            //set missile.attachment = missile.attach("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl", 100, 100, 0, 1)

            call missile.launch()
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A000', function thistype.onCast)
            call Cheat("iseedeadpeople")
        endmethod
    endstruct
endscope

 

Code (vJASS):

scope MissilesW
    private struct Missile extends Missiles
        method onHit takes unit hit returns boolean
            if hit != .source and UnitAlive(hit) then
                call KillUnit(hit)
            endif

            return false
        endmethod

        method onDestructable takes destructable dest returns boolean
            call KillDestructable(dest)

            return false
        endmethod
    endstruct

    private struct Test
        timer   t
        unit    c
        real    fx
        real    fy
        real    fz
        real    tx
        real    ty
        integer count
        integer i = 1

        private static method GetRandomRange takes real radius returns real
            local real r = GetRandomReal(0, 1) + GetRandomReal(0, 1)
 
            if r > 1 then
                return (2 - r)*radius
            endif
 
            return r*radius
        endmethod

        static method onLoop takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local real     maxRange
            local real     theta
            local real     radius
            local real     toX
            local real     toY
            local Missile  missile

            if count > 0 then
                set i      = -i
                set count  = count - 1
                set theta  = 2*bj_PI*GetRandomReal(0, 1)
                set radius = GetRandomRange(350)
                set toX    = tx + radius*Cos(theta)
                set toY    = ty + radius*Sin(theta)
                set missile = Missile.create(fx, fy, fz, toX, toY, 0)
 
                set missile.source    = c
                set missile.model     = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
                set missile.speed     = 800
                set missile.collision = 75
                set missile.arc       = GetRandomReal(10, 45)
                set missile.curve     = GetRandomReal(5, 20)*i

                call missile.launch()
            else
                call ReleaseTimer(t)
                set t = null
                set c = null
                call deallocate()
            endif
        endmethod

        static method onCast takes nothing returns nothing
            local thistype this = thistype.allocate()

            set t     = NewTimerEx(this)
            set c     = GetTriggerUnit()
            set fx    = GetUnitX(c)
            set fy    = GetUnitY(c)
            set fz    = GetUnitFlyHeight(c) + 50
            set tx    = GetSpellTargetX()
            set ty    = GetSpellTargetY()
            set count = 50

            call TimerStart(.t, 0.1, true, function thistype.onLoop)
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A001', function thistype.onCast)
        endmethod
    endstruct
endscope
 

Code (vJASS):

scope MissilesE
    private struct Missile extends Missiles
        method onHit takes unit hit returns boolean
            local real dz = GetLocZ(GetUnitX(hit), GetUnitY(hit)) - GetLocZ(x, y)
            local real uz = BlzGetUnitCollisionSize(hit)

            if hit != .source and UnitAlive(hit) and (dz + uz >= z - collision and dz <= z + collision) then
                call KillUnit(hit)
            endif

            return false
        endmethod

        method onDestructable takes destructable dest returns boolean
            local real dz = GetLocZ(GetWidgetX(dest), GetWidgetY(dest)) - GetLocZ(x, y)
            local real tz = GetDestructableOccluderHeight(dest)
 
            if dz + tz >= z - collision and dz <= z + collision then
                call KillDestructable(dest)
            endif

            return false
        endmethod

        method onRemove takes nothing returns nothing
            call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl", x, y))
        endmethod
    endstruct

    private struct Test
        timer   t
        unit    c
        real    fx
        real    fy
        real    tx
        real    ty
        integer count

        private static method AngleBetweenCoordinates takes real x, real y, real x2, real y2 returns real
            return Atan2(y2 - y, x2 - x)
        endmethod

        private static method GetRandomRange takes real radius returns real
            local real r = GetRandomReal(0, 1) + GetRandomReal(0, 1)
 
            if r > 1 then
                return (2 - r)*radius
            endif
 
            return r*radius
        endmethod

        static method onLoop takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local real     maxRange
            local real     theta
            local real     radius
            local real     toX
            local real     toY
            local real     fromX
            local real     fromY
            local Missile  missile

            if count > 0 then
                set count   = count - 1
                set theta   = 2*bj_PI*GetRandomReal(0, 1)
                set radius  = GetRandomRange(600)
                set toX     = tx + radius*Cos(theta)
                set toY     = ty + radius*Sin(theta)
                set theta   = AngleBetweenCoordinates(tx, ty, fx, fy)
                set fromX   = toX + 3000*Cos(theta)
                set fromY   = toY + 3000*Sin(theta)
                set missile = Missile.create(fromX, fromY, 1500, toX, toY, 0)
 
                set missile.source    = c
                set missile.model     = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
                set missile.scale     = 1.5
                set missile.duration  = 2.
                set missile.collision = 100

                call missile.launch()
            else
                call ReleaseTimer(t)
                set t = null
                set c = null
                call deallocate()
            endif
        endmethod

        static method onCast takes nothing returns nothing
            local thistype this = thistype.allocate()

            set t     = NewTimerEx(this)
            set c     = GetTriggerUnit()
            set fx    = GetUnitX(c)
            set fy    = GetUnitY(c)
            set tx    = GetSpellTargetX()
            set ty    = GetSpellTargetY()
            set count = 50

            call TimerStart(.t, 0.2, true, function thistype.onLoop)
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A002', function thistype.onCast)
        endmethod
    endstruct
endscope
 

Code (vJASS):

scope MissilesR
    private struct Missile extends Missiles
        method onFinish takes nothing returns boolean
            if UnitAlive(target) then
                call KillUnit(target)
            endif

            return false
        endmethod
    endstruct

    private struct Test
        static method onCast takes nothing returns nothing
            local unit    c = GetTriggerUnit()
            local real    x = GetUnitX(c)
            local real    y = GetUnitY(c)
            local real    z = GetUnitFlyHeight(c) + 50
            local group   g = CreateGroup()
            local unit    u
            local Missile hammer

            call GroupEnumUnitsInRange(g, GetSpellTargetX(), GetSpellTargetY(), 500, null)
            loop
                set u = FirstOfGroup(g)
                exitwhen u == null
                    if UnitAlive(u) and u != c then
                        set hammer = Missile.create(x, y, z, GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u) + 50)
                        set hammer.model  = "Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl"
                        set hammer.speed  = 500
                        set hammer.arc    = GetRandomReal(0, 35)
                        set hammer.curve  = GetRandomReal(-25, 25)
                        set hammer.target = u
                        call hammer.launch()
                    endif
                call GroupRemoveUnit(g, u)
            endloop
            call DestroyGroup(g)

            set c = null
            set g = null
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A003', function thistype.onCast)
        endmethod
    endstruct
endscope
 

Code (vJASS):

scope MissilesD
    private struct Missile extends Missiles
        boolean deflected = false

        method onFinish takes nothing returns boolean
            if not deflected then
                set deflected = true
                call deflect(GetUnitX(source), GetUnitY(source))
            endif
 
            return false
        endmethod

        static method onCast takes nothing returns nothing
            local unit     c = GetTriggerUnit()
            local real     x = GetUnitX(c)
            local real     y = GetUnitY(c)
            local real     z = GetUnitFlyHeight(c) + 50
            local real     tx = GetSpellTargetX()
            local real     ty = GetSpellTargetY()
            local real     tz = z
            local thistype this = thistype.create(x, y, z, tx, ty, tz)

            set source    = c
            set model     = "units\\human\\phoenix\\phoenix"
            set speed     = 1000
            set arc       = GetRandomReal(0, 35)
            set curve     = GetRandomReal(15, 30)*GetRandomInt(-1, 1)
            set target    = GetSpellTargetUnit()

            call BlzPlaySpecialEffect(effect.effect, ConvertAnimType(5))
            call launch()

            set c = null
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A004', function thistype.onCast)
        endmethod
    endstruct
endscope
 

Code (vJASS):

scope MissilesF
    private struct Missile extends Missiles
        method onHit takes unit hit returns boolean
            if hit != source and UnitAlive(hit) then
                call KillUnit(hit)
            endif
 
            return false
        endmethod

        method onDestructable takes destructable dest returns boolean
            call KillDestructable(dest)
 
            return false
        endmethod

        static method onCast takes nothing returns nothing
            local unit     c  = GetTriggerUnit()
            local real     x  = GetUnitX(c)
            local real     y  = GetUnitY(c)
            local real     z  = GetUnitFlyHeight(c)
            local real     a  = 0
            local real     i  = 0
            local real     tx
            local real     ty
            local thistype this

            loop
                exitwhen i >= 10
                    set tx        = x + 400*Cos(a)
                    set ty        = y + 400*Sin(a)
                    set this      = Missile.create(x, y, z, tx, ty, z)
                    set model     = "Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireMissile.mdl"
                    set speed     = 1050
                    set source    = c
                    set collision = 125
                    set a         = a + 36*bj_DEGTORAD

                    call launch()
                set i = i + 1
            endloop

            set c = null
        endmethod

        static method onInit takes nothing returns nothing
            call RegisterSpellEffectEvent('A005', function thistype.onCast)
        endmethod
    endstruct
endscope
 

And Last but not Least, a video with the examples above in action.


Credits
  • BPower
  • Dirac
  • Vexorian
  • AGD
  • emjlr3
  • AceHart
  • Nestharus
  • Maghteridon96
  • Bribe
  • Flux
Changelog

(v1.0)
  • Release
(v1.1)
  • Merged Normal and Relativistic code for both Legacy and Current versions.
  • You can use the SWEET_SPOT constant to determine the system behavior
    • SWEET_SPOT <= 0 -> normal method
    • SWEET_SPOT > 0 -> relativistic method
  • Included support for DummyRecycler by Flux for the Legacy version. This should improve its performance even further.
(v1.2)
  • Fixed a bug for homing missiles height
  • New configuration member
    boolean collideZ
    -> set to true to make the new missile consider z collisions
  • New Global Configuration
    constant boolean ROLL
    -> If true missile will consider roll in orientation. Roll can look really fishy for some users and was never possible until patch 1.31, so if you want it set it to true in the globlas block (1.31+ version only).
  • You can now attach effects to the main missile effect with an offset through 2 new methods (1.31+ version only)
    • // dx, dy, and dz are offsets from the main missile coordinates
      method attach takes string model, real dx, real dy, real dz, real scale returns effect
    • method dettach takes effect attachment returns nothing
  • Few minor optmizations.
(v1.3)
  • Fix a minor rect leak
Contents

Missiles (Map)

  1. Devalut

    Devalut

    Joined:
    Feb 9, 2009
    Messages:
    1,293
    Resources:
    2
    Spells:
    2
    Resources:
    2
    >That video with the humongous amount of missiles on screen
    fgYuZNB.gif
    You sir make me want to learn JASS.
     
  2. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,592
    Resources:
    9
    Spells:
    9
    Resources:
    9
    So instance-processing gets different on a relativistic version by also changing
    missile velocities to compensate when a certain instance peaks the maximum.
    Perhaps, one of the clever ways to gain performance and it's naked to the eye.

    While you nailed the performance, can you use the sweet spot value of 0 to
    merge the two libraries? Although it may just cause extra checks for this lol.
    (Which I think you'll probably not consider it)

    Also, you may incorporate our unit recyclers here for the legacy variant.
    (I can only recommend Flux's DummyRecycler or Bribe's MissileRecycler)
    I think just make it a requirement rather than optional if you implement.

    I'll make some further suggestions and recommendations soon after testings. ;)
     
  3. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    379
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Do it man, srly. Not because JASS is a great language, cuz it ain't, but for the sheer fun it is to mess around in Warcraft. 8 months ago I started in GUI and now, even doing College and Work I'm doing vJass. Also, vJass has the most sweet syntax in my opinion, its just good to read it.

    Cool right. Oh i forgot, Big credits to Albert Einstein!!

    Unfortunately no because SWEET_SPOT is in the condition to exit the loop as well, and if its 0 no instances are processed because it exits immediately.

    Initially I was using MissileRecycler by Bribe but I found out during the development of Mannoroth Hero Concept that it's leaking dummys, and they are never destroyed for some reason when the system call for UnitAddTimedLife(), which for a missile system is a nono. Dont know if it is because of Reforged. I will try DummyRecycler, because indeed, a unit recycler for the legacy can do wonders.

    I'll do what i can to make this system as complete as possible to everyone.
     
  4. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,592
    Resources:
    9
    Spells:
    9
    Resources:
    9
    I mean if it's configured zeroed by the user. It just means to disable the instance-processing you made and just use the normal instead.
    In this way, you can merge the two variant in one library for convenience. (or even one library at all by the use of one flag and static if)
    But it's up to your preference as it only matters to my case. (where I'll be forced to mix the two libraries in my demo-maps)
    I'm excited to try it out, I think I'll just wait for a DummyRecycler to be implemented first before using it.
     
  5. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    379
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Oh, got it, will do. Will release a version with the recycler by tomorrow or today very late, bot soon.
     
  6. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,592
    Resources:
    9
    Spells:
    9
    Resources:
    9
    Alright, I'll be waiting.

    Besides, @AGD could be notified by this. He may borrow this kind of implementation if he sees no flaw of it.
    His coding-efforts were also remarkable with the new reworked Missile system. Although, I'll prefer this as it
    have an updated legacy support which is a big deal for me. Otherwise, both of the submissions are worth it.
     
  7. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,465
    Resources:
    8
    Models:
    1
    Icons:
    2
    Maps:
    1
    Spells:
    3
    JASS:
    1
    Resources:
    8
    Such performance, much wow.

    And, this was done in vJASS, imagine the performance gain if this was written in Lua as well.
     
  8. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    379
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Yeah, @AGD is an absolute genius, he's a much better programmer than me and maybe be able to improve it even further.

    If only I knew it ahha, one day for sure. But feel free to convert it people, don't wait for the mountain.
     
  9. hemmedo

    hemmedo

    Joined:
    Jun 13, 2008
    Messages:
    320
    Resources:
    0
    Resources:
    0
    Liked because you talked about relativity in your description.
     
  10. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,515
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Adapting missile speed for the relativistiy system might compensate the travelling that happened during the not-processing, but doesn't an important feature get lost - the events that would have happened on collision?
     
  11. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    536
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    This is a nice trick imo. So with this, you can essentially cap the operations being done per period to a certain amount, regardless of the number of Missiles you have in the map. The results, atleast according to your benchmarks showed a huge gap in performance. Good job.

    Even though this is done in exchange of some precision, I think it's still a good trade-off (Also considering that this feature can also be turned off). It's just a matter of finding the right 'sweet spot' to balance the performance and precision depending on your map.

    But y'know I personally prefer BPower's handling of callback methods using modules instead of using interfaces like this one. This literally creates a duplicate of each of your callback methods, plus other extra stuff (again, for each callback) neccessary for polymorphism. Using modules like BPower's atleast gives you better control of the code that would be generated in the implementing struct and you can therefore make it very minimal. But I understand that not everyone cares about this and prefers the more straightforward OOP API, so you don't need to change yours.

    Nevertheless, it's amazing to see the amount of missiles you can have simultaneously using this technique. I would like to borrow this ;D (if you'd agree) for BPower's Missile update and see if the technique can still be improved (Had no idea yet right now).

    You too are free to take what you can from BPower's updated library. I can see yours doesn't factor in elevation for collision yet, as well as the collision size of units/destructables/items but only Missile collision size. This also doesn't have a roll incorporated for the special effects yet. Or if you just prefer to make this as lite as possible - your call.
     
  12. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,592
    Resources:
    9
    Spells:
    9
    Resources:
    9
    Can you also consider providing multiple effects handling per missile. I think this feature shouldn't be missed too.

    EDIT:

    Yes, sacrificing a bit of precision to gain performance. :D
    However, it's not visible (lots of iterations per second) and only happens if it reaches the threshold for the
    system to consider this 'not-processing' the missiles in a whole. (good thing it's configurable for this sake)
    You might want to consider this also instead of manually factoring them on collision. (from your examples)
     
    Last edited: Jul 11, 2020
  13. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    536
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Ah yes, I forgot to mention this.
     
  14. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    379
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Standing in the shoulders of giants :p

    Yeah, that's a trade-off that can be fine tuned for each map maker in their maps like AGD and JAKEZINC brilliantly pointed out. I can make it so that compensation for time dilation be an option as well if you require it. Below is a video of the relativistic method without compensation, it also another demonstration of how many missiles it can handle. At some point in there there was 3000 missiles existing and when i'm not looking at 3000 stuff at the screen the fps is pretty high.


    I know that interfaces can generate a lot of stuff that a lot would consider garbage, but I just wanted it to be a little more friendly syntactically. Sure BPower's implementation is more professional but as far as performance goes, interfaces don't seem to be such a problem.

    Absolutely, go ahead and use/tweak/change it however you like it. You helped me a lot with other stuff as well!

    Sure, why not, I'll add all that. Although, for the missiles attachments it might take some more time.

    I already have some minor optimizations ideas that I want to try out.
     
  15. JAKEZINC

    JAKEZINC

    Joined:
    Aug 13, 2013
    Messages:
    1,592
    Resources:
    9
    Spells:
    9
    Resources:
    9
    You can lower the time it takes by utilizing this: [vJASS] - SpecialEffect
     
  16. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    379
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Nice!, I just need to see if the orientation will not be broken, because it uses a different method than I do.
     
  17. hemmedo

    hemmedo

    Joined:
    Jun 13, 2008
    Messages:
    320
    Resources:
    0
    Resources:
    0
    Is this practically ever needed though? Mostly likely no one ever requires to utilize hundreds of missile instances simultaneously in a real map. Even if you rework all of the native projectile based spells and attacks to use this system and play a 12vs12 melee map, even in a very complex scenario the amount of missile instances would still be below your estimated threshold value I think.

    I can see the benefits of this to create very eye-candy crazy spells though:)
     
  18. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,515
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    That one can adjust "sweet spot" doesn't really change the design that comes with it. The thing is, that "so many" missiles can be processed is because the system actually only processes a fixed amount of missiles and ignores rest. Perhaps I'm a party pooper here, but also the idea of theory of relativity seems quite misleading for it. Don't get me wrong, it's at least some approach if many missiles are in use, but it's also not the holy grail and the not-processing definitely comes with a worth to be mentioned donwside imo. Missiles are prioritized, in meaning they get operation time while others simply don't. Missiles can't catch up collisions they maybe had, and adjusting speed-factor can also have influence on gameplay. It's sure not majority of cases, but definitely brings some unbalance factor with it.

    It should be probably an integer, btw
    public  constant real   SWEET_SPOT         = 133
     
  19. chopinski

    chopinski

    Joined:
    May 16, 2012
    Messages:
    379
    Resources:
    3
    Spells:
    3
    Resources:
    3
    Then the system will behave like the compensation never existed and have a good performance, and that's the good thing about it.

    You are not a party pooper, constructive criticism is always welcome. You are right when saying that there is downsides to this method, but i think it is still better than doing nothing, and there might be some adjustments possible to compensate for this loss of precision. For example, i thought of creating a configuration constant to limit the amount of compensation, like a compression or threshold, i thought about increasing collision size proportionally to the compensation factor as well, that might solve some of this problems. Playing around with acceleration instead of velocity might produce better precision as well, I'm open to all ideas, so if you have any, please tell me and I'll try them out.

    You are absolutely right, didn't notice that, will fix.

    Edit: Surprisingly enough, changing SWEET_SPOT to integer changes the behavior of the compensation, probably because the difference of dividing an integer by a real and integer by an integer resulting as a factor that is always round (1, 2, 3...), so to save 2 function calls per missile I'll keep it as a real.
     
    Last edited: Jul 12, 2020