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