/*
	Plugin generated by AMXX-Studio
	European Natural Selection League Plugin
	
	Author: Ari "jiriki" Timonen
	
	Credits:
	- DarkSnow's socket example
	- CAL Features by CAL-ns|Austin (xHomicide) and Jim "JazzX" Olson
	- Asmodee for waypoint fixes & weaponfix & other fixes
	- Multivac's Jetpack FIX
	- sawce's icons plugin
	- www.nsmod.org
	- www.amxmodx.org
	
	TODO:
	SQLx threaded
	-> gather early joining block
	/mercsok not working?
	amx_enslmove not working
	AFK kicker
*/

#include <amxmodx>
#include <amxmisc>
#include <sockets>
#include <fakemeta>
#include <fakemeta_stocks>
#include <engine>
#include <ns>
#include <fun>
#include <nsteam>


/**************************************************************************************
 * Global constants
 **************************************************************************************/

// Basic constants
#define PLUGIN "ENSL"	
#define VERSION "1.3-b3"
#define AUTHOR "jiriki"
#define	WEB "www.ensl.org"
#define CHANNEL "#ENSL"
#define PASSWORD "europe"

// AMX constants
#define A_LEN_CMD 32						// Maximum length of command name
#define A_LEN_CMDINFO 100					// Maximum length of command info

// Global settings
#define G_FREQ 5.0						// Global command delay

// User tracking
#define U_NUM_IDS 32						// Maximum number of users to track

// Task ID's
#define T_ADVERT 26
#define	T_MAPBOOT 27						// Map boot
#define T_JPCHECK 28						// Jetpack check
#define T_STARTUP 29						// Startup
#define T_AUTH 30						// Authentication
#define T_OPTIONS 31						// Optionchecking
#define T_RATECHECK 32						// Ratechecking
#define T_FFCLOCK 33						// Forfeit clock
#define T_HLTV 36						// Query: HLTV
#define T_ESI 37						// Query: ESI
#define T_AREQ 38						// Query: Admin Request
#define T_BAN 39						// Query: Global Ban 
#define T_USER 100						// Query: User

// HL/NS-related
#define HL_NUM_PLAYERS 32					// Maximum number of players
#define HL_NUM_COLUMN 10					// Maximum number of columns	
#define HL_LEN_NICK 31						// Maximum length of player nick
#define HL_LEN_STEAMID 31					// Maximum length of SteamID
#define HL_LEN_IP 15						// Maximum length of IP address
#define HL_LEN_PORT 5						// Maximum length of server port
#define HL_LEN_ADDR 21						// Maximum length of server address
#define HL_LEN_DNS 50						// Maximum length of DNS
#define HL_LEN_TEAM 15						// Maxmium length of teamname
#define HL_LEN_COLUMN 20					// Maxmium length of column
#define HL_LEN_SAY 191						// Maximum length of say msg
#define HL_LEN_MSG 140						// Maximum length of console msg
#define HL_LEN_POPUP 179					// Maxmium length of popup message
#define HL_LEN_HUDMESSAGE 478					// Maxmium length of HUD message
#define HL_LEN_MAP 25						// Maximum length of mapname
#define HL_LEN_CMD 31						// Maximum length of cmdname
#define HL_LEN_INFO 127						// Maximum length of cmdinfo
#define HL_LEN_PWD 20						// Maximum length of password
#define HL_ENT_MARINES 78					// Marine team joining entity
#define HL_ENT_ALIENS 77					// Alien --

// Help
#define H_NUM_CVARS 12						// Number of CVARs

// ENSL query
#define W_IP "78.46.36.107"					// Script IP
#define W_HOST "www.ensl.org"					// Script hostname
#define W_PORT 80						// Script port
#define W_TIMEOUT 1						// HTTP Timeout
#define W_READS 10						// Read attempts
#define W_NEWLINE 13						// Newline character code
#define W_NUM_LINES 30						// Number of script's output lines
#define W_LEN_URL 230						// Maximum length of query's URL
#define W_LEN_QUERY 250						// Maximum length of query
#define W_LEN_INPUT 2048					// Maximum length of input buffer
#define W_LEN_MARK 10						// Maximum length of start mark
#define W_LEN_LINE 160						// Maximum length of output line
#define W_LEN_CH 10						// Maximum length of challenge code
#define W_LEN_FUNC 30						// Maximum length of handl. func.
#define W_MARK "#RESPONSE#"					// General response mark

// ENSL Server Information
#define S_URL "http://www.ensl.org/plugin/esi"			// Script URL (ensl server info.)
#define S_MARK "#ESI#"						// Mark for found http line
#define	S_NUM_LINES 4						// Number of data lines (ex. chal.)
#define S_LEN_TS 10						// Maximum length of timestamp

// ENSL Admin request
#define AR_URL "http://www.ensl.org/plugin/admin"		// Script URL (adminrequest)
#define AR_LEN_MSG 100						// Maximum length of timestamp

// ENSL Global Bans
#define B_URL "http://www.ensl.org/plugin/ban/"			// Script URL (global ban)
#define B_LEN_MSG 50						// Maximum length of ban reason

// ENSL HLTV
#define HT_URL "http://www.ensl.org/plugin/hltv_"		// Script URL (HLTV)
#define HT_MARK "#HLTV#"					// Mark for found http line
#define	HT_NUM_LINES 1						// Number of data lines (ex. chal.)
#define HT_LEN_RESPONSE 160					// Maximum length of ENSL Servers
#define HT_LEN_DEMO 40						// Maximum length of demo name

// ENSL playerinfo
#define E_URL "http://www.ensl.org/plugin/user/"		// Script URL (user query)
#define E_MARK "#USER#"						// Mark for found HTTP line
#define	E_NUM_LINES 11						// Number of data lines (ex. chal.)
#define E_LEN_NICK 50						// Maximum length of username
#define E_LEN_IP 15						// Maximum length of IP
#define E_LEN_TEAM 50						// Maximum length of teamname
#define E_LEN_LEVEL 10						// Maximum length of level
#define E_LEN_RANK 10						// Maximum length of rank
#define E_LEN_IDS 10						// Maximum length of ID's
#define E_LEVEL_USER "User"					// User level: user
#define E_LEVEL_DONOR "Donor"					// User level: donor
#define E_LEVEL_REFEREE "Referee"				// User level: referee
#define E_LEVEL_ADMIN "Admin"					// User level: admin
#define E_LEVEL_NOTFOUND "Not Found"				// User level: not found
#define E_RANK_MEMBER "Member"					// User level: member
#define E_RANK_DEPUTEE "Deputee"				// User level: deputee
#define E_RANK_LEADER "Leader"					// User level: leader
#define E_RANK_NA "N/A"						// User level: not applicable

// Confirmation code
#define CC_PASSCODE1 "9WvcZ9hX"					// First part
#define CC_PASSCODE2 "KF7L4luQ"					// Second part
#define CC_LEN_CODE 10						// Length of result
#define CC_LEN_INPUT 250					// Length of input
#define CC_LEN_MD5 33						// Length of MD5 checksum

// Variable/rate-checking
#define RC_DISABLED 0						// Ratechecking: disabled
#define RC_ENABLED_KICK 1					// Ratechecking: enabled, kicks ON
#define RC_ENABLED_WARN 2					// Ratechecking: enabled, kicks OFF
#define RC_ENABLED_FETCH 3					// Ratechecking: enabled, fetch only
#define RC_WARN_NEGATIVE 0					// Rate warning: yes
#define RC_WARN_POSITIVE 1					// Rate warning: on
#define RC_WARN_REQUEST	2					// Rate warning: requested
#define RC_LEN_RANGE 20						// Maximum length of range string

// Server CVARs
#define C_LEN_NAME 30						// Maximum length of CVAR name
#define C_NUM_INTS 41						// Number of integer CVAR's
#define C_NUM_FLOATS 2						// Number of float CVAR's
#define C_LEVEL_MANDATORY 0					// Importance: mandatory
#define C_LEVEL_VOLUNTARY 1					// Importance: voluntary

// Referee commands
#define R_LEN_KICKREASON 30					// Maximum length of kick reason
#define R_LEN_MINS 3						// Maximum length of ban time (mins)
#define R_LEN_NUM 6						// Maximum length of a number value
#define R_MAX_MINS 180						// Maximum ban time (mins)

// Icons
#define I_AUTH_ICON 512						// Msg Type: custom icon
#define I_AUTH_NORMAL 0						// Msg Type: normal icon
#define I_LEN_ICON 35						// Maximum length of icon name		
#define I_LEN_PATH 255						// Maximum length of icon name

// Flashlight block
#define F_FREQ 0.3						// Flashlight delay
#define F_IMLUPSE 100						// Flashlight impulse number

// Timetext
#define TT_LEN_OUTPUT 40					// Maximum length of time text

// Join delay
#define DD_DELAY_DIGESTED 50					// Delay when disc. from belly

// Combat
#define CO_NORMAL 0						// Normal combat
#define CO_CELERESUPPLY 1					// Celeresupply combat
#define	CO_ENSL	2						// ENSL Combat
#define CO_SGSFADES 3						// SGs vs Fades
#define CO_SGSKULKS 4						// SGs vs Skulks
#define CO_LMGLERKS 5						// LMGs vs Lerks
#define CO_NUM_MODES 6						// Number of combat modes
#define CO_NUM_UPGRADES 40					// Maximum number of upgrades

// Forfeit counter
#define FF_TIME 600.0						// Forfeit counter time (secs)

// Unstuck
#define S_B MASK_PLAYER_STUNNED|MASK_ENSNARED|MASK_ALIEN_EMBRYO	// Blocked masks
#define S_START_DISTANCE 32					// The first search distance
#define S_NUM_ATTEMPTS 128					// Max attempts to search free spot
#define S_NUM_DISTANCE 1000					// Max unstuck spot distance

// Waypoints
#define WP_GUARD 5						// Waypoint guard


/**************************************************************************************
 * Enums
 **************************************************************************************/

enum
{
	SI_PLAYER = 1,
	SI_SCORE,
	SI_KILLS,
	SI_DEATHS,
	SI_LEVEL,
	SI_CLASS,
	SI_AUTH,
	SI_TEAM,
	SI_HEALTH,
	SI_ICON,
	SI_NUM_SCOREVARS
}

enum
{
	HL_RRSPECS = 0,
	HL_MARINES,
	HL_ALIENS,
	HL_NUM_TEAMS
}

enum
{
	CT_GENERAL = 0,
	CT_TEAMJOIN,
	CT_NUM_CMDS
}

enum
{
	W_I_ESI = 0,
	W_I_HLTV,
	W_I_AREQ,
	W_I_BAN,
	W_NUM_FUNCS
}

enum
{
	I_I_DONOR = 0,
	I_I_CHAMP,
	I_I_REF,
	I_I_ADMIN,
	I_NUM_ICONS
}

enum
{
	RC_I_RATE = 0,
	RC_I_CMDRATE,
	RC_I_UPDATERATE,
	RC_I_FPS,
	RC_NUM_VARS
}


/**************************************************************************************
 * Global variables
 **************************************************************************************/

new g_gameStarted = 0						// Game started?
new g_gameEnding = 0						// Game ending?
new g_gameModerated = 0						// Game chat moderated?
new g_gameHltvs = 0						// Number of HLTV on server

new g_pauseStatus = 0						// Game paused?
new g_pausePending = 0						// Pending pause cmd from user?
new g_pauseReq = 0						// Pause requestor?

new g_timeFetchedRemote	= 0					// Remote time when time was fetched
new Float:g_timeFetchedLocal = 0.0				// Local time when time was fetched

new g_userIndex = 0						// PID table index
new g_userCount = 0						// PID table cells used

new Float:g_flashTime[HL_NUM_PLAYERS + 1]			// List: User ID <-> last FL-time
new g_userAuthed[HL_NUM_PLAYERS + 1]				// List: User ID <-> authenticated?
new g_userIconIndex[HL_NUM_PLAYERS + 1]				// List: User ID <-> icon index
new g_userIconFlags[HL_NUM_PLAYERS + 1]				// List: User ID <-> icon flags
new Float:g_userCmdTime[HL_NUM_PLAYERS + 1][CT_NUM_CMDS]		// List: User ID <-> cmdId <-> time
new g_userCmdWarn[HL_NUM_PLAYERS + 1][CT_NUM_CMDS]		// List: User ID <-> cmdId <-> warned?
new g_userDefendWP[HL_NUM_PLAYERS + 1]				// List: User ID <-> guard waypoint
new g_userOrderType[HL_NUM_PLAYERS + 1];				// List: User ID <-> waypoint type
new g_userPidIndex[HL_NUM_PLAYERS + 1]				// List: User ID <-> PID

new g_iconName[I_NUM_ICONS][I_LEN_ICON + 1]			// Icon id <-> Icon name + color
new g_iconFlags[I_NUM_ICONS]					// Icon id <-> Icon flag

new g_userIdIndex[U_NUM_IDS]					// List: PID <-> User ID
new g_userStatus[U_NUM_IDS]					// List: PID <-> On server?
new g_userName[U_NUM_IDS][HL_LEN_NICK + 1]			// List: PID <-> Name
new g_userSteamID[U_NUM_IDS][HL_LEN_STEAMID + 1]			// List: PID <-> Steam ID
new g_userIP[U_NUM_IDS][HL_LEN_IP + 1]				// List: PID <-> IP Address
new g_userTeam[U_NUM_IDS]					// List. PID <-> Team
new g_userRCVars[U_NUM_IDS][RC_NUM_VARS]			// List: PID <-> var <-> value
new g_userRCWarn[U_NUM_IDS][RC_NUM_VARS]			// List: PID <-> var <-> warned?
new g_userWebSocket[U_NUM_IDS]					// List: PID <-> web socket
new g_userWebHex[U_NUM_IDS][CC_LEN_CODE + 1]			// List: PID <-> web hex challenge
new g_userWebReads[U_NUM_IDS]					// List: PID <-> web reads
new g_userWebData[U_NUM_IDS][E_NUM_LINES][W_LEN_LINE + 1]	// List: PID <-> web data
new g_userWebDataLine[U_NUM_IDS]				// List: PID <-> web data line idx
new g_userEnslInfo[U_NUM_IDS]					// List: PID <-> Exists in ENSL DB?
new g_userEnslNick[U_NUM_IDS][E_LEN_NICK + 1]			// List: PID <-> ENSL Nick
new g_userEnslIP[U_NUM_IDS][E_LEN_IP + 1]			// List: PID <-> ENSL IP
new g_userEnslTeam[U_NUM_IDS][E_LEN_TEAM + 1]			// List: PID <-> ENSL Team
new g_userEnslId[U_NUM_IDS]					// List: PID <-> ENSL User ID
new g_userEnslTid[U_NUM_IDS]					// List: PID <-> ENSL Team ID
new g_userEnslLevel[U_NUM_IDS][E_LEN_LEVEL + 1]			// List: PID <-> ENSL Level
new g_userEnslRank[U_NUM_IDS][E_LEN_RANK + 1]			// List: PID <-> ENSL Rank
new g_userEnslGather[U_NUM_IDS]					// List: PID <-> ENSL Gather status
new g_userCode[U_NUM_IDS][CC_LEN_CODE + 1]			// List: PID <-> Verification code
new g_userSSPassed[U_NUM_IDS]					// List: PID <-> Deaths /wo snapshot
new g_userSSCount[U_NUM_IDS]					// List: PID <-> Deaths /w snapshot
new g_userSSRand[U_NUM_IDS]					// List: PID <-> Random SS count
new g_userDDTime[U_NUM_IDS]					// List: PID <-> Rejoin time
new g_userMerc[U_NUM_IDS]					// List: PID <-> Mercable team
new g_userGagged[U_NUM_IDS]					// List: PID <-> Gagged?
new g_userVoice[U_NUM_IDS]					// List: PID <-> Allowed to talk?
new g_teamPlayers[HL_NUM_TEAMS]					// List: Team <-> player counts
new g_teamReady[HL_NUM_TEAMS]					// List: Team <-> ready?
new g_teamNames[HL_NUM_TEAMS][HL_LEN_TEAM + 1]			// List: Team <-> name
new g_teamENSLId[HL_NUM_TEAMS]					// List: Team <-> ENSL ID
new g_teamENSLName[HL_NUM_TEAMS][E_LEN_TEAM + 1]			// List: Team <-> ENSL Name
new g_teamMReq[HL_NUM_TEAMS]					// List: Team <-> requestor merc PID

new Float:g_FFCStarted = 0.0					// Forfeit clock: time started
new g_FFCUsed = 0						// Forfeit clock: time used

new g_cvarIntIndex = 0						// Server integer CVAR index
new g_cvarIntName[C_NUM_INTS][C_LEN_NAME + 1]			// Server integer CVAR names
new g_cvarIntLevel[C_NUM_INTS]					// Server integer CVAR importance
new g_cvarIntValue[C_NUM_INTS]					// Server integer CVAR values
new g_cvarIntPCW[C_NUM_INTS]					// Server integer CVAR PCW value
new g_cvarFltIndex = 0						// Server float CVAR index
new g_cvarFltName[C_NUM_FLOATS][C_LEN_NAME + 1]			// Server float CVAR names
new g_cvarFltLevel[C_NUM_FLOATS]				// Server float CVAR importance
new Float:g_cvarFltValue[C_NUM_FLOATS]				// Server float CVAR values
new Float:g_cvarFltPCW[C_NUM_FLOATS]				// Server float CVAR PCW value

new g_coNum[CO_NUM_MODES]					// Combat: mode <-> numbr. of blocks
new g_coBlocked[CO_NUM_MODES][CO_NUM_UPGRADES]			// Combat: mode <-> blocked impulses

new g_refSSUser = -1						// PID of the next ref SS target

new g_webSocket[W_NUM_FUNCS]					// Function ID <-> websocket
new g_webHex[W_NUM_FUNCS][CC_LEN_CODE + 1]			// Function ID <-> hex challenge
new g_webReads[W_NUM_FUNCS]					// Function ID <-> reads
new g_webData[W_NUM_FUNCS][W_NUM_LINES][W_LEN_LINE + 1]		// Function ID <-> line ID <-> data
new g_webDataLine[W_NUM_FUNCS]					// Function ID <-> data line pointer

new g_msgId_SayText						// MsgID: SayText
new g_msgId_Scoreboard						// MsgID: Scoreboard
new g_msgId_Waypoint						// MsgID: Waypoint
new g_msgId_DeathMsg = 0;					// MsgID: Death

new g_maxPlayers = 0;						// Maxplayers
new g_mapBootMins = 0						// Map boot cycles


/**************************************************************************************
 * CVARs
 **************************************************************************************/
 
new g_cvar_minrate
new g_cvar_maxrate
new g_cvar_mincmdrate
new g_cvar_maxcmdrate
new g_cvar_minupdaterate
new g_cvar_maxupdaterate
new g_cvar_minfps
new g_cvar_maxfps
new g_cvar_checkids
new g_cvar_checkrates
new g_cvar_snapshots
new g_cvar_ffclock
new g_cvar_merclimit
new g_cvar_teamlimit
new g_cvar_speclimit
new g_cvar_refaccess
new g_cvar_combatmode
new g_cvar_membersonly

new const g_cvarHelp[H_NUM_CVARS][HL_LEN_MSG + 1] =
{
	"ensl_minrate & ensl_maxrate: Minimum and maximum rate allowed for players",
	"ensl_mincmdrate & ensl_maxcmdrate: Minimum and maximum cl_cmdrate allowed for players.",
	"ensl_minupdaterate & ensl_maxupdaterate: Minimum and maximum cl_updaterate allowed for players.",
	"ensl_checkids [0 = off, 1 = on]: Player ID fetching from ENSL database.",
	"ensl_snapshots [0 = off, 1 = on]: Player snapshots on death.",
	"ensl_checkrates [0 = off, 1 = on+kick, 2 = on+warning, 3 = fetch only]: Ratechecking",
	"ensl_ffclock [0 = off, 1 = on]: Forfeit clock triggered by ready.",
	"ensl_merclimit [0 = off, 1 = on]: Merclimiting prevents players in dfferent ENSL team to join a team.",
	"ensl_teamlimit [num]: Allows teams to play with only given number of players.",
	"ensl_speclimit [0 = off, 1 = on]: Speclimiting prevents non-referees to join spectators mid-game.",
	"ensl_refaccess [0 = off, 1 = on]: Toggles usage of admin commands by referees.",
	"ensl_combatmode [0 = off, 1 = celeresupply, 2: ENSL-combat, 3: lifeforms]: Limits the usable combat upgrades."
}

	
/**************************************************************************************
 * Plugin loader functions
 **************************************************************************************/

