#using scripts\mp\_teamops;
#using scripts\mp\_util;
#using scripts\mp\bots\_bot;
#using scripts\mp\gametypes\_battlechatter;
#using scripts\mp\gametypes\_deathicons;
#using scripts\mp\gametypes\_dogtags;
#using scripts\mp\gametypes\_globallogic;
#using scripts\mp\gametypes\_globallogic_audio;
#using scripts\mp\gametypes\_globallogic_defaults;
#using scripts\mp\gametypes\_globallogic_score;
#using scripts\mp\gametypes\_globallogic_spawn;
#using scripts\mp\gametypes\_globallogic_ui;
#using scripts\mp\gametypes\_globallogic_utils;
#using scripts\mp\gametypes\_killcam;
#using scripts\mp\gametypes\_loadout;
#using scripts\mp\gametypes\_prop_controls;
#using scripts\mp\gametypes\_prop_dev;
#using scripts\mp\gametypes\_spawning;
#using scripts\mp\gametypes\_spawnlogic;
#using scripts\mp\killstreaks\_killstreaks;
#using scripts\mp\teams\_teams;
#using scripts\shared\ai_shared;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_shared;
#using scripts\shared\clientfield_shared;
#using scripts\shared\damagefeedback_shared;
#using scripts\shared\flag_shared;
#using scripts\shared\fx_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\scoreevents_shared;
#using scripts\shared\system_shared;
#using scripts\shared\tweakables_shared;
#using scripts\shared\util_shared;
#using scripts\shared\weapons\_weapons;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	   	                                                                                                                                                                                                                                  	                                                                                                                                                                                                                                                                                                                                                                                                                                                   
           	               	           	                                                    	      	       

#namespace prop;

#precache( "string", "OBJECTIVES_PH_ATTACKER" );
#precache( "string", "OBJECTIVES_PH_DEFENDER" );
#precache( "string", "OBJECTIVES_PH_ATTACKER_SCORE" );
#precache( "string", "OBJECTIVES_PH_DEFENDER_SCORE" );
#precache( "string", "OBJECTIVES_PH_ATTACKER_HINT" );
#precache( "string", "OBJECTIVES_PH_DEFENDER_HINT" );
#precache( "string", "MP_PH_STARTS_IN" );
#precache( "string", "MP_PH_WHISTLE_IN" );
#precache( "string", "MP_PH_WHISTLING" );
#precache( "string", "MP_PH_ALIVE" );
#precache( "string", "MP_PH_FINAL_WHISTLE" );
#precache( "string", "MP_PH_SPIN" );
#precache( "string", "MP_PH_SPIN_PC" );
#precache( "string", "MP_PH_SLOPE" );
#precache( "string", "MP_PH_SLOPE_PC" );
#precache( "string", "MP_PH_CHANGE" );
#precache( "string", "MP_PH_CLONE" );
#precache( "string", "MP_PH_ZOOM" );
#precache( "string", "MP_PH_SPECKEY" );
#precache( "string", "MP_PH_SLOPED" );
#precache( "string", "MP_PH_SLOPED_PC" );
#precache( "string", "MP_PH_LOCK" );
#precache( "string", "MP_PH_LOCKED" );
#precache( "string", "MP_PH_SPECCOMMANDS" );
#precache( "string", "MP_PH_FLASH" );
#precache( "string", "MP_PH_HIDEPROP" );
#precache( "string", "MP_PH_SHOWPROP" );
#precache( "string", "MP_PH_TIEBREAKER" );
#precache( "string", "SCORE_LAST_ALIVE" );
#precache( "string", "MP_PH_TIEBREAKER_KILL" );
#precache( "string", "MP_PH_TIEBREAKER_TIME" );
#precache( "string", "MP_PH_MINIGAME_FIRST" );
#precache( "string", "MP_PH_MINIGAME_SECOND" );
#precache( "string", "MP_PH_MINIGAME_THIRD" );
#precache( "string", "MP_PH_PREGAME_HUNT" );
#precache( "string", "MP_PH_PREGAME_CHASE" );
#precache( "string", "MP_PH_PING" );
#precache( "string", "MP_PH_EMPTY" );
#precache( "string", "SCORE_DECOY_KILLED" );

#precache( "material", "t7_hud_waypoints_safeguard_location" );

#precache( "fx", "explosions/fx_exp_grenade_concussion" );
#precache( "fx", "explosions/fx_prop_exp" );
#precache( "fx", "player/fx_plyr_clone_reaper_appear" );

/*
	Prop Hunt
*/

function autoexec __init__sytem__() {     system::register("prop",&__init__,undefined,undefined);    }
	
function __init__()
{
	clientfield::register( "allplayers", "hideTeamPlayer" , 27000, 2, "int" );
	clientfield::register( "allplayers", "pingHighlight" , 27000, 1, "int" );
}

function main()
{
	globallogic::init();
	
	util::registerRoundSwitch( 0, 9 );
	util::registerTimeLimit( 0, 4 );
	util::registerScoreLimit( 0, 0 );
	util::registerRoundLimit( 0, 4 );
	util::registerRoundWinLimit( 0, 3 );
	util::registerNumLives( 0, 1 );
	
	level.phSettings = SpawnStruct();
	level.phSettings.propHideTime = 30;
	level.phSettings.propWhistleTime = getMapMaxWhistleTime();
	level.phSettings.propChangeCount = 2;
	level.phSettings.propNumFlashes = 1;
	level.phSettings.propNumClones = 3;
	level.phSettings.propSpeedScale = 1.4;
	level.phSettings.hunterNumPings = 2;
	level.phSettings.allowLoadouts = false;
	level.phSettings.nuketownVariant = ( level.script == "mp_nuketown_x" );
	level.phSettings.altModeManyClones = level.phSettings.nuketownVariant;
	
	if ( level.phSettings.altModeManyClones )
	{
		level.phSettings.propNumClones = 9;
	}
			
	globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 );

	level.isPropHunt			= true;
	level.allow_teamchange		= "1";
	level.killstreaksEnabled	= false;
	level.teamBased 			= true;
	level.overrideTeamScore		= true;
	level.alwaysUseStartSpawns	= true;
	level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false );
	level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" );
	level.teamScorePerDeath = GetGametypeSetting( "teamScorePerDeath" );
	level.teamScorePerHeadshot = GetGametypeSetting( "teamScorePerHeadshot" );
	level.killstreaksGiveGameScore = GetGametypeSetting( "killstreaksGiveGameScore" );
	level.onStartGameType 					=&onStartGameType;
	level.onSpawnPlayer 					=&onSpawnPlayer;
	level.onPlayerDisconnect				=&onPlayerDisconnect;
	level.onRoundEndGame 					=&onRoundEndGame;
	level.onRoundSwitch 					=&onRoundSwitch;
	level.gameModeAssistedSuicide			=&gameModeAssistedSuicide;
	level.onPlayerKilled 					=&onPlayerKilled;
	level.onOneLeftEvent					=&onOneLeftEvent;
	level.onTimeLimit						=&onTimeLimit;
	level.onDeadEvent						=&onDeadEvent;
	level.customPlayPainSound				=&playDamageSoundPH;
	level.customPlayDeathSound				=&playDeathSoundPH;
	level.overridePlayerDamage				=&gamemodeModifyPlayerDamage;
	level.gametypeRoundEndScoreHud			=&gametypeRoundEndScoreHud;
	level.checkSpawnPoint					=&checkSpawnPoint;
	level.finalizeStartSpawn				=&finalizeStartSpawn;
	level.maySpawnGameMode					=&maySpawnGameMode;
	level.givecustomloadout					=&giveCustomLoadout;
	level.prematchPeriodSpawnFunc			=&prematchPeriodSpawn;
	level.playerAvoidDamageGameMode			=&playerAvoidDamageGameMode;
	level.customDamageShellshockAndRumble	=&customDamageShellshockAndRumble;
	level.determineWinner					=&determineWinner;
	
	level.disableWeaponAccuracyTracking		= true;
	
	gameobjects::register_allowed_gameobject( level.gameType );

	// Sets the scoreboard columns and determines with data is sent across the network
	globallogic::setvisiblescoreboardcolumns( "score", "objtime", "kills", "deaths", "assists" ); 

	level.propList 				= [];
	level.propIndex				= [];
	level.spawnPropList			= [];
	level.abilities				= Array( "FLASH", "CLONE" );
	
	populatePropList();
	
	level.gracePeriod = Int( level.phSettings.propHideTime + 0.5 );
		
	level thread onPlayerConnect();
	level thread delaySet();
	level thread monitorHostmigrationTime();
	
	if ( level.phSettings.nuketownVariant )
		level thread manageMannequins();
	
	ShatterAllGlass();
	util::set_dvar_int_if_unset( "scr_prop_minigame", 1 );
	
	/#
	level.alwaysAllowSpawn = false;
	thread prop_dev::propDevGui();
 	#/
}

function getMapMaxWhistleTime()
{
	if ( level.script == "mp_chinatown" )
	{
		return 18;
	}
	else if ( level.script == "mp_redwood" )
	{
		return 20;
	}
	else if ( level.script == "mp_nuketown_x" )
	{
		return 0;
	}
	
	return 30;
}

function delaySet()
{
	{wait(.05);};
	
	level.playStartConversation = false;
	level.allowSpecialistDialog = false;
}

function onEndGame( winningTeam )
{
	if ( isdefined( winningTeam ) && isdefined( level.teams[winningTeam] ) )
		globallogic_score::giveTeamScoreForObjective( winningTeam, 1 );
}

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

	if ( level.scoreRoundWinBased ) 
	{
		foreach( team in level.teams )
		{
			[[level._setTeamScore]]( team, game["roundswon"][team] );
		}
	}
}

function onRoundEndGame( finalRoundWinner )
{
	gameWinner = finalRoundWinner;
	
	// need to evaluate winner here for round based game modes
	if ( level.gameEnded )
	{
		gameWinner = determineWinnerInternal( gameWinner, true );
	}
	
	if ( ( gameWinner == "allies" ) || ( gameWinner == "axis" ) )
		ph_setFinalKillCamWinner( gameWinner );
	
	return gameWinner;
}

function determineWinner( roundWinner )
{
	return determineWinnerInternal( roundWinner, false );
}

function determineWinnerInternal( roundWinner, adjustTieScore )
{
	gameWinner = roundWinner;
	
	winEvent = "roundswon";
	
	level.propTieBreaker = "none";
    if ( game[winEvent]["allies"] == game[winEvent]["axis"] )
    {
    	level.propTieBreaker = "kills";
    	if ( game["propScore"]["axis"] == game["propScore"]["allies"] )
	    {
    		level.propTieBreaker = "time";
    		if ( game["hunterKillTime"]["axis"] == game["hunterKillTime"]["allies"] )
    		{
    			level.propTieBreaker = "tie";
    			gameWinner = "tie";
    		}
    		else if ( game["hunterKillTime"]["axis"] < game["hunterKillTime"]["allies"] )
			{
				gameWinner = "axis";
			}
			else
			{
	    		gameWinner = "allies";
			}
	    }
		else if ( game["propScore"]["axis"] > game["propScore"]["allies"] )
		{
			gameWinner = "axis";
		}
		else
		{
    		gameWinner = "allies";
		}
		
					
		// Give the tie-breaker winner one extra round win to showcase in the AAR (so it doesn't look like a tie)
		if ( gameWinner != "tie" && adjustTieScore )
			level thread givePHTeamScore( gameWinner );
    }
    else if ( game[winEvent]["axis"] > game[winEvent]["allies"] )
    {
		gameWinner = "axis";
    }
	else
	{
		gameWinner = "allies";
	}
	
	return gameWinner;
}

function onScoreCloseMusic()
{
	teamScores = [];
	
	while( !level.gameEnded )
	{
		scoreLimit = level.scoreLimit;
		scoreThreshold = scoreLimit * .1;
		scoreThresholdStart = abs(scoreLimit - scoreThreshold);
		scoreLimitCheck = scoreLimit - 10;
	
		topScore = 0;
		runnerUpScore = 0;
		foreach( team in level.teams )
		{
		    score = [[level._getTeamScore]]( team );
		    
		    if ( score > topScore )
		    {
		    	runnerUpScore = topScore;
		    	topScore = score;
		    }
		    else if ( score > runnerUpScore )
		    {
		    	runnerUpScore = score;
		    }
		}
	
		scoreDif = (topScore - runnerUpScore);
		
		if( topScore >= scoreLimit*.5)
		{
			level notify( "sndMusicHalfway" );
			return;
		}
		  
		wait(1);
	}
}

function onPlayerConnect()
{
	while ( true )
	{
		level waittill( "connected", player );
		
		player.noDrowning = true;
		
		if ( isdefined( level.allow_teamchange ) && level.allow_teamchange == "0" )
			player.hasDoneCombat = true;
		
		if ( !isdefined( player.pers["objtime"] ) ) // prop alive time
			player.pers["objtime"] = 0;
	}
}
function hideHUDIntermission()
{		
	level waittill( "game_ended" );
	
	if ( prop::usePropHudServer() )
	{
		level.elim_hud.alpha = 0;
		if ( level.phSettings.propWhistleTime > 0 )
		{
			level.phWhistleTimer.alpha = 0;
			level.whistling.alpha = 0;
		}
	}
	
	foreach( player in level.players )
	{	
		player prop_controls::propAbilityKeysVisible( false );
	}
		
}

