[Tutorial] Temporary Ban Commands (MySQL, ZCMD, sscanf)
#1

Temporary Ban Command
I searched for temporary ban command tutorials on the forums, and I struggled to find any. I'm not sure if this is my fault for not using the search tools properly, or if there actually aren't any tutorials, but regardless, I'm going to attempt to teach you how to create a very simple temporary ban system using ZCMD, sscanf and MySQL. By no means is this the most efficient way of creating a banning system, but it works! Also, just a quick note, this tutorial is aimed at people who have at least some understanding of MySQL and how to manage a MySQL database and SA-MP (pawn) coding (& using phpMyAdmin etc.). Anyway, let's get started.

Includes

First of all, we need a few includes, and the plugins that go with them. You can download them all here:

ZCMD
sscanf
MySQL - I'm using R6 for this, as I'm not using threaded queries. If you know how to use threaded queries, by all means do so! (Download link has broken and I cannot find another download... sorry!)

I'm going to assume you know how to where to put the include and plugin files.

Now, we need to include them at the top of our script under the a_samp include:
pawn Код:
#include <a_samp>
#include <sscanf2>
#include <zcmd>
#include <a_mysql>
... and add the plugins to our server.cfg file.
pawn Код:
plugins sscanf mysql
Now, on to the coding.

Coding

First, we need to tell our script to connect to the MySQL database. You may have already done this if you're using a MySQL-based gamemode, so feel free to move on.