// Initalize the plugin
public plugin_init()
{	
	// Register plugin
	register_plugin(PLUGIN, VERSION, AUTHOR)
	
	// Get Message ID's
	g_msgId_SayText = get_user_msgid("SayText")
	g_msgId_Scoreboard = get_user_msgid("ScoreInfo")
	g_msgId_Waypoint = get_user_msgid("SetOrder")
	g_msgId_DeathMsg = get_user_msgid("DeathMsg")
	
	// Globals
	g_maxPlayers = get_maxplayers();
	
	// Register CVARs
	g_cvar_minrate = register_cvar("ensl_minrate", "10000")
	g_cvar_maxrate = register_cvar("ensl_maxrate", "25000")	
	g_cvar_mincmdrate = register_cvar("ensl_mincmdrate", "50")
	g_cvar_maxcmdrate = register_cvar("ensl_maxcmdrate", "150")
	g_cvar_minupdaterate = register_cvar("ensl_minupdaterate", "40")
	g_cvar_maxupdaterate = register_cvar("ensl_maxupdaterate", "150")
	g_cvar_minfps = register_cvar("ensl_minfps", "50")
	g_cvar_maxfps = register_cvar("ensl_maxfps", "125")
	g_cvar_checkids = register_cvar("ensl_checkids", "1")
	g_cvar_checkrates = register_cvar("ensl_checkrates", "3")
	g_cvar_snapshots = register_cvar("ensl_snapshots", "0")
	g_cvar_ffclock = register_cvar("ensl_ffclock", "0")
	g_cvar_merclimit = register_cvar("ensl_merclimit", "0")
	g_cvar_teamlimit = register_cvar("ensl_teamlimit", "0")
	g_cvar_speclimit = register_cvar("ensl_speclimit", "0")
	g_cvar_refaccess = register_cvar("ensl_refaccess", "1")
	g_cvar_combatmode = register_cvar("ensl_combatmode", "1")
	g_cvar_membersonly = register_cvar("ensl_membersonly", "0")
	
	// Register basic events
	register_event("DeathMsg", "event_death", "a")
	register_event("GameStatus","event_roundend","abc","1=2")
	
	// Register scoreboard event for icons
	register_message(g_msgId_Scoreboard, "event_scoreboard")
	register_message(g_msgId_Waypoint, "func_msg_waypoint");
	register_message(g_msgId_DeathMsg, "func_msg_death");
	
	// Register team joins
	register_clcmd("jointeamone", "cmd_jointeamone")
	register_clcmd("jointeamtwo", "cmd_jointeamtwo")
	register_clcmd("readyroom", "cmd_readyroom")
	register_clcmd("impulse 5", "cmd_readyroom")
	register_clcmd("spectate", "cmd_spectate")
	register_clcmd("autoassign", "cmd_autoassign")
	register_touch("info_spectate", "player", "touch_spectate")
	register_touch("info_join_team","player","touch_teamjoin")
	register_touch("info_join_autoassign","player", "touch_autoassign")
	
	// Say commands
	register_clcmd("say", "cmd_say")
	register_clcmd("say /ensl", "cmd_ensl")
	register_clcmd("say_team /ensl", "cmd_ensl")
	register_clcmd("say /exp", "cmd_exp")
	register_clcmd("say_team /exp", "cmd_exp")
	register_clcmd("say /stuck", "cmd_unstuck")
	register_clcmd("say_team /stuck", "cmd_unstuck")
	register_clcmd("say /ok", "cmd_ok")
	register_clcmd("say_team /ok", "cmd_ok")
	register_clcmd("say /mercs", "cmd_mercs")
	register_clcmd("say_team /mercs", "cmd_mercs")
	register_clcmd("say /mercsok", "cmd_mercsok")
	register_clcmd("say_team /mercsok", "cmd_mercsok")
	register_clcmd("say /check", "cmd_check")
	register_clcmd("say_team /check", "cmd_check")
	register_clcmd("say /makeroom", "cmd_makeroom")
	register_clcmd("say_team /makeroom", "cmd_makeroom")
	register_clcmd("say ready", "cmd_ready")
	register_clcmd("say_team ready", "cmd_ready")
	register_clcmd("say notready", "cmd_notready")
	register_clcmd("say_team notready", "cmd_notready")
	register_clcmd("say /clearwp", "cmd_clearwp");
	register_clcmd("say_team /clearwp", "cmd_clearwp");
	register_clcmd("say /noweapons", "func_cmd_stripweapons");
	
	// Console commands
	register_concmd("amx_enslinfo", "cmd_enslinfo", ADMIN_USER, "[server/marines/aliens/others] (Print information)")
	register_concmd("amx_enslcfg", "cmd_enslcfg", ADMIN_LEVEL_E, "[pcw] (Load ENSL official or pcw settings)")
	register_concmd("amx_enslcvar", "cmd_enslcvar", ADMIN_LEVEL_E, "[name] [value] (Set server cvar)")
	register_concmd("amx_enslteam", "cmd_enslteam", ADMIN_LEVEL_E, "[name/#ID] [team] (Force player to team)")
	register_concmd("amx_enslkick", "cmd_enslkick", ADMIN_LEVEL_E, "[name/#ID] [bantime(mins)] [^"reason^"] (Kick/ban player)")
	register_concmd("amx_enslnicks", "cmd_enslnicks", ADMIN_LEVEL_E, "(Enforce ENSL nicks on all players)")
	register_concmd("amx_enslvoice", "cmd_enslvoice", ADMIN_LEVEL_E, "[empty=freetalk or name/#ID] (Limit talking)")
	register_concmd("amx_enslpause", "cmd_enslpause", ADMIN_LEVEL_E, "(Pause / unpause game)")
	register_concmd("amx_enslmap", "cmd_enslmap", ADMIN_LEVEL_E, "[mapname] (Change map)")
	register_concmd("amx_enslclock", "cmd_enslclock", ADMIN_LEVEL_E, "[mins] [secs] (Add time to the forfeit clock")
	register_concmd("amx_enslshot", "cmd_enslshot", ADMIN_LEVEL_E, "[name/#ID] (Force snapshot)")
	register_concmd("amx_enslsay", "cmd_enslsay", ADMIN_LEVEL_E, "[message] (Global say)")
	register_concmd("amx_enslpwd", "cmd_enslpwd", ADMIN_LEVEL_E, "[passord] (Set password)")
	register_concmd("amx_enslmerc", "cmd_enslmerc", ADMIN_LEVEL_E, "[name/#ID] (Toggle mercing)")
	register_concmd("amx_enslmove", "cmd_enslmove", ADMIN_LEVEL_E, "[#ID/ip:port] [password] (Connect everyone to another server)")
	register_concmd("amx_enslhltv", "cmd_enslhltv", ADMIN_LEVEL_E, "[matchID/demoname/stop] (Request a HLTV.)")
	register_concmd("amx_enslgag", "cmd_enslgag", ADMIN_LEVEL_E, "[name/#ID] (Prvents user from talking)")
	register_concmd("amx_enslhelp", "cmd_enslhelp", ADMIN_USER, "(Show help about ENSL Plugin commands)")
	register_concmd("amx_enslcvars", "cmd_enslcvars", ADMIN_USER, "(Show help about ENSL Plugin CVARs)")
	
	// Impulset
	register_impulse(81, "cmd_clearwp");
	
	// Special
	register_clcmd("pauseMark", "cmd_pause")
		
	// Set team names
	g_teamNames[HL_RRSPECS] = "RR/Specs"
	g_teamNames[HL_MARINES] = "Marines"
	g_teamNames[HL_ALIENS] = "Aliens"
	
	// Set server CVARs, init combat stuff, server PID and waypoints
	func_init_cvars()
	func_init_co()
	func_init_pid()
	func_init_waypoints()
	
	//  Rate / FPS checks
	set_task(10.0, "task_check_rates", T_RATECHECK, "", 0, "b", 0)
	
	// Set a task 10s after start
	set_task(10.0, "task_startup", T_STARTUP, "", 0, "a", 1)
	
	// Set a task 10s after start
	set_task(60.0, "task_mapboot", T_MAPBOOT, "", 0, "b", 0)

	// Set a task 10s after start
	set_task(300.0, "task_advert", T_ADVERT, "", 0, "b", 0)
}

// Force default WAD's
public plugin_precache()
{	
	// Force certain unmodified files
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/co_kestrel.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/hallwall_1.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns2.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_ayumi.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_bast.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_eclipse.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_eon.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_hera.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_lost.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_metal.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_nancy.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_nothing.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_shiva.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/ns_tanith.wad")
	force_unmodified(force_exactfile,{0,0,0},{0,0,0},"/v_wad.wad")
	
	// Initialize icons
	func_init_icons()
	
	// Precache custom icons
	new iconName[I_LEN_ICON + 1], shortIcon[I_LEN_ICON - 2], iconPath[I_LEN_PATH + 1]
	for ( new i = 0; i < I_NUM_ICONS; i++ )
	{
		iconName = g_iconName[i]
		copy(shortIcon, strlen(iconName) - 3, iconName)
		formatex(iconPath, I_LEN_PATH, "gfx/vgui/640_%s.tga", shortIcon)
		precache_generic(iconPath)
	}
}


/**************************************************************************************
 * Events concerning individual player
 **************************************************************************************/

// Event: player joins
public client_authorized(id)
{		
	// Authenticate the player
	func_authenticate(id)
	
	return PLUGIN_CONTINUE
}

// Event: player is connected
public client_putinserver(id)
{
	// If its a hltv, announce it
	if ( is_user_hltv(id) )
	{
		new ip[HL_LEN_IP  + HL_LEN_PORT + 2]; get_user_ip(id, ip, HL_LEN_IP  + HL_LEN_PORT + 1)
		client_print(0, print_chat, "[ENSL] HLTV (%s) has joined the server.", ip)
		g_gameHltvs++
	}
	
	// Increase RR+Spec counter
	g_teamPlayers[HL_RRSPECS]++
	
	return PLUGIN_CONTINUE
}

// Event: info changes
public client_infochanged(id)
{
	// Apply icon
	func_apply_icon(id)
	
	return PLUGIN_CONTINUE
}

// Event: player leaves
public client_disconnect(id)
{	
	// Authenticate if not already + get PID
	new pid = func_authenticate(id)
	
	// Reset variables
	g_userIconFlags[id] = 0
	g_userIconIndex[id] = -1
	g_userAuthed[id] = 0
	g_userStatus[pid] = 0
	
	// Reset rates so retrying with different rates wont get kicked for rate changing
	for ( new i = 0; i < RC_NUM_VARS; i++ )
	{
		g_userRCVars[pid][i] = 0
		g_userRCWarn[pid][i] = RC_WARN_NEGATIVE
	}
	
	// If user was being digested, user has to be out alteast 50 seconds
	if ( ns_get_mask(id, MASK_DIGESTING) )
	{
		g_userDDTime[pid] = get_systime() + DD_DELAY_DIGESTED
	}

	// Process team leave
	func_teamleave(id, g_userTeam[pid])
	g_userTeam[pid] = HL_RRSPECS
	
	// Decrease the HLTV counter if necessary
	if ( is_user_hltv(id) )
	{
		g_gameHltvs--
	}
	
	// Remove any authentication tasks
	if ( task_exists(T_USER + pid) )
	{	
		remove_task(T_USER + pid)
		socket_close(g_userWebSocket[pid])
	}
	
	// Set last username
	get_user_name(id, g_userName[pid], HL_LEN_NICK)
	
	return PLUGIN_CONTINUE
}

// Event: impulse command
public client_impulse(id, impulse)
{
	// Process flashlight
	if ( impulse == F_IMLUPSE )
	{
		// If the user is trying to use the flashlight too often, abort
		if ( get_gametime() - g_flashTime[id] < F_FREQ )
		{
			return PLUGIN_HANDLED
		}
	
		// Set the last time flashlight was used
		g_flashTime[id] = get_gametime()
	
		return PLUGIN_CONTINUE
	}
	
	// Block upgrades if celeresupply is on
	new mode = get_pcvar_num(g_cvar_combatmode)
	if ( ns_is_combat() && mode != CO_NORMAL && mode < CO_NUM_MODES )
	{	
		new coNum = g_coNum[mode]
		for ( new i = 0; i < coNum; i++ )
		{
			if ( impulse == g_coBlocked[mode][i] )
			{
				return PLUGIN_HANDLED
			}
		}
	}
	
	return PLUGIN_CONTINUE
}

// Event: player death
public event_death()
{
	// Authenticate
	new pid = func_authenticate(read_data(2))
	
	// Take a random snapshot if necessary
	if ( get_pcvar_num(g_cvar_snapshots) && func_random_snap(pid) )
	{
		func_snapshot(pid, 0)
	}
	
	return PLUGIN_CONTINUE
}


/**************************************************************************************
 * Global events
 **************************************************************************************/

// Event: round start
public round_start()
{
	new ratecheck; ratecheck = get_pcvar_num(g_cvar_checkrates)
	
	// Echo the warning if rate checking is enabled w/ kick
	if ( ratecheck == RC_ENABLED_KICK )
	{	
		client_print(0, print_chat, "[ENSL] Rate changing / breaching punishable by kick. Do not change rates.")
	}
	// Echo the warning if rate checking is enabled w/o kick
	else if ( ratecheck == RC_ENABLED_WARN )
	{	
		client_print(0, print_chat, "[ENSL] Rate changing / breaching not allowed.")
	}
	
	// Reset some player settings
	for ( new i = 0; i < g_userCount; i++ )
	{
		g_userDDTime[i] = 0
		g_userSSPassed[i] = 0
		g_userSSCount[i] = 0
	}

	// Mark game started and reset variables
	g_gameStarted = 1
	g_teamReady[HL_MARINES] = 0
	g_teamReady[HL_ALIENS] = 0
	
	// Stop the forfeit clock.
	func_stop_ffclock(1)
	
	// Init waypoints
	func_init_waypoints()
	
	return PLUGIN_CONTINUE
}

// Event: round end
public event_roundend()
{
	new playerCount, player
	new players[HL_NUM_PLAYERS]; get_players(players, playerCount, "h")

	// If screenshots are enabled, take the final shot
	if ( get_pcvar_num(g_cvar_snapshots) )
	{
		for ( new i = 0; i < playerCount; i++ )
		{
			func_snapshot(g_userPidIndex[players[i]], 1)
		}
	}
	
	// Print player info to everyone
	for ( new i = 0; i < playerCount; i++ )
	{
		player = players[i]
		func_print_players(player, HL_MARINES)
		func_print_players(player, HL_ALIENS)
		func_print_players(player, HL_RRSPECS)
	}
	
	// Reset times after snapshots
	g_gameStarted = 0
	g_gameEnding = 1
	
	// Reset some values
	for ( new i = 0; i < U_NUM_IDS; i++ )
	{
		g_userDDTime[i] = 0
		g_userMerc[i] = 0
		g_userSSCount[i] = 0
		g_userSSPassed[i] = 0
		g_userSSRand[i] = 0
	}
		
	// Reset team count, about six seconds after game-end, players are in RR
	set_task(6.0, "task_roundend", 0, "", 0, "a", 1)
	
	return PLUGIN_CONTINUE
}


/**************************************************************************************
 * Global tasks
 **************************************************************************************/

// Task: startup
public task_startup()
{
	func_get_esi()
	
	// Fix comm bug
	set_cvar_num("sv_maxspectatorspeed", 1000)
	set_cvar_num("sv_maxspeed", 4000)
}

// Task: round ends
public task_roundend()
{
	func_reset_teams()
	g_gameEnding = 0
}

// Task: map boot, fixes pgs
public task_mapboot()
{	
	if ( g_mapBootMins == 5 )
	{
		new mapname[HL_LEN_MAP + 1]; get_mapname(mapname, HL_LEN_MAP)
		server_cmd("wait; wait; wait; wait; wait; wait; wait; wait; changelevel %s", mapname)
	}
	else if ( get_playersnum() <= 2 )
	{
		g_mapBootMins++
	}
	else
	{
		g_mapBootMins = 0
	}
}

// Task: advert
public task_advert()
{	
	new playerCount = 0, target = 0
	new players[HL_NUM_PLAYERS]; get_players(players, playerCount, "h")
	
	new msg[HL_LEN_HUDMESSAGE + 1]
	
	// Print the hud message including serverside info (nick, ip etc.) + gametimes
	set_hudmessage(20, 120, 120, -1.0, 0.6, 0, 0.5, 10.0, 2.0, 2.0, 4)
	formatex(msg, HL_LEN_HUDMESSAGE, "Welcome to ENSL Server^n^n^nYou can join pick-up games^nby registering at www.ensl.org^nand joining at www.ensl.org/gather")
	
	// Check readyroom first
	for ( new i = 0; i < playerCount; i++ )
	{
		if ( nst_is_readyroom(players[i]) || nst_is_spectate(players[i])  )
		{
			target = players[i]
			show_hudmessage(target, msg)
		}
	}
}


/**************************************************************************************
 * Icons
 **************************************************************************************/

// Function: Initialize icons
public func_init_icons()
{
	g_iconName[I_I_DONOR] = "ensl_12_donor999"
	g_iconFlags[I_I_DONOR] = 1
	g_iconName[I_I_CHAMP] = "ensl_12_champ999"
	g_iconFlags[I_I_CHAMP] = 2
	g_iconName[I_I_REF] = "ensl_12_ref999"
	g_iconFlags[I_I_REF] = 4
	g_iconName[I_I_ADMIN] = "ensl_12_admin999"
	g_iconFlags[I_I_ADMIN] = 8
}
 
// Function: handle scoreboard call
public event_scoreboard()
{
	new id = get_msg_arg_int(SI_PLAYER)
		
	// Set custom icon if necessary
	if ( g_userIconIndex[id] != -1 )
	{
		set_msg_arg_int(SI_AUTH, ARG_SHORT, I_AUTH_ICON)
		set_msg_arg_string(SI_ICON, g_iconName[g_userIconIndex[id]])
	}
	
	return PLUGIN_CONTINUE	
}

// Function: enable next icon
public func_next_icon(id)
{	
	// Increase it as long as the icon is not allowed
	do
	{
		g_userIconIndex[id]--
		g_userIconIndex[id] = g_userIconIndex[id] == -2 ? I_NUM_ICONS - 1 : g_userIconIndex[id]
	}
	while ( g_userIconIndex[id] != -1 && !(g_iconFlags[g_userIconIndex[id]] & g_userIconFlags[id]) )
}

// Function: apply user's favourite icon
public func_apply_icon(id)
{
	new strIcon[E_LEN_IDS + 1]; get_user_info(id, "enslicon", strIcon, E_LEN_IDS)
	new intIcon = str_to_num(strIcon)
	
	// If icon is wanted to be default, set it
	if ( intIcon == -1 )
	{
		g_userIconIndex[id] = -1
	}
	// If the icon is set and within range
	else if ( intIcon > 0 && intIcon <= I_NUM_ICONS && (g_iconFlags[intIcon - 1] & g_userIconFlags[id]) )
	{
		g_userIconIndex[id] = intIcon - 1
	}
}


/**************************************************************************************
 * Unstucking functions
 **************************************************************************************/

 // Command: /unstuck
public cmd_unstuck(id)
{		
	// Get PID
	new pid = func_authenticate(id)
	
	// Check for too rapid action
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// Execute the unstuck after a random interval
	set_task(random_float(0.5,5.0), "task_unstuck", id)
	
	return PLUGIN_HANDLED
}
 
