Jump to content

Abyss

Administrators
  • Content Count

    26
  • Joined

  • Last visited

  • Days Won

    13

Abyss last won the day on July 1

Abyss had the most liked content!

Community Reputation

79 Excellent

3 Followers

About Abyss

  • Rank
    GvC Leader

Personal Information

  • Expertise
    Array

Recent Profile Visitors

94 profile views
  1. tức là bạn muốn cái textdraw login nó thông báo số lần sai mật khẩu khi nhập sai ấy hả
  2. Vẫn chưa hiểu ý bạn như ban đầu là như thế nào -_-
  3. Hôm qua chúng ta đã cùng tìm hiểu const, enum với bài học số 1 về Initialisers Code, chúng hơi khó hiểu có đúng không ^^. Với bài học hôm nay chúng ta sẽ cùng nghiên cứu 2 mã khởi tạo mà bạn sẽ luôn thấy chúng trong toàn bộ các gamemode, chúng thông dụng gần như các plain function đó. Đúng vậy, hôm nay chúng ta sẽ nghiên cứu về forward và public. Hãy cùng tìm hiểu lý do vì sao trong pawn, chỉ có public function mới có thể gọi từ bên ngoài script hay tại sao phải khai báo forward trước một public function nhé. Menu bài viết về Initialisers Code Bài 1 - const, enum Bài 2 - forward, public I. FORWARD Khi sử dụng mã khởi tạo forward, trình biên dịch sẽ hiểu đây là một function sẽ được gọi tới sau. Nó được yêu cầu bắt buộc cho tất cả các public function tuy nhiên chúng ta vẫn có thể sử dụng chúng trong nhiều nơi khác. Nó được sử dụng để "forward" (chuyển tiếp) với đầy đủ các tham số của function mà bạn muốn chuyển tiếp và kết thúc bằng dấu chấm phẩy.[Hidden Content]Khi nó được sử dụng với public funcion, forward có thể sử dụng để sửa các cảnh báo hiếm gặp như sử dụng một function trả về một tag kết quả (ví dụ: float) trước khi nó được khai báo. main() { new Float:myVar = MyFloatFunction(); } Float:MyFloatFunction() { return 5.0; } [/hide]Với đoạn script trên, trình biên dịch sẽ hiển thị cảnh báo rõ ràng bởi vì nó không biết làm sẽ phải trả về là số hay số thập phân. Trong ví dụ này, nó trả về dưới dạng float. Điều này có thể được giải quyết bằng cách khai báo hàm trước khi sử dụng.[Hidden Content]Hoặc bằng cách sử dụng forward trước hàm để trình biên dịch biết phải làm gì:[Hidden Content] Cách gọi một public function theo tên:[Hidden Content]public function với tiền tố 'public' hoặc '@' đều phải thiết lập chuyển tiếp 'forward' như đã nói ở mục 'forward' phía trên.[Hidden Content]Với ví dụ trên, Hàm SetTimerEx đã được sử dụng để gọi "MyOtherPublicFunction" sau 5 giây và truyền giá trị nguyên 7 để 'printf'. Hàm main được sử dụng nhiều trong các ví dụ như thế này, nó tương tự như một public function ở chỗ nó có thể gọi từ bên ngoài tập lệnh, tuy nhiên nó không phải là một public function, nó có một địa chỉ đặc biệt để server nhận biết nơi để gọi chúng trong tệp amx. Tất cả SA-MP Callbacks đều là public function và được gọi từ bên ngoài tập lệnh một cách tự động:[Hidden Content][Hidden Content] forward MyPublicFunc(); main() { MyPublicFunc(); } public MyPublicFunc() { printf("Hello"); } Nó nhanh hơn nhiều so với việc sử dụng CallLocalFunction hoặc native.
  4. Chắc hẳn các bạn đều đã nhìn thấy các đoạn mã stock, public, forward v.v. ở phía trước một hàm custom mà các scripter viết rồi đúng không? Vậy chúng là gì? Công dụng của chúng như thế nào? Cách sử dụng chúng ra làm sao? Tôi sẽ giải đáp các thắc mắc đó của bạn trong bài viết này. Menu bài viết về Initialisers Code Bài 1 - const, enum Bài 2 - forward, public I. CÁC LOẠI MÃ KHỞI TẠO Như các bạn đã biết, mã nguồn dùng để viết SA-MP được dựa trên Pawn - An Embed Scripting Language (mã kịch bản nhúng). Các mã khởi tạo trong SA-MP cũng tương đồng với nó. Chúng ta có thể liệt kê ra đây danh sách các mã khởi tạo như: const enum forward native new operator public static stock Mỗi mã khởi tạo lại có một ý nghĩa và công dụng khác nhau, tuy rằng gần như tất cả đều có thể gọi ra bằng cách thông thường. Hãy cùng tìm hiểu chi tiết từng đoạn mã khởi tạo nhé. II. CONST const không được sử dụng nhiều trong script tuy nhiên nó sẽ khai báo một biến không thể sửa đổi bằng mã, tức không thể thay đổi giá trị của chúng sau khi được xác định. Có một vài cách sử dụng cho mã khởi tạo này là với các biến với dạng chuỗi, mảng const đôi khi có thể được biên dịch (compiler) hiệu quả hơn hoặc với 1 số define dạng chuỗi (array). [Hidden Content]III. ENUM [Hidden Content]Nhìn chuyên nghiệp hơn rất nhiều phải không nào? Điều này cũng sẽ tiết kiệm các biến global của bạn làm giảm nhẹ quá trình biên dịch cho gamemode của bạn sẽ nhẹ hơn đi rất nhiều mà đơn giản, rút gọn các bước thiết lập để sử dụng một cách tối ưu.
  5. Abyss

    Lỗi F5 PAWNO.

    Lỗi này chủ yếu là do gamemode của bạn không được đính kèm các tệp script phụ của nó dẫn tới không thể xác định các hàm. Nếu như các tệp script đi kèm đã đủ, thì khả năng cao là bạn chưa đóng 1 trường code nào đó trước khi các function này được gọi tới.
  6. @Obbie h mình mới xem kỹ script của bạn, lý do bạn không select dc là do define sai cách với biến LoginPTD, bạn còn chưa cho hàm SelectTextDraw khi show Textdraws nên mới không click dc. Thay new Text: LoginTD[2], PlayerText: LoginPTD[MAX_PLAYERS]; và new Text: LoginTD[2], PlayerText:LoginPTD[MAX_PLAYERS][3]; Player Textdraw là dạng multi-array, không thể define nó giống như Global Textdraw bởi nó xác định theo ID Player người chơi. Nếu bạn define như cách trên thì Textdraw LoginPTD của bạn hiện đang tạo cho playerid 0, playerid 1, playerid 2 nên không thể select được. Script hoàn chỉnh #include <a_samp> #include <a_mysql> #include <foreach> #include <sscanf2> main() { print("\n----------------------------------"); print(" Blank Gamemode by your name here"); print("----------------------------------\n"); } new Text: LoginTD[2], PlayerText:LoginPTD[MAX_PLAYERS][3]; #define MYSQL_HOST "localhost" // Change this to your MySQL Remote IP or "localhost". #define MYSQL_USER "root" // Change this to your MySQL Database username. #define MYSQL_PASS "" // Change this to your MySQL Database password. #define MYSQL_DATABASE "gvc_test" // Change this to your MySQL Database name. // Well, don't just enter random information and expect it to work, information // Should be valid and working fine. #define DIALOG_REGISTER (0) #define DIALOG_LOGIN (1) // Make sure the dialog IDs above do not match any dialog ID you're using in your // gamemode otherwise they won't do their job properly. new MySQL: Database, Corrupt_Check[MAX_PLAYERS]; new string[128]; //Creating an enumerator to store player's data for further use (below). //============================================================================== enum ENUM_PLAYER_DATA { ID, Name[25], Password[65], Salt[11], PasswordFails, Kills, Deaths, Score, Cash, Cache: Player_Cache, bool:LoggedIn } new pInfo[MAX_PLAYERS][ENUM_PLAYER_DATA]; //============================================================================== public OnGameModeInit() { new MySQLOpt: option_id = mysql_init_options(); mysql_set_option(option_id, AUTO_RECONNECT, true); // We will set that option to automatically reconnect on timeouts. Database = mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS, MYSQL_DATABASE, option_id); // Setting up the "Database" handle on the given MySQL details above. if(Database == MYSQL_INVALID_HANDLE || mysql_errno(Database) != 0) // Checking if the database connection is invalid to shutdown. { print("I couldn't connect to the MySQL server, closing."); // Printing a message to the log. SendRconCommand("exit"); // Sending console command to shut down server. return 1; } print("I have connected to the MySQL server."); // If the given MySQL details were all okay, this message prints to the log. // Now, we will set up the information table of the player's information. 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`))"); // So, this code is probably the only one which you haven't understood. // Well, we firstly create a table only if not existing in the database which is "USERS". // We create "ID" and set it as a primary key with auto increment to use it in retrieving information and many more uses. // We create "USERNAME" and set it as a unique key, the USERNAME stores every player's name in the database so you can // Control the players in offline mode and when a player leaves everything storted like kills, deaths, password and Saltion key // Wouldn't be lost upon server's close or player's disconnection. // We store kills, deaths, score and cash as written above so they might be useful for further use. return 1; } public OnGameModeExit() { foreach(new i: Player) { if(IsPlayerConnected(i)) // Checking if the players stored in "i" are connected. { OnPlayerDisconnect(i, 1); // We do that so players wouldn't lose their data upon server's close. } } mysql_close(Database); // Closing the database. return 1; } public OnPlayerConnect(playerid) { new DB_Query[115]; //Resetting player information. pInfo[playerid][Kills] = 0; pInfo[playerid][Deaths] = 0; pInfo[playerid][PasswordFails] = 0; GetPlayerName(playerid, pInfo[playerid][Name], MAX_PLAYER_NAME); // Getting the player's name. Corrupt_Check[playerid]++; mysql_format(Database, DB_Query, sizeof(DB_Query), "SELECT * FROM `PLAYERS` WHERE `USERNAME` = '%e' LIMIT 1", pInfo[playerid][Name]); mysql_tquery(Database, DB_Query, "OnPlayerDataCheck", "ii", playerid, Corrupt_Check[playerid]); LoginTD[0] = TextDrawCreate(450.000000, 107.000000, "box"); TextDrawLetterSize(LoginTD[0], 0.000000, 20.000000); TextDrawTextSize(LoginTD[0], 179.000030, 0.000000); TextDrawAlignment(LoginTD[0], 1); TextDrawColor(LoginTD[0], -1); TextDrawUseBox(LoginTD[0], 1); TextDrawBoxColor(LoginTD[0], 353704161); TextDrawSetShadow(LoginTD[0], 0); TextDrawBackgroundColor(LoginTD[0], 255); TextDrawFont(LoginTD[0], 1); TextDrawSetProportional(LoginTD[0], 1); LoginTD[1] = TextDrawCreate(313.666687, 110.770324, "LOGIN"); TextDrawLetterSize(LoginTD[1], 0.400000, 1.600000); TextDrawTextSize(LoginTD[1], 0.000000, 253.000000); TextDrawAlignment(LoginTD[1], 2); TextDrawColor(LoginTD[1], -1); TextDrawUseBox(LoginTD[1], 1); TextDrawBoxColor(LoginTD[1], 255); TextDrawSetShadow(LoginTD[1], 0); TextDrawBackgroundColor(LoginTD[1], 255); TextDrawFont(LoginTD[1], 1); TextDrawSetProportional(LoginTD[1], 1); LoginPTD[playerid][0] = CreatePlayerTextDraw(playerid, 315.666870, 136.074127, "NAME : OBBIE"); PlayerTextDrawLetterSize(playerid, LoginPTD[playerid][0], 0.400000, 1.600000); PlayerTextDrawTextSize(playerid, LoginPTD[playerid][0], 0.000000, 165.000000); PlayerTextDrawAlignment(playerid, LoginPTD[playerid][0], 2); PlayerTextDrawColor(playerid, LoginPTD[playerid][0], -1); PlayerTextDrawUseBox(playerid, LoginPTD[playerid][0], 1); PlayerTextDrawBoxColor(playerid, LoginPTD[playerid][0], 255); PlayerTextDrawSetShadow(playerid, LoginPTD[playerid][0], 0); PlayerTextDrawBackgroundColor(playerid, LoginPTD[playerid][0], 255); PlayerTextDrawFont(playerid, LoginPTD[playerid][0], 1); PlayerTextDrawSetProportional(playerid, LoginPTD[playerid][0], 1); LoginPTD[playerid][1] = CreatePlayerTextDraw(playerid, 315.666748, 163.451828, "PASSWORD : CLICK"); PlayerTextDrawLetterSize(playerid, LoginPTD[playerid][1], 0.400000, 1.600000); PlayerTextDrawTextSize(playerid, LoginPTD[playerid][1], 30.000000, 165.000000); PlayerTextDrawAlignment(playerid, LoginPTD[playerid][1], 2); PlayerTextDrawColor(playerid, LoginPTD[playerid][1], -1); PlayerTextDrawUseBox(playerid, LoginPTD[playerid][1], 1); PlayerTextDrawBoxColor(playerid, LoginPTD[playerid][1], 255); PlayerTextDrawSetShadow(playerid, LoginPTD[playerid][1], 0); PlayerTextDrawBackgroundColor(playerid, LoginPTD[playerid][1], 255); PlayerTextDrawFont(playerid, LoginPTD[playerid][1], 1); PlayerTextDrawSetProportional(playerid, LoginPTD[playerid][1], 1); PlayerTextDrawSetSelectable(playerid, LoginPTD[playerid][1], true); LoginPTD[playerid][2] = CreatePlayerTextDraw(playerid, 315.099945, 248.488922, "Last Login~n~127.0.0.1 20:20:20"); PlayerTextDrawLetterSize(playerid, LoginPTD[playerid][2], 0.400000, 1.600000); PlayerTextDrawTextSize(playerid, LoginPTD[playerid][2], 0.000000, 252.000000); PlayerTextDrawAlignment(playerid, LoginPTD[playerid][2], 2); PlayerTextDrawColor(playerid, LoginPTD[playerid][2], -1); PlayerTextDrawUseBox(playerid, LoginPTD[playerid][2], 1); PlayerTextDrawBoxColor(playerid, LoginPTD[playerid][2], 255); PlayerTextDrawSetShadow(playerid, LoginPTD[playerid][2], 0); PlayerTextDrawBackgroundColor(playerid, LoginPTD[playerid][2], 255); PlayerTextDrawFont(playerid, LoginPTD[playerid][2], 1); PlayerTextDrawSetProportional(playerid, LoginPTD[playerid][2], 1); return 1; } public OnPlayerDisconnect(playerid, reason) { Corrupt_Check[playerid]++; new DB_Query[256]; //Running a query to save the player's data using the stored stuff. mysql_format(Database, DB_Query, sizeof(DB_Query), "UPDATE `PLAYERS` SET `SCORE` = %d, `CASH` = %d, `KILLS` = %d, `DEATHS` = %d WHERE `ID` = %d LIMIT 1", pInfo[playerid][Score], pInfo[playerid][Cash], pInfo[playerid][Kills], pInfo[playerid][Deaths], pInfo[playerid][ID]); mysql_tquery(Database, DB_Query); if(cache_is_valid(pInfo[playerid][Player_Cache])) //Checking if the player's cache ID is valid. { cache_delete(pInfo[playerid][Player_Cache]); // Deleting the cache. pInfo[playerid][Player_Cache] = MYSQL_INVALID_CACHE; // Setting the stored player Cache as invalid. } pInfo[playerid][LoggedIn] = false; print("OnPlayerDisconnect has been called."); // Sending message once OnPlayerDisconnect is called. return 1; } public OnPlayerDeath(playerid, killerid, reason) { if(killerid != INVALID_PLAYER_ID) // Checking if the killer of the player is valid. { //Increasing the kills of the killer and the deaths of the player. pInfo[killerid][Kills]++; pInfo[playerid][Deaths]++; } return 1; } public OnPlayerRequestSpawn(playerid) { if(pInfo[playerid][LoggedIn] == false) return 0; // Ignoring the request incase player isn't logged in. return 1; } public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]) { switch (dialogid) { case DIALOG_LOGIN: { if(!response) return Kick(playerid); new Salted_Key[65]; SHA256_PassHash(inputtext, pInfo[playerid][Salt], Salted_Key, 65); if(strcmp(Salted_Key, pInfo[playerid][Password]) == 0) { // Now, password should be correct as well as the strings // Matched with each other, so nothing is wrong until now. // We will activate the cache of player to make use of it e.g. // Retrieve their data. cache_set_active(pInfo[playerid][Player_Cache]); // Okay, we are retrieving the information now.. cache_get_value_int(0, "ID", pInfo[playerid][ID]); cache_get_value_int(0, "KILLS", pInfo[playerid][Kills]); cache_get_value_int(0, "DEATHS", pInfo[playerid][Deaths]); cache_get_value_int(0, "SCORE", pInfo[playerid][Score]); cache_get_value_int(0, "CASH", pInfo[playerid][Cash]); SetPlayerScore(playerid, pInfo[playerid][Score]); ResetPlayerMoney(playerid); GivePlayerMoney(playerid, pInfo[playerid][Cash]); // So, we have successfully retrieved data? Now deactivating the cache. cache_delete(pInfo[playerid][Player_Cache]); pInfo[playerid][Player_Cache] = MYSQL_INVALID_CACHE; pInfo[playerid][LoggedIn] = true; SendClientMessage(playerid, 0x00FF00FF, "Logged in to the account."); } else { new String[150]; pInfo[playerid][PasswordFails] += 1; printf("%s has been failed to login. (%d)", pInfo[playerid][Name], pInfo[playerid][PasswordFails]); // Printing the message that someone has failed to login to his account. if (pInfo[playerid][PasswordFails] >= 3) // If the fails exceeded the limit we kick the player. { format(String, sizeof(String), "%s has been kicked Reason: {FF0000}(%d/3) Login fails.", pInfo[playerid][Name], pInfo[playerid][PasswordFails]); SendClientMessageToAll(0x969696FF, String); Kick(playerid); } else { // If the player didn't exceed the limits we send him a message that the password is wrong. format(String, sizeof(String), "Wrong password, you have %d out of 3 tries.", pInfo[playerid][PasswordFails]); SendClientMessage(playerid, 0xFF0000FF, String); /*format(String, sizeof(String), "{FFFFFF}Welcome back, %s.\n\n{0099FF}This account is already registered.\n\ {0099FF}Please, input your password below to proceed to the game.\n\n", pInfo[playerid][Name]); ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login System", String, "Login", "Leave");*/ } } } case DIALOG_REGISTER: { if(!response) return Kick(playerid); if(strlen(inputtext) <= 5 || strlen(inputtext) > 60) { // If the password length is less than or equal to 5 and more than 60 // It repeats the process and shows error message as seen below. SendClientMessage(playerid, 0x969696FF, "Invalid password length, should be 5 - 60."); new String[150]; format(String, sizeof(String), "{FFFFFF}Welcome %s.\n\n{0099FF}This account is not registered.\n\ {0099FF}Please, input your password below to proceed.\n\n", pInfo[playerid][Name]); ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration System", String, "Register", "Leave"); } else { // Salting the player's password using SHA256 for a better security. for (new i = 0; i < 10; i++) { pInfo[playerid][Salt][i] = random(79) + 47; } pInfo[playerid][Salt][10] = 0; SHA256_PassHash(inputtext, pInfo[playerid][Salt], pInfo[playerid][Password], 65); new DB_Query[225]; // Storing player's information if everything goes right. mysql_format(Database, DB_Query, sizeof(DB_Query), "INSERT INTO `PLAYERS` (`USERNAME`, `PASSWORD`, `SALT`, `SCORE`, `KILLS`, `CASH`, `DEATHS`)\ VALUES ('%e', '%s', '%e', '20', '0', '0', '0')", pInfo[playerid][Name], pInfo[playerid][Password], pInfo[playerid][Salt]); mysql_tquery(Database, DB_Query, "OnPlayerRegister", "d", playerid); } } } return 1; } forward public OnPlayerDataCheck(playerid, corrupt_check); public OnPlayerDataCheck(playerid, corrupt_check) { if (corrupt_check != Corrupt_Check[playerid]) return Kick(playerid); // You'd have asked already what's corrput_check and how it'd benefit me? // Well basically MySQL query takes long, incase a player leaves while its not proceeded // With ID 1 for example, then another player comes as ID 1 it'll basically corrupt the data // So, once the query is done, the player will have the wrong data assigned for himself. new String[150]; if(cache_num_rows() > 0) { // If the player exists, everything is okay and nothing is wrongly detected // The player's password and Saltion key gets stored as seen below // So we won't have to get a headache just to match player's password. cache_get_value(0, "PASSWORD", pInfo[playerid][Password], 65); cache_get_value(0, "SALT", pInfo[playerid][Salt], 11); pInfo[playerid][Player_Cache] = cache_save(); // ^ Storing the cache ID of the player for further use later. format(string, sizeof(string), "NAME: %s", pInfo[playerid][Name]); PlayerTextDrawSetString(playerid, LoginPTD[playerid][0], string); PlayerTextDrawSetString(playerid, LoginPTD[playerid][1], "PASSWORD: ~b~click"); for(new i = 0; i < 3; i++) PlayerTextDrawShow(playerid, LoginPTD[playerid][i]); for(new i = 0; i < 2; i++) TextDrawShowForPlayer(playerid, LoginTD[i]); SelectTextDraw(playerid, 0xFF4040AA); /* format(String, sizeof(String), "{FFFFFF}Welcome back, %s.\n\n{0099FF}This account is already registered.\n\ {0099FF}Please, input your password below to proceed to the game.\n\n", pInfo[playerid][Name]); ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Login System", String, "Login", "Leave"); */ } else { format(String, sizeof(String), "{FFFFFF}Welcome %s.\n\n{0099FF}This account is not registered.\n\ {0099FF}Please, input your password below to proceed to the game.\n\n", pInfo[playerid][Name]); ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Registration System", String, "Register", "Leave"); } return 1; } forward public OnPlayerRegister(playerid); public OnPlayerRegister(playerid) { // This gets called only when the player registers a new account. SendClientMessage(playerid, 0x00FF00FF, "You are now registered and has been logged in."); pInfo[playerid][LoggedIn] = true; return 1; } public OnPlayerKeyStateChange(playerid, newkeys, oldkeys) { if(newkeys == KEY_SUBMISSION) { SelectTextDraw(playerid, 0xFF4040AA); } return 1; } public OnPlayerClickPlayerTextDraw(playerid, PlayerText:playertextid) { SendClientMessage(playerid, -1, "1"); if(playertextid == LoginPTD[playerid][1]) { SendClientMessage(playerid, 0xFFFFFFAA, "You clicked on a textdraw."); CancelSelectTextDraw(playerid); return 1; } return 0; } // End of script //
  7. Bạn thử tăng giá trị X từ 5.0 lên 10.0 và compiler lại xem sao, có thể textdraw của bạn rộng quá, nó không đủ vùng chọn. Mà phải chắc chắn rằng bạn chạy gamemode đã chỉnh sửa để test nhé
  8. Hack là vấn đề nan giải đối với SA-MP, do có quá nhiều bản mod có thể can thiệp sâu vào hệ thống của trò chơi khiến cho việc tạo nên 1 hệ thống bắt hack thật sự rất khó khăn. Đặc biệt là với những máy chủ mới với những scripter thiếu kinh nghiệm thì việc xử lý hack/cheat như là điều không tưởng mà phải phụ thuộc vào những hệ thống được tạo lập sẵn trên các diễn đàn SA-MP ở nước ngoài với hàng loạt những thiết lập mà có thể bạn còn không hiểu nổi. Vì vậy, tôi quyết định viết lên một series bài viết "TẠO DỰNG HỆ THỐNG SA-MP ANTI HACK" để hướng dẫn các bạn cách để tạo nên một hệ thống Anti Cheat có thể xử lý được hack thường thấy ở các máy chủ tại Việt Nam. I. Cách nhìn nhận vấn đề và xây dựng hệ thống Khi bạn gặp 1 người chơi sử dụng hack, hãy vận dụng đầu óc và kinh nghiệm của mình để phân tích chúng, các loại hack thường gặp hiện nay ta có thể liệt kê ra như: Health/Armour Hack Rapid fire Invisible Hack Infinite Run Stamina (Chạy không mệt) Teleport Hack Weapon Hack Money Hack v.v. Chúng ta cần phải có một mindset (tư duy) nhìn nhận vấn đề một cách rõ ràng và tự đặt vấn đề cho bản thân là cái tệp hack này nó hoạt động như thế nào? Tạm bỏ qua việc bạn tìm hiểu nó can thiệp vào sources game ra sao, hãy nhìn nhận vấn đề đúng trọng tâm và đơn giản. Đừng phức tạp hóa vấn đề lên bởi ngay cả chính tôi, người viết bài này cũng chưa thoát được khỏi vòng luẩn quẩn của việc phức tạp hóa vấn đề quá mức cần thiết. Nếu bạn gặp Health/Armour Hack, hãy đặt vấn đề ra một cách đơn giản: "À, cheater tăng được máu, tăng được giáp, tiền mà mình không thể phát hiện ra hình như do mình chưa có đồng bộ chúng" sau khi đã đặt vấn đề xong, chúng ta đã đi được 80% quãng đường rồi, bởi khi đặt được vấn đề, chúng ta bắt đầu sử dụng tư duy logic của mình để thiết lập nên cách khắc phục vấn đề. II. Giải thích [Hidden Content] III. Xử lý vấn đề a) Thiết lập Server-side function Hãy tạo cho mình một giá trị global để lưu trữ Health/Armour, chúng sẽ được thiết lập giá trị mỗi khi hàm thiết lập máu giáp của bạn được gọi trong máy chủ để lưu trữ giá trị Health/Armour của nhân vật. new Float:Player_Health[MAX_PLAYERS]; // Đặt ở bất kỳ đâu trong gamemode trước khi define stock SV_SetPlayyerHealth stock SV_SetPlayerHealth(playerrid, Float:health) { Player_Health[playerid] = health; SetPlayerHealth(playerid, health); } và thay thế toàn bộ hàm SetPlayerHealth thành SV_SetPlayerHealth trong gamemode của bạn để hoàn tất việc thiết lập server-side function. Sau khi lưu trữ được thông tin Health/Armour, ta sẽ tiến hành đối chiếu chúng nếu như có sự thay đổi về Health/Armour của nhân vật, cách để d đối chiếu thì ta dùng một vòng lặp sự kiện bằng SetTimer/SetTimerEx hoặc OnPlayerUpdate. Ở đây là Health/Armour, ta nên dùng OnPlayerUpdate thay vì SetTimer/SetTimerEx bởi tốc độ xử lý của chúng, tuy nhiên, không nên đặt các đoạn script xử lý các tác vụ nặng trong OnPlayerUpdate bởi nó gọi 30 lần/1s, điều này sẽ khiến máy chủ của bạn bị chậm đi. public OnPlayerUpdate(playerid) { new Float:health; // Tạo biến local để lưu trữ thông tin Health sau khi lấy GetPlayerHealth(playerid, health); // Lấy thông tin Health if(health < Player_Health[playerid]) Player_Health[playerid] = health; else SetPlayerHealth(playerid, Player_Health[playerid]); } a) Giải thích 1: if(health < Player_Health[playerid]) Player_Health[playerid] = health; [Hidden Content] b) Giải thích 2: else SetPlayerHealth(playerid, Player_Health[playerid]); [Hidden Content] Bạn cũng có thể sử dụng script trên để áp dụng với Armour/Money Hack bằng việc thay đổi hàm xử lý bên trong chúng IV. Kết Với bài học ngày hôm nay, tôi đã hướng dẫn các bạn những bước cơ bản ban đầu và quan trọng nhất đối với việc xử lý hack/cheat. Các vấn đề này thực tế rất đơn giản, chỉ cần chúng ta chịu khó sử dụng phương pháp "Thử và sai" ta sẽ tìm ra được cách giải quyết tối ưu nhất. Để bài học đạt được hiệu quả tốt nhất, tôi sẽ đưa cho các bạn 1 vấn đề nhỏ: Tạo ra hệ thống Anti Health Hack nhưng vẫn cho phép sử dụng các bình Sprunk trong game để hồi máu Các bạn hãy vận dụng những kiến thức tôi đã chia sẻ phía trên để thực hiện và comment chúng ở phía dưới bài viết để cùng thảo luận xem script của bạn đã đúng hay chưa nhé.
  9. Bạn cần phải thay đổi giá trị Text Size dòng này từ PlayerTextDrawTextSize(playerid, LoginPTD[1], 0.000000, 165.000000); thành PlayerTextDrawTextSize(playerid, LoginPTD[1], 5.000000, 165.000000); Lý do cho việc này thì vùng chọn của hàm PlayerTextDrawSetSelectable nó được xác định dựa trên Text Size, nếu như 1 giá trị X (Width/Chiều rộng) hoặc Y (Height/Chiều cao) của vùng chọn không được thiết lập, tức là bằng 0.0 thì vùng chọn sẽ không thể thiết lập dẫn tới việc không thể Select textdraw đó. TDE không thiết lập ra giá trị X của vùng chọn đối với Select Able Textdraw nên bạn cần phải tự chỉnh giá trị đó, mình cũng thường dùng TDE của adr1 nên khá quen với việc này rồi, không biết các Textdraw Editor khác có xử lý được vụ này không. Giá trị này sau khi làm nhiều bạn sẽ có kinh nghiệm trong việc tự đo kích thước X sao cho khớp nhất với kích thước Textdraw của bạn.
  10. GetSpriteSize Author: m1n1vv I. GIỚI THIỆU CHUNG Trong 0.3.DL, nhiều máy chủ sẽ có thể tạo ra các giao diện UI/Interface đặc biệt bằng các Sprite Textdraws. Nhưng trong Textdraws, thật không dễ để thiết kế chúng phù hợp với tất cả các độ phân giải màn hình (Screen Resolution). Hàm này sẽ tính toán mọi thứ phụ thuộc vào kích thước của Sprite và độ phân giải màn hình mà bạn thiết kế. Ngoài ra, thông qua hàm này bạn cũng có thể tính toán vị trí của Textdraw. II. Function stock GetSpriteSize(width, height, wt, ht, &Float:ws, &Float:hs) { ws = wt*(640.0*100.0/width)/100.0; hs = ht*(448.0*100.0/height)/100.0; return 1; } III. SỬ DỤNG CMD:test(playerid, params[]) { new Float:x, Float:y, width = 1920, height = 1080; GetSpriteSize(width, height, 15, 15, x, y); // Kích cỡ Texture Sprite là 15x15 printf("X: %.2f | Y: %.2f", floatroundto(x, 2), floatroundto(y, 2)); return 1; }
  11. ProbaRandom Author: Lev_Popov I. GIỚI THIỆU CHUNG Hàm này cho phép bạn lấy 1 số ngẫu nhiên với xác xuất II. Function stock ProbaRandom(array[], size_w = sizeof(array)) { if(size_w < 1) return -1; new sum = 0, result = 0; for(new i = size_w - 1; i > -1; i--) { sum += array[i]; if(random(sum) < array[i]) { result = i; } } return result; } III. SỬ DỤNG CMD:test(playerid, params[]) { new array[] = {5, 3, 15, 7, 50, 10}; for(new i; i < 10; i++) { printf("%d", ProbaRandom(array)); } return 1; }
  12. ExcludeRandom Author: Lev_Popov I. GIỚI THIỆU CHUNG Hàm này cho phép bạn lấy 1 số ngẫu nhiên nhưng không bao gồm các số ngoại trừ được bổ sung trong thông số của hàm. II. Function stock ExcludeRandom(const max_value, ...) { new result; rerandom: result = random(max_value + 1); for(new i = numargs() + 1; --i != 0;) if(result == getarg(i)) goto rerandom; return result; } III. SỬ DỤNG CMD:test(playerid, params[]) { new rand = ExcludeRandom(10, 3, 5, 7, 8); // Lấy số ngẫu nhiên từ 0 đến 10 không bao gồm 3, 5, 7, 8 printf("Rand: %d", rand); return 1; }
  13. GetDayOfWeek Author: Mr_David (IsPlayerInWaterZone) / OKStyle (IsPlayerInWater) I. GIỚI THIỆU CHUNG a) IsPlayerInWater Hàm này cho phép bạn xác định người chơi có đứng ở trong nước hay không bằng Animation, có thể hiểu là xác định xem người chơi có đang trong trạng thái bơi hay không để nhận biết người chơi đó hiện đang đứng ở mặt đất hay ở trong vùng nước. b) IsPlayerInWaterZone Hàm này cho phép bạn xác định người chơi đứng ở trong vùng nước hay không bằng tọa độ (coordinate), để mà nói thì hàm này mang tính bao quát hơn so với hàm sử dụng bằng Animation, bởi khi check bằng Animation bạn sẽ không thể xác định nếu người chơi đó đang bị Freeze hay ngồi trong phương tiện. Cả 2 hàm này đều có cách sử dụng riêng của chúng, hãy sử dụng nó theo cách của bạn. II. Function stock IsPlayerInWater(playerid) { if(IsPlayerInAnyVehicle(playerid)) return false; new animlib[32], animname[32]; GetAnimationName(GetPlayerAnimationIndex(playerid), animlib, sizeof(animlib), animname, sizeof(animname)); return (strfind(animname, "swim", true) != -1 || strfind(animname, "water", true) != -1) ? true : false; } stock IsPlayerInWaterZone(playerid); { new Float:FXF_wpos[3]; GetPlayerPos(playerid,FXF_wpos[0],FXF_wpos[1],FXF_wpos[2]); if((FXF_wpos[2] > 0.00) || IsPlayerInZone(playerid,1808.2019,1424.5392,-2230.5024,-2347.7979)) return false; else if((FXF_wpos[2] < 0.00) && (FXF_wpos[2] > -1.00)) return true; // 1 else if(FXF_wpos[2] < -1.00) return true; // 2 return false; } stock IsPlayerInZone(playerid, Float:minX, Float:minY, Float:maxX, Float:maxY) { new Float:FXF_ZoneX, Float:FXF_ZoneY, Float:FXF_ZoneZ; GetPlayerPos(playerid, FXF_ZoneX, FXF_ZoneY, FXF_ZoneZ); if((FXF_ZoneX >= minX && FXF_ZoneX <= maxX) && (FXF_ZoneY >= minY && FXF_ZoneY <= maxY) && FXF_ZoneZ <= 500) return true; return false; } III. SỬ DỤNG CMD:test(playerid, params[]) { if(IsPlayerInWater(playerid)) SendClientMessage(playerid, -1, "Ban dang boi"); return 1; } CMD:test1(playerid, params[]) { if(IsPlayerInWaterZone(playerid)) SendClientMessage(playerid, -1, "Ban dang dung o trong nuoc"); return 1; }
  14. GetDayOfWeek Author: No_1 I. GIỚI THIỆU CHUNG Hàm này cho phép bạn có thể xác định số ngày trong tuần dựa theo thời gian thực của máy chủ. Hữu ích trong việc phân loại thời gian logs của máy chủ. II. Function stock GetDayOfWeek(year, month, day) { new a = (14 - month)/12, y = (year + 4800) - a; return ((day + ((153*(month + (12*a) - 3) + 2)/5) + (365*y) + (y/4) - (y/100) + (y/400) - 32045) % 7); } III. SỬ DỤNG CMD:test(playerid, params[]) { static const day_of_week_name[][] = {"Thu hai", "Thu ba", "Thu tu", "Thu nam", "Thu sau", "Thu bay", "Chu nhat"}; new year, mounth, day; getdate(year, mounth, day); print(day_of_week_name[GetDayOfWeek(year, mounth, day)]); return 1; }
  15. GetPlayerID Author: Anton Styazhkin I. GIỚI THIỆU CHUNG Hàm này cho phép bạn có thể xác định ID người chơi theo tên gọi của người chơi đó. Khi biên dịch (compiler), hàm sẽ tự động xác định phương pháp tìm kiếm ID người chơi phù hợp nhất với gamemode của bạn theo thứ tự ưu tiên về tốc độ xử lý như sau: sscanf foreach GetPlayerPoolSize Và tùy chọn cuối cùng là sử dụng MAX_PLAYERS. Và trả về với ID của người chơi và trả về INVALID_PLAYER_ID nếu như không tìm kiếm được người chơi trong máy chủ. II. Function stock GetPlayerID(const name[]) { #if defined sscanf new id; sscanf(name, "r", id); return id; #else new buff[MAX_PLAYER_NAME]; #if defined foreach foreach (new i:Player) { #elseif defined GetPlayerPoolSize for (new i = GetPlayerPoolSize(); i != -1; i--) { if(!IsPlayerConnected(i)) continue; #else for (new i = 0; i < MAX_PLAYERS; i++) { if(!IsPlayerConnected(i)) continue; #endif GetPlayerName(i, buff, sizeof buff); if (strcmp(buff, name, false) == 0) return i; } return INVALID_PLAYER_ID; #endif } III. SỬ DỤNG CMD:test(playerid, params[]) { new targetid = GetPlayerID(params); if(targetid != INVALID_PLAYER_ID) { new string[128]; format(string, sizeof(string), "Player %s co ID la %d", params, targetid); SendClientMessage(playerid, -1, string); } else SendClientMessage(playerid, -1, "Khong the xac dinh ID nguoi choi"); return 1; }
×
×
  • Create New...