Due to a new release of a map, where i have implemented a very nice counter, i have set up a tutorial, which actually shows you on how to count in ET very effectively. As the counter in my map is firing every single second, and that into two seperate timer blocks for the axis and the allies, this timer had to be really good.
So i set up a special system which is described below. Im not long in this community and therefore probably might have missed a map where this also is implemented, but im pretty sure that something like this, that effective, hasnt been done before, as there are not many maps having a timer.
However enjoy. It might come in handy when you really have to make yourself some thoughts about performance issues.
Advanced Timer Tutorial
With this im gonna show you a very advanced method of implementing a timer or some sort of counter in ET. We need a func_timer for this, which triggers a target_script_trigger respectively the script every second.
One can imagine that such a timer, firing every second and reaching the mapscript to do some logic in there, can be very cpu intense, therefore the script needs to execute as few instructions in average per second as possible, and best would be if the memory used, that means the accums in use, are reduced to a minimum.
So now one can think of several methods. One would be to simply use an accum and count it up to the limit. But this is not what we want. Most of the time we want a script which points access to every different second ever passing in that time period to use remapshader commands or other stuff to make that timer actually say something. For example letting it say: 5 minutes left, 4 minutes left etc. For our example we simply want our counter to remap every second another pic, which somehwere in the map is visible.
So the most common way to achieve this would be to divide your system logically into seconds, 10th seconds, minutes etc and using an accum for each of this ‘logic blocks’. Could look like this:
block
{
spawn
{
accum 0 set 0 //seconds
accum 1 set 0 //10th seconds
accum 2 set 0 //minutes
}
//this block gets triggered by the counter each second
trigger counter
{
accum 0 inc 1
accum 0 trigger_if_equal 1 self sec1
accum 0 trigger_if_equal 2 self sec2
accum 0 trigger_if_equal 3 self sec3
accum 0 trigger_if_equal 4 self sec4
accum 0 trigger_if_equal 5 self sec5
accum 0 trigger_if_equal 6 self sec6
accum 0 trigger_if_equal 7 self sec7
accum 0 trigger_if_equal 8 self sec8
accum 0 trigger_if_equal 9 self sec9
accum 0 trigger_if_equal 10 self sec10
}
trigger sec1
{
}
trigger sec2
{
}
trigger sec3
{
}
trigger sec4
{
}
trigger sec5
{
}
trigger sec6
{
}
trigger sec7
{
}
trigger sec8
{
}
trigger sec9
{
}
trigger sec10
{
accum 0 inc -10
accum 1 inc 1
accum 1 trigger_if_equal 1 self sec10
accum 1 trigger_if_equal 2 self sec20
accum 1 trigger_if_equal 3 self sec30
accum 1 trigger_if_equal 4 self sec40
accum 1 trigger_if_equal 5 self sec50
accum 1 trigger_if_equal 6 self sec60
}
trigger sec10
{
}
trigger sec20
{
}
trigger sec30
{
}
trigger sec40
{
}
trigger sec50
{
}
trigger sec60
{
accum 1 inc -6
accum 2 inc 1
trigger accum 2 trigger_if_equal 1 self min1
trigger accum 2 trigger_if_equal 1 self min2
trigger accum 2 trigger_if_equal 1 self min3
trigger accum 2 trigger_if_equal 1 self min4
trigger accum 2 trigger_if_equal 1 self min5
}
trigger min1
{
}
trigger min2
{
}
trigger min3
{
}
trigger min4
{
}
trigger min5
{
}
}
May be that there is a fault somewhere, but thats not so much of a problem here. Only the concept should be shown.
So you see i now have blocks (sec0, sec1,…,sec00,…min0) where i could do stuff depending on the seconds, for example remapshadering a pic from 1 to 2 in block sec1.
So whats the problem with that method:
- it uses too much memory aka accums
- the average amount of add commands in worst case is 5.
- the average amount of trigger/jump instructions in worst case 21.
If our counter is really doing this every second, then this is way too much usage everywhere, making the script run slower and probably causing lag.
A possible solution to reduce point 3 could be using binary search. Binary search is a well known algorithm by programmers and there are alot of things in the IT World based on that concept. But using binary search in our method above would be at the cost of an additional add command + an additional accum. So basically binary search is a good idea, but not with the system used above. We need to find a workaround.
So what would be cool to have ? Well, reducing the amount of accums in use to a single one, reducing the amount of add commands to using a single one, and also if possible reducing the amount of jump instructions to one.
Ok. The bad news first. I was not able to reduce the amount of jump instructions to a single one. And most likely thats not possible due to the limits of this scripting language. But for point 2 and 1 i have good news. Can definitely be done and that very effectively.
So on achieving that goal and implementing a timer which is working the same way as shown in the example above, but faster and better, and less memory intense, one will have to use some neat tricks. The whole concept of the system we will use is based on the bitset and bitreset commands, using binary search.
We will gonna use our accum as follows:
bit 0 - 9 represents the seconds, whereas bit 0 = second 9, bit 9 = second 0
bit 10 - 15 represents the 10th seconds, bit 0 = 50th, bit 15 = 00th
bit 16 - 21 represents the minutes, bit 16 = minute 5, bit 21 = minute 0
bit 22 - 23 binary search seconds
bit 24 - 25 binary search 10th seconds
bit 26 - 27 binary search minutes
Im not sure about the virtual machine concept used within the Q3 engine, but using this on a 16 bit machine would probably crash the server. Or at least make it run slower. Better using a 32 bit server system for this.
So you see we already reduced our system to one single accum representing the time. For example the number…
0000 01 10 10 000 001 100 000 10000 00000
|
Bit
31
…means we are in second 0, 00th seconds, and in minute 5, counting backwards. I used that in my map One Way Beta3. So setting our accum to 109150720 would set that representation here.
Now for reducing the amount of add commands one must make oneself clear that the whole system is based on bitset and bitreset commands. And if one would use the plain set commands for this one would probably end up like in the script above, with a bunch of multiple bitset and bitreset commands, making our script slower. So the workaround for this is using one single add command instead. There is a whole theory behind that, id advice you to really draw yourself on paper what one is actually doing when reducing a bitset and bitreset command to a single add command. Basically if you want to bitset bit x and bitreset bit y you have to add a value of (2^x -2^y) to your actual accum. Other bits are not affected that way, thats also what one has to make oneself clear. And what also is cool that we could reduce multiple, theoretically an infinite amount of bitset and bitreset commands to one single add command.
So basically what you now have to do is setting up your system based on that bitset and bitreset principle, with an accum based on single bits for timer representation and then use single add commands and the binary search algorithm to make it run faster.
I hope you liked this tutorial. Below you have a copy of a working counter. It is complete, as there is also some code handling remapshader in there.
And sorry i was too lazy to explain the binary search for now, but with a little bit of thinking you will do this on your own. Credit me :>
Greetings
Qualmi
counter
{
spawn
{
wait 500
accum 0 set 109150720
}
trigger count_al
{
trigger self sec9-5
trigger self sec4-0
}
trigger sec9-5
{
accum 0 abort_if_not_bitset 22
trigger self sec9
trigger self sec8
trigger self sec7
trigger self sec6
trigger self sec5
}
trigger sec4-0
{
accum 0 abort_if_not_bitset 23
trigger self sec4
trigger self sec3
trigger self sec2
trigger self sec1
trigger self sec0
}
trigger sec9
{
accum 0 abort_if_not_bitset 0
accum 0 inc 1
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies9"
remapshaderflush
resetscript
}
trigger sec8
{
accum 0 abort_if_not_bitset 1
accum 0 inc 2
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies8"
remapshaderflush
resetscript
}
trigger sec7
{
accum 0 abort_if_not_bitset 2
accum 0 inc 4
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies7"
remapshaderflush
resetscript
}
trigger sec6
{
accum 0 abort_if_not_bitset 3
accum 0 inc 8
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies6"
remapshaderflush
resetscript
}
trigger sec5
{
accum 0 abort_if_not_bitset 4
accum 0 inc 4194320
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies5"
remapshaderflush
resetscript
}
trigger sec4
{
accum 0 abort_if_not_bitset 5
accum 0 inc 32
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies4"
remapshaderflush
resetscript
}
trigger sec3
{
accum 0 abort_if_not_bitset 6
accum 0 inc 64
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies3"
remapshaderflush
resetscript
}
trigger sec2
{
accum 0 abort_if_not_bitset 7
accum 0 inc 128
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies2"
remapshaderflush
resetscript
}
trigger sec1
{
accum 0 abort_if_not_bitset 8
accum 0 inc 256
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies1"
remapshaderflush
resetscript
}
trigger sec0
{
accum 0 abort_if_not_bitset 9
remapshader "gfx/hud/ic_stamina" "textures/one_way_b2t/scoreboard/s_allies0"
remapshaderflush
trigger self sec50-30
trigger self sec20-00
}
trigger sec50-30
{
accum 0 abort_if_not_bitset 24
trigger self sec50
trigger self sec40
trigger self sec30
}
trigger sec20-00
{
accum 0 abort_if_not_bitset 25
trigger self sec20
trigger self sec10
trigger self sec00
}
trigger sec50
{
accum 0 abort_if_not_bitset 10
accum 0 inc -4193791
resetscript
}
trigger sec40
{
accum 0 abort_if_not_bitset 11
accum 0 inc -4192767
resetscript
}
trigger sec30
{
accum 0 abort_if_not_bitset 12
accum 0 inc 12586497
resetscript
}
trigger sec20
{
accum 0 abort_if_not_bitset 13
accum 0 inc -4186623
resetscript
}
trigger sec10
{
accum 0 abort_if_not_bitset 14
accum 0 inc -4178431
resetscript
}
trigger sec00
{
accum 0 abort_if_not_bitset 15
trigger self min5-3
trigger self min2-0
}
trigger min5-3
{
accum 0 abort_if_not_bitset 26
trigger self min5
trigger self min4
trigger self min3
}
trigger min2-0
{
accum 0 abort_if_not_bitset 27
trigger self min2
trigger self min1
trigger self min0
}
trigger min5
{
accum 0 abort_if_not_bitset 16
accum 0 inc -20938239
alertentity printer_al_timeleft5
wm_announce "Minutes Left ~> ^45"
resetscript
}
trigger min4
{
accum 0 abort_if_not_bitset 17
accum 0 inc -20872703
setstate m_allies5 invisible
setstate m_allies4 default
wm_announce "Minutes Left ~> ^44"
resetscript
}
trigger min3
{
accum 0 abort_if_not_bitset 18
accum 0 inc 46367233
setstate m_allies4 invisible
setstate m_allies3 default
alertentity printer_al_timeleft3
wm_announce "Minutes Left ~> ^43"
resetscript
}
trigger min2
{
accum 0 abort_if_not_bitset 19
accum 0 inc -20479487
setstate m_allies3 invisible
setstate m_allies2 default
wm_announce "Minutes Left ~> ^42"
resetscript
}
trigger min1
{
accum 0 abort_if_not_bitset 20
accum 0 inc -19955199
setstate m_allies2 invisible
setstate m_allies1 default
alertentity printer_al_timeleft1
wm_announce "Minutes Left ~> ^41"
resetscript
}
trigger min0
{
accum 0 abort_if_not_bitset 21
accum 0 inc -86015487
trigger game_manager endgame_al
}
}