Advanced Timer Tutorial


(Qualmi) #1

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:

  1. it uses too much memory aka accums
  2. the average amount of add commands in worst case is 5.
  3. 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
    }
}


(stealth6) #2

accum 0 set 109150720

:smiley: :smiley:

That’s one crazy script

you could do it alot easier with 3 accums

minutes, seconds (tens), seconds (ones)

oopsie just read that part you wrote lol!

  1. it uses too much memory aka accums
  2. the average amount of add commands in worst case is 5.
  3. the average amount of trigger/jump instructions in worst case 21.
  1. lol
  2. lol
  3. lol

(Loffy) #3

Could you give a practical example, of what could be accomplished with this type of script, so that not-so-smart guys like me can appreciate the work and the potential here. Thx!


(YourFather_CZ) #4

Another example of timer
It is cutting-down version of timer.
It’s easy readable and it can be really easily customized without deep knowledge.

beach_flag
{

	spawn
	{

		accum 1 set 0 // Is timer enabled? (No: 0) | (Yes: 1)
		accum 2 set 0 // Is timer running? (No: 0) | (Yes: 1)

	}
	

	trigger axis_capture
	{
	
		trigger self timer_stop 
		
	}

	trigger allied_capture
	{
		
		trigger self timer_start 
		
	}
	
	// ********* ENABLE timer ********* //
	trigger timer_enable 
	{
	
	    accum 1 set 1
		
	}
	
	// ********* DISABLE timer ********* //
	trigger timer_disable // 
	{
	
	    accum 1 set 0
		
	}
	
	// ********* STOPS timer ********* //
	trigger timer_stop
	{
         accum 2 set 0
	}
	
	// ********* STARTS timer ********* //
	trigger timer_start
	{

		accum 1 abort_if_equal 0 // Aborts if timer is NOT enabled.
		accum 2 abort_if_equal 1 // Aborts if timer has already started.
		accum 2 set 1 // Properly sets accum for checking run state of timer
		
		//  0 seconds since start | 150 seconds remains
		wm_announce "Allied Forward Bunker will be reinforced in 2 minutes and 30 seconds."
		
		wait 30000
		//  30 seconds since start | 120 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker will be reinforced in 2 minutes."
		
		wait 30000
		//  60 seconds since start | 90 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker will be reinforced in 1 minutes and 30 seconds."
		
		wait 30000
		//  90 seconds since start | 60 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker will be reinforced in 1 minute."
	    
		wait 30000
		// 120 seconds since start | 30 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker will be reinforced in 30 seconds."
		
		wait 10000
		// 130 seconds since start | 20 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker will be reinforced in 20 seconds."
		
		wait 10000
		// 140 seconds since start | 10 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker will be reinforced in 10 seconds."
		
		wait 5000
		// 145 seconds since start | 5 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker will be reinforced in 5 seconds."
		
		wait 5000
		// 150 seconds since start | 0 seconds remains
		accum 1 abort_if_equal 0
		wm_announce "Allied Forward Bunker has been reinforced."
		
	}

}

This timer I’ve made for mapscript of Beach Invasion (et_beach). (see lines 722 to 817)

This mapscript is attached to this post in ZIP archive. It contains mapscript file and required .pk3 file.
Changelog is inside of mapscript file.
It works fine with NoQ 1.2.7 and ETPro 3.2.6 (other mods untested).


(zstarsales04) #5

Hello, everybody, I am new here. Here is something might be helpful for you.www.zstar.hk www.edgei-ds.cn (many movies to download)www.tigersupermall.com