View Single Post
Author Message
FLOY
Senior Member
Join Date: Dec 2013
Location: I love to www.steam.lv
Old 12-13-2020 , 05:15   [req] Server menu beta
Reply With Quote #1

Quote:
#define SQL
#define STATISTICS

// don't change this!
#if defined STATISTICS && !defined SQL
#define VAULT
#endif

// includes
#include <amxmodx>
#include <amxmisc>
#include <sockets>
#if defined VAULT
#include <nvault>
#endif
#if defined SQL
#include <sqlx>
#endif

// plugin defines
#define PLUGIN_NAME "xREDIRECT"
#define PLUGIN_VERSION "2.0RC2"
#define PLUGIN_AUTHOR "x0R"
#define PLUGIN_TAG "[Evilnet.Lv - Server]"

// SQL defines
#define SQL_PREFIX "xredirect_"
#define SQL_TABLENAME_SERVERS "servers"
#define SQL_TABLENAME_ATTRIBUTES "attributes"
#define SQL_TABLENAME_STATISTICS "statistics"


// Vault defines
#define VAULT_NAME "xREDIRECT"

// maximum values - don't change this if you don't know what you are doing!
#define MAX_FILE_LEN 256 // maximum length of file names
#define MAX_SERVERLINE_LEN 256 // maximum length of a line read from SERVERFILE
#define MAX_SERVERNAME_LEN 50 // maximum length of a server name read from SERVERFILE
#define MAX_SERVERADDRESS_LEN 100 // maximum length of a server address read from SERVERFILE
#define MAX_NAME_LEN 33 // maximum length of a player name
#define MAX_MENUBODY_LEN 512 // maximum length of a menu body
#define MAX_WELCOME_LEN 1024 // maximum length of the welcome message
#define MAX_INFO_LEN 1400 // maximum length of info reply - when longer than that the packet is fragmented (software side, not due to MTU)
#define MAX_INFO_FORMAT 100 // maximum length of a format string for an info reply
#define MAX_MAP_LEN 30 // maximum length of map names
#define MAX_IP_LEN 16 // maximum length of IP addresses
#define MAX_ID_LEN 35 // maximum length of a string containing a WON or Steam ID
#define MAX_PORT_LEN 6 // maximum length of port numbers (as strings of course)
#define MAX_ATTRIB_LEN 20 // maximum length of an attribute name in SERVERFILE or SQL_TABLENAME_ATTRIBUTES
#define MAX_PASSWORD_LEN 15 // maximum length of a password in SERVERFILE
#define MAX_VALUE_LEN 100 // maximum length of an attribute value in SERVERFILE or SQL_TABLENAME_ATTRIBUTES
#define MAX_PLAYERS 32 // maximum number of players on the server
#define MAX_VAULT_KEY_LEN 10 // maximum length a vault key can have (STATS_VAULT_TAG + ID number from a STATS_COUNT_ constant)
#define MAX_CATEGORIES 9 // maximum number of categories in the menu
#define MAX_SQL_TABLE_LEN 30 // maximum length of an SQL table name
#define MAX_SQL_ERROR_LEN 128 // maximum length of an SQL error message

// statistic count constants
#define STATS_VAULT_TAG "vault" // the tag prepended to the vault keys - remember to also change MAX_VAULT_KEY_LEN when you change this
enum eStatsCount
{
STATS_COUNT_FOLLOW=0, // count that the /follow feature was used
STATS_COUNT_REDIRECT_AUTO, // count that a user was auto-redirected
STATS_COUNT_REDIRECT_MANUAL, // count that a user was manually redirected
STATS_COUNT_MENU, // count that a user opened the server menu
STATS_COUNT_ENQUEUE, // count that a user enqueued himself for a server
STATS_COUNT_DEQUEUE, // count that a user dequeued himself
STATS_COUNT_REDIRECTED, // count that a user was redirected to this server
STATS_COUNT_RETRY, // count that the /retry feature was used
STATS_COUNT_DROP, // count that a user was dropped because there was no free slot on any server
}

// statistic info constants
enum eStatsInfo
{
STATS_INFO_REDIRECT=0,
STATS_INFO_ENQUEUE,
STATS_INFO_RETRY,
STATS_INFO_DEQUEUE,
}

// redirection type constants for statistics feature
enum eStats
{
STATS_REDIRTYPE_DROP = 0, // a user was dropped from the server because no free slot could be found
STATS_REDIRTYPE_AUTO, // a user was automatically redirected
STATS_REDIRTYPE_MANUAL, // a user manually redirected himself using the menu
STATS_REDIRTYPE_FOLLOW, // a user redirected himself by using /follow
STATS_REDIRTYPE_QUEUED, // a user redirected himself by using the queue (through menu or /retry command)
STATS_REDIRTYPE_ADMIN, // a user was redirected by an admin (redirect_user)
}

// unique task ID's - currently not needed but who knows when they will be
#define TASKID_QUERY 21934807
#define TASKID_QUERY_RECEIVE 21934808
#define TASKID_ANNOUNCE 21934809
#define TASKID_ADVERTISE 21934810

// options - these can be changed by the user, rememeber that you need to recompile for any changes here to take effect
#define SERVERFILE "serverlist.ini" // name of file in /configs containing the server forwards - you can also prepend a subdirectory
#define STATSFILE "xredirect-actions.csv" // name of file in /logs containing the redirection action statistics - you can also prepend a subdirectory
#define QUERY_INTERVAL 10.0 // interval of server querying (in seconds)
#define QUERY_TIMEOUT 1.0 // the maximum time to wait for a server answer (in seconds) before it is considered being down
#define MAX_SERVERFORWARDS 6 // maximum number of server forwards in forwards file
#define MAX_MENUPAGES 10 // maximum number of pages the server selection menu can have
#define DEFAULT_CMDBACKUP 2 // how often to resend the UDP request to servers by default
#define MENU_FORCENOCOLOR false // false = display colored menues if the mod supports it; true = never display colored menues
#define CANCEL_IS_BACK_KEY false // only when categories are enabled: true = "Cancel" key in server menu turns into a "Back" key and goes back to category menu
#define MIN_ADMIN_LEVEL ADMIN_RESERVATION // the minimum level a player must have to be treated as admin (= won't be automatically redirected, can use reserved slots, can join passworded servers with publicpassword=0...)
// can be one of these listed here: http://www.amxmodx.org/funcwiki.php?...=1#const_admin
#define MOD_DETECTION true // enable/disable automatic detection of mod and protocol of other servers - only disable this if you have problems with mod/protocol detection

// A2S_INFO definitions for source according to http://developer.valvesoftware.com/w...urce_servers_2
#define A2S_INFO_SOURCE_REPLY_FORMAT "411ssss21111111s" // there are some extra flags after this but we don't care
#define A2S_INFO_SOURCE_IDX_HEADER 0 // Should be FF FF FF FF
#define A2S_INFO_SOURCE_IDX_TYPE 1 // Should be equal to 'I' (0x49)
#define A2S_INFO_SOURCE_IDX_VERSION 2 // Network version. 0x07 is the current Steam version. Goldsource games will return 48 (0x30), also referred to as protocol version.
#define A2S_INFO_SOURCE_IDX_SERVERNAME 3 // The Source server's name
#define A2S_INFO_SOURCE_IDX_MAP 4 // The current map being played, eg: "de_dust"
#define A2S_INFO_SOURCE_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike"
#define A2S_INFO_SOURCE_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter Strike: Source"
#define A2S_INFO_SOURCE_IDX_APPID 7 // Steam Application ID, see http://developer.valvesoftware.com/w...pplication_IDs
#define A2S_INFO_SOURCE_IDX_NUMPLAYERS 8 // The number of players currently on the server
#define A2S_INFO_SOURCE_IDX_MAXPLAYERS 9 // Maximum allowed players for the server
#define A2S_INFO_SOURCE_IDX_NUMBOTS 10 // Number of bot players currently on the server
#define A2S_INFO_SOURCE_IDX_DEDICATED 11 // 'l' for listen, 'd' for dedicated, 'p' for SourceTV
#define A2S_INFO_SOURCE_IDX_OS 12 // Host operating system. 'l' for Linux, 'w' for Windows
#define A2S_INFO_SOURCE_IDX_PASSWORD 13 // If set to 0x01, a password is required to join this server
#define A2S_INFO_SOURCE_IDX_SECURE 14 // if set to 0x01, this server is VAC secured
#define A2S_INFO_SOURCE_IDX_GAMEVERSION 15 // The version of the game, eg: "1.0.0.22"

// A2S_INFO definitions for goldsource according to http://developer.valvesoftware.com/w...urce_servers_2
#define A2S_INFO_GOLD_REPLY_FORMAT "41sssss111111[ss14411]11"
#define A2S_INFO_GOLD_IDX_HEADER 0 // Should be FF FF FF FF
#define A2S_INFO_GOLD_IDX_TYPE 1 // Should be equal to 'm' (0x6D) - for older servers it's 'C' (0x43)
#define A2S_INFO_GOLD_IDX_IP 2 // Game Server IP address and port
#define A2S_INFO_GOLD_IDX_SERVERNAME 3 // The server's name
#define A2S_INFO_GOLD_IDX_MAP 4 //The current map being played, eg: "de_dust"
#define A2S_INFO_GOLD_IDX_GAMEDIR 5 // The name of the folder containing the game files, eg: "cstrike"
#define A2S_INFO_GOLD_IDX_GAMEDESC 6 // A friendly string name for the game type, eg: "Counter-Strike"
#define A2S_INFO_GOLD_IDX_NUMPLAYERS 7 // The number of players currently on the server
#define A2S_INFO_GOLD_IDX_MAXPLAYERS 8 // Maximum allowed players for the server
#define A2S_INFO_GOLD_IDX_VERSION 9 // Network version. 0x07 is the current Steam version.
#define A2S_INFO_GOLD_IDX_DEDICATED 10 // 'l' for listen, 'd' for dedicated, 'p' for HLTV
#define A2S_INFO_GOLD_IDX_OS 11 // Host operating system. 'l' for Linux, 'w' for Windows
#define A2S_INFO_GOLD_IDX_PASSWORD 12 // If set to 0x01, a password is required to join this server
#define A2S_INFO_GOLD_IDX_ISMOD 13 // If set to 0x01, this byte is followed by ModInfo (that is, all A2S_INFO_GOLD_IDX_MOD_ elements are included)
#define A2S_INFO_GOLD_IDX_SECURE 14 // if set to 0x01, this server is VAC secured - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is set to 0x01 A2S_INFO_GOLD_IDX_MOD_SECURE has to be used instead
#define A2S_INFO_GOLD_IDX_NUMBOTS 15 // Number of bot players currently on the server - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is set to 0x01 A2S_INFO_GOLD_IDX_MOD_NUMBOTS has to be used instead
#define A2S_INFO_GOLD_IDX_MOD_URLINFO 14 // URL containing information about this mod
#define A2S_INFO_GOLD_IDX_MOD_URLDL 15 // URL to download this mod
#define A2S_INFO_GOLD_IDX_MOD_NUL 16 // 0x00
#define A2S_INFO_GOLD_IDX_MOD_MODVERSION 17 // Version of the installed mod
#define A2S_INFO_GOLD_IDX_MOD_MODSIZE 18 // The download size of this mod
#define A2S_INFO_GOLD_IDX_MOD_SVONLY 19 // If 1 this is a server side only mod
#define A2S_INFO_GOLD_IDX_MOD_CIDLL 20 // If 1 this mod has a custom client dll
// the wiki specification is wrong about these two, they are switched:
#define A2S_INFO_GOLD_IDX_MOD_NUMBOTS 21 // Number of bot players currently on the server - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is not set to 0x01 A2S_INFO_GOLD_IDX_NUMBOTS has to be used instead
#define A2S_INFO_GOLD_IDX_MOD_SECURE 22 // if set to 0x01, this server is VAC secured - ATTENTION: if A2S_INFO_GOLD_IDX_ISMOD is not set to 0x01 A2S_INFO_GOLD_IDX_SECURE has to be used instead

// flags
#define SERVERFLAG_NOAUTO 0
#define SERVERFLAG_NOMANUAL 1
#define SERVERFLAG_NODISPLAY 2

// defines for "private" attribute
#define PRIVATE_NONE 0
#define PRIVATE_HIDE 1
#define PRIVATE_FULLHIDE 2


// --------------------------------------- end of defines ---------------------------------------

// -=[ global variables - remember to add an initialization in srvcmd_reload() for all variables you add here! ]=-
/// <summary>Defines whether the plugin was completely initialized.</summary>
new g_bInitialized = false // no srvcmd_reload() initialization needed for this one, as it's not directly related to the server list
/// <summary>Server ID.</summary>
new g_naServerIds[MAX_SERVERFORWARDS] = {-1, ...}
/// <summary>Server name.</summary>
new g_saServerNames[MAX_SERVERFORWARDS][MAX_SERVERNAME_LEN]
/// <summary>Server address.</summary>
new g_saServerAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN]
/// <summary>Server port.</summary>
new g_naServerPorts[MAX_SERVERFORWARDS] = {27015, ...}
/// <summary>Server password.</summary>
new g_saServerPasswords[MAX_SERVERFORWARDS][MAX_PASSWORD_LEN]
/// <summary>Is the server password public?</summary>
new g_naServerPublicPassword[MAX_SERVERFORWARDS] = {0, ...}
/// <summary>Currently active player count.</summary>
new g_naServerActivePlayers[MAX_SERVERFORWARDS] = {-1, ...}
/// <summary>Maximum number of players the server accepts. Does not take reserved slots into account.</summary>
new g_naServerMaxPlayers[MAX_SERVERFORWARDS] = {-1, ...}
/// <summary>Currently running map on server.</summary>
new g_saServerMap[MAX_SERVERFORWARDS][MAX_MAP_LEN]
/// <summary>The socket for the server to handle requests.</summary>
new g_naServerSockets[MAX_SERVERFORWARDS] = {0, ...}
/// <summary>The number how often server queries should be resent to that server.</summary>
new g_naServerCmdBackup[MAX_SERVERFORWARDS] = {DEFAULT_CMDBACKUP, ...}
/// <summary>Flags with several server options. Use the constant defines starting with SERVERFLAG_ to access these.</summary>
new g_naServerFlags[MAX_SERVERFORWARDS] = {0, ...}
/// <summary>Are admin slots reserved on this server?</summary>
new g_naServerReserveSlots[MAX_SERVERFORWARDS] = {0, ...}
/// <summary>Local server address.</summary>
new g_saServerLocalAddresses[MAX_SERVERFORWARDS][MAX_SERVERADDRESS_LEN]
/// <summary>The short name of the mod running on the server, e.g. "cstrike", "ns" or "dod".</summary>
new g_saServerMod[MAX_SERVERFORWARDS][MAX_NAME_LEN]
/// <summary>The network protocol version of the server, e.g. protocol 46 (CS 1.5), 47 (No-Steam Goldsource servers), 48 (Steam Source and Goldsource servers).</summary>
new g_naServerProtocol[MAX_SERVERFORWARDS]
/// <summary>At which real index does the menu page start? It is shifted because of hidden servers or servers filtered out by category.</summary>
new g_naMenuPageStart[MAX_PLAYERS][MAX_MENUPAGES]
/// <summary>The category the server belongs to.</summary>
new g_naServerCategory[MAX_SERVERFORWARDS] = {-1, ...}
/// <summary>The "private" setting for this server. Contains one of the PRIVATE_ constants.</summary>
new g_naServerPrivate[MAX_SERVERFORWARDS]
/// <summary>Is the server responding?</summary>
new bool:g_baServerResponding[MAX_SERVERFORWARDS] = {false, ...}
/// <summary>Number of servers found in server list file.</summary>
new g_nServerCount = 0
/// <summary>The last server someone has been redirected to. Needed for <seealso name="cmd_follow_player"/>.</summary>
new g_nLastRedirectServer = -1
/// <summary>The nick of the person who has been redirected at last. Needed for <seealso name="cmd_follow_player"/>.</summary>
new g_sLastRedirectName[MAX_NAME_LEN] = ""
/// <summary>The index of the current server. This is neccessary for the server to check its own data.</summary>
new g_nOwnServer = -1
/// <summary>The ID number of the current server as it was assigned in the serverlist.</summary>
new g_nOwnServerId = -1
/// <summary>The page number for each user which he had open last time, needed for switching back from sub menu to server menu.</summary>
new g_naLastMenuPages[MAX_PLAYERS] = {1, ...}
/// <summary>The category ID for each user which he had selected last time, needed for switching back from sub menu to server menu.</summary>
new g_naLastCategory[MAX_PLAYERS] = {-1, ...}
/// <summary>Hidden servers cause a difference between shown and real server numbers - this array associates the real server index with a given key - different for each user as some users can see servers that others don't.</summary>
new g_naServerSelections[MAX_PLAYERS][8]
/// <summary>This is the cycle variable that holds which server to begin from in <seealso name="announce_servers"/>.</summary>
new g_nNextAnnounceServer = 0
/// <summary>The last server the player came from through redirection. Needed in case he wants to send himself back with /retry.</summary>
new g_nLastServer[MAX_PLAYERS] = {-1, ...}
/// <summary>The last server the player has accessed the sub menu of. Needed when the player refreshes the sub menu.</summary>
new g_nLastSelected[MAX_PLAYERS] = {-1, ...}
/// <summary>This array contains the retry queue consisting of a player ID and a server number for each record.</summary>
new g_nRetryQueue[MAX_PLAYERS*MAX_SERVERFORWARDS][2]
/// <summary>Counter for global number of queue entries.</summary>
new g_nRetryCount = 0
/// <summary>Controls whether certain debug messages are shown. It is automatically set to true when the plugin has debug mode set in plugins.ini.</summary>
new bool:g_bDebug = false
/// <summary>List of categories found in SERVERFILE.</summary>
new g_saCategories[MAX_CATEGORIES][MAX_VALUE_LEN]
/// <summary>Number of categories found in SERVERFILE.</summary>
new g_nCategoryCount = 0

