/*
 *	Ptahhotep's Team Balancer (PTB)
 *	Version 1.7 BETA
 *	AUTHOR: Ptahhotep (ptahhotep@planethalflife.com)
 *
 *  1.7b3   PTB converted for AMX Mod by OLO
 *  1.7b5   Modified for CS 1.6 by XAD.
 *          Thanks Redmist ("slot1" to close old-style-menus).
 *          Added fix by 'Panzermensh' for lost kill when moved
 *          (thanks r0otd0wn).
 *  1.7b6   Ported to AMX Mod X 0.16 by XAD.
 *          Changed config file path to "addons/amxx/configs/".
 *  1.7b7   Added admin immunity by Ingerfara. (Thanks to EpsychE and rootdown)
 *  1.7b8	JGHG: changed the path of ptb.cfg to use AMXx's default custom path. (addons/amxx/custom/ptb.cfg)
 */

#include <amxmodx>
#include <amxmisc>

#define ACCESS_PTB ADMIN_RCON

// team ids
#define UNASSIGNED	 	0
#define TS 				1
#define CTS				2
#define AUTO_TEAM 		5

new const PTB_VERSION[] = "1.7b8"

// Uncomment to activate log debug messages.
//#define PTB_DEBUG
//#define PTB_DEBUG2

// If you experiance some problems with team switching then uncomment line below
//#define MANUAL_SWITCH

// If you are running CS 1.5 then comment line below
#define CS16_SWITCH

// team selection control
new bool:PTB_LIMITJOIN = true // set limits on team joining
new PTB_LIMITAFTER = 0 // number of rounds after which teams limiting begins
new PTB_LIMITMIN = 0 // number of minimum players on map for team limiting
new PTB_MAXSIZE = 10 // maximum team size per team
new PTB_MAXDIFF = 2 // maximum team size difference
new PTB_AUTOROUNDS = 3 // number of first rounds into match, which allow autojoin only
new PTB_WTJAUTO = 3 // wtj tries needed to become autojoined
new PTB_WTJKICK = 5 // wtj tries needed to become kicked
new bool:PTB_KICK = true // kick for wtj counts
new bool:PTB_SAVEWTJ = false // save wtjs to wtj.log

// team balancing actions
new bool:PTB_SWITCH = true // switch/transfer players
new PTB_SWITCHAFTER = 0 // number of rounds after which switching begins
new PTB_SWITCHMIN = 3 // number of minimum players on map for switching
new PTB_SWITCHFREQ = 1 // relative next possible switch round
new PTB_PLAYERFREQ = 3 // relative next possible switch round for player
new PTB_FORCESWITCH = 3 // number of tries after which PTB switches alive, if neccessary
new bool:PTB_DEADONLY = false // switch dead only

// messages
new bool:PTB_TELLWTJ = true // tell about wtj tries
new bool:PTB_ANNOUNCE = true // announce team status at beginning of round
new bool:PTB_SAYOK = true // announce team status, if teams are alright
new bool:PTB_TYPESAY = true // use typesay

// team strength limits
new PTB_MAXSTREAK = 2 // max. allowed team win streak
new PTB_MAXSCORE = 2 // max. allowed team score difference
new Float:PTB_MINRATING = 1.5 // minimum critical team rating
new Float:PTB_MAXRATING = 2.0 // maximum critical team rating
new Float:PTB_SUPERRATING = 3.0 // super critical team rating
new PTB_MAXINCIDENTS = 50 // maximum kills + deaths before the score is divided by PTB_SCALEDOWN
new PTB_SCALEDOWN = 2 // divisor for kills and deaths, when PTB_MAXINCIDENTS is reached

// sorted player indices are 0-based
new sortedTeams[3][32]
new sortedValidTargets[3][32]
new validTargetCounts[3]

new teamKills[3]
new teamDeaths[3]
new teamScores[3]
new winStreaks[3]

new wtConditions[3]
new winnerTeam
new loserTeam

new Float:ctKD
new Float:tKD
new Float:ctStrength
new Float:tStrength
new Float:ctRating
new Float:tRating

// player arrays are 1-based, there is no player 0
#if defined CS16_SWITCH
new clientVGUIMenu[33][2]
#endif
new bool:isBeingTransfered[33]
new playerTeam[33]
new lastRoundSwitched[33]
new wtjCount[33]
new teamCounts[3]
new kills[33]
new deaths[33]

new roundCounter
new lastSwitchRound
new couldNotSwitchCounter

new lastTeamBalanceCheck[32]

public plugin_init(){
	register_plugin("Team Balancer",PTB_VERSION,"Ptahhotep")
	register_cvar("amx_ptb_version",PTB_VERSION,FCVAR_SERVER|FCVAR_EXTDLL|FCVAR_UNLOGGED|FCVAR_SPONLY)
#if defined CS16_SWITCH
	register_menucmd(register_menuid("Team_Select",1),(1<<0)|(1<<1)|(1<<4),"teamselect")
	register_event("ShowMenu","menuclass","b","4&CT_Select","4&Terrorist_Select")
	register_clcmd("jointeam","jointeam")
#else
	register_menucmd(register_menuid("Team_Select",1),(1<<0)|(1<<1)|(1<<4),"teamselect")
	register_menucmd(-2,(1<<0)|(1<<1)|(1<<4),"teamselect")    // VGUI menu
#endif
	register_event("SendAudio","round_end","a","2=%!MRAD_terwin","2=%!MRAD_ctwin","2=%!MRAD_rounddraw") // Round End
	register_event("TeamScore","team_score","a") // Team Score
	register_event("RoundTime", "new_round", "bc") // Round Time
	register_event("DeathMsg","death_msg","a") // Kill
	register_event("TeamInfo","team_assign","a") // Team Assigment (also UNASSIGNED and SPECTATOR)
	register_event("TextMsg","team_join","a","1=1","2&Game_join_te","2&Game_join_ct") // Team Joining
	register_event("TextMsg","game_restart","a","1=4","2&#Game_C","2&#Game_w") // Game restart
	register_concmd("amx_ptb","admin_ptb",ACCESS_PTB,"- displays PTB options")

	new custompath[256]
	get_customdir(custompath, 255)
	format(custompath, 255, "exec %s/ptb.cfg", custompath)
	server_cmd(custompath)

	#if defined PTB_DEBUG2
	register_event("VGUIMenu","debugmenu","b")
	register_event("ShowMenu","debugmenu","b")
	register_clcmd("say /team","switchteam")
	register_clcmd("say /vgui","switchvgui")
	register_clcmd("menuselect","debugselect")
	#endif

#if defined CS16_SWITCH
	// Init clients VGUI-menu setting
	// Set terminating 0 to allow use of char processing instead of string
	// to improve performance.
	for (new i=0;i<33;i++){
		clientVGUIMenu[i][0] = '0'
		clientVGUIMenu[i][1] = 0
	}
#endif

	return PLUGIN_CONTINUE
}

#if defined PTB_DEBUG2
public client_command(id) {
	new arg[256]
	read_argv( 0, arg , 255 )
	log_message( "[PTB] Command #%i: %s",id,arg)
	return PLUGIN_CONTINUE
}

public debugmenu(id) {
	new d1 = read_data(1)
	new d2 = read_data(2)
	new d3 = read_data(3)
	new d4[11]; read_data(4,d4,10)
	log_message( "[PTB] MenuEvent #%i %d %d %d %s", id, d1, d2, d3, d4)
}

