#using scripts\shared\flag_shared;
#using scripts\shared\array_shared;
#using scripts\shared\callbacks_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\sound_shared;
#using scripts\shared\util_shared;
#using scripts\shared\abilities\_ability_util;

    	   	                                                                                                                                         	                                	                                                                                                                                                                                                                                                                                                                                                                    	        	     	              	    	   	               	                                              	                                                           	                               	                     	                                                                                                           	                                                                 	                                                              	                                                                                                                                             	                            	                                     	                                       	                                                                                                              	   	                  	       	                                                    	                   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            	                                                                                                 	                                    	                                                                                        	           	                        	                                            	                                             	                                                   	                                                             	                                                         	                                                                    	                                                                                                                                                                                                                                	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  	    	         	                                                              	                                                          	                                   	                                   	                                                    	                                       	                                           	     	                                                                                                                                                                                                                                                      	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         	                                                               	        	                                                                                                                              	                                                                                                                     	                               	                                                      	                          

#using scripts\mp\gametypes\_globallogic;
#using scripts\mp\gametypes\_globallogic_audio;
#using scripts\mp\gametypes\_globallogic_score;
#using scripts\mp\gametypes\_globallogic_spawn;
#using scripts\mp\gametypes\_globallogic_ui;
#using scripts\mp\gametypes\_loadout;
#using scripts\mp\gametypes\_spawning;
#using scripts\mp\gametypes\_spawnlogic;
#using scripts\mp\gametypes\_spectating;
#using scripts\mp\gametypes\_wager;

#using scripts\mp\bots\_bot;

#using scripts\mp\_teamops;
#using scripts\mp\_util;
#using scripts\mp\teams\_teams;

/*
	Infect - Infected
	Attackers objective: Infect all Defenders
	Defenders objective: Kill / Avoid Attackers and survive until time limit is reached
	Respawning:	No wait / Near teammates for Attackers, Defenders immediately respawned as Attackers when killed
*/

#precache( "string", "OBJECTIVES_INFECT" );
#precache( "string", "OBJECTIVES_INFECT_HINT" );
#precache( "string", "MP_DRAFT_STARTS_IN" );
#precache( "string", "MP_GOT_INFECTED" );
#precache( "string", "MPUI_INFECTED" );
#precache( "string", "MPUI_SURVIVORS" );
#precache( "string", "MP_SURVIVORS_ELIMINATED" );
#precache( "string", "MP_INFECTED_ELIMINATED" );
#precache( "string", "MP_INFECTED_TIME_EXTENDED" );

function main()
{
	globallogic::init();
	
	level.isInfectMode = true;
	
	level.weapon_FIRST_INFECTED_PRIMARY_WEAPON = GetWeapon( "pistol_standard" );
	level.weapon_INFECTED_PRIMARY_WEAPON = GetWeapon( "melee_boneglass" );
	level.weapon_INFECTED_PRIMARY_GRENADE_WEAPON = GetWeapon( "hatchet" );

	util::registerRoundSwitch( 0, 9 );
	util::registerTimeLimit( 0, 1440 );
	util::registerScoreLimit( 0, 50000 );
	util::registerRoundLimit( 0, 10 );
	util::registerRoundWinLimit( 0, 10 );
	util::registerNumLives( 0, 100 );
	
	globallogic::registerFriendlyFireDelay( level.gameType, 15, 0, 1440 );

	level.scoreRoundWinBased = ( GetGametypeSetting( "cumulativeRoundScores" ) == false );
	level.teamScorePerKill = GetGametypeSetting( "teamScorePerKill" );
	level.teamScorePerDeath = GetGametypeSetting( "teamScorePerDeath" );
	level.teamScorePerHeadshot = GetGametypeSetting( "teamScorePerHeadshot" );
	level.teamBased = true;
	level.overrideTeamScore = true;
	level.onStartGameType =&onStartGameType;
	level.onEndGame = &onEndGame;
	level.onSpawnPlayer =&onSpawnPlayer;
	level.onRoundEndGame =&onRoundEndGame;
	level.onRoundSwitch =&onRoundSwitch;
	level.onPlayerKilled =&onPlayerKilled;
	level.playerGetWeaponScavengeAmmo = &playerGetWeaponScavengeAmmo;
	level.getTimeLimit = &getTimeLimit;
	level.gameModeWeaponDropped = &weaponDropped;
//	level.gametypeRoundEndScoreHud =&gametypeRoundEndScoreHud;
//	level.onCloneSelectWeapon = &onCloneSelectWeapon;
	
	callback::on_connect( &onPlayerConnect );
	callback::on_disconnect( &onPlayerDisconnect );
	callback::on_joined_team( &onPlayerJoinedTeam );
	callback::on_joined_spectate( &onPlayerJoinedSpectate );

	gameobjects::register_allowed_gameobject( level.gameType );

//	globallogic_audio::set_leader_gametype_dialog ( "startTeamDeathmatch", "hcStartTeamDeathmatch", "gameBoost", "gameBoost" );
	
	// Sets the scoreboard columns and determines with data is sent across the network
	if ( !SessionModeIsSystemlink() && !SessionModeIsOnlineGame() && IsSplitScreen() )
		// local matches only show the first three columns
		globallogic::setvisiblescoreboardcolumns( "score", "kills", "infects", "deaths", "assists" );
	else
		globallogic::setvisiblescoreboardcolumns( "score", "kills", "deaths", "infects", "assists" );
	
	level.giveCustomLoadout =&giveCustomLoadout;
	level.getAutoAssignTeamName = &getAutoAssignTeamName;
	level.onTimeLimit =&onTimeLimit;
	level.mayDropWeapon = &mayDropWeapon;
	
	infect_perks = [];
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_longersprint";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_quieter";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_loudenemies";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_movefaster";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_jetnoradar";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_jetquiet";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fallheight";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fastladderclimb";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fastmantle";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_fastreload";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_detectexplosive";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_bulletaccuracy";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_stalker";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_jetcharger";;
	if ( !isdefined( infect_perks ) ) infect_perks = []; else if ( !IsArray( infect_perks ) ) infect_perks = array( infect_perks ); infect_perks[infect_perks.size]="specialty_overcharge";;
	level.infect_perks = infect_perks;
	
	infect_gadgets = [];
	if ( !isdefined( infect_gadgets ) ) infect_gadgets = []; else if ( !IsArray( infect_gadgets ) ) infect_gadgets = array( infect_gadgets ); infect_gadgets[infect_gadgets.size]="gadget_camo";;
	if ( !isdefined( infect_gadgets ) ) infect_gadgets = []; else if ( !IsArray( infect_gadgets ) ) infect_gadgets = array( infect_gadgets ); infect_gadgets[infect_gadgets.size]="gadget_clone";;
	if ( !isdefined( infect_gadgets ) ) infect_gadgets = []; else if ( !IsArray( infect_gadgets ) ) infect_gadgets = array( infect_gadgets ); infect_gadgets[infect_gadgets.size]="gadget_armor";;
	level.infect_gadgets = infect_gadgets;
}

