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

[vJASS] Missiles

Level 20
Joined
May 16, 2012
Messages
635
Hello Hive!

This is a Missile system that I've put together based on Vexorian , BPower and Dirac Missiles System, so Full credits to them. Also thanks to AGD for the tips and ideas to solve some problems.

Why?
- Well, recently I've been coding quite a bit of resources and the stuff that i was creating usually required a missile system, but all those system usually have a little flaw that forced me to use something else in order to achieve what i wanted.

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, 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 timer is never stopped and it do not have some useful events that the BPwer 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.

Since i based this system on their job, full credits to them.

There are 2 versions of this system. The first is for versions 1.30.x and lower. This version uses the dummy method. The second version is for patch 1.31+ and uses the new natives to manipulate effects, so no dummy, only effects.

Available Members:
JASS:
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)

Available Methods Calls:
JASS:
(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

Available Operators;
JASS:
/* -------------------------- 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

Available Events:
JASS:
(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.

JASS:
library Missiles requires optional WorldBounds
    /* ----------------------- Missiles v1.0 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
 
    //     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)
    //             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./32.
        // the avarage collision size compensation when detecting
        // collisions
        private constant real    COLLISION_SIZE     = 128.
        // 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

    private function DistanceBetweenCoordinates takes real x1, real y1, real x2, real y2 returns real
        local real dx = (x2 - x1)
        local real dy = (y2 - y1)
 
        return SquareRoot(dx*dx + dy*dy)
    endfunction

    //Little snippet to remove the dummy unit after a delay
    //I'm not using MissileRecycler because of a leaking problem i discover
    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 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
        //-------------------------------------------------------
        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
        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()

            if not HaveSavedBoolean(table, this, GetHandleId(d)) then
                call SaveBoolean(table, this, GetHandleId(d), true)
                if allocated and .onDestructable(d) then
                    set d = null
                    call terminate()
                    return
                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()

            if not HaveSavedBoolean(table, this, GetHandleId(i)) then
                call SaveBoolean(table, this, GetHandleId(i), true)
                if allocated and .onItem(i) then
                    set i = null
                    call terminate()
                    return
                endif
            endif

            set i = null
        endmethod

        /* ------------------------------ Reset members ----------------------------- */
        private method reset takes nothing returns nothing
            set launched     = 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()
            call Timed.Recycle(dummy, RECYCLE_TIME)
            call reset()
            set missiles[i] = missiles[didx]
            set didx        = didx - 1

            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

                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     i = 0
            local integer     j
            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        yaw
            local real        pitch
            local Missiles    missile
            local Coordinates o
            local thistype    this
    
            loop
                exitwhen i > didx
                set this = missiles[i]
                    set temp = this
                    if allocated then
                        set o = origin
                        set h = height
                        set c = open
                        set d = o.distance
                    
                        //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
                                                call SaveBoolean(table, this, GetHandleId(u), true)
                                                if allocated and .onHit(u) then
                                                    call terminate()
                                                    exitwhen true
                                                endif
                                            endif
                                        endif
                                    call GroupRemoveUnit(hitGroup, u)
                                endloop
                            endif
                        endif

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

                        // onDestructable Event
                        if .onDestructable.exists then
                            if allocated and collision > 0 then
                                set dx = collision
                                set RECT = 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
                                set RECT = 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) + impact.z)
                            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 x      = prevX + veloc*Cos(cA)
                        set y      = prevY + veloc*Sin(cA)
                        set s      = travel + veloc
                        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)
                    endif
                set i = i + 1
            endloop

            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 .dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_RAW_CODE, x, y, angle)

            call SetUnitFlyHeight(.dummy, z - GetLocZ(x, y), 0)
            call Coordinates.link(origin, impact)
            set .cA = origin.angle

            return this
        endmethod
    endstruct
endlibrary