public debugselect(id) {
	new d3, d4
	get_user_menu(id,d3,d4)
	log_message( "[PTB] MenuSelect #%i, %d %d", id, d3 ,d4)
}

public switchteam(id) {
	log_message("[PTB] Switch Team #%i", id )
	transferPlayer(id)
}

public switchvgui(id) {
	new opt[2] = { 0, ... }
	get_user_info(id,"_vgui_menus",opt,1)
	opt[0] = (opt[0]=='0')?'1':'0'
	opt[1] = 0
	log_message("[PTB] Swith VGUI %s",(opt[0]=='0')?"off":"on")
	set_user_info(id,"_vgui_menus",opt)
}
#endif

Float:fabs(Float:n)
	return ( n < 0.0 ) ? -n : n

Float:fdivWorkaround(Float:nom, Float:denom){
	if ( denom == 0.0) return nom
	return fabs(nom / denom)
}

doTypesay(string[], duration, r, g, b) {
	if (!PTB_TYPESAY) return
	set_hudmessage(r, g, b, 0.05, 0.55, 0, 6.0, float(duration) , 0.5, 0.15, 2)
	show_hudmessage(0, string )
}

say(string[]){
	client_print(0,print_chat,string)
	server_print(string)
}

bool:check_param_bool(param[])
	return (equali(param, "on") || equal(param, "1")) ? true : false

Float:check_param_float(param[],Float:n){
	new Float:a = floatstr(param)
	if (a < n) a = n
	return a
}

check_param_num(param[],n){
	new a = str_to_num(param)
	if (a < n) a = n
	return a
}

transferPlayer(id){
	if (isBeingTransfered[id]) return
	isBeingTransfered[id] = true
#if defined CS16_SWITCH
	get_user_info(id,"_vgui_menus",clientVGUIMenu[id],1)
	#if defined PTB_DEBUG
	log_message("[PTB] Transfer Player #%i (VGUI %s)",id,clientVGUIMenu[id])
	#endif
	if (clientVGUIMenu[id][0] != '0') {
		set_user_info(id,"_vgui_menus","0")
		new args[1]
		args[0] = id
		set_task(0.01,"delay_transfer",100+id,args,1)
	} else {
		user_kill(id,1)
		engclient_cmd(id,"jointeam",(playerTeam[id]==TS) ? "2" : "1")
		engclient_cmd(id,"joinclass","5")
	}
#else
#if defined MANUAL_SWITCH
	user_kill(id,1)
	client_cmd(id,"chooseteam;menuselect %d;menuselect 5",(playerTeam[id]==TS) ? 2 : 1)
	client_cmd(id,"wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;wait;slot1")
#else
	user_kill(id,1)
	engclient_cmd(id,"chooseteam")
	engclient_cmd(id,"menuselect",(playerTeam[id]==TS) ? "2" : "1")
	engclient_cmd(id,"menuselect","5")
	client_cmd(id,"slot1")
#endif
#endif

}

#if defined CS16_SWITCH
public delay_transfer(args[]) {
	new id = args[0]
	#if defined PTB_DEBUG
	log_message("[PTB] Delayed Transfer #%i ('%s',%i)",id,clientVGUIMenu[id],isBeingTransfered[id])
	#endif
	if (!isBeingTransfered[id]) return
	user_kill(id,1)
	engclient_cmd(id,"jointeam",(playerTeam[id]==TS) ? "2" : "1")
	engclient_cmd(id,"joinclass","5")
	if (clientVGUIMenu[id][0] != '0') {
		set_user_info( id, "_vgui_menus", clientVGUIMenu[id] )
		clientVGUIMenu[id][0] = '0'
	}
}
#endif

actAtEndOfRound(){
	if (!PTB_SWITCH) return
	// skip switching for the first few rounds
	if (roundCounter <= PTB_SWITCHAFTER) return
	// honor switch frequency setting
	if (roundCounter - lastSwitchRound < PTB_SWITCHFREQ) return
	// skip switching for a small number of players
	if (get_playersnum() < PTB_SWITCHMIN) return
	say("PTB: Round ended, checking teams.")
	checkTeamBalance()
	if (winnerTeam) {
		sortTeam(CTS)
		sortTeam(TS)
		if (teamCounts[winnerTeam] <= teamCounts[loserTeam])
			doSwitch()
		else if (teamCounts[loserTeam] < teamCounts[winnerTeam])
			doTransfer()
	}
}

createValidTargets(theTeam, bool:deadonly) {
	new n = 0
	for (new i = 0; i < teamCounts[theTeam]; ++i) {
		// Protection for admins
		if (get_user_flags(sortedTeams[theTeam][i]) & ADMIN_IMMUNITY) continue
		// Dead only condition
		if ( deadonly && is_user_alive(sortedTeams[theTeam][i]) ) continue
		// Already switched or in PTB_PLAYERFREQ time condition
		if ((lastRoundSwitched[sortedTeams[theTeam][i]] == roundCounter) ||
			(roundCounter - lastRoundSwitched[sortedTeams[theTeam][i]] < PTB_PLAYERFREQ))	continue
		sortedValidTargets[theTeam][n++] = sortedTeams[theTeam][i]
	}
	validTargetCounts[theTeam] = n
}

sortTeam(theTeam) {
	// create list of players
	new n = 0, a = get_maxplayers()
	for (new i = 1; i <= a; ++i) {
		// Get only members of specified team
		if (playerTeam[i] != theTeam) continue
		sortedTeams[theTeam][n++] = i
	}
	// do a selection sort
	new swap, count = n
	for (new i = count-1; i > 0; --i){
		for (new k = i-1; k >= 0; --k){
			// compare players (kills better then other or if equal then with less deaths)
			if ( (kills[sortedTeams[theTeam][k]]<kills[sortedTeams[theTeam][i]])
				|| ( (kills[sortedTeams[theTeam][k]]==kills[sortedTeams[theTeam][i]])	&&
				(deaths[sortedTeams[theTeam][k]]>deaths[sortedTeams[theTeam][i]]))) {
				// swap
				swap = sortedTeams[theTeam][k]
				sortedTeams[theTeam][k] = sortedTeams[theTeam][i]
				sortedTeams[theTeam][i] = swap
			}
		}
	}
}

Float:score(team, toBeAdded=0, toBeRemoved=0){
	new Float:sumKD = 0.0
	new a = get_maxplayers()
	for (new i = 1; i <= a; ++i) {
		if ( (playerTeam[i]!=team&&i!=toBeAdded)	|| (i==toBeRemoved)	)
			continue
		sumKD += fdivWorkaround(float(kills[i]), float(deaths[i]))
	}
	new Float:strength = float(teamCounts[team])
	if (sumKD) strength *= sumKD
	return strength
}

