• 🏆 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!

Missile Rework

Status
Not open for further replies.
Hello everyone.

Looking at the recent versions of Warcraft 3, I had felt the need to recreate Missile systems in order to use special effects directly. However, as it is currently in development, not many features have been developed yet, and my aim for this is to keep it as lightweight as possible, both in importation and resource usage.

I want to make this missile library as collaborative as possible, thus my decision in posting it here in the Lab. I also want to implement some functionalities that may arise from suggestions or feedbacks.

Dependencies:
JASS:
library EffectUtils requires /*
        -----------
        */ Alloc /*
        -----------
          - Sevion
          - link: https://www.hiveworkshop.com/threads/snippet-alloc.192348/
          
          (Note: The Alloc library supplied here is a modified version that bridges throughout
           the previous versions of warcraft 3 and the most recent versions.)
          
        --------------------------
        */ optional GameVersion /*
        --------------------------
          - TriggerHappy
          - link:  https://www.hiveworkshop.com/threads/detect-game-version-getpatchlevel.308204/
          
        --------------------------------------------------------------------
        
             -----------------------
            |   EffectUtils         |
             -----------------------
                - A structified version of the native effect handle.
                
             -----------------------
            |   Author notes:       |
             -----------------------
                - A game version detector is used in the case that the
                  patch version is at least 1.30+ (where the native
                  BlzGetLocalUnitZ will work). However, this is optional
                  and one can manually 
            
        --------------------------------------------------------------------
        
            API:
            
        --------------------------------------------------------------------
        
            - struct Effect
                --------------------------------------------------------
                Core methods:
                
                - method addXY(string mdlPath, real x, real y) -> Effect
                    - Acts like AddSpecialEffect()
                - method addTarget(string mdlPath, widget target, string attach) -> Effect
                    - Acts like AddSpecialEffectTarget()
                - method destroy()
                    - Destroys an Effect instance immediately.
                - method destroyTimed(real dur)
                    - Destroys an Effect instance after dur seconds.
                    - Note that calling destroy after this will not work.
                
                --------------------------------------------------------
                Library Extension methods:
                
                - static method getPairZ(real x, real y) -> real
                - static method getGroundZ(real x, real y) -> real
                    - Returns the z-coordinate of a given point.
                --------------------------------------------------------
                Functionality methods:
                
                - method setColorByChar(char colorCode)
                    - Modifies the vertex color of an Effect instance.
                    - The parameter type is integer, but it is recommended
                      to pass hexadecimal values for readability.
                    - The color code is interpreted as follows:
                        (RRGGBBAA)
                        
                - method setRed(integer value)
                - method setGreen(integer value)
                - method setBlue(integer value)
                - method setAlpha(integer value)
                    - Modifies the vertex color of an Effect instance.
                    - Effect is singular, and may waste performance
                      if used to modify all four directly.
                    - Integer values are recommended (from 0 - 255)
                
                - method setX(real x)
                - method setY(real y)
                - method setZ(real z)
                    - Sets the respective coordinates of the Effect instance.
                    
                - method setYaw(real yaw)
                - method setPitch(real pitch)
                - method setRoll(real roll)
                    - Modifies the orientation of the model of the Effect instance.
                    - Might be tricky to work with.
                    
                Note: All setter methods have a respective getter method
                      with no parameters.
                
                - method getModelPath() -> string
                    - Returns the model path of the Effect instance.
                    
                - method getAttachmentPoint() -> string
                    - Returns the attachment point of the Effect instance.
                    
                --------------------------------------------------------
                
        --------------------------------------------------------------------
    */
globals
    //  Will determine whether version detection will be handled by the script
    //  in interpreting which object to use when getting the z-coordinate of a
    //  certain point. (Either a unit or a location handle).
    
    //  Set this to false if you are sure of the game version you are using
    //  for development.
    private constant boolean AUTOMATIC_VERSION_DETECTION    = true
endglobals
static if DEBUG_MODE then
private function Error takes string msg, string funcName, boolean isError returns nothing
    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, msg)
    if isError then
        call PauseGame(true)
    endif
endfunction
endif
static if not LIBRARY_GameVersion then
private struct LibraryFlags extends array
    static constant boolean IS_PATCH_130_OR_GREATER     = false