// Task: Unstuck a player
public task_unstuck(id)
{
	new Float:origin[3], Float:newOrigin[3], hullsize, distance
	
	// Get username
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new pid = func_authenticate(id)
	
	// Do no not unstuck when gestating etc.
	if ( ns_get_mask(id, S_B) )
	{
		client_print(id, print_chat, "[ENSL] You cannot free yourself while evolving, stunned or webbed")
		log_message("[ENSL] [unstuck] [%s:%s] [FAILED] [Webbed/Stunned/Evolving]", g_userSteamID[pid], name)
		return PLUGIN_CONTINUE
	}

	// Get hull size of the player and abort if invalid
	switch ( ns_get_class(id) )
	{
		// Small lifeforms: return the smaller hull
		case 1,2,3:
			hullsize = HULL_HEAD
			
		// Others: If the user is ducking, return the smaller hull, otherwise midsize
		case 4,6,7,8:
			hullsize = (entity_get_int(id, EV_INT_flags) & FL_DUCKING) ? HULL_HEAD : HULL_HUMAN
		
		// Onos: If the user is ducking, return the smaller hull, otherwise midsize
		case 5:
			hullsize = (entity_get_int(id, EV_INT_flags) & FL_DUCKING) ? HULL_HUMAN : HULL_LARGE
		
		// Return, print and log error if not applicable
		default:
		{
			client_print(id, print_chat, "[ENSL] You cannot free yourself at this time.")
			log_message("[ENSL] [unstuck] [%s:%s] [FAILED] [Bad Hullsize]", g_userSteamID[pid], name)
			return PLUGIN_CONTINUE	
		}
	}
	
	// Get user coordinates and init default stuck distance
	entity_get_vector(id, EV_VEC_origin, origin)
	distance = S_START_DISTANCE

	// NOTE: Distance should never be further than 1000 units
	while ( distance < S_NUM_DISTANCE )
	{	
		for ( new i = 0; i < S_NUM_ATTEMPTS; ++i )
		{
			// Generate new random coordinates
			newOrigin[0] = random_float(origin[0]-distance, origin[0]+distance)
			newOrigin[1] = random_float(origin[1]-distance ,origin[1]+distance)
			newOrigin[2] = random_float(origin[2]-distance, origin[2]+distance)
			
			// Unstuck player
			if ( trace_hull(newOrigin, hullsize, id) == 0 )
			{
				entity_set_origin(id, newOrigin)
				client_print(0, print_chat, "[ENSL] Player (%s) unstucked.", name)
				log_message("[ENSL] [unstuck] [%s:%s] [OK]", g_userSteamID[pid], name)
				return PLUGIN_CONTINUE
			}
		}
		distance += S_START_DISTANCE
	}
	
	// Echo error in case no free spot found
	client_print(id, print_chat, "[ENSL] Couldn't find a free spot to move you too.")
	log_message("[ENSL] [unstuck] [%s:%s] [FAILED] [No Free Spot]", g_userSteamID[pid], name)
	
	return PLUGIN_CONTINUE
}


/**************************************************************************************
 * Plugin informational commands
 **************************************************************************************/

// Command: say /ensl (Print plugin info)
public cmd_ensl(id)
{	
	// Authenticate
	new pid = func_authenticate(id)
	
	// Not too rapid execution
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// Print verison and settings
	client_print(id, print_chat, "[ENSL] Plugin Version %s - %s - %s on IRC.Quakenet.Org", VERSION, WEB, CHANNEL)
	
	return PLUGIN_HANDLED
}

// Command /say whois (Find user information
public cmd_whois(id, msg[HL_LEN_SAY + 1])
{
	// Authenticate
	new pid = func_authenticate(id)
	
	// Fetch arguments
	new nick[HL_LEN_NICK + 1]; copy(nick, HL_LEN_NICK, msg[7])
	new targetID = func_get_player(id, nick)
	
	// Not too rapid execution
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// If user cannot be found, abort
	if ( targetID == -1 || equal(msg[7], "") )
	{
		client_print(id, print_chat, "[ENSL] User not found.")
		return PLUGIN_HANDLED
	}
	
	// Get pid and nick
	new tPid = g_userPidIndex[targetID]
	new name[HL_LEN_NICK + 1]; get_user_name(targetID, name, HL_LEN_NICK)
	
	// Print nick and steamid
	client_print(id, print_chat, "[ENSL] Nick: %s SteamID: %s IP: %s", name, g_userSteamID[tPid], g_userIP[tPid])
	
	// Print possible ENSL information
	if ( g_userEnslInfo[tPid] )
	{
		new args[2] = {0, 0}
		args[0] = id
		args[1] = tPid
		set_task(0.1, "task_whois_ensl", 0, args, 2, "a", 1)
		set_task(0.3, "task_whois_rates", 0, args, 2, "a", 1)
	}
	
	
	return PLUGIN_HANDLED	
}

// Task: print ensl data
public task_whois_ensl(const args[])
{
	client_print(args[0], print_chat, "[ENSL] [ENSL Name: %s] [ENSL Team: %s] [ENSL IP: %s]", g_userEnslNick[args[1]], g_userEnslTeam[args[1]], g_userEnslIP[args[1]])
}

// Task: print ensl data
public task_whois_rates(const args[])
{
	client_print(args[0], print_chat, "[ENSL] Rates [Upd: %d Cmd: %d Rate: %dk]", g_userRCVars[args[1]][RC_I_UPDATERATE], g_userRCVars[args[1]][RC_I_CMDRATE], g_userRCVars[args[1]][RC_I_RATE] / 1000)
}

// Command: amx_enslinfo (Print miscellaenous information)
public cmd_enslinfo(id)
{	
	// Authenticate
	new pid = func_authenticate(id)
	
	// Not too rapid execution
	if ( func_check_cmdtime(pid, CT_GENERAL, print_console) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}

	// In-game only specs can use it
	if ( g_gameStarted && !func_is_referee(pid) )
	{
		console_print(id, "This command can be used only by referees when round is in progress.")
		return PLUGIN_HANDLED
	}

	// Check arguments
	if ( read_argc() < 2 )
	{
		console_print(id, "Please use amx_enslinfo [server/marines/aliens/others]")
		return PLUGIN_HANDLED
	}
	
	// Fetch arguments
	new function[HL_LEN_TEAM + 1]; read_argv(1, function, HL_LEN_TEAM)
	
	// Print server information when necessary
	if ( equal(function, "server") )
	{
		func_print_server(id)
	}
	// Print server information when necessary
	else if ( equal(function, "marines") )
	{
		func_print_players(id, HL_MARINES)
	}
	// Print marine information when necessary
	else if ( equal(function, "aliens") )
	{
		func_print_players(id, HL_ALIENS)
	}
	// Print alien information when necessary
	else if ( equal(function, "others") )
	{
		func_print_players(id, HL_RRSPECS)
	}
	// Or print error for wrong argument
	else
	{
		console_print(id, "Valid values: server, marines, aliens or others")
	}
	
	return PLUGIN_HANDLED
}

// Function: print server info
public func_print_server(id)
{
	// Init variables
	new iInt = 0, iFlt = 0, cMandatory = 0, cVoluntary = 0, idx = 0
	new wrongInts[C_NUM_INTS], wrongFloats[C_NUM_FLOATS]
	
	// Check server integers
	for ( new i = 0; i < C_NUM_INTS; i++ )
	{
		// If an incorrect settings is found..
		if ( get_cvar_num(g_cvarIntName[i]) != g_cvarIntValue[i] )
		{						
			// Advance the mandatory cvar violation counter
			if ( g_cvarIntLevel[i] == C_LEVEL_MANDATORY )
			{
				cMandatory++
			}
			// Or increase the voluntary cvar violation counter
			else
			{
				cVoluntary++
			}
			
			// Record it: increase index + counter
			wrongInts[iInt] = i
			iInt++
		}
	}
	
	// Check server floats
	for ( new i = 0; i < C_NUM_FLOATS; i++ )
	{
		// If an incorrect settings is found, record it and increase index + counter
		if ( get_cvar_float(g_cvarFltName[i]) != g_cvarFltValue[i] )
		{			
			// Advance the mandatory cvar violation counter
			if ( g_cvarFltLevel[i] == C_LEVEL_MANDATORY )
			{
				cMandatory++
			}
			// Or increase the voluntary cvar violation counter
			else
			{
				cVoluntary++
			}
			
			// Record it: increase index + counter
			wrongFloats[iFlt] = i
			iFlt++
		}
	}
	
	// Check server settings, mandatory ones
	if ( cMandatory > 0 )
	{
		console_print(id, "Server has %d settings in unapproved position:", cMandatory)
	
		// Print integers cvars
		for ( new i = 0; i < iInt; i++ )
		{
			idx = wrongInts[i]
			
			if ( g_cvarIntLevel[idx] == C_LEVEL_MANDATORY )
			{
				console_print(id, "%s = %d (should be %d)", g_cvarIntName[idx], get_cvar_num(g_cvarIntName[idx]), g_cvarIntValue[idx])
			}
		}
		
		// Print float cvars
		for ( new i = 0; i < iFlt; i++ )
		{
			idx = wrongFloats[i]
			
			if ( g_cvarFltLevel[idx] == C_LEVEL_MANDATORY )
			{
				console_print(id, "%s = %f (should be %f)", g_cvarFltName[idx], get_cvar_float(g_cvarFltName[idx]), g_cvarFltValue[idx])
			}
		}
	}
	
	// Check server settings, voluntary ones
	if ( cVoluntary > 0 )
	{
		console_print(id, "^nServer has %d settings in non-recommended position:", cVoluntary)
	
		// Print integers cvars
		for ( new i = 0; i < iInt; i++ )
		{
			idx = wrongInts[i]
			
			if ( g_cvarIntLevel[idx] == C_LEVEL_VOLUNTARY )
			{
				console_print(id, "%s = %d (should be %d)", g_cvarIntName[idx], get_cvar_num(g_cvarIntName[idx]), g_cvarIntValue[idx])
			}
		}
		
		// Print float cvars
		for ( new i = 0; i < iFlt; i++ )
		{
			idx = wrongFloats[i]
			
			if ( g_cvarFltLevel[idx] == C_LEVEL_VOLUNTARY )
			{
				console_print(id, "%s = %f (should be %f)", g_cvarFltName[idx], get_cvar_float(g_cvarFltName[idx]), g_cvarFltValue[idx])
			}
		}
	}
	
	// Print rules
	if ( cMandatory > 0 )
	{
		console_print(id, "^nSince there are unapproved settings, match can only be played if both teams agree (say ready).")
		console_print(id, "If either of the teams disagree, contact server admin or use another server.")
	}
	else if ( cVoluntary == 0 )
	{
		console_print(id, "All settings OK.")
	}
	else
	{
		console_print(id, "Mandatory official settings OK.")
	}
}

// Print team's player information
public func_print_players(id, team)
{	
	// Ignore if the team does't have players
	if ( g_teamPlayers[team] == 0 )
	{
		console_print(id, "No players found of the given team.")
		return
	}
		
	// Generate header
	new columns[HL_NUM_COLUMN][HL_LEN_COLUMN + 1], header[HL_LEN_MSG + 1]
	copy(columns[0], 15, "Name")
	copy(columns[1], 15, "SteamID")
	copy(columns[2], 15, "IP Address")
	copy(columns[3], 15, "ENSL Name")
	copy(columns[4], 15, "ENSL IP")
	copy(columns[5], 15, "ENSL Team")
	copy(columns[6], 15, "ENSL Level")
	copy(columns[7], 15, "Upd/Cmd/Rat/FPS")	
	func_gentable(8, 15, columns, header)
	console_print(id, "-- %s --", g_teamNames[team])
	console_print(id, "%s", header)
		
	// Print players
	for ( new p = 1; p < g_userCount; p++ )
	{
		if ( g_userStatus[p] && g_userTeam[p] == team )
		{
			func_print_player(id, p)
		}
	}
	
	// If requestor has referee access, print past players
	if ( team == HL_RRSPECS && func_is_referee(g_userPidIndex[id]) )
	{
		// Print headers
		console_print(id, "^n -- Past Players --")
		console_print(id, "%s", header)
		
		// Print players, exclude server itself
		for ( new p = 1; p < g_userCount && p < 10; p++ )
		{
			if ( !g_userStatus[p] )
			{
				func_print_player(id, p)
			}
		}
	}
}

// Function: print players' playerinfo on 1 line
public func_print_player(id, pid)
{
	new values[HL_NUM_COLUMN][HL_LEN_COLUMN + 1], output[HL_LEN_MSG + 1]
			
	// Get/generate username + rates + shots
	new name[HL_LEN_NICK + 1]
	new rates[16]; formatex(rates, 15, "%d/%d/%dk/%d", g_userRCVars[pid][RC_I_UPDATERATE], g_userRCVars[pid][RC_I_CMDRATE], g_userRCVars[pid][RC_I_RATE] / 1000, g_userRCVars[pid][RC_I_FPS])
	
	// Get username
	if ( g_userStatus[pid] )
	{
		get_user_name(g_userIdIndex[pid], name, HL_LEN_NICK)
	}
	else
	{
		copy(name, HL_LEN_NICK, g_userName[pid])
	}
	
	// Insert values
	add(values[0], 15, name, 15)
	add(values[1], 15, g_userSteamID[pid], 15)
	add(values[2], 15, g_userIP[pid], HL_LEN_IP)
	add(values[3], 15, g_userEnslNick[pid], 15)
	add(values[4], 15, g_userEnslIP[pid], E_LEN_IP)
	add(values[5], 15, g_userEnslTeam[pid], 15)
	add(values[6], 15, g_userEnslLevel[pid], E_LEN_LEVEL)
	add(values[7], 15, rates, 15)
			
	// Add info if user is CL
	if ( equal(g_userEnslRank[pid], E_RANK_LEADER, 1) )
	{
		add(values[5], E_LEN_TEAM, " (CL)", 15)
	}
	// Or deputee	
	else if ( equal(g_userEnslRank[pid], E_RANK_DEPUTEE, 1) )
	{
		add(values[5], E_LEN_TEAM, " (D)", 15)
	}
	
	// Print output
	func_gentable(8, 15, values, output)
	console_print(id, output)
}


/**************************************************************************************
 * Combat stuff
 **************************************************************************************/

// Command: say /exp (giv exp)
public cmd_exp(id, level, cid)
{
	// Authenticate the player
	new pid = func_authenticate(id)
	
	// Not too rapid execution
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	new mode = get_pcvar_num(g_cvar_combatmode)
	if ( ns_is_combat() && mode > CO_ENSL )
	{
		ns_set_exp(id, 9999.0)
	}
	return PLUGIN_HANDLED
}


/**************************************************************************************
 * Admin request
 **************************************************************************************/

// Function: say /ensl [msg] (Send ENSL admin request)
public cmd_adminrequest(id, msg[HL_LEN_SAY + 1])
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Prevent multirun
	if ( task_exists(T_AREQ) )
	{
		client_print(id, print_chat, "[ENSL] Admin request already running, please wait and try again.")
		return PLUGIN_HANDLED
	}
	
	// Fetch arguments
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new nameParsed[HL_LEN_NICK + 1]; func_urlencode(name, nameParsed, HL_LEN_NICK)
	new addr[HL_LEN_ADDR + 1]; get_user_ip(0, addr, HL_LEN_ADDR)
	new pwd[HL_LEN_PWD + 1]; get_cvar_string("sv_password", pwd, HL_LEN_PWD)
	new msgParsed[HL_LEN_SAY + 1]; func_urlencode(msg[7], msgParsed, HL_LEN_SAY)
	new url[W_LEN_URL + 1]; formatex(url, W_LEN_URL, "%s?addr=%s&pwd=%s&msg=%s&player=%s&user=%d", AR_URL, addr, pwd, msgParsed, nameParsed, g_userEnslId[pid])

	// Schedule the query function
	set_task(0.5, "task_web_areq", T_AREQ, url, W_LEN_URL)
	client_print(id, print_chat, "[ENSL] Admin request sent.")

	return PLUGIN_HANDLED	
}

// Task: Send admin request
public task_web_areq(url[W_LEN_URL + 1])
{
	func_write_web(g_webSocket[W_I_AREQ], url)
	socket_close(g_webSocket[W_I_AREQ])
}

 
/**************************************************************************************
 * Miscellaeonus referee commands
 **************************************************************************************/

// Command: amx_enslmap [mapname] (change map)
public cmd_enslmap(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Fetch arguments
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new mapname[HL_LEN_MAP + 1]; read_argv(1, mapname, HL_LEN_MAP)
	
	// Check if the map is valid
	if ( !is_map_valid(mapname) )
	{
		console_print(id, "Map not found: %s", mapname)
		return PLUGIN_HANDLED
	}
	
	// Change map
	client_print(0, print_chat, "[ENSL] Referee (%s) changed map to %s", name, mapname)
	log_message("[ENSL] [enslmap] [%s:%s] [%s]", g_userSteamID[pid], name, mapname)
	server_cmd("wait; wait; wait; wait; wait; wait; wait; wait; changelevel %s", mapname)
	
	return PLUGIN_HANDLED
}

// Command: amx_enslteam [name/#ID] [team] (Force player to a team)
public cmd_enslteam(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 2) )
	{
		return PLUGIN_HANDLED
	}
	
	// Fetch arguments
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new target[HL_LEN_NICK + 1]; read_argv(1, target, HL_LEN_NICK); remove_quotes(target)
	new team[HL_LEN_TEAM +1]; read_argv(2, team, HL_LEN_TEAM); remove_quotes(team)
	new targetID = func_get_player(id, target)
		
	// If user cannot be found, abort
	if ( targetID == -1 || equal(target, "") )
	{
		return PLUGIN_HANDLED
	}
	
	// Get target user's name and PdI
	new targetName[HL_LEN_NICK + 1]; get_user_name(targetID, targetName, HL_LEN_NICK)
	new targetPid = func_authenticate(targetID)
	
	// If team is marines..
	if ( equal(team, g_teamNames[HL_MARINES]) )
	{
		engclient_cmd(targetID, "jointeamone")
		client_print(0, print_chat, "[ENSL] Referee (%s) enforced player %s to join marines.", name, targetName)
	}
	// If team is aliens..
	else if ( equal(team, g_teamNames[HL_ALIENS]) )
	{
		engclient_cmd(targetID, "jointeamtwo")
		client_print(0, print_chat, "[ENSL] Referee (%s) enforced player %s to join aliens.", name, targetName)
	}
	// If team is random..
	else if ( equal(team, "random") )
	{	
		// Get random team. Don't trust autoassign.
		if ( random(2) == 0 )
		{
			engclient_cmd(targetID, "jointeamone")
		}
		else
		{
			engclient_cmd(targetID, "jointeamtwo")
		}
		
		// Output
		client_print(0, print_chat, "[ENSL] Referee (%s) enforced player %s to join random team.", name, targetName)
	}
	// Instructions for lowsy arguments
	else
	{
		console_print(id, "Please give either Marines, Aliens or random.")
	}
	
	// Log event
	log_message("[ENSL] [enslteam] [%s:%s] [%d:%s] [%s]", g_userSteamID[pid], name, g_userSteamID[targetPid], targetName, team)
		
	return PLUGIN_HANDLED	
}