doSwitch() {
	new text[256]
	//displayStatistics(0,true)
	// don't switch, if at least one team is empty
	if ( teamCounts[winnerTeam] == 0 || teamCounts[loserTeam] == 0 ) {
		copy(text,255, "PTB: Can't switch players, need players in each team.")
		doTypesay(text, 5, 0, 255, 0)
		say(text)
		return
	}
	// don't switch, if winner is alone (RULER!!!)
	if (teamCounts[winnerTeam] == 1) {
		copy(text,255, "PTB: Won't switch players, best player makes the winning team.")
		doTypesay(text, 5, 0, 255, 0)
		say(text)
		return
	}
	// don't switch, if both teams are full
	if (teamCounts[winnerTeam] >= PTB_MAXSIZE && teamCounts[loserTeam] >= PTB_MAXSIZE) {
		copy(text,255, "PTB: Can't switch players, both teams are full.")
		doTypesay(text, 5, 0, 255, 0)
		say(text)
		return
	}
	if (!PTB_DEADONLY || couldNotSwitchCounter > PTB_FORCESWITCH) {
		// choose from random top or bottom x
		createValidTargets(winnerTeam, false)
		createValidTargets(loserTeam, false)

		if (validTargetCounts[winnerTeam] == 0 || validTargetCounts[loserTeam] == 0) {
			++couldNotSwitchCounter
			copy(text,255, "PTB: Can't switch players, need valid target in each team.")
			doTypesay(text, 5, 0, 255, 0)
			say(text)
			return
		}
	}
	else {
		//say("switch dead")
		createValidTargets(winnerTeam, true)
		createValidTargets(loserTeam, true)

		if (validTargetCounts[winnerTeam] == 0 || validTargetCounts[loserTeam] == 0) {
			if (++couldNotSwitchCounter > PTB_FORCESWITCH) {
				say("PTB: Couldn't switch dead, switching alive.")
				doSwitch()
				return
			}
			copy(text, 255,"PTB: Can't switch players, need valid target in each team.")
			doTypesay(text, 5, 0, 255, 0)
			say(text)
			return
		}
	}
	// Now search through the possible 1 to 1 swaps to equalize the strength as much as possible
	new Float:closestScore = fabs(score(winnerTeam) - score(loserTeam))
	new Float:myScore, toLoser, toWinner
	new winner = 0
	new loser = 0
	for (new w = 0; w < validTargetCounts[winnerTeam]; ++w) {
		toLoser = sortedValidTargets[winnerTeam][w]
		for (new l = 0; l < validTargetCounts[loserTeam]; ++l) {
			toWinner = sortedValidTargets[loserTeam][l]
			myScore = fabs(score(winnerTeam, toWinner, toLoser) - score(loserTeam, toLoser, toWinner))
			if (myScore < closestScore) {
				closestScore = myScore
				winner = toLoser
				loser = toWinner
			}
		}
	}
	if (winner == 0 && loser == 0) {
		copy(text, 255,"PTB: No switch would improve team balancing.")
		doTypesay(text, 5, 0, 255, 0)
		say(text)
		return
	}
	couldNotSwitchCounter = 0
	lastSwitchRound = roundCounter
	new winnerName[32], loserName[32]
	get_user_name(winner,winnerName,31)
	get_user_name(loser,loserName,31)
	// if one team is full, first move the the player from the full team ...
	if (teamCounts[winnerTeam] >= PTB_MAXSIZE){
		transferPlayer(winner)
		transferPlayer(loser)
	}
	else {
		transferPlayer(loser)
		transferPlayer(winner)
	}
	format(text,255,"PTB: Switching %s with %s.",winnerName,loserName)
	doTypesay(text, 5, 0, 255, 0)
	say(text)
}

doTransfer() {
	//displayStatistics(0,true)
	new text[256]
	if (teamCounts[winnerTeam] == 0) {
			copy(text,255, "PTB: Can't switch players, need players in each team.")
			doTypesay(text, 5, 0, 255, 0)
			say(text)
			return
	}
	if (teamCounts[loserTeam] >= PTB_MAXSIZE) {
		copy(text,255, "PTB: Can't transfer player, losing team is full.")
		doTypesay(text, 5, 0, 255, 0)
		say(text)
		return
	}
	if (!PTB_DEADONLY || couldNotSwitchCounter > PTB_FORCESWITCH) {
		createValidTargets(winnerTeam, false)
		if (validTargetCounts[winnerTeam] == 0) {
			copy(text,255, "PTB: Can't transfer player, no valid target in winning team.")
			doTypesay(text, 5, 0, 255, 0)
			say(text)
			++couldNotSwitchCounter
			return
		}
	}
	else {
		//say("switch dead")
		createValidTargets(winnerTeam, true)
		if (validTargetCounts[winnerTeam] == 0) {
			if (++couldNotSwitchCounter > PTB_FORCESWITCH) {
				say("PTB: Couldn't transfer dead, transferring alive.")
				doTransfer()
				return
			}
			copy(text,255, "PTB: Can't transfer player, no valid target in winning team.")
			doTypesay(text, 5, 0, 255, 0)
			say(text)
			return
		}
	}
	new Float:closestScore = fabs(score(winnerTeam) - score(loserTeam))
	new Float:myScore, toLoser
	new winner = 0
	for (new w = 0; w < validTargetCounts[winnerTeam]; ++w) {
		toLoser = sortedValidTargets[winnerTeam][w]
		myScore = fabs(score(winnerTeam, 0, toLoser) - score(loserTeam, toLoser, 0))
		if (myScore < closestScore) {
			closestScore = myScore
			winner = toLoser
		}
	}
	if (winner == 0) {
		copy(text, 255,"PTB: No transfer would improve team balancing.")
		doTypesay(text, 5, 0, 255, 0)
		say(text)
		return
	}
	couldNotSwitchCounter = 0
	new winnerName[32]
	get_user_name(winner,winnerName,31)
	transferPlayer(winner)
	format(text,255,"PTB: Transfering %s to the %s",winnerName, (winnerTeam == CTS) ? "Ts" : "CTs")
	doTypesay(text, 5, 0, 255, 0)
	say(text)
}

checkTeamBalance() {

	get_time("%m/%d/%Y - %H:%M:%S",lastTeamBalanceCheck,31 )
	calcTeamScores()
	ctStrength = score(CTS)
	tStrength = score(TS)
	ctRating = fdivWorkaround(ctStrength, tStrength)
	tRating = fdivWorkaround(tStrength, ctStrength)
	wtConditions[TS] = 0
	wtConditions[CTS] = 0

	// compare scores for unequal rating scores
	if (teamScores[TS] - teamScores[CTS] > PTB_MAXSCORE && tRating >= PTB_MINRATING)
		wtConditions[TS]++

	if (teamScores[CTS] - teamScores[TS] > PTB_MAXSCORE && ctRating >= PTB_MINRATING)
		wtConditions[CTS]++

	// check streaks for unequal rating scores
	if (winStreaks[TS] > PTB_MAXSTREAK && tRating >= PTB_MINRATING)
		wtConditions[TS]++

	if (winStreaks[CTS] > PTB_MAXSTREAK && ctRating >= PTB_MINRATING)
		wtConditions[CTS]++

	// check ratings
	if (tRating >= PTB_MAXRATING)
		wtConditions[TS]++

	if (ctRating >= PTB_MAXRATING)
		wtConditions[CTS]++


	// check ratings
	if (tRating >= PTB_SUPERRATING)
		wtConditions[TS]++

	if (ctRating >= PTB_SUPERRATING)
		wtConditions[CTS]++


	// check team sizes for unequal ratings
	if (teamCounts[TS] > teamCounts[CTS] && tRating >= PTB_MINRATING)
		wtConditions[TS]++

	if (teamCounts[CTS] > teamCounts[TS] && ctRating >= PTB_MINRATING)
		wtConditions[CTS]++

	// check conditions
	if (wtConditions[TS] >= 2) {
		winnerTeam = TS
		loserTeam = CTS
	}
	else if (wtConditions[CTS] >= 2) {
		winnerTeam = CTS
		loserTeam = TS
	}
	else {
		winnerTeam = 0
		loserTeam = 0
	}
}