/// <summary>Are server IDs used in SERVERFILE? Will be set to true on the first found ID if file is used.</summary>
#if defined SQL
new bool:g_bUseIds = true
#else
new bool:g_bUseIds = false
#endif

/// <summary>The name of the mod this server is running.</summary>
new g_sMod[MAX_NAME_LEN] = ""


#if defined VAULT
new g_nVaultId = -1
#endif

// -=[ global SQL variables ]=-
#if defined SQL
new sSqlError[MAX_SQL_ERROR_LEN]
new nSqlError
new Handle:hSqlInfo
new Handle:hSql
new SQL_TABLE_STATISTICS[MAX_SQL_TABLE_LEN]
#endif

// -=[ global CVAR's ]=-
new cvar_active
new cvar_auto
new cvar_manual
new cvar_follow
new cvar_external_address
new cvar_check_method
new cvar_advertise
new cvar_announce
new cvar_announce_mode
new cvar_announce_alivepos_x
new cvar_announce_alivepos_y
new cvar_announce_deadpos_x
new cvar_announce_deadpos_y
new cvar_show
new cvar_adminslots
new cvar_maxadmins
new cvar_retry
new cvar_hidedown
new cvar_localslots
new cvar_countbots
new cvar_categories

// --------------------------------------- end of global vars ---------------------------------------

#if AMXX_VERSION_NUM >= 180

/// <summary>Initialize CVARs, load servers, register commands, register menues, register dictionaries, start tasks...</summary>
public plugin_init() {
register_plugin(PLUGIN_NAME, PLUGIN_VERSION, PLUGIN_AUTHOR)

register_cvar("redirect_version", PLUGIN_VERSION, FCVAR_SERVER|FCVAR_SPONLY)
set_cvar_string("redirect_version", PLUGIN_VERSION)

// please see the description at top if you want to know what these CVAR's do
cvar_active = register_cvar("redirect_active", "0")
cvar_auto = register_cvar("redirect_auto", "0")
cvar_manual = register_cvar("redirect_manual", "0")
cvar_follow = register_cvar("redirect_follow", "0")
cvar_external_address = register_cvar("redirect_external_address", "")
cvar_check_method = register_cvar("redirect_check_method", "0")
cvar_advertise = register_cvar("redirect_advertise", "150")
cvar_announce = register_cvar("redirect_announce", "120")
cvar_announce_mode = register_cvar("redirect_announce_mode", "3")
cvar_announce_alivepos_x = register_cvar("redirect_announce_alivepos_x", "-1.0")
cvar_announce_alivepos_y = register_cvar("redirect_announce_alivepos_y", "0.01")
cvar_announce_deadpos_x = register_cvar("redirect_announce_deadpos_x", "-1.0")
cvar_announce_deadpos_y = register_cvar("redirect_announce_deadpos_y", "0.35")
cvar_show = register_cvar("redirect_show", "1")
cvar_adminslots = register_cvar("redirect_adminslots", "0")
cvar_maxadmins = register_cvar("redirect_maxadmins", "0")
cvar_retry = register_cvar("redirect_retry", "0")
cvar_hidedown = register_cvar("redirect_hidedown", "0")
cvar_localslots = register_cvar("redirect_localslots", "0")
cvar_countbots = register_cvar("redirect_countbots", "1")
cvar_categories = register_cvar("redirect_categories", "0")

register_dictionary("xredirect.txt")
register_dictionary("common.txt")
#if defined SQL
register_dictionary("admin.txt")
#endif // SQL

// check whether we are in debug mode or not
new saDummy[2]
new saStatus[6]
get_plugin(-1, saDummy, 0, saDummy, 0, saDummy, 0, saDummy, 0, saStatus, 5)
g_bDebug = bool:equal(saStatus, "debug")

// load servers from the SERVERLIST or SQL database
#if defined SQL
sql_connect()
load_servers_sql()
#else
load_servers_file()
#endif // SQL

if (g_nServerCount < 2)
log_amx("%L", LANG_SERVER, "MSG_ERROR_NOT_ENOUGH_SERVERS")

register_menu("Category Menu", 1023, "category_menu_select")
register_menu("Redirect Menu", 1023, "server_menu_select")
register_menu("Detail Menu", 1023, "sub_menu_select")

register_srvcmd("redirect_reload", "srvcmd_reload", -1, "- reload redirect servers")
#if defined VAULT
register_srvcmd("redirect_resetvault", "srvcmd_resetvault", -1, "- reset all vault statistics to 0")
#endif // VAULT
register_clcmd("say /server", "cmd_show_server_menu", 0, "- show server redirection menu")
register_clcmd("say_team /server", "cmd_show_server_menu", 0, "- show server redirection menu")
register_clcmd("pickserver", "cmd_pickserver", 0, "show server redirection menu")
register_clcmd("say /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server")
register_clcmd("say_team /follow", "cmd_follow_player", 0, "- follow the last redirected player to his server")
register_clcmd("say /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot")
register_clcmd("say_team /retry", "cmd_retry", 0, "- redirect back as soon as the foregoing server has a free slot")
register_clcmd("say /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server")
register_clcmd("say_team /stopretry", "cmd_stopretry", 0, "- stop retrying the foregoing server")
register_clcmd("redirect_announce_now", "announce_servers", ADMIN_KICK , "- announce server list immediately")
register_clcmd("redirect_user", "cmd_redirect_user", ADMIN_KICK , "<playername|playerid> [servernum] - redirect a player [to a given server]")
register_clcmd("redirect_queue", "cmd_redirect_queue", ADMIN_KICK , "- show the current redirect queue")

#if defined VAULT
register_srvcmd("redirect_stats", "srvcmd_stats", -1, "- show redirection statistics")
register_clcmd("redirect_stats", "cmd_stats", ADMIN_KICK , "- show redirection statistics")
#endif // VAULT

set_task(QUERY_INTERVAL, "query_servers", TASKID_QUERY, "", 0, "b")

#if defined VAULT
g_nVaultId = nvault_open(VAULT_NAME)
#endif //VAULT
#if defined SQL
formatex(SQL_TABLE_STATISTICS, MAX_SQL_TABLE_LEN-1, "%s%s", SQL_PREFIX, SQL_TABLENAME_STATISTICS)
#else
#if defined STATISTICS
new sBaseDir[MAX_FILE_LEN], sStatsFile[MAX_FILE_LEN]
get_basedir(sBaseDir, MAX_FILE_LEN-1)
format(sStatsFile, MAX_FILE_LEN-1, "%s/logs/%s", sBaseDir, STATSFILE)
// log a CSV header to the file if it doesn't exist yet
if (!file_exists(sStatsFile))
log_to_file(STATSFILE, ",User Name,User IP,User Auth-ID,Action,Server-ID,Target-Server-ID")
#endif // STATISTICS
#endif // SQL

// get and save the current mod
get_modname(g_sMod, MAX_NAME_LEN -1)
}

/// <summary>More initializations that have to be done here, because when <seealso name="plugin_init"/> is called CVARs are not yet set. They are in plugin_cfg(), but not for the first start of the game server with ./hlds_run so we use this extra function called once when the first player connects.</summary>
public plugin_postinit()
{
g_bInitialized = true
new sFullAddress[MAX_SERVERADDRESS_LEN]
new sTmpServerIP[MAX_IP_LEN]
new sTmpServerPort[MAX_PORT_LEN]
new sTmpServerAddress[MAX_IP_LEN + MAX_PORT_LEN], sTmpServerAddress2[MAX_IP_LEN + MAX_PORT_LEN]
new sTmpOwnAddress[MAX_SERVERADDRESS_LEN]

get_cvar_string("net_address", sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1)
get_cvar_string("ip", sTmpServerIP, MAX_IP_LEN - 1)
get_cvar_string("port", sTmpServerPort, MAX_PORT_LEN - 1)
formatex(sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1, "%s:%s", sTmpServerIP, sTmpServerPort)
get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1)

// determine the own server
new nServerCount = 0
while (nServerCount < g_nServerCount)
{
formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount])
if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerAddress) || equal(sFullAddress, sTmpServerAddress2))
{
g_nOwnServer = nServerCount
if (g_bUseIds)
g_nOwnServerId = g_naServerIds[nServerCount]
else
g_nOwnServerId = nServerCount
}
if (g_bUseIds && (g_naServerIds[nServerCount] == -1))
log_amx("%L", LANG_SERVER, "MSG_ID_MISSING", g_saServerNames[nServerCount])
nServerCount++
}

if (g_nOwnServer == -1) // we have not been able to detect the own server - inform the user about this
{
log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR")
return PLUGIN_CONTINUE
}

// we need to know our own server index to be able to load attributes from SQL - so now we can do that
#if defined SQL
load_attributes_sql()
#endif // SQL

if (get_pcvar_float(cvar_announce) > 0.0)
if (!task_exists(TASKID_ANNOUNCE))
set_task(get_pcvar_float(cvar_announce), "announce_servers", TASKID_ANNOUNCE, "", 0, "b")

if (get_pcvar_float(cvar_advertise) > 0.0)
if (!task_exists(TASKID_ADVERTISE))
set_task(get_pcvar_float(cvar_advertise), "advertise_server_command", TASKID_ADVERTISE, "", 0, "b")

if ((get_pcvar_num(cvar_categories) >= 1) && (g_nCategoryCount == 0))
log_amx("%L", LANG_SERVER, "MSG_WARN_NO_CATEGORIES", "redirect_categories", SERVERFILE)

return PLUGIN_CONTINUE
}

/// <summary>Cleanup. Close open sockets, vaults, database handles...</summary>
public plugin_end()
{
// close all open sockets
for (new nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++)
{
if (g_naServerSockets[nCounter] > 0)
{
socket_close(g_naServerSockets[nCounter])
g_naServerSockets[nCounter] = 0
}
}

#if defined VAULT
nvault_close(g_nVaultId)
#endif //VAULT

#if defined SQL
sql_disconnect()
#endif // SQL

return PLUGIN_CONTINUE
}

/// <summary>This is used to register the native redirect() function.</summary>
public plugin_natives()
{
register_native("redirect", "native_redirect", 1)
}

/// <summary>Tells AMXX which modules are required.</summary>
/// <remarks>The code to require sockets can be safely removed from the code when only redirect_check_method 0 will be used.</remarks>
public plugin_modules()
{
require_module("sockets")
#if defined VAULT
require_module("nvault")
#endif // VAULT
}

/// <summary>Gets the server index based on a server ID.</summary>
/// <param name="nServerId">The ID of the server.</param>
public get_server_index(nServerId)
{
new nServer = 0
while (nServer < g_nServerCount)
{
if (g_naServerIds[nServer] == nServerId)
return nServer
nServer++
}
return -1
}

/// <summary>Set an attribute for a server.</summary>
/// <param name="nServer">The internal server index.</param>
/// <param name="sAttribute">The attribute name.</param>
/// <param name="sValue">The attribute value.</param>
public set_server_attribute(nServer, sAttribute[MAX_ATTRIB_LEN], sValue[MAX_VALUE_LEN])
{
if (nServer < 0)
return
strtoupper(sAttribute)
if (strcmp(sAttribute, "ADDRESS") == 0)
copy(g_saServerAddresses[nServer], MAX_SERVERADDRESS_LEN - 1, sValue)
else
if (strcmp(sAttribute, "LOCALADDRESS") == 0)
copy(g_saServerLocalAddresses[nServer], MAX_SERVERADDRESS_LEN - 1, sValue)
else
if (strcmp(sAttribute, "PASSWORD") == 0)
copy(g_saServerPasswords[nServer], MAX_PASSWORD_LEN - 1, sValue)
else
if (strcmp(sAttribute, "PUBLICPASSWORD") == 0)
{
if (is_str_num(sValue))
if (str_to_num(sValue) == 1)
g_naServerPublicPassword[nServer] = 1
}
else
if (strcmp(sAttribute, "PORT") == 0)
{
if (is_str_num(sValue))
g_naServerPorts[nServer] = str_to_num(sValue)
else
g_naServerPorts[nServer] = 27015
if ((g_naServerPorts[nServer] > 65536) || (g_naServerPorts[nServer] < 1024))
g_naServerPorts[nServer] = 27015
}
else
if (strcmp(sAttribute, "CMDBACKUP") == 0)
{
if (is_str_num(sValue))
g_naServerCmdBackup[nServer] = str_to_num(sValue)
else
g_naServerCmdBackup[nServer] = DEFAULT_CMDBACKUP
// protect from insane values
if ((g_naServerCmdBackup[nServer] > 100) || (g_naServerCmdBackup[nServer] < 0))
g_naServerCmdBackup[nServer] = DEFAULT_CMDBACKUP
}
else
if (strcmp(sAttribute, "NOAUTO") == 0)
{
if (is_str_num(sValue))
if (str_to_num(sValue) == 1)
g_naServerFlags[nServer] = g_naServerFlags[nServer] | (1<<SERVERFLAG_NOAUTO)
}
else
if (strcmp(sAttribute, "NOMANUAL") == 0)
{
if (is_str_num(sValue))
if (str_to_num(sValue) == 1)
g_naServerFlags[nServer] = g_naServerFlags[nServer] | (1<<SERVERFLAG_NOMANUAL)

}
else
if (strcmp(sAttribute, "NODISPLAY") == 0)
{
if (is_str_num(sValue))
if (str_to_num(sValue) == 1)
g_naServerFlags[nServer] = g_naServerFlags[nServer] | (1<<SERVERFLAG_NODISPLAY)
}
else
if (strcmp(sAttribute, "ADMINSLOTS") == 0)
{
if (is_str_num(sValue))
g_naServerReserveSlots[nServer] = str_to_num(sValue)
else
g_naServerReserveSlots[nServer] = 0
if ((g_naServerReserveSlots[nServer] > MAX_PLAYERS) || (g_naServerReserveSlots[nServer] < 0))
g_naServerReserveSlots[nServer] = 0
}
else
if (strcmp(sAttribute, "CATEGORY") == 0)
{
// adds a category to the internal category list and stores the index of it for the current server
g_naServerCategory[nServer] = get_category_index(sValue)
}
else
if (strcmp(sAttribute, "ID") == 0)
{
g_bUseIds = true
g_naServerIds[nServer] = str_to_num(sValue)
}
else
if (strcmp(sAttribute, "PRIVATE") == 0)
{
strtoupper(sValue)
if (equal(sValue, "HIDE"))
g_naServerPrivate[nServer] = PRIVATE_HIDE
else if (equal(sValue, "FULLHIDE"))
g_naServerPrivate[nServer] = PRIVATE_FULLHIDE
else
g_naServerPrivate[nServer] = PRIVATE_NONE
}
}


