[Tutorial] MySQL Account and Admin System
#1

[CENTER]
MySQL Account and Admin System
Welcome to my tutorial on a MySQL account system, First off you'll need either:
  1. Wamp
  2. Xampp
  3. Or a host.
You will need these plugins too:
  1. ZCMD
  2. SSCANF
  3. MySQL (R37)
  4. Whirlpool
Right now we can begin.

pawn Код:
#include <a_samp>
#include <a_mysql>
#include <zcmd>
#include <sscanf2>

native WP_Hash(buffer[], len, const str[]);
First you include the includes you just downloaded and put that native in there. It is for Whirlpool. It's the format for the hashing of password that will be done later on.

pawn Код:
#define SQL_HOST "localhost"
#define SQL_USER "root"
#define SQL_PASS ""
#define SQL_DATABASE "tester"

new SQL = -1;
Now we define the MySQL details and the Connection handle. The value of SQL is irrelevant, we'll be setting it to something else shortly.

pawn Код:
enum pInfo {
    ID,
    Name[MAX_PLAYER_NAME],
    Password[129],
    Level,
    Admin,
    AdminTitle[64],
    Float:Health,
    Float:Armour,
    bool:IsLoggedIn,
    bool:IsRegistered,
    LoginAttempts,
   
   
}
   
new Player[MAX_PLAYERS][pInfo];
Here we define all of our player variables in an enum. We assign these to the player later on. E.g Player[playerid][Health] sets the players health to the value of Player[playerid][Health].

The bools are variables that can only be set to either true (1) or false (0). The floats are values that allow decimals.

pawn Код:
new MySQLCheck[MAX_PLAYERS];
This is the check we use to determine if it's the same player that is connected so that we don't assign the wrong data to the wrong player. It'll come into use later on.

pawn Код:
enum dialogs {
    DIALOG_REGISTER,
    DIALOG_LOGIN,
    DIALOG_UNUSED,
}
The above enum is to define all the dialogs. These will be used later on aswell.

pawn Код:
public OnGameModeInit()
{

    SetGameModeText("MySQL / Admin System v0.1");
    SQL = mysql_connect(SQL_HOST, SQL_USER, SQL_DATABASE, SQL_PASS);
    mysql_debug(1);
    SetupPlayerTable();
   
    return 1;
}
Here we set the gamemode text (As you would normally), We assign a value to SQL. The SQL is the connection handle. This means that if we were to use a query later on we can put SQL so that it know that it is to be performed on that connection.

pawn Код:
public OnGameModeExit()
{
    mysql_close();
    return 1;
}
Here we just close the connection.

pawn Код:
public OnPlayerConnect(playerid)
{
    new query[128];//New query being defined.
    MySQLCheck[playerid]++;Here is our check being put into place.
    for(new pInfo:e; e < pInfo; ++e)//Here we reset the playerdata to make sure no wrong data is assigned.
        Player[playerid][e] = 0;
       
    GetPlayerName(playerid, Player[playerid][Name], MAX_PLAYER_NAME);// We assign the players name to a the variable, Player[playerid][Name].
   
    mysql_format(SQL, query, sizeof(query), "SELECT * FROM `players` WHERE `username` = '%e' LIMIT 1", Player[playerid][Name]);
        //This is the select query we use to retrieve the playerdata from the SQL if any exists.
    mysql_tquery(SQL, query, "OnAccountRetrieve","dd", playerid, MySQLCheck[playerid]);//This is a tquery, it allows you to use cached or non-cached queries. This one is cached.
       
    return 1;
}
We tell it the Connhandle which is SQL, then we tell it the query it need to fire is the one we formatted. Then we give it a callback where we send the data retrieved. Which is OnAccountRetrieve. Then the specifiers need to match the parameters of the callback, in this case, They're both playerid's so it's "dd" for two integers. Then we name the parameters.

