- Reaction score
- 91
Basically, this snippet was inspired by (if I'm ot mistaking) ReVolver's WaitMissileSpeedDamage snippet. This is supposed to be an improved version simply because his one was inaccurate when calculating distance between the caster and target - it could damage units a bit late if they are moving. Though this requires TimerTicker system by Cohadar, it correctly damages units even if they're moving due to a simple check and recalculating distance again.
TT v4.0
If someone prefers another system, I've written for TimerUtils, ABC, HSAS and CSData.
Changes:
- Included a test map.
- Fixed inaccurate calculations because d.y received the value y1 instead of y2.
- Now uses TT_Once() instead of TT_StartEx().
- Added a global for better configuration.
JASS:
//===========================================================================
// Missile Damage snippet by tyrande_ma3x
//===========================================================================
// This snippet provides you only one function: MD_Start().
// However, it requires the TimerTicker system by Cohadar (v4.0).
//
// The function takes six (6) arguments followed in this order:
// unit a - The unit that casts the spell (will also be the damager)
// unit b - The target that is going to be damaged after the missile reaches
// real c - The damage dealt after the missile reaches its target
// real e - The missile's speed
// attacktype f - The attack type of the damaging (i.e. ATTACK_TYPE_MAGIC)
// damagetype g - The damage type of the damaging (i.e. DAMAGE_TYPE_UNIVERSAL)
//===========================================================================
// In this order the function would look something like this:
// call MD_Start(caster, target, 100., 800., ATTACK_TYPE_MAGIC, DAMAGE_TYPE_UNIVERSAL)
//===========================================================================
// What the snippet does is that it damages the target when the missile
// reaches its target, unlike WaitMissileSpeedDamage (a function by ReVolver
// if I'm not mistaking) which does damage earlier/later because of a sigle
// distance check.
//===========================================================================
// Credits to ReVolver for the original idea.
//===========================================================================
library MD requires TT
globals
// Minimum damage required for the unit to be damaged.
// If it's less than 100 then the unit will be hurt,
// else the damaging will be delayed until the condition is met.
private constant real LEAST_RANGE = 100.
endglobals
private struct Data
unit cast
unit targ
real dmg
real x
real y
real s
attacktype at
damagetype dt
static method create takes unit a, unit b, real c, real e, attacktype f, damagetype g returns Data
local Data d = Data.allocate()
set d.cast = a
set d.targ = b
set d.dmg = c
set d.s = e
set d.at = f
set d.dt = g
return d
endmethod
endstruct
private function Callback takes nothing returns boolean
local Data d = TT_GetData()
local real x1 = d.x
local real y1 = d.y
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
if r < LEAST_RANGE then
if IsUnitVisible(d.targ, GetOwningPlayer(d.cast)) then
if not IsUnitInvisible(d.targ, GetOwningPlayer(d.cast)) then
if GetWidgetLife(d.targ) > 0.405 then
call UnitDamageTarget(d.cast, d.targ, d.dmg, true, true, d.at, d.dt, WEAPON_TYPE_WHOKNOWS)
endif
endif
endif
call d.destroy()
else
set d.x = x2
set d.y = y2
call TT_Once(function Callback, d, tr)
endif
return true
endfunction
public function Start takes unit a, unit b, real c, real e, attacktype f, damagetype g returns nothing
local Data d = Data.create(a, b, c, e, f, g)
local real x1 = GetUnitX(d.cast)
local real y1 = GetUnitY(d.cast)
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
set d.x = x2
set d.y = y2
call TT_Once(function Callback, d, tr)
endfunction
endlibrary
TT v4.0
JASS:
//==============================================================================
// TT -- TIMER TICKER SYSTEM BY COHADAR -- v4.0
//==============================================================================
//
// PURPOUSE OF TT:
// * Passing data to timers
// * Avoiding direct use of timer handles
//
// PROS:
// * It is easier than using attaching
// * It is optimized to use only one timer on default high frequency
// * GetData method is the same for all timer frequencies
//
// CONS:
// * You must remember to always return true from your function
// when you want to stop timer, even if it is of one-shot type.
// (otherwise it will leak)
//
// START FUNCTIONS:
// * TT_Start(userFunc, struct)
// * TT_StartEx(userFunc, struct, period)
// * TT_Once(userFunc, struct, timeout)
// * TT_StartTimerDialog(userFunc, struct, timeout) -> timerdialog
//
// * userFunc is a user function that takes nothing and returns boolean
// it will be periodically called by the system until it returns true.
//
// GET FUNCTIONS:
// * TT_GetData() -> struct
// * TT_GetTimerDialog() -> timerdialog
//
// * These functions can only be called from inside userFunc
// TT_GetData() will return struct passed to any of the start functions
// TT_GetTimerDialog() returns timerdialog created by TT_StartTimerDialog
//
// DETAILS:
// * On default frequency all user functions are stored in an array.
// Timer will call all those functions each period.
//
// * While user function returns false timer will continue to call it each period
// Once user function returns true it will be removed from system
//
// * TT is using smart timer preloading and simple hash
// When colliding timer handle is found that timer is simply discarded
//
// REQUIREMENTS:
// * NewGen v4c and above (there might be some problems with older NewGen's)
//
// HOW TO IMPORT:
// * Just create a trigger named TT
// * convert it to text and replace the whole trigger text with this one
//
//==============================================================================
library TT initializer Init
//==============================================================================
// Configuration
//==============================================================================
globals
// List of recommended periods for high-frequency timer:
// 0.04 = 25 calls per second
// 0.03125 = 32 calls per second
// 0.025 = 40 calls per second
// 0.02 = 50 calls per second
public constant real PERIOD = 0.03125
// how many low-frequency timers to preload
// system can safely extend beyond this limit
private constant integer PRELOAD = 32
endglobals
//==============================================================================
// End of Configuration
//==============================================================================
//==============================================================================
globals
// "frames per second" of high-frequency timer
public constant integer FPS = R2I(1.0/PERIOD)
// globals for passing data to userFunc
private integer Data
private timerdialog timerDialog
// One Timer to rule them all, One Timer to find them,
// One Timer to call them all and in the jass bind them
// In the land of warcraft where the desyncs lie.
private timer HF_Timer = CreateTimer()
private integer HF_Counter = 0
private trigger array HF_Triggz
private integer array HF_Dataz
// we can safely use dummy hashing here because timers are preloaded
private constant integer LF_HASH = 8191
private integer array LF_Dataz
private trigger array LF_Triggz
private timer array LF_Timerz
private timerdialog array LF_Dialogz
// recycling
private integer array LF_Indexz
private integer LF_Counter = PRELOAD
endglobals
//==============================================================================
private constant function H2I takes handle h returns integer
return h
return 0
endfunction
//==============================================================================
// note how colliding timer handles are discarded
// so TT would work properly even after preload limit break
//==============================================================================
private function NewIndex takes nothing returns integer
local integer i
local timer t
if (LF_Counter==0) then
loop
debug call BJDebugMsg("WARNING: TT reached preloaded timer limit!")
set t = CreateTimer()
set i = H2I(t)
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
if LF_Timerz<i> == null then
set LF_Timerz<i> = t
set LF_Triggz<i> = CreateTrigger()
return i
endif
endloop
endif
set LF_Counter = LF_Counter - 1
return LF_Indexz[LF_Counter]
endfunction
//==============================================================================
private function HF_Handler takes nothing returns nothing
local trigger swap
local integer i = HF_Counter
loop
exitwhen i<=0
set Data = HF_Dataz<i>
if TriggerEvaluate(HF_Triggz<i>) then
set swap = HF_Triggz<i>
call TriggerClearConditions(swap)
set HF_Triggz<i> = HF_Triggz[HF_Counter]
set HF_Triggz[HF_Counter] = swap
set HF_Dataz<i> = HF_Dataz[HF_Counter]
set HF_Counter = HF_Counter - 1
endif
set i = i - 1
endloop
// who can guess why am I not nulling swap here?
endfunction
//==============================================================================
private function LF_Handler takes nothing returns nothing
local integer i = H2I(GetExpiredTimer())
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
set Data = LF_Dataz<i>
if TriggerEvaluate(LF_Triggz<i>) then
// recycle the trigger and timer
call TriggerClearConditions(LF_Triggz<i>)
call PauseTimer(LF_Timerz<i>)
set LF_Indexz[LF_Counter] = i
set LF_Counter = LF_Counter + 1
endif
endfunction
//==============================================================================
// Periodic timer that runs on TT_PERIOD
//==============================================================================
public function Start takes code userFunc, integer data returns nothing
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_Start - null userFunc")
debug return
debug endif
set HF_Counter = HF_Counter + 1
if HF_Triggz[HF_Counter] == null then
set HF_Triggz[HF_Counter] = CreateTrigger()
endif
set HF_Dataz[HF_Counter] = data
call TriggerAddCondition(HF_Triggz[HF_Counter], Condition(userFunc))
endfunction
//==============================================================================
// Periodic timer with custom period
//==============================================================================
public function StartEx takes code userFunc, integer data, real period returns nothing
local integer i
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_StartEx - null userFunc")
debug return
debug endif
set i = NewIndex()
call TriggerAddCondition(LF_Triggz<i>, Condition(userFunc))
set LF_Dataz<i> = data
call TimerStart(LF_Timerz<i>, period, true, function LF_Handler)
endfunction
//==============================================================================
// One shot timer, remember to return true in userFunc
//==============================================================================
public function Once takes code userFunc, integer data, real timeout returns nothing
local integer i
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_Once - null userFunc")
debug return
debug endif
set i = NewIndex()
call TriggerAddCondition(LF_Triggz<i>, Condition(userFunc))
set LF_Dataz<i> = data
call TimerStart(LF_Timerz<i>, timeout, false, function LF_Handler)
endfunction
//==============================================================================
public function StartTimerDialog takes code userFunc, integer data, real timeout returns timerdialog
local integer i
debug if userFunc == null then
debug call BJDebugMsg("ERROR: TT_StartTimerDialog - null userFunc")
debug return null
debug endif
set i = NewIndex()
call TriggerAddCondition(LF_Triggz<i>, Condition(userFunc))
set LF_Dataz<i> = data
call TimerStart(LF_Timerz<i>, timeout, false, function LF_Handler)
set bj_lastCreatedTimerDialog = CreateTimerDialog(LF_Timerz<i>)
set LF_Dialogz<i> = bj_lastCreatedTimerDialog
return bj_lastCreatedTimerDialog
endfunction
//==============================================================================
// Call this function only inside the userFunc
//==============================================================================
public function GetData takes nothing returns integer
return Data
endfunction
//==============================================================================
// Call this function only inside the userFunc
//==============================================================================
public function GetTimerDialog takes nothing returns timerdialog
local integer i = H2I(GetExpiredTimer())
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
return LF_Dialogz<i>
endfunction
//==============================================================================
// Preload LF timers and start HF timer.
//==============================================================================
private function Init takes nothing returns nothing
local integer i
local timer t
local integer j = 0
loop
exitwhen j>=PRELOAD
set t = CreateTimer()
set i = H2I(t)
set i = i - (i / LF_HASH) * LF_HASH // dummy modulo hash
if LF_Timerz<i> == null then
set LF_Timerz<i> = t
set LF_Triggz<i> = CreateTrigger()
set LF_Indexz[j] = i
set j = j + 1
endif
endloop
call TimerStart(HF_Timer, PERIOD, true, function HF_Handler)
endfunction
endlibrary
//==============================================================================
// END OF TIMER TICKER SYSTEM
//==============================================================================
</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>
If someone prefers another system, I've written for TimerUtils, ABC, HSAS and CSData.
TimerUtils:
ABC:
HSAS:
CSData:
JASS:
library MD requires TimerUtils
globals
// Minimum damage required for the unit to be damaged.
// If it's less than 100 then the unit will be hurt,
// else the damaging will be delayed until the condition is met.
private constant real LEAST_RANGE = 100.
endglobals
private struct Data
unit cast
unit targ
real dmg
real x
real y
real s
attacktype at
damagetype dt
static method create takes unit a, unit b, real c, real e, attacktype f, damagetype g returns Data
local Data d = Data.allocate()
set d.cast = a
set d.targ = b
set d.dmg = c
set d.s = e
set d.at = f
set d.dt = g
return d
endmethod
endstruct
private function Callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data d = GetTimerData(t)
local real x1 = d.x
local real y1 = d.y
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
if r < LEAST_RANGE then
if IsUnitVisible(d.targ, GetOwningPlayer(d.cast)) then
if not IsUnitInvisible(d.targ, GetOwningPlayer(d.cast)) then
if GetWidgetLife(d.targ) > 0.405 then
call UnitDamageTarget(d.cast, d.targ, d.dmg, true, true, d.at, d.dt, WEAPON_TYPE_WHOKNOWS)
endif
endif
endif
call ReleaseTimer(t)
call d.destroy()
else
set d.x = x2
set d.y = y2
call ReleaseTimer(t)
set t = NewTimer()
call SetTimerData(t, d)
call TimerStart(t, tr, false, function Callback)
endif
set t = null
endfunction
public function Start takes unit a, unit b, real c, real e, attacktype f, damagetype g returns nothing
local Data d = Data.create(a, b, c, e, f, g)
local real x1 = GetUnitX(d.cast)
local real y1 = GetUnitY(d.cast)
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
local timer t = NewTimer()
set d.x = x2
set d.y = y2
call SetTimerData(t, d)
call TimerStart(t, tr, false, function Callback)
set t = null
endfunction
endlibrary
JASS:
library MD requires ABC
globals
// Minimum damage required for the unit to be damaged.
// If it's less than 100 then the unit will be hurt,
// else the damaging will be delayed until the condition is met.
private constant real LEAST_RANGE = 100.
endglobals
private struct Data
unit cast
unit targ
real dmg
real x
real y
real s
attacktype at
damagetype dt
static method create takes unit a, unit b, real c, real e, attacktype f, damagetype g returns Data
local Data d = Data.allocate()
set d.cast = a
set d.targ = b
set d.dmg = c
set d.s = e
set d.at = f
set d.dt = g
return d
endmethod
endstruct
private function Callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data d = GetTimerStructA(t)
local real x1 = d.x
local real y1 = d.y
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
if r < LEAST_RANGE then
if IsUnitVisible(d.targ, GetOwningPlayer(d.cast)) then
if not IsUnitInvisible(d.targ, GetOwningPlayer(d.cast)) then
if GetWidgetLife(d.targ) > 0.405 then
call UnitDamageTarget(d.cast, d.targ, d.dmg, true, true, d.at, d.dt, WEAPON_TYPE_WHOKNOWS)
endif
endif
endif
call ClearTimerStructA(t)
call PauseTimer(t)
call DestroyTimer(t)
call d.destroy()
else
set d.x = x2
set d.y = y2
call PauseTimer(t)
call DestroyTimer(t)
set t = CreateTimer()
call SetTimerStructA(t, d)
call TimerStart(t, tr, false, function Callback)
endif
set t = null
endfunction
public function Start takes unit a, unit b, real c, real e, attacktype f, damagetype g returns nothing
local Data d = Data.create(a, b, c, e, f, g)
local real x1 = GetUnitX(d.cast)
local real y1 = GetUnitY(d.cast)
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
local timer t = CreateTimer()
set d.x = x2
set d.y = y2
call SetTimerStructA(t, d)
call TimerStart(t, tr, false, function Callback)
set t = null
endfunction
endlibrary
JASS:
library MD requires HSAS
//! runtextmacro HSAS_Static("MDmg", "32760", "private")
globals
// Minimum damage required for the unit to be damaged.
// If it's less than 100 then the unit will be hurt,
// else the damaging will be delayed until the condition is met.
private constant real LEAST_RANGE = 100.
endglobals
private struct Data
unit cast
unit targ
real dmg
real x
real y
real s
attacktype at
damagetype dt
static method create takes unit a, unit b, real c, real e, attacktype f, damagetype g returns Data
local Data d = Data.allocate()
set d.cast = a
set d.targ = b
set d.dmg = c
set d.s = e
set d.at = f
set d.dt = g
return d
endmethod
endstruct
private function Callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data d = GetAttachedStructMDmg(t)
local real x1 = d.x
local real y1 = d.y
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
if r < LEAST_RANGE then
if IsUnitVisible(d.targ, GetOwningPlayer(d.cast)) then
if not IsUnitInvisible(d.targ, GetOwningPlayer(d.cast)) then
if GetWidgetLife(d.targ) > 0.405 then
call UnitDamageTarget(d.cast, d.targ, d.dmg, true, true, d.at, d.dt, WEAPON_TYPE_WHOKNOWS)
endif
endif
endif
call PauseTimer(t)
call DestroyTimer(t)
call d.destroy()
else
set d.x = x2
set d.y = y2
call PauseTimer(t)
call DestroyTimer(t)
set t = CreateTimer()
call AttachStructMDmg(t, d)
call TimerStart(t, tr, false, function Callback)
endif
set t = null
endfunction
public function Start takes unit a, unit b, real c, real e, attacktype f, damagetype g returns nothing
local Data d = Data.create(a, b, c, e, f, g)
local real x1 = GetUnitX(d.cast)
local real y1 = GetUnitY(d.cast)
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
local timer t = CreateTimer()
set d.x = x2
set d.y = y2
call AttachStructMDmg(t, d)
call TimerStart(t, tr, false, function Callback)
set t = null
endfunction
endlibrary
JASS:
library MD requires CSData
globals
// Minimum damage required for the unit to be damaged.
// If it's less than 100 then the unit will be hurt,
// else the damaging will be delayed until the condition is met.
private constant real LEAST_RANGE = 100.
endglobals
private struct Data
unit cast
unit targ
real dmg
real x
real y
real s
attacktype at
damagetype dt
static method create takes unit a, unit b, real c, real e, attacktype f, damagetype g returns Data
local Data d = Data.allocate()
set d.cast = a
set d.targ = b
set d.dmg = c
set d.s = e
set d.at = f
set d.dt = g
return d
endmethod
endstruct
private function Callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data d = GetCSData(t)
local real x1 = d.x
local real y1 = d.y
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
if r < LEAST_RANGE then
if IsUnitVisible(d.targ, GetOwningPlayer(d.cast)) then
if not IsUnitInvisible(d.targ, GetOwningPlayer(d.cast)) then
if GetWidgetLife(d.targ) > 0.405 then
call UnitDamageTarget(d.cast, d.targ, d.dmg, true, true, d.at, d.dt, WEAPON_TYPE_WHOKNOWS)
endif
endif
endif
call PauseTimer(t)
call DestroyTimer(t)
call d.destroy()
else
set d.x = x2
set d.y = y2
call PauseTimer(t)
call DestroyTimer(t)
set t = CreateTimer()
call SetCSData(t, d)
call TimerStart(t, tr, false, function Callback)
endif
set t = null
endfunction
public function Start takes unit a, unit b, real c, real e, attacktype f, damagetype g returns nothing
local Data d = Data.create(a, b, c, e, f, g)
local real x1 = GetUnitX(d.cast)
local real y1 = GetUnitY(d.cast)
local real x2 = GetUnitX(d.targ)
local real y2 = GetUnitY(d.targ)
local real xx = x1 - x2
local real yy = y1 - y2
local real r = SquareRoot(xx * xx + yy * yy)
local real tr = r / d.s
local timer t = CreateTimer()
set d.x = x2
set d.y = y2
call SetCSData(t, d)
call TimerStart(t, tr, false, function Callback)
set t = null
endfunction
endlibrary
Changes:
- Included a test map.
- Fixed inaccurate calculations because d.y received the value y1 instead of y2.
- Now uses TT_Once() instead of TT_StartEx().
- Added a global for better configuration.