function onStartGameType()
{
	setClientNameMode( "manual_change" );

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

	if ( game["switchedsides"] )
	{
		oldAttackers = game["attackers"];
		oldDefenders = game["defenders"];
		game["attackers"] = oldDefenders;
		game["defenders"] = oldAttackers;
	}
	
	level.displayRoundEndText = false;
	
	// now that the game objects have been deleted place the influencers
	spawning::create_map_placed_influencers();
	
	level.spawnMins = ( 0, 0, 0 );
	level.spawnMaxs = ( 0, 0, 0 );
	
	util::setObjectiveText( game["attackers"], &"OBJECTIVES_PH_ATTACKER" );
	util::setObjectiveText( game["defenders"], &"OBJECTIVES_PH_DEFENDER" );
	util::setObjectiveHintText( game["attackers"], &"OBJECTIVES_PH_ATTACKER_HINT" );
	util::setObjectiveHintText( game["defenders"], &"OBJECTIVES_PH_DEFENDER_HINT" );

	if ( level.splitscreen )
	{
		util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_PH_ATTACKER" );
		util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_PH_DEFENDER" );
	}
	else
	{
		util::setObjectiveScoreText( game["attackers"], &"OBJECTIVES_PH_ATTACKER_SCORE" );
		util::setObjectiveScoreText( game["defenders"], &"OBJECTIVES_PH_DEFENDER_SCORE" );
	}

	foreach( team in level.teams )
	{		
		spawnlogic::add_spawn_points( team, "mp_tdm_spawn" );
		spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) );
	}
		
	spawning::updateAllSpawnPoints();
	
	
	//Swap spawns so props start on the other side of the map after halftime (aka if 2 or 3 rounds have been played)
	swapSpawnSides = (game["roundsplayed"]%4 == 2) || (game["roundsplayed"]%4 == 3);
	if ( swapSpawnSides )
		game["switchedsides"] = !game["switchedsides"];
	
	level.spawn_start = [];
	
	foreach( team in level.teams )
	{
		level.spawn_start[ team ] =  spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) );
	}
	
	if ( swapSpawnSides )
		game["switchedsides"] = !game["switchedsides"];

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

	spawnpoint = spawnlogic::get_random_intermission_point();
	setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles );
	
	
	//removed action loop -CDC
	level thread onScoreCloseMusic();

	if ( !util::isOneRound() )
	{
		level.displayRoundEndText = true;
		if( level.scoreRoundWinBased )
		{
			globallogic_score::resetTeamScores();
		}
	}
	
	if( ( isdefined( level.droppedTagRespawn ) && level.droppedTagRespawn ) )
		level.numLives = 1;
	
	level._effect[ "propFlash" ]	= "explosions/fx_exp_grenade_concussion";
	level._effect[ "propDeathFX" ] 	= "explosions/fx_prop_exp";
	
	if ( !IsDefined( game["propScore"] ) )
	{
		game["propScore"] = [];
		game["propScore"]["allies"] = 0;
		game["propScore"]["axis"] = 0;
	}
	
	if ( !IsDefined( game["propSurvivalTime"] ) )
	{
		game["propSurvivalTime"] = [];
		game["propSurvivalTime"]["allies"] = 0;
		game["propSurvivalTime"]["axis"] = 0;
	}
	
	if ( !IsDefined( game["hunterKillTime"] ) )
	{
		game["hunterKillTime"] = [];
		game["hunterKillTime"]["allies"] = 0;
		game["hunterKillTime"]["axis"] = 0;
	}
	
	level flag::init( "props_hide_over", false );

	level thread setupRoundStartHUD();
	if ( level.phSettings.propWhistleTime > 0 )
		level thread propWhistle();
	level thread hideHUDIntermission();
	level thread monitorTimers();
	level thread setPHTeamScores();
	level thread stillAliveXP();
	level thread deleteLevelTurrets();
	level thread trackTimeAlive();
}

function givePrimaryWeapon( weapon )
{
	self.primaryWeapon = weapon;
	self GiveWeapon( weapon );
	self switchToWeapon( weapon );
	self setSpawnWeapon( weapon );
	self.spawnWeapon = weapon;
	self SetBlockWeaponPickup( weapon, true );
}

function giveSecondayWeapon( weapon )
{
	self.secondaryWeapon = weapon;
	self GiveWeapon( weapon );
	self SetBlockWeaponPickup( weapon, true );
}

function giveOffhandPrimary( primaryOffhand, primaryOffhandCount )
{
	self GiveWeapon( primaryOffhand );
	self SetWeaponAmmoStock( primaryOffhand, primaryOffhandCount );
	self SwitchToOffhand( primaryOffhand );
	self.grenadeTypePrimary = primaryOffhand;
	self.grenadeTypePrimaryCount = primaryOffhandCount;
}

function giveOffhandSecondary( secondaryOffhand, secondaryOffhandCount )
{
	self GiveWeapon( secondaryOffhand );
	self SetWeaponAmmoClip( secondaryOffhand, secondaryOffhandCount );
	self SwitchToOffhand( secondaryOffhand );
	self.grenadeTypeSecondary = secondaryOffhand;
	self.grenadeTypeSecondaryCount = secondaryOffhandCount;
}

function givePerk( perkName )
{
	if ( !self HasPerk( perkName ) )
	{
		self SetPerk( perkName );
	}
}

function giveCustomLoadout()
{
	loadout::giveLoadout_init( true );	
	loadout::setClassNum( self.curClass );
	self ClearPerks();
	
	weapon = undefined;
	if ( self util::isProp() )
	{
		weapon = GetWeapon( "pistol_standard" );
		self givePrimaryWeapon( weapon );
		self giveOffhandPrimary( GetWeapon( "null_offhand_primary" ), 0 );
		self giveOffhandSecondary( GetWeapon( "null_offhand_secondary" ), 0 );
		
		self givePerk( "specialty_quieter" );
		self givePerk( "specialty_fastladderclimb" );
		self givePerk( "specialty_fastmantle" );
		self givePerk( "specialty_jetcharger" );
	}
	else
	{
		weapon = GetWeapon( "pistol_standard" );
		self giveSecondayWeapon( weapon );
		
		attachments = Array();
		attachments[attachments.size] = "extclip";
		attachments[attachments.size] = "steadyaim";
		weapon = GetWeapon( "smg_standard", attachments );
		self givePrimaryWeapon( weapon );
		
		if ( !propMiniGameActive() )
			self giveOffhandPrimary( GetWeapon( "concussion_grenade" ), 2 );
		else
			self giveOffhandPrimary( GetWeapon( "null_offhand_primary" ), 0 );
		
		self giveOffhandSecondary( GetWeapon( "null_offhand_secondary" ), 0 );
	    self attackerInitAmmo();
		
		self givePerk( "specialty_twogrenades" );
		self givePerk( "specialty_fastladderclimb" );
		self givePerk( "specialty_fastmantle" );
		self givePerk( "specialty_longersprint" );
		self givePerk( "specialty_sprintfire" );
		self givePerk( "specialty_jetcharger" );
	}
	
	self GiveWeapon( level.weaponBaseMelee );
	
	self notify( "applyLoadout" );
	return weapon;
}

function is_player_gamepad_enabled()
{
	return self GamepadUsedLast();
}

function whistleStartTimer( duration )
{
	level notify( "whistle_start_timer_beginning" );

	countTime = int( duration );

	if ( countTime >= 0 )
	{
		thread whistleStartTimer_Internal( countTime );
	}
}

function whistleStartTimer_Internal( countTime )
{
	level endon( "whistle_start_timer_beginning" ); // stop any existing whistle timer threads

	waittillframeend; // ensure we don't start in the middle of a frame

	while( ( countTime > 0 ) && !level.gameEnded )
	{
		countTime--;
			
		wait( 1 );
	}
}

function usePropHudServer()
{
	/#
	if ( GetDvarInt( "scr_ph_usePropHudServer", 0 ) != 0 )
		return true;
	#/
	
	return true;
}

function setupRoundStartHUD()
{
	//Initial Countdown Timer
	level.phCountdownTimer = hud::createServerTimer( "default", 1.5 );
	level.phCountdownTimer hud::setPoint( "CENTER", undefined, 0, 50 );
	level.phCountdownTimer.label = &"MP_PH_STARTS_IN";
	level.phCountdownTimer.alpha = 0;
	level.phCountdownTimer.archived = false;
	level.phCountdownTimer.hideWhenInMenu = true;
	level.phCountdownTimer.sort = 1;
	
	if ( usePropHudServer() )
	{
		yUpperLeft = 110;
		ySpace = 20;
		if ( !level.console )
		{
			yUpperLeft = 125;
			ySpace = 15;
		}
		//Props Alive Counter
		level.elim_hud = hud::createServerFontString( "default", 1.5 );
		level.elim_hud.label = &"MP_PH_ALIVE";
		level.elim_hud SetValue( 0 );
		level.elim_hud.x = 5;
		level.elim_hud.y = yUpperLeft;
		level.elim_hud.alignX = "left";
		level.elim_hud.alignY = "top";
		level.elim_hud.horzAlign = "left";
		level.elim_hud.vertAlign = "top";
		level.elim_hud.archived = true;
		level.elim_hud.alpha = 0;
		level.elim_hud.glowAlpha = 0;
		level.elim_hud.hidewheninmenu = false;
		level thread eliminatedHUDMonitor();
		
		if ( level.phSettings.propWhistleTime > 0 )
		{
			//Whistle Timer
			level.phWhistleTimer = hud::createServerTimer( "default", 1.5 );
			level.phWhistleTimer.x = 5;
			level.phWhistleTimer.y = yUpperLeft + ySpace;
			level.phWhistleTimer.alignX = "left";
			level.phWhistleTimer.alignY = "top";
			level.phWhistleTimer.horzAlign = "left";
			level.phWhistleTimer.vertAlign = "top";
			level.phWhistleTimer.label = &"MP_PH_WHISTLE_IN";
			level.phWhistleTimer.alpha = 0;
			level.phWhistleTimer.archived = true;
			level.phWhistleTimer.hideWhenInMenu = false;
			level.phWhistleTimer setTimer( 120 );
		
			//Whistling
			level.whistling = hud::createServerFontString( "default", 1.5 );
			level.whistling.label = &"MP_PH_WHISTLING";
			level.whistling.x = 5;
			level.whistling.y = yUpperLeft + ySpace;
			level.whistling.alignX = "left";
			level.whistling.alignY = "top";
			level.whistling.horzAlign = "left";
			level.whistling.vertAlign = "top";
			level.whistling.archived = true;
			level.whistling.alpha = 0;
			level.whistling.glowAlpha = 0.2;
			level.whistling.hidewheninmenu = false;
		}
	}
}

function eliminatedHUDMonitor()
{
	level endon ( "game_ended" );
	
	while(1)
	{
		props = get_alive_nonspecating_players( game["defenders"] );
		level.elim_hud SetValue( props.size );
		level util::waittill_any( "player_spawned", "player_killed", "player_eliminated", "playerCountChanged", "propCountChanged", "playerDisconnected" );
	}
}

function get_alive_nonspecating_players( team )
{
	alive_nonspec_players = [];
	
	foreach ( player in level.players ) 
	{
		if ( IsDefined( player ) && isalive( player ) && (!IsDefined( player.sessionstate ) || player.sessionstate == "playing" ) )
		{
			if ( !IsDefined( team ) || player.team == team )
		    	alive_nonspec_players[alive_nonspec_players.size] = player;
		}
	}
		    
	return alive_nonspec_players;
}

function onPlayerDisconnect()
{
	level notify( "playerDisconnected" );
	
	if ( propMiniGameActive() )
		thread propMiniGameUpdateScoreboard( 0.05 );
}

function waittillPrematchDone()
{
	while ( !( isdefined( level.prematch_over ) && level.prematch_over ) )
		{wait(.05);};
}

function onSpawnPlayer(predictedSpawn)
{
	self.breathingStopTime = 0;
	
	if ( self util::isProp() )
	{	
		self.pingsLeft = undefined;
		
		if ( !isdefined( self.abilityLeft ) )
			self.abilityLeft = 0;
		
		if ( !isdefined( self.clonesLeft ) )
			self.clonesLeft = 0;
		
		//remember ability selection between rounds
		if( !isDefined( self.pers["ability"] ) )
			self.pers["ability"] = 0;
		
		self.currentAbility = level.abilities[ self.pers["ability"] ];

		if ( usePropHudServer() )
			self thread prop_controls::propControlsHUD();
		
		self.isAngleOffset = false;
		
		changeCount = Int( level.phSettings.propChangeCount );
		abilityCount = undefined;
		cloneCount = undefined;
		if ( isdefined( self.spawnedOnce ) && isdefined( self.changesLeft ) )
		{
			changeCount = self.changesLeft;
			abilityCount = self.abilityLeft;
			cloneCount = self.clonesLeft;
		}
			
		self prop_controls::propSetChangesLeft( changeCount );
		self prop_controls::setNewAbilityCount( self.currentAbility, abilityCount );
		self prop_controls::setNewAbilityCount( "CLONE", cloneCount );

		self thread prop_controls::cleanupPropControlsHUDOnDeath();
		self thread handleProp();
	}
	else
	{	
		self.abilityLeft = undefined;
		self.clonesLeft = undefined;
		
		if ( !isdefined( self.pingsLeft ) )
			self.pingsLeft = 0;
		
		if ( !IsDefined( self.thrownSpecialCount ) )
			self.thrownSpecialCount = 0;
		
		if ( usePropHudServer() )
			self thread prop_controls::hunterControlsHUD();
		
		pingsLeft = level.phSettings.hunterNumPings;
		if ( isdefined( self.spawnedOnce ) && isdefined( self.pingsLeft ) )
		{
			pingsLeft = self.pingsLeft;
		}
		
		self prop_controls::hunterSetPingsLeft( pingsLeft );
		
		self thread prop_controls::cleanupHunterControlsHUDOnDeath();
	    self thread handleNonProp();
	}
	
	self thread attackersWaitTime();
	
	if ( level.useStartSpawns && !level.inGracePeriod && !level.playerQueuedRespawn )
	{
		level.useStartSpawns = false;
	}
	
	self.spawnedOnce = true;

	spawning::onSpawnPlayer( predictedSpawn );
}