function onStartGameType()
{
	setClientNameMode("auto_change");
	
	game["defenders"] = "allies";
	game["attackers"] = "axis";
	
	if ( !IsDefined( game["switchedsides"] ) )
		game["switchedsides"] = false;

	if ( game["switchedsides"] )
	{
		oldAttackers = game["attackers"];
		oldDefenders = game["defenders"];
		game["attackers"] = oldDefenders;
		game["defenders"] = oldAttackers;
	}
	
//	setCustomTeamNames();
	
	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 );

	foreach ( team in level.teams )
	{
		util::setObjectiveText( team, &"OBJECTIVES_INFECT" );
		util::setObjectiveHintText( team, &"OBJECTIVES_INFECT_HINT" );
		util::setObjectiveScoreText( team, &"OBJECTIVES_INFECT" );
		
		spawnlogic::add_spawn_points( team, "mp_tdm_spawn" );
		spawnlogic::place_spawn_points( spawning::getTDMStartSpawnName(team) );
	}
		
	spawning::updateAllSpawnPoints();
	
	level.spawn_start = [];
	
	foreach ( team in level.teams )
	{
		level.spawn_start[ team ] =  spawnlogic::get_spawnpoint_array( spawning::getTDMStartSpawnName(team) );
	}

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

	spawnpoint = spawnlogic::get_random_intermission_point();
	setDemoIntermissionPoint( spawnpoint.origin, spawnpoint.angles );

	if ( !util::isOneRound() )
	{
		level.displayRoundEndText = true;
		if( level.scoreRoundWinBased )
		{
			globallogic_score::resetTeamScores();
		}
	}
	
	level.infect_choseFirstInfected = false;
	level.infect_choosingFirstInfected = false;
	level.infect_allowSuicide = false;
	level.infect_awardedFinalSurvivor = false;
	
	level.infect_players = [];
	
	initHud();
	
	/# level thread devgui_infect(); #/
	
	// gametype is user-facing as FFA so we need to use FFA bot-population logic
	maxFree = GetDvarInt( "bot_maxFree", 0 );
	level thread bot::monitor_bot_population( maxFree );
	
	level thread abortedCountdownCleanup();
	level thread timeExtendedCleanup();
}

function setCustomTeamNames()
{
	setTeamCustomName( game["attackers"], "MPUI_INFECTED" );
	setTeamCustomName( game["defenders"], "MPUI_SURVIVORS" );
}

function setTeamCustomName( team, name )
{
	customTeamDvar = "g_customTeamName_" + team;
	if ( GetDvarString( customTeamDvar ) == "" )
	{
		SetDvar( customTeamDvar, name );
	}
}

function onEndGame( winningTeam )
{
	if ( !util::isOneRound() && !util::isLastRound() )
	{
		// give a win/loss for each round played
		infectUpdateWinLossStats( winningTeam );
	}
}

function infectUpdateWinLossStats( winningTeam )
{
	players = level.players;
		
	for ( i = 0; i < players.size; i++ )
	{
		if ( !isdefined( players[i].pers["team"] ) )
			continue;

		if ( level.hostForcedEnd && players[i] IsHost() )
			continue;

		if ( winningTeam == "tie" )
			globallogic_score::updateTieStats( players[i] );
		else if ( players[i].pers["team"] == winningTeam )
			globallogic_score::updateWinStats( players[i] );
		else
		{
			// need to add condition for arena late join loss prevention
			if ( level.rankedMatch && !level.leagueMatch && ( players[i].pers["lateJoin"] === true ) )
			{
				globallogic_score::updateLossLateJoinStats( players[i] );
			}
		 
		 	if ( !level.disableStatTracking )
		 	{
				players[i] SetDStat( "playerstatslist", "cur_win_streak", "StatValue", 0 );	
			}
		}
	}
}