#if defined SQL
/// <summary>Load server attributes from SQL database.</summary>
public load_attributes_sql()
{
new sSqlAttribTable[MAX_SQL_TABLE_LEN]
formatex(sSqlAttribTable, MAX_SQL_TABLE_LEN-1, "%s%s", SQL_PREFIX, SQL_TABLENAME_ATTRIBUTES)

new Handle:hQuery = SQL_PrepareQuery(hSql, "SELECT `target_id`, `attrib`, `value` FROM `%s` WHERE `source_id` = '%d' ORDER BY `target_id`", sSqlAttribTable, g_nOwnServerId)
if (!SQL_Execute(hQuery))
{
SQL_QueryError(hQuery, sSqlError, MAX_SQL_ERROR_LEN-1)
log_amx("%L", LANG_SERVER, "MSG_ERROR_SQL_TABLE", sSqlAttribTable)
}
else
{
new sAttribute[MAX_ATTRIB_LEN]
new sValue[MAX_VALUE_LEN]
new nTargetId
new cTargetId = SQL_FieldNameToNum(hQuery, "target_id")
new cAttribute = SQL_FieldNameToNum(hQuery, "attrib")
new cValue = SQL_FieldNameToNum(hQuery, "value")
while (SQL_MoreResults(hQuery))
{
SQL_ReadResult(hQuery, cTargetId, sValue, MAX_VALUE_LEN-1)
nTargetId = str_to_num(sValue)
SQL_ReadResult(hQuery, cAttribute, sAttribute, MAX_VALUE_LEN-1)
SQL_ReadResult(hQuery, cValue, sValue, MAX_VALUE_LEN-1)
set_server_attribute(get_server_index(nTarget Id), sAttribute, sValue)
SQL_NextRow(hQuery)
}
}
SQL_FreeHandle(hQuery)
}

/// <summary>Load servers from SQL database.</summary>
public load_servers_sql()
{
new sPort[MAX_PORT_LEN]
new sSqlServerTable[MAX_SQL_TABLE_LEN]
formatex(sSqlServerTable, MAX_SQL_TABLE_LEN-1, "%s%s", SQL_PREFIX, SQL_TABLENAME_SERVERS)

new Handle:hQuery = SQL_PrepareQuery(hSql, "SELECT * FROM `%s`", sSqlServerTable)
if (!SQL_Execute(hQuery))
{
SQL_QueryError(hQuery, sSqlError, MAX_SQL_ERROR_LEN-1)
log_amx("%L", LANG_SERVER, "MSG_ERROR_SQL_TABLE", sSqlServerTable)
}
else
{
new cId = SQL_FieldNameToNum(hQuery, "id")
new cName = SQL_FieldNameToNum(hQuery, "name")
new cAddress = SQL_FieldNameToNum(hQuery, "address")
new cLocalAddress = SQL_FieldNameToNum(hQuery, "localaddress")
new cPassword = SQL_FieldNameToNum(hQuery, "password")
new cPublicPassword = SQL_FieldNameToNum(hQuery, "publicpassword")
new cPort = SQL_FieldNameToNum(hQuery, "port")
new cCmdBackup = SQL_FieldNameToNum(hQuery, "cmdbackup")
new cNoAuto = SQL_FieldNameToNum(hQuery, "noauto")
new cNoManual = SQL_FieldNameToNum(hQuery, "nomanual")
new cNoDisplay = SQL_FieldNameToNum(hQuery, "nodisplay")
new cAdminSlots = SQL_FieldNameToNum(hQuery, "adminslots")
new cCategory = SQL_FieldNameToNum(hQuery, "category")
new cPrivate = SQL_FieldNameToNum(hQuery, "private")

new sValue[MAX_VALUE_LEN]
while ((SQL_MoreResults(hQuery)) && (g_nServerCount < MAX_SERVERFORWARDS))
{
SQL_ReadResult(hQuery, cId, sValue, MAX_VALUE_LEN-1)
g_naServerIds[g_nServerCount] = str_to_num(sValue)
SQL_ReadResult(hQuery, cName, g_saServerNames[g_nServerCount], MAX_SERVERNAME_LEN-1)
SQL_ReadResult(hQuery, cAddress, g_saServerAddresses[g_nServerCount], MAX_SERVERADDRESS_LEN-1)
SQL_ReadResult(hQuery, cLocalAddress, g_saServerLocalAddresses[g_nServerCount], MAX_SERVERADDRESS_LEN-1)
SQL_ReadResult(hQuery, cPassword, g_saServerPasswords[g_nServerCount], MAX_PASSWORD_LEN-1)
SQL_ReadResult(hQuery, cPublicPassword, sValue, MAX_VALUE_LEN-1)
g_naServerPublicPassword[g_nServerCount] = str_to_num(sValue)
SQL_ReadResult(hQuery, cPort, sValue, MAX_VALUE_LEN-1)
g_naServerPorts[g_nServerCount] = str_to_num(sValue)
SQL_ReadResult(hQuery, cCmdBackup, sValue, MAX_VALUE_LEN-1)
g_naServerCmdBackup[g_nServerCount] = str_to_num(sValue)
SQL_ReadResult(hQuery, cNoAuto, sValue, MAX_VALUE_LEN-1)
if (is_str_num(sValue))
if (str_to_num(sValue) == 1)
g_naServerFlags[g_nServerCount] = g_naServerFlags[g_nServerCount] | (1<<SERVERFLAG_NOAUTO)
SQL_ReadResult(hQuery, cNoManual, sValue, MAX_VALUE_LEN-1)
if (is_str_num(sValue))
if (str_to_num(sValue) == 1)
g_naServerFlags[g_nServerCount] = g_naServerFlags[g_nServerCount] | (1<<SERVERFLAG_NOMANUAL)
SQL_ReadResult(hQuery, cNoDisplay, sValue, MAX_VALUE_LEN-1)
if (is_str_num(sValue))
if (str_to_num(sValue) == 1)
g_naServerFlags[g_nServerCount] = g_naServerFlags[g_nServerCount] | (1<<SERVERFLAG_NODISPLAY)
SQL_ReadResult(hQuery, cAdminSlots, sValue, MAX_VALUE_LEN-1)
g_naServerReserveSlots[g_nServerCount] = str_to_num(sValue)
SQL_ReadResult(hQuery, cCategory, sValue, MAX_VALUE_LEN-1)
g_naServerCategory[g_nServerCount] = get_category_index(sValue)
SQL_ReadResult(hQuery, cPrivate, sValue, MAX_VALUE_LEN-1)
strtoupper(sValue)
if (equal(sValue, "HIDE"))
g_naServerPrivate[g_nServerCount] = PRIVATE_HIDE
else if (equal(sValue, "FULLHIDE"))
g_naServerPrivate[g_nServerCount] = PRIVATE_FULLHIDE
else
g_naServerPrivate[g_nServerCount] = PRIVATE_NONE

// at least a valid server address is required, otherwise ignore the server
if (!equal(g_saServerAddresses[g_nServerCount], ""))
{
num_to_str(g_naServerPorts[g_nServerCount], sPort, MAX_PORT_LEN - 1)
log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[g_nServerCount], g_saServerAddresses[g_nServerCount], sPort)
g_nServerCount++
}
SQL_NextRow(hQuery)
}
}
SQL_FreeHandle(hQuery)
}
#endif // SQL

/// <summary>Load servers from server list file.</summary>
public load_servers_file()
{
new sConfigDir[MAX_FILE_LEN], sServerFile[MAX_FILE_LEN]

get_configsdir(sConfigDir, MAX_FILE_LEN-1)
format(sServerFile, MAX_FILE_LEN-1, "%s/%s", sConfigDir, SERVERFILE)

if (!file_exists(sServerFile))
{
log_amx("%L", LANG_SERVER, "MSG_ERROR_NO_FILE", sServerFile)
return
}

new nFilePos = 0
new sFileLine[MAX_SERVERLINE_LEN], sFileLineTrim[MAX_SERVERLINE_LEN]
new nReadLen
new sPort[MAX_PORT_LEN]

new sAttribute[MAX_ATTRIB_LEN]
new sValue[MAX_VALUE_LEN]

new nCurrentServer = -1

while (read_file(sServerFile, nFilePos++, sFileLine, MAX_SERVERLINE_LEN-1, nReadLen))
{
sFileLineTrim = sFileLine
trim(sFileLineTrim)
if ((sFileLine[0] == ';') || (strlen(sFileLineTrim) == 0)) continue // skip comments and empty lines

if ((sFileLine[0] == '[') && (sFileLine[strlen(sFileLine) - 1] == ']')) // a section starts
{
nCurrentServer++
if (nCurrentServer > 0)
{
// check whether the previous server was valid
if ((g_naServerPorts[nCurrentServer - 1] != 0) && (strcmp(g_saServerAddresses[nCurrentServer - 1], "") != 0))
{
g_nServerCount++
num_to_str(g_naServerPorts[nCurrentServer - 1], sPort, MAX_PORT_LEN - 1)
log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer - 1], g_saServerAddresses[nCurrentServer - 1], sPort)
}
else
nCurrentServer--
}

if (nCurrentServer >= MAX_SERVERFORWARDS)
break;

copy(g_saServerNames[nCurrentServer], strlen(sFileLine) - 2, sFileLine[1])

continue
}

if (nCurrentServer >= 0) // do we already have found a section?
{
strtok(sFileLine, sAttribute, MAX_ATTRIB_LEN - 1, sValue, MAX_VALUE_LEN - 1, '=', 1)
set_server_attribute(nCurrentServer, sAttribute, sValue)
}
}

if ((nCurrentServer >= MAX_SERVERFORWARDS) || (nCurrentServer == -1))
return

// check whether the previous server was valid
if ((g_naServerPorts[nCurrentServer] != 0) && (strcmp(g_saServerAddresses[nCurrentServer], "") != 0))
{
g_nServerCount++
num_to_str(g_naServerPorts[nCurrentServer], sPort, MAX_PORT_LEN - 1)
log_amx("%L", LANG_SERVER, "MSG_LOADED_SERVER", g_saServerNames[nCurrentServer], g_saServerAddresses[nCurrentServer], sPort)
}
}

/// <summary>Gets the index of a category in the internal list. It is added to the list if it doesn't exist there already.</summary>
/// <param name="sCategory">The string containing the category name. Not case-sensitive when it comes to searching in the list.</param>
/// <returns>The zero-based array index of the category in the internal list or -1 if called with an empty category name.</returns>
public get_category_index(sCategory[MAX_VALUE_LEN])
{
if (equal(sCategory, ""))
return -1

for (new nCategoryIndex = 0; nCategoryIndex < g_nCategoryCount; nCategoryIndex++)
{
if (equali(g_saCategories[nCategoryIndex], sCategory))
return nCategoryIndex
}

// don't assign a category if we already reached the maximum number
if (g_nCategoryCount >= MAX_CATEGORIES - (get_pcvar_num(cvar_categories) - 1)) // if cvar_categories is set to 2 it's one category less we can use
return -1

if (g_bDebug)
log_amx("Found new category '%s'", sCategory)
g_saCategories[g_nCategoryCount] = sCategory
g_nCategoryCount++
return g_nCategoryCount - 1
}

/// <summary>Checks whether the IP in <paramref name="sCheckAddress"/> is a local address according to RFC 1918.</summary>
/// <summary>10.0.0.0 - 10.255.255.255 - single class A</summary>
/// <summary>172.16.0.0 - 172.31.255.255 - 16 contiguous class Bs</summary>
/// <summary>192.168.0.0 - 192.168.255.255 - 256 contiguous class Cs</summary>
/// <summary>169.254.0.0 - 169.254.255.255 - zeroconf</summary>
/// <param name="sCheckAddress">The IP address to check passed as a string.</param>
/// <returns>true if <paramref name="sCheckAddress"/> is a local IP address, false if not.</returns>
public bool:is_local_address(sCheckAddress[MAX_IP_LEN])
{
new sIPPart1[4]
new sIPPart2[4]
new nIPPart[4]
new sCompareIP[MAX_IP_LEN]
sCompareIP = sCheckAddress
strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.')
nIPPart[0] = str_to_num(sIPPart1);
strtok(sCheckAddress, sIPPart1, 3, sCheckAddress, MAX_IP_LEN - 1, '.')
nIPPart[1] = str_to_num(sIPPart1);
strtok(sCheckAddress, sIPPart1, 3, sIPPart2, 3, '.')
nIPPart[2] = str_to_num(sIPPart1);
nIPPart[3] = str_to_num(sIPPart2);
return ((nIPPart[0] == 10) || ((nIPPart[0] == 192) && (nIPPart[1] == 16) || ((nIPPart[0] == 172) && (nIPPart[1] > 15) && (nIPPart[1] < 32)) || ((nIPPart[0] == 169) && (nIPPart[1] == 254)))
}

/// <summary>Checks whether the given server differs from the current server with mod or protocol.</summary>
/// <param name="nServer">The server number which shall be checked whether it's having the same mod and protocol as the current server.</param>
/// <returns>true if the server's mod and protocol match that of the current server, otherwise false.</returns>
public bool:compare_mod(nServer)
{
#if defined MOD_DETECTION
// compensate for errors in protocol detection
if ((g_naServerProtocol[g_nOwnServer] <= 0) || (g_naServerProtocol[nServer] <= 0))
return true
// compensate for errors in mod detection
if ((equal(g_sMod, "")) || (equal(g_saServerMod[nServer], "")))
return true
// compare protocol and mod
return ((strcmp(g_sMod, g_saServerMod[nServer], 1) == 0) && (g_naServerProtocol[g_nOwnServer] == g_naServerProtocol[nServer]))
#elseif
return true
#endif
}

/// <summary>Checks whether the player with ID <paramref name="nPlayerID"/> can be redirected to the server with server number <paramref name="nServerNum"/>.</summary>
/// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection target.</param>
/// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param>
/// <param name="nMode">Defines the redirection mode - 1 = automatic, 2 = manual.</param>
/// <param name="bIgnoreAdmin">Set to true, when the plugin should not tread admins as special, otherwise false.</param>
/// <returns>0 if redirection is possible, otherwise an error code: 1 = current server, 2 = no permission(passworded), 3 = manual redirection disabled. 4 = server full, 5 = server down, 6 = automatic redirection disabled.</returns>
public can_redirect_player(nServer, nPlayerID, nMode, bIgnoreAdmin)
{
if (nServer == -1)
return 0
get_modname(g_sMod, MAX_NAME_LEN - 1)

new nCheckMethod = get_pcvar_num(cvar_check_method)

new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0))

