#using scripts\shared\callbacks_shared;
#using scripts\shared\challenges_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\demo_shared;
#using scripts\shared\gameobjects_shared;
#using scripts\shared\hostmigration_shared;
#using scripts\shared\hud_util_shared;
#using scripts\shared\math_shared;
#using scripts\shared\medals_shared;
#using scripts\shared\popups_shared;
#using scripts\shared\scoreevents_shared;
#using scripts\shared\sound_shared;
#using scripts\shared\util_shared;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	   	                                                                                                                                                                                                                                  	                                                                                                                                                                                                                                                                                                                                                                                                                                                   

#using scripts\mp\gametypes\_battlechatter;
#using scripts\mp\gametypes\_globallogic;
#using scripts\mp\gametypes\_globallogic_audio;
#using scripts\mp\gametypes\_globallogic_score;
#using scripts\mp\gametypes\_globallogic_utils;
#using scripts\mp\gametypes\_hostmigration;
#using scripts\mp\gametypes\_spawning;
#using scripts\mp\gametypes\_spawnlogic;

#using scripts\mp\_challenges;
#using scripts\mp\_util;








/*QUAKED mp_multi_team_spawn (1.0 0.0 0.0) (-16 -16 0) (16 16 72)
Spawns used for use in some multi team game modes to open up other portions of the map for multi team scenarios.*/

#precache( "string", "OBJECTIVES_KOTH" );
#precache( "string", "OBJECTIVES_KOTH_SCORE" );
#precache( "string", "MP_WAITING_FOR_HQ" );
#precache( "string", "MP_KOTH_CAPTURED_BY" );
#precache( "string", "MP_KOTH_CAPTURED_BY_ENEMY" );
#precache( "string", "MP_KOTH_MOVING_IN" );
#precache( "string", "MP_CAPTURING_OBJECTIVE" );
#precache( "string", "MP_KOTH_CONTESTED_BY_ENEMY" );
#precache( "string", "MP_KOTH_AVAILABLE_IN" );
#precache( "string", "MP_CONTROL_KOTH" );
#precache( "string", "MP_CAPTURE_KOTH" );
#precache( "string", "MP_DEFEND_KOTH" );
#precache( "string", "MP_KOTH_AVAILABLE_IN" );
#precache( "string", "MP_HQ_DESPAWN_IN" );
#precache( "string", "MP_HQ_REINFORCEMENTS_IN" );
#precache( "string", "MP_CAPTURING_HQ" );
#precache( "string", "MP_DESTROYING_HQ" );
#precache( "fx", "ui/fx_koth_marker_blue" );
#precache( "fx", "ui/fx_koth_marker_orng" );
#precache( "fx", "ui/fx_koth_marker_neutral" );
#precache( "fx", "ui/fx_koth_marker_contested" );
#precache( "fx", "ui/fx_koth_marker_blue_window" );
#precache( "fx", "ui/fx_koth_marker_orng_window" );
#precache( "fx", "ui/fx_koth_marker_neutral_window" );
#precache( "fx", "ui/fx_koth_marker_contested_window" );
#precache( "objective", "hardpoint" );

function main()
{
	globallogic::init();

	util::registerTimeLimit( 0, 1440 );
	util::registerScoreLimit( 0, 1000 );
	util::registerNumLives( 0, 100 );
	util::registerRoundSwitch( 0, 9 );
	util::registerRoundWinLimit( 0, 10 );

	globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 );
	
	level.teamBased = true;
	level.doPrematch = true;
	level.overrideTeamScore = true;
	level.scoreRoundWinBased = true;
	level.kothStartTime = 0;
	level.onStartGameType =&onStartGameType;
	level.playerSpawnedCB =&koth_playerSpawnedCB;
	level.onRoundSwitch =&onRoundSwitch;
	level.onPlayerKilled =&onPlayerKilled;
	level.onEndGame=&onEndGame;
		
	clientfield::register( "world", "hardpoint", 1, 5, "int" );
	clientfield::register( "world", "hardpointteam", 1, 5, "int" );

	level.zoneAutoMoveTime = GetGametypeSetting( "autoDestroyTime" );
	level.zoneSpawnTime = GetGametypeSetting( "objectiveSpawnTime" );
	level.kothMode = GetGametypeSetting( "kothMode" );
	level.captureTime = GetGametypeSetting( "captureTime" );
	level.destroyTime = GetGametypeSetting( "destroyTime" );
	level.delayPlayer = GetGametypeSetting( "delayPlayer" );
	level.randomZoneSpawn = GetGametypeSetting( "randomObjectiveLocations" );
	level.scorePerPlayer = GetGametypeSetting( "scorePerPlayer" );
	level.timePausesWhenInZone = GetGametypeSetting( "timePausesWhenInZone" );
	
	level.iconoffset = (0,0,32);
	
	level.onRespawnDelay =&getRespawnDelay;

	gameobjects::register_allowed_gameobject( level.gameType );

	globallogic_audio::set_leader_gametype_dialog ( "startHardPoint", "hcStartHardPoint", "objCapture", "objCapture" );
	
	game["objective_gained_sound"] = "mpl_flagcapture_sting_friend";
	game["objective_lost_sound"] = "mpl_flagcapture_sting_enemy";
	game["objective_contested_sound"] = "mpl_flagreturn_sting";

	level.lastDialogTime = 0;
	level.zoneSpawnQueue = [];
	
	// Sets the scoreboard columns and determines with data is sent across the network
	if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() )
		// local matches only show the first three columns
		globallogic::setvisiblescoreboardcolumns( "score", "kills", "objtime", "defends", "deaths" );
	else
		globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "objtime", "defends" );
	
	/#
		// HQ radio triggers are not scoped to exclude koth right now
		// going to delete them just so if we render triggers we dont see all these
		// TODO in the future is to get them into the automatic game type delete system
		trigs = getentarray("radiotrigger", "targetname");
		foreach( trig in trigs )
		{
			trig delete();
		}
	#/
}