function initHud()
{
	level.infect_timerDisplay = hud::createServerTimer( "objective", 1.5 );
	level.infect_timerDisplay hud::setPoint( "CENTER", undefined, 0, 50 );
	level.infect_timerDisplay.label = &"MP_DRAFT_STARTS_IN";
	level.infect_timerDisplay.alpha = 0;
	level.infect_timerDisplay.archived = false;
	level.infect_timerDisplay.hideWhenInMenu = true;
	
	level.infect_timeExtendedDisplay = hud::createServerFontString( "objective", 1.5 );
	level.infect_timeExtendedDisplay hud::setPoint( "CENTER", undefined, 0, 50 );
	level.infect_timeExtendedDisplay.label = &"MP_INFECTED_TIME_EXTENDED";
	level.infect_timeExtendedDisplay.alpha = 0;
	level.infect_timeExtendedDisplay.archived = false;
	level.infect_timeExtendedDisplay.hideWhenInMenu = true;
}

function onPlayerConnect()
{
	self.infect_firstSpawn = true;
	self.infect_joinedAtStart = level.inPrematchPeriod;

	if ( self.sessionteam != "spectator" )
	{
		self.pers["needteam"] = 1;
	}
	
	playerXuid = self GetXUID();
	if ( IsDefined( level.infect_players[playerXuid] ) )
	{
		self.infect_rejoined = true;
	}
	
	self.noDrowning = true;
}

function onPlayerJoinedTeam()
{
	if ( self.team == game["attackers"] )
	{
		self.disableClassSelection = true;
	}
	else
	{
		self.disableClassSelection = undefined;
	}
}

function getAutoAssignTeamName( player, comingFromMenu )
{
	if ( !comingFromMenu && player.sessionteam == "spectator" )
	{
		teamName = "spectator";
	}
	else if ( ( isdefined( level.infect_forceAssignDefender ) && level.infect_forceAssignDefender ) )
	{
		level.infect_forceAssignDefender = undefined;
		teamName = game["defenders"];
		level thread delayedForceSpawnDefenders();
	}
	else if ( ( isdefined( player.infect_rejoined ) && player.infect_rejoined ) || ( isdefined( level.infect_choseFirstInfected ) && level.infect_choseFirstInfected ) )
	{
		teamName = game["attackers"];
	}
	else
	{
		teamName = game["defenders"];
	}
	
	return teamName;
}

function playerWaitForStreamer()
{
	started_waiting = GetTime();
		
	// try to let them finish loading the level
	while ( ( !self isStreamerReady( -1, true ) ) && started_waiting + 90000 > GetTime() )
	{
		{wait(.05);};
	}
}

function onSpawnPlayer(predictedSpawn)
{
	if ( level.useStartSpawns && !level.inGracePeriod && !level.playerQueuedRespawn )
	{
		level.useStartSpawns = false;
	}
	
	updateTeamScores();
	
	if ( self.team == game["attackers"] )
	{
		sanitizeInfectedLoadouts();
	}
	
	//	let the first spawned player kick this off
	if ( !level.infect_choosingFirstInfected )
	{
		level.infect_choosingFirstInfected = true;
		level thread chooseFirstInfected();
	}

	spawning::onSpawnPlayer(predictedSpawn);
}

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

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

function playerHasParticipated()
{
	return ( self.pers["time_played_moving"] > 0 );
}

function teamCallout( team, calloutMessage, calloutPlayer )
{
	players = getPlayersOnTeam( team );
	foreach ( player in players )
	{
		player LUINotifyEvent( &"player_callout", 2, calloutMessage, calloutPlayer );
	}
}

function onPlayerKilled( eInflictor, attacker, iDamage, sMeansOfDeath, weapon, vDir, sHitLoc, psOffsetTime, deathAnimDuration )
{
	processKill = false;
	wasSuicide = false;
	
	if ( self.team == game["defenders"] && IsDefined( attacker ) )
	{
		if ( level.friendlyfire > 0 && isdefined( attacker.team ) && attacker.team == self.team )
		{
			processkill = false;
		}
		else if ( IsPlayer( attacker ) && attacker != self )
		{
			processKill = true;
		}
		else if ( level.infect_allowSuicide && ( attacker == self || !isPlayer( attacker ) ) )
		{
			processKill = true;
			wasSuicide = true;
		}
	}
	
	if ( !processKill )
	{
		return;
	}
	
	if ( !wasSuicide )
	{
		// regular attacker reward
		scoreevents::processScoreEvent( "infected_survivor", attacker, self, weapon );
		
		if ( isdefined( attacker.pers["infects"] ) )
		{
			attacker.pers["infects"] = attacker.pers["infects"] + 1;
			attacker.infects = attacker.pers["infects"];
		}
		attacker AddPlayerStatWithGameType( "INFECTS", 1 );
	}
	
	level thread handlePlayerDeath( self, wasSuicide );
}

