06.07.2014, 11:39
(
Последний раз редактировалось Twizted; 08.07.2014 в 14:55.
)
A complete login and registration system using y_ini:
ForewordBefore anything, read every single word in this thread, else you'll get everything wrong and you might even get a ton of errors, simply because you have not bothered to read anything. Before asking a question, read the thread once again. It could be an error from my side, but it could also be lack of your attention.
It's been a while since I decided to create a registration system using the y_ini include created by ******. I myself am not a fan of MySQL (maybe because I've not yet bothered to work with it) saving, so I've been using y_ini since it's release.
This login and registration system includes dialogs, used to get the user input for their password, age, gender. mSelection is used to allow the player to select his skin. This system also saves the user's cash and his position (x, y, z) on disconnect. To successfully have this system working, you will need:
y_ini: Download it from this thread. If you do not know how to use y_ini, read this thread.As for the colors you'll see in this thread, I created a folder called "inc" in the Gamemodes folder, created colors.pwn in it, then on the main gamemode, in which we use this sytem we'll be creating, I added the line #include "../gamemodes/inc/colors.pwn". This is the color defines, incase you want to use the colors I used:
mSelection: Download it from this thread.
Whirlpool: Download it from this thread.
pawn Код:
#define COLOR_PURPLE 0xC2A2DAAA
#define COLOR_GRAD2 0xBFC0C2FF
#define COLOR_GRAD1 0xB4B5B7FF
#define COLOR_GRAD2 0xBFC0C2FF
#define COLOR_GREY 0xAFAFAFAA
#define COLOR_GRAD3 0xCBCCCEFF
#define COLOR_LIGHTBLUE 0x006FDD96
#define COLOR_GRAD4 0xD8D8D8FF
#define COLOR_FADE 0xC8C8C8C8
#define COLOR_FADE2 0xC8C8C8C8
#define COLOR_FADE3 0xAAAAAAAA
#define COLOR_FADE4 0x8C8C8C8C
#define COLOR_YELLOW 0xDABB3E00
#define COLOR_FADE5 0x6E6E6E6E
#define COLOR_GRAD5 0xE3E3E3FF
#define COLOR_FADE1 0xE6E6E6E6
#define COLOR_GRAD6 0xF0F0F0FF
#define NORMAL_COL 0xFFFFFF00
pawn Код:
#define GivePlayerCash(%0,%1) SetPVarInt(%0,"Money",GetPlayerCash(%0)+%1),GivePlayerMoney(%0,%1)
#define ResetPlayerCash(%0) SetPVarInt(%0,"Money",0), ResetPlayerMoney(%0)
#define GetPlayerCash(%0) GetPVarInt(%0,"Money")
pawn Код:
#define function%0(%1) forward%0(%1); public%0(%1)
I. The includes
Ontop of your gamemode, you will need to include the includes you will be using for your server, as you might already know. For this tutorial, we will be using y_ini and mSelection, as indicated above. Therefore, you'll have to include them ontop of your script.
pawn Код:#include <a_samp> //Mandatory include
#include <mSelection>
#include <YSI\y_ini>
II. The enumerator
Don't know what an enumerator is? Read this thread.
As I mentioned previously, just a few lines above, we'll be saving the user password, the user's cash, the user's skin, age, deaths, kills and position on disconnect. For that, we will use an enumerator (we'll use an enum to store our variables). If you don't know what an enumerator is, you should read the thread that I linked above.
You can call the enum(erator) whatever you want. Make sure the enum is ontop of everything.
I bet you noticed that there's something I didn't explain: new PlayerInfo[MAX_PLAYERS][pInfo]; We will be using this to later load, save and/or write something to the user's textfile (number of deaths, his age). You'll see the use of it very soon.pawn Код:enum pInfo //Change pInfo (playerInfo) to whatever you'd like, If you wish.
{
pPass[129],
pCash,
pSex,
pAge,
Float:pPos_x,
Float:pPos_y,
Float:pPos_z,
pSkin,
pKills,
pDeaths
}
new PlayerInfo[MAX_PLAYERS][pInfo];
III. Showing the dialogs, creating user file if unexistent
So far, what have we got? We've included the includes we will be using for our system. We've created an enumerator, which will be used to store our variables. Now we need to start creating the visual stuff, if you get my point: the dialogs.
But before proceeding, we'll need to define the dialogs and the path that the user textfile will be created (in the Scriptfiles folder). In regards of the path where the user textfile will be created/stored, you'll need to create a folder in the 'Scriptfiles' folder, named after whatever you want (e.g. "Users", "Accounts"). The definitions are done ontop of the script, just below our includes.
I've created a folder called "Accounts" in the "Scriptfiles" folder (the folder that comes with the SA:MP server package). Incase you've named it different, just change the 'Accounts' text to the name of your folder (e.g. #define PATH "Users/%s.ini").
Now that we have defined the dialogs and the path to store the user textfile in, we will need to create a function that will grab the path we are using to store our users textfiles into.pawn Код:#include <a_samp> //Mandatory include
#include <mSelection>
#include <YSI\y_ini>
#define DIALOG_REGISTER 1
#define DIALOG_LOGIN 2
#define DIALOG_AGE 3
#define DIALOG_SEX 4
#define PATH "Accounts/%s.ini"
We'll also need to create a function to load the user's data. Notice that pSex will be an integer and not a string. That's because we'll be defining "male" as 1 and "female" as 2.pawn Код:stock UserPath(playerid)
{
new string[128], playername[MAX_PLAYER_NAME];
GetPlayerName(playerid,playername,sizeof(playername));
format(string,sizeof(string),PATH,playername);
return string;
}
Now we can work on the OnPlayerConnect callback because we've got everything we need to work on it. As I told you just a few lines above, we'll check if the player's registered (then, login) or not (then, register, creating a path). Let's suppose my in-game name is Twizted.pawn Код:forward LoadUser_data(playerid,name[],value[]);
public LoadUser_data(playerid,name[],value[])
{
INI_Int("Password",PlayerInfo[playerid][pPass]);
INI_Int("Cash",PlayerInfo[playerid][pCash]);
INI_Int("Sex",PlayerInfo[playerid][pSex]);
INI_Int("Age",PlayerInfo[playerid][pAge]);
INI_Float("Pos_x",PlayerInfo[playerid][pPos_x]);
INI_Float("Pos_y",PlayerInfo[playerid][pPos_y]);
INI_Float("Pos_z",PlayerInfo[playerid][pPos_z]);
INI_Int("Skin",PlayerInfo[playerid][pSkin]);
INI_Int("Kills",PlayerInfo[playerid][pKills]);
INI_Int("Deaths",PlayerInfo[playerid][pDeaths]);
return 1;
}
pawn Код:public OnPlayerConnect(playerid)
{
if(fexist(UserPath(playerid))) //If there's a textfile for Twizted, then the user should login
{
INI_ParseFile(UserPath(playerid), "LoadUser_%s", .bExtra = true, .extra = playerid); //Loads the data from the user's textfile (password)
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD,"Login","Type your password below in order to login.","Login","Quit");
}
else //If the path for Twizted in the Scriptfiles/Accounts directory is non-existent, the user is prompted to register or quit
{
ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD,"Register","Type your password below in order to register a new account.","Register","Quit");
}
return 1;
}
IV. The dialogs responses
Woah, that was long! By now, you should just get up, walk around for five minutes and have a drink. There's more to come, and there's more you must follow in order to have a great system. Now, we'll be working on our OnDialogResponse callback. We'll work on a specific order on the dialogs. But before working on the callback, we'll need to create a variable and a boolean. We'll also need to define SECONDS, which we'll use to save each user's account. You'll understand what I mean.
We'll also need to use Whirlpool hashing to hash the user's password as he responds to the registration dialog. The link to download this plugin is at the very start of the thread.pawn Код://Just below your other defines
#define SECONDS(%1) ((%1)*(1000))
//Your variables
new Logged[ MAX_PLAYERS ]; //Checks if the player is logged in.
//Your booleans
new bool:pNewlyRegged[MAX_PLAYERS]; //Checks if player registered
We need a function that will be used to fix the spectate mode @ OnPlayerSpawn.pawn Код://Below your includes
native WP_Hash(buffer[], len, const str[]);
In the callback OnGamemodeInit(), we'll need to set a timer that will be used in our SaveAccounts function.pawn Код:forward SpectateFix(playerid);
public SpectateFix(playerid)
{
SpawnPlayer(playerid);
return true;
}
We will need two functions to save the user's data.pawn Код:public OnGameModeInit()
{
//Anything else you might have in this callback
skinlist = LoadModelSelectionMenu("skins.txt");
SetTimer("SaveAccounts", SECONDS(13), 1);
return 1;
}
Now we're ready to work on the OnDialogResponse callback. Throughout the script, there are comments you must read in order to understand what we're doing.pawn Код:function SaveAccountStats(playerid)
{
if(Logged[playerid] == 1)
{
new
INI:File = INI_Open(UserPath(playerid))
;
INI_SetTag(File,"data"); //Settings tags in y_ini is very important!
PlayerInfo[playerid][pSkin] = GetPlayerSkin(playerid);
PlayerInfo[playerid][pCash] = GetPlayerCash(playerid);
new
Float:x,
Float:y,
Float:z
;
GetPlayerPos(playerid,x,y,z);
PlayerInfo[playerid][pPos_x] = x;
PlayerInfo[playerid][pPos_y] = y;
PlayerInfo[playerid][pPos_z] = z;
INI_WriteInt(File,"Cash",PlayerInfo[playerid][pCash]);
INI_WriteInt(File,"Age",PlayerInfo[playerid][pAge]);
INI_WriteInt(File,"Sex",PlayerInfo[playerid][pSex]);
INI_WriteInt(File,"Skin",PlayerInfo[playerid][pSkin]);
INI_WriteFloat(File,"Pos_x",PlayerInfo[playerid][pPos_x]);
INI_WriteFloat(File,"Pos_y",PlayerInfo[playerid][pPos_y]);
INI_WriteFloat(File,"Pos_z",PlayerInfo[playerid][pPos_z]);
INI_WriteInt(File,"Deaths",PlayerInfo[playerid][pDeaths]);
INI_WriteInt(File,"Kills",PlayerInfo[playerid][pKills]);
INI_Close(File);
}
return 1;
}
function SaveAccounts()
{
for(new i = 0; i < MAX_PLAYERS; i++)
{
if(IsPlayerConnected(i))
{
SaveAccountStats(i); //Will be used to save the account stats
}
}
}
pawn Код:public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
if(dialogid == DIALOG_AGE)
{
TogglePlayerSpectating(playerid, false);
if(!response)
{
Kick(playerid); //If the player clicks the second button
}
else
{
if(strlen(inputtext))
{
new age = strval(inputtext);
if(age > 100 || age < 16) //If the age doesn't meet the requirements (change to whatever you want)
{
ShowPlayerDialog(playerid, DIALOG_AGE, DIALOG_STYLE_INPUT, "Age","How old is your character?\nYour character's age must be between 16 and 100.","Proceed","Quit");
}
else //If the player types a age that meets the requirements
{
PlayerInfo[playerid][pAge] = age; //Sets the player's age
new
string[ 64 ]
;
format(string, sizeof(string), "{3685BA}SERVER: {FFFFFF}You are %d years old.",age);
SendClientMessage(playerid, -1, string);
GivePlayerCash(playerid, 15000); //Gives a $15,000 ammount of starting cash
SaveAccountStats(playerid); //Saves the player's account stats
SpawnPlayer(playerid); //Spawns the player
SetTimer("SpectateFix", 350, false); //Fixes a bug that needs this timer in order to successfully use mSelection
}
}
else
{
return 0;
}
}
}
if(dialogid == DIALOG_SEX)
{
if(response)
{
PlayerInfo[playerid][pSex] = 1; // Male = 1
SendClientMessage(playerid, -1, "{3685BA}SERVER: {FFFFFF}You have set your character's gender. You are a male.");
ShowPlayerDialog(playerid, DIALOG_AGE, DIALOG_STYLE_INPUT, "Age","How old is your character?\nYour character's age must be between 16 and 100.","Proceed","Quit"); //After the sex selection, you're sent to the age selection dialog
}
else
{
PlayerInfo[playerid][pSex] = 2; //Female = 2
SendClientMessage(playerid, -1, "{3685BA}SERVER: {FFFFFF}You have set your character's gender. You are a female.");
ShowPlayerDialog(playerid, DIALOG_AGE, DIALOG_STYLE_INPUT, "Age","How old is your character?\nYour character's age must be between 16 and 100.","Proceed","Quit");//After the sex selection, you're sent to the age selection dialog
}
}
switch( dialogid )
{
case DIALOG_REGISTER:
{
if (!response) return Kick(playerid); //If the player clicks the second button
if(response) //If he clicks the first button
{
if(!strlen(inputtext)) return ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_INPUT, "Registering","You have entered an invalid password.\nType your password below to register a new account.","Register","Quit"); //This happens if the user has input no password. You can add limits here.
new buf[129]; //Now we'll use our Whirlpool hashing
new INI:File = INI_Open(UserPath(playerid)); //Opening the user file
INI_SetTag(File,"data"); //Settings tags
WP_Hash(buf, sizeof(buf), inputtext); //Hashing the inputtext (password)
INI_WriteString(File,"Password", buf); //Writes a string, the password, hashed
INI_WriteInt(File,"Cash",0);
INI_WriteInt(File,"Skin",0);
INI_WriteInt(File,"Sex",0);
INI_WriteInt(File,"Age",0);
INI_WriteFloat(File,"Pos_x",0);
INI_WriteFloat(File,"Pos_y",0);
INI_WriteFloat(File,"Pos_z",0);
INI_WriteInt(File,"Deaths",0);
INI_WriteInt(File,"Kills",0);
INI_Close(File);
pNewlyRegged[playerid] = true; //Changing the value of our boolean
ShowPlayerDialog(playerid, DIALOG_SEX, DIALOG_STYLE_MSGBOX, "Sex","What is the gender of your character?","Male","Female"); //After the registration, you must select your gender
}
}
case DIALOG_LOGIN:
{
if ( !response ) return Kick ( playerid ); //If the player clicks the second button
if( response )
{
new hashpass[129];
WP_Hash(hashpass,sizeof(hashpass),inputtext);
if(!strcmp(hashpass, PlayerInfo[playerid][pPass], false)) //Compares the inputtext to the player's pass, pPass.
{
INI_ParseFile(UserPath(playerid), "LoadUser_%s", .bExtra = true, .extra = playerid);
TogglePlayerSpectating(playerid, false);
SetTimerEx("UnsetFirstSpawn", 5000, false, "i", playerid); //We're using this for other purposes, to fix a bug in skin selection (OnPlayerRequestClass)
Logged[ playerid ] = 1; //giving the 1 ("true") value to our variable "Logged"
GivePlayerCash(playerid, PlayerInfo[playerid][pCash]); //Gives the player the cash he has
SetSpawnInfo(playerid, 0 /*Because the player has no team*/, PlayerInfo[playerid][pSkin], PlayerInfo[playerid][pPos_x], PlayerInfo[playerid][pPos_y], PlayerInfo[playerid][pPos_z], 1.0, -1, -1, -1, -1, -1, -1); //Sets the player's spawn information
}
else
{
ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_INPUT,"Login","You have entered an incorrect password.\nType your password below to login.","Login","Quit"); //If the player has entered an invalid password, he's prompted to retype his password
}
return 1;
}
}
}
return 1;
}
V. OnPlayerSpawn (skin selection menu, player positioning)
Another long paragraph. Time for another drink, maybe?
We've completed the dialog responses. Well, most of them. We still need to work on our skin selection, after the spawn. You should've set up mSelection by now (atleast the part of including it, adding it to the pawno/include folder and adding the skins.txt file to the Scriptfiles folder). If you haven't, do it now.
And to have no errors while working on the skin selection, you must add the following line ontop of your skin (remember, always below your includes).
Now we can work on the OnPlayerSpawn callback. Read the comments throughout the script. But we'll need to create a function called RandomSpawnLoc(playerid); in order to randomly set the player's position if he has just registered.pawn Код:new skinlist = mS_INVALID_LISTID;
pawn Код:stock RandomSpawnLoc(playerid)
{
switch(random(3))
{
case 0: // ls
{
SetPlayerPos(playerid, 1642.1678, -2240.5181, 13.4952);
SendClientMessage(playerid, -1, "{3685BA}SERVER: {FFFFFF}You have spawned in Los Santos! This where you start your life.");
}
case 1: // sf
{
SetPlayerPos(playerid, -1409.0891, -306.1781, 14.1484);
SendClientMessage(playerid, -1, "{3685BA}SERVER: {FFFFFF}You have spawned in San Fierro! This where you start your life.");
}
case 2: // lv
{
SetPlayerPos(playerid, 1689.2460, 1446.9261, 10.7672);
SendClientMessage(playerid, -1, "{3685BA}SERVER: {FFFFFF}You have spawned in Las Venturas! This where you start your life.");
}
}
return 1;
}pawn Код:public OnPlayerSpawn(playerid)
{
if(pNewlyRegged[playerid]) //If the player is a newly registered player
{
ShowModelSelectionMenu(playerid, skinlist, "Skin Selection"); //Shows the skin selection menu for the player
}
if(PlayerInfo[playerid][pPos_x] == 0 && PlayerInfo[playerid][pPos_y] == 0) //Pretty much means that if the player isn't registered, his position is set to the position indicated in RandomSpawnLoc function
{
RandomSpawnLoc(playerid); //Sets player position randomly
Logged[playerid] = 1; //Changes the value of our variable "Logged" to 1("true").
}
else //If the player logged in
{
SetPlayerPos(playerid, PlayerInfo[playerid][pPos_x], PlayerInfo[playerid][pPos_y], PlayerInfo[playerid][pPos_z]); //Sets the player position to his last position
Logged[playerid] = 1;
}
return 1;
}
VI. Player selecting skin
The last paragraph wasn't so long, thankfully. And we're almost done here. Now we need to work on the the mSelection callback called OnPlayerModelSelection.
pawn Код:public OnPlayerModelSelection(playerid, response, listid, modelid)
{
if(listid == skinlist)
{
if(response)
{
new string[129];
format(string, sizeof(string), "{3685BA}SERVER: {FFFFFF}You have set your skin to ID %d!", modelid);
SendClientMessage(playerid, -1, string);
SetPlayerSkin(playerid, modelid);
new INI:File = INI_Open(UserPath(playerid));
INI_SetTag(File,"data");
INI_WriteInt(File,"Skin",modelid);
INI_Close(File);
}
else
{
SendClientMessage(playerid, -1, "{3685BA}SERVER: {FFFFFF}You must select a skin.");
ShowModelSelectionMenu(playerid, skinlist, "Skin Selection");
return 1;
}
}
return 1;
}
VII. OnPlayerDeath: Kills and Deaths count
This is most likely to be considered the easiest part of this tutorial. We want to increase the kills of the killerid and increase the deaths of the playerid. Therefore, what we have to add under the callback is simple:
pawn Код:public OnPlayerDeath( playerid, killerid, reason )
{
PlayerInfo[playerid][pDeaths] ++; // +1 death every time the player dies.
if( killerid != INVALID_PLAYER_ID )
{ // If the killerid is a valid player
PlayerInfo[killerid][pKills] ++; // +1 Kill every time the killerid a valid player
}
//Anything else you might have in this callback
return 1;
}
VIII. OnPlayerDisconnect: Saving the player's stats
Yes, you know this is the end. It's been a long journey, right? Well, not really If you've just copied the code between the tags, but if you've read everything, you're the man. Thanks for the time.
Remember we created a function called SaveAccountStats(playerid); on step IV? Well, we will use that to save the user's stats when he disconnects from your server (well, you certainly do not wish this to happen too often!). You just call the function inside the callback and you're done!
pawn Код:public OnPlayerDisconnect(playerid, reason)
{
SaveAccountStats(playerid); //Calls the function, saves the stats
return 1;
}
The End
This is the end of this big tutorial. I've certainly spent 3 to 4 hours writing this out, but luckily having the help of other threads which saved some time. I hope that you understood everything, and if you haven't, let me know. If you come across any errors, please, let me know. But as I said at the very first lines, read every single word in this thread, else you'll get everything wrong and you might even get a ton of errors, simply because you have not bothered to read anything. Before asking a question, read the thread once again. It could be an error from my side, but it could also be lack of your attention. I hope this has been a precious help to your gamemode.