#include maps\mp\_utility;
#include maps\mp\gametypes\_hud_util;
#include common_scripts\utility;

init()
{
	initAwards();

	level thread onPlayerConnect();
	level thread monitorMovementDistance();
	level thread monitorEnemyDistance();
}

onPlayerConnect()
{
	for(;;)
	{
		level waittill( "connected", player );

		// Reset player awards to none
		
		if ( !isDefined( player.pers["stats"] ) )
			player.pers["stats"] = [];

		player.stats = player.pers["stats"];
		
		if ( !player.stats.size )
		{
			player setPlayerData( "round", "awardCount", 0 );

			// Initialize player stats
			foreach ( ref, award in level._awards )
			{
				if ( isDefined( level._awards[ ref ].defaultvalue ) )
					player initPlayerStat( ref, level._awards[ ref ].defaultvalue );
				else
					player initPlayerStat( ref );
			}
		}

		player.prevPos = player.origin;
		player.previousDeaths = 0;
		player.altitudePolls = 0;
		player.totalAltitudeSum = 0;
		player.usedWeapons = [];
		
		player thread onPlayerSpawned();
		player thread monitorPositionCamping();
		player thread monitorClassChange();
	}
}

onPlayerSpawned()
{
	self endon( "disconnect" );

	for(;;)
	{
		self waittill( "spawned_player" );

		self thread monitorReloads();
		self thread monitorShotsFired();
		self thread monitorSwaps();
		self thread monitorExplosionsSurvived();
		self thread monitorExplosiveHits();
		self thread monitorShieldBlocks();
		self thread monitorFlashHits();
		self thread monitorStunHits();
		self thread monitorStanceTime();
	}
}

