Name | Type | is_array | initial_value |
tu | unit | No |
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library TNTI
private interface Ext
static integer Total = 0
static integer Current = 0
static constant real Intervall = .01
static timer TIM = null
real runtime = .01
method onBreak takes nothing returns boolean defaults true
method onLoop takes nothing returns nothing defaults nothing
endinterface
struct Indexable extends Ext
private static Ext array Index
private real timing = .0
/*stub method onBreak takes nothing returns boolean
call BJDebugMsg(SCOPE_PREFIX+": |c00FF0000onBreak() from struct with id: "+I2S(.getType())+" is not defined !")
return true
endmethod
stub method onLoop takes nothing returns nothing
call BJDebugMsg(SCOPE_PREFIX+": |c00FF0000onLoop() from struct with id: "+I2S(.getType())+" is not defined !")
endmethod*/
private method onDestroy takes nothing returns nothing
set Ext.Total = Ext.Total -1
set thistype.Index[Ext.Current] = thistype.Index[Ext.Total]
set Ext.Current = Ext.Current-1
if Ext.Total == 0 then
call PauseTimer(Ext.TIM)
endif
endmethod
private static method Loop takes nothing returns nothing
local Ext this
set Ext.Current = 0
loop
exitwhen Ext.Current == Ext.Total
set this = thistype.Index[Ext.Current]
set .timing = .timing + thistype.Intervall
if .timing >= .runtime then
if .onBreak() then
call .destroy()
else
call .onLoop()
set .timing = 0
endif
endif
set Ext.Current = Ext.Current+1
endloop
endmethod
static method create takes nothing returns thistype
local Ext this = thistype.allocate()
set thistype.Index[Ext.Total] = this
set Ext.Total = Ext.Total +1
if Ext.Total == 1 then
call TimerStart(Ext.TIM,Ext.Intervall,true, function thistype.Loop)
endif
return this
endmethod
private static method onInit takes nothing returns nothing
set Ext.TIM = CreateTimer()
endmethod
endstruct
endlibrary
//TESH.scrollpos=12
//TESH.alwaysfold=0
library TNTK requires TNTI
globals
private constant real OFFSET = 64.
private real MAX
private real MAY
private real MIX
private real MIY
endglobals
struct TNTKnock extends Indexable
unit knocker = null
unit target = null
integer data = 0x0
readonly real velx = .0
readonly real vely = .0
readonly boolean inuse = false
readonly real runtime = 0.02
readonly real x = .0
readonly real y = .0
readonly real distance = 10.
readonly real radiants = .0
readonly real time = .0
readonly real speed = .0
method IsInMap takes nothing returns boolean
return .x <= MAX and .x >= MIX and .y <= MAY and .y >= MIY
endmethod
stub method onKnock takes nothing returns nothing
call SetUnitPosition(.knocker,.x,.y)
//Sorry but for some unknown reason SetUnitX/Y bugs
//call SetUnitX(.knocker,.x)
//call SetUnitY(.knocker,.y)
endmethod
method onBreak takes nothing returns boolean
return not .IsInMap() or .distance <= .speed+1. /*
*/ or ( .target != null and IsUnitType(.target ,UNIT_TYPE_DEAD)) /*
*/ or ( .knocker != null and IsUnitType(.knocker,UNIT_TYPE_DEAD))
endmethod
method onLoop takes nothing returns nothing
if .inuse then
if .target == null then
set .x = .x + .velx
set .y = .y + .vely
set .time = .time - .runtime
set .distance = .distance - .speed
else
set .velx = GetWidgetX(.target) - GetWidgetX(.knocker)
set .vely = GetWidgetY(.target) - GetWidgetY(.knocker)
set .radiants = Atan2(.vely,.velx)
set .distance = SquareRoot(.velx*.velx+.vely*.vely) - .speed
set .x = .x + Cos(.radiants) * .speed
set .y = .y + Sin(.radiants) * .speed
set .time = .time + .runtime
endif
call .onKnock()
endif
endmethod
method onDestroy takes nothing returns nothing
set .knocker = null
set .target = null
set .inuse = false
endmethod
method ChangeSpeed takes real newspeed returns nothing
set .velx = Cos(.radiants) * newspeed
set .vely = Sin(.radiants) * newspeed
set .speed = newspeed
set .time = (.distance*.runtime)/newspeed
endmethod
method ChangeDirection takes real newrad returns nothing
set .velx = Cos(newrad) * .speed
set .vely = Sin(newrad) * .speed
set .radiants = newrad
endmethod
method Stop takes nothing returns nothing
set .inuse = true
set .distance = -1.
endmethod
method StartLinear takes unit u, real sx, real sy, real rad, real dis, real speed returns nothing
if speed > 1 and not .inuse then
set .knocker = u
set .target = null
set .x = sx
set .y = sy
set .distance = dis
set .radiants = rad
set .time = (dis*.runtime) / speed
set .speed = speed
set .velx = Cos(rad)*speed
set .vely = Sin(rad)*speed
set .inuse = true
endif
endmethod
method StartLinearTimed takes unit u, real sx, real sy, real rad, real dis, real time returns nothing
if time > .runtime and not .inuse then
set .knocker = u
set .target = null
set .x = sx
set .y = sy
set .distance = dis
set .radiants = rad
set .time = time
set .speed = (dis*.runtime) / time
set .velx = Cos(rad) * .speed
set .vely = Sin(rad) * .speed
set .inuse = true
endif
endmethod
method StartLinearTimedLoc takes unit u, location from, location to, real time, boolean noleak returns nothing
local real x = GetLocationX(to) - GetLocationX(from)
local real y = GetLocationY(to) - GetLocationY(from)
local real rad = Atan2(y,x)
local real dis = SquareRoot(x*x+y*y)
call .StartLinearTimed(u,GetLocationX(from),GetLocationY(from),rad,dis,time)
if noleak then
call RemoveLocation(from)
call RemoveLocation(to)
endif
endmethod
method StartHomingTimed takes unit u, unit targ, real sx, real sy, real speed returns nothing
if IsUnitType(targ,UNIT_TYPE_DEAD) != true and targ != null and speed > 0. then
set .knocker = u
set .target = targ
set .x = sx
set .y = sy
set .speed = speed
set .time = 0.
set .distance = speed+3.
set .inuse = true
endif
endmethod
private static method onInit takes nothing returns nothing
set MAX = GetRectMaxX(bj_mapInitialPlayableArea) - OFFSET
set MAY = GetRectMaxY(bj_mapInitialPlayableArea) - OFFSET
set MIX = GetRectMinX(bj_mapInitialPlayableArea) + OFFSET
set MIY = GetRectMinY(bj_mapInitialPlayableArea) + OFFSET
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library PolySlash initializer Init requires TNTK, GroupUtils
globals
private constant integer SID = 'pLsL'
//The rawcode of the ability
private constant real TRAVEL_SPEED = 8.43
//The movement speed of the "mirror images"
private constant real MOVE_ANIMATION_TIME = 1.133
//The duration of the animation which a mirror image performes
//while it is moving around
private constant real MOVE_ANIMATION_TIME_MOD = 1.5
//The animation de- or acceleration. Having a value of 1.0 means
//that the animation is played with regualr speed, a value of 2.0
//means the animation is played twice as fast as normal
private constant boolean FORCE_REPICK = true
//When the mirror images are reuniting back into the caster, it
//will be automatically picked for the controlling player
//if set to true
private constant string MOVE_ANIMATION_NAME = "Attack Slam"
//The name of the animation a mirror image performs while moving around
private constant string CASTER_SFX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
//The path of a special effect which is being attached to the mirror images
private constant string TARGET_SFX = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
//The path of a special effect which is being displayed when a target unit gets hit
private constant string CASTER_SFX_ATTACHMENT = "weapon"
//Where should the special effect be attached to the mirror image
private constant string TARGET_SFX_ATTACHMENT = "origin"
//Where should the special effect be attached to the target
//Selfexplaining damage options...
private constant attacktype ATTACK = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WEAPON = null
endglobals
//The amount of damage depending of the ability level being inflicted to a target when hit
private constant function GetDamage takes real level returns real
return 40 * level + 30
endfunction
//The amount of mirror images spawned per level
private constant function GetCopies takes integer level returns integer
return 1 * level + 2
endfunction
//The radius of the Area of Effect
private constant function GetAoERange takes real level returns real
return 100 * level + 150
endfunction
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//Spellcode begins here. PLEASE DO NOT TOUCH ! Thank you !
//_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-_-=-
//Forward struct declaration
private keyword polyslash_data
globals
private filterfunc ENUM_FILTER //Used to acquire valid targets
private player TMP_PLAYER //Used as paramter substitue
private group TMP_GROUP //Used filter invalid units off the selection
private constant real TM_RATIO = MOVE_ANIMATION_TIME / MOVE_ANIMATION_TIME_MOD
//Ratio between speed modifier an animation time
private integer TMP_COUNTER //Used to count the number of targets
endglobals
//Customized knockback for this spell
private struct polyslash_move extends TNTKnock
effect sfx
real anim_time = .0
method operator psd takes nothing returns polyslash_data
return polyslash_data(.data)
endmethod
method operator psd= takes polyslash_data psd returns nothing
set .data = integer(psd)
endmethod
//Responsible for movement and animations
method onKnock takes nothing returns nothing
call SetUnitX( .knocker, .x )
call SetUnitY( .knocker, .y )
call SetUnitFacing( .knocker, .radiants*bj_RADTODEG )
if anim_time <= .0 then
call SetUnitAnimation( .knocker, MOVE_ANIMATION_NAME )
set .anim_time = TM_RATIO
else
set .anim_time = .anim_time - .runtime
endif
endmethod
//Manages the target selection and ends a knockback
method onBreak takes nothing returns boolean
//If true a mirror image reached its target unit
if super.onBreak() then
//If the caster was the target this instance can be destroyed
if .target == .psd.caster then
return true
endif
//Damage the current target and play the effect
call UnitDamageTarget( .psd.caster, .target, .psd.damage, false, false, ATTACK, DAMAGE, WEAPON )
call DestroyEffect( AddSpecialEffectTarget( TARGET_SFX, .target, TARGET_SFX_ATTACHMENT ))
//Leave the old target behind ang acquire a new one if possible
if .psd.target_count > 0 then
call .psd.UpdateGroupState()
set .target = FirstOfGroup(.psd.targets)
call GroupRemoveUnit(.psd.targets, .target )
set .psd.target_count = .psd.target_count -1
else
//If all targets recieved their damage return to the caster
set .target = .psd.caster
endif
endif
//The mirror image is still on its way
return false
endmethod
//Customized knockback dstructor method
method onDestroy takes nothing returns nothing
//Clean up stuff
call RemoveUnit(.knocker)
call DestroyEffect(.sfx)
call SetUnitVertexColor( .psd.caster, 255, 255, 255, 255 / .psd.copy_count )
//If this is the first unit which should return to the caster then we must
//unhide the caster again
if .psd.target_count == 0 then
call ShowUnit( .psd.caster, true )
static if FORCE_REPICK then
if GetLocalPlayer() == GetOwningPlayer(.psd.caster) then
call SelectUnit( .psd.caster, true )
endif
endif
endif
//If there are no mirror images left we can destroy the remaining instances
set .psd.copy_count = .psd.copy_count -1
if .psd.copy_count == 0 then
call .psd.destroy()
endif
endmethod
endstruct
//Sorta managing struct
private struct polyslash_data
unit caster
group targets
integer target_count
integer copy_count
real damage
method onDestroy takes nothing returns nothing
call ReleaseGroup(.targets)
//call ShowUnit(.caster, true)
endmethod
//Loops through the selection and removes invalid units
static method UpdateGroupState_enum takes nothing returns nothing
local unit u = GetEnumUnit()
if (u == null) or (GetWidgetLife(u) < 0.405) then
call GroupRemoveUnit( TMP_GROUP, u )
else
set TMP_COUNTER = TMP_COUNTER +1
endif
set u = null
endmethod
//Starts the check loop
method UpdateGroupState takes nothing returns nothing
set TMP_COUNTER = 0
set TMP_GROUP = .targets
call ForGroup(.targets, function thistype.UpdateGroupState_enum )
set .target_count = TMP_COUNTER
endmethod
//Creates and configures a mirror image
method setup_copy takes real x, real y, unit t returns nothing
local unit u = CreateUnit( Player(13), GetUnitTypeId(.caster), x, y, 0. )
local polyslash_move psm = polyslash_move.create()
//Create the unit and its knockback instance
set psm.psd = this
set psm.sfx = AddSpecialEffectTarget( CASTER_SFX, u, CASTER_SFX_ATTACHMENT )
call psm.StartHomingTimed(u, t, x, y, TRAVEL_SPEED )
//Setup the knockback
call UnitAddAbility( u, 'Aloc' )
call SetUnitTimeScale( u, MOVE_ANIMATION_TIME_MOD )
call SetUnitColor( u, GetPlayerColor( TMP_PLAYER ))
call SetUnitVertexColor( u, 255, 255, 255, 50 + 255 / .copy_count )
call UnitShareVision( u, TMP_PLAYER , true )
//Setup the unit
endmethod
//Setup for the spell
static method create takes unit ca, real x, real y returns thistype
local thistype this = thistype.allocate()
local unit t
local integer i = GetUnitAbilityLevel(ca, SID)
local real cx = GetUnitX(ca)
local real cy = GetUnitY(ca)
set .caster = ca
set .targets = NewGroup()
set .damage = GetDamage(i)
set .copy_count = GetCopies(i)
set TMP_PLAYER = GetOwningPlayer(ca)
set TMP_COUNTER = 0
call GroupEnumUnitsInArea( .targets, x, y, GetAoERange(i), ENUM_FILTER )
call GroupRemoveUnit( .targets, .caster )
set .target_count = TMP_COUNTER
//Acquire target selection, level depeing stuff, coords, etc...
call SetUnitX( ca, x )
call SetUnitY( ca, y )
call ShowUnit( ca, false )
//Setup the caster
//In case there is no valid target we will just send a mirror image to the
//target location
if .target_count == 0 then
set .copy_count = 1
call .setup_copy( cx, cy, ca )
else
//In case there would be more mirror images than targets
//we will just leave out the obsolete ones
if .copy_count > .target_count then
set .copy_count = .target_count
endif
//Creates the required mirror images
set i = 0
loop
exitwhen i == .copy_count
call .UpdateGroupState()
set t = FirstOfGroup( .targets )
call GroupRemoveUnit( .targets, t)
call .setup_copy( cx, cy, t )
set i = i+1
endloop
call .UpdateGroupState()
endif
set t = null
return this
endmethod
endstruct
//Filter which specifies if a unit is a target or not
private function TargetFilter takes nothing returns boolean
local unit u = GetFilterUnit()
if IsUnitEnemy(u,TMP_PLAYER) and not ( /*
*/ GetWidgetLife(u) < 0.405 or /*
*/ IsUnitType(u, UNIT_TYPE_STRUCTURE) or /*
*/ IsUnitType(u, UNIT_TYPE_FLYING) or /*
*/ IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) or /*
*/ IsUnitType(u, UNIT_TYPE_ANCIENT) or /*
*/ IsUnitInvisible(u, TMP_PLAYER) ) /*
*/then
set TMP_COUNTER = TMP_COUNTER +1
set u = null
return true
endif
set u = null
return true
endfunction
//Launches the spell
private function onCast takes nothing returns boolean
if GetSpellAbilityId() == SID then
call polyslash_data.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY() )
endif
return false
endfunction
//Initializer....
private function Init takes nothing returns nothing
local trigger trig = CreateTrigger()
set ENUM_FILTER = Filter( function TargetFilter )
call Preload(CASTER_SFX)
call Preload(TARGET_SFX)
call PreloadStart()
call TriggerAddCondition( trig, Filter( function onCast ) )
call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT )
endfunction
endlibrary