RSSy MYGO.pl Opublikowano Grudzień 13, 2020 o 10:15 RSSy Udostępnij Opublikowano Grudzień 13, 2020 o 10:15 Quote: #define SQL #define STATISTICS // don't change this! #if defined STATISTICS && !defined SQL #define VAULT #endif // includes #include <amxmodx> #include <amxmisc> #include <sockets> #if defined VAULT #include <nvault> #endif #if defined SQL #include <sqlx> #endif // plugin defines #define PLUGIN_NAME "xREDIRECT" #define PLUGIN_VERSION "2.0RC2" #define PLUGIN_AUTHOR "x0R" #define PLUGIN_TAG "[Evilnet.Lv - Server]" // SQL defines #define SQL_PREFIX "xredirect_" #define SQL_TABLENAME_SERVERS "servers" #define SQL_TABLENAME_ATTRIBUTES "attributes" #define SQL_TABLENAME_STATISTICS "statistics" // Vault defines #define VAULT_NAME "xREDIRECT" // maximum values - don't change this if you don't know what you are doing! #define MAX_FILE_LEN 256 // maximum length of file names #define MAX_SERVERLINE_LEN 256 // maximum length of a line read from SERVERFILE #define MAX_SERVERNAME_LEN 50 // maximum length of a server name read from SERVERFILE #define MAX_SERVERADDRESS_LEN 100 // maximum length of a server address read from SERVERFILE #define MAX_NAME_LEN 33 // maximum length of a player name #define MAX_MENUBODY_LEN 512 // maximum length of a menu body #define MAX_WELCOME_LEN 1024 // maximum length of the welcome message #define MAX_INFO_LEN 1400 // maximum length of info reply - when longer than that the packet is fragmented (software side, not due to MTU) #define MAX_INFO_FORMAT 100 // maximum length of a format string for an info reply #define MAX_MAP_LEN 30 // maximum length of map names #define MAX_IP_LEN 16 // maximum length of IP addresses #define MAX_ID_LEN 35 // maximum length of a string containing a WON or Steam ID #define MAX_PORT_LEN 6 // maximum length of port numbers (as strings of course) #define MAX_ATTRIB_LEN 20 // maximum length of an attribute name in SERVERFILE or SQL_TABLENAME_ATTRIBUTES #define MAX_PASSWORD_LEN 15 // maximum length of a password in SERVERFILE #define MAX_VALUE_LEN 100 // maximum length of an attribute value in SERVERFILE or SQL_TABLENAME_ATTRIBUTES #define MAX_PLAYERS 32 // maximum number of players on the server #define MAX_VAULT_KEY_LEN 10 // maximum length a vault key can have (STATS_VAULT_TAG + ID number from a STATS_COUNT_ constant) #define MAX_CATEGORIES 9 // maximum number of categories in the menu #define MAX_SQL_TABLE_LEN 30 // maximum length of an SQL table name #define MAX_SQL_ERROR_LEN 128 // maximum length of an SQL error message // statistic count constants #define STATS_VAULT_TAG "vault" // the tag prepended to the vault keys - remember to also change MAX_VAULT_KEY_LEN when you change this enum eStatsCount { STATS_COUNT_FOLLOW=0, // count that the /follow feature was used STATS_COUNT_REDIRECT_AUTO, // count that a user was auto-redirected STATS_COUNT_REDIRECT_MANUAL, // count that a user was manually redirected STATS_COUNT_MENU, // count that a user opened the server menu STATS_COUNT_ENQUEUE, // count that a user enqueued himself for a server STATS_COUNT_DEQUEUE, // count that a user dequeued himself STATS_COUNT_REDIRECTED, // count that a user was redirected to this server STATS_COUNT_RETRY, // count that the /retry feature was used STATS_COUNT_DROP, // count that a user was dropped because there was no free slot on any server } // statistic info constants enum eStatsInfo { STATS_INFO_REDIRECT=0, STATS_INFO_ENQUEUE, STATS_INFO_RETRY, STATS_INFO_DEQUEUE, } // redirection type constants for statistics feature enum eStats { STATS_REDIRTYPE_DROP = 0, // a user was dropped from the server because no free slot could be found STATS_REDIRTYPE_AUTO, // a user was automatically redirected STATS_REDIRTYPE_MANUAL, // a user manually redirected himself using the menu STATS_REDIRTYPE_FOLLOW, // a user redirected himself by using /follow STATS_REDIRTYPE_QUEUED, // a user redirected himself by using the queue (through menu or /retry command) STATS_REDIRTYPE_ADMIN, // a user was redirected by an admin (redirect_user) } // unique task ID's - currently not needed but who knows when they will be #define TASKID_QUERY 21934807 #define TASKID_QUERY_RECEIVE 21934808 #define TASKID_ANNOUNCE 21934809 #define TASKID_ADVERTISE 21934810 // options - these can be changed by the user, rememeber that you need to recompile for any changes here to take effect #define SERVERFILE "serverlist.ini" // name of file in /configs containing the server forwards - you can also prepend a subdirectory #define STATSFILE "xredirect-actions.csv" // name of file in /logs containing the redirection action statistics - you can also prepend a subdirectory #define QUERY_INTERVAL 10.0 // interval of server querying (in seconds) #define QUERY_TIMEOUT 1.0 // the maximum time to wait for a server answer (in seconds) before it is considered being down #define MAX_SERVERFORWARDS 6 // maximum number of server forwards in forwards file #define MAX_MENUPAGES 10 // maximum number of pages the server selection menu can have #define DEFAULT_CMDBACKUP 2 // how often to resend the UDP request to servers by default #define MENU_FORCENOCOLOR false // false = display colored menues if the mod supports it; true = never display colored menues #define CANCEL_IS_BACK_KEY false // only when categories are enabled: true = "Cancel" key in server menu turns into a "Back" key and goes back to category menu #define MIN_ADMIN_LEVEL ADMIN_RESERVATION // the minimum level a player must have to be treated as admin (= won't be automatically redirected, can use reserved slots, can join passworded servers with publicpassword=0...) // can be one of these listed here: http://www.amxmodx.org/funcwiki.php?...=1#const_admin #define MOD_DETECTION true // enable/disable automatic detection of mod and protocol of other servers - only disable this if you have problems with mod/protocol detection // A2S_INFO definitions for source according to http://developer.valvesoftware.com/w...urce_servers_2 #define A2S_INFO_SOURCE_REPLY_FORMAT "411ssss21111111s" // there are some extra flags after this but we don't care #define A2S_INFO_SOURCE_IDX_HEADER 0 // Should be FF FF FF FF #define A2S_INFO_SOURCE_IDX_TYPE 1 // Should be equal to 'I' (0x49) #define A2S_INFO_SOURCE_IDX_VERSION 2 // Network version. 0x07 is the current Steam version. Goldsource games will return 48 (0x30), also referred to as protocol version. #define A2S_INFO_SOURCE_IDX_SERVERNAME 3 // The Source server's name #define A2S_INFO_SOURCE_IDX_MAP 4 // The current map being played, eg: "de_dust" #define A2S_INFO_SOURCE_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike" #define A2S_INFO_SOURCE_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter Strike: Source" #define A2S_INFO_SOURCE_IDX_APPID 7 // Steam Application ID, see http://developer.valvesoftware.com/w...pplication_IDs #define A2S_INFO_SOURCE_IDX_NUMPLAYERS 8 // The number of players currently on the server #define A2S_INFO_SOURCE_IDX_MAXPLAYERS 9 // Maximum allowed players for the server #define A2S_INFO_SOURCE_IDX_NUMBOTS 10 // Number of bot players currently on the server #define A2S_INFO_SOURCE_IDX_DEDICATED 11 // 'l' for listen, 'd' for dedicated, 'p' for SourceTV #define A2S_INFO_SOURCE_IDX_OS 12 // Host operating system. 'l' for Linux, 'w' for Windows #define A2S_INFO_SOURCE_IDX_PASSWORD 13 // If set to 0x01, a password is required to join this server #define A2S_INFO_SOURCE_IDX_SECURE 14 // if set to 0x01, this server is VAC secured #define A2S_INFO_SOURCE_IDX_GAMEVERSION 15 // The version of the game, eg: "1.0.0.22" // A2S_INFO definitions for goldsource according to http://developer.valvesoftware.com/w...urce_servers_2 #define A2S_INFO_GOLD_REPLY_FORMAT "41sssss111111[ss14411]11" #define A2S_INFO_GOLD_IDX_HEADER 0 // Should be FF FF FF FF #define A2S_INFO_GOLD_IDX_TYPE 1 // Should be equal to 'm' (0x6D) - for older servers it's 'C' (0x43) #define A2S_INFO_GOLD_IDX_IP 2 // Game Server IP address and port #define A2S_INFO_GOLD_IDX_SERVERNAME 3 // The server's name #define A2S_INFO_GOLD_IDX_MAP 4 //The current map being played, eg: "de_dust" #define A2S_INFO_GOLD_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike" #define A2S_INFO_GOLD_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter-Strike" #define A2S_INFO_GOLD_IDX_NUMPLAYERS 7 // The number of players currently on the server #define A2S_INFO_GOLD_IDX_MAXPLAYERS 8 // Maximum allowed players for the server #define A2S_INFO_GOLD_IDX_VERSION 9 // Network version. 0x07 is the current Steam version. #define A2S_INFO_GOLD_IDX_DEDICATED 10 // 'l' for listen, 'd' for dedicated, 'p' for HLTV #define A2S_INFO_GOLD_IDX_OS 11 // Host operating system. 'l' for Linux, 'w' for Windows #define A2S_INFO_GOLD_IDX_PASSWORD 12 // If set to 0x01, a password is required to join this server #define A2S_INFO_GOLD_IDX_ISMOD 13 // If set to 0x01, this byte is followed by ModInfo (that is, all A2S_INFO_GOLD_IDX_MOD_ elements are included) #define A2S_INFO_GOLD_IDX_SECURE 14 // if set to 0x01, this server is VAC secured - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is set to 0x01 A2S_INFO_GOLD_IDX_MOD_SECURE has to be used instead #define A2S_INFO_GOLD_IDX_NUMBOTS 15 // Number of bot players currently on the server - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is set to 0x01 A2S_INFO_GOLD_IDX_MOD_NUMBOTS has to be used instead #define A2S_INFO_GOLD_IDX_MOD_URLINFO 14 // URL containing information about this mod #define A2S_INFO_GOLD_IDX_MOD_URLDL 15 // URL to download this mod #define A2S_INFO_GOLD_IDX_MOD_NUL 16 // 0x00 #define A2S_INFO_GOLD_IDX_MOD_MODVERSION 17 // Version of the installed mod #define A2S_INFO_GOLD_IDX_MOD_MODSIZE 18 // The download size of this mod #define A2S_INFO_GOLD_IDX_MOD_SVONLY 19 // If 1 this is a server side only mod #define A2S_INFO_GOLD_IDX_MOD_CIDLL 20 // If 1 this mod has a custom client dll // the wiki specification is wrong about these two, they are switched: #define A2S_INFO_GOLD_IDX_MOD_NUMBOTS 21 // Number of bot players currently on the server - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is not set to 0x01 A2S_INFO_GOLD_IDX_NUMBOTS has to be used instead #define A2S_INFO_GOLD_IDX_MOD_SECURE 22 // if set to 0x01, this server is VAC secured - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is not set to 0x01 A2S_INFO_GOLD_IDX_SECURE has to be used instead // flags #define SERVERFLAG_NOAUTO 0 #define SERVERFLAG_NOMANUAL 1 #define SERVERFLAG_NODISPLAY 2 // defines for "private" attribute #define PRIVATE_NONE 0 #define PRIVATE_HIDE 1 #define PRIVATE_FULLHIDE 2 // --------------------------------------- end of defines --------------------------------------- // -=[ global variables - remember to add an initialization in srvcmd_reload() for all variables you add here! ]=- /// <summary>Defines whether the plugin was completely initialized.</summary> new g_bInitialized = false // no srvcmd_reload() initialization needed for this one, as it's not directly related to the server list /// <summary>Server ID.</summary> new g_naServerIds[MAX_SERVERFORWARDS] = {-1, ...} /// <summary>Server name.</summary> new g_saServerNames[MAX_SERVERFORWARDS][MAX_SERVERNAME_LEN] /// <summary>Server address.</summary> new g_saServerAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN] /// <summary>Server port.</summary> new g_naServerPorts[MAX_SERVERFORWARDS] = {27015, ...} /// <summary>Server password.</summary> new g_saServerPasswords[MAX_SERVERFORWARDS][MAX_PASSWORD_LEN] /// <summary>Is the server password public?</summary> new g_naServerPublicPassword[MAX_SERVERFORWARDS] = {0, ...} /// <summary>Currently active player count.</summary> new g_naServerActivePlayers[MAX_SERVERFORWARDS] = {-1, ...} /// <summary>Maximum number of players the server accepts. Does not take reserved slots into account.</summary> new g_naServerMaxPlayers[MAX_SERVERFORWARDS] = {-1, ...} /// <summary>Currently running map on server.</summary> new g_saServerMap[MAX_SERVERFORWARDS][MAX_MAP_LEN] /// <summary>The socket for the server to handle requests.</summary> new g_naServerSockets[MAX_SERVERFORWARDS] = {0, ...} /// <summary>The number how often server queries should be resent to that server.</summary> new g_naServerCmdBackup[MAX_SERVERFORWARDS] = {DEFAULT_CMDBACKUP, ...} /// <summary>Flags with several server options. Use the constant defines starting with SERVERFLAG_ to access these.</summary> new g_naServerFlags[MAX_SERVERFORWARDS] = {0, ...} /// <summary>Are admin slots reserved on this server?</summary> new g_naServerReserveSlots[MAX_SERVERFORWARDS] = {0, ...} /// <summary>Local server address.</summary> new g_saServerLocalAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN] /// <summary>The short name of the mod running on the server, e.g. "cstrike", "ns" or "dod".</summary> new g_saServerMod[MAX_SERVERFORWARDS][MAX_NAME_LEN] /// <summary>The network protocol version of the server, e.g. protocol 46 (CS 1.5), 47 (No-Steam Goldsource servers), 48 (Steam Source and Goldsource servers).</summary> new g_naServerProtocol[MAX_SERVERFORWARDS] /// <summary>At which real index does the menu page start? It is shifted because of hidden servers or servers filtered out by category.</summary> new g_naMenuPageStart[MAX_PLAYERS][MAX_MENUPAGES] /// <summary>The category the server belongs to.</summary> new g_naServerCategory[MAX_SERVERFORWARDS] = {-1, ...} /// <summary>The "private" setting for this server. Contains one of the PRIVATE_ constants.</summary> new g_naServerPrivate[MAX_SERVERFORWARDS] /// <summary>Is the server responding?</summary> new bool:g_baServerResponding[MAX_SERVERFORWARDS] = {false, ...} /// <summary>Number of servers found in server list file.</summary> new g_nServerCount = 0 /// <summary>The last server someone has been redirected to. Needed for <seealso name="cmd_follow_player"/>.</summary> new g_nLastRedirectServer = -1 /// <summary>The nick of the person who has been redirected at last. Needed for <seealso name="cmd_follow_player"/>.</summary> new g_sLastRedirectName[MAX_NAME_LEN] = "" /// <summary>The index of the current server. This is neccessary for the server to check its own data.</summary> new g_nOwnServer = -1 /// <summary>The ID number of the current server as it was assigned in the serverlist.</summary> new g_nOwnServerId = -1 /// <summary>The page number for each user which he had open last time, needed for switching back from sub menu to server menu.</summary> new g_naLastMenuPages[MAX_PLAYERS] = {1, ...} /// <summary>The category ID for each user which he had selected last time, needed for switching back from sub menu to server menu.</summary> new g_naLastCategory[MAX_PLAYERS] = {-1, ...} /// <summary>Hidden servers cause a difference between shown and real server numbers - this array associates the real server index with a given key - different for each user as some users can see servers that others don't.</summary> new g_naServerSelections[MAX_PLAYERS][8] /// <summary>This is the cycle variable that holds which server to begin from in <seealso name="announce_servers"/>.</summary> new g_nNextAnnounceServer = 0 /// <summary>The last server the player came from through redirection. Needed in case he wants to send himself back with /retry.</summary> new g_nLastServer[MAX_PLAYERS] = {-1, ...} /// <summary>The last server the player has accessed the sub menu of. Needed when the player refreshes the sub menu.</summary> new g_nLastSelected[MAX_PLAYERS] = {-1, ...} /// <summary>This array contains the retry queue consisting of a player ID and a server number for each record.</summary> new g_nRetryQueue[MAX_PLAYERS*MAX_SERVERFORWARDS][2] /// <summary>Counter for global number of queue entries.</summary> new g_nRetryCount = 0 /// <summary>Controls whether certain debug messages are shown. It is automatically set to true when the plugin has debug mode set in plugins.ini.</summary> new bool:g_bDebug = false /// <summary>List of categories found in SERVERFILE.</summary> new g_saCategories[MAX_CATEGORIES][MAX_VALUE_LEN] /// <summary>Number of categories found in SERVERFILE.</summary> new g_nCategoryCount = 0 /// <summary>Are server IDs used in SERVERFILE? Will be set to true on the first found ID if file is used.</summary> #if defined SQL new bool:g_bUseIds = true #else new bool:g_bUseIds = false #endif /// <summary>The name of the mod this server is running.</summary> new g_sMod[MAX_NAME_LEN] = "" #if defined VAULT new g_nVaultId = -1 #endif // -=[ global SQL variables ]=- #if defined SQL new sSqlError[MAX_SQL_ERROR_LEN] new nSqlError new Handle:hSqlInfo new Handle:hSql new SQL_TABLE_STATISTICS[MAX_SQL_TABLE_LEN] #endif // -=[ global CVAR's ]=- new cvar_active new cvar_auto new cvar_manual new cvar_follow new cvar_external_address new cvar_check_method new cvar_advertise new cvar_announce new cvar_announce_mode new cvar_announce_alivepos_x new cvar_announce_alivepos_y new cvar_announce_deadpos_x new cvar_announce_deadpos_y new cvar_show new cvar_adminslots new cvar_maxadmins new cvar_retry new cvar_hidedown new cvar_localslots new cvar_countbots new cvar_categories // --------------------------------------- end of global vars --------------------------------------- #if AMXX_VERSION_NUM >= 180 /// <summary>Initialize CVARs, load servers, register commands, register menues, register dictionaries, start tasks...</summary> public plugin_init() { register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR) register_cvar("redirect_version", PLUGIN_VERSION, FCVAR_SERVER|FCVAR_SPONLY) set_cvar_string("redirect_version", PLUGIN_VERSION) // please see the description at top if you want to know what these CVAR's do cvar_active = register_cvar("redirect_active", "0") cvar_auto = register_cvar("redirect_auto", "0") cvar_manual = register_cvar("redirect_manual", "0") cvar_follow = register_cvar("redirect_follow", "0") cvar_external_address = register_cvar("redirect_external_address", "") cvar_check_method = register_cvar("redirect_check_method", "0") cvar_advertise = register_cvar("redirect_advertise", "150") cvar_announce = register_cvar("redirect_announce", "120") cvar_announce_mode = register_cvar("redirect_announce_mode", "3") cvar_announce_alivepos_x = register_cvar("redirect_announce_alivepos_x", "-1.0") cvar_announce_alivepos_y = register_cvar("redirect_announce_alivepos_y", "0.01") cvar_announce_deadpos_x = register_cvar("redirect_announce_deadpos_x", "-1.0") cvar_announce_deadpos_y = register_cvar("redirect_announce_deadpos_y", "0.35") cvar_show = register_cvar("redirect_show", "1") cvar_adminslots = register_cvar("redirect_adminslots", "0") cvar_maxadmins = register_cvar("redirect_maxadmins", "0") cvar_retry = register_cvar("redirect_retry", "0") cvar_hidedown = register_cvar("redirect_hidedown", "0") cvar_localslots = register_cvar("redirect_localslots", "0") cvar_countbots = register_cvar("redirect_countbots", "1") cvar_categories = register_cvar("redirect_categories", "0") register_dictionary("xredirect.txt") register_dictionary("common.txt") #if defined SQL register_dictionary("admin.txt") #endif // SQL // check whether we are in debug mode or not new saDummy[2] new saStatus[6] get_plugin(-1, saDummy, 0, saDummy, 0, saDummy, 0, saDummy, 0, saStatus, 5) g_bDebug = bool:equal(saStatus, "debug") // load servers from the SERVERLIST or SQL database #if defined SQL sql_connect() load_servers_sql() #else load_servers_file() #endif // SQL if (g_nServerCount < 2) log_amx("%L", LANG_SERVER, "MSG_ERROR_NOT_ENOUGH_SERVERS") register_menu("Category Menu", 1023, "category_menu_select") register_menu("Redirect Menu", 1023, "server_menu_select") register_menu("Detail Menu", 1023, "sub_menu_select") register_srvcmd("redirect_reload", "srvcmd_reload", -1, "- reload redirect servers") #if defined VAULT register_srvcmd("redirect_resetvault", "srvcmd_resetvault", -1, "- reset all vault statistics to 0") #endif // VAULT register_clcmd("say /server", "cmd_show_server_menu", 0, "- show server redirection menu") register_clcmd("say_team /server", "cmd_show_server_menu", 0, "- show server redirection menu") register_clcmd("pickserver", "cmd_pickserver", 0, "show server redirection menu") register_clcmd("say /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server") register_clcmd("say_team /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server") register_clcmd("say /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot") register_clcmd("say_team /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot") register_clcmd("say /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server") register_clcmd("say_team /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server") register_clcmd("redirect_announce_now", "announce_servers", ADMIN_KICK , "- announce server list immediately") register_clcmd("redirect_user", "cmd_redirect_user", ADMIN_KICK , "<playername|playerid> [servernum] - redirect a player [to a given server]") register_clcmd("redirect_queue", "cmd_redirect_queue", ADMIN_KICK , "- show the current redirect queue") #if defined VAULT register_srvcmd("redirect_stats", "srvcmd_stats", -1, "- show redirection statistics") register_clcmd("redirect_stats", "cmd_stats", ADMIN_KICK , "- show redirection statistics") #endif // VAULT set_task(QUERY_INTERVAL, "query_servers", TASKID_QUERY, "", 0, "b") #if defined VAULT g_nVaultId = nvault_open(VAULT_NAME) #endif //VAULT #if defined SQL formatex(SQL_TABLE_STATISTICS, MAX_SQL_TABLE_LEN-1, "%s%s", SQL_PREFIX, SQL_TABLENAME_STATISTICS) #else #if defined STATISTICS new sBaseDir[MAX_FILE_LEN], sStatsFile[MAX_FILE_LEN] get_basedir(sBaseDir, MAX_FILE_LEN-1) format(sStatsFile, MAX_FILE_LEN-1, "%s/logs/%s", sBaseDir, STATSFILE) // log a CSV header to the file if it doesn't exist yet if (!file_exists(sStatsFile)) log_to_file(STATSFILE, ",User Name,User IP,User Auth-ID,Action,Server-ID,Target-Server-ID") #endif // STATISTICS #endif // SQL // get and save the current mod get_modname(g_sMod, MAX_NAME_LEN -1) } /// <summary>More initializations that have to be done here, because when <seealso name="plugin_init"/> is called CVARs are not yet set. They are in plugin_cfg(), but not for the first start of the game server with ./hlds_run so we use this extra function called once when the first player connects.</summary> public plugin_postinit() { g_bInitialized = true new sFullAddress[MAX_SERVERADDRESS_LEN] new sTmpServerIP[MAX_IP_LEN] new sTmpServerPort[MAX_PORT_LEN] new sTmpServerAddress[MAX_IP_LEN + MAX_PORT_LEN], sTmpServerAddress2[MAX_IP_LEN + MAX_PORT_LEN] new sTmpOwnAddress[MAX_SERVERADDRESS_LEN] get_cvar_string("net_address", sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1) get_cvar_string("ip", sTmpServerIP, MAX_IP_LEN - 1) get_cvar_string("port", sTmpServerPort, MAX_PORT_LEN - 1) formatex(sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1, "%s:%s", sTmpServerIP, sTmpServerPort) get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1) // determine the own server new nServerCount = 0 while (nServerCount < g_nServerCount) { formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount]) if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerAddress) || equal(sFullAddress, sTmpServerAddress2)) { g_nOwnServer = nServerCount if (g_bUseIds) g_nOwnServerId = g_naServerIds[nServerCount] else g_nOwnServerId = nServerCount } if (g_bUseIds && (g_naServerIds[nServerCount] == -1)) log_amx("%L", LANG_SERVER, "MSG_ID_MISSING", g_saServerNames[nServerCount]) nServerCount++ } if (g_nOwnServer == -1) // we have not been able to detect the own server - inform the user about this { log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR") return PLUGIN_CONTINUE } // we need to know our own server index to be able to load attributes from SQL - so now we can do that #if defined SQL load_attributes_sql() #endif // SQL if (get_pcvar_float(cvar_announce) > 0.0) if (!task_exists(TASKID_ANNOUNCE)) set_task(get_pcvar_float(cvar_announce), "announce_servers", TASKID_ANNOUNCE, "", 0, "b") if (get_pcvar_float(cvar_advertise) > 0.0) if (!task_exists(TASKID_ADVERTISE)) set_task(get_pcvar_float(cvar_advertise), "advertise_server_command", TASKID_ADVERTISE, "", 0, "b") if ((get_pcvar_num(cvar_categories) >= 1) && (g_nCategoryCount == 0)) log_amx("%L", LANG_SERVER, "MSG_WARN_NO_CATEGORIES", "redirect_categories", SERVERFILE) return PLUGIN_CONTINUE } /// <summary>Cleanup. Close open sockets, vaults, database handles...</summary> public plugin_end() { // close all open sockets for (new nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++) { if (g_naServerSockets[nCounter] > 0) { socket_close(g_naServerSockets[nCounter]) g_naServerSockets[nCounter] = 0 } } #if defined VAULT nvault_close(g_nVaultId) #endif //VAULT #if defined SQL sql_disconnect() #endif // SQL return PLUGIN_CONTINUE } /// <summary>This is used to register the native redirect() function.</summary> public plugin_natives() { register_native("redirect", "native_redirect", 1) } /// <summary>Tells AMXX which modules are required.</summary> /// <remarks>The code to require sockets can be safely removed from the code when only redirect_check_method 0 will be used.</remarks> public plugin_modules() { require_module("sockets") #if defined VAULT require_module("nvault") #endif // VAULT } /// <summary>Gets the server index based on a server ID.</summary> /// <param name="nServerId">The ID of the server.</param> public get_server_index(nServerId) { new nServer = 0 while (nServer < g_nServerCount) { if (g_naServerIds[nServer] == nServerId) return nServer nServer++ } return -1 } /// <summary>Set an attribute for a server.</summary> /// <param name="nServer">The internal server index.</param> /// <param name="sAttribute">The attribute name.</param> /// <param name="sValue">The attribute value.</param> public set_server_attribute(nServer, sAttribute[MAX_ATTRIB_LEN], sValue[MAX_VALUE_LEN]) { if (nServer < 0) return strtoupper(sAttribute) if (strcmp(sAttribute, "ADDRESS") == 0) copy(g_saServerAddresses[nServer], MAX_SERVERADDRESS_LEN - 1, sValue) else if (strcmp(sAttribute, "LOCALADDRESS") == 0) copy(g_saServerLocalAddresses[nServer], MAX_SERVERADDRESS_LEN - 1, sValue) else if (strcmp(sAttribute, "PASSWORD") == 0) copy(g_saServerPasswords[nServer], MAX_PASSWORD_LEN - 1, sValue) else if (strcmp(sAttribute, "PUBLICPASSWORD") == 0) { if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerPublicPassword[nServer] = 1 } else if (strcmp(sAttribute, "PORT") == 0) { if (is_str_num(sValue)) g_naServerPorts[nServer] = str_to_num(sValue) else g_naServerPorts[nServer] = 27015 if ((g_naServerPorts[nServer] > 65536) || (g_naServerPorts[nServer] < 1024)) g_naServerPorts[nServer] = 27015 } else if (strcmp(sAttribute, "CMDBACKUP") == 0) { if (is_str_num(sValue)) g_naServerCmdBackup[nServer] = str_to_num(sValue) else g_naServerCmdBackup[nServer] = DEFAULT_CMDBACKUP // protect from insane values if ((g_naServerCmdBackup[nServer] > 100) || (g_naServerCmdBackup[nServer] < 0)) g_naServerCmdBackup[nServer] = DEFAULT_CMDBACKUP } else if (strcmp(sAttribute, "NOAUTO") == 0) { if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerFlags[nServer] = g_naServerFlags[nServer] | (1<<SERVERFLAG_NOAUTO) } else if (strcmp(sAttribute, "NOMANUAL") == 0) { if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerFlags[nServer] = g_naServerFlags[nServer] | (1<<SERVERFLAG_NOMANUAL) } else if (strcmp(sAttribute, "NODISPLAY") == 0) { if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerFlags[nServer] = g_naServerFlags[nServer] | (1<<SERVERFLAG_NODISPLAY) } else if (strcmp(sAttribute, "ADMINSLOTS") == 0) { if (is_str_num(sValue)) g_naServerReserveSlots[nServer] = str_to_num(sValue) else g_naServerReserveSlots[nServer] = 0 if ((g_naServerReserveSlots[nServer] > MAX_PLAYERS) || (g_naServerReserveSlots[nServer] < 0)) g_naServerReserveSlots[nServer] = 0 } else if (strcmp(sAttribute, "CATEGORY") == 0) { // adds a category to the internal category list and stores the index of it for the current server g_naServerCategory[nServer] = get_category_index(sValue) } else if (strcmp(sAttribute, "ID") == 0) { g_bUseIds = true g_naServerIds[nServer] = str_to_num(sValue) } else if (strcmp(sAttribute, "PRIVATE") == 0) { strtoupper(sValue) if (equal(sValue, "HIDE")) g_naServerPrivate[nServer] = PRIVATE_HIDE else if (equal(sValue, "FULLHIDE")) g_naServerPrivate[nServer] = PRIVATE_FULLHIDE else g_naServerPrivate[nServer] = PRIVATE_NONE } } #if defined SQL /// <summary>Load server attributes from SQL database.</summary> public load_attributes_sql() { new sSqlAttribTable[MAX_SQL_TABLE_LEN] formatex(sSqlAttribTable, MAX_SQL_TABLE_LEN-1, "%s%s", SQL_PREFIX, SQL_TABLENAME_ATTRIBUTES) new Handle:hQuery = SQL_PrepareQuery(hSql, "SELECT `target_id`, `attrib`, `value` FROM `%s` WHERE `source_id` = '%d' ORDER BY `target_id`", sSqlAttribTable, g_nOwnServerId) if (!SQL_Execute(hQuery)) { SQL_QueryError(hQuery, sSqlError, MAX_SQL_ERROR_LEN-1) log_amx("%L", LANG_SERVER, "MSG_ERROR_SQL_TABLE", sSqlAttribTable) } else { new sAttribute[MAX_ATTRIB_LEN] new sValue[MAX_VALUE_LEN] new nTargetId new cTargetId = SQL_FieldNameToNum(hQuery, "target_id") new cAttribute = SQL_FieldNameToNum(hQuery, "attrib") new cValue = SQL_FieldNameToNum(hQuery, "value") while (SQL_MoreResults(hQuery)) { SQL_ReadResult(hQuery, cTargetId, sValue, MAX_VALUE_LEN-1) nTargetId = str_to_num(sValue) SQL_ReadResult(hQuery, cAttribute, sAttribute, MAX_VALUE_LEN-1) SQL_ReadResult(hQuery, cValue, sValue, MAX_VALUE_LEN-1) set_server_attribute(get_server_index(nTarget Id), sAttribute, sValue) SQL_NextRow(hQuery) } } SQL_FreeHandle(hQuery) } /// <summary>Load servers from SQL database.</summary> public load_servers_sql() { new sPort[MAX_PORT_LEN] new sSqlServerTable[MAX_SQL_TABLE_LEN] formatex(sSqlServerTable, MAX_SQL_TABLE_LEN-1, "%s%s", SQL_PREFIX, SQL_TABLENAME_SERVERS) new Handle:hQuery = SQL_PrepareQuery(hSql, "SELECT * FROM `%s`", sSqlServerTable) if (!SQL_Execute(hQuery)) { SQL_QueryError(hQuery, sSqlError, MAX_SQL_ERROR_LEN-1) log_amx("%L", LANG_SERVER, "MSG_ERROR_SQL_TABLE", sSqlServerTable) } else { new cId = SQL_FieldNameToNum(hQuery, "id") new cName = SQL_FieldNameToNum(hQuery, "name") new cAddress = SQL_FieldNameToNum(hQuery, "address") new cLocalAddress = SQL_FieldNameToNum(hQuery, "localaddress") new cPassword = SQL_FieldNameToNum(hQuery, "password") new cPublicPassword = SQL_FieldNameToNum(hQuery, "publicpassword") new cPort = SQL_FieldNameToNum(hQuery, "port") new cCmdBackup = SQL_FieldNameToNum(hQuery, "cmdbackup") new cNoAuto = SQL_FieldNameToNum(hQuery, "noauto") new cNoManual = SQL_FieldNameToNum(hQuery, "nomanual") new cNoDisplay = SQL_FieldNameToNum(hQuery, "nodisplay") new cAdminSlots = SQL_FieldNameToNum(hQuery, "adminslots") new cCategory = SQL_FieldNameToNum(hQuery, "category") new cPrivate = SQL_FieldNameToNum(hQuery, "private") new sValue[MAX_VALUE_LEN] while ((SQL_MoreResults(hQuery)) && (g_nServerCount < MAX_SERVERFORWARDS)) { SQL_ReadResult(hQuery, cId, sValue, MAX_VALUE_LEN-1) g_naServerIds[g_nServerCount] = str_to_num(sValue) SQL_ReadResult(hQuery, cName, g_saServerNames[g_nServerCount], MAX_SERVERNAME_LEN-1) SQL_ReadResult(hQuery, cAddress, g_saServerAddresses[g_nServerCount], MAX_SERVERADDRESS_LEN-1) SQL_ReadResult(hQuery, cLocalAddress, g_saServerLocalAddresses[g_nServerCount], MAX_SERVERADDRESS_LEN-1) SQL_ReadResult(hQuery, cPassword, g_saServerPasswords[g_nServerCount], MAX_PASSWORD_LEN-1) SQL_ReadResult(hQuery, cPublicPassword, sValue, MAX_VALUE_LEN-1) g_naServerPublicPassword[g_nServerCount] = str_to_num(sValue) SQL_ReadResult(hQuery, cPort, sValue, MAX_VALUE_LEN-1) g_naServerPorts[g_nServerCount] = str_to_num(sValue) SQL_ReadResult(hQuery, cCmdBackup, sValue, MAX_VALUE_LEN-1) g_naServerCmdBackup[g_nServerCount] = str_to_num(sValue) SQL_ReadResult(hQuery, cNoAuto, sValue, MAX_VALUE_LEN-1) if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerFlags[g_nServerCount] = g_naServerFlags[g_nServerCount] | (1<<SERVERFLAG_NOAUTO) SQL_ReadResult(hQuery, cNoManual, sValue, MAX_VALUE_LEN-1) if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerFlags[g_nServerCount] = g_naServerFlags[g_nServerCount] | (1<<SERVERFLAG_NOMANUAL) SQL_ReadResult(hQuery, cNoDisplay, sValue, MAX_VALUE_LEN-1) if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerFlags[g_nServerCount] = g_naServerFlags[g_nServerCount] | (1<<SERVERFLAG_NODISPLAY) SQL_ReadResult(hQuery, cAdminSlots, sValue, MAX_VALUE_LEN-1) g_naServerReserveSlots[g_nServerCount] = str_to_num(sValue) SQL_ReadResult(hQuery, cCategory, sValue, MAX_VALUE_LEN-1) g_naServerCategory[g_nServerCount] = get_category_index(sValue) SQL_ReadResult(hQuery, cPrivate, sValue, MAX_VALUE_LEN-1) strtoupper(sValue) if (equal(sValue, "HIDE")) g_naServerPrivate[g_nServerCount] = PRIVATE_HIDE else if (equal(sValue, "FULLHIDE")) g_naServerPrivate[g_nServerCount] = PRIVATE_FULLHIDE else g_naServerPrivate[g_nServerCount] = PRIVATE_NONE // at least a valid server address is required, otherwise ignore the server if (!equal(g_saServerAddresses[g_nServerCount], "")) { num_to_str(g_naServerPorts[g_nServerCount], sPort, MAX_PORT_LEN - 1) log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[g_nServerCount], g_saServerAddresses[g_nServerCount], sPort) g_nServerCount++ } SQL_NextRow(hQuery) } } SQL_FreeHandle(hQuery) } #endif // SQL /// <summary>Load servers from server list file.</summary> public load_servers_file() { new sConfigDir[MAX_FILE_LEN], sServerFile[MAX_FILE_LEN] get_configsdir(sConfigDir, MAX_FILE_LEN-1) format(sServerFile, MAX_FILE_LEN-1, "%s/%s", sConfigDir, SERVERFILE) if (!file_exists(sServerFile)) { log_amx("%L", LANG_SERVER, "MSG_ERROR_NO_FILE", sServerFile) return } new nFilePos = 0 new sFileLine[MAX_SERVERLINE_LEN], sFileLineTrim[MAX_SERVERLINE_LEN] new nReadLen new sPort[MAX_PORT_LEN] new sAttribute[MAX_ATTRIB_LEN] new sValue[MAX_VALUE_LEN] new nCurrentServer = -1 while (read_file(sServerFile, nFilePos++, sFileLine, MAX_SERVERLINE_LEN-1, nReadLen)) { sFileLineTrim = sFileLine trim(sFileLineTrim) if ((sFileLine[0] == ';') || (strlen(sFileLineTrim) == 0)) continue // skip comments and empty lines if ((sFileLine[0] == '[') && (sFileLine[strlen(sFileLine) - 1] == ']')) // a section starts { nCurrentServer++ if (nCurrentServer > 0) { // check whether the previous server was valid if ((g_naServerPorts[nCurrentServer - 1] != 0) && (strcmp(g_saServerAddresses[nCurrentServer - 1], "") != 0)) { g_nServerCount++ num_to_str(g_naServerPorts[nCurrentServer - 1], sPort, MAX_PORT_LEN - 1) log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer - 1], g_saServerAddresses[nCurrentServer - 1], sPort) } else nCurrentServer-- } if (nCurrentServer >= MAX_SERVERFORWARDS) break; copy(g_saServerNames[nCurrentServer], strlen(sFileLine) - 2, sFileLine[1]) continue } if (nCurrentServer >= 0) // do we already have found a section? { strtok(sFileLine, sAttribute, MAX_ATTRIB_LEN - 1, sValue, MAX_VALUE_LEN - 1, '=', 1) set_server_attribute(nCurrentServer, sAttribute, sValue) } } if ((nCurrentServer >= MAX_SERVERFORWARDS) || (nCurrentServer == -1)) return // check whether the previous server was valid if ((g_naServerPorts[nCurrentServer] != 0) && (strcmp(g_saServerAddresses[nCurrentServer], "") != 0)) { g_nServerCount++ num_to_str(g_naServerPorts[nCurrentServer], sPort, MAX_PORT_LEN - 1) log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer], g_saServerAddresses[nCurrentServer], sPort) } } /// <summary>Gets the index of a category in the internal list. It is added to the list if it doesn't exist there already.</summary> /// <param name="sCategory">The string containing the category name. Not case-sensitive when it comes to searching in the list.</param> /// <returns>The zero-based array index of the category in the internal list or -1 if called with an empty category name.</returns> public get_category_index(sCategory[MAX_VALUE_LEN]) { if (equal(sCategory, "")) return -1 for (new nCategoryIndex = 0; nCategoryIndex < g_nCategoryCount; nCategoryIndex++) { if (equali(g_saCategories[nCategoryIndex], sCategory)) return nCategoryIndex } // don't assign a category if we already reached the maximum number if (g_nCategoryCount >= MAX_CATEGORIES - (get_pcvar_num(cvar_categories) - 1)) // if cvar_categories is set to 2 it's one category less we can use return -1 if (g_bDebug) log_amx("Found new category '%s'", sCategory) g_saCategories[g_nCategoryCount] = sCategory g_nCategoryCount++ return g_nCategoryCount - 1 } /// <summary>Checks whether the IP in <paramref name="sCheckAddress"/> is a local address according to RFC 1918.</summary> /// <summary>10.0.0.0 - 10.255.255.255 - single class A</summary> /// <summary>172.16.0.0 - 172.31.255.255 - 16 contiguous class Bs</summary> /// <summary>192.168.0.0 - 192.168.255.255 - 256 contiguous class Cs</summary> /// <summary>169.254.0.0 - 169.254.255.255 - zeroconf</summary> /// <param name="sCheckAddress">The IP address to check passed as a string.</param> /// <returns>true if <paramref name="sCheckAddress"/> is a local IP address, false if not.</returns> public bool:is_local_address(sCheckAddress[MAX_IP_LEN]) { new sIPPart1[4] new sIPPart2[4] new nIPPart[4] new sCompareIP[MAX_IP_LEN] sCompareIP = sCheckAddress strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.') nIPPart[0] = str_to_num(sIPPart1); strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.') nIPPart[1] = str_to_num(sIPPart1); strtok(sCheckAddress, sIPPart1, 3, sIPPart2, 3, '.') nIPPart[2] = str_to_num(sIPPart1); nIPPart[3] = str_to_num(sIPPart2); return ((nIPPart[0] == 10) || ((nIPPart[0] == 192) && (nIPPart[1] == 168)) || ((nIPPart[0] == 172) && (nIPPart[1] > 15) && (nIPPart[1] < 32)) || ((nIPPart[0] == 169) && (nIPPart[1] == 254))) } /// <summary>Checks whether the given server differs from the current server with mod or protocol.</summary> /// <param name="nServer">The server number which shall be checked whether it's having the same mod and protocol as the current server.</param> /// <returns>true if the server's mod and protocol match that of the current server, otherwise false.</returns> public bool:compare_mod(nServer) { #if defined MOD_DETECTION // compensate for errors in protocol detection if ((g_naServerProtocol[g_nOwnServer] <= 0) || (g_naServerProtocol[nServer] <= 0)) return true // compensate for errors in mod detection if ((equal(g_sMod, "")) || (equal(g_saServerMod[nServer], ""))) return true // compare protocol and mod return ((strcmp(g_sMod, g_saServerMod[nServer], 1) == 0) && (g_naServerProtocol[g_nOwnServer] == g_naServerProtocol[nServer])) #elseif return true #endif } /// <summary>Checks whether the player with ID <paramref name="nPlayerID"/> can be redirected to the server with server number <paramref name="nServerNum"/>.</summary> /// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection target.</param> /// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param> /// <param name="nMode">Defines the redirection mode - 1 = automatic, 2 = manual.</param> /// <param name="bIgnoreAdmin">Set to true, when the plugin should not tread admins as special, otherwise false.</param> /// <returns>0 if redirection is possible, otherwise an error code: 1 = current server, 2 = no permission(passworded), 3 = manual redirection disabled. 4 = server full, 5 = server down, 6 = automatic redirection disabled.</returns> public can_redirect_player(nServer, nPlayerID, nMode, bIgnoreAdmin) { if (nServer == -1) return 0 get_modname(g_sMod, MAX_NAME_LEN - 1) new nCheckMethod = get_pcvar_num(cvar_check_method) new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0)) if (g_bDebug) log_amx("Mod comparison: local protocol/mod: %i/^"%s^", remote protocol/mod: (server %d): %i/^"%s^"", g_naServerProtocol[g_nOwnServer], g_sMod, nServer, g_naServerProtocol[nServer], g_saServerMod[nServer]) if (nServer == g_nOwnServer) return 1 else if ((nCheckMethod == 2) && (!compare_mod(nServer))) return 7 else if (access(nPlayerID, MIN_ADMIN_LEVEL) && (!bIgnoreAdmin)) // even for admins it doesn't make sense to redirect to the current server or to servers with a different mod/protocol so check admin rights from here return 0 else if ((nCheckMethod > 0) && (!g_baServerResponding[nServer])) return 5 else if (!bCanRedirectByPassword) return 2 else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL)) && (nMode == 2)) return 3 else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOAUTO)) && (nMode == 1)) return 6 else if ((nCheckMethod == 2) && (g_naServerReserveSlots[nServer] >= (g_naServerMaxPlayers[nServer] - g_naServerActivePlayers[nServer])) ) return 4 return 0 } /// <summary>Checks whether the player with ID <paramref name="nPlayerID"/> can be queued to redirect to the server with server number <paramref name="nServerNum"/>.</summary> /// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection queue target.</param> /// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param> /// <returns>true if queueing is possible, otherwise false.</returns> public bool:can_queue_player(nServer, nPlayerID) { if (nServer == -1) return false new bIsAdmin = access(nPlayerID, MIN_ADMIN_LEVEL) if ((get_pcvar_num(cvar_retry) == 0) && (!bIsAdmin)) // admin always can enqueue themselves, even when this feature is disabled return false new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0)) if (nServer == g_nOwnServer) return false if (bIsAdmin) return true if ((!bCanRedirectByPassword) || (g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL))) return false if ((get_pcvar_num(cvar_check_method) == 2) && (!compare_mod(nServer))) return false return true } /// <summary>Checks whether the player with ID <paramref name="id"/> is already in redirection queue for server with number <paramref name="nServer"/>.</summary> /// <param name="nServer">The server number which shall be checked whether player with <paramref name="id"/> is in its queue.</param> /// <param name="id">The internal player ID which shall be checked whether it is queued for server <paramref name="nServer"/>.</param> /// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks> /// <returns>true if player is in queue, false if not.</returns> /// <seealso name="queue_add"/> /// <seealso name="queue_remove"/> public bool:is_queued(id, nServer) { new nCount = 0 while (nCount < g_nRetryCount) { if ((g_nRetryQueue[nCount][0] == id) && (g_nRetryQueue[nCount][1] == nServer)) return true nCount++ } return false } /// <summary>Adds the player with ID <paramref name="id"/> to the redirection queue for server with number <paramref name="nServer"/>.</summary> /// <param name="nServer">The server number to add the player with <paramref name="id"/> to its queue.</param> /// <param name="id">The internal player ID which shall be added to the queue for server <paramref name="nServer"/>.</param> /// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks> /// <seealso name="is_queued"/> /// <seealso name="queue_remove"/> public queue_add(id, nServer) { if (get_pcvar_num(cvar_retry) > 0) { // first check whether the server-player-combination is not already in queue new nCount = 0 new nServerQueue = 0 while (nCount < g_nRetryCount) { // count how many people are in the queue for the target server if (g_nRetryQueue[nCount][1] == nServer) { nServerQueue++ // no need to continue when he already is in the queue if (g_nRetryQueue[nCount][0] == id) return } nCount++ } new sUserNick[MAX_NAME_LEN] get_user_name(id, sUserNick, MAX_NAME_LEN - 1) if (get_pcvar_num(cvar_show) == 1) { new naPlayers[MAX_PLAYERS] new nPlayerNum, nPlayerCount, nCurrentPlayer get_players(naPlayers, nPlayerNum, "c") for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { nCurrentPlayer = naPlayers[nPlayerCount] if (nCurrentPlayer != id) // he has his own message client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_QUEUE_ANNOUNCE", sUserNick, g_saServerNames[nServer]) } } #if defined STATISTICS stats_redirect(STATS_INFO_ENQUEUE, id, -1, nServer) #endif // STATISTICS if (g_bDebug) log_amx("added player %i to queue for server %i in slot %i", id, nServer, g_nRetryCount) g_nRetryQueue[g_nRetryCount][0] = id g_nRetryQueue[g_nRetryCount][1] = nServer g_nRetryCount++ client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_ADD", ++nServerQueue, g_saServerNames[nServer]) } else client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_DEACTIVATED") } /// <summary>Removes the player with ID <paramref name="id"/> from the redirection queue for server with number <paramref name="nServer"/>.</summary> /// <param name="nServer">The server number to remove the player with <paramref name="id"/> from its queue.</param> /// <param name="id">The internal player ID which shall be removed from the queue for server <paramref name="nServer"/>.</param> /// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks> /// <seealso name="is_queued"/> /// <seealso name="add_remove"/> public queue_remove(id, nServer) { new nCount = 0 while (nCount < g_nRetryCount) { if ((g_nRetryQueue[nCount][0] == id) && ((nServer == -1) || (g_nRetryQueue[nCount][1] == nServer))) { // ok, remove from queue and let all others go one place up // in case it's the last entry in queue where the following loop would never be executed: g_nRetryQueue[nCount][0] = -1 g_nRetryQueue[nCount][1] = -1 // move other entries up while ((nCount + 1) < g_nRetryCount) { g_nRetryQueue[nCount][0] = g_nRetryQueue[nCount + 1][0] g_nRetryQueue[nCount][1] = g_nRetryQueue[nCount + 1][1] nCount++ } g_nRetryCount-- break } nCount++ } } /// <summary>Resets the setinfo string of the player with <paramref name="id"/> by removing tags that xREDIRECT used.</summary> /// <param name="id">The internal player ID of the player that shall have the setinfo data resetted. It is passed as an array so that this function can easily be called from <seealso name="set_task"/>.</param> public reset_info(id[]) { client_cmd(id[0], "setinfo ^"xredir^" ^"^"") client_cmd(id[0], "setinfo ^"password^" ^"^"") } #if defined VAULT /// <summary>Converts a string to a value that can be written to a CSV file. Basically it handles escaping delimiters (,) and quotes (").</summary> public csv_value(value[]) { new sReturnValue[51] copy(sReturnValue, 50, value) if (containi(sReturnValue, "^"") || containi(sReturnValue, ",")) { replace_all(sReturnValue, 50, "^"", "^"^"") // double all quotes format(sReturnValue, 50, "^"%s^"", sReturnValue) // surround the whole term with quotes } return sReturnValue } /// <summary>Displays the current count stats to the given user ID.</summary> public cmd_stats(id, level, cid) { if (!cmd_access(id, level, cid, 1)) return PLUGIN_HANDLED if (g_bDebug) log_amx("showstats") new sVaultKey[MAX_VAULT_KEY_LEN] formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_AUTO) console_print(id, "%L: %d", id, "MSG_STATS_REDIRECT_AUTO", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_MANUAL) console_print(id, "%L: %d", id, "MSG_STATS_REDIRECT_MANUAL", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DROP) console_print(id, "%L: %d", id, "MSG_STATS_DROP", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_MENU) console_print(id, "%L: %d", id, "MSG_STATS_MENU", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_ENQUEUE) console_print(id, "%L: %d", id, "MSG_STATS_ENQUEUE", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DEQUEUE) console_print(id, "%L: %d", id, "MSG_STATS_DEQUEUE", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECTED) console_print(id, "%L: %d", id, "MSG_STATS_REDIRECTED", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_RETRY) console_print(id, "%L: %d", id, "MSG_STATS_RETRY", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_FOLLOW) console_print(id, "%L: %d", id, "MSG_STATS_FOLLOW", nvault_get(g_nVaultId, sVaultKey)) return PLUGIN_HANDLED } /// <summary>Displays the current count stats to the server console.</summary> public srvcmd_stats() { new sVaultKey[MAX_VAULT_KEY_LEN] formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_AUTO) server_print("%L: %d", LANG_SERVER, "MSG_STATS_REDIRECT_AUTO", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_MANUAL) server_print("%L: %d", LANG_SERVER, "MSG_STATS_REDIRECT_MANUAL", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DROP) server_print("%L: %d", LANG_SERVER, "MSG_STATS_DROP", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_MENU) server_print("%L: %d", LANG_SERVER, "MSG_STATS_MENU", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_ENQUEUE) server_print("%L: %d", LANG_SERVER, "MSG_STATS_ENQUEUE", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DEQUEUE) server_print("%L: %d", LANG_SERVER, "MSG_STATS_DEQUEUE", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECTED) server_print("%L: %d", LANG_SERVER, "MSG_STATS_REDIRECTED", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_RETRY) server_print("%L: %d", LANG_SERVER, "MSG_STATS_RETRY", nvault_get(g_nVaultId, sVaultKey)) formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_FOLLOW) server_print("%L: %d", LANG_SERVER, "MSG_STATS_FOLLOW", nvault_get(g_nVaultId, sVaultKey)) } /// <summary>Resets all vault statistics to 0.</summary> public srvcmd_resetvault() { new sVaultKey[MAX_VAULT_KEY_LEN] new nStatsCountCount = eStatsCount for (new nStatsType = 0; nStatsType < nStatsCountCount; nStatsType++) { formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, nStatsType) nvault_pset(g_nVaultId, sVaultKey, "0") } } /// <summary>Increases a statistics counter.</summary> public stats_count(stats_type, server_id) { new sVaultKey[MAX_VAULT_KEY_LEN] new sVaultValue[11] // 10 digits should be enough formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, stats_type) formatex(sVaultValue, 10, "%d", nvault_get(g_nVaultId, sVaultKey) + 1) nvault_pset(g_nVaultId, sVaultKey, sVaultValue) } #endif // VAULT #if defined STATISTICS #if defined SQL public sql_callback(nFailState, Handle:hQuery, sError[], nErrNum, aData[], nSize, Float:fQueueTime) { if (nFailState != TQUERY_SUCCESS) log_amx("SQLx error #%d: %s", nErrNum, sError); } #endif // SQL /// <summary>Stores an info line in the statistics.</summary> /// <param name="statstype">One of the STATS_INFO_ defines defining the type of the action.</param> /// <param name="id">The slot ID of the user that triggered the information line by being directly involved/affected.</param> /// <param name="redirtype">The redirection type. 0 = drop, 1 = auto, 2 = manual, 3 = manual/follow, 4 = manual/queue, 5 = manual/admin (redirect_user).</param> /// <param name="server">The target server number.</param> /// <remarks>Unused parameters are ignored but should still always be set to -1 for better code readability.</remarks> public stats_redirect(statstype, id, redirtype, server) { new sUserIp[MAX_IP_LEN] new sUserNick[MAX_NAME_LEN] new sUserId[MAX_ID_LEN] new nServerId = server if ((g_bUseIds) && (server >= 0)) nServerId = g_naServerIds[server] get_user_ip(id, sUserIp, MAX_IP_LEN-1, 1) get_user_name(id, sUserNick, MAX_NAME_LEN-1) get_user_authid(id, sUserId, MAX_ID_LEN-1) switch (statstype) { // File header: // log_to_file(STATSFILE, ",User Name,User IP,User Auth-ID,Action,Server-ID,Target-Server-ID") case STATS_INFO_ENQUEUE: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Enqueue", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Enqueue", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_ENQUEUE, nServerId) #endif // SQL } case STATS_INFO_RETRY: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Retry", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Retry", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_RETRY, nServerId) #endif // SQL } case STATS_INFO_DEQUEUE: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Dequeue", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Dequeue", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_DEQUEUE, nServerId) #endif // SQL } case STATS_INFO_REDIRECT: { switch (redirtype) { case STATS_REDIRTYPE_DROP: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Drop", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Drop", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_DROP, nServerId) #endif // SQL } case STATS_REDIRTYPE_AUTO: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Auto", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Auto", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_REDIRECT_AUTO, nServerId) #endif // SQL } case STATS_REDIRTYPE_MANUAL: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Manual", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Manual", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId) #endif // SQL } case STATS_REDIRTYPE_FOLLOW: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Follow", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Follow", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId) stats_count(STATS_COUNT_FOLLOW, nServerId) #endif // SQL } case STATS_REDIRTYPE_QUEUED: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Queued", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Queued", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId) #endif // SQL } case STATS_REDIRTYPE_ADMIN: { #if defined SQL SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Admin", g_nOwnServerId, nServerId) #else log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Admin", g_nOwnServerId, nServerId) stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId) #endif // SQL } } } } } #endif // STATISTICS /// <summary>Advertises the availability of the /server command.</summary> public advertise_server_command() { client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_ADVERTISE") } /// <summary>Announce the servers on top of the screen. The position and interval for announcements can be set by CVARs.</summary> public announce_servers() { if (get_pcvar_num(cvar_active) == 1) { if (g_nServerCount > 0) { new nCheckMethod = get_pcvar_num(cvar_check_method) new sAnnounceBody[MAX_MENUBODY_LEN] = "" new nDisplayCount = 0 new nServerCount = g_nNextAnnounceServer if (nServerCount >= g_nServerCount) nServerCount = 0 while ((nServerCount < g_nServerCount) && (nDisplayCount < 8)) { if (!((g_naServerPrivate[nServerCount] >= PRIVATE_HIDE) || (g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY)) || ((get_pcvar_num(cvar_hidedown) > 1) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer)))) { if (nServerCount == g_nOwnServer) { new sMap[MAX_MAP_LEN] get_mapname(sMap, MAX_MAP_LEN - 1) format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s [%s] (%d/%d)", sAnnounceBody, g_saServerNames[nServerCount], sMap, get_current_players(), get_maxplayers()) } else { if (nCheckMethod == 0) format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount]) else if (g_baServerResponding[nServerCount]) { if (nCheckMethod == 1) format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount]) else if (nCheckMethod == 2) format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s [%s] (%d/%d)", sAnnounceBody, g_saServerNames[nServerCount], g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) } else format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s (down)", sAnnounceBody, g_saServerNames[nServerCount]) } } nServerCount++ nDisplayCount++ } g_nNextAnnounceServer = nServerCount set_hudmessage(000, 100, 255, -1.0, 0.01, 0, 0.0, 10.0, 0.5, 0.10, 1) if (get_pcvar_float(cvar_announce) > 0.0) { new nAnnounceMode = get_pcvar_num(cvar_announce_mode) if (nAnnounceMode > 0) { new naPlayers[MAX_PLAYERS] new nPlayerNum, nPlayerCount new sAnnounceText[MAX_MENUBODY_LEN] if ((nAnnounceMode == 1) || (nAnnounceMode == 3)) { get_players(naPlayers, nPlayerNum, "ac") // alive players set_hudmessage(000, 100, 255, get_pcvar_float(cvar_announce_alivepos_x), get_pcvar_float(cvar_announce_alivepos_y), 0, 0.0, 10.0, 0.5, 0.10, 1) for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { if (get_pcvar_num(cvar_manual) >= 1) format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody) else sAnnounceText = sAnnounceBody show_hudmessage(naPlayers[nPlayerCount], sAnnounceText) } } if ((nAnnounceMode == 2) || (nAnnounceMode == 3)) { get_players(naPlayers, nPlayerNum, "bc") // dead players set_hudmessage(000, 100, 255, get_pcvar_float(cvar_announce_deadpos_x), get_pcvar_float(cvar_announce_deadpos_y), 0, 0.0, 10.0, 0.5, 0.10, 1) // show list at lower position for them so it is not covered by the "spectator bars" for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { if (get_pcvar_num(cvar_manual) >= 1) format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody) else sAnnounceText = sAnnounceBody show_hudmessage(naPlayers[nPlayerCount], sAnnounceText) } } } } } } return PLUGIN_HANDLED } /// <summary>Shows the sub menu for server with number <paramref name="nServer"/> to the the player with ID <paramref name="id"/>.</summary> /// <param name="nServer">The server to show the sub menu for.</param> /// <param name="id">The ID of the player to show the sub menu.</param> /// <seealso name="server_menu_select"/> /// <seealso name="sub_menu_select"/> /// <seealso name="show_server_menu"/> public show_sub_menu(id, nServer) { new nCanRedirect = can_redirect_player(nServer, id, 2, false) new nCanRedirectIgnoreAdmin = can_redirect_player(nServer, id, 2, true); new bool:bCanQueue = can_queue_player(nServer, id) new bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR) new nCheckMethod = get_pcvar_num(cvar_check_method) new sMenuBody[MAX_MENUBODY_LEN] new nCurrentCategory = g_naServerCategory[nServer] new sCurrentCategory[MAX_VALUE_LEN] = "" if (nCurrentCategory >= 0) sCurrentCategory = g_saCategories[g_naServerCategory[nServer]] // can we display colors? if (bColorMenu) { formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SRVINFO_CAPTION") format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer]) if (get_pcvar_num(cvar_categories) >= 1) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_CATEGORY", sCurrentCategory) } else { formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SRVINFO_CAPTION") format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer]) if (get_pcvar_num(cvar_categories) >= 1) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_CATEGORY", sCurrentCategory) } // can we display map and player information? if (((nCheckMethod == 2) && ((g_baServerResponding[nServer])) || (nServer == g_nOwnServer))) { if (bColorMenu) { if (nServer == g_nOwnServer) { new sMap[MAX_MAP_LEN] get_mapname(sMap, MAX_MAP_LEN - 1) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_current_players(), get_maxplayers()) } else { format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer]) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer]) } } else { if (nServer == g_nOwnServer) { new sMap[MAX_MAP_LEN] get_mapname(sMap, MAX_MAP_LEN - 1) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_current_players(), get_maxplayers()) } else { format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer]) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer]) } } } // make the next line red if colors are supported and (the user is no admin or it's the current server) if ((bColorMenu) && ((!access(id, MIN_ADMIN_LEVEL)) || (nCanRedirect == 1))) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\r", sMenuBody) else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n", sMenuBody) // now display reason why we can't redirect there switch (nCanRedirectIgnoreAdmin) { case 1: format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_CURRENT") case 2: format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_PERMISSION") case 3: format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_NOMANUAL") case 4: format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_FULL") case 5: format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_DOWN") case 7: format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_PROT") } // enable/disable key for redirection/queue functionality new key = (1<<9) // cancel key = key | (1<<8) // back if ((nCheckMethod > 0) || (nServer == g_nOwnServer)) key = key | (1<<2) // refresh if (nCanRedirect == 0) key = key | (1<<0) // redirect if (bCanQueue && (nCheckMethod > 1)) key = key | (1<<1) // enqueue new sQueueMsg[30] if (is_queued(id, nServer)) sQueueMsg = "MSG_LEAVEQUEUE" else sQueueMsg = "MSG_QUEUE" // display the last menu items according to availability if (bColorMenu) { if (nCanRedirect == 0) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \w %L", sMenuBody, id, "MSG_REDIRECT") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \d %L", sMenuBody, id, "MSG_REDIRECT") if (bCanQueue && (nCheckMethod > 1)) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \w %L", sMenuBody, id, sQueueMsg) else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \d %L", sMenuBody, id, sQueueMsg) if ((nCheckMethod > 0) || (nServer == g_nOwnServer)) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \w %L", sMenuBody, id, "MSG_REFRESH") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \d %L", sMenuBody, id, "MSG_REFRESH") format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9. \w %L", sMenuBody, id, "MSG_BACK") format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0. \w %L", sMenuBody, id, "MSG_CANCEL") } else { if (nCanRedirect == 0) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n1. %L", sMenuBody, id, "MSG_REDIRECT") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_REDIRECT") if (bCanQueue && (nCheckMethod > 1)) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n2. %L", sMenuBody, id, sQueueMsg) else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, sQueueMsg) if ((nCheckMethod > 0) || (nServer == g_nOwnServer)) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n3. %L", sMenuBody, id, "MSG_REFRESH") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, "MSG_REFRESH") format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_BACK") format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL") } g_nLastSelected[id - 1] = nServer show_menu(id, key, sMenuBody, -1, "Detail Menu") } /// <summary>Shows a selection menu with all server categories.</summary> /// <param name="id">The ID of the player to show the category menu to.</param> /// <seealso name="server_menu_select"/> /// <seealso name="sub_menu_select"/> /// <seealso name="show_sub_menu"/> /// <seealso name="show_server_menu"/> public show_category_menu(id) { new bool:bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR) new sMenuBody[MAX_MENUBODY_LEN] new nCategorySetting = get_pcvar_num(cvar_categories) new key = (1<<9) // cancel key is always enabled if (nCategorySetting == 2) key = key | (1<<0) // enable key for the "all categories" if (bColorMenu) { formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SELECT_CATEGORY") if (nCategorySetting == 2) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s\y1. \w%L^n", sMenuBody, id, "MSG_ALL_CATEGORIES") } else { formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SELECT_CATEGORY") if (nCategorySetting == 2) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s1. %L^n", sMenuBody, id, "MSG_ALL_CATEGORIES") } for (new nCategoryIndex = 0; nCategoryIndex < g_nCategoryCount; nCategoryIndex++) { key = key | (1<<(nCategoryIndex + (nCategorySetting - 1))) if (bColorMenu) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s\y%d. \w%s^n", sMenuBody, nCategoryIndex + nCategorySetting, g_saCategories[nCategoryIndex]) else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%d. %s^n", sMenuBody, nCategoryIndex + nCategorySetting, g_saCategories[nCategoryIndex]) } if (bColorMenu) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, "MSG_CANCEL") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL") show_menu(id, key, sMenuBody, -1, "Category Menu") } /// <summary>Shows the server menu page <paramref name="menupage"/> to the the player with ID <paramref name="id"/>.</summary> /// <param name="id">The ID of the player to show the server menu to.</param> /// <param name="menupage">The menu page number to show to the player. Offset is 0.</param> /// <param name="category">The category of servers to show. -1 or unspecified to show all servers regardless of their category.</param> /// <seealso name="server_menu_select"/> /// <seealso name="sub_menu_select"/> /// <seealso name="show_sub_menu"/> /// <seealso name="show_category_menu"/> public show_server_menu(id, menupage, category) { new nServerCount if (get_pcvar_num(cvar_active) == 1) { if (g_nServerCount > 0) { new bool:bSubMenu = (get_pcvar_num(cvar_manual) >= 2) new bool:bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR) new bool:bShowServer new nCheckMethod = get_pcvar_num(cvar_check_method) new sMenuBody[MAX_MENUBODY_LEN] if (bColorMenu) formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SELECT_SERVER") else formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SELECT_SERVER") if (menupage <= 1) nServerCount = 0 else nServerCount = g_naMenuPageStart[id - 1][menupage - 2] new nDisplayNumber = 1 new key = (1<<9) // cancel key is always enabled new nHideDown = get_pcvar_num(cvar_hidedown) if (nHideDown == 1) nHideDown = 3 // the 3 parts of a menu item, third part only displayed with redirect_check_method >= 2 new sMenuNumber[10] new sMenuSrvName[50] new sMenuInfo[50] if (nCheckMethod < 2) sMenuInfo = "" while ((nDisplayNumber < 9) && (nServerCount < g_nServerCount)) { // don't show the server if it's not the own server, the server is not responding and hidedown is set to a value > 2 bShowServer = (!((nHideDown > 2) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer))) // don't show the server if it has the SERVERFLAG_NODISPLAY flag set if (bShowServer) bShowServer = (!(g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY))) // don't show the server if it has the SERVERFLAG_PRIVATE flag set and user is no admin if (bShowServer && (!access(id, MIN_ADMIN_LEVEL)) && (g_naServerPrivate[nServerCount] >= PRIVATE_HIDE)) bShowServer = false // don't show the server if categories are enabled and this server doesn't belong to the currently selected category if (bShowServer && (category >= 0)) bShowServer = (category == g_naServerCategory[nServerCount]) if (bShowServer) { new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServerCount], "") && (g_naServerPublicPassword[nServerCount] == 0) && (!access(id, MIN_ADMIN_LEVEL))) if (bColorMenu) { formatex(sMenuNumber, 9, "\y%d. ", nDisplayNumber) if (bSubMenu) formatex(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount]) else formatex(sMenuSrvName, 49, "\d %s", g_saServerNames[nServerCount]) } else { formatex(sMenuNumber, 9, "%d. ", nDisplayNumber) formatex(sMenuSrvName, 49, " %s", g_saServerNames[nServerCount]) } new bool:bCanRedirect = true sMenuInfo = "" // manual redirection to that server is disabled or server is passworded but password is not public and user has insufficent admin rights if ((nCheckMethod == 2) && (((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NOMANUAL)) || !bCanRedirectByPassword))) { bCanRedirect = false if ((!bColorMenu) && (!bSubMenu)) sMenuNumber = "_. " if (nCheckMethod == 2) { if (bColorMenu) formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) else formatex(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) } } new nFreeSlotsAdmin = (g_naServerMaxPlayers[nServerCount] - g_naServerActivePlayers[nServerCount]) new nFreeSlots = nFreeSlotsAdmin - g_naServerReserveSlots[nServerCount] // normal players can't use the admin slots, so subtract them // server is full? (and player has insufficient rights to join on an admin slot/not enough admin slots?) if ((nCheckMethod == 2) && ((nFreeSlots <= 0) && (!access(id, MIN_ADMIN_LEVEL)) || nFreeSlotsAdmin <= 0)) { bCanRedirect = false if ((!bColorMenu) && (!bSubMenu)) sMenuNumber = "_. " if (bColorMenu) formatex(sMenuInfo, 49, " [%s] \r(\w%d/%d\r)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) else formatex(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) } // server is down if ((nCheckMethod > 0) && (!g_baServerResponding[nServerCount])) { if ((!bColorMenu) && (!bSubMenu)) sMenuNumber = "_. " bCanRedirect = false if (bColorMenu) sMenuInfo = " \r(\wdown\r)" else sMenuInfo = " (down)" } // server is current server if (nServerCount == g_nOwnServer) { if ((!bColorMenu) && (!bSubMenu)) sMenuNumber = "_. " bCanRedirect = false new sMap[MAX_MAP_LEN] get_mapname(sMap, MAX_MAP_LEN - 1) if (bSubMenu && bColorMenu) formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", sMap, get_current_players(), get_maxplayers()) else formatex(sMenuInfo, 49, " [%s] (%d/%d)", sMap, get_current_players(), get_maxplayers()) } // everything's fine, we can redirect here if (bCanRedirect) { if (bColorMenu) { formatex(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount]) if (nCheckMethod > 1) formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) } else { if (nCheckMethod > 1) formatex(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) } key = key | (1<<(nDisplayNumber - 1)) g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount } else if ((bSubMenu) && (nServerCount != g_nOwnServer)) // display server like it was enabled when submenues are enabled if (bColorMenu) { formatex(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount]) if ((nCheckMethod == 0) && (g_baServerResponding[nServerCount])) formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) } // assemble the menu item and append it to menu body format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%s%s%s", sMenuBody, sMenuNumber, sMenuSrvName, sMenuInfo) // if enabled a submenu is always possible to be displayed, regardless of the server's redirection status if (bSubMenu) { key = key | (1<<(nDisplayNumber - 1)) g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount } nDisplayNumber++ } nServerCount++ } if (nServerCount < g_nServerCount) { if (bColorMenu) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\w %L", sMenuBody, id, "MSG_MORE") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_MORE") key = key | (1<<8) } else { if (bColorMenu) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\d %L", sMenuBody, id, "MSG_MORE") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_MORE") } #if CANCEL_IS_BACK_KEY if (bColorMenu) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, (get_pcvar_num(cvar_categories) >= 1) ? "MSG_BACK" : "MSG_CANCEL") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, (get_pcvar_num(cvar_categories) >= 1) ? "MSG_BACK" : "MSG_CANCEL") #else if (bColorMenu) format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, "MSG_CANCEL") else format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL") #endif show_menu(id, key, sMenuBody, -1, "Redirect Menu") } } g_naMenuPageStart[id - 1][menupage - 1] = nServerCount g_naLastMenuPages[id - 1] = menupage } /// <summary>Reloads the servers from server list. Takes care of variable and array reinitialization.</summary> /// <remarks>To be able to rely on this in the future make sure to add an initialization here for all variables you add!</remarks> public srvcmd_reload() { new nCounter // clear all global arrays and variables before reloading for (nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++) { if (g_naServerSockets[nCounter] > 0) { socket_close(g_naServerSockets[nCounter]) g_naServerSockets[nCounter] = 0 } g_naServerIds[nCounter] = -1 g_naServerPorts[nCounter] = 27015 g_naServerActivePlayers[nCounter] = -1 g_naServerMaxPlayers[nCounter] = -1 g_naServerCmdBackup[nCounter] = DEFAULT_CMDBACKUP g_naServerFlags[nCounter] = 0 g_naServerReserveSlots[nCounter] = 0 g_baServerResponding[nCounter] = false g_saServerMap[nCounter] = "" g_saServerNames[nCounter] = "" g_saServerAddresses[nCounter] = "" g_saServerPasswords[nCounter] = "" g_naServerPublicPassword[nCounter] = 0 g_naServerCategory[nCounter] = -1 g_naServerPrivate[nCounter] = PRIVATE_NONE // don't reset these for the own server, as they are only queried once at plugin_postinit() if (nCounter != g_nOwnServer) { g_saServerMod[nCounter] = "" g_naServerProtocol[nCounter] = 0 } } // reset global variables g_nNextAnnounceServer = 0 g_nServerCount = 0 g_nCategoryCount = 0 g_nLastRedirectServer = -1 g_sLastRedirectName = "" g_nOwnServer = -1 g_nRetryCount = 0 for (new nPlrCnt = 0; nPlrCnt < MAX_PLAYERS; nPlrCnt++) { // server IDs might change and thus render all currently saved server IDs invalid, so remove them, to be sure g_nRetryQueue[nPlrCnt][0] = -1 g_nRetryQueue[nPlrCnt][1] = -1 g_nLastServer[nPlrCnt] = -1 g_nLastSelected[nPlrCnt] = -1 } #if defined SQL load_servers_sql() #else load_servers_file() #endif // SQL if (g_nServerCount < 2) log_amx("%L", LANG_SERVER, "MSG_ERROR_NOT_ENOUGH_SERVERS") new sFullAddress[MAX_SERVERADDRESS_LEN] new sTmpServerIP[MAX_IP_LEN] new sTmpServerPort[MAX_PORT_LEN] new sTmpServerAddress[MAX_IP_LEN + MAX_PORT_LEN], sTmpServerAddress2[MAX_IP_LEN + MAX_PORT_LEN] new sTmpOwnAddress[MAX_SERVERADDRESS_LEN] get_cvar_string("net_address", sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1) get_cvar_string("ip", sTmpServerIP, MAX_IP_LEN - 1) get_cvar_string("port", sTmpServerPort, MAX_PORT_LEN - 1) formatex(sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1, "%s:%s", sTmpServerIP, sTmpServerPort) get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1) // determine the own server new nServerCount = 0 while (nServerCount < g_nServerCount) { formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount]) if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerAddress) || equal(sFullAddress, sTmpServerAddress2)) { g_nOwnServer = nServerCount if (g_bUseIds) g_nOwnServerId = g_naServerIds[nServerCount] else g_nOwnServerId = nServerCount } if (g_bUseIds && (g_naServerIds[nServerCount] == -1)) log_amx("%L", LANG_SERVER, "MSG_ID_MISSING", g_saServerNames[nServerCount]) nServerCount++ } if (g_nOwnServer == -1) log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR") // we need to know our own server index to be able to load attributes from SQL - so now we can do that #if defined SQL load_attributes_sql() #endif // SQL // query all servers again nServerCount = g_nOwnServer g_nOwnServer = -1 // make sure the own server is queried too (for its protocol), maybe someone just corrected its address query_servers() g_nOwnServer = nServerCount } /// <summary>This is needed so server doesn't display "unknown command: pickserver". Returning PLUGIN_HANDLED directly in cmd_show_server_menu would supress the chat message so we use this workaround.</summary> public cmd_pickserver(id, level, cid) { cmd_show_server_menu(id, level, cid) return PLUGIN_HANDLED } /// <summary>This function does the actual redirection. It is also what <seealso name="native_redirect"/> is a wrapper for with <paramref name="nServer"/> preset to -1 (the external plugin does not know about our server list and numbers anyway) and <paramref name="bIgnoreSource"/> preset to true (an external plugin does not care whether this would mean redirecting the player back to where he came from).</summary> /// <summary>It is aware of user permissions and has several options which are set via parameters.</summary> /// <param name="id">ID of player to redirect.</param> /// <param name="nServer">Target server, -1 for automatic choosing according to redirect_auto.</param> /// <param name="bCanOther">If nServer is no valid redirect target can we use another server instead?</param> /// <param name="bCanDrop">Drop user if no server was found?</param> /// <param name="bIgnoreSource>"Redirect regardless of redirecting would be back to source server.</param> /// <seealso name="native_redirect"/> /// <seealso name="cmd_redirect_user"/> public redirect(id, nServer, bCanOther, bCanDrop, bIgnoreSource) { new nForwardServer = -1 new bool:bFoundServer = false new nRedirType if (nServer == -1) nRedirType = 1 else nRedirType = 2 new nSourceServer if (bIgnoreSource) { nSourceServer = -1 } else { //TODO: actually we got that information in g_nLastServer[] already, unfortunately in some cases this is set AFTER redirect() is called -> find a solution new sSourceServer[4] // maximum is 999 servers, so we have a maximum of 3 digits get_user_info(id, "xredir", sSourceServer, 3) if (!is_str_num(sSourceServer)) nSourceServer = -1 else nSourceServer = str_to_num(sSourceServer) if ((nSourceServer < 0) || (nSourceServer >= g_nServerCount)) nSourceServer = -1 if (g_bUseIds && (nSourceServer != -1)) nSourceServer = g_naServerIds[nSourceServer] } if ((can_redirect_player(nServer, id, nRedirType, false) > 0) || (nServer == -1)) { if (!bCanOther) { if (bCanDrop) { client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER") client_cmd(id, "disconnect") #if defined STATISTICS stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_DROP, -1) #endif // STATISTICS } return false } nForwardServer = 0 // make sure at least one valid server exists or the second loop could be endless while (nForwardServer < g_nServerCount) { if ((can_redirect_player(nForwardServer, id, nRedirType, false) == 0) && (nForwardServer != nSourceServer)) { bFoundServer = true break } nForwardServer++ } new nAutoMode = get_pcvar_num(cvar_auto) if ((nAutoMode == 1) || (nAutoMode == 3) || (nAutoMode == 5)) // redirect to random server nForwardServer = -1 } else { nForwardServer = nServer bFoundServer = true } if (bFoundServer) { while (nForwardServer == -1) { nForwardServer = random_num(0, g_nServerCount - 1) if ((can_redirect_player(nForwardServer, id, nRedirType, true) > 0) || ((nForwardServer == nSourceServer))) nForwardServer = -1 } new sUserNick[MAX_NAME_LEN] get_user_name(id, sUserNick, MAX_NAME_LEN - 1) if (!equal(g_saServerPasswords[nForwardServer], "")) // set the user's server connect password if needed client_cmd(id, "setinfo ^"password^" ^"%s^"", g_saServerPasswords[nForwardServer]) if (g_bUseIds) client_cmd(id, "setinfo ^"xredir^" ^"%d^"", g_nOwnServerId) else client_cmd(id, "setinfo ^"xredir^" ^"%d^"", g_nOwnServer) new sCheckAddress[MAX_IP_LEN] get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1) new sFullAddress[MAX_SERVERADDRESS_LEN] if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[nForwardServer], ""))) formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[nForwardServer], g_naServerPorts[nForwardServer]) else formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nForwardServer], g_naServerPorts[nForwardServer]) if (nRedirType == 1) client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_SERVER_FULL_REDIRECTING", g_saServerNames[nForwardServer]) client_cmd(id, "Connect %s", sFullAddress) #if defined STATISTICS if (nServer == -1) // is this an automatic redirection (because the target server is random)? stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_AUTO, nForwardServer) #endif // STATISTICS if (g_naServerPrivate[nForwardServer] < PRIVATE_FULLHIDE) // dont' announce anything if this server is set to fullhide, also don't save it as the last target for /follow { if (get_pcvar_num(cvar_show) == 1) { if (get_pcvar_num(cvar_check_method) == 2) client_print(0, print_chat, "%s: %L [%s] (%d/%d)", PLUGIN_TAG, LANG_PLAYER, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer], g_saServerMap[nForwardServer], g_naServerActivePlayers[nForwardServer], g_naServerMaxPlayers[nForwardServer]) else client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer]) if (get_pcvar_num(cvar_follow) == 1) client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOW") } g_nLastRedirectServer = nForwardServer g_sLastRedirectName = sUserNick } } else if (bCanDrop) { client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER") client_cmd(id, "disconnect") #if defined STATISTICS stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_DROP, -1) #endif // STATISTICS } return true } /// <summary>Basically a wrapper for <seealso name="redirect"/> to make it available to other pugins as native.</summary> /// <seealso name="redirect"/> /// <seealso name="cmd_redirect_user"/> public native_redirect(id, nServer, bCanDrop) { redirect(id, nServer, (nServer == -1), bCanDrop, true) return PLUGIN_HANDLED } /// <summary>Show the list of players in current queue.</summary> public cmd_redirect_queue(id, level, cid) { if (!cmd_access(id, level, cid, 1)) return PLUGIN_HANDLED new nSlot for (new nServer = 0; nServer < g_nServerCount; nServer++) { nSlot = 1 client_cmd(id, "echo %s: %s:", PLUGIN_TAG, g_saServerNames[nServer]) for (new nQueueIndex = 0; nQueueIndex < g_nRetryCount; nQueueIndex++) { if (g_nRetryQueue[nQueueIndex][1] == nServer) { new sUserNick[MAX_NAME_LEN] get_user_name(g_nRetryQueue[nQueueIndex][0], sUserNick, MAX_NAME_LEN - 1) client_cmd(id, "echo %s: %d. %s", PLUGIN_TAG, nSlot++, sUserNick) } } } return PLUGIN_HANDLED } /// <summary>Handler for in-game command <paramref name="redirect_user"/>, checks user permissions for this command and uses <seealso name="redirect"/> to do the redirection.</summary> /// <seealso name="redirect"/> /// <seealso name="native_redirect"/> public cmd_redirect_user(id, level, cid) { if (!cmd_access(id, level, cid, 2)) return PLUGIN_HANDLED new nForwardServer = -1 new sName[32] read_argv(1, sName, 31) new nCmdID = cmd_target(id, sName, 8) if (!nCmdID) return PLUGIN_HANDLED // contains destination server number? if (read_argc() > 2) { new argtmp[3] read_argv(2, argtmp, 2) if (is_str_num(argtmp)) nForwardServer = (str_to_num(argtmp) - 1) } redirect(nCmdID, nForwardServer, (nForwardServer == -1), true, true) #if defined STATISTICS stats_redirect(STATS_INFO_REDIRECT, nCmdID, STATS_REDIRTYPE_ADMIN, nForwardServer) #endif // STATISTICS return PLUGIN_HANDLED } /// <summary>Handler for in-game command <paramref name="pickserver"/> or chat command <paramref name="/server"/>. Shows the server menu to the player using <seealso name="show_server_menu"/>.</summary> /// <seealso name="show_server_menu"/> public cmd_show_server_menu(id, level, cid) { if (get_pcvar_num(cvar_manual) >= 1) { #if defined VAULT stats_count(STATS_COUNT_MENU, -1) #endif // VAULT if (get_pcvar_num(cvar_categories) >= 1) show_category_menu(id) else show_server_menu(id, 1, -1) } else client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_MANUAL_DISABLED") return PLUGIN_CONTINUE } /// <summary>Handler for chat command <paramref name="/retry"/>. Adds the user to the retry queue using <seealso name="queue_add"/>.</summary> /// <seealso name="queue_add"/> public cmd_retry(id, level, cid) { if (g_nLastServer[id - 1] > -1) { #if defined STATISTICS stats_redirect(STATS_INFO_RETRY, id, -1, g_nLastServer[id - 1]) #endif // STATISTICS queue_add(id, g_nLastServer[id - 1]) } else client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_NO_LAST") return PLUGIN_CONTINUE } /// <summary>Handler for chat command <paramref name="/stopretry"/>. Removes the user from the retry queue using <seealso name="queue_remove"/>.</summary> /// <seealso name="queue_remove"/> public cmd_stopretry(id, level, cid) { client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE_ALL", g_saServerNames[g_nLastServer[id - 1]]) queue_remove(id, -1) return PLUGIN_CONTINUE } /// <summary>Handler for chat command <paramref name="/follow"/>. Sends a player after the last player that was redirected using <seealso name="redirect"/>.</summary> /// <seealso name="redirect"/> public cmd_follow_player(id, level, cid) { if (get_pcvar_num(cvar_active) == 1) { if (get_pcvar_num(cvar_follow) == 1) { if (g_nLastRedirectServer >= 0) { console_print(id, "%s: %L", PLUGIN_TAG, id, "MSG_REDIRECTING", g_saServerNames[g_nLastRedirectServer]) new sFullAddress[MAX_SERVERADDRESS_LEN] new sCheckAddress[MAX_IP_LEN] get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1) if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[g_nLastRedirectServer], ""))) formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer]) else formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer]) client_cmd(id, "Connect %s", sFullAddress) #if defined STATISTICS stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_FOLLOW, g_nLastRedirectServer) #endif // STATISTICS new sUserNick[MAX_NAME_LEN] get_user_name(id, sUserNick, MAX_NAME_LEN - 1) if (get_pcvar_num(cvar_show) == 1) { if (get_pcvar_num(cvar_check_method) == 2) client_print(0, print_chat, "%s: %L [%s] (%d/%d)", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOWED", sUserNick, g_sLastRedirectName, g_saServerNames[g_nLastRedirectServer], g_saServerMap[g_nLastRedirectServer], g_naServerActivePlayers[g_nLastRedirectServer], g_naServerMaxPlayers[g_nLastRedirectServer]) else client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOWED", sUserNick, g_sLastRedirectName, g_saServerNames[g_nLastRedirectServer]) client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOW") } g_sLastRedirectName = sUserNick } else client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_CANT_FOLLOW") } else client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_FOLLOW_DISABLED") } return PLUGIN_CONTINUE } /// <summary>Event handler for category menu selection.</summary> /// <param name="id">Slot ID of player that selected a menu item.</param> /// <param name="key">Key that was pressed, number between 0 and 9.</param> /// <seealso name="server_menu_select"/> /// <seealso name="show_category_menu"/> /// <seealso name="show_server_menu"/> /// <seealso name="show_sub_menu"/> public category_menu_select(id, key) { if (key < 9) { new nCategorySetting = get_pcvar_num(cvar_categories) new nSelectedCategory = key - (nCategorySetting - 1) g_naLastCategory[id - 1] = nSelectedCategory show_server_menu(id, 1, nSelectedCategory) } else g_naLastCategory[id - 1] = -1 } /// <summary>Event handler for sub menu selection.</summary> /// <summary>When the user presses a number key in the sub menu this handler is called.</summary> /// <param name="id">Slot ID of player that selected a menu item.</param> /// <param name="key">Key that was pressed, number between 0 and 9.</param> /// <seealso name="server_menu_select"/> /// <seealso name="show_server_menu"/> /// <seealso name="show_sub_menu"/> public sub_menu_select(id, key) { new nServer = g_nLastSelected[id - 1] if (key == 0) // redirect { // check if meanwhile the redirection is not possible anymore - if so, refresh the detail menu if (can_redirect_player(nServer, id, 2, false) > 0) show_sub_menu(id, nServer) else { redirect(id, nServer, false, false, true) #if defined STATISTICS stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_MANUAL, nServer) #endif // STATISTICS } } else if (key == 1) // queue { if (is_queued(id, nServer)) { queue_remove(id, nServer) #if defined STATISTICS stats_redirect(STATS_INFO_DEQUEUE, id, -1, nServer) #endif // STATISTICS client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE", g_saServerNames[nServer]) } else { queue_add(id, nServer) } } else if (key == 2) // refresh { show_sub_menu(id, nServer) } else if (key == 8) // go back to where the user was before in main menu show_server_menu(id, g_naLastMenuPages[id - 1], g_naLastCategory[id - 1]) } /// <summary>Event handler for server menu selection.</summary> /// <summary>When the user presses a number key in the server menu this handler is called.</summary> /// <summary>Depending on settings it will display a sub menu or redirect the user.</summary> /// <param name="id">Slot ID of player that selected a menu item.</param> /// <param name="key">Key that was pressed, number between 0 and 9.</param> /// <seealso name="sub_menu_select"/> /// <seealso name="show_server_menu"/> /// <seealso name="show_sub_menu"/> public server_menu_select(id, key) { if (key < 8) { new nServerIdx = g_naServerSelections[id - 1][key] new nManualMode = get_pcvar_num(cvar_manual) // show the detail menu? if (((nManualMode == 2) && (can_redirect_player(nServerIdx, id, 2, false) > 0)) || (nManualMode == 3)) show_sub_menu(id, nServerIdx) else { redirect(id, nServerIdx, false, false, true) #if defined STATISTICS stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_MANUAL, nServerIdx) #endif // STATISTICS } } else { if (key == 8) // "more" button show_server_menu(id, g_naLastMenuPages[id - 1] + 1, g_naLastCategory[id - 1]) #if CANCEL_IS_BACK_KEY if ((get_pcvar_num(cvar_categories) >= 1) && (key == 9)) show_category_menu(id) #endif } } /// <summary>Sends the information query packets to all other servers.</summary> /// <summary>This sends the UDP server information query packets in old and new style HL format to all servers in the list.</summary> /// <summary>Receiving of server data is handled by <seealso name="receive_serverquery_answers"/>.</summary> /// <seealso name="receive_serverquery_answers"/> public query_servers() { new nCheckMethod = get_pcvar_num(cvar_check_method) if (nCheckMethod == 0) return PLUGIN_HANDLED new socket_error new sOldRequest[12] new sNewRequest[26] if (nCheckMethod == 1) { // we don't know what server it is so send both old and new style query formatex(sOldRequest, 8, "%c%c%c%c%s", 255, 255, 255, 255, "ping") formatex(sNewRequest, 5, "%c%c%c%c%c", 255, 255, 255, 255, 105) } else if (nCheckMethod == 2) { // we don't know what server it is so send both old and new style query formatex(sOldRequest, 11, "%c%c%c%c%s", 255, 255, 255, 255, "details") formatex(sNewRequest, 25, "%c%c%c%c%c%s%c", 255, 255, 255, 255, 84, "Source Engine Query", 0) } new nServerCount = 0 new nQuerySocket new nCmdBackup new nSendCount while (nServerCount < g_nServerCount) { if (nServerCount != g_nOwnServer) { nQuerySocket = g_naServerSockets[nServerCount] // first we clear the current receive buffer - we are sending a new request and don't care for old data anymore if (nQuerySocket > 0) { new sEmptyBufferDummy[512] new nEndlessProtection = 0 while ((socket_change(nQuerySocket, 1)) && (nEndlessProtection < 500)) { //log_amx("emptying socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount]) socket_recv(nQuerySocket, sEmptyBufferDummy, 512) nEndlessProtection++ } if (nEndlessProtection >= 500) { socket_close(nQuerySocket) log_amx("WARNING: endless protection triggered for socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount]) } } else { // socket debug //log_amx("opening socket for server %i (%s)", nServerCount, g_saServerNames[nServerCount]) if (!equal(g_saServerLocalAddresses[nServerCount], "")) nQuerySocket = socket_open(g_saServerLocalAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error) else nQuerySocket = socket_open(g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error) // socket debug //log_amx("opened socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount]) } if ((nQuerySocket > 0) && (socket_error == 0)) { g_naServerSockets[nServerCount] = nQuerySocket nCmdBackup = g_naServerCmdBackup[nServerCount] // socket debug //log_amx("sending query on socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount]) if (nCheckMethod == 1) { for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++) socket_send2(nQuerySocket, sOldRequest, 8) for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++) socket_send2(nQuerySocket, sNewRequest, 5) } else if (nCheckMethod == 2) { for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++) socket_send2(nQuerySocket, sOldRequest, 11) for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++) socket_send2(nQuerySocket, sNewRequest, 25) } } else { g_naServerSockets[nServerCount] = 0 log_amx("%L", LANG_SERVER, "MSG_SOCKET_ERROR", socket_error, nServerCount) } } nServerCount++ } set_task(QUERY_TIMEOUT, "receive_serverquery_answers", TASKID_QUERY_RECEIVE) return PLUGIN_HANDLED } /// <summary>Index an incoming UDP data packet.</summary> /// <param name="sData">The raw UDP data string that was received.</param> /// <param name="nDataLen">Length of the raw UDP data string as reported by the socket receive function.</param> /// <param name="sFormatString">The string containing the format. It can contain the elements 124 and s. A digit just declares the number of bytes the element (type) has, "s" declares a string. An opening square bracket declares a byte option followed by a sequence of sub options. The sequence ends with a closing square bracket. Such options can occur more than once but may not be nested.</param> /// <param name="aIndexes">The function stores the resulting character offsets of each index in this array.</param> /// <remarks>This function assumes the given format string is correct as it is only created internally by a programmer, so there is no error checking whatsoever (e.g. an unsupported format character would lead the function into an endless loop).</remarks> /// <returns>The number of indexes that were written (= the number of format elements).</returns> public index_create(sData[MAX_INFO_LEN], nDataLen, sFormatString[100], aIndexes[MAX_INFO_FORMAT]) { //log_amx("---------------------- indexing %s ----------------------", sFormatString) new nFormatPos = 0 // current position within the format array new nIndexPos = 0 // current position within the data array new nDataIndex = 0 // current chracter index within the data stream new nFormatPosMax = strlen(sFormatString) while ((nIndexPos < nFormatPosMax) && (nDataIndex <= nDataLen)) { switch (sFormatString[nFormatPos]) { case '1': // "byte" { //log_amx("indexed byte <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos) aIndexes[nIndexPos] = nDataIndex nDataIndex++ nIndexPos++ } case '2': // "short" { //log_amx("indexed short <%d %d> at %d, element %d, format position %d", sData[nDataIndex], sData[nDataIndex + 1], nDataIndex, nIndexPos, nFormatPos) aIndexes[nIndexPos] = nDataIndex nDataIndex += 2 nIndexPos++ } case '4': // "long" { //log_amx("indexed long <%d %d %d %d> at %d, element %d, format position %d", sData[nDataIndex], sData[nDataIndex + 1], sData[nDataIndex + 2], sData[nDataIndex + 3], nDataIndex, nIndexPos, nFormatPos) aIndexes[nIndexPos] = nDataIndex nDataIndex += 4 nIndexPos++ } case 's': // string { new sDebugString[250] arrayset(sDebugString, 0, 250) copyc(sDebugString, 250, sData[nDataIndex], 0) //log_amx("indexed string <%s> at %d, element %d, format position %d", sDebugString, nDataIndex, nIndexPos, nFormatPos) aIndexes[nIndexPos] = nDataIndex do { nDataIndex++; } while ((sData[nDataIndex] != 0) && (nDataIndex < nDataLen)) // find the end of the string by searching a 0 character nDataIndex++ nIndexPos++ } case '[': // byte switch and start of optional formats { //log_amx("indexed switch <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos) if (sData[nDataIndex] != 1) // skip options { do { nFormatPos++; } while ((sFormatString[nFormatPos] != ']') && (nFormatPos < nFormatPosMax)) //log_amx("skipped optional formats, now at format position %d") } else //log_amx("----------- start of optional formats -----------") aIndexes[nIndexPos] = nDataIndex nDataIndex++ nIndexPos++ } case ']': // end of optional formats { //log_amx("----------- end of optional formats -----------") //nDataIndex++ } default: nDataIndex++ } nFormatPos++ } //log_amx("---------------------- end of indexing ----------------------") //log_amx("%d < %d - %d <= %d", nIndexPos, nFormatPosMax, nDataIndex, nDataLen) return nIndexPos } /// <summary>Gets a byte from the element at the given index.</summary> /// <param name="sData">The raw UDP data string that was received.</param> /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param> /// <returns>The requested byte value.</returns> public index_get_byte(sData[MAX_INFO_LEN], nIndex) { return sData[nIndex] } /// <summary>Gets a short from the element at the given index.</summary> /// <param name="sData">The raw UDP data string that was received.</param> /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param> /// <returns>The requested short value.</returns> public index_get_short(sData[MAX_INFO_LEN], nIndex) { return ((sData[nIndex] << 8) | (sData[nIndex + 1] & 0x00FF)) } /// <summary>Gets a long from the element at the given index.</summary> /// <param name="sData">The raw UDP data string that was received.</param> /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param> /// <returns>The requested long value.</returns> public index_get_long(sData[MAX_INFO_LEN], nIndex) { return ((sData[nIndex] << 24) | (sData[nIndex + 1] << 16) | (sData[nIndex + 2] << 8) | (sData[nIndex + 3] & 0x000000FF)) } /// <summary>Gets a string from the element at the given index.</summary> /// <param name="sData">The raw UDP data string that was received.</param> /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param> /// <returns>The requested string value.</returns> public index_get_string(sData[MAX_INFO_LEN], nIndex) { new aRet[MAX_INFO_LEN] arrayset(aRet, 0, MAX_INFO_LEN) copyc(aRet, MAX_INFO_LEN, sData[nIndex], 0) return aRet } /// <summary>Handler for parsing the answers to server query packet.</summary> /// <summary>This handler parses the UDP information answer packets from the servers that have been queried with <seealso name="query_servers"/>.</summary> /// <seealso name="query_servers"/> public receive_serverquery_answers() { new nCheckMethod = get_pcvar_num(cvar_check_method) new sRcvBuf[MAX_INFO_LEN] new nRcvLen new nRecvCount new sMap[MAX_MAP_LEN] new sMod[MAX_NAME_LEN] new nServerCount = 0 while (nServerCount < g_nServerCount) { if (!g_naServerSockets[nServerCount]) { g_baServerResponding[nServerCount] = false /* should only happen for the g_nOwnServer client_print(0, print_chat, "%s no socket", g_saServerNames[nServerCount]) */ } else { nRecvCount = 0 new nCmdBackup = g_naServerCmdBackup[nServerCount] g_baServerResponding[nServerCount] = false new nSocket = g_naServerSockets[nServerCount] while (socket_change(nSocket, 1) && (nRecvCount <= nCmdBackup)) { // socket debug //log_amx("socket changed: %i (%s)", nSocket, g_saServerNames[nServerCount]) nRecvCount++ // initialize our receive buffer setc(sRcvBuf, MAX_INFO_LEN, 0); // socket debug //log_amx("receiving from socket: %i (%s)", nSocket, g_saServerNames[nServerCount]) nRcvLen = socket_recv(nSocket, sRcvBuf, MAX_INFO_LEN) // socket debug //log_amx("finished receiving from socket %i (%s), received %i bytes", nSocket, g_saServerNames[nServerCount], nRcvLen) //TODO: handle fragmented packets if (nRcvLen > 5) // shortest reply is a ping response with length of 6 { if (nCheckMethod == 1) { // ping response if (equal(sRcvBuf, {-1,-1,-1,-1,'j'}, 5)) { g_baServerResponding[nServerCount] = true break } } else if (nCheckMethod == 2) { new aIndexes[MAX_INFO_FORMAT] if (equal(sRcvBuf, {-1,-1,-1,-1}, 4)) { g_baServerResponding[nServerCount] = true if (sRcvBuf[4] == 'm') // old HL1 or "goldsource" protocol { index_create(sRcvBuf, nRcvLen, A2S_INFO_GOLD_REPLY_FORMAT, aIndexes) copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_GOLD_IDX_MAP]], 0) g_saServerMap[nServerCount] = sMap copyc(sMod, MAX_NAME_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_GOLD_IDX_GAMEDIR]], 0) g_saServerMod[nServerCount] = sMod g_naServerProtocol[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_VERSION]) g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_NUMPLAYERS]) g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_MAXPLAYERS]) if (get_pcvar_num(cvar_countbots) == 0) { if (index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_ISMOD]) == 1) g_naServerActivePlayers[nServerCount] -= index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_MOD_NUMBOTS]) else g_naServerActivePlayers[nServerCount] -= index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_NUMBOTS]) } } else if (sRcvBuf[4] == 'I') // source protocol { index_create(sRcvBuf, nRcvLen, A2S_INFO_SOURCE_REPLY_FORMAT, aIndexes) copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_SOURCE_IDX_MAP]], 0) g_saServerMap[nServerCount] = sMap copyc(sMod, MAX_NAME_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_SOURCE_IDX_GAMEDIR]], 0) g_naServerProtocol[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_VERSION]) g_saServerMod[nServerCount] = sMod g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_NUMPLAYERS]) g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_MAXPLAYERS]) if (get_pcvar_num(cvar_countbots) == 0) { g_naServerActivePlayers[nServerCount] -= index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_NUMBOTS]) } } } } } } /* if (nRecvCount == 0) log_amx("no change on socket %i (%s)", g_naServerSockets[nServerCount], g_saServerNames[nServerCount]) */ //socket_close(nSocket) //g_naServerSockets[nServerCount] = 0 } nServerCount++ } if (get_pcvar_num(cvar_retry) > 0) { // now search for players who queued themselves to be redirected new nServer new nPlrCnt = 0 while (nPlrCnt < g_nRetryCount) { nServer = g_nRetryQueue[nPlrCnt][1] if (nServer > -1) // just to be sure { new nPlr = g_nRetryQueue[nPlrCnt][0] if (can_redirect_player(nServer, nPlr, 2, false) == 0) { console_print(nPlr, "%s: %L", PLUGIN_TAG, nPlr, "MSG_RETRY_SUCCESS") #if defined STATISTICS stats_redirect(STATS_INFO_REDIRECT, nPlr, STATS_REDIRTYPE_QUEUED, nServer) #endif // STATISTICS redirect(nPlr, nServer, false, false, true) g_naServerActivePlayers[nServer]++ } } nPlrCnt++ } } return PLUGIN_HANDLED } /// <summary>Retrieves number of bots currently on the server.</summary> /// <returns>Number of bots currently on server.</returns> public get_bot_count() { new nBotCount = 0 new const nMaxPlayers = get_maxplayers() for (new nCount = 0; nCount <= nMaxPlayers; ++nCount) { // "We don't really support get_players() with flags anymore. It was a bad idea and if it was our choice, it would have never been added to the original AMX Mod." - BAILOPAN (http://www.amxmodx.org/funcwiki.php?go=func&id=174) // ok, so instead of using get_players() with flag c we will rather check each player with is_user_bot() if (is_user_bot(nCount)) nBotCount++ } return nBotCount } /// <summary>Retrieves number of admins currently on the server.</summary> /// <returns>Number of admins currently on server.</returns> public get_admin_count() { new nPlayers[MAX_PLAYERS] new nPlayerNum, nPlayerCount get_players(nPlayers, nPlayerNum, "ch") new nAdmins = 0 for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { if (access(nPlayers[nPlayerCount], MIN_ADMIN_LEVEL)) nAdmins++ } return nAdmins } /// <summary>Retrieves number of current players being aware of the redirect_countbots CVAR.</summary> /// <returns>The number of current players being aware of the redirect_countbots CVAR.</returns> public get_current_players() { new nCurrentPlayers = get_playersnum(1) if (get_pcvar_num(cvar_countbots) == 0) nCurrentPlayers -= get_bot_count() return nCurrentPlayers } /// <summary>Event handler for client disconnect event.</summary> /// <summary>This handler makes sure people that have been in queue while disconnecting are removed from it.</summary> /// <summary>Furthermore it resets the "last server" information for this now empty player slot.</summary> /// <param name="id">Slot ID of player that was disconnected.</param> public client_disconnect(id) { queue_remove(id, -1) g_nLastServer[id - 1] = -1 } /// <summary>Event handler for client authorized event.</summary> /// <summary>This handler is called as soon as a connecting client was authenticated with WON/Steam system and received a WON/Steam ID.</summary> /// <summary>It is used in favor of client_connected(), because here the client already logged in to AMXX user system and it can be determined whether the user is an admin, which is not the case for client_connected() event.</summary> /// <param name="id">Slot ID of player that was authorized.</param> public client_authorized(id) { if (is_user_bot(id) || is_user_hltv(id)) return PLUGIN_CONTINUE if ((g_nOwnServer == -1) && (!g_bInitialized)) { plugin_postinit() } g_naLastMenuPages[id - 1] = 1 g_naLastCategory[id - 1] = -1 new nAutoMode = get_pcvar_num(cvar_auto) if (get_pcvar_num(cvar_active) == 1) { if (nAutoMode > 0) { if (((get_maxplayers() - get_playersnum(1)) == 0) || (nAutoMode > 2)) { if (g_nServerCount > 0) { new bool:bLocalPriority = false // if local slot reservation is enabled we need to check whether this is a local player if (get_pcvar_num(cvar_localslots) == 1) { new sCheckAddress[MAX_IP_LEN] get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1) if (is_local_address(sCheckAddress)) bLocalPriority = true } new nMaxAdmins = get_pcvar_num(cvar_maxadmins) if (nMaxAdmins == 0) nMaxAdmins = MAX_PLAYERS new bool:bRedirect = false // to keep some better overview assemble the if-comparison part by part in bRedirect // redirect if automode is 1 or 2, user is no admin or is admin but there are no admin slots (disabled or max admin slots in use already) bRedirect = bRedirect | (((nAutoMode == 1) || (nAutoMode == 2)) && ((!access(id, MIN_ADMIN_LEVEL)) || (get_pcvar_num(cvar_adminslots) == 0) || (get_admin_count() > nMaxAdmins))) // redirect if automode is 3 or 4 and user is no admin bRedirect = bRedirect | (((nAutoMode == 3) || (nAutoMode == 4)) && (!access(id, MIN_ADMIN_LEVEL))) // redirect if automode is 5 or 6 bRedirect = bRedirect | ((nAutoMode == 5) || (nAutoMode == 6)) if (g_bDebug) { new sPlayerName[MAX_NAME_LEN] get_user_name(id, sPlayerName, MAX_NAME_LEN - 1) log_amx("Auto-redirect check for <%s> (%d), auto-redirect: %s, automode: %d, local priority: %s, admin: %s, admin slots: %s, admins/max: %d/%d, current players/bots/max: %d/%d/%d", sPlayerName, id, bRedirect ? "yes" : "no", nAutoMode, bLocalPriority ? "yes" : "no", access(id, MIN_ADMIN_LEVEL) ? "yes" : "no", (get_pcvar_num(cvar_adminslots) == 1) ? "yes" : "no", get_admin_count(), nMaxAdmins, get_playersnum(1), get_bot_count(), get_maxplayers()) } if (bRedirect) { //TODO: code in many parts redundant to what the redirect() function does except for the local-priority stuff - rather extend the redirect() function if (bLocalPriority) { // find the remote user that is connected for the shortest time and redirect him new nPlayers[MAX_PLAYERS] new nPlayerNum, nPlayerCount new nMinConnectedTime = 0x7FFFFFFF // make sure the first time value found will always be lower new nMinTimePlayer = -1 new nUserTime get_players(nPlayers, nPlayerNum, "ch") new nCurID new sCheckPlayerAddress[MAX_IP_LEN] for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { nCurID = nPlayers[nPlayerCount] get_user_ip(nCurID, sCheckPlayerAddress, MAX_IP_LEN - 1, 1) nUserTime = get_user_time(nCurID) if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL)) && (!is_local_address(sCheckPlayerAddress))) { nMinTimePlayer = nCurID nMinConnectedTime = nUserTime } } if (nMinTimePlayer >= 0) { client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORLOCAL") redirect(nMinTimePlayer, -1, true, true, true) return PLUGIN_CONTINUE } else if (g_bDebug) log_amx("no valid redirect target to free up slot for local player %i", id) } else { redirect(id, -1, true, (nAutoMode < 3), false) return PLUGIN_CONTINUE } } else { // find the user that is connected for the shortest time and redirect him away new nPlayers[MAX_PLAYERS] new nPlayerNum, nPlayerCount new nMinConnectedTime = 0x7FFFFFFF new nMinTimePlayer = -1 new nUserTime get_players(nPlayers, nPlayerNum, "ch") new nCurID for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { nCurID = nPlayers[nPlayerCount] nUserTime = get_user_time(nCurID) if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL))) { nMinTimePlayer = nCurID nMinConnectedTime = nUserTime } } if (nMinTimePlayer >= 0) { client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORADMIN") redirect(nMinTimePlayer, -1, true, true, true) return PLUGIN_CONTINUE } else if (g_bDebug) log_amx("no valid redirect target to free up slot for admin %i", id) } } } else { if (g_bDebug) { new sPlayerName[MAX_NAME_LEN] get_user_name(id, sPlayerName, MAX_NAME_LEN - 1) log_amx("Not auto-redirecting <%s> (%d), automode: %d, current players/bots/max: %d/%d/%d", sPlayerName, id, nAutoMode, get_playersnum(1), get_bot_count(), get_maxplayers()) } } } } new sSourceServer[4] // maximum is 999 servers, so we have a maximum of 3 digits get_user_info(id, "xredir", sSourceServer, 3) if (strcmp(sSourceServer, "") != 0) { new nSourceServer = str_to_num(sSourceServer) // show the welcome message delayed to that player new sID[1] sID[0] = id set_task(20.0, "welcome_message", 0, sID, 1) if (g_bUseIds && (nSourceServer != -1)) g_nLastServer[id - 1] = g_naServerIds[nSourceServer] else g_nLastServer[id - 1] = nSourceServer #if defined VAULT stats_count(STATS_COUNT_REDIRECTED, g_nLastServer[id - 1]) #endif // VAULT if (g_bDebug) log_amx("saved last server for player %i as server %i", id, g_nLastServer[id - 1]) if ((nSourceServer >= 0) && (nSourceServer < g_nServerCount)) { if (get_pcvar_num(cvar_show) == 1) { new nPlayers[MAX_PLAYERS] new nPlayerNum, nPlayerCount, nCurrentPlayer new sConnectNick[MAX_NAME_LEN] get_user_name(id, sConnectNick, MAX_NAME_LEN - 1) get_players(nPlayers, nPlayerNum, "c") set_hudmessage(000, 100, 255, get_pcvar_float(cvar_announce_alivepos_x), get_pcvar_float(cvar_announce_alivepos_y), 0, 0.0, 10.0, 0.5, 0.10, 1) for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { nCurrentPlayer = nPlayers[nPlayerCount] client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECT_RECEIVE", sConnectNick, g_saServerNames[nSourceServer]) } } } client_cmd(id, "setinfo ^"xredir^" ^"^"") client_cmd(id, "setinfo ^"password^" ^"^"") set_task(10.0, "reset_info", 0, sID, 1) } return PLUGIN_CONTINUE } /// <summary>This function shows a message to the player that has connected, to tell him that he was redirected and how he can use /retry to get back (if so).</summary> /// <summary>welcome_message is called with a set_task to show the welcome message delayed, so that the player has usually already chosen a team and his screen is clear to read it.</summary> /// <summary>This message is only displayed to players that have been redirected from another server in the chain. If redirect_retry is enabled, it also tells the player</summary> /// <summary>that he can use /retry command to have himself queued to redirect back to the source server.</summary> /// <param name="id">The slot ID of the player that should have the welcome message displayed. It is passed as array, because it is called with set_task.</param> public welcome_message(id[]) { new nID = id[0] if (is_user_connected(nID)) // make sure the player didn't already disconnect within the set_task delay { new nLastServer = g_nLastServer[nID - 1] if ((nLastServer >= 0) && (nLastServer != g_nOwnServer) && (nLastServer < MAX_SERVERFORWARDS)) { new sAnnounceText[MAX_WELCOME_LEN] formatex(sAnnounceText, MAX_WELCOME_LEN - 1, "%L", nID, "MSG_REDIRFROM", g_saServerNames[g_nOwnServer], g_saServerNames[nLastServer]) if ((get_pcvar_num(cvar_retry) == 1) && (get_pcvar_num(cvar_show) == 1)) format(sAnnounceText, MAX_WELCOME_LEN - 1, "%s^n%L", sAnnounceText, nID, "MSG_RETRY_BACK_ANNOUNCE") set_hudmessage(000, 100, 255, -1.0, -1.0, 0, 0.0, 10.0, 0.5, 2.0, 1) show_hudmessage(nID, sAnnounceText) } } } #if defined SQL public sql_connect() { hSqlInfo = SQL_MakeStdTuple() hSql = SQL_Connect(hSqlInfo, nSqlError, sSqlError, MAX_SQL_ERROR_LEN-1) if (hSql == Empty_Handle) { log_amx("%s %L", PLUGIN_TAG, LANG_SERVER, "SQL_CANT_CON", sSqlError) } } public sql_disconnect() { SQL_FreeHandle(hSql) SQL_FreeHandle(hSqlInfo) } #endif // SQL #else /// <summary>Dummy handler to catch the case where a user tried to compile the plugin with a too old compiler.</summary> public plugin_init() { log_amx("ERROR: Your AMXX version is too old for this plugin. You need at least version 1.80", ) } #endif Hi are sql connection but i not see we i can put sql data ? Meybi sambady can edit plugin ? Przeczytaj cały wpis Odnośnik do komentarza Udostępnij na innych stronach Więcej opcji udostępniania...
Rekomendowane odpowiedzi