function updateObjectiveHintMessages( defenderTeam, defendMessage, attackMessage )
{
	foreach( team in level.teams )
	{
		if ( defenderTeam == team )
		{
			game["strings"]["objective_hint_" + team] = defendMessage;
		}
		else
		{
			game["strings"]["objective_hint_" + team] = attackMessage;
		}
	}
}

function updateObjectiveHintMessage( message )
{
	foreach( team in level.teams )
	{
		game["strings"]["objective_hint_" + team] = message;
	}
}

function getRespawnDelay()
{
	self.lowerMessageOverride = undefined;

	if ( !isdefined( level.zone.gameobject ) )
		return undefined;
	
	zoneOwningTeam = level.zone.gameobject gameobjects::get_owner_team();
	if ( self.pers["team"] == zoneOwningTeam )
	{
		if ( !isdefined( level.zoneMoveTime ) )
			return undefined;
		
		timeRemaining = (level.zoneMoveTime - gettime()) / 1000;

		if (!level.playerObjectiveHeldRespawnDelay )
			return undefined;

		if ( level.playerObjectiveHeldRespawnDelay >= level.zoneAutoMoveTime )
			self.lowerMessageOverride = &"MP_WAITING_FOR_HQ";				
			
		if ( level.delayPlayer )
		{
			return min( level.spawnDelay, timeRemaining );
		}
		else
		{
			return ceil(timeRemaining);
		}
	}
}

function onStartGameType()
{
	if ( !isdefined( game["switchedsides"] ) )
		game["switchedsides"] = false;

	if ( game["switchedsides"] )
	{
		oldAttackers = game["attackers"];
		oldDefenders = game["defenders"];
		game["attackers"] = oldDefenders;
		game["defenders"] = oldAttackers;
	}
	
	
	globallogic_score::resetTeamScores();
	
	foreach( team in level.teams )
	{
		util::setObjectiveText( team, &"OBJECTIVES_KOTH" );
	
		if ( level.splitscreen )
		{
			util::setObjectiveScoreText( team, &"OBJECTIVES_KOTH" );
		}
		else
		{
			util::setObjectiveScoreText( team, &"OBJECTIVES_KOTH_SCORE" );
		}
	}
	
	level.kothTotalSecondsInZone = 0;

	level.objectiveHintPrepareZone = &"MP_CONTROL_KOTH";
	level.objectiveHintCaptureZone = &"MP_CAPTURE_KOTH";
	level.objectiveHintDefendHQ = &"MP_DEFEND_KOTH";
	
	if ( level.zoneSpawnTime )
		updateObjectiveHintMessage( level.objectiveHintPrepareZone );
	else
		updateObjectiveHintMessage( level.objectiveHintCaptureZone );
	
	setClientNameMode("auto_change");
		
	// now that the game objects have been deleted place the influencers
	spawning::create_map_placed_influencers();
	
	// TODO: HQ spawnpoints
	level.spawnMins = ( 0, 0, 0 );
	level.spawnMaxs = ( 0, 0, 0 );
	foreach( team in level.teams )
	{
		spawnlogic::add_spawn_points( team, "mp_tdm_spawn" );
		spawnlogic::add_spawn_points( team, "mp_multi_team_spawn" );

		spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) );
	}
	
	spawning::updateAllSpawnPoints();
	
	level.spawn_start = [];
	
	foreach( team in level.teams )
	{
		level.spawn_start[ team ] =  spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) );
	}

	level.mapCenter = math::find_box_center( level.spawnMins, level.spawnMaxs );
	setMapCenter( level.mapCenter );

	spawnpoint = spawnlogic::get_random_intermission_point();
	setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles );
	
	level.spawn_all = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" );
	if ( !level.spawn_all.size )
	{
/#
		println("^1No mp_tdm_spawn spawnpoints in level!");
#/
		callback::abort_level();
		return;
	}
	
	
	thread SetupZones();

	updateGametypeDvars();

	thread KothMainLoop();
}

function pause_time()
{
	if ( level.timePausesWhenInZone )
	{
		globallogic_utils::pauseTimer();
		level.timerPaused = true;
	}
}

function resume_time()
{
	if ( level.timePausesWhenInZone )
	{
    	globallogic_utils::resumeTimerDiscardOverride( level.kothTotalSecondsInZone * 1000 );
    	level.timerPaused = false;
	}
}

function updateGametypeDvars()
{
	level.playerCaptureLPM = GetGametypeSetting( "maxPlayerEventsPerMinute" );
	level.timePausesWhenInZone = GetGametypeSetting( "timePausesWhenInZone" );
}

function spawn_first_zone(delay)
{
	// pick next Zone object
	if ( level.randomZoneSpawn == 1 )
	{
		level.zone = GetNextZoneFromQueue();
	}
	else
	{
		level.zone = GetFirstZone();
	}
	
	if ( isdefined( level.zone ) )
	{
		/#print("zone spawned: ("+level.zone.trigOrigin[0]+","+level.zone.trigOrigin[1]+","+level.zone.trigOrigin[2]+")");#/
		
		level.zone spawning::enable_influencers(true);
	}

	level.zone.gameobject.trigger AllowTacticalInsertion( false ); 
	
	return;
}

function spawn_next_zone()
{
	level.zone.gameobject.trigger AllowTacticalInsertion( true ); 

	// pick next Zone object
	if ( level.randomZoneSpawn != 0 )
	{
		level.zone = GetNextZoneFromQueue();
	}
	else
	{
		level.zone = GetNextZone();
	}
	
	if ( isdefined( level.zone ) )
	{
		/#print("zone spawned: ("+level.zone.trigOrigin[0]+","+level.zone.trigOrigin[1]+","+level.zone.trigOrigin[2]+")");#/
		
		level.zone spawning::enable_influencers(true);
	}

	level.zone.gameobject.trigger AllowTacticalInsertion( false ); 
	
	return;
}

function getNumTouching( )
{
	numTouching = 0;
	foreach( team in level.teams )
	{
		numTouching += self.numTouching[team];
	}
	
	return numTouching;
}

