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. Fordította: BBk - Death of Legend
  419.  
  420.  */
  421.  
  422. // includes
  423. #include <amxmodx>
  424. #include <amxmisc>
  425. #include <sockets>
  426.  
  427. // plugin defines
  428. #define PLUGIN_NAME "xREDIRECT"
  429. #define PLUGIN_VERSION "1.0.3.3"
  430. #define PLUGIN_AUTHOR "x0R"
  431. #define PLUGIN_TAG "[xREDIRECT]"
  432.  
  433. // maximum values - don't change this if you don't know what you are doing!
  434. #define MAX_FILE_LEN 256 // maximum length of file names
  435. #define MAX_SERVERLINE_LEN 256 // maximum length of a line read from SERVERFILE
  436. #define MAX_SERVERNAME_LEN 50 // maximum length of a server name read from SERVERFILE
  437. #define MAX_SERVERADDRESS_LEN 100 // maximum length of a server address read from SERVERFILE
  438. #define MAX_NAME_LEN 33 // maximum length of a player name
  439. #define MAX_MENUBODY_LEN 512 // maximum length of a menu body
  440. #define MAX_WELCOME_LEN 1024 // maximum length of the welcome message
  441. #define MAX_INFO_LEN 1400 // maximum length of info reply - when longer than that the packet is fragmented (software side, not due to MTU)
  442. #define MAX_INFO_FORMAT 100 // maximum length of a format string for an info reply
  443. #define MAX_MAP_LEN 30 // maximum length of map names
  444. #define MAX_IP_LEN 16 // maximum length of IP addresses
  445. #define MAX_PORT_LEN 6 // maximum length of port numbers (as strings of course)
  446. #define MAX_KEY_LEN 20 // maximum length of a key name in SERVERFILE
  447. #define MAX_PASSWORD_LEN 15 // maximum length of a password in SERVERFILE
  448. #define MAX_VALUE_LEN 255 // maximum length of a value in SERVERFILE
  449. #define MAX_PLAYERS 32 // maximum number of players on the server
  450.  
  451. // unique task ID's - currently not needed but who knows when they will be
  452. #define TASKID_QUERY 21934807
  453. #define TASKID_QUERY_RECEIVE 21934808
  454. #define TASKID_ANNOUNCE 21934809
  455.  
  456. // options - these can be changed by the user, rememeber that you need to recompile for any changes here to take effect
  457. #define SERVERFILE "serverlist.ini" // name of file in /configs containing the server forwards - you can also prepend a subdirectory
  458. #define QUERY_INTERVAL 20.0 // interval of server querying (in seconds)
  459. #define QUERY_TIMEOUT 1.0 // the maximum time to wait for a server answer (in seconds) before it is considered being down
  460. #define MAX_SERVERFORWARDS 6 // maximum number of server forwards in forwards file
  461. #define MAX_MENUPAGES 10 // maximum number of pages the server selection menu can have
  462. #define DEFAULT_CMDBACKUP 2 // how often to resend the UDP request to servers by default
  463. #define MENU_FORCENOCOLOR false // false = display colored menues if the mod supports it; true = never display colored menues
  464. #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...)
  465. // can be one of these listed here: http://www.amxmodx.org/funcwiki.php?go=module&id=1#const_admin
  466. // A2S_INFO definitions for source according to http://developer.valvesoftware.com/wiki/Server_Queries#Source_servers_2
  467. #define A2S_INFO_SOURCE_REPLY_FORMAT "411ssss21111111s" // there are some extra flags after this but we don't care
  468. #define A2S_INFO_SOURCE_IDX_HEADER 0 // Should be FF FF FF FF
  469. #define A2S_INFO_SOURCE_IDX_TYPE 1 // Should be equal to 'I' (0x49)
  470. #define A2S_INFO_SOURCE_IDX_VERSION 2 // Network version. 0x07 is the current Steam version.
  471. #define A2S_INFO_SOURCE_IDX_SERVERNAME 3 // The Source server's name
  472. #define A2S_INFO_SOURCE_IDX_MAP 4 // The current map being played, eg: "de_dust"
  473. #define A2S_INFO_SOURCE_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike"
  474. #define A2S_INFO_SOURCE_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter Strike: Source"
  475. #define A2S_INFO_SOURCE_IDX_APPID 7 // Steam Application ID, see http://developer.valvesoftware.com/wiki/Steam_Application_IDs
  476. #define A2S_INFO_SOURCE_IDX_NUMPLAYERS 8 // The number of players currently on the server
  477. #define A2S_INFO_SOURCE_IDX_MAXPLAYERS 9 // Maximum allowed players for the server
  478. #define A2S_INFO_SOURCE_IDX_NUMBOTS 10 // Number of bot players currently on the server
  479. #define A2S_INFO_SOURCE_IDX_DEDICATED 11 // 'l' for listen, 'd' for dedicated, 'p' for SourceTV
  480. #define A2S_INFO_SOURCE_IDX_OS 12 // Host operating system. 'l' for Linux, 'w' for Windows
  481. #define A2S_INFO_SOURCE_IDX_PASSWORD 13 // If set to 0x01, a password is required to join this server
  482. #define A2S_INFO_SOURCE_IDX_SECURE 14 // if set to 0x01, this server is VAC secured
  483. #define A2S_INFO_SOURCE_IDX_GAMEVERSION 15 // The version of the game, eg: "1.0.0.22"
  484.  
  485. // A2S_INFO definitions for goldsource according to http://developer.valvesoftware.com/wiki/Server_Queries#Goldsource_servers_2
  486. #define A2S_INFO_GOLD_REPLY_FORMAT "41sssss111111[ss14411]11"
  487. #define A2S_INFO_GOLD_IDX_HEADER 0 // Should be FF FF FF FF
  488. #define A2S_INFO_GOLD_IDX_TYPE 1 // Should be equal to 'm' (0x6D) - for older servers it's 'C' (0x43)
  489. #define A2S_INFO_GOLD_IDX_IP 2 // Game Server IP address and port
  490. #define A2S_INFO_GOLD_IDX_SERVERNAME 3 // The server's name
  491. #define A2S_INFO_GOLD_IDX_MAP 4 //The current map being played, eg: "de_dust"
  492. #define A2S_INFO_GOLD_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike"
  493. #define A2S_INFO_GOLD_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter-Strike"
  494. #define A2S_INFO_GOLD_IDX_NUMPLAYERS 7 // The number of players currently on the server
  495. #define A2S_INFO_GOLD_IDX_MAXPLAYERS 8 // Maximum allowed players for the server
  496. #define A2S_INFO_GOLD_IDX_VERSION 9 // Network version. 0x07 is the current Steam version.
  497. #define A2S_INFO_GOLD_IDX_DEDICATED 10 // 'l' for listen, 'd' for dedicated, 'p' for HLTV
  498. #define A2S_INFO_GOLD_IDX_OS 11 // Host operating system. 'l' for Linux, 'w' for Windows
  499. #define A2S_INFO_GOLD_IDX_PASSWORD 12 // If set to 0x01, a password is required to join this server
  500. #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)
  501. #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
  502. #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
  503. #define A2S_INFO_GOLD_IDX_MOD_URLINFO 14 // URL containing information about this mod
  504. #define A2S_INFO_GOLD_IDX_MOD_URLDL 15 // URL to download this mod
  505. #define A2S_INFO_GOLD_IDX_MOD_NUL 16 // 0x00
  506. #define A2S_INFO_GOLD_IDX_MOD_MODVERSION 17 // Version of the installed mod
  507. #define A2S_INFO_GOLD_IDX_MOD_MODSIZE 18 // The download size of this mod
  508. #define A2S_INFO_GOLD_IDX_MOD_SVONLY 19 // If 1 this is a server side only mod
  509. #define A2S_INFO_GOLD_IDX_MOD_CIDLL 20 // If 1 this mod has a custom client dll
  510. #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
  511. #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
  512.  
  513. // flags
  514. #define SERVERFLAG_NOAUTO 0
  515. #define SERVERFLAG_NOMANUAL 1
  516. #define SERVERFLAG_NODISPLAY 2
  517.  
  518. // --------------------------------------- end of defines ---------------------------------------
  519.  
  520.  
  521. // -=[ global variables - remember to add an initialization in srvcmd_reload() for all variables you add here! ]=-
  522. /// <summary>Defines whether the plugin was completely initialized.</summary>
  523. new g_bInitialized = false // no srvcmd_reload() initialization needed for this one, as it's not directly related to the server list
  524. /// <summary>Server name.</summary>
  525. new g_saServerNames[MAX_SERVERFORWARDS][MAX_SERVERNAME_LEN]
  526. /// <summary>Server address.</summary>
  527. new g_saServerAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN]
  528. /// <summary>Server port.</summary>
  529. new g_naServerPorts[MAX_SERVERFORWARDS] = {27015, ...}
  530. /// <summary>Server password.</summary>
  531. new g_saServerPasswords[MAX_SERVERFORWARDS][MAX_PASSWORD_LEN]
  532. /// <summary>Is the server password public?</summary>
  533. new g_naServerPublicPassword[MAX_SERVERFORWARDS] = {0, ...}
  534. /// <summary>Currently active player count.</summary>
  535. new g_naServerActivePlayers[MAX_SERVERFORWARDS] = {-1, ...}
  536. /// <summary>Maximum number of players the server accepts. Does not take reserved slots into account.</summary>
  537. new g_naServerMaxPlayers[MAX_SERVERFORWARDS] = {-1, ...}
  538. /// <summary>Currently running map on server.</summary>
  539. new g_saServerMap[MAX_SERVERFORWARDS][MAX_MAP_LEN]
  540. /// <summary>The socket for the server to handle requests.</summary>
  541. new g_naServerSockets[MAX_SERVERFORWARDS] = {0, ...}
  542. /// <summary>The number how often server queries should be resent to that server.</summary>
  543. new g_naServerCmdBackup[MAX_SERVERFORWARDS] = {DEFAULT_CMDBACKUP, ...}
  544. /// <summary>Flags with several server options. Use the constant defines starting with SERVERFLAG_ to access these.</summary>
  545. new g_naServerFlags[MAX_SERVERFORWARDS] = {0, ...}
  546. /// <summary>Are admin slots reserved on this server?</summary>
  547. new g_naServerReserveSlots[MAX_SERVERFORWARDS] = {0, ...}
  548. /// <summary>Local server address.</summary>
  549. new g_saServerLocalAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN]
  550. /// <summary>At which real index does the menu page start? It is shifted because of hidden servers.</summary>
  551. new g_naMenuPageStart[MAX_PLAYERS][MAX_MENUPAGES]
  552. /// <summary>Is the server responding?</summary>
  553. new bool:g_baServerResponding[MAX_SERVERFORWARDS] = {false, ...}
  554. /// <summary>Number of servers found in server list file.</summary>
  555. new g_nServerCount = 0
  556. /// <summary>The last server someone has been redirected to. Needed for <seealso name="cmd_follow_player"/>.</summary>
  557. new g_nLastRedirectServer = -1
  558. /// <summary>The nick of the person who has been redirected at last. Needed for <seealso name="cmd_follow_player"/>.</summary>
  559. new g_sLastRedirectName[MAX_NAME_LEN] = ""
  560. /// <summary>The index of the current server. This is neccessary for the server to check its own data.</summary>
  561. new g_nOwnServer = -1
  562. /// <summary>The page number for each user which he had open last time, needed for switching back from sub menu to server menu.</summary>
  563. new g_naLastMenuPages[MAX_PLAYERS] = {1, ...}
  564. /// <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>
  565. new g_naServerSelections[MAX_PLAYERS][8]
  566. /// <summary>This is the cycle variable that holds which server to begin from in <seealso name="announce_servers"/>.</summary>
  567. new g_nNextAnnounceServer = 0
  568. /// <summary>The last server the player came from through redirection. Needed in case he wants to send himself back with /retry.</summary>
  569. new g_nLastServer[MAX_PLAYERS] = {-1, ...}
  570. /// <summary>The last server the player has accessed the sub menu of. Needed when the player refreshes the sub menu.</summary>
  571. new g_nLastSelected[MAX_PLAYERS] = {-1, ...}
  572. /// <summary>This array contains the retry queue consisting of a player ID and a serve number for each record.</summary>
  573. new g_nRetryQueue[MAX_PLAYERS*MAX_SERVERFORWARDS][2]
  574. /// <summary>Counter for global number of queue entries.</summary>
  575. new g_nRetryCount = 0
  576. /// <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>
  577. new bool:g_bDebug = false
  578.  
  579.  
  580. // -=[ global CVAR's ]=-
  581. new cvar_active
  582. new cvar_auto
  583. new cvar_manual
  584. new cvar_follow
  585. new cvar_external_address
  586. new cvar_check_method
  587. new cvar_announce
  588. new cvar_announce_mode
  589. new cvar_announce_alivepos_x
  590. new cvar_announce_alivepos_y
  591. new cvar_announce_deadpos_x
  592. new cvar_announce_deadpos_y
  593. new cvar_show
  594. new cvar_adminslots
  595. new cvar_maxadmins
  596. new cvar_retry
  597. new cvar_hidedown
  598. new cvar_localslots
  599.  
  600. // --------------------------------------- end of global vars ---------------------------------------
  601.  
  602. /*
  603. public plugin_precache()
  604. {
  605. }
  606. */
  607.  
  608. #if AMXX_VERSION_NUM >= 170
  609.  
  610. /// <summary>Initialize CVARs, load servers, register commands, register menues, register dictionaries, start tasks...</summary>
  611. public plugin_init() {
  612. register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR)
  613.  
  614. register_cvar("redirect_version", PLUGIN_VERSION, FCVAR_SERVER|FCVAR_SPONLY)
  615. set_cvar_string("redirect_version", PLUGIN_VERSION)
  616.  
  617. // please see the description at top if you want to know what these CVAR's do
  618. cvar_active = register_cvar("redirect_active", "0")
  619. cvar_auto = register_cvar("redirect_auto", "0")
  620. cvar_manual = register_cvar("redirect_manual", "0")
  621. cvar_follow = register_cvar("redirect_follow", "0")
  622. cvar_external_address = register_cvar("redirect_external_address", "")
  623. cvar_check_method = register_cvar("redirect_check_method", "0")
  624. cvar_announce = register_cvar("redirect_announce", "120")
  625. cvar_announce_mode = register_cvar("redirect_announce_mode", "3")
  626. cvar_announce_alivepos_x = register_cvar("redirect_announce_alivepos_x", "-1.0")
  627. cvar_announce_alivepos_y = register_cvar("redirect_announce_alivepos_y", "0.01")
  628. cvar_announce_deadpos_x = register_cvar("redirect_announce_deadpos_x", "-1.0")
  629. cvar_announce_deadpos_y = register_cvar("redirect_announce_deadpos_y", "0.35")
  630. cvar_show = register_cvar("redirect_show", "1")
  631. cvar_adminslots = register_cvar("redirect_adminslots", "0")
  632. cvar_maxadmins = register_cvar("redirect_maxadmins", "0")
  633. cvar_retry = register_cvar("redirect_retry", "0")
  634. cvar_hidedown = register_cvar("redirect_hidedown", "0")
  635. cvar_localslots = register_cvar("redirect_localslots", "0")
  636.  
  637. register_dictionary("xredirect.txt")
  638. register_dictionary("common.txt")
  639.  
  640. load_servers()
  641.  
  642. register_menu("Redirect Menu", 1023, "server_menu_select")
  643. register_menu("Detail Menu", 1023, "sub_menu_select")
  644.  
  645. register_srvcmd("redirect_reload", "srvcmd_reload", -1, "- reload redirect servers")
  646.  
  647. register_clcmd("say /server", "cmd_show_server_menu", 0, "- show server redirection menu")
  648. register_clcmd("say_team /server", "cmd_show_server_menu", 0, "- show server redirection menu")
  649. register_clcmd("pickserver", "cmd_pickserver", 0, "show server redirection menu")
  650. register_clcmd("say /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server")
  651. register_clcmd("say_team /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server")
  652. register_clcmd("say /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot")
  653. register_clcmd("say_team /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot")
  654. register_clcmd("say /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server")
  655. register_clcmd("say_team /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server")
  656. register_clcmd("redirect_announce_now", "announce_servers", ADMIN_KICK , "- announce server list immediately")
  657. register_clcmd("redirect_user", "cmd_redirect_user", ADMIN_KICK , "<playername|playerid> [servernum] - redirect a player [to a given server]")
  658.  
  659. set_task(QUERY_INTERVAL, "query_servers", TASKID_QUERY, "", 0, "b")
  660.  
  661. // check whether we are in debug mode or not
  662. new saDummy[2]
  663. new saStatus[6]
  664. get_plugin(-1, saDummy, 0, saDummy, 0, saDummy, 0, saDummy, 0, saStatus, 5)
  665. g_bDebug = bool:equal(saStatus, "debug")
  666. }
  667.  
  668. /// <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>
  669. public plugin_postinit()
  670. {
  671. g_bInitialized = true
  672. new sFullAddress[MAX_SERVERADDRESS_LEN]
  673. new sTmpServerIP[MAX_IP_LEN + MAX_PORT_LEN]
  674. get_cvar_string("net_address", sTmpServerIP, MAX_IP_LEN + MAX_PORT_LEN - 1)
  675. new sTmpOwnAddress[MAX_SERVERADDRESS_LEN]
  676. get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1)
  677.  
  678. new nServerCount = 0
  679. while (nServerCount < g_nServerCount)
  680. {
  681. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount])
  682. if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerIP))
  683. {
  684. g_nOwnServer = nServerCount
  685. break
  686. }
  687. nServerCount++
  688. }
  689.  
  690. if (g_nOwnServer == -1) // we have not been able to detect the own server - inform the user about this
  691. {
  692. log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR")
  693. return PLUGIN_CONTINUE
  694. }
  695.  
  696. if (get_pcvar_float(cvar_announce) > 0.0)
  697. if (!task_exists(TASKID_ANNOUNCE))
  698. set_task(get_pcvar_float(cvar_announce), "announce_servers", TASKID_ANNOUNCE, "", 0, "b")
  699.  
  700. return PLUGIN_CONTINUE
  701. }
  702.  
  703. /// <summary>Cleanup. Close open sockets.</summary>
  704. public plugin_end()
  705. {
  706. // close all open sockets
  707. for (new nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++)
  708. {
  709. if (g_naServerSockets[nCounter] > 0)
  710. {
  711. socket_close(g_naServerSockets[nCounter])
  712. g_naServerSockets[nCounter] = 0
  713. }
  714. }
  715. }
  716.  
  717. /// <summary>This is used to register the native redirect() function.</summary>
  718. public plugin_natives()
  719. {
  720. register_native("redirect", "native_redirect", 1)
  721. }
  722.  
  723. /// <summary>This is used to tell AMXX that the sockets module is required.</summary>
  724. /// <remarks>Can be safely removed from the code when only redirect_check_method 0 will be used.</remarks>
  725. public plugin_modules()
  726. {
  727. require_module("sockets")
  728. }
  729.  
  730. /// <summary>Load servers from server list file.</summary>
  731. /// <returns>true when servers have been successfully loaded, false if there were errors.</returns>
  732. public bool:load_servers()
  733. {
  734. new sConfigDir[MAX_FILE_LEN], sServerFile[MAX_FILE_LEN]
  735.  
  736. get_configsdir(sConfigDir, MAX_FILE_LEN-1)
  737. format(sServerFile, MAX_FILE_LEN-1, "%s/%s", sConfigDir, SERVERFILE)
  738.  
  739. if (!file_exists(sServerFile))
  740. {
  741. log_amx("%L", LANG_SERVER, "MSG_ERROR_NO_FILE", sServerFile)
  742. return false
  743. }
  744.  
  745. new nFilePos = 0
  746. new sFileLine[MAX_SERVERLINE_LEN]
  747. new nReadLen
  748. new sPort[MAX_PORT_LEN]
  749.  
  750. new sKey[MAX_KEY_LEN]
  751. new sValue[MAX_VALUE_LEN]
  752.  
  753. new nCurrentServer = -1
  754.  
  755. while (read_file(sServerFile, nFilePos++, sFileLine, MAX_SERVERLINE_LEN-1, nReadLen))
  756. {
  757. if ((sFileLine[0] == ';') && (strcmp(sFileLine, "") == 0)) continue // skip comment and empty lines
  758.  
  759. if ((sFileLine[0] == '[') && (sFileLine[strlen(sFileLine) - 1] == ']')) // a section starts
  760. {
  761. nCurrentServer++
  762. if (nCurrentServer > 0)
  763. {
  764. // check whether the previous server was valid
  765. if ((g_naServerPorts[nCurrentServer - 1] != 0) && (strcmp(g_saServerAddresses[nCurrentServer - 1], "") != 0))
  766. {
  767. g_nServerCount++
  768. num_to_str(g_naServerPorts[nCurrentServer - 1], sPort, MAX_PORT_LEN - 1)
  769. log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer - 1], g_saServerAddresses[nCurrentServer - 1], sPort)
  770.  
  771. }
  772. else
  773. nCurrentServer--
  774. }
  775.  
  776. if (nCurrentServer >= MAX_SERVERFORWARDS)
  777. break;
  778.  
  779. copy(g_saServerNames[nCurrentServer], strlen(sFileLine) - 2, sFileLine[1])
  780.  
  781. continue
  782. }
  783.  
  784. if (nCurrentServer >= 0) // do we already have found a section?
  785. {
  786. strtok(sFileLine, sKey, MAX_KEY_LEN - 1, sValue, MAX_VALUE_LEN - 1, '=', 1)
  787. strtoupper(sKey)
  788. if (strcmp(sKey, "ADDRESS") == 0)
  789. copy(g_saServerAddresses[nCurrentServer], MAX_SERVERADDRESS_LEN - 1, sValue)
  790. else
  791. if (strcmp(sKey, "LOCALADDRESS") == 0)
  792. copy(g_saServerLocalAddresses[nCurrentServer], MAX_SERVERADDRESS_LEN - 1, sValue)
  793. else
  794. if (strcmp(sKey, "PASSWORD") == 0)
  795. copy(g_saServerPasswords[nCurrentServer], MAX_PASSWORD_LEN - 1, sValue)
  796. else
  797. if (strcmp(sKey, "PUBLICPASSWORD") == 0)
  798. {
  799. if (is_str_num(sValue))
  800. if (str_to_num(sValue) == 1)
  801. g_naServerPublicPassword[nCurrentServer] = 1
  802. }
  803. else
  804. if (strcmp(sKey, "PORT") == 0)
  805. {
  806. if (is_str_num(sValue))
  807. g_naServerPorts[nCurrentServer] = str_to_num(sValue)
  808. else
  809. g_naServerPorts[nCurrentServer] = 27015
  810. if ((g_naServerPorts[nCurrentServer] > 65536) || (g_naServerPorts[nCurrentServer] < 1024))
  811. g_naServerPorts[nCurrentServer] = 27015
  812. }
  813. else
  814. if (strcmp(sKey, "CMDBACKUP") == 0)
  815. {
  816. if (is_str_num(sValue))
  817. g_naServerCmdBackup[nCurrentServer] = str_to_num(sValue)
  818. else
  819. g_naServerCmdBackup[nCurrentServer] = DEFAULT_CMDBACKUP
  820. // protect from insane values
  821. if ((g_naServerCmdBackup[nCurrentServer] > 100) || (g_naServerCmdBackup[nCurrentServer] < 0))
  822. g_naServerCmdBackup[nCurrentServer] = DEFAULT_CMDBACKUP
  823. }
  824. else
  825. if (strcmp(sKey, "NOAUTO") == 0)
  826. {
  827. if (is_str_num(sValue))
  828. if (str_to_num(sValue) == 1)
  829. g_naServerFlags[nCurrentServer] = g_naServerFlags[nCurrentServer] | (1<<SERVERFLAG_NOAUTO)
  830. }
  831. else
  832. if (strcmp(sKey, "NOMANUAL") == 0)
  833. {
  834. if (is_str_num(sValue))
  835. if (str_to_num(sValue) == 1)
  836. g_naServerFlags[nCurrentServer] = g_naServerFlags[nCurrentServer] | (1<<SERVERFLAG_NOMANUAL)
  837.  
  838. }
  839. else
  840. if (strcmp(sKey, "NODISPLAY") == 0)
  841. {
  842. if (is_str_num(sValue))
  843. if (str_to_num(sValue) == 1)
  844. g_naServerFlags[nCurrentServer] = g_naServerFlags[nCurrentServer] | (1<<SERVERFLAG_NODISPLAY)
  845. }
  846. else
  847. if (strcmp(sKey, "ADMINSLOTS") == 0)
  848. {
  849. if (is_str_num(sValue))
  850. g_naServerReserveSlots[nCurrentServer] = str_to_num(sValue)
  851. else
  852. g_naServerReserveSlots[nCurrentServer] = 0
  853. if ((g_naServerReserveSlots[nCurrentServer] > MAX_PLAYERS) || (g_naServerReserveSlots[nCurrentServer] < 0))
  854. g_naServerReserveSlots[nCurrentServer] = 0
  855. }
  856. }
  857. }
  858.  
  859. if ((nCurrentServer >= MAX_SERVERFORWARDS) || (nCurrentServer == -1))
  860. return true;
  861.  
  862. // check whether the previous server was valid
  863. if ((g_naServerPorts[nCurrentServer] != 0) && (strcmp(g_saServerAddresses[nCurrentServer], "") != 0))
  864. {
  865. g_nServerCount++
  866. num_to_str(g_naServerPorts[nCurrentServer], sPort, MAX_PORT_LEN - 1)
  867. log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer], g_saServerAddresses[nCurrentServer], sPort)
  868. }
  869.  
  870. if (g_nServerCount < 2)
  871. {
  872. log_amx("%L", LANG_SERVER, "MSG_ERROR_NOT_ENOUGH_SERVERS")
  873. return false
  874. }
  875.  
  876. return true
  877. }
  878.  
  879. /// <summary>Checks whether the IP in <paramref name="sCheckAddress"/> is a local address arcording to RFC 1918.</summary>
  880. /// <summary>10.0.0.0 - 10.255.255.255 - single class A</summary>
  881. /// <summary>172.16.0.0 - 172.31.255.255 - 16 contiguous class Bs</summary>
  882. /// <summary>192.168.0.0 - 192.168.255.255 - 256 contiguous class Cs</summary>
  883. /// <summary>169.254.0.0 - 169.254.255.255 - zeroconf</summary>
  884. /// <param name="sCheckAddress">The IP address to check passed as a string.</param>
  885. /// <returns>true if <paramref name="sCheckAddress"/> is a local IP address, false if not.</returns>
  886. public bool:is_local_address(sCheckAddress[MAX_IP_LEN])
  887. {
  888. new sIPPart1[4]
  889. new sIPPart2[4]
  890. new nIPPart[4]
  891. new sCompareIP[MAX_IP_LEN]
  892. sCompareIP = sCheckAddress
  893. strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.')
  894. nIPPart[0] = str_to_num(sIPPart1);
  895. strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.')
  896. nIPPart[1] = str_to_num(sIPPart1);
  897. strtok(sCheckAddress, sIPPart1, 3, sIPPart2, 3, '.')
  898. nIPPart[2] = str_to_num(sIPPart1);
  899. nIPPart[3] = str_to_num(sIPPart2);
  900. 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)))
  901. }
  902.  
  903.  
  904. /// <summary>Checks whether the player with ID <paramref name="nPlayerID"/> can be redirected to the server with server number <paramref name="nServerNum"/>.</summary>
  905. /// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection target.</param>
  906. /// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param>
  907. /// <param name="nMode">Defines the redirection mode - 1 = automatic, 2 = manual.</param>
  908. /// <param name="bIgnoreAdmin">Set to true, when the plugin should not tread admins as special, otherwise false.</param>
  909. /// <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>
  910. public can_redirect_player(nServer, nPlayerID, nMode, bIgnoreAdmin)
  911. {
  912. if (nServer == -1)
  913. return 0
  914. new nCheckMethod = get_pcvar_num(cvar_check_method)
  915.  
  916. new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0))
  917.  
  918. if (nServer == g_nOwnServer)
  919. return 1
  920. 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
  921. return 0
  922. else if ((nCheckMethod > 0) && (!g_baServerResponding[nServer]))
  923. return 5
  924. else if (!bCanRedirectByPassword)
  925. return 2
  926. else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL)) && (nMode == 2))
  927. return 3
  928. else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOAUTO)) && (nMode == 1))
  929. return 6
  930. else if ((nCheckMethod == 2) && (((g_naServerActivePlayers[nServer] == (g_naServerMaxPlayers[nServer] - 1)) && (g_naServerReserveSlots[nServer] > 0)) || (g_naServerActivePlayers[nServer] >= g_naServerMaxPlayers[nServer])))
  931. return 4
  932.  
  933. return 0
  934. }
  935.  
  936.  
  937. /// <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>
  938. /// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection queue target.</param>
  939. /// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param>
  940. /// <returns>true if queueing is possible, otherwise false</returns>
  941. public bool:can_queue_player(nServer, nPlayerID)
  942. {
  943. if (nServer == -1)
  944. return false
  945.  
  946. new bIsAdmin = access(nPlayerID, MIN_ADMIN_LEVEL)
  947.  
  948. if ((get_pcvar_num(cvar_retry) == 0) && (!bIsAdmin)) // admin always can enqueue themselves, even when this feature is disabled
  949. return false
  950.  
  951. new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0))
  952. if (nServer == g_nOwnServer)
  953. return false
  954. if (bIsAdmin)
  955. return true
  956. if (((!bCanRedirectByPassword) || (g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL))))
  957. return false
  958.  
  959. return true
  960. }
  961.  
  962. /// <summary>Checks whether the player with ID <paramref name="id"/> is already in redirection queue for server with number <paramref name="nServer"/>.</summary>
  963. /// <param name="nServer">The server number which shall be checked whether player with <paramref name="id"/> is in its queue.</param>
  964. /// <param name="id">The internal player ID which shall be checked whether it is queued for server <paramref name="nServer"/>.</param>
  965. /// <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>
  966. /// <returns>true if player is in queue, false if not.</returns>
  967. /// <seealso name="queue_add"/>
  968. /// <seealso name="queue_remove"/>
  969. public bool:is_queued(id, nServer)
  970. {
  971. new nCount = 0
  972. while (nCount < g_nRetryCount)
  973. {
  974. if ((g_nRetryQueue[nCount][0] == id) && (g_nRetryQueue[nCount][1] == nServer))
  975. return true
  976. nCount++
  977. }
  978. return false
  979. }
  980.  
  981. /// <summary>Adds the player with ID <paramref name="id"/> to the redirection queue for server with number <paramref name="nServer"/>.</summary>
  982. /// <param name="nServer">The server number to add the player with <paramref name="id"/> to its queue.</param>
  983. /// <param name="id">The internal player ID which shall be added to the queue for server <paramref name="nServer"/>.</param>
  984. /// <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>
  985. /// <seealso name="is_queued"/>
  986. /// <seealso name="queue_remove"/>
  987. public queue_add(id, nServer)
  988. {
  989. if (get_pcvar_num(cvar_retry) > 0)
  990. {
  991. //first check whether the server-player-combination is not already in queue
  992. new nCount = 0
  993. new nServerQueue = 0
  994. while (nCount < g_nRetryCount)
  995. {
  996. // count how many people are in the queue for the target server
  997. if (g_nRetryQueue[nCount][1] == nServer)
  998. {
  999. nServerQueue++
  1000. // no need to continue when he already is in the queue
  1001. if (g_nRetryQueue[nCount][0] == id)
  1002. return
  1003. }
  1004. nCount++
  1005. }
  1006.  
  1007. new sUserNick[MAX_NAME_LEN]
  1008. get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
  1009.  
  1010. if (get_pcvar_num(cvar_show) == 1)
  1011. {
  1012. new naPlayers[MAX_PLAYERS]
  1013. new nPlayerNum, nPlayerCount, nCurrentPlayer
  1014. get_players(naPlayers, nPlayerNum, "c")
  1015. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1016. {
  1017. nCurrentPlayer = naPlayers[nPlayerCount]
  1018. if (nCurrentPlayer != id) // he has his own message
  1019. client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_QUEUE_ANNOUNCE", sUserNick, g_saServerNames[nServer])
  1020. }
  1021. }
  1022.  
  1023.  
  1024. if (g_bDebug)
  1025. log_amx("added player %i to queue for server %i in slot %i", id, nServer, g_nRetryCount)
  1026.  
  1027.  
  1028. g_nRetryQueue[g_nRetryCount][0] = id
  1029. g_nRetryQueue[g_nRetryCount][1] = nServer
  1030. g_nRetryCount++
  1031.  
  1032. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_ADD", ++nServerQueue, g_saServerNames[nServer])
  1033. }
  1034. else
  1035. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_DEACTIVATED")
  1036. }
  1037.  
  1038. /// <summary>Removes the player with ID <paramref name="id"/> from the redirection queue for server with number <paramref name="nServer"/>.</summary>
  1039. /// <param name="nServer">The server number to remove the player with <paramref name="id"/> from its queue.</param>
  1040. /// <param name="id">The internal player ID which shall be removed from the queue for server <paramref name="nServer"/>.</param>
  1041. /// <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>
  1042. /// <seealso name="is_queued"/>
  1043. /// <seealso name="add_remove"/>
  1044. public queue_remove(id, nServer)
  1045. {
  1046. new nCount = 0
  1047. while (nCount < g_nRetryCount)
  1048. {
  1049. if ((g_nRetryQueue[nCount][0] == id) && ((nServer == -1) || (g_nRetryQueue[nCount][1] == nServer)))
  1050. { // ok, remove from queue and let all others go one place up
  1051.  
  1052. // in case it's the last entry in queue where the following loop would never be executed:
  1053. g_nRetryQueue[nCount][0] = -1
  1054. g_nRetryQueue[nCount][1] = -1
  1055.  
  1056. // move other entries up
  1057. while ((nCount + 1) < g_nRetryCount)
  1058. {
  1059. g_nRetryQueue[nCount][0] = g_nRetryQueue[nCount + 1][0]
  1060. g_nRetryQueue[nCount][1] = g_nRetryQueue[nCount + 1][1]
  1061. nCount++
  1062. }
  1063. g_nRetryCount--
  1064. break
  1065. }
  1066. nCount++
  1067. }
  1068. }
  1069.  
  1070. /// <summary>Resets the setinfo string of the player with <paramref name="id"/> by removing tags that xREDIRECT used.</summary>
  1071. /// <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>
  1072. public reset_info(id[])
  1073. {
  1074. client_cmd(id[0], "setinfo ^"xredir^" ^"^"")
  1075. client_cmd(id[0], "setinfo ^"password^" ^"^"")
  1076.  
  1077. }
  1078.  
  1079. /// <summary>Announce the servers on top of the screen. The position and interval for announcements can be set by CVARs.</summary>
  1080. public announce_servers()
  1081. {
  1082. if (get_pcvar_num(cvar_active) == 1)
  1083. {
  1084. if (g_nServerCount > 0)
  1085. {
  1086. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1087. new sAnnounceBody[MAX_MENUBODY_LEN] = ""
  1088. new nDisplayCount = 0
  1089. new nServerCount = g_nNextAnnounceServer
  1090. if (nServerCount >= g_nServerCount)
  1091. nServerCount = 0
  1092.  
  1093. while ((nServerCount < g_nServerCount) && (nDisplayCount < 8))
  1094. {
  1095.  
  1096. if (!((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY)) || ((get_pcvar_num(cvar_hidedown) > 1) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer))))
  1097. {
  1098. if (nServerCount == g_nOwnServer)
  1099. {
  1100. new sMap[MAX_MAP_LEN]
  1101. get_mapname(sMap, MAX_MAP_LEN - 1)
  1102. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s [%s] (%d/%d)", sAnnounceBody, g_saServerNames[nServerCount], sMap, get_playersnum(1), get_maxplayers())
  1103. }
  1104. else
  1105. {
  1106. if (nCheckMethod == 0)
  1107. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount])
  1108. else
  1109. if (g_baServerResponding[nServerCount])
  1110. {
  1111. if (nCheckMethod == 1)
  1112. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount])
  1113. else if (nCheckMethod == 2)
  1114. 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])
  1115. }
  1116. else
  1117. format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s (down)", sAnnounceBody, g_saServerNames[nServerCount])
  1118. }
  1119. }
  1120. nServerCount++
  1121. nDisplayCount++
  1122. }
  1123. g_nNextAnnounceServer = nServerCount
  1124. set_hudmessage(000, 100, 255, -1.0, 0.01, 0, 0.0, 10.0, 0.5, 0.10, 1)
  1125. //show_hudmessage(0, sAnnounceBody)
  1126.  
  1127. if (get_pcvar_float(cvar_announce) > 0.0)
  1128. {
  1129. new nAnnounceMode = get_pcvar_num(cvar_announce_mode)
  1130. if (nAnnounceMode > 0)
  1131. {
  1132. new naPlayers[MAX_PLAYERS]
  1133. new nPlayerNum, nPlayerCount
  1134. new sAnnounceText[MAX_MENUBODY_LEN]
  1135. if ((nAnnounceMode == 1) || (nAnnounceMode == 3))
  1136. {
  1137. get_players(naPlayers, nPlayerNum, "ac") // alive players
  1138. 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)
  1139. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1140. {
  1141. if (get_pcvar_num(cvar_manual) >= 1)
  1142. format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody)
  1143. else
  1144. sAnnounceText = sAnnounceBody
  1145. show_hudmessage(naPlayers[nPlayerCount], sAnnounceText)
  1146. }
  1147. }
  1148. if ((nAnnounceMode == 2) || (nAnnounceMode == 3))
  1149. {
  1150. get_players(naPlayers, nPlayerNum, "bc") // dead players
  1151. 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"
  1152. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1153. {
  1154. if (get_pcvar_num(cvar_manual) >= 1)
  1155. format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody)
  1156. else
  1157. sAnnounceText = sAnnounceBody
  1158. show_hudmessage(naPlayers[nPlayerCount], sAnnounceText)
  1159. }
  1160. }
  1161. }
  1162. }
  1163. }
  1164. }
  1165. return PLUGIN_HANDLED
  1166. }
  1167.  
  1168.  
  1169. /// <summary>Shows the sub menu for server with number <paramref name="nServer"/> to the the player with ID <paramref name="id"/>.</summary>
  1170. /// <param name="nServer">The server to show the sub menu for.</param>
  1171. /// <param name="id">The ID of the player to show the sub menu.</param>
  1172. /// <seealso name="server_menu_select"/>
  1173. /// <seealso name="sub_menu_select"/>
  1174. /// <seealso name="show_server_menu"/>
  1175. public show_sub_menu(id, nServer)
  1176. {
  1177. new nCanRedirect = can_redirect_player(nServer, id, 2, false)
  1178. new nCanRedirectIgnoreAdmin = can_redirect_player(nServer, id, 2, true);
  1179. new bool:bCanQueue = can_queue_player(nServer, id)
  1180. new bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR)
  1181. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1182. new sMenuBody[MAX_MENUBODY_LEN]
  1183.  
  1184. // can we display colors?
  1185. if (bColorMenu)
  1186. {
  1187. format(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SRVINFO_CAPTION")
  1188. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer])
  1189. }
  1190. else
  1191. {
  1192. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SRVINFO_CAPTION")
  1193. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer])
  1194. }
  1195.  
  1196. // can we display map and player information?
  1197. if (((nCheckMethod == 2) && ((g_baServerResponding[nServer])) || (nServer == g_nOwnServer)))
  1198. {
  1199. if (bColorMenu)
  1200. {
  1201. if (nServer == g_nOwnServer)
  1202. {
  1203. new sMap[MAX_MAP_LEN]
  1204. get_mapname(sMap, MAX_MAP_LEN - 1)
  1205. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap)
  1206. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_playersnum(1), get_maxplayers())
  1207. }
  1208. else
  1209. {
  1210. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer])
  1211. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer])
  1212. }
  1213. }
  1214. else
  1215. {
  1216. if (nServer == g_nOwnServer)
  1217. {
  1218. new sMap[MAX_MAP_LEN]
  1219. get_mapname(sMap, MAX_MAP_LEN - 1)
  1220. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap)
  1221. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_playersnum(1), get_maxplayers())
  1222. }
  1223. else
  1224. {
  1225. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer])
  1226. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer])
  1227. }
  1228. }
  1229. }
  1230.  
  1231. // make the next line red if colors are supported and (the user is no admin or it's the current server)
  1232. if ((bColorMenu) && ((!access(id, MIN_ADMIN_LEVEL)) || (nCanRedirect == 1)))
  1233. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\r", sMenuBody)
  1234. else
  1235. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n", sMenuBody)
  1236.  
  1237. // now display reason why we can't redirect there
  1238. switch (nCanRedirectIgnoreAdmin)
  1239. {
  1240. case 1:
  1241. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_CURRENT")
  1242. case 2:
  1243. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_PERMISSION")
  1244. case 3:
  1245. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_NOMANUAL")
  1246. case 4:
  1247. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_FULL")
  1248. case 5:
  1249. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_DOWN")
  1250. }
  1251.  
  1252. // enable/disable key for redirection/queue functionality
  1253. new key = (1<<9) // cancel
  1254. key = key | (1<<8) // back
  1255. if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
  1256. key = key | (1<<2) // refresh
  1257. if (nCanRedirect == 0)
  1258. key = key | (1<<0) // redirect
  1259. if (bCanQueue && (nCheckMethod > 1))
  1260. key = key | (1<<1) // enqueue
  1261.  
  1262. new sQueueMsg[30]
  1263. if (is_queued(id, nServer))
  1264. sQueueMsg = "MSG_LEAVEQUEUE"
  1265. else
  1266. sQueueMsg = "MSG_QUEUE"
  1267.  
  1268. // display the last menu items according to availability
  1269. if (bColorMenu)
  1270. {
  1271. if (nCanRedirect == 0)
  1272. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \w %L", sMenuBody, id, "MSG_REDIRECT")
  1273. else
  1274. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \d %L", sMenuBody, id, "MSG_REDIRECT")
  1275. if (bCanQueue && (nCheckMethod > 1))
  1276. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \w %L", sMenuBody, id, sQueueMsg)
  1277. else
  1278. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \d %L", sMenuBody, id, sQueueMsg)
  1279. if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
  1280. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \w %L", sMenuBody, id, "MSG_REFRESH")
  1281. else
  1282. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \d %L", sMenuBody, id, "MSG_REFRESH")
  1283. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9. \w %L", sMenuBody, id, "MSG_BACK")
  1284. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0. \w %L", sMenuBody, id, "MSG_CANCEL")
  1285. }
  1286. else
  1287. {
  1288. if (nCanRedirect == 0)
  1289. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n1. %L", sMenuBody, id, "MSG_REDIRECT")
  1290. else
  1291. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_REDIRECT")
  1292. if (bCanQueue && (nCheckMethod > 1))
  1293. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n2. %L", sMenuBody, id, sQueueMsg)
  1294. else
  1295. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, sQueueMsg)
  1296. if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
  1297. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n3. %L", sMenuBody, id, "MSG_REFRESH")
  1298. else
  1299. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, "MSG_REFRESH")
  1300. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_BACK")
  1301. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL")
  1302. }
  1303. g_nLastSelected[id - 1] = nServer
  1304. show_menu(id, key, sMenuBody, -1, "Detail Menu")
  1305. }
  1306.  
  1307. /// <summary>Shows the server menu page <paramref name="menupage"/> to the the player with ID <paramref name="id"/>.</summary>
  1308. /// <param name="id">The ID of the player to show the server menu to.</param>
  1309. /// <param name="menupage">The menu page number to show to the player. Offset is 0.</param>
  1310. /// <seealso name="server_menu_select"/>
  1311. /// <seealso name="sub_menu_select"/>
  1312. /// <seealso name="show_sub_menu"/>
  1313. public show_server_menu(id, menupage)
  1314. {
  1315. new nServerCount
  1316. if (get_pcvar_num(cvar_active) == 1)
  1317. {
  1318. if (g_nServerCount > 0)
  1319. {
  1320. new bool:bSubMenu = (get_pcvar_num(cvar_manual) >= 2)
  1321. new bool:bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR)
  1322. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1323. new sMenuBody[MAX_MENUBODY_LEN]
  1324. if (bColorMenu)
  1325. format(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SELECT_SERVER")
  1326. else
  1327. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SELECT_SERVER")
  1328.  
  1329.  
  1330. if (menupage <= 1)
  1331. nServerCount = 0
  1332. else
  1333. nServerCount = g_naMenuPageStart[id - 1][menupage - 2]
  1334.  
  1335. new nDisplayNumber = 1
  1336.  
  1337. new key = (1<<9) // cancel key is always enabled
  1338.  
  1339. new nHideDown = get_pcvar_num(cvar_hidedown)
  1340. if (nHideDown == 1)
  1341. nHideDown = 3
  1342.  
  1343. // the 3 parts of a menu item, third part only displayed with redirect_check_method >= 2
  1344. new sMenuNumber[10]
  1345. new sMenuSrvName[50]
  1346. new sMenuInfo[50]
  1347. if (nCheckMethod < 2)
  1348. sMenuInfo = ""
  1349.  
  1350. while ((nDisplayNumber < 9) && (nServerCount < g_nServerCount))
  1351. {
  1352. if (!((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY)) || ((nHideDown > 2) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer))))
  1353. {
  1354. new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServerCount], "") && (g_naServerPublicPassword[nServerCount] == 0) && (!access(id, MIN_ADMIN_LEVEL)))
  1355.  
  1356. if (bColorMenu)
  1357. {
  1358. format(sMenuNumber, 9, "\y%d. ", nDisplayNumber)
  1359. if (bSubMenu)
  1360. format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
  1361. else
  1362. format(sMenuSrvName, 49, "\d %s", g_saServerNames[nServerCount])
  1363. }
  1364. else
  1365. {
  1366. format(sMenuNumber, 9, "%d. ", nDisplayNumber)
  1367. format(sMenuSrvName, 49, " %s", g_saServerNames[nServerCount])
  1368. }
  1369.  
  1370. new bool:bCanRedirect = true
  1371. sMenuInfo = ""
  1372.  
  1373. // manual redirection to that server is disabled or server is passworded but password is not public and user has insufficent admin rights
  1374. if ((nCheckMethod == 2) && (((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NOMANUAL)) || !bCanRedirectByPassword)))
  1375. {
  1376. bCanRedirect = false
  1377. if ((!bColorMenu) && (!bSubMenu))
  1378. sMenuNumber = "_. "
  1379. if (nCheckMethod == 2)
  1380. format(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1381. }
  1382. // server is full (and player has insufficient rights to join on an admin slot)
  1383. 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])))
  1384. {
  1385. bCanRedirect = false
  1386. if ((!bColorMenu) && (!bSubMenu))
  1387. sMenuNumber = "_. "
  1388. if (bColorMenu)
  1389. format(sMenuInfo, 49, " [%s] \r(\w%d/%d\r)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1390. else
  1391. format(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1392. }
  1393. // server is down
  1394. if ((nCheckMethod > 0) && (!g_baServerResponding[nServerCount]))
  1395. {
  1396. if ((!bColorMenu) && (!bSubMenu))
  1397. sMenuNumber = "_. "
  1398. bCanRedirect = false
  1399. if (bColorMenu)
  1400. sMenuInfo = " \r(\wdown\r)"
  1401. else
  1402. sMenuInfo = " (down)"
  1403. }
  1404. // server is current server
  1405. if (nServerCount == g_nOwnServer)
  1406. {
  1407. if ((!bColorMenu) && (!bSubMenu))
  1408. sMenuNumber = "_. "
  1409. bCanRedirect = false
  1410. new sMap[MAX_MAP_LEN]
  1411. get_mapname(sMap, MAX_MAP_LEN - 1)
  1412. if (bSubMenu && bColorMenu)
  1413. format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", sMap, get_playersnum(1), get_maxplayers())
  1414. else
  1415. format(sMenuInfo, 49, " [%s] (%d/%d)", sMap, get_playersnum(1), get_maxplayers())
  1416. }
  1417.  
  1418. // everything's fine, we can redirect here
  1419. if (bCanRedirect)
  1420. {
  1421. if (bColorMenu)
  1422. {
  1423. format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
  1424. if (nCheckMethod > 1)
  1425. format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1426. }
  1427. else
  1428. {
  1429. if (nCheckMethod > 1)
  1430. format(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1431. }
  1432.  
  1433. key = key | (1<<(nDisplayNumber - 1))
  1434. g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount
  1435. }
  1436. else if ((bSubMenu) && (nServerCount != g_nOwnServer)) // display server like it was enabled when submenues are enabled
  1437. if (bColorMenu)
  1438. {
  1439. format(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
  1440. if ((nCheckMethod == 0) && (g_baServerResponding[nServerCount]))
  1441. format(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
  1442. }
  1443.  
  1444. // assemble the menu item and append it to menu body
  1445. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%s%s%s", sMenuBody, sMenuNumber, sMenuSrvName, sMenuInfo)
  1446.  
  1447. // if enabled a submenu is always possible to be displayed, regardless of the server's redirection status
  1448. if (bSubMenu)
  1449. {
  1450. key = key | (1<<(nDisplayNumber - 1))
  1451. g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount
  1452. }
  1453.  
  1454. nDisplayNumber++
  1455. }
  1456. nServerCount++
  1457. }
  1458.  
  1459. if (nServerCount < g_nServerCount)
  1460. {
  1461. if (bColorMenu)
  1462. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\w %L", sMenuBody, id, "MSG_MORE")
  1463. else
  1464. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_MORE")
  1465. key = key | (1<<8)
  1466. }
  1467. else
  1468. {
  1469. if (bColorMenu)
  1470. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\d %L", sMenuBody, id, "MSG_MORE")
  1471. else
  1472. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_MORE")
  1473. }
  1474.  
  1475. if (bColorMenu)
  1476. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, "MSG_CANCEL")
  1477. else
  1478. format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL")
  1479.  
  1480. show_menu(id, key, sMenuBody, -1, "Redirect Menu")
  1481. }
  1482. }
  1483. g_naMenuPageStart[id - 1][menupage - 1] = nServerCount
  1484.  
  1485. g_naLastMenuPages[id - 1] = menupage
  1486. }
  1487.  
  1488.  
  1489. /// <summary>Reloads the servers from server list. Takes care of variable and array reinitialization.</summary>
  1490. /// <remarks>To be able to rely on this in the future make sure to add an initialization here for all variables you add!</remarks>
  1491. public srvcmd_reload()
  1492. {
  1493. new nCounter
  1494.  
  1495. // clear all global arrays and variables before reloading
  1496. for (nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++)
  1497. {
  1498. if (g_naServerSockets[nCounter] > 0)
  1499. {
  1500. socket_close(g_naServerSockets[nCounter])
  1501. g_naServerSockets[nCounter] = 0
  1502. }
  1503. g_naServerPorts[nCounter] = 27015
  1504. g_naServerActivePlayers[nCounter] = -1
  1505. g_naServerMaxPlayers[nCounter] = -1
  1506. g_naServerCmdBackup[nCounter] = DEFAULT_CMDBACKUP
  1507. g_naServerFlags[nCounter] = 0
  1508. g_naServerReserveSlots[nCounter] = 0
  1509. g_baServerResponding[nCounter] = false
  1510. g_saServerMap[nCounter] = ""
  1511. g_saServerNames[nCounter] = ""
  1512. g_saServerAddresses[nCounter] = ""
  1513. g_saServerPasswords[nCounter] = ""
  1514. g_naServerPublicPassword[nCounter] = 0
  1515. }
  1516.  
  1517. // reset global variables
  1518. g_nNextAnnounceServer = 0
  1519. g_nServerCount = 0
  1520. g_nLastRedirectServer = -1
  1521. g_sLastRedirectName = ""
  1522. g_nOwnServer = -1
  1523. g_nRetryCount = 0
  1524.  
  1525. for (new nPlrCnt = 0; nPlrCnt < MAX_PLAYERS; nPlrCnt++)
  1526. {
  1527. // server IDs might change and thus render all currently saved server IDs invalid, so remove them, to be sure
  1528. g_nRetryQueue[nPlrCnt][0] = -1
  1529. g_nRetryQueue[nPlrCnt][1] = -1
  1530. g_nLastServer[nPlrCnt] = -1
  1531. g_nLastSelected[nPlrCnt] = -1
  1532. }
  1533.  
  1534. load_servers()
  1535.  
  1536. new sFullAddress[MAX_SERVERADDRESS_LEN]
  1537. new sTmpServerIP[MAX_IP_LEN + MAX_PORT_LEN]
  1538. get_cvar_string("net_address", sTmpServerIP, MAX_IP_LEN + MAX_PORT_LEN - 1)
  1539. new sTmpOwnAddress[MAX_SERVERADDRESS_LEN]
  1540. get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1)
  1541.  
  1542. // define the own server again
  1543. new nServerCount = 0
  1544. while (nServerCount < g_nServerCount)
  1545. {
  1546. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount])
  1547. if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerIP))
  1548. {
  1549. g_nOwnServer = nServerCount
  1550. break
  1551. }
  1552. nServerCount++
  1553. }
  1554. if (g_nOwnServer == -1)
  1555. log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR")
  1556. }
  1557.  
  1558.  
  1559. /// <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>
  1560. public cmd_pickserver(id, level, cid)
  1561. {
  1562. cmd_show_server_menu(id, level, cid)
  1563. return PLUGIN_HANDLED
  1564. }
  1565.  
  1566. /// <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>
  1567. /// <summary>It is aware of user permissions and has several options which are set via parameters.</summary>
  1568. /// <param name="id">ID of player to redirect.</param>
  1569. /// <param name="nServer">Target server, -1 for automatic choosing according to redirect_auto.</param>
  1570. /// <param name="bCanOther">If nServer is no valid redirect target can we use another server instead?</param>
  1571. /// <param name="bCanDrop">Drop user if no server was found?</param>
  1572. /// <param name="bIgnoreSource>"Redirect regardless of redirecting would be back to source server.</param>
  1573. /// <seealso name="native_redirect"/>
  1574. /// <seealso name="cmd_redirect_user"/>
  1575. public redirect(id, nServer, bCanOther, bCanDrop, bIgnoreSource)
  1576. {
  1577.  
  1578. new nForwardServer = -1
  1579. new bool:bFoundServer = false
  1580. new nRedirType
  1581. if (nServer == -1)
  1582. nRedirType = 1
  1583. else
  1584. nRedirType = 2
  1585.  
  1586. new nSourceServer
  1587.  
  1588. if (bIgnoreSource)
  1589. {
  1590. nSourceServer = -1
  1591. }
  1592. else
  1593. {
  1594. new sSourceServer[3]
  1595. get_user_info(id, "xredir", sSourceServer, 2)
  1596. if (!is_str_num(sSourceServer))
  1597. nSourceServer = -1
  1598. else
  1599. nSourceServer = str_to_num(sSourceServer)
  1600. if ((nSourceServer < 0) || (nSourceServer >= g_nServerCount))
  1601. nSourceServer = -1
  1602. }
  1603.  
  1604. if ((can_redirect_player(nServer, id, nRedirType, false) > 0) || (nServer == -1))
  1605. {
  1606. if (!bCanOther)
  1607. {
  1608. if (bCanDrop)
  1609. {
  1610. client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER")
  1611. client_cmd(id, "disconnect")
  1612. }
  1613. return false
  1614. }
  1615.  
  1616. nForwardServer = 0
  1617.  
  1618. // make sure at least one valid server exists or the second loop could be endless
  1619. while (nForwardServer < g_nServerCount)
  1620. {
  1621. if ((can_redirect_player(nForwardServer, id, nRedirType, false) == 0) && (nForwardServer != nSourceServer))
  1622. {
  1623. bFoundServer = true
  1624. break
  1625. }
  1626. nForwardServer++
  1627. }
  1628. new nAutoMode = get_pcvar_num(cvar_auto)
  1629. if ((nAutoMode == 1) || (nAutoMode == 3) || (nAutoMode == 5)) // redirect to random server
  1630. nForwardServer = -1
  1631. }
  1632. else
  1633. {
  1634. nForwardServer = nServer
  1635. bFoundServer = true
  1636. }
  1637.  
  1638. if (bFoundServer)
  1639. {
  1640. while (nForwardServer == -1)
  1641. {
  1642. nForwardServer = random_num(0, g_nServerCount - 1)
  1643. if ((can_redirect_player(nForwardServer, id, nRedirType, false) > 0) || ((nForwardServer == nSourceServer)))
  1644. nForwardServer = -1
  1645. }
  1646.  
  1647. new sUserNick[MAX_NAME_LEN]
  1648. get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
  1649. if (!equal(g_saServerPasswords[nForwardServer], "")) // set the user's server connect password if needed
  1650. client_cmd(id, "setinfo ^"password^" ^"%s^"", g_saServerPasswords[nForwardServer])
  1651. client_cmd(id, "setinfo ^"xredir^" ^"%d^"", g_nOwnServer)
  1652.  
  1653. new sCheckAddress[MAX_IP_LEN]
  1654. get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
  1655. new sFullAddress[MAX_SERVERADDRESS_LEN]
  1656. if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[nForwardServer], "")))
  1657. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[nForwardServer], g_naServerPorts[nForwardServer])
  1658. else
  1659. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nForwardServer], g_naServerPorts[nForwardServer])
  1660. if (nRedirType == 1)
  1661. client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_SERVER_FULL_REDIRECTING", g_saServerNames[nForwardServer])
  1662. client_cmd(id, "Connect %s", sFullAddress)
  1663.  
  1664.  
  1665. if (get_pcvar_num(cvar_show) == 1)
  1666. {
  1667. new nPlayers[MAX_PLAYERS]
  1668. new nPlayerNum, nPlayerCount, nCurrentPlayer
  1669. get_players(nPlayers, nPlayerNum, "c")
  1670. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  1671. {
  1672. nCurrentPlayer = nPlayers[nPlayerCount]
  1673. if (get_pcvar_num(cvar_follow) == 1)
  1674. client_print(nCurrentPlayer, print_chat, "%s: %L - %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer], nCurrentPlayer, "MSG_FOLLOW")
  1675. else
  1676. client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer])
  1677. }
  1678. }
  1679.  
  1680. g_nLastRedirectServer = nForwardServer
  1681. g_sLastRedirectName = sUserNick
  1682. }
  1683. else if (bCanDrop)
  1684. {
  1685. client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER")
  1686. client_cmd(id, "disconnect")
  1687. }
  1688. return true
  1689. }
  1690.  
  1691. /// <summary>Basically a wrapper for <seealso name="redirect"/> to make it available to other pugins as native.</summary>
  1692. /// <seealso name="redirect"/>
  1693. /// <seealso name="cmd_redirect_user"/>
  1694. public native_redirect(id, nServer, bCanDrop)
  1695. {
  1696. redirect(id, nServer, (nServer == -1), bCanDrop, true)
  1697. return PLUGIN_HANDLED
  1698. }
  1699.  
  1700. /// <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>
  1701. /// <seealso name="redirect"/>
  1702. /// <seealso name="native_redirect"/>
  1703. public cmd_redirect_user(id, level, cid)
  1704. {
  1705. if (!cmd_access(id, level, cid, 2))
  1706. return PLUGIN_HANDLED
  1707.  
  1708. new nForwardServer = -1
  1709. new sName[32]
  1710. read_argv(1, sName, 31)
  1711. new nCmdID = cmd_target(id, sName, 8)
  1712.  
  1713. if (!nCmdID)
  1714. return PLUGIN_HANDLED
  1715.  
  1716. // contains destination server number?
  1717. if (read_argc() > 2)
  1718. {
  1719. new argtmp[3]
  1720. read_argv(2, argtmp, 2)
  1721. if (is_str_num(argtmp))
  1722. nForwardServer = (str_to_num(argtmp) - 1)
  1723. }
  1724.  
  1725. redirect(nCmdID, nForwardServer, (nForwardServer == -1), true, true)
  1726.  
  1727. return PLUGIN_HANDLED
  1728. }
  1729.  
  1730. /// <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>
  1731. /// <seealso name="show_server_menu"/>
  1732. public cmd_show_server_menu(id, level, cid)
  1733. {
  1734. if (get_pcvar_num(cvar_manual) >= 1)
  1735. show_server_menu(id, 1)
  1736. else
  1737. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_MANUAL_DISABLED")
  1738. return PLUGIN_CONTINUE
  1739. }
  1740.  
  1741. /// <summary>Handler for chat command <paramref name="/retry"/>. Adds the user to the retry queue using <seealso name="queue_add"/>.</summary>
  1742. /// <seealso name="queue_add"/>
  1743. public cmd_retry(id, level, cid)
  1744. {
  1745. if (g_nLastServer[id - 1] > -1)
  1746. queue_add(id, g_nLastServer[id - 1])
  1747. else
  1748. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_NO_LAST")
  1749. return PLUGIN_CONTINUE
  1750. }
  1751.  
  1752. /// <summary>Handler for chat command <paramref name="/stopretry"/>. Removes the user from the retry queue using <seealso name="queue_remove"/>.</summary>
  1753. /// <seealso name="queue_remove"/>
  1754. public cmd_stopretry(id, level, cid)
  1755. {
  1756. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE_ALL", g_saServerNames[g_nLastServer[id - 1]])
  1757. queue_remove(id, -1)
  1758. return PLUGIN_CONTINUE
  1759. }
  1760.  
  1761. /// <summary>Handler for chat command <paramref name="/follow"/>. Sends a player after the last player that was redirected using <seealso name="redirect"/>.</summary>
  1762. /// <seealso name="redirect"/>
  1763. public cmd_follow_player(id, level, cid)
  1764. {
  1765. if (get_pcvar_num(cvar_active) == 1)
  1766. {
  1767. if (get_pcvar_num(cvar_follow) == 1)
  1768. {
  1769. if (g_nLastRedirectServer >= 0)
  1770. {
  1771. console_print(id, "%s: %L", PLUGIN_TAG, id, "MSG_REDIRECTING", g_saServerNames[g_nLastRedirectServer])
  1772. new sFullAddress[MAX_SERVERADDRESS_LEN]
  1773. new sCheckAddress[MAX_IP_LEN]
  1774. get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
  1775. if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[g_nLastRedirectServer], "")))
  1776. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer])
  1777. else
  1778. format(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer])
  1779. client_cmd(id, "Connect %s", sFullAddress)
  1780. new sUserNick[MAX_NAME_LEN]
  1781. get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
  1782. if (get_pcvar_num(cvar_show) == 1)
  1783. client_print(0, print_chat, "%s: %L - %L", PLUGIN_TAG, id, "MSG_FOLLOWED", sUserNick, g_sLastRedirectName, g_saServerNames[g_nLastRedirectServer], id, "MSG_FOLLOW")
  1784. g_sLastRedirectName = sUserNick
  1785. }
  1786. else
  1787. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_CANT_FOLLOW")
  1788.  
  1789. }
  1790. else
  1791. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_FOLLOW_DISABLED")
  1792. }
  1793. return PLUGIN_CONTINUE
  1794. }
  1795.  
  1796. /// <summary>Event handler for sub menu selection.</summary>
  1797. /// <summary>When the user presses a number key in the sub menu this handler is called.</summary>
  1798. /// <param name="id">Slot ID of player that selected a menu item.</param>
  1799. /// <param name="key">Key that was pressed, number between 0 and 9.</param>
  1800. /// <seealso name="server_menu_select"/>
  1801. /// <seealso name="show_server_menu"/>
  1802. /// <seealso name="show_sub_menu"/>
  1803. public sub_menu_select(id, key)
  1804. {
  1805. new nServer = g_nLastSelected[id - 1]
  1806. if (key == 0) // redirect
  1807. {
  1808. // check if meanwhile the redirection is not possible anymore - if so, refresh the detail menu
  1809. if (can_redirect_player(nServer, id, 2, false) > 0)
  1810. show_sub_menu(id, nServer)
  1811. else
  1812. redirect(id, nServer, false, false, true)
  1813. }
  1814. else if (key == 1) // queue
  1815. {
  1816. if (is_queued(id, nServer))
  1817. {
  1818. queue_remove(id, nServer)
  1819. client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE", g_saServerNames[nServer])
  1820. }
  1821. else
  1822. {
  1823. queue_add(id, nServer)
  1824. }
  1825. }
  1826. else if (key == 2) // refresh
  1827. {
  1828. show_sub_menu(id, nServer)
  1829. }
  1830. else if (key == 8) // go back to where the user was before in main menu
  1831. show_server_menu(id, g_naLastMenuPages[id - 1])
  1832. }
  1833.  
  1834. /// <summary>Event handler for server menu selection.</summary>
  1835. /// <summary>When the user presses a number key in the server menu this handler is called.</summary>
  1836. /// <summary>Depending on settings it will display a sub menu or redirect the user.</summary>
  1837. /// <param name="id">Slot ID of player that selected a menu item.</param>
  1838. /// <param name="key">Key that was pressed, number between 0 and 9.</param>
  1839. /// <seealso name="sub_menu_select"/>
  1840. /// <seealso name="show_server_menu"/>
  1841. /// <seealso name="show_sub_menu"/>
  1842. public server_menu_select(id, key)
  1843. {
  1844. if (key < 8)
  1845. {
  1846. new nServerIdx = g_naServerSelections[id - 1][key]
  1847.  
  1848. new nManualMode = get_pcvar_num(cvar_manual)
  1849. // show the detail menu?
  1850. if (((nManualMode == 2) && (can_redirect_player(nServerIdx, id, 2, false) > 0)) || (nManualMode == 3))
  1851. show_sub_menu(id, nServerIdx)
  1852. else
  1853. redirect(id, nServerIdx, false, false, true)
  1854. }
  1855. else
  1856. {
  1857. if (key == 8)
  1858. show_server_menu(id, g_naLastMenuPages[id - 1] + 1)
  1859. }
  1860. }
  1861.  
  1862.  
  1863. /// <summary>Sends the information query packets to all other servers.</summary>
  1864. /// <summary>This sends the UDP server information query packets in old and new style HL format to all servers in the list.</summary>
  1865. /// <summary>Receiving of server data is handled by <seealso name="receive_serverquery_answers"/>.</summary>
  1866. /// <seealso name="receive_serverquery_answers"/>
  1867. public query_servers()
  1868. {
  1869. new nCheckMethod = get_pcvar_num(cvar_check_method)
  1870. if (nCheckMethod == 0)
  1871. return PLUGIN_HANDLED
  1872. new socket_error
  1873. new sOldRequest[12]
  1874. new sNewRequest[26]
  1875.  
  1876. if (nCheckMethod == 1)
  1877. {
  1878. // we don't know what server it is so send both old and new style query
  1879. format(sOldRequest, 8, "%c%c%c%c%s", 255, 255, 255, 255, "ping")
  1880. format(sNewRequest, 5, "%c%c%c%c%c", 255, 255, 255, 255, 105)
  1881. }
  1882. else if (nCheckMethod == 2)
  1883. {
  1884. // we don't know what server it is so send both old and new style query
  1885. format(sOldRequest, 11, "%c%c%c%c%s", 255, 255, 255, 255, "details")
  1886. format(sNewRequest, 25, "%c%c%c%c%c%s%c", 255, 255, 255, 255, 84, "Source Engine Query", 0)
  1887. }
  1888.  
  1889. new nServerCount = 0
  1890. new nQuerySocket
  1891. new nCmdBackup
  1892. new nSendCount
  1893. while (nServerCount < g_nServerCount)
  1894. {
  1895. if (nServerCount != g_nOwnServer)
  1896. {
  1897. nQuerySocket = g_naServerSockets[nServerCount]
  1898. // first we clear the current receive buffer - we are sending a new request and don't care for old data anymore
  1899. if (nQuerySocket > 0)
  1900. {
  1901. new sEmptyBufferDummy[512]
  1902. new nEndlessProtection = 0
  1903. while ((socket_change(nQuerySocket, 1)) && (nEndlessProtection < 500))
  1904. {
  1905. //log_amx("emptying socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount])
  1906. socket_recv(nQuerySocket, sEmptyBufferDummy, 512)
  1907. nEndlessProtection++
  1908. }
  1909. if (nEndlessProtection >= 500)
  1910. {
  1911. socket_close(nQuerySocket)
  1912. log_amx("WARNING: endless protection triggered for socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount])
  1913. }
  1914.  
  1915. }
  1916. else
  1917. {
  1918. // socket debug
  1919. //log_amx("opening socket for server %i (%s)", nServerCount, g_saServerNames[nServerCount])
  1920. if (!equal(g_saServerLocalAddresses[nServerCount], ""))
  1921. nQuerySocket = socket_open(g_saServerLocalAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error)
  1922. else
  1923. nQuerySocket = socket_open(g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error)
  1924. // socket debug
  1925. //log_amx("opened socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount])
  1926. }
  1927.  
  1928. if ((nQuerySocket > 0) && (socket_error == 0))
  1929. {
  1930. g_naServerSockets[nServerCount] = nQuerySocket
  1931. nCmdBackup = g_naServerCmdBackup[nServerCount]
  1932. // socket debug
  1933. //log_amx("sending query on socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount])
  1934. if (nCheckMethod == 1)
  1935. {
  1936. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1937. socket_send2(nQuerySocket, sOldRequest, 8)
  1938. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1939. socket_send2(nQuerySocket, sNewRequest, 5)
  1940. }
  1941. else if (nCheckMethod == 2)
  1942. {
  1943. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1944. socket_send2(nQuerySocket, sOldRequest, 11)
  1945. for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
  1946. socket_send2(nQuerySocket, sNewRequest, 25)
  1947. }
  1948. }
  1949. else
  1950. {
  1951. g_naServerSockets[nServerCount] = 0
  1952. log_amx("%L", LANG_SERVER, "MSG_SOCKET_ERROR", socket_error, nServerCount)
  1953. }
  1954. }
  1955. nServerCount++
  1956. }
  1957. set_task(QUERY_TIMEOUT, "receive_serverquery_answers", TASKID_QUERY_RECEIVE)
  1958.  
  1959. return PLUGIN_HANDLED
  1960. }
  1961.  
  1962.  
  1963. /// <summary>Index an incoming UDP data packet.</summary>
  1964. /// <param name="sData">The raw UDP data string that was received.</param>
  1965. /// <param name="nDataLen">Length of the raw UDP data string as reported by the socket receive function.</param>
  1966. /// <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>
  1967. /// <param name="aIndexes">The function stores the resulting character offsets of each index in this array.</param>
  1968. /// <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>
  1969. /// <returns>The number of indexes that were written (= the number of format elements).</returns>
  1970. public index_create(sData[MAX_INFO_LEN], nDataLen, sFormatString[100], aIndexes[MAX_INFO_FORMAT])
  1971. {
  1972. //log_amx("---------------------- indexing %s ----------------------", sFormatString)
  1973. new nFormatPos = 0 // current position within the format array
  1974. new nIndexPos = 0 // current position within the data array
  1975. new nDataIndex = 0 // current chracter index within the data stream
  1976. new nFormatPosMax = strlen(sFormatString)
  1977. while ((nIndexPos < nFormatPosMax) && (nDataIndex <= nDataLen))
  1978. {
  1979. switch (sFormatString[nFormatPos])
  1980. {
  1981. case '1': // "byte"
  1982. {
  1983. //log_amx("indexed byte <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos)
  1984. aIndexes[nIndexPos] = nDataIndex
  1985. nDataIndex++
  1986. nIndexPos++
  1987. }
  1988. case '2': // "short"
  1989. {
  1990. //log_amx("indexed short <%d %d> at %d, element %d, format position %d", sData[nDataIndex], sData[nDataIndex + 1], nDataIndex, nIndexPos, nFormatPos)
  1991. aIndexes[nIndexPos] = nDataIndex
  1992. nDataIndex += 2
  1993. nIndexPos++
  1994. }
  1995. case '4': // "long"
  1996. {
  1997. //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)
  1998. aIndexes[nIndexPos] = nDataIndex
  1999. nDataIndex += 4
  2000. nIndexPos++
  2001. }
  2002. case 's': // string
  2003. {
  2004. /*
  2005. new sDebugString[250]
  2006. arrayset(sDebugString, 0, 250)
  2007. copyc(sDebugString, 250, sData[nDataIndex], 0)
  2008. log_amx("indexed string <%s> at %d, element %d, format position %d", sDebugString, nDataIndex, nIndexPos, nFormatPos)
  2009. */
  2010. aIndexes[nIndexPos] = nDataIndex
  2011. do { nDataIndex++; } while ((sData[nDataIndex] != 0) && (nDataIndex < nDataLen)) // find the end of the string by searching a 0 character
  2012. nDataIndex++
  2013. nIndexPos++
  2014. }
  2015. case '[': // byte switch and start of optional formats
  2016. {
  2017. //log_amx("indexed switch <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos)
  2018. if (sData[nDataIndex] != 1) // skip options
  2019. {
  2020. do { nFormatPos++; } while ((sFormatString[nFormatPos] != ']') && (nFormatPos < nFormatPosMax))
  2021. //log_amx("skipped optional formats, now at format position %d")
  2022. }
  2023. else
  2024. //log_amx("----------- start of optional formats -----------")
  2025. nDataIndex++
  2026. nIndexPos++
  2027. }
  2028. case ']': // end of optional formats
  2029. {
  2030. //log_amx("----------- end of optional formats -----------")
  2031. nDataIndex++
  2032. }
  2033. default:
  2034. nDataIndex++
  2035. }
  2036. nFormatPos++
  2037. }
  2038. //log_amx("---------------------- end of indexing ----------------------")
  2039. //log_amx("%d < %d - %d <= %d", nIndexPos, nFormatPosMax, nDataIndex, nDataLen)
  2040. return nIndexPos
  2041. }
  2042.  
  2043. /// <summary>Gets a byte from the element at the given index.</summary>
  2044. /// <param name="sData">The raw UDP data string that was received.</param>
  2045. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2046. /// <returns>The requested byte value.</returns>
  2047. public index_get_byte(sData[MAX_INFO_LEN], nIndex)
  2048. {
  2049. return sData[nIndex]
  2050. }
  2051.  
  2052. /// <summary>Gets a short from the element at the given index.</summary>
  2053. /// <param name="sData">The raw UDP data string that was received.</param>
  2054. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2055. /// <returns>The requested short value.</returns>
  2056. public index_get_short(sData[MAX_INFO_LEN], nIndex)
  2057. {
  2058. return ((sData[nIndex] << 8) | (sData[nIndex + 1] & 0x00FF))
  2059. }
  2060.  
  2061. /// <summary>Gets a long from the element at the given index.</summary>
  2062. /// <param name="sData">The raw UDP data string that was received.</param>
  2063. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2064. /// <returns>The requested long value.</returns>
  2065. public index_get_long(sData[MAX_INFO_LEN], nIndex)
  2066. {
  2067. return ((sData[nIndex] << 24) | (sData[nIndex + 1] << 16) | (sData[nIndex + 2] << 8) | (sData[nIndex + 3] & 0x000000FF))
  2068. }
  2069.  
  2070. /// <summary>Gets a string from the element at the given index.</summary>
  2071. /// <param name="sData">The raw UDP data string that was received.</param>
  2072. /// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
  2073. /// <returns>The requested string value.</returns>
  2074. public index_get_string(sData[MAX_INFO_LEN], nIndex)
  2075. {
  2076. new aRet[MAX_INFO_LEN]
  2077. arrayset(aRet, 0, MAX_INFO_LEN)
  2078. copyc(aRet, MAX_INFO_LEN, sData[nIndex], 0)
  2079. return aRet
  2080. }
  2081.  
  2082. /// <summary>Handler for parsing the answers to server query packet.</summary>
  2083. /// <summary>This handler parses the UDP information answer packets from the servers that have been queried with <seealso name="query_servers"/>.</summary>
  2084. /// <seealso name="query_servers"/>
  2085. public receive_serverquery_answers()
  2086. {
  2087. new nCheckMethod = get_pcvar_num(cvar_check_method)
  2088.  
  2089. new sRcvBuf[MAX_INFO_LEN]
  2090. new nRcvLen
  2091. new nRecvCount
  2092. new sMap[MAX_MAP_LEN]
  2093. new nServerCount = 0
  2094. while (nServerCount < g_nServerCount)
  2095. {
  2096. if (!g_naServerSockets[nServerCount])
  2097. {
  2098. g_baServerResponding[nServerCount] = false
  2099. /*
  2100. should only happen for the g_nOwnServer
  2101.  
  2102. client_print(0, print_chat, "%s no socket", g_saServerNames[nServerCount])
  2103. */
  2104. }
  2105. else
  2106. {
  2107. nRecvCount = 0
  2108. new nCmdBackup = g_naServerCmdBackup[nServerCount]
  2109. g_baServerResponding[nServerCount] = false
  2110. new nSocket = g_naServerSockets[nServerCount]
  2111. while (socket_change(nSocket, 1) && (nRecvCount <= nCmdBackup))
  2112. {
  2113. // socket debug
  2114. //log_amx("socket changed: %i (%s)", nSocket, g_saServerNames[nServerCount])
  2115. nRecvCount++
  2116.  
  2117. // initialize our receive buffer
  2118. setc(sRcvBuf, MAX_INFO_LEN, 0);
  2119. //for (nClearCounter = 0; nClearCounter < MAX_INFO_LEN; nClearCounter++)
  2120. //sRcvBuf[nClearCounter] = 0
  2121. // socket debug
  2122. //log_amx("receiving from socket: %i (%s)", nSocket, g_saServerNames[nServerCount])
  2123. nRcvLen = socket_recv(nSocket, sRcvBuf, MAX_INFO_LEN)
  2124. // socket debug
  2125. //log_amx("finished receiving from socket %i (%s), received %i bytes", nSocket, g_saServerNames[nServerCount], nRcvLen)
  2126.  
  2127. //TODO: handle fragmented packets
  2128.  
  2129. if (nRcvLen > 5) // shortest reply is a ping response with length of 6
  2130. {
  2131. if (nCheckMethod == 1)
  2132. {
  2133. // ping response
  2134. if (equal(sRcvBuf, {-1,-1,-1,-1,'j'}, 5))
  2135. {
  2136. g_baServerResponding[nServerCount] = true
  2137. break
  2138. }
  2139. }
  2140. else if (nCheckMethod == 2)
  2141. {
  2142. new aIndexes[MAX_INFO_FORMAT]
  2143. if (equal(sRcvBuf, {-1,-1,-1,-1}, 4))
  2144. {
  2145. g_baServerResponding[nServerCount] = true
  2146. if (sRcvBuf[4] == 'm') // old HL1 or "goldsource" protocol
  2147. {
  2148. index_create(sRcvBuf, nRcvLen, A2S_INFO_GOLD_REPLY_FORMAT, aIndexes)
  2149. copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_GOLD_IDX_MAP]], 0)
  2150. g_saServerMap[nServerCount] = sMap
  2151. g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_NUMPLAYERS])
  2152. g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_MAXPLAYERS])
  2153. }
  2154. else if (sRcvBuf[4] == 'I') // source protocol
  2155. {
  2156. index_create(sRcvBuf, nRcvLen, A2S_INFO_SOURCE_REPLY_FORMAT, aIndexes)
  2157. copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_SOURCE_IDX_MAP]], 0)
  2158. g_saServerMap[nServerCount] = sMap
  2159. g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_NUMPLAYERS])
  2160. g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_MAXPLAYERS])
  2161. }
  2162. }
  2163. }
  2164. }
  2165. }
  2166. /*
  2167. if (nRecvCount == 0)
  2168. log_amx("no change on socket %i (%s)", g_naServerSockets[nServerCount], g_saServerNames[nServerCount])
  2169. */
  2170. //socket_close(nSocket)
  2171. //g_naServerSockets[nServerCount] = 0
  2172. }
  2173. nServerCount++
  2174. }
  2175.  
  2176. if (get_pcvar_num(cvar_retry) > 0)
  2177. {
  2178. // now search for players who queued themselves to be redirected
  2179. new nServer
  2180. new nPlrCnt = 0
  2181.  
  2182. while (nPlrCnt < g_nRetryCount)
  2183. {
  2184. nServer = g_nRetryQueue[nPlrCnt][1]
  2185. if (nServer > -1) // just to be sure
  2186. {
  2187. new nPlr = g_nRetryQueue[nPlrCnt][0]
  2188. if (can_redirect_player(nServer, nPlr, 2, false) == 0)
  2189. {
  2190. console_print(nPlr, "%s: %L", PLUGIN_TAG, nPlr, "MSG_RETRY_SUCCESS")
  2191. redirect(nPlr, nServer, false, false, true)
  2192. g_naServerActivePlayers[nServer]++
  2193. }
  2194. }
  2195. nPlrCnt++
  2196. }
  2197. }
  2198.  
  2199. return PLUGIN_HANDLED
  2200. }
  2201.  
  2202. /// <summary>Retrieves number of admins currently on the server.</summary>
  2203. /// <returns>Number of admins currently on server.</returns>
  2204. public get_admin_count()
  2205. {
  2206. new nPlayers[MAX_PLAYERS]
  2207. new nPlayerNum, nPlayerCount
  2208. get_players(nPlayers, nPlayerNum, "ch")
  2209. new nAdmins = 0
  2210. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2211. {
  2212. if (access(nPlayers[nPlayerCount], MIN_ADMIN_LEVEL))
  2213. nAdmins++
  2214. }
  2215. return nAdmins
  2216. }
  2217.  
  2218. /// <summary>Event handler for client disconnect event.</summary>
  2219. /// <summary>This handler makes sure people that have been in queue while disconnecting are removed from it.</summary>
  2220. /// <summary>Furthermore it resets the "last server" information for this now empty player slot.</summary>
  2221. /// <param name="id">Slot ID of player that was disconnected.</param>
  2222. public client_disconnect(id)
  2223. {
  2224. queue_remove(id, -1)
  2225. g_nLastServer[id - 1] = -1
  2226. }
  2227.  
  2228. /// <summary>Event handler for client authorized event.</summary>
  2229. /// <summary>This handler is called as soon as a connecting client was authenticated with WON/Steam system and received a WON/Steam ID.</summary>
  2230. /// <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>
  2231. /// <param name="id">Slot ID of player that was authorized.</param>
  2232. public client_authorized(id)
  2233. {
  2234. if (is_user_bot(id) || is_user_hltv(id))
  2235. return PLUGIN_CONTINUE
  2236.  
  2237. if ((g_nOwnServer == -1) && (!g_bInitialized))
  2238. {
  2239. plugin_postinit()
  2240. }
  2241.  
  2242. g_naLastMenuPages[id - 1] = 1
  2243.  
  2244. new nAutoMode = get_pcvar_num(cvar_auto)
  2245. if (get_pcvar_num(cvar_active) == 1)
  2246. {
  2247. if (nAutoMode > 0)
  2248. {
  2249. if (((get_maxplayers() - get_playersnum(1)) == 0) || (nAutoMode > 2))
  2250. {
  2251. if (g_nServerCount > 0)
  2252. {
  2253. new bool:bLocalPriority = false
  2254. // if local slot reservation is enabled we need to check whether this is a local player
  2255. if (get_pcvar_num(cvar_localslots) == 1)
  2256. {
  2257. new sCheckAddress[MAX_IP_LEN]
  2258. get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
  2259. if (is_local_address(sCheckAddress))
  2260. bLocalPriority = true
  2261. }
  2262. new nMaxAdmins = get_pcvar_num(cvar_maxadmins)
  2263. if (nMaxAdmins == 0)
  2264. nMaxAdmins = MAX_PLAYERS
  2265. new bool:bRedirect = false // to keep some better overview assemble the if-comparison part by part in bRedirect
  2266. // 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)
  2267. bRedirect = bRedirect | (((nAutoMode == 1) || (nAutoMode == 2)) && ((!access(id, MIN_ADMIN_LEVEL)) || (get_pcvar_num(cvar_adminslots) == 0) || (get_admin_count() > nMaxAdmins)))
  2268. // redirect if automode is 3 or 4 and user is no admin
  2269. bRedirect = bRedirect | (((nAutoMode == 3) || (nAutoMode == 4)) && (!access(id, MIN_ADMIN_LEVEL)))
  2270. // redirect if automode is 5 or 6
  2271. bRedirect = bRedirect | ((nAutoMode == 5) || (nAutoMode == 6))
  2272. if (g_bDebug)
  2273. {
  2274. new sPlayerName[MAX_NAME_LEN]
  2275. get_user_name(id, sPlayerName, MAX_NAME_LEN - 1)
  2276. 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())
  2277. }
  2278. if (bRedirect)
  2279. {
  2280. //TODO: code in many parts redundant to what the redirect() function does except for the local-priority stuff - rather extend the redirect() function
  2281. if (bLocalPriority)
  2282. {
  2283. // find the remote user that is connected for the shortest time and redirect him
  2284.  
  2285. new nPlayers[MAX_PLAYERS]
  2286. new nPlayerNum, nPlayerCount
  2287. new nMinConnectedTime = 0x7FFFFFFF // make sure the first time value found will always be lower
  2288. new nMinTimePlayer = -1
  2289. new nUserTime
  2290. get_players(nPlayers, nPlayerNum, "ch")
  2291. new nCurID
  2292. new sCheckPlayerAddress[MAX_IP_LEN]
  2293. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2294. {
  2295. nCurID = nPlayers[nPlayerCount]
  2296. get_user_ip(nCurID, sCheckPlayerAddress, MAX_IP_LEN - 1, 1)
  2297.  
  2298. nUserTime = get_user_time(nCurID)
  2299. if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL)) && (!is_local_address(sCheckPlayerAddress)))
  2300. {
  2301. nMinTimePlayer = nCurID
  2302. nMinConnectedTime = nUserTime
  2303. }
  2304. }
  2305. if (nMinTimePlayer >= 0)
  2306. {
  2307. client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORLOCAL")
  2308. redirect(nMinTimePlayer, -1, true, true, true)
  2309. return PLUGIN_CONTINUE
  2310. }
  2311. else
  2312. if (g_bDebug)
  2313. log_amx("no valid redirect target to free up slot for local player %i", id)
  2314.  
  2315. }
  2316. else
  2317. {
  2318. redirect(id, -1, true, (nAutoMode < 3), false)
  2319. return PLUGIN_CONTINUE
  2320. }
  2321. }
  2322. else
  2323. {
  2324. // find the user that is connected for the shortest time and redirect him away
  2325.  
  2326. new nPlayers[MAX_PLAYERS]
  2327. new nPlayerNum, nPlayerCount
  2328. new nMinConnectedTime = 0x7FFFFFFF
  2329. new nMinTimePlayer = -1
  2330. new nUserTime
  2331. get_players(nPlayers, nPlayerNum, "ch")
  2332. new nCurID
  2333. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2334. {
  2335. nCurID = nPlayers[nPlayerCount]
  2336.  
  2337. nUserTime = get_user_time(nCurID)
  2338. if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL)))
  2339. {
  2340. nMinTimePlayer = nCurID
  2341. nMinConnectedTime = nUserTime
  2342. }
  2343. }
  2344. if (nMinTimePlayer >= 0)
  2345. {
  2346. client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORADMIN")
  2347. redirect(nMinTimePlayer, -1, true, true, true)
  2348. return PLUGIN_CONTINUE
  2349. }
  2350. else
  2351. if (g_bDebug)
  2352. log_amx("no valid redirect target to free up slot for admin %i", id)
  2353. }
  2354.  
  2355. }
  2356. }
  2357. else
  2358. {
  2359. if (g_bDebug)
  2360. {
  2361. new sPlayerName[MAX_NAME_LEN]
  2362. get_user_name(id, sPlayerName, MAX_NAME_LEN - 1)
  2363. log_amx("Not auto-redirecting <%s> (%d), automode: %d, current players/max: %d/%d", sPlayerName, id, nAutoMode, get_playersnum(1), get_maxplayers())
  2364. }
  2365. }
  2366. }
  2367. }
  2368.  
  2369. // show the welcome message delayed to that player
  2370. new sID[1]
  2371. sID[0] = id
  2372. set_task(20.0, "welcome_message", 0, sID, 1)
  2373.  
  2374. new sSourceServer[4] // maximum is 999 servers, so we have a maximum of 3 digits
  2375. get_user_info(id, "xredir", sSourceServer, 3)
  2376. if (strcmp(sSourceServer, "") != 0)
  2377. {
  2378. new nSourceServer = str_to_num(sSourceServer)
  2379. g_nLastServer[id - 1] = nSourceServer
  2380. if (g_bDebug)
  2381. log_amx("saved last server for player %i as server %i", id, g_nLastServer[id - 1])
  2382.  
  2383. if ((nSourceServer >= 0) && (nSourceServer < g_nServerCount))
  2384. {
  2385. if (get_pcvar_num(cvar_show) == 1)
  2386. {
  2387. new nPlayers[MAX_PLAYERS]
  2388. new nPlayerNum, nPlayerCount, nCurrentPlayer
  2389. new sConnectNick[MAX_NAME_LEN]
  2390. get_user_name(id, sConnectNick, MAX_NAME_LEN - 1)
  2391. get_players(nPlayers, nPlayerNum, "c")
  2392. 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)
  2393. for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
  2394. {
  2395. nCurrentPlayer = nPlayers[nPlayerCount]
  2396. client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECT_RECEIVE", sConnectNick, g_saServerNames[nSourceServer])
  2397. }
  2398. }
  2399. }
  2400. new sID[1]
  2401. sID[0] = id
  2402.  
  2403. client_cmd(id, "setinfo ^"xredir^" ^"^"")
  2404. client_cmd(id, "setinfo ^"password^" ^"^"")
  2405.  
  2406. set_task(10.0, "reset_info", 0, sID, 1)
  2407. }
  2408. return PLUGIN_CONTINUE
  2409. }
  2410.  
  2411.  
  2412. /// <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>
  2413. /// <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>
  2414. /// <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>
  2415. /// <summary>that he can use /retry command to have himself queued to redirect back to the source server.</summary>
  2416. /// <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>
  2417. public welcome_message(id[])
  2418. {
  2419. new nID = id[0]
  2420. if (is_user_connected(nID)) // make sure the player didn't already disconnect within the set_task delay
  2421. {
  2422. new nLastServer = g_nLastServer[nID - 1]
  2423. if ((nLastServer >= 0) && (nLastServer != g_nOwnServer) && (nLastServer < MAX_SERVERFORWARDS))
  2424. {
  2425. new sAnnounceText[MAX_WELCOME_LEN]
  2426. format(sAnnounceText, MAX_WELCOME_LEN - 1, "%L", nID, "MSG_REDIRFROM", g_saServerNames[g_nOwnServer], g_saServerNames[nLastServer])
  2427. if ((get_pcvar_num(cvar_retry) == 1) && (get_pcvar_num(cvar_show) == 1))
  2428. format(sAnnounceText, MAX_WELCOME_LEN - 1, "%s^n%L", sAnnounceText, nID, "MSG_RETRY_BACK_ANNOUNCE")
  2429.  
  2430. set_hudmessage(000, 100, 255, -1.0, -1.0, 0, 0.0, 10.0, 0.5, 2.0, 1)
  2431. show_hudmessage(nID, sAnnounceText)
  2432. }
  2433. }
  2434. }
  2435.  
  2436.  
  2437. #else
  2438.  
  2439. /// <summary>Dummy handler to catch the case where a user tried to compile the plugin with a too old compiler.</summary>
  2440. public plugin_init()
  2441. {
  2442. log_amx("ERROR: Your AMXX version is too old for this plugin.")
  2443. }
  2444. #endif
  2445.