endstruct
endif
private module Init
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod
endmodule
private module EffectMethods
    private static method setOrientation takes effect fx, real yaw, real pitch, real roll returns nothing
        call BlzSetSpecialEffectOrientation(fx, roll, pitch, yaw)
    endmethod
    
    method getRed takes nothing returns integer
        return this.red
    endmethod
    method getGreen takes nothing returns integer
        return this.green
    endmethod
    method getBlue takes nothing returns integer
        return this.blue
    endmethod
    method getAlpha takes nothing returns integer
        return this.alpha
    endmethod
    method getX takes nothing returns real
        return BlzGetLocalSpecialEffectX(this.fx)
    endmethod
    
    method getY takes nothing returns real
        return BlzGetLocalSpecialEffectY(this.fx)
    endmethod
    method getZ takes nothing returns real
        return BlzGetLocalSpecialEffectZ(this.fx)
    endmethod
    
    method getHeight takes nothing returns real
        return this.getZ() - thistype.getPairZ(this.getX(), this.getY())
    endmethod
    
    method getYaw takes nothing returns real
        return this.yaw
    endmethod
    
    method getFacing takes nothing returns real
        return this.yaw*bj_RADTODEG
    endmethod
    method getPitch takes nothing returns real
        return this.pitch
    endmethod
    method getRoll takes nothing returns real
        return this.roll
    endmethod
    method getModelPath takes nothing returns string
        return this.model
    endmethod
    
    method getAttachmentPoint takes nothing returns string
        return this.orient
    endmethod
    
    method setColorByChar takes integer hex returns nothing
        local integer array color
        if hex < 0 then
            set color[0] = color[0] + 0x80
            set hex      = hex - 0x80000000
        endif
        
        set color[0] = color[0] + ModuloInteger(hex/0x1000000, 0x100)
        set color[1] = color[1] + ModuloInteger(hex/0x10000, 0x100)
        set color[2] = color[2] + ModuloInteger(hex/0x100, 0x100)
        set color[3] = color[3] + ModuloInteger(hex, 0x100)        
        
        set this.red    = color[0]
        set this.green  = color[1]
        set this.blue   = color[2]
        set this.alpha  = color[3]
        
        call BlzSetSpecialEffectColor(this.fx, color[0], color[1], color[2])
        call BlzSetSpecialEffectAlpha(this.fx, color[3])
    endmethod
    
    method setRed takes integer newRed returns nothing
        set this.red = newRed
        call BlzSetSpecialEffectColor(this.fx, newRed, this.green, this.blue)
    endmethod
    method setGreen takes integer newGreen returns nothing
        set this.green = newGreen
        call BlzSetSpecialEffectColor(this.fx, this.red, newGreen, this.blue)
    endmethod
    method setBlue takes integer newBlue returns nothing
        set this.blue = newBlue
        call BlzSetSpecialEffectColor(this.fx, this.red, this.green, newBlue)
    endmethod
    method setAlpha takes integer newAlpha returns nothing
        set this.alpha = newAlpha
        call BlzSetSpecialEffectAlpha(this.fx, newAlpha)
    endmethod
    
    method setX takes real newX returns nothing
        call BlzSetSpecialEffectPosition(this.fx, newX, this.getY(), this.getZ())
    endmethod
    
    method setY takes real newY returns nothing
        call BlzSetSpecialEffectPosition(this.fx, this.getX(), newY, this.getZ())
    endmethod
    
    method setZ takes real newZ returns nothing
        call BlzSetSpecialEffectPosition(this.fx, this.getX(), this.getY(), newZ)
    endmethod
    
    method setYaw takes real newYaw returns nothing
        set this.yaw = ModuloReal(newYaw, 2*bj_PI)
        call thistype.setOrientation(this.fx, this.yaw, -this.pitch, this.roll)
    endmethod
    
    method setFacing takes real newFacing returns nothing
        set this.yaw = ModuloReal(newFacing*bj_DEGTORAD, 2*bj_PI)
        call thistype.setOrientation(this.fx, this.yaw, -this.pitch, this.roll)
    endmethod
    
    method setPitch takes real newPitch returns nothing
        set this.pitch = ModuloReal(newPitch, 2*bj_PI)
        call thistype.setOrientation(this.fx, this.yaw, -this.pitch, this.roll)
    endmethod
    
    method setRoll takes real newRoll returns nothing
        set this.roll = ModuloReal(newRoll, 2*bj_PI)
        call thistype.setOrientation(this.fx, this.yaw, -this.pitch, this.roll)
    endmethod    