if (g_bDebug)
log_amx("Mod comparison: local protocol/mod: %i/^"%s^", remote protocol/mod: (server %d): %i/^"%s^"", g_naServerProtocol[g_nOwnServer], g_sMod, nServer, g_naServerProtocol[nServer], g_saServerMod[nServer])

if (nServer == g_nOwnServer)
return 1
else if ((nCheckMethod == 2) && (!compare_mod(nServer)))
return 7
else if (access(nPlayerID, MIN_ADMIN_LEVEL) && (!bIgnoreAdmin)) // even for admins it doesn't make sense to redirect to the current server or to servers with a different mod/protocol so check admin rights from here
return 0
else if ((nCheckMethod > 0) && (!g_baServerResponding[nServer]))
return 5
else if (!bCanRedirectByPassword)
return 2
else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL)) && (nMode == 2))
return 3
else if ((g_naServerFlags[nServer] & (1<<SERVERFLAG_NOAUTO)) && (nMode == 1))
return 6
else if ((nCheckMethod == 2) && (g_naServerReserveSlots[nServer] >= (g_naServerMaxPlayers[nServer] - g_naServerActivePlayers[nServer])) )
return 4

return 0
}


/// <summary>Checks whether the player with ID <paramref name="nPlayerID"/> can be queued to redirect to the server with server number <paramref name="nServerNum"/>.</summary>
/// <param name="nServer">The server number which shall be checked whether it is currently a valid redirection queue target.</param>
/// <param name="nPlayerID">The internal player ID which shall be checked for access to <paramref name="nServerNum"/>.</param>
/// <returns>true if queueing is possible, otherwise false.</returns>
public bool:can_queue_player(nServer, nPlayerID)
{
if (nServer == -1)
return false

new bIsAdmin = access(nPlayerID, MIN_ADMIN_LEVEL)

if ((get_pcvar_num(cvar_retry) == 0) && (!bIsAdmin)) // admin always can enqueue themselves, even when this feature is disabled
return false

new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServer], "") && (g_naServerPublicPassword[nServer] == 0))
if (nServer == g_nOwnServer)
return false
if (bIsAdmin)
return true
if ((!bCanRedirectByPassword) || (g_naServerFlags[nServer] & (1<<SERVERFLAG_NOMANUAL)))
return false
if ((get_pcvar_num(cvar_check_method) == 2) && (!compare_mod(nServer)))
return false

return true
}

/// <summary>Checks whether the player with ID <paramref name="id"/> is already in redirection queue for server with number <paramref name="nServer"/>.</summary>
/// <param name="nServer">The server number which shall be checked whether player with <paramref name="id"/> is in its queue.</param>
/// <param name="id">The internal player ID which shall be checked whether it is queued for server <paramref name="nServer"/>.</param>
/// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks>
/// <returns>true if player is in queue, false if not.</returns>
/// <seealso name="queue_add"/>
/// <seealso name="queue_remove"/>
public bool:is_queued(id, nServer)
{
new nCount = 0
while (nCount < g_nRetryCount)
{
if ((g_nRetryQueue[nCount][0] == id) && (g_nRetryQueue[nCount][1] == nServer))
return true
nCount++
}
return false
}

/// <summary>Adds the player with ID <paramref name="id"/> to the redirection queue for server with number <paramref name="nServer"/>.</summary>
/// <param name="nServer">The server number to add the player with <paramref name="id"/> to its queue.</param>
/// <param name="id">The internal player ID which shall be added to the queue for server <paramref name="nServer"/>.</param>
/// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks>
/// <seealso name="is_queued"/>
/// <seealso name="queue_remove"/>
public queue_add(id, nServer)
{
if (get_pcvar_num(cvar_retry) > 0)
{
// first check whether the server-player-combination is not already in queue
new nCount = 0
new nServerQueue = 0
while (nCount < g_nRetryCount)
{
// count how many people are in the queue for the target server
if (g_nRetryQueue[nCount][1] == nServer)
{
nServerQueue++
// no need to continue when he already is in the queue
if (g_nRetryQueue[nCount][0] == id)
return
}
nCount++
}

new sUserNick[MAX_NAME_LEN]
get_user_name(id, sUserNick, MAX_NAME_LEN - 1)

if (get_pcvar_num(cvar_show) == 1)
{
new naPlayers[MAX_PLAYERS]
new nPlayerNum, nPlayerCount, nCurrentPlayer
get_players(naPlayers, nPlayerNum, "c")
for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
{
nCurrentPlayer = naPlayers[nPlayerCount]
if (nCurrentPlayer != id) // he has his own message
client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_QUEUE_ANNOUNCE", sUserNick, g_saServerNames[nServer])
}
}

#if defined STATISTICS
stats_redirect(STATS_INFO_ENQUEUE, id, -1, nServer)
#endif // STATISTICS

if (g_bDebug)
log_amx("added player %i to queue for server %i in slot %i", id, nServer, g_nRetryCount)


g_nRetryQueue[g_nRetryCount][0] = id
g_nRetryQueue[g_nRetryCount][1] = nServer
g_nRetryCount++

client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_ADD", ++nServerQueue, g_saServerNames[nServer])
}
else
client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_DEACTIVATED")
}

/// <summary>Removes the player with ID <paramref name="id"/> from the redirection queue for server with number <paramref name="nServer"/>.</summary>
/// <param name="nServer">The server number to remove the player with <paramref name="id"/> from its queue.</param>
/// <param name="id">The internal player ID which shall be removed from the queue for server <paramref name="nServer"/>.</param>
/// <remarks>A player can be in more than one queue but not twice in the queue for one server, <seealso name="queue_add"/> prevents double adding.</remarks>
/// <seealso name="is_queued"/>
/// <seealso name="add_remove"/>
public queue_remove(id, nServer)
{
new nCount = 0
while (nCount < g_nRetryCount)
{
if ((g_nRetryQueue[nCount][0] == id) && ((nServer == -1) || (g_nRetryQueue[nCount][1] == nServer)))
{ // ok, remove from queue and let all others go one place up

// in case it's the last entry in queue where the following loop would never be executed:
g_nRetryQueue[nCount][0] = -1
g_nRetryQueue[nCount][1] = -1

// move other entries up
while ((nCount + 1) < g_nRetryCount)
{
g_nRetryQueue[nCount][0] = g_nRetryQueue[nCount + 1][0]
g_nRetryQueue[nCount][1] = g_nRetryQueue[nCount + 1][1]
nCount++
}
g_nRetryCount--
break
}
nCount++
}
}

/// <summary>Resets the setinfo string of the player with <paramref name="id"/> by removing tags that xREDIRECT used.</summary>
/// <param name="id">The internal player ID of the player that shall have the setinfo data resetted. It is passed as an array so that this function can easily be called from <seealso name="set_task"/>.</param>
public reset_info(id[])
{
client_cmd(id[0], "setinfo ^"xredir^" ^"^"")
client_cmd(id[0], "setinfo ^"password^" ^"^"")
}

#if defined VAULT
/// <summary>Converts a string to a value that can be written to a CSV file. Basically it handles escaping delimiters (,) and quotes (").</summary>
public csv_value(value[])
{
new sReturnValue[51]
copy(sReturnValue, 50, value)

if (containi(sReturnValue, "^"") || containi(sReturnValue, ","))
{
replace_all(sReturnValue, 50, "^"", "^"^"") // double all quotes
format(sReturnValue, 50, "^"%s^"", sReturnValue) // surround the whole term with quotes
}
return sReturnValue
}

/// <summary>Displays the current count stats to the given user ID.</summary>
public cmd_stats(id, level, cid)
{
if (!cmd_access(id, level, cid, 1))
return PLUGIN_HANDLED
if (g_bDebug)
log_amx("showstats")
new sVaultKey[MAX_VAULT_KEY_LEN]

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_AUTO)
console_print(id, "%L: %d", id, "MSG_STATS_REDIRECT_AUTO", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_MANUAL)
console_print(id, "%L: %d", id, "MSG_STATS_REDIRECT_MANUAL", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DROP)
console_print(id, "%L: %d", id, "MSG_STATS_DROP", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_MENU)
console_print(id, "%L: %d", id, "MSG_STATS_MENU", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_ENQUEUE)
console_print(id, "%L: %d", id, "MSG_STATS_ENQUEUE", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DEQUEUE)
console_print(id, "%L: %d", id, "MSG_STATS_DEQUEUE", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECTED)
console_print(id, "%L: %d", id, "MSG_STATS_REDIRECTED", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_RETRY)
console_print(id, "%L: %d", id, "MSG_STATS_RETRY", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_FOLLOW)
console_print(id, "%L: %d", id, "MSG_STATS_FOLLOW", nvault_get(g_nVaultId, sVaultKey))

return PLUGIN_HANDLED
}

/// <summary>Displays the current count stats to the server console.</summary>
public srvcmd_stats()
{
new sVaultKey[MAX_VAULT_KEY_LEN]

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_AUTO)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_REDIRECT_AUTO", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECT_MANUAL)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_REDIRECT_MANUAL", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DROP)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_DROP", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_MENU)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_MENU", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_ENQUEUE)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_ENQUEUE", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_DEQUEUE)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_DEQUEUE", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_REDIRECTED)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_REDIRECTED", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_RETRY)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_RETRY", nvault_get(g_nVaultId, sVaultKey))

formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, STATS_COUNT_FOLLOW)
server_print("%L: %d", LANG_SERVER, "MSG_STATS_FOLLOW", nvault_get(g_nVaultId, sVaultKey))
}

/// <summary>Resets all vault statistics to 0.</summary>
public srvcmd_resetvault()
{
new sVaultKey[MAX_VAULT_KEY_LEN]
new nStatsCountCount = eStatsCount
for (new nStatsType = 0; nStatsType < nStatsCountCount; nStatsType++)
{
formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, nStatsType)
nvault_pset(g_nVaultId, sVaultKey, "0")
}
}

/// <summary>Increases a statistics counter.</summary>
public stats_count(stats_type, server_id)
{
new sVaultKey[MAX_VAULT_KEY_LEN]
new sVaultValue[11] // 10 digits should be enough
formatex(sVaultKey, MAX_VAULT_KEY_LEN-1, "%s%d", STATS_VAULT_TAG, stats_type)
formatex(sVaultValue, 10, "%d", nvault_get(g_nVaultId, sVaultKey) + 1)
nvault_pset(g_nVaultId, sVaultKey, sVaultValue)
}
#endif // VAULT

#if defined STATISTICS
#if defined SQL
public sql_callback(nFailState, Handle:hQuery, sError[], nErrNum, aData[], nSize, Float:fQueueTime)
{
if (nFailState != TQUERY_SUCCESS)
log_amx("SQLx error #%d: %s", nErrNum, sError);
}
#endif // SQL

/// <summary>Stores an info line in the statistics.</summary>
/// <param name="statstype">One of the STATS_INFO_ defines defining the type of the action.</param>
/// <param name="id">The slot ID of the user that triggered the information line by being directly involved/affected.</param>
/// <param name="redirtype">The redirection type. 0 = drop, 1 = auto, 2 = manual, 3 = manual/follow, 4 = manual/queue, 5 = manual/admin (redirect_user).</param>
/// <param name="server">The target server number.</param>
/// <remarks>Unused parameters are ignored but should still always be set to -1 for better code readability.</remarks>
public stats_redirect(statstype, id, redirtype, server)
{
new sUserIp[MAX_IP_LEN]
new sUserNick[MAX_NAME_LEN]
new sUserId[MAX_ID_LEN]
new nServerId = server
if ((g_bUseIds) && (server >= 0))
nServerId = g_naServerIds[server]

get_user_ip(id, sUserIp, MAX_IP_LEN-1, 1)
get_user_name(id, sUserNick, MAX_NAME_LEN-1)
get_user_authid(id, sUserId, MAX_ID_LEN-1)

switch (statstype)
{
// File header:
// log_to_file(STATSFILE, ",User Name,User IP,User Auth-ID,Action,Server-ID,Target-Server-ID")
case STATS_INFO_ENQUEUE:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Enqueue", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Enqueue", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_ENQUEUE, nServerId)
#endif // SQL
}
case STATS_INFO_RETRY:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Retry", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Retry", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_RETRY, nServerId)
#endif // SQL
}
case STATS_INFO_DEQUEUE:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Dequeue", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Dequeue", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_DEQUEUE, nServerId)
#endif // SQL
}
case STATS_INFO_REDIRECT:
{
switch (redirtype)
{
case STATS_REDIRTYPE_DROP:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Drop", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Drop", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_DROP, nServerId)
#endif // SQL
}
case STATS_REDIRTYPE_AUTO:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Auto", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Auto", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_REDIRECT_AUTO, nServerId)
#endif // SQL
}
case STATS_REDIRTYPE_MANUAL:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Manual", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Manual", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId)
#endif // SQL
}
case STATS_REDIRTYPE_FOLLOW:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Follow", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Follow", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId)
stats_count(STATS_COUNT_FOLLOW, nServerId)
#endif // SQL
}
case STATS_REDIRTYPE_QUEUED:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Queued", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Queued", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId)
#endif // SQL
}
case STATS_REDIRTYPE_ADMIN:
{
#if defined SQL
SQL_QueryAndIgnore(hSql, "INSERT INTO `%s` (`user_name`, `user_ip`, `user_authid`, `action`, `server_id`, `target_server_id`) VALUES ('%s', '%s', '%s', '%s', '%d', '%d')", SQL_TABLE_STATISTICS, sUserNick, sUserIp, sUserId, "Redirect: Admin", g_nOwnServerId, nServerId)
#else
log_to_file(STATSFILE, ",%s,%s,%s,%s,%d,%d", csv_value(sUserNick), sUserIp, sUserId, "Redirect: Admin", g_nOwnServerId, nServerId)
stats_count(STATS_COUNT_REDIRECT_MANUAL, nServerId)
#endif // SQL
}
}
}
}
}
#endif // STATISTICS

/// <summary>Advertises the availability of the /server command.</summary>
public advertise_server_command()
{
client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_ADVERTISE")
}

/// <summary>Announce the servers on top of the screen. The position and interval for announcements can be set by CVARs.</summary>
public announce_servers()
{
if (get_pcvar_num(cvar_active) == 1)
{
if (g_nServerCount > 0)
{
new nCheckMethod = get_pcvar_num(cvar_check_method)
new sAnnounceBody[MAX_MENUBODY_LEN] = ""
new nDisplayCount = 0
new nServerCount = g_nNextAnnounceServer
if (nServerCount >= g_nServerCount)
nServerCount = 0

while ((nServerCount < g_nServerCount) && (nDisplayCount < )
{
if (!((g_naServerPrivate[nServerCount] >= PRIVATE_HIDE) || (g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY)) || ((get_pcvar_num(cvar_hidedown) > 1) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer))))
{
if (nServerCount == g_nOwnServer)
{
new sMap[MAX_MAP_LEN]
get_mapname(sMap, MAX_MAP_LEN - 1)
format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s [%s] (%d/%d)", sAnnounceBody, g_saServerNames[nServerCount], sMap, get_current_players(), get_maxplayers())
}
else
{
if (nCheckMethod == 0)
format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount])
else
if (g_baServerResponding[nServerCount])
{
if (nCheckMethod == 1)
format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s", sAnnounceBody, g_saServerNames[nServerCount])
else if (nCheckMethod == 2)
format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s [%s] (%d/%d)", sAnnounceBody, g_saServerNames[nServerCount], g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
}
else
format(sAnnounceBody, MAX_MENUBODY_LEN - 1, "%s^n%s (down)", sAnnounceBody, g_saServerNames[nServerCount])
}
}
nServerCount++
nDisplayCount++
}
g_nNextAnnounceServer = nServerCount
set_hudmessage(000, 100, 255, -1.0, 0.01, 0, 0.0, 10.0, 0.5, 0.10, 1)