function monitorTimers()
{
	level endon( "game_ended" );
	
	waittillPrematchDone();
	
	level.allow_teamchange = "0";
	foreach ( player in level.players )
		player.hasDoneCombat = true;
		
	level thread pausePHTimerForMigration();
	
	if ( level.phSettings.propHideTime > 0 )
	{
		level.phCountdownTimer setTimer( level.phSettings.propHideTime );
		level.phCountdownTimer.alpha = 1;
	}

	if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 )
	{
		level.phWhistleTimer setTimer( level.phsettings.propWhistleTime + level.phSettings.propHideTime );
	}
	
	if ( level.phSettings.propHideTime > 0 || level.phSettings.propWhistleTime > 0 )
		whistleStartTimer( level.phsettings.propWhistleTime + level.phSettings.propHideTime );
	
	if ( level.phSettings.propHideTime > 0 )
		waittillCountdownComplete( level.phSettings.propHideTime );
	
	level flag::set( "props_hide_over" );
	
	if ( usePropHudServer() )
	{
		if ( level.phSettings.propWhistleTime > 0 )
			level.phWhistleTimer.alpha = 1;
		level.elim_hud.alpha = 1;
	}
	
	level.phCountdownTimer.alpha = 0;
}

function pausePHTimerForMigration()
{
	level endon( "game_ended" );
	level endon( "props_hide_over" );
	
	countdownEndTime = int( ( level.phSettings.propHideTime ) + ( GetTime() / 1000 ) );
	totalTimePassed = 0;
	
	while( 1 )
	{
		level waittill( "host_migration_begin" );
	
		level.phCountdownTimer.alpha = 0;
		if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 )
			level.phWhistleTimer.alpha = 0;
		timePassed = int( hostmigration::waitTillHostMigrationDone() / 1000 );
		totalTimePassed += timePassed;
		timePassed = totalTimePassed;

		intendedEnd = countdownEndTime + timePassed - int( gettime() / 1000 );
		level.phCountdownTimer setTimer( intendedEnd );
		if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 )
			level.phWhistleTimer setTimer( level.phsettings.propWhistleTime + intendedEnd );
		whistleStartTimer( level.phsettings.propWhistleTime + intendedEnd );
		
		level.phCountdownTimer.alpha = 1;
		if ( usePropHudServer() && level.phSettings.propWhistleTime > 0 )
			level.phWhistleTimer.alpha = 1;
	}
}

function handleProp()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "death" );
	
	self waittill( "applyLoadout" );
	
	self AllowProne( false );
	self AllowCrouch( false );
	self AllowSprint( false );
	self AllowSlide( false );
	self SetMoveSpeedScale( level.phSettings.propSpeedScale );
	self PlayerKnockback( false );
	self TakeAllWeapons();
	self AllowSpectateTeam( game["attackers"], true );
	self Ghost();
	self SetClientUIVisibilityFlag( "weapon_hud_visible", 0 );
	
	self.healthRegenDisabled = true;
	self.concussionImmune = undefined;
	
	Assert( !IsDefined( self.prop ) );
	self thread setupProp();
	self thread prop_controls::setupKeyBindings();
	self thread setupDamage();
	self thread prop_controls::propInputWatch();
	self thread propWatchDeath();
	self thread propWatchCleanupOnDisconnect();
	self thread propWatchCleanupOnRoundEnd();
	self thread propWatchPreMatchSettings();
}

function getThirdPersonRangeForSize( propSize )
{
	switch ( propSize )
	{
		case 50:
			return 120;
		case 100:
			return 150;
		case 250:
			return 180;
		case 450:
			return 260;
		case 550:
			return 320;
		default:
			AssertMsg( "Unknown size: " + propSize );
			break;
	}
	
	return 120;
}

function getThirdPersonHeightOffsetForSize( propSize )
{
	switch ( propSize )
	{
		case 50:
			return -30;
		case 100:
			return -20;
		case 250:
			return 0;
		case 450:
			return 20;
		case 550:
			return 40;
		default:
			AssertMsg( "Unknown size: " + propSize );
			break;
	}
	
	return 0;
}

function applyXYZOffset()
{
	if( !isDefined( self.prop.xyzOffset ) )
		return;
	
	self.prop.angles = self.angles;
	
	forward = AnglesToForward( self.prop.angles ) * self.prop.xyzOffset[0];
	right = AnglesToRight( self.prop.angles ) * self.prop.xyzOffset[1];
	up = AnglesToUp( self.prop.angles ) * self.prop.xyzOffset[2];

	self.prop.origin += forward;
	self.prop.origin += right;
	self.prop.origin += up;
}

function applyAnglesOffset()
{
	if( !isDefined( self.prop.anglesOffset ) )
		return;
	
	self.prop.angles = self.angles;
	self.prop.angles += self.prop.anglesOffset;
	self.isAngleOffset = true;
}

function propWhistle()
{
	level endon( "game_ended" );
	
	waittillPrematchDone();
	
	time = GetTime();
	whistleTime = level.phSettings.propWhistleTime*1000;
	whistleTimeMinTime = 20000;
	whistleTimeFinal = whistleTimeMinTime;
	whistleTimeDifference = 500; // start the whistling a bit earlier so that it matches with how we want to show the UI
	//time in miliseconds when the game will not allow whistles
	endGameGracePeriod = 5000;
	
	whistleCount = 0;
	whistleSortOrigin = ( 0, 0, 0 );
	minimapCorners = getentarray("minimap_corner", "targetname");
	if ( minimapCorners.size > 0 )
		whistleSortOrigin = minimapCorners[0].origin;
	
	hostmigration::waitLongDurationWithHostMigrationPause( level.phSettings.propHideTime+level.phSettings.propWhistleTime );
	
	while( 1 )
	{
		if( ( time + whistleTime - whistleTimeDifference ) < GetTime() )
		{
			whistleCount++;
			
			if ( usePropHudServer() )
			{
				level.phWhistleTimer.alpha = 0;
				level.whistling.alpha = 1;
				level.whistling fadeOverTime( 0.75 );
				level.whistling.alpha = .6;
			}
			
			sortedPlayers = ArraySortClosest(level.players, whistleSortOrigin);
			foreach( player in sortedPlayers )
			{
				if( !IsDefined( player ) )
				   continue;
				   
				
				if ( player util::isProp() && isAlive( player ) )
				{
					PlaySoundAtPosition( "mpl_phunt_char_whistle", player.origin + ( 0, 0, 60 ) );
					//due to this wait each player takes 1.5 seconds to whistle
					hostmigration::waitLongDurationWithHostMigrationPause( 1.5 );
					
				}
			}
			time = GetTime();
			
			if( whistleCount % 2 == 0 )
				whistleTime = max( whistleTime - 5000, whistleTimeMinTime );
			
			if( ( WhistleTimeFinal ) >= ( globallogic_utils::getTimeRemaining() - endGameGracePeriod ) )
			{
				if ( usePropHudServer() )
					level.whistling.alpha = 0;
				return;
			}
			else
			{
				if( ( WhistleTimeFinal * 2 ) + ( getTeamPlayersAlive( game["defenders"] ) * 2500 ) >= ( globallogic_utils::getTimeRemaining() - endGameGracePeriod ) )
				{
					if ( usePropHudServer() )
						level.phWhistleTimer.label = &"MP_PH_FINAL_WHISTLE";
					whistleTimeFinal += getTeamPlayersAlive( game["defenders"] ) * 2500;
				}
				
				if ( usePropHudServer() )
					level.phWhistleTimer setTimer( int( whistleTime/1000 ) );
				whistleStartTimer( int( whistleTime/1000 ) );
				
				if ( usePropHudServer() )
				{
					level.whistling.alpha = 0;
					level.phWhistleTimer.alpha = 1;
				}
			}
		}
		hostmigration::waitLongDurationWithHostMigrationPause( 0.5 );
	}
}

function getLivingPlayersOnTeam( team )
{
	players = [];
	foreach( player in level.players )
	{
		if ( !IsDefined( player.team ) )
			continue;
		
		if ( IsAlive(player) && player.team == team )
		{
			players[players.size] = player;
		}
	}
	
	return players;
}

function setupDamage()
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	
	hostmigration::waitLongDurationWithHostMigrationPause( 0.5 );
	
	self.prop.health = 99999;
	self.prop.maxhealth = 99999;
	self.prop thread entityDamageWatcher( &damageWatch );
}

function entityDamageWatcher( damageCallback )
{
	level endon( "game_ended" );
	self endon( "death" );
	
	while ( true )
	{
		self waittill ( "damage", damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags );
		
		self thread [[ damageCallback ]]( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags );
	}
}
		
function damageWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags )
{
	if ( !IsDefined( attacker ) )
		return;
	
	if ( !IsDefined( self.owner ) )
		return;
	
	if ( isPlayer( attacker ) )
	{
		if ( attacker.pers["team"] == self.owner.pers["team"] )
			return;
	
		attacker thread damagefeedback::update();
		
		if ( isdefined( weapon ) && weapon.rootWeapon.name == "concussion_grenade" && isdefined( meansOfDeath ) && meansOfDeath != "MOD_IMPACT" )
			prop_controls::registerConcussionEvent( attacker, undefined, meansOfDeath, damage, point, weapon );
	}
	
	self.owner DoDamage( damage, point, attacker, attacker, "none", meansOfDeath, iDFlags, weapon );
	
	self.health = 99999;
	self.maxhealth = 99999;
}

function propCleanup() // self == player
{
	array = Array( self.prop, self.propAnchor, self.propEnt );
	self thread propCleanupDelayed( array );
}

function propCleanupDelayed( propEnts )
{
	foreach( prop in propEnts )
	{
		if ( IsDefined( prop ) )
			prop Unlink();
	}
	
	{wait(.05);};
	
	foreach( prop in propEnts )
	{
		if ( IsDefined( prop ) )
			prop Delete();
	}
}

function propWatchDeath() // self == player
{
	level endon( "game_ended" );
	self endon("disconnect");
		
	self waittill( "death" );
	
	corpse = self.body;

	PlaySoundAtPosition( "wpn_flash_grenade_explode", self.prop.origin + ( 0, 0, 4 ) );
	PlayFX( fx::get( "propDeathFX" ), ( self.prop.origin + ( 0, 0, 4 ) ) );
	
	if ( IsDefined( corpse ) )
		corpse Delete();
	
	self propCleanup();
}

function propWatchCleanupOnDisconnect() // self == player
{
	self notify( "propWatchCleanupOnDisconnect" );
	self endon( "propWatchCleanupOnDisconnect" );
	level endon( "game_ended" );
	
	self waittill( "disconnect" );
	
	self propCleanup();
	self propCloneCleanup();
}

function propWatchCleanupOnRoundEnd() // self == player
{
	self notify( "propWatchDeleteRoundEnd" );
	self endon( "propWatchDeleteRoundEnd" );
	self endon( "disconnect" );
	
	level waittill( "round_end_done" );
	
	self propCleanup();
	self propCloneCleanup();
}

function propCloneCleanup()
{
	if ( IsDefined( self.propClones ) )
	{
		foreach( clone in self.propClones )
		{
			if ( isdefined( clone ) )
				clone Delete();
		}
	}
}

function propWatchPreMatchSettings()
{
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "joined_team" );
	self endon( "joined_spectators" );
	
	waittillPrematchDone();
	
	// Any other settings that can be overwritten by the prematch freeze controls
	self AllowProne( false );
	self AllowCrouch( false );
	self AllowSprint( false );
}

function organizePropList(inArray)
{
	return array::randomize(inArray);
}

function randGetPropSizeToAllocate()
{
	WEIGHT_XSMALL = 10 * IsDefined(level.propList[50]);
	WEIGHT_SMALL = 30 * IsDefined(level.propList[100]);
	WEIGHT_MEDIUM = 40 * IsDefined(level.propList[250]);
	WEIGHT_LARGE = 20 * IsDefined(level.propList[450]);
	WEIGHT_XLARGE = 10 * IsDefined(level.propList[550]);
	
	randomRange = WEIGHT_XSMALL + WEIGHT_SMALL + WEIGHT_MEDIUM + WEIGHT_LARGE + WEIGHT_XLARGE;
	
	randomVal = RandomInt(randomRange);
	
	if ( randomVal < WEIGHT_XSMALL ) 
		return 50;
	randomVal -= WEIGHT_XSMALL;
	
	if ( randomVal < WEIGHT_SMALL )
		return 100;
	randomVal -= WEIGHT_SMALL;
	
	if ( randomVal < WEIGHT_MEDIUM )
		return 250;
	randomVal -= WEIGHT_MEDIUM;
	
	if ( randomVal < WEIGHT_LARGE )
		return 450;
	randomVal -= WEIGHT_LARGE;

	return 550;
}