initAwards()
{
	if ( isDefined( level._initGametypeAwards ) )
		[[ level._initGametypeAwards ]]();

	// flags generated from other stats, these are not awards,
	// but are checked against for the assignment of some awards
	initAwardFlag( "10kills", 			   ::isAtleast, 10, "kills" );
	initAwardFlag( "1death", 			   ::isAtleast, 1, "deaths" );
	initAwardFlag( "nodeaths",		 	   ::isAtMost, 0, "deaths" );
	initAwardFlag( "nokills",		 	   ::isAtMost, 0, "kills" );

	// ordered in priority assigned
	// TODO: should decouple dependency processing order from order assigned
	initMultiAward( "mvp", 				   "kills", "deaths" );
	initMultiAward( "highlander",          "10kills", "nodeaths" );
	initThresholdAward( "kdratio10",       ::isAtleast, 10, "kdratio" );
	initMultiAward( "punisher", 		   "kills", "killstreak" );
	initMultiAward( "overkill", 		   "kills", "headshots" );
	initDerivedAward( "killedotherteamonelife" );	
	initStatAward( "kdratio", 			0, ::highestWins );
	initStatAward( "kills",				0, ::highestWins );
	initStatAward( "higherrankkills", 	0, ::highestWins );
	initStatAward( "deaths", 			0, ::lowestWithHalfPlayedTime );
	initStatAward( "killstreak",		0, ::highestWins );
	initStatAward( "headshots",			0, ::highestWins );
	initDerivedAward( "finalkill" );
	initDerivedAward( "killedotherteam" );	
	initStatAward( "closertoenemies", 	0, ::highestWins );
	initStatAward( "throwingknifekills", 0, ::highestWins );
	initStatAward( "grenadekills", 		0, ::highestWins );
	initStatAward( "helicopters", 		0, ::highestWins );
	initStatAward( "airstrikes", 		0, ::highestWins );
	initStatAward( "uavs", 				0, ::highestWins );
	initStatAward( "mostmultikills",	0, ::highestWins );
	initStatAward( "multikill", 		0, ::highestWins );
	initStatAward( "knifekills", 		0, ::highestWins );
	initStatAward( "flankkills", 		0, ::highestWins );
	initStatAward( "bulletpenkills", 	0, ::highestWins );
	initStatAward( "laststandkills", 	0, ::highestWins );
	initStatAward( "laststanderkills", 	0, ::highestWins );
	initStatAward( "assists", 			0, ::highestWins );
	initStatAward( "checkpoints", 		0, ::highestWins );
	initStatAward( "c4kills", 			0, ::highestWins );
	initStatAward( "claymorekills", 	0, ::highestWins );
	initStatAward( "fragkills", 		0, ::highestWins );	
	initStatAward( "semtexkills", 		0, ::highestWins );
	initStatAward( "explosionssurvived",0, ::highestWins );	
	initStat( "explosivehits",          0 );	
	initStatAward( "mosttacprevented",	0, ::highestWins );	
	initStatAward( "avengekills", 		0, ::highestWins );
	initStatAward( "rescues", 			0, ::highestWins );
	initStatAward( "longshots", 		0, ::highestWins );
	initStatAward( "adskills", 			0, ::highestWins );
	initStatAward( "hipfirekills", 		0, ::highestWins );
	initStatAward( "revengekills", 		0, ::highestWins );
	initStatAward( "longestlife", 		0, ::highestWins );
	initStatAward( "throwbacks", 		0, ::highestWins );
	initStatAward( "thumperkills",		0, ::highestWins );	
	initStatAward( "otherweaponkills", 	0, ::highestWins );
	initStatAward( "killedsameplayer",	0, ::highestWins, 2 );
	initStatAward( "mostweaponsused",	0, ::highestWins, 3 );
	initStatAward( "distancetraveled",	0, ::highestWins );
	initStatAward( "mostreloads",		0, ::highestWins );
	initStatAward( "mostswaps",			0, ::highestWins );
	initStat( "flankdeaths", 			0 );	
	initDerivedAward( "noflankdeaths",	   ::isAtMostWithHalfPlayedTime, 0, "flankdeaths" );
	initStatAward( "thermalkills", 		0, ::highestWins );		
	initStatAward( "mostcamperkills",	0, ::highestWins );
	initStatAward( "fbhits",			0, ::highestWins );
	initStatAward( "stunhits",			0, ::highestWins );
	initStatAward( "scopedkills", 		0, ::highestWins );
	initStatAward( "arkills", 			0, ::highestWins );
	initStatAward( "arheadshots", 		0, ::highestWins );
	initStatAward( "lmgkills", 			0, ::highestWins );
	initStatAward( "lmgheadshots", 		0, ::highestWins );
	initStatAward( "sniperkills", 		0, ::highestWins );
	initStatAward( "sniperheadshots", 	0, ::highestWins );
	initStatAward( "shieldblocks",		0, ::highestWins );
	initStatAward( "shieldkills",		0, ::highestWins );
	initStatAward( "smgkills", 			0, ::highestWins );
	initStatAward( "smgheadshots", 		0, ::highestWins );
	initStatAward( "shotgunkills", 		0, ::highestWins );
	initStatAward( "shotgunheadshots", 	0, ::highestWins );
	initStatAward( "pistolkills", 		0, ::highestWins );
	initStatAward( "pistolheadshots", 	0, ::highestWins );
	initStatAward( "rocketkills", 		0, ::highestWins );
	initStatAward( "equipmentkills",	0, ::highestWins );
	initStatAward( "mostclasseschanged",0, ::highestWins );
	initStatAward( "lowerrankkills", 	0, ::highestWins );
	initStatAward( "sprinttime", 		0, ::highestWins, 1 );
	initStatAward( "crouchtime",		0, ::highestWins );
	initStatAward( "pronetime",			0, ::highestWins );	
	initStatAward( "comebacks", 		0, ::highestWins );
	initStatAward( "mostshotsfired",	0, ::highestWins );
	initStatAward( "timeinspot",		0, ::highestWins );	
	initStatAward( "killcamtimewatched",0, ::highestWins );
	initStatAward( "greatestavgalt",	0, ::highestWins );
	initStatAward( "leastavgalt",		9999999, ::lowestWins );
	initStat( "machinepistolkills", 	0 );
	initStat( "machinepistolheadshots", 0 );
	initStat( "killswhilepushingugv", 	0 );
	initStat( "killsenemywhopushesugv", 0 );
	initStat( "intelCompleted", 0 );
	initStat( "crouchkills", 	0 );
	initStat( "pronekills", 0 );
	initStat( "firstblood", 0 );
	initStat( "backstabber", 0 );
	initStat( "bombsdefender", 0 );
	initStat( "killstunnedenemy", 0 );
	initStat( "killcookedgrenade", 0 );
	initStat( "killwithstolenweapons", 0 );
	initStat( "killwithkillstreaks", 0 );
	initStat( "gamewinningkill", 0 );
	initStat( "killstreakdestroyed", 0 );
	initStat( "killduringenemyairkillstreak", 0 );
	initStat( "topscore", 0 );
	
	// Offline/private match only awards
	if ( !matchMakingGame() )
	{
		initStatAward( "killcamskipped",	0, ::highestWins );	
		initStatAward( "killsteals",		0, ::highestWins );
		if ( !getGametypeNumLives() )
			initStatAward( "deathstreak", 		0, ::highestWins );
		initStatAward( "shortestlife", 		9999999, ::lowestWins );
		initStatAward( "suicides", 			0, ::highestWins );
		initStatAward( "mostff",			0, ::highestWins );
		initStatAward( "shotgundeaths", 	0, ::highestWins );
		initStatAward( "shielddeaths",		0, ::highestWins );
		initStatAward( "flankdeaths", 		0, ::highestWins ); // sets the process property of the "stat only" init above
		initMultiAward( "participant", 		   "nokills", "1death" );
		initMultiAward( "afk", 				   "nokills", "nodeaths" );

		// special case post process award
		initDerivedAward( "noawards" );
	}
}