JASS:
library Missiles requires optional WorldBounds
    /* ----------------------- Missiles v1.0 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 library into your map and optionally WorldBounds
    //     WorldBounds: https://raw.githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
 
    // 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)
    //             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 update period of the system
        public  constant real    PERIOD             = 1./32.
        // the avarage collision size compensation when detecting
        // collisions
        private constant real    COLLISION_SIZE     = 128.
        // 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

    // To check missiles collisions
    private function DistanceBetweenCoordinates takes real x1, real y1, real x2, real y2 returns real
        local real dx = (x2 - x1)
        local real dy = (y2 - y1)
 
        return SquareRoot(dx*dx + dy*dy)
    endfunction

    // The Missile effect
    private struct Effect
        effect effect
        string path
        real   size

        method scale takes real scale returns nothing
            call BlzSetSpecialEffectScale(effect, scale)
        endmethod

        // Must be set in this order
        method orient takes real yaw, real pitch, real roll returns nothing
            call BlzSetSpecialEffectYaw(effect, yaw)
            call BlzSetSpecialEffectPitch(effect, pitch)
            call BlzSetSpecialEffectRoll(effect, roll)
        endmethod

        method move takes real x, real y, real z returns nothing
            static if LIBRARY_WorldBounds then
                if not (x > WorldBounds.maxX or x < WorldBounds.minX or y > WorldBounds.maxY or y < WorldBounds.minY) then
                    call BlzSetSpecialEffectPosition(effect, x, y, z)
                endif
            else
                if RectContainsCoords(bj_mapInitialPlayableArea, x, y) then
                    call BlzSetSpecialEffectPosition(effect, x, y, z)
                endif
            endif
        endmethod

        /* -------------------------- Contructor/Destructor ------------------------- */
        method destroy takes nothing returns nothing
            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
            call BlzSetSpecialEffectZ(effect, z)

            return this
        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)
            // 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 thistype       temp      = 0
        private static thistype array missiles
        //-------------------------------------------------------
        private real     cA     // Current Angle
        private real     height // Arcs
        private real     open   // Curves
        //-------------------------------------------------------
        Coordinates      impact
        Coordinates      origin
        Effect           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
        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(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

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

            if not HaveSavedBoolean(table, this, GetHandleId(d)) then
                call SaveBoolean(table, this, GetHandleId(d), true)
                if allocated and .onDestructable(d) then
                    set d = null
                    call terminate()
                    return
                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()

            if not HaveSavedBoolean(table, this, GetHandleId(i)) then
                call SaveBoolean(table, this, GetHandleId(i), true)
                if allocated and .onItem(i) then
                    set i = null
                    call terminate()
                    return
                endif
            endif

            set i = null
        endmethod

        /* ------------------------------ Reset members ----------------------------- */
        private method reset takes nothing returns nothing
            set launched     = 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

            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

                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     i = 0
            local integer     j
            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        yaw
            local real        pitch
            local Missiles    missile
            local Coordinates o
            local thistype    this
        
            loop
                exitwhen i > didx
                set this = missiles[i]
                    set temp = this
                    if allocated then
                        set o = origin
                        set h = height
                        set c = open
                        set d = o.distance
                    
                        //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
                                                call SaveBoolean(table, this, GetHandleId(u), true)
                                                if allocated and .onHit(u) then
                                                    call terminate()
                                                    exitwhen true
                                                endif
                                            endif
                                        endif
                                    call GroupRemoveUnit(hitGroup, u)
                                endloop
                            endif
                        endif

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

                        // onDestructable Event
                        if .onDestructable.exists then
                            if allocated and collision > 0 then
                                set dx = collision
                                set RECT = 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
                                set RECT = 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) + impact.z)
                            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 yaw    = cA
                        set pitch  = o.alpha
                        set x      = prevX + veloc*Cos(yaw)
                        set y      = prevY + veloc*Sin(yaw)
                        set s      = travel + veloc
                        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
                            call effect.orient(yaw, -pitch, 0)
                        endif
                        call effect.move(x, y, z)
                    else
                        set i = remove(i)
                    endif
                set i = i + 1
            endloop

            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    = Effect.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
            call Coordinates.link(origin, impact)
            set .cA = origin.angle

            return this
        endmethod
    endstruct