endmodule
struct Effect extends array
    implement Alloc
    private static timer      tm                = null
    private static unit       zGetter           = null
    private static location   zGetterLoc        = null
    private static Table      timerMap          = 0
    
    private effect fx
    private string model
    private string orient
    
    private real yaw
    private real pitch
    private real roll
            
    private integer red
    private integer green
    private integer blue
    private integer alpha
    
    static method getPairZ takes real x, real y returns real
        local boolean flag = false
        
    static if LIBRARY_GameVersion and AUTOMATIC_VERSION_DETECTION then
        set flag = (GetPatchLevel() <= PATCH_LEVEL_129)
    else
        set flag = not LibraryFlags.IS_PATCH_130_OR_GREATER
    endif
        
        if flag then
            call MoveLocation(Effect.zGetterLoc, x, y)
            return GetLocationZ(Effect.zGetterLoc)
        endif
        call SetUnitX(Effect.zGetter, x)
        call SetUnitY(Effect.zGetter, y)
        
        return BlzGetLocalUnitZ(Effect.zGetter)
    endmethod
    
    static method getGroundZ takes real x, real y returns real
        return Effect.getPairZ(x, y)
    endmethod
    
    implement EffectMethods
    method destroy takes nothing returns nothing
        if this.fx != null then
            if  Effect.timerMap.timer.has(this) then
                debug call Error("Cannot destroy timed effect manually.", "Effect.destroy", true)
            else
                call DestroyEffect(this.fx)
                set this.fx     = null
                set this.model  = null
                set this.orient = null
                
                set this.yaw    = 0
                set this.pitch  = 0
                set this.roll   = 0
                
                call this.deallocate()
            endif
        endif
    endmethod
    
    private static method create takes string filePath returns Effect
        local Effect result     = Effect.allocate()
        set result.model        = filePath
        return result
    endmethod
    
    private static method onDestroyCallback takes nothing returns nothing
        local Effect object
        
        set Effect.tm       = GetExpiredTimer()
        set object          = Effect(Effect.timerMap.integer[-GetHandleId(Effect.tm)])
        
        call Effect.timerMap.timer.remove(object)
        call Effect.timerMap.remove(-GetHandleId(Effect.tm))
                
        call DestroyTimer(Effect.tm)
        call object.destroy()
    endmethod
    
    method destroyTimed takes real dur returns nothing
        //  If the effect has already been destroyed, return.
        if this.fx == null then
            return
        endif
        
        //  If the effect object is to be destroyed later on.
        if not Effect.timerMap.timer.has(this) then
            set Effect.tm   = CreateTimer()
            set Effect.timerMap.timer[this]              = Effect.tm
            set Effect.timerMap[-GetHandleId(Effect.tm)] = this
            
            call TimerStart(Effect.tm, dur, false, function Effect.onDestroyCallback)
        else
            set Effect.tm   = Effect.timerMap.timer[this]
            if TimerGetRemaining(Effect.tm) < dur then
                call PauseTimer(Effect.tm)
                call TimerStart(Effect.tm, dur, false, function Effect.onDestroyCallback)
            endif
        endif
    endmethod
    
    static method addXY takes string filePath, real x, real y returns Effect
        local Effect object = Effect.create(filePath)
        set object.fx       = AddSpecialEffect(filePath, x, y)
        return object
    endmethod
    
    static method addTarget takes string filePath, widget targ, string orient returns Effect
        local Effect object = Effect.create(filePath)
        set object.fx       = AddSpecialEffectTarget(filePath, targ, orient)
        set object.orient   = orient
        return object
    endmethod
        
    private static method init takes nothing returns nothing
        set Effect.timerMap         =   Table.create()
        set Effect.zGetter          =   CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'hfoo', 0, 0, 0)
        set Effect.zGetterLoc       =   Location(0, 0)
        
        call ShowUnit(Effect.zGetter, false)
        call PauseUnit(Effect.zGetter, true)
        call SetUnitInvulnerable(Effect.zGetter, true)
    endmethod
    
    implement Init