manageWtjFile(id) {
	if (!PTB_SAVEWTJ) return
	//say("Trying to write wtj.log ....")
	if (wtjCount[id] < 4) return
	//say("wtj.log should be written to now ....")
	new text[256], stime[32], mapname[32], name[32], authid[32]
	get_time("%m/%d/%Y - %H:%M:%S",stime,31 )
	get_mapname(mapname,31)
	get_user_name(id,name,31)
	get_user_authid(id,authid,31)
	format(text, 255, "%s %s <%s> %s", stime, name, authid, mapname)
	write_file("addons/amx/wtj.log", text)
}

#if defined CS16_SWITCH
public menuclass(id) {
	#if defined PTB_DEBUG
	log_message("[PTB] Show MenuClass #%i ('%s',%i)",id,clientVGUIMenu[id],isBeingTransfered[id])
	#endif
	if (!isBeingTransfered[id]) return PLUGIN_CONTINUE
	client_cmd(id,"slot1")
	isBeingTransfered[id] = false
	return PLUGIN_CONTINUE
}
#endif

#if defined CS16_SWITCH
public jointeam(id) {
	new arg[2]
	read_argv(1,arg,1)
	#if defined PTB_DEBUG
	log_message( "[PTB] Join Team #%i %s (%i)", id, arg, isBeingTransfered[id] )
	#endif
	if (isBeingTransfered[id]) return PLUGIN_HANDLED
	return checkTeamSwitch(id,str_to_num(arg)) // team is key pressed + 1
}
#endif

public teamselect(id,key) {
	#if defined PTB_DEBUG
	log_message( "[PTB] Team Select #%i %i", id, key+1 )
	#endif
	return checkTeamSwitch(id,key+1) // team is key pressed + 1
}

