// Developers notes /* # Coding guideline * Add +1 at the end of array declarations to visually account for null bit. * Always log first. * Always notify discord last. * Always wait for other queries to finish before initiating one: "sql_wait(sqlHandle); // Wait for other queries to finish" * Delete GVar strings as soon as possible, but at least before the player quits or the gamemode exits, as per: https://forum.sa-mp.com/showthread.php?t=151076 */ /* # Style guide * GLOBAL_CONSTANT * local_variable * someFunction * SomeClass * playerid // Variable storing in-game player ID * player_id // Variable storing database player ID */ /* # Adding a discord bot to your guild. https://discordapp.com/oauth2/authorize?client_id=%CLIENT_ID%&scope=bot&permissions=3072 Where %CLEINT_ID% is the client id. */ /* # To do: * Add geoiplib, to make connect messages fancier, and add a locate command. * Show underscores as spaces and convert spaces in changename input to undescores before regex chack. * Vip limit on characters. * See why sql_insert_id wont work. * Add all ADMIN_ECHO_CHANNEL messages to admin chat. (not the real admin chat) * Check if timestamps work (implemented on temp ban), before implementing it more. */ /// Global definitions // Colours /* #define COLOR_GREEN 0x33AA33AA #define COLOR_YELLOW 0xFFFF00AA #define COLOR_WHITE 0xFFFFFFAA #define COLOR_BLUE 0x0000BBAA #define COLOR_LIGHTBLUE 0x33CCFFAA #define COLOR_ORANGE 0xFF9900AA #define COLOR_RED 0xAA3333AA #define COLOR_LIME 0x10F441AA #define COLOR_NAVY 0x000080AA #define COLOR_AQUA 0xF0F8FFAA #define COLOR_CRIMSON 0xDC143CAA #define COLOR_BISQUE 0xFFE4C4AA #define COLOR_BLACK 0x000000AA #define COLOR_CHARTREUSE 0x7FFF00AA #define COLOR_BROWN 0XA52A2AAA #define COLOR_CORAL 0xFF7F50AA #define COLOR_GOLD 0xB8860BAA #define COLOR_GREENYELLOW 0xADFF2FAA #define COLOR_INDIGO 0x4B00B0AA #define COLOR_IVORY 0xFFFF82AA #define COLOR_LAWNGREEN 0x7CFC00AA #define COLOR_SEAGREEN 0x20B2AAAA #define COLOR_SEAGREEN 0x2E8B57AA #define COLOR_LIMEGREEN 0x32CD32AA //<--- Dark lime #define COLOR_MIDNIGHTBLUE 0X191970AA #define COLOR_MAROON 0x800000AA #define COLOR_OLIVE 0x808000AA #define COLOR_ORANGERED 0xFF4500AA #define COLOR_PINK 0xFFC0CBAA // - Light light pink #define COLOR_SPRINGGREEN 0x00FF7FAA #define COLOR_TOMATO 0xFF6347AA // - Tomato >:/ sounds wrong lol... well... :P #define COLOR_YELLOWGREEN 0x9ACD32AA //- like military green #define COLOR_MEDIUMAQUA 0x83BFBFAA #define COLOR_MEDIUMMAGENTA 0x8B008BAA // dark magenta ^^ */ #define COLOR_ACTIVEBORDER 0xB4B4B4FF #define COLOR_ACTIVECAPTION 0x99B4D1FF #define COLOR_ACTIVECAPTIONTEXT 0x000000FF #define COLOR_ALICEBLUE 0xF0F8FFFF #define COLOR_ANTIQUEWHITE 0xFAEBD7FF #define COLOR_APPWORKSPACE 0xABABABFF #define COLOR_AQUA 0x00FFFFFF #define COLOR_AQUAMARINE 0x7FFFD4FF #define COLOR_AZURE 0xF0FFFFFF #define COLOR_BEIGE 0xF5F5DCFF #define COLOR_BISQUE 0xFFE4C4FF #define COLOR_BLACK 0x000000FF #define COLOR_BLANCHEDALMOND 0xFFEBCDFF #define COLOR_BLUE 0x0000FFFF #define COLOR_BLUEVIOLET 0x8A2BE2FF #define COLOR_BROWN 0xA52A2AFF #define COLOR_BURLYWOOD 0xDEB887FF #define COLOR_BUTTONFACE 0xF0F0F0FF #define COLOR_BUTTONHIGHLIGHT 0xFFFFFFFF #define COLOR_BUTTONSHADOW 0xA0A0A0FF #define COLOR_CADETBLUE 0x5F9EA0FF #define COLOR_CHARTREUSE 0x7FFF00FF #define COLOR_CHOCOLATE 0xD2691EFF #define COLOR_CONTROL 0xF0F0F0FF #define COLOR_CONTROLDARK 0xA0A0A0FF #define COLOR_CONTROLDARKDARK 0x696969FF #define COLOR_CONTROLLIGHT 0xE3E3E3FF #define COLOR_CONTROLLIGHTLIGHT 0xFFFFFFFF #define COLOR_CONTROLTEXT 0x000000FF #define COLOR_CORAL 0xFF7F50FF #define COLOR_CORNFLOWERBLUE 0x6495EDFF #define COLOR_CORNSILK 0xFFF8DCFF #define COLOR_CRIMSON 0xDC143CFF #define COLOR_CYAN 0x00FFFFFF #define COLOR_DARKBLUE 0x00008BFF #define COLOR_DARKCYAN 0x008B8BFF #define COLOR_DARKGOLDENROD 0xB8860BFF #define COLOR_DARKGRAY 0xA9A9A9FF #define COLOR_DARKGREEN 0x006400FF #define COLOR_DARKKHAKI 0xBDB76BFF #define COLOR_DARKMAGENTA 0x8B008BFF #define COLOR_DARKOLIVEGREEN 0x556B2FFF #define COLOR_DARKORANGE 0xFF8C00FF #define COLOR_DARKORCHID 0x9932CCFF #define COLOR_DARKRED 0x8B0000FF #define COLOR_DARKSALMON 0xE9967AFF #define COLOR_DARKSEAGREEN 0x8FBC8BFF #define COLOR_DARKSLATEBLUE 0x483D8BFF #define COLOR_DARKSLATEGRAY 0x2F4F4FFF #define COLOR_DARKTURQUOISE 0x00CED1FF #define COLOR_DARKVIOLET 0x9400D3FF #define COLOR_DEEPPINK 0xFF1493FF #define COLOR_DEEPSKYBLUE 0x00BFFFFF #define COLOR_DEFAULT_CHAT 0xFFFFFFFF #define COLOR_DESKTOP 0x000000FF #define COLOR_DIMGRAY 0x696969FF #define COLOR_DODGERBLUE 0x1E90FFFF #define COLOR_FIREBRICK 0xB22222FF #define COLOR_FLBLUE 0x6495EDAA #define COLOR_FLORALWHITE 0xFFFAF0FF #define COLOR_FORESTGREEN 0x228B22FF #define COLOR_GAINSBORO 0xDCDCDCFF #define COLOR_GHOSTWHITE 0xF8F8FFFF #define COLOR_GOLD 0xFFD700FF #define COLOR_GOLDENROD 0xDAA520FF #define COLOR_GRADIENTACTIVECAPTION 0xB9D1EAFF #define COLOR_GRADIENTINACTIVECAPTION 0xD7E4F2FF #define COLOR_GRAY 0x808080FF #define COLOR_GRAYTEXT 0x808080FF #define COLOR_GREEN 0x008000FF #define COLOR_GREENYELLOW 0xADFF2FFF #define COLOR_GREY 0xAFAFAFAA #define COLOR_HIGHLIGHT 0x3399FFFF #define COLOR_HIGHLIGHTTEXT 0xFFFFFFFF #define COLOR_HONEYDEW 0xF0FFF0FF #define COLOR_HOTPINK 0xFF69B4FF #define COLOR_HOTTRACK 0x0066CCFF #define COLOR_INACTIVEBORDER 0xF4F7FCFF #define COLOR_INACTIVECAPTION 0xBFCDDBFF #define COLOR_INACTIVECAPTIONTEXT 0x434E54FF #define COLOR_INDIANRED 0xCD5C5CFF #define COLOR_INDIGO 0x4B0082FF #define COLOR_INFO 0xFFFFE1FF #define COLOR_INFOTEXT 0x000000FF #define COLOR_IVORY 0xFFFFF0FF #define COLOR_KHAKI 0xF0E68CFF #define COLOR_LAVENDER 0xE6E6FAFF #define COLOR_LAVENDERBLUSH 0xFFF0F5FF #define COLOR_LAWNGREEN 0x7CFC00FF #define COLOR_LEMONCHIFFON 0xFFFACDFF #define COLOR_LIGHTBLUE 0xADD8E6FF #define COLOR_LIGHTCORAL 0xF08080FF #define COLOR_LIGHTCYAN 0xE0FFFFFF #define COLOR_LIGHTGOLDENRODYELLOW 0xFAFAD2FF #define COLOR_LIGHTGRAY 0xD3D3D3FF #define COLOR_LIGHTGREEN 0x90EE90FF #define COLOR_LIGHTPINK 0xFFB6C1FF #define COLOR_LIGHTSALMON 0xFFA07AFF #define COLOR_LIGHTSEAGREEN 0x20B2AAFF #define COLOR_LIGHTSKYBLUE 0x87CEFAFF #define COLOR_LIGHTSLATEGRAY 0x778899FF #define COLOR_LIGHTSTEELBLUE 0xB0C4DEFF #define COLOR_LIGHTYELLOW 0xFFFFE0FF #define COLOR_LIME 0x00FF00FF #define COLOR_LIMEGREEN 0x32CD32FF #define COLOR_LINEN 0xFAF0E6FF #define COLOR_MAGENTA 0xFF00FFFF #define COLOR_MAROON 0x800000FF #define COLOR_MEDIUMAQUAMARINE 0x66CDAAFF #define COLOR_MEDIUMBLUE 0x0000CDFF #define COLOR_MEDIUMORCHID 0xBA55D3FF #define COLOR_MEDIUMPURPLE 0x9370DBFF #define COLOR_MEDIUMSEAGREEN 0x3CB371FF #define COLOR_MEDIUMSLATEBLUE 0x7B68EEFF #define COLOR_MEDIUMSPRINGGREEN 0x00FA9AFF #define COLOR_MEDIUMTURQUOISE 0x48D1CCFF #define COLOR_MEDIUMVIOLETRED 0xC71585FF #define COLOR_MENU 0xF0F0F0FF #define COLOR_MENUBAR 0xF0F0F0FF #define COLOR_MENUHIGHLIGHT 0x3399FFFF #define COLOR_MENUTEXT 0x000000FF #define COLOR_MIDNIGHTBLUE 0x191970FF #define COLOR_MINTCREAM 0xF5FFFAFF #define COLOR_MISTYROSE 0xFFE4E1FF #define COLOR_MOCCASIN 0xFFE4B5FF #define COLOR_NAVAJOWHITE 0xFFDEADFF #define COLOR_NAVY 0x000080FF #define COLOR_OLDLACE 0xFDF5E6FF #define COLOR_OLIVE 0x808000FF #define COLOR_OLIVEDRAB 0x6B8E23FF #define COLOR_ORANGE 0xFFA500FF #define COLOR_ORANGERED 0xFF4500FF #define COLOR_ORCHID 0xDA70D6FF #define COLOR_PALEGOLDENROD 0xEEE8AAFF #define COLOR_PALEGREEN 0x98FB98FF #define COLOR_PALETURQUOISE 0xAFEEEEFF #define COLOR_PALEVIOLETRED 0xDB7093FF #define COLOR_PAPAYAWHIP 0xFFEFD5FF #define COLOR_PEACHPUFF 0xFFDAB9FF #define COLOR_PERU 0xCD853FFF #define COLOR_PINK 0xFFC0CBFF #define COLOR_PLUM 0xDDA0DDFF #define COLOR_POWDERBLUE 0xB0E0E6FF #define COLOR_PURPLE 0x800080FF #define COLOR_RED 0xFF0000FF #define COLOR_ROSYBROWN 0xBC8F8FFF #define COLOR_ROYALBLUE 0x4169E1FF #define COLOR_SADDLEBROWN 0x8B4513FF #define COLOR_SALMON 0xFA8072FF #define COLOR_SANDYBROWN 0xF4A460FF #define COLOR_SCROLLBAR 0xC8C8C8FF #define COLOR_SEAGREEN 0x2E8B57FF #define COLOR_SEASHELL 0xFFF5EEFF #define COLOR_SIENNA 0xA0522DFF #define COLOR_SILVER 0xC0C0C0FF #define COLOR_SKYBLUE 0x87CEEBFF #define COLOR_SLATEBLUE 0x6A5ACDFF #define COLOR_SLATEGRAY 0x708090FF #define COLOR_SNOW 0xFFFAFAFF #define COLOR_SPRINGGREEN 0x00FF7FFF #define COLOR_STEELBLUE 0x4682B4FF #define COLOR_TAN 0xD2B48CFF #define COLOR_TEAL 0x008080FF #define COLOR_THISTLE 0xD8BFD8FF #define COLOR_TOMATO 0xFF6347FF #define COLOR_TRANSPARENT 0xFFFFFF00 #define COLOR_TURQUOISE 0x40E0D0FF #define COLOR_VIOLET 0xEE82EEFF #define COLOR_WHEAT 0xF5DEB3FF #define COLOR_WHITE 0xFFFFFFFF #define COLOR_WHITESMOKE 0xF5F5F5FF #define COLOR_WINDOW 0xFFFFFFFF #define COLOR_WINDOWFRAME 0x646464FF #define COLOR_WINDOWTEXT 0x000000FF #define COLOR_YELLOW 0xFFFF00FF #define COLOR_YELLOWGREEN 0x9ACD32FF #define COLOR_STEALTH_ORANGE 0xFF880000 #define COLOR_STEALTH_OLIVE 0x66660000 #define COLOR_STEALTH_GREEN 0x33DD1100 #define COLOR_STEALTH_PINK 0xFF22EE00 #define COLOR_STEALTH_BLUE 0x0077BB00 // Color groups #define COLOR_COMMAND_OUTPUT 0xFFFFFFFF // White #define COLOR_NOTICE 0xAFAFAFAA // Grey #define COLOR_WARNING_MESSAGE 0xFFA500FF // Orange? (Looks more yellow to me) #define COLOR_ERROR_MESSAGE 0xFF0000FF // Red #define COLOR_GLOBAL_CHAT 0xFFFFFFFF // White #define COLOR_PM_CHAT 0xFFFF00FF // Yellow #define COLOR_VIP_CHAT 0x800080FF // Purple #define COLOR_CREW_CHAT 0xFF9900AA // Orange #define COLOR_ADMIN_CHAT 0xB8860BAA // Gold // SQL datatypes #define MAX_SQL_INTEGER 10 #define MAX_SQL_TIMESTAMP 19 // 1999-01-08 04:05:06 [5 + 4 + 2 + 2 +2 + 2 + 2] #define MAX_SQL_FLOAT 16 #define MAX_SQL_HASH 128 #define MAX_SQL_IP 45 #define MAX_SQL_REASON 121 // Log levels #define LOGLEVEL_CHAT -2 #define LOGLEVEL_COMMAND -1 #define LOGLEVEL_DEBUG 0 #define LOGLEVEL_INFO 1 #define LOGLEVEL_NOTICE 2 #define LOGLEVEL_WARNING 3 #define LOGLEVEL_ERROR 4 #define LOGLEVEL_CRITICAL 5 #define LOGLEVEL_PANIC 6 // Userlevels enum{ // Noted values as comments, for database reference. UNREGISTERED_PLAYER, // 0 REGISTERED_PLAYER, // 1 REGULAR_PLAYER, // 2 Spent many hours in and around the comunity. VIP_PLAYER, // 3 Donated. MODERATOR_CREW, // 4 VETERAN_CREW, // 5 Inactive admins and management that are allowed to keep their rights. ADMIN_CREW, // 6 UNDERCOVER_ADMIN_CREW, // 7 MANAGEMENT_CREW, // 8 UNDRECOVER_MANAGEMENT_CREW, // 9 FOUNDER_PLAYER, // 10 UNDERCOVER_FOUNDER_PLAYER // 11 } // Discord channels enum{ ECHO_CHANNEL, MAIN_CHANNEL, ADMIN_ECHO_CHANNEL, ADMIN_CHANNEL, MANAGEMENT_CHANNEL } // Chats enum{ CHAT_LOCAL, // Has to be 0, else a gVar would have to be created OnJoin, this conserves memory when no chatmode is set (which will be the case most likely) CHAT_WHISPER, CHAT_LOW, CHAT_ACTION, CHAT_SHOUT, CHAT_OC, CHAT_GLOBAL, CHAT_CALL, CHAT_SMS, CHAT_RADIO, CHAT_PM, CHAT_GANG, CHAT_GANG_OC, CHAT_FACTION, CHAT_FACTION_OC, CHAT_VIP, CHAT_CREW, CHAT_ADMIN, CHAT_MANAGEMENT, CHAT_PARTYLINE } // Dialogs enum{ DIALOG_CHANGENAME, DIALOG_REGISTER, DIALOG_ACCOUNT_CREATED, DIALOG_LOGIN, DIALOG_CHANGE_USERNAME, DIALOG_CHARACTERS, DIALOG_LOGIN_FAILED, DIALOG_DELETE_CHARACTER } // Pickups enum{ PICKUP_PORTAL } // Environment settings THESE SHOULD ALL BE READ FROM A CONFIG FILE! static bool:scriptDebug = true; // Debug setting #define MODE_NAME "0.0a Build 6" #define SERVER_NAME "Bone County RPG" #define PG_HOST "127.0.0.1" #define PG_ROLE "rpfw-dev" #define PG_PASS "nJd&1k$0fs" #define PG_DB "rpfw-dev" #define PG_PORT 5432 #define DISCORD_HOME_GUILD_ID "666077037470941184" // Emerald City Roleplay #define DISCORD_ECHO_CHANNEL_ID "677855051166777344" // #bcrp-echo //#define DISCORD_ECHO_CHANNEL_ID "666078187079598080" // #ecrp-echo #define DISCORD_MAIN_CHANNEL_ID "677855315898793984" // #bcrp //#define DISCORD_MAIN_CHANNEL_ID "667396220402270228" // #development #define DISCORD_ADMIN_ECHO_CHANNEL_ID "672841892169383936" // #admin-echo #define DISCORD_ADMIN_CHANNEL_ID "667396026058932236" // #admins #define DISCORD_MANAGEMENT_CHANNEL_ID "666091376240361492" // #management // Game-mode limits #define MAX_CHARACTERS_PER_USER 20 // Maximum of MAX_CHARACTERS_PER_USER_DIGITS digits (Due to SQL query string length) #define MAX_CHARACTERS_PER_USER_DIGITS 3 // TODO user sizeof() and possibly move to OnGamemodeInit // SQL plugin new SQL:sqlHandle; // discord-connector //static DCC_Guild:homeGuild; // Discord guild controlled by game community. static DCC_Channel:echoChannel; // Public echo channel. static DCC_Channel:mainChannel; // Channel for communirty notifications. static DCC_Channel:adminEchoChannel; // Admin echo channel. static DCC_Channel:adminChannel; // Channel for admin notifications. static DCC_Channel:managementChannel; // Channel for management notifications. DiscordEcho(const message[], messageLevel){ // Write to echo channels. switch(messageLevel) // Output facilities. { case ECHO_CHANNEL: { // Public echo message DiscordSendChannelMessage(echoChannel, message); DiscordSendChannelMessage(adminEchoChannel, message); // Also send to admin echo channel, so admin can see everything in one channel without needing to constantly switch. Also handy as log on conflicts/complaints. } case MAIN_CHANNEL: {DiscordSendChannelMessage(mainChannel, message);} // Public notification case ADMIN_ECHO_CHANNEL: {DiscordSendChannelMessage(adminEchoChannel, message);} // Admin echo message case ADMIN_CHANNEL: {DiscordSendChannelMessage(adminChannel, message);} // Admin notification case MANAGEMENT_CHANNEL: {DiscordSendChannelMessage(managementChannel, message);} // Management notification } return 0; } // Natives native WP_Hash(buffer[], len, const str[]); // https://forum.sa-mp.com/showthread.php?t=570945 //native Float:loadavg(); // https://forum.sa-mp.com/showthread.php?t=260206 LINUX ONLY // Includes #include // https://sa-mp.com #include // https://github.com/udan11/samp-plugin-sql (Fastest player in town and only one supporting postgreSQL) | Examples: https://pastebin.com/67y2nq2n https://github.com/udan11/samp-plugin-sql/issues/10 #include // Newest version: https://github.com/maddinat0r/sscanf/releases | Better readme: https://github.com/Y-Less/sscanf #include // https://github.com/oscar-broman/strlib #include // https://github.com/urShadow/Pawn.Regex #include // https://github.com/urShadow/Pawn.CMD (Fastest player in town) #include // https://github.com/samp-incognito/samp-gvar-plugin #include // https://github.com/maddinat0r/samp-discord-connector (Only player in town) #include // https://github.com/samp-incognisto/samp-streamer-plugin /// Middle-ware // Logging logger(const log_level, const message[]){ // Write to logging facility // Do not log commands or debug when script debugging is turned off. if(scriptDebug == false){ // Debug is off if(log_level == LOGLEVEL_COMMAND || log_level == LOGLEVEL_DEBUG){ // Command or debug message return 0; // Stop and do not log } } // Messagelevel tag new human_readable_log_level[8 + 1]; switch(log_level){ // Assign log level. case LOGLEVEL_CHAT: human_readable_log_level = "chat"; case LOGLEVEL_COMMAND: human_readable_log_level = "command"; case LOGLEVEL_DEBUG: human_readable_log_level = "debug"; case LOGLEVEL_INFO: human_readable_log_level = "info"; case LOGLEVEL_NOTICE: human_readable_log_level = "notice"; case LOGLEVEL_WARNING: human_readable_log_level = "warning"; case LOGLEVEL_ERROR: human_readable_log_level = "error"; case LOGLEVEL_CRITICAL: human_readable_log_level = "critical"; case LOGLEVEL_PANIC: human_readable_log_level = "panic"; } printf("[%s] %s", human_readable_log_level, message); // Print to STDOUT. return 0; } /*// SQL plugin (BROKEN, look at it again after I have more then a week of experience) //forward sqlQuery(SQL:handle, query[]); //public sqlQuery(SQL:handle, query[]){ sqlQuery(SQL:handle, query[]){ //sql_wait(handle); // Wait for all queries to finish. new Result:result = sql_query(handle, query); if(sql_error(result)){printf("SQL error");} // Did not work during a test with a faulty statement. return result; }*/ // discord-connector DiscordSendChannelMessage(DCC_Channel:channel, const message[]){ if(scriptDebug){ // Log middle-ware event in debugging mode only new channel_name[100 + 1]; // Default value from tutorial DCC_GetChannelName(channel, channel_name); new logMessage[26 + 100 + 2000 + 1]; // Discord max message length = 2000 format(logMessage, sizeof(logMessage), "Send to discord channel %s: %s", channel_name, message); logger(LOGLEVEL_DEBUG, logMessage); // Actually log the message } DCC_SendChannelMessage(channel, message); // Call discord-connector DISABLE THIS TO STOP ALL OUTGOING DISCORD MESSAGES } /// Game-mode // Account functions forward changeName(playerid, const name[]); public changeName(playerid, const name[]){ // Check if name is valid and force change if needed. // Prevent regex error when comparing against null if(isempty(name)){ // No name entered ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "You must enter a name.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", ""); // Force RP name. return 0; } // Check name new Regex:r = Regex_New("^[A-Z][a-z]{1,}([A-Z][a-z]{1,})?(_([A-Z][a-z]{1,}([A-Z][a-z]{1,})?|[A-Z]\\.(_[A-Z]\\.)?))?_[A-Z][a-z]{1,}([A-Z][a-z]{1,})?$"); // Regex name filter new isValidName = Regex_Check(name, r); // Validate name to filter Regex_Delete(r); if(!isValidName){ // Invalid role-play name ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Please pick a realistic name, separate first and last name with an underscore.\n\nExamples:\n Jo_Bo\n Dingle_P._J._Berry\n Jackson_DeForest_Kelley\n MaryJo_Ann_LaFluer", "Change", ""); // Force RP name. } else { // Valid role-play name // Check for name in use /*new escaped_name[MAX_PLAYER_NAME + 1]; // TODO should be more, to account for escape characters. sql_escape_string(sqlHandle, name, escaped_name, sizeof(escaped_name)); // Escape player name BROKEN argument type mismatch on argument 2, but name is a sting...*/ new character_query[40 + MAX_PLAYER_NAME + 1], Result:character_result = sql_query(sqlHandle, character_query); format(character_query, sizeof(character_query), "SELECT id FROM character WHERE name = '%s'", name); if(sql_num_rows(character_result) > 0){ // Name taken ShowPlayerDialog(playerid, DIALOG_CHANGENAME, DIALOG_STYLE_INPUT, "Character name", "Name already taken. Please pick an original name.", "Change", ""); // Force RP name. } else{ // Name free // Notify all players new message[11 + 4 + MAX_PLAYER_NAME + 1]; format(message, sizeof(message), "* [%i] %s joined.", playerid, fromPlayerName(name)); SendClientMessageToAll(COLOR_NOTICE, message); // Notify all players DiscordEcho(message, ECHO_CHANNEL); // Notify discord public echo new client_connect_username[MAX_PLAYER_NAME + 1], admin_message[38 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1]; GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid); // Get client connect name if(GetPlayerState(playerid) == PLAYER_STATE_NONE){ // Not spawned: character creation (Creating a character without registering) // Notify admins format(admin_message, sizeof(admin_message), "* [%i] %s temporary charcter, created by: %s", playerid, fromPlayerName(name), client_connect_username); // TODO inform in-game adminchat DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL); // Do nothing let continue to spawn, after spawn character will be saved to database. } else{ // Spawnedplayer: Creating permanent or renaming an existing permanent character // Notify admins format(admin_message, sizeof(admin_message), "* [%i] %s created by: %s", playerid, fromPlayerName(name), client_connect_username); // TODO inform in-game adminchat DiscordEcho(admin_message, ADMIN_ECHO_CHANNEL); // Send to skin selection ForceClassSelection(playerid); TogglePlayerSpectating(playerid, true); TogglePlayerSpectating(playerid, false); } // Change name (No way yet to change name ingame) /*if(GetGVarInt("userlevel", playerid) > 1){ // Registered player. // Get username new username[MAX_PLAYER_NAME + 1], message[75 + MAX_PLAYER_NAME + 1]; GetGVarString("username", username, sizeof(username), playerid); // Update or create character name in database new callback[1]; new Result:result; new query[51 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 1]; format(query, sizeof(query), "UPDATE \"user\"(name) VALUES('%s') WHERE username == '%s'", name, username); sql_wait(sqlHandle); // Wait for other queries to finish result = sql_query(sqlHandle, query, callback = "", "r"); // Inform user format(message, sizeof(message), "SERVER: Your username remains unchanged, next time connect as: %s", client_connect_username); SendClientMessage(playerid, COLOR_WHITE, message); }*/ SetPlayerName(playerid, name); // Change name in-game } } return 0; } forward register(playerid); public register(playerid){ // Register player in database if(GetPlayerState(playerid) == PLAYER_STATE_NONE){ // Not spawned SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "ERROR: You need to have spawned to register."); } else{ // Spawned player if(GetGVarInt("userlevel", playerid) != UNREGISTERED_PLAYER){ // Registered player. new message[35 + MAX_PLAYER_NAME + 1]; format(message, sizeof(message), "ERROR: You are already registered"); //, %s.", SendClientMessage(playerid, COLOR_WHITE, message); } else{ // Unregistered player. ShowPlayerDialog(playerid, DIALOG_REGISTER, DIALOG_STYLE_PASSWORD, "Password", "Please pick a strong and safe password, that you are able to remember.", "Continue", "Cancel"); // Password prompt } } } forward createCharacterRecord(playerid, id); public createCharacterRecord(playerid, id){ new character_query[94 + MAX_SQL_INTEGER + MAX_PLAYER_NAME + 3 + 1]; format(character_query, sizeof(character_query), "INSERT INTO character(user_id, name, skin_id) VALUES(%i, '%s', %i)", id, getPlayerName(playerid), GetPlayerSkin(playerid)); sql_query(sqlHandle, character_query); } forward authenticate(playerid); public authenticate(playerid){ ShowPlayerDialog(playerid, DIALOG_LOGIN, DIALOG_STYLE_PASSWORD, "Sign in", "Enter your password to log on", "Log in", "Cancel"); // Show password confirmation dialog } forward characterSelection(playerid); public characterSelection(playerid){ if(GetGVarInt("userlevel", playerid) < REGISTERED_PLAYER){ // Unregistered player (Or worse?) SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Register first with: /my account register"); } else{ // Registered player print("Character selection registered player."); // If spawned, save current character if(GetPlayerState(playerid) != PLAYER_STATE_NONE){ savePlayerState(playerid); } new id = getUserID(playerid); new character_query[75 + MAX_SQL_INTEGER + MAX_CHARACTERS_PER_USER_DIGITS + 1]; format(character_query, sizeof(character_query), "SELECT id, name FROM character WHERE user_id = %i LIMIT %i", id, MAX_CHARACTERS_PER_USER); new Result:character_result = sql_query(sqlHandle, character_query); new character_string[1024 + 1], character_array[MAX_CHARACTERS_PER_USER]; for(new i = 0; i < sql_num_rows(character_result); i++){ new name[MAX_PLAYER_NAME + 1]; sql_get_field_assoc_ex(character_result, i, "name", name, sizeof(name)); format(character_string, sizeof(character_string), "%s%s\n", character_string, name); character_array[i] = sql_get_field_assoc_int_ex(character_result, i, "id"); } print(character_string); new character_array_string[MAX_CHARACTERS_PER_USER * MAX_SQL_INTEGER + 1]; strfrombin(character_array_string, character_array); // Convert array to string for use with GVar SetGVarString("character_array_string", character_array_string, playerid); print("About to show dialog"); ShowPlayerDialog(playerid, DIALOG_CHARACTERS, DIALOG_STYLE_LIST, "Characters", character_string, "Spawn", "New"); } } forward savePlayerState(playerid); public savePlayerState(playerid){ new escaped_playername[MAX_PLAYER_NAME + 1], character_query[183 + MAX_SQL_INTEGER + 3 + 3 + 1 + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_SQL_FLOAT + MAX_PLAYER_NAME + 1]; // Should be longer to allow for escaped characters sql_escape_string(sqlHandle, getPlayerName(playerid), escaped_playername, sizeof(escaped_playername)); // Escape player name new Float:health, Float:armour, Float:x, Float:y, Float:z, Float:Angle; // Unrealiable TODO change to gvars GetPlayerHealth(playerid, health); GetPlayerArmour(playerid, armour); GetPlayerPos(playerid, x, y, z); GetPlayerFacingAngle(playerid, Angle); format(character_query, sizeof(character_query), "UPDATE character SET (cash, health, armour, jailed, pos_x, pos_y, pos_z, rotation) = (%i, '%f', '%f', %i, '%f', '%f', '%f', '%f') WHERE name = '%s'", GetPlayerMoney(playerid), health, armour, 0, x, y, z, Angle, escaped_playername); sql_query(sqlHandle, character_query); } forward kickPlayerDelay(playerid); public kickPlayerDelay(playerid){ Kick(playerid); } forward kickPlayer(playerid, kickerid, const reason[], banned); public kickPlayer(playerid, kickerid, const reason[], banned){ // Kick a player // Issuer of kick new kickername[MAX_PLAYER_NAME + 1], kicker_id[MAX_SQL_INTEGER + 1]; if(kickerid == -1){ // Not kicked by in-game player kickername = "SERVER"; kicker_id = "NULL"; } else{ // Kicked by in-game player kickername = getPlayerName(playerid); kicker_id = "%s", getUserID(kickerid); } strreplace(kickername, "_", " "); // Action new action[16 + MAX_SQL_INTEGER + 1]; if(banned){ if(banned < 1){ // Permanent ban action = "banned permanently"; } else{ // Temporary ban format(action, sizeof(action), "banned for %i days", banned); } } else{ action = "kicked"; } // Notify all players new playername[MAX_PLAYER_NAME + 1], message[16 + 4 + MAX_PLAYER_NAME + MAX_PLAYER_NAME + 16 + MAX_SQL_REASON + 1], adminMessage[23 + 4 + MAX_PLAYER_NAME + 16 + MAX_PLAYER_NAME + MAX_SQL_REASON + 1]; // Max samp chat message length 128 - 8 for "/kick ? ". GetPlayerName(playerid, playername, sizeof(playername)); format(message, sizeof(message), "* [%i] %s %s, reason: %s", playerid, fromPlayerName(playername), action, reason); // TODO get player name form player id format(adminMessage, sizeof(adminMessage), "* [%i] %s kicked %s, reason: %s", kickerid, kickername, fromPlayerName(playername), action, reason); logger(LOGLEVEL_INFO, message); // Log event SendClientMessageToAll(COLOR_NOTICE, message); // Notify all players. DiscordEcho(message, ECHO_CHANNEL); //TODO notify all in-game admins DiscordEcho(adminMessage, ADMIN_ECHO_CHANNEL); // Inform player (Redundant, but some people are blind or stupid) new player_message[30 + 6 + MAX_PLAYER_NAME + MAX_SQL_REASON + 1]; // Max samp chat message length 128 - 8 for "/kick ? ". format(player_message, sizeof(player_message), "SERVER: You have been %s, reason: %s", action, reason); SendClientMessage(playerid, COLOR_ERROR_MESSAGE, player_message); // Notify player if(banned){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "You may appeal this ban via the forum or Discord."); } new ip_id = getIPID(playerid); // Crearte IP kick record new ip_query[61 + 4 + MAX_SQL_REASON + 4 + 1]; format(ip_query, sizeof(ip_query), "INSERT INTO ip_kick(ip_id, reason, kicker_id) VALUES(%i, '%s', %s)", ip_id, reason, kicker_id); sql_query(sqlHandle, ip_query); // Get IP kick record new ip_id_query[64 + 4 + MAX_SQL_REASON + 4 + 1]; format(ip_id_query, sizeof(ip_id_query), "SELECT id FROM ip_kick WHERE ip_id = %i ORDER BY id DESC LIMIT 1", ip_id); new Result:ip_id_result = sql_query(sqlHandle, ip_id_query); new ip_kick_id = sql_get_field_assoc_int(Result:ip_id_result, "id"); // User kick record printf("Userlevel: %i", GetGVarInt("userlevel", playerid)); if(GetGVarInt("userlevel", playerid) > UNREGISTERED_PLAYER){ // Not logged in to a user account new user_query[79 + 4 + MAX_SQL_REASON + 4 + 1]; format(user_query, sizeof(user_query), "INSERT INTO user_kick(user_id, reason, kicker_id, ip_kick_id) VALUES(%i, '%s', %s, %i)", getUserID(playerid), reason, kicker_id, ip_kick_id); sql_query(sqlHandle, user_query); } SetTimerEx("kickPlayerDelay", 50, false, "i", playerid); // Give the message 50 milliseconds to reach the player before kicking. } banExpiration(days); banExpiration(days){ if(days < 1){ // Permanent ban new foo[MAX_SQL_TIMESTAMP + 1] = "0"; return foo; } else{ // Temporary ban new year, month, day, hour, minute, second, expires[MAX_SQL_TIMESTAMP + 1]; getdate(year, month, day); gettime(hour, minute, second); day = day + days; format(expires, sizeof(expires), "%i-%i-%i %i:%i:%i", year, month, day, hour, minute, second); return expires; //return year, month, day, hour, minute, second; //return 0; } } forward banPlayer(playerid, bannerid, const reason[], days); public banPlayer(playerid, bannerid, const reason[], days){ // Ban & kick a player new ip_id = getIPID(playerid); // Create IP ban record new banner_id, ip_query[72 + MAX_SQL_INTEGER + MAX_SQL_TIMESTAMP + MAX_SQL_REASON + MAX_SQL_INTEGER + 1]; if(bannerid < 0){ // Not banned by in-game player format(ip_query, sizeof(ip_query), "INSERT INTO ip_ban(ip_id, expires, reason) VALUES(%i, '%s', '%s')", ip_id, banExpiration(days), reason); } else{ // Banned by in-game player banner_id = getUserID(bannerid); format(ip_query, sizeof(ip_query), "INSERT INTO ip_ban(ip_id, expires, reason, banner_id) VALUES(%i, '%s', '%s', %i)", ip_id, banExpiration(days), reason, banner_id); } sql_query(sqlHandle, ip_query); // Get IP ban record ID new ban_id_query[61 + MAX_PLAYER_NAME + 1]; format(ban_id_query, sizeof(ban_id_query), "SELECT id FROM ip_ban WHERE ip_id = %i ORDER BY id DESC LIMIT 1", ip_id); new Result:ban_id_result = sql_query(sqlHandle, ban_id_query); new ip_ban_id = sql_get_field_assoc_int(ban_id_result, "id"); // User ban record (As banned players get only kicked, noneed to check for active ban) if(GetGVarInt("userlevel", playerid) > 1){ // Registered player new user_query[97 + MAX_SQL_INTEGER + MAX_SQL_TIMESTAMP + MAX_SQL_REASON + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1]; format(user_query, sizeof(user_query), "INSERT INTO user_ban(user_id, expires, reason, banner_id, ip_ban_id) VALUES(%i, %s, %s, %i, %i) WHERE id = %i", ip_id, banExpiration(days), reason, banner_id, ip_ban_id); sql_query(sqlHandle, user_query); } // Kick player kickPlayer(playerid, bannerid, reason, days); } // Database functions forward getIPID(playerid); public getIPID(playerid){ new player_ip[MAX_SQL_IP + 1], query[37 + MAX_SQL_IP + 1]; // Create varaibles GetPlayerIp(playerid, player_ip, sizeof(player_ip)); // Polulate player_ip variable format(query, sizeof(query), "SELECT id FROM ip WHERE address = '%s'", player_ip); // Format query string new Result:result = sql_query(sqlHandle, query); // Execute query return sql_get_field_assoc_int(result, "id"); // Get id from result } forward getUserID(playerid); public getUserID(playerid){ // SQL escape username new client_connect_username[MAX_PLAYER_NAME + 1], escaped_username[MAX_PLAYER_NAME + 1]; // TODO escaped_username should be longer to account for escape characters (Also increate database column size!) GetGVarString("client_connect_username", client_connect_username, sizeof(client_connect_username), playerid); // Get client connect name sql_escape_string(sqlHandle, client_connect_username, escaped_username, sizeof(escaped_username)); // Escape player name // Get user ID new userid_query[47 + MAX_PLAYER_NAME + 1]; format(userid_query, sizeof(userid_query), "SELECT id FROM \"user\" WHERE name = '%s'", escaped_username); new Result:result = sql_query(sqlHandle, userid_query); return sql_get_field_assoc_int(result, "id"); } // Player functions forward deleteAllGVars(playerid); public deleteAllGVars(playerid){ // Delete GVars as per https://forum.sa-mp.com/showthread.php?t=151076 DeleteGVar("client_connect_username", playerid); // From OnPlayerConnect() DeleteGVar("userlevel", playerid); // From OnPlayerConnect() DeleteGVar("hash", playerid); // From DIALOG_REGISTER DeleteGVar("character_array_string", playerid); // From OnDialogResponse() DeleteGVar("authentication_count", playerid); // From DIALOG_LOGIN DeleteGVar("chatmode", playerid); // From cmd:my DeleteGVar("disable_portals", playerid); // From OnPlayerPickUpDynamicPickup() } getPlayerName(playerid); getPlayerName(playerid){ // new playername[MAX_PLAYER_NAME + 1]; new const playername[MAX_PLAYER_NAME + 1]; GetPlayerName(playerid, playername, sizeof(playername)); // new output[MAX_PLAYER_NAME + 1]; // strfromliteral(output, playername); return playername; } // Chat functions fromPlayerName(const name[]); fromPlayerName(const name[]){ // Convert name from player name new output[MAX_PLAYER_NAME + 1]; strfromliteral(output, name); strreplace(output, "_", " "); return output; } forward sendToChat(playerid, target, text[], receiver_id); public sendToChat(playerid, target, text[], receiver_id){ strtrim(text); // Trim edge whitespaces if(isempty(text)){ return 0; // Fail if text is empty } new playername[MAX_PLAYER_NAME + 1]; playername = getPlayerName(playerid); new chat_format, chat_color, chat_range, chat_userlevel, chat_character[1 + 1], chat_name[13 + 1], discord_channel; switch(target){ case CHAT_WHISPER:{ chat_format = 2; chat_range = 3; chat_name = "whispers"; chat_color = COLOR_COMMAND_OUTPUT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_LOW:{ chat_format = 2; chat_range = 10; chat_name = "speaks softly"; chat_color = COLOR_COMMAND_OUTPUT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_LOCAL:{ chat_format = 1; chat_range = 20; chat_color = COLOR_COMMAND_OUTPUT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_SHOUT:{ chat_format = 2; chat_range = 50; chat_name = "shouts"; chat_color = COLOR_COMMAND_OUTPUT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_ACTION:{ chat_format = 0; chat_range = 20; chat_color = COLOR_COMMAND_OUTPUT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_OC:{ chat_format = 3; chat_range = 20; chat_character = "'"; chat_name = "OOC"; chat_color = COLOR_COMMAND_OUTPUT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_GLOBAL:{ chat_format = 3; chat_character = "`"; chat_name = "Global"; chat_color = COLOR_COMMAND_OUTPUT; discord_channel = ECHO_CHANNEL; } case CHAT_GANG:{ chat_format = 3; chat_character = "~"; chat_name = "Gang"; chat_color = COLOR_CREW_CHAT; // TODO: Output to gang Discord channel discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_GANG_OC:{ chat_format = 3; chat_character = "$"; chat_name = "Gang OC"; chat_color = COLOR_CREW_CHAT; // TODO: Output to gang Discord channel discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_FACTION:{ chat_format = 3; chat_character = "!"; chat_name = "Faction"; chat_color = COLOR_CREW_CHAT; // TODO: Output to faction Discord channel discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_FACTION_OC:{ chat_format = 3; chat_character = "%"; chat_name = "Faction OC"; chat_color = COLOR_CREW_CHAT; // TODO: Output to faction Discord channel discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_CREW:{ chat_format = 3; chat_character = "@"; chat_name = "Crew"; chat_color = COLOR_CREW_CHAT; chat_userlevel = MODERATOR_CREW; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_ADMIN:{ chat_format = 3; chat_character = "#"; chat_name = "Admin"; chat_color = COLOR_ADMIN_CHAT; chat_userlevel = ADMIN_CREW; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_MANAGEMENT:{ chat_format = 3; chat_character = "&"; chat_name = "Management"; chat_color = COLOR_ADMIN_CHAT; chat_userlevel = MANAGEMENT_CREW; discord_channel = MANAGEMENT_CHANNEL; } case CHAT_VIP:{ chat_format = 3; chat_character = "^"; chat_name = "VIP"; chat_color = COLOR_VIP_CHAT; chat_userlevel = VIP_PLAYER; discord_channel = ADMIN_ECHO_CHANNEL; //TODO: Add VIP discord channel. } case CHAT_PARTYLINE:{ chat_format = -1; chat_character = "*"; chat_color = COLOR_COMMAND_OUTPUT; chat_userlevel = ADMIN_CREW; discord_channel = ECHO_CHANNEL; } case CHAT_PM:{ chat_format = 3; chat_character = ">"; chat_name = "PM"; chat_color = COLOR_PM_CHAT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_CALL:{ chat_format = 3; chat_character = "+"; chat_name = "Call"; chat_color = COLOR_PM_CHAT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_SMS:{ chat_format = 3; chat_character = "-"; chat_name = "SMS"; chat_color = COLOR_PM_CHAT; discord_channel = ADMIN_ECHO_CHANNEL; } case CHAT_RADIO:{ chat_format = 3; chat_character = "="; chat_name = "Radio"; chat_color = COLOR_PM_CHAT; discord_channel = ADMIN_ECHO_CHANNEL; } } // Format chat message new message[9 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 128 + 1]; switch(chat_format){ case -1: format(message, sizeof(message), "%s %s", chat_character, text); case 0: format(message, sizeof(message), "[%i] %s %s", playerid, fromPlayerName(playername), text); case 1: format(message, sizeof(message), "[%i] %s: %s", playerid, fromPlayerName(playername), text); case 2: format(message, sizeof(message), "[%i] %s %s: %s", playerid, fromPlayerName(playername), chat_name, text); case 3: format(message, sizeof(message), "%s (%s) [%i] %s: %s", chat_character, chat_name, playerid, fromPlayerName(playername), text); } // Authorisation if(chat_userlevel && chat_userlevel > GetGVarInt("userlevel", playerid)){ // User not privilged to read chat if(target == CHAT_CREW){ // Show the user the message was sent to crew chat. SendClientMessage(playerid, chat_color, message); } else{ SendClientMessage(playerid, COLOR_CREW_CHAT, "ERROR: You are not authorized to speak in this chat."); return 0; // Fail to send the message } } // Send TODO: Add checks for muted. if(chat_range){ // Ranged chats logger(LOGLEVEL_CHAT, message); // Log event // Show message to players in range new Float:x, Float:y, Float:z; GetPlayerPos(playerid, x, y, z); for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){ if(IsPlayerConnected(receiver_id)){ new Float:distance = GetPlayerDistanceFromPoint(recipient_id, x, y, z); if(distance <= chat_range){ // Player nearby SendClientMessage(recipient_id, chat_color, message); } } } DiscordEcho(message, discord_channel); } else{ // Global chats if(target == CHAT_GLOBAL){ // Public chat logger(LOGLEVEL_CHAT, message); // Log event SendClientMessageToAll(chat_color, message); DiscordEcho(message, discord_channel); } else if(target == CHAT_GANG || target == CHAT_GANG_OC || target == CHAT_FACTION || target == CHAT_FACTION_OC || target == CHAT_CALL || target == CHAT_SMS || target == CHAT_RADIO ){ // TODO // TODO sendToAdmins(); // TODO send to specific discord channel SendClientMessage(playerid, chat_color, "You are not a member of the required entity."); } else if(target == CHAT_PM){ new output[sizeof(message)]; strdel(message, 0, 1); format(output, sizeof(output), "<%s", message); SendClientMessage(receiver_id, chat_color, output); new receipt[12 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 128 + 1]; format(receipt, sizeof(receipt), "%s (%s) to [%i] %s: %s", chat_character, chat_name, receiver_id, fromPlayerName(playername), text); SendClientMessage(playerid, chat_color, receipt); new admin_message[15 + 1 + sizeof(chat_name) + 4 + MAX_PLAYER_NAME + 4 + 128 + 1]; format(admin_message, sizeof(admin_message), "%s (%s) [%i] to %s [%i]: %s", chat_character, chat_name, playerid, fromPlayerName(playername), receiver_id, text); logger(LOGLEVEL_CHAT, admin_message); // Log event sendToAdmins(chat_color, admin_message); DiscordEcho(message, discord_channel); } else{ // Chat with select users for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){ if(IsPlayerConnected(recipient_id)){ if(GetGVarInt("userlevel", recipient_id) >= chat_userlevel){ // Privileged logger(LOGLEVEL_CHAT, message); // Log event SendClientMessage(recipient_id, chat_color, message); DiscordEcho(message, discord_channel); } } } } } return 1; } forward sendToAdmins(chat_color, const message[]); public sendToAdmins(chat_color, const message[]){ for(new recipient_id, a = GetMaxPlayers(); recipient_id < a; recipient_id++){ if(IsPlayerConnected(recipient_id)){ if(GetGVarInt("userlevel", recipient_id) >= ADMIN_CREW){ // Privileged SendClientMessage(recipient_id, chat_color, message); } } } } forward sendPM(playerid, text[], recipient_id); public sendPM(playerid, text[], recipient_id){ if (recipient_id == INVALID_PLAYER_ID){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "ERROR: Player not found."); } else{ sendToChat(playerid, CHAT_PM, text, recipient_id); } } // Pickup functions forward createDynamicPickup(object, type, Float:x, Float:y, Float:z, world, interior); public createDynamicPickup(object, type, Float:x, Float:y, Float:z, world, interior){ if(object == 19902){ z = z - 0.5; } else if(object == 19607){ z = z - 0.5; } return CreateDynamicPickup(object, type, x, y, z, world, interior); } forward spawnPortal(id, object, Float:pos_x, Float:pos_y, Float:pos_z, world, interior, exit_object, Float:exit_pos_x, Float:exit_pos_y, Float:exit_pos_z, exit_world, exit_interior); public spawnPortal(id, object, Float:pos_x, Float:pos_y, Float:pos_z, world, interior, exit_object, Float:exit_pos_x, Float:exit_pos_y, Float:exit_pos_z, exit_world, exit_interior){ new pickup_id = createDynamicPickup(object, 1, pos_x, pos_y, pos_z, world, interior); SetGVarInt("pickup_type", PICKUP_PORTAL, pickup_id); SetGVarFloat("pickup_x", exit_pos_x, pickup_id); SetGVarFloat("pickup_y", exit_pos_y, pickup_id); SetGVarFloat("pickup_z", exit_pos_z, pickup_id); SetGVarInt("pickup_world", exit_world, pickup_id); SetGVarInt("pickup_interior", exit_interior, pickup_id); new exit_pickup_id = createDynamicPickup(exit_object, 1, exit_pos_x, exit_pos_y, exit_pos_z, exit_world, exit_interior); SetGVarInt("pickup_type", PICKUP_PORTAL, exit_pickup_id); SetGVarFloat("pickup_x", pos_x, exit_pickup_id); SetGVarFloat("pickup_y", pos_y, exit_pickup_id); SetGVarFloat("pickup_z", pos_z, exit_pickup_id); SetGVarInt("pickup_world", world, exit_pickup_id); SetGVarInt("pickup_interior", interior, exit_pickup_id); new portal_query[64 + MAX_SQL_INTEGER + MAX_SQL_INTEGER + MAX_SQL_INTEGER + 1]; format(portal_query, sizeof(portal_query), "UPDATE portal SET (pickup_id, exit_pickup_id) = (%i, %i) WHERE id = %i", pickup_id, exit_pickup_id, id); sql_query(sqlHandle, portal_query); } forward destroyPortal(id); public destroyPortal(id){ DestroyDynamicPickup(id); DeleteGVar("pickup_type", id); DeleteGVar("pickup_x", id); DeleteGVar("pickup_y", id); DeleteGVar("pickup_z", id); DeleteGVar("pickup_world", id); DeleteGVar("pickup_interior", id); } // Commands public OnPlayerCommandPerformed(playerid, cmd[], params[], result, flags){ if(result == -1){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Unknown command."); return 0; } return 1; } public OnPlayerCommandReceived(playerid, cmd[], params[], flags){ new userlevel = GetGVarInt("userlevel", playerid); if(flags){ printf("Flags: %i", flags); if(flags > userlevel){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Permission denied."); printf("player %d doesn’t have access to command '%s'", playerid, cmd); // TODO decide to send this to a chat or not. return 0; } } //if (!(flags & userlevel)){} // From sa-mp return 1; } public PC_OnInit(){ // TODO return 1; // Remove this once stuff is in place. } flags:test(ADMIN_CREW); cmd:test(playerid, params[]){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Executed"); return 1; } // Command commands cmd:help(playerid, params[]){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "SERVER: Ask questions in the chat, on Discord or the forum.\nTo list all commands: /cmds"); return 1; } alias:help("h"); cmd:cmds(playerid, params[]){ SendClientMessage(playerid, COLOR_NOTICE, "Command help syntax: \"/\" = start of cmd, \"()\" = cmd aliasses, \"|\" = or, \"<>\" = required parameter, \"[]\" = optional parameter, \",\" = next item."); SendClientMessage(playerid, COLOR_NOTICE, "Command help syntax example: /command (/alias | /other_alias) [optional_parameter], /next_command"); SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "General commands: /help (/h), /cmds (/cmd | /commands), /my (/myself | /mine), /v (/vehivle | /veh), /p (/player)"); SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Chat commands: /me (/emote | /action), /w (/whisper), /low (/soft), /l (/local), /s (/shout | /scream), /o (' | /oc | /ooc)"); SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Chat commands: /g (` | /global | /public), /gc (~) /fc (!), /vc (&), /cc (@), /my chatmode "); if(GetGVarInt("userlevel", playerid) >= MANAGEMENT_CREW){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Management commands: /property"); } return 0; } alias:cmds("commands", "cmd"); // User commands cmd:register(playerid, params[]){ register(playerid); } cmd:my(playerid, params[]){ // No option specified if(strlen(params) < 1){ SendClientMessage(playerid, COLOR_COMMAND_OUTPUT, "Usage: /my