initBaseAward( ref )
{
	assert( isDefined( ref ) );

	level._awards[ ref ] = spawnStruct();
	level._awards[ ref ].winners = [];
	level._awards[ ref ].exclusive = true; // For now just make all awards exclusive
}

initAwardProcess( ref, process, var1, var2 )
{
	if ( isDefined( process ) )
		level._awards[ ref ].process = process;

	if ( isDefined( var1 ) )
		level._awards[ ref ].var1 = var1;

	if ( isDefined( var2 ) )
		level._awards[ ref ].var2 = var2;
}

initStat( ref, defaultvalue )
{
	initBaseAward( ref );
	level._awards[ ref ].defaultvalue = defaultvalue;
	level._awards[ ref ].type = "stat";
}

initStatAward( ref, defaultvalue, process, var1, var2 )
{
	initBaseAward( ref );
	initAwardProcess( ref, process, var1, var2 );
	level._awards[ ref ].defaultvalue = defaultvalue;
	level._awards[ ref ].type = "stat";
}

initDerivedAward( ref, process, var1, var2 )
{
	initBaseAward( ref );
	initAwardProcess( ref, process, var1, var2 );
	level._awards[ ref ].type = "derived";
}

initAwardFlag( ref, process, var1, var2 )
{
	initBaseAward( ref );
	initAwardProcess( ref, process, var1, var2 );
	level._awards[ ref ].type = "flag";
}

initMultiAward( ref, award1_ref, award2_ref )
{
	assert( isDefined( award1_ref ) );
	assert( isDefined( award2_ref ) );

	initBaseAward( ref );
	level._awards[ ref ].award1_ref = award1_ref;
	level._awards[ ref ].award2_ref = award2_ref;
	level._awards[ ref ].type = "multi";
}

initThresholdAward( ref, process, var1, var2 )
{
	initBaseAward( ref );
	initAwardProcess( ref, process, var1, var2 );
	level._awards[ ref ].type = "threshold";
}

