Missile Engine

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
Missile Engine v1.0.0
Created by: BloodForBlood

Requirements:
1. vJASS compiler
2. Table version 4.0 or above(NewTable)
3. TimerUtils 2.0(Chromatic TimerUtils)
4. Vexorian's dummy.mdx or any dummy model with "origin" attachment

What is Missile Engine:
It generates custom missiles and this has
a modified "
BoundSentinel" inside this
library for safety.

How to Implement:
1. Import or copy the dummy model("war3mapImported/dummy.mdx") to your map and
use the "
ObjectMerger" macro script above "globals" to create the dummy unit.
You can also copy the unit(
Base Model) manually from the Object Editor to
your map.
2. Implement or copy "
Table" and "TimerUtils" to your map.
3. Copy this library/trigger to your map.

Library Code:
vJASS:
library MissileEngine /* v1.0.0
****************************************************************************************************
*                                      _____________________
*                                      *   MissileEngine   *
*                                      * By: BloodForBlood *
*                                      ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*   */requires/*
*
*       */ Table /*
*           - https://www.hiveworkshop.com/threads/snippet-new-table.188084/
*       */ TimerUtils /*
*           - http://www.wc3c.net/showthread.php?t=101322
*
*   What is Missile Engine:
*       It generates custom missiles and this has
*       a modified "BoundSentinel" inside this
*       library for safety.
*
*   Requirements:x
*       1. vJASS compiler
*       2. Table version 4.0 or above(NewTable)
*       3. TimerUtils 2.0(Chromatic TimerUtils)
*       4. Vexorian's dummy.mdx or any dummy model with "origin" attachment
*
*   Features:
*       1. SingleTarget or MultiTarget
*           - SingleTarget means after hitting a unit, the missile will be destroyed
*             MultiTarget means after hitting a unit, the missile will not be destroyed
*       2. Custom Missile Model
*           - Custom missile model on create, like:
*             "Abilities\\Weapons\\BallistaMissile\\BallistaMissile.mdl"
*       3. Configurable Missile
*           - You can configure the missile's owner, AoE or collision, speed, max distance
*             angle, starting X and Y point
*       4. Safe
*           - Any missile that leaves the map's boundary will be destroyed
*             to avoid game crash
*
*   How to Implement:
*       1. Import or copy the dummy model("war3mapImported/dummy.mdx") to your map and
*          use the "ObjectMerger" macro script above "globals" to create the dummy unit.
*          You can also copy the unit(Base Model) manually from the Object Editor to
*          your map.
*       2. Implement or copy "Table" and "TimerUtils" to your map.
*       3. Copy this library/trigger to your map.
*
*   API:
*       struct Missile
*           static method getData takes nothing returns Missile
*               - Returns the Missile(struct data) on registered
*                 codes(functions) by using onHit, onPeriodic
*                 or onDest
*
*           method getUnit takes nothing returns unit
*               - Returns the Missile's unit
*
*           method getLastHittedUnit takes nothing returns unit
*               - Returns the last hitted unit by the missile
*
*           method getMissileOwner takes nothing returns unit
*               - Returns the unit where the missile came from
*
*           method getSpeed takes nothing returns real
*               - Returns the missile's speed
*
*           method getDistTraveled takes nothing returns real
*               - Returns the distance traveled of the missile
*
*           method setSpeed takes real r returns nothing
*               - Sets the missile's speed
*
*           method setAoE takes real r returns nothing
*               - Sets the missile's collision or AoE
*
*           method setAngle takes real r returns nothing
*               - Sets the missile's angle
*
*           method onHit takes code c returns nothing
*               - Registers the onHit event to the function
*
*           method onPeriodic takes code c returns nothing
*               - Registers the onPeriodic event to the function
*
*           method onDest takes code c returns nothing
*               - Registers the onDestory event to the function
*
*           static method create takes unit owner, string mdl, real AoE, real spd, real dist, real angle,
*                                      real x, real y, real height, boolean single, boolean enemy,
*                                      boolean alive, boolean bldg returns Missile
*               - Creates a missile, see Demo for help
*
*           method destroy takes nothing returns nothing
*               - Destroys the missile properly
*
*   Credits:
*       Bribe - for NewTable
*       Vexorian - for TimerUtils, BoundSentinel and dummy.mdx
*
***************************************************************************************************/
// external ObjectMerger w3u hpea uMis ufoo 0 uabi Aloc uico "ReplaceableTextures\WorldEditUI\DoodadPlaceholder.blp" umdl "war3mapImported\dummy.mdl" ushu " " unam "Base Model" udtm "1" umvt "fly" ucol 0 uhom 1 usid 0 usin 0 utyp " " umvr 0.10 uaen 0
    globals
        private constant integer BASE_MODEL_ID = 'uMis'
        private constant real PERIODIC = 0.03125
        private constant real EXTRA = -100 // The extra offset of BoundSentinel for missiles
  
        private HashTable data
        private real maxx
        private real maxy
        private real minx
        private real miny
    endglobals
 
    struct Missile
        private unit missile
        private real angle
        private real collision
        private real destRange
        private real distTraveled
        private real speed
        private boolean single
        private timer time
        private unit owner
        private boolean enemyOnly
        private effect sfx
        private HashTable ht
        private trigger array eventTrig[3]
        private unit hitted
        private boolean alive
        private boolean bldg
  
        static method getData takes nothing returns Missile
            return data[GetHandleId(GetTriggeringTrigger())][StringHash("ME_STRUCTDATA")]
        endmethod
  
        method getUnit takes nothing returns unit
            return .missile
        endmethod
  
        method getLastHittedUnit takes nothing returns unit
            return .hitted
        endmethod
  
        method getMissileOwner takes nothing returns unit
            return .owner
        endmethod
  
        method getSpeed takes nothing returns real
            return .speed
        endmethod
  
        method getDistTraveled takes nothing returns real
            return .distTraveled
        endmethod
  
        method setSpeed takes real r returns nothing
            set .speed = r
        endmethod
  
        method setAoE takes real r returns nothing
            set .collision = r
        endmethod
  
        method setAngle takes real r returns nothing
            set .angle = r
            call SetUnitFacing(.missile, r)
        endmethod
  
        method onHit takes code c returns nothing
            call TriggerAddCondition(.eventTrig[0], Filter(c))
        endmethod
  
        method onPeriodic takes code c returns nothing
            call TriggerAddCondition(.eventTrig[1], Filter(c))
        endmethod
  
        method onDest takes code c returns nothing
            call TriggerAddCondition(.eventTrig[2], Filter(c))
        endmethod
  
        private static method filter takes unit u, unit FoG returns boolean
            local Missile dt = data[0][GetHandleId(u)]
            if (u == null or FoG == null) then
                return false
            endif
            if (FoG == dt.owner) then
                return false
            endif
            if (FoG == u) then
                return false
            endif
            if (dt.ht[GetHandleId(FoG)].boolean[StringHash("ME_HITTED")]) then
                return false
            endif
            if (dt.enemyOnly) then
                if (IsUnitAlly(FoG, GetOwningPlayer(u))) then
                    return false
                endif
            endif
            if (dt.alive) then
                if (GetWidgetLife(FoG) <= 0.405) then
                    return false
                endif
            endif
            if (not dt.bldg) then
                if (IsUnitType(FoG, UNIT_TYPE_STRUCTURE)) then
                    return false
                endif
            endif
            return true
        endmethod
  
        private static method periodic takes nothing returns nothing
            local timer t = GetExpiredTimer()
            local Missile dt = GetTimerData(t)
            local real speed = dt.speed * PERIODIC
            local real x = GetUnitX(dt.missile) + speed * Cos(dt.angle * bj_DEGTORAD)
            local real y = GetUnitY(dt.missile) + speed * Sin(dt.angle * bj_DEGTORAD)
            local group g = CreateGroup()
            local unit u
            call TriggerEvaluate(dt.eventTrig[1])
            if (GetWidgetLife(dt.missile) > 0.405 or dt.missile != null) then
                set dt.distTraveled = dt.distTraveled + speed
                call SetUnitX(dt.missile, x)
                call SetUnitY(dt.missile, y)
                if (dt.distTraveled >= dt.destRange) then
                    call dt.destroy()
                endif
                call GroupEnumUnitsInRange(g, x, y, dt.collision, null)
                loop
                    set u = FirstOfGroup(g)
                    exitwhen u == null
                    call GroupRemoveUnit(g, u)
                    if (thistype.filter(dt.missile, u)) then
                        set dt.ht[GetHandleId(u)].boolean[StringHash("ME_HITTED")] = true
                        set dt.hitted = u
                        call TriggerEvaluate(dt.eventTrig[0])
                        if (dt.single) then
                            call dt.destroy()
                        endif
                    endif
                endloop
            else
                call dt.destroy()
            endif
            call DestroyGroup(g)
            set t = null
            set g = null
            set u = null
        endmethod
  
        static method create takes unit owner, string mdl, real AoE, real spd, real dist, real angle,/*
                                  */ real x, real y, real height, boolean single, boolean enemy,/*
                                  */ boolean alive, boolean bldg returns Missile
            local Missile this = .allocate()
            set .missile = CreateUnit(GetOwningPlayer(owner), BASE_MODEL_ID, x, y, angle)
            set .owner = owner
            set .collision = AoE
            set .speed = spd
            set .destRange = dist
            set .angle = angle
            set .single = single
            set .enemyOnly = enemy
            set .alive = alive
            set .bldg = bldg
            set .time = NewTimerEx(this)
            set data[0][GetHandleId(.missile)] = this
            set .sfx = AddSpecialEffectTarget(mdl, .missile, "origin")
            set .ht = HashTable.create()
            set .ht[GetHandleId(.missile)].boolean[StringHash("ME_HITTED")] = true
            set .ht[GetHandleId(.owner)].boolean[StringHash("ME_HITTED")] = true
            set .eventTrig[0] = CreateTrigger()
            set data[GetHandleId(.eventTrig[0])][StringHash("ME_STRUCTDATA")] = this
            set .eventTrig[1] = CreateTrigger()
            set data[GetHandleId(.eventTrig[1])][StringHash("ME_STRUCTDATA")] = this
            set .eventTrig[2] = CreateTrigger()
            set data[GetHandleId(.eventTrig[2])][StringHash("ME_STRUCTDATA")] = this
            set data[GetHandleId(.missile)][StringHash("ME_STRUCTDATA")] = this
            call SetUnitFlyHeight(.missile, height, 9999)
            call TimerStart(.time, PERIODIC, true, function thistype.periodic)
            return this
        endmethod
  
        method destroy takes nothing returns nothing
            if (.eventTrig[2] != null) then
                call TriggerEvaluate(.eventTrig[2])
            endif
            if (.time != null) then
                call ReleaseTimer(.time)
            endif
            if (.sfx != null) then
                call DestroyEffect(.sfx)
            endif
            if (.missile != null) then
                call KillUnit(.missile)
            endif
            if (.ht != null) then
                call .ht.destroy()
            endif
            if (.eventTrig[0] != null) then
                set data[GetHandleId(.eventTrig[0])][StringHash("ME_STRUCTDATA")] = 0
                call DestroyTrigger(.eventTrig[0])
            endif
            if (.eventTrig[1] != null) then
                set data[GetHandleId(.eventTrig[1])][StringHash("ME_STRUCTDATA")] = 0
                call DestroyTrigger(.eventTrig[1])
            endif
            if (.eventTrig[2] != null) then
                set data[GetHandleId(.eventTrig[2])][StringHash("ME_STRUCTDATA")] = 0
                call DestroyTrigger(.eventTrig[2])
            endif
            set .missile = null
            set .collision = 0
            set .speed = 0
            set .destRange = 0
            set .distTraveled = 0
            set .angle = 0
            set .single = false
            set .enemyOnly = false
            set .time = null
            set .sfx = null
            set .ht = 0
            set .eventTrig[0] = null
            set .eventTrig[1] = null
            set .eventTrig[2] = null
            set .hitted = null
            call .deallocate()
        endmethod
    endstruct
 
    // Special thanks to Vexorian
    private function dis takes nothing returns nothing
        local unit u=GetTriggerUnit()
        local real x=GetUnitX(u)
        local real y=GetUnitY(u)
        local Missile dt

        if(x>maxx) then
            set x=maxx
            elseif(x<minx) then
            set x=minx
        endif
        if(y>maxy) then
            set y=maxy
        elseif(y<miny) then
            set y=miny
        endif
        if (GetUnitTypeId(u) == BASE_MODEL_ID) then
            set dt = data[GetHandleId(u)][StringHash("ME_STRUCTDATA")]
            call dt.destroy()
        else
            call SetUnitX(u,x)
            call SetUnitY(u,y)
        endif
        set u=null
   endfunction
 
    private module m
        private static method onInit takes nothing returns nothing
            local trigger t=CreateTrigger()
            local region  r=CreateRegion()
            local rect    rc=GetWorldBounds()
      
            set data = HashTable.create()
  
            set minx=GetRectMinX(rc) - -100
            set miny=GetRectMinY(rc) - -100
            set maxx=GetRectMaxX(rc) + -100
            set maxy=GetRectMaxY(rc) + -100
            set rc=Rect(minx,miny,maxx,maxy)
            call RegionAddRect(r, rc)
            call RemoveRect(rc)
  
            call TriggerRegisterLeaveRegion(t,r, null)
            call TriggerAddAction(t, function dis)
  
            //this is not necessary but I'll do it anyway:
            set t=null
            set r=null
            set rc=null
        endmethod
    endmodule
 
    private struct s
        implement m
    endstruct
 