function handlePlayerDeath( victim, wasSuicide )
{
	level endon( "game_ended" );
	
	waittillframeend; // give laststand a chance to kick in
	
	if ( isdefined( victim.laststand ) )
	{
		result = victim playerWaitForLastStand();
		if ( result === "player_input_revive" )
		{
			return;
		}
	}
	
	{wait(.05);}; // allows obituary to have expected team colors (i.e. Infected as enemy, Survivor as friendly)
	
	if ( IsDefined( victim ) )
	{
		level thread displayTimeExtended();
		switchPlayerToInfectedTeam( victim );
		sanitizeInfectedLoadouts();
	}
	
	//	generic messages/sounds, and reward survivors
	totalDefenders = [[level._getTeamScore]]( game["defenders"] );
	if ( totalDefenders > 1 && IsDefined( victim ) )
	{
		sound::play_on_players( "mpl_flagget_sting_enemy", game["defenders"] );
		sound::play_on_players( "mpl_flagget_sting_friend", game["attackers"] );
		
		teamCallout( game["defenders"], &"MP_GOT_INFECTED", victim.entnum );
		
		if ( !wasSuicide )
		{
			teamCallout( game["attackers"], &"SCORE_INFECTED_SURVIVOR", victim.entnum );
			
			survivors = getPlayersOnTeam( game["defenders"] );
			foreach ( survivor in survivors )
			{
				if ( survivor != victim && survivor playerHasParticipated() )
				{
					survivor scoreevents::processScoreEvent( "survivor_still_alive", survivor );
				}
			}		
		}	
	}		
	//	inform/reward last
	else if ( totalDefenders == 1 )
	{
		onFinalSurvivor();
	}
	//	infected win
	else if ( totalDefenders == 0 )
	{
		onSurvivorsEliminated();
	}
}

function playerWaitForLastStand()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	
	return self util::waittill_any_return( "player_input_revive", "death" );
}

function onFinalSurvivor()
{
	if ( ( isdefined( level.infect_announcedFinalSurvivor ) && level.infect_announcedFinalSurvivor ) )
	{
		return;
	}
	
	sound::play_on_players( "mpl_ballreturn_sting" );
	
	finalSurvivor = getPlayersOnTeam( game["defenders"] )[0];

	if ( !level.infect_awardedFinalSurvivor )
	{
		finalSurvivor playerFullyChargeHeroWeapon();
		
		if ( finalSurvivor.infect_joinedAtStart && finalSurvivor playerHasParticipated() )
		{
			finalSurvivor scoreevents::processScoreEvent( "final_survivor", finalSurvivor );
		}
		level.infect_awardedFinalSurvivor = true;
	}
			
	LUINotifyEvent( &"player_callout", 2, &"SCORE_FINAL_SURVIVOR", finalSurvivor.entnum );
			
	doFinalSurvivorUAV = GetDvarInt( "scr_infect_finaluav" );
	if ( doFinalSurvivorUAV )
	{
		level thread finalSurvivorUAV( finalSurvivor );
	}
	
	level.infect_announcedFinalSurvivor = true;
}

function playerFullyChargeHeroWeapon()
{
	if ( !IsDefined( self.heroWeapon ) )
	{
		return;
	}
	
	heroWeaponSlot = self GadgetGetSlot( self.heroWeapon );
	if ( self GadgetIsReady( heroWeaponSlot ) )
	{
		return;
	}

	self GadgetPowerSet( heroWeaponSlot, 100.0 );
}

function finalSurvivorUAV( finalSurvivor )
{
	level endon( "game_ended" );
	finalSurvivor endon( "disconnect" );
	finalSurvivor endon( "death" );
	level endon( "infect_lateJoiner" );
	level thread endUAVonLateJoiner( finalSurvivor );
	
	SetTeamSpyplane( game["attackers"], 1 );
	util::set_team_radar( game["attackers"], 1 );
	removeUAV = false;
	
	while ( true )
	{
		prevPos = finalSurvivor.origin;
		
		wait( 4.0 );
		
		if ( removeUAV )
		{
			SetTeamSpyplane( game["attackers"], 0 );
			util::set_team_radar( game["attackers"], 0 );
			removeUAV = false;
		}
		
		wait( 6.0 );
		
		if ( DistanceSquared( prevPos, finalSurvivor.origin ) < ( (200) * (200) ) )
		{
			SetTeamSpyplane( game["attackers"], 1 );
			util::set_team_radar( game["attackers"], 1 );
			removeUAV = true;
			
			foreach ( player in level.players )
			{
				sound::play_on_players( "fly_hunter_raise_plr" );
			}
		}
	}
}

function endUAVonLateJoiner( finalSurvivor )
{
	level endon( "game_ended" );
	finalSurvivor endon( "disconnect" );
	finalSurvivor endon( "death" );
	
	while( true )
	{
		totalDefenders = [[level._getTeamScore]]( game["defenders"] );
		if ( totalDefenders > 1 )
		{
			level notify( "infect_lateJoiner" );
			{wait(.05);};
			SetTeamSpyplane( game["attackers"], 1 );
			util::set_team_radar( game["attackers"], 1 );
			break;
		}
		{wait(.05);};
	}
}

function onTimeLimit()
{
	winner = game["defenders"];
	level thread endGame( winner, game["strings"]["time_limit_reached"] );	
}

function onSurvivorsEliminated()
{
	updateTeamScores();
	
	winner = game["attackers"];
	loser = util::getOtherTeam( winner );
	level thread endGame( winner, game["strings"][loser + "_eliminated"] );
}

function endGame( winner, endReasonText )
{
	if ( ( isdefined( level.infect_gameEnded ) && level.infect_gameEnded ) )
	{
		return;
	}
	
	level.infect_gameEnded = true;
	
	util::wait_network_frame(); // give final player a chance to switch teams in native
	
	globallogic::endGame( winner, endReasonText );
}

