AwDod

mysql cache exemple login

Oct 26th, 2023
49
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Pawn 13.29 KB | None | 0 0
  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.  
  10. // MySQL configuration
  11. #define     MYSQL_HOST          "127.0.0.1"
  12. #define     MYSQL_USER          "username"
  13. #define     MYSQL_PASSWORD      "password"
  14. #define     MYSQL_DATABASE      "database"
  15.  
  16. // how many seconds until it kicks the player for taking too long to login
  17. #define     SECONDS_TO_LOGIN    30
  18.  
  19. // default spawn point: Las Venturas (The High Roller)
  20. #define     DEFAULT_POS_X       1958.3783
  21. #define     DEFAULT_POS_Y       1343.1572
  22. #define     DEFAULT_POS_Z       15.3746
  23. #define     DEFAULT_POS_A       270.1425
  24.  
  25. // MySQL connection handle
  26. new MySQL: g_SQL;
  27.  
  28. // player data
  29. enum E_PLAYERS
  30. {
  31.     ID,
  32.     Name[MAX_PLAYER_NAME],
  33.     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
  34.     Salt[17],
  35.     Kills,
  36.     Deaths,
  37.     Float: X_Pos,
  38.     Float: Y_Pos,
  39.     Float: Z_Pos,
  40.     Float: A_Pos,
  41.     Interior,
  42.  
  43.     Cache: Cache_ID,
  44.     bool: IsLoggedIn,
  45.     LoginAttempts,
  46.     LoginTimer
  47. };
  48. new Player[MAX_PLAYERS][E_PLAYERS];
  49.  
  50. new g_MysqlRaceCheck[MAX_PLAYERS];
  51.  
  52. // dialog data
  53. enum
  54. {
  55.     DIALOG_UNUSED,
  56.  
  57.     DIALOG_LOGIN,
  58.     DIALOG_REGISTER
  59. };
  60.  
  61. main() {}
  62.  
  63.  
  64. public OnGameModeInit()
  65. {
  66.     new MySQLOpt: option_id = mysql_init_options();
  67.  
  68.     mysql_set_option(option_id, AUTO_RECONNECT, true); // it automatically reconnects when loosing connection to mysql server
  69.  
  70.     g_SQL = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE, option_id); // AUTO_RECONNECT is enabled for this connection handle only
  71.     if (g_SQL == MYSQL_INVALID_HANDLE || mysql_errno(g_SQL) != 0)
  72.     {
  73.         print("MySQL connection failed. Server is shutting down.");
  74.         SendRconCommand("exit"); // close the server if there is no connection
  75.         return 1;
  76.     }
  77.  
  78.     print("MySQL connection is successful.");
  79.  
  80.     // if the table has been created, the "SetupPlayerTable" function does not have any purpose so you may remove it completely
  81.     SetupPlayerTable();
  82.     return 1;
  83. }
  84.  
  85. public OnGameModeExit()
  86. {
  87.     // save all player data before closing connection
  88.     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
  89.     {
  90.         if (IsPlayerConnected(i))
  91.         {
  92.             // reason is set to 1 for normal 'Quit'
  93.             OnPlayerDisconnect(i, 1);
  94.         }
  95.     }
  96.  
  97.     mysql_close(g_SQL);
  98.     return 1;
  99. }
  100.  
  101. public OnPlayerConnect(playerid)
  102. {
  103.     g_MysqlRaceCheck[playerid]++;
  104.  
  105.     // reset player data
  106.     static const empty_player[E_PLAYERS];
  107.     Player[playerid] = empty_player;
  108.  
  109.     GetPlayerName(playerid, Player[playerid][Name], MAX_PLAYER_NAME);
  110.  
  111.     // send a query to recieve all the stored player data from the table
  112.     new query[103];
  113.     mysql_format(g_SQL, query, sizeof query, "SELECT * FROM `players` WHERE `username` = '%e' LIMIT 1", Player[playerid][Name]);
  114.     mysql_tquery(g_SQL, query, "OnPlayerDataLoaded", "dd", playerid, g_MysqlRaceCheck[playerid]);
  115.     return 1;
  116. }
  117.  
  118. public OnPlayerDisconnect(playerid, reason)
  119. {
  120.     g_MysqlRaceCheck[playerid]++;
  121.  
  122.     UpdatePlayerData(playerid, reason);
  123.  
  124.     // if the player was kicked (either wrong password or taking too long) during the login part, remove the data from the memory
  125.     if (cache_is_valid(Player[playerid][Cache_ID]))
  126.     {
  127.         cache_delete(Player[playerid][Cache_ID]);
  128.         Player[playerid][Cache_ID] = MYSQL_INVALID_CACHE;
  129.     }
  130.  
  131.     // if the player was kicked before the time expires (30 seconds), kill the timer
  132.     if (Player[playerid][LoginTimer])
  133.     {
  134.         KillTimer(Player[playerid][LoginTimer]);
  135.         Player[playerid][LoginTimer] = 0;
  136.     }
  137.  
  138.     // sets "IsLoggedIn" to false when the player disconnects, it prevents from saving the player data twice when "gmx" is used
  139.     Player[playerid][IsLoggedIn] = false;
  140.     return 1;
  141. }
  142.  
  143. public OnPlayerSpawn(playerid)
  144. {
  145.     // spawn the player to their last saved position
  146.     SetPlayerInterior(playerid, Player[playerid][Interior]);
  147.     SetPlayerPos(playerid, Player[playerid][X_Pos], Player[playerid][Y_Pos], Player[playerid][Z_Pos]);
  148.     SetPlayerFacingAngle(playerid, Player[playerid][A_Pos]);
  149.  
  150.     SetCameraBehindPlayer(playerid);
  151.     return 1;
  152. }
  153.  
  154. public OnPlayerDeath(playerid, killerid, reason)
  155. {
  156.     UpdatePlayerDeaths(playerid);
  157.     UpdatePlayerKills(killerid);
  158.     return 1;
  159. }
  160.  
  161. public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
  162. {
  163.     switch (dialogid)
  164.     {
  165.         case DIALOG_UNUSED: return 1; // Useful for dialogs that contain only information and we do nothing depending on whether they responded or not
  166.  
  167.         case DIALOG_LOGIN:
  168.         {
  169.             if (!response) return Kick(playerid);
  170.  
  171.             new hashed_pass[65];
  172.             SHA256_PassHash(inputtext, Player[playerid][Salt], hashed_pass, 65);
  173.  
  174.             if (strcmp(hashed_pass, Player[playerid][Password]) == 0)
  175.             {
  176.                 //correct password, spawn the player
  177.                 ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "You have been successfully logged in.", "Okay", "");
  178.  
  179.                 // sets the specified cache as the active cache so we can retrieve the rest player data
  180.                 cache_set_active(Player[playerid][Cache_ID]);
  181.  
  182.                 AssignPlayerData(playerid);
  183.  
  184.                 // remove the active cache from memory and unsets the active cache as well
  185.                 cache_delete(Player[playerid][Cache_ID]);
  186.                 Player[playerid][Cache_ID] = MYSQL_INVALID_CACHE;
  187.  
  188.                 KillTimer(Player[playerid][LoginTimer]);
  189.                 Player[playerid][LoginTimer] = 0;
  190.                 Player[playerid][IsLoggedIn] = true;
  191.  
  192.                 // spawn the player to their last saved position after login
  193.                 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);
  194.                 SpawnPlayer(playerid);
  195.             }
  196.             else
  197.             {
  198.                 Player[playerid][LoginAttempts]++;
  199.  
  200.                 if (Player[playerid][LoginAttempts] >= 3)
  201.                 {
  202.                     ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "You have mistyped your password too often (3 times).", "Okay", "");
  203.                     DelayedKick(playerid);
  204.                 }
  205.                 else ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", "Wrong password!\nPlease enter your password in the field below:", "Login", "Abort");
  206.             }
  207.         }
  208.         case DIALOG_REGISTER:
  209.         {
  210.             if (!response) return Kick(playerid);
  211.  
  212.             if (strlen(inputtext) <= 5) return ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration", "Your password must be longer than 5 characters!\nPlease enter your password in the field below:", "Register", "Abort");
  213.  
  214.             // 16 random characters from 33 to 126 (in ASCII) for the salt
  215.             for (new i = 0; i < 16; i++) Player[playerid][Salt][i] = random(94) + 33;
  216.             SHA256_PassHash(inputtext, Player[playerid][Salt], Player[playerid][Password], 65);
  217.  
  218.             new query[221];
  219.             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]);
  220.             mysql_tquery(g_SQL, query, "OnPlayerRegister", "d", playerid);
  221.         }
  222.  
  223.         default: return 0; // dialog ID was not found, search in other scripts
  224.     }
  225.     return 1;
  226. }
  227.  
  228. //-----------------------------------------------------
  229.  
  230. forward OnPlayerDataLoaded(playerid, race_check);
  231. public OnPlayerDataLoaded(playerid, race_check)
  232. {
  233.     /*  race condition check:
  234.         player A connects -> SELECT query is fired -> this query takes very long
  235.         while the query is still processing, player A with playerid 2 disconnects
  236.         player B joins now with playerid 2 -> our laggy SELECT query is finally finished, but for the wrong player
  237.  
  238.         what do we do against it?
  239.         we create a connection count for each playerid and increase it everytime the playerid connects or disconnects
  240.         we also pass the current value of the connection count to our OnPlayerDataLoaded callback
  241.         then we check if current connection count is the same as connection count we passed to the callback
  242.         if yes, everything is okay, if not, we just kick the player
  243.     */
  244.     if (race_check != g_MysqlRaceCheck[playerid]) return Kick(playerid);
  245.  
  246.     new string[115];
  247.     if(cache_num_rows() > 0)
  248.     {
  249.         // we store the password and the salt so we can compare the password the player inputs
  250.         // and save the rest so we won't have to execute another query later
  251.         cache_get_value(0, "password", Player[playerid][Password], 65);
  252.         cache_get_value(0, "salt", Player[playerid][Salt], 17);
  253.  
  254.         // saves the active cache in the memory and returns an cache-id to access it for later use
  255.         Player[playerid][Cache_ID] = cache_save();
  256.  
  257.         format(string, sizeof string, "This account (%s) is registered. Please login by entering your password in the field below:", Player[playerid][Name]);
  258.         ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login", string, "Login", "Abort");
  259.  
  260.         // from now on, the player has 30 seconds to login
  261.         Player[playerid][LoginTimer] = SetTimerEx("OnLoginTimeout", SECONDS_TO_LOGIN * 1000, false, "d", playerid);
  262.     }
  263.     else
  264.     {
  265.         format(string, sizeof string, "Welcome %s, you can register by entering your password in the field below:", Player[playerid][Name]);
  266.         ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration", string, "Register", "Abort");
  267.     }
  268.     return 1;
  269. }
  270.  
  271. forward OnLoginTimeout(playerid);
  272. public OnLoginTimeout(playerid)
  273. {
  274.     // reset the variable that stores the timerid
  275.     Player[playerid][LoginTimer] = 0;
  276.  
  277.     ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Login", "You have been kicked for taking too long to login successfully to your account.", "Okay", "");
  278.     DelayedKick(playerid);
  279.     return 1;
  280. }
  281.  
  282. forward OnPlayerRegister(playerid);
  283. public OnPlayerRegister(playerid)
  284. {
  285.     // retrieves the ID generated for an AUTO_INCREMENT column by the sent query
  286.     Player[playerid][ID] = cache_insert_id();
  287.  
  288.     ShowPlayerDialog(playerid, DIALOG_UNUSED, DIALOG_STYLE_MSGBOX, "Registration", "Account successfully registered, you have been automatically logged in.", "Okay", "");
  289.  
  290.     Player[playerid][IsLoggedIn] = true;
  291.  
  292.     Player[playerid][X_Pos] = DEFAULT_POS_X;
  293.     Player[playerid][Y_Pos] = DEFAULT_POS_Y;
  294.     Player[playerid][Z_Pos] = DEFAULT_POS_Z;
  295.     Player[playerid][A_Pos] = DEFAULT_POS_A;
  296.  
  297.     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);
  298.     SpawnPlayer(playerid);
  299.     return 1;
  300. }
  301.  
  302. forward _KickPlayerDelayed(playerid);
  303. public _KickPlayerDelayed(playerid)
  304. {
  305.     Kick(playerid);
  306.     return 1;
  307. }
  308.  
  309.  
  310. //-----------------------------------------------------
  311.  
  312. AssignPlayerData(playerid)
  313. {
  314.     cache_get_value_int(0, "id", Player[playerid][ID]);
  315.  
  316.     cache_get_value_int(0, "kills", Player[playerid][Kills]);
  317.     cache_get_value_int(0, "deaths", Player[playerid][Deaths]);
  318.  
  319.     cache_get_value_float(0, "x", Player[playerid][X_Pos]);
  320.     cache_get_value_float(0, "y", Player[playerid][Y_Pos]);
  321.     cache_get_value_float(0, "z", Player[playerid][Z_Pos]);
  322.     cache_get_value_float(0, "angle", Player[playerid][A_Pos]);
  323.     cache_get_value_int(0, "interior", Player[playerid][Interior]);
  324.     return 1;
  325. }
  326.  
  327. DelayedKick(playerid, time = 500)
  328. {
  329.     SetTimerEx("_KickPlayerDelayed", time, false, "d", playerid);
  330.     return 1;
  331. }
  332.  
  333. SetupPlayerTable()
  334. {
  335.     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`))");
  336.     return 1;
  337. }
  338.  
  339. UpdatePlayerData(playerid, reason)
  340. {
  341.     if (Player[playerid][IsLoggedIn] == false) return 0;
  342.  
  343.     // if the client crashed, it's not possible to get the player's position in OnPlayerDisconnect callback
  344.     // 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)
  345.     if (reason == 1)
  346.     {
  347.         GetPlayerPos(playerid, Player[playerid][X_Pos], Player[playerid][Y_Pos], Player[playerid][Z_Pos]);
  348.         GetPlayerFacingAngle(playerid, Player[playerid][A_Pos]);
  349.     }
  350.  
  351.     new query[145];
  352.     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]);
  353.     mysql_tquery(g_SQL, query);
  354.     return 1;
  355. }
  356.  
  357. UpdatePlayerDeaths(playerid)
  358. {
  359.     if (Player[playerid][IsLoggedIn] == false) return 0;
  360.  
  361.     Player[playerid][Deaths]++;
  362.  
  363.     new query[70];
  364.     mysql_format(g_SQL, query, sizeof query, "UPDATE `players` SET `deaths` = %d WHERE `id` = %d LIMIT 1", Player[playerid][Deaths], Player[playerid][ID]);
  365.     mysql_tquery(g_SQL, query);
  366.     return 1;
  367. }
  368.  
  369. UpdatePlayerKills(killerid)
  370. {
  371.     // we must check before if the killer wasn't valid (connected) player to avoid run time error 4
  372.     if (killerid == INVALID_PLAYER_ID) return 0;
  373.     if (Player[killerid][IsLoggedIn] == false) return 0;
  374.  
  375.     Player[killerid][Kills]++;
  376.  
  377.     new query[70];
  378.     mysql_format(g_SQL, query, sizeof query, "UPDATE `players` SET `kills` = %d WHERE `id` = %d LIMIT 1", Player[killerid][Kills], Player[killerid][ID]);
  379.     mysql_tquery(g_SQL, query);
  380.     return 1;
  381. }
Add Comment
Please, Sign In to add comment