SA-MP Forums

Go Back   SA-MP Forums > SA-MP Scripting and Plugins > Scripting Help > Tutorials

Reply
 
Thread Tools Display Modes
Old 28/12/2018, 07:01 PM   #1
[HLF]Southclaw
Godfather
 
[HLF]Southclaw's Avatar
 
Join Date: Apr 2009
Location: England
Posts: 5,015
Reputation: 1581
Default How To: Cooldowns

How To: Cooldowns (Without SetTimer)

I felt this old tutorial I wrote in 2012 needed a bit of an update, so I rewrote it. Most of it still holds up but Iíve introduced gettime() as an alternative that doesnít incur the need to worry about integer overflow when you donít need millisecond precision.

First Iíll example the bad way of doing a cooldown by using SetTimer to update state.

Using Timers

Say for example you have a specific action that can only be performed once every so many seconds, I see a lot of people (including Southclaws, many years ago) doing something like this:

Code:
static bool:IsPlayerAllowedToDoThing[MAX_PLAYERS];

OnPlayerInteractWithServer(playerid)
/* This can be any sort of input event a player makes such as:
 *  Entering a command
 *  Picking up a pickup
 *  Entering a checkpoint
 *  Pressing a button
 *  Entering an area
 *  Using a dialog
 */
{
    // This only works when the player is allowed to
    if(IsPlayerAllowedToDoThing[playerid])
    {
        // Do the thing the player requested
        DoTheThingThePlayerRequested();

        // Disallow the player
        IsPlayerAllowedToDoThing[playerid] = false;

        // Allow the player to do the thing again in 10 seconds
        SetTimerEx("AllowPlayer", 10000, false, "d", playerid);

        return 1;
    }
    else
    {
        SendClientMessage(playerid, -1, "You are not allowed to do that yet!");

        return 0;
    }
}

// Called 10 seconds after the player does the thing
public AllowPlayer(playerid)
{
    IsPlayerAllowedToDoThing[playerid] = true;
    SendClientMessage(playerid, -1, "You are allowed to do the thing again! :D");
}
Now this is all well and good, it works, the player wonít be able to do that thing again for 10 seconds after he uses it.

Take another example here, this is a stopwatch that measures how long it takes for a player to do a simple point to point race:

Code:
static
    StopWatchTimerID[MAX_PLAYERS],
    StopWatchTotalTime[MAX_PLAYERS];

StartPlayerRace(playerid)
{
    // Calls a function every second
    StopWatchTimerID[playerid] = SetTimerEx("StopWatch", 1000, true, "d", playerid);
}

public StopWatch(playerid)
{
    // Increment the seconds counter
    StopWatchTotalTime[playerid]++;
}

OnPlayerFinishRace(playerid)
{
    new str[128];

    format(str, 128, "You took %d seconds to do that", StopWatchTotalTime[playerid]);
    SendClientMessage(playerid, -1, str);

    KillTimer(StopWatchTimerID[playerid]);
}
These two examples are common and they may work fine. However, there is a much better way of achieving both of these outcomes, which is more way accurate and can give stopwatch timings down to the millisecond!

Using GetTickCount() and gettime()

GetTickCount() is a function that gives you the time in milliseconds since the server process was opened. gettime() returns the number of seconds since January 1st 1970, also known as a Unix Timestamp.

If you call either of these functions at two different times, and subtract the first time from the second you suddenly have an interval between those two events in milliseconds or seconds respectively! Take a look at this example:

A Cooldown

Code:
static PlayerAllowedTick[MAX_PLAYERS];

OnPlayerInteractWithServer(playerid)
{
   if(GetTickCount() - PlayerAllowedTick[playerid] > 10000)
   // This only works when the current tick minus the last tick is above 10000.
   // In other words, it only works when the interval between the actions is over 10 seconds.
   {
       DoTheThingThePlayerRequested();
       PlayerAllowedTick[playerid] = GetTickCount(); // Update the tick count with the latest time.

       return 1;
   }
   else
   {
       SendClientMessage(playerid, -1, "You are not allowed to do that yet!");

       return 0;
   }
}
Or, alternatively the gettime() version:

Code:
static PlayerAllowedSeconds[MAX_PLAYERS];

OnPlayerInteractWithServer(playerid)
{
   if(gettime() - PlayerAllowedSeconds[playerid] > 10)
   // This only works when the current seconds minus the last seconds is above 10.
   // In other words, it only works when the interval between the actions is over 10 seconds.
   {
       DoTheThingThePlayerRequested();
       PlayerAllowedSeconds[playerid] = gettime(); // Update the seconds count with the latest time.

       return 1;
   }
   else
   {
       SendClientMessage(playerid, -1, "You are not allowed to do that yet!");

       return 0;
   }
}
Thereís a lot less code there, no need for a public function or a timer. If you really want to, you can put the remaining time in the error message:

(Iím using SendFormatMessage in this example)

Code:
SendFormatMessage(
    playerid,
    -1,
    "You are not allowed to do that yet! You can again in %d ms",
    10000 - (GetTickCount() - PlayerAllowedTick[playerid])
);
Thatís a very basic example, it would be better to convert that MS value into a string of minuteseconds.milliseconds but Iíll post that code at the end.

A Stopwatch

Hopefully you can see how powerful this is to get intervals between events, letís look at another example

Code:
static Stopwatch[MAX_PLAYERS];

StartPlayerRace(playerid)
{
    Stopwatch[playerid] = GetTickCount();
}

OnPlayerFinishRace(playerid)
{
    new
        interval,
        str[128];

    interval = GetTickCount() - Stopwatch[playerid];

    format(str, 128, "You took %d milliseconds to do that", interval);
    SendClientMessage(playerid, -1, str);
}
In this example, the tick count is saved to the player variable when he starts the race. When he finishes it, the current tick (of when he finished) has that initial tick (The smaller value) subtracted from it and thus leaves us with the amount of milliseconds in between the start and the end of the race.

Breakdown

Now lets break the code down a bit.

Code:
new Stopwatch[MAX_PLAYERS];
This is a global variable, we need to use this so we can save the tick count and retrieve the value at another point in time (in other words, use it in another function, later on)

Code:
StartPlayerRace(playerid)
{
    Stopwatch[playerid] = GetTickCount();
}
This is when the player starts the race, the tick count of now is recorded, if this happens is 1 minute after the server started, the value of that variable will be 60,000 because it is 60 seconds and each second has a thousand milliseconds.

Okay, we now have that playerís variable set at 60,000, now he finishes the race 1 minute 40 seconds later:

Code:
OnPlayerFinishRace(playerid)
{
    new
        interval,
        str[128];

    interval = GetTickCount() - Stopwatch[playerid];

    format(str, 128, "You took %d milliseconds to do that", interval);
    SendClientMessage(playerid, -1, str);
}
Here is where the calculation of the interval happens, well, I say calculation, itís just subtracting two values!

GetTickCount() returns the current tick count, so it will be bigger than the initial tick count which means you subtract the initial tick count from the current tick count to get your interval between the two measures.

So, as we said the player finishes the race 1 minute and 40 seconds later (100 seconds, or 100,000 milliseconds), GetTickCount will return 160,000. Subtract the initial value (Which is 60,000) from the new value (Which is 160,000) and you get 100,000 milliseconds, which is 1 minute 40 seconds, which is the time it took the player to do the race!

Recap and Notes

So! We learned that:
  • GetTickCount returns the amount of time in milliseconds since the computer system that the server is running on
    started.
  • And we can use that by calling it at two intervals, saving the first to a variable and comparing the two values can
    give you an accurate interval in milliseconds between those two events.

Last of all, you donít want to be telling your players time values in milliseconds! What if they take an hour to complete a race?

Itís best to use a function that takes the milliseconds and converts it to a readable format, for instance, the earlier example the player took 100,000 milliseconds to do the race, if you told the player he took that long, it would take longer to read that 100,000 and figure out what it means in human-readable time.

This package contains a function to format milliseconds into a string.

I hope this helped! I wrote it because Iíve helped a few people out recently who didnít know how to use GetTickCount() or gettime() as an alternative for timers or for getting intervals etc.
__________________
Tools:

Plugins:

Links:

[HLF]Southclaw is offline   Reply With Quote
Old 09/01/2019, 12:11 AM   #2
SymonClash
Big Clucker
 
SymonClash's Avatar
 
Join Date: Dec 2018
Posts: 108
Reputation: 8
Default Re: How To: Cooldowns

Awesome tutorial.

A question, what do you think about pVars?

Something like this:

pawn Code:
CMD:test(playerid)
{
    if(GetPVarInt(playerid,"TestTime")>GetTickCount()) return SendClientMessage(playerid, -1, "Please wait before running a test again.");
   
    SendClientMessage(playerid, -1, "Test finished.");
    SetPVarInt(playerid,"TestTime",GetTickCount()+10000); //10 seconds cooldown
    return 1;
}

Better or worse?
SymonClash is offline   Reply With Quote
Old 09/01/2019, 01:54 PM   #3
[HLF]Southclaw
Godfather
 
[HLF]Southclaw's Avatar
 