endlibrary

JASS:
scope MissilesQ
    private struct Misisle extends Missiles
        unit caster

        // method onPeriod takes nothing returns boolean
        //     call ClearTextMessages()
        //     call DisplayTimedTextToPlayer(Player(0), 0, 0, 0.1, "onPeriod(" + (I2S(this)) + ") X: " + R2S(.x))
        //     call DisplayTimedTextToPlayer(Player(0), 0, 0, 0.1, "onPeriod(" + (I2S(this)) + ") Y: " + R2S(.y))
        //     call DisplayTimedTextToPlayer(Player(0), 0, 0, 0.1, "onPeriod(" + (I2S(this)) + ") Z: " + R2S(.z))

        //     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 m  = Misisle.create(fromX, fromY, fromZ, toX, toY, 50)

            set m.model     = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
            set m.speed     = 500
            set m.target    = t
            set m.source    = c
            //set m.arc       = GetRandomReal(10, 40)
            //set m.curve     = GetRandomReal(-25, 25)
            set m.collision = 75
            set m.caster    = c

            call m.launch()
        endmethod

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

JASS:
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

JASS:
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

JASS:
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

JASS:
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

JASS:
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 a little video demonstrating the system
 

Attachments

  • Missiles.w3x
    947.9 KB · Views: 38
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Why code a new missile system based on dummy.mdx instead of going with sfx based missiles right away? This seems very backwards to me for a 2020 submission.

Just my thoughts. I cant find anything wrong with it aside from that except maybe the dependency on TimerUtils, which I think has lost its usefulness since the implementation of hashtables in 1.24, as it pointlessly adds overhead just to save 2 lines of code and a periodic timer.

AGD's rewrite of BPowers Missile is imho the more modern approach as it doesnt rely on dummy.mdx anymore.
 
Level 20
Joined
May 16, 2012
Messages
635
Why code a new missile system based on dummy.mdx instead of going with sfx based missiles right away? This seems very backwards to me for a 2020 submission.

Just my thoughts. I cant find anything wrong with it aside from that except maybe the dependency on TimerUtils, which I think has lost its usefulness since the implementation of hashtables in 1.24, as it pointlessly adds overhead just to save 2 lines of code and a periodic timer.

AGD's rewrite of BPowers Missile is imho the more modern approach as it doesnt rely on dummy.mdx anymore.

I coded it using the dummy approach because the new effect orientation natives produce a wrong effect orientation for some cases. As i put it in the thread, if someone comes up with a effect orientation API that's is capable of orienting an effect no matter the yaw, pitch or roll used as parameters than I'll update this library to use such API. I'm aware of AGD's work in BPower's missile system, I'm the one who first pointed to him that the effect orientation methods that he was using were producing wrong results, and i also tested a few other methods that i found here in Hive and none of them produced a satisfactory result. I'm aware that the dummy approach is costly, CreateUnit() and SetUnitFacing() are the worst but for now this is the most accurate method.

TimerUtils is Optional, like WorldBounds. If anyone importing do not have it, the system will use the hashtable, but i can take it out if it is necessary.
 
Level 1
Joined
Apr 8, 2020
Messages
110
Can we (as in I) get access to the missile group or better yet get an event like onHitMissile? Missile to missile collision would be nice for some fun.

I must say your system is way easier to interpret than BPower's, no offense to his dedicated time spent in honing his system.
 
Level 20
Joined
May 16, 2012
Messages
635
Can we (as in I) get access to the missile group or better yet get an event like onHitMissile? Missile to missile collision would be nice for some fun.

I must say your system is way easier to interpret than BPower's, no offense to his dedicated time spent in honing his system.