setMatchRecordIfGreater( ref )
{
	playerValue = self getPlayerStat( ref );
	playerTime = self getPlayerStatTime( ref );
	recordValue = getAwardRecord( ref );
	recordTime = getAwardRecordTime( ref );

	if ( !IsDefined( recordValue ) || ( playerValue > recordValue ) )
	{
		clearAwardWinners( ref );
		addAwardWinner( ref, self.clientid );
		setAwardRecord( ref, playerValue, playerTime );
	}
	else if ( playerValue == recordValue )
	{
		if ( isAwardExclusive( ref ) )
		{				
			if ( !IsDefined( recordTime ) || ( playerTime < recordTime ) )
			{
				clearAwardWinners( ref );
				addAwardWinner( ref, self.clientid );
				setAwardRecord( ref, playerValue, playerTime );
			}
		}
		else
			addAwardWinner( ref, self.clientid );
	}
}

setMatchRecordIfLower( ref )
{
	playerValue = self getPlayerStat( ref );
	playerTime = self getPlayerStatTime( ref );
	recordValue = getAwardRecord( ref );
	recordTime = getAwardRecordTime( ref );

	if ( !IsDefined( recordValue ) || ( playerValue < recordValue ) )
	{
		clearAwardWinners( ref );
		addAwardWinner( ref, self.clientid );
		setAwardRecord( ref, playerValue, playerTime );
	}
	else if ( playerValue == recordValue )
	{
		if ( isAwardExclusive( ref ) )
		{				
			if ( !IsDefined( recordTime ) || ( playerTime < recordTime ) )
			{
				clearAwardWinners( ref );
				addAwardWinner( ref, self.clientid );
				setAwardRecord( ref, playerValue, playerTime );
			}
		}
		else
			addAwardWinner( ref, self.clientid );
	}
}

getDecodedRatio( value )
{
	loVal = getRatioLoVal( value );
	hiVal = getRatioHiVal( value );
	
	if ( !loVal )
		return ( hiVal + 0.001 ); // favor the "n:0" case
		
	return ( hiVal / loVal );	
}

setPersonalBestIfGreater( ref )
{
	recordValue = self getPlayerData( "bests", ref );
	playerValue = self getPlayerStat( ref );

    if ( recordValue == 0 || ( playervalue > recordValue ) )
    {
	    playerValue = getFormattedValue( ref, playerValue );
	    self setPlayerData( "bests", ref, playerValue );
    }
}

setPersonalBestIfLower( ref )
{
	recordvalue = self getPlayerData( "bests", ref );
	playervalue = self getPlayerStat( ref );

	if ( recordValue == 0 || ( playervalue < recordvalue ) )
	{
		playerValue = getFormattedValue( ref, playerValue );
		self setPlayerData( "bests", ref, playerValue );
	}
}

incPlayerRecord( ref )
{
	recordValue = self getPlayerData( "awards", ref );
	self setPlayerData( "awards", ref, recordValue + 1 );
	
	/#
	if ( !isDefined( self.statprint ) )
		self.statprint = [];

	if ( hasDisplayValue( ref ) )
		value = self getPlayerStat( ref );
	else
		value = true;

	stat = spawnstruct();
	stat.ref = ref;
	stat.value = value;

	self.statprint[ self.statprint.size ] = stat;
	#/
}

addAwardWinner( ref, clientid )
{
	foreach ( winner in level._awards[ ref ].winners )
	{
		if ( winner == clientid )
			return;
	}

	level._awards[ ref ].winners[ level._awards[ ref ].winners.size ] = clientid;
}

getAwardWinners( ref )
{
	return level._awards[ ref ].winners;
}

clearAwardWinners( ref )
{
	level._awards[ ref ].winners = [];
}

setAwardRecord( ref, value, time )
{
	level._awards[ ref ].value = value;
	level._awards[ ref ].time = time;
}

getAwardRecord( ref )
{
	return level._awards[ ref ].value;
}

getAwardRecordTime( ref )
{
	return level._awards[ ref ].time;
}