if (get_pcvar_float(cvar_announce) > 0.0)
{
new nAnnounceMode = get_pcvar_num(cvar_announce_mode)
if (nAnnounceMode > 0)
{
new naPlayers[MAX_PLAYERS]
new nPlayerNum, nPlayerCount
new sAnnounceText[MAX_MENUBODY_LEN]
if ((nAnnounceMode == 1) || (nAnnounceMode == 3))
{
get_players(naPlayers, nPlayerNum, "ac") // alive players
set_hudmessage(000, 100, 255, get_pcvar_float(cvar_announce_alivepos_x), get_pcvar_float(cvar_announce_alivepos_y), 0, 0.0, 10.0, 0.5, 0.10, 1)
for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
{
if (get_pcvar_num(cvar_manual) >= 1)
format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody)
else
sAnnounceText = sAnnounceBody
show_hudmessage(naPlayers[nPlayerCount], sAnnounceText)
}
}
if ((nAnnounceMode == 2) || (nAnnounceMode == 3))
{
get_players(naPlayers, nPlayerNum, "bc") // dead players
set_hudmessage(000, 100, 255, get_pcvar_float(cvar_announce_deadpos_x), get_pcvar_float(cvar_announce_deadpos_y), 0, 0.0, 10.0, 0.5, 0.10, 1) // show list at lower position for them so it is not covered by the "spectator bars"
for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
{
if (get_pcvar_num(cvar_manual) >= 1)
format(sAnnounceText, MAX_MENUBODY_LEN - 1, "%L^n%s", naPlayers[nPlayerCount], "MSG_SAY_SERVER", sAnnounceBody)
else
sAnnounceText = sAnnounceBody
show_hudmessage(naPlayers[nPlayerCount], sAnnounceText)
}
}
}
}
}
}
return PLUGIN_HANDLED
}


/// <summary>Shows the sub menu for server with number <paramref name="nServer"/> to the the player with ID <paramref name="id"/>.</summary>
/// <param name="nServer">The server to show the sub menu for.</param>
/// <param name="id">The ID of the player to show the sub menu.</param>
/// <seealso name="server_menu_select"/>
/// <seealso name="sub_menu_select"/>
/// <seealso name="show_server_menu"/>
public show_sub_menu(id, nServer)
{
new nCanRedirect = can_redirect_player(nServer, id, 2, false)
new nCanRedirectIgnoreAdmin = can_redirect_player(nServer, id, 2, true);
new bool:bCanQueue = can_queue_player(nServer, id)
new bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR)
new nCheckMethod = get_pcvar_num(cvar_check_method)
new sMenuBody[MAX_MENUBODY_LEN]
new nCurrentCategory = g_naServerCategory[nServer]
new sCurrentCategory[MAX_VALUE_LEN] = ""
if (nCurrentCategory >= 0)
sCurrentCategory = g_saCategories[g_naServerCategory[nServer]]

// can we display colors?
if (bColorMenu)
{
formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SRVINFO_CAPTION")
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer])
if (get_pcvar_num(cvar_categories) >= 1)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_CATEGORY", sCurrentCategory)
}
else
{
formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SRVINFO_CAPTION")
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_NAME", g_saServerNames[nServer])
if (get_pcvar_num(cvar_categories) >= 1)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_CATEGORY", sCurrentCategory)
}

// can we display map and player information?
if (((nCheckMethod == 2) && ((g_baServerResponding[nServer])) || (nServer == g_nOwnServer)))
{
if (bColorMenu)
{
if (nServer == g_nOwnServer)
{
new sMap[MAX_MAP_LEN]
get_mapname(sMap, MAX_MAP_LEN - 1)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_current_players(), get_maxplayers())
}
else
{
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer])
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y%L \w%d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer])
}
}
else
{
if (nServer == g_nOwnServer)
{
new sMap[MAX_MAP_LEN]
get_mapname(sMap, MAX_MAP_LEN - 1)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", sMap)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", get_current_players(), get_maxplayers())
}
else
{
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %s", sMenuBody, id, "MSG_SRVINFO_MAP", g_saServerMap[nServer])
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%L %d/%d", sMenuBody, id, "MSG_SRVINFO_PLAYERS", g_naServerActivePlayers[nServer], g_naServerMaxPlayers[nServer])
}
}
}

// make the next line red if colors are supported and (the user is no admin or it's the current server)
if ((bColorMenu) && ((!access(id, MIN_ADMIN_LEVEL)) || (nCanRedirect == 1)))
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\r", sMenuBody)
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n", sMenuBody)

// now display reason why we can't redirect there
switch (nCanRedirectIgnoreAdmin)
{
case 1:
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_CURRENT")
case 2:
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_PERMISSION")
case 3:
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_NOMANUAL")
case 4:
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_FULL")
case 5:
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_DOWN")
case 7:
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%L", sMenuBody, id, "MSG_SRVINFO_ERR_PROT")
}

// enable/disable key for redirection/queue functionality
new key = (1<<9) // cancel
key = key | (1<< // back
if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
key = key | (1<<2) // refresh
if (nCanRedirect == 0)
key = key | (1<<0) // redirect
if (bCanQueue && (nCheckMethod > 1))
key = key | (1<<1) // enqueue

new sQueueMsg[30]
if (is_queued(id, nServer))
sQueueMsg = "MSG_LEAVEQUEUE"
else
sQueueMsg = "MSG_QUEUE"

// display the last menu items according to availability
if (bColorMenu)
{
if (nCanRedirect == 0)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \w %L", sMenuBody, id, "MSG_REDIRECT")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y1. \d %L", sMenuBody, id, "MSG_REDIRECT")
if (bCanQueue && (nCheckMethod > 1))
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \w %L", sMenuBody, id, sQueueMsg)
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y2. \d %L", sMenuBody, id, sQueueMsg)
if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \w %L", sMenuBody, id, "MSG_REFRESH")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y3. \d %L", sMenuBody, id, "MSG_REFRESH")
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9. \w %L", sMenuBody, id, "MSG_BACK")
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0. \w %L", sMenuBody, id, "MSG_CANCEL")
}
else
{
if (nCanRedirect == 0)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n1. %L", sMenuBody, id, "MSG_REDIRECT")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_REDIRECT")
if (bCanQueue && (nCheckMethod > 1))
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n2. %L", sMenuBody, id, sQueueMsg)
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, sQueueMsg)
if ((nCheckMethod > 0) || (nServer == g_nOwnServer))
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n3. %L", sMenuBody, id, "MSG_REFRESH")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n_. %L", sMenuBody, id, "MSG_REFRESH")
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_BACK")
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL")
}
g_nLastSelected[id - 1] = nServer
show_menu(id, key, sMenuBody, -1, "Detail Menu")
}

/// <summary>Shows a selection menu with all server categories.</summary>
/// <param name="id">The ID of the player to show the category menu to.</param>
/// <seealso name="server_menu_select"/>
/// <seealso name="sub_menu_select"/>
/// <seealso name="show_sub_menu"/>
/// <seealso name="show_server_menu"/>
public show_category_menu(id)
{
new bool:bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR)
new sMenuBody[MAX_MENUBODY_LEN]
new nCategorySetting = get_pcvar_num(cvar_categories)
new key = (1<<9) // cancel key is always enabled
if (nCategorySetting == 2)
key = key | (1<<0) // enable key for the "all categories"

if (bColorMenu)
{
formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SELECT_CATEGORY")
if (nCategorySetting == 2)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s\y1. \w%L^n", sMenuBody, id, "MSG_ALL_CATEGORIES")
}
else
{
formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SELECT_CATEGORY")
if (nCategorySetting == 2)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s1. %L^n", sMenuBody, id, "MSG_ALL_CATEGORIES")
}

for (new nCategoryIndex = 0; nCategoryIndex < g_nCategoryCount; nCategoryIndex++)
{
key = key | (1<<(nCategoryIndex + (nCategorySetting - 1)))
if (bColorMenu)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s\y%d. \w%s^n", sMenuBody, nCategoryIndex + nCategorySetting, g_saCategories[nCategoryIndex])
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s%d. %s^n", sMenuBody, nCategoryIndex + nCategorySetting, g_saCategories[nCategoryIndex])
}
if (bColorMenu)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, "MSG_CANCEL")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL")

show_menu(id, key, sMenuBody, -1, "Category Menu")
}

/// <summary>Shows the server menu page <paramref name="menupage"/> to the the player with ID <paramref name="id"/>.</summary>
/// <param name="id">The ID of the player to show the server menu to.</param>
/// <param name="menupage">The menu page number to show to the player. Offset is 0.</param>
/// <param name="category">The category of servers to show. -1 or unspecified to show all servers regardless of their category.</param>
/// <seealso name="server_menu_select"/>
/// <seealso name="sub_menu_select"/>
/// <seealso name="show_sub_menu"/>
/// <seealso name="show_category_menu"/>
public show_server_menu(id, menupage, category)
{
new nServerCount
if (get_pcvar_num(cvar_active) == 1)
{
if (g_nServerCount > 0)
{
new bool:bSubMenu = (get_pcvar_num(cvar_manual) >= 2)
new bool:bColorMenu = (colored_menus() && !MENU_FORCENOCOLOR)
new bool:bShowServer
new nCheckMethod = get_pcvar_num(cvar_check_method)
new sMenuBody[MAX_MENUBODY_LEN]
if (bColorMenu)
formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "\y%L^n", id, "MSG_SELECT_SERVER")
else
formatex(sMenuBody, MAX_MENUBODY_LEN - 1, "%L^n", id, "MSG_SELECT_SERVER")


if (menupage <= 1)
nServerCount = 0
else
nServerCount = g_naMenuPageStart[id - 1][menupage - 2]

new nDisplayNumber = 1

new key = (1<<9) // cancel key is always enabled

new nHideDown = get_pcvar_num(cvar_hidedown)
if (nHideDown == 1)
nHideDown = 3

// the 3 parts of a menu item, third part only displayed with redirect_check_method >= 2
new sMenuNumber[10]
new sMenuSrvName[50]
new sMenuInfo[50]
if (nCheckMethod < 2)
sMenuInfo = ""

while ((nDisplayNumber < 9) && (nServerCount < g_nServerCount))
{
// don't show the server if it's not the own server, the server is not responding and hidedown is set to a value > 2
bShowServer = (!((nHideDown > 2) && (!g_baServerResponding[nServerCount]) && (nServerCount != g_nOwnServer)))
// don't show the server if it has the SERVERFLAG_NODISPLAY flag set
if (bShowServer)
bShowServer = (!(g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NODISPLAY)))
// don't show the server if it has the SERVERFLAG_PRIVATE flag set and user is no admin
if (bShowServer && (!access(id, MIN_ADMIN_LEVEL)) && (g_naServerPrivate[nServerCount] >= PRIVATE_HIDE))
bShowServer = false
// don't show the server if categories are enabled and this server doesn't belong to the currently selected category
if (bShowServer && (category >= 0))
bShowServer = (category == g_naServerCategory[nServerCount])

if (bShowServer)
{
new bool:bCanRedirectByPassword = !(!equal(g_saServerPasswords[nServerCount], "") && (g_naServerPublicPassword[nServerCount] == 0) && (!access(id, MIN_ADMIN_LEVEL)))

if (bColorMenu)
{
formatex(sMenuNumber, 9, "\y%d. ", nDisplayNumber)
if (bSubMenu)
formatex(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
else
formatex(sMenuSrvName, 49, "\d %s", g_saServerNames[nServerCount])
}
else
{
formatex(sMenuNumber, 9, "%d. ", nDisplayNumber)
formatex(sMenuSrvName, 49, " %s", g_saServerNames[nServerCount])
}

new bool:bCanRedirect = true
sMenuInfo = ""

// manual redirection to that server is disabled or server is passworded but password is not public and user has insufficent admin rights
if ((nCheckMethod == 2) && (((g_naServerFlags[nServerCount] & (1<<SERVERFLAG_NOMANUAL)) || !bCanRedirectByPassword)))
{
bCanRedirect = false
if ((!bColorMenu) && (!bSubMenu))
sMenuNumber = "_. "
if (nCheckMethod == 2)
{
if (bColorMenu)
formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
else
formatex(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
}
}
new nFreeSlotsAdmin = (g_naServerMaxPlayers[nServerCount] - g_naServerActivePlayers[nServerCount])
new nFreeSlots = nFreeSlotsAdmin - g_naServerReserveSlots[nServerCount] // normal players can't use the admin slots, so subtract them
// server is full? (and player has insufficient rights to join on an admin slot/not enough admin slots?)
if ((nCheckMethod == 2) && ((nFreeSlots <= 0) && (!access(id, MIN_ADMIN_LEVEL)) || nFreeSlotsAdmin <= 0))
{
bCanRedirect = false
if ((!bColorMenu) && (!bSubMenu))
sMenuNumber = "_. "
if (bColorMenu)
formatex(sMenuInfo, 49, " [%s] \r(\w%d/%d\r)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
else
formatex(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
}
// server is down
if ((nCheckMethod > 0) && (!g_baServerResponding[nServerCount]))
{
if ((!bColorMenu) && (!bSubMenu))
sMenuNumber = "_. "
bCanRedirect = false
if (bColorMenu)
sMenuInfo = " \r(\wdown\r)"
else
sMenuInfo = " (down)"
}
// server is current server
if (nServerCount == g_nOwnServer)
{
if ((!bColorMenu) && (!bSubMenu))
sMenuNumber = "_. "
bCanRedirect = false
new sMap[MAX_MAP_LEN]
get_mapname(sMap, MAX_MAP_LEN - 1)
if (bSubMenu && bColorMenu)
formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", sMap, get_current_players(), get_maxplayers())
else
formatex(sMenuInfo, 49, " [%s] (%d/%d)", sMap, get_current_players(), get_maxplayers())
}

// everything's fine, we can redirect here
if (bCanRedirect)
{
if (bColorMenu)
{
formatex(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
if (nCheckMethod > 1)
formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
}
else
{
if (nCheckMethod > 1)
formatex(sMenuInfo, 49, " [%s] (%d/%d)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
}

key = key | (1<<(nDisplayNumber - 1))
g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount
}
else if ((bSubMenu) && (nServerCount != g_nOwnServer)) // display server like it was enabled when submenues are enabled
if (bColorMenu)
{
formatex(sMenuSrvName, 49, "\w %s", g_saServerNames[nServerCount])
if ((nCheckMethod == 0) && (g_baServerResponding[nServerCount]))
formatex(sMenuInfo, 49, " \y[\w%s\y] \y(\w%d/%d\y)", g_saServerMap[nServerCount], g_naServerActivePlayers[nServerCount], g_naServerMaxPlayers[nServerCount])
}

// assemble the menu item and append it to menu body
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n%s%s%s", sMenuBody, sMenuNumber, sMenuSrvName, sMenuInfo)

// if enabled a submenu is always possible to be displayed, regardless of the server's redirection status
if (bSubMenu)
{
key = key | (1<<(nDisplayNumber - 1))
g_naServerSelections[id - 1][nDisplayNumber - 1] = nServerCount
}

nDisplayNumber++
}
nServerCount++
}

if (nServerCount < g_nServerCount)
{
if (bColorMenu)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\w %L", sMenuBody, id, "MSG_MORE")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n9. %L", sMenuBody, id, "MSG_MORE")
key = key | (1<<
}
else
{
if (bColorMenu)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n\y9.\d %L", sMenuBody, id, "MSG_MORE")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n^n_. %L", sMenuBody, id, "MSG_MORE")
}

#if CANCEL_IS_BACK_KEY
if (bColorMenu)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, (get_pcvar_num(cvar_categories) >= 1) ? "MSG_BACK" : "MSG_CANCEL")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, (get_pcvar_num(cvar_categories) >= 1) ? "MSG_BACK" : "MSG_CANCEL")
#else
if (bColorMenu)
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n\y0.\w %L", sMenuBody, id, "MSG_CANCEL")
else
format(sMenuBody, MAX_MENUBODY_LEN - 1, "%s^n0. %L", sMenuBody, id, "MSG_CANCEL")
#endif

show_menu(id, key, sMenuBody, -1, "Redirect Menu")
}
}
g_naMenuPageStart[id - 1][menupage - 1] = nServerCount

g_naLastMenuPages[id - 1] = menupage
}


/// <summary>Reloads the servers from server list. Takes care of variable and array reinitialization.</summary>
/// <remarks>To be able to rely on this in the future make sure to add an initialization here for all variables you add!</remarks>
public srvcmd_reload()
{
new nCounter

// clear all global arrays and variables before reloading
for (nCounter = 0; nCounter < MAX_SERVERFORWARDS; nCounter++)
{
if (g_naServerSockets[nCounter] > 0)
{
socket_close(g_naServerSockets[nCounter])
g_naServerSockets[nCounter] = 0
}
g_naServerIds[nCounter] = -1
g_naServerPorts[nCounter] = 27015
g_naServerActivePlayers[nCounter] = -1
g_naServerMaxPlayers[nCounter] = -1
g_naServerCmdBackup[nCounter] = DEFAULT_CMDBACKUP
g_naServerFlags[nCounter] = 0
g_naServerReserveSlots[nCounter] = 0
g_baServerResponding[nCounter] = false
g_saServerMap[nCounter] = ""
g_saServerNames[nCounter] = ""
g_saServerAddresses[nCounter] = ""
g_saServerPasswords[nCounter] = ""
g_naServerPublicPassword[nCounter] = 0
g_naServerCategory[nCounter] = -1
g_naServerPrivate[nCounter] = PRIVATE_NONE

// don't reset these for the own server, as they are only queried once at plugin_postinit()
if (nCounter != g_nOwnServer)
{
g_saServerMod[nCounter] = ""
g_naServerProtocol[nCounter] = 0
}
}

// reset global variables
g_nNextAnnounceServer = 0
g_nServerCount = 0
g_nCategoryCount = 0
g_nLastRedirectServer = -1
g_sLastRedirectName = ""
g_nOwnServer = -1
g_nRetryCount = 0

for (new nPlrCnt = 0; nPlrCnt < MAX_PLAYERS; nPlrCnt++)
{
// server IDs might change and thus render all currently saved server IDs invalid, so remove them, to be sure
g_nRetryQueue[nPlrCnt][0] = -1
g_nRetryQueue[nPlrCnt][1] = -1
g_nLastServer[nPlrCnt] = -1
g_nLastSelected[nPlrCnt] = -1
}

#if defined SQL
load_servers_sql()
#else
load_servers_file()
#endif // SQL

if (g_nServerCount < 2)
log_amx("%L", LANG_SERVER, "MSG_ERROR_NOT_ENOUGH_SERVERS")

new sFullAddress[MAX_SERVERADDRESS_LEN]
new sTmpServerIP[MAX_IP_LEN]
new sTmpServerPort[MAX_PORT_LEN]
new sTmpServerAddress[MAX_IP_LEN + MAX_PORT_LEN], sTmpServerAddress2[MAX_IP_LEN + MAX_PORT_LEN]
new sTmpOwnAddress[MAX_SERVERADDRESS_LEN]
get_cvar_string("net_address", sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1)
get_cvar_string("ip", sTmpServerIP, MAX_IP_LEN - 1)
get_cvar_string("port", sTmpServerPort, MAX_PORT_LEN - 1)
formatex(sTmpServerAddress, MAX_IP_LEN + MAX_PORT_LEN - 1, "%s:%s", sTmpServerIP, sTmpServerPort)
get_pcvar_string(cvar_external_address, sTmpOwnAddress, MAX_SERVERADDRESS_LEN - 1)


// determine the own server
new nServerCount = 0
while (nServerCount < g_nServerCount)
{
formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount])
if (equal(sFullAddress, sTmpOwnAddress) || equal(sFullAddress, sTmpServerAddress) || equal(sFullAddress, sTmpServerAddress2))
{
g_nOwnServer = nServerCount
if (g_bUseIds)
g_nOwnServerId = g_naServerIds[nServerCount]
else
g_nOwnServerId = nServerCount
}
if (g_bUseIds && (g_naServerIds[nServerCount] == -1))
log_amx("%L", LANG_SERVER, "MSG_ID_MISSING", g_saServerNames[nServerCount])
nServerCount++
}
if (g_nOwnServer == -1)
log_amx("%L", LANG_SERVER, "MSG_OWN_DETECTION_ERROR")