function onPlayerJoinedSpectate()
{
	checkTeamStates();
}

function onPlayerDisconnect()
{
	checkTeamStates();
}

function checkTeamStates()
{
	if ( ( isdefined( level.gameEnded ) && level.gameEnded ) )
	{
		return;
	}
	
	updateTeamScores();
	
	totalAttackers = [[level._getTeamScore]]( game["attackers"] );
	totalDefenders = [[level._getTeamScore]]( game["defenders"] );
	
	//	team actions or win condition necessary?
	if ( isDefined( self.infect_isBeingChosen ) || level.infect_choseFirstInfected )
	{
		if ( totalAttackers > 0 && totalDefenders > 0 )
		{
			if ( totalDefenders == 1 )
			{
				//	final survivor was abandoned: inform, reward, call uav
				onFinalSurvivor();
			}
		}
		else if ( totalDefenders == 0 )
		{
			//	no more survivors, infected win
			onSurvivorsEliminated();
		}
		else if ( totalAttackers == 0 )
		{
			if ( totalDefenders == 1 )
			{
				//	last survivor wins
				winner = game["defenders"];
				loser = util::getOtherTeam( winner );
				level thread endGame( winner, game["strings"][loser + "_eliminated"] );
			}
			else if ( totalDefenders > 1 )
			{
				//	pick a new infected and keep the game going
				level.infect_choseFirstInfected = false;
				level thread chooseFirstInfected();
			}
		}
	}
	else
	{
		activeDefenders = getActivePlayersOnTeam( game["defenders"] );
		if ( activeDefenders.size < 1 )
		{
			level notify( "infect_stopCountdown" );
		}
	}
}

function giveCustomLoadout( takeAllWeapons, alreadySpawned )
{
	if ( self.team == game["attackers"] )
	{
		self giveInfectedLoadout();
		self.movementSpeedModifier = 1.2;
	}
	else if ( self.team == game["defenders"] )
	{
		self giveWeaponClassLoadout();
		self.movementSpeedModifier = undefined;
	}
	
	return self.spawnWeapon;
}

// direct copy of non-giveCustomLoadout block from loadout::giveLoadout to avoid invasive changes to _loadout.gsc
function giveWeaponClassLoadout()
{
	loadout::giveLoadout_init( true );

	loadout::setClassNum( self.curClass );

	self SetActionSlot( 3, "altMode" );
	self SetActionSlot( 4, "" );

	allocationSpent = self GetLoadoutAllocation( self.class_num );
	overAllocation = ( allocationSpent > level.maxAllocation );
	
	if ( !overAllocation )
	{
		//Perks must come first in case other give-functions check for perks
		givePerks();

		loadout::giveWeapons();	
		loadout::givePrimaryOffhand();
	}
	else
	{
		loadout::giveBaseWeapon();
	}
	
	loadout::giveSecondaryOffhand();
	
	if ( GetDvarint( "tu11_enableClassicMode") == 0 )
	{
		loadout::giveSpecialOffhand();
		loadout::giveHeroWeapon();
	}
	
	loadout::giveKillStreaks();
}

function givePerks()
{
	self.specialty = self GetLoadoutPerks( self.class_num );
		
	if ( level.leagueMatch )
	{
		for ( i = 0; i < self.specialty.size; i++ )
		{
			if ( loadout::isLeagueItemRestricted( self.specialty[i] ) )
			{
				ArrayRemoveIndex( self.specialty, i );
				i--;
			}
		}
	}
	
	// re-registering perks to code since perks are cleared after respawn in case if players switch classes
	self loadout::register_perks();

	// now that perks are re-registered...
	// update player momentum taking into account anteup perk; any score less than the initial value is boosted to that value
	anteup_bonus = GetDvarInt( "perk_killstreakAnteUpResetValue" );
	momentum_at_spawn_or_game_end = (isdefined(self.pers["momentum_at_spawn_or_game_end"])?self.pers["momentum_at_spawn_or_game_end"]:0);
	hasNotDoneCombat = !( self.hasDoneCombat === true ); // fixes dev gui bot spawning in grace period
	if ( ( level.inPreMatchPeriod || ( level.inGracePeriod && hasNotDoneCombat ) ) && momentum_at_spawn_or_game_end < anteup_bonus )
	{
		new_momentum = ( self HasPerk( "specialty_anteup" ) ? anteup_bonus : momentum_at_spawn_or_game_end );
		globallogic_score::_setPlayerMomentum( self, new_momentum, false );
	}
}