// does this work correctly for roundbased games?
assignAwards()
{
	println( "Awards: Assigning" );
	
	// Special case handling of awards which get stat values updated at the end of a game
	foreach ( player in level._players )
	{
		kills = player getPlayerStat( "kills" );
		deaths = player getPlayerStat( "deaths" );

		if ( deaths == 0 )
			deaths = 1;
		
		player setPlayerStat( "kdratio", ( kills / deaths ) );

		if ( isAlive( player ) )
		{
			timeAlive = getTime() - player.spawnTime;
			player setPlayerStatIfGreater( "longestlife", timeAlive );
		}
	}

	// process end of match stats
	foreach ( ref, award in level._awards )
	{
		if ( !isDefined( level._awards[ ref ].process ) )
			continue;
			
		process = level._awards[ ref ].process;
		var1 = level._awards[ ref ].var1;
		var2 = level._awards[ ref ].var2;

		if ( isDefined( var1 ) && isDefined( var2 ) )
			[[ process ]]( ref, var1, var2 );
		else if ( isDefined( var1 ) )
			[[ process ]]( ref, var1 );
		else
			[[ process ]]( ref );
	}

	// set multi-award winners
	foreach ( ref, award in level._awards )
	{
		if ( !isMultiAward( ref ) )
			continue;
		
		award1_ref = level._awards[ ref ].award1_ref;
		award2_ref = level._awards[ ref ].award2_ref;
		award1_winners = getAwardWinners( award1_ref );
		award2_winners = getAwardWinners( award2_ref );

		if ( !isDefined( award1_winners ) || !isDefined( award2_winners ) )
			continue;
			
		foreach ( winner1 in award1_winners )
		{
			foreach ( winner2 in award2_winners )
			{
				if ( winner1 == winner2 )
				{
					addAwardWinner( ref, winner1 );
					player = playerForClientId( winner1 );
					stat1 = player getPlayerStat( award1_ref );
					stat2 = player getPlayerStat( award2_ref );
					player setPlayerStat( ref, encodeRatio( stat1, stat2 ) );
				}
			}
		}
	}

	// assign awards
	foreach ( ref, award in level._awards )
	{
		if ( !isAwardFlag( ref ) )
			assignAward( ref );
	}

	// assign "noawards" winners
	if ( !matchMakingGame() )
	{
		foreach ( player in level._players )
		{
			awardCount = player getPlayerData( "round", "awardCount" );
			if ( awardCount == 0 )
				player giveAward( "noawards" );
		}
	}

	foreach ( player in level._players )
	{
		awardCount = player getPlayerData( "round", "awardCount" );

		println( "Awards: [", player.name, "] won ", awardCount, " awards" );
		
		for ( i = 0; (i < awardCount && i < 3 ); i++ )
		{
			award = player getPlayerData( "round", "awards", i, "award" );
			value = player getPlayerData( "round", "awards", i, "value" );

			println( "Awards: [", player.name, "][", i, "] ", award, " ", value );
		}
		
		/#
		if ( isDefined( player.statprint ) )
		{
			for ( i = 3; i < player.statprint.size; i++ )
				println( "Awards: [", player.name, "][", i, "] ", player.statprint[i].ref, " ", player.statprint[i].value );
		}
		#/
	}
	
	println( "Awards: Finished assigning" );
}

assignAward( ref )
{
	winners = getAwardWinners( ref );
	if ( !isDefined( winners ) )
		return;
		
	foreach ( winner in winners )
	{
		foreach ( player in level._players )
		{
			if ( player.clientid == winner )
			{
				player giveAward( ref );
				// /# player writeAwardLine( ref ); #/
			}
		}
	}
}

getAwardType( ref )
{
	if ( isDefined( level._awards[ ref ].type ) )
		return level._awards[ ref ].type;
	else
		return "none";
}

isMultiAward( ref )
{
	return ( getAwardType( ref ) == "multi" );
}

isStatAward( ref )
{
	return ( getAwardType( ref ) == "stat" );
}

isThresholdAward( ref )
{
	return ( getAwardType( ref ) == "threshold" );
}

