/* xREDIRECT - redirect menu plugin - İ 2006-2009 x0R (xor@boxxor.net) - www.n-ice.org Original file: xredirect.sma/xredirect.amxx License: ŻŻŻŻŻŻŻŻ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA In addition, as a special exception, the author gives permission to link the code of this program with the Half-Life Game Engine ("HL Engine") and Modified Game Libraries ("MODs") developed by Valve, L.L.C ("Valve"). You must obey the GNU General Public License in all respects for all of the code used other than the HL Engine and MODs from Valve. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. Description/Features: ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ First of all, if you are too lazy to read all this don't bother me with problems or questions! The plugin does several things that all can be turned on or off seperately by CVAR's: - on startup it reads the available servers from SERVERFILE ("amxmodx/config/serverlist.ini" by default), see next section for an example - saying /server shows a list of available servers (if redirect_manual > 0) - people can choose a number from the list and are immediately sent to that server - when the server is full (one free slot left, that is) people are automatically forwarded to a random server from the list - redirect_auto enables or disables this - when a server from the list is full or down the server is disabled in the menu and players are not redirected there automatically - to be able to check whether a server is down redirect_check_method must be > 0 and to check whether it is full redirect_check_method must be > 1 - the servers are announced every redirect_announce seconds - set to 0 to turn announcements off; the server list is shown as HUD message and for living players displayed at the top and for dead players displayed somewhere below the top so it is not covered by the "spectator bars"; how much information the announcements include depends on redirect_check_method - depending on redirect_check_method servers can be checked for being down/full or even current map, number of current players and maximum players can be displayed in the menu and in the announcements - when no server is available for automatic redirection the player is just dropped with an appropriate message - when someone is redirected either manually or automatically this is shown to the other players telling who was redirected and to which server - it is also announced that people can say /follow to follow this player to the server and they are redirected as well - both the announcements and the follow feature can be enabled or disabled by CVAR (redirect_follow) - the plugin is language aware (thus you need to place the xredirect.txt in amxmodx/data/lang/) - the server can show whether someone that just connects was redirected to the server and from what server he is coming from - the own IP address is detected automatically and disabled in the server list - automatic detection does not work if you use DNS names in the SERVERFILE - in this case set the DNS address of the own server in redirect_external_address for the detection to work - detecting the own server is NECESSARY for the plugin to work correctly - with CVAR redirect_retry set to 1 the server can put people into a retry queue to be redirected back to the last server (e.g. when they were automatically redirected but only want to play on the server they connected to) Server List File: ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ The file is in ini format. The section name is the server name. The following keys are recognized: - address = server address (can be IP or DNS name) - port = server port - a value between 1025 and 65536, default 27015 - cmdbackup = defines how often the UDP request is resent to the server (with redirect_check_method > 0), default 2 - noauto = overrides the redirect_auto setting for this server, default is redirect_auto - nomanual = if set to 1, users can't manually redirect themselves to this server - nodisplay = if this is set to 1 it will hide the server from the /server list and announcements, default 0 - adminslots = if this is set to 1 the plugin will redirect only people with reserved slot there if it's e.g. 12/13 players on the target server, default 0 - password = the password that is needed to connect to the server, default - publicpassword = if set to 1, all players can connect to passworded servers, when set to 0 only admins, default 0 If a value is not specified the default value is used. The "address" key always must be specified and doesn't have a default value Here is an example how the server file could look like: /ŻŻŻŻŻŻŻŻŻŻ serverlist.ini ŻŻŻŻŻŻŻŻŻŻŻŻ\ [my example server] address=example.n-ice.org localaddress=192.168.0.3 port=27015 cmdbackup=5 noauto=1 nomanual=0 nodisplay=0 [my 2nd example server] address=example2.n-ice.org port=27015 \______________________________________/ I recommend that all servers have the same SERVERFILE. This is not necessary with redirect_show 0 but it's still better, because it could confuse users when not all servers are in the same place in the menu on every server. Please be aware that when using more than 6 servers in SERVERFILE you have to change the define MAX_SERVERFORWARDS and recompile the plugin. If there are more servers in the file than specified by MAX_SERVERFORWARDS the other servers will be ignored. Available CVAR's: ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ redirect_active - 1/0 activate/deactivate redirect plugin - when this is set to 0 all other CVAR's are ignored, default 0 redirect_auto - 0 = disable automatic redirection - 1 = only redirect when server is full, redirect to random server - 2 = only redirect when server is full, redirect to next server in list - 3 = always redirect except admins, redirect to random server - 4 = always redirect except admins, redirect to next server in list - 5 = always redirect including admins, redirect to random server - 6 = always redirect including admins, redirect to next server in list redirect_manual - controls behaviour of manual redirect menu: - 0 = disabled - 1 = selecting a server in main menu directly redirects there (if possible) - 2 = selecting a server in main menu directly redirects there, or if not possible brings up a sub menu with detail information, the reason why redirection does not work and a retry option (if redirect_retry 1) redirect_follow - 1/0 enable/disable following players with /follow to a server they were redirected to - people can still use /server to follow a player though, default 0 redirect_external_address - own external server address - only needed when you use DNS names instead of IPs in SERVERFILE - this must match the name in SERVERFILE - include the port! redirect_check_method - check the servers in the list - 0 = no checks, 1 = ping only(to check whether a server is down), 2 = check active players and max. players as well, default 0 redirect_announce - announce server list with stats (depends on redirect_check_method) in center every redirect_announce seconds - set to 0 for off, default 60 redirect_announce_mode - control who announcements are displayed for: 1 = alive players , 2 = dead players, 3 = both redirect_announce_alivepos_x - the vertical position of the announcements displayed to living people, default -1.0 redirect_announce_alivepos_y - the horizontal position of the announcements displayed to living people, default 0.01 redirect_announce_deadpos_x - the vertical position of the announcements displayed to living people, default -1.0 redirect_announce_deadpos_y - the horizontal position of the announcements displayed to living people, default 0.35 redirect_show - 1/0 enable/disable redirection information in chat area, default 1 redirect_adminslots - 1/0 enable/disable adminslots - when set to 1 people are redirected off the server when someone with a reserved slot connects, default 0 redirect_retry - 1/0 enable/disable retry queue feature - when set to 1 players can say /retry and are redirected as soon as a slot on the target server is free redirect_hidedown - control hiding of servers that are down (not responding): 0 = don't hide, 1 = hide in menu, 2 = hide in announcements, 3 = hide in menu and announcements - has no effect with redirect_check_method 0, default 0 redirect_localslots - 1/0 enable/disable slot reserving for local players - remote players are redirected off the server when a local player connects Advanced users should also check out the defines for more options, especially QUERY_INTERVAL could be interesting. Min. Requirements: ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ - Metamod v1.18 - HLDS v3.1.1.1 - AMXX v1.70 Modules: ŻŻŻŻŻŻŻŻŻ The plugin requires the modules engine and sockets to be loaded. You can enable them in your modules.ini. If you don't want to load the sockets module search for the line containing require_module("sockets") and comment it out or delete it. When doing this you can only use redirect_check_method 0. If you set this to something different your server might crash or other problems arise. Known issues: ŻŻŻŻŻŻŻŻŻŻŻŻŻ #1 some people report crashes with CS 1.6 and redirect_check_method 1/2 if the server being checked is on the same IP (only different port) so set to 0 if your server hangs after some time #2 as the length of menu items is limited don't specify too long server names - if an item is too long it is just truncated Changelog: ŻŻŻŻŻŻŻŻŻŻ Note: The first version that was released to the public was v0.3. v0.1 and v0.2 were only running on my servers for some time before I started the next version. v0.1: - reads the available servers from a config file - people can show a server menu by saying /server - automatic server forwarding to the next server in the list when server is full - announcing of redirection for other players on the server - people can say /follow to follow the last forwarded player - when redirect_external_address is set the own address is automatically detected v0.2: - external address is automatically detected without having redirect_external_address set, for DNS names redirect_external_address is still needed to be set though - introduced CVAR redirect_check_method where 0 = disabled 1 = the servers in the list are pinged every QUERY_INTERVAL seconds to check whether they are online 2 = the servers in the list are queried for actual and maximum players and map every QUERY_INTERVAL seconds - depending on redirect_check_method the menu displays: 0 = own server as disabled, others as available in format: server name (server address) 1 = own server as disabled, others as available in format "server name (server address)" or down in format "server name (server address) (down)" 2 = own server as disabled, others as available in format "server name [current map] (active players/max. players)" or down in format "server name (server address) (down)" - servers that are down are displayed as down in the list and people cannot have themselves redirected there (disabled in menu) - random automatic forward instead of choosing the next free server from the list - the server doesn't forward to servers considered being down when server is full - if no server is available (due to being down) players are not redirected and have a message displayed that they couldn't be forwarded v0.3 - for redirect_check_method 2: - no automatic redirection to full servers anymore (active players = maximum players) - full servers are displayed in the menu with red brackets surrounding the player numbers and can't be selected - the plugin is now language system aware - introduced CVAR redirect_send_tag where 0 = disabled 1 = when redirecting it prepends [R] to people's names with being the own server number in SERVERFILE indicating to the receive server that this player was redirected from this server - introduced CVAR redirect_receive_tag where 0 = disabled 1 = when someone connects with prepended [R] to his name the server will show a message that the player was redirected from that server and remove the tag from the name - introduced CVAR redirect_announce: The value of this CVAR can be 0 for turning announcements of. If set to a higher floating point value this is the time in seconds how often the announcements are shown. - made status detection more stable by sending more UDP packets - made status detection more stable through a completely new code for receive handling - several small optimizations and bug fixes here and there I don't recall in detail :P v0.4 - modified default messages for connect and team joining so that they display player names without redirect tags if redirect_receive_tag = 1 - modified the name change code to be more reliable - HL1 steam server status querying is now working - this should make status information work for all HL1 mods running on Steam - plugin now uses the much faster cvar querying introduced with AMXX 1.70 - load routine now checks whether there are more servers in the file than can be loaded v0.4.1 - name changes from tagged names are now hidden as well - this means with redirect_receive_tag = 1 the complete procedure of tagging is hidden v0.4.7 - fixed a bug where automatic redirection didn't work with redirect_check_method 0 - fixed a bug where the PLUGIN_TAG was missing in the message MSG_NO_REDIRECT_SERVER - fixed many messages that were still displayed with server's default language - fixed code to display announcement in different height for dead and alive players - introduced CVAR redirect_announce_mode where 1 = announcements while playing 2 = announcements while dead/spectator 3 = both default is 3 - only effective when redirect_announce > 0 v0.4.8 - added code to require_module sockets - introduced cvar redirect_version for external version check v0.5.0 - fixed an error that crashed the server when redirecting automatically v0.6.0 - sorted out several unused variables - redirect_auto 2 redirects to next server in list (1 = random server, like before) - added new format for SERVERFILE - it is now an ini file - server status (or ping) request should be more stable due to added cmdbackup of 2 (2 additional request packets are sent) - the default cmdbackup value can be overridden in SERVERFILE for each server - redirect_manual can be overriden in SERVERFILE for each server - redirect_auto can be overriden in SERVERFILE for each server - servers in the list can be hidden (from list + announcements) with a key in SERVERFILE - added server command redirect_reload which will make the plugin reload the SERVERFILE v0.6.3 - changed UDP timeout value to default with hope to fix crash problem with redirect_check_method > 0 - sockets are now initialized to 0 after freeing them - fixed a bug where second server was displayed as being down - fixed a bug where for first server the map of the second server was displayed - changed default QUERY_INTERVAL to 20 - changed UDP receive code to be faster and handle responses more secure and reliable v0.7.0 - UDP timeout value change didn't help and was changed back to 1 - fixed the "unknown command: pickserver" message when using pickserver command - removed nick tagging - server now displays where someone was redirected from without nick tagging - removed text message hooks as they are not needed anymore - removed CVAR redirect_send_tag - removed CVAR redirect_receive_tag - servers are not redirecting back to source servers anymore, thus preventing an endless loop - fixed a bug where redirect_auto 2 wouldn't choose the next but the first server in list - introduced CVAR redirect_show where 0 = disabled 1 = show redirection information in chat area when someone was redirected (default) v0.7.5 - removed support for AMXX versions older than 1.70 to cleanup code - added command redirect_announce_now which will immediately display server announcement to all players - introduced CVAR redirect_announce_alivepos_x - introduced CVAR redirect_announce_alivepos_y - introduced CVAR redirect_announce_deadpos_x - introduced CVAR redirect_announce_deadpos_y - menues now don't have colors anymore when the mod does not support coloured menues - added command redirect_user which can redirect a user - added native redirect(id, nServer) which can be called by other plugins - sentence "say /server..." is no longer displayed in announcement if redirect_manual is set to 0 v0.8.0 - introduced CVAR redirect_adminslots - plugin will now redirect another user to free up a slot when someone with a reserved slot connects - improved detection for coloured menues - servers in server list can have a new setting "adminslots" to tell whether they have adminslots - plugin will not allow to redirect manually anymore when there is only one free slot left on the target server, except when the target server has an admin slot and the player to be redirected has reservation flag - native redirect function got a new parameter to tell whether people are dropped when no valid target server is found v0.8.2 - setting redirect_announce to 0 will now stop the announcements from being displayed immediately - fixed a bug where announcements were displayed although redirect_announce is set to 0 - plugin will not allow to redirect manually anymore when there is only one free slot left on the target server, except when the target server has an admin slot and the player to be redirected has reservation flag [should work now] v0.8.4 - introduced CVAR redirect_maxadmins - with this the maximum number of admins can be limited - if the maximum number of connected admins is reached the plugin acts like there were no admin slots - fixed a bug where server parameters in server list beginning with "no" were always interpreted as "1" v0.9.0 - added functionality for splitting up servers on several selection pages - selection menu can now handle up to 999 servers - server announcements now cycle through servers with same grouping like menu if servers are more than 8 - redirection to passworded servers is now possible, either for admins or even normal players - introduced serverlist option "password" which sets the connect password needed for this server - introduced serverlist option "publicpassword": 0 = password not public, only admins can have themselves redirected to this server (if passworded) 1 = password is public, all players can have themselves redirected to this server (if passworded) - optimized performance of redirect_reload - changed some messages that were displayed in console to be displayed in chat area - added a retry queue people can add themselves to with /retry command to be redirected to the server they came from - added command /stopretry to drop out of the retry queue - added many messages and error messages - moved some initialization code from client_putinserver to plugin_cfg to increase performance - added debug messages (english only) that will show in server log when plugin is running in debug mode - fixed a bug where message "player has been redirected to server..." was displayed to all others in the language of the player that has been redirected - fixed a bug where message "player has been redirected here from..." was displayed to all others in the language of the player that has been redirected - added welcome message when a player was redirected from another server - added announcement that the player can use /retry (displayed only if redirected_retry 1 and redirect_show 1) v0.9.1 - fixed a bug where the plugin would try to display a welcome message even if the player was not redirected, causing an error message in AMXX log v1.0 - changed plugin name/tag to xREDIRECT and plugin file to xredirect.sma/xredirect.amxx, renamed dictionary file to xredirect.txt, renamed include file to xredirect.inc - added a missing message to tell people when the own server was not detected - added internal option MIN_ADMIN_LEVEL to define the level that is needed for a player to be treated as admin - introduced cvar redirect_hidedown to control hiding of offline servers - added a message that tells the player when he was redirected to free up a slot for an admin - added a new sub menu that shows detail information about a server including the reason why redirection is not possible to this server - by using the sub menu players can now retry all servers, not only the last - added additional modes for redirect_auto: - 3 = always redirect except admins, redirect to random server - 4 = always redirect except admins, redirect to next server in list - 5 = always redirect including admins, redirect to random server - 6 = always redirect including admins, redirect to next server in list - added additional modes for redirect_manual: - 2 = show a sub menu when player can't be redirected to server - 3 = always show a sub menu from which the player can choose to be redirected (if possible) - added server option localaddress - the plugin now detects local players and sends them to localaddress=, while remote players are still sent to address= - fixed an issue that caused wrong menu numbering under certain circumstances (not very likely to happen though) - completely rewrote menu creation code for better code readability and modularity - changed menu coloring to be more straight forward - queue functionality is not limited to the last server of a player anymore - a player can even queue himself for more than one server at a time! - several internal code structure improvements - introduced cvar redirect_localslots: controls slot reserving for local players - remote players are redirected off the server when the server is full and a local player connects - for the menu without colors inactive menu entries are now displayed as being deactivated by putting a "_" instead of the number in front of the menu entry - added XMLDoc documentation to source code - removed client_putinserver() handler and integrated into client_authorized() - fixed a bug where always on first startup with ./hlds_run the plugin could not detect the own server - fixed an error with an internal array overflow on servers with 32 maximum players - fixed a bug where admins would not be able to have themselves redirected to a full server - removed server address from server name in annoucements - if someone wants this he can still add it to the server name - annoucements now display server information for own server - server menu now displays server information for own server - fixed a bug where menu displayed incorrectly with redirect_check_method 0 - fixed a bug where redirecting was not possible with redirect_check_method 0 - fixed an error in the log message for socket errors - completely rewritten redirection decision and menu build code for better maintainability - fixed a still remaining error on servers with 32 slots - changed menu to use integrated redirect function instead of direct code for better overall stability - queue option is now no longer available with redirect_check_method < 2 - refreshing a sub menu is no longer possible with redirect_check_method 0 (doesn't make sense) except for the current server - fixed a bug where the plugin would not automatically redirect to a plugin that has the NOMANUAL flag set - fixed a bug where a server would be displayed as having (-1/-1) players when the server is down and sub menues are enabled - fixed a bug where the sub menu would display a servers as "server full" when it is down - sub menu: admins can now also see the reason why redirecting is not possible to a server for non-admins, but in white instead of red - the welcome message can no longer state that someone came from the server he currently is connected to - fixed a bug where completely wrong information is shown in the menu when sub menues are enabled and redirect_check_method is set to 1 - automatic redirection didn't take MIN_ADMIN_LEVEL as admin level but REDIRECT_RESERVATION - readded the message "Server full, redirecting you to..." in the console of a player that is auto redirected (it got lost over the update from 0.7.5 to 0.8.0) - fixed a bug where the own server always would be hidden from the menu/announcements when redirect_hidedown is enabled - slight speed optimization in socket query code - fixed an array error that occurs when a player would disconnect within 20 seconds after connecting - HLTVs will no longer be automatically redirected v1.0.3 - added debug messages for autoredirect decisions the plugin makes - players with a local address were always redirected to the server local address parameter, even when it's empty - completely rewritten socket query code - plug-in now supports both old HL1 and new source protocol v1.0.3.1 - added workaround for "Server tried to send invalid command" issue, making redirection work again v1.0.3.2 - once more added workaround for "Server tried to send invalid command" issue, making redirection work again */ // includes #include #include #include // plugin defines #define PLUGIN_NAME "xREDIRECT" #define PLUGIN_VERSION "1.0.3.2" #define PLUGIN_AUTHOR "x0R" #define PLUGIN_TAG "[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_PORT_LEN 6 // maximum length of port numbers (as strings of course) #define MAX_KEY_LEN 20 // maximum length of a key name in SERVERFILE #define MAX_PASSWORD_LEN 15 // maximum length of a password in SERVERFILE #define MAX_VALUE_LEN 255 // maximum length of a value in SERVERFILE #define MAX_PLAYERS 32 // maximum number of players on the server // 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 // 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 QUERY_INTERVAL 20.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 18 // 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 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?go=module&id=1#const_admin // A2S_INFO definitions for source according to http://developer.valvesoftware.com/wiki/Server_Queries#Source_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. #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/wiki/Steam_Application_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/wiki/Server_Queries#Goldsource_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 #define A2S_INFO_GOLD_IDX_MOD_SECURE 21 // 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 #define A2S_INFO_GOLD_IDX_MOD_NUMBOTS 22 // 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 // flags #define SERVERFLAG_NOAUTO 0 #define SERVERFLAG_NOMANUAL 1 #define SERVERFLAG_NODISPLAY 2 // --------------------------------------- end of defines --------------------------------------- // -=[ global variables - remember to add an initialization in srvcmd_reload() for all variables you add here! ]=- /// Defines whether the plugin was completely initialized. new g_bInitialized = false // no srvcmd_reload() initialization needed for this one, as it's not directly related to the server list /// Server name. new g_saServerNames[MAX_SERVERFORWARDS][MAX_SERVERNAME_LEN] /// Server address. new g_saServerAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN] /// Server port. new g_naServerPorts[MAX_SERVERFORWARDS] = {27015, ...} /// Server password. new g_saServerPasswords[MAX_SERVERFORWARDS][MAX_PASSWORD_LEN] /// Is the server password public? new g_naServerPublicPassword[MAX_SERVERFORWARDS] = {0, ...} /// Currently active player count. new g_naServerActivePlayers[MAX_SERVERFORWARDS] = {-1, ...} /// Maximum number of players the server accepts. Does not take reserved slots into account. new g_naServerMaxPlayers[MAX_SERVERFORWARDS] = {-1, ...} /// Currently running map on server. new g_saServerMap[MAX_SERVERFORWARDS][MAX_MAP_LEN] /// The socket for the server to handle requests. new g_naServerSockets[MAX_SERVERFORWARDS] = {0, ...} /// The number how often server queries should be resent to that server. new g_naServerCmdBackup[MAX_SERVERFORWARDS] = {DEFAULT_CMDBACKUP, ...} /// Flags with several server options. Use the constant defines starting with SERVERFLAG_ to access these. new g_naServerFlags[MAX_SERVERFORWARDS] = {0, ...} /// Are admin slots reserved on this server? new g_naServerReserveSlots[MAX_SERVERFORWARDS] = {0, ...} /// Local server address. new g_saServerLocalAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN] /// At which real index does the menu page start? It is shifted because of hidden servers. new g_naMenuPageStart[MAX_PLAYERS][MAX_MENUPAGES] /// Is the server responding? new bool:g_baServerResponding[MAX_SERVERFORWARDS] = {false, ...} /// Number of servers found in server list file. new g_nServerCount = 0 /// The last server someone has been redirected to. Needed for . new g_nLastRedirectServer = -1 /// The nick of the person who has been redirected at last. Needed for . new g_sLastRedirectName[MAX_NAME_LEN] = "" /// The index of the current server. This is neccessary for the server to check its own data. new g_nOwnServer = -1 /// The page number for each user which he had open last time, needed for switching back from sub menu to server menu. new g_naLastMenuPages[MAX_PLAYERS] = {1, ...} /// 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. new g_naServerSelections[MAX_PLAYERS][8] /// This is the cycle variable that holds which server to begin from in . new g_nNextAnnounceServer = 0 /// The last server the player came from through redirection. Needed in case he wants to send himself back with /retry. new g_nLastServer[MAX_PLAYERS] = {-1, ...} /// The last server the player has accessed the sub menu of. Needed when the player refreshes the sub menu. new g_nLastSelected[MAX_PLAYERS] = {-1, ...} /// This array contains the retry queue consisting of a player ID and a serve number for each record. new g_nRetryQueue[MAX_PLAYERS*MAX_SERVERFORWARDS][2] /// Counter for global number of queue entries. new g_nRetryCount = 0 /// Controls whether certain debug messages are shown. It is autoamtically set to true when the plugin has debug mode set in plugins.ini. new bool:g_bDebug = false // -=[ 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_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 // --------------------------------------- end of global vars --------------------------------------- /* public plugin_precache() { } */ #if AMXX_VERSION_NUM >= 170 /// Initialize CVARs, load servers, register commands, register menues, register dictionaries, start tasks... 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_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") register_dictionary("xredirect.txt") register_dictionary("common.txt") load_servers() 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") 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 , " [servernum] - redirect a player [to a given server]") set_task(QUERY_INTERVAL, "query_servers", TASKID_QUERY, "", 0, "b") // 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") } /// More initializations that have to be done here, because when 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. public plugin_postinit() { g_bInitialized = true new sFullAddress[MAX_SERVERADDRESS_LEN] new sTmpServerIP[MAX_IP_LEN + MAX_PORT_LEN] get_cvar_string("net_address", sTmpServerIP, MAX_IP_LEN + MAX_PORT_LEN - 1) new sTmpOwnAddress[MAX_SERVERADDRESS_LEN] get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1) new nServerCount = 0 while (nServerCount < g_nServerCount) { format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount]) if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerIP)) { g_nOwnServer = nServerCount break } 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 } 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") return PLUGIN_CONTINUE } /// This is used to register the native redirect() function. public plugin_natives() { register_native("redirect", "native_redirect", 1) } /// This is used to tell AMXX that the sockets module is required. /// Can be safely removed from the code when only redirect_check_method 0 will be used. public plugin_modules() { require_module("sockets") } /// Load servers from server list file. /// true when servers have been successfully loaded, false if there were errors. public bool:load_servers() { 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 false } new nFilePos = 0 new sFileLine[MAX_SERVERLINE_LEN] new nReadLen new sPort[MAX_PORT_LEN] new sKey[MAX_KEY_LEN] new sValue[MAX_VALUE_LEN] new nCurrentServer = -1 while (read_file(sServerFile, nFilePos++, sFileLine, MAX_SERVERLINE_LEN-1, nReadLen)) { if ((sFileLine[0] == ';') && (strcmp(sFileLine, "") == 0)) continue // skip comment 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, sKey, MAX_KEY_LEN - 1, sValue, MAX_VALUE_LEN - 1, '=', 1) strtoupper(sKey) if (strcmp(sKey, "ADDRESS") == 0) copy(g_saServerAddresses[nCurrentServer], MAX_SERVERADDRESS_LEN - 1, sValue) else if (strcmp(sKey, "LOCALADDRESS") == 0) copy(g_saServerLocalAddresses[nCurrentServer], MAX_SERVERADDRESS_LEN - 1, sValue) else if (strcmp(sKey, "PASSWORD") == 0) copy(g_saServerPasswords[nCurrentServer], MAX_PASSWORD_LEN - 1, sValue) else if (strcmp(sKey, "PUBLICPASSWORD") == 0) { if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerPublicPassword[nCurrentServer] = 1 } else if (strcmp(sKey, "PORT") == 0) { if (is_str_num(sValue)) g_naServerPorts[nCurrentServer] = str_to_num(sValue) else g_naServerPorts[nCurrentServer] = 27015 if ((g_naServerPorts[nCurrentServer] > 65536) || (g_naServerPorts[nCurrentServer] < 1024)) g_naServerPorts[nCurrentServer] = 27015 } else if (strcmp(sKey, "CMDBACKUP") == 0) { if (is_str_num(sValue)) g_naServerCmdBackup[nCurrentServer] = str_to_num(sValue) else g_naServerCmdBackup[nCurrentServer] = DEFAULT_CMDBACKUP // protect from insane values if ((g_naServerCmdBackup[nCurrentServer] > 100) || (g_naServerCmdBackup[nCurrentServer] < 0)) g_naServerCmdBackup[nCurrentServer] = DEFAULT_CMDBACKUP } else if (strcmp(sKey, "NOAUTO") == 0) { if (is_str_num(sValue)) if (str_to_num(sValue) == 1) g_naServerFlags[nCurrentServer] = g_naServerFlags[nCurrentServer] | (1< MAX_PLAYERS) || (g_naServerReserveSlots[nCurrentServer] < 0)) g_naServerReserveSlots[nCurrentServer] = 0 } } } if ((nCurrentServer >= MAX_SERVERFORWARDS) || (nCurrentServer == -1)) return true; // 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) } if (g_nServerCount < 2) { log_amx("%L", LANG_SERVER, "MSG_ERROR_NOT_ENOUGH_SERVERS") return false } return true } /// Checks whether the IP in is a local address arcording to RFC 1918. /// 10.0.0.0 - 10.255.255.255 - single class A /// 172.16.0.0 - 172.31.255.255 - 16 contiguous class Bs /// 192.168.0.0 - 192.168.255.255 - 256 contiguous class Cs /// 169.254.0.0 - 169.254.255.255 - zeroconf /// The IP address to check passed as a string. /// true if is a local IP address, false if not. 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))) } /// Checks whether the player with ID can be redirected to the server with server number . /// The server number which shall be checked whether it is currently a valid redirection target. /// The internal player ID which shall be checked for access to . /// Defines the redirection mode - 1 = automatic, 2 = manual. /// Set to true, when the plugin should not tread admins as special, otherwise false. /// 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. public can_redirect_player(nServer, nPlayerID, nMode, bIgnoreAdmin) { if (nServer == -1) return 0 new nCheckMethod = get_pcvar_num(cvar_check_method) new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0)) if (nServer == g_nOwnServer) return 1 else if (access(nPlayerID, MIN_ADMIN_LEVEL) && (!bIgnoreAdmin)) // even for admins it doesn't make sense to redirect to the current server 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< 0)) || (g_naServerActivePlayers[nServer] >= g_naServerMaxPlayers[nServer]))) return 4 return 0 } /// Checks whether the player with ID can be queued to redirect to the server with server number . /// The server number which shall be checked whether it is currently a valid redirection queue target. /// The internal player ID which shall be checked for access to . /// true if queueing is possible, otherwise false 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<Checks whether the player with ID is already in redirection queue for server with number . /// The server number which shall be checked whether player with is in its queue. /// The internal player ID which shall be checked whether it is queued for server . /// A player can be in more than one queue but not twice in the queue for one server, prevents double adding. /// true if player is in queue, false if not. /// /// 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 } /// Adds the player with ID to the redirection queue for server with number . /// The server number to add the player with to its queue. /// The internal player ID which shall be added to the queue for server . /// A player can be in more than one queue but not twice in the queue for one server, prevents double adding. /// /// 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 (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") } /// Removes the player with ID from the redirection queue for server with number . /// The server number to remove the player with from its queue. /// The internal player ID which shall be removed from the queue for server . /// A player can be in more than one queue but not twice in the queue for one server, prevents double adding. /// /// 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++ } } /// Resets the setinfo string of the player with by removing tags that xREDIRECT used. /// 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 . public reset_info(id[]) { client_cmd(id[0], "setinfo ^"xredir^" ^"^"") client_cmd(id[0], "setinfo ^"password^" ^"^"") } /// Announce the servers on top of the screen. The position and interval for announcements can be set by CVARs. 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_naServerFlags[nServerCount] & (1< 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_playersnum(1), 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) //show_hudmessage(0, sAnnounceBody) 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 } /// Shows the sub menu for server with number to the the player with ID . /// The server to show the sub menu for. /// The ID of the player to show the sub 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] // can we display colors? if (bColorMenu) { format(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]) } else { format(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]) } // 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_playersnum(1), 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_playersnum(1), 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") } // 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") } /// Shows the server menu page to the the player with ID . /// The ID of the player to show the server menu to. /// The menu page number to show to the player. Offset is 0. /// /// /// public show_server_menu(id, menupage) { 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 nCheckMethod = get_pcvar_num(cvar_check_method) new sMenuBody[MAX_MENUBODY_LEN] if (bColorMenu) format(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SELECT_SERVER") else format(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)) { if (!((g_naServerFlags[nServerCount] & (1< 2) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer)))) { new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServerCount], "") && (g_naServerPublicPassword[nServerCount] == 0) && (!access(id, MIN_ADMIN_LEVEL))) if (bColorMenu) { format(sMenuNumber, 9, "\y%d. ", nDisplayNumber) if (bSubMenu) format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount]) else format(sMenuSrvName, 49, "\d %s", g_saServerNames[nServerCount]) } else { format(sMenuNumber, 9, "%d. ", nDisplayNumber) format(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< 0) && (!access(id, MIN_ADMIN_LEVEL))) || (g_naServerActivePlayers[nServerCount] >= g_naServerMaxPlayers[nServerCount]))) { bCanRedirect = false if ((!bColorMenu) && (!bSubMenu)) sMenuNumber = "_. " if (bColorMenu) format(sMenuInfo, 49, " [%s] \r(\w%d/%d\r)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) else format(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) format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", sMap, get_playersnum(1), get_maxplayers()) else format(sMenuInfo, 49, " [%s] (%d/%d)", sMap, get_playersnum(1), get_maxplayers()) } // everything's fine, we can redirect here if (bCanRedirect) { if (bColorMenu) { format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount]) if (nCheckMethod > 1) format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount]) } else { if (nCheckMethod > 1) format(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) { format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount]) if ((nCheckMethod == 0) && (g_baServerResponding[nServerCount])) format(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 (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, "Redirect Menu") } } g_naMenuPageStart[id - 1][menupage - 1] = nServerCount g_naLastMenuPages[id - 1] = menupage } /// Reloads the servers from server list. Takes care of variable and array reinitialization. /// To be able to rely on this in the future make sure to add an initialization here for all variables you add! 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_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 } // reset global variables g_nNextAnnounceServer = 0 g_nServerCount = 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 } load_servers() new sFullAddress[MAX_SERVERADDRESS_LEN] new sTmpServerIP[MAX_IP_LEN + MAX_PORT_LEN] get_cvar_string("net_address", sTmpServerIP, MAX_IP_LEN + MAX_PORT_LEN - 1) new sTmpOwnAddress[MAX_SERVERADDRESS_LEN] get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1) // define the own server again new nServerCount = 0 while (nServerCount < g_nServerCount) { format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount]) if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerIP)) { g_nOwnServer = nServerCount break } nServerCount++ } if (g_nOwnServer == -1) log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR") } /// 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. public cmd_pickserver(id, level, cid) { cmd_show_server_menu(id, level, cid) return PLUGIN_HANDLED } /// This function does the actual redirection. It is also what is a wrapper for with preset to -1 (the external plugin does not know about our server list and numbers anyway) and preset to true (an external plugin does not care whether this would mean redirecting the player back to where he came from). /// It is aware of user permissions and has several options which are set via parameters. /// ID of player to redirect. /// Target server, -1 for automatic choosing according to redirect_auto. /// If nServer is no valid redirect target can we use another server instead? /// Drop user if no server was found? /// /// /// 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 { new sSourceServer[3] get_user_info(id, "xredir", sSourceServer, 2) if (!is_str_num(sSourceServer)) nSourceServer = -1 else nSourceServer = str_to_num(sSourceServer) if ((nSourceServer < 0) || (nSourceServer >= g_nServerCount)) nSourceServer = -1 } 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") } 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, false) > 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]) 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], ""))) format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[nForwardServer], g_naServerPorts[nForwardServer]) else format(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 (get_pcvar_num(cvar_show) == 1) { new nPlayers[MAX_PLAYERS] new nPlayerNum, nPlayerCount, nCurrentPlayer get_players(nPlayers, nPlayerNum, "c") for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++) { nCurrentPlayer = nPlayers[nPlayerCount] if (get_pcvar_num(cvar_follow) == 1) client_print(nCurrentPlayer, print_chat, "%s: %L - %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer], nCurrentPlayer, "MSG_FOLLOW") else client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer]) } } 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") } return true } /// Basically a wrapper for to make it available to other pugins as native. /// /// public native_redirect(id, nServer, bCanDrop) { redirect(id, nServer, (nServer == -1), bCanDrop, true) return PLUGIN_HANDLED } /// Handler for in-game command , checks user permissions for this command and uses to do the redirection. /// /// 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) return PLUGIN_HANDLED } /// Handler for in-game command or chat command . Shows the server menu to the player using . /// public cmd_show_server_menu(id, level, cid) { if (get_pcvar_num(cvar_manual) >= 1) show_server_menu(id, 1) else client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_MANUAL_DISABLED") return PLUGIN_CONTINUE } /// Handler for chat command . Adds the user to the retry queue using . /// public cmd_retry(id, level, cid) { if (g_nLastServer[id - 1] > -1) 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 } /// Handler for chat command . Removes the user from the retry queue using . /// 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 } /// Handler for chat command . Sends a player after the last player that was redirected using . /// 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], ""))) format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer]) else format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer]) client_cmd(id, "Connect %s", sFullAddress) new sUserNick[MAX_NAME_LEN] get_user_name(id, sUserNick, MAX_NAME_LEN - 1) if (get_pcvar_num(cvar_show) == 1) client_print(0, print_chat, "%s: %L - %L", PLUGIN_TAG, id, "MSG_FOLLOWED", sUserNick, g_sLastRedirectName, g_saServerNames[g_nLastRedirectServer], id, "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 } /// Event handler for sub menu selection. /// When the user presses a number key in the sub menu this handler is called. /// Slot ID of player that selected a menu item. /// Key that was pressed, number between 0 and 9. /// /// /// 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) } else if (key == 1) // queue { if (is_queued(id, nServer)) { queue_remove(id, nServer) 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]) } /// Event handler for server menu selection. /// When the user presses a number key in the server menu this handler is called. /// Depending on settings it will display a sub menu or redirect the user. /// Slot ID of player that selected a menu item. /// Key that was pressed, number between 0 and 9. /// /// /// 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) } else { if (key == 8) show_server_menu(id, g_naLastMenuPages[id - 1] + 1) } } /// Sends the information query packets to all other servers. /// This sends the UDP server information query packets in old and new style HL format to all servers in the list. /// Receiving of server data is handled by . /// 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 format(sOldRequest, 8, "%c%c%c%c%s", 255, 255, 255, 255, "ping") format(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 format(sOldRequest, 11, "%c%c%c%c%s", 255, 255, 255, 255, "details") format(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 } /// Index an incoming UDP data packet. /// The raw UDP data string that was received. /// Length of the raw UDP data string as reported by the socket receive function. /// 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. /// The function stores the resulting character offsets of each index in this array. /// 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). /// The number of indexes that were written (= the number of format elements). 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 -----------") 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 } /// Gets a byte from the element at the given index. /// The raw UDP data string that was received. /// The format index of the data to be requested, e.g. 3 for the third data element. /// The requested byte value. public index_get_byte(sData[MAX_INFO_LEN], nIndex) { return sData[nIndex] } /// Gets a short from the element at the given index. /// The raw UDP data string that was received. /// The format index of the data to be requested, e.g. 3 for the third data element. /// The requested short value. public index_get_short(sData[MAX_INFO_LEN], nIndex) { return ((sData[nIndex] << 8) | (sData[nIndex + 1] & 0x00FF)) } /// Gets a long from the element at the given index. /// The raw UDP data string that was received. /// The format index of the data to be requested, e.g. 3 for the third data element. /// The requested long value. 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)) } /// Gets a string from the element at the given index. /// The raw UDP data string that was received. /// The format index of the data to be requested, e.g. 3 for the third data element. /// The requested string value. 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 } /// Handler for parsing the answers to server query packet. /// This handler parses the UDP information answer packets from the servers that have been queried with . /// 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 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); //for (nClearCounter = 0; nClearCounter < MAX_INFO_LEN; nClearCounter++) //sRcvBuf[nClearCounter] = 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 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]) } 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 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 (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") redirect(nPlr, nServer, false, false, true) g_naServerActivePlayers[nServer]++ } } nPlrCnt++ } } return PLUGIN_HANDLED } /// Retrieves number of admins currently on the server. /// Number of admins currently on server. 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 } /// Event handler for client disconnect event. /// This handler makes sure people that have been in queue while disconnecting are removed from it. /// Furthermore it resets the "last server" information for this now empty player slot. /// Slot ID of player that was disconnected. public client_disconnect(id) { queue_remove(id, -1) g_nLastServer[id - 1] = -1 } /// Event handler for client authorized event. /// This handler is called as soon as a connecting client was authenticated with WON/Steam system and received a WON/Steam ID. /// 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. /// Slot ID of player that was authorized. 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 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/max: %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_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/max: %d/%d", sPlayerName, id, nAutoMode, get_playersnum(1), get_maxplayers()) } } } } // show the welcome message delayed to that player new sID[1] sID[0] = id set_task(20.0, "welcome_message", 0, sID, 1) 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) g_nLastServer[id - 1] = nSourceServer 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]) } } } new sID[1] sID[0] = id client_cmd(id, "setinfo ^"xredir^" ^"^"") client_cmd(id, "setinfo ^"password^" ^"^"") set_task(10.0, "reset_info", 0, sID, 1) } return PLUGIN_CONTINUE } /// 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). /// 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. /// 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 /// that he can use /retry command to have himself queued to redirect back to the source server. /// 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. 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] format(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) } } } #else /// Dummy handler to catch the case where a user tried to compile the plugin with a too old compiler. public plugin_init() { log_amx("ERROR: Your AMXX version is too old for this plugin.") } #endif