· 9 years ago · Jan 29, 2017, 02:08 AM
1#include <a_samp>
2#include <a_mysql>
3
4//Configuring MySQL Stuff to set up the connection
5
6#define MYSQL_HOST "Your DB Host" // Change this to your MySQL Remote IP or "localhost".
7#define MYSQL_USER "Your DB User" // Change this to your MySQL Database username.
8#define MYSQL_PASS "Your DB Pass" // Change this to your MySQL Database password.
9#define MYSQL_DATABASE "Your DB Name" // Change this to your MySQL Database name.
10
11// Well, don't just enter random information and expect it to work, information
12// Should be valid and working fine.
13
14#define DIALOG_REGISTER (0)
15#define DIALOG_LOGIN (1)
16
17// Make sure the dialog IDs above do not match any dialog ID you're using in your
18// gamemode otherwise they won't do their job properly.
19
20new
21 MySQL: Database, Corrupt_Check[MAX_PLAYERS];
22
23// Now creating an enumerator to store player's data for further use (as below).
24
25enum ENUM_PLAYER_DATA
26{
27 ID,
28
29 Password[65],
30 Salt[11],
31
32 PasswordFails,
33
34 Kills,
35 Deaths,
36
37 Score,
38 Cash,
39
40 Cache: Player_Cache,
41 LoggedIn
42}
43
44new pInfo[MAX_PLAYERS][ENUM_PLAYER_DATA];
45
46//==============================================================================
47
48public OnGameModeInit()
49{
50 new MySQLOpt: option_id = mysql_init_options();
51 mysql_set_option(option_id, AUTO_RECONNECT, true); // We will set that option to automatically reconnect on timeouts.
52
53 Database = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DATABASE, option_id); // Setting up the "Database" handle on the given MySQL details above.
54
55 if(Database == MYSQL_INVALID_HANDLE || mysql_errno(Database) != 0) // Checking if the database connection is invalid to shutdown.
56 {
57 print("I couldn't connect to the MySQL server, closing."); // Printing a message to the log.
58
59 SendRconCommand("exit"); // Sending console command to shut down server.
60 return 1;
61 }
62
63 print("I have connected to the MySQL server."); // If the given MySQL details were all okay, this message prints to the log.
64
65 // Now, we will set up the information table of the player's information.
66
67 mysql_tquery(Database, "CREATE TABLE IF NOT EXISTS `PLAYERS` (`ID` int(11) NOT NULL AUTO_INCREMENT,`USERNAME` varchar(24) NOT NULL,`PASSWORD` char(65) NOT NULL,`SALT` char(11) NOT NULL,`SCORE` mediumint(7), `KILLS` mediumint(7), `CASH` mediumint(7) NOT NULL DEFAULT '0',`DEATHS` mediumint(7) NOT NULL DEFAULT '0', PRIMARY KEY (`ID`), UNIQUE KEY `USERNAME` (`USERNAME`))");
68
69 // So, this code is probably the only one which you haven't understood.
70 // Well, we firstly create a table only if not existing in the database which is "USERS".
71 // We create "ID" and set it as a primary key with auto increment to use it in retrieving information and many more uses.
72 // We create "USERNAME" and set it as a unique key, the USERNAME stores every player's name in the database so you can
73 // Control the players in offline mode and when a player leaves everything storted like kills, deaths, password and Saltion key
74 // Wouldn't be lost upon server's close or player's disconnection.
75 // We store kills, deaths, score and cash as written above so they might be useful for further use.
76
77 return 1;
78}
79
80public OnGameModeExit()
81{
82 for(new i = 0, j = GetPlayerPoolSize(); i <= j; i++) // Getting the highest player ID in the game.
83 {
84 if(IsPlayerConnected(i)) // Checking if the players stored in "i" are connected.
85 {
86 OnPlayerDisconnect(i, 1); // We do that so players wouldn't lose their data upon server's close.
87 }
88 }
89
90 mysql_close(Database); // Closing the database.
91 return 1;
92}
93
94public OnPlayerConnect(playerid)
95{
96 new DB_Query[115], pName[MAX_PLAYER_NAME];
97
98 //Resetting player information.
99 pInfo[playerid][Kills] = 0;
100 pInfo[playerid][Deaths] = 0;
101 pInfo[playerid][PasswordFails] = 0;
102
103 GetPlayerName(playerid, pName, sizeof(pName)); // Getting the player's name.
104 Corrupt_Check[playerid]++;
105
106 mysql_format(Database, DB_Query, sizeof(DB_Query), "SELECT * FROM `PLAYERS` WHERE `USERNAME` = '%e' LIMIT 1", pName);
107 mysql_tquery(Database, DB_Query, "OnPlayerDataCheck", "ii", playerid, Corrupt_Check[playerid]);
108 return 1;
109}
110
111public OnPlayerDisconnect(playerid, reason)
112{
113 Corrupt_Check[playerid]++;
114
115 new DB_Query[256];
116 //Running a query to save the player's data using the stored stuff.
117 mysql_format(Database, DB_Query, sizeof(DB_Query), "UPDATE `PLAYERS` SET `SCORE` = %d, `CASH` = %d, `KILLS` = %d, `DEATHS` = %d WHERE `ID` = %d LIMIT 1",
118 pInfo[playerid][Score], pInfo[playerid][Cash], pInfo[playerid][Kills], pInfo[playerid][Deaths], pInfo[playerid][ID]);
119
120 mysql_tquery(Database, DB_Query);
121
122 if(cache_is_valid(pInfo[playerid][Player_Cache])) //Checking if the player's cache ID is valid.
123 {
124 cache_delete(pInfo[playerid][Player_Cache]); // Deleting the cache.
125 pInfo[playerid][Player_Cache] = MYSQL_INVALID_CACHE; // Setting the stored player Cache as invalid.
126 }
127
128 pInfo[playerid][LoggedIn] = 0;
129
130 return 1;
131}
132
133public OnPlayerDeath(playerid, killerid, reason)
134{
135 if(killerid != INVALID_PLAYER_ID) // Checking if the killer of the player is valid.
136 {
137 //Increasing the kills of the killer and the deaths of the player.
138 pInfo[killerid][Kills]++;
139 pInfo[playerid][Deaths]++;
140 }
141 return 1;
142}
143
144public OnPlayerRequestSpawn(playerid)
145{
146 if(pInfo[playerid][LoggedIn] == 0) return 0; // Ignoring the request incase player isn't logged in.
147 return 1;
148}
149
150public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
151{
152 switch (dialogid)
153 {
154 case DIALOG_LOGIN:
155 {
156 if(!response) return Kick(playerid);
157
158 new Salted_Key[65];
159 SHA256_PassHash(inputtext, pInfo[playerid][Salt], Salted_Key, 65);
160
161 if(strcmp(Salted_Key, pInfo[playerid][Password]) == 0)
162 {
163 // Now, password should be correct as well as the strings
164 // Matched with each other, so nothing is wrong until now.
165
166 // We will activate the cache of player to make use of it e.g.
167 // Retrieve their data.
168
169 cache_set_active(pInfo[playerid][Player_Cache]);
170
171 // Okay, we are retrieving the information now..
172 cache_get_value_int(0, "ID", pInfo[playerid][ID]);
173
174 cache_get_value_int(0, "KILLS", pInfo[playerid][Kills]);
175 cache_get_value_int(0, "DEATHS", pInfo[playerid][Deaths]);
176
177 cache_get_value_int(0, "SCORE", pInfo[playerid][Score]);
178 cache_get_value_int(0, "CASH", pInfo[playerid][Cash]);
179
180 SetPlayerScore(playerid, pInfo[playerid][Score]);
181
182 ResetPlayerMoney(playerid);
183 GivePlayerMoney(playerid, pInfo[playerid][Cash]);
184
185 // So, we have successfully retrieved data? Now deactivating the cache.
186
187 cache_delete(pInfo[playerid][Player_Cache]);
188 pInfo[playerid][Player_Cache] = MYSQL_INVALID_CACHE;
189
190 pInfo[playerid][LoggedIn] = 1;
191 SendClientMessage(playerid, 0x00FF00FF, "Logged in to the account.");
192 }
193 else
194 {
195 new String[150], pName[MAX_PLAYER_NAME];
196 GetPlayerName(playerid, pName, sizeof(pName));
197
198 pInfo[playerid][PasswordFails] += 1;
199 printf("%s has been failed to login. (%d)", pName, pInfo[playerid][PasswordFails]);
200 // Printing the message that someone has failed to login to his account.
201
202 if (pInfo[playerid][PasswordFails] >= 3) // If the fails exceeded the limit we kick the player.
203 {
204 format(String, sizeof(String), "%s has been kicked Reason: {FF0000}(%d/3) Login fails.", pName, pInfo[playerid][PasswordFails]);
205 SendClientMessageToAll(0x969696FF, String);
206 Kick(playerid);
207 }
208 else
209 {
210 // If the player didn't exceed the limits we send him a message that the password is wrong.
211 format(String, sizeof(String), "Wrong password, you have %d out of 3 tries.", pInfo[playerid][PasswordFails]);
212 SendClientMessage(playerid, 0xFF0000FF, String);
213
214 format(String, sizeof(String), "{FFFFFF}Welcome back, %s.\n\n{0099FF}This account is already registered.\n\
215 {0099FF}Please, input your password below to proceed to the game.\n\n", pName);
216 ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login System", String, "Login", "Leave");
217 }
218 }
219 }
220 case DIALOG_REGISTER:
221 {
222 if(!response) return Kick(playerid);
223
224 if(strlen(inputtext) <= 5 || strlen(inputtext) > 60)
225 {
226 // If the password length is less than or equal to 5 and more than 60
227 // It repeats the process and shows error message as seen below.
228
229 SendClientMessage(playerid, 0x969696FF, "Invalid password length, should be 5 - 60.");
230
231 new String[150], pName[MAX_PLAYER_NAME];
232 GetPlayerName(playerid, pName, sizeof(pName));
233
234 format(String, sizeof(String), "{FFFFFF}Welcome %s.\n\n{0099FF}This account is not registered.\n\
235 {0099FF}Please, input your password below to proceed.\n\n", pName);
236 ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration System", String, "Register", "Leave");
237 }
238 else
239 {
240
241 // Salting the player's password using SHA256 for a better security.
242
243 for (new i = 0; i < 10; i++)
244 {
245 pInfo[playerid][Salt][i] = random(79) + 47;
246 }
247
248 pInfo[playerid][Salt][10] = 0;
249 SHA256_PassHash(inputtext, pInfo[playerid][Salt], pInfo[playerid][Password], 65);
250
251 new DB_Query[225], pName[MAX_PLAYER_NAME];
252 GetPlayerName(playerid, pName, sizeof(pName));
253
254 // Storing player's information if everything goes right.
255 mysql_format(Database, DB_Query, sizeof(DB_Query), "INSERT INTO `PLAYERS` (`USERNAME`, `PASSWORD`, `SALT`, `SCORE`, `KILLS`, `CASH`, `DEATHS`)\
256 VALUES ('%e', '%s', '%e', '20', '0', '0', '0')", pName, pInfo[playerid][Password], pInfo[playerid][Salt]);
257 mysql_tquery(Database, DB_Query, "OnPlayerRegister", "d", playerid);
258 }
259 }
260 }
261 return 1;
262}
263
264forward public OnPlayerDataCheck(playerid, corrupt_check);
265public OnPlayerDataCheck(playerid, corrupt_check)
266{
267 if (corrupt_check != Corrupt_Check[playerid]) return Kick(playerid);
268 // You'd have asked already what's corrput_check and how it'd benefit me?
269 // Well basically MySQL query takes long, incase a player leaves while its not proceeded
270 // With ID 1 for example, then another player comes as ID 1 it'll basically corrupt the data
271 // So, once the query is done, the player will have the wrong data assigned for himself.
272
273 new String[150], pName[MAX_PLAYER_NAME];
274 GetPlayerName(playerid, pName, sizeof(pName));
275
276 if(cache_num_rows() > 0)
277 {
278 // If the player exists, everything is okay and nothing is wrongly detected
279 // The player's password and Saltion key gets stored as seen below
280 // So we won't have to get a headache just to match player's password.
281
282 cache_get_value(0, "PASSWORD", pInfo[playerid][Password], 65);
283 cache_get_value(0, "SALT", pInfo[playerid][Salt], 11);
284
285 pInfo[playerid][Player_Cache] = cache_save();
286 // ^ Storing the cache ID of the player for further use later.
287
288 format(String, sizeof(String), "{FFFFFF}Welcome back, %s.\n\n{0099FF}This account is already registered.\n\
289 {0099FF}Please, input your password below to proceed to the game.\n\n", pName);
290 ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login System", String, "Login", "Leave");
291 }
292 else
293 {
294 format(String, sizeof(String), "{FFFFFF}Welcome %s.\n\n{0099FF}This account is not registered.\n\
295 {0099FF}Please, input your password below to proceed to the game.\n\n", pName);
296 ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration System", String, "Register", "Leave");
297 }
298 return 1;
299}
300
301forward public OnPlayerRegister(playerid);
302public OnPlayerRegister(playerid)
303{
304 // This gets called only when the player registers a new account.
305 SendClientMessage(playerid, 0x00FF00FF, "You are now registered and has been logged in.");
306 pInfo[playerid][LoggedIn] = 1;
307 return 1;
308}