By missile group you mean the hit group? if so than yeah, it's possible, just making it not private would do, but you will need to be very careful with what you do with it, because it might break stuff. For the onMissile event, its possible as well and i will add it tomorrow since someone already requested it.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I coded it using the dummy approach because the new effect orientation natives produce a wrong effect orientation for some cases. As i put it in the thread, if someone comes up with a effect orientation API that's is capable of orienting an effect no matter the yaw, pitch or roll used as parameters than I'll update this library to use such API. I'm aware of AGD's work in BPower's missile system, I'm the one who first pointed to him that the effect orientation methods that he was using were producing wrong results, and i also tested a few other methods that i found here in Hive and none of them produced a satisfactory result. I'm aware that the dummy approach is costly, CreateUnit() and SetUnitFacing() are the worst but for now this is the most accurate method.

TimerUtils is Optional, like WorldBounds. If anyone importing do not have it, the system will use the hashtable, but i can take it out if it is necessary.
Have you tried the latest one I posted (3.0.2)? The issues regarding sfx orientation were fixed, except for a new issue Zwiebelchen pointed out to me regarding orientation on curved ground. But it can be fixed by introducing some correction for ground curvature. I don't think reverting back to using dummy units should be the choice here.
 
Level 20
Joined
May 16, 2012
Messages
635
Have you tried the latest one I posted (3.0.2)? The issues regarding sfx orientation were fixed, except for a new issue Zwiebelchen pointed out to me regarding orientation on curved ground. But it can be fixed by introducing some correction for ground curvature. I don't think reverting back to using dummy units should be the choice here.

Haven't yet, but will do. Have you posted the effect API separately somewhere?
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
You have that other videos revealing the number of projectiles this system can handle at once,
it seems like this one is superior in performance. (500+ vs. 300+) is obviously comparable. Now
I wonder how this gets rid most of the performance problems than the other Missile submissions.

You also provided a legacy support which is a plus. Although, maybe state some specific details
or maybe structured in pros/cons format that your system manifest than the other Missile systems.
Make the features bold & clear. I think the example of situation you explained was not enough to
distinguished this from the others. (As performance impact is the most intriguing part in my opinion)
 
Level 20
Joined
May 16, 2012
Messages
635
You have that other videos revealing the number of projectiles this system can handle at once,
it seems like this one is superior in performance. (500+ vs. 300+) is obviously comparable. Now
I wonder how this gets rid most of the performance problems than the other Missile submissions.

You also provided a legacy support which is a plus. Although, maybe state some specific details
or maybe structured in pros/cons format that your system manifest than the other Missile systems.
Make the features bold & clear. I think the example of situation you explained was not enough to
distinguished this from the others. (As performance impact is the most intriguing part in my opinion)

I'm creating the thread now explaining a lot of stuff, so just give me a moment. Also there's even more performance gain with the new stuff I did, it's going to be fun! Oh and btw, that Phoenix ability in the demo video, you inspired me to be able to do that with this system :p
 
Level 20
Joined
Aug 13, 2013
Messages
1,696
I see, I'm definitely looking forward to it. The legacy + reforged variant
you've provided makes this very attractive to implement within my resources.
(You know I love supporting the both sides of our community)

I'm seeing this one to be included in one of my resources' dependencies,
that is if only "units vs effects" differentiates the feature of both variants.

I would also like to know more of your benchmark tests. It's very critical.
This clarifies the performance change we may get from "units vs effects".
 
Level 20
Joined
May 16, 2012
Messages
635
I see, I'm definitely looking forward to it. The legacy + reforged variant
you've provided makes this very attractive to implement within my resources.
(You know I love supporting the both sides of our community)

I'm seeing this one to be included in one of my resources' dependencies,
that is if only "units vs effects" differentiates the feature of both variants.

I would also like to know more of your benchmark tests. It's very critical.
This clarifies the performance change we may get from "units vs effects".

Look here . The only difference between legacy and reforged if the use of the dummy method. Apart from that, everything else is the same.

About the performance using dummy vs effcect: Yeah theres a difference because when using dummy unit we are forced to use SetUnitFacing and CreateUnit which are 2 of the most expansive functions. Effect onthe other hand seem to be very light, so big pro in there. But the main gain in performance is the way missile instances are processed. I explained my method in the thread, check it out.
 
Top