Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*
* Chain Bolt spell v1.3b by jim7777
*
*
* Copy the Chain Bolt(Thor) and ChainLightningBolt abilities
* Copy the libraries listed below then copy this trigger into your map
*
* Summons a hammer towards the target, dealing damage and stunning it then bounces to the nearest enemy
*
* Requires:
* GetClosestWidget (Spinnaker)
* TimerUtils (Vexorian)
* RegisterPlayerUnitEvent (Magtheridon96)
* SpellEffectEvent (Bribe)
********************************************************************************************************/
scope ChainBolt
globals
private constant integer ABILITY_ID = 'A001' // ability id of Chain Bolt (Thor) ability
private constant integer UNIT_ID = 'h001' // unit id of ChainLightningBolt unit
private constant integer STUN_ID = 'A000' //ability id of ChainLightningBolt ability. Eyecandy and stun purposes
private constant real INTERVAL = 0.03 //interval of movement of the hammer, better leave this alone
private constant real SPEED = 25 //how fast should the hammer move every INTERVAL seconds, better leave this alone
private constant real DETECTOR = 80 //range to detect if we will damage/stun the target, this should be a nice value
private constant real RADIUS = 800 //range to detect possible targets when bouncing the hammer. Change this to your liking
private constant integer MAX_BOUNCE = 4 //maximum number the hammer will bounce including the target
private constant boolean BOUNCE_BACK = false //should the hammer bounce back? If it is true, it will only look for the nearest unit where the hammer last striked
private constant string ORDER_STR = "thunderbolt" //order string of the STUN_ID, only change this if you aren't using Storm Bolt as base ability
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL //attack type of damage to be dealt
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC // damage type of damage to be dealt
endglobals
//=== set the damage dealt here
private function GetDmg takes real lvl returns real
return lvl*25
endfunction
//============== END OF CONFIGURABLES TOUCH THE CODES BELOW IF YOU KNOW WHAT YOU ARE DOING! ==========
private struct CLightning
unit caster
unit target
unit bolt
real dmg
player owner
integer bounce
group stack = null
private static thistype tmpDat
static method Filt takes nothing returns boolean
return tmpDat.target != GetFilterUnit() and IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(tmpDat.caster)) and IsUnitVisible(GetFilterUnit(),GetOwningPlayer(tmpDat.caster)) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD) and not IsUnitType(GetFilterUnit(),UNIT_TYPE_MAGIC_IMMUNE) and GetFilterUnit() != tmpDat.target and not IsUnitInGroup(GetFilterUnit(),tmpDat.stack)
endmethod
static method ReturnHammer takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype dat = GetTimerData(t)
local real ux = GetUnitX(dat.bolt)
local real uy = GetUnitY(dat.bolt)
local real tx = GetUnitX(dat.caster)
local real ty = GetUnitY(dat.caster)
local real dx = tx - ux
local real dy = ty - uy
local real x
local real y
local real angle
if SquareRoot(dx * dx + dy * dy) > DETECTOR then
set angle = Atan2(dy, dx)
set x = ux + SPEED * Cos(angle)
set y = uy + SPEED * Sin(angle)
call SetUnitX(dat.bolt,x)
call SetUnitY(dat.bolt,y)
call SetUnitFacing(dat.bolt,angle*bj_RADTODEG)
else
static if not BOUNCE_BACK then
call DestroyGroup(dat.stack)
endif
call RemoveUnit(dat.bolt)
call ReleaseTimer(t)
call dat.destroy()
endif
set t = null
endmethod
static method OnLoop takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype dat = GetTimerData(t)
local real ux = GetUnitX(dat.bolt)
local real uy = GetUnitY(dat.bolt)
local real tx = GetUnitX(dat.target)
local real ty = GetUnitY(dat.target)
local real dx = tx - ux
local real dy = ty - uy
local real x
local real y
local real angle
if not IsUnitType(dat.target,UNIT_TYPE_DEAD) and null != dat.target then
if SquareRoot(dx * dx + dy * dy) > DETECTOR then
set angle = Atan2(dy, dx)
set x = ux + SPEED * Cos(angle)
set y = uy + SPEED * Sin(angle)
call SetUnitX(dat.bolt,x)
call SetUnitY(dat.bolt,y)
call SetUnitFacing(dat.bolt,angle*bj_RADTODEG)
else
if IssueTargetOrder(dat.bolt,ORDER_STR,dat.target) then
call UnitDamageTarget(dat.caster,dat.target,dat.dmg,false,false,ATTACK_TYPE,DAMAGE_TYPE,null)
endif
if dat.bounce < MAX_BOUNCE then
set dat.bounce = dat.bounce + 1
set tmpDat = dat
set dat.target = GetClosestUnitInRange(ux,uy,RADIUS,Filter(function thistype.Filt))
if dat.target == null then
call PauseTimer(t)
call TimerStart(t,INTERVAL,true,function thistype.ReturnHammer)
else
static if not BOUNCE_BACK then
call GroupAddUnit(dat.stack,dat.target)
endif
endif
endif
endif
elseif dat.bounce < MAX_BOUNCE then
set dat.bounce = dat.bounce + 1
set tmpDat = dat
set dat.target = GetClosestUnitInRange(ux,uy,RADIUS,Filter(function thistype.Filt))
if dat.target == null then
call PauseTimer(t)
call TimerStart(t,INTERVAL,true,function thistype.ReturnHammer)
else
static if not BOUNCE_BACK then
call GroupAddUnit(dat.stack,dat.target)
endif
endif
endif
if dat.bounce >= MAX_BOUNCE or null == dat.target then
call PauseTimer(t)
call TimerStart(t,INTERVAL,true,function thistype.ReturnHammer)
endif
set t = null
endmethod
static method start takes nothing returns boolean
local thistype dat
local real angle
local timer t = NewTimer()
set dat = thistype.allocate()
set dat.caster = GetTriggerUnit()
set dat.target = GetSpellTargetUnit()
set dat.dmg = GetDmg(GetUnitAbilityLevel(dat.caster,ABILITY_ID))
set dat.bounce = 0
static if not BOUNCE_BACK then
set dat.stack = CreateGroup()
call GroupAddUnit(dat.stack,dat.target)
endif
set angle = Atan2(GetUnitY(dat.target) - GetUnitY(dat.caster), GetUnitX(dat.target) - GetUnitX(dat.caster))
set dat.owner = GetOwningPlayer(dat.caster)
set dat.bolt = CreateUnit(dat.owner,UNIT_ID,GetUnitX(dat.caster),GetUnitY(dat.caster),bj_RADTODEG * angle)
call UnitAddAbility(dat.bolt,STUN_ID)
call SetUnitAbilityLevel(dat.bolt,STUN_ID,GetUnitAbilityLevel(dat.caster,ABILITY_ID))
call SetTimerData(t,dat)
call TimerStart(t,INTERVAL,true,function thistype.OnLoop)
set t = null
return false
endmethod
static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABILITY_ID,function thistype.start)
endmethod
endstruct
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*****************************************************************************
*
* Get Closest Widget (light version)
* by Spinnaker v2.0.0.0
*
* Special thanks to Troll-Brain
*
******************************************************************************
*
* This snippet contains several functions which returns closest
* widget to given coordinates and passed filter.
*
******************************************************************************
*
* Important:
* Performance drop depends on amount of units currently on the map
* Snippet functions are designed to reduce the search time as much as
* posible, although it's recommended to use non specific functions
* wisely, especialy if your map contains large numbers of units.
*
******************************************************************************
*
* Functions:
* function GetClosestUnit takes real x, real y, boolexpr filter returns unit
* - Returns closest unit matching specific filter
* function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
* - Gets nearest unit in range matching specific filter
* function GetClosestUnitInGroup takes real x, real y, group g returns unit
* - Retrieves closest unit in given group
*
* function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group g, boolexpr filter returns nothing
* - Returns up to N nearest units in range of passed coordinates
* function GetClosestNUnitsInGroup takes real x, real y, integer n, group sourceGroup, group destGroup returns nothing
* - Retrieves up to N closest units in passed group
*
*****************************************************************************/
library GetClosestWidget
private keyword Init
globals
private unit array Q
private real array V
private integer C=0
endglobals
struct ClosestWidget extends array
readonly static real distance=0
readonly static real cX=0
readonly static real cY=0
readonly static unit cUnit=null
static method resetData takes real x, real y returns nothing
set distance=100000
set cUnit=null
set cX=x
set cY=y
endmethod
static method enumUnits takes nothing returns nothing
local unit u=GetEnumUnit()
local real dx=GetUnitX(u)-cX
local real dy=GetUnitY(u)-cY
set dx=(dx*dx+dy*dy)/10000.
if dx<distance then
set cUnit=u
set distance=dx
endif
set u=null
endmethod
static method sortUnits takes integer l, integer r returns nothing
local integer i=l
local integer j=r
local real v=V[(l+r)/2]
loop
loop
exitwhen V[i]>=v
set i=i+1
endloop
loop
exitwhen V[j]<=v
set j=j-1
endloop
if i<=j then
set V[0]=V[i]
set V[i]=V[j]
set V[j]=V[0]
set Q[0]=Q[i]
set Q[i]=Q[j]
set Q[j]=Q[0]
set i=i+1
set j=j-1
endif
exitwhen i>j
endloop
if l<j then
call sortUnits(l,j)
endif
if r>i then
call sortUnits(i,r)
endif
endmethod
static method saveGroup takes nothing returns nothing
local real dx
local real dy
set C=C+1
set Q[C]=GetEnumUnit()
set dx=GetUnitX(Q[C])-cX
set dy=GetUnitY(Q[C])-cY
set V[C]=(dx*dx+dy*dy)/10000.
endmethod
endstruct
function GetClosestUnit takes real x, real y, boolexpr filter returns unit
local real r=800.
call ClosestWidget.resetData(x,y)
loop
if r>3200. then
call GroupEnumUnitsInRect(bj_lastCreatedGroup, bj_mapInitialPlayableArea, filter)
exitwhen true
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, r, filter)
exitwhen FirstOfGroup(bj_lastCreatedGroup)!=null
endif
set r=2*r
endloop
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.enumUnits)
return ClosestWidget.cUnit
endfunction
function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
call ClosestWidget.resetData(x,y)
if radius>=0 then
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, filter)
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.enumUnits)
endif
return ClosestWidget.cUnit
endfunction
function GetClosestUnitInGroup takes real x, real y, group g returns unit
call ClosestWidget.resetData(x,y)
call ForGroup(g, function ClosestWidget.enumUnits)
return ClosestWidget.cUnit
endfunction
function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group g, boolexpr filter returns nothing
local integer q=n+1
call ClosestWidget.resetData(x,y)
if radius>=0 then
call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, radius, filter)
call ForGroup(bj_lastCreatedGroup, function ClosestWidget.saveGroup)
call ClosestWidget.sortUnits(1,C)
loop
exitwhen n==0 or Q[-n+q]==null
call GroupAddUnit(g, Q[-n+q])
set n =n-1
endloop
endif
set C=0
endfunction
function GetClosestNUnitsInGroup takes real x, real y, integer n, group sourceGroup, group destGroup returns nothing
local integer q=n+1
call ClosestWidget.resetData(x,y)
call ForGroup(sourceGroup, function ClosestWidget.saveGroup)
call ClosestWidget.sortUnits(1,C)
loop
exitwhen n==0 or Q[-n+q]==null
call GroupAddUnit(destGroup, Q[-n+q])
set n=n-1
endloop
set C=0
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=29
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.0.0.0
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must
* return false. They can return nothing as well. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent
* takes
* playerunitevent whichEvent : The event you would like to register
* code whichFunction : The code you would like to register
* returns
* nothing
*
* - Registers code that will execute when an event fires.
*
* - function RegisterPlayerUnitEventForPlayer
* takes
* playerunitevent whichEvent : The event you would like to register
* code whichFunction : The code you would like to register
* player whichPlayer : The player you would like to register the event for
* returns
* nothing
*
* - Registers code that will execute when an event fires for a certain player.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 260 + 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
endlibrary
//TESH.scrollpos=24
//TESH.alwaysfold=0
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary