Login and register
MySQL/SQLite both supported with one line change
EasyDB

Hi, there since i am now only supporting EasyDB and no other of my SQL includes (deleted now); I decided to make a tutorial of a basic login and register system with dialogs and couple of commands.

Following tutorial covers SQL based database and since we are using EasyDB, you can switch to any database type i.e. SQLite or MySQL anytime by editing just one line of code and you'll be done. One of the greatest advantage of EasyDB.

What this tutorial covers?
Links to all requirements
Scripting section, writing the code of login and register system
Optional commands (/changepass)
Finish (overall script sample)

Requirements
MySQL plugin (at least version R39-5):
http://forum.sa-mp.com/showthread.php?t=56564

Sscanf2 plugin (at least version v2.8)
http://forum.sa-mp.com/showthread.php?t=570927

EasyDB include (at least version v2.4.2)
http://forum.sa-mp.com/showthread.php?t=581453

I-ZCMD include (at least version v0.2.2.3)
http://forum.sa-mp.com/showthread.php?t=576114

EasyDialog include
https://github.com/eider-i128/porgy/...easyDialog.inc

Scripting (main part)
SCRIPT TYPE
First of all you should decide if you are creating a filterscript or a gamemode, because a lot of libraries depends on the script's type. So
this is really a good practice and really appreciated by scripters if you define FILTERSCRIPT in your script's top if your script is a filterscript.

So in case you are using a filterscript, add this in the very top:
Code:
#define FILTERSCRIPT // add this your script is a filterscript

INCLUDING LIBRARIES
Now here is a simple part where you decide you want SQLite or MySQL.

First we would add <a_samp>:
Code:
#include <a_samp>

// rest of the libraries goes here

For MySQL users:
Code:
#define CONNECTION_TYPE_MYSQL // tells "easydb" that we are using mysql
#define ENABLE_CONSOLE_MESSAGES // tells "easydb" that we would need console messages/debugs while run time to keep checks and detect if any errors/warnings

#include <easydb>
#include <izcmd>
#include <sscanf2>
#include <easydialog>

For SQLite users:
Code:
#define ENABLE_CONSOLE_MESSAGES // tells "easydb" that we would need console messages/debugs while run time to keep checks and detect if any errors/warnings

#include <easydb>
#include <izcmd>
#include <sscanf2>
#include <easydialog>

DECLARING VARIABLES & MACROS
Second step will be defining general settings like password length:
Code:
#define MIN_PASSWORD_LENGTH (5)
#define MAX_PASSWORD_LENGTH (45)

Maximum login attempts a user can get: (i am setting it 3 | note this should not be below 1)
Code:
#define MAX_LOGIN_ATTEMPTS (3)

And a macro for defining passwords salt (Password salts are really confidential information, so you should keep it safe with you and avoid sharing)
Code:
#define PASSWORD_SALT "334t!t>D<QW**@!)#$>C_)_AAgddh"

Declare a player information holder enumerator:
Code:
enum E_ACCOUNT
{
E_ACCOUNT_SQLID,
E_ACCOUNT_PASSWORD[64],
E_ACCOUNT_KILLS,
E_ACCOUNT_DEATHS,
E_ACCOUNT_MONEY,
E_ACCOUNT_SCORE
};
new p_Account[MAX_PLAYERS][E_ACCOUNT];

And another variable to store player's total login attempts:
Code:
new p_LoginAttempts[MAX_PLAYERS];

INITIALIZING DATABASE
If you are using filterscript:
Code:
public OnFilterScriptInit()
{
}

Or gamemode users will use this callback:
Code:
public OnGameModeInit()
{
}

I'll be using gamemode as an example for now.

For MySQL users:
Code:
public OnGameModeInit()
{
DB::Init("database name", "server/host name", "username", "password");

// Creating/Verifying table where we'll save user data
DB::VerifyTable("Users", "ID", false,
"Name", STRING,
"Password", STRING,
"IP", STRING,
"Kills", INTEGER,
"Deaths", INTEGER,
"Money", INTEGER,
"Score", INTEGER);

return 1;
}

For SQLite users:
Code:
public OnGameModeInit()
{
DB::Init("database name");

// Creating/Verifying table where we'll save user data
DB::VerifyTable("Users", "ID", false,
"Name", STRING,
"Password", STRING,
"IP", STRING,
"Kills", INTEGER,
"Deaths", INTEGER,
"Money", INTEGER,
"Score", INTEGER);

return 1;
}

NOTE: If you want to add more columns, you can simply edit the VerifyTable function in your script. For example my table "Users" is already existing with all the given rows below. But now i want to create another column/field called "Health" as FLOAT (say), so i can very easily do that with the following edition:
Code:
DB::VerifyTable("Users", "ID", false,
"Name", STRING,
"Password", STRING,
"IP", STRING,
"Kills", INTEGER,
"Deaths", INTEGER,
"Money", INTEGER,
"Score", INTEGER,
"Health", FLOAT);
Types you can have are:
Code:
* STRING
* INTEGER
* FLOAT

SHOWING LOGIN/REGISTER DIALOGS
Player account existence is a only checked by the name's registration. So here we do the same, we check if the user's name is already existing in the database table "Users", if yes show login dialog otherwise a register dialog.

Code:
public OnPlayerConnect(playerid)
{
p_LoginAttempts[playerid] = 0; // reset login attempts to 0

new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, MAX_PLAYER_NAME);

DB::Fetch("Users", _, _, _, "`Name` = '%q'", name);
if (fetch_rows_count() > 0) // if the user is existing member (LOGIN)
{
p_Account[playerid][E_ACCOUNT_SQLID] = fetch_row_id();
fetch_string("Password", p_Account[playerid][E_ACCOUNT_PASSWORD], 64);
p_Account[playerid][E_ACCOUNT_KILLS] = fetch_int("Kills");
p_Account[playerid][E_ACCOUNT_DEATHS] = fetch_int("Deaths");
p_Account[playerid][E_ACCOUNT_MONEY] = fetch_int("Money");
p_Account[playerid][E_ACCOUNT_SCORE] = fetch_int("Score");

Dialog_Show(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login account...", "{FFFFFF}Good to see you back to SA-MP 0.3.7 Server! Please complete the login formality to access your account (password required).", "Login", "Quit");
}
else // user is new (REGISTER)
{
p_Account[playerid][E_ACCOUNT_SQLID] = -1;
p_Account[playerid][E_ACCOUNT_PASSWORD][0] = EOS;
p_Account[playerid][E_ACCOUNT_KILLS] = 0;
p_Account[playerid][E_ACCOUNT_DEATHS] = 0;
p_Account[playerid][E_ACCOUNT_MONEY] = 0;
p_Account[playerid][E_ACCOUNT_SCORE] = 0;

Dialog_Show(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Register account...", "{FFFFFF}Welcome to SA-MP 0.3.7 Server! Please complete this small registeration formality to sign-up with us (password required).", "Register", "Quit");
}

fetcher_close();
return 1;
}

Now we'll handle the dialog's responses...

<easydialog> handles dialog responses just like <zcmd>, so this piece of code shall be placed independently maybe under OnPlayerConnect callback or whereever you wish.

DIALOG_LOGIN
Code:
DialogIALOG_LOGIN(playerid, response, listitem, inputtext[])
{
// if player clicks "Quit" (will be kicked)
if (!response)
return Kick(playerid);

// we'll hash player password here which will be further used for comparing
new password[64];
SHA256_PassHash(inputtext, PASSWORD_SALT, password, sizeof (password));

// as we have stored player data when he/she connected, so we'll check the password is matching
if (strcmp(p_Account[playerid][E_ACCOUNT_PASSWORD], password)) // if password is incorrect
{
p_LoginAttempts[playerid]++;

new str[150];
format(str, sizeof (str), "Incorrect password, you are left with %i/%i attempts.", p_LoginAttempts[playerid], MAX_LOGIN_ATTEMPTS);
SendClientMessage(playerid, 0xFFFFFFFF, str);

if (p_LoginAttempts[playerid] >= MAX_LOGIN_ATTEMPTS)
return Kick(playerid);

return Dialog_Show(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login account...", "{FFFFFF}Good to see you back to SA-MP 0.3.7 Server! Please complete the login formality to access your account (password required).", "Login", "Quit");
}

// player has successfully logged-in
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, MAX_PLAYER_NAME);

new str[150];
format(str, sizeof (str), "Welcome back %s [id: %i]", name, playerid);
SendClientMessage(playerid, 0xFFFFFFFF, str);

ResetPlayerMoney(playerid);
GivePlayerMoney(playerid, p_Account[playerid][E_ACCOUNT_MONEY]);
SetPlayerScore(playerid, p_Account[playerid][E_ACCOUNT_SCORE]);

// update player information
new ip[18];
GetPlayerIp(playerid, ip, sizeof (ip));

DB::Update("Users", p_Account[playerid][E_ACCOUNT_SQLID], 1,
"IP", STRING, ip);

return 1;
}

DIALOG_REGISTER
Code:
DialogIALOG_REGISTER(playerid, response, listitem, inputtext[])
{
// if player clicks "Quit" (will be kicked)
if (!response)
return Kick(playerid);

// check if password is not empty
if (!inputtext[0] || inputtext[0] == ' ')
{
SendClientMessage(playerid, 0xFFFFFFFF, "Invalid password length, cannot be empty.");

return Dialog_Show(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Register account...", "{FFFFFF}Welcome to SA-MP 0.3.7 Server! Please complete this small registeration formality to sign-up with us (password required).", "Register", "Quit");
}

// check for password lengths
new len = strlen(inputtext);
if (len > MAX_PASSWORD_LENGTH || len < MIN_PASSWORD_LENGTH)
{
new str[150];
format(str, sizeof (str), "Invalid password length, must be between %i - %i chars.", MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH);
SendClientMessage(playerid, 0xFFFFFFFF, str);

return Dialog_Show(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Register account...", "{FFFFFF}Welcome to SA-MP 0.3.7 Server! Please complete this small registeration formality to sign-up with us (password required).", "Register", "Quit");
}

// hashing the password
SHA256_PassHash(inputtext, PASSWORD_SALT, p_Account[playerid][E_ACCOUNT_PASSWORD], 64);

// player has successfully registered
new name[MAX_PLAYER_NAME];
GetPlayerName(playerid, name, MAX_PLAYER_NAME);

new str[150];
format(str, sizeof (str), "Welcome %s [id: %i]", name, playerid);
SendClientMessage(playerid, 0xFFFFFFFF, str);

// create player's row into "Users" table
new ip[18];
GetPlayerIp(playerid, ip, sizeof (ip));

DB::CreateRow("Users",
"Name", STRING, name,
"Password", STRING, p_Account[playerid][E_ACCOUNT_PASSWORD],
"IP", STRING, ip);

DB::Fetch("Users", _, _, _, "`Name` = '%q'", name);
p_Account[playerid][E_ACCOUNT_SQLID] = fetch_row_id();
fetcher_close();

return 1;
}

INCREASING KILLS & DEATHS
Code:
public OnPlayerDeath(playerid, killerid, reason)
{
p_Account[playerid][E_ACCOUNT_DEATHS]++; // increase player's deaths count

if (killerid != INVALID_PLAYER_ID)
p_Account[killerid][E_ACCOUNT_KILLS]++; // increase killer's kills count if the id is of a connected player

return 1;
}

SAVING PLAYER DATA
I am only saving player's data when they disconnect/leave the server. Because this callback is called every time no matter if player's PC shuts down or whatever...

Code:
public OnPlayerDisconnect(playerid, reason)
{
DB::Update("Users", p_Account[playerid][E_ACCOUNT_SQLID], 1,
"Kills", INTEGER, p_Account[playerid][E_ACCOUNT_KILLS],
"Deaths", INTEGER, p_Account[playerid][E_ACCOUNT_DEATHS],
"Score", INTEGER, GetPlayerScore(playerid),
"Money", INTEGER, GetPlayerMoney(playerid));
return 1;
}

EXITING EASYDB
EasyDB has to be closed when your server is closed or most importantly in case of filterscripts when they are unloaded:
Code:
// for filterscripts
public OnFilterScriptExit()
{
DB::Exit();
return 1;
}

// if using gamemode
public OnGameModeExit()
{
DB::Exit();
return 1;
}

Optional Commands
A simple password changing command: /changepass
Code:
CMD:changepass(playerid, params[])
{
new password[64];
if (sscanf(params, "s[64]", password))
return SendClientMessage(playerid, 0xFFFFFFFF, "Usage: /changepass [new password]");

// check password's length
new len = strlen(password);
if (len > MAX_PASSWORD_LENGTH || len < MIN_PASSWORD_LENGTH)
{
new str[150];
format(str, sizeof (str), "Invalid password length, must be between %i - %i chars.", MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH);
return SendClientMessage(playerid, 0xFFFFFFFF, str);
}

// hashing the password
SHA256_PassHash(password, PASSWORD_SALT, p_Account[playerid][E_ACCOUNT_PASSWORD], 64);

// update player's password
DB::Update("Users", p_Account[playerid][E_ACCOUNT_SQLID], 1,
"Password", STRING, p_Account[playerid][E_ACCOUNT_PASSWORD]);

return SendClientMessage(playerid, 0xFFFFFFFF, "Password successfully updated.");
}