checkTeamSwitch(id,iNewTeam) {
	// don't care where player joins
	if (!PTB_LIMITJOIN) return PLUGIN_CONTINUE
	// Protection for admins
	if (get_user_flags(id) & ADMIN_IMMUNITY) return PLUGIN_CONTINUE
	// players is transfered so don't care with rest
	if (isBeingTransfered[id]) {
		//say("TRANSFER")
		isBeingTransfered[id] = false
		return PLUGIN_CONTINUE
	}
	//say("NO TRANSFER")
	// skip limiting for a few rounds into the map
	if (PTB_LIMITAFTER && roundCounter <= PTB_LIMITAFTER) return PLUGIN_CONTINUE
	// skip limiting for a small number of players
	if (get_playersnum() < PTB_LIMITMIN) return PLUGIN_CONTINUE

	new iOldTeam = playerTeam[id]

	// disallow free team choices in the first rounds of a map
	if (PTB_AUTOROUNDS&&(iOldTeam==UNASSIGNED)&&roundCounter<=PTB_AUTOROUNDS)
		iNewTeam = AUTO_TEAM

	// prevent unwanted rejoining of the same team ...
	if (iNewTeam == iOldTeam) {
		//say("Preventing rejoining of the same team.")
		client_print(id,print_chat,"PTB: Joining to the same team is not allowed...")
#if !defined MANUAL_SWITCH
		engclient_cmd(id,"chooseteam") // display menu again
#endif
		return PLUGIN_HANDLED
	}

	checkTeamBalance()
	//displayStatistics(0,true)

	// Player for sure was in CT or T team and now is joining to the opposite team
	if ((iNewTeam==CTS&&iOldTeam==TS)||(iNewTeam==TS&&iOldTeam==CTS)){
		// If someone is in new team and old team weren't full
		// and the winning team is a destination team or in
		// new team is more players than in old then treat it as wtj
		if ( teamCounts[iNewTeam]&&(teamCounts[iOldTeam]<PTB_MAXSIZE)&&
			((iNewTeam==winnerTeam)||(teamCounts[iNewTeam]>=teamCounts[iOldTeam])) ) {
			// player is wtjing
			new text[256],name[32]
			get_user_name(id,name,31)
			// Kick wtj player if reached set limit
			if (++wtjCount[id] >= PTB_WTJKICK && PTB_KICK) {
				format(text, 255, "PTB: Kicking %s for a WTJ count %d of %d.", name, wtjCount[id],PTB_WTJKICK )
				doTypesay(text, 5, 0, 255, 0)
				say(text)
				server_cmd("kick #%d",get_user_userid(id))
				return PLUGIN_HANDLED
			}
			// Announce about WTJ
			if (PTB_TELLWTJ) {
				if (iNewTeam == CTS) {
					format(text, 255, "PTB: The CTs are strong enough, %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
					doTypesay(text, 5, 0, 50, 255)
				}
				else {
					format(text, 255, "PTB: The Ts are strong enough, %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
					doTypesay(text, 5, 255, 50, 0)
				}
				say(text)
			}
#if !defined MANUAL_SWITCH
			engclient_cmd(id,"chooseteam") // display menu again
#endif
			return PLUGIN_HANDLED
		}
		// check for maximum team size
		if (teamCounts[iNewTeam] >= PTB_MAXSIZE) {
			client_print(id,print_chat,"PTB: Maximum team size prohibits team change.")
#if !defined MANUAL_SWITCH
			engclient_cmd(id,"chooseteam") // display menu again
#endif
			return PLUGIN_HANDLED
		}
		// check team size difference limits
		if ( teamCounts[iNewTeam]+1-teamCounts[iOldTeam] >= PTB_MAXDIFF ) {
			client_print(id,print_chat,"PTB: Maximum team size difference prohibits team change.")
#if !defined MANUAL_SWITCH
			engclient_cmd(id,"chooseteam") // display menu again
#endif
			return PLUGIN_HANDLED
		}
		return PLUGIN_CONTINUE // OK! He can join to the oppsoite team!!!
	}

	// Player is choosing his team for the first time!
	if (iNewTeam==CTS||iNewTeam==TS){
		// Get opposite team
		new opposingTeam = (iNewTeam==CTS)? TS : CTS
		// Players is joining to one team but the opposite is not full
		// and his team is bettter then opposite or has more players
		if (teamCounts[iNewTeam] && teamCounts[opposingTeam]<PTB_MAXSIZE &&
		     (iNewTeam==winnerTeam||(!winnerTeam&&teamCounts[iNewTeam]>teamCounts[opposingTeam]))) {
			new text[256],name[32]
			get_user_name(id,name,31)
			if (++wtjCount[id] >= PTB_WTJKICK && PTB_KICK) {
				format(text, 255, "PTB: Kicking %s for a WTJ count %d of %d.", name, wtjCount[id],PTB_WTJKICK)
				doTypesay(text, 5, 0, 255, 0)
				say(text)
				server_cmd("kick #%d", get_user_userid(id))
				return PLUGIN_HANDLED
			}
			if (iNewTeam==CTS) {
				if (wtjCount[id]>=PTB_WTJAUTO) {
					manageWtjFile(id)
					format(text, 255, "PTB: Forcing %s to the Ts (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
#if defined CS16_SWITCH
					engclient_cmd(id,"jointeam","1")
#else
#if defined MANUAL_SWITCH
					client_cmd(id,"chooseteam;menuselect 1")
#else
					engclient_cmd(id,"menuselect","1")
#endif
#endif
					doTypesay(text, 5, 255, 50, 0)
					say(text)
				}
				else if (PTB_TELLWTJ) {
					format(text, 255, "PTB: The CTs are strong enough, %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
					doTypesay(text, 5, 0, 50, 255)
					say(text)
#if !defined MANUAL_SWITCH
					engclient_cmd(id,"chooseteam") // display menu again
#endif
				}
			}
			else {
				if (wtjCount[id]>=PTB_WTJAUTO) {
					manageWtjFile(id)
					format(text, 255, "PTB: Forcing %s to the CTs (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
#if defined CS16_SWITCH
					engclient_cmd(id,"jointeam","2")
#else
#if defined MANUAL_SWITCH
					client_cmd(id,"chooseteam;menuselect 2")
#else
					engclient_cmd(id,"menuselect","2")
#endif
#endif
					doTypesay(text, 5, 0, 50, 255)
					say(text)
				}
				else if (PTB_TELLWTJ) {
					format(text, 255, "PTB: The Ts are strong enough, %s (WTJ: %d/%d).", name, wtjCount[id],PTB_WTJKICK)
					doTypesay(text, 5, 255, 50, 0)
					say(text)
#if !defined MANUAL_SWITCH
					engclient_cmd(id,"chooseteam") // display menu again
#endif
				}
			}
			return PLUGIN_HANDLED
		}
		// check for maximum team size
		if (teamCounts[iNewTeam] >= PTB_MAXSIZE) {
			client_print(id,print_chat,"PTB: Maximum team size prohibits team join.")
#if !defined MANUAL_SWITCH
			engclient_cmd(id,"chooseteam") // display menu again
#endif
			return PLUGIN_HANDLED
		}
		// check team size difference limits
		if ( teamCounts[iNewTeam]-teamCounts[opposingTeam] >= PTB_MAXDIFF) {
			client_print(id,print_chat,"PTB: Maximum team size difference prohibits team join.")
#if !defined MANUAL_SWITCH
			engclient_cmd(id,"chooseteam") // display menu again
#endif
			return PLUGIN_HANDLED
		}
		return PLUGIN_CONTINUE // OK! He can join to the oppsoite team!!!
	}

	// He is choosing the AUTO-SELECT but he was already in game (He wants to play fair!)
	if (iNewTeam==AUTO_TEAM&&(iOldTeam==CTS||iOldTeam==TS)) {
		//say("Changing team automatically.")
		new opposingTeam = (iOldTeam==CTS) ? TS : CTS
		if (teamCounts[opposingTeam] && ( (teamCounts[opposingTeam]>=PTB_MAXSIZE)
				|| (iOldTeam==loserTeam) || (!loserTeam&&teamCounts[iOldTeam]<=teamCounts[opposingTeam])
				|| (teamCounts[opposingTeam]+1-teamCounts[iOldTeam]>=PTB_MAXDIFF)) ) {
			client_print(id,print_chat,"PTB: You have better stay in your current team...")
			return PLUGIN_HANDLED
		}
		client_print(id,print_chat,"PTB: You have been auto-assigned...")
#if defined CS16_SWITCH
		engclient_cmd(id,"jointeam",(opposingTeam==CTS)?"2":"1")
#else
#if defined MANUAL_SWITCH
		client_cmd(id,"chooseteam;menuselect %d",(opposingTeam==CTS) ? 2 : 1 )
#else
		engclient_cmd(id,"menuselect",(opposingTeam==CTS) ? "2" : "1")
#endif
#endif
		return PLUGIN_HANDLED
	}
	// He is choosing the team for the first time with AUTO-SELECT (What a nice kid!)
	if (iNewTeam==AUTO_TEAM) {
		/* this is the "always smaller team" version
		if (teamCounts[CTS] < teamCounts[TS] || teamCounts[TS] >= PTB_MAXSIZE) iNewTeam = CTS
		else if (teamCounts[TS] < teamCounts[CTS] || teamCounts[CTS] >= PTB_MAXSIZE) iNewTeam = TS
		// both teams have same size ...
		else if (winnerTeam && teamCounts[loserTeam]<PTB_MAXSIZE) iNewTeam = loserTeam
		else if (teamCounts[TS] >= PTB_MAXSIZE) iNewTeam = CTS
		else if (teamCounts[CTS] >= PTB_MAXSIZE) iNewTeam = TS
		else iNewTeam = (random_num(0,100) < 50) ? CTS : TS
		*/
		// this version prefers the losing team (but still honors PTB_MAXDIFF)
		if (teamCounts[CTS] >= PTB_MAXSIZE) iNewTeam = TS
		else if (teamCounts[TS] >= PTB_MAXSIZE) iNewTeam = CTS
		else if (teamCounts[CTS]-teamCounts[TS] >= PTB_MAXDIFF) iNewTeam = TS
		else if (teamCounts[TS]-teamCounts[CTS] >= PTB_MAXDIFF) iNewTeam = CTS
		else if (winnerTeam) iNewTeam = loserTeam
		else if (teamCounts[CTS]<teamCounts[TS]) iNewTeam = CTS
		else if (teamCounts[TS]<teamCounts[CTS]) iNewTeam = TS
		// both teams have same size ...
		else iNewTeam = (random_num(0,100) < 50) ? CTS : TS
		// check for maximum team size
		if (teamCounts[iNewTeam]>=PTB_MAXSIZE) {
			client_print(id,print_chat,"PTB: Maximum team size prohibits team join.") // ??? - only a spectator???
#if !defined MANUAL_SWITCH
			engclient_cmd(id,"chooseteam") // display menu again
#endif
			return PLUGIN_HANDLED
		}
		client_print(id,print_chat,"PTB: You have been auto-assigned...")
#if defined CS16_SWITCH
		engclient_cmd(id,"jointeam",(iNewTeam==CTS)?"2":"1")
#else
#if defined MANUAL_SWITCH
		client_cmd(id,"chooseteam;menuselect %d",(iNewTeam==CTS) ? 2 : 1 )
#else
		engclient_cmd(id,"menuselect",(iNewTeam == CTS) ? "2" : "1")
#endif
#endif
		return PLUGIN_HANDLED
	}
	return PLUGIN_CONTINUE
}

public team_score(){
	new arg[2]
	read_data(1,arg,1)
	teamScores[ ( arg[0] == 'T' ) ? TS : CTS ] = read_data(2)
}

public win_streaks(param[]){
	new winner = param[0]
	new looser = param[1]
	if (winStreaks[winner] < 0) {
		winStreaks[winner] = 1
		winStreaks[looser] = -1
	}
	else {
		winStreaks[winner]++
		winStreaks[looser]--
	}
	actAtEndOfRound()
}

public round_end(){
	new param[12]
	read_data(2,param,8)
	if (param[7]=='c') {//%!MRAD_ctwin
		param[0] = CTS
		param[1] = TS
	}
	else if (param[7]=='t') {//%!MRAD_terwin
		param[0] = TS
		param[1] = CTS
	}
	else
		return // %!MRAD_rounddraw (both teams have left the game)
	set_task(1.5,"win_streaks",0,param,2)
}

public new_round() {
	if ( floatround(get_cvar_float("mp_roundtime") * 60.0) != read_data(1) ) return
	++roundCounter
	announceStatus()
}

// Happen only at team select (also auto-join)
public team_join() {
	new arg[32]
	read_data(3,arg,31)
	lastRoundSwitched[ get_user_index(arg) ] = roundCounter
}

// Can happen at begin of round or team select
public team_assign() {
	new arg[2], team
	new i = read_data(1)
	read_data(2,arg,1)
	if ( arg[0] == 'C'  )
		team = CTS
	else if ( arg[0] == 'T' )
		team = TS
	else
		team = UNASSIGNED
	teamCounts[playerTeam[i]]-- // Unregister from old team
	teamCounts[team]++ // Increase ammount in new team
	playerTeam[i] = team // Assign to new
}

public game_restart(){
	roundCounter = 0
	lastSwitchRound = 0
	couldNotSwitchCounter = 0
	teamKills[0] = teamKills[1] = teamKills[2] = 0
	teamDeaths[0] = teamDeaths[1] = teamDeaths[2] = 0
	teamScores[0] = teamScores[1] = teamScores[2] = 0
	winStreaks[0]	= winStreaks[1] = winStreaks[2] = 0
	wtConditions[0] = wtConditions[1] = wtConditions[2] = 0
	validTargetCounts[0] = validTargetCounts[1] = validTargetCounts[2] = 0
	new a = get_maxplayers()
	for (new i = 1; i <= a; ++i){
		kills[i] = 0
		deaths[i] = 0
		wtjCount[i] = 0
		lastRoundSwitched[i] = -999
	}
}

public death_msg(){
	new iWinner = read_data(1)
	new iLoser = read_data(2)
	if ( iWinner < 1 || iWinner > 32 || iLoser < 1 || iLoser > 32 )
		return
	if ( playerTeam[iWinner] == playerTeam[iLoser] )
		return // no TKS!!!
	kills[iWinner]++
	deaths[iLoser]++
	if (PTB_SCALEDOWN <= 1) return
	if (kills[iWinner] + deaths[iWinner] >= PTB_MAXINCIDENTS) {
		kills[iWinner] /= PTB_SCALEDOWN
		deaths[iWinner] /= PTB_SCALEDOWN
	}
	if (kills[iLoser] + deaths[iLoser] >= PTB_MAXINCIDENTS) {
		kills[iLoser] /= PTB_SCALEDOWN
		deaths[iLoser] /= PTB_SCALEDOWN
	}
}

calcTeamScores() {
	teamDeaths[UNASSIGNED] = 0
	teamDeaths[CTS] = 0
	teamDeaths[TS] = 0
	teamKills[UNASSIGNED] = 0
	teamKills[CTS] = 0
	teamKills[TS] = 0

	new team, a = get_maxplayers()
	for (new i = 1; i <= a; ++i) {
		team = playerTeam[i]
		teamKills[team] += kills[i]
		teamDeaths[team] += deaths[i]
	}

	ctKD = fdivWorkaround(float(teamKills[CTS]), float(teamDeaths[CTS]))
	tKD = fdivWorkaround(float(teamKills[TS]), float(teamDeaths[TS]))
}

announceStatus() {
	if (!PTB_ANNOUNCE) return
	checkTeamBalance()
	new text[256]
	if (winnerTeam == TS) {
		format(text, 255, "PTB: The COUNTER-TERRORIST team could use some support.")
		doTypesay(text, 5, 0, 50, 255)
		say("PTB: The COUNTER-TERRORIST team could use some support.")
	}
	else if (winnerTeam == CTS) {
		format(text, 255, "PTB: The TERRORIST team could use some support.")
		doTypesay(text, 5, 255, 50, 0)
		say("PTB: The TERRORIST team could use some support.")
	}
	else if (wtConditions[TS] > wtConditions[CTS]) {
		format(text, 255, "PTB: Observing TERRORIST team advantage.")
		doTypesay(text, 5, 255, 50, 0)
		say("PTB: Observing TERRORIST team advantage.")
	}
	else if (wtConditions[CTS] > wtConditions[TS]) {
		format(text, 255, "PTB: Observing COUNTER-TERRORIST team advantage.")
		doTypesay(text, 5, 0, 50, 255)
		say("PTB: Observing COUNTER-TERRORIST team advantage.")
	}
	else if (PTB_SAYOK) {
		format(text, 255, "PTB: Teams look fine, no action required.")
		doTypesay(text, 5, 200, 100, 0)
		say("PTB: Teams look fine, no action required.")
	}
}

public admin_ptb(id,level,cid) {
	if (!cmd_access(id,level,cid,1))
		return PLUGIN_HANDLED
	new cmd[32], arg[32], lastcmd

	if ( read_argv(1,cmd,31) == 0 ) { // no command - plain amx_ptb
		console_print(id,"PTB: Ptahhotep's Team Balancer %s", PTB_VERSION)
		console_print(id,"PTB: (ptahhotep@planethalflife.com)")
		checkTeamBalance()
		displayStatistics(id)
		return PLUGIN_HANDLED
	}
	if (equali(cmd, "on") || equal(cmd, "1")) {
		PTB_LIMITJOIN = true
		PTB_SWITCH = true
		PTB_ANNOUNCE = true
		console_print(id,"PTB: Enabled all PTB actions.")
		return PLUGIN_HANDLED
	}
	if (equali(cmd, "off") || equal(cmd, "0")) {
		PTB_SWITCH = false
		PTB_ANNOUNCE = false
		PTB_LIMITJOIN = false
		console_print(id,"PTB: Disabled all PTB actions.")
		return PLUGIN_HANDLED
	}
	if (equal(cmd, "list") || equal(cmd, "help")) {
		console_print(id,"PTB: Available Commands:")
		console_print(id,"PTB: Team Join Control: ^"limitjoin^", ^"limitafter^", ^"limitmin^", ^"maxsize^", ^"autorounds^",")
		console_print(id,"PTB: ^"maxdiff^", ^"wtjauto^", ^"wtjkick^", ^"kick^", ^"savewtj^"")
		console_print(id,"PTB: Team Balancing Actions: ^"switch^", ^"switchafter^", ^"switchmin^", ^"switchfreq^", ^"playerfreq^",")
		console_print(id,"PTB: ^"forceswitch^", ^"deadonly^"")
		console_print(id,"PTB: Team Strength Limits: ^"maxstreak^", ^"maxscore^", ^"minrating^", ^"maxrating^", ^"superrating^"")
		console_print(id,"PTB: Messages: ^"tellwtj^", ^"announce^", ^"sayok^", ^"typesay^"")
		console_print(id,"PTB: Misc: ^"^", ^"status^", ^"list^", ^"help^", ^"on^", ^"off^", ^"save^", ^"load^"")
		console_print(id,"PTB: To view all PTB settings, type ^"amx_ptb status^".")
		console_print(id,"PTB: To view or change a single PTB setting, type ^"amx_ptb <setting> <on|off|value>^".")
		console_print(id,"PTB: For PTB statistics, simply type ^"amx_ptb^".")
		return PLUGIN_HANDLED
	}
	new arglen = read_argv(2,arg,31)
	new status = equal(cmd, "status")

	// team selection control
	if ( status ) console_print(id,"PTB: ---------- Team Selection Control ----------")

	// PTB_LIMITJOIN
	if ( (lastcmd = equal(cmd, "limitjoin")) && arglen ) PTB_LIMITJOIN = check_param_bool(arg)
	if ( status ||  lastcmd )	console_print(id,"PTB: (limitjoin) WTJ prevention is %s.", PTB_LIMITJOIN ? "ON" : "OFF")

	// PTB_LIMITAFTER
	if ( (lastcmd = equal(cmd, "limitafter")) && arglen )	PTB_LIMITAFTER = check_param_num(arg,0)
	if ( status || lastcmd )	console_print(id,"PTB: (limitafter) Team limiting starts after %d round(s).", PTB_LIMITAFTER)

	// PTB_LIMITMIN
	if ( (lastcmd = equal(cmd, "limitmin")) && arglen ) PTB_LIMITMIN = check_param_num(arg,0)
	if ( status || lastcmd )	console_print(id,"PTB: (limitmin) Team limiting needs at least %d player(s).", PTB_LIMITMIN)

	// PTB_MAXSIZE
	if ( (lastcmd = equal(cmd, "maxsize")) && arglen ) 	PTB_MAXSIZE = check_param_num(arg,0)
	if ( status || lastcmd )	console_print(id,"PTB: (maxsize) Maximum team size is %d player(s).", PTB_MAXSIZE)

	// PTB_MAXDIFF
	if ( (lastcmd = equal(cmd, "maxdiff")) && arglen ) PTB_MAXDIFF = check_param_num(arg,1)
	if ( status || lastcmd )	console_print(id,"PTB: (maxdiff) Maximum team size difference is %d.", PTB_MAXDIFF)

	// PTB_AUTOROUNDS
	if ( (lastcmd = equal(cmd, "autorounds")) && arglen )	PTB_AUTOROUNDS = check_param_num(arg,0)
	if ( status || lastcmd )	console_print(id, "PTB: (autorounds) First %d rounds no free team choice.", PTB_AUTOROUNDS)

	// PTB_WTJAUTO
	if ( (lastcmd = equal(cmd, "wtjauto")) && arglen )	PTB_WTJAUTO = check_param_num(arg,0)
	if ( status || lastcmd )	console_print(id,"PTB: (wtjauto) Auto-joining WTJ after %d tr(y/ies).", PTB_WTJAUTO)

	// PTB_WTJKICK
	if ( (lastcmd = equal(cmd, "wtjkick")) && arglen ) PTB_WTJKICK = check_param_num(arg,1)
	if ( status || lastcmd )	console_print(id,"PTB: (wtjauto) Auto-kicking WTJ after %d tr(y/ies).", PTB_WTJKICK)

	// PTB_KICK
	if ( (lastcmd = equal(cmd, "kick")) && arglen ) PTB_KICK = check_param_bool(arg)
	if ( status || lastcmd ) console_print(id,"PTB: (kick) WTJ kicking is %s.", PTB_KICK ? "ON" : "OFF" )

	// PTB_SAVEWTJ
	if (  (lastcmd = equal(cmd, "savewtj")) && arglen ) PTB_SAVEWTJ = check_param_bool(arg)
	if ( status || lastcmd ) 	console_print(id,"PTB: (savewtj) Saving to wtj.log is %s.", PTB_SAVEWTJ ? "ON" : "OFF");

	// team balancing actions
	if ( status ) console_print(id,"PTB: ---------- Team Balancing Actions ----------")

	// PTB_SWITCH
	if ( (lastcmd = equal(cmd, "switch")) && arglen ) PTB_SWITCH = check_param_bool(arg)
	if ( status || lastcmd ) console_print(id,"PTB: (switch) Team switching is %s.", PTB_SWITCH ? "ON" : "OFF")

	// PTB_SWITCHAFTER
	if ( (lastcmd = equal(cmd, "switchafter")) && arglen ) PTB_SWITCHAFTER = check_param_num(arg,0)
	if ( status || lastcmd ) console_print(id,"PTB: (switchafter) Switching starts after %d round(s).", PTB_SWITCHAFTER)

	// PTB_SWITCHMIN
	if ( (lastcmd = equal(cmd, "switchmin")) && arglen ) PTB_SWITCHMIN = check_param_num(arg,0)
	if ( status || lastcmd ) console_print(id,"PTB: (switchmin) Switching needs at least %d player(s).", PTB_SWITCHMIN)

	// PTB_PLAYERFREQ
	if ( (lastcmd = equal(cmd, "playerfreq")) && arglen )	PTB_PLAYERFREQ = check_param_num(arg,0)
	if ( status || lastcmd )	console_print(id,"PTB: (playerfreq) Individual players are switched every %d round(s) at maximum.", PTB_PLAYERFREQ)

	// PTB_SWITCHFREQ
	if (  (lastcmd = equal(cmd, "switchfreq")) && arglen )	PTB_SWITCHFREQ = check_param_num(arg,1)
	if ( status || lastcmd )	console_print(id,"PTB: (switchfreq) Switch occurs every %d round(s) at maximum.", PTB_SWITCHFREQ)

	// PTB_FORCESWITCH
	if ( (lastcmd = equal(cmd, "forceswitch")) && arglen )	PTB_FORCESWITCH = check_param_num(arg,0)
	if ( status || lastcmd ) console_print(id,"PTB: (forceswitch) Forcing switch after %d unsuccessful switch(es).", PTB_FORCESWITCH)

	// PTB_DEADONLY
	if ( (lastcmd =  equal(cmd, "deadonly")) && arglen ) 	PTB_DEADONLY = check_param_bool(arg)
	if ( status || lastcmd ) console_print(id,"PTB: (deadonly) Switching dead only is %s.",PTB_DEADONLY ? "ON" : "OFF" )

	// messages
	if ( status ) console_print(id,"PTB: ---------- Messages ----------")

	// PTB_TELLWTJ
	if ( (lastcmd =  equal(cmd, "tellwtj")) && arglen ) PTB_TELLWTJ = check_param_bool(arg)
	if ( status || lastcmd ) console_print(id,"PTB: (tellwtj) Telling about WTJ tries is %s.",PTB_TELLWTJ ? "ON" : "OFF")

	// PTB_ANNOUNCE
	if ( (lastcmd = equal(cmd, "announce")) && arglen ) PTB_ANNOUNCE = check_param_bool(arg)
	if ( status || lastcmd )  console_print(id,"PTB: (announce) Announcements are %s.",PTB_ANNOUNCE ? "ON" : "OFF")

	// PTB_SAYOK
	if ( (lastcmd = equal(cmd, "sayok")) && arglen ) PTB_SAYOK = check_param_bool(arg)
	if ( status || lastcmd ) console_print(id,"PTB: (sayok) ^"OK^" announcements are %s.",PTB_SAYOK ? "ON" : "OFF")

	// PTB_TYPESAY
	if ( (lastcmd = equal(cmd, "typesay")) && arglen ) PTB_TYPESAY = check_param_bool(arg)
	if ( status || lastcmd ) console_print(id,"PTB: (typesay) typesay usage is %s.",PTB_TYPESAY ? "ON" : "OFF")

	// team strength limits
	if ( status ) console_print(id,"PTB: ---------- Team Strength Limits ----------")

	// PTB_MAXSTREAK
	if ( (lastcmd = equal(cmd, "maxstreak")) && arglen ) PTB_MAXSTREAK = check_param_num(arg,1)
	if ( status || lastcmd )	console_print(id,"PTB: (maxstreak) Maximum accepted win streak is %d.", PTB_MAXSTREAK)

	// PTB_MAXSCORE
	if ( (lastcmd = equal(cmd, "maxscore")) && arglen )	PTB_MAXSCORE = check_param_num(arg,1)
	if ( status || lastcmd ) console_print(id,"PTB: (maxscore) Maximum accepted team score difference is %d.", PTB_MAXSCORE)

	// PTB_MINRATING
	if ( (lastcmd = equal(cmd, "minrating")) && arglen ) PTB_MINRATING = check_param_float(arg,1.0)
	if ( status || lastcmd ) 	console_print(id,"PTB: (minrating) Minimum critical strength rating is %.2f.",PTB_MINRATING)

	// PTB_MAXRATING
	if ( (lastcmd = equal(cmd, "maxrating")) && arglen ) PTB_MAXRATING = check_param_float(arg,1.0)
	if ( status || lastcmd ) 	console_print(id,"PTB: (maxrating) Maximum critical strength rating is %.2f.",PTB_MAXRATING)

	// PTB_SUPERRATING
	if ( (lastcmd = equal(cmd, "superrating")) && arglen ) PTB_SUPERRATING = check_param_float(arg,1.0)
	if ( status || lastcmd ) 	console_print(id,"PTB: (superrating) Super critical strength rating is %.2f.",PTB_SUPERRATING)

	// PTB_MAXINCIDENTS
	if ( (lastcmd = equal(cmd, "maxincidents")) && arglen )	PTB_MAXINCIDENTS = check_param_num(arg,1)
	if ( status || lastcmd ) 	console_print(id,"PTB: (maxincidents) Maximum incidents before internal player score scale down is %d.", PTB_MAXINCIDENTS)

	// PTB_SCALEDOWN
	if ( (lastcmd =  equal(cmd, "scaledown")) && arglen )	PTB_SCALEDOWN = check_param_num(arg,1)
	if ( status || lastcmd ) 	console_print(id,"PTB: (scaledown) Integer scale down factor for player scores is %d.", PTB_SCALEDOWN)

	// misc
	if ( status ) {
		console_print(id,"PTB: ---------- Misc ----------")
		console_print(id,"PTB: To enable or disable PTB, type ^"admin_ptb <on|1|off|0>^".")
		console_print(id,"PTB: To view or change a single PTB setting, type ^"amx_ptb <setting> <on|off|value>^".")
		console_print(id,"PTB: To view a brief overview of PTB commands, type ^"amx_ptb help^" or ^"amx_ptb list^".")
		console_print(id,"PTB: For PTB statistics, simply type ^"amx_ptb^".")
	}

	return PLUGIN_HANDLED
}

stock displayStatistics(id,bool:toLog = false) {
	//say("displayStatistics")
	new text[256]
	// time
	format(text, 255, "PTB: Statistics generated at: %s", lastTeamBalanceCheck)
	if (toLog) log_message(text)
	console_print(id,text)
	// connected players
	format(text, 255, "PTB: Connected players: %d", get_playersnum())
	if (toLog) log_message(text)
	console_print(id,text)
	// team sizes
	format(text, 255, "PTB: Team sizes: CTs %d, Ts %d", teamCounts[CTS], teamCounts[TS])
	if (toLog) log_message(text)
	console_print(id,text)
	// team scores
	format(text, 255, "PTB: Team scores: CTs %d, Ts %d", teamScores[CTS], teamScores[TS])
	if (toLog) log_message(text)
	console_print(id,text)
	// Kills:Deaths
	format(text, 255, "PTB: Team kills:deaths: CTs %d:%d, Ts %d:%d", teamKills[CTS], teamDeaths[CTS], teamKills[TS], teamDeaths[TS])
	if (toLog) log_message(text)
	console_print(id,text)
	// Kills/Deaths
	format(text, 255, "PTB: Team kills/deaths: CTs %.2f, Ts %.2f", ctKD , tKD	)
	if (toLog) log_message(text)
	console_print(id,text)
	// strength
	format(text, 255, "PTB: Team strengths: CTs %.2f, Ts %.2f",ctStrength , tStrength	)
	if (toLog) log_message(text)
	console_print(id,text)
	// rating
	format(text, 255, "PTB: Team ratings: CTs %.2f, Ts %.2f",ctRating,tRating	)
	if (toLog) log_message(text)
	console_print(id,text)
	// won rounds
	if (winStreaks[CTS] > 0) {
		format(text, 255, "PTB: Last %d round(s) won by CTs.", winStreaks[CTS])
		if (toLog) log_message(text)
		console_print(id,text)
	}
	else if (winStreaks[TS] > 0) {
		format(text, 255, "PTB: Last %d round(s) won by Ts.", winStreaks[TS])
		if (toLog) log_message(text)
		console_print(id,text)
	}

	// winning team
	switch(winnerTeam){
		case CTS: format(text, 255, "PTB: The CTs are the winning team.")
		case TS: format(text, 255, "PTB: The Ts are the winning team.")
		default: format(text, 255, "PTB: Teams are balanced.")
	}
	if (toLog) log_message(text)
	console_print(id,text)

	format(text, 255, "PTB: These statistics might be already outdated.")
	if (toLog) log_message(text)
	console_print(id,text)

	format(text, 255, "PTB: To view a brief overview of PTB commands, type ^"amx_ptb help^" or ^"amx_ptb list^".")
	if (toLog) log_message(text)
	console_print(id,text)

	format(text, 255, "PTB: To view all PTB settings, type ^"amx_ptb status^".")
	if (toLog) log_message(text)
	console_print(id,text)
}

public client_connect(id){
	kills[id] = 0
	deaths[id] = 0
	isBeingTransfered[id] = false
	playerTeam[id] = UNASSIGNED
	lastRoundSwitched[id] = -999
	wtjCount[id] = 0
#if defined CS16_SWITCH
	get_user_info(id,"_vgui_menus",clientVGUIMenu[id],1)
	#if defined PTB_DEBUG
	log_message("[PTB] Connect #%i (VGUI %s)",id,clientVGUIMenu[id])
	#endif
	clientVGUIMenu[id][0] = '0'
#endif
	return PLUGIN_CONTINUE
}

public client_disconnect(id) {
	kills[id] = 0
	deaths[id] = 0
	isBeingTransfered[id] = false
	playerTeam[id] = UNASSIGNED
	lastRoundSwitched[id] = -999
	wtjCount[id] = 0
	// redundant team size check
	teamCounts[UNASSIGNED] = 0
	teamCounts[CTS] = 0
	teamCounts[TS] = 0

	new a = get_maxplayers()
	for (new i = 1; i <= a; ++i)
		++teamCounts[playerTeam[i]]

#if defined CS16_SWITCH
	if (clientVGUIMenu[id][0] != '0') {
		set_user_info( id, "_vgui_menus", clientVGUIMenu[id] )
		clientVGUIMenu[id][0] = '0'
	}
#endif
	return PLUGIN_CONTINUE
}
