· 5 years ago · Jun 20, 2020, 12:20 PM
1#include <a_samp>
2#include <a_mysql>
3#include <foreach>
4#include "sscanf2"
5
6main()
7{
8 print("\n----------------------------------");
9 print(" Blank Gamemode by your name here");
10 print("----------------------------------\n");
11}
12
13
14new
15 Text: LoginTD[2],
16 PlayerText: LoginPTD[MAX_PLAYERS];
17
18
19#define MYSQL_HOST "localhost" // Change this to your MySQL Remote IP or "localhost".
20#define MYSQL_USER "root" // Change this to your MySQL Database username.
21#define MYSQL_PASS "" // Change this to your MySQL Database password.
22#define MYSQL_DATABASE "gamemode" // Change this to your MySQL Database name.
23
24// Well, don't just enter random information and expect it to work, information
25// Should be valid and working fine.
26
27#define DIALOG_REGISTER (0)
28#define DIALOG_LOGIN (1)
29
30// Make sure the dialog IDs above do not match any dialog ID you're using in your
31// gamemode otherwise they won't do their job properly.
32
33new MySQL: Database, Corrupt_Check[MAX_PLAYERS];
34new string[128];
35
36//Creating an enumerator to store player's data for further use (below).
37
38//==============================================================================
39
40enum ENUM_PLAYER_DATA
41{
42 ID,
43 Name[25],
44
45 Password[65],
46 Salt[11],
47
48 PasswordFails,
49
50 Kills,
51 Deaths,
52
53 Score,
54 Cash,
55
56 Cache: Player_Cache,
57 bool:LoggedIn
58}
59
60new pInfo[MAX_PLAYERS][ENUM_PLAYER_DATA];
61
62//==============================================================================
63
64public OnGameModeInit()
65{
66 new MySQLOpt: option_id = mysql_init_options();
67 mysql_set_option(option_id, AUTO_RECONNECT, true); // We will set that option to automatically reconnect on timeouts.
68
69 Database = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DATABASE, option_id); // Setting up the "Database" handle on the given MySQL details above.
70
71 if(Database == MYSQL_INVALID_HANDLE || mysql_errno(Database) != 0) // Checking if the database connection is invalid to shutdown.
72 {
73 print("I couldn't connect to the MySQL server, closing."); // Printing a message to the log.
74
75 SendRconCommand("exit"); // Sending console command to shut down server.
76 return 1;
77 }
78
79 print("I have connected to the MySQL server."); // If the given MySQL details were all okay, this message prints to the log.
80
81 // Now, we will set up the information table of the player's information.
82
83 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`))");
84
85 // So, this code is probably the only one which you haven't understood.
86 // Well, we firstly create a table only if not existing in the database which is "USERS".
87 // We create "ID" and set it as a primary key with auto increment to use it in retrieving information and many more uses.
88 // We create "USERNAME" and set it as a unique key, the USERNAME stores every player's name in the database so you can
89 // Control the players in offline mode and when a player leaves everything storted like kills, deaths, password and Saltion key
90 // Wouldn't be lost upon server's close or player's disconnection.
91 // We store kills, deaths, score and cash as written above so they might be useful for further use.
92 return 1;
93}
94
95public OnGameModeExit()
96{
97 foreach(new i: Player)
98 {
99 if(IsPlayerConnected(i)) // Checking if the players stored in "i" are connected.
100 {
101 OnPlayerDisconnect(i, 1); // We do that so players wouldn't lose their data upon server's close.
102 }
103 }
104
105 mysql_close(Database); // Closing the database.
106 return 1;
107}
108
109public OnPlayerConnect(playerid)
110{
111 new DB_Query[115];
112
113 //Resetting player information.
114 pInfo[playerid][Kills] = 0;
115 pInfo[playerid][Deaths] = 0;
116 pInfo[playerid][PasswordFails] = 0;
117
118 GetPlayerName(playerid, pInfo[playerid][Name], MAX_PLAYER_NAME); // Getting the player's name.
119 Corrupt_Check[playerid]++;
120
121 mysql_format(Database, DB_Query, sizeof(DB_Query), "SELECT * FROM `PLAYERS` WHERE `USERNAME` = '%e' LIMIT 1", pInfo[playerid][Name]);
122 mysql_tquery(Database, DB_Query, "OnPlayerDataCheck", "ii", playerid, Corrupt_Check[playerid]);
123
124 LoginTD[0] = TextDrawCreate(450.000000, 107.000000, "box");
125 TextDrawLetterSize(LoginTD[0], 0.000000, 20.000000);
126 TextDrawTextSize(LoginTD[0], 179.000030, 0.000000);
127 TextDrawAlignment(LoginTD[0], 1);
128 TextDrawColor(LoginTD[0], -1);
129 TextDrawUseBox(LoginTD[0], 1);
130 TextDrawBoxColor(LoginTD[0], 353704161);
131 TextDrawSetShadow(LoginTD[0], 0);
132 TextDrawBackgroundColor(LoginTD[0], 255);
133 TextDrawFont(LoginTD[0], 1);
134 TextDrawSetProportional(LoginTD[0], 1);
135
136 LoginTD[1] = TextDrawCreate(313.666687, 110.770324, "LOGIN");
137 TextDrawLetterSize(LoginTD[1], 0.400000, 1.600000);
138 TextDrawTextSize(LoginTD[1], 0.000000, 253.000000);
139 TextDrawAlignment(LoginTD[1], 2);
140 TextDrawColor(LoginTD[1], -1);
141 TextDrawUseBox(LoginTD[1], 1);
142 TextDrawBoxColor(LoginTD[1], 255);
143 TextDrawSetShadow(LoginTD[1], 0);
144 TextDrawBackgroundColor(LoginTD[1], 255);
145 TextDrawFont(LoginTD[1], 1);
146 TextDrawSetProportional(LoginTD[1], 1);
147
148
149 LoginPTD[0] = CreatePlayerTextDraw(playerid, 315.666870, 136.074127, "NAME : OBBIE");
150 PlayerTextDrawLetterSize(playerid, LoginPTD[0], 0.400000, 1.600000);
151 PlayerTextDrawTextSize(playerid, LoginPTD[0], 0.000000, 165.000000);
152 PlayerTextDrawAlignment(playerid, LoginPTD[0], 2);
153 PlayerTextDrawColor(playerid, LoginPTD[0], -1);
154 PlayerTextDrawUseBox(playerid, LoginPTD[0], 1);
155 PlayerTextDrawBoxColor(playerid, LoginPTD[0], 255);
156 PlayerTextDrawSetShadow(playerid, LoginPTD[0], 0);
157 PlayerTextDrawBackgroundColor(playerid, LoginPTD[0], 255);
158 PlayerTextDrawFont(playerid, LoginPTD[0], 1);
159 PlayerTextDrawSetProportional(playerid, LoginPTD[0], 1);
160
161 LoginPTD[1] = CreatePlayerTextDraw(playerid, 315.666748, 163.451828, "PASSWORD : CLICK");
162 PlayerTextDrawLetterSize(playerid, LoginPTD[1], 0.400000, 1.600000);
163 PlayerTextDrawTextSize(playerid, LoginPTD[1], 0.000000, 165.000000);
164 PlayerTextDrawAlignment(playerid, LoginPTD[1], 2);
165 PlayerTextDrawColor(playerid, LoginPTD[1], -1);
166 PlayerTextDrawUseBox(playerid, LoginPTD[1], 1);
167 PlayerTextDrawBoxColor(playerid, LoginPTD[1], 255);
168 PlayerTextDrawSetShadow(playerid, LoginPTD[1], 0);
169 PlayerTextDrawBackgroundColor(playerid, LoginPTD[1], 255);
170 PlayerTextDrawFont(playerid, LoginPTD[1], 1);
171 PlayerTextDrawSetProportional(playerid, LoginPTD[1], 1);
172 PlayerTextDrawSetSelectable(playerid, LoginPTD[1], true);
173
174 LoginPTD[2] = CreatePlayerTextDraw(playerid, 315.099945, 248.488922, "Last Login~n~127.0.0.1 20:20:20");
175 PlayerTextDrawLetterSize(playerid, LoginPTD[2], 0.400000, 1.600000);
176 PlayerTextDrawTextSize(playerid, LoginPTD[2], 0.000000, 252.000000);
177 PlayerTextDrawAlignment(playerid, LoginPTD[2], 2);
178 PlayerTextDrawColor(playerid, LoginPTD[2], -1);
179 PlayerTextDrawUseBox(playerid, LoginPTD[2], 1);
180 PlayerTextDrawBoxColor(playerid, LoginPTD[2], 255);
181 PlayerTextDrawSetShadow(playerid, LoginPTD[2], 0);
182 PlayerTextDrawBackgroundColor(playerid, LoginPTD[2], 255);
183 PlayerTextDrawFont(playerid, LoginPTD[2], 1);
184 PlayerTextDrawSetProportional(playerid, LoginPTD[2], 1);
185
186 return 1;
187}
188
189public OnPlayerDisconnect(playerid, reason)
190{
191 Corrupt_Check[playerid]++;
192
193 new DB_Query[256];
194 //Running a query to save the player's data using the stored stuff.
195 mysql_format(Database, DB_Query, sizeof(DB_Query), "UPDATE `PLAYERS` SET `SCORE` = %d, `CASH` = %d, `KILLS` = %d, `DEATHS` = %d WHERE `ID` = %d LIMIT 1",
196 pInfo[playerid][Score], pInfo[playerid][Cash], pInfo[playerid][Kills], pInfo[playerid][Deaths], pInfo[playerid][ID]);
197
198 mysql_tquery(Database, DB_Query);
199
200 if(cache_is_valid(pInfo[playerid][Player_Cache])) //Checking if the player's cache ID is valid.
201 {
202 cache_delete(pInfo[playerid][Player_Cache]); // Deleting the cache.
203 pInfo[playerid][Player_Cache] = MYSQL_INVALID_CACHE; // Setting the stored player Cache as invalid.
204 }
205
206 pInfo[playerid][LoggedIn] = false;
207 print("OnPlayerDisconnect has been called."); // Sending message once OnPlayerDisconnect is called.
208 return 1;
209}
210
211public OnPlayerDeath(playerid, killerid, reason)
212{
213 if(killerid != INVALID_PLAYER_ID) // Checking if the killer of the player is valid.
214 {
215 //Increasing the kills of the killer and the deaths of the player.
216 pInfo[killerid][Kills]++;
217 pInfo[playerid][Deaths]++;
218 }
219 return 1;
220}
221
222public OnPlayerRequestSpawn(playerid)
223{
224 if(pInfo[playerid][LoggedIn] == false) return 0; // Ignoring the request incase player isn't logged in.
225 return 1;
226}
227
228public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
229{
230 switch (dialogid)
231 {
232 case DIALOG_LOGIN:
233 {
234 if(!response) return Kick(playerid);
235
236 new Salted_Key[65];
237 SHA256_PassHash(inputtext, pInfo[playerid][Salt], Salted_Key, 65);
238
239 if(strcmp(Salted_Key, pInfo[playerid][Password]) == 0)
240 {
241 // Now, password should be correct as well as the strings
242 // Matched with each other, so nothing is wrong until now.
243
244 // We will activate the cache of player to make use of it e.g.
245 // Retrieve their data.
246
247 cache_set_active(pInfo[playerid][Player_Cache]);
248
249 // Okay, we are retrieving the information now..
250 cache_get_value_int(0, "ID", pInfo[playerid][ID]);
251
252 cache_get_value_int(0, "KILLS", pInfo[playerid][Kills]);
253 cache_get_value_int(0, "DEATHS", pInfo[playerid][Deaths]);
254
255 cache_get_value_int(0, "SCORE", pInfo[playerid][Score]);
256 cache_get_value_int(0, "CASH", pInfo[playerid][Cash]);
257
258 SetPlayerScore(playerid, pInfo[playerid][Score]);
259
260 ResetPlayerMoney(playerid);
261 GivePlayerMoney(playerid, pInfo[playerid][Cash]);
262
263 // So, we have successfully retrieved data? Now deactivating the cache.
264
265 cache_delete(pInfo[playerid][Player_Cache]);
266 pInfo[playerid][Player_Cache] = MYSQL_INVALID_CACHE;
267
268 pInfo[playerid][LoggedIn] = true;
269 SendClientMessage(playerid, 0x00FF00FF, "Logged in to the account.");
270 }
271 else
272 {
273 new String[150];
274
275 pInfo[playerid][PasswordFails] += 1;
276 printf("%s has been failed to login. (%d)", pInfo[playerid][Name], pInfo[playerid][PasswordFails]);
277 // Printing the message that someone has failed to login to his account.
278
279 if (pInfo[playerid][PasswordFails] >= 3) // If the fails exceeded the limit we kick the player.
280 {
281 format(String, sizeof(String), "%s has been kicked Reason: {FF0000}(%d/3) Login fails.", pInfo[playerid][Name], pInfo[playerid][PasswordFails]);
282 SendClientMessageToAll(0x969696FF, String);
283 Kick(playerid);
284 }
285 else
286 {
287 // If the player didn't exceed the limits we send him a message that the password is wrong.
288 format(String, sizeof(String), "Wrong password, you have %d out of 3 tries.", pInfo[playerid][PasswordFails]);
289 SendClientMessage(playerid, 0xFF0000FF, String);
290
291 /*format(String, sizeof(String), "{FFFFFF}Welcome back, %s.\n\n{0099FF}This account is already registered.\n\
292 {0099FF}Please, input your password below to proceed to the game.\n\n", pInfo[playerid][Name]);
293 ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login System", String, "Login", "Leave");*/
294 }
295 }
296 }
297 case DIALOG_REGISTER:
298 {
299 if(!response) return Kick(playerid);
300
301 if(strlen(inputtext) <= 5 || strlen(inputtext) > 60)
302 {
303 // If the password length is less than or equal to 5 and more than 60
304 // It repeats the process and shows error message as seen below.
305
306 SendClientMessage(playerid, 0x969696FF, "Invalid password length, should be 5 - 60.");
307
308 new String[150];
309
310 format(String, sizeof(String), "{FFFFFF}Welcome %s.\n\n{0099FF}This account is not registered.\n\
311 {0099FF}Please, input your password below to proceed.\n\n", pInfo[playerid][Name]);
312 ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration System", String, "Register", "Leave");
313 }
314 else
315 {
316
317 // Salting the player's password using SHA256 for a better security.
318
319 for (new i = 0; i < 10; i++)
320 {
321 pInfo[playerid][Salt][i] = random(79) + 47;
322 }
323
324 pInfo[playerid][Salt][10] = 0;
325 SHA256_PassHash(inputtext, pInfo[playerid][Salt], pInfo[playerid][Password], 65);
326
327 new DB_Query[225];
328
329 // Storing player's information if everything goes right.
330 mysql_format(Database, DB_Query, sizeof(DB_Query), "INSERT INTO `PLAYERS` (`USERNAME`, `PASSWORD`, `SALT`, `SCORE`, `KILLS`, `CASH`, `DEATHS`)\
331 VALUES ('%e', '%s', '%e', '20', '0', '0', '0')", pInfo[playerid][Name], pInfo[playerid][Password], pInfo[playerid][Salt]);
332 mysql_tquery(Database, DB_Query, "OnPlayerRegister", "d", playerid);
333 }
334 }
335 }
336 return 1;
337}
338
339forward public OnPlayerDataCheck(playerid, corrupt_check);
340public OnPlayerDataCheck(playerid, corrupt_check)
341{
342 if (corrupt_check != Corrupt_Check[playerid]) return Kick(playerid);
343 // You'd have asked already what's corrput_check and how it'd benefit me?
344 // Well basically MySQL query takes long, incase a player leaves while its not proceeded
345 // With ID 1 for example, then another player comes as ID 1 it'll basically corrupt the data
346 // So, once the query is done, the player will have the wrong data assigned for himself.
347
348 new String[150];
349
350 if(cache_num_rows() > 0)
351 {
352 // If the player exists, everything is okay and nothing is wrongly detected
353 // The player's password and Saltion key gets stored as seen below
354 // So we won't have to get a headache just to match player's password.
355
356 cache_get_value(0, "PASSWORD", pInfo[playerid][Password], 65);
357 cache_get_value(0, "SALT", pInfo[playerid][Salt], 11);
358
359 pInfo[playerid][Player_Cache] = cache_save();
360 // ^ Storing the cache ID of the player for further use later.
361 format(string, sizeof(string), "NAME: %s", pInfo[playerid][Name]);
362 PlayerTextDrawSetString(playerid, LoginPTD[0], string);
363 PlayerTextDrawSetString(playerid, LoginPTD[1], "PASSWORD: ~b~click");
364
365 for(new i = 0; i < 3; i++) PlayerTextDrawShow(playerid, LoginPTD[i]);
366 for(new i = 0; i < 2; i++) TextDrawShowForPlayer(playerid, LoginTD[i]);
367
368 /*
369 format(String, sizeof(String), "{FFFFFF}Welcome back, %s.\n\n{0099FF}This account is already registered.\n\
370 {0099FF}Please, input your password below to proceed to the game.\n\n", pInfo[playerid][Name]);
371 ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login System", String, "Login", "Leave");
372 */
373 }
374 else
375 {
376 format(String, sizeof(String), "{FFFFFF}Welcome %s.\n\n{0099FF}This account is not registered.\n\
377 {0099FF}Please, input your password below to proceed to the game.\n\n", pInfo[playerid][Name]);
378 ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration System", String, "Register", "Leave");
379 }
380 return 1;
381}
382
383forward public OnPlayerRegister(playerid);
384public OnPlayerRegister(playerid)
385{
386 // This gets called only when the player registers a new account.
387 SendClientMessage(playerid, 0x00FF00FF, "You are now registered and has been logged in.");
388 pInfo[playerid][LoggedIn] = true;
389 return 1;
390}
391
392public OnPlayerKeyStateChange(playerid, newkeys, oldkeys)
393{
394 if(newkeys == KEY_SUBMISSION)
395 {
396 SelectTextDraw(playerid, 0xFF4040AA);
397 }
398 return 1;
399}
400
401public OnPlayerClickPlayerTextDraw(playerid, PlayerText:playertextid)
402{
403 if(playertextid == LoginPTD[1])
404 {
405 SendClientMessage(playerid, 0xFFFFFFAA, "You clicked on a textdraw.");
406 CancelSelectTextDraw(playerid);
407 return 1;
408 }
409 return 0;
410}
411// End of script //