endlibrary

Demo:
vJASS:
scope Demo initializer onInit
 
    globals
        constant string MODEL = "Abilities\\Weapons\\BallistaMissile\\BallistaMissile.mdl"
  
        private unit tester
    endglobals
 
    private function unitHitted takes nothing returns nothing
        local Missile arrow = Missile.getData()
        local unit u = arrow.getLastHittedUnit()
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 1, GetUnitName(u) + " is hitted by a missile")
        set u = null
        // On unit hit functions
    endfunction
 
    private function periodic takes nothing returns nothing
        // Periodic functions
    endfunction
 
    private function death takes nothing returns nothing
        // On missile destroy functions
    endfunction
 
    private function onCast takes nothing returns nothing
        local Missile arrow
        local real AoE = 100
        local real speed = 522
        local real maxRange = 1200
        local real angle = GetUnitFacing(tester)
        local real x = GetUnitX(tester)
        local real y = GetUnitY(tester)
        local real height = 50
        local boolean singleTarget = false
        local boolean enemyOnly = true
        local boolean aliveOnly = true
        local boolean includeBuilding = true
        set arrow = Missile.create(tester, MODEL, AoE, speed, maxRange, angle, x, y, height, singleTarget, enemyOnly, aliveOnly, includeBuilding)
        call arrow.onHit(function unitHitted)
        call arrow.onPeriodic(function periodic)
        call arrow.onDest(function death)
    endfunction
 
    private function onInit takes nothing returns nothing
        local trigger t = CreateTrigger()
        set tester = CreateUnit(Player(0), 'Hvwd', GetStartLocationX(0), GetStartLocationY(0), bj_UNIT_FACING)
        call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
        call TriggerAddAction(t, function onCast)
        set t = null
        call Preload(MODEL)
        call BJDebugMsg("Press ESC to generate harmless missiles")
    endfunction
 
