Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* 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)
//* 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.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//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")
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")
set tT[0]=CreateTimer()
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
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
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
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=180
//TESH.alwaysfold=0
library CTL /* v1.1.0.0
*************************************************************************************
*
* CTL or Constant Timer Loop provides a loop for constant merged timers of timeout .03125
*
* Similar to T32 but pauses timer when no structs have instances and removes structs
* from timer trigger when those structs have no instances.
*
* This can also create new timers after destroying a previous timer and generates less
* code in the module. It also generates no triggers so long as the module is implemented
* at the top of the struct.
*
************************************************************************************
*
* static method create takes nothing returns thistype
* - CTL
* - Creates new timer
*
* method destroy takes nothing returns nothing
* - CTL
* - Destroys created timer
*
* Module
*
* module CTL
* - Declare locals in here
* - Run ini code
* module CTLExpire
* - Run timer code
* -
* - thistype this refers to current expiring timer\
* module CTLNull
* - Null locals here
* module CTLEnd
*
* Example of Constant Timer Loop 32
* struct MyTimer extends array
* integer myValue
* implement CTL
* local string s="My value is "
* implement CTLExpire
* call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,s+I2S(myValue))
* call destroy()
* implement CTLNull
* set s=null //pointless, but shows how to use null block
* implement CTLEnd
* endstruct
*
* set MyTimer.create().myValue=16 //will display "My value is 16" in 5 seconds
*
* module CT32
* - A constant running timer. Useful when the timer is pretty much never ever
* - going to stop. Also allows control over loop (just provides an expiring timer).
* - Code goes in between two methods
* module CT32End
*
* Example of Constant Timer 32
*
* //Displays
* // 1
* // 2
* // 3
* struct MyTimers extends array
* integer myValue
* thistype next
* implement CTL2
* local thistype this=thistype(0).next
* loop
* exitwhen 0==this
* call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,s+I2S(myValue))
* set this=next
* endloop
* implement CTL2End
* private static method onInit takes nothing returns nothing
* set thistype(0).next=1
* set thistype(1).next=2
* set thistype(2).next=3
* set thistype(1).myValue=1
* set thistype(2).myValue=2
* set thistype(3).myValue=3
* endmethod
* endstruct
*
************************************************************************************/
globals
private integer ic=0 //instance count
private integer tc=0 //timer count
private integer array rf //root first
private integer array n //next
private integer array p //previous
private integer array th //timer head
private integer array ns //next stack
private trigger t=CreateTrigger()
private timer m=CreateTimer()
private triggercondition array ct
private conditionfunc array rc
endglobals
private function E takes nothing returns nothing
local integer i=ns[0]
set ns[0]=0
loop
exitwhen 0==i
if (0==p[i]) then
if (0==n[i]) then
call TriggerRemoveCondition(t,ct[th[i]])
set ct[th[i]]=null
set tc=tc-1
set rf[th[i]]=0
else
set rf[th[i]]=n[i]
set p[n[i]]=0
endif
else
set p[n[i]]=p[i]
set n[p[i]]=n[i]
endif
set n[i]=n[0]
set n[0]=i
set i=ns[i]
endloop
if (0==tc) then
call PauseTimer(m)
else
call TriggerEvaluate(t)
endif
endfunction
private function CT takes integer r returns integer
local integer i
local integer f
if (0==n[0]) then
set i=ic+1
set ic=i
else
set i=n[0]
set n[0]=n[i]
endif
set th[i]=r
set ns[i]=-1
set f=rf[r]
if (0==f) then
set n[i]=0
set p[i]=0
set rf[r]=i
set ct[r]=TriggerAddCondition(t,rc[r])
if (0==tc) then
call TimerStart(m,.031250000,true,function E)
endif
set tc=tc+1
else
set n[i]=f
set p[i]=0
set p[f]=i
set rf[r]=i
endif
return i
endfunction
private function DT takes integer t returns nothing
debug if (0>ns[t]) then
set ns[t]=ns[0]
set ns[0]=t
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"TIMER LOOP ERROR: ATTEMPT TO DESTROY NULL TIMER")
debug endif
endfunction
private keyword r
private keyword e
module CTL
static integer r
static method create takes nothing returns thistype
return CT(r)
endmethod
method destroy takes nothing returns nothing
call DT(this)
endmethod
static method e takes nothing returns boolean
local thistype this=rf[r]
endmodule
module CTLExpire
loop
exitwhen 0==this
endmodule
module CTLNull
set this=n[this]
endloop
endmodule
module CTLEnd
return false
endmethod
private static method onInit takes nothing returns nothing
set r=ic+1
set ic=r
set rc[r]=Condition(function thistype.e)
endmethod
endmodule
module CT32
static method e takes nothing returns boolean
endmodule
module CT32End
return false
endmethod
private static method onInit takes nothing returns nothing
call TriggerAddCondition(t,Condition(function thistype.e))
if (0==tc) then
call TimerStart(m,.031250000,true,function E)
endif
set tc=tc+1
endmethod
endmodule
endlibrary
//TESH.scrollpos=88
//TESH.alwaysfold=0
// ********************************************************************************* //
// This spells requires the TimerUtils library //
// ********************************************************************************* //
// ********************************************************************************* //
// HOW TO IMPLEMENT //
// ********************************************************************************* //
// //
// 1. Copy the Rupture Ability (object editor) //
// 2. Copy the Rupture Buff (object editor) //
// 3. Copy this trigger //
// 4. Enjoy :), but remember, this spell requires TimerUtils //
// //
// ********************************************************************************* //
library Rupture initializer init requires TimerUtils
globals
// Rawcode of the Rupture ability
private constant integer RUPTURE_RAWCODE = 'A000'
// Rawcode of the Rupture buff
private constant integer RUPTURE_BUFF_RAWCODE = 'B000'
// Period of the timer
private constant real PERIOD = 0.25
// Special effect (blood)
private constant string SPECIAL_EFFECT = "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl"
// Special effect attachment
private constant string ATTACHMENT_POINT = "chest"
// Rupture initial damage
private constant real INITIAL_DAMAGE = 150.
// Rupture attack type
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// Rupture damage type (universal = "pure")
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL
// Do you want preload the special effect?
private constant boolean PRELOAD = true
endglobals
// Returns the initial damage
private function getInitialDamage takes integer level returns real
return INITIAL_DAMAGE + (level * 50)
endfunction
// Returns the distance covered by the target
// a_x and a_y represents the actual position of the target
// x and y represents the position of the unit in the beggining of the timer
// Remember, the x and y variables are updated every time when timer expires
private function getDistance takes real x, real y, real a_x, real a_y returns real
return SquareRoot((a_x - x) * (a_x - x) + (a_y - y) * (a_y - y))
endfunction
// Returns the damage by the distance covered by the target
private function getDamage takes real distance, integer level returns real
return (distance * (20 * level)) / 100
endfunction
// Returns the affected units
private function affectedUnit takes unit target returns boolean
return true
endfunction
private struct Spell
// Timer (do not touch)
timer t
// Caster unit
unit caster
// Rupture level of the caster
integer level
// Target unit
unit target
// a = Actual (target actual x)
real a_x
// Target actual y
real a_y
// Target x
real x
// Target y
real y
method destroy takes nothing returns nothing
call ReleaseTimer(this.t)
set this.t = null
set this.caster = null
set this.target = null
call this.deallocate()
endmethod
static method action takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real distance
// Actual position of the unit
set this.a_x = GetUnitX(this.target)
set this.a_y = GetUnitY(this.target)
// Distance between the target's actual position and the
// target's old position
set distance = getDistance(this.x, this.y, this.a_x, this.a_y)
// If the target has the Rupture buff and is alive...
if 0 < GetUnitAbilityLevel(this.target, RUPTURE_BUFF_RAWCODE) and 0.405 < GetWidgetLife(this.target) then
// AND the distance is greater than 0 but less than 1300.
if 0. < distance and 1300. > distance then
// Special effect
call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, ATTACHMENT_POINT))
// Damage
call UnitDamageTarget(this.caster, this.target, getDamage(distance, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
// Updating the target's position
set this.x = this.a_x
set this.y = this.a_y
else
// If not, destroy
call this.destroy()
endif
endmethod
static method create takes unit caster, unit target, integer level returns thistype
local thistype this = thistype.allocate()
set this.t = NewTimer()
set this.caster = caster
set this.level = level
set this.target = target
set this.x = GetUnitX(target)
set this.y = GetUnitY(target)
call SetTimerData(this.t, this)
call TimerStart(this.t, PERIOD, true, function thistype.action)
return this
endmethod
endstruct
private function condition takes nothing returns boolean
local unit caster
local unit target
local integer level
if GetSpellAbilityId() == RUPTURE_RAWCODE and affectedUnit(GetSpellTargetUnit()) then
// Ability caster
set caster = GetTriggerUnit()
// Ability target
set target = GetSpellTargetUnit()
// Rupture level
set level = GetUnitAbilityLevel(caster, RUPTURE_RAWCODE)
// Making the initial damage
call UnitDamageTarget(caster, target, getInitialDamage(level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
call Spell.create(caster, target, level)
endif
set caster = null
set target = null
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function condition))
set t = null
// Preload special effect
static if PRELOAD then
call Preload(SPECIAL_EFFECT)
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Rupture requires CTL
globals
// Rawcode of the Rupture ability
private constant integer RUPTURE_RAWCODE = 'A000'
// Rawcode of the Rupture buff
private constant integer RUPTURE_BUFF_RAWCODE = 'B000'
// Period in that the extra damage will be done
private constant real PERIOD = 0.25
// Special effect (blood)
private constant string SPECIAL_EFFECT = "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl"
// Special effect attachment
private constant string ATTACHMENT_POINT = "chest"
// Rupture initial damage
private constant real INITIAL_DAMAGE = 150.
// Rupture attack type
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
// Rupture damage type (universal = "pure")
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL
// Do you want preload the special effect?
private constant boolean PRELOAD = true
endglobals
// Returns the distance covered by the target
// a_x and a_y represents the actual position of the target
// x and y represents the position of the unit in the beggining of the timer
// Remember, the x and y variables are updated every time when timer expires
private function getDistance takes real x, real y, real a_x, real a_y returns real
return SquareRoot((a_x - x) * (a_x - x) + (a_y - y) * (a_y - y))
endfunction
// Returns the initial damage
private function getInitialDamage takes integer level returns real
return INITIAL_DAMAGE + (level * 50)
endfunction
// Returns the damage by the distance covered by the target
private function getDamage takes real distance, integer level returns real
return (distance * (20 * level)) / 100
endfunction
// Returns the affected units
private function affectedUnit takes unit target returns boolean
return true
endfunction
private keyword Init
private struct Spell extends array
// Caster
unit caster
// Rupture level
integer level
// Target
unit target
// a = actual (actual x of the target)
real a_x
// actual y
real a_y
// x of target
real x
// y of target
real y
// period, represents the period
// that the extra damage will be done (default: 0.25)
real period
implement CTL
// Distance covered by the target
local real distance
implement CTLExpire
// If the target still has the Rupture buff and is not dead...
if 0 < GetUnitAbilityLevel(this.target, RUPTURE_BUFF_RAWCODE) and not(IsUnitType(this.target, UNIT_TYPE_DEAD)) then
// If the PERIOD seconds was pass...
if PERIOD < this.period then
// Reset period to 0
set this.period = 0.
// Actual X and Y of the target
set this.a_x = GetUnitX(this.target)
set this.a_y = GetUnitY(this.target)
set distance = getDistance(this.x, this.y, this.a_x, this.a_y)
// Make the extra damage only if distance is greater than 0
// but less than 1300
if 0 < distance and distance < 1300 then
call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, ATTACHMENT_POINT))
call UnitDamageTarget(this.caster, this.target, getDamage(distance, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
// Refreshing the coordenates of the target
set this.x = this.a_x
set this.y = this.a_y
else
// If the PERIOD seconds was not pass, add
// the period of the CTL timer (library)
set this.period = this.period + 0.031250000
endif
else
set this.caster = null
set this.target = null
call this.destroy()
endif
implement CTLNull
implement CTLEnd
private static method run takes nothing returns boolean
local thistype this
// If the ability being cast is the Rupture and the target can be affected
// (units that can be affected can be changed in the affectedUnit function)
if GetSpellAbilityId() == RUPTURE_RAWCODE and affectedUnit(GetSpellTargetUnit()) then
set this = thistype.create()
set this.caster = GetTriggerUnit()
set this.target = GetSpellTargetUnit()
set this.level = GetUnitAbilityLevel(this.caster,RUPTURE_RAWCODE)
set this.x = GetUnitX(this.target)
set this.y = GetUnitY(this.target)
// Make the initial damage
call UnitDamageTarget(this.caster, this.target, getInitialDamage(this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
endif
return false
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t,Condition(function thistype.run))
// Preload the special effect...
static if PRELOAD then
call Preload(SPECIAL_EFFECT)
endif
set t = null
endmethod
endmodule
endlibrary