// we need to know our own server index to be able to load attributes from SQL - so now we can do that
#if defined SQL
load_attributes_sql()
#endif // SQL

// query all servers again
nServerCount = g_nOwnServer
g_nOwnServer = -1 // make sure the own server is queried too (for its protocol), maybe someone just corrected its address
query_servers()
g_nOwnServer = nServerCount
}


/// <summary>This is needed so server doesn't display "unknown command: pickserver". Returning PLUGIN_HANDLED directly in cmd_show_server_menu would supress the chat message so we use this workaround.</summary>
public cmd_pickserver(id, level, cid)
{
cmd_show_server_menu(id, level, cid)
return PLUGIN_HANDLED
}

/// <summary>This function does the actual redirection. It is also what <seealso name="native_redirect"/> is a wrapper for with <paramref name="nServer"/> preset to -1 (the external plugin does not know about our server list and numbers anyway) and <paramref name="bIgnoreSource"/> preset to true (an external plugin does not care whether this would mean redirecting the player back to where he came from).</summary>
/// <summary>It is aware of user permissions and has several options which are set via parameters.</summary>
/// <param name="id">ID of player to redirect.</param>
/// <param name="nServer">Target server, -1 for automatic choosing according to redirect_auto.</param>
/// <param name="bCanOther">If nServer is no valid redirect target can we use another server instead?</param>
/// <param name="bCanDrop">Drop user if no server was found?</param>
/// <param name="bIgnoreSource>"Redirect regardless of redirecting would be back to source server.</param>
/// <seealso name="native_redirect"/>
/// <seealso name="cmd_redirect_user"/>
public redirect(id, nServer, bCanOther, bCanDrop, bIgnoreSource)
{
new nForwardServer = -1
new bool:bFoundServer = false
new nRedirType
if (nServer == -1)
nRedirType = 1
else
nRedirType = 2

new nSourceServer

if (bIgnoreSource)
{
nSourceServer = -1
}
else
{
//TODO: actually we got that information in g_nLastServer[] already, unfortunately in some cases this is set AFTER redirect() is called -> find a solution
new sSourceServer[4] // maximum is 999 servers, so we have a maximum of 3 digits
get_user_info(id, "xredir", sSourceServer, 3)
if (!is_str_num(sSourceServer))
nSourceServer = -1
else
nSourceServer = str_to_num(sSourceServer)
if ((nSourceServer < 0) || (nSourceServer >= g_nServerCount))
nSourceServer = -1
if (g_bUseIds && (nSourceServer != -1))
nSourceServer = g_naServerIds[nSourceServer]
}

if ((can_redirect_player(nServer, id, nRedirType, false) > 0) || (nServer == -1))
{
if (!bCanOther)
{
if (bCanDrop)
{
client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER")
client_cmd(id, "disconnect")
#if defined STATISTICS
stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_DROP, -1)
#endif // STATISTICS
}
return false
}

nForwardServer = 0

// make sure at least one valid server exists or the second loop could be endless
while (nForwardServer < g_nServerCount)
{
if ((can_redirect_player(nForwardServer, id, nRedirType, false) == 0) && (nForwardServer != nSourceServer))
{
bFoundServer = true
break
}
nForwardServer++
}
new nAutoMode = get_pcvar_num(cvar_auto)
if ((nAutoMode == 1) || (nAutoMode == 3) || (nAutoMode == 5)) // redirect to random server
nForwardServer = -1
}
else
{
nForwardServer = nServer
bFoundServer = true
}

if (bFoundServer)
{
while (nForwardServer == -1)
{
nForwardServer = random_num(0, g_nServerCount - 1)
if ((can_redirect_player(nForwardServer, id, nRedirType, true) > 0) || ((nForwardServer == nSourceServer)))
nForwardServer = -1
}

new sUserNick[MAX_NAME_LEN]
get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
if (!equal(g_saServerPasswords[nForwardServer], "")) // set the user's server connect password if needed
client_cmd(id, "setinfo ^"password^" ^"%s^"", g_saServerPasswords[nForwardServer])
if (g_bUseIds)
client_cmd(id, "setinfo ^"xredir^" ^"%d^"", g_nOwnServerId)
else
client_cmd(id, "setinfo ^"xredir^" ^"%d^"", g_nOwnServer)

new sCheckAddress[MAX_IP_LEN]
get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
new sFullAddress[MAX_SERVERADDRESS_LEN]
if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[nForwardServer], "")))
formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[nForwardServer], g_naServerPorts[nForwardServer])
else
formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[nForwardServer], g_naServerPorts[nForwardServer])
if (nRedirType == 1)
client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_SERVER_FULL_REDIRECTING", g_saServerNames[nForwardServer])

client_cmd(id, "Connect %s", sFullAddress)
#if defined STATISTICS
if (nServer == -1) // is this an automatic redirection (because the target server is random)?
stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_AUTO, nForwardServer)
#endif // STATISTICS


if (g_naServerPrivate[nForwardServer] < PRIVATE_FULLHIDE) // dont' announce anything if this server is set to fullhide, also don't save it as the last target for /follow
{
if (get_pcvar_num(cvar_show) == 1)
{
if (get_pcvar_num(cvar_check_method) == 2)
client_print(0, print_chat, "%s: %L [%s] (%d/%d)", PLUGIN_TAG, LANG_PLAYER, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer], g_saServerMap[nForwardServer], g_naServerActivePlayers[nForwardServer], g_naServerMaxPlayers[nForwardServer])
else
client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_REDIRECTED", sUserNick, g_saServerNames[nForwardServer])
if (get_pcvar_num(cvar_follow) == 1)
client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOW")
}
g_nLastRedirectServer = nForwardServer
g_sLastRedirectName = sUserNick
}
}
else if (bCanDrop)
{
client_cmd(id, "echo %s: %L", PLUGIN_TAG, id, "MSG_NO_REDIRECT_SERVER")
client_cmd(id, "disconnect")
#if defined STATISTICS
stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_DROP, -1)
#endif // STATISTICS
}
return true
}

/// <summary>Basically a wrapper for <seealso name="redirect"/> to make it available to other pugins as native.</summary>
/// <seealso name="redirect"/>
/// <seealso name="cmd_redirect_user"/>
public native_redirect(id, nServer, bCanDrop)
{
redirect(id, nServer, (nServer == -1), bCanDrop, true)
return PLUGIN_HANDLED
}

/// <summary>Show the list of players in current queue.</summary>
public cmd_redirect_queue(id, level, cid)
{
if (!cmd_access(id, level, cid, 1))
return PLUGIN_HANDLED

new nSlot

for (new nServer = 0; nServer < g_nServerCount; nServer++)
{
nSlot = 1
client_cmd(id, "echo %s: %s:", PLUGIN_TAG, g_saServerNames[nServer])
for (new nQueueIndex = 0; nQueueIndex < g_nRetryCount; nQueueIndex++)
{
if (g_nRetryQueue[nQueueIndex][1] == nServer)
{
new sUserNick[MAX_NAME_LEN]
get_user_name(g_nRetryQueue[nQueueIndex][0], sUserNick, MAX_NAME_LEN - 1)
client_cmd(id, "echo %s: %d. %s", PLUGIN_TAG, nSlot++, sUserNick)
}
}
}
return PLUGIN_HANDLED
}

/// <summary>Handler for in-game command <paramref name="redirect_user"/>, checks user permissions for this command and uses <seealso name="redirect"/> to do the redirection.</summary>
/// <seealso name="redirect"/>
/// <seealso name="native_redirect"/>
public cmd_redirect_user(id, level, cid)
{
if (!cmd_access(id, level, cid, 2))
return PLUGIN_HANDLED

new nForwardServer = -1
new sName[32]
read_argv(1, sName, 31)
new nCmdID = cmd_target(id, sName,

if (!nCmdID)
return PLUGIN_HANDLED

// contains destination server number?
if (read_argc() > 2)
{
new argtmp[3]
read_argv(2, argtmp, 2)
if (is_str_num(argtmp))
nForwardServer = (str_to_num(argtmp) - 1)
}

redirect(nCmdID, nForwardServer, (nForwardServer == -1), true, true)
#if defined STATISTICS
stats_redirect(STATS_INFO_REDIRECT, nCmdID, STATS_REDIRTYPE_ADMIN, nForwardServer)
#endif // STATISTICS

return PLUGIN_HANDLED
}

/// <summary>Handler for in-game command <paramref name="pickserver"/> or chat command <paramref name="/server"/>. Shows the server menu to the player using <seealso name="show_server_menu"/>.</summary>
/// <seealso name="show_server_menu"/>
public cmd_show_server_menu(id, level, cid)
{
if (get_pcvar_num(cvar_manual) >= 1)
{
#if defined VAULT
stats_count(STATS_COUNT_MENU, -1)
#endif // VAULT
if (get_pcvar_num(cvar_categories) >= 1)
show_category_menu(id)
else
show_server_menu(id, 1, -1)
}
else
client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_MANUAL_DISABLED")
return PLUGIN_CONTINUE
}

/// <summary>Handler for chat command <paramref name="/retry"/>. Adds the user to the retry queue using <seealso name="queue_add"/>.</summary>
/// <seealso name="queue_add"/>
public cmd_retry(id, level, cid)
{
if (g_nLastServer[id - 1] > -1)
{
#if defined STATISTICS
stats_redirect(STATS_INFO_RETRY, id, -1, g_nLastServer[id - 1])
#endif // STATISTICS
queue_add(id, g_nLastServer[id - 1])
}
else
client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_NO_LAST")
return PLUGIN_CONTINUE
}

/// <summary>Handler for chat command <paramref name="/stopretry"/>. Removes the user from the retry queue using <seealso name="queue_remove"/>.</summary>
/// <seealso name="queue_remove"/>
public cmd_stopretry(id, level, cid)
{
client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE_ALL", g_saServerNames[g_nLastServer[id - 1]])
queue_remove(id, -1)
return PLUGIN_CONTINUE
}

/// <summary>Handler for chat command <paramref name="/follow"/>. Sends a player after the last player that was redirected using <seealso name="redirect"/>.</summary>
/// <seealso name="redirect"/>
public cmd_follow_player(id, level, cid)
{
if (get_pcvar_num(cvar_active) == 1)
{
if (get_pcvar_num(cvar_follow) == 1)
{
if (g_nLastRedirectServer >= 0)
{
console_print(id, "%s: %L", PLUGIN_TAG, id, "MSG_REDIRECTING", g_saServerNames[g_nLastRedirectServer])
new sFullAddress[MAX_SERVERADDRESS_LEN]
new sCheckAddress[MAX_IP_LEN]
get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
if (is_local_address(sCheckAddress) && (!equal(g_saServerLocalAddresses[g_nLastRedirectServer], "")))
formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerLocalAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer])
else
formatex(sFullAddress, MAX_SERVERADDRESS_LEN - 1, "%s:%d", g_saServerAddresses[g_nLastRedirectServer], g_naServerPorts[g_nLastRedirectServer])
client_cmd(id, "Connect %s", sFullAddress)
#if defined STATISTICS
stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_FOLLOW, g_nLastRedirectServer)
#endif // STATISTICS
new sUserNick[MAX_NAME_LEN]
get_user_name(id, sUserNick, MAX_NAME_LEN - 1)
if (get_pcvar_num(cvar_show) == 1)
{
if (get_pcvar_num(cvar_check_method) == 2)
client_print(0, print_chat, "%s: %L [%s] (%d/%d)", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOWED", sUserNick, g_sLastRedirectName, g_saServerNames[g_nLastRedirectServer], g_saServerMap[g_nLastRedirectServer], g_naServerActivePlayers[g_nLastRedirectServer], g_naServerMaxPlayers[g_nLastRedirectServer])
else
client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOWED", sUserNick, g_sLastRedirectName, g_saServerNames[g_nLastRedirectServer])
client_print(0, print_chat, "%s: %L", PLUGIN_TAG, LANG_PLAYER, "MSG_FOLLOW")
}
g_sLastRedirectName = sUserNick
}
else
client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_CANT_FOLLOW")

}
else
client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_FOLLOW_DISABLED")
}
return PLUGIN_CONTINUE
}

