//TESH.scrollpos=0
//TESH.alwaysfold=0
///! import "Starfall.j"
Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xedummy requires xebasic
//******************************************************************************
// xedummy 0.9
// ------
// For all your xe dummy recycling needs.
//
//******************************************************************************
//==============================================================================
globals
// The number of different angles at which the dummy units will be stored.
private constant integer ANGLE_RESOLUTION = 12
// The total number of xe dummy units that will be preloaded on map initialization.
private constant integer INITIAL_DUMMY_COUNT = 36
// Don't allow to keep more than DUMMY_STACK_LIMIT inactive dummy units.
private constant integer DUMMY_STACK_LIMIT = 240
endglobals
// END OF CALIBRATION SECTION
// ================================================================
private keyword xedummy
private struct recycleQueue extends array
recycleQueue next
recycleQueue prev
real angle
integer size
xedummy first
xedummy last
static method onInit takes nothing returns nothing
local integer i=0
loop
exitwhen i==ANGLE_RESOLUTION
set i=i+1
set recycleQueue(i).prev=recycleQueue(i-1)
set recycleQueue(i).next=recycleQueue(i+1)
set recycleQueue(i).angle=(i-0.5)*(360.0/ANGLE_RESOLUTION)
endloop
set recycleQueue(1).prev=recycleQueue(i)
set recycleQueue(i).next=recycleQueue(1)
endmethod
static method get takes real angle returns recycleQueue
return recycleQueue(R2I(angle/360.0*ANGLE_RESOLUTION)+1)
endmethod
endstruct
// ================================================================
struct xedummy
private static group g=CreateGroup()
private unit u
// ----------------------------------------------------------------
private xedummy next
private method queueInsert takes recycleQueue q returns nothing
call SetUnitFacing(.u, q.angle)
if q.size==0 then
set q.first=this
else
set q.last.next=this
endif
set q.last=this
set .next=0
// Recursively check adajcent queues and migrate xedummies as needed.
if q.size>q.next.size then
set this=q.first
set q.first=.next
call .queueInsert(q.next)
elseif q.size>q.prev.size then
set this=q.first
set q.first=.next
call .queueInsert(q.prev)
else
set q.size=q.size+1
endif
endmethod
private static method queueRemove takes recycleQueue q returns xedummy
// Recursively check adajcent queues and migrate xedummies as needed.
local xedummy this
if q.size<q.next.size then
set this=q.last
set q.last=.queueRemove(q.next)
set .next=q.last
call SetUnitFacing(q.last.u, q.angle)
elseif q.size<q.prev.size then
set this=q.last
set q.last=.queueRemove(q.prev)
set .next=q.last
call SetUnitFacing(q.last.u, q.angle)
else
set q.size=q.size-1
if q.size==0 then
set q.last=0
endif
endif
set this=q.first
set q.first=.next
set .next=0
return this
endmethod
// ----------------------------------------------------------------
private static method create takes unit u returns xedummy
local xedummy this
if GetUnitTypeId(u)!=XE_DUMMY_UNITID then
debug call BJDebugMsg("ReleaseXEDummy error: Method called on a unit of an incorrect type.")
elseif IsUnitInGroup(u, .g) then
debug call BJDebugMsg("ReleaseXEDummy error: Method called on an already released unit.")
else
set this=.allocate()
if integer(this)>DUMMY_STACK_LIMIT then
call RemoveUnit(u)
call .deallocate()
return 0
endif
set .u=u
call GroupAddUnit(.g, u)
call .queueInsert(recycleQueue.get(GetUnitFacing(u)))
call SetUnitAnimationByIndex(u, 90)
call SetUnitScale(u, 1, 0, 0)
call SetUnitVertexColor(u, 255, 255, 255, 255)
// call ShowUnit(u, false) // Do not hide the unit, it is rather costly and not needed.
return this
endif
return 0
endmethod
private method destroy takes nothing returns nothing
call GroupRemoveUnit(.g, .u)
call ShowUnit(.u, true) // Show the unit in case it was hidden before being recycled.
set .u=null
call .deallocate()
endmethod
// ----------------------------------------------------------------
private static unit dummy
private static method onInit takes nothing returns nothing
local integer i=INITIAL_DUMMY_COUNT
local recycleQueue q=recycleQueue(1)
if i>DUMMY_STACK_LIMIT then
debug call BJDebugMsg("xedummy error: INITIAL_DUMMY_COUNT can not be larger than DUMMY_STACK_LIMIT.")
set i=DUMMY_STACK_LIMIT
endif
loop
exitwhen i==0
set .dummy = CreateUnit(Player(15), XE_DUMMY_UNITID, 0.0,0.0,q.angle)
call UnitAddAbility(.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(.dummy,'Aloc')
call UnitRemoveAbility(.dummy,XE_HEIGHT_ENABLER)
call .create(.dummy)
set i=i-1
set q=q.next
endloop
endmethod
// ----------------------------------------------------------------
static method new takes player p, real x, real y, real face returns unit
local recycleQueue q
local xedummy this
loop
exitwhen face>0.0
set face=face+360.0
endloop
loop
exitwhen face<360.0
set face=face-360.0
endloop
set q=recycleQueue.get(face)
if q.size==0 then
set .dummy = CreateUnit(p, XE_DUMMY_UNITID, x,y,face)
call UnitAddAbility(.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(.dummy,'Aloc')
call UnitRemoveAbility(.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(.dummy, x)
call SetUnitY(.dummy, y)
else
set this=.queueRemove(q)
set .dummy=.u
call .destroy()
call SetUnitX(.dummy, x)
call SetUnitY(.dummy, y)
call SetUnitFacing(.dummy, face)
call SetUnitOwner(.dummy, p, true)
endif
return .dummy
endmethod
static method release takes unit u returns nothing
call .create(u)
endmethod
endstruct
// ================================================================
function XE_NewDummyUnit takes player p, real x, real y, real face returns unit
return xedummy.new( p,x,y,face )
endfunction
function XE_ReleaseDummyUnit takes unit u returns nothing
call xedummy.release( u )
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xefx initializer init requires xebasic, optional xedummy
//**************************************************
// xefx 0.9
// --------
// Recommended: ARGB (adds ARGBrecolor method)
// For your movable fx needs
//
//**************************************************
//==================================================
globals
private constant integer MAX_INSTANCES = 8190 //change accordingly.
//Delay in order to show the death animation of the effect correctly when xefx is destroyed.
//You may need to increase this if you are using effects with longer death animations.
private constant real MIN_RECYCLE_DELAY = 4.0
//The delay does not need to be exact so we do cleanup in batches instead of individually.
//This determines how often the recycler runs, should be less than MIN_RECYCLE_DELAY.
private constant real RECYCLE_INTERVAL = 0.5
//if this is true and the xedummy library is present, units will be recycled instead of removed.
private constant boolean RECYCLE_DUMMY_UNITS = true
private timer recycler
endglobals
private struct recyclebin
unit u
private recyclebin next=0
private static recyclebin array list
private static integer readindex=1
private static integer writeindex=0
private static integer count=0
static method Recycle takes nothing returns nothing
local recyclebin this = .list[readindex]
loop
exitwhen this==0
static if RECYCLE_DUMMY_UNITS and LIBRARY_xedummy then
call XE_ReleaseDummyUnit(this.u)
else
call RemoveUnit(this.u)
endif
set this.u=null
set .count=.count-1
call this.destroy()
set this=this.next
endloop
set .list[readindex]=0
set .writeindex=.readindex
set .readindex=.readindex+1
if .readindex>R2I(MIN_RECYCLE_DELAY/RECYCLE_INTERVAL+1.0) then
set .readindex=0
endif
if count!=0 then
call TimerStart(recycler, RECYCLE_INTERVAL, false, function recyclebin.Recycle)
endif
endmethod
static method create takes unit u returns recyclebin
local recyclebin this=recyclebin.allocate()
if .count==0 then
call TimerStart(recycler, RECYCLE_INTERVAL, false, function recyclebin.Recycle)
endif
set .count=.count+1
set .u=u
call SetUnitOwner(u,Player(15),false)
set .next=.list[.writeindex]
set .list[.writeindex]=this
return this
endmethod
endstruct
private function init takes nothing returns nothing
set recycler=CreateTimer()
endfunction
struct xefx[MAX_INSTANCES]
public integer tag=0
private unit dummy
private effect fx=null
private real zang=0.0
private integer r=255
private integer g=255
private integer b=255
private integer a=255
private integer abil=0
static method create takes real x, real y, real facing returns xefx
local xefx this=xefx.allocate()
static if RECYCLE_DUMMY_UNITS and LIBRARY_xedummy then
set this.dummy= XE_NewDummyUnit(Player(15), x,y, facing*bj_RADTODEG)
else
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, facing*bj_RADTODEG)
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
endif
return this
endmethod
method operator owner takes nothing returns player
return GetOwningPlayer(this.dummy)
endmethod
method operator owner= takes player p returns nothing
call SetUnitOwner(this.dummy,p,false)
endmethod
method operator teamcolor= takes playercolor c returns nothing
call SetUnitColor(this.dummy,c)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(this.dummy,value,value,value)
endmethod
//! textmacro XEFX_colorstuff takes colorname, colorvar
method operator $colorname$ takes nothing returns integer
return this.$colorvar$
endmethod
method operator $colorname$= takes integer value returns nothing
set this.$colorvar$=value
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
//! endtextmacro
//! runtextmacro XEFX_colorstuff("red","r")
//! runtextmacro XEFX_colorstuff("green","g")
//! runtextmacro XEFX_colorstuff("blue","b")
//! runtextmacro XEFX_colorstuff("alpha","a")
method recolor takes integer r, integer g , integer b, integer a returns nothing
set this.r=r
set this.g=g
set this.b=b
set this.a=a
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
implement optional ARGBrecolor
method operator abilityid takes nothing returns integer
return this.abil
endmethod
method operator abilityid= takes integer a returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(a!=0) then
call UnitAddAbility(this.dummy,a)
endif
set this.abil=a
endmethod
method operator abilityLevel takes nothing returns integer
return GetUnitAbilityLevel( this.dummy, this.abil)
endmethod
method operator abilityLevel= takes integer newLevel returns nothing
call SetUnitAbilityLevel(this.dummy, this.abil, newLevel)
endmethod
method flash takes string fx returns nothing
call DestroyEffect(AddSpecialEffectTarget(fx,this.dummy,"origin"))
endmethod
method operator xyangle takes nothing returns real
return GetUnitFacing(this.dummy)*bj_DEGTORAD
endmethod
method operator xyangle= takes real value returns nothing
call SetUnitFacing(this.dummy,value*bj_RADTODEG)
endmethod
method operator zangle takes nothing returns real
return this.zang
endmethod
method operator zangle= takes real value returns nothing
local integer i=R2I(value*bj_RADTODEG+90.5)
set this.zang=value
if(i>=180) then
set i=179
elseif(i<0) then
set i=0
endif
call SetUnitAnimationByIndex(this.dummy, i )
endmethod
method operator x takes nothing returns real
return GetUnitX(this.dummy)
endmethod
method operator y takes nothing returns real
return GetUnitY(this.dummy)
endmethod
method operator z takes nothing returns real
return GetUnitFlyHeight(this.dummy)
endmethod
method operator z= takes real value returns nothing
call SetUnitFlyHeight(this.dummy,value,0)
endmethod
method operator x= takes real value returns nothing
call SetUnitX(this.dummy,value)
endmethod
method operator y= takes real value returns nothing
call SetUnitY(this.dummy,value)
endmethod
method operator fxpath= takes string newpath returns nothing
if (this.fx!=null) then
call DestroyEffect(this.fx)
endif
if (newpath=="") then
set this.fx=null
else
set this.fx=AddSpecialEffectTarget(newpath,this.dummy,"origin")
endif
endmethod
method hiddenReset takes string newfxpath, real newfacing returns nothing
local real x = GetUnitX(this.dummy)
local real y = GetUnitY(this.dummy)
local real z = this.z
local real za = this.zangle
local integer level = this.abilityLevel
set .fxpath=null
static if RECYCLE_DUMMY_UNITS and LIBRARY_xedummy then
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
call recyclebin.create(this.dummy)
set this.dummy=XE_NewDummyUnit(Player(15), x,y, newfacing*bj_RADTODEG)
else
call RemoveUnit(this.dummy)
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, newfacing*bj_RADTODEG)
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
endif
set .fxpath=newfxpath
if(level != 0) then
call UnitAddAbility(this.dummy, this.abil)
call SetUnitAbilityLevel(this.dummy, this.abil, level)
endif
set this.z = z
set zangle = za
endmethod
method destroy takes nothing returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(this.fx!=null) then
call DestroyEffect(this.fx)
set this.fx=null
endif
call recyclebin.create(this.dummy)
set this.dummy=null
call this.deallocate()
endmethod
method hiddenDestroy takes nothing returns nothing
call ShowUnit(dummy,false)
call destroy()
endmethod
endstruct
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
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
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=0
//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=18
//TESH.alwaysfold=0
library GetTerrainZ /* v1.0.0.0
********************************************************************
*
* function GetTerrainZ takes real x, real y returns real
*
********************************************************************/
globals
private constant location L = Location(0, 0)
endglobals
function GetTerrainZ takes real x, real y returns real
call MoveLocation(L, x, y)
return GetLocationZ(L)
endfunction
endlibrary
library UnitZ /* v1.0.0.0
********************************************************************
*
* */uses/*
* */ GetTerrainZ /*
* */optional/*
* */ AutoFly /* hiveworkshop.com/forums/jass-resources-412/snippet-autofly-unitindexer-version-195563/
*
* function GetUnitZ takes unit whichUnit returns real
* function SetUnitZ takes unit whichUnit, real z returns real
*
********************************************************************/
function GetUnitZ takes unit u returns real
return GetTerrainZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u)
endfunction
function SetUnitZ takes unit u, real z returns nothing
call SetUnitFlyHeight(u, z - GetTerrainZ(GetUnitX(u), GetUnitY(u)), 0)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library StarfallSpell initializer init uses /*
*/ xebasic /* http://www.wc3c.net/showthread.php?t=101150
*/, xedummy /* -//-
*/, xefx /* -//-
*/, TimerUtils /* http://www.wc3c.net/showthread.php?t=101322
*/, GroupUtils /* http://www.wc3c.net/showthread.php?t=104464
*/, UnitZ /* http://www.hiveworkshop.com/forums/jass-resources-412/snippet-getterrainz-unitz-236942/
*/
globals
private constant integer SPELL_ID = 'A000'
private constant real INITIAL_BEHIND = 200
private constant real INITIAL_Z = 1200
private constant real FALLING_SPEED = 1000
private constant string MODEL = "Abilities\\Spells\\Undead\\DevourMagic\\DevourMagicBirthMissile.mdl"
private constant real SCALE = 3.0
// "Abilities\\Spells\\NightElf\\Starfall\\StarfallTarget.mdl"
//"Abilities\\Weapons\\Mortar\\MortarMissile.mdl"
endglobals
private function IsUnitInvulnerable takes unit u returns boolean
return GetUnitAbilityLevel(u, 'Avul') > 0 or IsUnitLoaded(u)
endfunction
private function targets_allowed takes unit caster, unit target returns boolean
return not IsUnitType(target, UNIT_TYPE_DEAD) /*
*/ and IsUnitEnemy(target, GetOwningPlayer(caster)) /*
*/ and not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) /*
*/ and not IsUnitInvulnerable(target)
endfunction
private function distance_between takes unit u1, unit u2 returns real
local real x1 = GetUnitX(u1)
local real y1 = GetUnitY(u1)
local real x2 = GetUnitX(u2)
local real y2 = GetUnitY(u2)
local real dx = x2 - x1
local real dy = y2 - y1
return SquareRoot(dx * dx + dy * dy)
endfunction
private function say takes string s returns nothing
call BJDebugMsg(s)
// call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, s)
endfunction
globals
private hashtable HT = InitHashtable()
private constant real DT = 0.03125
private real tx
private real ty
private real tz
private real dx
private real dy
private real dz
private real dist_behind
private real vlen
private real mvlen
private real array random_delays
private constant integer random_delays_count = 65
endglobals
private function init_random_delays takes nothing returns nothing
set random_delays[0] = 0
set random_delays[1] = 16
set random_delays[2] = 31
set random_delays[3] = 47
set random_delays[4] = 63
set random_delays[5] = 78
set random_delays[6] = 94
set random_delays[7] = 109
set random_delays[8] = 125
set random_delays[9] = 141
set random_delays[10] = 156
set random_delays[11] = 172
set random_delays[12] = 188
set random_delays[13] = 203
set random_delays[14] = 219
set random_delays[15] = 234
set random_delays[16] = 250
set random_delays[17] = 266
set random_delays[18] = 281
set random_delays[19] = 297
set random_delays[20] = 313
set random_delays[21] = 328
set random_delays[22] = 344
set random_delays[23] = 359
set random_delays[24] = 375
set random_delays[25] = 391
set random_delays[26] = 406
set random_delays[27] = 422
set random_delays[28] = 438
set random_delays[29] = 453
set random_delays[30] = 469
set random_delays[31] = 485
set random_delays[32] = 500
set random_delays[33] = 516
set random_delays[34] = 531
set random_delays[35] = 547
set random_delays[36] = 563
set random_delays[37] = 578
set random_delays[38] = 594
set random_delays[39] = 609
set random_delays[40] = 625
set random_delays[41] = 641
set random_delays[42] = 656
set random_delays[43] = 672
set random_delays[44] = 688
set random_delays[45] = 703
set random_delays[46] = 719
set random_delays[47] = 735
set random_delays[48] = 750
set random_delays[49] = 766
set random_delays[50] = 782
set random_delays[51] = 797
set random_delays[52] = 813
set random_delays[53] = 828
set random_delays[54] = 844
set random_delays[55] = 859
set random_delays[56] = 875
set random_delays[57] = 891
set random_delays[58] = 906
set random_delays[59] = 922
set random_delays[60] = 938
set random_delays[61] = 953
set random_delays[62] = 969
set random_delays[63] = 984
set random_delays[64] = 1000
endfunction
public keyword Starfall
private keyword FallingStars
private struct FallingStar extends array
FallingStars fs
xefx star
static method move_star takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
local FallingStars fsx = this.fs
local real speed = fsx.sf.speed
local real angle_behind = (GetUnitFacing(fsx.target) - 180) * bj_DEGTORAD
set tx = GetUnitX(fsx.target)
set ty = GetUnitY(fsx.target)
set tz = GetUnitZ(fsx.target)
set dx = tx - star.x
set dy = ty - star.y
set dz = tz - star.z
set vlen = SquareRoot(dx * dx + dy * dy + dz * dz)
set mvlen = 1 / vlen
set dx = dx * mvlen
set dy = dy * mvlen
set dz = dz * mvlen
set star.x = star.x + dx * speed * DT
set star.y = star.y + dy * speed * DT
set star.z = star.z + dz * speed * DT
set dx = tx - star.x
set dy = ty - star.y
set dz = tz - star.z
set dist_behind = SquareRoot(dx * dx + dy * dy)
set star.x = tx + Cos(angle_behind) * (dist_behind)
set star.y = ty + Sin(angle_behind) * (dist_behind)
set star.xyangle = Atan2(ty - star.y, tx - star.x)
set star.zangle = -Atan((star.z - tz) / dist_behind)
if vlen <= 32 then
if not IsUnitInvulnerable(fsx.target) then
call UnitDamageTarget(fsx.sf.caster, fsx.target, fsx.sf.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
endif
call ReleaseTimer(t)
call star.destroy()
endif
set t = null
endmethod
private static thistype that = 0
static method fall takes FallingStars fs returns thistype
local real angle_behind
local thistype this = that - (that / 8190) * 8190 + 1
set that = this
set this.fs = fs
set angle_behind = GetUnitFacing(fs.target) - 180
set tx = GetUnitX(fs.target) + Cos(angle_behind * bj_DEGTORAD) * fs.sf.initial_behind
set ty = GetUnitY(fs.target) + Sin(angle_behind * bj_DEGTORAD) * fs.sf.initial_behind
set star = xefx.create(tx, ty, (angle_behind + 180) * bj_DEGTORAD)
set star.fxpath = fs.sf.model
set star.scale = fs.sf.scale
set star.z = fs.sf.initial_z + GetUnitZ(fs.target)
set star.zangle = -Atan(star.z / fs.sf.initial_behind)
call TimerStart(NewTimerEx(this), DT, true, function thistype.move_star)
return this
endmethod
endstruct
private struct FallingStars extends array
Starfall sf
unit target
private static thistype self
private static trigger call_starfall
private static method star_fall takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
if sf.duration > 0 then
if distance_between(sf.caster, target) <= sf.AOE and targets_allowed(sf.caster, target) then
call FallingStar.fall(this)
endif
call ReleaseTimer(t)
set self = this
call TriggerExecute(call_starfall) // self.starfall(), i.e drop another star
else
call ReleaseTimer(t)
endif
set t = null
endmethod
private static method starfall takes nothing returns nothing
local thistype this = self
local real random_delay = random_delays[GetRandomInt(0, random_delays_count - 1)]
local real delay
set delay = random_delay / 1000.0
if GetRandomInt(1, 100) <= 50 then
set delay = -delay
endif
set delay = sf.initial_delay + delay
call TimerStart(NewTimerEx(this), delay, false, function thistype.star_fall)
endmethod
private static thistype that = 0
static method fall_over takes Starfall sf, unit target returns thistype
local thistype this = that - (that / 8190) * 8190 + 1
set that = this
set this.sf = sf
set this.target = target
set self = this
call thistype.starfall()
return this
endmethod
private static method onInit takes nothing returns nothing
call ExecuteFunc(SCOPE_PRIVATE + "init_random_delays")
set call_starfall = CreateTrigger()
call TriggerAddAction(call_starfall, function thistype.starfall)
endmethod
endstruct
struct Starfall extends array
unit caster
real AOE
real duration
real damage
real initial_delay
real initial_behind
real initial_z
real speed
string model
real scale
private group affected_units
private boolean stopped
private static thistype self
private static trigger call_starfall
private static method starfall_recur takes nothing returns nothing
local timer t = GetExpiredTimer()
local Starfall this = GetTimerData(t)
set duration = duration - initial_delay
if duration > 0 then
call ReleaseTimer(t)
set t = null
set self = this
call TriggerExecute(call_starfall) // call self.starfall()
return
else
if not stopped then
call IssueImmediateOrder(caster, "stop")
endif
call stop()
call ReleaseTimer(t)
set t = null
endif
endmethod
private static method starfall takes nothing returns nothing
local thistype this = self
local unit u
call GroupUnitsInArea(ENUM_GROUP, GetUnitX(caster), GetUnitY(caster), AOE)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
if targets_allowed(caster, u) and not IsUnitInGroup(u, affected_units) then
call GroupAddUnit(affected_units, u)
call FallingStars.fall_over(this, u)
endif
endloop
call TimerStart(NewTimerEx(this), initial_delay, false, function thistype.starfall_recur)
endmethod
private static thistype that = 0
static method cast takes unit caster, real AOE, real duration, real damage, real initial_delay, real initial_behind, real initial_z, real falling_speed, string model, real scale returns thistype
local thistype this = that - (that / 8190) * 8190 + 1
set that = this
set this.caster = caster
set this.AOE = AOE
set this.duration = duration
set this.damage = damage
set this.initial_delay = initial_delay
set this.initial_behind = initial_behind
set this.initial_z = initial_z
set this.speed = falling_speed
set this.model = model
set this.scale = scale
set affected_units = NewGroup()
set stopped = false
call SetUnitAnimation(caster, "channel")
set self = this
call thistype.starfall() // call self.starfall()
return this
endmethod
method stop takes nothing returns nothing
if stopped then
return
endif
set stopped = true
set duration = 0
call ReleaseGroup(affected_units)
endmethod
private static method onInit takes nothing returns nothing
set call_starfall = CreateTrigger()
call TriggerAddAction(call_starfall, function thistype.starfall)
endmethod
endstruct
private function on_spell_effect takes nothing returns boolean
local Starfall sf
local unit caster
local integer spell_level
local real AOE
local real damage
local real duration
local real initial_delay
local real initial_behind
local real initial_z
local real falling_speed
local string model
local real scale
if GetSpellAbilityId() != SPELL_ID then
return false
endif
set caster = GetTriggerUnit()
set spell_level = GetUnitAbilityLevel(caster, SPELL_ID)
set AOE = 1000
set duration = 30 // seconds
set damage = 50
set initial_delay = 1.5
set initial_behind = INITIAL_BEHIND
set initial_z = INITIAL_Z
set falling_speed = FALLING_SPEED
set model = MODEL
set scale = SCALE
set sf /*
*/ = Starfall.cast( /*
*/ caster /*
*/ , AOE /*
*/ , duration /*
*/ , damage /*
*/ , initial_delay /*
*/ , initial_behind /*
*/ , initial_z /*
*/ , falling_speed /*
*/ , model /*
*/ , scale /*
*/ )
call SaveInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID, sf)
set caster = null
return false
endfunction
private function on_end_cast takes nothing returns boolean
local integer sf
if GetSpellAbilityId() == SPELL_ID then
set sf = LoadInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID)
call Starfall(sf).stop()
call FlushChildHashtable(HT, GetHandleId(GetTriggerUnit()))
endif
return false
endfunction
private function init takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function on_spell_effect))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t, Condition(function on_end_cast))
endfunction
endlibrary
//TESH.scrollpos=33
//TESH.alwaysfold=0
library Rockets initializer init uses StarfallSpell
globals
private constant integer SPELL_ID = 'A001'
private constant real INITIAL_BEHIND = 500
private constant real INITIAL_Z = 800
private constant real FALLING_SPEED = 700
private constant string MODEL = "Abilities\\Weapons\\Mortar\\MortarMissile.mdl"
private constant real SCALE = 1.0
endglobals
globals
private hashtable HT = InitHashtable() // might be a good idea to use a SPELL_HT for the whole map
endglobals
private function on_spell_effect takes nothing returns boolean
local StarfallSpell_Starfall sf
local unit caster
local integer spell_level
local real AOE
local real damage
local real duration
local real initial_delay
local real initial_behind
local real initial_z
local real falling_speed
local string model
local real scale
if GetSpellAbilityId() != SPELL_ID then
return false
endif
set caster = GetTriggerUnit()
set spell_level = GetUnitAbilityLevel(caster, SPELL_ID)
set AOE = 1000
set duration = spell_level * 5
set damage = 50
set initial_delay = 0.5
set initial_behind = INITIAL_BEHIND
set initial_z = INITIAL_Z
set falling_speed = FALLING_SPEED
set model = MODEL
set scale = SCALE
set sf /*
*/ = StarfallSpell_Starfall.cast( /*
*/ caster /*
*/ , AOE /*
*/ , duration /*
*/ , damage /*
*/ , initial_delay /*
*/ , initial_behind /*
*/ , initial_z /*
*/ , falling_speed /*
*/ , model /*
*/ , scale /*
*/ )
call SaveInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID, sf)
set caster = null
return false
endfunction
private function on_end_cast takes nothing returns boolean
local integer sf
if GetSpellAbilityId() == SPELL_ID then
set sf = LoadInteger(HT, GetHandleId(GetTriggerUnit()), SPELL_ID)
call StarfallSpell_Starfall(sf).stop()
call FlushChildHashtable(HT, GetHandleId(GetTriggerUnit()))
endif
return false
endfunction
private function init takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function on_spell_effect))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(t, Condition(function on_end_cast))
endfunction
endlibrary