[Tutorial] Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES]
#1

Introduction

Many tutorials promote bad written code or not the most optimised code and I've decided to take it upon myself to spend time updating these tutorials. The creator of the original tutorial is linked at the end of each tutorial. All code is rewritten by myself and tested by myself. It is recommended to read everything and not just recklessly copy the code (which I'm sure that many will do).

Notes

This tutorial assumes that you know where includes and plugins are installed. If not, you may refer to the following wikipage: https://sampwiki.blast.hk/wiki/Scripting_Basics or a tutorial regarding that on the forum. This tutorial also assumes that you have a fairly basic understanding of scripting. I will not go in-depth with basic functions and variables. If you don't have a basic understanding of scripting, then don't bother creating your own login/register script.

Necessities

- The YSI library: https://sampforum.blast.hk/showthread.php?tid=570883
- Whirlpool: https://sampforum.blast.hk/showthread.php?tid=570945
- A 'Users' folder in 'scriptfiles'.

Script

To use the code from the libraries stated in the necessities section, we need to include them. We can include them using the 'include' directive:
PHP Code:
#include <a_samp>
#include <YSI\y_ini> 
Since Whirlpool only defines one native function, you don't need an include for it. You simply place the following line after including all the necessary includes:

PHP Code:
native WP_Hash(buffer[], len, const str[]); 
Now that we included the libraries, we are able to use the code to achieve a login/register script. We have to define a path to where a user's file is saved to. We will use this path when saving the user's data and to load their data. We can define a constant path with the 'define' directive. It is convenient to write definitions in full caps:

PHP Code:
#define USER_PATH "/Users/%s.ini" 
'%s' is a format specifier for strings. The string value in this tutorial is the user's name with which they registered. More about specifiers and formatting strings: https://sampwiki.blast.hk/wiki/Format

We need a function to replace the '%s' format specifier with the user's name, don't we? The following is a function without an initialiser because we don't need one:

PHP Code:
UserPath(playerid) {
    
// Declare our variables used in this function
    
new
        
str[36], // 'str' will be our variable used to format a string, the size of that string will never exceed 36 characters.
        
name[MAX_PLAYER_NAME]; // 'name' will be our variable used to store the player's name in the scope of this function. MAX_PLAYER_NAME is defined as 24.
    // Get the player's name.
    
GetPlayerName(playeridnamesizeof(name));
    
// Format USER_PATH with the name that we got with GetPlayerName.
    
format(strsizeof(str), USER_PATHname); // USER_PATH has been defined as: "/Users/%s.ini", %s will be replaced the player's name.
    
return str;

The PAWN precompiler replaces USER_PATH with the value that we defined earlier. USER_PATH in this case will be replaced by "/Users/%s.ini" and the '%s' will be formatted with the player's name that we retrieved with the GetPlayerName function. More about specifiers and formatting strings: https://sampwiki.blast.hk/wiki/Format

Once that is done, we can start declaring an enum for the dialogs and the player's data used in this tutorial. We put our dialogs in an enum to avoid ID collision (note: it uses memory to store the dialog IDs). More about dialogs: https://sampwiki.blast.hk/wiki/ShowPlayerDialog
To define an enum, we use the 'enum' initialiser. Every variable in an enum is referred to as an enumerator.

PHP Code:
enum {
    
DIALOG_LOGIN,
    
DIALOG_REGISTER
}; 
You can name the enum's name to whatever name you like as well as the names of the enumerators.

PHP Code:
enum E_PLAYER_DATA {
    
Password[129],
    
AdminLevel,
    
VIPLevel,
    
Money,
    
Score,
    
Kills,
    
Deaths,
    
bool:LoggedIn
};
new 
PlayerInfo[MAX_PLAYERS][E_PLAYER_DATA]; 
We declare a variable at the end of an enum to address the enumerators in the enum. Each enumerator has a memory address that we will use to store data in. More about enums and how they work: https://sampforum.blast.hk/showthread.php?tid=318307 and https://sampwiki.blast.hk/wiki/Keywords:Initialisers#enum

All right. We have the variables, we have set up the files necessary to save and load the user's data and we have the dialog IDs. We now have to use all this code. We start writing our code from the very beginning; when the player connects.

PHP Code:
public OnPlayerConnect(playerid) {
    
// Reset the variables to avoid data corruption
    
PlayerInfo[playerid][AdminLevel] = 0;
    
PlayerInfo[playerid][VIPLevel] = 0;
    
PlayerInfo[playerid][Money] = 0;
    
PlayerInfo[playerid][Score] = 0;
    
PlayerInfo[playerid][Kills] = 0;
    
PlayerInfo[playerid][Deaths] = 0;
    
PlayerInfo[playerid][LoggedIn] = false;
    new
        
name[MAX_PLAYER_NAME]; // 'name' will be our variable used to store the player's name in the scope of this function. MAX_PLAYER_NAME is defined as 24.
    
GetPlayerName(playeridnamesizeof(name));
    
TogglePlayerSpectating(playeridtrue);
    if(
fexist(UserPath(playerid))) {
        
// This will check whether the user's file exists (he is registered).
        // When it exists, run the following code:
        
INI_ParseFile(UserPath(playerid), "LoadPlayerData_PlayerData", .bExtra true, .extra playerid);
        
ShowPlayerDialog(playeridDIALOG_LOGINDIALOG_STYLE_PASSWORD"Login""Welcome back. This account is registered.\n\nEnter your password below to log in:""Login""Quit");
    }
    else {
        
// When the user's file doesn't exist (he isn't registered).
        // When it doesn't exist, run the following code:
        
ShowPlayerDialog(playeridDIALOG_REGISTERDIALOG_STYLE_INPUT"Register""Welcome. This account is not registered.\n\nEnter your desired password below to register:""Register""Quit");
    }
    return 
1;

The code is pretty self-explanatory. INI_ParseFile is a function from the Y_INI include and calls a forwarded function to load the player's data. More about y_ini: https://sampforum.blast.hk/showthread.php?tid=570957

We now have written code that calls a dialog and if the player is registered, a function too. We have to do something with that. We have to declare that function first:

PHP Code:
forward LoadPlayerData_PlayerData(playeridname[], value[]);
public 
LoadPlayerData_PlayerData(playeridname[], value[]) {
    
INI_String("Password"PlayerInfo[playerid][Password], 129);
    
INI_Int("AdminLevel"PlayerInfo[playerid][AdminLevel]);
    
INI_Int("VIPLevel"PlayerInfo[playerid][VIPLevel]);
    
INI_Int("Money"PlayerInfo[playerid][Money]);
    
INI_Int("Score"PlayerInfo[playerid][Score]);
    
INI_Int("Kills"PlayerInfo[playerid][Kills]);
    
INI_Int("Deaths"PlayerInfo[playerid][Deaths]);
    return 
1;

The functions with the 'INI_' prefix load the player's data and assigns an enumerator to it so we can use it throughout the script.

We now have to create our dialogs:
PHP Code:
public OnDialogResponse(playeriddialogidresponselistiteminputtext[]) {
    switch(
dialogid) {
        case 
DIALOG_REGISTER: {
            if(!
responseKick(playerid);
            else {
                if(
isnull(inputtext)) {
                    
SendClientMessage(playerid, -1"You have to enter your desired password.");
                    return 
ShowPlayerDialog(playeridDIALOG_REGISTERDIALOG_STYLE_INPUT"Register""Welcome. This account is not registered.\n\nEnter your desired password below to register:""Register""Quit");
                }
                
WP_Hash(PlayerInfo[playerid][Password], 129inputtext);
                new 
INI:file INI_Open(UserPath(playerid));
                
INI_SetTag(file"PlayerData");
                
INI_WriteString(file"Password"PlayerInfo[playerid][Password]);
                
INI_WriteInt(file"AdminLevel"0);
                
INI_WriteInt(file"VIPLevel"0);
                
INI_WriteInt(file"Money"0);
                
INI_WriteInt(file"Score"0);
                
INI_WriteInt(file"Kills"0);
                
INI_WriteInt(file"Deaths"0);
                
INI_Close(file);
                
SendClientMessage(playerid, -1"You have successfully registered.");
                
PlayerInfo[playerid][LoggedIn] = true;
                
TogglePlayerSpectating(playeridfalse);
                return 
1;
            }
        }
        case 
DIALOG_LOGIN: {
            if(!
responseKick(playerid);
            else {
                new
                    
hashpass[129];
                
WP_Hash(hashpasssizeof(hashpass), inputtext);
                if(!
strcmp(hashpassPlayerInfo[playerid][Password])) {
                    
// The player has entered the correct password
                    
SetPlayerScore(playeridPlayerInfo[playerid][Score]);
                    
GivePlayerMoney(playeridPlayerInfo[playerid][Money]);
                    
SendClientMessage(playerid, -1"Welcome back! You have successfully logged in!");
                    
PlayerInfo[playerid][LoggedIn] = true;
                    
TogglePlayerSpectating(playeridfalse);
                }
                else {
                    
// The player has entered an incorrect password
                    
SendClientMessage(playerid, -1"You have entered an incorrect password.");
                    
ShowPlayerDialog(playeridDIALOG_LOGINDIALOG_STYLE_PASSWORD"Login""Welcome back. This account is registered.\n\nEnter your password below to log in:""Login""Quit");
                }
                return 
1;
            }
        }
    }
    return 
0;

The code is pretty self-explanatory. We use a switch statement because it is faster than having a lot of if-statements. Though, you should only use switch statements where needed and not when there's only one if-statement. There are only two if-statements, but I'm sure you'll end up adding more dialogs and thus having a switch-statement is the best option here.

If you don't have ZCMD in your gamemode, then add this to your gamemode:
PHP Code:
        #define isnull(%1) \
                                
((!(%1[0])) || (((%1[0]) == '\1') && (!(%1[1])))) 
The WP_Hash function is used to hash a certain string (the text that the user entered in the dialog), we immediately save the hashed string into the user's password enumerator to make sure that the password is in plain text for a minimal amount of time.

I return 0 in this callback because you might have a filterscript that uses OnDialogResponse. Returning 1 instead makes the callback unusable

I am a strong pioneer of saving data when it is changed and not when the player disconnects. The reason for that is because data might get corrupted when the player disconnects. For example: when the player gets killed, we want to add a death to their record and a kill to the killer's record:

PHP Code:
public OnPlayerDeath(playeridkilleridreason) {
    if(
killerid != INVALID_PLAYER_ID) {
        
// We check whether the killer is a valid player
        
PlayerInfo[playerid][Deaths] ++; // ++ means +1
        
PlayerInfo[killerid][Kills] ++;
        
// Save the deaths
        
new INI:file INI_Open(UserPath(playerid));
        
INI_SetTag(file"PlayerData");
        
INI_WriteInt(file"Deaths"PlayerInfo[playerid][Deaths]);
        
INI_Close(file);
        
// Save the kills
        
new INI:file2 INI_Open(UserPath(killerid));
        
INI_SetTag(file2"PlayerData");
        
INI_WriteInt(file2"Kills"PlayerInfo[killerid][Kills]);
        
INI_Close(file2);
    }
    return 
1;

We add a kill to the killer's data and a death to the player's data. We immediately save it to their files to avoid possible data corruption.


FAQ

I am able to log on with any password!
Change:
PHP Code:
INI_ParseFile(UserPath(playerid), "LoadPlayerData_%s", .bExtra true, .extra playerid); 
To
PHP Code:
INI_ParseFile(UserPath(playerid), "LoadPlayerData_PlayerData", .bExtra true, .extra playerid); 
The data which the placeholder '%s' is replacing, is the name of the file's tag. A huge thanks to Misiur: http://forum.sa-mp.com/showpost.php?...2&postcount=13

Footnote

And there we go. You've successfully created your own secured login/register script using y_ini to save the player's data.

I've decided to just rewrite the tutorial and repost it instead of contacting newbienoob because otherwise he had to update the whole topic, which in my opinion is just worth a repost. I have also actively been adding and editing pages on the samp wiki and decided to do the same on the forum.

This tutorial is based on newbienoob's tutorial: https://sampforum.blast.hk/showthread.php?tid=352703
The credits of mentioned URLs go to their respective creators.

I decided to update this tutorial first because it's one of the most popular tutorials on the SA:MP forum. If there's a tutorial that you think needs an update, then let me know!
Reply


Messages In This Thread
Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 02.01.2016, 23:31
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by Crystallize - 02.01.2016, 23:34
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by Joron - 02.01.2016, 23:36
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 02.01.2016, 23:36
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by saffierr - 03.01.2016, 06:21
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by NourNN - 03.01.2016, 09:44
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by Michael B - 03.01.2016, 10:07
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by -CaRRoT - 09.01.2016, 07:32
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by Wiseco - 09.01.2016, 22:20
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 10.01.2016, 07:39
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by NealPeteros - 11.01.2016, 13:19
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 11.01.2016, 14:02
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by MicroKyrr - 11.01.2016, 14:02
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by MicroKyrr - 28.02.2016, 14:17
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 28.02.2016, 15:41
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by Kasis - 03.03.2016, 10:46
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 04.03.2016, 17:54
Respuesta: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by DragonZafiro - 12.03.2016, 18:22
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by MicroKyrr - 12.03.2016, 22:53
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by Crayder - 12.03.2016, 23:00
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 12.03.2016, 23:04
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by MicroKyrr - 12.03.2016, 23:32
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 12.03.2016, 23:36
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by MicroKyrr - 12.03.2016, 23:40
Respuesta: Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by DragonZafiro - 12.03.2016, 23:40
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by SickAttack - 12.03.2016, 23:49
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by SyS - 09.07.2016, 14:58
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by AndySedeyn - 09.07.2016, 15:50
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by SyS - 10.07.2016, 09:52
Re: Login/Register [Y_INI][WHIRLPOOL][UPDATE SERIES] - by ChandraLouis - 12.10.2016, 12:19

Forum Jump:


Users browsing this thread: 1 Guest(s)