/// <summary>Event handler for category menu selection.</summary>
/// <param name="id">Slot ID of player that selected a menu item.</param>
/// <param name="key">Key that was pressed, number between 0 and 9.</param>
/// <seealso name="server_menu_select"/>
/// <seealso name="show_category_menu"/>
/// <seealso name="show_server_menu"/>
/// <seealso name="show_sub_menu"/>
public category_menu_select(id, key)
{
if (key < 9)
{
new nCategorySetting = get_pcvar_num(cvar_categories)
new nSelectedCategory = key - (nCategorySetting - 1)
g_naLastCategory[id - 1] = nSelectedCategory
show_server_menu(id, 1, nSelectedCategory)
}
else
g_naLastCategory[id - 1] = -1
}

/// <summary>Event handler for sub menu selection.</summary>
/// <summary>When the user presses a number key in the sub menu this handler is called.</summary>
/// <param name="id">Slot ID of player that selected a menu item.</param>
/// <param name="key">Key that was pressed, number between 0 and 9.</param>
/// <seealso name="server_menu_select"/>
/// <seealso name="show_server_menu"/>
/// <seealso name="show_sub_menu"/>
public sub_menu_select(id, key)
{
new nServer = g_nLastSelected[id - 1]
if (key == 0) // redirect
{
// check if meanwhile the redirection is not possible anymore - if so, refresh the detail menu
if (can_redirect_player(nServer, id, 2, false) > 0)
show_sub_menu(id, nServer)
else
{
redirect(id, nServer, false, false, true)
#if defined STATISTICS
stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_MANUAL, nServer)
#endif // STATISTICS
}
}
else if (key == 1) // queue
{
if (is_queued(id, nServer))
{
queue_remove(id, nServer)
#if defined STATISTICS
stats_redirect(STATS_INFO_DEQUEUE, id, -1, nServer)
#endif // STATISTICS
client_print(id, print_chat, "%s: %L", PLUGIN_TAG, id, "MSG_QUEUE_REMOVE", g_saServerNames[nServer])
}
else
{
queue_add(id, nServer)
}
}
else if (key == 2) // refresh
{
show_sub_menu(id, nServer)
}
else if (key == // go back to where the user was before in main menu
show_server_menu(id, g_naLastMenuPages[id - 1], g_naLastCategory[id - 1])
}

/// <summary>Event handler for server menu selection.</summary>
/// <summary>When the user presses a number key in the server menu this handler is called.</summary>
/// <summary>Depending on settings it will display a sub menu or redirect the user.</summary>
/// <param name="id">Slot ID of player that selected a menu item.</param>
/// <param name="key">Key that was pressed, number between 0 and 9.</param>
/// <seealso name="sub_menu_select"/>
/// <seealso name="show_server_menu"/>
/// <seealso name="show_sub_menu"/>
public server_menu_select(id, key)
{
if (key <
{
new nServerIdx = g_naServerSelections[id - 1][key]

new nManualMode = get_pcvar_num(cvar_manual)
// show the detail menu?
if (((nManualMode == 2) && (can_redirect_player(nServerIdx, id, 2, false) > 0)) || (nManualMode == 3))
show_sub_menu(id, nServerIdx)
else
{
redirect(id, nServerIdx, false, false, true)
#if defined STATISTICS
stats_redirect(STATS_INFO_REDIRECT, id, STATS_REDIRTYPE_MANUAL, nServerIdx)
#endif // STATISTICS
}
}
else
{
if (key == // "more" button
show_server_menu(id, g_naLastMenuPages[id - 1] + 1, g_naLastCategory[id - 1])
#if CANCEL_IS_BACK_KEY
if ((get_pcvar_num(cvar_categories) >= 1) && (key == 9))
show_category_menu(id)
#endif
}
}


/// <summary>Sends the information query packets to all other servers.</summary>
/// <summary>This sends the UDP server information query packets in old and new style HL format to all servers in the list.</summary>
/// <summary>Receiving of server data is handled by <seealso name="receive_serverquery_answers"/>.</summary>
/// <seealso name="receive_serverquery_answers"/>
public query_servers()
{
new nCheckMethod = get_pcvar_num(cvar_check_method)
if (nCheckMethod == 0)
return PLUGIN_HANDLED
new socket_error
new sOldRequest[12]
new sNewRequest[26]

if (nCheckMethod == 1)
{
// we don't know what server it is so send both old and new style query
formatex(sOldRequest, 8, "%c%c%c%c%s", 255, 255, 255, 255, "ping")
formatex(sNewRequest, 5, "%c%c%c%c%c", 255, 255, 255, 255, 105)
}
else if (nCheckMethod == 2)
{
// we don't know what server it is so send both old and new style query
formatex(sOldRequest, 11, "%c%c%c%c%s", 255, 255, 255, 255, "details")
formatex(sNewRequest, 25, "%c%c%c%c%c%s%c", 255, 255, 255, 255, 84, "Source Engine Query", 0)
}

new nServerCount = 0
new nQuerySocket
new nCmdBackup
new nSendCount
while (nServerCount < g_nServerCount)
{
if (nServerCount != g_nOwnServer)
{
nQuerySocket = g_naServerSockets[nServerCount]
// first we clear the current receive buffer - we are sending a new request and don't care for old data anymore
if (nQuerySocket > 0)
{
new sEmptyBufferDummy[512]
new nEndlessProtection = 0
while ((socket_change(nQuerySocket, 1)) && (nEndlessProtection < 500))
{
//log_amx("emptying socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount])
socket_recv(nQuerySocket, sEmptyBufferDummy, 512)
nEndlessProtection++
}
if (nEndlessProtection >= 500)
{
socket_close(nQuerySocket)
log_amx("WARNING: endless protection triggered for socket %i (%s)", nQuerySocket, g_saServerNames[nServerCount])
}

}
else
{
// socket debug
//log_amx("opening socket for server %i (%s)", nServerCount, g_saServerNames[nServerCount])
if (!equal(g_saServerLocalAddresses[nServerCount], ""))
nQuerySocket = socket_open(g_saServerLocalAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error)
else
nQuerySocket = socket_open(g_saServerAddresses[nServerCount], g_naServerPorts[nServerCount], SOCKET_UDP, socket_error)
// socket debug
//log_amx("opened socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount])
}

if ((nQuerySocket > 0) && (socket_error == 0))
{
g_naServerSockets[nServerCount] = nQuerySocket
nCmdBackup = g_naServerCmdBackup[nServerCount]
// socket debug
//log_amx("sending query on socket %i for server %i (%s)", nQuerySocket, nServerCount, g_saServerNames[nServerCount])
if (nCheckMethod == 1)
{
for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
socket_send2(nQuerySocket, sOldRequest,
for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
socket_send2(nQuerySocket, sNewRequest, 5)
}
else if (nCheckMethod == 2)
{
for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
socket_send2(nQuerySocket, sOldRequest, 11)
for (nSendCount = -1; nSendCount < nCmdBackup; nSendCount++)
socket_send2(nQuerySocket, sNewRequest, 25)
}
}
else
{
g_naServerSockets[nServerCount] = 0
log_amx("%L", LANG_SERVER, "MSG_SOCKET_ERROR", socket_error, nServerCount)
}
}
nServerCount++
}
set_task(QUERY_TIMEOUT, "receive_serverquery_answers", TASKID_QUERY_RECEIVE)

return PLUGIN_HANDLED
}


/// <summary>Index an incoming UDP data packet.</summary>
/// <param name="sData">The raw UDP data string that was received.</param>
/// <param name="nDataLen">Length of the raw UDP data string as reported by the socket receive function.</param>
/// <param name="sFormatString">The string containing the format. It can contain the elements 124 and s. A digit just declares the number of bytes the element (type) has, "s" declares a string. An opening square bracket declares a byte option followed by a sequence of sub options. The sequence ends with a closing square bracket. Such options can occur more than once but may not be nested.</param>
/// <param name="aIndexes">The function stores the resulting character offsets of each index in this array.</param>
/// <remarks>This function assumes the given format string is correct as it is only created internally by a programmer, so there is no error checking whatsoever (e.g. an unsupported format character would lead the function into an endless loop).</remarks>
/// <returns>The number of indexes that were written (= the number of format elements).</returns>
public index_create(sData[MAX_INFO_LEN], nDataLen, sFormatString[100], aIndexes[MAX_INFO_FORMAT])
{
//log_amx("---------------------- indexing %s ----------------------", sFormatString)
new nFormatPos = 0 // current position within the format array
new nIndexPos = 0 // current position within the data array
new nDataIndex = 0 // current chracter index within the data stream
new nFormatPosMax = strlen(sFormatString)
while ((nIndexPos < nFormatPosMax) && (nDataIndex <= nDataLen))
{
switch (sFormatString[nFormatPos])
{
case '1': // "byte"
{
//log_amx("indexed byte <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos)
aIndexes[nIndexPos] = nDataIndex
nDataIndex++
nIndexPos++
}
case '2': // "short"
{
//log_amx("indexed short <%d %d> at %d, element %d, format position %d", sData[nDataIndex], sData[nDataIndex + 1], nDataIndex, nIndexPos, nFormatPos)
aIndexes[nIndexPos] = nDataIndex
nDataIndex += 2
nIndexPos++
}
case '4': // "long"
{
//log_amx("indexed long <%d %d %d %d> at %d, element %d, format position %d", sData[nDataIndex], sData[nDataIndex + 1], sData[nDataIndex + 2], sData[nDataIndex + 3], nDataIndex, nIndexPos, nFormatPos)
aIndexes[nIndexPos] = nDataIndex
nDataIndex += 4
nIndexPos++
}
case 's': // string
{
new sDebugString[250]
arrayset(sDebugString, 0, 250)
copyc(sDebugString, 250, sData[nDataIndex], 0)
//log_amx("indexed string <%s> at %d, element %d, format position %d", sDebugString, nDataIndex, nIndexPos, nFormatPos)
aIndexes[nIndexPos] = nDataIndex
do { nDataIndex++; } while ((sData[nDataIndex] != 0) && (nDataIndex < nDataLen)) // find the end of the string by searching a 0 character
nDataIndex++
nIndexPos++
}
case '[': // byte switch and start of optional formats
{
//log_amx("indexed switch <%d> at %d, element %d, format position %d", sData[nDataIndex], nDataIndex, nIndexPos, nFormatPos)
if (sData[nDataIndex] != 1) // skip options
{
do { nFormatPos++; } while ((sFormatString[nFormatPos] != ']') && (nFormatPos < nFormatPosMax))
//log_amx("skipped optional formats, now at format position %d")
}
else
//log_amx("----------- start of optional formats -----------")
aIndexes[nIndexPos] = nDataIndex
nDataIndex++
nIndexPos++
}
case ']': // end of optional formats
{
//log_amx("----------- end of optional formats -----------")
//nDataIndex++
}
default:
nDataIndex++
}
nFormatPos++
}
//log_amx("---------------------- end of indexing ----------------------")
//log_amx("%d < %d - %d <= %d", nIndexPos, nFormatPosMax, nDataIndex, nDataLen)
return nIndexPos
}

/// <summary>Gets a byte from the element at the given index.</summary>
/// <param name="sData">The raw UDP data string that was received.</param>
/// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
/// <returns>The requested byte value.</returns>
public index_get_byte(sData[MAX_INFO_LEN], nIndex)
{
return sData[nIndex]
}

/// <summary>Gets a short from the element at the given index.</summary>
/// <param name="sData">The raw UDP data string that was received.</param>
/// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
/// <returns>The requested short value.</returns>
public index_get_short(sData[MAX_INFO_LEN], nIndex)
{
return ((sData[nIndex] << | (sData[nIndex + 1] & 0x00FF))
}

/// <summary>Gets a long from the element at the given index.</summary>
/// <param name="sData">The raw UDP data string that was received.</param>
/// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
/// <returns>The requested long value.</returns>
public index_get_long(sData[MAX_INFO_LEN], nIndex)
{
return ((sData[nIndex] << 24) | (sData[nIndex + 1] << 16) | (sData[nIndex + 2] << | (sData[nIndex + 3] & 0x000000FF))
}

/// <summary>Gets a string from the element at the given index.</summary>
/// <param name="sData">The raw UDP data string that was received.</param>
/// <param name="nIndex">The format index of the data to be requested, e.g. 3 for the third data element.</param>
/// <returns>The requested string value.</returns>
public index_get_string(sData[MAX_INFO_LEN], nIndex)
{
new aRet[MAX_INFO_LEN]
arrayset(aRet, 0, MAX_INFO_LEN)
copyc(aRet, MAX_INFO_LEN, sData[nIndex], 0)
return aRet
}

/// <summary>Handler for parsing the answers to server query packet.</summary>
/// <summary>This handler parses the UDP information answer packets from the servers that have been queried with <seealso name="query_servers"/>.</summary>
/// <seealso name="query_servers"/>
public receive_serverquery_answers()
{
new nCheckMethod = get_pcvar_num(cvar_check_method)

new sRcvBuf[MAX_INFO_LEN]
new nRcvLen
new nRecvCount
new sMap[MAX_MAP_LEN]
new sMod[MAX_NAME_LEN]
new nServerCount = 0
while (nServerCount < g_nServerCount)
{
if (!g_naServerSockets[nServerCount])
{
g_baServerResponding[nServerCount] = false
/*
should only happen for the g_nOwnServer

client_print(0, print_chat, "%s no socket", g_saServerNames[nServerCount])
*/
}
else
{
nRecvCount = 0
new nCmdBackup = g_naServerCmdBackup[nServerCount]
g_baServerResponding[nServerCount] = false
new nSocket = g_naServerSockets[nServerCount]
while (socket_change(nSocket, 1) && (nRecvCount <= nCmdBackup))
{
// socket debug
//log_amx("socket changed: %i (%s)", nSocket, g_saServerNames[nServerCount])
nRecvCount++

// initialize our receive buffer
setc(sRcvBuf, MAX_INFO_LEN, 0);
// socket debug
//log_amx("receiving from socket: %i (%s)", nSocket, g_saServerNames[nServerCount])
nRcvLen = socket_recv(nSocket, sRcvBuf, MAX_INFO_LEN)
// socket debug
//log_amx("finished receiving from socket %i (%s), received %i bytes", nSocket, g_saServerNames[nServerCount], nRcvLen)

//TODO: handle fragmented packets

if (nRcvLen > 5) // shortest reply is a ping response with length of 6
{
if (nCheckMethod == 1)
{
// ping response
if (equal(sRcvBuf, {-1,-1,-1,-1,'j'}, 5))
{
g_baServerResponding[nServerCount] = true
break
}
}
else if (nCheckMethod == 2)
{
new aIndexes[MAX_INFO_FORMAT]
if (equal(sRcvBuf, {-1,-1,-1,-1}, 4))
{
g_baServerResponding[nServerCount] = true
if (sRcvBuf[4] == 'm') // old HL1 or "goldsource" protocol
{
index_create(sRcvBuf, nRcvLen, A2S_INFO_GOLD_REPLY_FORMAT, aIndexes)
copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_GOLD_IDX_MAP]], 0)
g_saServerMap[nServerCount] = sMap
copyc(sMod, MAX_NAME_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_GOLD_IDX_GAMEDIR]], 0)
g_saServerMod[nServerCount] = sMod
g_naServerProtocol[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_VERSION])
g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_NUMPLAYERS])
g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_MAXPLAYERS])
if (get_pcvar_num(cvar_countbots) == 0)
{
if (index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_ISMOD]) == 1)
g_naServerActivePlayers[nServerCount] -= index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_MOD_NUMBOTS])
else
g_naServerActivePlayers[nServerCount] -= index_get_byte(sRcvBuf, aIndexes[A2S_INFO_GOLD_IDX_NUMBOTS])
}
}
else if (sRcvBuf[4] == 'I') // source protocol
{
index_create(sRcvBuf, nRcvLen, A2S_INFO_SOURCE_REPLY_FORMAT, aIndexes)
copyc(sMap, MAX_MAP_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_SOURCE_IDX_MAP]], 0)
g_saServerMap[nServerCount] = sMap
copyc(sMod, MAX_NAME_LEN - 1, sRcvBuf[aIndexes[A2S_INFO_SOURCE_IDX_GAMEDIR]], 0)
g_naServerProtocol[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_VERSION])
g_saServerMod[nServerCount] = sMod
g_naServerActivePlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_NUMPLAYERS])
g_naServerMaxPlayers[nServerCount] = index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_MAXPLAYERS])
if (get_pcvar_num(cvar_countbots) == 0)
{
g_naServerActivePlayers[nServerCount] -= index_get_byte(sRcvBuf, aIndexes[A2S_INFO_SOURCE_IDX_NUMBOTS])
}
}
}
}
}
}
/*
if (nRecvCount == 0)
log_amx("no change on socket %i (%s)", g_naServerSockets[nServerCount], g_saServerNames[nServerCount])
*/
//socket_close(nSocket)
//g_naServerSockets[nServerCount] = 0
}
nServerCount++
}