isAwardFlag( ref )
{
	return ( getAwardType( ref ) == "flag" );
}

isAwardExclusive( ref )
{
	if ( isDefined( level._awards[ ref ].exclusive ) )
		return level._awards[ ref ].exclusive;
	else
		return true;
}

hasDisplayValue( ref )
{
	type = getAwardType( ref );
	switch ( type )
	{
		case "derived":
			hasDisplayValue = false;
			break;
		case "stat":
		case "multi":
		default:
			hasDisplayValue = true;
			break;					
	}
	
	return ( hasDisplayValue );
}

giveAward( ref )
{
	awardCount = self getPlayerData( "round", "awardCount" );
	self incPlayerRecord( ref );

	if ( hasDisplayValue( ref ) )
	{
		if ( isStatAward( ref ) )
			assertex( self getPlayerStat( ref ) == getAwardRecord( ref ), "Ref is: " + ref + ", PlayerStat is: " + self getPlayerStat( ref ) + ", MatchRecord is: " + getAwardRecord( ref ) );
		
		value = self getPlayerStat( ref );
	}
	else
		value = true;

	value = getFormattedValue( ref, value );

	if ( awardCount < 3 )
	{	
		self setPlayerData( "round", "awards", awardCount, "award", ref );
		self setPlayerData( "round", "awards", awardCount, "value", value );
	}
	
	awardCount++;
	self SetPlayerData( "round", "awardCount", awardCount );

	if ( awardCount == 1 )
		maps\mp\_highlights::giveHighlight( ref, value );
}

getFormattedValue( ref, value )
{
	awardFormat = tableLookup( "mp/awardTable.csv", 1, ref, 7 );
	switch ( awardFormat )
	{
		case "float":
			value = limitDecimalPlaces( value, 2 );
			value = value * 100;
			break;
		case "ratio":
		case "multi":
		case "count":
		case "time":
		case "distance":
		case "none":
		default:
			break;					
	}

	value = int( value );
	return ( value );
}

highestWins( ref, minAwardable )
{
	foreach ( player in level._players )
	{
		if ( player statValueChanged( ref ) && ( !isDefined( minAwardable ) || player getPlayerStat( ref ) >= minAwardable ) )
		{
			player setMatchRecordIfGreater( ref );
			if ( !isAwardFlag( ref ) )
				player setPersonalBestIfGreater( ref );
		}
	}
}

lowestWins( ref, maxAwardable)
{
	foreach ( player in level._players )
	{
		if ( player statValueChanged( ref ) && ( !isDefined( maxAwardable ) || player getPlayerStat( ref ) <= maxAwardable ) )
		{
			player setMatchRecordIfLower( ref );	
			if ( !isAwardFlag( ref ) )
				player setPersonalBestIfLower( ref );
		}
	}
}

lowestWithHalfPlayedTime( ref )
{
	gameLength = getTimePassed() / 1000;
	halfGameLength = gameLength * 0.5;

	foreach ( player in level._players )
	{
		// hasSpawned check is required or players who pick a team and never spawn can win awards
		if ( player.hasSpawned && player.timePlayed["total"] >= halfGameLength )
		{
			player setMatchRecordIfLower( ref );
			if ( !isAwardFlag( ref ) )
				player setPersonalBestIfLower( ref );
		}
	}
}

statValueChanged( ref )
{
	playervalue = self getPlayerStat( ref );
	defaultvalue = level._awards[ ref ].defaultvalue;

	if ( playervalue == defaultvalue )
		return false;
	else
		return true;
}


isAtLeast( ref, minimum, checkAwardRef )
{
	foreach ( player in level._players )
	{
		playerValue = player getPlayerStat( checkAwardRef );
		checkValue = playerValue;

		if ( checkValue >= minimum )
			addAwardWinner( ref, player.clientid );

		// TODO: Instead of copying the value, reference the other stat directly
		if ( isThresholdAward( ref ) || isAwardFlag( ref ) )
			player setPlayerStat( ref, playerValue );
	}
}