pawn Код:
forward OnAccountRetrieve(playerid, mysql_check);//Here we use forward because we're creating a public.
public OnAccountRetrieve(playerid, mysql_check)
{
    if(mysql_check != MySQLCheck[playerid])//Here we check if the check does not equal the value stored in MySQLCheck, we kick the player.
    {
        Kick(playerid);
    }
    new str[128];//New string.
    if(cache_num_rows() > 0)//If there are rows then we load the login.
    {
        SetPlayerData(playerid);//Here we set the players data, This assigns data to all the variables.

        format(str, sizeof(str), "MySQL Account and Admin System, Welcome %s. Please login below:", Player[playerid][Name]);//Here we format a message for out password dialog.
        ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", str, "Continue", "Cancel");
        Player[playerid][IsRegistered] = true;//We set the Variable IsRegistered to True, so it knows we're not a new player.
    }
    else //Else we load the registration.
    {
        format(str, sizeof(str), "MySQL Account and Admin System, Welcome %s. Please register below:", Player[playerid][Name]);//Registration String,
        ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration", str, "Continue", "Cancel");//Registration Dialog
        Player[playerid][IsRegistered] = false;//He we set it to false so we know that the player is new.
    }
    return 1;
}
The above callback is there to determine whether or not you have an account or not. If you do, then the script checks if you have any rows and calls them, then lets you log in. If not, then you're directed to a register dialog.

pawn Код:
public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
    switch(dialogid)
    {
        case DIALOG_LOGIN://Setting the case to the right dialog.
        {
            if(!response)//This means, if they click cancel. They'll be kicked.
            {
                Kick(playerid);
            }
            if(strlen(inputtext) <= 5)//This checks if the password is equal or shorter than 5 characters.
            {
                ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login",
                "Your password must be longer than 5 characters!\nPlease enter your password in the field below:",
                "Login", "Abort");// If it is less or equal to 5 characters, It brings the dialog back up along with an error message.
            }
            new encoded_pass[129];//Defining the hashed password.
            WP_Hash(encoded_pass, sizeof(encoded_pass), inputtext);//This hashes the password that you entered.

            if(strcmp(encoded_pass, Player[playerid][Password]) == 0)//This is checking if the password is the same as the password in the password variable.
            {

                ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Success!", "You have successfully logged in!", "Continue", "");//If it is, It logs you in successfully.
                Player[playerid][IsLoggedIn] = true;//This set the LoggedIn variable to true, for use in later commands.
                SetSpawnInfo(playerid, 0, 20,2177.6423,-1770.3905,13.5434,0.0,0,0,0,0,0,0);
                SpawnPlayer(playerid);
            }
            else//This is if your password is wrong, it gives you 3 chances to re-enter it before kicking you.
            {
                Player[playerid][LoginAttempts]++;//This increases the variable by one each time you get your password wrong.
                if(Player[playerid][LoginAttempts] >= 3)//This if statement determines if you have enter the password incorrectly too many time.
                {
                    ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Error","You have mistyped your password too often (3 times).", "Okay", "");
                    DelayedKick(playerid);//This is the kick.
                }
                else
                ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Error","Wrong password!\nPlease enter your password in the field below:", "Login", "Abort");
                //This is the error message for entering the wrong password.
            }
        }

        case DIALOG_REGISTER://This sets the dialog to the DIALOG_REGISTER.
        {
            if(!response)//Again, this kicks the player if he declines the dialog.
                return Kick(playerid);

            if(strlen(inputtext) <= 5)// This is the same as previously.
            {
                ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration",
                "Your password must be longer than 5 characters!\nPlease enter your password in the field below:",
                "Continue", "Cancel");
            }
            new query[256];//Defining the query to insert the data into the SQL
            WP_Hash(Player[playerid][Password], 129, inputtext);//Hashing the password.
            mysql_format(SQL, query, sizeof(query), "INSERT INTO `players` (`username`, `password`) VALUES ('%e', '%s')", Player[playerid][Name], Player[playerid][Password]);
            //This is mysql_format, it formats the query so you can tell it what to insert into the database. %e is used for usernames and %s strings, e.g. the password.
            mysql_tquery(SQL, query, "OnPlayerRegistration", "d", playerid);//This is the query, The format is mysql_tquery(Connhandle,query[],Callback[],format[])
           
        }

        default:
            return 0;
    }
    return 1;
}
Here we first set the dialog, then we see if the player has clicked the cancel button, If yes. We kick them. The we check if the players password is below or equal to five characters, If it is, we send an error message and bring the login dialog back up. If the password is long enough we then check if it is the same as the hashed password stored in the database. If yes it lets you log in. We set IsLoggedIn to true for later commands.

Now, we do the exact same with the register dialog except when they have a password longer than 5 characters we simply hash the password with whirlpool and insert the username and password into the database. We then send it to another callback for data to be assigned to variable for the first time.

pawn Код:
forward OnPlayerRegistration(playerid);
public OnPlayerRegistration(playerid)
{
    Player[playerid][ID] = cache_insert_id();//This means the ID in the SQL is saved to a variable.

    ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Registration", "Account successfully registered, you have been automatically logged in.", "Okay", "");
    Player[playerid][IsLoggedIn] = true;//Sets the IsLoggedIn Variable to true.
    Player[playerid][IsRegistered] = true;//The same as above but for IsRegistered.
    SetSpawnInfo(playerid, 0, 20,2177.6423,-1770.3905,13.5434,0.0,0,0,0,0,0,0);//This sets the spawn info. Team, Skin, PosX, PosY, PosZ, Angle and Weapon ID's and ammo.
    Player[playerid][Health] = 100.00; //Sets the players health.
    Player[playerid][Armour] = 0.00; //Sets the player armour.
    Player[playerid][Level] = 1;//Set's the level to 1.
    SpawnPlayer(playerid);
    return 1;
}
Here we send the player a confirmation message and then set the data to variable like IsLoggedIn to true and the health to be 100.00. This is neccessary due to it being the first time your data has entered the database.

pawn Код:
stock SetPlayerData(playerid)// This is assigning the data to variable.
{
    Player[playerid][ID] = cache_get_field_content_int(0, "id");// The ID is and int because it's an integer (Number).
    cache_get_field_content(0, "password", Player[playerid][Password], SQL, 129);
    //The password is a varchar so you need to use cache_get_field_content
    Player[playerid][Admin] = cache_get_field_content_int(0,"admin");//The admin is saved as an int because again, it's an integer.
    Player[playerid][Health] = cache_get_field_content_float(0, "health");
    //The health and armour are floats. This lets them hold decimal values like "84.56". So, Positions would be floats too.
    Player[playerid][Armour] = cache_get_field_content_float(0, "armour");
    cache_get_field_content(0, "admintitle", Player[playerid][AdminTitle], SQL, 64);
    //the admin title is a varchar so it is again retrieved by cache_get_field_content.
    Player[playerid][Level] = cache_get_field_content_int(0,"level");
    //the level is an integer.
    return 1;
}
Above is the SetPlayerData from earlier, he we assign the data retrieved in the query we fired in OnAccountRetrieve.

pawn Код:
stock SavePlayerData(playerid)
{
    if(Player[playerid][IsLoggedIn] == false)//This checks if the player is logged in, if not, It saves nothing.
        return 0;

    new Float:GetHealth, Float:GetArmour, admtitle2;
    admtitle2 = Player[playerid][AdminTitle];
    GetPlayerHealth(playerid, GetHealth);
    GetPlayerArmour(playerid, GetArmour);
    new query[1000];
    mysql_format(SQL, query, sizeof(query), "UPDATE `players` SET `admin` = '%d', `health` = '%f', `armour` = '%f', `admintitle` = '%s', `level` = '%d' WHERE `id` = '%d' LIMIT 1",
    Player[playerid][Admin],GetHealth,GetArmour, admtitle2,Player[playerid][Level], Player[playerid][ID]);
    mysql_tquery(SQL, query, "", "");
    return 1;
}
This is one of the most important parts. The saving. For the health and armour, We use GetPlayerHealth instead of Player[playerid][Health] because the Player[playerid][Health] isn't always the current health they have. It's just what was assigned to it. We then fire a query to update certain fields in the SQL table "Players".

pawn Код:
stock SetupPlayerTable()
{
    mysql_query(SQL, "CREATE TABLE IF NOT EXISTS `players` (`id` int(11) NOT NULL auto_increment PRIMARY KEY,`username` varchar(30) NOT NULL,`password` varchar(130) NOT NULL,`admin` int(10) NOT NULL default '0', `health` float(10) NOT NULL default '100.00', `armour` float(10) NOT NULL default '0.00',`admintitle` varchar(64) NOT NULL default 'None',`level` int(10) NOT NULL default '0')", false);
    return 1;// This create the table in the database if it isn't there. If it is there. It leaves it.
}
The above query is to make sure that if you don't have the right tables in your database when the script starts that it will make them for you.