function getNextProp(inPlayer)
{
	firstChoicePropSize = randGetPropSizeToAllocate();
	allPropSizes = GetArrayKeys(level.propList);
	allPropSizes = array::randomize(allPropSizes);
	
	propSizes = Array( firstChoicePropSize );
	foreach(size in allPropSizes)
	{
		if( size != firstChoicePropSize )
			propSizes[propSizes.size] = size;
	}
	
	prop = undefined;
	for(i=0; i<propSizes.size; i++)
	{
		size = propSizes[i];
		
		if( !IsDefined(level.propList[size]) || !level.propList[size].size )
			continue;
		
		randomPropsOfSize = array::randomize(level.propList[size]);
		for(j=0; j<randomPropsOfSize.size; j++)
		{
			prop = randomPropsOfSize[j];
			
			propUsed = false;
			if ( isDefined(inPlayer.usedProps) && inPlayer.usedProps.size )
			{
				for ( index = 0; index < inPlayer.usedProps.size; index++ ) 
				{
					if ( prop.modelName == inPlayer.usedProps[index].modelName )
					{
						propUsed = true;
						break;
					}	
				}
			}
			
			if( !propUsed )
				return prop;
		}
	}
	
	return prop; //This case shouldn't be hit but here just in case
}

function getMapName()
{
	return level.script;
}

function TableLookupByRow( propTablePath, rowIndex, columnIndex )
{
	columns = TableLookupRow( propTablePath, rowIndex );
	
	if ( columnIndex < columns.size )
		return columns[columnIndex];
	
	return "";
}

function populatePropList()
{
	mapName = getMapName();

	propTablePath = "gamedata/tables/mp/" + mapName + "_ph.csv";
	
	numRows = TableLookupRowCount( propTablePath );
	
	for( rowIndex = 0; rowIndex < numRows; rowIndex++)
	{
		modelName	 = TableLookupByRow( propTablePath, rowIndex, 0 );
		propSizeText = TableLookupByRow( propTablePath, rowIndex, 1 );
		
		propScale = Float( TableLookupByRow( propTablePath, rowIndex, 2 ) );
		
		offsetX = Int( TableLookupByRow( propTablePath, rowIndex, 3 ) );
		offsetY = Int( TableLookupByRow( propTablePath, rowIndex, 4 ) );
		offsetZ = Int( TableLookupByRow( propTablePath, rowIndex, 5 ) );
		
		rotationX = Int( TableLookupByRow( propTablePath, rowIndex, 6 ) );
		rotationY = Int( TableLookupByRow( propTablePath, rowIndex, 7 ) );
		rotationZ = Int( TableLookupByRow( propTablePath, rowIndex, 8 ) );
		
		propHeight = TableLookupByRow( propTablePath, rowIndex, 9 );
		propRange = TableLookupByRow( propTablePath, rowIndex, 10 );

		offset = undefined;
		if ( IsDefined( offsetX ) && IsDefined( offsetY ) && IsDefined( offsetZ ) )
		{
			offset = ( offsetX, offsetY, offsetZ );
		}
		
		rotation = undefined;
		if ( IsDefined( rotationX ) && IsDefined( rotationY ) && IsDefined( rotationZ ) )
		{
			rotation = ( rotationX, rotationY, rotationZ );
		}
		
		if ( !isdefined( propScale ) || propScale == 0 )
			propScale = 1.0;
		
		propSize = getPropSize( propSizeText );
		
		if ( !isdefined( propHeight ) || propHeight == "" )
			propHeight = getThirdPersonHeightOffsetForSize( propSize );
		else
			propHeight = Int( propHeight );
		
		if ( !isdefined( propRange ) || propRange == "" )
			propRange = getThirdPersonRangeForSize( propSize );
		else
			propRange = Int( propRange );
		
		addPropToList( modelName, propSize, offset, rotation, propSizeText, propScale, propHeight, propRange );
	}
	
	if ( numrows == 0 ) // props not setup
		addPropToList( "tag_origin", 250, ( 0, 0, 0 ), ( 0, 0, 0 ), "medium", 1.0, getThirdPersonHeightOffsetForSize( 250 ), getThirdPersonRangeForSize( 250 ) );
	
	level.propList = organizePropList(level.propList);
}

function setupProp()
{
	self NotSolid();
	if ( !isdefined( level.phSettings.playerContents ) || level.phSettings.playerContents == 0 )
		level.phSettings.playerContents = self SetContents( 0 );
	else
		self SetContents( 0 );
	
	self SetPlayerCollision( false );
	
	propInfo = self.propInfo;
	if ( !isdefined( self.propInfo ) )
		propInfo = getNextProp( self );
			
	//Used so the propEnt can link to the player, and change angles, without effecting players 3rd person camera.
	self.propAnchor = Spawn( "script_model", self.origin );
	self.propAnchor.targetname = "propAnchor";
	self.propAnchor LinkTo( self );
	self.propAnchor SetContents( 0 );
	self.propAnchor NotSolid();
	self.propAnchor SetPlayerCollision( false );

	//Used so that the prop can set an xyz and angle offset, allowing for more prop varity, and not have to worry about angles after it is set. propEnt is the prop after setting prop offset and attaching it to propEnt.
	self.propEnt = Spawn( "script_model", self.origin );
	self.propEnt.targetname = "propEnt";
	self.propEnt LinkTo(self.propAnchor);
	self.propEnt SetContents( 0 );
	self.propEnt NotSolid();
	self.propEnt SetPlayerCollision( false );

	//this is the actual prop Model and what the player hits when they shoot
	self.prop = Spawn("script_model", self.propEnt.origin);
	self.prop.targetname = "prop";
	self.prop setModel( propInfo.modelName );
	self.prop SetScale( propInfo.propScale, true );
	self.prop SetCanDamage( true );
	self.prop SetOwner( self );
	self.prop SetTeam( self.team );
	self.prop.xyzOffset = propInfo.xyzOffset;
	self.prop.anglesOffset = propInfo.anglesOffset;
	self applyXYZOffset();
	self applyAnglesOffset();
	self.prop LinkTo( self.propEnt );
	self.prop.owner	 = self;
	self.prop.health = 10000;
	self.prop SetPlayerCollision( false );
	self.prop NotSolidCapsule();
	self.prop clientfield::set( "enemyequip", 1 );
	
	if ( propMiniGameActive() )
		self thread propMiniGameSetPropVisibility( false );
	
	self.thirdPersonRange = propInfo.propRange;
	self.thirdPersonHeightOffset = propInfo.propHeight;
	self SetClientThirdPerson( true, self.thirdPersonRange, self.thirdPersonHeightOffset );
	
	self.prop.info = propInfo;
	self.propInfo = propInfo;
	if ( !isdefined( self.spawnedOnce ) )
		self.usedProps = [];
	
	self.health = getPropHealth( propInfo );
	self.maxhealth = self.health;
}

function getPropHealth( propInfo )
{
	return Int( propInfo.propSize );
}

function getPropSize( propSizeText )
{
	/#
	if ( propSizeText == "remove" )
		return 0;	
	#/
		
	propSize = 0;
	switch( propSizeText )
	{
		case "xsmall":
			propSize = 50;
			break;
		case "small":
			propSize = 100;
			break;
		case "medium":
			propSize = 250;
			break;
		case "large":
			propSize = 450;
			break;
		case "xlarge":
			propSize = 550;
			break;
		default:
			mapName = getMapName();
			propTablePath = "gamedata/tables/mp/" + mapName + "_ph.csv";
			AssertMsg( "Invalid prop size '" + propSizeText + "' used in table '" + propTablePath + "'!" );
			propSize = 100;
			break;
	}
	
	return propSize;
}

function addPropToList( modelName, propSize, xyzOffset, anglesOffset, propSizeText, propScale, propHeight, propRange )
{
	if( !IsDefined( level.propList ) )
		level.propList = [];
	if( !IsDefined( level.propIndex ) )
		level.propIndex = [];
	if (!isDefined(level.propList[propSize]))
		level.propList[propSize] = [];
		
	propInfo = SpawnStruct();
	propInfo.modelName = modelName;
	propInfo.propScale = propScale;
	propInfo.propSize = int(propSize);
	propInfo.propSizeText = propSizeText;
	
	//forward, right, up
	if( isDefined( xyzOffset ) )
		propInfo.xyzOffset = xyzOffset;
	
	//pitch, yaw, roll
	if( isDefined( anglesOffset ) )
		propInfo.anglesOffset = anglesOffset;
	
	propInfo.propRange = propRange;
	propInfo.propHeight = propHeight;
		
	index = level.propIndex.size;
	level.propIndex[index] = [];
	level.propIndex[index][0] = propSize;
	level.propIndex[index][1] = level.propList[propSize].size;
	level.propList[propSize][level.propList[propSize].size] = propInfo;
}

function ph_endGame( winningTeam, endReasonText )
{
	if( ( isdefined( level.endingPH ) && level.endingPH ) )
		return;
	
	level.endingPH = true;
	ph_setFinalKillCamWinner( winningTeam );
	thread globallogic::endGame( winningTeam, endReasonText );
	level thread givePHTeamScore( winningTeam );
}

function ph_setFinalKillCamWinner( winningTeam )
{
	level.finalKillCam_winner = winningTeam;
	if( level.finalKillCam_winner == game["defenders"] )
		level.skipKillcamSlowdown = true;
}

function givePHTeamScore( team )
{
	level endon( "game_ended" );
	
	intendedScore = 0;
	if ( IsDefined( game["roundswon"] ) )
	{
		intendedScore = game["roundswon"][team];
	}
	
	setTeamScore( team, intendedScore );
}

function setPHTeamScores()
{
	level endon( "game_ended" );
	
	intendedscoreattacker = 0;
	intendedscoredefender = 0;
	if ( IsDefined( game["roundswon"] ) )
	{
		intendedScoreDefender = game["roundswon"][game["defenders"]];
		intendedScoreAttacker = game["roundswon"][game["attackers"]];
	}
	
	setTeamScore( game["defenders"], intendedScoreDefender );
	setTeamScore( game["attackers"], intendedScoreAttacker );
}

function onOneLeftEvent( team )
{
	if( ( isdefined( level.gameEnded ) && level.gameEnded ) )
		return;
	
	if( team == game["attackers"] )
		return;
	
	lastPlayer = undefined;

	foreach ( player in level.players )
	{
		if ( IsDefined( team ) && player.team != team )
			continue;

		if ( !isAlive( player ) && !player globallogic_spawn::maySpawn() )
			continue;
		
		if ( isDefined( lastPlayer ) )
			return;
		
		lastPlayer = player;				
	}

	if ( !IsDefined( lastPlayer ) )
		return;
	
	lastPlayer thread giveLastOnTeamWarning();
}

function waitTillRecoveredHealth( time, interval )
{
	self endon("death");
	self endon("disconnect");

	fullHealthTime = 0;
	
	if( !IsDefined( interval ) )
		interval = .05;

	if( !IsDefined( time ) )
		time = 0;
	
	while(1)
	{
		if ( self.health != self.maxhealth )
			fullHealthTime = 0;
		else
			fullHealthTime += interval;
		
		wait interval;
		
		if ( self.health == self.maxhealth && fullHealthTime >= time )
			break;
	}

	return;
}

function giveLastOnTeamWarning()
{
	self endon("death");
	self endon("disconnect");
	level endon( "game_ended" );
		
	self waitTillRecoveredHealth( 3 );
	
	level thread playerCardSplash( &"SCORE_LAST_ALIVE", self );
	
	if ( self util::isProp() )
	{
		level notify ( "noPropsToSpectate" );
		level.noPropsSpectate = true;
	}
	
	level notify ( "last_alive", self );	
}

function playerCardSplash( calloutMessage, calloutPlayer )
{
	LUINotifyEvent( &"player_callout", 2, calloutMessage, calloutPlayer.entnum );
}

function onTimeLimit()
{	
	if ( !( isdefined( level.gameEnding ) && level.gameEnding ) )
	{
		storeTimePassed();

		chooseFinalKillCam();
		ph_endGame( game["defenders"], game["strings"]["time_limit_reached"] );
	}
}

function storeTimePassed()
{
	timeLimitInMilliseconds = ( globallogic_defaults::default_getTimeLimit() * 60 * 1000 );
	timePassed = globallogic_utils::getTimePassed();
		
	timePassedClamped = Int( min( timeLimitInMilliseconds, timePassed ) );
		
	game["propSurvivalTime"][ game["defenders"] ] += timePassedClamped;
	game["hunterKillTime"][ game["attackers"] ] += timePassedClamped;
}

function chooseFinalKillCam()
{
	livingProps = getLivingPlayersOnTeam( game["defenders"] );
	if ( livingProps.size < 1 )
	{
		return;
	}
		
	livingHunters = getLivingPlayersOnTeam( game["attackers"] );
	if ( livingHunters.size < 1 )
	{
		return;
	}
	
	killCamProp = chooseBestPropForKillcam( livingProps, livingHunters );
	
	if ( IsPlayer( killCamProp ) )
	{
		attackerNum = killCamProp getEntityNumber();
	}
	else
	{
		attackerNum = -1;
	}
 	
	victim = livingHunters[0];
	victim.deathtime = getTime() - 1000;
	
	weap = GetWeapon( "none" );
	killcam_entity_info = killcam::get_killcam_entity_info( killCamProp, killCamProp, weap );
	level thread killcam::record_settings( attackerNum, victim getEntityNumber(), weap, "MOD_UNKNOWN", victim.deathtime, 0, 0, killcam_entity_info, [], [], killCamProp );
}