if (get_pcvar_num(cvar_retry) > 0)
{
// now search for players who queued themselves to be redirected
new nServer
new nPlrCnt = 0

while (nPlrCnt < g_nRetryCount)
{
nServer = g_nRetryQueue[nPlrCnt][1]
if (nServer > -1) // just to be sure
{
new nPlr = g_nRetryQueue[nPlrCnt][0]
if (can_redirect_player(nServer, nPlr, 2, false) == 0)
{
console_print(nPlr, "%s: %L", PLUGIN_TAG, nPlr, "MSG_RETRY_SUCCESS")
#if defined STATISTICS
stats_redirect(STATS_INFO_REDIRECT, nPlr, STATS_REDIRTYPE_QUEUED, nServer)
#endif // STATISTICS
redirect(nPlr, nServer, false, false, true)
g_naServerActivePlayers[nServer]++
}
}
nPlrCnt++
}
}

return PLUGIN_HANDLED
}

/// <summary>Retrieves number of bots currently on the server.</summary>
/// <returns>Number of bots currently on server.</returns>
public get_bot_count()
{
new nBotCount = 0
new const nMaxPlayers = get_maxplayers()
for (new nCount = 0; nCount <= nMaxPlayers; ++nCount)
{
// "We don't really support get_players() with flags anymore. It was a bad idea and if it was our choice, it would have never been added to the original AMX Mod." - BAILOPAN (http://www.amxmodx.org/funcwiki.php?go=func&id=174)
// ok, so instead of using get_players() with flag c we will rather check each player with is_user_bot()
if (is_user_bot(nCount))
nBotCount++
}
return nBotCount
}

/// <summary>Retrieves number of admins currently on the server.</summary>
/// <returns>Number of admins currently on server.</returns>
public get_admin_count()
{
new nPlayers[MAX_PLAYERS]
new nPlayerNum, nPlayerCount
get_players(nPlayers, nPlayerNum, "ch")
new nAdmins = 0
for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
{
if (access(nPlayers[nPlayerCount], MIN_ADMIN_LEVEL))
nAdmins++
}
return nAdmins
}

/// <summary>Retrieves number of current players being aware of the redirect_countbots CVAR.</summary>
/// <returns>The number of current players being aware of the redirect_countbots CVAR.</returns>
public get_current_players()
{
new nCurrentPlayers = get_playersnum(1)
if (get_pcvar_num(cvar_countbots) == 0)
nCurrentPlayers -= get_bot_count()
return nCurrentPlayers
}

/// <summary>Event handler for client disconnect event.</summary>
/// <summary>This handler makes sure people that have been in queue while disconnecting are removed from it.</summary>
/// <summary>Furthermore it resets the "last server" information for this now empty player slot.</summary>
/// <param name="id">Slot ID of player that was disconnected.</param>
public client_disconnect(id)
{
queue_remove(id, -1)
g_nLastServer[id - 1] = -1
}

/// <summary>Event handler for client authorized event.</summary>
/// <summary>This handler is called as soon as a connecting client was authenticated with WON/Steam system and received a WON/Steam ID.</summary>
/// <summary>It is used in favor of client_connected(), because here the client already logged in to AMXX user system and it can be determined whether the user is an admin, which is not the case for client_connected() event.</summary>
/// <param name="id">Slot ID of player that was authorized.</param>
public client_authorized(id)
{
if (is_user_bot(id) || is_user_hltv(id))
return PLUGIN_CONTINUE

if ((g_nOwnServer == -1) && (!g_bInitialized))
{
plugin_postinit()
}

g_naLastMenuPages[id - 1] = 1
g_naLastCategory[id - 1] = -1

new nAutoMode = get_pcvar_num(cvar_auto)
if (get_pcvar_num(cvar_active) == 1)
{
if (nAutoMode > 0)
{
if (((get_maxplayers() - get_playersnum(1)) == 0) || (nAutoMode > 2))
{
if (g_nServerCount > 0)
{
new bool:bLocalPriority = false
// if local slot reservation is enabled we need to check whether this is a local player
if (get_pcvar_num(cvar_localslots) == 1)
{
new sCheckAddress[MAX_IP_LEN]
get_user_ip(id, sCheckAddress, MAX_IP_LEN - 1, 1)
if (is_local_address(sCheckAddress))
bLocalPriority = true
}
new nMaxAdmins = get_pcvar_num(cvar_maxadmins)
if (nMaxAdmins == 0)
nMaxAdmins = MAX_PLAYERS
new bool:bRedirect = false // to keep some better overview assemble the if-comparison part by part in bRedirect
// redirect if automode is 1 or 2, user is no admin or is admin but there are no admin slots (disabled or max admin slots in use already)
bRedirect = bRedirect | (((nAutoMode == 1) || (nAutoMode == 2)) && ((!access(id, MIN_ADMIN_LEVEL)) || (get_pcvar_num(cvar_adminslots) == 0) || (get_admin_count() > nMaxAdmins)))
// redirect if automode is 3 or 4 and user is no admin
bRedirect = bRedirect | (((nAutoMode == 3) || (nAutoMode == 4)) && (!access(id, MIN_ADMIN_LEVEL)))
// redirect if automode is 5 or 6
bRedirect = bRedirect | ((nAutoMode == 5) || (nAutoMode == 6))
if (g_bDebug)
{
new sPlayerName[MAX_NAME_LEN]
get_user_name(id, sPlayerName, MAX_NAME_LEN - 1)
log_amx("Auto-redirect check for <%s> (%d), auto-redirect: %s, automode: %d, local priority: %s, admin: %s, admin slots: %s, admins/max: %d/%d, current players/bots/max: %d/%d/%d", sPlayerName, id, bRedirect ? "yes" : "no", nAutoMode, bLocalPriority ? "yes" : "no", access(id, MIN_ADMIN_LEVEL) ? "yes" : "no", (get_pcvar_num(cvar_adminslots) == 1) ? "yes" : "no", get_admin_count(), nMaxAdmins, get_playersnum(1), get_bot_count(), get_maxplayers())
}
if (bRedirect)
{
//TODO: code in many parts redundant to what the redirect() function does except for the local-priority stuff - rather extend the redirect() function
if (bLocalPriority)
{
// find the remote user that is connected for the shortest time and redirect him

new nPlayers[MAX_PLAYERS]
new nPlayerNum, nPlayerCount
new nMinConnectedTime = 0x7FFFFFFF // make sure the first time value found will always be lower
new nMinTimePlayer = -1
new nUserTime
get_players(nPlayers, nPlayerNum, "ch")
new nCurID
new sCheckPlayerAddress[MAX_IP_LEN]
for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
{
nCurID = nPlayers[nPlayerCount]
get_user_ip(nCurID, sCheckPlayerAddress, MAX_IP_LEN - 1, 1)

nUserTime = get_user_time(nCurID)
if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL)) && (!is_local_address(sCheckPlayerAddress)))
{
nMinTimePlayer = nCurID
nMinConnectedTime = nUserTime
}
}
if (nMinTimePlayer >= 0)
{
client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORLOCAL")
redirect(nMinTimePlayer, -1, true, true, true)
return PLUGIN_CONTINUE
}
else
if (g_bDebug)
log_amx("no valid redirect target to free up slot for local player %i", id)

}
else
{
redirect(id, -1, true, (nAutoMode < 3), false)
return PLUGIN_CONTINUE
}
}
else
{
// find the user that is connected for the shortest time and redirect him away

new nPlayers[MAX_PLAYERS]
new nPlayerNum, nPlayerCount
new nMinConnectedTime = 0x7FFFFFFF
new nMinTimePlayer = -1
new nUserTime
get_players(nPlayers, nPlayerNum, "ch")
new nCurID
for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
{
nCurID = nPlayers[nPlayerCount]

nUserTime = get_user_time(nCurID)
if ((nUserTime < nMinConnectedTime) && (!access(nCurID, MIN_ADMIN_LEVEL)))
{
nMinTimePlayer = nCurID
nMinConnectedTime = nUserTime
}
}
if (nMinTimePlayer >= 0)
{
client_cmd(nMinTimePlayer, "echo %s: %L", PLUGIN_TAG, nMinTimePlayer, "MSG_REDIRFORADMIN")
redirect(nMinTimePlayer, -1, true, true, true)
return PLUGIN_CONTINUE
}
else
if (g_bDebug)
log_amx("no valid redirect target to free up slot for admin %i", id)
}

}
}
else
{
if (g_bDebug)
{
new sPlayerName[MAX_NAME_LEN]
get_user_name(id, sPlayerName, MAX_NAME_LEN - 1)
log_amx("Not auto-redirecting <%s> (%d), automode: %d, current players/bots/max: %d/%d/%d", sPlayerName, id, nAutoMode, get_playersnum(1), get_bot_count(), get_maxplayers())
}
}
}
}

new sSourceServer[4] // maximum is 999 servers, so we have a maximum of 3 digits
get_user_info(id, "xredir", sSourceServer, 3)
if (strcmp(sSourceServer, "") != 0)
{
new nSourceServer = str_to_num(sSourceServer)

// show the welcome message delayed to that player
new sID[1]
sID[0] = id
set_task(20.0, "welcome_message", 0, sID, 1)

if (g_bUseIds && (nSourceServer != -1))
g_nLastServer[id - 1] = g_naServerIds[nSourceServer]
else
g_nLastServer[id - 1] = nSourceServer
#if defined VAULT
stats_count(STATS_COUNT_REDIRECTED, g_nLastServer[id - 1])
#endif // VAULT
if (g_bDebug)
log_amx("saved last server for player %i as server %i", id, g_nLastServer[id - 1])

if ((nSourceServer >= 0) && (nSourceServer < g_nServerCount))
{
if (get_pcvar_num(cvar_show) == 1)
{
new nPlayers[MAX_PLAYERS]
new nPlayerNum, nPlayerCount, nCurrentPlayer
new sConnectNick[MAX_NAME_LEN]
get_user_name(id, sConnectNick, MAX_NAME_LEN - 1)
get_players(nPlayers, nPlayerNum, "c")
set_hudmessage(000, 100, 255, get_pcvar_float(cvar_announce_alivepos_x), get_pcvar_float(cvar_announce_alivepos_y), 0, 0.0, 10.0, 0.5, 0.10, 1)
for (nPlayerCount = 0; nPlayerCount < nPlayerNum; nPlayerCount++)
{
nCurrentPlayer = nPlayers[nPlayerCount]
client_print(nCurrentPlayer, print_chat, "%s: %L", PLUGIN_TAG, nCurrentPlayer, "MSG_REDIRECT_RECEIVE", sConnectNick, g_saServerNames[nSourceServer])
}
}
}

client_cmd(id, "setinfo ^"xredir^" ^"^"")
client_cmd(id, "setinfo ^"password^" ^"^"")

set_task(10.0, "reset_info", 0, sID, 1)
}
return PLUGIN_CONTINUE
}


/// <summary>This function shows a message to the player that has connected, to tell him that he was redirected and how he can use /retry to get back (if so).</summary>
/// <summary>welcome_message is called with a set_task to show the welcome message delayed, so that the player has usually already chosen a team and his screen is clear to read it.</summary>
/// <summary>This message is only displayed to players that have been redirected from another server in the chain. If redirect_retry is enabled, it also tells the player</summary>
/// <summary>that he can use /retry command to have himself queued to redirect back to the source server.</summary>
/// <param name="id">The slot ID of the player that should have the welcome message displayed. It is passed as array, because it is called with set_task.</param>
public welcome_message(id[])
{
new nID = id[0]
if (is_user_connected(nID)) // make sure the player didn't already disconnect within the set_task delay
{
new nLastServer = g_nLastServer[nID - 1]
if ((nLastServer >= 0) && (nLastServer != g_nOwnServer) && (nLastServer < MAX_SERVERFORWARDS))
{
new sAnnounceText[MAX_WELCOME_LEN]
formatex(sAnnounceText, MAX_WELCOME_LEN - 1, "%L", nID, "MSG_REDIRFROM", g_saServerNames[g_nOwnServer], g_saServerNames[nLastServer])
if ((get_pcvar_num(cvar_retry) == 1) && (get_pcvar_num(cvar_show) == 1))
format(sAnnounceText, MAX_WELCOME_LEN - 1, "%s^n%L", sAnnounceText, nID, "MSG_RETRY_BACK_ANNOUNCE")

set_hudmessage(000, 100, 255, -1.0, -1.0, 0, 0.0, 10.0, 0.5, 2.0, 1)
show_hudmessage(nID, sAnnounceText)
}
}
}

#if defined SQL
public sql_connect()
{
hSqlInfo = SQL_MakeStdTuple()
hSql = SQL_Connect(hSqlInfo, nSqlError, sSqlError, MAX_SQL_ERROR_LEN-1)

if (hSql == Empty_Handle)
{
log_amx("%s %L", PLUGIN_TAG, LANG_SERVER, "SQL_CANT_CON", sSqlError)
}
}

public sql_disconnect()
{
SQL_FreeHandle(hSql)
SQL_FreeHandle(hSqlInfo)
}
#endif // SQL


#else

/// <summary>Dummy handler to catch the case where a user tried to compile the plugin with a too old compiler.</summary>
public plugin_init()
{
log_amx("ERROR: Your AMXX version is too old for this plugin. You need at least version 1.80", )
}
#endif
Hi are sql connection but i not see we i can put sql data ?
Meybi sambady can edit plugin ?
__________________
homepage:steam.lv
topsites:top.steam.lv
FLOY is offline
Send a message via Skype™ to FLOY