function toggleZoneEffects( enabled )
{
	index = 0;
	
	if ( enabled )
	{
		index = self.script_index;
	}
	
	level clientfield::set( "hardpoint", index );
	level clientfield::set( "hardpointteam", 0 );
}

function KothCaptureLoop()
{
	level endon("game_ended");
	level endon("zone_moved");
	level.kothStartTime = gettime();
	
	while( 1 )
	{
		level.zone.gameobject gameobjects::allow_use( "any" );
		level.zone.gameobject gameobjects::set_use_time( level.captureTime );
		level.zone.gameobject gameobjects::set_use_text( &"MP_CAPTURING_OBJECTIVE" );
		
		numTouching = level.zone.gameobject getNumTouching( );

		level.zone.gameobject gameobjects::set_visible_team( "any" );
		level.zone.gameobject gameobjects::set_model_visibility( true );
		level.zone.gameobject gameobjects::must_maintain_claim( false );
		level.zone.gameobject gameobjects::can_contest_claim( true );
		
		level.zone.gameobject.onUse =&onZoneCapture;
		level.zone.gameobject.onBeginUse =&onBeginUse;
		level.zone.gameobject.onEndUse =&onEndUse;

		level.zone toggleZoneEffects( true );

		msg = level util::waittill_any_return( "zone_captured", "zone_destroyed", "game_ended", "zone_moved" );
	
		// this happens if it goes from contested to neutral
		if ( msg == "zone_destroyed" )
			continue;
			
		ownerTeam = level.zone.gameobject gameobjects::get_owner_team();

		foreach( team in level.teams )
		{
			updateObjectiveHintMessages( ownerTeam, level.objectiveHintDefendHQ, level.objectiveHintCaptureZone );
		}

		level.zone.gameobject gameobjects::allow_use( "none" );
		
		level.zone.gameobject.onUse = undefined;
		level.zone.gameobject.onUnoccupied =&onZoneUnoccupied;
		level.zone.gameobject.onContested =&onZoneContested;
		level.zone.gameobject.onUncontested =&onZoneUncontested;
			
		level waittill( "zone_destroyed", destroy_team );
		
		if ( !level.kothMode || level.zoneDestroyedByTimer )
			break;
		
		thread forceSpawnTeam( ownerTeam );
		
		if ( isdefined( destroy_team ) )
		{
			level.zone.gameobject gameobjects::set_owner_team( destroy_team );
		}
		else
		{
			level.zone.gameobject gameobjects::set_owner_team( "none" );
		}
	}
}