// Command: amx_enslkick [name/#ID] [bantime(min)] [reason] (kick user)
public cmd_enslkick(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Prevent multirun
	if ( task_exists(T_BAN) )
	{
		client_print(id, print_chat, "[ENSL] Ban being processed, please wait.")
		return PLUGIN_HANDLED
	}
	
	// Fetch arguments
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new target[HL_LEN_NICK + 1]; read_argv(1, target, HL_LEN_NICK); remove_quotes(target)
	new sMins[R_LEN_MINS +1]; read_argv(2, sMins, R_LEN_MINS); remove_quotes(sMins)
	new reason[R_LEN_KICKREASON + 1]; read_argv(3, reason, R_LEN_KICKREASON); remove_quotes(reason)
	new mins = str_to_num(sMins)
	new targetID = func_get_player(id, target)
	
	// Parse reason for security purposes
	func_parse_string(reason, R_LEN_KICKREASON)
	
	// If user cannot be found, abort
	if ( targetID == -1 || equal(target, "") )
	{
		return PLUGIN_HANDLED
	}
	
	// Get user ID
	new targetName[HL_LEN_NICK + 1]; get_user_name(targetID, targetName, HL_LEN_NICK)
	new targetUID = get_user_userid(targetID)
	new targetPid = func_authenticate(targetID)
	
	// Ban player if minutes specified
	if ( mins > 0 )
	{
		// Ban only for maximum minutes
		mins = mins > R_MAX_MINS ? R_MAX_MINS : mins
				
		// Generate variables
		new addr[HL_LEN_ADDR + 1]; get_user_ip(0, addr, HL_LEN_ADDR)
		new eReason[HL_LEN_SAY + 1]; func_urlencode(reason, eReason, HL_LEN_SAY)
		new ts = func_get_time()
		new chal[CC_LEN_INPUT + 1]; formatex(chal, CC_LEN_INPUT, "%s%d", g_userSteamID[pid], ts)
		new sign[CC_LEN_CODE + 1]; func_crypt(chal, sign)
		new url[W_LEN_URL + 1]; formatex(url, W_LEN_URL, "%s%s?len=%d&user=%d&addr=%s&reason=%s&ts=%d&sign=%s", B_URL, g_userSteamID[targetPid], mins, g_userEnslId[pid], addr, eReason, ts, sign)
		
		// Schedule the sending and reading function
		set_task(0.5, "task_web_ban", T_BAN, url, W_LEN_URL)
		
		// Ban player
		server_cmd("banid %d #%d; wait; kick #%d ^"BANNED: %s^"", mins, targetUID, targetUID, reason)
		client_print(0, print_chat, "[ENSL] Referee (%s) banned player %s for %d minutes", name, targetName, mins)
		log_message("[ENSL] [enslban] [%s:%s] [%d:%s] [%sm] [%s]", g_userSteamID[pid], name, g_userSteamID[targetPid], targetName, mins, reason)
	}
	// Otherwise just kick the player
	else
	{
		server_cmd("kick #%d ^"%s^"", targetUID, reason)
		client_print(0, print_chat, "[ENSL] Referee (%s) kicked player %s", name, targetName)
		log_message("[ENSL] [enslkick] [%s:%s] [%d:%s] [%s]", g_userSteamID[pid], name, g_userSteamID[targetPid], targetName, reason)
	}
	
	return PLUGIN_HANDLED	
}


// Task: Send ban query
public task_web_ban(url[W_LEN_URL + 1])
{
	func_write_web(g_webSocket[W_I_BAN], url)
	socket_close(g_webSocket[W_I_BAN])
}

// Function: enforce real nicks
public cmd_enslnicks(id, level, cid)
{
	// Authenticate user
	func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 0) )
	{
		return PLUGIN_HANDLED
	}
	
	// Go through all players
	new name[HL_LEN_NICK + 1]
	for ( new i = 1; i < g_userCount; i++ )
	{
		// If player is on server
		if ( g_userStatus[i] )
		{
			// Get player name
			name = "^0"; get_user_name(g_userIdIndex[i], name, HL_LEN_NICK)
			
			// If the name is wrong, fix it
			if ( g_userEnslInfo[i] && containi(name, g_userEnslNick[i]) == -1 )
			{
				client_cmd(g_userIdIndex[i], "name ^"%s^"", g_userEnslNick[i])
			}
		}
	}
	
	// Announce
	name = "^0"; get_user_name(id, name, HL_LEN_NICK)
	client_print(0, print_chat, "[ENSL] Referee (%s) enforced ENSL nicks.", name)
	
	return PLUGIN_HANDLED
}

// Function: amx_enslsay [message] (Global say)
public cmd_enslsay(id, level, cid)
{
	// Authenticate user
	func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Say colored message
	new msg[HL_LEN_SAY + 1]; read_args(msg, HL_LEN_SAY); remove_quotes(msg)
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	
	// Print the message
	client_print(0, print_chat, "%s : %s", name, msg[1])
	
	return PLUGIN_HANDLED
}

// Function: amx_enslpwd [password] (Set password)
public cmd_enslpwd(id, level, cid)
{
	// Authenticate user
	func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Init variables
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new pwd[HL_LEN_PWD + 1]; read_args(pwd, HL_LEN_PWD); remove_quotes(pwd)
	
	// Parse the password
	func_parse_string(pwd, HL_LEN_PWD)
	
	// Say the message
	set_cvar_string("sv_password", pwd)
	
	return PLUGIN_HANDLED
}

// Function: amx_enslmove [#ID/ip:port] [password] (Connect everyone to another server)
public cmd_enslmove(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Init variables
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new addr[HL_LEN_ADDR + 1]; get_user_ip(0, addr, HL_LEN_ADDR)
	new newaddr[HL_LEN_DNS + 1]; read_argv(1, newaddr, HL_LEN_DNS + 1); remove_quotes(newaddr)
	new pwd[HL_LEN_PWD + 1], port[HL_LEN_PORT + 1]
	
	// Set port and password correctly if port is given (amxx splits args. by :)
	new delim[2]; read_argv(2, delim, 1); remove_quotes(delim)
	if ( equal(delim, ":") )
	{
		read_argv(3, port, HL_LEN_PORT); remove_quotes(port)
		add(newaddr, HL_LEN_DNS, delim, 1)
		add(newaddr, HL_LEN_DNS, port, HL_LEN_PORT)
		read_argv(4, pwd, HL_LEN_PWD); remove_quotes(pwd)
	}
	// Normal procedure without port
	else
	{
		add(newaddr, HL_LEN_DNS, ":27015")
		read_argv(2, pwd, HL_LEN_PWD); remove_quotes(pwd)
	}

	// Inform people and log
	client_print(0, print_chat, "[ENSL] Referee (%s) is moving everyone to server: %s", name, newaddr)
	log_message("[ENSL] [enslmove] [%s:%s] [%s]", g_userSteamID[pid], name, addr)
		
	// Move everyone
	new cmd[HL_LEN_MSG + 1]; formatex(cmd, HL_LEN_MSG, " connect %s", newaddr)
	log_message("[DEBUG] enslmove: %s", cmd)
	set_task(5.0, "task_enslmove", 0, cmd, HL_LEN_MSG, "b", 1)
	
	// Move the HLTV if necessary
	if ( g_gameHltvs > 0 )
	{
		new url[W_LEN_URL + 1], readargs[1]; readargs[0] = id
		formatex(url, W_LEN_URL, "%smove?addr=%s&newaddr=%s&newpwd=%s", HT_URL, addr, newaddr, pwd)
		func_write_web(g_webSocket[W_I_HLTV], url)
		set_task(0.5, "task_web_hltv_send", 0, url, W_LEN_URL)
		set_task(1.0, "task_web_hltv", T_HLTV, readargs, 1)
	}
	
	return PLUGIN_HANDLED
}

// Task: move everyone to another server
public task_enslmove(args[])
{
	client_cmd(0, args)
}

// Function: amx_enslhelp (Show help about ENSL Plugin commands)
public cmd_enslhelp(id, level, cid)
{
	// Authenticate user
	func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 0) )
	{
		return PLUGIN_HANDLED
	}
	
	// Show information about all commands
	new cmd[A_LEN_CMD + 1], info[A_LEN_CMDINFO + 1], flags
	for ( new i = 0; get_concmd(i, cmd, A_LEN_CMD, flags, info, A_LEN_CMDINFO, -1, 1); i++)
	{
		console_print(id, "%s %s", cmd, info)
	}
	
	return PLUGIN_HANDLED
}

// Function: amx_enslcvars (Show help about ENSL Plugin CVARs)
public cmd_enslcvars(id, level, cid)
{
	// Authenticate user
	func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 0) )
	{
		return PLUGIN_HANDLED
	}
	
	// Show information about all cvars
	for ( new i = 0; i < H_NUM_CVARS; i++)
	{
		console_print(id, "%s", g_cvarHelp[i])
	}
	
	return PLUGIN_HANDLED
}

// Function: amx_enslgag [name/#ID] (Prvents user from talking)
public cmd_enslgag(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Fetch arguments
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new target[HL_LEN_NICK + 1]; read_argv(1, target, HL_LEN_NICK)
	new targetID = func_get_player(id, target)
	
	// If user cannot be found, abort
	if ( targetID == -1 || equal(target, "") )
	{
		return PLUGIN_HANDLED
	}
	
	// Get user ID
	new targetName[HL_LEN_NICK + 1]; get_user_name(targetID, targetName, HL_LEN_NICK)
	new targetPid = func_authenticate(targetID)
	
	// If user is gagged...
	if ( g_userGagged[targetPid] )
	{
		// Ungag
		g_userGagged[targetPid] = 0

		// Log and inform people
		client_print(0, print_chat, "[ENSL] Referee (%s) ungagged player: %s", name, targetName)
		log_message("[ENSL] [enslgag] [%s:%s] [%d:%s]", g_userSteamID[pid], name, g_userSteamID[targetPid], targetName)
	}
	// If user is not gaggged..
	else
	{
		// Gag
		g_userGagged[targetPid] = 1
		
		// Log and inform people
		client_print(0, print_chat, "[ENSL] Referee (%s) gagged player: %s", name, targetName)
		log_message("[ENSL] [enslgag] [ungag] [%s:%s] [%d:%s]", g_userSteamID[pid], name, g_userSteamID[targetPid], targetName)
	}
	
	return PLUGIN_HANDLED	
}

/**************************************************************************************
 * HLTV handling
 **************************************************************************************/

// Function: amx_enslhltv [matchID/demoname/stop] (Request a HLTV.)
public cmd_enslhltv(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Prevent multirun
	if ( task_exists(T_HLTV) )
	{
		console_print(id, "HLTV query already running, please wait and try again.")
		return PLUGIN_HANDLED
	}
	
	// Fetch arguments
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new arg[HT_LEN_DEMO + 1]; read_argv(1, arg, HT_LEN_DEMO); remove_quotes(arg)
	new addr[HL_LEN_IP + HL_LEN_PORT + 2]; get_user_ip(0, addr, HL_LEN_IP + HL_LEN_PORT + 1)
	new url[W_LEN_URL + 1], Float:delay = 0.0
	
	// If stop-command was requested, generate stop query
	if ( equal(arg, "shoutcast") )
	{
		new pwd[HL_LEN_PWD + 1]; get_cvar_string("sv_password", pwd, HL_LEN_PWD)
		console_print(id, "Requesting a shoutcast HLTV to this server (%s pwd: %s)", addr, pwd)
		formatex(url, W_LEN_URL, "%sshoutcast?addr=%s&pwd=%s", HT_URL, addr, pwd)
	}
	else if ( equal(arg, "stop") )
	{
		console_print(id, "HLTV will be stopped in 90 seconds.")
		formatex(url, W_LEN_URL, "%sstop?addr=%s", HT_URL, addr)
		delay = 90.0
	}
	// If immediate stop was requested, generate stop query
	else if ( equal(arg, "stopnow") )
	{
		console_print(id, "HLTV will be stopped now.")
		formatex(url, W_LEN_URL, "%sstop?addr=%s", HT_URL, addr)
	}
	// Otherwise send normal request
	else
	{
		new pwd[HL_LEN_PWD + 1]; get_cvar_string("sv_password", pwd, HL_LEN_PWD)
		new challenge[CC_LEN_INPUT + 1]; copy(challenge, CC_LEN_INPUT, arg)
		new sign[CC_LEN_CODE + 1]; func_crypt(challenge, sign)
		console_print(id, "Requesting a HLTV to this server (%s pwd: %s) for game: %s", addr, pwd, arg)
		formatex(url, W_LEN_URL, "%sreq?game=%s&addr=%s&pwd=%s&user=%d&sign=%s", HT_URL, arg, addr, pwd, g_userEnslId[pid], sign)
	}

	// Schedule the sending and reading functions
	new readargs[1]; readargs[0] = id
	set_task(delay, "task_web_hltv_send", 0, url, W_LEN_URL)
	set_task(delay + 0.5, "task_web_hltv", T_HLTV, readargs, 1)

	return PLUGIN_HANDLED	
}

// Task: Send HLTV command 
public task_web_hltv_send(url[W_LEN_URL + 1])
{
	func_write_web(g_webSocket[W_I_HLTV], url)
}

// Function: Reads HLTV response from the web
public task_web_hltv(args[])
{	
	// Increase reads
	g_webReads[W_I_HLTV]++
		
	// If there's data in the socket, read it.
	if ( socket_change(g_webSocket[W_I_HLTV], 200) )
	{
		func_read_web(g_webSocket[W_I_HLTV], HT_MARK, HT_NUM_LINES, g_webData[W_I_HLTV], g_webDataLine[W_I_HLTV])
	}

	// If socket hasn't finished, retry
	if ( g_webDataLine[W_I_HLTV] != HT_NUM_LINES && g_webReads[W_I_HLTV] < W_READS )
	{
		set_task(0.5, "task_web_hltv", T_HLTV, args, 1)
		return
	}	
	
	// Print output to user if there's data
	if ( g_webDataLine[W_I_HLTV] > 0 )
	{
		console_print(args[0], "[HLTV] %s", g_webData[W_I_HLTV][0])
	}
	
	// Finally close the socket and reset vars
	socket_close(g_webSocket[W_I_HLTV])
	g_webReads[W_I_HLTV] = 0
	g_webDataLine[W_I_HLTV] = 0
	
	// Reset data
	for ( new i = 0; i < W_NUM_LINES; i++ )
	{
		g_webData[W_I_HLTV][i] = "^0"
	}
}
 

/**************************************************************************************
 * Game pausing
 **************************************************************************************/

// Command: pause
public cmd_pause(id)
{
	// If pausing or unpausing is not awaited, ignore
	if ( !g_pausePending )
	{
		return PLUGIN_HANDLED
	}

	// Get ref's name and say the message
	new name[HL_LEN_NICK + 1]; get_user_name(g_pauseReq, name, HL_LEN_NICK)

	// Disable pause wait
	g_pauseReq = 0
	g_pausePending = 0
	server_cmd("pausable 0")

	// If already paused, this will be unpause
	if ( g_pauseStatus )
	{
		g_pauseStatus = 0
		client_print(0, print_chat, "[ENSL] Referee (%s) has unpaused the game", name)
	}
	// Otherwise this will be pause
	else
	{
		g_pauseStatus = 1
		client_print(0, print_chat, "[ENSL] Referee (%s) has pause the game", name)
	}
	
	return PLUGIN_CONTINUE
}
 
// Function: amx_enslpause (Pause / unpause)
public cmd_enslpause(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 0) )
	{
		return PLUGIN_HANDLED
	}
	
	// Init variables
	new pauser = find_player("h")
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)

	// If pausable player cannot be found, abort
	if ( !pauser )
	{
		console_print(id, "Unable to (un)pause")
		return PLUGIN_HANDLED
	}
	
	// Set the plugin to wait for pause from client
	g_pauseReq = id
	g_pausePending = 1
	server_cmd("pausable 1")
	client_cmd(pauser, "pause; pauseMark")
	log_message("[ENSL] [enslpause] [%s:%s] [status=%d]", g_userSteamID[pid], name, g_pauseStatus)
		
	return PLUGIN_HANDLED
}


/**************************************************************************************
 * Voice blocking
 **************************************************************************************/
 
// Command: public say
public cmd_say(id)
{	
	// Authenticate
	new pid = func_authenticate(id)
	
	// Get the message
	new msg[HL_LEN_SAY + 1]; read_args(msg, HL_LEN_SAY); remove_quotes(msg)
	
	// Check if its a whois query
	if ( contain(msg, "/whois") == 0 )
	{
		cmd_whois(id, msg)
		return PLUGIN_HANDLED
	}
	
	// Check if user is disallowed to talk
	if ( g_gameModerated && !g_userVoice[pid] && !func_is_referee(pid) )
	{
		client_print(id, print_chat, "[ENSL] Public chat is moderated and you are not allowed to talk.")
		return PLUGIN_HANDLED
	}
	
	// Check if user has been disallowed to talk
	if ( g_userGagged[pid] )
	{
		client_print(id, print_chat, "[ENSL] You have been gagged by a referee.")
		return PLUGIN_HANDLED
	}
	
	// Check if user has global talk features
	if ( g_userTeam[pid] == HL_RRSPECS )
	{
		new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
		client_print(0, print_chat, "%s : %s", name, msg)
		return PLUGIN_HANDLED
	}
	
	return PLUGIN_CONTINUE
}

// Command: empty=freetalk or [name/#ID] (Limit talking)
public cmd_enslvoice(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 0) )
	{
		return PLUGIN_HANDLED
	}
	
	// Initialize variables
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new userStr[HL_LEN_MSG + 1], userNum = 0, argC = read_argc()
	new userSearch[HL_LEN_NICK + 1], userName[HL_LEN_NICK + 1], userId, userPid
	
	// Take action if arguments are specified
	if ( argC > 1 )
	{
		// Loop through the arguments
		for ( new i = 1; i < argC; i++ )
		{
			// Fetch the given users
			userSearch = "^0"; read_argv(i, userSearch, HL_LEN_NICK)
			userId = func_get_player(id, userSearch)
			
			// If user can be found..
			if ( userId != -1 )
			{
				// Authenticate + fetch username & pid
				userName = "^0"; get_user_name(userId, userName, HL_LEN_NICK)
				userPid = func_authenticate(userId)

				// If user is voiced, unvoice user
				if ( g_userVoice[userPid] )
				{	
					g_userVoice[userPid] = 0
					console_print(id, "Talk privilege revoked from: %s", userName)
					log_message("[ENSL] [enslvoice] [%s:%s] [revoke] [%s:%s]", g_userSteamID[pid], name, userName, g_userSteamID[userPid])
				}
				// If user is not voiced, voice user
				else
				{
					g_userVoice[userPid] = 1
					console_print(id, "Talk privilege granted to: %s", userName)
					log_message("[ENSL] [enslvoice] [%s:%s] [grant] [%s:%s]", g_userSteamID[pid], name, userName, g_userSteamID[userPid])
					
					// If moderation is enabled, add user to the list of privileged people
					if ( g_gameModerated )
					{
						add(userStr, HL_LEN_MSG, " [", 2)
						add(userStr, HL_LEN_MSG, userName, HL_LEN_NICK)
						add(userStr, HL_LEN_MSG, "]", 1)
						userNum++
					}
				}
			}
		}
	}
	
	// Set moderation if arguments specified or it already disabled
	if ( !g_gameModerated || argC > 1 )
	{			
		// Find users who may talk. if we are enabling the chat moderation
		if ( !g_gameModerated )
		{
			for ( new i = 1; i < g_userCount; i++ )
			{
				// If user is on server and is able talk, add user
				if ( g_userStatus[i] && g_userVoice[i] )
				{
					userName = "^0"; get_user_name(g_userIdIndex[i], userName, HL_LEN_NICK)
					add(userStr, HL_LEN_MSG, " [", 2)
					add(userStr, HL_LEN_MSG, userName, HL_LEN_NICK)
					add(userStr, HL_LEN_MSG, "]", 1)
					userNum++
				}
			}
		}
		
		// Set moderation
		g_gameModerated = 1
		client_print(0, print_chat, "[ENSL] Referee (%s) has enabled chat moderation.", name)
		log_message("[ENSL] [enslvoice] [%s:%s] [ENABLE]", g_userSteamID[pid], name)
		
		// Echo who may talk
		if ( userNum > 0 )
		{
			client_print(0, print_chat, "[ENSL] Chat allowed for:%s", userStr)
		}
	}
	// Otherwise ...
	else
	{
		// Remove voice privilege
		for ( new i = 1; i < g_userCount; i++ )
		{
			g_userVoice[i] = 0
		}
		
		// Unset moderation
		g_gameModerated = 0
		client_print(0, print_chat, "[ENSL] Referee (%s) has disabled chat moderation.", name)
		log_message("[ENSL] [enslvoice] [%s:%s] [DISABLED]", g_userSteamID[pid], name)
	}
	
	return PLUGIN_HANDLED
}