endstruct
endlibrary

Main library:
JASS:
library Missile requires /*
        ----------------------
        */  EffectUtils     /*
        ----------------------
            # Alloc
            # Table
            
    */
private module Init
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod
endmodule
struct Missile extends array
    implement Alloc
    
    private static constant real INTERVAL           = 1/32.
    private static timer tm                         = null
    private static code  missileCallback            = null
    
    private boolean hasHit
    private boolean destroyWasInvoked
    private boolean isLaunched
    
    private thistype next
    private thistype prev
    
    /*
        ----------------
       |                |
       | Missile mems   |
       |                |
        ----------------
    */
    
    private Effect fx
    
    private trigger onHit
    
    private integer moveType
    private real moveSpeed
    private real facingSpeed
    private real initialHeight
    private real initialDist
    private real maxHeight
    
    private real zSpeed
    private real zRate
    
    private boolean hasBounced
    
    readonly boolean isArced
    
    readonly real targX
    readonly real targY
    readonly real targHeight
    
    readonly widget target
    readonly unit   targetUnit
    boolean destroyOnDeath
    boolean destroyOnRemove
    
    integer data
    
    method getSpeed takes nothing returns real
        return this.moveSpeed/thistype.INTERVAL
    endmethod
    
    method getX takes nothing returns real
        return this.fx.getX()
    endmethod
    
    method getY takes nothing returns real
        return this.fx.getY()
    endmethod
    
    method setSpeed takes real speed returns thistype
        set this.moveSpeed  = speed*thistype.INTERVAL
        return this
    endmethod
    
    method setFacingSpeed takes real facing returns thistype
        set this.facingSpeed = RMinBJ(facing * bj_DEGTORAD, 2*bj_PI)
        return this
    endmethod
    
    method setTargXY takes real x, real y returns thistype
        if not this.isLaunched then
            set this.moveType   = 1
            set this.targX      = x
            set this.targY      = y
            set this.targHeight = 0.
        endif
        return this
    endmethod
    method setTarget takes widget targ returns thistype
        set this.moveType   = 2
        set this.isArced    = false
        set this.target     = targ
        set this.targX      = GetWidgetX(targ)
        set this.targY      = GetWidgetY(targ)
        set this.targetUnit = null
        return this
    endmethod
    method setTargetUnit takes unit targUnit returns thistype
        call this.setTarget(targUnit)
        set this.targetUnit = targUnit
        set this.targHeight = GetUnitFlyHeight(targUnit)
        return this
    endmethod
        
    method setXY takes real x, real y returns thistype
        call this.fx.setX(x)
        call this.fx.setY(y)
        return this
    endmethod
    
    method onHitEvent takes code func returns thistype
        if this.onHit != null then
            call DestroyTrigger(this.onHit)
        endif
        
        set this.onHit = CreateTrigger()
        call TriggerAddCondition(this.onHit, Condition(func))
        return this
    endmethod
    
    method setTargHeight takes real newHeight returns thistype
        local real dist
        if (this.moveType == 1) then
            set this.targHeight = RMaxBJ(newHeight, 0)
            if not this.isArced then
                set dist        = (this.fx.getX() - this.targX)*(this.fx.getX() - this.targX) + (this.fx.getY() - this.targY)*(this.fx.getY() - this.targY)
                set dist        = SquareRoot(dist)
                
                set this.zSpeed = (this.targHeight - this.initialHeight)/dist
            endif
        endif
        return this
    endmethod
    
    method applyArc takes real initHeight, real maxHeight returns thistype
        local real h
        local real a
        local real dist
        
        local real x1   = this.fx.getX()
        local real x2   = this.targX
        local real y1   = this.fx.getY()
        local real y2   = this.targY
        
        local real dist2
        local real y3
        local real dy
        
        if (this.moveType == 1) then
            set dist2   = (x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2)
            set dist    = SquareRoot(dist2)
            
            call this.fx.setZ(Effect.getGroundZ(this.fx.getX(), this.fx.getY()) + initHeight)
            set this.initialHeight = this.fx.getHeight()
            
            if not this.isArced then
                set this.isArced            = true
                set this.initialDist        = dist 
            
            elseif this.hasBounced then
                set maxHeight       = maxHeight * (dist/this.initialDist)                
            endif
            
            set maxHeight       = RMaxBJ(maxHeight, RMaxBJ(this.initialHeight, this.targHeight))
            set this.maxHeight  = maxHeight
            
            if not (this.initialHeight != this.targHeight) then
                set h = dist/2
                
                set a = (this.initialHeight - maxHeight)/((-h)*(-h))
            else
                set dy = this.initialHeight - maxHeight
                set y3 = this.initialHeight - this.targHeight
                set h  = (((dy)*dist) + SquareRoot((dy*dy*dist2)-(y3)*(dy*dist2)))/(y3)
                
                set a  = (y3)/(2*h*dist -dist2)
                
                /*
                call BJDebugMsg("Distance (x2): " + R2S(dist))
                call BJDebugMsg("Initial height (y1): " + R2S(this.initialHeight))
                call BJDebugMsg("Target height (y2): " + R2S(this.targHeight))
                call BJDebugMsg("h -> " + R2S(h))
                call BJDebugMsg("a -> " + R2S(a))
                call BJDebugMsg("k -> " + R2S(maxHeight) + "\n\n")
                */
            endif
            set this.zRate  = 2*a
            set this.zSpeed = this.zRate*(-h)
        endif
        return this
    endmethod
    
    //  Launch does not allow chained calls.
    method launch takes real x, real y, boolean faceImmediately returns nothing
        local real dist
        if this.moveType <= 0 then
            //  The instance has either been destroyed, or has not been mapped out yet
            return
        elseif this.isLaunched then
            //  The instance has been included in the list of moving projectiles.
            return
        endif
        
        call this.setXY(x, y)
        if faceImmediately then
            call this.fx.setFacing(Atan2(this.targY - y, this.targX - x))
        endif
        set this.initialHeight =    this.fx.getHeight()
        
        set this.next = 0
        set this.prev = this.next.prev
        set this.next.prev  = this
        set this.prev.next  = this
        
        set this.isLaunched = true
        
        set dist = SquareRoot((x - this.targX)*(x - this.targX) + (y - this.targY)*(y - this.targY))
        set this.zSpeed = (this.targHeight - this.initialHeight)/dist
        
        if thistype(0).next != 0 then
            call TimerStart(thistype.tm, thistype.INTERVAL, true, thistype.missileCallback)
        endif
    endmethod
    
    method destroy takes nothing returns nothing
        //  Destroying a missile while the onHit event is being processed is considered invalid.
        //  However, any data associated with the Missile instance is nullified.
        if this.hasHit then
            set this.destroyWasInvoked  = true
            set this.data               = 0
            debug call BJDebugMsg("Attempted to destroy Missile instance ("+I2S(this)+") during an onHit event!")
            return
        endif
        
        //  Perhaps the missile was launched, but has not hit anything yet
        if this.isLaunched then
            set this.next.prev  = this.prev
            set this.prev.next  = this.next
            
            set this.isLaunched = false        
            debug call BJDebugMsg("Missile: User has called this function.")
        endif
        call DestroyTrigger(this.onHit)
        call this.fx.destroy()
        
        set this.onHit      = null
        set this.target     = null
        set this.targetUnit = null
        
        set this.fx             = 0
        set this.data           = 0
        set this.moveSpeed      = 0.
        set this.facingSpeed    = 0.
        set this.targX          = 0.
        set this.targY          = 0.
        
        set this.destroyOnDeath     = false
        set this.destroyOnRemove    = false        
        set this.destroyWasInvoked  = false
        
        call this.deallocate()
    endmethod
    
    static method create takes string mdlFile returns thistype
        local thistype this = thistype.allocate()
        set this.fx         = Effect.addXY(mdlFile, 0, 0)
        set this.moveType   = -1
        return this
    endmethod
    
    readonly static thistype current        = 0
    readonly static real array rVar
    /*
        Handler functions are processed here.
    */
    private method onHitCallback takes nothing returns nothing
        set this.hasHit      = true
        call TriggerEvaluate(this.onHit)
        set this.hasHit      = false
    endmethod
    
    private static method applyZMovement takes nothing returns nothing
        set rVar[13]    = Effect.getGroundZ(current.fx.getX(), current.fx.getY())
        set rVar[6]     = current.fx.getZ()
        set rVar[7]     = RMaxBJ(rVar[6] + current.zSpeed*current.moveSpeed, rVar[13])
            
        if current.isArced then
            set current.zSpeed = current.zSpeed + (current.zRate*current.moveSpeed)
        endif
        
        set rVar[8] = Atan2((rVar[7] - rVar[6])*Cos(rVar[5]), current.moveSpeed)
        if (current.facingSpeed*bj_RADTODEG != 0.) then
            set rVar[12] = (rVar[5] - rVar[11])*bj_RADTODEG
            //call BJDebugMsg("Difference of angles: " + R2S(rVar[12]))
            set rVar[9] = RMinBJ(Atan2(rVar[5] - rVar[11], current.facingSpeed), bj_PI/2)
        endif
        
        //call BJDebugMsg("Radian value for pitch: " + R2S(rVar[8]))
        //call BJDebugMsg("Radian value for roll: " + R2S(rVar[9]))
        
        call current.fx.setZ(rVar[7])
        call current.fx.setPitch(rVar[8])
        call current.fx.setRoll(rVar[9])
            
        if current.isArced then
            if rVar[7] <= rVar[13] then
                set current.hasBounced = true
                call current.applyArc(current.fx.getHeight(), current.maxHeight)
                set current.hasBounced = false
            endif
            // call BJDebugMsg("Current fx's ("+I2S(current)+") z-speed: " + R2S(current.zSpeed))
        endif
    endmethod
    
    private static method retrieveFacing takes nothing returns nothing
        if current.facingSpeed <= 0 then
            //  The facing speed is instantaneous
            set rVar[5] = rVar[3]
        else
            //  The facing speed is not instantaneous, simulate rotation.
            set rVar[3] = ModuloReal(rVar[3], 2*bj_PI)
            set rVar[5] = current.fx.getYaw()
            set rVar[6] = rVar[3] + 2*bj_PI
            set rVar[9] = rVar[3] - 2*bj_PI
            
            set rVar[7] = RAbsBJ(rVar[6] - rVar[5])
            set rVar[8] = RAbsBJ(rVar[3] - rVar[5])
            set rVar[10]= RAbsBJ(rVar[9] - rVar[5])
            
            if rVar[7] < rVar[8] then
                set rVar[11] = rVar[6]
                
            elseif rVar[8] < rVar[10] then
                set rVar[11] = rVar[3]
                
            else
                set rVar[11] = rVar[9]
            endif
            
            if RAbsBJ(rVar[11] - rVar[5]) <= current.facingSpeed*thistype.INTERVAL then
                set rVar[5] = rVar[11]
            else
                if rVar[11] > rVar[5] then
                    set rVar[5] = rVar[5] + current.facingSpeed*thistype.INTERVAL
                else
                    set rVar[5] = rVar[5] - current.facingSpeed*thistype.INTERVAL
                endif
            endif
        endif
        set rVar[11] = current.fx.getYaw()
    endmethod
    
    private static method onMissileUpdate takes nothing returns nothing
        set rVar[0]    = 0.
        set current    = thistype(0).next
        
        loop
            exitwhen current == 0
            if current.moveType == 1 then
                set rVar[1] = current.fx.getX()
                set rVar[2] = current.fx.getY()
                
                set rVar[3] = Atan2(current.targY - rVar[2], current.targX - rVar[1])
                set rVar[4] = (rVar[1] - current.targX)*(rVar[1] - current.targX) + (rVar[2] - current.targY)*(rVar[2] - current.targY)
                call thistype.retrieveFacing()
                call current.fx.setFacing(rVar[5])
                                
                if rVar[4] > current.moveSpeed*current.moveSpeed then
                    call current.fx.setX(rVar[1] + current.moveSpeed*Cos(rVar[5]))
                    call current.fx.setY(rVar[2] + current.moveSpeed*Sin(rVar[5]))
                
                    call thistype.applyZMovement()
                else
                    call current.fx.setX(current.targX)
                    call current.fx.setY(current.targY)                   
                    call thistype.applyZMovement()
                    set current.moveType    = -1
                    call current.onHitCallback()
                    
                    //  Either the instance was destroyed, or it wasn't included in the move list any longer
                    if current.moveType <= 0 then
                        set current.isLaunched  = false
                        set current.next.prev   = current.prev
                        set current.prev.next   = current.next
                        
                        if current.destroyWasInvoked then
                            call current.destroy()
                            set rVar[0] = rVar[0] - 1
                        endif
                    endif
                endif
            elseif current.moveType == 2 then
                set rVar[1] = current.fx.getX()
                set rVar[2] = current.fx.getY()
                
                //  
                if GetHandleId(current.target) != 0 then
                    set current.targX       = GetWidgetX(current.target)
                    set current.targY       = GetWidgetY(current.target)                    
                else
                    set current.moveType    = 1
                    set current.target      = null
                    
                    if current.targetUnit != null and (current.destroyOnRemove or (current.destroyOnDeath and (GetWidgetLife(current.target) <= 0.))) then
                        call current.destroy()
                        set rVar[0] = rVar[0] - 1
                    else
                        set rVar[3] = Atan2(current.targY - rVar[2], current.targX - rVar[1])
                        set rVar[4] = (rVar[1] - current.targX)*(rVar[1] - current.targX) + (rVar[2] - current.targY)*(rVar[2] - current.targY)
                        call thistype.retrieveFacing()
                        call current.fx.setFacing(rVar[5])
                        
                        if rVar[4] > current.moveSpeed*current.moveSpeed then
                            call current.fx.setX(rVar[1] + current.moveSpeed*Cos(rVar[5]))
                            call current.fx.setY(rVar[2] + current.moveSpeed*Sin(rVar[5]))
                        else
                            call current.fx.setX(current.targX)
                            call current.fx.setY(current.targY)                    
                            
                            set current.moveType    = -1
                            call current.onHitCallback()
                            
                            //  Either the instance was destroyed, or it wasn't included in the move list any longer
                            if current.moveType <= 0 then
                                set current.isLaunched  = false
                                set current.next.prev   = current.prev
                                set current.prev.next   = current.next
                                
                                if current.destroyWasInvoked then
                                    call current.destroy()
                                    set rVar[0] = rVar[0] - 1
                                endif
                            endif
                        endif
                    endif
                endif
            endif
            
            set rVar[0]     = rVar[0] + 1
            set current     = current.next
        endloop
        
        if rVar[0] < 0.5 then
            call PauseTimer(thistype.tm)
        endif
    endmethod
    
    private static method init takes nothing returns nothing
        set thistype.tm         = CreateTimer()
        set thistype(0).next    = 0
        set thistype(0).prev    = 0
        set thistype.missileCallback    = function thistype.onMissileUpdate
    endmethod
    
    implement Init
endstruct
endlibrary

For those who read up until the end, congratulations, you are an excellent reader. And for those of you who just want to enjoy the write-up, I wish a Happy New Year to you.
 

Attachments

  • Missile.w3x
    50.8 KB · Views: 56
I would prefer MissileStruct over a Missile Struct XD (get it?)

In the map I am currently making, I use textmacro (or modules, depends which ever works) for generating systems like this (eg. Missiles, Aura, Buff). function handlers are so easy to implement and faster than using triggers.

Hmm..., interesting. I suppose that these are somehow related to //! import scripts or something. Mind if I ask for a sample here?
 
Level 1
Joined
Dec 4, 2011
Messages
2
I've made a similar system for myself, its a bit different though.

Based on extending Structs, so you need vJASS. It uses vectors as basis and is highly customizable, with main point being pathing collision. If you want it i can send you the code. I'd post it as a resource, but i dotn wanna deal with bureaucracy of having to do that :S

Contact me in PM or on discord (Dellirium#7091)
 
Last edited:
Status
Not open for further replies.
Top