function chooseBestPropForKillcam( livingProps, livingHunters )
{
	bestProp = undefined;
	shortestPathDistOverall = ( 1 << 30 );
	
	foreach ( prop in livingProps )
	{
		Assert( isAlive( prop ) );
		
		closestHunter = undefined;
		shortestPathDist = ( 1 << 30 );
		
		foreach ( hunter in livingHunters )
		{
			pathDist = PathDistance( prop.origin, hunter.origin );
			if ( !isdefined( pathDist ) )
				pathDist = Distance( prop.origin, hunter.origin );
			
			if ( pathDist < shortestPathDist )
			{
				shortestPathDist = pathDist;
				closestHunter = hunter;
			}
		}
		
		if ( shortestPathDist < shortestPathDistOverall )
		{
			shortestPathDistOverall = shortestPathDist;
			bestProp = prop;
		}
	}
	
	if ( !IsDefined( bestProp ) )
	{
		bestProp = array::random( livingProps );
	}
	
	return bestProp;
}

function showPlayerToAll( setClientField )
{
	self Show();
	self notify( "showPlayer" );
	if ( setClientField )
		self clientfield::set( "hideTeamPlayer", 0 );
}

function hidePlayerFromTeam( team, setClientField )
{
	self Hide(); // turns off the rest: footstep/landing fx, gun tracers/whizbys, etc
	if ( setClientField )
		self thread hidePlayerClientfield( team );
	
	foreach ( player in level.players )
		self thread hidePlayerFromTeamOnPlayerSpawned( player, team );
	
	self thread hidePlayerFromTeamOnPlayerConnect( team );
}

function hidePlayerClientfield( team )
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "showPlayer" );
	
	{wait(.05);}; // need to give time for the player entity to be created before setting clientfields
	
	teamInt = 1;
	if ( team == "axis" )
		teamInt = 2;

	self clientfield::set( "hideTeamPlayer", teamInt ); // makes the player invisible
}

function hidePlayerFromTeamOnPlayerConnect( team )
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "showPlayer" );
	
	while ( true )
	{
		level waittill( "connected", player );
		
		self thread hidePlayerFromTeamOnPlayerSpawned( player, team );
	}
}

function hidePlayerFromTeamOnPlayerSpawned( player, team )
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "showPlayer" );
	player endon( "disconnect" );
		
	while ( true )
	{
		if ( ( isdefined( player.hasSpawned ) && player.hasSpawned ) && player.team != team )
		{
			self ShowToPlayer( player );
			
			// ShowToPlayer turns off the EF_NODRAW flag; turn it back on since props should never render the player body
			if ( self util::IsProp() )
				self Ghost();
		}
		
		player waittill( "spawned" );
	}
}

function handleNonProp()
{
	self.thirdPersonRange = undefined;
	self SetClientThirdPerson( false, 0 );
	self AllowProne( true );
	self AllowSprint( true );
	self SetMoveSpeedScale( 1 );
	self PlayerKnockback( true );
	self Show();
	self SetClientUIVisibilityFlag( "weapon_hud_visible", 1 );
	
	if ( propMiniGameActive() )
		self propMiniGameSetHunterVisibility( false );
	
	self thread prop_controls::setupHunterKeyBindings();
	self thread prop_controls::hunterInputWatch();
	
	self.concussionImmune = true;
	
	self.healthRegenDisabled = false;
	   	 
    self thread attackerRegenAmmo();
}

function stillAliveXP()
{
	level endon( "game_ended" );
	
	level.xpEventInfo["kill"]["value"] = 300;
	
	level waittill( "props_hide_over" );
	
	while(1)
	{	
		hostmigration::waitLongDurationWithHostMigrationPause( 10 );
		
		/#
		if ( GetGametypeSetting( "timelimit" ) == 0 )
			continue;
		#/
		
		foreach ( player in level.players )
		{	
			if ( !IsDefined(player.team) )
				continue;

			if ( player.team == game[ "attackers" ] )
				continue;
			
			if ( !isAlive( player ) )
				continue;
			
			if ( !isdefined( player.prop ) )
				continue;
			
			scoreevents::processScoreEvent( "still_alive", player );
			
			switch( player.prop.info.propSize )
			{
				case 250:
					{
						scoreevents::processScoreEvent( "still_alive_medium_bonus", player );
						break;
					}
				case 450:
					{
						scoreevents::processScoreEvent( "still_alive_large_bonus", player );
						break;
					}
				case 550:
					{
						scoreevents::processScoreEvent( "still_alive_extra_large_bonus", player );
						break;
					}
				default:
					break;						
			}
		}
	}
}

function trackTimeAlive()
{
	level endon( "game_ended" );
	
	waittillPrematchDone();
	
	while(1)
	{	
		foreach ( player in level.players )
		{	
			if ( !IsDefined(player.team) )
				continue;
						
			if ( player.team == game[ "attackers" ] )
				continue;
			
			if ( !IsAlive( player ) )
				continue;
			
			player.pers["objtime"]++; // prop alive time on scoreboard
			player.objtime = player.pers["objtime"];
			
			player AddPlayerStatWithGameType( "OBJECTIVE_TIME", 1 );
		}
		
		hostmigration::waitLongDurationWithHostMigrationPause( 1 );
	}
}

function gamemodeModifyPlayerDamage( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, sWeapon, vPoint, vDir, sHitLoc, psOffsetTime, boneIndex )
{
	victim = self;
	
	if ( IsDefined( eAttacker ) && IsPlayer( eAttacker ) && isAlive( eAttacker ) )
	{
		if( !isDefined( eAttacker.hasHitPlayer ) )
			eAttacker.hasHitPlayer = true;
	}

	return iDamage;
}

function playerAvoidDamageGameMode( iDFlags, sHitLoc, weapon, friendlyFire, attackerIsHittingSelf, sMeansOfDeath )
{
	if ( IsDefined( sMeansOfDeath ) && sMeansOfDeath == "MOD_FALLING" )
	{
		return true;
	}
	
	if ( self isHunter() )
	{
		if ( weapon.name == "concussion_grenade" )
		{
			return true;
		}
		
		if ( IsSubStr( weapon.name, "destructible" ) )
		{
			return true;
		}
	}
	
	return false;
}

function attackersWaitTime()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	waittillPrematchDone();
	
	if ( self.team == game[ "defenders" ]  )
	{
		self notify( "cancelCountdown" );
		return;
	}
	
	while ( !IsDefined( level.startTime ) )
		{wait(.05);};
	
	while ( IsDefined( self.controlsFrozen ) && self.controlsFrozen )
		{wait(.05);};
	
	gameSecondsElapsed = getGameSecondsElapsed();
	remainingTime = level.phSettings.propHideTime - gameSecondsElapsed;

	result = false;
	if ( remainingTime > 0 )
	{
		if ( !propMiniGameActive() )
			result = attackersWaitTimeNormal( gameSecondsElapsed, remainingTime );
		else
			result = attackersWaitTimeMinigame( gameSecondsElapsed, remainingTime );
	}
}

function attackersWaitTimeNormal( gameSecondsElapsed, remainingTime )
{
	self freezeControls( true );

	if ( Int( gameSecondsElapsed ) > 0 )
		fadeInTime = 0.0;
	else
		fadeInTime = 1.0;
	
	fadeOutTime = 1.0;
	if ( fadeInTime + fadeOutTime > remainingTime )
	{
		fadeInTime = 0.0;
		fadeOutTime = 0.0;
	}
	self thread prop_controls::countdownFadeToBlackForXSec( remainingTime, fadeInTime, fadeOutTime );

	result = self waittillCountdownComplete( remainingTime );
	
	self freezeControls( false );
	
	return result;
}

function attackersWaitTimeMinigame( gameSecondsElapsed, remainingTime )
{
	CONST_MINIGAME_TIME_TO_END_SEC = 3;
	CONST_MINIGAME_FADE_TIME = 1;
	
	fadeInTime = 0.0;
	result = false;
	
	timeToMinigame = remainingTime - CONST_MINIGAME_TIME_TO_END_SEC;
	self thread propWaitMiniGameInit( timeToMinigame + CONST_MINIGAME_FADE_TIME );
	
	waittillframeend; // make sure spawnPlayer() runs since it will unfreeze controls and make sure the player is not spectating before grabbing the origin
	self.phSpawnOrigin = undefined;
	self.phSpawnAngles = undefined;
	if ( remainingTime > 8 )
	{
		self.phSpawnOrigin = self.origin;
		self.phSpawnAngles = self.angles;
		
		result = self waittillCountdownComplete( timeToMinigame );
		
		fadeInTime = 1.0;
	}
	
	gameSecondsElapsed = getGameSecondsElapsed();
	remainingTime = level.phSettings.propHideTime - gameSecondsElapsed;

	if ( remainingTime > 0 )
	{
		self freezeControls( true );
	
		fadeOutTime = 1.0;
		if ( fadeInTime + fadeOutTime > remainingTime )
		{
			fadeInTime = 0.0;
			fadeOutTime = 0.0;
		}
		self thread prop_controls::countdownFadeToBlackForXSec( remainingTime, fadeInTime, fadeOutTime );
	
		result = self waittillCountdownComplete( remainingTime );
		
		self freezeControls( false );
	}
	
	return result;
}

function getGameSecondsElapsed()
{
	return ( ( GetTime() - level.startTime - level.totalHostMigrationTime ) / 1000.0 );
}

function monitorHostmigrationTime()
{
	level.totalHostMigrationTime = 0;
	
	while ( true )
	{
		level waittill( "host_migration_begin" );
		startTime = GetTime();
		level waittill( "host_migration_end" );
		passedTime = GetTime() - startTime;
		
		level.totalHostMigrationTime += passedTime;
	}	
}

function waittillCountdownComplete( remainingTime )
{
	result = waittillCountdownCompleteInternal( remainingTime );
	
	/#
	while ( GetDvarInt( "scr_ph_pauseCountdown", 0 ) != 0 )
		{wait(.05);};
	#/
		
	return result;
}

function waittillCountdownCompleteInternal( remainingTime )
{
	self endon( "cancelCountdown" );
	
	hostmigration::waitLongDurationWithHostMigrationPause( remainingTime );
	
	return true;
}

function prematchPeriodSpawn()
{
	if ( self.pers["team"] == game["attackers"] )
		self util::freeze_player_controls( true );
	else
		self thread propPrematchLock();

	team = self.pers["team"];
	
	if( isdefined( self.pers["music"].spawn ) && self.pers["music"].spawn == false )
	{
		
		if (level.wagerMatch)
		{
			music = "SPAWN_WAGER";
		}
		else 
		{
			music = game["music"]["spawn_" + team];
		}
		
		if( game[ "roundsplayed" ] == 0 )
		{
			self thread globallogic_spawn::sndDelayedMusicStart("spawnFull");
		}
		else
		{
			self thread globallogic_spawn::sndDelayedMusicStart("spawnShort");
		}
		
		self.pers["music"].spawn = true;
	}
	
	if ( level.splitscreen )
	{
		if ( isdefined( level.playedStartingMusic ) )
			music = undefined;
		else
			level.playedStartingMusic = true;
	}

	self thread globallogic_spawn::doInitialSpawnMessaging();
}

function propPrematchLock()
{
	self endon( "disconnect" );
	
	self FreezeControlsAllowLook( true );
	
	waittillPrematchDone();

	self FreezeControlsAllowLook( false );
}

function attackerInitAmmo()
{
	primaryWeapons = self GetWeaponsListPrimaries();
	foreach ( weapon in primaryWeapons )
	{
		self GiveMaxAmmo( weapon );
		self SetWeaponAmmoClip( weapon, 999 );
	}
	
	if ( !propMiniGameActive() )
	{
		if ( !IsDefined( self.thrownSpecialCount ) )
			self.thrownSpecialCount = 0;

		// Handle tactical grenades that may have already been used
		weapon = GetWeapon( "concussion_grenade" );
		tacticalCount = self GetWeaponAmmoStock( weapon );
		tacticalCount = tacticalCount - self.thrownSpecialCount;
		tacticalCount = Int( max( tacticalCount, 0 ) );
		self SetWeaponAmmoStock( weapon, tacticalCount );
		
		if ( tacticalCount > 0 )
			self thread prop_controls::watchSpecialGrenadeThrow();
	}
}

function attackerRegenAmmo()
{
	self endon( "death" );
	self endon( "disconnect" );
	
	self notify( "attackerRegenAmmo" );
	self endon( "attackerRegenAmmo" );

    level endon( "game_ended" );
    
    while ( true )
    {
     	self waittill( "reload" );
     	
   		primaryWeapon = self GetCurrentWeapon();
    	self GiveMaxAmmo( primaryWeapon );
    }
}

function checkKillRespawn()
{
	self endon( "disconnect" );
	level endon( "game_ended" );
	
	hostmigration::waitLongDurationWithHostMigrationPause( 0.1 );
	
	if( self.pers["lives"] == 1 )
	{
		self.pers["lives"]--;
		level.livesCount[self.team]--;
		
		globallogic::updateGameEvents();
		level notify( "propCountChanged" );
		return;
	}
}