endscope

Changelog:
Code:
v1.0.0:
    First release

Credits:
Bribe - for NewTable
Vexorian - for TimerUtils, BoundSentinel and dummy.mdx
Previews
Contents

Missile Engine (Map)

Reviews
Wareditor
New 1.29+ natives make this outdated. You don't have to use units anymore and thus Vexorian's model isn't useful (as you can pitch special effects). The high cost of creating and removing units justify easily the use of the new natives.
Bannar
I believe we do not approve uncompatible resources. You can leave such subjects in The Lab. You cannot expect users to use outdated software. Most users moved to new patch already. Even Chinese ladder did. Ask a mod to move the thread or close it.
Level 14
Joined
Jan 16, 2009
Messages
716
New 1.29+ natives make this outdated.
You don't have to use units anymore and thus Vexorian's model isn't useful (as you can pitch special effects).
The high cost of creating and removing units justify easily the use of the new natives.
 
Level 5
Joined
Mar 15, 2017
Messages
96
New 1.29+ natives make this outdated.
You don't have to use units anymore and thus Vexorian's model isn't useful (as you can pitch special effects).
The high cost of creating and removing units justify easily the use of the new natives.
This is just for old warcraft 3 versions, I don't have 1.29..

Edit:
I think I won't be updating my Warcraft 3 because of Memory Hack.
 
Top