pawn Код:
public OnPlayerSpawn(playerid)
{
    new str[128];
    SetPlayerHealth(playerid, Player[playerid][Health]);//Set's the health and armour.
    SetPlayerArmour(playerid, Player[playerid][Armour]);
    ShowPlayerDialog(playerid, 200, DIALOG_STYLE_MSGBOX,"MySQL Account and Admin System","This is a simple account system, Hopefully you got it right!","Ok","Cancel");
    if(Player[playerid][Admin] >= 1)//If the player is admin, It will do whatever is in the parameter for that player.
    {
        format(str, 128, "Notice: You're logged in as a %s!", Player[playerid][AdminTitle]);
        SendClientMessage(playerid, -1, str);//It sends a message notifying the admin of their rank.
    }
    return 1;
}
Here we set the variables when a player spawns. Like the health and armour. Also, we have a message that shows to all admins if they are an admin.


pawn Код:
stock DelayedKick(playerid, time=500)
{
    SetTimerEx("_KickPlayerDelayed", time, false, "d", playerid);
    return 1;
}

forward _KickPlayerDelayed(playerid);
public _KickPlayerDelayed(playerid)
{
    Kick(playerid);
    return 1;
}
The above is just a delayed kick so that error messages can show before they get kicked.

Commands

pawn Код:
CMD:adminoverride(playerid, params[])
{
    new override[64], OverrideAttempts;//We define the variable we need for the command.
    if(sscanf(params,"s[64]",override)) return SendClientMessage(playerid, -1, "USAGE:/adminoverride [Password]");
    //If the command parameters are empty, then we return a message about the command usage.
    if(!strcmp(override, adminpassword))//Here we check if the override entered is the same as the admin password defined at the top.
    {
        Player[playerid][Admin] = 7;//If it is, we set the admin level to 7.
        SendClientMessage(playerid, -1, "NOTICE: You're now a level 7 administrator!");
    }
    else//Otherwise, we send them a message with and increase their override attempts.
    {
        SendClientMessage(playerid, -1, "NOTICE: Wrong Password!");
        OverrideAttempts ++;//Here we increase the attempts.
    }
    if(OverrideAttempts == 3)// Now we say if it gets to 3 then we kick the player.
    {
        DelayedKick(playerid);
    }
    return 1;
}

CMD:setadmintitle(playerid, params[])
{
    new str[128], target,pName[MAX_PLAYER_NAME], targetname[MAX_PLAYER_NAME],admtitle[64];//Command variables.
    if(Player[playerid][Admin] != 7) return SendClientMessage(playerid, -1, "SERVER:You're not admin!");//Error message if the player isn't an admin.
    if(sscanf(params, "is[64]", target,admtitle)) return SendClientMessage(playerid, -1, "SERVER:/setadmintitle [ID] [Title]");//The usage message like before.
    {
        GetPlayerName(target, targetname, sizeof(targetname));//Here we get the targets name,
        GetPlayerName(playerid, pName, sizeof(pName));// The playerid's name.
        format(str, 128, "AdmWarn: %s has set %s's admin title to %s", pName, targetname, admtitle);//Here we format a msg to send to all admins online.
        MessageToAdmins(-1, str);
        Player[target][AdminTitle] = admtitle;// We set the admintitle variable to what the player entered.
    }
    return 1;
}
The last thing now,

pawn Код:
#define adminpassword "testpassword"
To define the adminpassword required to access admin.

The two above commands are explained in the comments. But, The first one make your admin if the password you enter is correct. The second set your admin title. This can be used if you make an /a command for admin chat. You could display the admintitle infront of their name instead of a number.

Thanks, If you have any question, post a comment.

NOTE: I do not claim ownership of all the code, I simply wanted to make a tutorial for people, so they know how to use it.

Almost forgot, Add this somewhere.

pawn Код:
stock MessageToAdmins(color,const string[])
{
    for(new i = 0; i < MAX_PLAYERS; i++) // Loops through all players
    {
          if(IsPlayerConnected(i) == 1) // Checks if the player is connected
          {
                if(Player[i][Admin] >= 1)
                {
                    SendClientMessage(i, color, string);//if player is connected and he's admin level higher then 1 he will get this message
                }
          }
    }
    return 1;
}

-------------------------------------UPDATED--------------------------------------------

pawn Код:
CMD:deleteaccount(playerid, params[])
{
    new query[128];//We define the query
    new pid;//Define the accountid
    if(Player[playerid][Admin] == 7)//check if the player is a high enough admin level.
    {
        if(sscanf(params,"d",pid)) return SendClientMessage(playerid, -1, "Usage: /deleteaccount [SQLid]");//Usage Message
        mysql_format(SQL, query, sizeof(query),"DELETE FROM `players` WHERE `id` = '%d'",pid);//Here we format the query to delete the players account.
        mysql_tquery(SQL, query, "OnAccountDelete","d",playerid);//We fire the query to another call back.
    }
    else
    {
        SendClientMessage(playerid,-1, "Notice: You're not an admin!");//if the players is not an admim, It sends this error message.
    }
    return 1;
}