function gameModeAssistedSuicide( attacker, sMeansOfDeath, weapon )
{
	bestPlayer = undefined;
	bestPlayerMeansOfDeath = undefined;
	bestPlayerWeapon = undefined;
	
	if ( !level flag::get( "props_hide_over" ) )
		return;

	if ( ( !isdefined( attacker ) || attacker.classname == "trigger_hurt" || attacker.classname == "worldspawn" || ( isdefined( attacker.isMagicBullet ) && attacker.isMagicBullet == true ) || attacker == self )  )
	{		
		for ( i = 0; i < self.attackers.size; i++ )
		{
			player = self.attackers[i];
			if ( !isdefined( player ) )
				continue;
			
			if ( !isdefined( self.attackerDamage[ player.clientId ] ) || ! isdefined( self.attackerDamage[ player.clientId ].damage ) )
				continue;
			
			if ( player == self || ( level.teamBased && player.team == self.team ) )
				continue;
			
			if ( self.attackerDamage[ player.clientId ].damage > 1 && !isdefined( bestPlayer ) )
			{
				bestPlayer = player;
				bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath;
				bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon;
			}
			else if ( isdefined( bestPlayer ) && self.attackerDamage[ player.clientId ].lasttimedamaged > self.attackerDamage[ bestPlayer.clientId ].lasttimedamaged )
			{
				bestPlayer = player;	
				bestPlayerMeansOfDeath = self.attackerDamage[ player.clientId ].meansOfDeath;
				bestPlayerWeapon = self.attackerDamage[ player.clientId ].weapon;
			}
		}
		
		if ( !isdefined( bestPlayer ) && self util::IsProp() )
		{
			bestDistSq = undefined;
			foreach ( player in level.players )
			{
				if ( IsAlive( player ) && player.team != self.team )
				{
					distSq = DistanceSquared( player.origin, self.origin );
					if ( !isdefined( bestDistSq ) || distSq < bestDistSq )
					{
						bestPlayer = player;
						bestDistSq = distSq;
					}
				}
			}
			
			if ( isdefined( bestPlayer ) )
			{
				bestPlayerMeansOfDeath = "MOD_MELEE";
				bestPlayerWeapon = 	GetWeapon( "none" );
			}
		}
	}
	
	result = undefined;
	if ( isdefined( bestPlayer ) )
	{
		result = [];
		result["bestPlayer"] = bestPlayer;
		result["bestPlayerWeapon"] = bestPlayerWeapon;
		result["bestMeansOfDeath"] = bestPlayerMeansOfDeath;
	}
	
	return result;
}

function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, sWeapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration, lifeId )
{
	victim = self;
	
	killedByEnemy = false;
	
	level notify( "playerCountChanged" );

	if ( victim.team == game[ "attackers" ] )
	{
		self thread respawnPlayer();
	}
	else
	{
		if ( !level flag::get( "props_hide_over" ) )
		{
			self thread respawnPlayer();
			return;
		}
		else
		{
			thread deathicons::add( victim.body, victim, victim.team, 5.0 );
		}
	}
		
	if ( ( IsDefined( attacker ) && IsPlayer( attacker ) && attacker != victim && victim.team != attacker.team ) )
	{
		killedByEnemy = true;
	}
	
	if ( killedByEnemy )
	{
		scoreevents::processScoreEvent( "prop_finalblow", attacker, victim );
		
		// Play local sound for players who did damage
		foreach( assailant in victim.attackers )
		{
			if ( assailant == attacker )
			{
				assailant PlayHitMarker( "mpl_hit_alert" );
			}
			else
			{
				assailant PlayHitMarker( "mpl_hit_alert_escort" );
			}
		}
		
	}
		
	foreach ( player in level.players )
	{	
		if ( player != attacker && player util::isProp() && isalive( player ) && victim util::isProp() )
		{
			scoreevents::processScoreEvent( "prop_survived", player );
		}
		else if ( player != attacker && player isHunter() && victim.team == game["defenders"] )
		{
			scoreevents::processScoreEvent( "prop_killed", player, victim );
		}
	}
	
	if ( victim util::isProp() )
	{
		attackerTeam = util::getOtherTeam( victim.team );
		game["propScore"][attackerTeam] += 1;
	}
}

function respawnPlayer()
{
	self thread waitTillCanSpawnClient(); 
}

// Adding the same fix below, that I did to Reinforce, to make sure this function ends correctly
// instead of continously running while a person is stuck in an incrementing wait loop, due to spawning in the same frame as another player
function waitTillCanSpawnClient()
{
	self endon ( "started_spawnPlayer" );
	self endon ( "disconnect" );
	level endon ( "game_ended" );
	
	for (;;)
	{
		wait ( .05 );
		if ( isDefined( self ) && isdefined( self.curClass ) && ( self.sessionstate == "spectator" || !isAlive( self ) ) )
		{
			self.pers["lives"] = 1;
			self globallogic_spawn::spawnClient();
			
			//we need to continue here because spawn client can fail for up to 3 server frames in this instance
			continue;
		}
		
		//player either disconnected or has spawned
		return;
	}
}

function onDeadEvent( team )
{
	if ( team == game[ "defenders" ] )
	{
		/#
		if ( isdefined( level.allow_teamchange ) && level.allow_teamchange == "1" )
			return;
		#/
		level thread propKilledEnd();
	}
}

function propKilledEnd()
{
	if ( ( isdefined( level.huntersWonEnding ) && level.huntersWonEnding ) )
		return;
	
	if ( ( isdefined( level.gameEnding ) && level.gameEnding ) )
		return;
	
	level.huntersWonEnding = true;
	
	storeTimePassed();

	level.gameEnding = 1;
	hostmigration::waitLongDurationWithHostMigrationPause( 3 );
	thread ph_endGame( game["attackers"], game["strings"][game["defenders"]+"_eliminated"] );
}

function playDamageSoundPH( sMeansOfDeath )
{
	if ( self.team == game["attackers"] )
		self battlechatter::pain_vox( sMeansOfDeath );
}

function playDeathSoundPH( body, attacker, weapon, sMeansOfDeath )
{
	if ( self.team == game["attackers"] && IsDefined( body ) )
		self battlechatter::play_death_vox( body, attacker, weapon, sMeansOfDeath );
}

function round( value )
{
	value = int( value + 0.5 );
	return value;
}

function gametypeRoundEndScoreHud( winner, endType, endReasonText, outcomeText, team, winnerEnum, notifyRoundEndToUI, matchBonus )
{
	if ( endType == "gameend" && IsDefined( level.propTieBreaker ) )
	{
		if ( !isdefined( team ) || team == "spectator" )
		{
			if ( isdefined( self.team ) && self.team != "spectator" && isdefined( game[ "propScore" ][ self.team ] ) )
				team = self.team;
			else if ( isdefined( self.sessionteam ) && self.sessionteam != "spectator" && isdefined( game[ "propScore" ][ self.sessionteam ] ) )
				team = self.sessionteam;
			
			if ( !isdefined( team ) )
				return true;
		}
		
		otherTeam = util::getotherteam( team );
		
		if( level.propTieBreaker == "kills" )
		{			
			winnerScore = game[ "propScore" ][ team ];			
			loserScore = game[ "propScore" ][ otherTeam ];
			if ( winnerScore < loserScore )
			{
				winnerScore = game[ "propScore" ][ otherTeam ];
				loserScore = game[ "propScore" ][ team ];
			}
			
			scorePacked = ( winnerScore << 8 ) + loserScore;
			
			self LUINotifyEvent( &"show_outcome", 6, outcomeText, &"MP_PH_TIEBREAKER_KILL", int( matchBonus ), winnerEnum, notifyRoundEndToUI, scorePacked );
			return true;
		}
		else if ( level.propTieBreaker == "time" )
		{
			teamSeconds = game[ "hunterKillTime" ][ team ] / 1000;
			otherTeam = util::getotherteam( team );
			otherTeamSeconds = game[ "hunterKillTime" ][ otherTeam ] / 1000;
			
			teamSecondsRounded = round(teamSeconds);
			otherTeamSecondsRounded = round(otherTeamSeconds);
			
			if( teamSecondsRounded == otherTeamSecondsRounded )
			{
				if( teamSeconds > otherTeamSeconds )
					teamSecondsRounded++;
				else
					otherTeamSecondsRounded++;
			}
			
			winnerTime = teamSecondsRounded;			
			loserTime = otherTeamSecondsRounded;
			if ( winnerTime < loserTime )
			{
				winnerTime = otherTeamSecondsRounded;
				loserTime = teamSecondsRounded;
			}
														  
			self LUINotifyEvent( &"show_outcome", 7, outcomeText, &"MP_PH_TIEBREAKER_TIME", int( matchBonus ), winnerEnum, notifyRoundEndToUI, winnerTime, loserTime );
			return true;
		}
	}
	
	return false;
}

function finalizeStartSpawn( spawnPoint, predictedSpawn )
{
	if ( !predictedspawn )
	{
		self.startSpawn = spawnPoint;
	}
}

function checkSpawnPoint( spawnPoint, predictedspawn )
{
	// Fail if any of the (start) spawns have been given to another player
	foreach ( player in level.players )
	{
		if ( IsDefined( player.pers["team"] ) && player.pers["team"] != "spectator" )
		{
			if ( IsDefined( player.startSpawn ) && player.startSpawn == spawnPoint )
				return false;
		}
	}
	
	return true;
}

// from maySpawn()
function gameHasStarted()
{
	if ( level.teamBased )
		return ( globallogic_spawn::allTeamsHaveExisted() );
	else
		return (level.maxPlayerCount > 1) || ( !util::isOneRound() && !util::isFirstRound() );
}

function maySpawnGameMode()
{
	/#
	if ( level.alwaysAllowSpawn )
		return true;
	#/
		
	if ( level.inOvertime )
		return false;

	if ( level.playerQueuedRespawn && !isdefined(self.allowQueueSpawn) && !level.inGracePeriod && !level.useStartSpawns )
		return false;

	if ( level.numLives || level.numTeamLives )
	{
		gameHasStarted = gameHasStarted();
		
		if ( gameHasStarted && ( level.numLives && !self.pers["lives"] ) || ( level.numTeamLives && !game[self.team+"_lives"] ) )
		{
			return false;
		}
		else if ( gameHasStarted )
		{
			// disallow spawning for late comers
			if ( !level.inGracePeriod && !self.hasSpawned && !level.wagerMatch )
				return false;
		}
		
		if ( self disableSpawningForPlayer() )
			return false;
	}
	return true;
}

function disableSpawningForPlayer()
{
	if ( !gameHasStarted() )
	{
		return false;
	}
	
	if ( self isHunter() )
	{
		return false;
	}
	else if ( self util::isProp() )
	{
		return ( !level.inGracePeriod );
	}
	
	return false;
}

function isHunter()
{
	return ( isdefined( self.team ) && self.team == game["attackers"] );
}

function deleteLevelTurrets()
{
	turrets = getEntArray( "misc_turret", "classname" );
	foreach ( turret in turrets )
		turret Delete();
}

function customDamageShellshockAndRumble( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage, vPoint )
{
	self thread weaponsOnDamage( eAttacker, eInflictor, weapon, sMeansOfDeath, iDamage, vPoint );
		
	if ( !self util::isUsingRemote() )
	{
		self PlayRumbleOnEntity( "damage_heavy" );
	}
}

function weaponsOnDamage( eAttacker, eInflictor, weapon, meansOfDeath, damage, point )
{
	self endon ( "death" );
	self endon ( "disconnect" );

	if ( isdefined( level._custom_weapon_damage_func ) )
	{
		is_weapon_registered = self [[level._custom_weapon_damage_func]]( eAttacker, eInflictor, weapon, meansOfDeath, damage );
		if( is_weapon_registered )
		{
			return;
		}
	}

	switch ( weapon.rootWeapon.name )
	{
		case "concussion_grenade":			
			if ( ( isdefined( self.concussionImmune ) && self.concussionImmune ) )
				return;

			radius = weapon.explosionRadius;
			
			if (self == eAttacker) // TFLAME 8/1/12 - reduce effects on attacker 
				radius *= 0.5;
			
			damageOrigin = eInflictor.origin;
			if ( isdefined( point ) )
				damageOrigin = point;
			
			if ( self prop_controls::wasConcussedAtOrigin( damageOrigin ) )
				return;
			
			scale = 1 - (distance( self.origin, damageOrigin ) / radius);
			
			if ( scale < 0 )
				scale = 0;
			
			time = 0.25 + (4 * scale);
			
			{wait(.05);};

			if ( meansOfDeath != "MOD_IMPACT" )
			{
				if ( self HasPerk ( "specialty_stunprotection" ) )
				{
					time *= 0.1;
				}
				else
				{
					if ( self util::mayApplyScreenEffect() ) 
					{
						self shellShock( "concussion_grenade_mp", time, false ); 
					}
				}
				
				self thread weapons::play_concussion_sound( time );
				self.concussionEndTime = getTime() + (time * 1000); 
				self.lastConcussedBy = eAttacker;
				
				if ( self util::IsProp() )
				{
					if ( ( isdefined( self.lock ) && self.lock ) )
						self prop_controls::unlockProp();
					
					self prop_controls::registerConcussionEvent( einflictor, self, meansOfDeath, damage, damageOrigin, weapon );
				}
			}
			break;

		default:
			// shellshock will only be done if meansofdeath is an appropriate type and if there is enough damage.
			if ( isdefined( level.shellshockOnPlayerDamage ) )
			{
				 [[level.shellshockOnPlayerDamage]]( meansOfDeath, damage, weapon );
			}
			break;
	}
}

function manageMannequins()
{
	level endon( "game_ended" );
	
	{wait(.05);};
	
	while ( !isdefined( level.mannequins ) )
		{wait(.05);};
	
	foreach ( mannequin in level.mannequins )
	{
		mannequin NotSolid();
	}
	
	level waittill( "props_hide_over" );
	
	foreach ( mannequin in level.mannequins )
	{
		mannequin Solid();
	}
}



// -------------------------------------------------------------------------------------------------------------