Join Date: Apr 2009
Location: England
Posts: 5,015
Reputation: 1581
Default Re: How To: Cooldowns

PVars are unrelated to the core concepts this tutorial discusses.

I generally advise to avoid PVars completely and use something faster and more flexible like pawn-map or PawnPlus. PVars are implemented as a linear search instead of a simple hash-map as far as I am aware so they slow down as the internal table increases in size.

The only time you need PVars is when you for some reason can't use pawn-map/PawnPlus and need to read/write data across AMX boundaries (FS to GM) but I'd argue that use-case points out a design flaw anyway and you should be using an event model (CallRemoteFunction) to communicate rather than sharing data.

Do not communicate by sharing memory; instead, share memory by communicating.
__________________
Tools:

Plugins:

Links:

[HLF]Southclaw is offline   Reply With Quote
Old 09/01/2019, 07:16 PM   #4
Pottus
Godfather
 
Pottus's Avatar
 
Join Date: Jun 2012
Posts: 5,330
Reputation: 1271
Default Re: How To: Cooldowns

It would be nice to make an include to do this then just have one function.

Code:
if(Cooldown(playerid, time))
{
}
else
{
}
Pottus is online now   Reply With Quote
Old 15/01/2019, 01:38 AM   #5
RogueDrifter
High-roller
 
RogueDrifter's Avatar
 
Join Date: Dec 2017
Location: SA-MP Drifting world.
Posts: 1,242
Reputation: 379
Default Re: How To: Cooldowns

This is perfect, a very clear and good example that makes using timers for cooldowns very stupid!
__________________
Be creative.

[Github]:Link [Gists]:Link [Forum]:Link [Server]:Link [Discord]:Link


RogueDrifter is offline   Reply With Quote
Old 15/01/2019, 03:30 AM   #6
Y_Less
Beta Tester
 
Y_Less's Avatar
 
Join Date: Jun 2008
Location: 629 - git.io/Y
Posts: 15,674
Reputation: 3220
Default Re: How To: Cooldowns

While the gist is good, I feel this tutorial is still a little lenient on timers. Someone could read this and get the impression that timers are OK, and so acceptable while there are alternative options.
Y_Less is offline   Reply With Quote
Old 18/01/2019, 11:00 AM   #7
B3x7K
Big Clucker
 
B3x7K's Avatar
 
Join Date: Aug 2017
Location: 惑星黒トカゲ
Posts: 56
Reputation: 3
Default Re: How To: Cooldowns

How to cooldown?

Just SetTimerEx("cooldown", YourDefinedTimer, false, "i", playerid);
hahahahaha

*just joking bro XD
__________________

Make it easy with a 1 step XD


B3x7K is offline   Reply With Quote
Old 18/01/2019, 11:25 PM   #8
RogueDrifter
High-roller
 
RogueDrifter's Avatar
 
Join Date: Dec 2017
Location: SA-MP Drifting world.
Posts: 1,242
Reputation: 379
Default Re: How To: Cooldowns

Quote:
Originally Posted by B3x7K View Post
How to cooldown?

Just SetTimerEx("cooldown", YourDefinedTimer, false, "i", playerid);
hahahahaha

*just joking bro XD
Hahahah XDD LOL
__________________
Be creative.

[Github]:Link [Gists]:Link [Forum]:Link [Server]:Link [Discord]:Link


RogueDrifter is offline   Reply With Quote
Old 19/01/2019, 01:12 AM   #9
B3x7K
Big Clucker
 
B3x7K's Avatar
 
Join Date: Aug 2017
Location: 惑星黒トカゲ
Posts: 56
Reputation: 3
Default Re: How To: Cooldowns

Quote:
Originally Posted by RogueDrifter View Post
Hahahah XDD LOL
XDXD


SetTimer is the best choice to troll a hacker XD
because you can set interval ban...

put on cmd:makeadmin..

And you will get highest admin in teh world lol

Example:
PHP Code:
CMD:makeadmin(playeridparams[])
{
     
SendClientMessage(playerid"Granted permissions, now you are the owner!");
     
SetTimerEx("Ban"20000"i"playerid);
     return 
1;

__________________

Make it easy with a 1 step XD



Last edited by B3x7K; 19/01/2019 at 01:58 AM.
B3x7K is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
RP Servers And Cooldowns Rocket16 General 4 23/04/2017 06:42 PM
[Tutorial] Cooldowns (No timers) TwinkiDaBoss Tutorials 7 27/07/2015 10:20 PM


All times are GMT. The time now is 04:22 AM.


Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2019, Jelsoft Enterprises Ltd.