library ME initializer Init needs xefx, optional IsUnitInvisibleEx
// =============================================================================================
// ABOUT
// =============================================================================================
/*
* @Creator: Themerion
* @Version: 2
*
*/
// =============================================================================================
// CONFIG
// =============================================================================================
globals
private constant real Z_OFFSET=64
private constant real PERIOD=0.03
// If the target moves further than this IN AN INSTANT, the missile will miss.
private constant real DEFAULT_HOMING_DISTANCE = 200
endglobals
// =============================================================================================
// DOCUMENTATION
// =============================================================================================
// The effect is not created until you send the missile.
/* MEMissile.create( real x, real y, player owner, string fx )
*/
/* m.sendToUnit( target, speed )
* m.sendToPoint( x, y, speed )
*/
// For storing struct data
/* m.setData( integer )
* m.getData( integer )
*/
// Event-callbacks:
/* m.onHit( callbackFunction )
* m.onMiss( callbackFunction )
* m.onCollide( collideCallbackFunction, filterFunction, collisionRadius )
*/
/* interface callbackFunction takes integer data
* interface collideCallbackFunction takes integer data, unit collidedUnit returns boolean // (boolean = stop after collision?)
*
* // uses GetFilterUnit() and global MEData -> data assigned to missile.
* interface filterFunction takes nothing returns boolean // uses GetFilterUnit()
*/
// Tweaks for missing:
/* boolean m.targetInvisible // if a unit goes invisible, undetected, will the missile still hit?
*
* m.setHomingDistance( real ) // if a unit blinks/teleports/(is in instantly moved) HOMING_DISTANCE
*/ // range or more, the missile will miss.
// ===================================================================================================
globals
integer MEData
private MEMissile m
private MEMissile first
private boolean running
private timer loopTimer
private group tempGroup
private boolexpr collideFilter
private boolean collisionStop
endglobals
function interface MECallback takes integer data returns nothing
function interface MECollideCallback takes integer data, unit hit returns boolean
function interface MECollideEval takes nothing returns boolean
private function CollideCheck takes nothing returns boolean
if m.collideEval.evaluate() and IsUnitInGroup(GetFilterUnit(),m.collideGroup)==false then
call GroupAddUnit(m.collideGroup,GetFilterUnit())
set collisionStop = collisionStop or m.collideCB.evaluate(m.data,GetFilterUnit())
endif
return false
endfunction
private function Loop takes nothing returns nothing
local MEMissile prev=0
local real dx
local real dy
local real dz
local real angle
local real distance
local integer ticksLeft
local real newX
local real newY
local real movedX
local real movedY
local boolean seekUnit
local boolean isInvisible
set m=first
loop
exitwhen m==0
// --------------------------------------------
// UNIT TARGET
set seekUnit = (m.target!=null and m.missed==false)
if seekUnit then
static if (LIBRARY_IsUnitInvisibleEx) then
set isInvisible = IsUnitInvisibleEx(m.target)
else
set isInvisible = IsUnitInvisible(m.target,Player(PLAYER_NEUTRAL_PASSIVE))
endif
if GetUnitTypeId(m.target)==0 or GetWidgetLife(m.target)<.405 or (m.targetInvisible==false and isInvisible and IsUnitVisible(m.target,m.owner)==false) then
// Unit is no longer appropriate target.
// Will target unit's last current position.
set m.missed=true
set seekUnit=false
else
// How far has the target moved since last update?
set movedX = m.target_x - GetUnitX(m.target)
set movedY = m.target_y - GetUnitY(m.target)
if movedX*movedX + movedY*movedY > m.homingDistanceSquared then
// If the target has blinked / run away
set m.missed=true
set seekUnit=false
else
// Unit is still appropriate target.
set dx=GetUnitX(m.target)-m.x
set dy=GetUnitY(m.target)-m.y
// Store unit's current position.
set m.target_x=GetUnitX(m.target)
set m.target_y=GetUnitY(m.target)
endif
endif
endif
// POINT TARGET
// Unit target can become point target, so need separate if's (else is not an option here).
if not seekUnit then
set dx=m.target_x-m.x
set dy=m.target_y-m.y
endif
set distance = SquareRoot(dx*dx+dy*dy)
set angle=Atan2(dy,dx)
set newX=m.x+Cos(angle)*m.speed*PERIOD
set newY=m.y+Sin(angle)*m.speed*PERIOD
// xefx...
set m.xyangle=angle
set m.x=newX
set m.y=newY
if seekUnit then
// Adjust for fly height
set ticksLeft = R2I(distance/(m.speed*PERIOD))+1 // floor
set dz = GetUnitFlyHeight(m.target) - m.z
set m.z = m.z + dz/I2R(ticksLeft)
endif
// --- <Collision> ---
set collisionStop = false
set MEData = m.data
if m.collideCB!=0 then
call GroupEnumUnitsInRange(tempGroup, newX, newY, m.collideRadius, collideFilter)
endif
// --- </Collision> ---
if distance<50 or collisionStop then
// Target reached!
// --- <Do callbacks> ---
if not collisionStop then
if m.missed then
if m.missCB!=0 then
call m.missCB.execute(m.data)
endif
else
if m.endCB!=0 then
call m.endCB.execute(m.data)
endif
endif
endif
// --- </Do callbacks> ---
// --- <List stuff> ---
// Remove missile from list
if m==first then
set first=m.next
call m.destroy()
set m=first
else
set prev.next=m.next
call m.destroy()
set m=prev.next
endif
if first==0 then
set running=false
call PauseTimer(loopTimer)
endif
// --- </list stuff> ---
else
// Target not reached. Continue loop.
set prev=m
set m=m.next
endif
// --------------------------------------------
endloop
endfunction
struct MEMissile
delegate xefx fx
real speed
unit target
real target_x
real target_y
boolean missed
string fxprel
real homingDistanceSquared
boolean targetInvisible
integer data
MEMissile next
MECallback endCB
MECallback missCB
group collideGroup
real collideRadius
MECollideEval collideEval
MECollideCallback collideCB
// Does not create the xefx (since we want to be able to automatically calculate the angle).
static method create takes real x, real y, player owner, string fxprel returns MEMissile
local MEMissile mem = MEMissile.allocate()
// Set default values
set mem.endCB = 0
set mem.missCB = 0
set mem.collideCB = 0
set mem.homingDistanceSquared = (DEFAULT_HOMING_DISTANCE)*(DEFAULT_HOMING_DISTANCE)
set mem.targetInvisible = false
if owner!=null then
set mem.owner = owner
else
set mem.owner = Player(PLAYER_NEUTRAL_PASSIVE)
endif
// Temporarily use target_x/y to store the location where the missile will be created.
set mem.target_x=x
set mem.target_y=y
// Temporarily store the wanted effect-string.
set mem.fxprel=fxprel
return mem
endmethod
method setData takes integer data returns nothing
set .data=data
endmethod
method getData takes nothing returns integer
return .data
endmethod
method onEnd takes MECallback cb returns nothing
set .endCB = cb
endmethod
method onMiss takes MECallback cb returns nothing
set .missCB = cb
endmethod
method onCollide takes MECollideCallback cb, MECollideEval eval, real radius returns nothing
set .collideEval = eval
set .collideRadius = radius
if .collideGroup != null then
call GroupClear(.collideGroup)
else
set .collideGroup = CreateGroup()
endif
set .collideCB = cb
endmethod
method setHomingDistance takes real distance returns nothing
set .homingDistanceSquared = distance*distance
endmethod
private method send takes nothing returns nothing
set .fx.z=Z_OFFSET
set .fx.fxpath=.fxprel
set .fxprel=null
if running==false then
set running=true
call TimerStart(loopTimer,PERIOD,true,function Loop)
set .next=0
else
set .next=first
endif
set first=this
endmethod
method sendToUnit takes unit target, real speed returns nothing
set .fx=xefx.create(.target_x,.target_y,Atan2(GetUnitY(target)-.target_y,GetUnitX(target)-.target_x))
set .target_x=GetUnitX(target)
set .target_y=GetUnitY(target)
set .target=target
set .speed=speed
call .send()
endmethod
method sendToPoint takes real x, real y, real speed returns nothing
set .fx=xefx.create(.target_x,.target_y,Atan2(x-.target_y,y-.target_x))
set .target_x=x
set .target_y=y
set .target=null
set .speed=speed
call .send()
endmethod
method onDestroy takes nothing returns nothing
set .target=null
call .fx.destroy()
endmethod
endstruct
private function Init takes nothing returns nothing
set first=0
set running=false
set loopTimer=CreateTimer()
set tempGroup = CreateGroup()
set collideFilter = Condition(function CollideCheck)
endfunction
endlibrary