function propWaitMiniGameInit( time )
{
	if ( !isdefined( level.ph_minigame ) )
		level.ph_minigame = SpawnStruct();
	
	if ( !( isdefined( level.ph_minigame.started ) && level.ph_minigame.started ) )
	{
		level.ph_minigame.started = true;
		self thread propMiniGameGlobal( time );
	}
	
	self.ph_miniGameScore = 0;
	self.ph_miniGameTime = 0;
	
	if ( level.ph_minigame.doChase && self IsHunter() && time > 8 )
	{
		waittillframeend;
		
		if ( level.ph_minigame_clonerunners.size < 6 )
			self propMiniGameSpawnCloneRunner();
	}
}

function propMiniGameGlobal( time )
{
	if ( time <= 0 )
	{
		level.ph_minigame.active = false;
		return;
	}
	
	thread propMiniGameStart();
	
	waittillCountdownComplete( time );
	
	level notify( "propMiniGameComplete" );
	
	level.ph_minigame.active = false;
	
	foreach ( player in level.players )
	{
		if ( isdefined( player.pers["team"] ) && player.pers["team"] == game["defenders"] )
			player playerDefenderPropMiniGameComplete();
		else if ( isdefined( player.pers["team"] ) && player.pers["team"] == game["attackers"] )
			player playerAttackerPropMiniGameComplete();
	}
	
	if ( isdefined( level.ph_minigame.hudPregame ) )
		level.ph_minigame.hudPregame Destroy();
	
	thread propMiniGameUpdateShowWinner( level.ph_minigame.hudPlaces[0], -80, 2.0 );	
	thread propMiniGameUpdateShowWinner( level.ph_minigame.hudPlaces[1], -50, 1.75 );
	thread propMiniGameUpdateShowWinner( level.ph_minigame.hudPlaces[2], -20, 1.5 );
	
	if ( isdefined( level.ph_minigame.targets ) )
	{
		foreach ( target in level.ph_minigame.targets )
		{
			if ( isdefined( target ) )
				target Delete();
		}
	}
	
	if ( isdefined( level.ph_minigame_clonerunners ) )
	{
		foreach ( clone in level.ph_minigame_clonerunners )
		{
			if ( isdefined( clone ) )
			{
				if ( isdefined( clone.cloneTarget ) )
					clone.cloneTarget Delete();
				
				if ( isdefined( clone.targetObjectiveId ) )
					gameobjects::release_obj_id( clone.targetObjectiveId );
				
				clone Delete();
			}
		}
	}
}

function propMiniGameUpdateShowWinner( hud, winYOffset, winFontScale )
{
	hud endon("death");
	
	moveTime = 0.5;
	showTime = 2.5;
	fadeTime = 0.5;
	
	hud hud::setPoint( "BOTTOM", "CENTER", 0, winYOffset, moveTime );
	hud changefontscaleovertime( moveTime );
	hud.fontScale = winFontScale;
	
	wait moveTime+showTime;
	
	hud.alpha = 0;
	hud fadeovertime( fadeTime );
	
	wait fadeTime;
	
	if ( isdefined( hud ) )
		hud Destroy();
}

function playerAttackerPropMiniGameComplete()
{
	self propMiniGameSetHunterVisibility( true );
	self TakeWeapon( GetWeapon( "null_offhand_primary" ) );
	self giveOffhandPrimary( GetWeapon( "concussion_grenade" ), 2 );
	self attackerInitAmmo();
}

function playerDefenderPropMiniGameComplete()
{
	self propMiniGameSetPropVisibility( true );
}

function propMiniGameActive()
{
	if ( !isdefined( level.ph_minigame ) )
		level.ph_minigame = SpawnStruct();
	
	if ( !isdefined( level.ph_minigame.active ) )
		level.ph_minigame.active = GetDvarInt( "scr_prop_minigame", 0 );
	
	return level.ph_minigame.active;
}

function propMiniGameSetHunterVisibility( isVisible )
{
	if ( isVisible )
	{
		self Solid();
		self SolidCapsule();
		self showPlayerToAll( true );
		if ( isdefined( self.phSpawnOrigin ) )
		{
			self SetOrigin( self.phSpawnOrigin );
			self SetPlayerAngles( self.phSpawnAngles );
		}
		
		if ( isdefined( self.phClone ) )
			self.phClone Delete();
	}
	else
	{
		self NotSolid();
		self NotSolidCapsule();
		self thread hidePlayerFromTeam( game["defenders"], true );		
		self thread propMiniGameHunterClone( self );
	}
}

function propMiniGameHunterClone( player )
{
	player endon( "disconnect" );
	level endon( "game_ended" );
	
	if ( !isdefined( player.phClone ) )
	{
		waittillPrematchDone();
		wait 0.1;
		
		clone = util::spawn_player_clone( player, "pb_stand_alert" );
		
		weapon = player GetCurrentWeapon();
		if( IsDefined( weapon.worldModel ) )
		{
			clone Attach( weapon.worldModel, "tag_weapon_right" );
		}
		
		clone NotSolid();
		clone NotSolidCapsule();
		clone HideFromTeam( player.pers["team"] );
		
		player.phClone = clone;
		
		player thread propMiniGameCloneCleanup(player, player.phClone);
	}
}

function propMiniGameCloneCleanup( player, clone )
{
	clone endon( "entityshutdown" );
	clone endon( "death" );
	
	player waittill( "disconnect" );
	
	if ( isdefined( clone ) )
		clone Delete();
}

function propMiniGameSetPropVisibility( isVisible )
{
	if ( isVisible )
	{
		if ( isdefined( self.prop ) )
		{
			self.prop Show();
			self.prop Solid();
		}
		self showPlayerToAll( false );
		self Ghost();
		
		if ( isdefined( self.propClones ) )
		{
			foreach ( clone in self.propClones )
			{
				clone Show();
				clone Solid();
			}
		}
	}
	else
	{
		if ( isdefined( self.prop ) )
		{
			self.prop NotSolid();
			self.prop HideFromTeam( game["attackers"] );
		}
		self thread hidePlayerFromTeam( game["attackers"], false );
	}
}

function propMiniGameStart()
{
	level.ph_minigame.doChase = false;
	label = &"MP_PH_PREGAME_HUNT";
	if ( RandomFloat( 1 ) < 0.5 )
	{
		level.ph_minigame.doChase = true;
		label = &"MP_PH_PREGAME_CHASE";
	}
	
	/#
	if ( GetDvarInt( "scr_ph_debugDoMinigameType", 0 ) == 2 && level.ph_minigame.doChase )
	{
		level.ph_minigame.doChase = false;
		label = &"MP_PH_PREGAME_HUNT";
	}
	else if ( GetDvarInt( "scr_ph_debugDoMinigameType", 0 ) == 1 && !level.ph_minigame.doChase )
	{
		level.ph_minigame.doChase = true;
		label = &"MP_PH_PREGAME_CHASE";
	}
	#/

	thread propMiniGameHud( label );
	
	level.ph_minigame.targetLocations = propMiniGameGetTargetLocations();
	level.ph_minigame.targetLocations = array::randomize( level.ph_minigame.targetLocations );
	level.ph_minigame.nextIndex = 0;

	if ( !level.ph_minigame.doChase )
		thread propMiniGameSpawnTargets();
	else
		level.ph_minigame_clonerunners = [];
	
	foreach ( player in level.players )
	{
		if ( isdefined( player.pers["team"] ) && player.pers["team"] == game["attackers"] )
			player thread playerAttackerPropMiniGameStart( &"MP_PH_EMPTY" );
	}
}

function propMiniGameGetTargetLocations()
{
	CONST_TARGET_DIST_SQ = 90000;
	
	targetLocations = [];
	allLocations = spawnlogic::get_spawnpoint_array( "mp_tdm_spawn" );
	hunters = getLivingPlayersOnTeam( game["attackers"] );
	hunter = hunters[0];
	
	foreach ( location in allLocations )
	{
		distSq = DistanceSquared( location.origin, hunter.origin );
		if ( distSq > CONST_TARGET_DIST_SQ )
			targetLocations[targetLocations.size] = location;
	}
	
	return targetLocations;
}

function propMiniGameGetTargetModel()
{
	return "wpn_t7_uplink_ball_world";
}

function propMiniGameSpawnTargets()
{
	CONST_MAX_TARGETS = 40;
	CONST_SPAWNS_PER_FRAME = 4;
	
	model = propMiniGameGetTargetModel();
	
	numTargets = min( level.ph_minigame.targetLocations.size, CONST_MAX_TARGETS );
	
	level.ph_minigame.targets = [];
	
	num = 0;
	for ( i = 0; i < numTargets; i++ )
	{
		origin = getNextTargetOrigin();
		target = propMiniGameSpawnTarget( origin, model );
		level.ph_minigame.targets[level.ph_minigame.targets.size] = target;
		
		num++;
		if ( num >= CONST_SPAWNS_PER_FRAME )
		{
			{wait(.05);};
			num = 0;
		}
	}
}

function propMiniGameTargetFx( targetEnt )
{
	{wait(.05);};
	
	if ( isdefined( targetEnt ) )
		PlayFxOnTag( "ui/fx_uplink_ball_vanish", targetEnt, "tag_origin" );
}

function propMiniGameSpawnTarget( origin, model )
{
	target = Spawn( "script_model", origin );
	target SetModel( model );
    target.targetname = "propTarget";
    target SetCanDamage( true );
    target.fakeHealth = 50;
	target.health = 99999;
	target.maxhealth = 99999;
	target thread entityDamageWatcher( &propMiniGameDamageTargetWatch );
	target SetPlayerCollision( false );
	target MakeSentient();
	target NotSolidCapsule();
	target SetTeam( game["defenders"] );
	target HideFromTeam( game["defenders"] );
	target SetScale( 2, true );
	
	thread propMiniGameTargetFx( target );
	
	return target;
}
	
function propMiniGameDamageTargetWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags )
{
    if ( !IsDefined( attacker ) )
        return;
    
    if ( isPlayer( attacker ) )
    {
    	if ( ( isdefined( self.isDying ) && self.isDying ) )
    		return;
    	
        attacker thread damagefeedback::update();
        
        self.lastattacker = attacker;
        
    	self.fakeHealth -= damage;
    	if ( self.fakeHealth <= 0 )
    	{
    		propMiniGamePlayerScored( attacker );
    		self thread moveTarget();
    	}
    }
    
    self.health += damage;
}

function moveTarget()
{
    self.isDying = true;
    
    {wait(.05);};
	
    self.fakeHealth = 50;
	
    fxEnt = PlayFx( fx::get( "propDeathFX" ), ( self.origin + ( 0, 0, 4 ) ) );
    fxEnt Hide();
    foreach ( player in level.players )
    {
    	if ( player IsHunter() )
    		fxEnt ShowToPlayer( player );
    }
    fxEnt PlaySoundToTeam( "wpn_flash_grenade_explode", game["attackers"] );
    
    self.origin = getNextTargetOrigin();
    self DontInterpolate();
	
    self.isDying = false;
}

function isLocationNearOtherTargets( location )
{
	CONST_CLOSEST_DIST_SQ = 90000;
	
	foreach ( target in level.ph_minigame.targets )
	{
		distSq = DistanceSquared( target.origin, location );
		if ( distSq < CONST_CLOSEST_DIST_SQ )
			return true;
	}
	
	return false;
}

function getNextTargetOrigin()
{
    if ( level.ph_minigame.nextIndex >= level.ph_minigame.targetLocations.size )
    	level.ph_minigame.nextIndex = 0;
    
	location = level.ph_minigame.targetLocations[level.ph_minigame.nextIndex];
	if ( !isdefined( location.altOrigin ) )
	{
		dir = ( level.mapCenter - location.origin );
		dist = Distance( level.mapCenter, location.origin );
		if ( dist > 0 )
			dir = ( dir[0] / dist, dir[1] / dist, dir[2] / dist );
		
		attempts = 9;
		newLocation = location.origin;
		rand = RandomFloat( 1 );
		while ( attempts > 0 )
		{
			randDist = dist * rand;
			newLocation = location.origin + ( dir * randDist );
			
			if ( !isLocationNearOtherTargets( newLocation ) )
				break;
			
			rand -= 0.1;
			if ( rand < 0 )
			{
				newLocation = location.origin;
				break;
			}
			
			attempts--;
		}
		newLocation = GetClosestPointOnNavMesh( newLocation, 100 );
		if ( !isdefined( newlocation ) )
			newLocation = location.origin;
		location.altOrigin = newLocation;
	}
	
	origin = location.altOrigin + ( 0, 0, 40 );
	level.ph_minigame.nextIndex++;
	
	return origin;
}

function propMiniGameHudCreatePlace( x, y, label, color )
{
	hudPlace = hud::createServerFontString( "default", 1.5, game["attackers"] );
	hudPlace.label = label;
	hudPlace.x = x;
	hudPlace.y = y;
	hudPlace.alignX = "left";
	hudPlace.alignY = "top";
	hudPlace.horzAlign = "left";
	hudPlace.vertAlign = "top";
	hudPlace.color = color;
	hudPlace.archived = true;
	hudPlace.alpha = 0;
	hudPlace.glowAlpha = 0;
	hudPlace.hidewheninmenu = false;
	hudPlace.sort = 1001;
	
	return hudPlace;
}