/**************************************************************************************
 * ENSL Official Match setting CVAR enforcement
 **************************************************************************************/

// Function: initialize server CVARs
public func_init_cvars()
{
	// Integers
	func_add_int_cvar("mp_blockscripts", 0)
	func_add_int_cvar("mp_consistency", 1)
	func_add_int_cvar("mp_combattime", 15)
	func_add_int_cvar("mp_drawdamage", 0)
	func_add_int_cvar("mp_falldamage", 1)
	func_add_int_cvar("mp_flashlight", 1)
	func_add_int_cvar("mp_footsteps", 1)
	func_add_int_cvar("mp_friendlyfire", 1)
	func_add_int_cvar("mp_killdelay", 3)
	func_add_int_cvar("mp_team1damagepercent", 100)
	func_add_int_cvar("mp_team2damagepercent", 100)
	func_add_int_cvar("mp_timelimit", 0)
	func_add_int_cvar("mp_tournamentmode", 1)
	func_add_int_cvar("pausable", 0)
	func_add_int_cvar("sv_aim", 0)
	func_add_int_cvar("sv_cheats", 0)
	func_add_int_cvar("sv_maxspeed", 4000)
	func_add_int_cvar("sv_minrate", 10000)
	func_add_int_cvar("sv_maxrate", 20000)
	func_add_int_cvar("sv_minupdaterate", 40)
	func_add_int_cvar("sv_maxupdaterate", 100)
	func_add_int_cvar("sv_proxies", 5)
	func_add_int_cvar("ensl_minrate", 10000)
	func_add_int_cvar("ensl_maxrate", 25000)
	func_add_int_cvar("ensl_mincmdrate", 50)
	func_add_int_cvar("ensl_maxcmdrate", 150)
	func_add_int_cvar("ensl_minupdaterate", 40)
	func_add_int_cvar("ensl_maxupdaterate", 150)
	func_add_int_cvar("ensl_minfps", 50)
	func_add_int_cvar("ensl_maxfps", 125)
	func_add_int_cvar("ensl_snapshots", 1, 0)
	func_add_int_cvar("ensl_checkids", 1, 1)
	func_add_int_cvar("ensl_checkrates", 1, RC_ENABLED_FETCH)
	func_add_int_cvar("ensl_ffclock", 1, 0)
	func_add_int_cvar("ensl_merclimit", 1, 0)
	func_add_int_cvar("ensl_teamlimit", 6, 0)
	func_add_int_cvar("ensl_speclimit", 1, 0)
	func_add_int_cvar("ensl_refaccess", 1, 1)
	func_add_int_cvar("ensl_combatmode", 2, 1)
	
	// Floats
	func_add_flt_cvar("mp_countdowntime", 0.2)
	func_add_flt_cvar("sv_clienttrace", 3.5)
}

 // Command: amx_enslcfg (Execute ENSL Match Settings)
public cmd_enslcfg(id, level, cid)
{
	// Authenticate user if necessary + get username
	new pid = func_authenticate(id)
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 0) )
	{
		return PLUGIN_HANDLED
	}
	
	// If it is pcw mode, run pcw mode settings
	new mode[4]; read_argv(1, mode, 3); new pcw = equal(mode, "pcw")
	
	// Set Server ENSL Cvars, integers
	for ( new i = 0; i < C_NUM_INTS; i++ )
	{
		set_cvar_num(g_cvarIntName[i], pcw ? g_cvarIntPCW[i] : g_cvarIntValue[i])
	}
	
	// Set Server ENSL Cvars, floats
	for ( new i = 0; i < C_NUM_FLOATS; i++ )
	{
		set_cvar_float(g_cvarFltName[i], pcw ? g_cvarFltPCW[i] : g_cvarFltValue[i])
	}
		
	// Log and announce the event
	if ( pcw )
	{
		client_print(0, print_chat, "[ENSL] Version %s PCW Settings Loaded", VERSION)
	}
	else
	{
		client_print(0, print_chat, "[ENSL] Version %s OFFICIAL Settings Loaded", VERSION)
	}
	
	// Log it
	log_message("[ENSL] [enslcfg] [%s:%s] [%d]", g_userSteamID[pid], name, pcw)
	
	return PLUGIN_HANDLED
}

 // Command: amx_enslcvar (Change CVAR value)
public cmd_enslcvar(id, level, cid)
{
	// Authenticate user if necessary
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 2) )
	{
		return PLUGIN_HANDLED
	}
	
	// Get referee name and argument
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new cvarName[C_LEN_NAME + 1]; read_argv(1, cvarName, C_LEN_NAME)
	new cvarValue[R_LEN_NUM + 1]; read_argv(2, cvarValue, R_LEN_NUM)
	
	// Find the CVAR, integers
	for ( new i = 0; i < C_NUM_INTS; i++ )
	{
		// If found, set it
		if ( equal(g_cvarIntName[i], cvarName) )
		{
			// Format values for an integer cvar
			new intValue = str_to_num(cvarValue), oldValue = get_cvar_num(g_cvarIntName[i])
			set_cvar_num(g_cvarIntName[i], intValue)
			
			// Announce it, log it and exit
			client_print(0, print_chat, "[ENSL] Referee (%s) changed %s from %d to %d", name, cvarName, oldValue, intValue)
			log_message("[ENSL] [cvar] [%s:%s] [%s] %d -> %d", g_userSteamID[pid], name, cvarName, oldValue, intValue)
			return PLUGIN_HANDLED
		}
	}
	
	// Find the CVAR, floats
	for ( new i = 0; i < C_NUM_FLOATS; i++ )
	{
		// If found, set it
		if ( equal(g_cvarFltName[i], cvarName) )
		{
			// Format values for a float cvar
			new Float:fltValue = str_to_float(cvarValue), Float:oldValue = get_cvar_float(g_cvarFltName[i])
			set_cvar_float(g_cvarFltName[i], fltValue)
			
			// Announce it, log it and exit
			client_print(0, print_chat, "[ENSL] Referee (%s) changed %s from %.1f to %.1f", name, cvarName, oldValue, fltValue)
			log_message("[ENSL] [cvar] [%s:%s] [%s] %f -> %f", g_userSteamID[pid], name, cvarName, oldValue, fltValue)
			return PLUGIN_HANDLED
		}
	}
	
	// Otherwise abort since cvar was not found
	console_print(id, "CVAR not found.")
	
	return PLUGIN_HANDLED
}

// Function: add integer cvar
stock func_add_int_cvar(name[C_LEN_NAME + 1], value = 0,  pcw = -1, level = C_LEVEL_MANDATORY)
{
	// Don't add over borders
	if ( g_cvarIntIndex == sizeof(g_cvarIntName) )
	{
		return g_cvarIntIndex
	}
	
	g_cvarIntName[g_cvarIntIndex] = name
	g_cvarIntValue[g_cvarIntIndex] = value
	g_cvarIntPCW[g_cvarIntIndex] = pcw != -1 ? pcw : value
	g_cvarIntLevel[g_cvarIntIndex] = level
	g_cvarIntIndex++
	
	return g_cvarIntIndex - 1
}

// Function: add float cvar
stock func_add_flt_cvar(name[C_LEN_NAME + 1], Float:value = 0.0,  Float:pcw = -1.0, level = C_LEVEL_MANDATORY)
{
	// Don't add over borders
	if ( g_cvarFltIndex == sizeof(g_cvarFltName) )
	{
		return g_cvarFltIndex
	}
	
	g_cvarFltName[g_cvarFltIndex] = name
	g_cvarFltValue[g_cvarFltIndex] = value
	g_cvarFltPCW[g_cvarFltIndex] = pcw != -1.0 ? pcw : value
	g_cvarFltLevel[g_cvarFltIndex] = level
	g_cvarFltIndex++
	
	return g_cvarFltIndex - 1
}

/**************************************************************************************
 * Spectation block
 **************************************************************************************/

