04.08.2013, 02:35
(
Last edited by 2KY; 20/12/2013 at 10:46 PM.
)
Introduction
I know what you're thinking - "ANOTHER Y_INI tutorial?!", and yes, it has to do with Y_INI, but I promise that this tutorial is a bit different from the other ones that you have seen. I've viewed them myself before creating this tutorial, and have concluded that they all make Y_INI seem like a foreign language - it's really easy once you get the hang of it!What do I need?
In order to use Y_INI, you're going to need to download YSI. You can do so by going to the YSI topic and following the instructions there. It's a very simple installation, and it gives you access to a whole plethora of new features.Creating a User System (Register/Login)
Creating our Dialog EnumThis simple nameless enum will assign a number value to each new dialog which is entered. This helps to prevent dialog ID mismatches that come from definitions! No more changing dialog ids around to make sure they don't conflict - this automatically makes sure they don't conflict.Creating our User Variables
pawn Code:enum
{
DiagRegister, // Our Register Dialog (ID 0)
DiagLogin // Our Login Dialog (ID 1)
}First off, we will create our account enum, this stores all of our account variables! This is what is saved/loaded in this tutorial.Useful Definitions
Next, we will create our PlayerInfo array along with a simple bool (true/false) variable which lets us detect if someone has logged in or not, but they don't do anything yet, we're simply creating them right now!pawn Code:enum AccountVariables
{
Password [129], // a string with a length of 129 cells.
Admin, // a simple integer used to store our Administrator value
Float: LastX, // a float used to store the last known X coordinate
Float: LastY, // a float used to store the last known Y coordinate
Float: LastZ, // a float used to store the last known Z coordinate
Float: LastA // a float used to store the last known angle (which is also a coordinate)
}
pawn Code:new
PlayerInfo [ MAX_PLAYERS ] [ AccountVariables ],
bool: PlayerLogged [ MAX_PLAYERS ] = false;These are a couple definitions that I personally use to make things easier - you can choose to do things the hard way, or you can spend a couple minutes creating something that will save you time in the long run.Useful Functions
pawn Code:#define ACCOUNT_PATH "Players/%s.ini"
#define ShowLoginDialog ShowPlayerDialog (playerid, DiagLogin, DIALOG_STYLE_PASSWORD, "Welcome back!", "Please enter your account password to continue.", "OK", "Quit")
#define ShowRegisterDialog ShowPlayerDialog (playerid, DiagRegister, DIALOG_STYLE_INPUT, "Welcome to My Server!", "Please enter a password for your account.", "OK", "Quit")While you don't necessarily NEED this bit of code, it definitely helps to have it! This takes our "ACCOUNT_PATH" definition and inserts their name, allowing us to figure out where their file will be located.Part 1: What happens when a player connects?
pawn Code:GetPlayerPath (playerid)
{
new
PlayerPath [64],
PlayerName [MAX_PLAYER_NAME];
GetPlayerName (playerid, PlayerName, MAX_PLAYER_NAME);
format (PlayerPath, sizeof PlayerPath, ACCOUNT_PATH, PlayerName);
return PlayerPath;
}
Well, what do you WANT to happen when a player connects? For this tutorial, we will be showing them a register or a login dialog (depending if their file exists or not) and asking them to authenticate their account. Depending if they have to register or login, this will either create their file and load their information, or just simply load their information.Part 2: What happens when a player disconnects?
Now that we have shown them the dialog, what next? You'll quickly find out that the dialog you just showed them does absolutely nothing! Why is this? This is because you have not put any code under OnDialogResponse! OnDialogResponse handles everything that happens inside of a dialog - from pressing the ESC key, to pressing buttons.pawn Code:public OnPlayerConnect(playerid)
{
if(fexist(GetPlayerPath(playerid)))
{
INI_ParseFile (GetPlayerPath (playerid), "LoadAccount_%s", .bExtra = true, .extra = playerid);
/* INI_ParseFile - it may seem VERY complicated, but it's really pretty simple!
The parameters of it are like so:
INI_ParseFile(PATH, FUNCTION, SEND EXTRA DATA?, EXTRA DATA);
Keeping that in mind, we are loading the file at "Players/(playername).ini" through
the callback "LoadAccount" and sending our playerid as extra data. */
ShowLoginDialog;
}
else
{
ShowRegisterDialog;
/* You can only use "ShowRegisterDialog;" if you used my "useful" definitions above, otherwise
just show them the "DiagRegister" dialog. */
}
return 1;
}
Now, we're going to save all of their information (except their password) every time they disconnect.Part 3: Making the Register dialog work correctly
pawn Code:public OnPlayerDisconnect(playerid, reason)
{
if (PlayerLogged [playerid] == true) // Make sure they're logged in first!
{
new
INI: Player = INI_Open ( GetPlayerPath (playerid) ), // Open their file!
Float: CurX,
Float: CurY,
Float: CurZ,
Float: CurA;
GetPlayerPos (playerid, CurX, CurY, CurZ);
GetPlayerFacingAngle (playerid, CurA);
INI_SetTag (Player, "data");
// Make sure every time you write anything to their file, you set the tag!
INI_WriteInt (Player, "Admin", PlayerInfo [playerid] [Admin]);
INI_WriteFloat (Player, "LastX", CurX); // Write their X coordinate
INI_WriteFloat (Player, "LastY", CurY); // Write their Y coordinate
INI_WriteFloat (Player, "LastZ", CurZ); // Write their Z coordinate
INI_WriteFloat (Player, "LastA", CurA); // Write their Angle coordinate
INI_Close (Player); // Close the file!
PlayerLogged [playerid] = false; // Set them as logged out!
}
return 1;
}
I will explain everything that is inside of the callback (OnDialogResponse) inside of the code itself, so please read along with the green comments next to the code and DO NOT simply copy & paste the code into your script, this does not help you learn, and it ends up hurting you more than it helps you.Part 4: Making the Login dialog work correctly
pawn Code:public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
switch (dialogid) // This is our switch statement, it allows us to go through our dialogid's.
{
case DiagRegister: // This allows us to handle what happens inside of the Register dialog.
{
if (response) // Any code inside of here will be executed if they press the first button.
{
new
INI: Player = INI_Open ( GetPlayerPath (playerid) );
/* This creates our INI file ("Player") and opens the file that is located at
"Players/(playername).ini", this could be different for you, depending on if
you edited the "ACCOUNT_PATH" definition. */
INI_SetTag (Player, "data");
/* The thing that makes Y_INI so weird to people is the use of tags - this allows
us to separate our INI files into sections, in this tutorial we will only be using
one tag - "data", but remember that for later! */
INI_WriteString (Player, "Password", inputtext);
/* This stores whatever we wrote in our dialog into the "Password" variable inside of
our enum. I also HIGHLY recommend hashing your passwords using Whirlpool! */
INI_WriteInt (Player, "Admin", 0);
// This stores the value of zero into the "Admin" variable inside of our enum.
INI_Close (Player);
// This closes our file ("Player") - ALWAYS remember to do this!
INI_ParseFile (GetPlayerPath (playerid), "LoadAccount_%s", .bExtra = true, .extra = playerid);
/* INI_ParseFile - it may seem VERY complicated, but it's really pretty simple!
The parameters of it are like so:
INI_ParseFile(PATH, FUNCTION, SEND EXTRA DATA?, EXTRA DATA);
Keeping that in mind, we are loading the file at "Players/(playername).ini" through
the callback "LoadAccount" and sending our playerid as extra data. */
PlayerLogged [playerid] = true;
// Now that their file is created and loaded, we can set them as logged in!
SetSpawnInfo (playerid, NO_TEAM, 119, 0.0, 0.0, 5.0, 0.0, 0,0, 0,0, 0,0);
SpawnPlayer (playerid);
// Simple spawn code for testing purposes.
}
else // Any code inside of here will be executed if they press the second button or ESC key.
{
SendClientMessage (playerid, -1, "Sorry to hear that! Hopefully we meet again.");
Kick (playerid);
// Send them a message and kick them if they click "Quit"
}
}
}
return 1;
}
Inside of our switch statement, under the DiagRegister case, we're going to add our DiagLogin case! Once again, I will explain everything in comments in the code.Part 5: Creating the "LoadAccount" function
pawn Code:case DiagLogin:
{
if (response) // They clicked the first button!
{
if( isnull (inputtext) ) // Assuming you have sscanf, this is already included!
{
/* This is NOT optional, without it if you just press the button on the dialog
it will automatically log you in, without any password. */
ShowLoginDialog;
return 0;
}
if ( strcmp (inputtext, PlayerInfo [playerid] [Password], true) == 0 )
{ // This checks if the password they entered on the dialog is the same as their file.
PlayerLogged [playerid] = true; // Set them as logged in!
// Spawn them at their last known position!
SetSpawnInfo (playerid, NO_TEAM, 119, PlayerInfo [playerid] [LastX], PlayerInfo [playerid] [LastY], PlayerInfo [playerid] [LastZ], \
PlayerInfo [playerid] [LastA], 0,0, 0,0, 0,0
);
SpawnPlayer (playerid);
}
else // What happens if their password is wrong?
{
SendClientMessage (playerid, -1, "Incorrect password.");
ShowLoginDialog;
}
}
else // Any code inside of here will be executed if they press the second button or ESC key.
{
SendClientMessage (playerid, -1, "Sorry to hear that! Hopefully we meet again.");
Kick (playerid);
// Send them a message and kick them if they click "Quit"
}
}
If you have tried your code up til now, you will notice that no loading functions work. If you're curious as to why, wrap your mind around this - in our INI_ParseFile function, we transfer the data to a "LoadAccount" function - did we create that function yet? No we have not, and THAT is why loading is non functional as of right now. Let's get to it, shall we?Part 6: Just kidding, there is no part 6
pawn Code:forward LoadAccount_data (playerid, name[], value[]);
public LoadAccount_data (playerid, name[], value[])
{
// You'll notice that the actual function name is "LoadAccount_data",
// "data" is the tag we used above, therefore it'll load correctly.
// "playerid" is the value we passed from our INI_ParseFile function.
// "name" is the value we are reading ("Password", "Admin", ect)
// "value" is the value of the "name" we are currently reading
INI_String ("Password", PlayerInfo [playerid] [Password], 129); // Loads "Password" into our PlayerInfo array.
INI_Int ("Admin", PlayerInfo [playerid] [Admin]); // Loads "Admin" into our PlayerInfo array.
INI_Float ("LastX", PlayerInfo [playerid] [LastX]); // Loads "LastX" into our PlayerInfo array.
INI_Float ("LastY", PlayerInfo [playerid] [LastY]); // Loads "LastY" into our PlayerInfo array.
INI_Float ("LastZ", PlayerInfo [playerid] [LastZ]); // Loads "LastZ" into our PlayerInfo array.
INI_Float ("LastA", PlayerInfo [playerid] [LastA]); // Loads "LastA" into our PlayerInfo array.
return 1;
}
Congratulations! You have completed your first Y_INI register/login system, how does it feel? You can only get better from here, and I hope everything was explained well enough to allow you to learn. If there is anything I can better explain, then please reply and let me know. With all of that being said, take a breath of relief that this HUGE tutorial is done, and you have a fully functioning user system!