function propMiniGameHud( titleLabel )
{
	level.ph_minigame.hudPlaces = [];
	
	yUpperLeft = 110;
	ySpace = 20;
	if ( !level.console )
	{
		yUpperLeft = 125;
		ySpace = 15;
	}
	
	x = 5;
	y = yUpperLeft;
	level.ph_minigame.hudPlaces[level.ph_minigame.hudPlaces.size] = propMiniGameHudCreatePlace( x, y, &"MP_PH_MINIGAME_FIRST", (1.000, 0.843, 0.000) );
	y += ySpace;
	level.ph_minigame.hudPlaces[level.ph_minigame.hudPlaces.size] = propMiniGameHudCreatePlace( x, y, &"MP_PH_MINIGAME_SECOND", (0.300, 0.300, 0.300) );
	y += ySpace;
	level.ph_minigame.hudPlaces[level.ph_minigame.hudPlaces.size] = propMiniGameHudCreatePlace( x, y, &"MP_PH_MINIGAME_THIRD", (0.804, 0.498, 0.196) );
	
	level.ph_minigame.hudPregame = hud::createServerFontString( "default", 2.5, game["attackers"] );
	level.ph_minigame.hudPregame hud::setPoint( "CENTER", undefined, 0, -30 );
	level.ph_minigame.hudPregame.label = titleLabel;
	level.ph_minigame.hudPregame.x = 0;
	level.ph_minigame.hudPregame.archived = true;
	level.ph_minigame.hudPregame.alpha = 1;
	level.ph_minigame.hudPregame.glowAlpha = 0;
	level.ph_minigame.hudPregame.hidewheninmenu = false;
	
	thread propMinigameDelayedHud();
}

function propMiniGameDelayedHud()
{
	level endon( "game_ended" );
	
	wait 5.5;
	
	level.ph_minigame.hudPregame MoveOverTime( 1.0 );
	level.ph_minigame.hudPregame hud::setPoint( "CENTER", undefined, 0, -100 );
	
	wait 1.0;
	
	level.ph_minigame.hudPregame FadeOverTime( 1.0 );
	level.ph_minigame.hudPregame.color = ( 0, 1, 0 );
	
	wait 1.0;
	
	level.ph_minigame.hudPregame FadeOverTime( 1.0 );
	level.ph_minigame.hudPregame.color = ( 1, 1, 1 );
}

function propMiniGamePlayerScored( player )
{
	gameMSElapsed = ( GetTime() - level.startTime - level.totalHostMigrationTime );
	
	player.ph_miniGameScore++;
	player.ph_miniGameTime += gameMSElapsed;
	
	player.phIndicator SetValue( player.ph_miniGameScore );
	
	player thread propMiniGameFlashIndicator();
	
	propMiniGameUpdateScoreboard();
}

function propMiniGameUpdateScoreboard( delayTime )
{
	level endon( "game_ended" );
	
	if ( isdefined( delayTime ) )
		wait delayTime;
	
	hunters = getLivingPlayersOnTeam( game["attackers"] );	
	sortedHunters = array::quickSort( hunters, &propMiniGameScoreCompare );
	
	for ( i = 0; i < 3; i++ )
	{
		if ( isdefined( sortedHunters[i] ) && isdefined( sortedHunters[i].ph_miniGameScore ) && sortedHunters[i].ph_miniGameScore > 0 )
		{
			level.ph_minigame.hudPlaces[i].alpha = 1;
			level.ph_minigame.hudPlaces[i] SetPlayerNameString( sortedHunters[i] );
		}
		else if ( isdefined( level.ph_minigame.hudPlaces ) && isdefined( level.ph_minigame.hudPlaces[i] ) && level.ph_minigame.hudPlaces[i].alpha > 0 )
		{
			level.ph_minigame.hudPlaces[i].alpha = 0;
		}
	}
}

function propMiniGameScoreCompare( p1, p2 )
{
	if ( !isdefined( p1 ) || !isdefined( p1.ph_miniGameScore ) )
		return false;
	
	if ( !isdefined( p2 ) || !isdefined( p2.ph_miniGameScore ) )
		return true;
	
	if ( p1.ph_miniGameScore > p2.ph_miniGameScore )
		return true;
	
	return ( ( p1.ph_miniGameScore == p2.ph_miniGameScore ) && ( p1.ph_miniGameTime <= p2.ph_miniGameTime ) );
}

function propMiniGameIndicator( label )
{
	self.phIndicator = hud::createFontString( "objective", 1 );
	self.phIndicator.label = label;
	self.phIndicator.x = 0;
	self.phIndicator.y = 20;
	self.phIndicator.alignX = "center";
	self.phIndicator.alignY = "middle"; 
	self.phIndicator.horzAlign = "user_center";
	self.phIndicator.vertAlign = "middle";
	self.phIndicator.archived = true;
	self.phIndicator.fontscale = 1;
	self.phIndicator.alpha = 0;
	self.phIndicator.glowAlpha = 0.5;
	self.phIndicator.hidewheninmenu = false;
}

function playerAttackerPropMiniGameStart( label )
{
	self propMiniGameIndicator( label );
}

function propMiniGameFlashIndicator()
{
	self.phIndicator.alpha = 1;
	self.phIndicator FadeOverTime( 3 );
	self.phIndicator.alpha = 0;
}

//function propMiniGameSpawnCloneRunners()
//{
//	CONST_NUM_RUNNERS = 6;
//	
//	level.ph_minigame_clonerunners = [];
//	
//	foreach ( player in level.players )
//	{
//		propMiniGameSpawnCloneRunner();
//	}
//}

function propMiniGameSpawnCloneRunner()
{
	forward =  anglestoforward( self getangles() );
	origin = self.origin + VectorScale( forward, 100 );
	origin = GetClosestPointOnNavMesh( origin, 600 );
			
	clone = SpawnActor( "spawner_bo3_robot_grunt_assault_mp", origin, self.angles, "", true );
	
	clone.cloneTarget = propMiniGameSpawnCloneTarget( origin + ( 0, 0, 40 ) );
	clone.cloneTarget LinkTo( clone );
	
	level.ph_minigame_clonerunners[level.ph_minigame_clonerunners.size] = clone;
	
	propMiniGameConfigureClone( clone, self, forward );
}







function propMiniGameConfigureClone( clone, player, forward ) // self is player
{
	clone.isAiClone = true;
	clone.properName = "";
	clone.ignoreTriggerDamage = true;
	clone.minWalkDistance = 125;
	clone.overrideActorDamage = &cloneDamageOverride;
	clone.spawnTime = GetTime();
	clone.skipGibs = true;
	clone setmaxhealth( 9999 );	
	clone PushActors( true );  // Don't collide with other actors.
	clone PushPlayer( true );  // Don't collide with players.
	clone SetContents( (1 << 13) );  // Collide with bullets.
	clone SetAvoidanceMask( "avoid none" );  // Disable all avoidance.
	
	clone.targetObjectiveId = gameobjects::get_next_obj_id();
	Objective_Add( clone.targetObjectiveId, "active" );
	Objective_Team( clone.targetObjectiveId, game["attackers"] );
	Objective_Position( clone.targetObjectiveId, clone.origin );
	Objective_Icon( clone.targetObjectiveId, "t7_hud_waypoints_safeguard_location" );
	Objective_SetColor( clone.targetObjectiveId, &"FriendlyBlue" );
	Objective_OnEntity( clone.targetObjectiveId, clone );
	
	clone ASMSetAnimationRate( 1.20 );
	
	clone setclone();
	clone._goal_center_point = getNextCloneGoalOrigin();
	
	queryResult = undefined;
	
	if ( IsDefined( clone._goal_center_point ) &&
		clone FindPath( clone.origin, clone._goal_center_point, true, false ) )
	{
		queryResult = PositionQuery_Source_Navigation(
			clone._goal_center_point,
			0,
			450,
			450,
			100,
			clone );
	}
	else
	{
		queryResult = PositionQuery_Source_Navigation(
			clone.origin,
			500,
			750,
			750,
			50,
			clone );
	}
	
	if( queryResult.data.size > 0 )
	{
		clone setgoalpos( queryResult.data[0].origin, true );
		clone._clone_goal = queryresult.data[0].origin;
		clone._clone_goal_max_dist = 450;
	}
	else
	{
		clone._goal_center_point = clone.origin;
	}
	
	clone thread _UpdateClonePathing();
	clone HideFromTeam( game["defenders"] );
	clone Ghost();
	_ConfigureCloneTeam( clone, player );
}

function cloneDamageOverride( eInflictor, eAttacker, iDamage, iDFlags, sMeansOfDeath, weapon, vPoint, vDir, sHitLoc, vDamageOrigin, timeOffset, boneIndex, modelIndex, surfaceType, surfaceNormal )
{	
	return 0;
}

function _ConfigureCloneTeam( clone, player )
{
	team = util::getOtherTeam( player.team );
	clone.ignoreall = true;
	clone SetTeam( team );
	clone.team = team;
}

function _GetCloseClones()
{
	CLONE_TOO_CLOSE_DIST_SQ = 10000;
	
	closeClones = [];
	
	foreach ( clone in level.ph_minigame_clonerunners )
	{
		if ( self == clone )
			continue;
		
		distSq = DistanceSquared( clone.origin, self.origin );
		if ( distSq < CLONE_TOO_CLOSE_DIST_SQ )
			closeClones[closeClones.size] = clone;
	}
	
	return closeClones;
}

function _UpdateClonePathing()
{
	self endon( "death" );
	
	CLONE_NOT_MOVING_DIST_SQ = 576;
	CLONE_NOT_MOVING_POLL_TIME = 2000;
	CLONE_TOO_CLOSE_POLL_TIME = 1500;
	
	if ( !isdefined( level.ph_minigame.lastPathFindTime ) )
		level.ph_minigame.lastPathFindTime = 0;

	while( true )
	{		
		if ( !IsDefined( self.lastKnownPos ) )
		{
			self.lastKnownPos = self.origin;
			self.lastKnownPosTime = GetTime();
		}
		
		if ( !isdefined( self.lastTooCloseTime ) )
		{
			self.lastTooCloseTime = GetTime();
		}
				
		distance = 0;
		if( isDefined( self._clone_goal ) )
			distance = DistanceSquared( self._clone_goal, self.origin );
		
		findNewPath = false;
		if ( distance < 120 * 120 )
		{
			findNewPath = true;
		}
		else if ( !self HasPath() )
		{
			findNewPath = true;
		}
		else if ( ( self.lastKnownPosTime + CLONE_NOT_MOVING_POLL_TIME ) <= GetTime() )
		{
			if ( DistanceSquared( self.lastKnownPos, self.origin ) < CLONE_NOT_MOVING_DIST_SQ )
				findNewPath = true;
			
			self.lastKnownPos = self.origin;
			self.lastKnownPosTime = GetTime();
		}		
		else if ( ( self.lastTooCloseTime + CLONE_TOO_CLOSE_POLL_TIME ) <= GetTime() && level.ph_minigame.lastPathFindTime != GetTime() ) // one path find per frame
		{
			clones = _GetCloseClones();
			
			if ( clones.size > 0 )
				findNewPath = true;
			
			// give this clone a chance to separate
			for ( i = 0; i < clones.size; i++ )
			{
				clones[i].lastTooCloseTime = GetTime();
			}
			
			self.lastTooCloseTime = GetTime();
		}
		
		if ( findNewPath )
		{
			level.ph_minigame.lastPathFindTime = GetTime();
			
			self._goal_center_point = getNextCloneGoalOrigin();
		
			queryResult = PositionQuery_Source_Navigation(
			self._goal_center_point,
			500,
			750,
			750,
			100,
			self );
			
			if( queryResult.data.size == 0 )
			{
				queryResult = PositionQuery_Source_Navigation(
				self.origin,
				500,
				750,
				750,
				100,
				self );
			}
			
			if( queryResult.data.size > 0 )
			{
				randIndex = RandomIntRange( 0, queryResult.data.size );
			
				self setgoalpos( queryResult.data[ randIndex ].origin, true );
				self._clone_goal = queryresult.data[ randIndex ].origin;
				self._clone_goal_max_dist = 750;
			}
		}
//		util::drawcylinder( self._goal_center_point, self._clone_goal_max_dist, 10, .5, "stop_notify_asdf" );
//		util::debug_sphere( self._clone_goal, 10, ( 1, 0, 1 ), 1, 1 );
		wait( .5 );
	}
}

function getNextCloneGoalOrigin()
{
    if ( level.ph_minigame.nextIndex >= level.ph_minigame.targetLocations.size )
    	level.ph_minigame.nextIndex = 0;
    
	location = level.ph_minigame.targetLocations[level.ph_minigame.nextIndex];
	level.ph_minigame.nextIndex++;
	
	return location.origin;
}

function propMiniGameSpawnCloneTarget( origin )
{
	model = propMiniGameGetTargetModel();
	target = Spawn( "script_model", origin );
	target SetModel( model );
    target.targetname = "propTarget";
    target SetCanDamage( true );
    target.fakeHealth = 50;
	target.health = 99999;
	target.maxhealth = 99999;
	target thread entityDamageWatcher( &propMiniGameDamageCloneTargetWatch );
	target SetPlayerCollision( false );
	target MakeSentient();
	target NotSolidCapsule();
	target SetTeam( game["defenders"] );
	target HideFromTeam( game["defenders"] );
	target SetScale( 2, true );
	
	thread propMiniGameTargetFx( target );
	
	return target;
}
	
function propMiniGameDamageCloneTargetWatch( damage, attacker, direction_vec, point, meansOfDeath, modelName, tagName, partName, weapon, iDFlags )
{
    if ( !IsDefined( attacker ) )
        return;
    
    if ( isPlayer( attacker ) )
    {
        attacker thread damagefeedback::update();
        
        self.lastattacker = attacker;
        
    	propMiniGamePlayerScored( attacker );
    }
    
    self.health += damage;
}