isAtMost( ref, maximum, award_ref )
{
	foreach ( player in level._players )
	{
		playerValue = player getPlayerStat( award_ref );

		if ( playerValue <= maximum )
			addAwardWinner( ref, player.clientid );
	}
}


isAtMostWithHalfPlayedTime( ref, maximum, award_ref )
{
	gameLength = getTimePassed() / 1000;
	halfGameLength = gameLength * 0.5;
	
	foreach ( player in level._players )
	{
		// hasSpawned check is required or players who pick a team and never spawn can win awards
		if ( player.hasSpawned && player.timePlayed["total"] >= halfGameLength )
		{
			playerValue = player getPlayerStat( award_ref );
	
			if ( playerValue <= maximum )
				addAwardWinner( ref, player.clientid );
		}
	}
}


setRatio( ref, award1_ref, award2_ref )
{
	foreach ( player in level._players )
	{
		playerValue1 = player getPlayerStat( award1_ref );
		playerValue2 = player getPlayerStat( award2_ref );

		if ( playerValue2 == 0 )
			player setPlayerStat( ref, playerValue1 );
		else
		{
			ratio = playerValue1 / playerValue2;
			player setPlayerStat( ref, ratio );
		}
	}
}

getKillstreakAwardRef( streakName )
{
	switch( streakName )
	{
		case "uav":
		case "double_uav":
		case "counter_uav":
			return "uavs";
		case "airstrike":
		case "precision_airstrike":
		case "stealth_airstrike":
		case "harrier_airstrike":
		case "super_airstrike":
			return "airstrikes";
		case "helicopter":
		case "helicopter_minigun":
		case "helicopter_mk19":
		case "helicopter_blackbox":
		case "helicopter_flares":
			return "helicopters";
		default:
			return undefined;
	}
}

monitorReloads()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );

	for ( ;; )
	{
		self waittill( "reload" );
		self incPlayerStat( "mostreloads", 1 );
	}
}

monitorShotsFired()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );

	for ( ;; )
	{
		self waittill( "weapon_fired" );
		self incPlayerStat( "mostshotsfired", 1 );
	}
}

monitorSwaps()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );

	lastWeapon = "none"; 

	for ( ;; )
	{
		self waittill( "weapon_change", weapon );
		
		if ( lastWeapon == weapon )
			continue;
		
		if ( weapon == "none" )
			continue;
		
		if ( ! maps\mp\gametypes\_weapons::isPrimaryWeapon( weapon ) )
			continue;
		
		lastWeapon = weapon;
		
		self incPlayerStat( "mostswaps", 1 );
		
		alreadyUsed = false;
		
		//creates an array of weapons used
		foreach( usedWeapon in self.usedWeapons )
		{
			if ( weapon == usedWeapon )
			{
				alreadyUsed = true;
				break;
			}
		}
	
		if ( !alreadyUsed )
		{
			self.usedWeapons[self.usedWeapons.size] = weapon;
			self incPlayerStat( "mostweaponsused", 1 );
		}
	}
}


monitorMovementDistance()
{
	level endon( "game_ended" );
	self endon( "disconnect" );

	for ( ;; )
	{
		foreach ( player in level._players )
		{
			if ( !isAlive( Player ) )
				continue;

			if ( player.deaths != player.previousDeaths )
			{
				player.prevPos = player.origin;
				player.previousDeaths = player.deaths;
			}

			distanceTraveled = Distance( player.origin, player.prevPos );
			player incPlayerStat( "distancetraveled", distanceTraveled );

			player.prevPos = player.origin;
			
			player.altitudePolls++;
			player.totalAltitudeSum += player.origin[2];
			avgAltitude = player.totalAltitudeSum / player.altitudePolls;
			player setPlayerStat( "leastavgalt", avgAltitude );
			player setPlayerStat( "greatestavgalt", avgAltitude );
			
			wait( 0.05 );
		}
		wait( 0.05 );
	}
}