I'm going to connect to the database under OnGameModeInit:
pawn Код:
public OnGameModeInit()
{
    mysql_debug(); // I'm enabling the debug so if anything goes wrong, we can check it in the log file.
    mysql_connect("localhost", "root", "tutorial", ""); //Connecting to the database, using mysql_host, mysql_user, mysql_database and mysql_password.
    if(mysql_ping() < 1) //I'm checking the ping of the server to the MySQL server. If this is 0, the connection has failed.
    {
        print("MYSQL CONNECTION FAILED!");
    }
    else
    {
        print("MYSQL CONNECTION SUCCESSFUL!"); //Otherwise, the connection was successful.
    }
By default, your MySQL username should be root and your password shouldn't be set. If you've changed them, you can use your username and password.

Now, we can create the table in the database. We need to go to our MySQL database in phpMyAdmin (or another GUI if you don't use phpMyAdmin) and create a new table called "bans" with 7 columns:


We need to name them as follows:

Make sure to tick the auto increment box on ban_id so each ban has its own unique ID, you'll see why later.

Now, onwards to the command.

I'm going to use ZCMD and sscanf to create my command at the bottom of the gamemode file.

pawn Код:
CMD:tempban(playerid, params[])
{
    new banner[MAX_PLAYER_NAME], banned[MAX_PLAYER_NAME], bannedid, reason[25], hours, string[400], secs, unbants, ip[16], query[180];
Here, I've started to create the command and I've declared some variables. Let's break them down:
  • banner - the name of the administrator who performed the command
  • banned - the name of the player that is being banned
  • bannedid - the ID of the player that is being banned
  • reason - the reason, entered by the admin, for the ban
  • hours - the amount of hours the player will remain banned for
  • string - a string to hold the messages that will be sent to show the ban
  • secs - the hours converted to seconds
  • unbants - the timestamp for the time and date the player will be unbanned
  • ip - the player's ip address
  • query - the mysql query that we'll format
Those will all become clear soon.

Now we need to add some checks that will run when the administrator performs the command:

pawn Код:
if(!IsPlayerAdmin(playerid) && pData[playerid][Level] < 2) return SendClientMessage(playerid, ERROR, "You do not have sufficient permissions to use this command."); //this is just my admin check, yours may be different.
    if(sscanf(params, "uds[25]", bannedid, hours, reason)) return SendClientMessage(playerid, -1, "Usage: /tempban [player name/ID] [hours] [reason]"); // here, we're checking if the administrator entered the command correctly. "u" is a player name or ID, "d" is the amount of hours as an integer, and "s[25]" is the reason.
    if(!IsPlayerConnected(bannedid)) return SendClientMessage(playerid, -1, "Invalid player name/ID."); //checking if the player that the admin is trying to ban is connected to the server
    if(pData[playerid][Level] < pData[bannedid][Level]) return SendClientMessage(playerid, -1, "You cannot ban someone with a higher level than you!"); //I don't allow admins to ban those with higher levels than their own.
    if(strlen(reason) < 3 || strlen(reason) > 24) return SendClientMessage(playerid, -1, "Your reason must be between 3 and 24 characters."); //limiting the reason length to 3-24 characters
    if(hours < 12 || hours > 720) return SendClientMessage(playerid, -1, "Ban hours are between 12 and 720 hours"); //making sure the ban duration is no shorter than 12 hours and no longer than 30 days
Now we've made sure the command is infallible, we can create the main bulk of the command.

We need to save the administrator and the player's names in order to present them in a message later on, as well as the player's IP address, so I'll get it out of the way now:
pawn Код:
GetPlayerName(playerid, banner, sizeof(banner));
    GetPlayerName(bannedid, banned, sizeof(banned));
     GetPlayerIp(bannedid, ip, sizeof(ip));
pawn Код:
secs = hours*3600
Here, I have multiplied the inputted ban hours by the amount of seconds in one hour (3600) to convert the hours to seconds.

Now I can work out the unix timestamp at the time that the player will be unbanned by adding the current unix time to the amount of seconds that the player will be banned for. If you don't know what a unix timestamp is, it is the amount of seconds that have passed since the 1st of January, 1970.
pawn Код:
unbants = gettime()+ secs;
gettime() gets the current unix timestamp.

Now it's time to save our information to the MySQL database. We need to format the "query" variable:
pawn Код:
format(query, sizeof(query), "INSERT INTO `bans` (ban_id, player, ip, admin, reason, ban_time, unban_time) VALUES ('0', '%s', '%s', '%s', '%s', %d, %d)",
     banned, ip, banner, reason, gettime(), unbants);
    mysql_query(query);
Okay, we need to tell the player that they've been banned.
pawn Код:
format(string, sizeof(string), "You've been temporarily banned.\n\n{FFFFFF}Staff member: %s\nReason: %s", banner, reason);
    ShowPlayerDialog(bannedid, 678, DIALOG_STYLE_MSGBOX, "Banned!", string, "Close", "");
I'm showing them by using a dialog. You can present the information in any way you like, of course. Also, you can add more information, for example in my own server, I tell the player the time and date of the ban, and the exact time and date that they'll be able to connect again.

Also, I want to tell everybody else in the server that the person has been banned:
pawn Код:
format(string, sizeof(string), "%s(%d) has banned %s(%d) for %d hours for: %s", banner, playerid, banned, bannedid, hours, reason);
    SendClientMessageToAll(-1, string);
And finally, we need to kick the player. If we kick the player straight away, we won't give the server enough time to send the dialog through to the player, so we need to add a small delay before they're kicked:
pawn Код:
SetTimerEx("KickEx", 500, false, playerid, "i");
I added this timer to the command. This will add a timer of 500 seconds before the player is kicked. Now I can add the function that the timer will run when its time is up. I add this after the command:
pawn Код:
forward KickEx(playerid);
public KickEx(playerid)
{
    Kick(playerid);
    return 1;
}
There. That's the command finished. We're getting close to completing our ban system, but we're missing possibly the most important detail... stopping banned players from entering our server.

Ban Checking
When a player connects to the server, we need to check if their IP address is in our ban table, and if it is, we need to check if their ban has expired or if they need to remain banned.

First, under OnPlayerConnect, we'll declare two variables:
pawn Код:
new banquery[180], ip[16];
Now, we can get the player's IP address.
pawn Код:
GetPlayerIp(playerid, ip, 16);
Great. Now we need to run a query to search the table for an unban time against the player's IP address. This'll let us know if they're in the ban table:
pawn Код:
format(banquery, sizeof(banquery), "SELECT unban_time FROM `bans` WHERE ip = '%s'", ip);
    mysql_query(banquery);
    mysql_store_result();
If an entry was found, we need to check if the ban has expired or not. If it has expired, we let the player connect to the server. If it's still active, we kick the player:
pawn Код:
if(mysql_num_rows() != 0) //if an unban time was found with their ip address
    {
        new unbantimestamp = mysql_fetch_int(); //get the timestamp from the database
        if(unbantimestamp > gettime()) //if the unban time hasn't been reached yet
        {
            SendClientMessage(playerid, -1, "You're banned from this server.");
            SetTimerEx("KickEx", 500, false, playerid, "i");
        }
    }
You can add more of an interesting message there, of course.

You could also, using your knowledge gained from this tutorial, create an unban command which deactivates bans, hence the unique ban IDs.

Well, that's basically it. I don't think I've forgotten anything, but feel free to leave a reply if there's something I didn't explain very well. This is my first tutorial afterall.

Thanks.
Reply
#2

Can I have Y_INI to save/load all the data and MYSQL to load/save only the bans?
Reply
#3

Quote:
Originally Posted by AdamsP
Посмотреть сообщение
Can I have Y_INI to save/load all the data and MYSQL to load/save only the bans?
Bump, anyone??
Reply
#4

Yes, but isn't recommended, if you already have a database save it all as mysql.
Reply
#5

Quote:
Originally Posted by xVIP3Rx
Посмотреть сообщение
Yes, but isn't recommended, if you already have a database save it all as mysql.
Not recommended by who other than yourself? And why is it not recommended? I would said it's perfectly recommended if you feel like MySQL is too difficult. Y_INI is great, and I'm not even biased - I use BlueG's MySQL for my scripts.
Reply
#6

It's not recommended by most programmers to save one thing in a database and the other in files.
Either completely use a database, or keep using files.

I also had issues learning MySQL, as I figured installing a 500Mb+ package to hold only a few hundred kilobytes on data was just stupid.

But I went MySQL all the way now, and I find it alot easier now, compared to using files.
Also, certain things you can't simply do with files.

Gathering a list of players with the most money (getting the 25 richest players on your server, even the offline ones for example) is virtually impossible with files.
And if you can do it, it will certainly add alot of lag to your server, as you would need to open all player-files, read them all to read the money, sort them yourself manually and keep only the 25 richest players.
It will add alot of lag, as this cannot be threaded.
The script needs to wait to run other code until all files have been read, data sorted and such.

All that would also require alot of code.

Using MySQL, it's only a matter of creating one simple query like this:
pawn Код:
SELECT Name FROM players ORDER BY Money DESC LIMIT 25
This gathers the playernames (Name) from the table "players", orders it in a descending order using the "Money" column and gathers only 25 rows.
By ordering them descending, your sorting them from richest to poorest.

This single line replaces your entire loop to read all player-files line by line to find the money of each player, it also replaces your entire sort-function to sort them.

Now you only need a loop to read the data from the result and display it.
Pseudo-code (really short code):
pawn Код:
new DialogContents[2000], NameToAdd[24];
for (new Row; Row < RowsFromResult; Row++)
{
    cache_get_field_content(Row, "Name", NameToAdd, SQL_connection, 24); // Read the Name from each row and put it inside variable NameToAdd
    format(DialogContents, sizeof(DialogContents), "%s%s\n", DialogContents, NameToAdd); // Add the name to a string, which will be used inside the dialog for showing
}
ShowPlayerDialog(playerid, 100, DIALOG_STYLE_MSGBOX, "Richest players:", DialogContents, "OK");
No need to do the sorting manually, no need to open all files and read them line by line.

And the best of it: it won't lag at all, as you can use threaded queries.
MySQL will gather the data in the background while your script continues to run other stuff.
When MySQL has gathered all the data, your callback (which you specified) will be called automatically, and inside the callback you read the results and display it.

As you see, you only need one query and only about 7 lines of code to get all that data, sort it and display it.
And in a threaded way so you won't get any lag.

Try the same with files (8 lines of code to read all your files, sort them descending by each player's money value, keep only 25 results and display it).
Reply
#7

Thank you guys

I have one more question; how to make the command as so to temp.ban someone if I want for minutes and hours? It would be really useful to temp.ban someone for like 5'if he's annoying instead of kick.
Reply
#8

Nice Tutorial...
Reply
#9

nice
Reply
#10

Sorry, but dont consider this spam.... How can i convert that unbantimestamp to days... i mean i want to tell player for how many days he's banned (how many days are remaining)
Reply
#11

Nick.D.
First of all, why MySQL? Are you planning to synchronize this database with a website? That would be not really useful for temporary bans.
Also, since you are using MySQL, you could simply create those tables within SA:MP.
And as last, file writing is the best way for SA:MP (Y_INI/DJSon). But if you still choose SQL, you should use SQLite. SA:MP already supports that, less server memory (it does not have to load a separate MySQL plugin), and it is actually faster.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)