· 7 years ago · Jan 22, 2019, 11:10 AM
1#include <a_samp>
2
3// change MAX_PLAYERS to the amount of players (slots) you want
4// It is by default 1000 (as of 0.3.7 version)
5#undef MAX_PLAYERS
6#define MAX_PLAYERS 50
7
8#include <a_mysql>
9#include <zcmd>
10
11
12// MySQL configuration
13#define MYSQL_HOST "127.0.0.1"
14#define MYSQL_USER "root"
15#define MYSQL_PASSWORD "Pass123!"
16#define MYSQL_DATABASE "dbrp"
17
18
19
20// colori
21// system color
22#define COLOR_SYSTEM_ERROR 0xAA3333AA
23#define COLOR_SYSTEM_TITLE 0xFFFFFFAA
24#define COLOR_SYSTEM_TEXT 0xFFFFFFAA
25#define COLOR_SYSTEM_SUCCESS 0x33AA33AA
26#define COLOR_SYSTEM_INFO 0xAFAFAFAA
27
28
29// how many seconds until it kicks the player for taking too long to login
30#define SECONDS_TO_LOGIN 30
31
32// default spawn point: Las Venturas (The High Roller)
33#define DEFAULT_POS_X 1958.3783
34#define DEFAULT_POS_Y 1343.1572
35#define DEFAULT_POS_Z 15.3746
36#define DEFAULT_POS_A 270.1425
37
38// MySQL connection handle
39new MySQL: g_SQL;
40
41// player data
42enum E_PLAYERS
43{
44 ID,
45 Name[MAX_PLAYER_NAME],
46 Password[65], // the output of SHA256_PassHash function (which was added in 0.3.7 R1 version) is always 256 bytes in length, or the equivalent of 64 Pawn cells
47 Salt[17],
48 Kills,
49 Admin,
50 Age,
51 Sex,
52 Deaths,
53 Float: X_Pos,
54 Float: Y_Pos,
55 Float: Z_Pos,
56 Float: A_Pos,
57 Interior,
58
59 Cache: Cache_ID,
60 bool: IsLoggedIn,
61 LoginAttempts,
62 LoginTimer
63};
64new Player[MAX_PLAYERS][E_PLAYERS];
65
66new g_MysqlRaceCheck[MAX_PLAYERS];
67
68// dialog data
69enum
70{
71 DIALOG_UNUSED,
72
73 DIALOG_LOGIN,
74 DIALOG_REGISTER,
75 DIALOG_REGISTER_AGE,
76 DIALOG_REGISTER_SEX
77};
78
79main() {}
80
81
82public OnGameModeInit()
83{
84 new MySQLOpt: option_id = mysql_init_options();
85
86 mysql_set_option(option_id, AUTO_RECONNECT, true); // it automatically reconnects when loosing connection to mysql server
87
88 g_SQL = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, option_id); // AUTO_RECONNECT is enabled for this connection handle only
89 if (g_SQL == MYSQL_INVALID_HANDLE || mysql_errno(g_SQL) != 0)
90 {
91 print("MySQL connection failed. Server is shutting down.");
92 SendRconCommand("exit"); // close the server if there is no connection
93 return 1;
94 }
95
96 print("MySQL connection is successful.");
97
98 // if the table has been created, the "SetupPlayerTable" function does not have any purpose so you may remove it completely
99 SetupPlayerTable();
100 return 1;
101}
102
103public OnGameModeExit()
104{
105 // save all player data before closing connection
106 for (new i = 0, j = GetPlayerPoolSize(); i <= j; i++) // GetPlayerPoolSize function was added in 0.3.7 version and gets the highest playerid currently in use on the server
107 {
108 if (IsPlayerConnected(i))
109 {
110 // reason is set to 1 for normal 'Quit'
111 OnPlayerDisconnect(i, 1);
112 }
113 }
114
115 mysql_close(g_SQL);
116 return 1;
117}
118
119public OnPlayerConnect(playerid)
120{
121 g_MysqlRaceCheck[playerid]++;
122
123 // reset player data
124 static const empty_player[E_PLAYERS];
125 Player[playerid] = empty_player;
126
127 GetPlayerName(playerid, Player[playerid][Name], MAX_PLAYER_NAME);
128
129 // send a query to recieve all the stored player data from the table
130 new query[103];
131 mysql_format(g_SQL, query, sizeof query, "SELECT * FROM `players` WHERE `username` = '%e' LIMIT 1", Player[playerid][Name]);
132 mysql_tquery(g_SQL, query, "OnPlayerDataLoaded", "dd", playerid, g_MysqlRaceCheck[playerid]);
133 return 1;
134}
135
136public OnPlayerDisconnect(playerid, reason)
137{
138 g_MysqlRaceCheck[playerid]++;
139
140 UpdatePlayerData(playerid, reason);
141
142 // if the player was kicked (either wrong password or taking too long) during the login part, remove the data from the memory
143 if (cache_is_valid(Player[playerid][Cache_ID]))
144 {
145 cache_delete(Player[playerid][Cache_ID]);
146 Player[playerid][Cache_ID] = MYSQL_INVALID_CACHE;
147 }
148
149 // if the player was kicked before the time expires (30 seconds), kill the timer
150 if (Player[playerid][LoginTimer])
151 {
152 KillTimer(Player[playerid][LoginTimer]);
153 Player[playerid][LoginTimer] = 0;
154 }
155
156 // sets "IsLoggedIn" to false when the player disconnects, it prevents from saving the player data twice when "gmx" is used
157 Player[playerid][IsLoggedIn] = false;
158 return 1;
159}
160
161public OnPlayerSpawn(playerid)
162{
163 // spawn the player to their last saved position
164 SetPlayerInterior(playerid, Player[playerid][Interior]);
165 SetPlayerPos(playerid, Player[playerid][X_Pos], Player[playerid][Y_Pos], Player[playerid][Z_Pos]);
166 SetPlayerFacingAngle(playerid, Player[playerid][A_Pos]);
167
168 SetCameraBehindPlayer(playerid);
169 return 1;
170}
171
172public OnPlayerDeath(playerid, killerid, reason)
173{
174 UpdatePlayerDeaths(playerid);
175 UpdatePlayerKills(killerid);
176 return 1;
177}
178
179public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
180{
181 switch (dialogid)
182 {
183 case DIALOG_UNUSED: return 1; // Useful for dialogs that contain only information and we do nothing depending on whether they responded or not
184
185 case DIALOG_LOGIN:
186 {
187 if (!response) return Kick(playerid);
188
189 new hashed_pass[65];
190 SHA256_PassHash(inputtext, Player[playerid][Salt], hashed_pass, 65);
191
192 if (strcmp(hashed_pass, Player[playerid][Password]) == 0)
193 {
194 //correct password, spawn the player
195 ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "Login con successo.", "Okay", "");
196 // sets the specified cache as the active cache so we can retrieve the rest player data
197 cache_set_active(Player[playerid][Cache_ID]);
198 AssignPlayerData(playerid);
199 // remove the active cache from memory and unsets the active cache as well
200 cache_delete(Player[playerid][Cache_ID]);
201 Player[playerid][Cache_ID] = MYSQL_INVALID_CACHE;
202
203 KillTimer(Player[playerid][LoginTimer]);
204 Player[playerid][LoginTimer] = 0;
205 Player[playerid][IsLoggedIn] = true;
206
207 // spawn the player to their last saved position after login
208 SetSpawnInfo(playerid, NO_TEAM, 0, Player[playerid][X_Pos], Player[playerid][Y_Pos], Player[playerid][Z_Pos], Player[playerid][A_Pos], 0, 0, 0, 0, 0, 0);
209 SpawnPlayer(playerid);
210 }
211 else
212 {
213 Player[playerid][LoginAttempts]++;
214
215 if (Player[playerid][LoginAttempts] >= 3)
216 {
217 ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "Hai superato i tentativi di accesso (3 tentativi).", "Okay", "");
218 DelayedKick(playerid);
219 }
220 else ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", "{FF0000}Password errata!\n{AFAFAF}Inserisci la password per entrare:", "Login", "Annulla");
221 }
222 }
223 case DIALOG_REGISTER:
224 {
225 if (!response) return Kick(playerid);
226
227 if (strlen(inputtext) <= 5) return ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registrazione", "{FF0000}[ERRORE:]Hai inserito una password troppo corta, usa almeno 5 caratteri!\n{AFAFAF}Inserisci una password per registrarti:", "Continua", "Annulla");
228
229 // 16 random characters from 33 to 126 (in ASCII) for the salt
230 for (new i = 0; i < 16; i++) Player[playerid][Salt][i] = random(94) + 33;
231 SHA256_PassHash(inputtext, Player[playerid][Salt], Player[playerid][Password], 65);
232 new query[221];
233 mysql_format(g_SQL, query, sizeof query, "INSERT INTO `players` (`username`, `password`, `salt`) VALUES ('%e', '%s', '%e')", Player[playerid][Name], Player[playerid][Password], Player[playerid][Salt]);
234// mysql_tquery(g_SQL, query, "OnPlayerRegister", "d", playerid);
235 ShowPlayerDialog(playerid, DIALOG_REGISTER_AGE, DIALOG_STYLE_INPUT, "Registrazione", "Inserisci l'età che vuoi avere nel server\nNon influenzerà il gioco", "Continua", "Annulla");
236 }
237
238 case DIALOG_REGISTER_AGE:
239 {
240 if (!response) return Kick(playerid);
241
242 if (!strlen(inputtext)) return ShowPlayerDialog(playerid, DIALOG_REGISTER_AGE, DIALOG_STYLE_INPUT, "Registrazione", "{FF0000}[ERRORE:]Devi inserire l'età per continuare!\n{AFAFAF}Non influenzerà il gioco:", "Continua", "Annulla");
243 if (!IsNumeric(inputtext)) return ShowPlayerDialog(playerid, DIALOG_REGISTER_AGE, DIALOG_STYLE_INPUT, "Registrazione", "{FF0000}[ERRORE:]Devi inserire un numero per l'età !\n{AFAFAF}Non influenzerà il gioco:", "Continua", "Annulla");
244 Player[playerid][Age] = strlen(inputtext);
245 new query[221];
246 mysql_format(g_SQL, query, sizeof query, "INSERT INTO `players` (`username`, `password`, `salt`, `age`) VALUES ('%e', '%s', '%e', '%e')", Player[playerid][Name], Player[playerid][Password], Player[playerid][Salt], Player[playerid][Age]);
247 ShowPlayerDialog(playerid, DIALOG_REGISTER_SEX, DIALOG_STYLE_LIST, "Seleziona il sesso", "{FF6666}Donna\n{0000cc}Uomo", "Continua", "");
248 }
249
250 case DIALOG_REGISTER_SEX:
251 {
252 switch(listitem)// Dialog: registrazione fase 3: sesso
253 {
254 case 0: // Registrazione sesso: selezionato: donna
255 {
256 Player[playerid][Sex] = 1; // 1 = donna
257 new query[221];
258 mysql_format(g_SQL, query, sizeof query, "INSERT INTO `players` (`username`, `password`, `salt`, `age`, `sex`) VALUES ('%e', '%s', '%e', '%e', '%d')", Player[playerid][Name], Player[playerid][Password], Player[playerid][Salt], Player[playerid][Age], Player[playerid][Sex]);
259 mysql_tquery(g_SQL, query, "OnPlayerRegister", "d", playerid);
260 new Messaggio[256];
261 format(Messaggio, sizeof (Messaggio), "[INFO]: %s sei {FF6666}una donna", Player[playerid][Name]);
262 SendClientMessage(playerid, COLOR_SYSTEM_INFO, Messaggio);
263 }
264 case 1: // Registrazione sesso: selezionato: uomo
265 {
266 Player[playerid][Sex] = 2; // 2 = uomo
267 new query[221];
268 mysql_format(g_SQL, query, sizeof query, "INSERT INTO `players` (`username`, `password`, `salt`, `age`, `sex`) VALUES ('%e', '%s', '%e', '%e', '%d')", Player[playerid][Name], Player[playerid][Password], Player[playerid][Salt], Player[playerid][Age], Player[playerid][Sex]);
269 mysql_tquery(g_SQL, query, "OnPlayerRegister", "d", playerid);
270 new Messaggio[256];
271 format(Messaggio, sizeof (Messaggio), "[INFO]: %s sei {0000cc}un uomo", Player[playerid][Name]);
272 SendClientMessage(playerid, COLOR_SYSTEM_INFO, Messaggio);
273 }
274 }
275 }
276 default: return 0; // dialog ID was not found, search in other scripts
277 }
278 return 1;
279}
280
281//-----------------------------------------------------
282
283
284
285// controllo se una string è numerica
286IsNumeric(const string[])
287{
288 for (new i = 0, j = strlen(string); i < j; i++)
289 {
290 if (string[i] > '9' || string[i] < '0') return 0;
291 }
292 return 1;
293}
294
295forward OnPlayerDataLoaded(playerid, race_check);
296public OnPlayerDataLoaded(playerid, race_check)
297{
298 /* race condition check:
299 player A connects -> SELECT query is fired -> this query takes very long
300 while the query is still processing, player A with playerid 2 disconnects
301 player B joins now with playerid 2 -> our laggy SELECT query is finally finished, but for the wrong player
302 what do we do against it?
303 we create a connection count for each playerid and increase it everytime the playerid connects or disconnects
304 we also pass the current value of the connection count to our OnPlayerDataLoaded callback
305 then we check if current connection count is the same as connection count we passed to the callback
306 if yes, everything is okay, if not, we just kick the player
307 */
308 if (race_check != g_MysqlRaceCheck[playerid]) return Kick(playerid);
309
310 new string[115];
311 if(cache_num_rows() > 0)
312 {
313 // we store the password and the salt so we can compare the password the player inputs
314 // and save the rest so we won't have to execute another query later
315 cache_get_value(0, "password", Player[playerid][Password], 65);
316 cache_get_value(0, "salt", Player[playerid][Salt], 17);
317
318 // saves the active cache in the memory and returns an cache-id to access it for later use
319 Player[playerid][Cache_ID] = cache_save();
320
321 format(string, sizeof string, "%s risulta registrato sul server.\nInserisci la password per entrare:", Player[playerid][Name]);
322 ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", string, "Login", "Annulla");
323
324 // from now on, the player has 30 seconds to login
325 Player[playerid][LoginTimer] = SetTimerEx("OnLoginTimeout", SECONDS_TO_LOGIN * 1000, false, "d", playerid);
326 }
327 else
328 {
329 format(string, sizeof string, "Benvenuto %s,\nInserisci una password per iniziare la registrazione:", Player[playerid][Name]);
330 ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registrazione", string, "Continua", "Annulla");
331 }
332 return 1;
333}
334
335forward OnLoginTimeout(playerid);
336public OnLoginTimeout(playerid)
337{
338 // reset the variable that stores the timerid
339 Player[playerid][LoginTimer] = 0;
340
341 ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "Sei stato kickato dal server per aver impiegato troppo tempo nel loggarti.", "Okay", "");
342 DelayedKick(playerid);
343 return 1;
344}
345
346forward OnPlayerRegister(playerid);
347public OnPlayerRegister(playerid)
348{
349 // retrieves the ID generated for an AUTO_INCREMENT column by the sent query
350 Player[playerid][ID] = cache_insert_id();
351
352 ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Registration", "Account successfully registered, you have been automatically logged in.", "Okay", "");
353
354 Player[playerid][IsLoggedIn] = true;
355
356 Player[playerid][X_Pos] = DEFAULT_POS_X;
357 Player[playerid][Y_Pos] = DEFAULT_POS_Y;
358 Player[playerid][Z_Pos] = DEFAULT_POS_Z;
359 Player[playerid][A_Pos] = DEFAULT_POS_A;
360
361 SetSpawnInfo(playerid, NO_TEAM, 0, Player[playerid][X_Pos], Player[playerid][Y_Pos], Player[playerid][Z_Pos], Player[playerid][A_Pos], 0, 0, 0, 0, 0, 0);
362 SpawnPlayer(playerid);
363 return 1;
364}
365
366forward _KickPlayerDelayed(playerid);
367public _KickPlayerDelayed(playerid)
368{
369 Kick(playerid);
370 return 1;
371}
372
373
374//-----------------------------------------------------
375
376AssignPlayerData(playerid)
377{
378 cache_get_value_int(0, "id", Player[playerid][ID]);
379
380 cache_get_value_int(0, "kills", Player[playerid][Kills]);
381 cache_get_value_int(0, "deaths", Player[playerid][Deaths]);
382 cache_get_value_int(0, "admin", Player[playerid][Admin]);
383 cache_get_value_int(0, "sex", Player[playerid][Sex]);
384 cache_get_value_int(0, "age", Player[playerid][Age]);
385 cache_get_value_float(0, "x", Player[playerid][X_Pos]);
386 cache_get_value_float(0, "y", Player[playerid][Y_Pos]);
387 cache_get_value_float(0, "z", Player[playerid][Z_Pos]);
388 cache_get_value_float(0, "angle", Player[playerid][A_Pos]);
389 cache_get_value_int(0, "interior", Player[playerid][Interior]);
390 return 1;
391}
392
393DelayedKick(playerid, time = 500)
394{
395 SetTimerEx("_KickPlayerDelayed", time, false, "d", playerid);
396 return 1;
397}
398
399SetupPlayerTable()
400{
401 mysql_tquery(g_SQL, "CREATE TABLE IF NOT EXISTS `players` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(24) NOT NULL,`password` char(64) NOT NULL,`salt` char(16) NOT NULL,`kills` mediumint(8) NOT NULL DEFAULT '0',`deaths` mediumint(8) NOT NULL DEFAULT '0',`x` float NOT NULL DEFAULT '0',`y` float NOT NULL DEFAULT '0',`z` float NOT NULL DEFAULT '0',`angle` float NOT NULL DEFAULT '0',`interior` tinyint(3) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `username` (`username`))");
402 return 1;
403}
404
405UpdatePlayerData(playerid, reason)
406{
407 if (Player[playerid][IsLoggedIn] == false) return 0;
408
409 // if the client crashed, it's not possible to get the player's position in OnPlayerDisconnect callback
410 // so we will use the last saved position (in case of a player who registered and crashed/kicked, the position will be the default spawn point)
411 if (reason == 1)
412 {
413 GetPlayerPos(playerid, Player[playerid][X_Pos], Player[playerid][Y_Pos], Player[playerid][Z_Pos]);
414 GetPlayerFacingAngle(playerid, Player[playerid][A_Pos]);
415 }
416
417 new query[145];
418 mysql_format(g_SQL, query, sizeof query, "UPDATE `players` SET `x` = %f, `y` = %f, `z` = %f, `angle` = %f, `interior` = %d WHERE `id` = %d LIMIT 1", Player[playerid][X_Pos], Player[playerid][Y_Pos], Player[playerid][Z_Pos], Player[playerid][A_Pos], GetPlayerInterior(playerid), Player[playerid][ID]);
419 mysql_tquery(g_SQL, query);
420 return 1;
421}
422
423UpdatePlayerDeaths(playerid)
424{
425 if (Player[playerid][IsLoggedIn] == false) return 0;
426
427 Player[playerid][Deaths]++;
428
429 new query[70];
430 mysql_format(g_SQL, query, sizeof query, "UPDATE `players` SET `deaths` = %d WHERE `id` = %d LIMIT 1", Player[playerid][Deaths], Player[playerid][ID]);
431 mysql_tquery(g_SQL, query);
432 return 1;
433}
434
435UpdatePlayerKills(killerid)
436{
437 // we must check before if the killer wasn't valid (connected) player to avoid run time error 4
438 if (killerid == INVALID_PLAYER_ID) return 0;
439 if (Player[killerid][IsLoggedIn] == false) return 0;
440
441 Player[killerid][Kills]++;
442
443 new query[70];
444 mysql_format(g_SQL, query, sizeof query, "UPDATE `players` SET `kills` = %d WHERE `id` = %d LIMIT 1", Player[killerid][Kills], Player[killerid][ID]);
445 mysql_tquery(g_SQL, query);
446 return 1;
447}
448//-----------------------------------------------------------------------
449// comandi - zcmd
450/*
451prova
452CMD:admin(playerid, params[])
453{
454 if(Player[playerid][Admin] == 0)
455 {
456 SendClientMessage(playerid, COLOR_SYSTEM_ERROR, "asd");
457 return 1;
458 }
459 else if(Player[playerid][Admin] >= 1)
460 {
461 SendClientMessage(playerid, COLOR_SYSTEM_SUCCESS, "123");
462 return 1;
463 }
464 return 1;
465}
466*/