function KothMainLoop()
{
	level endon("game_ended");
	
	level.zoneRevealTime = -100000;
	
	zoneSpawningInStr = &"MP_KOTH_AVAILABLE_IN";
	if ( level.kothMode )
	{
		zoneDestroyedInFriendlyStr = &"MP_HQ_DESPAWN_IN";
		zoneDestroyedInEnemyStr = &"MP_KOTH_MOVING_IN";
	}
	else
	{
		zoneDestroyedInFriendlyStr = &"MP_HQ_REINFORCEMENTS_IN";
		zoneDestroyedInEnemyStr = &"MP_HQ_DESPAWN_IN";
	}
		
	spawn_first_zone();
	
	while ( level.inPrematchPeriod )
		{wait(.05);};
	
	pause_time();
	
	wait 5;		

	SetBombTimer( "A", 0 );
	setMatchFlag( "bomb_timer_a", 0 );

	thread hideTimerDisplayOnGameEnd();
	
	while( 1 )
	{
		resume_time();

		sound::play_on_players( "mp_suitcase_pickup" );

		globallogic_audio::leader_dialog( "kothLocated", undefined, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
		
		level.zone.gameobject gameobjects::set_model_visibility( true );
		
		level.zoneRevealTime = gettime();
	
		if ( level.zoneSpawnTime )
		{
			level.zone.gameobject gameobjects::set_visible_team( "any" );
			level.zone.gameobject gameobjects::set_flags( 1 );

			updateObjectiveHintMessage( level.objectiveHintPrepareZone );
			setMatchFlag( "bomb_timer_a", 1 );
			SetBombTimer( "A", int( gettime() + 1000 + level.zoneSpawnTime * 1000 ) );

			wait level.zoneSpawnTime;

			level.zone.gameobject gameobjects::set_flags( 0 );
			globallogic_audio::leader_dialog( "kothOnline", undefined, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
		}

		setMatchFlag( "bomb_timer_a", 0 );
		
		waittillframeend;
		
		updateObjectiveHintMessage( level.objectiveHintCaptureZone );
		sound::play_on_players( "mpl_hq_cap_us" );

		level.zone.gameobject gameobjects::enable_object();
		// Attach the objective to the visuals since it is at the right position and we can't attch it to the koth_zone_center script_origin
		Objective_OnEntity( level.zone.gameobject.objectiveID, level.zone.objectiveAnchor );
		
		level.zone.gameobject.captureCount = 0;
		
		if ( level.zoneAutoMoveTime )
		{
			thread MoveZoneAfterTime( level.zoneAutoMoveTime );
			setMatchFlag( "bomb_timer_a", 1 );
			SetBombTimer( "A", int( gettime() + 1000 + level.zoneAutoMoveTime * 1000 ) );
		}
		else
		{
			level.zoneDestroyedByTimer = false;
		}
	
		KothCaptureLoop();
		
		ownerTeam = level.zone.gameobject gameobjects::get_owner_team();
		
		if ( level.zone.gameobject.captureCount == 1 )
		{
		// Copy touch list so there aren't any threading issues
		touchList = [];
		touchKeys = GetArrayKeys( level.zone.gameobject.touchList[ownerTeam] );
		for ( i = 0 ; i < touchKeys.size ; i++ )
			touchList[touchKeys[i]] = level.zone.gameobject.touchList[ownerTeam][touchKeys[i]];
			thread give_held_credit( touchList );		
		}
		
		pause_time();
		
		level.zone spawning::enable_influencers(false);
		level.zone.gameobject.lastCaptureTeam = undefined;
		level.zone.gameobject gameobjects::disable_object();
		level.zone.gameobject gameobjects::allow_use( "none" );
		level.zone.gameobject gameobjects::set_owner_team( "neutral" );
		level.zone.gameobject gameobjects::set_model_visibility( false );
		level.zone.gameobject gameobjects::must_maintain_claim( false );
		level.zone toggleZoneEffects( false );
	
		level notify("zone_reset");
		
		setMatchFlag( "bomb_timer_a", 0 );
		spawn_next_zone();
		
		wait 0.5;
		
		thread forceSpawnTeam( ownerTeam );
		
		wait 0.5;
	}
}


function hideTimerDisplayOnGameEnd()
{
	level waittill("game_ended");
	setMatchFlag( "bomb_timer_a", 0 );
}


function forceSpawnTeam( team )
{
	players = level.players;
	for ( i = 0; i < players.size; i++ )
	{
		player = players[i];
		if ( !isdefined( player ) )
			continue;
		
		if ( player.pers["team"] == team )
		{
			player notify( "force_spawn" );
			wait .1;
		}
	}
}

function updateTeamClientField()
{
	ownerTeam = self gameobjects::get_owner_team();

	if ( ( isdefined( self.isContested ) && self.isContested ) )
	{
		level clientfield::set( "hardpointteam", 3 );
	}
	else if ( ownerTeam == "neutral" )
	{
		level clientfield::set( "hardpointteam", 0 );
	}
	else
	{
		if ( ownerTeam == "allies" )
			level clientfield::set( "hardpointteam", 1 );
		else
			level clientfield::set( "hardpointteam", 2 );
	}
}

function onBeginUse( player )
{
	ownerTeam = self gameobjects::get_owner_team();

	if ( ownerTeam == "neutral" )
	{
		player thread battlechatter::gametype_specific_battle_chatter( "hq_protect", player.pers["team"] );
	}
	else
	{
		player thread battlechatter::gametype_specific_battle_chatter( "hq_attack", player.pers["team"] );
	}
}


function onEndUse( team, player, success )
{
	player notify( "event_ended" );
}


function onZoneCapture( player )
{
	capture_team = player.pers["team"];
	captureTime = getTime();

	/#print( "zone captured" );#/

	pause_time();
	
	string = &"MP_KOTH_CAPTURED_BY";
	
	level.zone.gameobject.isContested = false;
	level.useStartSpawns = false;

	if ( !isdefined( self.lastCaptureTeam )  || self.lastCaptureTeam != capture_team )
	{
		// Copy touch list so there aren't any threading issues
		touchList = [];
		touchKeys = GetArrayKeys( self.touchList[capture_team] );
		for ( i = 0 ; i < touchKeys.size ; i++ )
			touchList[touchKeys[i]] = self.touchList[capture_team][touchKeys[i]];
		thread give_capture_credit( touchList, string, captureTime, capture_team, self.lastCaptureTeam );
	}

	level.kothCapTeam = capture_team;

	self gameobjects::set_owner_team( capture_team );
	if ( !level.kothMode )
		self gameobjects::set_use_time( level.destroyTime );
	
	foreach( team in level.teams )
	{
		if ( team == capture_team )
		{
			if ( !isdefined( self.lastCaptureTeam ) || ( self.lastCaptureTeam != team ) ) // If retaking this point after being contested, don't play VO again
			{
				globallogic_audio::leader_dialog( "kothSecured", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
				for ( index = 0; index < level.players.size; index++ )
				{
					player = level.players[index];
					
					if ( player.pers["team"] == team )
					{
						if ( player.lastKilltime + 500 > getTime() )
						{
							player challenges::killedLastContester();	
						}
					}
				}
			}
			thread sound::play_on_players( game["objective_gained_sound"], team );
		}
		else
		{
			if ( !isdefined( self.lastCaptureTeam ) )
			{
				globallogic_audio::leader_dialog( "kothCaptured", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
			}
			else if ( self.lastCaptureTeam == team )
			{
				globallogic_audio::leader_dialog( "kothLost", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
			}
			thread sound::play_on_players( game["objective_lost_sound"], team );
		}
	}
			
	self thread awardCapturePoints( capture_team, self.lastCaptureTeam );
	self.captureCount++;
	self.lastCaptureTeam = capture_team;
	
	self gameobjects::must_maintain_claim( true );
	
	self updateTeamClientField();
	
	player RecordGameEvent( "hardpoint_captured" );
	
	level notify( "zone_captured" );
	level notify( "zone_captured" + capture_team );
	player notify( "event_ended" );
}

function track_capture_time()
{
}

function give_capture_credit( touchList, string, captureTime, capture_team, lastCaptureTeam )
{
	wait .05;
	util::WaitTillSlowProcessAllowed();

	players = getArrayKeys( touchList );
	for ( i = 0; i < players.size; i++ )
	{
		player = touchList[players[i]].player;

		player updateCapsPerMinute( lastCaptureTeam );

		if ( !isScoreBoosting( player ) )
		{
			player challenges::capturedObjective( captureTime, self.trigger );
			if ( level.kothStartTime + 3000 > captureTime && level.kothCapTeam == capture_team ) 
			{
				scoreevents::processScoreEvent( "quickly_secure_point", player );
			}
		
			scoreevents::processScoreEvent( "koth_secure", player );
			player RecordGameEvent("capture");

			level thread popups::DisplayTeamMessageToAll( string, player );

			if( isdefined(player.pers["captures"]) )
			{
				player.pers["captures"]++;
				player.captures = player.pers["captures"];
			}		
				
			if ( level.kothStartTime + 500 > captureTime ) 
			{
				player challenges::immediateCapture();
			}

			demo::bookmark( "event", gettime(), player );
			player AddPlayerStatWithGameType( "CAPTURES", 1 );
		}
		else
		{
			/#
				player IPrintlnBold( "GAMETYPE DEBUG: NOT GIVING YOU CAPTURE CREDIT AS BOOSTING PREVENTION" );
			#/
		}
	}
}

function give_held_credit( touchList, team )
{
	wait .05;
	util::WaitTillSlowProcessAllowed();

	players = getArrayKeys( touchList );
	for ( i = 0; i < players.size; i++ )
	{
		player = touchList[players[i]].player;
			
		//scoreevents::processScoreEvent( "koth_held", player );

		// do not know if we want the following
		//player RecordGameEvent("held");

	}
}

function onZoneDestroy( player )
{
	destroyed_team = player.pers["team"];

	/#print( "zone destroyed" );#/
	scoreevents::processScoreEvent( "zone_destroyed", player );	
	player RecordGameEvent("destroy");	
	player AddPlayerStatWithGameType( "DESTRUCTIONS", 1 );
		
	if( isdefined(player.pers["destructions"]) )
	{
		player.pers["destructions"]++;
		player.destructions = player.pers["destructions"];
	}
	
	destroyTeamMessage = &"MP_HQ_DESTROYED_BY";
	otherTeamMessage = &"MP_HQ_DESTROYED_BY_ENEMY";
	
	if ( level.kothMode )
	{
		destroyTeamMessage = &"MP_KOTH_CAPTURED_BY";
		otherTeamMessage = &"MP_KOTH_CAPTURED_BY_ENEMY";
	}
	
	level thread popups::DisplayTeamMessageToAll( destroyTeamMessage, player );

	foreach( team in level.teams )
	{
		if ( team == destroyed_team )
		{
			globallogic_audio::leader_dialog( "koth_secured", team, undefined, "gamemode_objective" );
		}
		else
		{
			globallogic_audio::leader_dialog( "koth_destroyed", team, undefined, "gamemode_objective" );	
		}
	}

	level notify( "zone_destroyed", destroyed_team );
	
	if ( level.kothMode )
		level thread awardCapturePoints( destroyed_team );

	player notify( "event_ended" );
}

function onZoneUnoccupied()
{
	level notify( "zone_destroyed" );
	level.kothCapTeam = "neutral";
	level.zone.gameobject.wasLeftUnoccupied = true;
	level.zone.gameobject.isContested = false;

	level.zone.gameobject RecordGameEventNonPlayer( "hardpoint_empty" );
		
	resume_time();
	
	self updateTeamClientField();
}

function onZoneContested()
{	
	zoneOwningTeam = self gameobjects::get_owner_team();
	self.wasContested = true;
	self.isContested = true;

	self updateTeamClientField();

	self RecordGameEventNonPlayer( "hardpoint_contested" );
	
	resume_time();

	foreach( team in level.teams )
	{
		if ( team == zoneOwningTeam )
		{
			thread sound::play_on_players( game["objective_contested_sound"], team );
			globallogic_audio::leader_dialog( "kothContested", team, undefined, "gamemode_objective", undefined, "kothActiveDialogBuffer" );
		}
	}
}

function onZoneUncontested( lastClaimTeam )
{	
	assert( lastClaimTeam ==  level.zone.gameobject gameobjects::get_owner_team() );
	
	self.isContested = false;

	pause_time();

	self gameobjects::set_claim_team( lastClaimTeam );
	
	self updateTeamClientField();
	
	self RecordGameEventNonPlayer( "hardpoint_uncontested" );
}

function MoveZoneAfterTime( time )
{
	level endon( "game_ended" );
	level endon( "zone_reset" );
	
	level.zoneMoveTime = gettime() + time * 1000;
	level.zoneDestroyedByTimer = false;
	
	wait time;

	if ( !isdefined( level.zone.gameobject.wasContested ) || level.zone.gameobject.wasContested == false )
	{
		if ( !isdefined( level.zone.gameobject.wasLeftUnoccupied ) || level.zone.gameobject.wasLeftUnoccupied == false )
		{
			zoneOwningTeam = level.zone.gameobject gameobjects::get_owner_team();
			challenges::controlZoneEntirely( zoneOwningTeam );
		}
	}

	level.zoneDestroyedByTimer = true;
	
	level.zone.gameobject RecordGameEventNonPlayer( "hardpoint_moved" );

	level notify( "zone_moved" );
}


function awardCapturePoints( team, lastCaptureTeam )
{
	level endon( "game_ended" );
	level endon( "zone_destroyed" );
	level endon( "zone_reset" );
	level endon( "zone_moved" );
	
	level notify("awardCapturePointsRunning");
	level endon("awardCapturePointsRunning");
	
	seconds = 1;
	score = 1;

	while ( !level.gameEnded )
	{
		wait seconds;
	
		hostmigration::waitTillHostMigrationDone();
		
		if ( !level.zone.gameobject.isContested )
		{
			if ( level.scorePerPlayer )
			{
				score = level.zone.gameobject.numTouching[team];
			}
			
			globallogic_score::giveTeamScoreForObjective( team, score );
			level.kothTotalSecondsInZone++;
			
			foreach( player in level.aliveplayers[team] )
			{
				if ( !IsDefined( player.touchTriggers[self.entNum] ) )
					continue;
				
				if( isdefined(player.pers["objtime"]) )
				{
					player.pers["objtime"]++;
					player.objtime = player.pers["objtime"];
				}		
				player AddPlayerStatWithGameType( "OBJECTIVE_TIME", 1 );
			}
		}
	}
}

function koth_playerSpawnedCB()
{
	self.lowerMessageOverride = undefined;
}

function CompareZoneIndexes( zone_a, zone_b )
{
	script_index_a = zone_a.script_index;
	script_index_b = zone_b.script_index;
	
	if( !isdefined(script_index_a) && !isdefined(script_index_b) )
	{
		return false;
	}

	if( !isdefined(script_index_a) && isdefined(script_index_b) )
	{
/#
		println( "KOTH: Missing script_index on zone at " + zone_a.origin );
#/
		return true;
	}
	
	if( isdefined(script_index_a) && !isdefined(script_index_b) )
	{
/#
		println( "KOTH: Missing script_index on zone at " + zone_b.origin );
#/
		return false;
	}
	
	if( script_index_a > script_index_b )
	{
		return true;
	}
	
	return false;
}


function getZoneArray()
{
  zones = getentarray( "koth_zone_center", "targetname" );

	if( !isdefined( zones ) )
	{
		return undefined;
	}
	
	swapped = true;
	n = zones.size;
	while ( swapped )
	{
		swapped = false;
		for( i = 0 ; i < n-1 ; i++ )
		{
			if( CompareZoneIndexes(zones[i], zones[i+1]) )
			{
				temp = zones[i];
				zones[i] = zones[i+1];
				zones[i+1] = temp;
				swapped = true;
			}
		}
		n--;
	}
	return zones;
}


function SetupZones()
{
	maperrors = [];

	zones = getZoneArray();
	
//	if ( zones.size < 2 )
//	{
//		maperrors[maperrors.size] = "There are not at least 2 entities with targetname \"zone\"";
//	}
	
	trigs = getentarray("koth_zone_trigger", "targetname");
	for ( i = 0; i < zones.size; i++ )
	{
		errored = false;
		
		zone = zones[i];
		zone.trig = undefined;
		for ( j = 0; j < trigs.size; j++ )
		{
			if ( zone istouching( trigs[j] ) )
			{
				if ( isdefined( zone.trig ) )
				{
					maperrors[maperrors.size] = "Zone at " + zone.origin + " is touching more than one \"zonetrigger\" trigger";
					errored = true;
					break;
				}
				zone.trig = trigs[j];
				break;
			}
		}
		
		if ( !isdefined( zone.trig ) )
		{
			if ( !errored )
			{
				maperrors[maperrors.size] = "Zone at " + zone.origin + " is not inside any \"zonetrigger\" trigger";
				continue;
			}
			
			// possible fallback (has been tested)
			//zone.trig = spawn( "trigger_radius", zone.origin, 0, 128, 128 );
			//errored = false;
		}
		
		assert( !errored );
		
		zone.trigorigin = zone.trig.origin;
		
		zone.objectiveAnchor = Spawn( "script_model", zone.origin ); // We need a script_model to attach the objective to
		
		visuals = [];
		visuals[0] = zone;
		
		if ( isdefined( zone.target ) )
		{
			otherVisuals = getEntArray( zone.target, "targetname" );
			for ( j = 0; j < otherVisuals.size; j++ )
			{
				visuals[visuals.size] = otherVisuals[j];
			}
		}
		
		objective_name = istring("hardpoint");

		zone.gameObject = gameobjects::create_use_object( "neutral", zone.trig, visuals, (0,0,0), objective_name );
		zone.gameObject gameobjects::set_objective_entity( zone );
		zone.gameObject gameobjects::disable_object();
		zone.gameObject gameobjects::set_model_visibility( false );
		zone.trig.useObj = zone.gameObject;
		zone.trig.remote_control_player_can_trigger = true;
		zone setUpNearbySpawns();
		zone createZoneSpawnInfluencer();
	}
	
	if (maperrors.size > 0)
	{
		/#
		println("^1------------ Map Errors ------------");
		for(i = 0; i < maperrors.size; i++)
			println(maperrors[i]);
		println("^1------------------------------------");
		
		util::error("Map errors. See above");
		#/
		callback::abort_level();
		
		return;
	}
	
	level.zones = zones;
	
	level.prevzone = undefined;
	level.prevzone2 = undefined;
	
	setupZoneExclusions();
	
	return true;
}

function setupZoneExclusions()
{
	if ( !isdefined( level.levelkothDisable ) ) 
		return;
		
/*	foreach( nullZone in level.levelkothDisable ) 
	{
		foreach( zone in level.zones ) 
		{
//			if ( zone.gameObject.trigger istouching( nullZone ) )
			if ( zone.gameObject.trigger istouchingvolume( nullZone.origin, nullZone getmins(), nullZone getmaxs() ) )
			{
				if ( !isdefined( zone.gameObject.exclusions ) )
				{
					zone.gameObject.exclusions = [];
				}
				
				zone.gameObject.exclusions[ zone.gameObject.exclusions.size ] = nullZone;			
			}
		}
	}	
*/

  	foreach( nullZone in level.levelkothDisable )
	{
		mindist = 10000000000;
		foundZone = undefined;
		
		foreach( zone in level.zones ) 
		{
			distance = DistanceSquared( nullZone.origin, zone.origin );
			
			if ( distance < mindist )
			{
				foundZone = zone;
				mindist = distance;
			}
		}
		
		if ( isdefined( foundZone ) )
		{
			if ( !isdefined( foundZone.gameObject.exclusions ) )
			{
				foundZone.gameObject.exclusions = [];
			}
			foundZone.gameObject.exclusions[ foundZone.gameObject.exclusions.size ] = nullZone;
		}
	}

}

function setUpNearbySpawns()
{
	spawns = level.spawn_all;
	
	for ( i = 0; i < spawns.size; i++ )
	{
		spawns[i].distsq = distanceSquared( spawns[i].origin, self.origin );
	}
	
	// sort by distsq
	for ( i = 1; i < spawns.size; i++ )
	{
		thespawn = spawns[i];
		for ( j = i - 1; j >= 0 && thespawn.distsq < spawns[j].distsq; j-- )
			spawns[j + 1] = spawns[j];
		spawns[j + 1] = thespawn;
	}
	
	first = [];
	second = [];
	third = [];
	outer = [];
	
	thirdSize = spawns.size / 3;
	for ( i = 0; i <= thirdSize; i++ )
	{
		first[ first.size ] = spawns[i];
	}
	for ( ; i < spawns.size; i++ )
	{
		outer[ outer.size ] = spawns[i];
		if ( i <= (thirdSize*2) )
			second[ second.size ] = spawns[i];
		else			
			third[ third.size ] = spawns[i];
	}
	
	self.gameObject.nearSpawns = first;
	self.gameObject.midSpawns = second;
	self.gameObject.farSpawns = third;
	self.gameObject.outerSpawns = outer;
}

function GetFirstZone()
{
	zone = level.zones[ 0 ];

	// old linear and "random" systems 
	level.prevzone2 = level.prevzone;
	level.prevzone = zone;
	level.prevZoneIndex = 0;

	// new shuffled system
	ShuffleZones();
	ArrayRemoveValue( level.zoneSpawnQueue, zone );

	return zone;
}

function GetNextZone()
{
	nextZoneIndex = 	(level.prevZoneIndex + 1) % level.zones.size;
	zone = level.zones[ nextZoneIndex ];
	level.prevzone2 = level.prevzone;
	level.prevzone = zone;
	level.prevZoneIndex = nextZoneIndex;
	
	return zone;
}

function PickRandomZoneToSpawn()
{
	level.prevZoneIndex = randomint( level.zones.size);
	zone = level.zones[ level.prevZoneIndex ];
	level.prevzone2 = level.prevzone;
	level.prevzone = zone;
	
	return zone;
}

function ShuffleZones()
{
	level.zoneSpawnQueue = [];

	spawnQueue = ArrayCopy(level.zones);	
	total_left = spawnQueue.size;
	
	while( total_left > 0 )
	{
		index = randomint( total_left );
		
		valid_zones = 0;
		for( zone = 0; zone < level.zones.size; zone++ )
		{
			if ( !isdefined(spawnQueue[zone]) )
				continue;
				
			if ( valid_zones == index )
			{
				// dont allow the last radio from the previous shuffle to be put first in the next
				if ( level.zoneSpawnQueue.size == 0 && isdefined( level.zone ) && level.zone == spawnQueue[zone] )
					continue;

				level.zoneSpawnQueue[level.zoneSpawnQueue.size] = spawnQueue[zone];
				spawnQueue[zone] = undefined;
				break;
			}
			
			valid_zones++; 
		}
		
		total_left--;
	}
}

// shuffled picking
function GetNextZoneFromQueue()
{
	if ( level.zoneSpawnQueue.size == 0 )
		ShuffleZones();

	assert( level.zoneSpawnQueue.size > 0 );

	next_zone = level.zoneSpawnQueue[0];
	ArrayRemoveIndex( level.zoneSpawnQueue, 0 );
	
	return next_zone;
}

function GetCountOfTeamsWithPlayers(num)
{
	has_players = 0;
	
	foreach( team in level.teams )
	{
		if ( num[team] > 0 )
			has_players++;
	}
	
	return has_players;
}

function GetPointCost( avgpos, origin )
{
	avg_distance = 0;
	total_error = 0;
	distances = [];
	
	foreach( team, position in avgpos )
	{
		distances[team] = Distance(origin, avgpos[team]);
		avg_distance += distances[team];
	}
	
	avg_distance = avg_distance / distances.size;
	
	foreach( team, dist in distances )
	{
		err = (distances[team] - avg_distance);
		total_error += err * err;
	}
	
	return total_error;
}

function PickZoneToSpawn()
{
	// find average of positions of each team
	// (medians would be better, to get rid of outliers...)
	// and find the zone which has the least difference in distance from those two averages
	
	foreach( team in level.teams )
	{	
		avgpos[team] = (0,0,0);
		num[team] = 0;
	}
	
	for ( i = 0; i < level.players.size; i++ )
	{
		player = level.players[i];
		if ( isalive( player ) )
		{
			avgpos[ player.pers["team"] ] += player.origin;
			num[ player.pers["team"] ]++;
		}
	}
	
	if ( GetCountOfTeamsWithPlayers(num) <= 1 )
	{
		zone = level.zones[ randomint( level.zones.size) ];
		while ( isdefined( level.prevzone ) && zone == level.prevzone ) // so lazy
			zone = level.zones[ randomint( level.zones.size) ];
		
		level.prevzone2 = level.prevzone;
		level.prevzone = zone;
		
		return zone;
	}
	
	foreach( team in level.teams )
	{	
		if ( num[team] == 0 )
		{
			avgpos[team] = undefined;
		}
		else
		{
			avgpos[team] = avgpos[team] / num[team];
		}
	}
		
	bestzone = undefined;
	lowestcost = undefined;
	for ( i = 0; i < level.zones.size; i++ )
	{
		zone = level.zones[i];
		
		// (purposefully using distance instead of distanceSquared)
		cost = GetPointCost( avgpos, zone.origin );
		
		if ( isdefined( level.prevzone ) && zone == level.prevzone )
		{
			continue;
		}
		if ( isdefined( level.prevzone2 ) && zone == level.prevzone2 )
		{
			if ( level.zones.size > 2 )
				continue;
			else
				cost += 512 * 512;
		}
		
		if ( !isdefined( lowestcost ) || cost < lowestcost )
		{
			lowestcost = cost;
			bestzone = zone;
		}
	}
	assert( isdefined( bestzone ) );
	
	level.prevzone2 = level.prevzone;
	level.prevzone = bestzone;
	
	return bestzone;
}

function onRoundSwitch()
{
	game["switchedsides"] = !game["switchedsides"];
}


function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration )
{
	if ( !isPlayer( attacker ) || (level.captureTime && !self.touchTriggers.size && !attacker.touchTriggers.size) || attacker.pers["team"] == self.pers["team"] )
		return;

	medalGiven = false;
	scoreEventProcessed = false;
	
	ownerTeam = undefined;
	
	if ( level.captureTime == 0 )
	{
		if ( !isdefined( level.zone ) )
			return;

		ownerTeam = level.zone.gameObject.ownerTeam ;
		
		if ( !isdefined( ownerTeam ) || ownerTeam == "neutral" )
			return;
	}
	
	if ( self.touchTriggers.size || ( level.captureTime == 0 && self IsTouching( level.zone.trig ) ) )
	{
		if ( level.captureTime > 0 )
		{
			triggerIds = getArrayKeys( self.touchTriggers );
			ownerTeam = self.touchTriggers[triggerIds[0]].useObj.ownerTeam;
		}
		
		if ( ownerTeam != "neutral" )
		{
			attacker.lastKilltime = getTime();
			team = attacker.pers["team"];
			if ( team == ownerTeam )
			{
				if ( !medalGiven ) 
				{
					attacker medals::offenseGlobalCount();
					attacker thread challenges::killedBaseOffender(level.zone.trig, weapon);
					
					medalGiven = true;
				}
				//scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
				scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
				self RecordKillModifier("defending");
				scoreEventProcessed = true;
			}
			else
			{
				if ( !medalGiven ) 
				{
					if( isdefined(attacker.pers["defends"]) )
					{
						attacker.pers["defends"]++;
						attacker.defends = attacker.pers["defends"];
					}

					attacker medals::defenseGlobalCount();
					medalGiven = true;
					attacker thread challenges::killedBaseDefender(level.zone.trig);
					attacker RecordGameEvent("defending");
				}
				attacker challenges::killedZoneAttacker( weapon );
				//scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon ); // TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
				scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
				self RecordKillModifier("assaulting");
				scoreEventProcessed = true;
			}
		}		
	}	
	
	if ( attacker.touchTriggers.size || ( level.captureTime == 0 && attacker IsTouching( level.zone.trig ) ) )
	{
		if ( level.captureTime > 0 )
		{
			triggerIds = getArrayKeys( attacker.touchTriggers );
			ownerTeam = attacker.touchTriggers[triggerIds[0]].useObj.ownerTeam;
		}
		
		if ( ownerTeam != "neutral" )
		{
			team = self.pers["team"];
			if ( team == ownerTeam )
			{
				if ( !medalGiven ) 
				{					
					if( isdefined(attacker.pers["defends"]) )
					{
						attacker.pers["defends"]++;
						attacker.defends = attacker.pers["defends"];
					}

					attacker medals::defenseGlobalCount();
					medalGiven = true;
					attacker thread challenges::killedBaseDefender(level.zone.trig);
					attacker RecordGameEvent("defending");
				}
				if ( scoreEventProcessed == false )
				{
					attacker challenges::killedZoneAttacker( weapon );
					//scoreevents::processScoreEvent( "killed_attacker", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
					scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
					self RecordKillModifier("assaulting");
				}
			}
			else
			{
				if ( !medalGiven ) 
				{
					attacker medals::offenseGlobalCount();
					medalGiven = true;
					attacker thread challenges::killedBaseOffender(level.zone.trig, weapon);
				}
				if ( scoreEventProcessed == false )
				{
					//scoreevents::processScoreEvent( "killed_defender", attacker, undefined, weapon );// TFLAME 9/3/12 - Changing these events to "hardpoint_kill" as attacker / defender changes so often in Hardpoint
					scoreevents::processScoreEvent( "hardpoint_kill", attacker, undefined, weapon );
					self RecordKillModifier("defending");
				}
			}		
		}
	}
	
	if ( medalGiven == true )
	{
		if ( ( isdefined( level.zone.gameobject.isContested ) && level.zone.gameobject.isContested ) )
		{
			attacker thread killWhileContesting();
		}
	}
}

function watchKillWhileContesting( zone_captured_team )
{
	level endon( zone_captured_team );
	level endon( "zone_destroyed" );
	level endon( "zone_captured" );
	level endon( "death" );
	
	self util::waittill_any_return( "killWhileContesting", "disconnect" );
	level notify( "abortKillWhileContesting" );
}

function killWhileContesting()
{	
	self notify( "killWhileContesting" );
	self endon( "killWhileContesting" );
	self endon( "disconnect" );
	
	killTime = getTime();
	playerteam = self.pers["team"];
	if ( !isdefined ( self.clearEnemyCount ) )
	{
		self.clearEnemyCount = 0;
	}
	
	self.clearEnemyCount++;
	
	zone_captured_team = "zone_captured" + playerteam;
	self thread watchKillWhileContesting( zone_captured_team );
	zoneReturn = level util::waittill_any_return( zone_captured_team, "zone_destroyed", "zone_captured", "death", "abortKillWhileContesting" );
	
	if ( zoneReturn == "death" || playerteam != self.pers["team"] )
	{
		self.clearEnemyCount = 0;
		return;
	}

	if ( self.clearEnemyCount >= 2 && killTime + 200 > getTime() )
	{
		scoreevents::processScoreEvent( "clear_2_attackers", self );
	}
	self.clearEnemyCount = 0;
}
			
function onEndGame( winningTeam )
{
	for ( i = 0; i < level.zones.size; i++ )
	{
		level.zones[i].gameobject gameobjects::allow_use( "none" );
	}
}

function createZoneSpawnInfluencer()
{
		// this affects both teams
	self spawning::create_influencer( "koth_large", self.gameobject.curOrigin, 0 );
	self spawning::create_influencer( "koth_small", self.gameobject.curOrigin, 0 );

	// turn it off for now
	self spawning::enable_influencers(false);
}

function updateCapsPerMinute(lastOwnerTeam)
{
	if ( !isdefined( self.capsPerMinute ) )
	{
		self.numCaps = 0;
		self.capsPerMinute = 0;
	}
	
	// not including neutral flags as part of the boosting prevention
	// to help with false positives at the start
	if ( !isdefined ( lastOwnerTeam ) || lastOwnerTeam == "neutral" )
		return;
		
	self.numCaps++;
	
	minutesPassed = globallogic_utils::getTimePassed() / ( 60 * 1000 );
	
	// players use the actual time played
	if ( IsPlayer( self ) && isdefined(self.timePlayed["total"]) )
		minutesPassed = self.timePlayed["total"] / 60;
		
	self.capsPerMinute = ( minutesPassed ? self.numCaps / minutesPassed : 0 );
	if ( self.capsPerMinute > self.numCaps )
		self.capsPerMinute = self.numCaps;
}

function isScoreBoosting( player )
{
	if ( !level.rankedMatch )
		return false;
		
	if ( player.capsPerMinute > level.playerCaptureLPM )
		return true;

	return false;
}