function giveInfectedLoadout()
{
	self loadout::giveLoadout_init( true );
	
	loadout::setClassNum( self.curClass );
	
	self ClearPerks();
	foreach ( perkName in level.infect_perks )
	{
		if ( !self HasPerk( perkName ) )
		{
			self SetPerk( perkName );
		}
	}
	
	totalAttackers = [[level._getTeamScore]]( game["attackers"] );
	if ( totalAttackers == 1 )
	{
		defaultWeapon = level.weapon_FIRST_INFECTED_PRIMARY_WEAPON;
	}
	else
	{
		defaultWeapon = level.weapon_INFECTED_PRIMARY_WEAPON;
	}
	self playerInfectedGivePrimaryWeapon( defaultWeapon );
	self SwitchToWeapon( defaultWeapon );
	self SetSpawnWeapon( defaultWeapon );
	self.spawnWeapon = defaultWeapon;

	primaryOffhand = level.weapon_INFECTED_PRIMARY_GRENADE_WEAPON;
	primaryOffhandCount = 1;
	self GiveWeapon( primaryOffhand );
	self SetWeaponAmmoStock( primaryOffhand, primaryOffhandCount );
	self SwitchToOffhand( primaryOffhand );
	self.grenadeTypePrimary = primaryOffhand;
	self.grenadeTypePrimaryCount = primaryOffhandCount;
	
	secondaryOffhand = GetWeapon( "null_offhand_secondary" );
	secondaryOffhandCount = 0;
	self GiveWeapon( secondaryOffhand );
	self SetWeaponAmmoClip( secondaryOffhand, secondaryOffhandCount );
	self SwitchToOffhand( secondaryOffhand );
	self.grenadeTypeSecondary = secondaryOffhand;
	self.grenadeTypeSecondaryCount = secondaryOffhandCount;
	
	self GiveWeapon( level.weaponBaseMelee );
	
	giveRandomGadget();
	
	self.heroWeapon = undefined;
}

function giveRandomGadget()
{
	specialOffhand = self.infect_randomGadget;
	resetCharge = false;
	
	if ( !isdefined( specialOffhand ) )
	{
		randomGadget = array::random( level.infect_gadgets );
		specialOffhand = GetWeapon( randomGadget );
		resetCharge = true;
	}
	
	specialOffhandCount = specialOffhand.startammo;
	self GiveWeapon( specialOffhand );
		
	self SetWeaponAmmoClip( specialOffhand, specialOffhandCount );
	self SwitchToOffhand( specialOffhand );
	self.grenadeTypeSpecial = specialOffhand;
	self.grenadeTypeSpecialCount = specialOffhandCount;

	if ( ( isdefined( resetCharge ) && resetCharge ) )
	{
		slot = self GadgetGetSlot( specialOffhand );
		self GadgetPowerReset( slot );
	}
	
	self.infect_randomGadget = specialOffhand;
}

function chooseFirstInfected()
{
	level endon( "game_ended" );
	level endon( "infect_stopCountdown" );
	
	//	no suicides
	level.infect_allowSuicide = false;
	
	level.lastInfectionTime = undefined;
	
	if ( level.inPrematchPeriod )
	{
		level waittill( "prematch_over" );
	}
	
	level.infect_timerDisplay.label = &"MP_DRAFT_STARTS_IN";
	level.infect_timerDisplay setTimer( 8 );
	level.infect_timerDisplay.alpha = 1;	
	
	hostmigration::waitLongDurationWithHostMigrationPause( 8.0 );
	
	level.infect_timerDisplay.alpha = 0;		
	
	activeDefenders = getActivePlayersOnTeam( game["defenders"] );
	if ( activeDefenders.size > 0 )
	{
		activeDefenders[GetArrayKeys(activeDefenders)[RandomInt(GetArrayKeys(activeDefenders).size)]] setFirstInfected();
	}
	else
	{
		level.infect_choosingFirstInfected = false;
	}
}

function abortedCountdownCleanup()
{
	while ( true )
	{
		event = level util::waittill_any_return( "game_ended", "infect_stopCountdown" );
		
		if ( isdefined( level.infect_timerDisplay ) )
		{
			level.infect_timerDisplay.alpha = 0;
		}
		
		if ( event == "game_ended" )
		{
			return;
		}
		
		level.infect_choosingFirstInfected = false;
	}
}

function displayTimeExtended()
{
	level notify( "timeExtended" );
	
	level endon( "game_ended" );
	level endon( "infect_stopTimeExtended" );
	level endon( "timeExtended" );
	
	timeout = 0;
	
	while( isDefined( level.infect_timerDisplay ) && level.infect_timerDisplay.alpha > 0 )
	{
		hostmigration::waitLongDurationWithHostMigrationPause( 0.5 );
		timeout++;
		
		if( timeout == 20 )
			return;
	}
	
	level.infect_timeExtendedDisplay.alpha = 1;
	
	hostmigration::waitLongDurationWithHostMigrationPause( 1.0 );
	
	level.infect_timeExtendedDisplay FadeOverTime( 2 );
	
	level.infect_timeExtendedDisplay.alpha = 0;
}

function timeExtendedCleanup()
{
	while ( true )
	{
		event = level util::waittill_any_return( "game_ended", "infect_stopTimeExtended" );
		
		if ( isdefined( level.infect_timeExtendedDisplay ) )
		{
			level.infect_timeExtendedDisplay.alpha = 0;
		}
		
		if ( event == "game_ended" )
		{
			return;
		}
	}
}

function getActivePlayersOnTeam( team )
{
	activePlayers = [];
	
	teamPlayers = getPlayersOnTeam( team );
	foreach ( player in teamPlayers )
	{
		if ( player.sessionstate == "spectator" )
		{
			continue;
		}
		
		if ( !isdefined( activePlayers ) ) activePlayers = []; else if ( !IsArray( activePlayers ) ) activePlayers = array( activePlayers ); activePlayers[activePlayers.size]=player;;
	}
	
	return activePlayers;
}