forward OnAccountDelete(playerid);
public OnAccountDelete(playerid)//New forward and public callback.
{
    new affectedrows,str[128];//We define some variables.
    affectedrows = cache_affected_rows(SQL);
    //Here we set one of the variables to cache_affected_rows which will tell you if anything gets deleted.
    format(str,128, "%d rows affected!", affectedrows);//We format the string with our message
    SendClientMessage(playerid, -1, str);//Then we send it to the player.
    return 1;

}


CMD:resetpassword(playerid, inputtext[])
{
    new pid,query[500],WPhash[129];//We define our variables again
    if(Player[playerid][Admin] == 7)//We've seen this before, checking if the player is admin
    {
        if(sscanf(inputtext,"ds[129]",pid,inputtext)) return SendClientMessage(playerid, -1, "Usage: /resetpassword [SQLid] [Password]");//Usage Message
        WP_Hash(WPhash, sizeof(WPhash), inputtext);
        //Here we hash the password the player enters so it can be stored in the database and recognised properly when the player logs in.
        mysql_format(SQL, query, 500, "UPDATE `players` SET `password` = '%s' WHERE `id` = '%d'",WPhash, pid);
        //Here we format our query to update the players password where the ID = the pid.
        mysql_tquery(SQL, query, "OnPasswordReset","d",playerid);//Fire the query and send it to another callback.
        return 1;
    }
    return 1;
}

forward OnPasswordReset(playerid);//New callback
public OnPasswordReset(playerid)
{
    new affectedrows, str[128];//Deinfe the variables.
    affectedrows = cache_affected_rows(SQL);
    format(str, 128, "%d rows affected!",affectedrows);
    SendClientMessage(playerid, -1, str);
    return 1;
}


CMD:getsqlid(playerid, params[])
{
    new pName[MAX_PLAYER_NAME],query[500];//This ia a command to get the SQL ID needed for the other commands.
    if(Player[playerid][Admin] == 7)//Checks the admin level of the player.
    {
        if(sscanf(params,"s",pName)) return SendClientMessage(playerid, -1, "Usage: /getsqlid [AccountName]");//USage message
        mysql_format(SQL,query,500, "SELECT `id`, `username` FROM `players` WHERE `username` = '%s'",pName);//Format query
        mysql_tquery(SQL, query, "OnSQLIDRetrieve","d",playerid);//Fire it to another callback.
        return 1;
    }
    return 1;
}

forward OnSQLIDRetrieve(playerid);//New callback.
public OnSQLIDRetrieve(playerid)
{
    if(cache_num_rows() < 0)//If there is data in the ID slots you entered in /getsqlid
    {
        new pid,tempname[64],str[128];
        pid = cache_get_field_content_int(0, "id");//We assign the mysql id retrieve to pid.
        cache_get_field_content(0, "username", tempname, SQL, 64);//the username to tempname.
        format(str, 128, "Username: %s | SQL ID: %d", tempname, pid);//Here we format our string
        SendClientMessage(playerid, -1, str);//then send it to the player.
        return 1;
    }
    else//if there isn't data
    {
        SendClientMessage(playerid, RED,"No results found!");//Send this message
    }
    return 1;
}
Ban commands and system coming soon!

Reply
#2

Nice.
Reply
#3

Thanks, I'm going to keep it updated.
Reply
#4

Script[gamemodes/mysql.amx]: Run time error 19: "File or function is not found"

What to do?
Reply
#5

@Giannidw please add msql in server.cfg
and AWESOME TUTORIAL NIGGA! keep making tuts like this please!
Reply
#6

Quote:
Originally Posted by superrobot48
Посмотреть сообщение
@Giannidw please add msql in server.cfg
and AWESOME TUTORIAL NIGGA! keep making tuts like this please!
Already done.. But it works now! Really fast and nice system! +rep.
Reply
#7

Thanks for tutorial but what is function and lines for not hash pass please.
Reply
#8

Nice+-.
Reply
#9

Nice Bro
Reply
#10

pawn Код:
new query[128];//New query being defined.
I think it is wrong.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)