monitorPositionCamping()
{
	level endon( "game_ended" );
	self endon( "disconnect" );

	self.lastCampChecked = getTime();
	self.positionArray = [];
	
	CAMPTHRESHOLD = 512;
	
	for ( ;; )
	{	
		if ( !isAlive( self ) )
		{
			wait( 0.5 );
			self.lastCampChecked = getTime();
			self.positionArray = [];	
			continue;
		}
		self.positionArray[self.positionArray.size] = self.origin;
				
		if ( ( getTime() - self.lastCampChecked ) >= 15000 )
		{
			if ( Distance( self.positionArray[0], self.origin ) < CAMPTHRESHOLD && Distance( self.positionArray[1], self.positionArray[0] ) < CAMPTHRESHOLD )
			{
				timeCamping = getTime() - self.lastCampChecked;
				self incPlayerStat( "timeinspot", timeCamping  );
			}
			
			self.positionArray = [];
			
			self.lastCampChecked = getTime();
		}
		wait( 5 );
	}		
}

encodeRatio( value1, value2 )
{
	return ( value1 + (value2 << 16) );
}

getRatioHiVal( value )
{
	return (value & 65535);
}

getRatioLoVal( value )
{
	return (value >> 16);
}

monitorEnemyDistance()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	while( level._players.size < 3 )
		wait( 1 );
	
	prof_begin( "EnemyDistance" );
	for ( ;; )
	{
		foreach ( player in level._players )
		{
			
			if ( !isdefined( player ) )
				continue;
				
			if ( player.team == "spectator" )
				continue;
			
			if ( !isAlive( player ) )
				continue;
			
			sortedPlayersByDistance = SortByDistance( level._players, player.origin );
			
			if( !sortedPlayersByDistance.size )
			{
				wait( 0.05 );
				continue;
			}
			
			if ( sortedPlayersByDistance.size < 2 )
			{
				wait( 0.05 );
				continue;
			}
			
			if ( sortedPlayersByDistance[1].team != player.team )
				player incPlayerStat( "closertoenemies", 0.05 );
			
			wait( 0.05 );
		}
		
		wait( 0.05);
	}

	prof_end( "EnemyDistance" );	
}

monitorClassChange()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	for(;;)
	{
		self waittill( "spawned" );
		
		if ( self.team == "spectator" )
			continue;
		
		if( isDefined( self.lastClass ) && self.lastClass != "" && self.lastClass != self.class )
		{
			self incPlayerStat( "mostclasseschanged", 1 );
		}
		
		self.lastClass = self.class;
	}
}

monitorExplosionsSurvived()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );
	
	for(;;)
	{
		self waittill( "survived_explosion" );
		self incPlayerStat( "explosionssurvived", 1 );
		wait( 0.05 );
	}
}

monitorExplosiveHits()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );
	
	for(;;)
	{
		self waittill( "explosive_hits" );
		self incPlayerStat( "explosivehits", 1 );
		wait( 0.05 );
	}
}

monitorShieldBlocks()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );
	
	for(;;)
	{
		self waittill( "shield_blocked" );
		self incPlayerStat( "shieldblocks", 1 );
		wait( 0.05 );
	}
}

monitorFlashHits()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );
	
	for(;;)
	{
		self waittill( "flash_hit" );
		self incPlayerStat( "fbhits", 1 );
		wait( 0.05 );
	}
}

monitorStunHits()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );
	
	for(;;)
	{
		self waittill( "stun_hit" );
		self incPlayerStat( "stunhits", 1 );
		wait( 0.05 );
	}
}

//this approximates a players time spent in crouch or prone.
monitorStanceTime()
{
	level endon( "game_ended" );
	self endon( "spawned_player" );
	self endon( "death" );
	self endon( "disconnect" );
	
	for(;;)
	{
		if ( self GetStance() == "crouch" )
			self incPlayerStat( "crouchtime", 500 );
		else if ( self GetStance() == "prone" )
			self incPlayerStat( "pronetime", 500 );
		
		wait( 0.5 );
	}
}	