function setFirstInfected()
{	
	self endon( "disconnect" );
	
	self.infect_isBeingChosen = true;
	
	//	wait alive
	while( !IsAlive( self ) || self util::isUsingRemote() )
		{wait(.05);};
	
	//	remove placement item if carrying
	if ( ( isdefined( self.isCarrying ) && self.isCarrying ) )
	{
		self notify( "force_cancel_placement" );
		{wait(.05);};
	}				
	
	//	not while mantling
	while ( self IsMantling() )
		{wait(.05);};
	
	//	not while in air
	while ( !self IsOnGround() && !self IsOnLadder() )
		{wait(.05);};
	
	switchPlayerToInfectedTeam( self );
	self.switching_teams = undefined; // otherwise first death will be ignored by stats system
	
	if ( self IsUsingOffHand() )
	{
		self ForceOffHandEnd();
	}
	self DisableOffhandSpecial(); // circumvents a weapon-flag issue if drafted while cooking an offhand weapon
	self thread playerEnableOffhandAfterWeaponSwitch();
	
	loadout::giveLoadout( self.team, self.curClass );
	
	totalDefenders = [[level._getTeamScore]]( game["defenders"] );
	if ( totalDefenders < 1 )
	{
		level.infect_forceAssignDefender = true;
	}
	else
	{
		forceSpawnTeam( game["defenders"] );
	}
	
	//	tell the world!
	LUINotifyEvent( &"player_callout", 2, &"SCORE_FIRST_INFECTED", self.entnum );
	scoreevents::processScoreEvent( "first_infected", self );
	sound::play_on_players( "mpl_flagget_sting_enemy" );
	
	level.infect_allowSuicide = true;
	level.infect_choseFirstInfected = true;
	self.infect_isBeingChosen = undefined;
}

function forceSpawnTeam( team )
{
	players = getPlayersOnTeam( team );
	foreach ( player in players )
	{
		player thread playerForceSpawn();
	}
}

function playerForceSpawn()
{
	level endon( "game_ended" );
	self endon( "death" );
	self endon( "disconnect" );
	self endon( "spawned" );
	
	if ( self.hasSpawned )
	{
		return;
	}
	
	if ( self.pers["team"] == "spectator" )
	{
		return;
	}
	
	self playerWaitForStreamer();
	
	self.pers["class"] = level.defaultClass;
	self.curClass = level.defaultClass;
	
	self globallogic_ui::closeMenus();
	self CloseMenu( "ChooseClass_InGame" );
	self thread [[level.spawnClient]]();
}

function switchPlayerToInfectedTeam( player )
{
	storeInfectedPlayer( player );
	destroyPlayerEquipment( player );
	player changeTeam( game["attackers"] );
	updateTeamScores();
	resetTimeLimit();
}

function playerEnableOffhandAfterWeaponSwitch()
{
	level endon( "game_ended" );
	self endon( "disconnect" );
	self endon( "death" );
	
	self waittill( "weapon_change" );
	
	self EnableOffhandSpecial();
}

function storeInfectedPlayer( player )
{
	playerXuid = player GetXUID();
	level.infect_players[playerXuid] = true;
}

function changeTeam( team )
{
	if ( self.sessionstate != "dead" )
	{
		// Set a flag on the player to they aren't robbed points for dying - the callback will remove the flag
		self.switching_teams = true;
		self.switchedTeamsResetGadgets = true;
		self.joining_team = team;
		self.leaving_team = self.pers["team"];
	}

	self.pers["team"] = team;
	self.team = team;
	self.pers["weapon"] = undefined;
	self.pers["spawnweapon"] = undefined;
	self.pers["savedmodel"] = undefined;
	self.pers["teamTime"] = undefined;
	self.sessionteam = self.pers["team"];

	self globallogic_ui::updateObjectiveText();
	
	// update spectator permissions immediately on change of team
	self spectating::set_permissions();
	
	self notify("end_respawn");
}

function getPlayersOnTeam( team )
{
	playersOnTeam = [];
	
	foreach ( player in level.players )
	{
		if ( !isdefined( player ) )
		{
			continue;
		}
		
		playerTeam = player.pers[ "team" ];
		if ( IsDefined( playerTeam ) && IsDefined( level.teams[ playerTeam ] ) && playerTeam == team )
		{
			if ( !isdefined( playersOnTeam ) ) playersOnTeam = []; else if ( !IsArray( playersOnTeam ) ) playersOnTeam = array( playersOnTeam ); playersOnTeam[playersOnTeam.size]=player;;
		}
	}
	
	return playersOnTeam;
}

function countPlayersOnTeam( team )
{
	playersOnTeam = getPlayersOnTeam( team );
	return playersOnTeam.size;
}

function updateTeamScores()
{
	updateTeamScore( game["attackers"] );
	updateTeamScore( game["defenders"] );
}

function updateTeamScore( team )
{
	score = countPlayersOnTeam( team );
	game["teamScores"][team] = score;
	globallogic_score::updateTeamScores( team );
}

function mayDropWeapon( weapon )
{
	if ( self.team === game["attackers"] )
	{
		return false;
	}
	
	return true;
}

function playerInfectedGivePrimaryWeapon( weapon )
{
	self GiveWeapon( weapon );
	self GiveStartAmmo( weapon );
	self SetBlockWeaponPickup( weapon, true );
	self.infect_primaryWeapon = weapon;
}

