HLMOD.HU Forrás Megtekintés - www.hlmod.hu
  1. /*
  2. xREDIRECT - redirect menu plugin - © 2006-2011 x0R (xor@x-base.org) - www.x-base.org
  3. Original file: xredirect.sma/xredirect.amxx
  4.  
  5. License:
  6. ¯¯¯¯¯¯¯¯
  7. This program is free software; you can redistribute it and/or modify it
  8. under the terms of the GNU General Public License as published by the
  9. Free Software Foundation; either version 2 of the License, or (at
  10. your option) any later version.
  11.  
  12. This program is distributed in the hope that it will be useful, but
  13. WITHOUT ANY WARRANTY; without even the implied warranty of
  14. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. General Public License for more details.
  16.  
  17. You should have received a copy of the GNU General Public License
  18. along with this program; if not, write to the Free Software Foundation,
  19. Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  20.  
  21. In addition, as a special exception, the author gives permission to
  22. link the code of this program with the Half-Life Game Engine ("HL
  23. Engine") and Modified Game Libraries ("MODs") developed by Valve,
  24. L.L.C ("Valve"). You must obey the GNU General Public License in all
  25. respects for all of the code used other than the HL Engine and MODs
  26. from Valve. If you modify this file, you may extend this exception
  27. to your version of the file, but you are not obligated to do so. If
  28. you do not wish to do so, delete this exception statement from your
  29. version.
  30.  
  31.  
  32.  
  33. Description/Features:
  34. ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  35. First of all, if you are too lazy to read all this don't bother me with problems or questions!
  36.  
  37. The plugin does several things that all can be turned on or off seperately by CVAR's:
  38. - on startup it reads the available servers from SERVERFILE ("amxmodx/config/serverlist.ini" by default),
  39.   see next section for an example
  40. - saying /server shows a list of available servers (if redirect_manual > 0) - people can choose a
  41.   number from the list and are immediately sent to that server
  42. - when the server is full (one free slot left, that is) people are automatically forwarded to a random
  43.   server from the list - redirect_auto enables or disables this
  44. - when a server from the list is full or down the server is disabled in the menu and players are not
  45.   redirected there automatically - to be able to check whether a server is down redirect_check_method
  46.   must be > 0 and to check whether it is full redirect_check_method must be > 1
  47. - the servers are announced every redirect_announce seconds - set to 0 to turn announcements off;
  48.   the server list is shown as HUD message and for living players displayed at the top and for dead
  49.   players displayed somewhere below the top so it is not covered by the "spectator bars"; how much
  50.   information the announcements include depends on redirect_check_method
  51. - depending on redirect_check_method servers can be checked for being down/full or even current map, number
  52.   of current players and maximum players can be displayed in the menu and in the announcements
  53. - when no server is available for automatic redirection the player is just dropped with an appropriate
  54.   message
  55. - when someone is redirected either manually or automatically this is shown to the other players
  56.   telling who was redirected and to which server
  57. - it is also announced that people can say /follow to follow this player to the server and they are
  58.   redirected as well - both the announcements and the follow feature can be enabled or disabled by
  59.   CVAR (redirect_follow)
  60. - the plugin is language aware (thus you need to place the xredirect.txt in amxmodx/data/lang/)
  61. - the server can show whether someone that just connects was redirected to the server and from what
  62.   server he is coming from
  63. - the own IP address is detected automatically and disabled in the server list - automatic detection
  64.   does not work if you use DNS names in the SERVERFILE - in this case set the DNS address of the own
  65.   server in redirect_external_address for the detection to work - detecting the own server is NECESSARY
  66.   for the plugin to work correctly
  67. - with CVAR redirect_retry set to 1 the server can put people into a retry queue to be redirected back to
  68.   the last server (e.g. when they were automatically redirected but only want to play on the server they
  69.   connected to)
  70.  
  71.  
  72. Server List File:
  73. ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  74.  
  75. The file is in ini format. The section name is the server name. The following keys are recognized:
  76. - address = server address (can be IP or DNS name)
  77. - port = server port - a value between 1025 and 65536, default 27015
  78. - cmdbackup = defines how often the UDP request is resent to the server (with redirect_check_method > 0), default 2
  79. - noauto = overrides the redirect_auto setting for this server, default is redirect_auto
  80. - nomanual = if set to 1, users can't manually redirect themselves to this server
  81. - nodisplay = if this is set to 1 it will hide the server from the /server list and announcements, default 0
  82. - 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
  83. - password = the password that is needed to connect to the server, default <none>
  84. - publicpassword = if set to 1, all players can connect to passworded servers, when set to 0 only admins, default 0
  85.  
  86.  
  87. If a value is not specified the default value is used. The "address" key always must be specified and
  88. doesn't have a default value
  89.  
  90. Here is an example how the server file could look like:
  91.  
  92. /¯¯¯¯¯¯¯¯¯¯ serverlist.ini ¯¯¯¯¯¯¯¯¯¯¯¯\
  93. [my example server]
  94. address=example.n-ice.org
  95. localaddress=192.168.0.3
  96. port=27015
  97. cmdbackup=5
  98. noauto=1
  99. nomanual=0
  100. nodisplay=0
  101.  
  102. [my 2nd example server]
  103. address=example2.n-ice.org
  104. port=27015
  105. \______________________________________/
  106.  
  107.  
  108. I recommend that all servers have the same SERVERFILE. This is not necessary with redirect_show 0 but it's still better,
  109. because it could confuse users when not all servers are in the same place in the menu on every server.
  110.  
  111. Please be aware that when using more than 6 servers in SERVERFILE you have to change the define
  112. MAX_SERVERFORWARDS and recompile the plugin. If there are more servers in the file than specified by
  113. MAX_SERVERFORWARDS the other servers will be ignored.
  114.  
  115.  
  116.  
  117. Available CVAR's:
  118. ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  119. redirect_active - 1/0 activate/deactivate redirect plugin - when this is set to 0 all other CVAR's are ignored, default 0
  120. redirect_auto - 0 = disable automatic redirection
  121. - 1 = only redirect when server is full, redirect to random server
  122. - 2 = only redirect when server is full, redirect to next server in list
  123. - 3 = always redirect except admins, redirect to random server
  124. - 4 = always redirect except admins, redirect to next server in list
  125. - 5 = always redirect including admins, redirect to random server
  126. - 6 = always redirect including admins, redirect to next server in list
  127. redirect_manual - controls behaviour of manual redirect menu:
  128. - 0 = disabled
  129. - 1 = selecting a server in main menu directly redirects there (if possible)
  130. - 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)
  131. 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
  132. 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!
  133. 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
  134. 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
  135. redirect_announce_mode - control who announcements are displayed for: 1 = alive players , 2 = dead players, 3 = both
  136. redirect_announce_alivepos_x - the vertical position of the announcements displayed to living people, default -1.0
  137. redirect_announce_alivepos_y - the horizontal position of the announcements displayed to living people, default 0.01
  138. redirect_announce_deadpos_x - the vertical position of the announcements displayed to living people, default -1.0
  139. redirect_announce_deadpos_y - the horizontal position of the announcements displayed to living people, default 0.35
  140. redirect_show - 1/0 enable/disable redirection information in chat area, default 1
  141. 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
  142. 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
  143. 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
  144. redirect_localslots - 1/0 enable/disable slot reserving for local players - remote players are redirected off the server when a local player connects
  145.  
  146. Advanced users should also check out the defines for more options, especially QUERY_INTERVAL could be interesting.
  147.  
  148.  
  149.  
  150. Min. Requirements:
  151. ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
  152. - Metamod v1.18
  153. - HLDS v3.1.1.1
  154. - AMXX v1.70
  155.  
  156.  
  157.  
  158. Modules:
  159. ¯¯¯¯¯¯¯¯¯
  160. The plugin requires the modules engine and sockets to be loaded. You can enable them in your modules.ini.
  161. If you don't want to load the sockets module search for the line containing require_module("sockets")
  162. and comment it out or delete it. When doing this you can only use redirect_check_method 0. If you set
  163. this to something different your server might crash or other problems arise.
  164.  
  165.  
  166.  
  167. Known issues:
  168. ¯¯¯¯¯¯¯¯¯¯¯¯¯
  169. #1 some people report crashes with CS 1.6 and redirect_check_method 1/2 if the server being checked
  170. is on the same IP (only different port) so set to 0 if your server hangs after some time
  171. #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
  172.  
  173.  
  174. Changelog:
  175. ¯¯¯¯¯¯¯¯¯¯
  176.  
  177. Note:
  178. The first version that was released to the public was v0.3. v0.1 and v0.2 were only running on my
  179. servers for some time before I started the next version.
  180.  
  181. v0.1:
  182. - reads the available servers from a config file
  183. - people can show a server menu by saying /server
  184. - automatic server forwarding to the next server in the list when server is full
  185. - announcing of redirection for other players on the server
  186. - people can say /follow to follow the last forwarded player
  187. - when redirect_external_address is set the own address is automatically detected
  188.  
  189. v0.2:
  190. - external address is automatically detected without having redirect_external_address set, for DNS names redirect_external_address
  191.   is still needed to be set though
  192. - introduced CVAR redirect_check_method where
  193. 0 = disabled
  194. 1 = the servers in the list are pinged every QUERY_INTERVAL seconds to check whether they are online
  195. 2 = the servers in the list are queried for actual and maximum players and map every QUERY_INTERVAL seconds
  196. - depending on redirect_check_method the menu displays:
  197. 0 = own server as disabled, others as available in format: server name (server address)
  198. 1 = own server as disabled, others as available in format "server name (server address)" or down in format "server name (server address) (down)"
  199. 2 = own server as disabled, others as available in format "server name [current map] (active players/max. players)"
  200. or down in format "server name (server address) (down)"
  201. - servers that are down are displayed as down in the list and people cannot have themselves redirected there (disabled in menu)
  202. - random automatic forward instead of choosing the next free server from the list
  203. - the server doesn't forward to servers considered being down when server is full
  204. - if no server is available (due to being down) players are not redirected and have a message displayed that they couldn't be forwarded
  205.  
  206. v0.3
  207. - for redirect_check_method 2:
  208. - no automatic redirection to full servers anymore (active players = maximum players)
  209. - full servers are displayed in the menu with red brackets surrounding the player numbers and can't be selected
  210. - the plugin is now language system aware
  211. - introduced CVAR redirect_send_tag where
  212. 0 = disabled
  213. 1 = when redirecting it prepends [R<server number>] to people's names with <server number> being the own server number in SERVERFILE
  214. indicating to the receive server that this player was redirected from this server
  215. - introduced CVAR redirect_receive_tag where
  216. 0 = disabled
  217. 1 = when someone connects with prepended [R<server number>] to his name the server will show a message that the player was redirected
  218. from that server and remove the tag from the name
  219. - introduced CVAR redirect_announce:
  220. The value of this CVAR can be 0 for turning announcements of. If set to a higher floating point
  221. value this is the time in seconds how often the announcements are shown.
  222. - made status detection more stable by sending more UDP packets
  223. - made status detection more stable through a completely new code for receive handling
  224. - several small optimizations and bug fixes here and there I don't recall in detail :P
  225.  
  226. v0.4
  227. - modified default messages for connect and team joining so that they display player names without redirect
  228.   tags if redirect_receive_tag = 1
  229. - modified the name change code to be more reliable
  230. - HL1 steam server status querying is now working - this should make status information work for all HL1
  231.   mods running on Steam
  232. - plugin now uses the much faster cvar querying introduced with AMXX 1.70
  233. - load routine now checks whether there are more servers in the file than can be loaded
  234.  
  235. v0.4.1
  236. - name changes from tagged names are now hidden as well - this means with redirect_receive_tag = 1 the
  237.   complete procedure of tagging is hidden
  238.  
  239. v0.4.7
  240. - fixed a bug where automatic redirection didn't work with redirect_check_method 0
  241. - fixed a bug where the PLUGIN_TAG was missing in the message MSG_NO_REDIRECT_SERVER
  242. - fixed many messages that were still displayed with server's default language
  243. - fixed code to display announcement in different height for dead and alive players
  244. - introduced CVAR redirect_announce_mode where
  245. 1 = announcements while playing
  246. 2 = announcements while dead/spectator
  247. 3 = both
  248. default is 3 - only effective when redirect_announce > 0
  249.  
  250. v0.4.8
  251. - added code to require_module sockets
  252. - introduced cvar redirect_version for external version check
  253.  
  254. v0.5.0
  255. - fixed an error that crashed the server when redirecting automatically
  256.  
  257. v0.6.0
  258. - sorted out several unused variables
  259. - redirect_auto 2 redirects to next server in list (1 = random server, like before)
  260. - added new format for SERVERFILE - it is now an ini file
  261. - server status (or ping) request should be more stable due to added cmdbackup of 2 (2 additional request packets are sent)
  262. - the default cmdbackup value can be overridden in SERVERFILE for each server
  263. - redirect_manual can be overriden in SERVERFILE for each server
  264. - redirect_auto can be overriden in SERVERFILE for each server
  265. - servers in the list can be hidden (from list + announcements) with a key in SERVERFILE
  266. - added server command redirect_reload which will make the plugin reload the SERVERFILE
  267.  
  268. v0.6.3
  269. - changed UDP timeout value to default with hope to fix crash problem with redirect_check_method > 0
  270. - sockets are now initialized to 0 after freeing them
  271. - fixed a bug where second server was displayed as being down
  272. - fixed a bug where for first server the map of the second server was displayed
  273. - changed default QUERY_INTERVAL to 20
  274. - changed UDP receive code to be faster and handle responses more secure and reliable
  275.  
  276. v0.7.0
  277. - UDP timeout value change didn't help and was changed back to 1
  278. - fixed the "unknown command: pickserver" message when using pickserver command
  279. - removed nick tagging
  280. - server now displays where someone was redirected from without nick tagging
  281. - removed text message hooks as they are not needed anymore
  282. - removed CVAR redirect_send_tag
  283. - removed CVAR redirect_receive_tag
  284. - servers are not redirecting back to source servers anymore, thus preventing an endless loop
  285. - fixed a bug where redirect_auto 2 wouldn't choose the next but the first server in list
  286. - introduced CVAR redirect_show where
  287. 0 = disabled
  288. 1 = show redirection information in chat area when someone was redirected (default)
  289.  
  290. v0.7.5
  291. - removed support for AMXX versions older than 1.70 to cleanup code
  292. - added command redirect_announce_now which will immediately display server announcement to all players
  293. - introduced CVAR redirect_announce_alivepos_x
  294. - introduced CVAR redirect_announce_alivepos_y
  295. - introduced CVAR redirect_announce_deadpos_x
  296. - introduced CVAR redirect_announce_deadpos_y
  297. - menues now don't have colors anymore when the mod does not support coloured menues
  298. - added command redirect_user which can redirect a user
  299. - added native redirect(id, nServer) which can be called by other plugins
  300. - sentence "say /server..." is no longer displayed in announcement if redirect_manual is set to 0
  301.  
  302. v0.8.0
  303. - introduced CVAR redirect_adminslots
  304. - plugin will now redirect another user to free up a slot when someone with a reserved slot connects
  305. - improved detection for coloured menues
  306. - servers in server list can have a new setting "adminslots" to tell whether they have adminslots
  307. - plugin will not allow to redirect manually anymore when there is only one free slot left on the target server,
  308.   except when the target server has an admin slot and the player to be redirected has reservation flag
  309.  - native redirect function got a new parameter to tell whether people are dropped when no valid target server is found
  310.  
  311. v0.8.2
  312.  - setting redirect_announce to 0 will now stop the announcements from being displayed immediately
  313.  - fixed a bug where announcements were displayed although redirect_announce is set to 0
  314. - plugin will not allow to redirect manually anymore when there is only one free slot left on the target server,
  315.   except when the target server has an admin slot and the player to be redirected has reservation flag [should work now]
  316.  
  317. v0.8.4
  318. - introduced CVAR redirect_maxadmins - with this the maximum number of admins can be limited
  319. - if the maximum number of connected admins is reached the plugin acts like there were no admin slots
  320. - fixed a bug where server parameters in server list beginning with "no" were always interpreted as "1"
  321.  
  322. v0.9.0
  323. - added functionality for splitting up servers on several selection pages
  324. - selection menu can now handle up to 999 servers
  325. - server announcements now cycle through servers with same grouping like menu if servers are more than 8
  326. - redirection to passworded servers is now possible, either for admins or even normal players
  327. - introduced serverlist option "password" which sets the connect password needed for this server
  328. - introduced serverlist option "publicpassword":
  329.   0 = password not public, only admins can have themselves redirected to this server (if passworded)
  330.   1 = password is public, all players can have themselves redirected to this server (if passworded)
  331. - optimized performance of redirect_reload
  332. - changed some messages that were displayed in console to be displayed in chat area
  333. - added a retry queue people can add themselves to with /retry command to be redirected to the server they came from
  334. - added command /stopretry to drop out of the retry queue
  335. - added many messages and error messages
  336. - moved some initialization code from client_putinserver to plugin_cfg to increase performance
  337. - added debug messages (english only) that will show in server log when plugin is running in debug mode
  338. - fixed a bug where message "player has been redirected to server..." was displayed to all others in
  339.   the language of the player that has been redirected
  340. - fixed a bug where message "player has been redirected here from..." was displayed to all others in
  341.   the language of the player that has been redirected
  342. - added welcome message when a player was redirected from another server
  343. - added announcement that the player can use /retry (displayed only if redirected_retry 1 and redirect_show 1)
  344.  
  345. v0.9.1
  346. - fixed a bug where the plugin would try to display a welcome message even if the player was not redirected,
  347.   causing an error message in AMXX log
  348.  
  349. v1.0
  350. - 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
  351. - added a missing message to tell people when the own server was not detected
  352. - added internal option MIN_ADMIN_LEVEL to define the level that is needed for a player to be treated as admin
  353. - introduced cvar redirect_hidedown to control hiding of offline servers
  354. - added a message that tells the player when he was redirected to free up a slot for an admin
  355. - added a new sub menu that shows detail information about a server including the reason why redirection is not possible to this server
  356. - by using the sub menu players can now retry all servers, not only the last
  357. - added additional modes for redirect_auto:
  358. - 3 = always redirect except admins, redirect to random server
  359. - 4 = always redirect except admins, redirect to next server in list
  360. - 5 = always redirect including admins, redirect to random server
  361. - 6 = always redirect including admins, redirect to next server in list
  362. - added additional modes for redirect_manual:
  363. - 2 = show a sub menu when player can't be redirected to server
  364. - 3 = always show a sub menu from which the player can choose to be redirected (if possible)
  365. - added server option localaddress
  366. - the plugin now detects local players and sends them to localaddress=, while remote players are still sent to address=
  367. - fixed an issue that caused wrong menu numbering under certain circumstances (not very likely to happen though)
  368. - completely rewrote menu creation code for better code readability and modularity
  369. - changed menu coloring to be more straight forward
  370. - 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!
  371. - several internal code structure improvements
  372. - 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
  373. - 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
  374. - added XMLDoc documentation to source code
  375. - removed client_putinserver() handler and integrated into client_authorized()
  376. - fixed a bug where always on first startup with ./hlds_run the plugin could not detect the own server
  377. - fixed an error with an internal array overflow on servers with 32 maximum players
  378. - fixed a bug where admins would not be able to have themselves redirected to a full server
  379. - removed server address from server name in annoucements - if someone wants this he can still add it to the server name
  380. - annoucements now display server information for own server
  381. - server menu now displays server information for own server
  382. - fixed a bug where menu displayed incorrectly with redirect_check_method 0
  383. - fixed a bug where redirecting was not possible with redirect_check_method 0
  384. - fixed an error in the log message for socket errors
  385. - completely rewritten redirection decision and menu build code for better maintainability
  386. - fixed a still remaining error on servers with 32 slots
  387. - changed menu to use integrated redirect function instead of direct code for better overall stability
  388. - queue option is now no longer available with redirect_check_method < 2
  389. - refreshing a sub menu is no longer possible with redirect_check_method 0 (doesn't make sense) except for the current server
  390. - fixed a bug where the plugin would not automatically redirect to a plugin that has the NOMANUAL flag set
  391. - fixed a bug where a server would be displayed as having (-1/-1) players when the server is down and sub menues are enabled
  392. - fixed a bug where the sub menu would display a servers as "server full" when it is down
  393. - 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
  394. - the welcome message can no longer state that someone came from the server he currently is connected to
  395. - 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
  396. - automatic redirection didn't take MIN_ADMIN_LEVEL as admin level but REDIRECT_RESERVATION
  397. - 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)
  398. - fixed a bug where the own server always would be hidden from the menu/announcements when redirect_hidedown is enabled
  399. - slight speed optimization in socket query code
  400. - fixed an array error that occurs when a player would disconnect within 20 seconds after connecting
  401. - HLTVs will no longer be automatically redirected
  402.  
  403.  v1.0.3
  404.  - added debug messages for autoredirect decisions the plugin makes
  405.  - players with a local address were always redirected to the server local address parameter, even when it's empty
  406. - completely rewritten socket query code
  407. - plug-in now supports both old HL1 and new source protocol
  408.  
  409. v1.0.3.1
  410. - added workaround for "Server tried to send invalid command" issue, making redirection work again
  411.  
  412. v1.0.3.2
  413. - once more added workaround for "Server tried to send invalid command" issue, making redirection work again
  414.  
  415. v1.0.3.3
  416. - added fix for network socket problems on some servers
  417.  
  418.  */
  419.  
  420. // includes
  421. #include <amxmodx>
  422. #include <amxmisc>
  423. #include <sockets>
  424.  
  425. // plugin defines
  426. #define PLUGIN_NAME "xREDIRECT"
  427. #define PLUGIN_VERSION "1.0.3.3"
  428. #define PLUGIN_AUTHOR "x0R"
  429. #define PLUGIN_TAG "[xREDIRECT]"
  430.  
  431. // maximum values - don't change this if you don't know what you are doing!
  432. #define MAX_FILE_LEN 256 // maximum length of file names
  433. #define MAX_SERVERLINE_LEN 256 // maximum length of a line read from SERVERFILE
  434. #define MAX_SERVERNAME_LEN 50 // maximum length of a server name read from SERVERFILE
  435. #define MAX_SERVERADDRESS_LEN 100 // maximum length of a server address read from SERVERFILE
  436. #define MAX_NAME_LEN 33 // maximum length of a player name
  437. #define MAX_MENUBODY_LEN 512 // maximum length of a menu body
  438. #define MAX_WELCOME_LEN 1024 // maximum length of the welcome message
  439. #define MAX_INFO_LEN 1400 // maximum length of info reply - when longer than that the packet is fragmented (software side, not due to MTU)
  440. #define MAX_INFO_FORMAT 100 // maximum length of a format string for an info reply
  441. #define MAX_MAP_LEN 30 // maximum length of map names
  442. #define MAX_IP_LEN 16 // maximum length of IP addresses
  443. #define MAX_PORT_LEN 6 // maximum length of port numbers (as strings of course)
  444. #define MAX_KEY_LEN 20 // maximum length of a key name in SERVERFILE
  445. #define MAX_PASSWORD_LEN 15 // maximum length of a password in SERVERFILE
  446. #define MAX_VALUE_LEN 255 // maximum length of a value in SERVERFILE
  447. #define MAX_PLAYERS 32 // maximum number of players on the server
  448.  
  449. // unique task ID's - currently not needed but who knows when they will be
  450. #define TASKID_QUERY 21934807
  451. #define TASKID_QUERY_RECEIVE 21934808
  452. #define TASKID_ANNOUNCE 21934809
  453.  
  454. // options - these can be changed by the user, rememeber that you need to recompile for any changes here to take effect
  455. #define SERVERFILE "serverlist.ini" // name of file in /configs containing the server forwards - you can also prepend a subdirectory
  456. #define QUERY_INTERVAL 20.0 // interval of server querying (in seconds)
  457. #define QUERY_TIMEOUT 1.0 // the maximum time to wait for a server answer (in seconds) before it is considered being down
  458. #define MAX_SERVERFORWARDS 6 // maximum number of server forwards in forwards file
  459. #define MAX_MENUPAGES 10 // maximum number of pages the server selection menu can have
  460. #define DEFAULT_CMDBACKUP 2 // how often to resend the UDP request to servers by default
  461. #define MENU_FORCENOCOLOR false // false = display colored menues if the mod supports it; true = never display colored menues
  462. #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...)
  463. // can be one of these listed here: http://www.amxmodx.org/funcwiki.php?go=module&id=1#const_admin
  464. // A2S_INFO definitions for source according to http://developer.valvesoftware.com/wiki/Server_Queries#Source_servers_2
  465. #define A2S_INFO_SOURCE_REPLY_FORMAT "411ssss21111111s" // there are some extra flags after this but we don't care
  466. #define A2S_INFO_SOURCE_IDX_HEADER 0 // Should be FF FF FF FF
  467. #define A2S_INFO_SOURCE_IDX_TYPE 1 // Should be equal to 'I' (0x49)
  468. #define A2S_INFO_SOURCE_IDX_VERSION 2 // Network version. 0x07 is the current Steam version.
  469. #define A2S_INFO_SOURCE_IDX_SERVERNAME 3 // The Source server's name
  470. #define A2S_INFO_SOURCE_IDX_MAP 4 // The current map being played, eg: "de_dust"
  471. #define A2S_INFO_SOURCE_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike"
  472. #define A2S_INFO_SOURCE_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter Strike: Source"
  473. #define A2S_INFO_SOURCE_IDX_APPID 7 // Steam Application ID, see http://developer.valvesoftware.com/wiki/Steam_Application_IDs
  474. #define A2S_INFO_SOURCE_IDX_NUMPLAYERS 8 // The number of players currently on the server
  475. #define A2S_INFO_SOURCE_IDX_MAXPLAYERS 9 // Maximum allowed players for the server
  476. #define A2S_INFO_SOURCE_IDX_NUMBOTS 10 // Number of bot players currently on the server
  477. #define A2S_INFO_SOURCE_IDX_DEDICATED 11 // 'l' for listen, 'd' for dedicated, 'p' for SourceTV
  478. #define A2S_INFO_SOURCE_IDX_OS 12 // Host operating system. 'l' for Linux, 'w' for Windows
  479. #define A2S_INFO_SOURCE_IDX_PASSWORD 13 // If set to 0x01, a password is required to join this server
  480. #define A2S_INFO_SOURCE_IDX_SECURE 14 // if set to 0x01, this server is VAC secured
  481. #define A2S_INFO_SOURCE_IDX_GAMEVERSION 15 // The version of the game, eg: "1.0.0.22"
  482.  
  483. // A2S_INFO definitions for goldsource according to http://developer.valvesoftware.com/wiki/Server_Queries#Goldsource_servers_2
  484. #define A2S_INFO_GOLD_REPLY_FORMAT "41sssss111111[ss14411]11"
  485. #define A2S_INFO_GOLD_IDX_HEADER 0 // Should be FF FF FF FF
  486. #define A2S_INFO_GOLD_IDX_TYPE 1 // Should be equal to 'm' (0x6D) - for older servers it's 'C' (0x43)
  487. #define A2S_INFO_GOLD_IDX_IP 2 // Game Server IP address and port
  488. #define A2S_INFO_GOLD_IDX_SERVERNAME 3 // The server's name
  489. #define A2S_INFO_GOLD_IDX_MAP 4 //The current map being played, eg: "de_dust"
  490. #define A2S_INFO_GOLD_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike"
  491. #define A2S_INFO_GOLD_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter-Strike"
  492. #define A2S_INFO_GOLD_IDX_NUMPLAYERS 7 // The number of players currently on the server
  493. #define A2S_INFO_GOLD_IDX_MAXPLAYERS 8 // Maximum allowed players for the server
  494. #define A2S_INFO_GOLD_IDX_VERSION 9 // Network version. 0x07 is the current Steam version.
  495. #define A2S_INFO_GOLD_IDX_DEDICATED 10 // 'l' for listen, 'd' for dedicated, 'p' for HLTV
  496. #define A2S_INFO_GOLD_IDX_OS 11 // Host operating system. 'l' for Linux, 'w' for Windows
  497. #define A2S_INFO_GOLD_IDX_PASSWORD 12 // If set to 0x01, a password is required to join this server
  498. #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)
  499. #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
  500. #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
  501. #define A2S_INFO_GOLD_IDX_MOD_URLINFO 14 // URL containing information about this mod
  502. #define A2S_INFO_GOLD_IDX_MOD_URLDL 15 // URL to download this mod
  503. #define A2S_INFO_GOLD_IDX_MOD_NUL 16 // 0x00
  504. #define A2S_INFO_GOLD_IDX_MOD_MODVERSION 17 // Version of the installed mod
  505. #define A2S_INFO_GOLD_IDX_MOD_MODSIZE 18 // The download size of this mod
  506. #define A2S_INFO_GOLD_IDX_MOD_SVONLY 19 // If 1 this is a server side only mod
  507. #define A2S_INFO_GOLD_IDX_MOD_CIDLL 20 // If 1 this mod has a custom client dll
  508. #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
  509. #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
  510.  
  511. // flags
  512. #define SERVERFLAG_NOAUTO 0
  513. #define SERVERFLAG_NOMANUAL 1
  514. #define SERVERFLAG_NODISPLAY 2
  515.  
  516. // --------------------------------------- end of defines ---------------------------------------
  517.  
  518.  
  519. // -=[ global variables - remember to add an initialization in srvcmd_reload() for all variables you add here! ]=-
  520. /// <summary>Defines whether the plugin was completely initialized.</summary>
  521. new g_bInitialized = false // no srvcmd_reload() initialization needed for this one, as it's not directly related to the server list
  522. /// <summary>Server name.</summary>
  523. new g_saServerNames[MAX_SERVERFORWARDS][MAX_SERVERNAME_LEN]
  524. /// <summary>Server address.</summary>
  525. new g_saServerAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN]
  526. /// <summary>Server port.</summary>
  527. new g_naServerPorts[MAX_SERVERFORWARDS] = {27015, ...}
  528. /// <summary>Server password.</summary>
  529. new g_saServerPasswords[MAX_SERVERFORWARDS][MAX_PASSWORD_LEN]
  530. /// <summary>Is the server password public?</summary>
  531. new g_naServerPublicPassword[MAX_SERVERFORWARDS] = {0, ...}
  532. /// <summary>Currently active player count.</summary>
  533. new g_naServerActivePlayers[MAX_SERVERFORWARDS] = {-1, ...}
  534. /// <summary>Maximum number of players the server accepts. Does not take reserved slots into account.</summary>
  535. new g_naServerMaxPlayers[MAX_SERVERFORWARDS] = {-1, ...}
  536. /// <summary>Currently running map on server.</summary>
  537. new g_saServerMap[MAX_SERVERFORWARDS][MAX_MAP_LEN]
  538. /// <summary>The socket for the server to handle requests.</summary>
  539. new g_naServerSockets[MAX_SERVERFORWARDS] = {0, ...}
  540. /// <summary>The number how often server queries should be resent to that server.</summary>
  541. new g_naServerCmdBackup[MAX_SERVERFORWARDS] = {DEFAULT_CMDBACKUP, ...}
  542. /// <summary>Flags with several server options. Use the constant defines starting with SERVERFLAG_ to access these.</summary>
  543. new g_naServerFlags[MAX_SERVERFORWARDS] = {0, ...}
  544. /// <summary>Are admin slots reserved on this server?</summary>
  545. new g_naServerReserveSlots[MAX_SERVERFORWARDS] = {0, ...}
  546. /// <summary>Local server address.</summary>
  547. new g_saServerLocalAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN]
  548. /// <summary>At which real index does the menu page start? It is shifted because of hidden servers.</summary>
  549. new g_naMenuPageStart[MAX_PLAYERS][MAX_MENUPAGES]
  550. /// <summary>Is the server responding?</summary>
  551. new bool:g_baServerResponding[MAX_SERVERFORWARDS] = {false, ...}
  552. /// <summary>Number of servers found in server list file.</summary>
  553. new g_nServerCount = 0
  554. /// <summary>The last server someone has been redirected to. Needed for <seealso name="cmd_follow_player"/>.</summary>
  555. new g_nLastRedirectServer = -1
  556. /// <summary>The nick of the person who has been redirected at last. Needed for <seealso name="cmd_follow_player"/>.</summary>
  557. new g_sLastRedirectName[MAX_NAME_LEN] = ""
  558. /// <summary>The index of the current server. This is neccessary for the server to check its own data.</summary>
  559. new g_nOwnServer = -1
  560. /// <summary>The page number for each user which he had open last time, needed for switching back from sub menu to server menu.</summary>
  561. new g_naLastMenuPages[MAX_PLAYERS] = {1, ...}
  562. /// <summary>Hidden servers cause a difference between shown and real server numbers - this array associates the real server index with a given key - different for each user as some users can see servers that others don't.</summary>
  563. new g_naServerSelections[MAX_PLAYERS][8]
  564. /// <summary>This is the cycle variable that holds which server to begin from in <seealso name="announce_servers"/>.</summary>
  565. new g_nNextAnnounceServer = 0
  566. /// <summary>The last server the player came from through redirection. Needed in case he wants to send himself back with /retry.</summary>
  567. new g_nLastServer[MAX_PLAYERS] = {-1, ...}
  568. /// <summary>The last server the player has accessed the sub menu of. Needed when the player refreshes the sub menu.</summary>
  569. new g_nLastSelected[MAX_PLAYERS] = {-1, ...}
  570. /// <summary>This array contains the retry queue consisting of a player ID and a serve number for each record.</summary>
  571. new g_nRetryQueue[MAX_PLAYERS*MAX_SERVERFORWARDS][2]
  572. /// <summary>Counter for global number of queue entries.</summary>
  573. new g_nRetryCount = 0
  574. /// <summary>Controls whether certain debug messages are shown. It is autoamtically set to true when the plugin has debug mode set in plugins.ini.</summary>
  575. new bool:g_bDebug = false
  576.  
  577.  
  578. // -=[ global CVAR's ]=-
  579. new cvar_active
  580. new cvar_auto
  581. new cvar_manual
  582. new cvar_follow
  583. new cvar_external_address
  584. new cvar_check_method
  585. new cvar_announce
  586. new cvar_announce_mode
  587. new cvar_announce_alivepos_x
  588. new cvar_announce_alivepos_y
  589. new cvar_announce_deadpos_x
  590. new cvar_announce_deadpos_y
  591. new cvar_show
  592. new cvar_adminslots
  593. new cvar_maxadmins
  594. new cvar_retry
  595. new cvar_hidedown
  596. new cvar_localslots
  597.  
  598. // --------------------------------------- end of global vars ---------------------------------------
  599.  
  600. /*
  601. public plugin_precache()
  602. {
  603. }
  604. */
  605.  
  606. #if AMXX_VERSION_NUM >= 170
  607.  
  608. /// <summary>Initialize CVARs, load servers, register commands, register menues, register dictionaries, start tasks...</summary>
  609. public plugin_init() {
  610. register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR)
  611.  
  612. register_cvar("redirect_version", PLUGIN_VERSION, FCVAR_SERVER|FCVAR_SPONLY)
  613. set_cvar_string("redirect_version", PLUGIN_VERSION)
  614.  
  615. // please see the description at top if you want to know what these CVAR's do
  616. cvar_active = register_cvar("redirect_active", "0")
  617. cvar_auto = register_cvar("redirect_auto", "0")
  618. cvar_manual = register_cvar("redirect_manual", "0")
  619. cvar_follow = register_cvar("redirect_follow", "0")
  620. cvar_external_address = register_cvar("redirect_external_address", "")
  621. cvar_check_method = register_cvar("redirect_check_method", "0")
  622. cvar_announce = register_cvar("redirect_announce", "120")
  623. cvar_announce_mode = register_cvar("redirect_announce_mode", "3")
  624. cvar_announce_alivepos_x = register_cvar("redirect_announce_alivepos_x", "-1.0")
  625. cvar_announce_alivepos_y = register_cvar("redirect_announce_alivepos_y", "0.01")
  626. cvar_announce_deadpos_x = register_cvar("redirect_announce_deadpos_x", "-1.0")
  627. cvar_announce_deadpos_y = register_cvar("redirect_announce_deadpos_y", "0.35")
  628. cvar_show = register_cvar("redirect_show", "1")
  629. cvar_adminslots = register_cvar("redirect_adminslots", "0")
  630. cvar_maxadmins = register_cvar("redirect_maxadmins", "0")
  631. cvar_retry = register_cvar("redirect_retry", "0")
  632. cvar_hidedown = register_cvar("redirect_hidedown", "0")
  633. cvar_localslots = register_cvar("redirect_localslots", "0")
  634.  
  635. register_dictionary("xredirect.txt")
  636. register_dictionary("common.txt")
  637.  
  638. load_servers()
  639.  
  640. register_menu("Redirect Menu", 1023, "server_menu_select")
  641. register_menu("Detail Menu", 1023, "sub_menu_select")
  642.  
  643. register_srvcmd("redirect_reload", "srvcmd_reload", -1, "- reload redirect servers")
  644.  
  645. register_clcmd("say /server", "cmd_show_server_menu", 0, "- show server redirection menu")
  646. register_clcmd("say_team /server", "cmd_show_server_menu", 0, "- show server redirection menu")
  647. register_clcmd("pickserver", "cmd_pickserver", 0, "show server redirection menu")
  648. register_clcmd("say /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server")
  649. register_clcmd("say_team /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server")
  650. register_clcmd("say /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot")
  651. register_clcmd("say_team /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot")
  652. register_clcmd("say /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server")
  653. register_clcmd("say_team /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server")
  654. register_clcmd("redirect_announce_now", "announce_servers", ADMIN_KICK , "- announce server list immediately")
  655. register_clcmd("redirect_user", "cmd_redirect_user", ADMIN_KICK , "<playername|playerid> [servernum] - redirect a player [to a given server]")
  656.  
  657. set_task(QUERY_INTERVAL, "query_servers", TASKID_QUERY, "", 0, "b")
  658.  
  659. // check whether we are in debug mode or not
  660. new saDummy[2]
  661. new saStatus[6]
  662. get_plugin(-1, saDummy, 0, saDummy, 0, saDummy, 0, saDummy, 0, saStatus, 5)
  663. g_bDebug = bool:equal(saStatus, "debug")
  664. }
  665.  
  666. /// <summary>More initializations that have to be done here, because when <seealso name="plugin_init"/> is called CVARs are not yet set. They are in plugin_cfg(), but not for the first start of the game server with ./hlds_run so we use this extra function called once when the first player connects.</summary>
  667. public plugin_postinit()
  668. {
  669. g_bInitialized = true
  670. new sFullAddress[MAX_SERVERADDRESS_LEN]
  671. new sTmpServerIP[MAX_IP_LEN + MAX_PORT_LEN]
  672. get_cvar_string("net_address", sTmpServerIP, MAX_IP_LEN + MAX_PORT_LEN - 1)
  673. new sTmpOwnAddress[MAX_SERVERADDRESS_LEN]
  674. get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1)
  675.  
  676. new nServerCount = 0
  677. while (nServerCount < g_nServerCount)
  678. {
  679. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount])
  680. if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerIP))
  681. {
  682. g_nOwnServer = nServerCount
  683. break
  684. }
  685. nServerCount++
  686. }
  687.  
  688. if (g_nOwnServer == -1) // we have not been able to detect the own server - inform the user about this
  689. {
  690. log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR")
  691. return PLUGIN_CONTINUE
  692. }
  693.  
  694. if (get_pcvar_float(cvar_announce) > 0.0)
  695. if (!task_exists(TASKID_ANNOUNCE))
  696. set_task(get_pcvar_float(cvar_announce), "announce_servers", TASKID_ANNOUNCE, "", 0, "b")
  697.  
  698. return PLUGIN_CONTINUE
  699. }
  700.  
  701. /// <summary>Cleanup. Close open sockets.</summary>
  702. public plugin_end()
  703. {
  704. // close all open sockets
  705. for (new nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++)
  706. {
  707. if (g_naServerSockets[nCounter] > 0)
  708. {
  709. socket_close(g_naServerSockets[nCounter])
  710. g_naServerSockets[nCounter] = 0
  711. }
  712. }
  713. }
  714.  
  715. /// <summary>This is used to register the native redirect() function.</summary>
  716. public plugin_natives()
  717. {
  718. register_native("redirect", "native_redirect", 1)
  719. }
  720.  
  721. /// <summary>This is used to tell AMXX that the sockets module is required.</summary>
  722. /// <remarks>Can be safely removed from the code when only redirect_check_method 0 will be used.</remarks>
  723. public plugin_modules()
  724. {
  725. require_module("sockets")
  726. }
  727.  
  728. /// <summary>Load servers from server list file.</summary>
  729. /// <returns>true when servers have been successfully loaded, false if there were errors.</returns>
  730. public bool:load_servers()
  731. {
  732. new sConfigDir[MAX_FILE_LEN], sServerFile[MAX_FILE_LEN]
  733.  
  734. get_configsdir(sConfigDir, MAX_FILE_LEN-1)
  735. format(sServerFile, MAX_FILE_LEN-1, "%s/%s", sConfigDir, SERVERFILE)
  736.  
  737. if (!file_exists(sServerFile))
  738. {
  739. log_amx("%L", LANG_SERVER, "MSG_ERROR_NO_FILE", sServerFile)
  740. return false
  741. }
  742.  
  743. new nFilePos = 0
  744. new sFileLine[MAX_SERVERLINE_LEN]
  745. new nReadLen
  746. new sPort[MAX_PORT_LEN]
  747.  
  748. new sKey[MAX_KEY_LEN]
  749. new sValue[MAX_VALUE_LEN]
  750.  
  751. new nCurrentServer = -1
  752.  
  753. while (read_file(sServerFile, nFilePos++, sFileLine, MAX_SERVERLINE_LEN-1, nReadLen))
  754. {
  755. if ((sFileLine[0] == ';') && (strcmp(sFileLine, "") == 0)) continue // skip comment and empty lines
  756.  
  757. if ((sFileLine[0] == '[') && (sFileLine[strlen(sFileLine) - 1] == ']')) // a section starts
  758. {
  759. nCurrentServer++
  760. if (nCurrentServer > 0)
  761. {
  762. // check whether the previous server was valid
  763. if ((g_naServerPorts[nCurrentServer - 1] != 0) && (strcmp(g_saServerAddresses[nCurrentServer - 1], "") != 0))
  764. {
  765. g_nServerCount++
  766. num_to_str(g_naServerPorts[nCurrentServer - 1], sPort, MAX_PORT_LEN - 1)
  767. log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer - 1], g_saServerAddresses[nCurrentServer - 1], sPort)
  768.  
  769. }
  770. else
  771. nCurrentServer--
  772. }
  773.  
  774. if (nCurrentServer >= MAX_SERVERFORWARDS)
  775. break;
  776.  
  777. copy(g_saServerNames[nCurrentServer], strlen(sFileLine) - 2, sFileLine[1])
  778.  
  779. continue
  780. }
  781.  
  782. if (nCurrentServer >= 0) // do we already have found a section?
  783. {
  784. strtok(sFileLine, sKey, MAX_KEY_LEN - 1, sValue, MAX_VALUE_LEN - 1, '=', 1)
  785. strtoupper(sKey)
  786. if (strcmp(sKey, "ADDRESS") == 0)
  787. copy(g_saServerAddresses[nCurrentServer], MAX_SERVERADDRESS_LEN - 1, sValue)
  788. else
  789. if (strcmp(sKey, "LOCALADDRESS") == 0)
  790. copy(g_saServerLocalAddresses[nCurrentServer], MAX_SERVERADDRESS_LEN - 1, sValue)
  791. else
  792. if (strcmp(sKey, "PASSWORD") == 0)
  793. copy(g_saServerPasswords[nCurrentServer], MAX_PASSWORD_LEN - 1, sValue)
  794. else
  795. if (strcmp(sKey, "PUBLICPASSWORD") == 0)
  796. {
  797. if (is_str_num(sValue))
  798. if (str_to_num(sValue) == 1)
  799. g_naServerPublicPassword[nCurrentServer] = 1
  800. }
  801. else
  802. if (strcmp(sKey, "PORT") == 0)
  803. {
  804. if (is_str_num(sValue))
  805. g_naServerPorts[nCurrentServer] = str_to_num(sValue)
  806. else
  807. g_naServerPorts[nCurrentServer] = 27015
  808. if ((g_naServerPorts[nCurrentServer] > 65536) || (g_naServerPorts[nCurrentServer] < 1024))
  809. g_naServerPorts[nCurrentServer] = 27015
  810. }
  811. else
  812. if (strcmp(sKey, "CMDBACKUP") == 0)
  813. {
  814. if (is_str_num(sValue))
  815. g_naServerCmdBackup[nCurrentServer] = str_to_num(sValue)
  816. else
  817. g_naServerCmdBackup[nCurrentServer] = DEFAULT_CMDBACKUP
  818. // protect from insane values
  819. if ((g_naServerCmdBackup[nCurrentServer] > 100) || (g_naServerCmdBackup[nCurrentServer] < 0))
  820. g_naServerCmdBackup[nCurrentServer] = DEFAULT_CMDBACKUP
  821. }
  822. else
  823. if (strcmp(sKey, "NOAUTO") == 0)
  824. {
  825. if (is_str_num(sValue))
  826. if (str_to_num(sValue) == 1)
  827. g_naServerFlags[nCurrentServer] = g_naServerFlags[nCurrentServer] | (1<<SERVERFLAG_NOAUTO)
  828. }
  829. else
  830. if (strcmp(sKey, "NOMANUAL") == 0)
  831. {
  832. if (is_str_num(sValue))
  833. if (str_to_num(sValue) == 1)
  834. g_naServerFlags[nCurrentServer] = g_naServerFlags[nCurrentServer] | (1<<SERVERFLAG_NOMANUAL)
  835.  
  836. }
  837. else
  838. if (strcmp(sKey, "NODISPLAY") == 0)
  839. {
  840. if (is_str_num(sValue))
  841. if (str_to_num(sValue) == 1)
  842. g_naServerFlags[nCurrentServer] = g_naServerFlags[nCurrentServer] | (1<<SERVERFLAG_NODISPLAY)
  843. }
  844. else
  845. if (strcmp(sKey, "ADMINSLOTS") == 0)
  846. {
  847. if (is_str_num(sValue))
  848. g_naServerReserveSlots[nCurrentServer] = str_to_num(sValue)
  849. else
  850. g_naServerReserveSlots[nCurrentServer] = 0
  851. if ((g_naServerReserveSlots[nCurrentServer] > MAX_PLAYERS) || (g_naServerReserveSlots[nCurrentServer] < 0))
  852. g_naServerReserveSlots[nCurrentServer] = 0
  853. }
  854. }
  855. }
  856.  
  857. if ((nCurrentServer >= MAX_SERVERFORWARDS) || (nCurrentServer == -1))
  858. return true;
  859.  
  860. // check whether the previous server was valid
  861. if ((g_naServerPorts[nCurrentServer] != 0) && (strcmp(g_saServerAddresses[nCurrentServer], "") != 0))
  862. {
  863. g_nServerCount++
  864. num_to_str(g_naServerPorts[nCurrentServer], sPort, MAX_PORT_LEN - 1)
  865. log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer], g_saServerAddresses[nCurrentServer], sPort)
  866. }
  867.  
  868. if (g_nServerCount < 2)
  869. {
  870. log_amx("%L", LANG_SERVER, "MSG_ERROR_NOT_ENOUGH_SERVERS")
  871. return false
  872. }
  873.  
  874. return true
  875. }
  876.  
  877. /// <summary>Checks whether the IP in <paramref name="sCheckAddress"/> is a local address arcording to RFC 1918.</summary>
  878. /// <summary>10.0.0.0 - 10.255.255.255 - single class A</summary>
  879. /// <summary>172.16.0.0 - 172.31.255.255 - 16 contiguous class Bs</summary>
  880. /// <summary>192.168.0.0 - 192.168.255.255 - 256 contiguous class Cs</summary>
  881. /// <summary>169.254.0.0 - 169.254.255.255 - zeroconf</summary>
  882. /// <param name="sCheckAddress">The IP address to check passed as a string.</param>
  883. /// <returns>true if <paramref name="sCheckAddress"/> is a local IP address, false if not.</returns>
  884. public bool:is_local_address(sCheckAddress[MAX_IP_LEN])
  885. {
  886. new sIPPart1[4]
  887. new sIPPart2[4]
  888. new nIPPart[4]
  889. new sCompareIP[MAX_IP_LEN]
  890. sCompareIP = sCheckAddress
  891. strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.')
  892. nIPPart[0] = str_to_num(sIPPart1);
  893. strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.')
  894. nIPPart[1] = str_to_num(sIPPart1);
  895. strtok(sCheckAddress, sIPPart1, 3, sIPPart2, 3, '.')
  896. nIPPart[2] = str_to_num(sIPPart1);
  897. nIPPart[3] = str_to_num(sIPPart2);
  898. 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)))
  899. }
  900.  
  901.  
  902. /// <summary>Checks whether the player with ID <paramref name="nPlayerID"/> can be redirected to the server with server number <paramref name="nServerNum"/>.</summary>
  903. /// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection target.</param>
  904. /// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param>
  905. /// <param name="nMode">Defines the redirection mode - 1 = automatic, 2 = manual.</param>
  906. /// <param name="bIgnoreAdmin">Set to true, when the plugin should not tread admins as special, otherwise false.</param>
  907. /// <returns>0 if redirection is possible, otherwise an error code: 1 = current server, 2 = no permission(passworded), 3 = manual redirection disabled. 4 = server full, 5 = server down, 6 = automatic redirection disabled.</returns>
  908. public can_redirect_player(nServer, nPlayerID, nMode, bIgnoreAdmin)
  909. {
  910. if (nServer == -1)
  911. return 0
  912. new nCheckMethod = get_pcvar_num(cvar_check_method)
  913.  
  914. new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0))
  915.  
  916. if (nServer == g_nOwnServer)
  917. return 1
  918. 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
  919. return 0
  920. else if ((nCheckMethod > 0) && (!g_baServerResponding[nServer]))
  921. return 5
  922. else if (!bCanRedirectByPassword)
  923. return 2
  924. else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL)) && (nMode == 2))
  925. return 3
  926. else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOAUTO)) && (nMode == 1))
  927. return 6
  928. else if ((nCheckMethod == 2) && (((g_naServerActivePlayers[nServer] == (g_naServerMaxPlayers[nServer] - 1)) && (g_naServerReserveSlots[nServer] > 0)) || (g_naServerActivePlayers[nServer] >= g_naServerMaxPlayers[nServer])))
  929. return 4
  930.  
  931. return 0
  932. }
  933.  
  934.  
  935. /// <summary>Checks whether the player with ID <paramref name="nPlayerID"/> can be queued to redirect to the server with server number <paramref name="nServerNum"/>.</summary>
  936. /// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection queue target.</param>
  937. /// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param>
  938. /// <returns>true if queueing is possible, otherwise false</returns>
  939. public bool:can_queue_player(nServer, nPlayerID)
  940. {
  941. if (nServer == -1)
  942. return false
  943.  
  944. new bIsAdmin = access(nPlayerID, MIN_ADMIN_LEVEL)
  945.  
  946. if ((get_pcvar_num(cvar_retry) == 0) && (!bIsAdmin)) // admin always can enqueue themselves, even when this feature is disabled
  947. return false
  948.  
  949. new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0))
  950. if (nServer == g_nOwnServer)
  951. return false
  952. if (bIsAdmin)
  953. return true
  954. if (((!bCanRedirectByPassword) || (g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL))))
  955. return false
  956.  
  957. return true
  958. }
  959.  
  960. /// <summary>Checks whether the player with ID <paramref name="id"/> is already in redirection queue for server with number <paramref name="nServer"/>.</summary>
  961. /// <param name="nServer">The server number which shall be checked whether player with <paramref name="id"/> is in its queue.</param>
  962. /// <param name="id">The internal player ID which shall be checked whether it is queued for server <paramref name="nServer"/>.</param>
  963. /// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks>
  964. /// <returns>true if player is in queue, false if not.</returns>
  965. /// <seealso name="queue_add"/>
  966. /// <seealso name="queue_remove"/>
  967. public bool:is_queued(id, nServer)
  968. {
  969. new nCount = 0
  970. while (nCount < g_nRetryCount)
  971. {
  972. if ((g_nRetryQueue[nCount][0] == id) && (g_nRetryQueue[nCount][1] == nServer))
  973. return true
  974. nCount++
  975. }
  976. return false
  977. }
  978.  
  979. /// <summary>Adds the player with ID <paramref name="id"/> to the redirection queue for server with number <paramref name="nServer"/>.</summary>
  980. /// <param name="nServer">The server number to add the player with <paramref name="id"/> to its queue.</param>
  981. /// <param name="id">The internal player ID which shall be added to the queue for server <paramref name="nServer"/>.</param>
  982. /// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks>
  983. /// <seealso name="is_queued"/>
  984. /// <seealso name="queue_remove"/>
  985. public queue_add(id, nServer)
  986. {
  987. if (get_pcvar_num(cvar_retry) > 0)
  988. {
  989. //first check whether the server-player-combination is not already in queue
  990. new nCount = 0
  991. new nServerQueue = 0
  992. while (nCount < g_nRetryCount)
  993. {
  994. // count how many people are in the queue for the target server
  995. if (g_nRetryQueue[nCount][1] == nServer)
  996. {
  997. nServerQueue++
  998. // no need to continue when he already is in the queue
  999. if (g_nRetryQueue[nCount][0] == id)
  1000. return
  1001. }
  1002. nCount++
  1003. }
  1004.  
  1005. new sUserNick[MAX_NAME_LEN]
  1006. get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
  1007.  
  1008. if (get_pcvar_num(cvar_show) == 1)
  1009. {
  1010. new naPlayers[MAX_PLAYERS]
  1011. new nPlayerNum, nPlayerCount, nCurrentPlayer
  1012. get_players(naPlayers, nPlayerNum, "c")
  1013. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1014. {
  1015. nCurrentPlayer = naPlayers[nPlayerCount]
  1016. if (nCurrentPlayer != id) // he has his own message
  1017. client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_QUEUE_ANNOUNCE", sUserNick, g_saServerNames[nServer])
  1018. }
  1019. }
  1020.  
  1021.  
  1022. if (g_bDebug)
  1023. log_amx("added player %i to queue for server %i in slot %i", id, nServer, g_nRetryCount)
  1024.  
  1025.  
  1026. g_nRetryQueue[g_nRetryCount][0] = id
  1027. g_nRetryQueue[g_nRetryCount][1] = nServer
  1028. g_nRetryCount++
  1029.  
  1030. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_ADD", ++nServerQueue, g_saServerNames[nServer])
  1031. }
  1032. else
  1033. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_DEACTIVATED")
  1034. }
  1035.  
  1036. /// <summary>Removes the player with ID <paramref name="id"/> from the redirection queue for server with number <paramref name="nServer"/>.</summary>
  1037. /// <param name="nServer">The server number to remove the player with <paramref name="id"/> from its queue.</param>
  1038. /// <param name="id">The internal player ID which shall be removed from the queue for server <paramref name="nServer"/>.</param>
  1039. /// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks>
  1040. /// <seealso name="is_queued"/>
  1041. /// <seealso name="add_remove"/>
  1042. public queue_remove(id, nServer)
  1043. {
  1044. new nCount = 0
  1045. while (nCount < g_nRetryCount)
  1046. {
  1047. if ((g_nRetryQueue[nCount][0] == id) && ((nServer == -1) || (g_nRetryQueue[nCount][1] == nServer)))
  1048. { // ok, remove from queue and let all others go one place up
  1049.  
  1050. // in case it's the last entry in queue where the following loop would never be executed:
  1051. g_nRetryQueue[nCount][0] = -1
  1052. g_nRetryQueue[nCount][1] = -1
  1053.  
  1054. // move other entries up
  1055. while ((nCount + 1) < g_nRetryCount)
  1056. {
  1057. g_nRetryQueue[nCount][0] = g_nRetryQueue[nCount + 1][0]
  1058. g_nRetryQueue[nCount][1] = g_nRetryQueue[nCount + 1][1]
  1059. nCount++
  1060. }
  1061. g_nRetryCount--
  1062. break
  1063. }
  1064. nCount++
  1065. }
  1066. }
  1067.  
  1068. /// <summary>Resets the setinfo string of the player with <paramref name="id"/> by removing tags that xREDIRECT used.</summary>
  1069. /// <param name="id">The internal player ID of the player that shall have the setinfo data resetted. It is passed as an array so that this function can easily be called from <seealso name="set_task"/>.</param>
  1070. public reset_info(id[])
  1071. {
  1072. client_cmd(id[0], "setinfo ^"xredir^" ^"^"")
  1073. client_cmd(id[0], "setinfo ^"password^" ^"^"")
  1074.  
  1075. }
  1076.  
  1077. /// <summary>Announce the servers on top of the screen. The position and interval for announcements can be set by CVARs.</summary>
  1078. public announce_servers()
  1079. {
  1080. if (get_pcvar_num(cvar_active) == 1)
  1081. {
  1082. if (g_nServerCount > 0)
  1083. {
  1084. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1085. new sAnnounceBody[MAX_MENUBODY_LEN] = ""
  1086. new nDisplayCount = 0
  1087. new nServerCount = g_nNextAnnounceServer
  1088. if (nServerCount >= g_nServerCount)
  1089. nServerCount = 0
  1090.  
  1091. while ((nServerCount < g_nServerCount) && (nDisplayCount < 8))
  1092. {
  1093.  
  1094. if (!((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY)) || ((get_pcvar_num(cvar_hidedown) > 1) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer))))
  1095. {
  1096. if (nServerCount == g_nOwnServer)
  1097. {
  1098. new sMap[MAX_MAP_LEN]
  1099. get_mapname(sMap, MAX_MAP_LEN - 1)
  1100. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s [%s] (%d/%d)", sAnnounceBody, g_saServerNames[nServerCount], sMap, get_playersnum(1), get_maxplayers())
  1101. }
  1102. else
  1103. {
  1104. if (nCheckMethod == 0)
  1105. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount])
  1106. else
  1107. if (g_baServerResponding[nServerCount])
  1108. {
  1109. if (nCheckMethod == 1)
  1110. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount])
  1111. else if (nCheckMethod == 2)
  1112. 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])
  1113. }
  1114. else
  1115. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s (down)", sAnnounceBody, g_saServerNames[nServerCount])
  1116. }
  1117. }
  1118. nServerCount++
  1119. nDisplayCount++
  1120. }
  1121. g_nNextAnnounceServer = nServerCount
  1122. set_hudmessage(000, 100, 255, -1.0, 0.01, 0, 0.0, 10.0, 0.5, 0.10, 1)
  1123. //show_hudmessage(0, sAnnounceBody)
  1124.  
  1125. if (get_pcvar_float(cvar_announce) > 0.0)
  1126. {
  1127. new nAnnounceMode = get_pcvar_num(cvar_announce_mode)
  1128. if (nAnnounceMode > 0)
  1129. {
  1130. new naPlayers[MAX_PLAYERS]
  1131. new nPlayerNum, nPlayerCount
  1132. new sAnnounceText[MAX_MENUBODY_LEN]
  1133. if ((nAnnounceMode == 1) || (nAnnounceMode == 3))
  1134. {
  1135. get_players(naPlayers, nPlayerNum, "ac") // alive players
  1136. 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)
  1137. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1138. {
  1139. if (get_pcvar_num(cvar_manual) >= 1)
  1140. format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody)
  1141. else
  1142. sAnnounceText = sAnnounceBody
  1143. show_hudmessage(naPlayers[nPlayerCount], sAnnounceText)
  1144. }
  1145. }
  1146. if ((nAnnounceMode == 2) || (nAnnounceMode == 3))
  1147. {
  1148. get_players(naPlayers, nPlayerNum, "bc") // dead players
  1149. 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"
  1150. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1151. {
  1152. if (get_pcvar_num(cvar_manual) >= 1)
  1153. format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody)
  1154. else
  1155. sAnnounceText = sAnnounceBody
  1156. show_hudmessage(naPlayers[nPlayerCount], sAnnounceText)
  1157. }
  1158. }
  1159. }
  1160. }
  1161. }
  1162. }
  1163. return PLUGIN_HANDLED
  1164. }
  1165.  
  1166.  
  1167. /// <summary>Shows the sub menu for server with number <paramref name="nServer"/> to the the player with ID <paramref name="id"/>.</summary>
  1168. /// <param name="nServer">The server to show the sub menu for.</param>
  1169. /// <param name="id">The ID of the player to show the sub menu.</param>
  1170. /// <seealso name="server_menu_select"/>
  1171. /// <seealso name="sub_menu_select"/>
  1172. /// <seealso name="show_server_menu"/>
  1173. public show_sub_menu(id, nServer)
  1174. {
  1175. new nCanRedirect = can_redirect_player(nServer, id, 2, false)
  1176. new nCanRedirectIgnoreAdmin = can_redirect_player(nServer, id, 2, true);
  1177. new bool:bCanQueue = can_queue_player(nServer, id)
  1178. new bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR)
  1179. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1180. new sMenuBody[MAX_MENUBODY_LEN]
  1181.  
  1182. // can we display colors?
  1183. if (bColorMenu)
  1184. {
  1185. format(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SRVINFO_CAPTION")
  1186. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer])
  1187. }
  1188. else
  1189. {
  1190. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SRVINFO_CAPTION")
  1191. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer])
  1192. }
  1193.  
  1194. // can we display map and player information?
  1195. if (((nCheckMethod == 2) && ((g_baServerResponding[nServer])) || (nServer == g_nOwnServer)))
  1196. {
  1197. if (bColorMenu)
  1198. {
  1199. if (nServer == g_nOwnServer)
  1200. {
  1201. new sMap[MAX_MAP_LEN]
  1202. get_mapname(sMap, MAX_MAP_LEN - 1)
  1203. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap)
  1204. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_playersnum(1), get_maxplayers())
  1205. }
  1206. else
  1207. {
  1208. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer])
  1209. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer])
  1210. }
  1211. }
  1212. else
  1213. {
  1214. if (nServer == g_nOwnServer)
  1215. {
  1216. new sMap[MAX_MAP_LEN]
  1217. get_mapname(sMap, MAX_MAP_LEN - 1)
  1218. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap)
  1219. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_playersnum(1), get_maxplayers())
  1220. }
  1221. else
  1222. {
  1223. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer])
  1224. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer])
  1225. }
  1226. }
  1227. }
  1228.  
  1229. // make the next line red if colors are supported and (the user is no admin or it's the current server)
  1230. if ((bColorMenu) && ((!access(id, MIN_ADMIN_LEVEL)) || (nCanRedirect == 1)))
  1231. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\r", sMenuBody)
  1232. else
  1233. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n", sMenuBody)
  1234.  
  1235. // now display reason why we can't redirect there
  1236. switch (nCanRedirectIgnoreAdmin)
  1237. {
  1238. case 1:
  1239. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_CURRENT")
  1240. case 2:
  1241. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_PERMISSION")
  1242. case 3:
  1243. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_NOMANUAL")
  1244. case 4:
  1245. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_FULL")
  1246. case 5:
  1247. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_DOWN")
  1248. }
  1249.  
  1250. // enable/disable key for redirection/queue functionality
  1251. new key = (1<<9) // cancel
  1252. key = key | (1<<8) // back
  1253. if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
  1254. key = key | (1<<2) // refresh
  1255. if (nCanRedirect == 0)
  1256. key = key | (1<<0) // redirect
  1257. if (bCanQueue && (nCheckMethod > 1))
  1258. key = key | (1<<1) // enqueue
  1259.  
  1260. new sQueueMsg[30]
  1261. if (is_queued(id, nServer))
  1262. sQueueMsg = "MSG_LEAVEQUEUE"
  1263. else
  1264. sQueueMsg = "MSG_QUEUE"
  1265.  
  1266. // display the last menu items according to availability
  1267. if (bColorMenu)
  1268. {
  1269. if (nCanRedirect == 0)
  1270. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \w %L", sMenuBody, id, "MSG_REDIRECT")
  1271. else
  1272. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \d %L", sMenuBody, id, "MSG_REDIRECT")
  1273. if (bCanQueue && (nCheckMethod > 1))
  1274. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \w %L", sMenuBody, id, sQueueMsg)
  1275. else
  1276. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \d %L", sMenuBody, id, sQueueMsg)
  1277. if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
  1278. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \w %L", sMenuBody, id, "MSG_REFRESH")
  1279. else
  1280. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \d %L", sMenuBody, id, "MSG_REFRESH")
  1281. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9. \w %L", sMenuBody, id, "MSG_BACK")
  1282. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0. \w %L", sMenuBody, id, "MSG_CANCEL")
  1283. }
  1284. else
  1285. {
  1286. if (nCanRedirect == 0)
  1287. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n1. %L", sMenuBody, id, "MSG_REDIRECT")
  1288. else
  1289. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_REDIRECT")
  1290. if (bCanQueue && (nCheckMethod > 1))
  1291. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n2. %L", sMenuBody, id, sQueueMsg)
  1292. else
  1293. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, sQueueMsg)
  1294. if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
  1295. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n3. %L", sMenuBody, id, "MSG_REFRESH")
  1296. else
  1297. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, "MSG_REFRESH")
  1298. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_BACK")
  1299. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL")
  1300. }
  1301. g_nLastSelected[id - 1] = nServer
  1302. show_menu(id, key, sMenuBody, -1, "Detail Menu")
  1303. }
  1304.  
  1305. /// <summary>Shows the server menu page <paramref name="menupage"/> to the the player with ID <paramref name="id"/>.</summary>
  1306. /// <param name="id">The ID of the player to show the server menu to.</param>
  1307. /// <param name="menupage">The menu page number to show to the player. Offset is 0.</param>
  1308. /// <seealso name="server_menu_select"/>
  1309. /// <seealso name="sub_menu_select"/>
  1310. /// <seealso name="show_sub_menu"/>
  1311. public show_server_menu(id, menupage)
  1312. {
  1313. new nServerCount
  1314. if (get_pcvar_num(cvar_active) == 1)
  1315. {
  1316. if (g_nServerCount > 0)
  1317. {
  1318. new bool:bSubMenu = (get_pcvar_num(cvar_manual) >= 2)
  1319. new bool:bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR)
  1320. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1321. new sMenuBody[MAX_MENUBODY_LEN]
  1322. if (bColorMenu)
  1323. format(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SELECT_SERVER")
  1324. else
  1325. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SELECT_SERVER")
  1326.  
  1327.  
  1328. if (menupage <= 1)
  1329. nServerCount = 0
  1330. else
  1331. nServerCount = g_naMenuPageStart[id - 1][menupage - 2]
  1332.  
  1333. new nDisplayNumber = 1
  1334.  
  1335. new key = (1<<9) // cancel key is always enabled
  1336.  
  1337. new nHideDown = get_pcvar_num(cvar_hidedown)
  1338. if (nHideDown == 1)
  1339. nHideDown = 3
  1340.  
  1341. // the 3 parts of a menu item, third part only displayed with redirect_check_method >= 2
  1342. new sMenuNumber[10]
  1343. new sMenuSrvName[50]
  1344. new sMenuInfo[50]
  1345. if (nCheckMethod < 2)
  1346. sMenuInfo = ""
  1347.  
  1348. while ((nDisplayNumber < 9) && (nServerCount < g_nServerCount))
  1349. {
  1350. if (!((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY)) || ((nHideDown > 2) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer))))
  1351. {
  1352. new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServerCount], "") && (g_naServerPublicPassword[nServerCount] == 0) && (!access(id, MIN_ADMIN_LEVEL)))
  1353.  
  1354. if (bColorMenu)
  1355. {
  1356. format(sMenuNumber, 9, "\y%d. ", nDisplayNumber)
  1357. if (bSubMenu)
  1358. format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
  1359. else
  1360. format(sMenuSrvName, 49, "\d %s", g_saServerNames[nServerCount])
  1361. }
  1362. else
  1363. {
  1364. format(sMenuNumber, 9, "%d. ", nDisplayNumber)
  1365. format(sMenuSrvName, 49, " %s", g_saServerNames[nServerCount])
  1366. }
  1367.  
  1368. new bool:bCanRedirect = true
  1369. sMenuInfo = ""
  1370.  
  1371. // manual redirection to that server is disabled or server is passworded but password is not public and user has insufficent admin rights
  1372. if ((nCheckMethod == 2) && (((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NOMANUAL)) || !bCanRedirectByPassword)))
  1373. {
  1374. bCanRedirect = false
  1375. if ((!bColorMenu) && (!bSubMenu))
  1376. sMenuNumber = "_. "
  1377. if (nCheckMethod == 2)
  1378. format(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1379. }
  1380. // server is full (and player has insufficient rights to join on an admin slot)
  1381. if ((nCheckMethod == 2) && (((g_naServerActivePlayers[nServerCount] == (g_naServerMaxPlayers[nServerCount] - 1)) && (g_naServerReserveSlots[nServerCount] > 0) && (!access(id, MIN_ADMIN_LEVEL))) || (g_naServerActivePlayers[nServerCount] >= g_naServerMaxPlayers[nServerCount])))
  1382. {
  1383. bCanRedirect = false
  1384. if ((!bColorMenu) && (!bSubMenu))
  1385. sMenuNumber = "_. "
  1386. if (bColorMenu)
  1387. format(sMenuInfo, 49, " [%s] \r(\w%d/%d\r)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1388. else
  1389. format(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1390. }
  1391. // server is down
  1392. if ((nCheckMethod > 0) && (!g_baServerResponding[nServerCount]))
  1393. {
  1394. if ((!bColorMenu) && (!bSubMenu))
  1395. sMenuNumber = "_. "
  1396. bCanRedirect = false
  1397. if (bColorMenu)
  1398. sMenuInfo = " \r(\wdown\r)"
  1399. else
  1400. sMenuInfo = " (down)"
  1401. }
  1402. // server is current server
  1403. if (nServerCount == g_nOwnServer)
  1404. {
  1405. if ((!bColorMenu) && (!bSubMenu))
  1406. sMenuNumber = "_. "
  1407. bCanRedirect = false
  1408. new sMap[MAX_MAP_LEN]
  1409. get_mapname(sMap, MAX_MAP_LEN - 1)
  1410. if (bSubMenu && bColorMenu)
  1411. format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", sMap, get_playersnum(1), get_maxplayers())
  1412. else
  1413. format(sMenuInfo, 49, " [%s] (%d/%d)", sMap, get_playersnum(1), get_maxplayers())
  1414. }
  1415.  
  1416. // everything's fine, we can redirect here
  1417. if (bCanRedirect)
  1418. {
  1419. if (bColorMenu)
  1420. {
  1421. format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
  1422. if (nCheckMethod > 1)
  1423. format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1424. }
  1425. else
  1426. {
  1427. if (nCheckMethod > 1)
  1428. format(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1429. }
  1430.  
  1431. key = key | (1<<(nDisplayNumber - 1))
  1432. g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount
  1433. }
  1434. else if ((bSubMenu) && (nServerCount != g_nOwnServer)) // display server like it was enabled when submenues are enabled
  1435. if (bColorMenu)
  1436. {
  1437. format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
  1438. if ((nCheckMethod == 0) && (g_baServerResponding[nServerCount]))
  1439. format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1440. }
  1441.  
  1442. // assemble the menu item and append it to menu body
  1443. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%s%s%s", sMenuBody, sMenuNumber, sMenuSrvName, sMenuInfo)
  1444.  
  1445. // if enabled a submenu is always possible to be displayed, regardless of the server's redirection status
  1446. if (bSubMenu)
  1447. {
  1448. key = key | (1<<(nDisplayNumber - 1))
  1449. g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount
  1450. }
  1451.  
  1452. nDisplayNumber++
  1453. }
  1454. nServerCount++
  1455. }
  1456.  
  1457. if (nServerCount < g_nServerCount)
  1458. {
  1459. if (bColorMenu)
  1460. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\w %L", sMenuBody, id, "MSG_MORE")
  1461. else
  1462. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_MORE")
  1463. key = key | (1<<8)
  1464. }
  1465. else
  1466. {
  1467. if (bColorMenu)
  1468. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\d %L", sMenuBody, id, "MSG_MORE")
  1469. else
  1470. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_MORE")
  1471. }
  1472.  
  1473. if (bColorMenu)
  1474. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, "MSG_CANCEL")
  1475. else
  1476. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL")
  1477.  
  1478. show_menu(id, key, sMenuBody, -1, "Redirect Menu")
  1479. }
  1480. }
  1481. g_naMenuPageStart[id - 1][menupage - 1] = nServerCount
  1482.  
  1483. g_naLastMenuPages[id - 1] = menupage
  1484. }
  1485.  
  1486.  
  1487. /// <summary>Reloads the servers from server list. Takes care of variable and array reinitialization.</summary>
  1488. /// <remarks>To be able to rely on this in the future make sure to add an initialization here for all variables you add!</remarks>
  1489. public srvcmd_reload()
  1490. {
  1491. new nCounter
  1492.  
  1493. // clear all global arrays and variables before reloading
  1494. for (nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++)
  1495. {
  1496. if (g_naServerSockets[nCounter] > 0)
  1497. {
  1498. socket_close(g_naServerSockets[nCounter])
  1499. g_naServerSockets[nCounter] = 0
  1500. }
  1501. g_naServerPorts[nCounter] = 27015
  1502. g_naServerActivePlayers[nCounter] = -1
  1503. g_naServerMaxPlayers[nCounter] = -1
  1504. g_naServerCmdBackup[nCounter] = DEFAULT_CMDBACKUP
  1505. g_naServerFlags[nCounter] = 0
  1506. g_naServerReserveSlots[nCounter] = 0
  1507. g_baServerResponding[nCounter] = false
  1508. g_saServerMap[nCounter] = ""
  1509. g_saServerNames[nCounter] = ""
  1510. g_saServerAddresses[nCounter] = ""
  1511. g_saServerPasswords[nCounter] = ""
  1512. g_naServerPublicPassword[nCounter] = 0
  1513. }
  1514.  
  1515. // reset global variables
  1516. g_nNextAnnounceServer = 0
  1517. g_nServerCount = 0
  1518. g_nLastRedirectServer = -1
  1519. g_sLastRedirectName = ""
  1520. g_nOwnServer = -1
  1521. g_nRetryCount = 0
  1522.  
  1523. for (new nPlrCnt = 0; nPlrCnt < MAX_PLAYERS; nPlrCnt++)
  1524. {
  1525. // server IDs might change and thus render all currently saved server IDs invalid, so remove them, to be sure
  1526. g_nRetryQueue[nPlrCnt][0] = -1
  1527. g_nRetryQueue[nPlrCnt][1] = -1
  1528. g_nLastServer[nPlrCnt] = -1
  1529. g_nLastSelected[nPlrCnt] = -1
  1530. }
  1531.  
  1532. load_servers()
  1533.  
  1534. new sFullAddress[MAX_SERVERADDRESS_LEN]
  1535. new sTmpServerIP[MAX_IP_LEN + MAX_PORT_LEN]
  1536. get_cvar_string("net_address", sTmpServerIP, MAX_IP_LEN + MAX_PORT_LEN - 1)
  1537. new sTmpOwnAddress[MAX_SERVERADDRESS_LEN]
  1538. get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1)
  1539.  
  1540. // define the own server again
  1541. new nServerCount = 0
  1542. while (nServerCount < g_nServerCount)
  1543. {
  1544. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount])
  1545. if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerIP))
  1546. {
  1547. g_nOwnServer = nServerCount
  1548. break
  1549. }
  1550. nServerCount++
  1551. }
  1552. if (g_nOwnServer == -1)
  1553. log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR")
  1554. }
  1555.  
  1556.  
  1557. /// <summary>This is needed so server doesn't display "unknown command: pickserver". Returning PLUGIN_HANDLED directly in cmd_show_server_menu would supress the chat message so we use this workaround.</summary>
  1558. public cmd_pickserver(id, level, cid)
  1559. {
  1560. cmd_show_server_menu(id, level, cid)
  1561. return PLUGIN_HANDLED
  1562. }
  1563.  
  1564. /// <summary>This function does the actual redirection. It is also what <seealso name="native_redirect"/> is a wrapper for with <paramref name="nServer"/> preset to -1 (the external plugin does not know about our server list and numbers anyway) and <paramref name="bIgnoreSource"/> preset to true (an external plugin does not care whether this would mean redirecting the player back to where he came from).</summary>
  1565. /// <summary>It is aware of user permissions and has several options which are set via parameters.</summary>
  1566. /// <param name="id">ID of player to redirect.</param>
  1567. /// <param name="nServer">Target server, -1 for automatic choosing according to redirect_auto.</param>
  1568. /// <param name="bCanOther">If nServer is no valid redirect target can we use another server instead?</param>
  1569. /// <param name="bCanDrop">Drop user if no server was found?</param>
  1570. /// <param name="bIgnoreSource>"Redirect regardless of redirecting would be back to source server.</param>
  1571. /// <seealso name="native_redirect"/>
  1572. /// <seealso name="cmd_redirect_user"/>
  1573. public redirect(id, nServer, bCanOther, bCanDrop, bIgnoreSource)
  1574. {
  1575.  
  1576. new nForwardServer = -1
  1577. new bool:bFoundServer = false
  1578. new nRedirType
  1579. if (nServer == -1)
  1580. nRedirType = 1
  1581. else
  1582. nRedirType = 2
  1583.  
  1584. new nSourceServer
  1585.  
  1586. if (bIgnoreSource)
  1587. {
  1588. nSourceServer = -1
  1589. }
  1590. else
  1591. {
  1592. new sSourceServer[3]
  1593. get_user_info(id, "xredir", sSourceServer, 2)
  1594. if (!is_str_num(sSourceServer))
  1595. nSourceServer = -1
  1596. else
  1597. nSourceServer = str_to_num(sSourceServer)
  1598. if ((nSourceServer < 0) || (nSourceServer >= g_nServerCount))
  1599. nSourceServer = -1
  1600. }
  1601.  
  1602. if ((can_redirect_player(nServer, id, nRedirType, false) > 0) || (nServer == -1))
  1603. {
  1604. if (!bCanOther)
  1605. {
  1606. if (bCanDrop)
  1607. {
  1608. client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER")
  1609. client_cmd(id, "disconnect")
  1610. }
  1611. return false
  1612. }
  1613.  
  1614. nForwardServer = 0
  1615.  
  1616. // make sure at least one valid server exists or the second loop could be endless
  1617. while (nForwardServer < g_nServerCount)
  1618. {
  1619. if ((can_redirect_player(nForwardServer, id, nRedirType, false) == 0) && (nForwardServer != nSourceServer))
  1620. {
  1621. bFoundServer = true
  1622. break
  1623. }
  1624. nForwardServer++
  1625. }
  1626. new nAutoMode = get_pcvar_num(cvar_auto)
  1627. if ((nAutoMode == 1) || (nAutoMode == 3) || (nAutoMode == 5)) // redirect to random server
  1628. nForwardServer = -1
  1629. }
  1630. else
  1631. {
  1632. nForwardServer = nServer
  1633. bFoundServer = true
  1634. }
  1635.  
  1636. if (bFoundServer)
  1637. {
  1638. while (nForwardServer == -1)
  1639. {
  1640. nForwardServer = random_num(0, g_nServerCount - 1)
  1641. if ((can_redirect_player(nForwardServer, id, nRedirType, false) > 0) || ((nForwardServer == nSourceServer)))
  1642. nForwardServer = -1
  1643. }
  1644.  
  1645. new sUserNick[MAX_NAME_LEN]
  1646. get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
  1647. if (!equal(g_saServerPasswords[nForwardServer], "")) // set the user's server connect password if needed
  1648. client_cmd(id, "setinfo ^"password^" ^"%s^"", g_saServerPasswords[nForwardServer])
  1649. client_cmd(id, "setinfo ^"xredir^" ^"%d^"", g_nOwnServer)
  1650.  
  1651. new sCheckAddress[MAX_IP_LEN]
  1652. get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
  1653. new sFullAddress[MAX_SERVERADDRESS_LEN]
  1654. if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[nForwardServer], "")))
  1655. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[nForwardServer], g_naServerPorts[nForwardServer])
  1656. else
  1657. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nForwardServer], g_naServerPorts[nForwardServer])
  1658. if (nRedirType == 1)
  1659. client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_SERVER_FULL_REDIRECTING", g_saServerNames[nForwardServer])
  1660. client_cmd(id, ";^"Connect^"%s", sFullAddress)
  1661.  
  1662.  
  1663. if (get_pcvar_num(cvar_show) == 1)
  1664. {
  1665. new nPlayers[MAX_PLAYERS]
  1666. new nPlayerNum, nPlayerCount, nCurrentPlayer
  1667. get_players(nPlayers, nPlayerNum, "c")
  1668. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1669. {
  1670. nCurrentPlayer = nPlayers[nPlayerCount]
  1671. if (get_pcvar_num(cvar_follow) == 1)
  1672. client_print(nCurrentPlayer, print_chat, "%s: %L - %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer], nCurrentPlayer, "MSG_FOLLOW")
  1673. else
  1674. client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer])
  1675. }
  1676. }
  1677.  
  1678. g_nLastRedirectServer = nForwardServer
  1679. g_sLastRedirectName = sUserNick
  1680. }
  1681. else if (bCanDrop)
  1682. {
  1683. client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER")
  1684. client_cmd(id, "disconnect")
  1685. }
  1686. return true
  1687. }
  1688.  
  1689. /// <summary>Basically a wrapper for <seealso name="redirect"/> to make it available to other pugins as native.</summary>
  1690. /// <seealso name="redirect"/>
  1691. /// <seealso name="cmd_redirect_user"/>
  1692. public native_redirect(id, nServer, bCanDrop)
  1693. {
  1694. redirect(id, nServer, (nServer == -1), bCanDrop, true)
  1695. return PLUGIN_HANDLED
  1696. }
  1697.  
  1698. /// <summary>Handler for in-game command <paramref name="redirect_user"/>, checks user permissions for this command and uses <seealso name="redirect"/> to do the redirection.</summary>
  1699. /// <seealso name="redirect"/>
  1700. /// <seealso name="native_redirect"/>
  1701. public cmd_redirect_user(id, level, cid)
  1702. {
  1703. if (!cmd_access(id, level, cid, 2))
  1704. return PLUGIN_HANDLED
  1705.  
  1706. new nForwardServer = -1
  1707. new sName[32]
  1708. read_argv(1, sName, 31)
  1709. new nCmdID = cmd_target(id, sName, 8)
  1710.  
  1711. if (!nCmdID)
  1712. return PLUGIN_HANDLED
  1713.  
  1714. // contains destination server number?
  1715. if (read_argc() > 2)
  1716. {
  1717. new argtmp[3]
  1718. read_argv(2, argtmp, 2)
  1719. if (is_str_num(argtmp))
  1720. nForwardServer = (str_to_num(argtmp) - 1)
  1721. }
  1722.  
  1723. redirect(nCmdID, nForwardServer, (nForwardServer == -1), true, true)
  1724.  
  1725. return PLUGIN_HANDLED
  1726. }
  1727.  
  1728. /// <summary>Handler for in-game command <paramref name="pickserver"/> or chat command <paramref name="/server"/>. Shows the server menu to the player using <seealso name="show_server_menu"/>.</summary>
  1729. /// <seealso name="show_server_menu"/>
  1730. public cmd_show_server_menu(id, level, cid)
  1731. {
  1732. if (get_pcvar_num(cvar_manual) >= 1)
  1733. show_server_menu(id, 1)
  1734. else
  1735. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_MANUAL_DISABLED")
  1736. return PLUGIN_CONTINUE
  1737. }
  1738.  
  1739. /// <summary>Handler for chat command <paramref name="/retry"/>. Adds the user to the retry queue using <seealso name="queue_add"/>.</summary>
  1740. /// <seealso name="queue_add"/>
  1741. public cmd_retry(id, level, cid)
  1742. {
  1743. if (g_nLastServer[id - 1] > -1)
  1744. queue_add(id, g_nLastServer[id - 1])
  1745. else
  1746. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_NO_LAST")
  1747. return PLUGIN_CONTINUE
  1748. }
  1749.  
  1750. /// <summary>Handler for chat command <paramref name="/stopretry"/>. Removes the user from the retry queue using <seealso name="queue_remove"/>.</summary>
  1751. /// <seealso name="queue_remove"/>
  1752. public cmd_stopretry(id, level, cid)
  1753. {
  1754. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE_ALL", g_saServerNames[g_nLastServer[id - 1]])
  1755. queue_remove(id, -1)
  1756. return PLUGIN_CONTINUE
  1757. }
  1758.  
  1759. /// <summary>Handler for chat command <paramref name="/follow"/>. Sends a player after the last player that was redirected using <seealso name="redirect"/>.</summary>
  1760. /// <seealso name="redirect"/>
  1761. public cmd_follow_player(id, level, cid)
  1762. {
  1763. if (get_pcvar_num(cvar_active) == 1)
  1764. {
  1765. if (get_pcvar_num(cvar_follow) == 1)
  1766. {
  1767. if (g_nLastRedirectServer >= 0)
  1768. {
  1769. console_print(id, "%s: %L", PLUGIN_TAG, id, "MSG_REDIRECTING", g_saServerNames[g_nLastRedirectServer])
  1770. new sFullAddress[MAX_SERVERADDRESS_LEN]
  1771. new sCheckAddress[MAX_IP_LEN]
  1772. get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
  1773. if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[g_nLastRedirectServer], "")))
  1774. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer])
  1775. else
  1776. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer])
  1777. client_cmd(id, ";^"Connect^"%s", sFullAddress)
  1778. new sUserNick[MAX_NAME_LEN]
  1779. get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
  1780. if (get_pcvar_num(cvar_show) == 1)
  1781. client_print(0, print_chat, "%s: %L - %L", PLUGIN_TAG, id, "MSG_FOLLOWED", sUserNick, g_sLastRedirectName, g_saServerNames[g_nLastRedirectServer], id, "MSG_FOLLOW")
  1782. g_sLastRedirectName = sUserNick
  1783. }
  1784. else
  1785. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_CANT_FOLLOW")
  1786.  
  1787. }
  1788. else
  1789. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_FOLLOW_DISABLED")
  1790. }
  1791. return PLUGIN_CONTINUE
  1792. }
  1793.  
  1794. /// <summary>Event handler for sub menu selection.</summary>
  1795. /// <summary>When the user presses a number key in the sub menu this handler is called.</summary>
  1796. /// <param name="id">Slot ID of player that selected a menu item.</param>
  1797. /// <param name="key">Key that was pressed, number between 0 and 9.</param>
  1798. /// <seealso name="server_menu_select"/>
  1799. /// <seealso name="show_server_menu"/>
  1800. /// <seealso name="show_sub_menu"/>
  1801. public sub_menu_select(id, key)
  1802. {
  1803. new nServer = g_nLastSelected[id - 1]
  1804. if (key == 0) // redirect
  1805. {
  1806. // check if meanwhile the redirection is not possible anymore - if so, refresh the detail menu
  1807. if (can_redirect_player(nServer, id, 2, false) > 0)
  1808. show_sub_menu(id, nServer)
  1809. else
  1810. redirect(id, nServer, false, false, true)
  1811. }
  1812. else if (key == 1) // queue
  1813. {
  1814. if (is_queued(id, nServer))
  1815. {
  1816. queue_remove(id, nServer)
  1817. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE", g_saServerNames[nServer])
  1818. }
  1819. else
  1820. {
  1821. queue_add(id, nServer)
  1822. }
  1823. }
  1824. else if (key == 2) // refresh
  1825. {
  1826. show_sub_menu(id, nServer)
  1827. }
  1828. else if (key == 8) // go back to where the user was before in main menu
  1829. show_server_menu(id, g_naLastMenuPages[id - 1])
  1830. }
  1831.  
  1832. /// <summary>Event handler for server menu selection.</summary>
  1833. /// <summary>When the user presses a number key in the server menu this handler is called.</summary>
  1834. /// <summary>Depending on settings it will display a sub menu or redirect the user.</summary>
  1835. /// <param name="id">Slot ID of player that selected a menu item.</param>
  1836. /// <param name="key">Key that was pressed, number between 0 and 9.</param>
  1837. /// <seealso name="sub_menu_select"/>
  1838. /// <seealso name="show_server_menu"/>
  1839. /// <seealso name="show_sub_menu"/>
  1840. public server_menu_select(id, key)
  1841. {
  1842. if (key < 8)
  1843. {
  1844. new nServerIdx = g_naServerSelections[id - 1][key]
  1845.  
  1846. new nManualMode = get_pcvar_num(cvar_manual)
  1847. // show the detail menu?
  1848. if (((nManualMode == 2) && (can_redirect_player(nServerIdx, id, 2, false) > 0)) || (nManualMode == 3))
  1849. show_sub_menu(id, nServerIdx)
  1850. else
  1851. redirect(id, nServerIdx, false, false, true)
  1852. }
  1853. else
  1854. {
  1855. if (key == 8)
  1856. show_server_menu(id, g_naLastMenuPages[id - 1] + 1)
  1857. }
  1858. }
  1859.  
  1860.  
  1861. /// <summary>Sends the information query packets to all other servers.</summary>
  1862. /// <summary>This sends the UDP server information query packets in old and new style HL format to all servers in the list.</summary>
  1863. /// <summary>Receiving of server data is handled by <seealso name="receive_serverquery_answers"/>.</summary>
  1864. /// <seealso name="receive_serverquery_answers"/>
  1865. public query_servers()
  1866. {
  1867. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1868. if (nCheckMethod == 0)
  1869. return PLUGIN_HANDLED
  1870. new socket_error
  1871. new sOldRequest[12]
  1872. new sNewRequest[26]
  1873.  
  1874. if (nCheckMethod == 1)
  1875. {
  1876. // we don't know what server it is so send both old and new style query
  1877. format(sOldRequest, 8, "%c%c%c%c%s", 255, 255, 255, 255, "ping")
  1878. format(sNewRequest, 5, "%c%c%c%c%c", 255, 255, 255, 255, 105)
  1879. }
  1880. else if (nCheckMethod == 2)
  1881. {
  1882. // we don't know what server it is so send both old and new style query
  1883. format(sOldRequest, 11, "%c%c%c%c%s", 255, 255, 255, 255, "details")
  1884. format(sNewRequest, 25, "%c%c%c%c%c%s%c", 255, 255, 255, 255, 84, "Source Engine Query", 0)
  1885. }
  1886.  
  1887. new nServerCount = 0
  1888. new nQuerySocket
  1889. new nCmdBackup
  1890. new nSendCount
  1891. while (nServerCount < g_nServerCount)
  1892. {
  1893. if (nServerCount != g_nOwnServer)
  1894. {
  1895. nQuerySocket = g_naServerSockets[nServerCount]
  1896. // first we clear the current receive buffer - we are sending a new request and don't care for old data anymore
  1897. if (nQuerySocket > 0)
  1898. {
  1899. new sEmptyBufferDummy[512]
  1900. new nEndlessProtection = 0
  1901. while ((socket_change(nQuerySocket, 1)) && (nEndlessProtection < 500))
  1902. {
  1903. //log_amx("emptying socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount])
  1904. socket_recv(nQuerySocket, sEmptyBufferDummy, 512)
  1905. nEndlessProtection++
  1906. }
  1907. if (nEndlessProtection >= 500)
  1908. {
  1909. socket_close(nQuerySocket)
  1910. log_amx("WARNING: endless protection triggered for socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount])
  1911. }
  1912.  
  1913. }
  1914. else
  1915. {
  1916. // socket debug
  1917. //log_amx("opening socket for server %i (%s)", nServerCount, g_saServerNames[nServerCount])
  1918. if (!equal(g_saServerLocalAddresses[nServerCount], ""))
  1919. nQuerySocket = socket_open(g_saServerLocalAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error)
  1920. else
  1921. nQuerySocket = socket_open(g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error)
  1922. // socket debug
  1923. //log_amx("opened socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount])
  1924. }
  1925.  
  1926. if ((nQuerySocket > 0) && (socket_error == 0))
  1927. {
  1928. g_naServerSockets[nServerCount] = nQuerySocket
  1929. nCmdBackup = g_naServerCmdBackup[nServerCount]
  1930. // socket debug
  1931. //log_amx("sending query on socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount])
  1932. if (nCheckMethod == 1)
  1933. {
  1934. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1935. socket_send2(nQuerySocket, sOldRequest, 8)
  1936. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1937. socket_send2(nQuerySocket, sNewRequest, 5)
  1938. }
  1939. else if (nCheckMethod == 2)
  1940. {
  1941. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1942. socket_send2(nQuerySocket, sOldRequest, 11)
  1943. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1944. socket_send2(nQuerySocket, sNewRequest, 25)
  1945. }
  1946. }
  1947. else
  1948. {
  1949. g_naServerSockets[nServerCount] = 0
  1950. log_amx("%L", LANG_SERVER, "MSG_SOCKET_ERROR", socket_error, nServerCount)
  1951. }
  1952. }
  1953. nServerCount++
  1954. }
  1955. set_task(QUERY_TIMEOUT, "receive_serverquery_answers", TASKID_QUERY_RECEIVE)
  1956.  
  1957. return PLUGIN_HANDLED
  1958. }
  1959.  
  1960.  
  1961. /// <summary>Index an incoming UDP data packet.</summary>
  1962. /// <param name="sData">The raw UDP data string that was received.</param>
  1963. /// <param name="nDataLen">Length of the raw UDP data string as reported by the socket receive function.</param>
  1964. /// <param name="sFormatString">The string containing the format. It can contain the elements 124 and s. A digit just declares the number of bytes the element (type) has, "s" declares a string. An opening square bracket declares a byte option followed by a sequence of sub options. The sequence ends with a closing square bracket. Such options can occur more than once but may not be nested.</param>
  1965. /// <param name="aIndexes">The function stores the resulting character offsets of each index in this array.</param>
  1966. /// <remarks>This function assumes the given format string is correct as it is only created internally by a programmer, so there is no error checking whatsoever (e.g. an unsupported format character would lead the function into an endless loop).</remarks>
  1967. /// <returns>The number of indexes that were written (= the number of format elements).</returns>
  1968. public index_create(sData[MAX_INFO_LEN], nDataLen, sFormatString[100], aIndexes[MAX_INFO_FORMAT])
  1969. {
  1970. //log_amx("---------------------- indexing %s ----------------------", sFormatString)
  1971. new nFormatPos = 0 // current position within the format array
  1972. new nIndexPos = 0 // current position within the data array
  1973. new nDataIndex = 0 // current chracter index within the data stream
  1974. new nFormatPosMax = strlen(sFormatString)
  1975. while ((nIndexPos < nFormatPosMax) && (nDataIndex <= nDataLen))
  1976. {
  1977. switch (sFormatString[nFormatPos])
  1978. {
  1979. case '1': // "byte"
  1980. {
  1981. //log_amx("indexed byte <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos)
  1982. aIndexes[nIndexPos] = nDataIndex
  1983. nDataIndex++
  1984. nIndexPos++
  1985. }
  1986. case '2': // "short"
  1987. {
  1988. //log_amx("indexed short <%d %d> at %d, element %d, format position %d", sData[nDataIndex], sData[nDataIndex + 1], nDataIndex, nIndexPos, nFormatPos)
  1989. aIndexes[nIndexPos] = nDataIndex
  1990. nDataIndex += 2
  1991. nIndexPos++
  1992. }
  1993. case '4': // "long"
  1994. {
  1995. //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)
  1996. aIndexes[nIndexPos] = nDataIndex
  1997. nDataIndex += 4
  1998. nIndexPos++
  1999. }
  2000. case 's': // string
  2001. {
  2002. /*
  2003. new sDebugString[250]
  2004. arrayset(sDebugString, 0, 250)
  2005. copyc(sDebugString, 250, sData[nDataIndex], 0)
  2006. log_amx("indexed string <%s> at %d, element %d, format position %d", sDebugString, nDataIndex, nIndexPos, nFormatPos)
  2007. */
  2008. aIndexes[nIndexPos] = nDataIndex
  2009. do { nDataIndex++; } while ((sData[nDataIndex] != 0) && (nDataIndex < nDataLen)) // find the end of the string by searching a 0 character
  2010. nDataIndex++
  2011. nIndexPos++
  2012. }
  2013. case '[': // byte switch and start of optional formats
  2014. {
  2015. //log_amx("indexed switch <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos)
  2016. if (sData[nDataIndex] != 1) // skip options
  2017. {
  2018. do { nFormatPos++; } while ((sFormatString[nFormatPos] != ']') && (nFormatPos < nFormatPosMax))
  2019. //log_amx("skipped optional formats, now at format position %d")
  2020. }
  2021. else
  2022. //log_amx("----------- start of optional formats -----------")
  2023. nDataIndex++
  2024. nIndexPos++
  2025. }
  2026. case ']': // end of optional formats
  2027. {
  2028. //log_amx("----------- end of optional formats -----------")
  2029. nDataIndex++
  2030. }
  2031. default:
  2032. nDataIndex++
  2033. }
  2034. nFormatPos++
  2035. }
  2036. //log_amx("---------------------- end of indexing ----------------------")
  2037. //log_amx("%d < %d - %d <= %d", nIndexPos, nFormatPosMax, nDataIndex, nDataLen)
  2038. return nIndexPos
  2039. }
  2040.  
  2041. /// <summary>Gets a byte from the element at the given index.</summary>
  2042. /// <param name="sData">The raw UDP data string that was received.</param>
  2043. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2044. /// <returns>The requested byte value.</returns>
  2045. public index_get_byte(sData[MAX_INFO_LEN], nIndex)
  2046. {
  2047. return sData[nIndex]
  2048. }
  2049.  
  2050. /// <summary>Gets a short from the element at the given index.</summary>
  2051. /// <param name="sData">The raw UDP data string that was received.</param>
  2052. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2053. /// <returns>The requested short value.</returns>
  2054. public index_get_short(sData[MAX_INFO_LEN], nIndex)
  2055. {
  2056. return ((sData[nIndex] << 8) | (sData[nIndex + 1] & 0x00FF))
  2057. }
  2058.  
  2059. /// <summary>Gets a long from the element at the given index.</summary>
  2060. /// <param name="sData">The raw UDP data string that was received.</param>
  2061. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2062. /// <returns>The requested long value.</returns>
  2063. public index_get_long(sData[MAX_INFO_LEN], nIndex)
  2064. {
  2065. return ((sData[nIndex] << 24) | (sData[nIndex + 1] << 16) | (sData[nIndex + 2] << 8) | (sData[nIndex + 3] & 0x000000FF))
  2066. }
  2067.  
  2068. /// <summary>Gets a string from the element at the given index.</summary>
  2069. /// <param name="sData">The raw UDP data string that was received.</param>
  2070. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2071. /// <returns>The requested string value.</returns>
  2072. public index_get_string(sData[MAX_INFO_LEN], nIndex)
  2073. {
  2074. new aRet[MAX_INFO_LEN]
  2075. arrayset(aRet, 0, MAX_INFO_LEN)
  2076. copyc(aRet, MAX_INFO_LEN, sData[nIndex], 0)
  2077. return aRet
  2078. }
  2079.  
  2080. /// <summary>Handler for parsing the answers to server query packet.</summary>
  2081. /// <summary>This handler parses the UDP information answer packets from the servers that have been queried with <seealso name="query_servers"/>.</summary>
  2082. /// <seealso name="query_servers"/>
  2083. public receive_serverquery_answers()
  2084. {
  2085. new nCheckMethod = get_pcvar_num(cvar_check_method)
  2086.  
  2087. new sRcvBuf[MAX_INFO_LEN]
  2088. new nRcvLen
  2089. new nRecvCount
  2090. new sMap[MAX_MAP_LEN]
  2091. new nServerCount = 0
  2092. while (nServerCount < g_nServerCount)
  2093. {
  2094. if (!g_naServerSockets[nServerCount])
  2095. {
  2096. g_baServerResponding[nServerCount] = false
  2097. /*
  2098. should only happen for the g_nOwnServer
  2099.  
  2100. client_print(0, print_chat, "%s no socket", g_saServerNames[nServerCount])
  2101. */
  2102. }
  2103. else
  2104. {
  2105. nRecvCount = 0
  2106. new nCmdBackup = g_naServerCmdBackup[nServerCount]
  2107. g_baServerResponding[nServerCount] = false
  2108. new nSocket = g_naServerSockets[nServerCount]
  2109. while (socket_change(nSocket, 1) && (nRecvCount <= nCmdBackup))
  2110. {
  2111. // socket debug
  2112. //log_amx("socket changed: %i (%s)", nSocket, g_saServerNames[nServerCount])
  2113. nRecvCount++
  2114.  
  2115. // initialize our receive buffer
  2116. setc(sRcvBuf, MAX_INFO_LEN, 0);
  2117. //for (nClearCounter = 0; nClearCounter < MAX_INFO_LEN; nClearCounter++)
  2118. //sRcvBuf[nClearCounter] = 0
  2119. // socket debug
  2120. //log_amx("receiving from socket: %i (%s)", nSocket, g_saServerNames[nServerCount])
  2121. nRcvLen = socket_recv(nSocket, sRcvBuf, MAX_INFO_LEN)
  2122. // socket debug
  2123. //log_amx("finished receiving from socket %i (%s), received %i bytes", nSocket, g_saServerNames[nServerCount], nRcvLen)
  2124.  
  2125. //TODO: handle fragmented packets
  2126.  
  2127. if (nRcvLen > 5) // shortest reply is a ping response with length of 6
  2128. {
  2129. if (nCheckMethod == 1)
  2130. {
  2131. // ping response
  2132. if (equal(sRcvBuf, {-1,-1,-1,-1,'j'}, 5))
  2133. {
  2134. g_baServerResponding[nServerCount] = true
  2135. break
  2136. }
  2137. }
  2138. else if (nCheckMethod == 2)
  2139. {
  2140. new aIndexes[MAX_INFO_FORMAT]
  2141. if (equal(sRcvBuf, {-1,-1,-1,-1}, 4))
  2142. {
  2143. g_baServerResponding[nServerCount] = true
  2144. if (sRcvBuf[4] == 'm') // old HL1 or "goldsource" protocol
  2145. {
  2146. index_create(sRcvBuf, nRcvLen, A2S_INFO_GOLD_REPLY_FORMAT, aIndexes)
  2147. copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_GOLD_IDX_MAP]], 0)
  2148. g_saServerMap[nServerCount] = sMap
  2149. g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_NUMPLAYERS])
  2150. g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_MAXPLAYERS])
  2151. }
  2152. else if (sRcvBuf[4] == 'I') // source protocol
  2153. {
  2154. index_create(sRcvBuf, nRcvLen, A2S_INFO_SOURCE_REPLY_FORMAT, aIndexes)
  2155. copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_SOURCE_IDX_MAP]], 0)
  2156. g_saServerMap[nServerCount] = sMap
  2157. g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_NUMPLAYERS])
  2158. g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_MAXPLAYERS])
  2159. }
  2160. }
  2161. }
  2162. }
  2163. }
  2164. /*
  2165. if (nRecvCount == 0)
  2166. log_amx("no change on socket %i (%s)", g_naServerSockets[nServerCount], g_saServerNames[nServerCount])
  2167. */
  2168. //socket_close(nSocket)
  2169. //g_naServerSockets[nServerCount] = 0
  2170. }
  2171. nServerCount++
  2172. }
  2173.  
  2174. if (get_pcvar_num(cvar_retry) > 0)
  2175. {
  2176. // now search for players who queued themselves to be redirected
  2177. new nServer
  2178. new nPlrCnt = 0
  2179.  
  2180. while (nPlrCnt < g_nRetryCount)
  2181. {
  2182. nServer = g_nRetryQueue[nPlrCnt][1]
  2183. if (nServer > -1) // just to be sure
  2184. {
  2185. new nPlr = g_nRetryQueue[nPlrCnt][0]
  2186. if (can_redirect_player(nServer, nPlr, 2, false) == 0)
  2187. {
  2188. console_print(nPlr, "%s: %L", PLUGIN_TAG, nPlr, "MSG_RETRY_SUCCESS")
  2189. redirect(nPlr, nServer, false, false, true)
  2190. g_naServerActivePlayers[nServer]++
  2191. }
  2192. }
  2193. nPlrCnt++
  2194. }
  2195. }
  2196.  
  2197. return PLUGIN_HANDLED
  2198. }
  2199.  
  2200. /// <summary>Retrieves number of admins currently on the server.</summary>
  2201. /// <returns>Number of admins currently on server.</returns>
  2202. public get_admin_count()
  2203. {
  2204. new nPlayers[MAX_PLAYERS]
  2205. new nPlayerNum, nPlayerCount
  2206. get_players(nPlayers, nPlayerNum, "ch")
  2207. new nAdmins = 0
  2208. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2209. {
  2210. if (access(nPlayers[nPlayerCount], MIN_ADMIN_LEVEL))
  2211. nAdmins++
  2212. }
  2213. return nAdmins
  2214. }
  2215.  
  2216. /// <summary>Event handler for client disconnect event.</summary>
  2217. /// <summary>This handler makes sure people that have been in queue while disconnecting are removed from it.</summary>
  2218. /// <summary>Furthermore it resets the "last server" information for this now empty player slot.</summary>
  2219. /// <param name="id">Slot ID of player that was disconnected.</param>
  2220. public client_disconnect(id)
  2221. {
  2222. queue_remove(id, -1)
  2223. g_nLastServer[id - 1] = -1
  2224. }
  2225.  
  2226. /// <summary>Event handler for client authorized event.</summary>
  2227. /// <summary>This handler is called as soon as a connecting client was authenticated with WON/Steam system and received a WON/Steam ID.</summary>
  2228. /// <summary>It is used in favor of client_connected(), because here the client already logged in to AMXX user system and it can be determined whether the user is an admin, which is not the case for client_connected() event.</summary>
  2229. /// <param name="id">Slot ID of player that was authorized.</param>
  2230. public client_authorized(id)
  2231. {
  2232. if (is_user_bot(id) || is_user_hltv(id))
  2233. return PLUGIN_CONTINUE
  2234.  
  2235. if ((g_nOwnServer == -1) && (!g_bInitialized))
  2236. {
  2237. plugin_postinit()
  2238. }
  2239.  
  2240. g_naLastMenuPages[id - 1] = 1
  2241.  
  2242. new nAutoMode = get_pcvar_num(cvar_auto)
  2243. if (get_pcvar_num(cvar_active) == 1)
  2244. {
  2245. if (nAutoMode > 0)
  2246. {
  2247. if (((get_maxplayers() - get_playersnum(1)) == 0) || (nAutoMode > 2))
  2248. {
  2249. if (g_nServerCount > 0)
  2250. {
  2251. new bool:bLocalPriority = false
  2252. // if local slot reservation is enabled we need to check whether this is a local player
  2253. if (get_pcvar_num(cvar_localslots) == 1)
  2254. {
  2255. new sCheckAddress[MAX_IP_LEN]
  2256. get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
  2257. if (is_local_address(sCheckAddress))
  2258. bLocalPriority = true
  2259. }
  2260. new nMaxAdmins = get_pcvar_num(cvar_maxadmins)
  2261. if (nMaxAdmins == 0)
  2262. nMaxAdmins = MAX_PLAYERS
  2263. new bool:bRedirect = false // to keep some better overview assemble the if-comparison part by part in bRedirect
  2264. // 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)
  2265. bRedirect = bRedirect | (((nAutoMode == 1) || (nAutoMode == 2)) && ((!access(id, MIN_ADMIN_LEVEL)) || (get_pcvar_num(cvar_adminslots) == 0) || (get_admin_count() > nMaxAdmins)))
  2266. // redirect if automode is 3 or 4 and user is no admin
  2267. bRedirect = bRedirect | (((nAutoMode == 3) || (nAutoMode == 4)) && (!access(id, MIN_ADMIN_LEVEL)))
  2268. // redirect if automode is 5 or 6
  2269. bRedirect = bRedirect | ((nAutoMode == 5) || (nAutoMode == 6))
  2270. if (g_bDebug)
  2271. {
  2272. new sPlayerName[MAX_NAME_LEN]
  2273. get_user_name(id, sPlayerName, MAX_NAME_LEN - 1)
  2274. 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())
  2275. }
  2276. if (bRedirect)
  2277. {
  2278. //TODO: code in many parts redundant to what the redirect() function does except for the local-priority stuff - rather extend the redirect() function
  2279. if (bLocalPriority)
  2280. {
  2281. // find the remote user that is connected for the shortest time and redirect him
  2282.  
  2283. new nPlayers[MAX_PLAYERS]
  2284. new nPlayerNum, nPlayerCount
  2285. new nMinConnectedTime = 0x7FFFFFFF // make sure the first time value found will always be lower
  2286. new nMinTimePlayer = -1
  2287. new nUserTime
  2288. get_players(nPlayers, nPlayerNum, "ch")
  2289. new nCurID
  2290. new sCheckPlayerAddress[MAX_IP_LEN]
  2291. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2292. {
  2293. nCurID = nPlayers[nPlayerCount]
  2294. get_user_ip(nCurID, sCheckPlayerAddress, MAX_IP_LEN - 1, 1)
  2295.  
  2296. nUserTime = get_user_time(nCurID)
  2297. if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL)) && (!is_local_address(sCheckPlayerAddress)))
  2298. {
  2299. nMinTimePlayer = nCurID
  2300. nMinConnectedTime = nUserTime
  2301. }
  2302. }
  2303. if (nMinTimePlayer >= 0)
  2304. {
  2305. client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORLOCAL")
  2306. redirect(nMinTimePlayer, -1, true, true, true)
  2307. return PLUGIN_CONTINUE
  2308. }
  2309. else
  2310. if (g_bDebug)
  2311. log_amx("no valid redirect target to free up slot for local player %i", id)
  2312.  
  2313. }
  2314. else
  2315. {
  2316. redirect(id, -1, true, (nAutoMode < 3), false)
  2317. return PLUGIN_CONTINUE
  2318. }
  2319. }
  2320. else
  2321. {
  2322. // find the user that is connected for the shortest time and redirect him away
  2323.  
  2324. new nPlayers[MAX_PLAYERS]
  2325. new nPlayerNum, nPlayerCount
  2326. new nMinConnectedTime = 0x7FFFFFFF
  2327. new nMinTimePlayer = -1
  2328. new nUserTime
  2329. get_players(nPlayers, nPlayerNum, "ch")
  2330. new nCurID
  2331. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2332. {
  2333. nCurID = nPlayers[nPlayerCount]
  2334.  
  2335. nUserTime = get_user_time(nCurID)
  2336. if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL)))
  2337. {
  2338. nMinTimePlayer = nCurID
  2339. nMinConnectedTime = nUserTime
  2340. }
  2341. }
  2342. if (nMinTimePlayer >= 0)
  2343. {
  2344. client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORADMIN")
  2345. redirect(nMinTimePlayer, -1, true, true, true)
  2346. return PLUGIN_CONTINUE
  2347. }
  2348. else
  2349. if (g_bDebug)
  2350. log_amx("no valid redirect target to free up slot for admin %i", id)
  2351. }
  2352.  
  2353. }
  2354. }
  2355. else
  2356. {
  2357. if (g_bDebug)
  2358. {
  2359. new sPlayerName[MAX_NAME_LEN]
  2360. get_user_name(id, sPlayerName, MAX_NAME_LEN - 1)
  2361. log_amx("Not auto-redirecting <%s> (%d), automode: %d, current players/max: %d/%d", sPlayerName, id, nAutoMode, get_playersnum(1), get_maxplayers())
  2362. }
  2363. }
  2364. }
  2365. }
  2366.  
  2367. // show the welcome message delayed to that player
  2368. new sID[1]
  2369. sID[0] = id
  2370. set_task(20.0, "welcome_message", 0, sID, 1)
  2371.  
  2372. new sSourceServer[4] // maximum is 999 servers, so we have a maximum of 3 digits
  2373. get_user_info(id, "xredir", sSourceServer, 3)
  2374. if (strcmp(sSourceServer, "") != 0)
  2375. {
  2376. new nSourceServer = str_to_num(sSourceServer)
  2377. g_nLastServer[id - 1] = nSourceServer
  2378. if (g_bDebug)
  2379. log_amx("saved last server for player %i as server %i", id, g_nLastServer[id - 1])
  2380.  
  2381. if ((nSourceServer >= 0) && (nSourceServer < g_nServerCount))
  2382. {
  2383. if (get_pcvar_num(cvar_show) == 1)
  2384. {
  2385. new nPlayers[MAX_PLAYERS]
  2386. new nPlayerNum, nPlayerCount, nCurrentPlayer
  2387. new sConnectNick[MAX_NAME_LEN]
  2388. get_user_name(id, sConnectNick, MAX_NAME_LEN - 1)
  2389. get_players(nPlayers, nPlayerNum, "c")
  2390. 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)
  2391. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2392. {
  2393. nCurrentPlayer = nPlayers[nPlayerCount]
  2394. client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECT_RECEIVE", sConnectNick, g_saServerNames[nSourceServer])
  2395. }
  2396. }
  2397. }
  2398. new sID[1]
  2399. sID[0] = id
  2400.  
  2401. client_cmd(id, "setinfo ^"xredir^" ^"^"")
  2402. client_cmd(id, "setinfo ^"password^" ^"^"")
  2403.  
  2404. set_task(10.0, "reset_info", 0, sID, 1)
  2405. }
  2406. return PLUGIN_CONTINUE
  2407. }
  2408.  
  2409.  
  2410. /// <summary>This function shows a message to the player that has connected, to tell him that he was redirected and how he can use /retry to get back (if so).</summary>
  2411. /// <summary>welcome_message is called with a set_task to show the welcome message delayed, so that the player has usually already chosen a team and his screen is clear to read it.</summary>
  2412. /// <summary>This message is only displayed to players that have been redirected from another server in the chain. If redirect_retry is enabled, it also tells the player</summary>
  2413. /// <summary>that he can use /retry command to have himself queued to redirect back to the source server.</summary>
  2414. /// <param name="id">The slot ID of the player that should have the welcome message displayed. It is passed as array, because it is called with set_task.</param>
  2415. public welcome_message(id[])
  2416. {
  2417. new nID = id[0]
  2418. if (is_user_connected(nID)) // make sure the player didn't already disconnect within the set_task delay
  2419. {
  2420. new nLastServer = g_nLastServer[nID - 1]
  2421. if ((nLastServer >= 0) && (nLastServer != g_nOwnServer) && (nLastServer < MAX_SERVERFORWARDS))
  2422. {
  2423. new sAnnounceText[MAX_WELCOME_LEN]
  2424. format(sAnnounceText, MAX_WELCOME_LEN - 1, "%L", nID, "MSG_REDIRFROM", g_saServerNames[g_nOwnServer], g_saServerNames[nLastServer])
  2425. if ((get_pcvar_num(cvar_retry) == 1) && (get_pcvar_num(cvar_show) == 1))
  2426. format(sAnnounceText, MAX_WELCOME_LEN - 1, "%s^n%L", sAnnounceText, nID, "MSG_RETRY_BACK_ANNOUNCE")
  2427.  
  2428. set_hudmessage(000, 100, 255, -1.0, -1.0, 0, 0.0, 10.0, 0.5, 2.0, 1)
  2429. show_hudmessage(nID, sAnnounceText)
  2430. }
  2431. }
  2432. }
  2433.  
  2434.  
  2435. #else
  2436.  
  2437. /// <summary>Dummy handler to catch the case where a user tried to compile the plugin with a too old compiler.</summary>
  2438. public plugin_init()
  2439. {
  2440. log_amx("ERROR: Your AMXX version is too old for this plugin.")
  2441. }
  2442. #endif
  2443. /* AMXX-Studio Notes - DO NOT MODIFY BELOW HERE
  2444. *{\\ rtf1\\ ansi\\ deff0{\\ fonttbl{\\ f0\\ fnil Tahoma;}}\n\\ viewkind4\\ uc1\\ pard\\ lang1045\\ f0\\ fs16 \n\\ par }
  2445. */
  2446.