No Marlo no game.
Timers and how to pass data to them
This little tutorial aims to teach you how to use timers and make MUI spells and systems with them.
You need to have a nice knowledge about structs and Jass in general. This tutorial contains lots of vJass stuff, so you must have NewGen and newest JassHelper
Basic Timer Knowledge
Timer is a really usefull handle, because it gives us a control over the time itself. We can delay our actions or call some function in really fast periods, which is needed for stuff like moving units smoothly around.
List of useful timer functions:
In these days, we dont destroy timers anymore. They are usually recycled.
More about this later.
This starts our timer. Function handlerFunction is the function which is called when timer expires. Boolean periodic determines whether timer is paused after it expires, or does it keep calling the handlerFunction.
You can use this in handlerFunction. It gives you the timer, which is calling the handlerFunction, so you can for example pause it using PauseTimer.
There is more timer related functions in Jass, and you can find them using NewGens Function List.
The MUI problem
The problem with timers is that the actions are called in handlerFunction, which prevents us from using local variables. To make our spells and systems MUI, we need to somehow pass all needed data to the handlerFunction. There is several ways of doing this.
globals timer time=CreateTimer() endglobals function handler takes nothing returns nothing call KillUnit(u) // error, local unit u doesnt work here. // What should we do now? endfunction function start takes nothing returns nothing local unit u=GetTriggerUnit() call TimerStart(time,5.,false,function handler) endfunction
There is dozens cool timer and struct attachment -systems around there, which can do this job for you.
The purpose of this tutorial is not to teach you use those systems, but to learn few techniques you can use to do it by yourself.
I mention TimerUtils here as an example of a timer system.
For fast periodic timers you can use one timer with struct arrays. This is pretty easy technique, and Im now going to tell how it works.
The idea is to have all spell instances in a struct array and then loop through that array and call actions for all instances no matter whether there is 1 or 100 of them.
You should already know something about structs and how they work.
For this technique you need a timer, an integer and a Data array.
Adding Struct to array:
When you have created a new spell instance ( Data ), you need to add it to array D, so timer can start calling its actions.
Integer Total tells us how many active instances there is currently. Our first action is to check if Total is 0. This means that we need to start our timer.
if Total==0 then call TimerStart(T,0.03,true,function TimerLoop) endif
You better not to forget adding your struct to array.
After this we incerease Total, because there is now a new instance created.
Our handlerFunction is a function called TimerLoop. We need to loop through the array now.
You also need to do all the actions. If you are making a damage over time spell, you do your damage here. If you are making a knockback, this is where the units are moved.
One option is to have your actions in a method.
Removing instances from array:
When your spell is done, you need to remove that instance from array.
We also need to keep our array whole. When an instance is removed from array it leaves an empty spot. The best way to fix this spot, is to move our last array member to there..
We also need to decerease Total, because we are just removing an instance.
call D<i>.destroy() // dont forget to destroy your struct set Total=Total-1 // decerease total by one // If Total is greater than 0 we need to reorganize our array a bit if Total > 0 then set D<i>=D[Total] // Here we fill that empty spot. set i=i-1 // Loop index must also be decereased else // Total is 0 so there is no active instances call PauseTimer(T) // We can pause our timer endif </i></i>
Complete TimerLoop function:
private function TimerLoop takes nothing returns nothing local integer i=0 loop exitwhen i>=Total if D<i>.end then // end is a boolean which tells us when to stop calling actions call D<i>.destroy() set Total=Total-1 if Total > 0 then set D<i>=D[Total] set i=i-1 else call PauseTimer(T) endif else call D<i>.action() endif set i=i+1 endloop endfunction</i></i></i></i>
Using a struct array is a fast and safe method. It is an easy and good solution for periodic timers and you should use it whenever you can.
Attaching Data to Timers
There is still many situations where above mehod is not an option.
You might need a non periodic timer for waiting few seconds, or 0.0 timer for damage blocking.
In these situations, you need to attach your data to timer. There is many ways to do this, yet all of them are somehow based on famous H2I function.
This function uses wc3´s return bug and allows us to use any handles unique handle index.
The problem of hanlde indexes is that they are too big integers to be used as array indexes. LocArray[ H2I(loc) ] doesnt really work, but there is few solutions for that.
You can use some hashing algorithm or decerease handle id by wc3´s initial handle amount, which is 0x100000. ( Actutally BJ creates some extra handles too, but this should work )
You can search these timer systems from different wc3 forums and check what kind of methods they use for timer attaching.
This Timer System does pretty much the same thing TimerUtils (Red) does. This part aims to explain how a system like it works.
Some Basic stuff:
library TimerSystem globals //======================================================================== // wc3´s initial handle count. If your map has lots of preplaced stuff, // which incereases handle count, you should incerease this. private constant integer MIN_HANDLE_COUNT = 0x100000 // How many timers you are gonna need? // You should use one timer & struct array in most of cases, so you // should not need too many timers private constant integer TIMER_COUNT = 100 //======================================================================== // These are needed for timer recycling. private timer array Timers private integer Index endglobals // H2I is one of the coolest functions ever. private function H2I takes handle h returns integer return h return 0 endfunction endlibrary
In our initializer function, we fill our array with timers. Note that the amount of timers this system can support is limited. You need to know how many timers your need for your map.
This system requires you to use NewTimer and ReleaseTimer functions.
These functions handle the recycling.
function NewTimer takes nothing returns timer if Index==0 then // if index is 0 , all our 100 timers are in use. call BJDebugMsg("no more timers left") return null endif set Index=Index-1 // We use a same kind of array handling here than we return Timers[Index] // used with struct arrays endfunction function ReleaseTimer takes timer t returns nothing if t != null then call PauseTimer(t) // Pausing the timer is a good idea. set Timers[Index]=t // We return our timer back to array set Index=Index+1 // and incerease Index endif endfunction
Because all timers are created in map init, their handle id´s should be low enough to be converted to suitable array index´s. However, if you are creating a lot of other handles in map init, you might need a bigger number for MIN_HANDLE_COUNT. Suitable MIN_HANDLE_COUNT can be easily found by adding a simple debug msg to init function:
A function like this can be used to get timers index:
You can use this index as an array index. An easy way to make spells MUI.
globals private Data array D endglobals function Expire takes nothing returns nothing local timer t=GetExpiredTimer() local Data d = D[ GetTimerId(t) ] //.... call ReleaseTimer(t) endfunction function Start takes nothing returns nothing local Data d=Data.create() local timer t=NewTimer() //.... set D[ GetTimerId(t) ] = d endfunction
You can also have a one integer array for storing all struct types, and some nice GetTimerData and SetTimerData -functions.
Vexorian´s TimerUtils ( Red flavor ) uses this method. Studying that system is a good idea.
This kind of timer attaching system is really fast, because it only takes an array lookup, substraction and H2I call, but it is not very practical because of limited number of timers.
And still, it is really usefull when you cant use struct arrays for some reason.
library TimerSystem initializer Init globals //======================================================================== private constant integer MIN_HANDLE_COUNT = 0x100000 private constant integer TIMER_COUNT = 100 //======================================================================== private timer array Timers private integer Index endglobals private function H2I takes handle h returns integer return h return 0 endfunction function GetTimerId takes timer t returns integer return H2I(t)-MIN_HANDLE_COUNT endfunction function NewTimer takes nothing returns timer if Index==0 then call BJDebugMsg("no more timers left") return null endif set Index=Index-1 return Timers[Index] endfunction function ReleaseTimer takes timer t returns nothing if t != null then call PauseTimer(t) set Timers[Index]=t set Index=Index+1 endif endfunction private function Init takes nothing returns nothing local integer i=1 set Timers=CreateTimer() debug call BJDebugMsg("MIN_HANDLE_COUNT: "+I2S(H2I(Timers))) loop exitwhen i > TIMER_COUNT set Timers<i>=CreateTimer() set i = i + 1 endloop set Index=TIMER_COUNT+1 endfunction endlibrary</i>
This is my first tutorial, and I know that it will need a lot of editing before its complete. Comments and criticism are welcome.
I hope this helps those guys who want to learn about the use of timers.