function sanitizeInfectedLoadouts()
{
	attackers = getPlayersOnTeam( game["attackers"] );
	if ( attackers.size < 2 )
	{
		return;
	}
	
	foreach ( player in attackers )
	{
		if ( !IsAlive( player ) )
		{
			continue;
		}
		
		if ( player.infect_primaryWeapon !== level.weapon_INFECTED_PRIMARY_WEAPON )
		{
			if ( isdefined( player.infect_primaryWeapon ) )
			{
				player TakeWeapon( player.infect_primaryWeapon );
			}
			
			newWeapon = level.weapon_INFECTED_PRIMARY_WEAPON;
			player playerInfectedGivePrimaryWeapon( newWeapon );
			player SwitchToWeapon( newWeapon );
		}
	}
}

function playerGetWeaponScavengeAmmo( weapon, defaultAmmo )
{
	if ( weapon == self.grenadeTypePrimary || weapon == self.grenadeTypeSecondary )
	{
		return 0;
	}
	
	return defaultAmmo;
}

function resetTimeLimit()
{
	level.lastInfectionTime = GetTime();
}

function getTimeLimit()
{
	defaultTimeLimit = GetGametypeSetting( "timeLimit" );
	if ( defaultTimeLimit == 0 )
	{
		return 0;
	}
	
	if ( !isdefined( level.lastInfectionTime ) )
	{
		return 0;
	}
	
	minutesElapsed = ( ( level.lastInfectionTime - level.startTime + 1000 ) / 60000 );
	timeLimit = minutesElapsed + defaultTimeLimit;
	return timeLimit;
}

function destroyPlayerEquipment( player )
{
	for ( i = 0; i < level.missileEntities.size; i++ )
	{
		item = level.missileEntities[i];
		
		if ( !isdefined( item ) )
		{
			continue;
		}

		if ( !isdefined( item.weapon ) )
		{
			continue;
		}

		if ( item.owner !== player && item.originalowner !== player )
		{
			continue;
		}

		item notify( "detonating" );
		if ( isdefined( item ) )
			item Delete();
	}
}

function gametypeRoundEndScoreHud( winner, endType, endReasonText, outcomeText, team, winnerEnum, notifyRoundEndToUI, matchbonus )
{
	if ( endType == "roundend" )
	{
		if ( winner == "tie" )
		{
			outcomeText = game["strings"]["draw"];
		}
		else if ( isdefined( self.pers["team"] ) && winner == team )
		{
			outcomeText = game["strings"]["victory"];
			overrideSpectator = true;
		}
		else
		{
			outcomeText = game["strings"]["defeat"];
			
			if ( ( level.rankedMatch || level.leagueMatch ) && ( self.pers[ "lateJoin" ]  === true ) )
			{
			   endReasonText = game["strings"]["join_in_progress_loss"];	
			}
			
			overrideSpectator = true;
			
		}
		notifyRoundEndToUI = false;
		
		// Codcaster Specific Outcome Override
		if ( team == "spectator" && overrideSpectator )
		{
			// Special case handling of end reason text
			foreach ( team in level.teams )
			{
				if ( endreasontext == game["strings"][team + "_eliminated"] )
				{
					endReasonText = game["strings"]["cod_caster_team_eliminated"];
					break;
				}
			}
		
			outcomeText = game["strings"]["cod_caster_team_wins"];
		}
			
		self LUINotifyEvent( &"show_outcome", 5, outcomeText, endReasonText, int( matchBonus ), winnerEnum, notifyRoundEndToUI );
		return true;
	}
	
	return false;
}

function onCloneSelectWeapon( player )
{
	if ( player.team === game["attackers"] )
	{
		playerWeapon = player GetCurrentWeapon();
		if ( isdefined( playerWeapon.worldModel ) )
		{
			return playerWeapon;
		}
	}
	
	return undefined; // use default logic
}

function delayedForceSpawnDefenders()
{
	level endon( "game_ended" );
	
	level notify( "infect_forcespawn_defenders" );
	level endon( "infect_forcespawn_defenders" );
	
	wait( 30 );
	
	forceSpawnTeam( game["defenders"] );
}

function weaponDropped( weaponItem )
{
	weaponItem HideFromTeam( game["attackers"] );
}

/#
function runDvarHandler( dvarName, commandHandlerFunc )
{
	SetDvar( dvarName, "" );
	
	while ( true )
	{
		{wait(.05);};
		
		dvarValue = GetDvarString( dvarName );
		if ( dvarValue == "" )
		{
			continue;
		}
		
		SetDvar( dvarName, "" );
		
		tokens = StrTok( dvarValue, " " );
		if ( !IsDefined( tokens ) || tokens.size < 1 )
		{
			continue;
		}
		
		command = tokens[0];
		ArrayRemoveIndex( tokens, 1 );
		
		[[ commandHandlerFunc ]]( command, tokens );
	}
}



function handleDevCommand( command, args )
{
	switch ( command )
	{
		case "killall_bots":
			foreach ( player in level.players )
			{
				if ( player util::is_bot() )
				{
					player Kill();
				}
			}
			break;
			
		default:
			break;
	}
}

function devgui_infect()
{
	level thread runDvarHandler( "scr_infect_devcmd", &handleDevCommand );
	
	level flag::wait_till("all_players_spawned" );
	
	devguiRoot = "devgui_cmd \"Infected/";
	
	AddDebugCommand( devguiRoot + "Kill All Bots" + "\" \"set " + "scr_infect_devcmd" + " killall_bots\" \n");
}
#/