// Command: spectate
public cmd_spectate(id)
{	
	// Authenticate
	new pid = func_authenticate(id)
	
	// Check for too rapid action
	if ( func_check_cmdtime(pid, CT_TEAMJOIN, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// Check if user can join
	if ( get_pcvar_num(g_cvar_speclimit) && !func_is_referee(pid) )
	{
		client_print(id, print_chat, "[ENSL] You cannot join spectators. Only referees and admins allowed.")
		return PLUGIN_HANDLED
	}
	
	return PLUGIN_CONTINUE
}

// Touch: spectate
public touch_spectate(ent, id)
{
	return cmd_spectate(id)
}


/**************************************************************************************
 * Team management functions
 **************************************************************************************/

// Event: player changes team
public client_changeteam(id, newTeam, oldTeam)
{
	// Authenticate
	new pid = func_authenticate(id)
	
	// Set the new team and increase the playercount
	g_userTeam[pid] = newTeam
	g_teamPlayers[newTeam]++
	
	// Waypoint target
	g_userDefendWP[id] = 0;
	
	// Process team leave
	func_teamleave(id, oldTeam)
	
	return PLUGIN_CONTINUE
}

// Entity catches
public touch_teamjoin(ent, id)
{
	if ( ent == HL_ENT_MARINES )
	{
		return cmd_jointeamone(id)
	}
	else if ( ent == HL_ENT_ALIENS )
	{
		return cmd_jointeamtwo(id)
	}
	
	return PLUGIN_HANDLED
}

// Entity catch for autoassign
public touch_autoassign(ent, id)
{
	client_print(id, print_chat, "[ENSL] Autoassign disabled.")
	return PLUGIN_HANDLED
}

// Command: autoassign
public cmd_autoassign(id)
{
	client_print(id, print_chat, "[ENSL] Autoassign disabled.")
	return PLUGIN_HANDLED
}

// Command: jointeamone (marine join)
// NOTE: This doesn't cover all teamchanges -> counters not updated here
public cmd_jointeamone(id)
{	
	// Authenticate player
	new pid = func_authenticate(id)
	
	// Check if user was digested
	if ( g_userDDTime[pid] != 0 && g_userDDTime[pid] > get_systime() )
	{
		client_print(id, print_chat, "[ENSL] Please wait %d seconds before joining.", g_userDDTime[pid] - get_systime())
		return PLUGIN_HANDLED
	}
	
	// Check if user can join (merclimit)
	return func_teamjoin(id, HL_MARINES)
}

// Command: jointeamtwo (alien join)
// NOTE: This doesn't cover all teamchanges -> counters not updated here
public cmd_jointeamtwo(id)
{	
	// Authenticate player
	func_authenticate(id)
	
	return func_teamjoin(id, HL_ALIENS)
}

// Command: enter readyroom
public cmd_readyroom(id)
{
	// Authenticate
	new pid = func_authenticate(id)
	
	// Allow f4f1
	g_userCmdTime[pid][CT_TEAMJOIN] = get_gametime() - G_FREQ
	
	// Reset rates so user can change them
	for ( new i = 0; i < RC_NUM_VARS; i++ )
	{
		g_userRCVars[pid][i] = 0
		g_userRCWarn[pid][i] = RC_WARN_NEGATIVE
	}
	
	return PLUGIN_CONTINUE
}

// Function: player tries to join a team (marines/aliens)
public func_teamjoin(id, newTeam)
{
	new maxPlayers = get_pcvar_num(g_cvar_teamlimit), pid = g_userPidIndex[id], otherTeam = newTeam == HL_ALIENS ? HL_MARINES : HL_ALIENS
	
	// Limit spamming
	if ( func_check_cmdtime(pid, CT_TEAMJOIN, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// Check if member limit is one
	if ( func_check_membersonly(id, pid) == 0 )
	{
		return PLUGIN_HANDLED
	}
	
	// If player is already on a proper team, ignore it
	if ( g_userTeam[pid] != HL_RRSPECS )
	{
		client_print(id, print_chat, "[ENSL] You are already on a team")
		return PLUGIN_HANDLED
	}
	
	// If game has started, also limit player count if enabled
	if ( g_gameStarted && maxPlayers > 0 && g_teamPlayers[newTeam] >= maxPlayers )
	{
		client_print(id, print_chat, "[ENSL] Player limiting is enabled. You cannot join. Maximum is %d players.", maxPlayers)
		return PLUGIN_HANDLED
	}
	
	// Check if user is allowed to merc
	if ( (g_gameStarted || g_teamReady[newTeam]) && get_pcvar_num(g_cvar_merclimit) && g_userMerc[pid] != newTeam && g_userEnslTid[pid] != g_teamENSLId[newTeam] )
	{
		new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
		client_print(0, print_chat, "[ENSL] %s is trying to merc for %s. %s can approve it by typing /ok", name, g_teamNames[newTeam], g_teamNames[otherTeam])
		g_teamMReq[newTeam] = pid
		return PLUGIN_HANDLED
	}
	
	return PLUGIN_CONTINUE
}

// Function:process player leave from team
public func_teamleave(id, oldTeam)
{
	// Decrease the player count
	g_teamPlayers[oldTeam]--
	
	// For marines & aliens
	if ( oldTeam == HL_MARINES || oldTeam == HL_ALIENS )
	{
		// If game has not started...
		if ( !g_gameStarted && !g_gameEnding )
		{		
			// If neither of teams have players, stop the clock
			if ( g_teamPlayers[HL_MARINES] == 0 && g_teamPlayers[HL_ALIENS] == 0 )
			{
				func_stop_ffclock(0)
				func_reset_teams()
			}
			// If only the team which was left, is empty, reset its vars
			else if ( g_teamPlayers[oldTeam] == 0 )
			{
				func_reset_team(oldTeam)
			}
			// Otherwise force unready
			else if ( g_teamReady[oldTeam] )
			{
				// Loop through all players
				for ( new pid = 1; pid < g_userCount; pid++ )
				{						
					// Unready the first player, who is on server, of the team given
					if ( g_userStatus[pid] && g_userTeam[pid] == oldTeam )
					{
						client_print(g_userIdIndex[pid], print_chat, "[ENSL] A player left your team and auto-unready is forced.")
						client_cmd(g_userIdIndex[pid], "say notready")
						break
					}
				}
			}
		}
	}
	
	return PLUGIN_CONTINUE
}
 
// Command: allow mercs of opponent team to play
public cmd_mercsok(id)
{
	// Authenticate and get team ids
	new pid = func_authenticate(id)
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new team = g_userTeam[pid], other = team == HL_ALIENS ? HL_MARINES : HL_ALIENS
	new userNum = 0, userStr[HL_LEN_MSG + 1]
	
	// Check for too rapid action
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// Do not accept it from other than aliens / marines before game start
	if ( (team != HL_MARINES && team!= HL_ALIENS) || g_gameStarted )
	{
		client_print(id, print_chat, "[ENSL] Only marines/aliens can approve mercs before game start.")
		return PLUGIN_HANDLED
	}
	
	// If merclimiting is off, ignore it
	if ( get_pcvar_num(g_cvar_merclimit) == 0 )
	{
		client_print(id, print_chat, "[ENSL] Merclimiting is not enabled.")
		return PLUGIN_HANDLED
	}
	
	// If opponent hasn't registered yet, don't bother
	if ( g_teamENSLId[other] == 0 )
	{
		client_print(id, print_chat, "[ENSL] Other team has not registered yet.")
		return PLUGIN_HANDLED
	}
	
	// Go through all players
	for ( new i = 1; i < g_userCount; i++ )
	{
		// Check if the player on the other team is a merc
		if ( g_userStatus[i] && g_userTeam[i] == other && g_userEnslTid[i] != g_teamENSLId[other] && g_userMerc[i] != other )
		{
			// Allow mercing
			g_userMerc[i] = other
			
			// Add seperating comma for 2nd+ mercs
			if ( userNum > 0 )
			{
				add(userStr, HL_LEN_MSG, ", ", 2)
			}
			
			// Add player name to the list
			new userName[HL_LEN_NICK]; get_user_name(g_userIdIndex[i], userName, HL_LEN_NICK)
			add(userStr, HL_LEN_MSG, userName, 15)
			userNum++
		}
	}
	
	// If no mercs were found, ignore it
	if ( userNum == 0 )
	{
		client_print(id, print_chat, "[ENSL] No mercs were found from %s.", g_teamNames[other])
	}
	// Otherwise announce it
	else
	{
		client_print(0, print_chat, "[ENSL] %s allowed mercs: %s.", g_teamNames[team], userStr)
		log_message("[ENSL] [mercsok] [%s:%s] [%s]", g_userSteamID[pid], userStr)
	}
	
	return PLUGIN_HANDLED
}

// Command: list mercs of the opposite team
public cmd_mercs(id)
{
	// Authenticate and get team ids
	new pid = func_authenticate(id)
	new team = g_userTeam[pid], other = team == HL_ALIENS ? HL_MARINES : HL_ALIENS
	new userNum = 0, userStr[HL_LEN_MSG + 1]
	
	// Check for too rapid action
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// Do not accept it from other than aliens / marines before game start
	if ( (team != HL_MARINES && team!= HL_ALIENS) || g_gameStarted )
	{
		client_print(id, print_chat, "[ENSL] Only marines/aliens can list mercs before game start.")
		return PLUGIN_HANDLED
	}
	
	// If opponent hasn't registered yet, don't bother
	if ( g_teamENSLId[other] == 0 )
	{
		client_print(id, print_chat, "[ENSL] Other team has not registered yet.")
		return PLUGIN_HANDLED
	}
	
	// Go through all players
	for ( new i = 1; i < g_userCount; i++ )
	{
		// Check if the player on the other team is a merc
		if ( g_userStatus[i] && g_userTeam[i] == other && g_userEnslTid[i] != g_teamENSLId[other] )
		{	
			// Add seperating comma for 2nd+ mercs
			if ( userNum > 0 )
			{
				add(userStr, HL_LEN_MSG, ", ", 2)
			}
			
			// Add player name to the list
			new userName[HL_LEN_NICK]; get_user_name(g_userIdIndex[i], userName, HL_LEN_NICK)
			add(userStr, HL_LEN_MSG, userName, 15)
			userNum++
		}
	}
	
	// If no mercs were found, say it
	if ( userNum == 0 )
	{
		client_print(id, print_chat, "[ENSL] No mercs were found from %s.", g_teamNames[other])
	}
	// Otherwise announce it
	else
	{
		client_print(id, print_chat, "[ENSL] %s' mercs: %s.", g_teamNames[other], userStr)
	}
	
	return PLUGIN_HANDLED
}

// Command: approve single merc
public cmd_ok(id)
{
	// Authenticate and get team ids
	new pid = func_authenticate(id)
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new team = g_userTeam[pid], other = team == HL_ALIENS ? HL_MARINES : HL_ALIENS
	new merc = g_teamMReq[other]
	
	// Do not accept it from othe than aliens / marines
	if ( team != HL_MARINES && team!= HL_ALIENS )
	{
		return PLUGIN_HANDLED
	}
	
	// If a merc has requested to join
	if ( merc != -1 && g_userMerc[merc] != other && g_userStatus[merc] )
	{
		// Allowd mercing, reset requestor spot
		g_userMerc[merc] = other
		g_teamMReq[other] = -1
		
		// Announce it
		new mercName[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[merc], mercName, HL_LEN_NICK)
		client_print(0, print_chat, "[ENSL] %s has been allowed to merc for %s.", mercName, g_teamNames[other])
		log_message("[ENSL] [mercok] [%s:%s] [%s:%s] [%d]", g_userSteamID[pid], name, g_userSteamID[merc], mercName, team)
	}
	// Otherwise print error
	else
	{
		client_print(id, print_chat, "[ENSL] No merc requests found.")
	}
	
	return PLUGIN_HANDLED
}

// Command: [user/#ID] [team] (toggle mercing)
public cmd_enslmerc(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 1) )
	{
		return PLUGIN_HANDLED
	}
	
	// Initialize variables
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new strMerc[HL_LEN_NICK + 1]; read_argv(1, strMerc, HL_LEN_NICK)
	new mercId = func_get_player(id, strMerc)
	
	// Check that player is found, not already mercing and on server
	if ( mercId == -1  )
	{
		return PLUGIN_HANDLED
	}
	
	// Get PID
	new mercPid = func_authenticate(mercId)
	
	// If merc has been granted access, revoke it
	if ( g_userMerc[mercPid] )
	{
		// Revoke mercing
		g_userMerc[mercPid] = 0
		
		// Announec and log it
		new mercName[HL_LEN_NICK + 1]; get_user_name(mercId, mercName, HL_LEN_NICK)
		client_print(0, print_chat, "[ENSL] Referee (%s) has disallowed %s's mercing.", name, mercName)
		log_message("[ENSL] [enslmerc] [%s:%s] [%s:%s] [REVOKE]", g_userSteamID[pid], name, g_userSteamID[mercPid], mercName)
		
		return PLUGIN_HANDLED
	}
	
	// Fetch team
	new strTeam[HL_LEN_TEAM + 1]; read_argv(2, strTeam, HL_LEN_TEAM)
	new team = func_get_team(id, strTeam)
	
	// Abort it team cannot be found
	if ( team == -1 )
	{
		return PLUGIN_HANDLED
	}
	
	// Allow mercing, reset requestor spot
	g_userMerc[mercPid] = team
		
	// Announec and log it
	new mercName[HL_LEN_NICK + 1]; get_user_name(mercId, mercName, HL_LEN_NICK)
	client_print(0, print_chat, "[ENSL] Referee (%s) has allowed %s to merc for %s.", name, mercName, g_teamNames[team])
	log_message("[ENSL] [enslmerc] [%s:%s] [%s:%s] [%d] [PERMIT]", g_userSteamID[pid], name, g_userSteamID[mercPid], mercName, team)
	
	return PLUGIN_HANDLED
}

// Function: reset team variables at round end
public func_reset_team(team)
{	
	// Reset teams (marines & aliens)
	g_teamReady[team] = 0
	g_teamENSLId[team] = 0
	g_teamENSLName[team] = "^0"
	g_teamMReq[team] = -1
}

// Task: reset both teams
public func_reset_teams()
{
	// Reset teams
	func_reset_team(HL_MARINES)
	func_reset_team(HL_ALIENS)
	
	// Remove merc status
	for ( new i = 1; i < U_NUM_IDS; i++ )
	{
		g_userMerc[i] = 0
	}
}


/**************************************************************************************
 * Ready related functions & forfeitclock
 **************************************************************************************/

// Command: say ready
public cmd_ready(id)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Initialize variables
	new team = g_userTeam[pid], maxPlayers = get_pcvar_num(g_cvar_teamlimit)
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	
	// If game has started, requester is spec or team is already ready, skip process
	if ( g_gameStarted || team == HL_RRSPECS || g_teamReady[team] )
	{
		return PLUGIN_CONTINUE
	}
	
	// If pause is on, dont allow ready
	if (  g_pauseStatus )
	{
		client_print(id, print_chat, "[ENSL] You cannot ready while game is paused.")
		return PLUGIN_HANDLED
	}
	
	// Regenerate the player count
	g_teamPlayers[team] = 0
	for ( new i = 0; i < U_NUM_IDS; i++ )
	{
		if ( g_userTeam[i] == team )
		{
			g_teamPlayers[team]++
		}
	}
	
	// If teams are limited, check players
	if ( !ns_is_combat() && maxPlayers != 0 && g_teamPlayers[team] != maxPlayers )
	{
		client_print(id, print_chat, "[ENSL] You can only ready with %d players on your team.", maxPlayers)
		return PLUGIN_HANDLED
	}
	
	// If mercliming is on...
	if ( get_pcvar_num(g_cvar_merclimit) )
	{
		// If user is unteamed, abort
		if ( !g_userEnslTid[pid] )
		{
			client_print(id, print_chat, "[ENSL] Unteamed players are not allowed to ready when merclimiting is enabled.")
			return PLUGIN_HANDLED
		}

		// Register requestor's team as team's choice and announce team registration
		g_teamENSLId[team] = g_userEnslTid[pid]
		g_teamENSLName[team] = g_userEnslTeam[pid]
		client_print(id, print_chat, "[ENSL] You are registered as ^"%s^".", g_teamENSLName[team])
		
		// Loop through all players
		for ( new iPid = 1; iPid < g_userCount; iPid++ )
		{
			// If the marine / alien has different ENSL team..
			if ( g_userStatus[iPid] && g_userTeam[iPid] == team && g_userEnslTid[iPid] != g_teamENSLId[team] && !g_userMerc[iPid] )
			{
				// Fetch the name
				new iName[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[iPid], iName, HL_LEN_NICK)
			
				// Echo warning and abort
				client_print(id, print_chat, "[ENSL] Player ^"%s^" isn't on your ENSL roster.", iName)				
				return PLUGIN_HANDLED
			}
		}
	}
		
	// Mark team ready
	g_teamReady[team] = 1
	log_message("[ENSL] [ready] [%s:%s] [%s] [%d:%s]", g_userSteamID[pid], name, g_teamNames[team], g_teamENSLId[team], g_teamENSLName[team])
	
	// If both teams are ready...
	if ( g_teamReady[HL_MARINES] && g_teamReady[HL_ALIENS] )
	{
		// Stop the clock
		func_stop_ffclock(0)
		
		// Set game started
		g_gameStarted = 1
	}
	// Otherwise start it
	else
	{
		func_start_ffclock()
	}
	
	return PLUGIN_CONTINUE
}

// Command: not ready
public cmd_notready(id)
{	
	// Authenticate user and get team
	new pid = func_authenticate(id)
	new team = g_userTeam[pid]
	
	// Mark team unready if its aliens or marines
	if ( !g_gameStarted && team > HL_RRSPECS )
	{
		g_teamReady[team] = 0
		
		// Pause the forfeit clock if both teams are unready
		if ( !g_teamReady[HL_MARINES] && !g_teamReady[HL_ALIENS] )
		{
			func_pause_ffclock()
		}
	}
	
	return PLUGIN_CONTINUE
}

// Command: amx_enslclock [mins] [secs] (add extra time to the forfeit clock)
public cmd_enslclock(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command and game is not running
	if ( !func_check_access(id, level, cid, 1) || g_gameStarted || !get_pcvar_num(g_cvar_ffclock) )
	{
		return PLUGIN_HANDLED
	}
	
	// Init variables
	new secs = 0
	new strMins[R_LEN_MINS + 1]; read_argv(1, strMins, R_LEN_MINS); secs += str_to_num(strMins) * 60
	new strSecs[R_LEN_MINS + 1]; read_argv(2, strSecs, R_LEN_MINS); secs += str_to_num(strSecs)
	new timeText[TT_LEN_OUTPUT + 1]; func_format_time(secs+0.0, timeText)
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	
	// Or add extra time
	g_FFCUsed -= secs
	client_print(0, print_chat, "[ENSL] Referee (%s) has %s the forfeit clock by %s", name, secs < 0 ? "decreased" : "increased", timeText)
	log_message("[ENSL] [enslclock] [%s:%s] [%s]", g_userSteamID[pid], name, timeText)
	
	return PLUGIN_HANDLED
}

// Function: start the forfeit clock
// Requirements: 1 team is ready
public func_start_ffclock()
{
	// Do not start if the task exists
	if ( task_exists(T_FFCLOCK) || !get_pcvar_num(g_cvar_ffclock) || g_gameStarted )
	{
		return PLUGIN_HANDLED
	}
	
	// Start the clock
	g_FFCStarted = get_gametime()
	set_task(1.0, "task_ffclock", T_FFCLOCK, "", 0, "b")
	
	// Print the start message
	new other = g_teamReady[HL_ALIENS] ? HL_MARINES : HL_ALIENS
	new timeText[TT_LEN_OUTPUT + 1]; func_format_time(FF_TIME - g_FFCUsed, timeText)
	client_print(0, print_chat, "[ENSL] Forfeit clock started. %s have %s to ready.", g_teamNames[other], timeText)
	log_message("[ENSL] [enslclock] [START] [%s] [%s]", timeText, g_teamNames[other])
	
	return PLUGIN_CONTINUE
}

// Function: pause the forfeit clock
public func_pause_ffclock()
{
	// Exit if the task doesn't exist
	if ( !task_exists(T_FFCLOCK) )
	{
		return PLUGIN_HANDLED
	}
	
	// Save the used forfeit time. If it is zero or negative, give 1min extra
	g_FFCUsed = floatround(get_gametime() - g_FFCStarted + g_FFCUsed, floatround_floor)
	g_FFCUsed = g_FFCUsed <= 0 ? floatround(FF_TIME) - 60 : g_FFCUsed
	remove_task(T_FFCLOCK)
		
	// Announce it
	new timeText[TT_LEN_OUTPUT + 1]; func_format_time(FF_TIME - g_FFCUsed, timeText)
	client_print(0, print_chat, "[ENSL] Forfeit clock has paused with %s left. Type ready to start it again.", timeText)
	log_message("[ENSL] [enslclock] [PAUSE] [%s]", timeText)
	
	return PLUGIN_CONTINUE
}

// Function: stop the forfeit clock
public func_stop_ffclock(silent)
{
	// If the task exists, stop it
	if ( task_exists(T_FFCLOCK) )
	{
		remove_task(T_FFCLOCK)	
	}
	
	// Reset timers
	g_FFCStarted = 0.0
	g_FFCUsed = 0
	
	// Announce it if not silent stop
	if ( !silent )
	{
		client_print(0, print_console, "[ENSL] Forfeit clock has been stopped and timers have been reset.")
	}
	
	// Log the event
	log_message("[ENSL] [enslclock] [STOP]")
	
	return PLUGIN_CONTINUE
}

// Task: forfeit clock
public task_ffclock()
{	
	new timeText[TT_LEN_OUTPUT + 1]
	
	// Calculate the remaining time
	new Float:remaining = FF_TIME - get_gametime() + g_FFCStarted - g_FFCUsed
	
	// If the time is out, announce the forfeit
	if ( remaining <= 0 )
	{
		// Give the forfeit
		if ( !g_teamReady[HL_MARINES] )
		{
			client_print(0, print_chat, "Marines (%s) have received the forfeit loss.", g_teamENSLName[1])
		}
		else if ( !g_teamReady[HL_ALIENS] )
		{
			client_print(0, print_chat, "Aliens (%s) have received the forfeit loss.", g_teamENSLName[2])
		}
		
		// Remove the task, reset variables and quit
		remove_task(T_FFCLOCK)
		g_FFCStarted = 0.0
		g_FFCUsed = 0
		
		// Run through and give everyone a final snapshot
		for ( new pid = 1; pid < g_userCount; pid++ )
		{	
			// Ignore RR+Specs
			if ( g_userStatus[pid] && g_userTeam[pid] > 0 )
			{
				func_snapshot(pid, 1)
			}
		}
		
		return PLUGIN_HANDLED
	}

	// Format unready team and timetext
	new team = !g_teamReady[HL_MARINES] ? HL_MARINES : HL_ALIENS
	func_format_time(remaining, timeText)
	
	// Announce the team lacking players
	for ( new pid = 1; pid < g_userCount; pid++ )
	{
		// Print it to everyone on the server
		if ( g_userStatus[pid] )
		{
			set_hudmessage(255, 0, 0, -1.0, 0.9, 0, 0.0, 0.9, 0.0, 0.0)
			show_hudmessage(g_userIdIndex[pid], "%s have %s to ready.", g_teamNames[team], timeText)
		}
	}
		
	// Print casual message every 30 seconds
	if ( floatround(remaining) % 30 == 0 )
	{
		client_print(0, print_chat, "%s have %s to ready.", g_teamNames[team], timeText)
	}
	
	return PLUGIN_CONTINUE
}

/**************************************************************************************
 * Combat modes
 **************************************************************************************/

public func_init_co()
{
	// If its not combat, don't bother	
	if ( !ns_is_combat() )
	{
		return PLUGIN_HANDLED
	}
	
	register_event("WeapPickup", "evolve_lerk", "b", "1=6")
	
	// Normal combat
	g_coNum[CO_NORMAL] = 0
	g_coBlocked[CO_NORMAL] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	
	// Celeresupply
	g_coNum[CO_CELERESUPPLY] = 31
	g_coBlocked[CO_CELERESUPPLY] = {20, 21, 22, 23, 24, 25, 27, 33, 37, 38, 39, 53, 61, 62, 64, 65, 66, 101, 102, 103, 108, 109, 110, 111, 112, 114, 115, 116, 117, 118, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	
	// ENSL combat
	g_coNum[CO_ENSL] = 5
	g_coBlocked[CO_ENSL] = {38, 39, 117, 126, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

	// SGs vs Fades
	g_coNum[CO_SGSFADES] = 21
	g_coBlocked[CO_SGSFADES] = {65, 66, 38, 39, 27, 53, 33, 126, 114, 115, 117, 101, 102, 103, 108, 109, 110, 111, 112, 21, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

	// SGs vs Leap Skulks
	g_coNum[CO_SGSKULKS] = 27
	g_coBlocked[CO_SGSKULKS] = {20, 21, 22, 24, 25, 27, 33, 37, 38, 39, 53, 61, 62, 65, 66, 102, 103, 108, 109, 110, 111, 112, 114, 115, 116, 117, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

	// LMGs vs Cele Lerks
	g_coNum[CO_LMGLERKS] = 26
	g_coBlocked[CO_LMGLERKS] = {21, 22, 27, 33, 37, 38, 39, 53, 61, 62, 64, 65, 66, 101, 102, 103, 108, 109, 110, 111, 112, 114, 116, 117, 118, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

	
	return PLUGIN_CONTINUE
}

public evolve_lerk(id)
{
	if ( get_pcvar_num(g_cvar_combatmode) == CO_LMGLERKS )
	{
		strip_user_weapons(id);
		ns_give_item(id, "weapon_bite2gun");
	}
}

public func_cmd_stripweapons(id)
{
	strip_user_weapons(id);
	ns_give_item(id, "weapon_knife");
	return PLUGIN_HANDLED;
}

/**************************************************************************************
 * Extra user kicker, slot maker, widow maker, roommaker
 **************************************************************************************/

 // Command: /say /makeroom (kick specs)
public cmd_makeroom(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check for too rapid action
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}

	if ( get_maxplayers() > get_playersnum(1) )
	{
		client_print(id, print_chat, "[ENSL] There is still room. No-one kicked.")
		return PLUGIN_HANDLED
	}

	new playerCount = 0, target = 0, maxTime = 0
	new players[HL_NUM_PLAYERS]; get_players(players, playerCount, "h")
	
	// Check readyroom first
	for ( new i = 0; i < playerCount; i++ )
	{
		if ( nst_is_readyroom(players[i]) && (target == 0 || get_user_time(players[i]) < maxTime) )
		{
			target = players[i]
			maxTime = get_user_time(players[i])
		}
	}
	
	// Then go to specs
	if ( target == 0 )
	{
		for ( new i = 0; i < playerCount; i++ )
		{
			if ( nst_is_spectate(players[i]) && (target == 0 || get_user_time(players[i]) < maxTime) )
			{
				target = players[i]
				maxTime = get_user_time(players[i])
			}
		}
	}
	
	if ( target == 0 )
	{
		client_print(id, print_chat, "[ENSL] No suitable player found, contact admin.")
		return PLUGIN_HANDLED
	}
	
	new targetUID = get_user_userid(target)
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new targetName[HL_LEN_NICK + 1]; get_user_name(target, targetName, HL_LEN_NICK)
	server_cmd("kick #%d ^"Room needed for players.^"", targetUID)
	client_print(0, print_chat, "[ENSL] Player (%s) kicked spec: %s", name, targetName)
	log_message("[ENSL] [makeroom] [%s:%s] [%d:%s]", g_userSteamID[g_userPidIndex[id]], name, g_userSteamID[g_userPidIndex[target]], targetName)
	
	return PLUGIN_CONTINUE
}


/**************************************************************************************
 * Client rate and variable checking functions
 **************************************************************************************/

// Task: check all players' rates
public task_check_rates()
{
	// Fetch players (non-dead, non-hltv, non-bots)
	new playerCount, players[HL_NUM_PLAYERS]; get_players(players, playerCount, "ch")
	
	// Loop through all players
	for ( new i = 0; i < playerCount; i++ )
	{						
		func_check_rates(players[i])
	}
	
	return PLUGIN_CONTINUE
}

// Command: check own rates
public cmd_check(id)
{
	// Check that user is authenticated
	new pid = func_authenticate(id)
	
	// Check for too rapid action
	if ( func_check_cmdtime(pid, CT_GENERAL, print_chat) == PLUGIN_HANDLED )
	{
		return PLUGIN_HANDLED
	}
	
	// Set requested ratecheck
	for ( new i = 0; i < RC_NUM_VARS; i++ )
	{
		g_userRCWarn[pid][i] = RC_WARN_REQUEST
	}
	
	// Inform user
	client_print(id, print_chat, "[ENSL] Checking your rates...")
	
	// Check rates manually
	func_check_rates(id)
	
	return PLUGIN_HANDLED
}

// Function: check player's rates and variables
public func_check_rates(id)
{	
	// Check that user is authenticated
	new pid = func_authenticate(id)
	
	// Don't bother with players on HLTV, dead, RR or Spec
	if ( g_userTeam[pid] == HL_RRSPECS || ns_get_class(id) >= 11 )
	{
		return
	}
	
	// Check for illegal values
	query_client_cvar(id, "rate", "func_check_rate", 1, {RC_I_RATE})
	query_client_cvar(id, "cl_updaterate", "func_check_rate", 1, {RC_I_UPDATERATE})
	query_client_cvar(id, "cl_cmdrate", "func_check_rate", 1, {RC_I_CMDRATE})
	query_client_cvar(id, "fps_max", "func_check_rate", 1, {RC_I_FPS})
}

// Function: check player's rate CVAR
public func_check_rate(id, const cvar[], const value[], const args[])
{
	new tMin = 0, tMax = 500, pid = g_userPidIndex[id], nValue = str_to_num(value), pValue = g_userRCVars[pid][args[0]]
	
	// Get min and max values
	switch ( args[0] )
	{
		case RC_I_RATE:
		{
			tMin = get_pcvar_num(g_cvar_minrate)
			tMax = get_pcvar_num(g_cvar_maxrate)
		}
		case RC_I_UPDATERATE:
		{
			tMin = get_pcvar_num(g_cvar_minupdaterate)
			tMax = get_pcvar_num(g_cvar_maxupdaterate)
		}
		case RC_I_CMDRATE:
		{
			tMin = get_pcvar_num(g_cvar_mincmdrate)
			tMax = get_pcvar_num(g_cvar_maxcmdrate)
		}
		case RC_I_FPS:
		{
			tMin = get_pcvar_num(g_cvar_minfps)
			tMax = get_pcvar_num(g_cvar_maxfps)
			
			// TODO: check marine team
			// Remove jetpack
			if ( 125 < str_to_num(value) &&  pev(id, pev_team) == 1 && is_user_alive(id) )
			{
				ns_set_mask(id, MASK_JETPACK, 0)
			}
		}
	}
	
	// If the value is out of range...
	if ( nValue < tMin || nValue > tMax ) 
	{
		new range[RC_LEN_RANGE + 1]; formatex(range, RC_LEN_RANGE, "%d - %d", tMin, tMax)
		func_illegal_value(pid, cvar, nValue, range, args[0])
	}
	// If the value has been changed...
	else if ( nValue != pValue  && pValue != 0 && g_gameStarted && get_pcvar_num(g_cvar_checkrates) == RC_ENABLED_KICK )
	{
		func_illegal_change(pid, cvar, pValue, nValue)
	}
	
	// Save the value
	g_userRCVars[pid][args[0]] = nValue
}

// Function: Action when illegal rate/var value is found
public func_illegal_value(pid, const cvar[], const value, const range[RC_LEN_RANGE + 1], index)
{
	// Get playername and userID
	new name[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[pid], name, HL_LEN_NICK)
	new rc = get_pcvar_num(g_cvar_checkrates), userId = get_user_userid(g_userIdIndex[pid])
		
	// Kick if necessary
	if ( rc == RC_ENABLED_KICK && g_gameStarted )
	{
		client_print(0, print_chat, "[ENSL] Player (%s) was kicked for illegal %s value %d. Correct: %s.", name, cvar, value, range)
		server_cmd("kick #%d ^"[ENSL] CVAR violation (%s set to %d). Correct: %s^"", userId, cvar, value, range)
		log_message("[ENSL] [ratecheck] [%s:%s] [ILLEGAL VALUE] [%s=%d] [KICKED]", g_userSteamID[pid], name, cvar, value)
	}
	// Global warning if necessary
	else if ( (rc == RC_ENABLED_WARN || rc == RC_ENABLED_KICK) && (g_userRCWarn[pid][index] == RC_WARN_NEGATIVE || g_userRCWarn[pid][index] == RC_WARN_REQUEST || g_userRCVars[pid][index] != value) )
	{
		client_print(0, print_chat, "[ENSL] Player (%s) has illegally %s set to %d. Correct: %s.", name, cvar, value, range)
		log_message("[ENSL] [ratecheck] [%s:%s] [ILLEGAL VALUE] [%s=%d]", g_userSteamID[pid], name, cvar, value)
		g_userRCWarn[pid][index] = RC_WARN_POSITIVE
	}
	// Personal warning if necessary
	else if ( g_userRCWarn[pid][index] == RC_WARN_REQUEST )
	{
		client_print(g_userIdIndex[pid], print_chat, "[ENSL] Illegal CVAR value (%s set to %d) / Correct: %s", cvar, value, range)
		g_userRCWarn[pid][index] = RC_WARN_POSITIVE
	}
}

// Function: Action when rate changed
public func_illegal_change(pid, const cvar[], const oldValue, const newValue)
{
	// Get playername and userID
	new name[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[pid], name, HL_LEN_NICK)
	new userId = get_user_userid(g_userIdIndex[pid])
	
	// Perform the kick
	client_print(0, print_chat, "[ENSL] Player (%s) kicked for changing %s from %d to %d.", name, cvar, oldValue, newValue)
	server_cmd("kick #%d ^"[ENSL] CVAR (%s) changed in-game.^"", userId, cvar)
	log_message("[ENSL] [ratecheck] [%s:%s] [ILLEGAL CHANGE] [%s] [%d->%d] [KICKED]", g_userSteamID[pid], name, cvar, oldValue, newValue)
}


/**************************************************************************************
 * Snapshot functions
 **************************************************************************************/
 
// Command: amx_enslshot [name/ID] (force snapshot)
public cmd_enslshot(id, level, cid)
{
	// Authenticate user
	new pid = func_authenticate(id)
	
	// Check if user can access the command
	if ( !func_check_access(id, level, cid, 0) )
	{
		return PLUGIN_HANDLED
	}
	
	// Get referee name + fetch arguments
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	new target[HL_LEN_NICK + 1]; read_argv(1, target, HL_LEN_NICK)
	new targetID = read_argc() > 1 ? func_get_player(id, target) : g_refSSUser
		
	// If user was searched and the ID is not found, abort
	if ( read_argc() > 1 && targetID == -1 )
	{
		return PLUGIN_HANDLED
	}
	// If random was requested but not yet set it
	else if ( read_argc() == 1 && g_refSSUser == -1 )
	{
		// Find the first user on marines/aliens to get the snapshot
		for ( new i = 1; i < U_NUM_IDS; i++ )
		{
			if ( g_userStatus[i] && g_userTeam[i] > HL_RRSPECS )
			{
				g_refSSUser = i
				g_userSSRand[i]++
				break
			}
		}
		
		// Say if a a proper player cannot be found
		if ( g_refSSUser == -1 )
		{
			client_print(pid, print_chat, "[ENSL] Cannot find any aliens/marines.")
			client_print(pid, print_console, "Cannot find any aliens/marines.")
		}
		// Otherwise print it
		else
		{
			new targetName[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[g_refSSUser], targetName, HL_LEN_NICK)
			client_print(pid, print_chat, "[ENSL] Next random snapshot goes to: %s", targetName)
			client_print(pid, print_console, "Next random snapshot goes to: %s", targetName)
		}
		
		return PLUGIN_HANDLED
	}
	
	// Fetch target's pid & name
	new targetPID = func_authenticate(targetID)
	new targetName[HL_LEN_NICK + 1]; get_user_name(targetID, targetName, HL_LEN_NICK)
	
	// Take snaphsot
	func_snapshot(targetPID, 0)
	client_print(targetID, print_chat, "[ENSL] Referee enforced snapshot was taken.")
	
	// If random snapshot was taken
	if ( read_argc() == 1 )
	{
		new lowestUser = -1, lowestLevel = -1
		
		// Find the user with lowest number of random ref shots
		for ( new i = 0; i < U_NUM_IDS; i++ )
		{	
			// If player is in game and on marines/aliens
			if ( g_userStatus[i] && g_userTeam[i] > HL_RRSPECS )
			{
				// Check if user's count is lower or equal to the lowest count
				if ( g_userSSRand[i] < lowestLevel ||  lowestLevel == -1 )
				{
					lowestUser = i
					lowestLevel = g_userSSRand[i]
				}
			}
		}
		
		// If the lowest user was found, set the player as the next victim
		if ( lowestUser != -1 )
		{
			g_refSSUser = lowestUser
			g_userSSRand[lowestUser]++
			
			// Print it
			new targetName[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[g_refSSUser], targetName, HL_LEN_NICK)
			client_print(pid, print_chat, "[ENSL] Next random snapshot goes to: %s", targetName)
			client_print(pid, print_console, "Next random snapshot goes to: %s", targetName)
		}
		// Otherwise print it
		else
		{
			g_refSSUser = -1
			client_print(pid, print_chat, "[ENSL] Cannot find any aliens/marines.")
			client_print(pid, print_console, "Cannot find any aliens/marines.")
		}
	}
	
	return PLUGIN_HANDLED
}

// Function: check if user is next on line for random snapshot
public func_random_snap(pid)
{		
	// Check if user won the snapshot prize
	if ( random_num(1, 3 - g_userSSPassed[pid] + g_userSSCount[pid] * 4) == 1 )
	{
		return true
	}
	
	// Otherwise just increase the passed death counter
	g_userSSPassed[pid]++
	return false
}
 
// Take a snapshot
public func_snapshot(pid, extra)
{
	// Initialize variables
	new msg[2][HL_LEN_HUDMESSAGE + 1], num = 0
	new watermark[] = "`xxxxxxxxx``xx````xx```xxxxxxx``xx```````^n`xx`````````xxx```xx``xx````````xx```````^n`xx`````````xx`x``xx``xx````````xx```````^n`xxxxxx`````xx`x``xx``xxxxxxx```xx```````^n`xx`````````xx``x`xx````````xx``xx```````^n`xx`````````xx``x`xx````````xx``xx```````^n`xxxxxxxxx``xx````xx``xxxxxxx```xxxxxxxx`"
	new name[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[pid], name, HL_LEN_NICK)	
	new strTime[18]; format_time(strTime, 17, "%d.%m.%y %H:%M", func_get_time())
	
	// Increase the shot counter
	g_userSSCount[pid]++
	
	// If its endsnapshot, print count
	if ( extra )
	{	
		// Generate teammate info
		for ( new i = 0; i < U_NUM_IDS && num < 6; i++ )
		{
			// If user is on server and in the same aetm
			if ( g_userStatus[i] && g_userTeam[i] == g_userTeam[pid] )
			{
				// Generate name and cast integers to string
				new iName[HL_LEN_NICK + 1]; get_user_name(g_userIdIndex[i], iName, HL_LEN_NICK)
				new sId[E_LEN_IDS + 1]; num_to_str(g_userEnslId[i], sId, E_LEN_IDS)
				new sTid[E_LEN_IDS + 1]; num_to_str(g_userEnslTid[i], sTid, E_LEN_IDS)
			
				// Insert values
				add(msg[0], HL_LEN_HUDMESSAGE, iName, 15)
				add(msg[0], HL_LEN_HUDMESSAGE, "  ", 2)
				add(msg[0], HL_LEN_HUDMESSAGE, g_userSteamID[i], 15)
				add(msg[0], HL_LEN_HUDMESSAGE, " ", 2)
				add(msg[0], HL_LEN_HUDMESSAGE, g_userIP[i], HL_LEN_IP)
				add(msg[0], HL_LEN_HUDMESSAGE, "^n", 1)
				add(msg[0], HL_LEN_HUDMESSAGE, g_userEnslNick[i], 15)
				add(msg[0], HL_LEN_HUDMESSAGE, "  (", 3)
				add(msg[0], HL_LEN_HUDMESSAGE, sId, E_LEN_IDS)
				add(msg[0], HL_LEN_HUDMESSAGE, "/", 1)
				add(msg[0], HL_LEN_HUDMESSAGE, sTid, E_LEN_IDS)		
				add(msg[0], HL_LEN_HUDMESSAGE, ") ", 3)
				add(msg[0], HL_LEN_HUDMESSAGE, g_userCode[i], CC_LEN_CODE)
				add(msg[0], HL_LEN_HUDMESSAGE, "^n^n")
				
				num++
			}
		}
		
		// Print snapshot count in the last shot
		client_print(g_userIdIndex[pid], print_chat, "[ENSL] %d snapshots were taken.", g_userSSCount[pid])
	}
	// Otherwise just input the specific player
	else
	{		
		// Cast integers ot string
		new sId[E_LEN_IDS + 1]; num_to_str(g_userEnslId[pid], sId, E_LEN_IDS)
		new sTid[E_LEN_IDS + 1]; num_to_str(g_userEnslTid[pid], sTid, E_LEN_IDS)
			
		// Insert values
		add(msg[0], HL_LEN_HUDMESSAGE, name, 15)
		add(msg[0], HL_LEN_HUDMESSAGE, "  ", 2)
		add(msg[0], HL_LEN_HUDMESSAGE, g_userSteamID[pid], 15)
		add(msg[0], HL_LEN_HUDMESSAGE, " ", 2)
		add(msg[0], HL_LEN_HUDMESSAGE, g_userIP[pid], HL_LEN_IP)
		add(msg[0], HL_LEN_HUDMESSAGE, "^n", 1)
		add(msg[0], HL_LEN_HUDMESSAGE, g_userEnslNick[pid], 15)
		add(msg[0], HL_LEN_HUDMESSAGE, "  (", 3)
		add(msg[0], HL_LEN_HUDMESSAGE, sId, E_LEN_IDS)
		add(msg[0], HL_LEN_HUDMESSAGE, "/", 1)
		add(msg[0], HL_LEN_HUDMESSAGE, sTid, E_LEN_IDS)		
		add(msg[0], HL_LEN_HUDMESSAGE, ") ", 3)
		add(msg[0], HL_LEN_HUDMESSAGE, g_userCode[pid], CC_LEN_CODE)
		add(msg[0], HL_LEN_HUDMESSAGE, "^n^n")
	}
	
	// Print teammate info
	set_hudmessage(50, 50, 50, -1.0, 0.05, 0, 0.0, 2.0, 0.0, 0.2, 1)
	show_hudmessage(g_userIdIndex[pid], msg[0])
	
	// Print the hud message including serverside info (nick, ip etc.) + gametimes
	set_hudmessage(120, 0, 0, -1.0, 0.6, 0, 0.0, 2.0, 0.0, 0.2, 4)
	formatex(msg[1], HL_LEN_HUDMESSAGE, "^n%s^n%s^n%s^nSnapshot #%d^n^n%s", name, g_userSteamID[pid], strTime, g_userSSCount[pid], watermark)
	show_hudmessage(g_userIdIndex[pid], msg[1])
	
	// Take the actual snapshot
	client_cmd(g_userIdIndex[pid], "wait; wait; snapshot")
	
	// Log the event
	log_message("[ENSL] [snapshot] [%s:%s]", g_userSteamID[pid], name)
}


/**************************************************************************************
 * Authentication / Player tracking functions
 **************************************************************************************/
 
// Function: init server pid
public func_init_pid()
{
	// Set appropiate values
	g_userIndex = 1
	g_userCount = 1
	g_userAuthed[0] = 1
	g_userIdIndex[0] = 0
	g_userPidIndex[0] = 0
	g_userStatus[0] = 0
	g_userSteamID[0] = "SERVER"
	g_userIP[0] = "127.0.0.1"
	g_userTeam[0] = HL_RRSPECS
}

// Function: authenticate a player
public func_authenticate(id)
{	
	// Do not dbl authenticate
	if ( g_userAuthed[id] )
	{
		new tempsteamid[HL_LEN_STEAMID];
		get_user_authid(id, tempsteamid, HL_LEN_STEAMID);
		replace(tempsteamid, HL_LEN_STEAMID, "STEAM_", "");
		new temppid = g_userPidIndex[id];
		
		if (!equal(tempsteamid, g_userSteamID[temppid]))
		{
			log_message("[ENSL ERROR CHECK] [func_authenticate fail] [%s:%s]", tempsteamid, g_userSteamID[temppid]);
		}
		
		return g_userPidIndex[id]
	}
	
	// Initialize variables
	new pid = -1, steamid[HL_LEN_STEAMID + 1]; get_user_authid(id, steamid, HL_LEN_STEAMID)
	
	// Remove prefix from steam ID
	replace(steamid, HL_LEN_STEAMID, "STEAM_", "")
	
	// Search for an existing history entry
	for ( new i = 0; i < U_NUM_IDS; i++ )
	{
		// Find a match
		if ( equal(steamid, g_userSteamID[i]) )
		{
			pid = i;
		}
	}
	
	// If the user is not found, create a new entry
	if ( pid == -1 )
	{
		// If history is full..
		if ( g_userCount == U_NUM_IDS )
		{
			// Search for the first entry of a player not on server..
			for ( new i = 1; i < U_NUM_IDS; i++ )
			{
				// Exclude players who are playing
				if ( !g_userStatus[i] )
				{
					// Use it and reset variables so old data won't be used
					// NOTE: No need to format variables twice
					g_userIndex = i
					g_userName[i] = "^0"
					g_userSteamID[i] = "^0"
					g_userIP[id] = "^0"
					g_userName[i]  = "^0"
					g_userEnslInfo[i] = 0
					g_userEnslNick[i] = "^0"
					g_userEnslTeam[i] = "^0"
					g_userEnslIP[i] = "^0"
					g_userEnslId[i] = 0
					g_userEnslTid[i] = 0
					g_userEnslLevel[i] = "^0"
					g_userEnslGather[i] = 0
					g_userEnslRank[i] = "^0"
					g_userCode[i] = "^0"
					g_userSSPassed[i] = 0
					g_userSSCount[i] = 0
					g_userSSRand[i] = 0
					g_userVoice[i] = 0
					g_userGagged[i] = 0
					g_userDDTime[i] = 0
					g_userMerc[i] = 0
					g_userWebHex[i] = "^0"
					g_userWebReads[i] = 0
					g_userWebSocket[i] = 0
					
					// Reset rates
					for ( new n = 0; n < RC_NUM_VARS; n++ )
					{
						g_userRCVars[i][n] = 0
						g_userRCWarn[i][n] = 0
					}
					break
				}
			}
		}
	
		// Use the marked entry, save it
		pid = g_userIndex
		
		if (pid >= U_NUM_IDS)
		{
			log_message("[ENSL ERROR CHECK] [pid overflow] [%d]", pid);
		}
		
		copy(g_userSteamID[pid], HL_LEN_STEAMID, steamid)
		
		// Advance the index and the counter
		g_userCount = ( g_userCount < U_NUM_IDS ) ? g_userCount + 1 : U_NUM_IDS
		g_userIndex++
	}
	
	// Save user's information
	g_userPidIndex[id] = pid
	g_userAuthed[id] = 1
	g_userIdIndex[pid] = id
	g_userStatus[pid] = 1
	
	// Save IP
	get_user_ip(id, g_userIP[pid], HL_LEN_IP, 1)
	
	// Reset icon data
	g_userIconFlags[id] = 0
	g_userIconIndex[id] = -1
	
	// Fetch player info when id checking is enabled
	if ( get_pcvar_num(g_cvar_checkids) )
	{
		// Empty existing data if necessary
		if ( g_userEnslInfo[pid] || equal(g_userEnslNick[pid], "") )
		{
			g_userEnslNick[pid] = "^0"
			g_userEnslIP[pid] = "^0"
			g_userEnslTeam[pid] = "^0"
			g_userEnslId[pid] = 0
			g_userEnslTid[pid] = 0
			g_userEnslLevel[pid] = "^0"
			g_userEnslGather[pid] = 0
			g_userEnslRank[pid] = "^0"
		}
		
		// Fetch ensl info
		func_get_enslinfo(pid)
	}
	
	// Log the event
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	log_message("[ENSL] [authenticate] [%s:%s]", g_userSteamID[pid], name)
	
	return pid
}


// Function: check if user has access to this command
public func_check_access(id, level, cid, params)
{
	// Get user's pid
	new pid = func_authenticate(id)

	// Check param count
	if (  read_argc() - 1 < params )
	{
		new cmd[HL_LEN_CMD + 1], info[HL_LEN_INFO + 1], cflags
		get_clcmd(cid, cmd, HL_LEN_CMD, cflags, info, HL_LEN_INFO, level)
		console_print(id, "Wrong parameter count. Usage: %s %s", cmd, info)
		return false
	}
	
	// Check user's access
	if ( (level == ADMIN_LEVEL_E && !func_is_referee(pid)) && !(get_user_flags(id) & level) )
	{
		console_print(id, "You don't have access to that command")
		return false
	}
	
	// Check if ref commands are allowed
	if ( level == ADMIN_LEVEL_E && !get_pcvar_num(g_cvar_refaccess) )
	{
		console_print(id, "Referee admin access is disabled by server")
		return false
	}
	
	return true
}

// Function: check if player is referee
public func_is_referee(pid)
{
	// Ref or admin is okay
	if ( equal(g_userEnslLevel[pid], E_LEVEL_REFEREE, 1) || equal(g_userEnslLevel[pid], E_LEVEL_ADMIN, 1) )
	{
		return true
	}
	
	return false
}


/**************************************************************************************
 * ENSL web query handling functions
 **************************************************************************************/

// Function: Fetches ENSL Server information
public func_get_esi()
{
	// Initialize variables
	func_get_randhex(W_LEN_CH, g_webHex[W_I_ESI])
	new url[W_LEN_URL + 1]; formatex(url, W_LEN_URL, "%s?ch=%s", S_URL, g_webHex[W_I_ESI])
	
	// Write web and start the reading function
	func_write_web(g_webSocket[W_I_ESI], url)
	set_task(0.5, "task_web_esi", T_ESI)
	
	return PLUGIN_CONTINUE
}

// Function: Reads ENSL Server information from web
public task_web_esi()
{
	// Increase reads
	g_webReads[W_I_ESI]++
	
	// If there's data in the socket, read it.
	if ( socket_change(g_webSocket[W_I_ESI], W_TIMEOUT) )
	{
		func_read_web(g_webSocket[W_I_ESI], S_MARK, S_NUM_LINES, g_webData[W_I_ESI], g_webDataLine[W_I_ESI])
	}

	// If socket hasn't finished, retry
	if ( g_webDataLine[W_I_ESI] != S_NUM_LINES && g_webReads[W_I_ESI] < W_READS )
	{
		set_task(0.5, "task_web_esi", T_ESI)
		return
	}
	
	// Finally close the socket and reset vars
	socket_close(g_webSocket[W_I_ESI])
	g_webReads[W_I_ESI] = 0
	
	// Exit if there is no data
	if ( g_webDataLine[W_I_ESI] == 0 )
	{
		return
	}
	
	// Set time
	g_timeFetchedRemote = str_to_num(g_webData[W_I_ESI][1])
	g_timeFetchedLocal = get_gametime()
	
	// Set ENSL Plugin version
	if ( strcmp(VERSION, g_webData[W_I_ESI][2]) != 0 )
	{
		client_print(0, print_chat, "[ENSL] Plugin is outdated, please update.")
	}
	
	// Reset data
	g_webDataLine[W_I_ESI] = 0
	for ( new i = 0; i < W_NUM_LINES; i++ )
	{
		g_webData[W_I_ESI][i] = "^0"
	}

}

// Function: Fetches playerinfo from ENSL DB
public func_get_enslinfo(pid)
{	
	// If task exists, abort
	if ( task_exists(T_USER + pid) )
	{	
		return
	}
	
	// Set task for data send
	new args[1]; args[0] = pid
	set_task(0.1, "task_send_enslinfo", T_USER + pid, args, 1)
}

// Task: Open and send socket data to ENSL DB
public task_send_enslinfo(args[])
{
	new pid = args[0], url[W_LEN_URL + 1]
	
	// Set URL and webhex
	func_get_randhex(W_LEN_CH, g_userWebHex[pid])
	formatex(url, W_LEN_URL, "%s%s?ch=%s", E_URL, g_userSteamID[pid], g_userWebHex[pid])
	// log_message("[DEBUG] url: %s", url)
	
	// Open socket and write to the web
	func_write_web(g_userWebSocket[pid], url)

	// Start reading function
	set_task(0.5, "task_web_enslinfo", T_USER + pid, args, 1)
}

// Task: Attempt to read playerdata from ENSL DB
public task_web_enslinfo(args[])
{
	new pid = args[0]
	
	// Increase reads
	g_userWebReads[pid]++
	
	// If there's data in the socket, read it.
	if ( socket_change(g_userWebSocket[pid], W_TIMEOUT) )
	{
		// log_message("[DEBUG] reading data...")
		func_read_web(g_userWebSocket[pid], E_MARK, E_NUM_LINES, g_userWebData[pid], g_userWebDataLine[pid])
	}
	
	// If socket hasn't finished, retry
	if ( g_userWebDataLine[pid] != E_NUM_LINES && g_userWebReads[pid] < W_READS )
	{
		// log_message("[DEBUG] waiting for data...")
		set_task(0.5, "task_web_enslinfo", T_USER + pid, args, 1)
		return
	}
	
	// Finally close the socket and reset vars
	socket_close(g_userWebSocket[pid])
	g_userWebReads[pid] = 0
	
	// Exit if there is no data
	if ( g_userWebDataLine[pid] == 0 )
	{
		return
	}
	
	// Check if user is banned
	if ( contain(g_userWebData[pid][0], "BANNED") != -1 )
	{
		// Ban user + call disconnect manually since its not called for some reason
		new uid = get_user_userid(g_userIdIndex[pid])
		new mins = (str_to_num(g_userWebData[pid][1]) - func_get_time()) / 60
		log_message("user %s banned for %d mins (time: %d, %s)", g_userSteamID[pid], mins, func_get_time(), g_userWebData[pid][1])
		server_cmd("banid %d STEAM_%s; wait; kick #%d ^"Banned: %.20s^"", mins, g_userSteamID[pid], uid, g_userWebData[pid][2])
		client_disconnect(g_userIdIndex[pid])
		
		// Reset data
		g_userWebDataLine[pid] = 0
		for ( new i = 0; i < E_NUM_LINES; i++ )
		{
			g_userWebData[pid][i] = "^0"
		}
		return
	}

	// Set data..
	copy(g_userEnslNick[pid], E_LEN_NICK, g_userWebData[pid][2])
	copy(g_userEnslIP[pid], E_LEN_IP, g_userWebData[pid][3])
	copy(g_userEnslTeam[pid], E_LEN_TEAM, g_userWebData[pid][4])
	g_userEnslId[pid] = str_to_num(g_userWebData[pid][5])
	g_userEnslTid[pid] = str_to_num(g_userWebData[pid][6])
	copy(g_userEnslLevel[pid], E_LEN_LEVEL, g_userWebData[pid][7])
	copy(g_userEnslRank[pid], E_LEN_RANK, g_userWebData[pid][8])
	g_userIconFlags[g_userIdIndex[pid]] = str_to_num(g_userWebData[pid][9])
	g_userEnslGather[g_userIdIndex[pid]] = str_to_num(g_userWebData[pid][10])
	g_userEnslInfo[pid] = 1
	
	// Enable icon
	g_userIconIndex[g_userIdIndex[pid]] = -1
	func_next_icon(g_userIdIndex[pid])
	func_apply_icon(g_userIdIndex[pid])
	
	// Calculate verification code
	new challenge[CC_LEN_INPUT + 1]
	new sId[E_LEN_IDS + 1]; num_to_str(g_userEnslId[pid], sId, E_LEN_IDS)
	new sTid[E_LEN_IDS + 1]; num_to_str(g_userEnslTid[pid], sTid, E_LEN_IDS)
	add(challenge, CC_LEN_INPUT, g_userSteamID[pid], HL_LEN_STEAMID)
	add(challenge, CC_LEN_INPUT, g_userIP[pid], HL_LEN_IP)
	add(challenge, CC_LEN_INPUT, sId, E_LEN_IDS)
	add(challenge, CC_LEN_INPUT, sTid, E_LEN_IDS)
	func_crypt(challenge, g_userCode[pid])
		
	// Reset data
	g_userWebDataLine[pid] = 0
	for ( new i = 0; i < E_NUM_LINES; i++ )
	{
		g_userWebData[pid][i] = "^0"
	}
}


/**************************************************************************************
 * Web queyring functions
 **************************************************************************************/

// Function: write to web
public func_write_web(&websocket, url[W_LEN_URL + 1])
{
	// Open HTTP socket connection
	new error; websocket = socket_open(W_IP, W_PORT, SOCKET_TCP, error)
	
	// Process any errors
	if ( websocket <= 0 )
	{
		// Log it
		switch ( error )
		{
			case 1: { log_message("[ENSL] Error creating socket"); }
			case 2: { log_message("[ENSL] Error resolving remote hostname"); }
			case 3: { log_message("[ENSL] Error connecting socket"); }
		}
		
		// Print to players
		client_print(0, print_chat, "[ENSL] Unable to connect to ENSL database")
	}

	// Write data
	new output[W_LEN_QUERY + 1]; formatex(output, W_LEN_QUERY, "GET %s HTTP/1.1^r^n", url)
	socket_send(websocket, output, W_LEN_INPUT)
	output = "^0"; formatex(output, W_LEN_QUERY, "Host: %s^r^n^r^n", W_HOST)
	socket_send(websocket, output, W_LEN_INPUT)
}

// Function: read from web
public func_read_web(&websocket, mark[W_LEN_MARK + 1], lines, data[][W_LEN_LINE + 1], &pointer)
{
	// Receive the HTTP data
	new input[W_LEN_INPUT + 1], numChars = socket_recv(websocket, input, W_LEN_INPUT)	
	if ( numChars == 0 )
	{
		return 0
	}
	
	// Go through all lines
	new line[W_LEN_LINE + 1], i = 0, numCopied = 0, found = 0;
	
	// If no data has been fetched before, find the start mark
	if ( pointer == 0 )
	{
		// Search until end of data
		while ( numCopied < numChars )
		{	
			// Copy the line to a variable.Increase copy char ocunter + 1 (newline)
			line = "^0"; numCopied += copyc(line, W_LEN_LINE, input[numCopied], W_NEWLINE) + 1
			
			// If the matching line is found, stop here
			if ( strfind(line, mark) != -1 )
			{
				found = 1
				break
			}
		}
		
		// Exit if the marker was not found
		if ( found == 0 )
		{
			return 0
		}
	}
	
	// Get data line by line
	for ( i = pointer; i < W_NUM_LINES && i < lines && numCopied < numChars; i++ )
	{
		numCopied += copyc(data[i], W_LEN_LINE, input[numCopied], W_NEWLINE) + 1
	}
			
	// Save the data line pointer and return number of copied lines
	new numLines = i - pointer;
	pointer = i
	return numLines
}

/**************************************************************************************
 * Waypoint fix by Asmodee
 **************************************************************************************/

public func_init_waypoints()
{
	for ( new i = 0; i <= g_maxPlayers; i++ )
	{
		g_userOrderType[i] = 0;
	}
}

public func_msg_waypoint()
{
	new id = get_msg_arg_int(1);
	g_userOrderType[id] = get_msg_arg_int(2);
	return PLUGIN_CONTINUE;
}

public cmd_clearwp(id)
{
	if ( pev(id, pev_team) == 1 )
	{
		new type = g_userOrderType[id];
		if ( type )
		{
			message_begin(MSG_ONE, g_msgId_Waypoint, _, id);
			write_byte(id);
			write_byte(type);
			if ( (type == 5) || (type == 3) )
			{
				write_short(0);
			}
			else
			{
				write_coord(0);
				write_coord(0);
				write_coord(0);
			}
			write_byte(0);
			write_byte(0);
			write_byte(1);
			message_end();
			g_userOrderType[id] = 0;
		}
	}
	return PLUGIN_HANDLED;
}


/**************************************************************************************
 * Teamjoining limit for ENSL members
 **************************************************************************************/

public func_check_membersonly(id, pid)
{
	if ( get_pcvar_num(g_cvar_membersonly) == 1 && g_userEnslId[pid] == 0 && g_userEnslGather[pid] > 0 )
	{
		client_print(id, print_center, "^n^n^n^n^n^n Only registered ENSL members who are in THIS gather can play. ^n^n Plese register at www.ensl.org. ^n^n ..and join at www.ensl.org/gather")
		return 0
	}
	return 1
}
 
/**************************************************************************************
 * Floating gun fix
 **************************************************************************************/

public func_msg_death()
{
        new victim = get_msg_arg_int(2);
        if ( pev(victim, pev_team) == 1 )
	{
                func_fix_guns(victim);
        }
}
 
public func_fix_guns(id)
{
        new Float:oPlayer[3];
        pev(id, pev_origin, oPlayer);

        new classname[32];

        new entid = -1;
        while ((entid = EF_FindEntityInSphere(entid, oPlayer, 128.0)) > 0)
	{
                pev(entid, pev_classname, classname, 31);
                if (equal(classname, "weapon_shotgun")||
                 equal(classname, "weapon_welder") ||
                 equal(classname, "weapon_heavymachinegun") ||
                 equal(classname, "weapon_mine") ||
                 equal(classname, "weapon_grenadegun"))
		{
                        EF_DropToFloor(entid);
                }
        }
}


/**************************************************************************************
 * Helper functions
 **************************************************************************************/
 
// Function: color message
stock func_color_msg(id, msg[])
{
	new name[HL_LEN_NICK + 1]; get_user_name(id, name, HL_LEN_NICK)
	
	// If user is referee, make comments red
	if ( func_is_referee(g_userPidIndex[id]) )
	{
		format(msg, HL_LEN_SAY, "^x04%s: ^x01%s", name, msg)
	}

	// Send it to all
	new playerCount, player, players[HL_NUM_PLAYERS]; get_players(players, playerCount)
	for ( new i = 0; i < playerCount; i++ )
	{
		player = players[i]
		message_begin(MSG_ALL, g_msgId_SayText, {0,0,0}, player)
		write_byte(player)
		write_string(msg)
		message_end()
	}
}

// Function: check that command isn't used too quickly
public func_check_cmdtime(pid, cmdId, type)
{
	// Get elapsed time since last slow ENSL cmd
	new Float:elapsed = get_gametime() - g_userCmdTime[pid][cmdId]
	
	// If the user is trying to use the command too often, abort
	if ( elapsed < G_FREQ )
	{
		// Print message when appropiate
		if ( type != 0 && !g_userCmdWarn[pid][cmdId] )
		{
			g_userCmdWarn[pid][cmdId] = 1
			client_print(g_userIdIndex[pid], type, "[ENSL] Please wait %.1fs before using this command.", G_FREQ - elapsed)
		}
		
		return PLUGIN_HANDLED
	}
	
	// Set the last time command used
	g_userCmdTime[pid][cmdId] = get_gametime()
	g_userCmdWarn[pid][cmdId] = 0
	
	return PLUGIN_CONTINUE
}

// Print a table of data
stock func_gentable(numColumn, lenColumn, data[HL_NUM_COLUMN][HL_LEN_COLUMN + 1], output[HL_LEN_MSG + 1])
{	
	// Go through all columns
	for ( new i = 0; i < numColumn; i++ )
	{
		// Calculate number of space needed
		new space[HL_LEN_COLUMN + 1], numSpace = lenColumn - strlen(data[i])
		
		// Add the space
		for ( new n = 0; n < numSpace; n++ )
		{
			add(space, lenColumn, " ")
		}
		
		// Join the data and space
		add(output, HL_LEN_MSG, data[i])
		add(output, HL_LEN_MSG, space)
	}
}

// Function: format time: eg. 2 minutes 10 seconds
stock func_format_time(Float:inSecs, timeText[TT_LEN_OUTPUT + 1])
{	
	// Calculate result. Minutes have to be floored obviously
	new Float:fMins = floatdiv(floatabs(inSecs), 60.0)
	new outMins = floatround(fMins, floatround_floor)
	new outSecs = floatround(floatfract(fMins) * 60, floatround_round)
	
	// Change 1min 60 secs -> 2mins
	if ( outSecs == 60 )
	{
		outMins++
		outSecs = 0
	}
	
	// Print different text if seconds is zero
	if ( outSecs == 0 )
	{
		formatex(timeText, TT_LEN_OUTPUT, "%d mins", outMins)
	}
	else if ( outMins == 0 )
	{
		formatex(timeText, TT_LEN_OUTPUT, "%d secs", outSecs)
	}
	else
	{
		formatex(timeText, TT_LEN_OUTPUT, "%d mins and %d secs", outMins, outSecs)
	}
	
	return PLUGIN_CONTINUE
}

// Function: print confirmation code
stock func_crypt(input[CC_LEN_INPUT + 1], output[CC_LEN_CODE + 1])
{
	// Init variables
	new chars[6] = { 'A', 'B', 'C', 'D', 'E', 'F'}, nums[10], curChar[2], curPos = 0, curNum = 0
	new md5input[HL_LEN_MSG + 1]; formatex(md5input, HL_LEN_MSG, "%s%s%s", CC_PASSCODE1, input, CC_PASSCODE2)
	new sum[CC_LEN_MD5 + 1]; md5(md5input, sum); strtoupper(sum)
	new lastChar[2]; copy(lastChar, 1, sum[31])
	new lastPos = str_to_num(lastChar)
	
	// Puzl it
	for ( new i = 0; i < CC_LEN_CODE; i++ )
	{
		// Get the current character and position out of it
		copy(curChar, 1, sum[i])
		curPos = str_to_num(curChar)
		
		// Edit the position depending on the value
		if ( curPos == 0 )
		{
			curPos = power(lastPos, i % 4)
		}
		else if ( (curPos % 4) == 0 )
		{
			curPos = curPos * lastPos + i
		}
		else if ( (curPos % 3) == 0 )
		{
			curPos = power(curPos, i % 4)
		}
		else if ( (curPos % 2) == 0 )
		{
			curPos = curPos * i + curPos
		}
		
		// Decrease the position if it is over the top
		if ( curPos > 31 )
		{
			curPos = curPos % 32
		}
		
		// Save the character by position
		copy(curChar, 1, sum[CC_LEN_MD5 - 2 - curPos])
		curNum = str_to_num(curChar)
		
		// If the code has been used before, mix it more
		if ( nums[curNum] > 0 )
		{
			// If it is letter or zero
			if ( curNum == 0 )
			{
				copy(curChar, 1, chars[curPos % 6])
				curNum = str_to_num(curChar)				
			}
			// Or if it is a number
			else
			{
				num_to_str(curPos % 10, curChar, 1)
				curNum = str_to_num(curChar)				
			}

		}
		
		// Save the final result
		nums[curNum]++
		add(output, CC_LEN_CODE, curChar, 1)
		copy(lastChar, 1, curChar)
		lastPos = curPos
	}
	
	return PLUGIN_CONTINUE
}

// Function: generate a random hex value
stock func_get_randhex(length, output[])
{
	static hex[6][2] = { "A", "B", "C", "D", "E", "F" }
	
	for ( new i = 0; i < length; i++ )
	{
		new num = random(15), str[2]
		
		// Add character value
		if ( num > 9 )
		{
			add(output, length, hex[num - 10], 1)
		}
		// Or number value
		else
		{
			num_to_str(num, str, 1)
			add(output, length, str, 1)
		}
	}
}

// Function: search for a player by username / ID
stock func_get_player(id, target[HL_LEN_NICK + 1])
{
	new targetID = -1
	
	// Check if hash can be found -> ID search
	if ( strfind(target, "#", 0, 0) != -1 )
	{
		// Get numerical value
		targetID = str_to_num(target[1])
		
		// Check player ID
		if ( !is_user_connected(targetID) )
		{
			console_print(id, "Unable to find username by user ID: %d", targetID)
			return -1
		}
	}
	// Otherwise its a string search for playernames
	else
	{
		// Fetch players (non-dead, non-hltv, non-bots)
		new playerCount, players[HL_NUM_PLAYERS], targetName[HL_LEN_NICK + 1]
		get_players(players, playerCount)

		// Loop through all players.
		// NOTE: ID list also includes non-authed players
		for ( new i = 0; i < playerCount; i++ )
		{
			// Fetch playername
			targetName = "^0"; get_user_name(players[i], targetName, HL_LEN_NICK)
				
			// If it matches...
			if ( strfind(targetName, target) != -1 && players[i] != 0 )
			{
				// If the string is in 2+ players' name
				if ( targetID != -1 )
				{
					console_print(id, "Two users match, aborting.")
					return -1
				}
				// Otherwise just save it
				else
				{
					targetID = players[i]
				}
			}
		}
		
		// If not found, announce it
		if ( targetID == -1 )
		{
			console_print(id, "Unable to find user by username: %s", target)
		}
	}
	
	return targetID
}

// Function: check team
stock func_get_team(id, input[HL_LEN_TEAM + 1])
{	
	new intTarget = str_to_num(input)
	
	// Check if it is a valid team
	if ( intTarget != 1 && intTarget != 2 )
	{
		console_print(id, "Invalid team. Please enter: 1=marines or 2=aliens")
		return -1
	}
	
	return intTarget
}

// Function: parse arguments
stock func_parse_string(string[], len)
{
	replace_all(string, len, ";", "")
	replace_all(string, len, "^2", "")
	replace_all(string, len, "'", "")
	
	return true
}

// Function: URL encode a string
stock func_urlencode(input[], output[], outLen)
{
	new c, inLen = strlen(input), letter1, letter2, hex[3]
	
	for ( new i = 0; i < inLen; i++ )
	{
		c = input[i]
		
		// Find non-alphanumeric characters, and replace them with %ASCIIHEX
		if ( c < 48 || (c > 57 && c < 65) || (c > 90 && c < 97) || c > 122 )
		{
			letter1 = c/16; letter2 = c % 16
			hex[0] = (letter1 < 10) ? letter1 + 48 : letter1 + 55 
			hex[1] = (letter2 < 10) ? letter2 + 48 : letter2 + 55 
			add(output, outLen, "%", 1)
			add(output, outLen, hex, 2)
		}
		// Add other characters normally
		else
		{
			add(output, outLen, input[i], 1)
		}
	}	
}

// Function: get most precise time
stock func_get_time()
{
	// If ENSL time has been fetched, use it
	if ( g_timeFetchedRemote > 0  )
	{
		return floatround(g_timeFetchedRemote + (get_gametime() - g_timeFetchedLocal), floatround_ceil) 
	}
	
	// Otherwise use unreliable system time
	return get_systime()
}

// Convert decimal to hex
stock func_dec_to_hex(dec, hex[3])
{
	static l
	hex[0] = (letter1 < 10) ? letter1+48 : letter1+55 
	hex[1] = (letter2 < 10) ? letter2+48 : letter2+55
}
